fix: [inspection] 절곡 검사성적서 재공품 대응 통합 수정

- 검사부위 공백 수정 (템플릿 컬럼 "부위" 라벨 매칭)
- hasWipItems 판정 보완 (sidebar order fallback)
- bending_wip 7제품 폼 통합 (products 배열 저장)
- 도면치수 실제 품목 길이 반영 (3000 하드코딩 제거)
- 테스트입력 버튼 7제품 데이터 채우기
- 하단 버튼 분리 유지 (작업일지/검사성적서)
- STOCK 단일부품 해당 부품만 검사항목 표시
- bendingInfo 기반 동적 검사 제품 생성
- 작업일지 LOT NO 원자재 투입 로트번호 표시
This commit is contained in:
김보곤
2026-03-21 21:21:06 +09:00
parent d91057aeb1
commit f483cff206
10 changed files with 441 additions and 238 deletions

View File

@@ -547,8 +547,9 @@ export function InspectionInputModal({
workOrderId,
}: InspectionInputModalProps) {
// 템플릿 모드 여부
// 절곡(bending)은 7제품 커스텀 폼 사용 → TemplateInspectionContent의 bending 셀 키와 연동
const useTemplateMode = processType !== 'bending' && !!(templateData?.has_template && templateData.template);
// 절곡(bending/bending_wip)은 7제품 커스텀 폼 사용 → TemplateInspectionContent의 bending 셀 키와 연동
const isBendingProcess = processType === 'bending' || processType === 'bending_wip';
const useTemplateMode = !isBendingProcess && !!(templateData?.has_template && templateData.template);
const [formData, setFormData] = useState<InspectionData>({
productName,
@@ -577,19 +578,21 @@ export function InspectionInputModal({
// API에서 절곡 제품 gap_points 동적 로딩
useEffect(() => {
if (!open || processType !== 'bending' || !workOrderId) return;
if (!open || !isBendingProcess || !workOrderId) return;
let cancelled = false;
getInspectionConfig(workOrderId).then(result => {
if (cancelled) return;
if (result.success && result.data?.items?.length) {
// 실제 품목 길이: workItemDimensions.height (예: 2438mm) 우선, 없으면 3000 폴백
const actualLen = workItemDimensions?.height ? String(workItemDimensions.height) : '3000';
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: '' },
guide_rail_wall: { label: '가이드레일 (벽면형)', len: actualLen, wid: 'N/A' },
guide_rail_side: { label: '가이드레일 (측면형)', len: actualLen, wid: 'N/A' },
case_box: { label: '케이스 (500X380)', len: actualLen, wid: 'N/A' },
bottom_bar: { label: '하단마감재 (60X40)', len: actualLen, wid: 'N/A' },
bottom_l_bar: { label: '하단L-BAR (17X60)', len: actualLen, wid: 'N/A' },
smoke_w50: { label: '연기차단재 (W50)', len: actualLen, wid: '' },
smoke_w80: { label: '연기차단재 (W80)', len: actualLen, wid: '' },
};
const defs: BendingProductDef[] = result.data.items.map(item => {
const d = displayMap[item.id] || { label: item.name, len: '-', wid: 'N/A' };
@@ -605,11 +608,11 @@ export function InspectionInputModal({
}
});
return () => { cancelled = true; };
}, [open, processType, workOrderId]);
}, [open, processType, workOrderId, workItemDimensions?.height]);
// API 제품 정의 로딩 시 bendingProducts 갱신 (gap 개수 동기화)
useEffect(() => {
if (!apiProductDefs || processType !== 'bending') return;
if (!apiProductDefs || !isBendingProcess) return;
setBendingProducts(prev => {
return apiProductDefs.map((def, idx) => {
// 기존 입력값 보존 (ID 매칭 또는 인덱스 폴백)
@@ -663,7 +666,7 @@ export function InspectionInputModal({
gapMeasured: def.gapPoints.map((_, gi) => saved.gapPoints?.[gi]?.measured || ''),
};
}));
} else if (processType === 'bending' && initialData.judgment) {
} else if (isBendingProcess && initialData.judgment) {
// 이전 형식 데이터 호환: products 배열 없이 저장된 경우
// judgment 값으로 제품별 상태 추론 (pass → 전체 양호)
const restoredStatus: 'good' | 'bad' | null =
@@ -751,7 +754,7 @@ export function InspectionInputModal({
return computeDynamicJudgment(templateData.template, dynamicFormValues, workItemDimensions);
}
// 절곡 7개 제품 전용 판정
if (processType === 'bending') {
if (isBendingProcess) {
let allGood = true;
let allFilled = true;
for (const p of bendingProducts) {
@@ -785,7 +788,7 @@ export function InspectionInputModal({
};
// 절곡: products 배열을 성적서와 동일 포맷으로 저장
if (processType === 'bending') {
if (isBendingProcess) {
const products = bendingProducts.map((p, idx) => ({
id: p.id,
bendingStatus: p.bendingStatus === 'good' ? '양호' : p.bendingStatus === 'bad' ? '불량' : null,
@@ -841,22 +844,55 @@ export function InspectionInputModal({
)}
onClick={() => {
if (!formData.judgment) {
const w = workItemDimensions?.width || 1000;
const h = workItemDimensions?.height || 500;
setFormData((prev) => ({
...prev,
bendingStatus: 'good',
processingStatus: 'good',
sewingStatus: 'good',
assemblyStatus: 'good',
length: w,
width: h,
height1: h,
height2: h,
judgment: 'pass',
nonConformingContent: '',
}));
// 동적 템플릿 모드: 각 항목의 기준값을 사용하여 적합한 값 입력
if (useTemplateMode && templateData?.template) {
const testValues: Record<string, unknown> = {};
for (const section of templateData.template.sections) {
for (const item of section.items) {
const fieldKey = `section_${section.id}_item_${item.id}`;
if (isNumericItem(item)) {
const design = resolveDesignValue(item, workItemDimensions);
testValues[fieldKey] = design ?? 100;
} else {
testValues[fieldKey] = 'ok';
}
}
}
setDynamicFormValues(testValues);
}
if (!useTemplateMode) {
// 레거시 모드: 기존 로직
const w = workItemDimensions?.width || 1000;
const h = workItemDimensions?.height || 500;
setFormData((prev) => ({
...prev,
bendingStatus: 'good',
processingStatus: 'good',
sewingStatus: 'good',
assemblyStatus: 'good',
length: w,
width: h,
height1: h,
height2: h,
judgment: 'pass',
nonConformingContent: '',
}));
// 절곡 7제품: 모든 제품 양호 + 도면치수와 동일한 측정값 입력
if (isBendingProcess) {
setBendingProducts(effectiveProductDefs.map(def => ({
id: def.id,
bendingStatus: 'good' as const,
lengthMeasured: def.lengthDesign || '',
widthMeasured: def.widthDesign || '',
gapMeasured: def.gapPoints.map(gp => gp.design || ''),
})));
}
}
} else {
// 초기화
if (useTemplateMode) {
setDynamicFormValues({});
}
setFormData((prev) => ({
...prev,
bendingStatus: null,
@@ -870,6 +906,10 @@ export function InspectionInputModal({
judgment: null,
nonConformingContent: '',
}));
// 절곡 7제품 초기화
if (isBendingProcess) {
setBendingProducts(createInitialBendingProducts());
}
}
}}
>
@@ -914,8 +954,8 @@ export function InspectionInputModal({
{/* ===== 레거시: 공정별 하드코딩 검사 항목 (템플릿 없을 때만 표시) ===== */}
{/* ===== 재고생산 (bending_wip) 검사 항목 ===== */}
{!useTemplateMode && processType === 'bending_wip' && (
{/* ===== 재고생산 (bending_wip) 검사 항목 — 7제품 폼으로 통합됨 (위 절곡 검사 항목 참조) ===== */}
{false && processType === 'bending_wip' && (
<>
<div className="space-y-1.5">
<span className="text-sm font-bold"> </span>
@@ -1139,7 +1179,7 @@ export function InspectionInputModal({
)}
{/* ===== 절곡 검사 항목 (7개 제품별) ===== */}
{!useTemplateMode && processType === 'bending' && (
{!useTemplateMode && isBendingProcess && (
<div className="space-y-4">
{effectiveProductDefs.map((productDef, pIdx) => {
const pState = bendingProducts[pIdx];