tenantId(); $model = DesignModel::query() ->where('tenant_id', $tenantId) ->find($modelId); if (!$model) { throw new NotFoundHttpException(__('error.not_found')); } return ModelVersion::query() ->where('tenant_id', $tenantId) ->where('model_id', $modelId) ->orderBy('version_no') ->get(); } /** DRAFT 생성 (version_no 자동/수동) */ public function createDraft(int $modelId, array $extra = []): ModelVersion { $tenantId = $this->tenantId(); $model = DesignModel::query() ->where('tenant_id', $tenantId) ->find($modelId); if (!$model) { throw new NotFoundHttpException(__('error.not_found')); } return DB::transaction(function () use ($tenantId, $model, $extra) { $versionNo = $extra['version_no'] ?? null; if ($versionNo === null) { $max = ModelVersion::query() ->where('model_id', $model->id) ->max('version_no'); $versionNo = (int)($max ?? 0) + 1; } else { $exists = ModelVersion::query() ->where('model_id', $model->id) ->where('version_no', $versionNo) ->exists(); if ($exists) { throw ValidationException::withMessages(['version_no' => __('error.duplicate')]); } } return ModelVersion::create([ 'tenant_id' => $tenantId, 'model_id' => $model->id, 'version_no' => $versionNo, 'status' => 'DRAFT', 'is_active' => true, 'notes' => $extra['notes'] ?? null, 'effective_from' => $extra['effective_from'] ?? null, 'effective_to' => $extra['effective_to'] ?? null, ]); }); } /** RELEASED 전환 */ public function release(int $versionId): ModelVersion { $tenantId = $this->tenantId(); $mv = ModelVersion::query() ->where('tenant_id', $tenantId) ->find($versionId); if (!$mv) { throw new NotFoundHttpException(__('error.not_found')); } if ($mv->status === 'RELEASED') { return $mv; // 멱등 } // 릴리즈 전 유효성 검사 app(\App\Services\Design\BomTemplateService::class)->validateForRelease($mv->id); $before = [ 'status' => $mv->status, 'effective_from' => $mv->effective_from ? $mv->effective_from->toISOString() : null, ]; $mv->status = 'RELEASED'; $mv->effective_from = $mv->effective_from ?? now(); $mv->save(); // 감사 로그 app(\App\Services\Audit\AuditLogger::class)->log( tenantId: $tenantId, targetType: 'model_version', targetId: $mv->id, action: 'released', before: $before, after: ['status' => $mv->status, 'effective_from' => $mv->effective_from->toISOString()] ); return $mv; } }