diff --git a/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx b/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx index 12f5792d..593f15c6 100644 --- a/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx +++ b/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx @@ -278,6 +278,28 @@ export function ImportInspectionInputModal({ return map; }, [resolveData, template]); + // ===== Item → section/row mapping for normalized records ===== + const itemSectionMap = useMemo(() => { + if (!resolveData) return new Map(); + const map = new Map(); + for (const section of resolveData.template.sections) { + section.items.forEach((item, idx) => { + map.set(String(item.id), { sectionId: section.id, rowIndex: idx }); + }); + } + return map; + }, [resolveData]); + + // ===== Complex column ID (measurements) and result column ID (judgment) ===== + const { complexColumnId, resultColumnId } = useMemo(() => { + if (!resolveData) return { complexColumnId: null as number | null, resultColumnId: null as number | null }; + const cols = resolveData.template.columns; + const complex = cols.find(c => c.input_type === 'complex') ?? null; + const result = cols.find(c => c.input_type === 'select' || c.input_type === 'check') ?? + cols.find(c => c.label.includes('판정')) ?? null; + return { complexColumnId: complex?.id ?? null, resultColumnId: result?.id ?? null }; + }, [resolveData]); + // ===== Load template on modal open ===== useEffect(() => { if (!open || !itemId) return; @@ -317,6 +339,14 @@ export function ImportInspectionInputModal({ return; } + // Reverse map: (sectionId, rowIndex) → inspectionItem.id + const reverseMap = new Map(); + for (const section of resolve.template.sections) { + section.items.forEach((item, idx) => { + reverseMap.set(`${section.id}_${idx}`, String(item.id)); + }); + } + const meas: Record> = {}; const okng: Record> = {}; let savedRemark = ''; @@ -326,28 +356,61 @@ export function ImportInspectionInputModal({ const key = d.field_key; const val = d.field_value || ''; - // {itemId}_n{index} - 숫자 측정값 - const nMatch = key.match(/^(\d+)_n(\d+)$/); - if (nMatch) { - const id = nMatch[1]; - const idx = parseInt(nMatch[2]) - 1; + // Footer fields + if (key === 'remark' || key === 'footer_remark') { savedRemark = val; continue; } + if (key === 'overall_result' || key === 'footer_judgement') { + savedOverall = (val === 'pass' || val === '적합') ? 'pass' + : (val === 'fail' || val === '부적합') ? 'fail' : null; + continue; + } + + // 정규화 형식: section_id가 있으면 reverse map으로 item 찾기 + if (d.section_id != null) { + const itemId = reverseMap.get(`${d.section_id}_${d.row_index}`); + if (!itemId) continue; + + const nMatch = key.match(/^n(\d+)$/); + if (nMatch) { + const idx = parseInt(nMatch[1]) - 1; + if (!meas[itemId]) meas[itemId] = {}; + meas[itemId][idx] = val; + continue; + } + const okMatch = key.match(/^n(\d+)_ok$/); + if (okMatch && val === 'OK') { + const idx = parseInt(okMatch[1]) - 1; + if (!okng[itemId]) okng[itemId] = {}; + okng[itemId][idx] = 'ok'; + continue; + } + const ngMatch = key.match(/^n(\d+)_ng$/); + if (ngMatch && val === 'NG') { + const idx = parseInt(ngMatch[1]) - 1; + if (!okng[itemId]) okng[itemId] = {}; + okng[itemId][idx] = 'ng'; + continue; + } + continue; + } + + // 레거시 형식 fallback: {itemId}_n{N} + const nLegacy = key.match(/^(\d+)_n(\d+)$/); + if (nLegacy) { + const id = nLegacy[1]; + const idx = parseInt(nLegacy[2]) - 1; if (!meas[id]) meas[id] = {}; meas[id][idx] = val; continue; } - - // {itemId}_okng_n{index} - OK/NG 값 - const okngMatch = key.match(/^(\d+)_okng_n(\d+)$/); - if (okngMatch) { - const id = okngMatch[1]; - const idx = parseInt(okngMatch[2]) - 1; + // 레거시: {itemId}_okng_n{N} + const okngLegacy = key.match(/^(\d+)_okng_n(\d+)$/); + if (okngLegacy) { + const id = okngLegacy[1]; + const idx = parseInt(okngLegacy[2]) - 1; if (!okng[id]) okng[id] = {}; okng[id][idx] = val === 'ok' ? 'ok' : val === 'ng' ? 'ng' : null; continue; } - - if (key === 'remark') savedRemark = val; - if (key === 'overall_result') savedOverall = val === 'pass' ? 'pass' : val === 'fail' ? 'fail' : null; } setMeasurements(meas); @@ -505,44 +568,61 @@ export function ImportInspectionInputModal({ uploadedFileIds = (uploadResult.data || []).map((f) => f.id); } - // 2. field_key 기반 데이터 배열 생성 + // 2. 정규화 형식 데이터 배열 생성 const data: Array<{ - section_id?: number | null; + section_id: number | null; + column_id: number | null; row_index: number; field_key: string; field_value: string | null; }> = []; for (const item of template.inspectionItems) { + const mapping = itemSectionMap.get(item.id); + const sectionId = mapping?.sectionId ?? null; + const rowIndex = mapping?.rowIndex ?? 0; const isOkng = item.measurementType === 'okng'; for (let n = 0; n < item.measurementCount; n++) { if (isOkng) { + const val = okngValues[item.id]?.[n]; data.push({ - row_index: 0, - field_key: `${item.id}_okng_n${n + 1}`, - field_value: okngValues[item.id]?.[n] || null, + section_id: sectionId, column_id: complexColumnId, row_index: rowIndex, + field_key: `n${n + 1}_ok`, field_value: val === 'ok' ? 'OK' : '', + }); + data.push({ + section_id: sectionId, column_id: complexColumnId, row_index: rowIndex, + field_key: `n${n + 1}_ng`, field_value: val === 'ng' ? 'NG' : '', }); } else { data.push({ - row_index: 0, - field_key: `${item.id}_n${n + 1}`, - field_value: measurements[item.id]?.[n] || null, + section_id: sectionId, column_id: complexColumnId, row_index: rowIndex, + field_key: `n${n + 1}`, field_value: measurements[item.id]?.[n] || null, }); } } - // 항목별 판정 - data.push({ - row_index: 0, - field_key: `${item.id}_result`, - field_value: getItemResult(item), - }); + // 항목별 판정 (판정 컬럼이 있을 때만) + if (resultColumnId) { + const itemResult = getItemResult(item); + data.push({ + section_id: sectionId, column_id: resultColumnId, row_index: rowIndex, + field_key: 'value', + field_value: itemResult === 'ok' ? '적합' : itemResult === 'ng' ? '부적합' : null, + }); + } } // 종합판정 + 비고 - data.push({ row_index: 0, field_key: 'overall_result', field_value: overallResult }); - data.push({ row_index: 0, field_key: 'remark', field_value: remark || null }); + data.push({ + section_id: null, column_id: null, row_index: 0, + field_key: 'overall_result', + field_value: overallResult === 'pass' ? '적합' : overallResult === 'fail' ? '부적합' : null, + }); + data.push({ + section_id: null, column_id: null, row_index: 0, + field_key: 'remark', field_value: remark || null, + }); // 3. 첨부파일 (기존 + 신규) const attachments = [ diff --git a/src/components/material/ReceivingManagement/actions.ts b/src/components/material/ReceivingManagement/actions.ts index 5e17abbb..460ded23 100644 --- a/src/components/material/ReceivingManagement/actions.ts +++ b/src/components/material/ReceivingManagement/actions.ts @@ -1900,7 +1900,7 @@ export async function saveInspectionData(params: { templateId: number; itemId: number; title?: string; - data: Array<{ section_id?: number | null; row_index: number; field_key: string; field_value: string | null }>; + data: Array<{ section_id?: number | null; column_id?: number | null; row_index: number; field_key: string; field_value: string | null }>; attachments?: Array<{ file_id: number; attachment_type: string; description?: string }>; receivingId: string; inspectionResult?: 'pass' | 'fail' | null;