From e92baec6e239c262572eca8a3b2172072a8673ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Tue, 17 Mar 2026 12:50:35 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20[plans]=20=EC=9E=AC=EA=B3=A0=EC=83=9D?= =?UTF-8?q?=EC=82=B0=20=EC=A0=88=EA=B3=A1=ED=92=88=20LOT=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EA=B0=9C=ED=8E=B8=20=EA=B8=B0=ED=9A=8D=EC=84=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- INDEX.md | 1 + .../stock-production-lot-form-plan.md | 523 ++++++++++++++++++ 2 files changed, 524 insertions(+) create mode 100644 dev/dev_plans/stock-production-lot-form-plan.md diff --git a/INDEX.md b/INDEX.md index 33cb281..5f24da7 100644 --- a/INDEX.md +++ b/INDEX.md @@ -32,6 +32,7 @@ | API 품질 | `system/api-code-quality-audit.md` | 정석 패턴 R1~R6, 안티패턴, 보안, 체크리스트 | | API 학습 | `dev/guides/api-request-lifecycle.md` | Client API로 배우는 요청 생명주기 8단계 | | API 개선 | `dev/dev_plans/api-route-improvement-plan.md` | API 라우트 구조 개선 계획 (1,099개 분석) | +| 재고생산 개편 | `dev/dev_plans/stock-production-lot-form-plan.md` | 절곡품 LOT 방식 도입 (캐스케이딩 드롭다운, LOT 자동 생성) | | 운영 배포 | `dev/dev_plans/production-deployment-plan.md` | 배포 계획 | | 서버 운영 | `dev/deploys/ops-manual/README.md` | 서버 운영 매뉴얼 | | 서버 접근/백업 | `system/server-access-management.md` | 계정, 권한, 백업, 리플리케이션 | diff --git a/dev/dev_plans/stock-production-lot-form-plan.md b/dev/dev_plans/stock-production-lot-form-plan.md new file mode 100644 index 0000000..219f30f --- /dev/null +++ b/dev/dev_plans/stock-production-lot-form-plan.md @@ -0,0 +1,523 @@ +# 재고생산 입력 개편 — 절곡품 LOT 방식 도입 + +> **작성일**: 2026-03-17 +> **상태**: 기획 +> **배경**: 레거시 5130 `lot/list.php`의 절곡품 생산 LOT 등록 방식을 SAM 서비스에 도입 + +--- + +## 1. 개요 + +### 1.1 현재 문제 + +현재 SAM 서비스의 재고생산 등록은 **일반 수주 입력 폼을 재활용**한다. +품목명/규격을 수동 텍스트로 입력하고, 품목코드를 직접 타이핑해야 한다. +절곡품 생산 현장에서는 **제품-종류-모양&길이** 조합으로 품목을 특정하고 LOT 번호를 자동 부여하는 것이 자연스럽다. + +### 1.2 목표 + +레거시 5130의 `절곡품 생산 LOT 신규 등록` 방식을 SAM 서비스에 도입한다. + +- **캐스케이딩 드롭다운**: 품목명 → 종류 → 모양&길이 (연동 필터링) +- **LOT 번호 자동 생성**: `[제품][종류][날짜코드]-[모양&길이코드]` +- **원자재/원단 LOT 연결**: 투입 자재의 추적성(traceability) 확보 +- **items 테이블 연동**: 선택 조합에 맞는 `BD-*` 품목을 자동 매핑 + +--- + +## 2. LOT 번호 부여법 (레거시 준용) + +### 2.1 구조 + +``` +[제품코드][종류코드][날짜코드]-[모양&길이코드] + 1자리 1자리 4자리 2자리 + +예시: GI6C17-53 + G = 연기차단재 + I = 화이바원단 + 6 = 2026년 + C = 12월 + 17 = 17일 + 53 = W50 × 3000 +``` + +### 2.2 날짜 코드 (4자리) + +| 구분 | 규칙 | 예시 | +|------|------|------| +| 년 | 끝자리 1자리 | 2026 → `6` | +| 월 | 1~9 그대로, 10=`A`, 11=`B`, 12=`C` | 3월 → `3`, 10월 → `A` | +| 일 | 2자리 zero-pad | 5일 → `05`, 17일 → `17` | + +### 2.3 제품 코드 (7종) + +| 코드 | 제품명 | +|------|--------| +| `R` | 가이드레일(벽면형) | +| `S` | 가이드레일(측면형) | +| `G` | 연기차단재 | +| `B` | 하단마감재(스크린) | +| `T` | 하단마감재(철재) | +| `L` | L-Bar | +| `C` | 케이스 | + +### 2.4 종류 코드 (제품별 종속) + +| 코드 | 종류명 | 사용 가능 제품 | +|------|--------|---------------| +| `M` | 본체 | R, S | +| `T` | 본체(철재) | R, S | +| `C` | C형 | R, S | +| `D` | D형 | R, S | +| `S` | SUS(마감) | R, S, B, T | +| `U` | SUS(마감)2 | S | +| `E` | EGI(마감) | R, S, B, T | +| `I` | 화이바원단 | G | +| `A` | 스크린용 | L | +| `F` | 전면부 | C | +| `P` | 점검구 | C | +| `L` | 린텔부 | C | +| `B` | 후면코너부 | C | + +### 2.5 모양&길이 코드 (2자리) + +| 코드 | 연기차단재용 | 코드 | 일반용 | +|------|-------------|------|--------| +| `53` | W50 × 3000 | `12` | 1219 | +| `54` | W50 × 4000 | `24` | 2438 | +| `83` | W80 × 3000 | `30` | 3000 | +| `84` | W80 × 4000 | `35` | 3500 | +| | | `40` | 4000 | +| | | `41` | 4150 | +| | | `42` | 4200 | +| | | `43` | 4300 | + +### 2.6 제품-종류 연동 규칙 + +``` +G(연기차단재) → I(화이바원단) +B(하단마감재스크린) → S(SUS), E(EGI) +T(하단마감재철재) → S(SUS), E(EGI) +L(L-Bar) → A(스크린용) +R(가이드레일벽면) → M(본체), T(본체철재), C(C형), D(D형), S(SUS), E(EGI) +S(가이드레일측면) → M(본체), T(본체철재), C(C형), D(D형), S(SUS), U(SUS2), E(EGI) +C(케이스) → F(전면부), P(점검구), L(린텔부), B(후면코너부) +``` + +### 2.7 제품+종류 → 원자재(재질) 매핑 + +| 제품 | 종류 | 원자재 | +|------|------|--------| +| G | I | 화이바원단 | +| B, T | S | SUS 1.2T | +| B, T | E | EGI 1.55T | +| L | A | EGI 1.55T | +| R, S | S, U | SUS 1.2T | +| R, S | M, T, C, D, E | EGI 1.55T | +| C | F, P, L, B | EGI 1.55T | + +--- + +## 3. 화면 설계 + +### 3.1 입력 폼 구성 (레거시 5130 준용) + +``` +┌─────────────────────────────────────────────────┐ +│ 절곡품 재고생산 등록 │ +├─────────────────────────────────────────────────┤ +│ 등록일 [2026-03-17 📅] 작성자 [홍길동] │ +│ │ +│ 생산품 LOT [GI6317-53 ] (자동 생성) │ +│ │ +│ 품목명 [연기차단재 ▾] 종류 [화이바원단 ▾] │ +│ │ +│ 모양&길이 [W50×3000 ▾] 수량 [100 ] │ +│ │ +│ 원자재(철판) LOT [클릭하여 선택] │ +│ 원단 LOT(연기차단재) │ +│ [클릭하여 선택] │ +│ │ +│ 메모 [ ] │ +├─────────────────────────────────────────────────┤ +│ [저장] [취소] │ +└─────────────────────────────────────────────────┘ +``` + +### 3.2 UI 동작 흐름 + +``` +1. 품목명 선택 + └→ 종류 드롭다운 옵션이 연동 필터링됨 + └→ 모양&길이 드롭다운 옵션이 연동 필터링됨 (연기차단재: W50/W80, 기타: 일반) + +2. 종류 선택 + └→ LOT 번호 자동 갱신 + └→ 원자재 재질 자동 결정 (원자재 LOT 모달 필터링에 사용) + +3. 모양&길이 선택 + └→ LOT 번호 완성 + └→ items 테이블에서 매칭 품목 자동 검색 (item_code 매핑) + +4. 원자재 LOT 클릭 + └→ 모달: 해당 재질의 가용 LOT 목록 표시 + └→ 선택 시 LOT 번호 입력 + +5. 원단 LOT 클릭 (연기차단재만 표시) + └→ 모달: 화이바원단 가용 LOT 목록 표시 + +6. 저장 + └→ orders 테이블에 STOCK 주문 생성 + └→ order_items에 매핑된 품목 저장 + └→ options에 LOT 정보 저장 +``` + +--- + +## 4. 백엔드 (API) 작업 + +### 4.1 신규 API 엔드포인트 + +#### 4.1.1 절곡 품목 코드맵 조회 + +``` +GET /api/v1/bending/code-map +``` + +**응답**: 캐스케이딩 드롭다운에 필요한 코드 체계 전체를 반환한다. +프론트엔드에서 하드코딩하지 않고 API에서 제공하여 추후 품목 추가 시 백엔드만 수정한다. + +```json +{ + "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": "S", "name": "SUS(마감)", "products": ["R", "S", "B", "T"] } + ], + "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", + "R:M": "EGI 1.55T" + } +} +``` + +#### 4.1.2 절곡 품목 매핑 조회 + +``` +GET /api/v1/bending/resolve-item?prod={prodCode}&spec={specCode}&length={lengthCode} +``` + +**목적**: 드롭다운 선택 조합에 대응하는 `items` 테이블의 실제 품목을 반환한다. + +**응답**: +```json +{ + "item_id": 15604, + "item_code": "BD-RM-42", + "item_name": "가이드레일(벽면) 본체 4200mm", + "specification": "EGI 1.55T 4200mm", + "unit": "EA", + "unit_price": 0 +} +``` + +**매핑 로직**: `item_category = 'BENDING'` + `options->prefix` + `options->length_code`로 검색한다. +매핑 테이블이 없으면 코드 규칙 기반으로 유추한다. + +#### 4.1.3 원자재 LOT 목록 조회 + +``` +GET /api/v1/stock-lots?material={materialName}&status=available +``` + +**목적**: 원자재(철판) LOT 선택 모달에 표시할 가용 LOT 목록이다. +기존 `StockService`의 LOT 조회를 재활용한다. + +**응답**: +```json +[ + { + "id": 1, + "lot_no": "RM-20260301-001", + "item_name": "SUS 1.2T", + "qty": 500, + "available_qty": 350, + "receipt_date": "2026-03-01", + "status": "available" + } +] +``` + +### 4.2 기존 API 수정 + +#### 4.2.1 OrderService::store() — STOCK 주문 options 확장 + +현재 `options`에 `production_reason`, `target_stock_qty`만 저장한다. +절곡품 LOT 정보를 추가 저장한다. + +```php +// options 구조 확장 +'options' => [ + 'production_reason' => '재고생산', + 'target_stock_qty' => 100, + // 절곡품 LOT 정보 (신규) + 'bending_lot' => [ + 'lot_number' => 'GI6317-53', + 'prod_code' => 'G', + 'spec_code' => 'I', + 'length_code' => '53', + 'raw_lot_no' => 'RM-20260301-001', // 원자재 LOT + 'fabric_lot_no' => 'FB-20260215-003', // 원단 LOT (연기차단재만) + 'material' => '화이바원단', + ], +] +``` + +#### 4.2.2 StoreOrderRequest — validation 추가 + +```php +'options.bending_lot' => 'nullable|array', +'options.bending_lot.lot_number' => 'nullable|string|max:20', +'options.bending_lot.prod_code' => 'nullable|string|max:2', +'options.bending_lot.spec_code' => 'nullable|string|max:2', +'options.bending_lot.length_code' => 'nullable|string|max:2', +'options.bending_lot.raw_lot_no' => 'nullable|string|max:50', +'options.bending_lot.fabric_lot_no' => 'nullable|string|max:50', +``` + +### 4.3 백엔드 파일 목록 + +| 파일 | 작업 | 설명 | +|------|------|------| +| `app/Http/Controllers/Api/V1/BendingController.php` | 신규 | 코드맵, 품목매핑 API | +| `app/Services/BendingCodeService.php` | 신규 | 코드 체계 관리, 품목 매핑 로직 | +| `app/Http/Requests/Order/StoreOrderRequest.php` | 수정 | bending_lot validation 추가 | +| `app/Http/Requests/Order/UpdateOrderRequest.php` | 수정 | 동일 | +| `routes/api/v1/production.php` | 수정 | bending 라우트 추가 | +| `app/Swagger/v1/BendingApi.php` | 신규 | Swagger 문서 | + +--- + +## 5. 프론트엔드 (React) 작업 + +### 5.1 컴포넌트 구조 + +``` +src/components/stocks/ +├── StockProductionList.tsx # 기존 (목록) +├── StockProductionDetail.tsx # 기존 (상세) +├── StockProductionForm.tsx # 수정: 모드 분기 +│ # - mode='order': 기존 수주형 입력 +│ # - mode='bending': 절곡품 LOT 입력 (신규) +├── BendingLotForm.tsx # 신규: 절곡품 LOT 등록 폼 +├── RawMaterialLotModal.tsx # 신규: 원자재 LOT 선택 모달 +├── FabricLotModal.tsx # 신규: 원단 LOT 선택 모달 +└── actions.ts # 수정: bending API 함수 추가 +``` + +### 5.2 BendingLotForm 핵심 로직 + +```typescript +// 캐스케이딩 드롭다운 상태 +const [prodCode, setProdCode] = useState(''); // 품목 +const [specCode, setSpecCode] = useState(''); // 종류 +const [lengthCode, setLengthCode] = useState(''); // 모양&길이 +const [lotNumber, setLotNumber] = useState(''); // 자동 생성 LOT + +// 품목 변경 → 종류 필터링 +useEffect(() => { + const filtered = codeMap.specs.filter(s => s.products.includes(prodCode)); + setAvailableSpecs(filtered); + setSpecCode(''); + setLengthCode(''); +}, [prodCode]); + +// LOT 번호 자동 생성 +useEffect(() => { + if (prodCode && specCode && lengthCode && regDate) { + const dateCode = generateDateCode(regDate); + setLotNumber(`${prodCode}${specCode}${dateCode}-${lengthCode}`); + } +}, [prodCode, specCode, lengthCode, regDate]); + +// 날짜코드 생성 +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')}`; +} +``` + +### 5.3 저장 데이터 변환 + +```typescript +// BendingLotForm → API 전송 데이터 +function transformToStockOrder(form: BendingLotFormData): StockOrderFormData { + return { + orderTypeCode: 'STOCK', + memo: form.memo, + remarks: '', + productionReason: '절곡품 재고생산', + targetStockQty: form.quantity, + // bending_lot 정보는 options에 저장 + bendingLot: { + lotNumber: form.lotNumber, + prodCode: form.prodCode, + specCode: form.specCode, + lengthCode: form.lengthCode, + rawLotNo: form.rawLotNo, + fabricLotNo: form.fabricLotNo, + material: form.material, + }, + items: [{ + itemId: form.resolvedItem?.item_id, + itemCode: form.resolvedItem?.item_code || '', + itemName: form.resolvedItem?.item_name || form.productDisplayName, + specification: form.resolvedItem?.specification || '', + quantity: form.quantity, + unit: 'EA', + unitPrice: 0, + }], + }; +} +``` + +### 5.4 프론트엔드 파일 목록 + +| 파일 | 작업 | 설명 | +|------|------|------| +| `src/components/stocks/BendingLotForm.tsx` | 신규 | 절곡품 LOT 등록 폼 | +| `src/components/stocks/RawMaterialLotModal.tsx` | 신규 | 원자재 LOT 선택 모달 | +| `src/components/stocks/FabricLotModal.tsx` | 신규 | 원단 LOT 선택 모달 | +| `src/components/stocks/StockProductionForm.tsx` | 수정 | 절곡품/일반 모드 분기 | +| `src/components/stocks/actions.ts` | 수정 | bending API 함수 추가 | +| `src/app/[locale]/(protected)/sales/stocks/page.tsx` | 수정 | `?mode=new-bending` 라우트 추가 | + +--- + +## 6. 데이터 흐름 + +``` +사용자 입력 API 처리 DB 저장 +┌──────────────┐ ┌─────────────────┐ ┌──────────┐ +│ 품목: G │ │ │ │ orders │ +│ 종류: I │──resolve-item──→│ items 테이블 │ │ (STOCK) │ +│ 길이: 53 │ │ BD-GI-53 조회 │ │ │ +│ 수량: 100 │ │ │ │ │ +│ 날짜: 3/17 │ │ │ │ │ +├──────────────┤ │ │ ├──────────┤ +│ LOT: GI6317-53│──store order───→│ OrderService │──────────→│ options │ +│ 원자재LOT: xx │ │ ::store() │ │ .bending │ +│ 원단LOT: yy │ │ │ │ _lot │ +└──────────────┘ └─────────────────┘ └──────────┘ + │ + ▼ + ┌─────────────────┐ + │ 확정 → 생산지시 │ + │ (기존 흐름 유지) │ + └─────────────────┘ +``` + +--- + +## 7. 구현 순서 + +### Phase 1: 백엔드 API (API 프로젝트) + +1. `BendingCodeService` — 코드 체계 관리 서비스 +2. `BendingController` — 코드맵 조회 + 품목 매핑 API +3. `StoreOrderRequest` 수정 — bending_lot validation +4. 라우트 등록 + Swagger 문서 + +### Phase 2: 프론트엔드 (React 프로젝트) + +1. `actions.ts` — bending API 함수 추가 +2. `BendingLotForm.tsx` — 절곡품 LOT 등록 폼 +3. `RawMaterialLotModal.tsx` — 원자재 LOT 선택 +4. `FabricLotModal.tsx` — 원단 LOT 선택 +5. `StockProductionForm.tsx` 수정 — 모드 분기 +6. 페이지 라우트 수정 + +### Phase 3: 연동 검증 + +1. 드롭다운 연동 동작 확인 +2. LOT 번호 자동 생성 확인 +3. items 테이블 매핑 확인 +4. 저장 → 상세 → 확정 → 생산지시 흐름 확인 + +--- + +## 8. 검토 사항 + +### 8.1 결정 필요 + +| 항목 | 질문 | 제안 | +|------|------|------| +| **LOT 중복** | 동일 LOT 번호가 생길 수 있는가? (같은 날 같은 조합) | 일련번호 suffix 추가 (`GI6317-53-001`) 또는 하루에 한 건만 허용 | +| **items 매핑** | 모든 제품-종류-길이 조합에 대응하는 품목이 items에 존재하는가? | BendingItemSeeder로 미리 생성, 없으면 자동 생성 | +| **모양&길이 확장** | 새 길이 규격이 추가되면? | API 코드맵에서 관리, DB classifications 테이블 활용 가능 | +| **일반 재고생산** | 절곡품 외 다른 재고생산도 있는가? | 기존 폼 유지, 등록 시 "절곡품/일반" 선택 | +| **원자재 LOT 필수** | 원자재 LOT 입력은 필수인가? | 레거시에서는 선택 사항, SAM에서도 선택 권장 | + +### 8.2 items 테이블 매핑 전략 + +현재 `items` 테이블에 `BD-*` 접두사로 절곡 품목이 등록되어 있다. +모든 제품-종류-길이 조합에 대응하는 품목이 존재하지 않을 수 있다. + +**전략 A (권장)**: 매핑 테이블 사용 +- `bending_item_mappings` 테이블 신규: `prod_code, spec_code, length_code → item_id` +- 관리자가 MNG에서 매핑 관리 + +**전략 B**: 코드 규칙 기반 유추 +- `BD-{prodPrefix}{specCode}-{lengthCode}` 패턴으로 items 검색 +- 예: prod=R, spec=M, length=42 → `BD-RM-42` 검색 + +**전략 C**: 품목 자동 등록 +- 매핑되는 품목이 없으면 items에 자동 생성 +- 품목명: `{제품명} {종류명} {길이}mm` + +--- + +## 관련 문서 + +- [재고생산관리 기능 설명](../../features/sales/stock-production.md) +- [재고생산관리 API 명세](../../frontend/api-specs/stock-production-api.md) +- [재고생산 변경이력](../changes/20260316_stock_production_order.md) +- [품목 정책](../../rules/item-policy.md) +- [재공품 생산 정책](../../rules/wip-production-policy.md) + +--- + +**최종 업데이트**: 2026-03-17