From 8bcabafd082d3b42b605b529a13dc93e37378a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Tue, 3 Mar 2026 20:58:10 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[production]=20=EC=9E=90=EC=9E=AC?= =?UTF-8?q?=ED=88=AC=EC=9E=85=20=EB=AA=A8=EB=8B=AC=20=E2=80=94=20bomGroupK?= =?UTF-8?q?ey=20=EA=B7=B8=EB=A3=B9=ED=95=91,=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EC=A0=95=EB=A0=AC,=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bomGroupKey 기반 그룹핑 (같은 item_id라도 category+partType별 분리) - 카테고리 순서 정렬 (가이드레일→하단마감재→셔터박스→연기차단재) - 카테고리 내 원형번호(①②③) 표시 - partType 배지 추가 - MaterialForItemInput에 bomGroupKey 필드 추가 --- .../WorkerScreen/MaterialInputModal.tsx | 31 ++++++++++++++++--- .../production/WorkerScreen/actions.ts | 3 ++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/components/production/WorkerScreen/MaterialInputModal.tsx b/src/components/production/WorkerScreen/MaterialInputModal.tsx index 09052c14..1d026227 100644 --- a/src/components/production/WorkerScreen/MaterialInputModal.tsx +++ b/src/components/production/WorkerScreen/MaterialInputModal.tsx @@ -99,16 +99,22 @@ export function MaterialInputModal({ const getLotKey = (material: MaterialForInput) => String(material.stockLotId ?? `item-${material.itemId}`); - // 품목별 그룹핑 + // 품목별 그룹핑 (BOM 엔트리별 고유키 사용 — 같은 item_id라도 category+partType 다르면 별도 그룹) const materialGroups: MaterialGroup[] = useMemo(() => { - // dynamic_bom 항목은 (itemId, workOrderItemId) 쌍으로 그룹핑 const groups = new Map(); for (const m of materials) { - const groupKey = m.workOrderItemId ? `${m.itemId}_${m.workOrderItemId}` : String(m.itemId); + const itemInput = m as unknown as MaterialForItemInput; + const groupKey = itemInput.bomGroupKey + ?? (m.workOrderItemId ? `${m.itemId}_${m.workOrderItemId}` : String(m.itemId)); const existing = groups.get(groupKey) || []; existing.push(m); groups.set(groupKey, existing); } + // 작업일지와 동일한 카테고리 순서 + const categoryOrder: Record = { + guideRail: 0, bottomBar: 1, shutterBox: 2, smokeBarrier: 3, + }; + return Array.from(groups.entries()).map(([groupKey, lots]) => { const first = lots[0]; const itemInput = first as unknown as MaterialForItemInput; @@ -129,6 +135,10 @@ export function MaterialInputModal({ partType: first.partType, category: first.category, }; + }).sort((a, b) => { + const catA = categoryOrder[a.category ?? ''] ?? 99; + const catB = categoryOrder[b.category ?? ''] ?? 99; + return catA - catB; }); }, [materials]); @@ -344,7 +354,15 @@ export function MaterialInputModal({ ) : (
- {materialGroups.map((group) => { + {materialGroups.map((group, groupIdx) => { + // 같은 카테고리 내 순번 계산 (①②③...) + const categoryIndex = group.category + ? materialGroups.slice(0, groupIdx).filter(g => g.category === group.category).length + : -1; + const circledNumbers = ['①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩']; + const circledNum = categoryIndex >= 0 && categoryIndex < circledNumbers.length + ? circledNumbers[categoryIndex] : ''; + const groupAllocated = group.lots.reduce( (sum, lot) => sum + (allocations.get(getLotKey(lot)) || 0), 0 @@ -372,6 +390,11 @@ export function MaterialInputModal({ group.category} )} + {group.partType && ( + + {circledNum}{group.partType} + + )} {group.materialName} diff --git a/src/components/production/WorkerScreen/actions.ts b/src/components/production/WorkerScreen/actions.ts index e6b04708..8ca84135 100644 --- a/src/components/production/WorkerScreen/actions.ts +++ b/src/components/production/WorkerScreen/actions.ts @@ -316,6 +316,7 @@ export async function registerMaterialInput( export interface MaterialForItemInput extends MaterialForInput { alreadyInputted: number; // 이미 투입된 수량 remainingRequiredQty: number; // 남은 필요 수량 + bomGroupKey?: string; // BOM 엔트리별 고유 그룹키 (category+partType 기반) } export async function getMaterialsForItem( @@ -336,6 +337,7 @@ export async function getMaterialsForItem( receipt_date: string | null; supplier: string | null; // dynamic_bom 추가 필드 work_order_item_id?: number; lot_prefix?: string; part_type?: string; category?: string; + bom_group_key?: string; } const result = await executeServerAction({ url: `${API_URL}/api/v1/work-orders/${workOrderId}/items/${itemId}/materials`, @@ -354,6 +356,7 @@ export async function getMaterialsForItem( remainingRequiredQty: item.remaining_required_qty, workOrderItemId: item.work_order_item_id, lotPrefix: item.lot_prefix, partType: item.part_type, category: item.category, + bomGroupKey: item.bom_group_key, })), }; }