Files
sam-docs/dev/dev_plans/stock-production-lot-form-plan.md

18 KiB
Raw Blame History

재고생산 입력 개편 — 절곡품 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에서 제공하여 추후 품목 추가 시 백엔드만 수정한다.

{
  "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 테이블의 실제 품목을 반환한다.

응답:

{
  "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 조회를 재활용한다.

응답:

[
  {
    "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 확장

현재 optionsproduction_reason, target_stock_qty만 저장한다. 절곡품 LOT 정보를 추가 저장한다.

// 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 추가

'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 핵심 로직

// 캐스케이딩 드롭다운 상태
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 저장 데이터 변환

// 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

관련 문서


최종 업데이트: 2026-03-17