diff --git a/INDEX.md b/INDEX.md index 5f24da7..54a8077 100644 --- a/INDEX.md +++ b/INDEX.md @@ -28,6 +28,7 @@ | 바로빌 출시 계획 | `dev/dev_plans/barobill-service-launch-plan.md` | 4단계 출시 로드맵 (SOAP 이관→UI→베타→출시) | | 재공품 생산 정책 | `rules/wip-production-policy.md` | 재공품(WIP) 개념, 제조업 공통 패턴, SAM 처리 방식 | | 재고생산관리 API | `frontend/api-specs/stock-production-api.md` | 재고생산 API 명세 (기존 수주 API + STOCK 타입) | +| 절곡품 LOT API | `frontend/api-specs/bending-lot-api.md` | 절곡품 코드맵/품목매핑/LOT 채번 API (프론트엔드 구현 가이드 포함) | | 결재관리 | `dev/dev_plans/approval-system-unification-plan.md` | MNG→API 결재 통합 계획 | | API 품질 | `system/api-code-quality-audit.md` | 정석 패턴 R1~R6, 안티패턴, 보안, 체크리스트 | | API 학습 | `dev/guides/api-request-lifecycle.md` | Client API로 배우는 요청 생명주기 8단계 | diff --git a/frontend/api-specs/bending-lot-api.md b/frontend/api-specs/bending-lot-api.md new file mode 100644 index 0000000..296c4c0 --- /dev/null +++ b/frontend/api-specs/bending-lot-api.md @@ -0,0 +1,385 @@ +# 절곡품 재고생산 LOT API 명세 + +> **작성일**: 2026-03-17 +> **상태**: 개발 완료 (백엔드) +> **대상**: React 프론트엔드 개발자 + +--- + +## 1. 개요 + +재고생산(STOCK) 등록 시 절곡품 전용 입력 폼을 위한 API이다. +**캐스케이딩 드롭다운**(품목→종류→모양&길이)으로 품목을 선택하고, **LOT 번호를 자동 생성**한다. + +### 1.1 API 엔드포인트 요약 + +| Method | Path | 설명 | +|--------|------|------| +| `GET` | `/api/v1/bending/code-map` | 드롭다운 코드 체계 전체 조회 | +| `GET` | `/api/v1/bending/resolve-item` | 선택 조합 → 품목 매핑 조회 | +| `POST` | `/api/v1/bending/generate-lot` | LOT 번호 생성 (일련번호 포함) | +| `POST` | `/api/v1/orders` | 재고생산 저장 (기존 API, options 확장) | + +### 1.2 인증 + +모든 엔드포인트: `X-API-KEY` + `Bearer Token` (기존과 동일) + +--- + +## 2. 코드맵 조회 + +``` +GET /api/v1/bending/code-map +``` + +캐스케이딩 드롭다운 구성에 필요한 전체 코드 체계를 반환한다. +앱 초기 로드 시 1회 호출하여 캐싱한다. + +### 응답 + +```json +{ + "success": true, + "data": { + "products": [ + { "code": "R", "name": "가이드레일(벽면형)" }, + { "code": "S", "name": "가이드레일(측면형)" }, + { "code": "G", "name": "연기차단재" }, + { "code": "B", "name": "하단마감재(스크린)" }, + { "code": "T", "name": "하단마감재(철재)" }, + { "code": "L", "name": "L-Bar" }, + { "code": "C", "name": "케이스" } + ], + "specs": [ + { "code": "M", "name": "본체", "products": ["R", "S"] }, + { "code": "T", "name": "본체(철재)", "products": ["R", "S"] }, + { "code": "C", "name": "C형", "products": ["R", "S"] }, + { "code": "D", "name": "D형", "products": ["R", "S"] }, + { "code": "S", "name": "SUS(마감)", "products": ["R", "S", "B", "T"] }, + { "code": "U", "name": "SUS(마감)2", "products": ["S"] }, + { "code": "E", "name": "EGI(마감)", "products": ["R", "S", "B", "T"] }, + { "code": "I", "name": "화이바원단", "products": ["G"] }, + { "code": "A", "name": "스크린용", "products": ["L"] }, + { "code": "F", "name": "전면부", "products": ["C"] }, + { "code": "P", "name": "점검구", "products": ["C"] }, + { "code": "L", "name": "린텔부", "products": ["C"] }, + { "code": "B", "name": "후면코너부", "products": ["C"] } + ], + "lengths": { + "smoke_barrier": [ + { "code": "53", "name": "W50 × 3000" }, + { "code": "54", "name": "W50 × 4000" }, + { "code": "83", "name": "W80 × 3000" }, + { "code": "84", "name": "W80 × 4000" } + ], + "general": [ + { "code": "12", "name": "1219" }, + { "code": "24", "name": "2438" }, + { "code": "30", "name": "3000" }, + { "code": "35", "name": "3500" }, + { "code": "40", "name": "4000" }, + { "code": "41", "name": "4150" }, + { "code": "42", "name": "4200" }, + { "code": "43", "name": "4300" } + ] + }, + "material_map": { + "G:I": "화이바원단", + "B:S": "SUS 1.2T", + "B:E": "EGI 1.55T", + "R:S": "SUS 1.2T", + "R:M": "EGI 1.55T" + } + } +} +``` + +### 프론트엔드 사용법 + +```typescript +// 1. 품목(prod) 선택 → 종류(spec) 필터링 +const availableSpecs = codeMap.specs.filter(s => s.products.includes(selectedProd)); + +// 2. 품목 코드가 'G'(연기차단재)이면 smoke_barrier 길이, 아니면 general 길이 +const lengths = selectedProd === 'G' + ? codeMap.lengths.smoke_barrier + : codeMap.lengths.general; + +// 3. 원자재 재질 결정 +const material = codeMap.material_map[`${selectedProd}:${selectedSpec}`]; +// 예: 'G:I' → '화이바원단', 'R:S' → 'SUS 1.2T' +``` + +--- + +## 3. 품목 매핑 조회 + +``` +GET /api/v1/bending/resolve-item?prod={prodCode}&spec={specCode}&length={lengthCode} +``` + +드롭다운에서 3개를 모두 선택했을 때 호출한다. +`bending_item_mappings` 테이블에서 대응하는 `items` 레코드를 반환한다. + +### 파라미터 + +| 파라미터 | 필수 | 설명 | 예시 | +|---------|------|------|------| +| `prod` | O | 제품 코드 | `R` | +| `spec` | O | 종류 코드 | `M` | +| `length` | O | 모양&길이 코드 | `42` | + +### 성공 응답 (200) + +```json +{ + "success": true, + "data": { + "item_id": 15604, + "item_code": "BD-RM-42", + "item_name": "가이드레일(벽면) 본체 4200mm", + "specification": "EGI 1.55T 4200mm", + "unit": "EA" + } +} +``` + +### 매핑 없음 응답 (404) + +```json +{ + "success": false, + "message": "[404] 해당 조합에 매핑된 품목이 없습니다." +} +``` + +> **참고**: 매핑이 없으면 품목 없이도 저장 가능하다 (item_id=null). 추후 관리자가 MNG에서 매핑을 등록하면 연결된다. + +--- + +## 4. LOT 번호 생성 + +``` +POST /api/v1/bending/generate-lot +``` + +저장 직전에 호출하여 일련번호가 확정된 LOT 번호를 받는다. + +### 요청 + +```json +{ + "prod_code": "G", + "spec_code": "I", + "length_code": "53", + "reg_date": "2026-03-17" +} +``` + +| 필드 | 필수 | 설명 | +|------|------|------| +| `prod_code` | O | 제품 코드 | +| `spec_code` | O | 종류 코드 | +| `length_code` | O | 모양&길이 코드 | +| `reg_date` | △ | 등록일 (미입력 시 오늘) | + +### 응답 + +```json +{ + "success": true, + "data": { + "lot_base": "GI6317-53", + "lot_number": "GI6317-53-001", + "date_code": "6317", + "material": "화이바원단" + } +} +``` + +### LOT 번호 구조 + +``` +GI6317-53-001 +││││││ ││ └── 일련번호 (같은 날 같은 조합의 순번) +││││││ └┴── 모양&길이 코드 +││││└┴────── 일 (17일) +│││└──────── 월 (3월) +││└───────── 년 (2026 → 6) +│└────────── 종류 (화이바원단) +└─────────── 제품 (연기차단재) +``` + +> **날짜코드 월 변환**: 1~9 그대로, 10=A, 11=B, 12=C + +--- + +## 5. 재고생산 저장 (기존 API 확장) + +``` +POST /api/v1/orders +``` + +기존 수주 생성 API를 사용한다. `options.bending_lot`에 LOT 정보를 추가한다. + +### 요청 예시 + +```json +{ + "order_type_code": "STOCK", + "memo": "절곡품 재고생산", + "options": { + "production_reason": "절곡품 재고생산", + "target_stock_qty": 100, + "bending_lot": { + "lot_number": "GI6317-53-001", + "prod_code": "G", + "spec_code": "I", + "length_code": "53", + "raw_lot_no": "RM-20260301-001", + "fabric_lot_no": "FB-20260215-003", + "material": "화이바원단" + } + }, + "items": [ + { + "item_id": 15604, + "item_code": "BD-GI-53", + "item_name": "연기차단재 화이바원단 W50×3000", + "specification": "화이바원단", + "quantity": 100, + "unit": "EA", + "unit_price": 0 + } + ] +} +``` + +### `options.bending_lot` 필드 + +| 필드 | 타입 | 필수 | 설명 | +|------|------|------|------| +| `lot_number` | string | O | 확정된 LOT 번호 (generate-lot API 결과) | +| `prod_code` | string | O | 제품 코드 | +| `spec_code` | string | O | 종류 코드 | +| `length_code` | string | O | 모양&길이 코드 | +| `raw_lot_no` | string | △ | 원자재(철판) LOT 번호 (선택) | +| `fabric_lot_no` | string | △ | 원단 LOT 번호 — 연기차단재(G)만 해당 (선택) | +| `material` | string | △ | 원자재 재질명 | + +--- + +## 6. 프론트엔드 구현 가이드 + +### 6.1 화면 구성 (레거시 5130 참고) + +``` +┌──────────────────────────────────────────────┐ +│ 절곡품 재고생산 등록 │ +├──────────────────────────────────────────────┤ +│ 등록일 [날짜피커] 작성자 [자동표시] │ +│ │ +│ 생산품 LOT [GI6317-53-001] (자동, readonly) │ +│ │ +│ 품목명 [Select ▾] 종류 [Select ▾] │ +│ │ +│ 모양&길이 [Select ▾] 수량 [숫자입력] │ +│ │ +│ 원자재(철판) LOT [선택 버튼 → 모달] │ +│ 원단 LOT(연기차단재) [선택 버튼 → 모달] │ +│ ↑ 품목이 G(연기차단재)일 때만 표시 │ +│ │ +│ 메모 [텍스트 입력] │ +├──────────────────────────────────────────────┤ +│ ← 목록 [저장] [취소] │ +└──────────────────────────────────────────────┘ +``` + +### 6.2 동작 흐름 + +``` +1. 페이지 로드 + └→ GET /bending/code-map 호출 → 드롭다운 옵션 세팅 + +2. 품목명 Select 변경 + └→ specs 필터링: codeMap.specs.filter(s => s.products.includes(prod)) + └→ lengths 결정: prod === 'G' ? smoke_barrier : general + └→ 종류/모양&길이 초기화 + +3. 종류 Select 변경 + └→ LOT 프리뷰 갱신 (프론트 날짜코드 생성) + └→ material 결정: codeMap.material_map[`${prod}:${spec}`] + +4. 모양&길이 Select 변경 + └→ GET /bending/resolve-item?prod=&spec=&length= 호출 + └→ 매핑된 품목 정보 표시 (또는 "매핑 없음" 안내) + +5. 저장 버튼 클릭 + └→ POST /bending/generate-lot 호출 → lot_number 확정 + └→ POST /orders 호출 (STOCK + bending_lot + items) + └→ 상세 페이지로 이동 +``` + +### 6.3 LOT 프리뷰 (프론트 날짜코드 생성) + +```typescript +function generateDateCode(date: Date): string { + const year = date.getFullYear() % 10; + const month = date.getMonth() + 1; + const day = date.getDate(); + const monthCode = month >= 10 + ? String.fromCharCode(55 + month) // A=10, B=11, C=12 + : String(month); + return `${year}${monthCode}${String(day).padStart(2, '0')}`; +} + +// 프리뷰: GI6317-53 (일련번호 제외) +const lotPreview = `${prodCode}${specCode}${generateDateCode(regDate)}-${lengthCode}`; +``` + +### 6.4 원자재/원단 LOT 선택 + +원자재 LOT 목록은 기존 재고 API를 활용한다: + +``` +GET /api/v1/stock-lots?material={재질명}&status=available +``` + +> 이 엔드포인트가 아직 없으면 `stocks` 관련 API에서 item_name 검색으로 대체 가능하다. + +### 6.5 상세 페이지 LOT 정보 표시 + +`StockProductionDetail.tsx`에서 `order.options.bending_lot`이 있으면 LOT 정보를 추가 표시한다: + +```typescript +const bendingLot = order.options?.bending_lot; +if (bendingLot) { + // 생산품 LOT: GI6317-53-001 + // 원자재 LOT: RM-20260301-001 + // 원단 LOT: FB-20260215-003 + // 원자재: 화이바원단 +} +``` + +--- + +## 7. 기존 폼과의 관계 + +**기존 `StockProductionForm.tsx`를 전면 교체한다.** + +- 기존: `ItemAddDialog`로 수동 품목 입력 (수주 폼 재활용) +- 변경: `BendingLotForm`으로 캐스케이딩 드롭다운 + LOT 자동 생성 + +일반 재고생산 모드는 고려하지 않는다. 재고생산 = 절곡품 LOT 입력으로 통일. + +--- + +## 관련 문서 + +- [재고생산 개편 기획서](../../dev/dev_plans/stock-production-lot-form-plan.md) +- [재고생산관리 기능 설명](../../features/sales/stock-production.md) +- [기존 재고생산 API 명세](stock-production-api.md) + +--- + +**최종 업데이트**: 2026-03-17