fix: [inspection] 절곡 검사성적서 재공품 대응 통합 수정
- 검사부위 공백 수정 (템플릿 컬럼 "부위" 라벨 매칭) - hasWipItems 판정 보완 (sidebar order fallback) - bending_wip 7제품 폼 통합 (products 배열 저장) - 도면치수 실제 품목 길이 반영 (3000 하드코딩 제거) - 테스트입력 버튼 7제품 데이터 채우기 - 하단 버튼 분리 유지 (작업일지/검사성적서) - STOCK 단일부품 해당 부품만 검사항목 표시 - bendingInfo 기반 동적 검사 제품 생성 - 작업일지 LOT NO 원자재 투입 로트번호 표시
This commit is contained in:
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user