From d6e3131c6aa92a38588b3fd23594470a6ba43fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Thu, 5 Mar 2026 10:39:45 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[production]=20=EC=A0=88=EA=B3=A1=20?= =?UTF-8?q?=EC=A4=91=EA=B0=84=EA=B2=80=EC=82=AC=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=83=88=EB=A1=9C=EA=B3=A0=EC=B9=A8=20=EC=8B=9C=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InspectionInputModal: 이전 형식 데이터(products 배열 없음) 로드 시 judgment 기반 제품별 상태 추론 - InspectionInputModal: skipAutoJudgmentRef로 이전 형식 로드 시 auto-judgment 덮어쓰기 방지 - BendingInspectionContent: products/bendingStatus 없을 때 judgment 기반 fallback 추가 --- .../documents/BendingInspectionContent.tsx | 9 ++ .../WorkerScreen/InspectionInputModal.tsx | 96 +++++++++++++++++-- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx b/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx index 0aaaf15c..0e640696 100644 --- a/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx +++ b/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx @@ -213,6 +213,15 @@ export const BendingInspectionContent = forwardRef prev.map(p => ({ + ...p, + bendingStatus: inferredStatus, + }))); + } } // 부적합 내용 로드 diff --git a/src/components/production/WorkerScreen/InspectionInputModal.tsx b/src/components/production/WorkerScreen/InspectionInputModal.tsx index 707225a7..2020ab16 100644 --- a/src/components/production/WorkerScreen/InspectionInputModal.tsx +++ b/src/components/production/WorkerScreen/InspectionInputModal.tsx @@ -11,7 +11,7 @@ * - bending_wip: 재고생산(재공품) 중간검사 */ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect, useMemo, useRef } from 'react'; import { Dialog, DialogContent, @@ -24,6 +24,8 @@ import { Textarea } from '@/components/ui/textarea'; import { cn } from '@/lib/utils'; import type { InspectionTemplateData, InspectionTemplateSectionItem } from './types'; import { formatNumber } from '@/lib/utils/amount'; +import { getInspectionConfig } from '@/components/production/WorkOrders/actions'; +import type { InspectionConfigData } from '@/components/production/WorkOrders/actions'; // 중간검사 공정 타입 export type InspectionProcessType = @@ -71,6 +73,8 @@ interface InspectionInputModalProps { templateData?: InspectionTemplateData; /** 작업 아이템의 실제 치수 (reference_attribute 연동용) */ workItemDimensions?: { width?: number; height?: number }; + /** 작업지시 ID (절곡 gap_points API 조회용) */ + workOrderId?: string; } // ===== 절곡 7개 제품 검사 항목 (BendingInspectionContent의 INITIAL_PRODUCTS와 동일 구조) ===== @@ -541,6 +545,7 @@ export function InspectionInputModal({ onComplete, templateData, workItemDimensions, + workOrderId, }: InspectionInputModalProps) { // 템플릿 모드 여부 // 절곡(bending)은 7제품 커스텀 폼 사용 → TemplateInspectionContent의 bending 셀 키와 연동 @@ -556,14 +561,72 @@ export function InspectionInputModal({ // 동적 폼 값 (템플릿 모드용) const [dynamicFormValues, setDynamicFormValues] = useState>({}); + // 이전 형식 데이터 로드 시 auto-judgment가 judgment를 덮어쓰지 않도록 보호 + const skipAutoJudgmentRef = useRef(false); + // 절곡용 간격 포인트 초기화 (레거시 — bending_wip 등에서 사용) const [gapPoints, setGapPoints] = useState<{ left: number | null; right: number | null }[]>( Array(5).fill(null).map(() => ({ left: null, right: null })) ); + // 절곡 API 제품 정의 (gap_points 동적 로딩) + const [apiProductDefs, setApiProductDefs] = useState(null); + const effectiveProductDefs = apiProductDefs || BENDING_PRODUCTS; + // 절곡 7개 제품별 상태 (bending 전용) const [bendingProducts, setBendingProducts] = useState(createInitialBendingProducts); + // API에서 절곡 제품 gap_points 동적 로딩 + useEffect(() => { + if (!open || processType !== 'bending' || !workOrderId) return; + let cancelled = false; + getInspectionConfig(workOrderId).then(result => { + if (cancelled) return; + if (result.success && result.data?.items?.length) { + const displayMap: Record = { + guide_rail_wall: { label: '가이드레일 (벽면형)', len: '3000', wid: 'N/A' }, + guide_rail_side: { label: '가이드레일 (측면형)', len: '3000', wid: 'N/A' }, + case_box: { label: '케이스 (500X380)', len: '3000', wid: 'N/A' }, + bottom_bar: { label: '하단마감재 (60X40)', len: '3000', wid: 'N/A' }, + bottom_l_bar: { label: '하단L-BAR (17X60)', len: '3000', wid: 'N/A' }, + smoke_w50: { label: '연기차단재 (W50)', len: '3000', wid: '' }, + smoke_w80: { label: '연기차단재 (W80)', len: '3000', wid: '' }, + }; + const defs: BendingProductDef[] = result.data.items.map(item => { + const d = displayMap[item.id] || { label: item.name, len: '-', wid: 'N/A' }; + return { + id: item.id, + label: d.label, + lengthDesign: d.len, + widthDesign: d.wid, + gapPoints: item.gap_points.map(gp => ({ point: gp.point, design: gp.design_value })), + }; + }); + setApiProductDefs(defs); + } + }); + return () => { cancelled = true; }; + }, [open, processType, workOrderId]); + + // API 제품 정의 로딩 시 bendingProducts 갱신 (gap 개수 동기화) + useEffect(() => { + if (!apiProductDefs || processType !== 'bending') return; + setBendingProducts(prev => { + return apiProductDefs.map((def, idx) => { + // 기존 입력값 보존 (ID 매칭 또는 인덱스 폴백) + const existing = prev.find(p => p.id === def.id || p.id.replace(/[-_]/g, '') === def.id.replace(/[-_]/g, '')) + || (idx < prev.length ? prev[idx] : undefined); + return { + id: def.id, + bendingStatus: existing?.bendingStatus ?? null, + lengthMeasured: existing?.lengthMeasured ?? '', + widthMeasured: existing?.widthMeasured ?? '', + gapMeasured: def.gapPoints.map((_, gi) => existing?.gapMeasured?.[gi] ?? ''), + }; + }); + }); + }, [apiProductDefs, processType]); + useEffect(() => { if (open) { // initialData가 있으면 기존 저장 데이터로 복원 @@ -588,8 +651,10 @@ export function InspectionInputModal({ gapPoints: Array<{ point: string; designValue: string; measured: string }>; }> | undefined; if (savedProducts && Array.isArray(savedProducts)) { - setBendingProducts(BENDING_PRODUCTS.map((def, idx) => { - const saved = savedProducts.find(sp => sp.id === def.id); + setBendingProducts(effectiveProductDefs.map((def, idx) => { + const saved = savedProducts.find(sp => + sp.id === def.id || sp.id.replace(/[-_]/g, '') === def.id.replace(/[-_]/g, '') + ) || (idx < savedProducts.length ? savedProducts[idx] : undefined); if (!saved) return { id: def.id, bendingStatus: null, lengthMeasured: '', widthMeasured: '', gapMeasured: def.gapPoints.map(() => '') }; return { id: def.id, @@ -599,6 +664,21 @@ export function InspectionInputModal({ gapMeasured: def.gapPoints.map((_, gi) => saved.gapPoints?.[gi]?.measured || ''), }; })); + } else if (processType === 'bending' && initialData.judgment) { + // 이전 형식 데이터 호환: products 배열 없이 저장된 경우 + // judgment 값으로 제품별 상태 추론 (pass → 전체 양호) + const restoredStatus: 'good' | 'bad' | null = + initialData.judgment === 'pass' ? 'good' : initialData.judgment === 'fail' ? 'bad' : null; + setBendingProducts(effectiveProductDefs.map(def => ({ + id: def.id, + bendingStatus: restoredStatus, + lengthMeasured: '', + widthMeasured: '', + gapMeasured: def.gapPoints.map(() => ''), + }))); + // 이전 형식은 lengthMeasured가 없어 autoJudgment가 null이 되므로 + // 로드된 judgment를 덮어쓰지 않도록 보호 + skipAutoJudgmentRef.current = true; } else { setBendingProducts(createInitialBendingProducts()); } @@ -686,8 +766,12 @@ export function InspectionInputModal({ return computeJudgment(processType, formData); }, [useTemplateMode, templateData, dynamicFormValues, workItemDimensions, processType, formData, bendingProducts]); - // 판정값 자동 동기화 + // 판정값 자동 동기화 (이전 형식 데이터 로드 시 첫 번째 동기화 건너뜀) useEffect(() => { + if (skipAutoJudgmentRef.current) { + skipAutoJudgmentRef.current = false; + return; + } setFormData((prev) => { if (prev.judgment === autoJudgment) return prev; return { ...prev, judgment: autoJudgment }; @@ -708,7 +792,7 @@ export function InspectionInputModal({ bendingStatus: p.bendingStatus === 'good' ? '양호' : p.bendingStatus === 'bad' ? '불량' : null, lengthMeasured: p.lengthMeasured, widthMeasured: p.widthMeasured, - gapPoints: BENDING_PRODUCTS[idx].gapPoints.map((gp, gi) => ({ + gapPoints: (effectiveProductDefs[idx]?.gapPoints || []).map((gp, gi) => ({ point: gp.point, designValue: gp.design, measured: p.gapMeasured[gi] || '', @@ -1008,7 +1092,7 @@ export function InspectionInputModal({ {/* ===== 절곡 검사 항목 (7개 제품별) ===== */} {!useTemplateMode && processType === 'bending' && (
- {BENDING_PRODUCTS.map((productDef, pIdx) => { + {effectiveProductDefs.map((productDef, pIdx) => { const pState = bendingProducts[pIdx]; if (!pState) return null;