feat: Phase 1.2 - 다건 BOM 기반 자동산출 API 구현

- QuoteBomBulkCalculateRequest 생성 (React camelCase → API 약어 변환)
- QuoteCalculationService.calculateBomBulk() 메서드 추가
- POST /api/v1/quotes/calculate/bom/bulk 엔드포인트 추가
- Swagger 스키마 및 문서 업데이트

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-02 13:13:50 +09:00
parent 660300cebf
commit 4e59bbf574
6 changed files with 499 additions and 0 deletions

View File

@@ -271,6 +271,66 @@
* )
*
* @OA\Schema(
* schema="QuoteBomBulkCalculateRequest",
* type="object",
* required={"items"},
* description="다건 BOM 기반 자동산출 요청. React QuoteFormItem 필드명(camelCase)과 API 변수명(약어) 모두 지원합니다.",
*
* @OA\Property(property="items", type="array", minItems=1, description="견적 품목 배열",
* @OA\Items(ref="#/components/schemas/QuoteBomBulkItemInput")
* ),
* @OA\Property(property="debug", type="boolean", example=false, description="디버그 모드 (10단계 디버깅 정보 포함)")
* )
*
* @OA\Schema(
* schema="QuoteBomBulkItemInput",
* type="object",
* required={"finished_goods_code"},
* description="개별 품목 입력. React QuoteFormItem 필드명(camelCase)과 API 변수명(약어) 모두 지원합니다.",
*
* @OA\Property(property="finished_goods_code", type="string", example="SC-1000", description="완제품 코드 (items.code where item_type='FG')"),
* @OA\Property(property="openWidth", type="number", format="float", example=3000, description="개구부 폭(mm) - React 필드명"),
* @OA\Property(property="openHeight", type="number", format="float", example=2500, description="개구부 높이(mm) - React 필드명"),
* @OA\Property(property="quantity", type="integer", example=1, description="수량 - React 필드명"),
* @OA\Property(property="productCategory", type="string", example="SCREEN", description="제품 카테고리 - React 필드명"),
* @OA\Property(property="guideRailType", type="string", example="wall", description="가이드레일 타입 - React 필드명"),
* @OA\Property(property="motorPower", type="string", example="single", description="모터 전원 - React 필드명"),
* @OA\Property(property="controller", type="string", example="basic", description="컨트롤러 - React 필드명"),
* @OA\Property(property="wingSize", type="number", format="float", example=50, description="날개 크기 - React 필드명"),
* @OA\Property(property="inspectionFee", type="number", format="float", example=50000, description="검사비 - React 필드명"),
* @OA\Property(property="W0", type="number", format="float", example=3000, description="개구부 폭(mm) - API 변수명"),
* @OA\Property(property="H0", type="number", format="float", example=2500, description="개구부 높이(mm) - API 변수명"),
* @OA\Property(property="QTY", type="integer", example=1, description="수량 - API 변수명"),
* @OA\Property(property="PC", type="string", example="SCREEN", description="제품 카테고리 - API 변수명"),
* @OA\Property(property="GT", type="string", example="wall", description="가이드레일 타입 - API 변수명"),
* @OA\Property(property="MP", type="string", example="single", description="모터 전원 - API 변수명"),
* @OA\Property(property="CT", type="string", example="basic", description="컨트롤러 - API 변수명"),
* @OA\Property(property="WS", type="number", format="float", example=50, description="날개 크기 - API 변수명"),
* @OA\Property(property="INSP", type="number", format="float", example=50000, description="검사비 - API 변수명")
* )
*
* @OA\Schema(
* schema="QuoteBomBulkCalculationResult",
* type="object",
*
* @OA\Property(property="success", type="boolean", example=true, description="전체 성공 여부 (실패 건이 없으면 true)"),
* @OA\Property(property="summary", type="object", description="처리 요약",
* @OA\Property(property="total_count", type="integer", example=3, description="전체 품목 수"),
* @OA\Property(property="success_count", type="integer", example=2, description="성공 건수"),
* @OA\Property(property="fail_count", type="integer", example=1, description="실패 건수"),
* @OA\Property(property="grand_total", type="number", format="float", example=1500000, description="성공한 품목 총계")
* ),
* @OA\Property(property="items", type="array", description="품목별 산출 결과",
* @OA\Items(type="object",
* @OA\Property(property="index", type="integer", example=0, description="요청 배열에서의 인덱스"),
* @OA\Property(property="finished_goods_code", type="string", example="SC-1000"),
* @OA\Property(property="inputs", type="object", description="정규화된 입력 변수"),
* @OA\Property(property="result", ref="#/components/schemas/QuoteBomCalculationResult")
* )
* )
* )
*
* @OA\Schema(
* schema="QuoteBomCalculationResult",
* type="object",
*
@@ -576,6 +636,39 @@ public function calculate() {}
*/
public function calculateBom() {}
/**
* @OA\Post(
* path="/api/v1/quotes/calculate/bom/bulk",
* tags={"Quote"},
* summary="다건 BOM 기반 자동산출",
* description="여러 품목의 완제품 코드와 입력 변수를 받아 BOM 기반으로 일괄 계산합니다. React QuoteFormItem 필드명(camelCase)과 API 변수명(약어) 모두 지원합니다.",
* security={{"BearerAuth":{}}},
*
* @OA\RequestBody(
* required=true,
* description="품목 배열과 디버그 옵션",
*
* @OA\JsonContent(ref="#/components/schemas/QuoteBomBulkCalculateRequest")
* ),
*
* @OA\Response(
* response=200,
* description="산출 성공 (부분 실패 포함 가능)",
*
* @OA\JsonContent(
*
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="견적이 일괄 산출되었습니다."),
* @OA\Property(property="data", ref="#/components/schemas/QuoteBomBulkCalculationResult")
* )
* ),
*
* @OA\Response(response=400, description="유효성 검증 실패 (items 배열 누락 등)"),
* @OA\Response(response=401, description="인증 필요")
* )
*/
public function calculateBomBulk() {}
/**
* @OA\Post(
* path="/api/v1/quotes/{id}/pdf",