fix: [production] 절곡 중간검사 데이터 새로고침 시 초기화 버그 수정

- InspectionInputModal: 이전 형식 데이터(products 배열 없음) 로드 시 judgment 기반 제품별 상태 추론
- InspectionInputModal: skipAutoJudgmentRef로 이전 형식 로드 시 auto-judgment 덮어쓰기 방지
- BendingInspectionContent: products/bendingStatus 없을 때 judgment 기반 fallback 추가
This commit is contained in:
2026-03-05 10:39:45 +09:00
parent 1d3805781c
commit d6e3131c6a
2 changed files with 99 additions and 6 deletions

View File

@@ -213,6 +213,15 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
...p,
bendingStatus: bendingStatusValue,
})));
} else if (itemData.judgment) {
// 이전 형식 호환: products/bendingStatus 없이 judgment만 있는 경우
const inferredStatus: CheckStatus = itemData.judgment === 'pass' ? '양호' : itemData.judgment === 'fail' ? '불량' : null;
if (inferredStatus) {
setProducts(prev => prev.map(p => ({
...p,
bendingStatus: inferredStatus,
})));
}
}
// 부적합 내용 로드

View File

@@ -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<Record<string, unknown>>({});
// 이전 형식 데이터 로드 시 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<BendingProductDef[] | null>(null);
const effectiveProductDefs = apiProductDefs || BENDING_PRODUCTS;
// 절곡 7개 제품별 상태 (bending 전용)
const [bendingProducts, setBendingProducts] = useState<BendingProductState[]>(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<string, { label: string; len: string; wid: string }> = {
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' && (
<div className="space-y-4">
{BENDING_PRODUCTS.map((productDef, pIdx) => {
{effectiveProductDefs.map((productDef, pIdx) => {
const pState = bendingProducts[pIdx];
if (!pState) return null;