feat: Design BOM 템플릿 diff/clone API 및 모델버전 릴리즈 유효성 검사 도입

- Design BOM 템플릿 diff/clone 엔드포인트 추가
- 컨트롤러 검증 로직 FormRequest 분리(DiffRequest/CloneRequest/Upsert/ReplaceItems)
- BomTemplateService에 diffTemplates/cloneTemplate/replaceItems/쇼우 로직 정리
- ModelVersionController createDraft FormRequest 적용 및 서비스 호출 정리
- 모델버전 release 전 유효성 검사(존재/활성/테넌트 일치, qty>0, 중복 금지) 추가
- DB enum 미사용 방침 준수(status 문자열 유지)
- model_versions 인덱스 최적화(tenant_id, model_id, status / 기간 범위)
- Swagger 문서(Design BOM) 및 i18n 메시지 키 추가
This commit is contained in:
2025-09-11 13:34:20 +09:00
parent 4bf02b7424
commit 17fa82c35b
12 changed files with 508 additions and 40 deletions

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Swagger\v1;
/**
* @OA\Tag(
* name="Design BOM",
* description="Design-time BOM template operations"
* )
*/
class DesignBomTemplateExtras
{
/**
* @OA\Get(
* path="/api/v1/design/bom-templates/{templateId}/diff",
* tags={"Design BOM"},
* summary="Diff two BOM templates",
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
* @OA\Parameter(name="templateId", in="path", required=true, @OA\Schema(type="integer")),
* @OA\Parameter(name="other_template_id", in="query", required=true, @OA\Schema(type="integer")),
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="boolean"),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", type="object",
* @OA\Property(property="left_template_id", type="integer"),
* @OA\Property(property="right_template_id", type="integer"),
* @OA\Property(property="summary", type="object",
* @OA\Property(property="added", type="integer"),
* @OA\Property(property="removed", type="integer"),
* @OA\Property(property="changed", type="integer"),
* ),
* @OA\Property(property="added", type="array", @OA\Items(type="object",
* @OA\Property(property="ref_type", type="string", example="MATERIAL"),
* @OA\Property(property="ref_id", type="integer"),
* @OA\Property(property="qty", type="number"),
* @OA\Property(property="waste_rate", type="number"),
* @OA\Property(property="uom_id", type="integer", nullable=true),
* @OA\Property(property="notes", type="string", nullable=true),
* @OA\Property(property="sort_order", type="integer"),
* )),
* @OA\Property(property="removed", type="array", @OA\Items(ref="#/components/schemas/DesignBomItemDiffRow")),
* @OA\Property(property="changed", type="array", @OA\Items(type="object",
* @OA\Property(property="ref_type", type="string"),
* @OA\Property(property="ref_id", type="integer"),
* @OA\Property(property="changes", type="object")
* ))
* )
* )
* )
* )
*
* @OA\Post(
* path="/api/v1/design/bom-templates/{templateId}/clone",
* tags={"Design BOM"},
* summary="Clone a BOM template (deep copy)",
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
* @OA\Parameter(name="templateId", in="path", required=true, @OA\Schema(type="integer")),
* @OA\RequestBody(
* required=false,
* @OA\JsonContent(type="object",
* @OA\Property(property="target_version_id", type="integer", nullable=true),
* @OA\Property(property="name", type="string", nullable=true),
* @OA\Property(property="is_primary", type="boolean", nullable=true),
* @OA\Property(property="notes", type="string", nullable=true)
* )
* ),
* @OA\Response(
* response=200,
* description="Cloned",
* @OA\JsonContent(
* type="object",
* @OA\Property(property="success", type="boolean"),
* @OA\Property(property="message", type="string"),
* @OA\Property(property="data", type="object",
* @OA\Property(property="id", type="integer"),
* @OA\Property(property="tenant_id", type="integer"),
* @OA\Property(property="model_version_id", type="integer"),
* @OA\Property(property="name", type="string"),
* @OA\Property(property="is_primary", type="boolean"),
* @OA\Property(property="notes", type="string", nullable=true)
* )
* )
* )
* )
*
* @OA\Schema(
* schema="DesignBomItemDiffRow",
* type="object",
* @OA\Property(property="ref_type", type="string", example="MATERIAL"),
* @OA\Property(property="ref_id", type="integer"),
* @OA\Property(property="qty", type="number"),
* @OA\Property(property="waste_rate", type="number"),
* @OA\Property(property="uom_id", type="integer", nullable=true),
* @OA\Property(property="notes", type="string", nullable=true),
* @OA\Property(property="sort_order", type="integer")
* )
*/
public function docs() {}
}