Files
sam-docs/frontend/api-specs/bending-lot-api.md

480 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 절곡품 재고생산 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 번호 생성 (일련번호 포함) |
| `GET` | `/api/v1/bending/material-lots` | 원자재 LOT 목록 (수입검사 완료 입고) |
| `POST` | `/api/v1/orders` | 재고생산 저장 (기존 API, options 확장) |
| `PATCH` | `/api/v1/orders/{id}/status` | 상태 변경 (취소→등록 복원 포함) |
### 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. 원자재 LOT 목록 조회
```
GET /api/v1/bending/material-lots?material={재질명}
```
수입검사가 완료된(`status=completed`) 입고 건 중 재질이 일치하는 LOT 목록을 반환한다.
원자재 LOT 선택 모달에서 사용한다.
### 파라미터
| 파라미터 | 필수 | 설명 | 예시 |
|---------|------|------|------|
| `material` | △ | 원자재 재질명 (부분 일치 검색) | `SUS 1.2T`, `EGI 1.55T`, `화이바원단` |
### 응답
```json
{
"success": true,
"data": [
{
"id": 42,
"lot_no": "RM-20260301-001",
"supplier_lot": "SUP-2026-A123",
"item_name": "SUS 1.2T",
"specification": "1219 × 2438",
"receiving_qty": "500.00",
"receiving_date": "2026-03-01",
"supplier": "○○철강",
"options": {
"inspection_status": "적",
"inspection_result": "합격"
}
}
]
}
```
### 프론트엔드 사용법
```typescript
// 원자재 재질은 code-map의 material_map에서 결정
const material = codeMap.material_map[`${prodCode}:${specCode}`];
// 예: 'SUS 1.2T', 'EGI 1.55T', '화이바원단'
// 해당 재질의 입고 LOT 목록 조회
const lots = await fetchMaterialLots(material);
// → 모달에서 사용자가 LOT 선택
```
---
## 6. 취소 → 등록 복원
```
PATCH /api/v1/orders/{id}/status
```
기존 상태 변경 API를 사용한다. `CANCELLED → DRAFT` 전환이 허용된다.
### 요청
```json
{
"status": "DRAFT"
}
```
### 상태 전환 규칙
| 현재 상태 | 가능한 전환 |
|----------|-----------|
| `DRAFT` | `CONFIRMED`, `CANCELLED` |
| `CONFIRMED` | `IN_PROGRESS`, `CANCELLED` |
| `IN_PROGRESS` | `COMPLETED`, `CANCELLED` |
| `COMPLETED` | (변경 불가) |
| **`CANCELLED`** | **`DRAFT` (복원)** |
### 프론트엔드 구현
취소 상태일 때 "수정" 버튼 또는 "복원" 버튼을 표시하고, 클릭 시 `updateStockOrderStatus(id, 'draft')`를 호출한다.
---
## 7. 담당자 기본값
STOCK 주문 생성 시 `options.manager_name`이 비어 있으면 **로그인 사용자 이름이 자동 설정**된다.
프론트엔드에서 별도 처리 불필요. 저장 후 응답에서 `manager_name`이 채워져 돌아온다.
---
## 8. 재고생산 저장 (기존 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