- LOT 일련번호 suffix 전략 확정 (-001, -002) - items 매핑: 매핑 테이블(bending_item_mappings) 확정 - 일반 재고생산 모드 분기 제거 (절곡품 전용)
20 KiB
재고생산 입력 개편 — 절곡품 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자리 3자리
예시: GI6317-53-001
G = 연기차단재
I = 화이바원단
6 = 2026년
3 = 3월
17 = 17일
53 = W50 × 3000
001 = 일련번호 (같은 날 같은 조합의 순번)
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
}
매핑 로직: bending_item_mappings 테이블에서 prod_code + spec_code + length_code → item_id로 조회한다.
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 확장
현재 options에 production_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 LOT 일련번호 생성 (백엔드)
같은 날 같은 조합의 LOT가 여러 건 등록될 수 있으므로, 저장 시 백엔드에서 일련번호를 부여한다.
// BendingCodeService::generateLotNumber()
public function generateLotNumber(string $base): string
{
// base = 'GI6317-53'
// orders.options->bending_lot->lot_number 에서 같은 base로 시작하는 건 수 조회
$count = Order::where('tenant_id', $this->tenantId())
->where('order_type_code', Order::TYPE_STOCK)
->where('options->bending_lot->lot_number', 'LIKE', $base . '-%')
->count();
$seq = str_pad($count + 1, 3, '0', STR_PAD_LEFT);
return "{$base}-{$seq}"; // GI6317-53-001
}
4.4 매핑 테이블 마이그레이션
// bending_item_mappings 테이블
Schema::create('bending_item_mappings', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id');
$table->string('prod_code', 2)->comment('제품코드: R,S,G,B,T,L,C');
$table->string('spec_code', 2)->comment('종류코드: M,S,I,E...');
$table->string('length_code', 2)->comment('모양&길이코드: 53,42...');
$table->unsignedBigInteger('item_id')->comment('매핑된 품목 ID');
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->unique(['tenant_id', 'prod_code', 'spec_code', 'length_code']);
$table->foreign('item_id')->references('id')->on('items');
$table->foreign('tenant_id')->references('id')->on('tenants');
});
4.5 백엔드 파일 목록
| 파일 | 작업 | 설명 |
|---|---|---|
app/Http/Controllers/Api/V1/BendingController.php |
신규 | 코드맵, 품목매핑, LOT 채번 API |
app/Services/BendingCodeService.php |
신규 | 코드 체계 관리, 품목 매핑, LOT 일련번호 생성 |
app/Models/Production/BendingItemMapping.php |
신규 | 매핑 테이블 모델 |
database/migrations/xxx_create_bending_item_mappings_table.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 컴포넌트 구조
일반 재고생산은 고려하지 않는다. 재고생산 = 절곡품 LOT 입력으로 통일한다.
src/components/stocks/
├── StockProductionList.tsx # 기존 (목록)
├── StockProductionDetail.tsx # 기존 (상세) — LOT 정보 표시 추가
├── StockProductionForm.tsx # 전면 교체: BendingLotForm 기반
├── 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 번호 자동 생성 (일련번호는 백엔드에서 부여)
// 프론트에서는 프리뷰용으로 base 부분만 표시
useEffect(() => {
if (prodCode && specCode && lengthCode && regDate) {
const dateCode = generateDateCode(regDate);
setLotNumberBase(`${prodCode}${specCode}${dateCode}-${lengthCode}`);
// 실제 LOT: GI6317-53-001 (suffix는 저장 시 API가 부여)
}
}, [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 |
전면 교체 | BendingLotForm 기반으로 교체 |
src/components/stocks/StockProductionDetail.tsx |
수정 | LOT 정보(bending_lot) 표시 추가 |
src/components/stocks/actions.ts |
수정 | bending API 함수 추가 |
src/app/[locale]/(protected)/sales/stocks/page.tsx |
수정 | 신규 등록 시 BendingLotForm 사용 |
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 프로젝트)
bending_item_mappings마이그레이션 + 모델BendingCodeService— 코드 체계 관리, 품목 매핑, LOT 일련번호 생성BendingController— 코드맵 조회 + 품목 매핑 + LOT 채번 APIStoreOrderRequest/UpdateOrderRequest수정 — bending_lot validation- 라우트 등록 + Swagger 문서
- 매핑 데이터 시딩 (기존
BD-*품목과 연결)
Phase 2: 프론트엔드 (React 프로젝트)
actions.ts— bending API 함수 추가BendingLotForm.tsx— 절곡품 LOT 등록 폼 (핵심)RawMaterialLotModal.tsx— 원자재 LOT 선택FabricLotModal.tsx— 원단 LOT 선택StockProductionForm.tsx— BendingLotForm 기반으로 전면 교체StockProductionDetail.tsx— LOT 정보 표시 추가
Phase 3: 연동 검증
- 드롭다운 연동 동작 확인
- LOT 번호 자동 생성 확인
- items 테이블 매핑 확인
- 저장 → 상세 → 확정 → 생산지시 흐름 확인
8. 확정 결정 사항
| 항목 | 결정 | 비고 |
|---|---|---|
| LOT 중복 | 일련번호 suffix 사용 (-001, -002...) |
같은 날 같은 조합이면 순번 증가. 백엔드에서 자동 부여 |
| items 매핑 | 전략 A: 매핑 테이블 (bending_item_mappings) |
prod_code + spec_code + length_code → item_id |
| 일반 재고생산 | 고려하지 않음 | 재고생산 = 절곡품 LOT 입력으로 통일. 모드 분기 불필요 |
| 모양&길이 확장 | API 코드맵에서 관리 | 추후 DB classifications 테이블 활용 가능 |
| 원자재 LOT | 선택 사항 (nullable) | 레거시와 동일하게 선택 권장 |
관련 문서
최종 업데이트: 2026-03-17