From ad1180daa50ce53d1f642bd0784e9f8ec7a26dc4 Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 22 Apr 2026 18:09:57 +0900 Subject: [PATCH] feat: Allow duplicate items at the same level in BOM management - Updated the logic in the BomManagementPage to permit the registration of duplicate items at the same level, enabling separate rows for items with different requirements or processes. - Removed the previous check for duplicate items at the same level, enhancing flexibility in item management within the BOM structure. --- .../(main)/COMPANY_10/production/bom/page.tsx | 9 +---- .../(main)/COMPANY_16/production/bom/page.tsx | 9 +---- .../(main)/COMPANY_29/production/bom/page.tsx | 9 +---- .../(main)/COMPANY_30/production/bom/page.tsx | 9 +---- .../(main)/COMPANY_7/production/bom/page.tsx | 9 +---- .../(main)/COMPANY_8/production/bom/page.tsx | 9 +---- .../(main)/COMPANY_9/production/bom/page.tsx | 9 +---- .../components/DetailFormModal.tsx | 35 +++++++++++++++---- 8 files changed, 35 insertions(+), 63 deletions(-) diff --git a/frontend/app/(main)/COMPANY_10/production/bom/page.tsx b/frontend/app/(main)/COMPANY_10/production/bom/page.tsx index 01e7ee14..0cfa7a61 100644 --- a/frontend/app/(main)/COMPANY_10/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_10/production/bom/page.tsx @@ -834,14 +834,7 @@ export default function BomManagementPage() { return; } - // 같은 레벨(같은 부모) 중복 품목 체크 - const siblings = addTargetParentId - ? (findNodeById(editingTree, addTargetParentId)?.children || []) - : editingTree; - if (siblings.some((n) => n.child_item_id === item.id)) { - toast.error("같은 레벨에 이미 동일 품목이 존재합니다"); - return; - } + // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음 const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null; diff --git a/frontend/app/(main)/COMPANY_16/production/bom/page.tsx b/frontend/app/(main)/COMPANY_16/production/bom/page.tsx index 01e7ee14..0cfa7a61 100644 --- a/frontend/app/(main)/COMPANY_16/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_16/production/bom/page.tsx @@ -834,14 +834,7 @@ export default function BomManagementPage() { return; } - // 같은 레벨(같은 부모) 중복 품목 체크 - const siblings = addTargetParentId - ? (findNodeById(editingTree, addTargetParentId)?.children || []) - : editingTree; - if (siblings.some((n) => n.child_item_id === item.id)) { - toast.error("같은 레벨에 이미 동일 품목이 존재합니다"); - return; - } + // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음 const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null; diff --git a/frontend/app/(main)/COMPANY_29/production/bom/page.tsx b/frontend/app/(main)/COMPANY_29/production/bom/page.tsx index 01e7ee14..0cfa7a61 100644 --- a/frontend/app/(main)/COMPANY_29/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_29/production/bom/page.tsx @@ -834,14 +834,7 @@ export default function BomManagementPage() { return; } - // 같은 레벨(같은 부모) 중복 품목 체크 - const siblings = addTargetParentId - ? (findNodeById(editingTree, addTargetParentId)?.children || []) - : editingTree; - if (siblings.some((n) => n.child_item_id === item.id)) { - toast.error("같은 레벨에 이미 동일 품목이 존재합니다"); - return; - } + // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음 const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null; diff --git a/frontend/app/(main)/COMPANY_30/production/bom/page.tsx b/frontend/app/(main)/COMPANY_30/production/bom/page.tsx index d3c52b1a..e44cd38f 100644 --- a/frontend/app/(main)/COMPANY_30/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_30/production/bom/page.tsx @@ -841,14 +841,7 @@ export default function BomManagementPage() { return; } - // 같은 레벨(같은 부모) 중복 품목 체크 - const siblings = addTargetParentId - ? (findNodeById(editingTree, addTargetParentId)?.children || []) - : editingTree; - if (siblings.some((n) => n.child_item_id === item.id)) { - toast.error("같은 레벨에 이미 동일 품목이 존재합니다"); - return; - } + // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음 const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null; diff --git a/frontend/app/(main)/COMPANY_7/production/bom/page.tsx b/frontend/app/(main)/COMPANY_7/production/bom/page.tsx index 01e7ee14..0cfa7a61 100644 --- a/frontend/app/(main)/COMPANY_7/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_7/production/bom/page.tsx @@ -834,14 +834,7 @@ export default function BomManagementPage() { return; } - // 같은 레벨(같은 부모) 중복 품목 체크 - const siblings = addTargetParentId - ? (findNodeById(editingTree, addTargetParentId)?.children || []) - : editingTree; - if (siblings.some((n) => n.child_item_id === item.id)) { - toast.error("같은 레벨에 이미 동일 품목이 존재합니다"); - return; - } + // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음 const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null; diff --git a/frontend/app/(main)/COMPANY_8/production/bom/page.tsx b/frontend/app/(main)/COMPANY_8/production/bom/page.tsx index 01e7ee14..0cfa7a61 100644 --- a/frontend/app/(main)/COMPANY_8/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_8/production/bom/page.tsx @@ -834,14 +834,7 @@ export default function BomManagementPage() { return; } - // 같은 레벨(같은 부모) 중복 품목 체크 - const siblings = addTargetParentId - ? (findNodeById(editingTree, addTargetParentId)?.children || []) - : editingTree; - if (siblings.some((n) => n.child_item_id === item.id)) { - toast.error("같은 레벨에 이미 동일 품목이 존재합니다"); - return; - } + // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음 const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null; diff --git a/frontend/app/(main)/COMPANY_9/production/bom/page.tsx b/frontend/app/(main)/COMPANY_9/production/bom/page.tsx index 51bc486a..3d27f801 100644 --- a/frontend/app/(main)/COMPANY_9/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_9/production/bom/page.tsx @@ -841,14 +841,7 @@ export default function BomManagementPage() { return; } - // 같은 레벨(같은 부모) 중복 품목 체크 - const siblings = addTargetParentId - ? (findNodeById(editingTree, addTargetParentId)?.children || []) - : editingTree; - if (siblings.some((n) => n.child_item_id === item.id)) { - toast.error("같은 레벨에 이미 동일 품목이 존재합니다"); - return; - } + // 같은 레벨 중복 허용 — 소요량/공정 등이 다른 동일 품목을 별도 row로 등록할 수 있음 const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2)}`; const parentNode = addTargetParentId ? findNodeById(editingTree, addTargetParentId) : null; diff --git a/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx b/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx index d2ba60fc..78a9b2bc 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx @@ -131,13 +131,33 @@ export function DetailFormModal({ }, [selectedItemCode]); // BOM 자재 로드 완료 후 체크 상태 초기화 + // bomChecked 키는 BOM detail 고유 id(mat.id) 기준 — 동일 child_item_id가 여러 row로 + // 있어도 독립적으로 체크되도록 함. legacy 저장값은 child_item_id로 저장되어 있을 수 + // 있으므로 폴백 매핑으로 복원. useEffect(() => { if (!open || bomMaterials.length === 0) return; if (mode === "edit" && editData?.selected_bom_items) { const savedBom = editData.selected_bom_items; const parsedBom = typeof savedBom === "string" ? JSON.parse(savedBom) : savedBom; if (Array.isArray(parsedBom)) { - setBomChecked(new Set(parsedBom)); + const matIds = new Set(bomMaterials.map((m) => m.id)); + const restored = new Set(); + const seenChildForLegacy = new Set(); + for (const saved of parsedBom) { + if (matIds.has(saved)) { + restored.add(saved); // 신규 포맷: bom_detail id 저장값 + } else { + // legacy 폴백: child_item_id로 저장된 값 → 해당 품목의 첫 번째 행 1건 자동 체크 + const legacyKey = String(saved); + if (seenChildForLegacy.has(legacyKey)) continue; + const firstMat = bomMaterials.find((m) => m.child_item_id === legacyKey); + if (firstMat) { + restored.add(firstMat.id); + seenChildForLegacy.add(legacyKey); + } + } + } + setBomChecked(restored); return; } } @@ -310,8 +330,9 @@ export function DetailFormModal({ submitData.content = submitData.content || "작업수량 / 불량수량 / 양품수량"; } if (type === "material_input") { - // 선택된 BOM 자재를 각각 개별 상세 항목으로 등록 - const checkedMats = bomMaterials.filter(m => bomChecked.has(m.child_item_id)); + // 선택된 BOM 자재를 각각 개별 상세 항목으로 등록 — 동일 품목 중복 row를 독립 + // 처리하기 위해 bom_detail 고유 id(m.id) 기준으로 매칭 + const checkedMats = bomMaterials.filter(m => bomChecked.has(m.id)); if (checkedMats.length > 0) { const resolveType = (code: string) => itemTypeCatMap[code] || code || ""; for (const mat of checkedMats) { @@ -955,7 +976,7 @@ export function DetailFormModal({ } onCheckedChange={(checked) => { if (checked) { - setBomChecked(new Set(bomMaterials.map((m) => m.child_item_id))); + setBomChecked(new Set(bomMaterials.map((m) => m.id))); } else { setBomChecked(new Set()); } @@ -994,12 +1015,12 @@ export function DetailFormModal({ className="flex items-center gap-2.5 border-b px-3 py-2.5 last:border-b-0 hover:bg-sky-50/50" > { setBomChecked((prev) => { const next = new Set(prev); - if (checked) next.add(mat.child_item_id); - else next.delete(mat.child_item_id); + if (checked) next.add(mat.id); + else next.delete(mat.id); return next; }); }}