diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx index 5de99505..5882ce0e 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/production-order/page.tsx @@ -259,40 +259,68 @@ function matchItemToProcess( return null; } -// 수주 품목들에서 매칭되는 공정의 workSteps 추출 -function getWorkStepsForOrder( - items: Array<{ itemName: string; itemCode?: string }>, +// 공정별 작업지시 그룹 타입 +interface ProcessWorkOrderGroup { + process: Process; + items: Array<{ itemName: string; itemCode?: string; quantity: number }>; +} + +// 수주 품목들을 공정별로 그룹핑 +function groupItemsByProcess( + items: Array<{ itemName: string; itemCode?: string; quantity: number }>, processes: Process[] -): string[] { - // 첫 번째 품목으로 공정 매칭 시도 - if (items.length > 0 && processes.length > 0) { - const firstItem = items[0]; +): ProcessWorkOrderGroup[] { + if (items.length === 0 || processes.length === 0) { + return []; + } + + // 공정별 품목 그룹 맵 + const processGroupMap = new Map(); + // 매칭되지 않은 품목들 + const unmatchedItems: Array<{ itemName: string; itemCode?: string; quantity: number }> = []; + + for (const item of items) { const matchedProcess = matchItemToProcess( - firstItem.itemName, - firstItem.itemCode, + item.itemName, + item.itemCode, processes ); - if (matchedProcess && matchedProcess.workSteps.length > 0) { - // workSteps에 번호 추가하여 반환 - return matchedProcess.workSteps.map( - (step, idx) => `${idx + 1}. ${step}` - ); + if (matchedProcess) { + if (!processGroupMap.has(matchedProcess.id)) { + processGroupMap.set(matchedProcess.id, { + process: matchedProcess, + items: [], + }); + } + processGroupMap.get(matchedProcess.id)!.items.push(item); + } else { + unmatchedItems.push(item); } } - // 매칭된 공정이 없거나 workSteps가 없으면 첫 번째 공정의 workSteps 사용 - if (processes.length > 0) { - const firstProcess = processes[0]; - if (firstProcess.workSteps.length > 0) { - return firstProcess.workSteps.map( - (step, idx) => `${idx + 1}. ${step}` - ); - } + // 매칭되지 않은 품목이 있으면 "기타" 그룹 생성 + const groups = Array.from(processGroupMap.values()); + + if (unmatchedItems.length > 0) { + // 기타 품목용 가상 공정 생성 + groups.push({ + process: { + id: "unmatched", + processName: "기타", + processCode: "ETC", + status: "사용중", + description: "", + classificationRules: [], + workSteps: [], + createdAt: "", + updatedAt: "", + } as Process, + items: unmatchedItems, + }); } - // 공정이 없으면 빈 배열 반환 - return []; + return groups; } export default function ProductionOrderCreatePage() { @@ -432,7 +460,23 @@ export default function ProductionOrderCreatePage() { } const selectedConfig = getSelectedPriorityConfig(); - const workOrderCount = 1; // 현재는 수주당 하나의 작업지시 생성 + + // 공정별 품목 그룹핑 + const allGroups = groupItemsByProcess( + (order.items || []).map((item) => ({ + itemName: item.itemName, + itemCode: item.itemCode, + quantity: item.quantity, + })), + processes + ); + + // 공정 매칭된 그룹과 기타(미분류) 그룹 분리 + const processGroups = allGroups.filter((g) => g.process.id !== "unmatched"); + const unmatchedGroup = allGroups.find((g) => g.process.id === "unmatched"); + + // 작업지시 수 = 공정 매칭된 그룹 수만 (기타 제외) + const workOrderCount = processGroups.length; // order.items에서 스크린 품목 상세 데이터 변환 const screenItems: ScreenItemDetail[] = (order.items || []).map((item, index) => ({ @@ -635,54 +679,91 @@ export default function ProductionOrderCreatePage() {
- {/* 수주 데이터 기반 작업지시 카드 */} -
-
- - 스크린 - - {order.lotNumber} -
-
-

품목 수

-

{screenItems.length}건

-
-
-

공정 순서

-
- {(() => { - const workSteps = getWorkStepsForOrder( - (order.items || []).map(item => ({ - itemName: item.itemName, - itemCode: item.itemCode, - })), - processes - ); - - if (workSteps.length === 0) { - return ( + {processGroups.length > 0 ? ( + processGroups.map((group, groupIdx) => ( +
+
+ + {group.process.processName} + + + {order.lotNumber}-{String(groupIdx + 1).padStart(2, "0")} + +
+
+

품목 수

+

{group.items.length}건

+
+
+

공정 순서

+
+ {group.process.workSteps.length > 0 ? ( + group.process.workSteps.map((step, idx) => ( + + {idx + 1}. {step} + + )) + ) : ( 공정관리에서 세부 작업단계를 등록해 주세요. - ); - } - - return workSteps.map((step, idx) => ( - - {step} - - )); - })()} + )} +
+
+
+ )) + ) : ( +
+
+

+ 매칭되는 공정이 없습니다. 공정관리에서 분류규칙을 등록해 주세요. +

-
+ )}
+ {/* 기타 품목 (공정 미매칭) */} + {unmatchedGroup && unmatchedGroup.items.length > 0 && ( + + + + 기타 품목 + + {unmatchedGroup.items.length}건 + + + + +

+ 공정에 매칭되지 않은 품목입니다. 출하 시 별도로 준비해 주세요. +

+
    + {unmatchedGroup.items.map((item, idx) => { + const qty = Number(item.quantity); + return ( +
  • + + {item.itemName} + + ({Number.isInteger(qty) ? qty : qty.toFixed(2).replace(/\.?0+$/, "")}개) + +
  • + ); + })} +
+
+
+ )} + {/* 자재 소요량 및 재고 현황 - 추후 BOM API 연동 예정 */}