diff --git a/backend-node/src/controllers/processWorkStandardController.ts b/backend-node/src/controllers/processWorkStandardController.ts
index d745bab4..622d8f89 100644
--- a/backend-node/src/controllers/processWorkStandardController.ts
+++ b/backend-node/src/controllers/processWorkStandardController.ts
@@ -463,7 +463,7 @@ export async function getWorkItemDetails(req: AuthenticatedRequest, res: Respons
SELECT id, work_item_id, detail_type, content, is_required, sort_order, remark,
inspection_code, inspection_method, unit, lower_limit, upper_limit,
duration_minutes, input_type, lookup_target, display_fields,
- selected_bom_items, created_date
+ selected_bom_items, process_inspection_apply, equip_inspection_apply, created_date
FROM process_work_item_detail
WHERE work_item_id = $1 AND company_code = $2
ORDER BY sort_order, created_date
@@ -492,7 +492,7 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
work_item_id, detail_type, content, is_required, sort_order, remark,
inspection_code, inspection_method, unit, lower_limit, upper_limit,
duration_minutes, input_type, lookup_target, display_fields,
- selected_bom_items,
+ selected_bom_items, process_inspection_apply, equip_inspection_apply,
} = req.body;
if (!work_item_id || !content) {
@@ -515,8 +515,9 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
INSERT INTO process_work_item_detail
(company_code, work_item_id, detail_type, content, is_required, sort_order, remark, writer,
inspection_code, inspection_method, unit, lower_limit, upper_limit,
- duration_minutes, input_type, lookup_target, display_fields, selected_bom_items)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
+ duration_minutes, input_type, lookup_target, display_fields, selected_bom_items,
+ process_inspection_apply, equip_inspection_apply)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
RETURNING *
`;
@@ -542,6 +543,8 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
lookup_target || null,
display_fields || null,
bomItemsJson,
+ process_inspection_apply || null,
+ equip_inspection_apply || null,
]);
logger.info("작업 항목 상세 생성", { companyCode, id: result.rows[0].id });
@@ -567,7 +570,7 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
detail_type, content, is_required, sort_order, remark,
inspection_code, inspection_method, unit, lower_limit, upper_limit,
duration_minutes, input_type, lookup_target, display_fields,
- selected_bom_items,
+ selected_bom_items, process_inspection_apply, equip_inspection_apply,
} = req.body;
const bomItemsJson = Array.isArray(selected_bom_items) ? JSON.stringify(selected_bom_items) : selected_bom_items ?? null;
@@ -589,6 +592,8 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
lookup_target = $15,
display_fields = $16,
selected_bom_items = $17,
+ process_inspection_apply = $18,
+ equip_inspection_apply = $19,
updated_date = NOW()
WHERE id = $6 AND company_code = $7
RETURNING *
@@ -612,6 +617,8 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
lookup_target || null,
display_fields || null,
bomItemsJson,
+ process_inspection_apply || null,
+ equip_inspection_apply || null,
]);
if (result.rowCount === 0) {
diff --git a/backend-node/src/controllers/workInstructionController.ts b/backend-node/src/controllers/workInstructionController.ts
index 9f855147..b8960006 100644
--- a/backend-node/src/controllers/workInstructionController.ts
+++ b/backend-node/src/controllers/workInstructionController.ts
@@ -443,7 +443,8 @@ export async function getWorkStandard(req: AuthenticatedRequest, res: Response)
const detailsResult = await pool.query(
`SELECT id, wi_work_item_id AS work_item_id, detail_type, content, is_required, sort_order, remark,
inspection_code, inspection_method, unit, lower_limit, upper_limit,
- duration_minutes, input_type, lookup_target, display_fields
+ duration_minutes, input_type, lookup_target, display_fields,
+ process_inspection_apply, equip_inspection_apply
FROM wi_process_work_item_detail
WHERE wi_work_item_id = $1 AND company_code = $2
ORDER BY sort_order`,
@@ -467,7 +468,8 @@ export async function getWorkStandard(req: AuthenticatedRequest, res: Response)
const detailsResult = await pool.query(
`SELECT id, work_item_id, detail_type, content, is_required, sort_order, remark,
inspection_code, inspection_method, unit, lower_limit, upper_limit,
- duration_minutes, input_type, lookup_target, display_fields
+ duration_minutes, input_type, lookup_target, display_fields,
+ process_inspection_apply, equip_inspection_apply
FROM process_work_item_detail
WHERE work_item_id = $1 AND company_code = $2
ORDER BY sort_order`,
@@ -548,9 +550,9 @@ export async function copyWorkStandard(req: AuthenticatedRequest, res: Response)
for (const origDetail of origDetails.rows) {
await client.query(
- `INSERT INTO wi_process_work_item_detail (company_code, wi_work_item_id, detail_type, content, is_required, sort_order, remark, inspection_code, inspection_method, unit, lower_limit, upper_limit, duration_minutes, input_type, lookup_target, display_fields, writer)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)`,
- [companyCode, newItemId, origDetail.detail_type, origDetail.content, origDetail.is_required, origDetail.sort_order, origDetail.remark, origDetail.inspection_code, origDetail.inspection_method, origDetail.unit, origDetail.lower_limit, origDetail.upper_limit, origDetail.duration_minutes, origDetail.input_type, origDetail.lookup_target, origDetail.display_fields, userId]
+ `INSERT INTO wi_process_work_item_detail (company_code, wi_work_item_id, detail_type, content, is_required, sort_order, remark, inspection_code, inspection_method, unit, lower_limit, upper_limit, duration_minutes, input_type, lookup_target, display_fields, process_inspection_apply, equip_inspection_apply, writer)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)`,
+ [companyCode, newItemId, origDetail.detail_type, origDetail.content, origDetail.is_required, origDetail.sort_order, origDetail.remark, origDetail.inspection_code, origDetail.inspection_method, origDetail.unit, origDetail.lower_limit, origDetail.upper_limit, origDetail.duration_minutes, origDetail.input_type, origDetail.lookup_target, origDetail.display_fields, origDetail.process_inspection_apply || null, origDetail.equip_inspection_apply || null, userId]
);
}
}
@@ -612,9 +614,9 @@ export async function saveWorkStandard(req: AuthenticatedRequest, res: Response)
if (wi.details && Array.isArray(wi.details)) {
for (const d of wi.details) {
await client.query(
- `INSERT INTO wi_process_work_item_detail (company_code, wi_work_item_id, detail_type, content, is_required, sort_order, remark, inspection_code, inspection_method, unit, lower_limit, upper_limit, duration_minutes, input_type, lookup_target, display_fields, writer)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)`,
- [companyCode, newId, d.detail_type, d.content, d.is_required, d.sort_order, d.remark || null, d.inspection_code || null, d.inspection_method || null, d.unit || null, d.lower_limit || null, d.upper_limit || null, d.duration_minutes || null, d.input_type || null, d.lookup_target || null, d.display_fields || null, userId]
+ `INSERT INTO wi_process_work_item_detail (company_code, wi_work_item_id, detail_type, content, is_required, sort_order, remark, inspection_code, inspection_method, unit, lower_limit, upper_limit, duration_minutes, input_type, lookup_target, display_fields, process_inspection_apply, equip_inspection_apply, writer)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)`,
+ [companyCode, newId, d.detail_type, d.content, d.is_required, d.sort_order, d.remark || null, d.inspection_code || null, d.inspection_method || null, d.unit || null, d.lower_limit || null, d.upper_limit || null, d.duration_minutes || null, d.input_type || null, d.lookup_target || null, d.display_fields || null, d.process_inspection_apply || null, d.equip_inspection_apply || null, userId]
);
}
}
diff --git a/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx b/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx
index 424993b0..86f01a94 100644
--- a/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx
+++ b/frontend/app/(main)/COMPANY_10/quality/inspection/page.tsx
@@ -136,7 +136,7 @@ export default function InspectionManagementPage() {
await Promise.all(
catList.map(async ({ table, col }) => {
try {
- const res = await apiClient.get(`/table-categories/${table}/${col}/values?filterCompanyCode=COMPANY_10`);
+ const res = await apiClient.get(`/table-categories/${table}/${col}/values?filterCompanyCode=COMPANY_7`);
if (res.data?.data?.length > 0) {
optMap[`${table}.${col}`] = flattenCategories(res.data.data);
}
@@ -330,6 +330,11 @@ export default function InspectionManagementPage() {
toast.error("판단기준은 필수예요");
return;
}
+ const judgmentLabel = (catOptions[`${INSPECTION_TABLE}.judgment_criteria`] || []).find((o) => o.code === inspForm.judgment_criteria)?.label;
+ if (judgmentLabel === "선택형" && !inspForm.selection_options?.trim()) {
+ toast.error("선택형은 옵션을 1개 이상 추가해주세요");
+ return;
+ }
setInspSaving(true);
try {
let finalCode = inspForm.inspection_code || "";
@@ -1178,6 +1183,50 @@ export default function InspectionManagementPage() {
+ {/* 선택형 옵션 입력 (판단기준이 선택형일 때만) */}
+ {(catOptions[`${INSPECTION_TABLE}.judgment_criteria`] || []).find((o) => o.code === inspForm.judgment_criteria)?.label === "선택형" && (
+
+
+
+
+ {(inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : []).map((opt: string, idx: number) => (
+
+ {opt}
+
+
+ ))}
+
+
+ {
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
+ e.preventDefault();
+ const v = (e.target as HTMLInputElement).value.trim();
+ if (!v) return;
+ const existing = inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : [];
+ if (existing.includes(v)) { toast.error("이미 존재하는 옵션입니다."); return; }
+ setInspForm((p: any) => ({ ...p, selection_options: [...existing, v].join(",") }));
+ (e.target as HTMLInputElement).value = "";
+ }
+ }}
+ />
+
+
옵션명을 입력하고 Enter를 눌러 추가하세요. POP에서 이 옵션 중 선택하여 검사 결과를 입력합니다.
+
+
+ )}
{/* 단위 */}
diff --git a/frontend/app/(main)/COMPANY_10/quality/item-inspection/page.tsx b/frontend/app/(main)/COMPANY_10/quality/item-inspection/page.tsx
index d6aaaabe..d3b008d7 100644
--- a/frontend/app/(main)/COMPANY_10/quality/item-inspection/page.tsx
+++ b/frontend/app/(main)/COMPANY_10/quality/item-inspection/page.tsx
@@ -49,6 +49,9 @@ type InspectionRow = {
apply_process: string;
acceptance_criteria: string;
is_required: boolean;
+ judgment_criteria?: string; // 판단기준 라벨 (수치(범위)/텍스트입력/O·X/선택형)
+ selection_options?: string; // 선택형일 때 옵션 (콤마 구분)
+ unit?: string; // 검사 단위
};
export default function ItemInspectionInfoPage() {
@@ -74,15 +77,25 @@ export default function ItemInspectionInfoPage() {
// FK 옵션
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
- const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
+ const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; judgment_criteria: string; selection_options: string; unit: string; types: string[] }[]>([]);
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [judgmentCatOptions, setJudgmentCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [inspUnitCatOptions, setInspUnitCatOptions] = useState<{ code: string; label: string }[]>([]);
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
+ // 품목 카테고리 코드→라벨 (type, inventory_unit)
+ const [itemCatMap, setItemCatMap] = useState
>>({});
+ const itemCatMapRef = React.useRef(itemCatMap);
+ itemCatMapRef.current = itemCatMap;
+
// 검사유형별 검사항목 rows (모달용)
const [inspectionRows, setInspectionRows] = useState>({});
const [collapsedTypes, setCollapsedTypes] = useState>({});
+ // 기본 라우팅 공정 목록 (적용공정 Select용)
+ const [processOptions, setProcessOptions] = useState<{ code: string; name: string }[]>([]);
+
// 품목 선택 모달
const [itemModalOpen, setItemModalOpen] = useState(false);
const [itemSearchKeyword, setItemSearchKeyword] = useState("");
@@ -116,9 +129,26 @@ export default function ItemInspectionInfoPage() {
label: r.inspection_criteria || r.inspection_standard || r.id,
detail: r.inspection_item || r.inspection_criteria || "",
method: r.inspection_method || "",
+ judgment_criteria: r.judgment_criteria || "",
+ selection_options: r.selection_options || "",
+ unit: r.unit || "",
types: r.inspection_type ? r.inspection_type.split(",").filter(Boolean) : [],
})));
+ // 품목 카테고리 (type, unit)
+ const catMap: Record> = {};
+ for (const col of ["type", "unit", "inventory_unit"]) {
+ try {
+ const catRes = await apiClient.get(`/table-categories/item_info/${col}/values`);
+ if (catRes.data?.success && catRes.data.data?.length) {
+ catMap[col] = {};
+ const fl = (arr: any[]) => { for (const v of arr) { catMap[col][v.valueCode] = v.valueLabel; if (v.children?.length) fl(v.children); } };
+ fl(catRes.data.data);
+ }
+ } catch { /* skip */ }
+ }
+ setItemCatMap(catMap);
+
// 검사유형 카테고리
try {
const catRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_type/values`);
@@ -137,6 +167,24 @@ export default function ItemInspectionInfoPage() {
setInspMethodCatOptions(flatMethods);
} catch { /* skip */ }
+ // 판단기준 카테고리
+ try {
+ const jcRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/judgment_criteria/values`);
+ const flatJc: { code: string; label: string }[] = [];
+ const flattenJc = (arr: any[]) => { for (const v of arr) { flatJc.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenJc(v.children); } };
+ if (jcRes.data?.data?.length) flattenJc(jcRes.data.data);
+ setJudgmentCatOptions(flatJc);
+ } catch { /* skip */ }
+
+ // 검사 단위 카테고리
+ try {
+ const unitRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/unit/values`);
+ const flatUnit: { code: string; label: string }[] = [];
+ const flattenU = (arr: any[]) => { for (const v of arr) { flatUnit.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenU(v.children); } };
+ if (unitRes.data?.data?.length) flattenU(unitRes.data.data);
+ setInspUnitCatOptions(flatUnit);
+ } catch { /* skip */ }
+
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
setUserOptions(users.map((u: any) => ({
code: u.user_id || u.id,
@@ -147,6 +195,23 @@ export default function ItemInspectionInfoPage() {
loadOptions();
}, []);
+ // 품목별 기본 라우팅 공정 로드
+ const loadProcessOptions = async (itemCode: string) => {
+ try {
+ const res = await apiClient.get(`/work-instruction/__lookup__/routing-versions/${encodeURIComponent(itemCode)}`);
+ if (res.data?.success && res.data.data?.length > 0) {
+ const defaultVer = res.data.data.find((v: any) => v.is_default) || res.data.data[0];
+ const procs = (defaultVer.processes || []).map((p: any) => ({
+ code: p.process_code,
+ name: p.process_name || p.process_code,
+ }));
+ setProcessOptions(procs);
+ } else {
+ setProcessOptions([]);
+ }
+ } catch { setProcessOptions([]); }
+ };
+
/* ═══════════════════ 품목 선택 모달 ═══════════════════ */
const searchItemServer = async (page?: number) => {
const p = page ?? itemPage;
@@ -163,15 +228,21 @@ export default function ItemInspectionInfoPage() {
});
const resData = res.data?.data;
const rows = resData?.data || resData?.rows || [];
- setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: r.type || "", unit: r.inventory_unit || "" })));
+ const cm = itemCatMapRef.current;
+ setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: cm["type"]?.[r.type] || r.type || "", unit: cm["inventory_unit"]?.[r.inventory_unit] || r.inventory_unit || "" })));
setItemTotal(resData?.total || resData?.totalCount || rows.length);
} catch { /* skip */ } finally { setItemSearchLoading(false); }
};
const openItemModal = () => { setItemSearchKeyword(""); setItemPage(1); setItemModalOpen(true); searchItemServer(1); };
const handleItemSearch = () => { setItemPage(1); searchItemServer(1); };
const selectItem = (item: typeof itemOptions[0]) => {
+ if (groupedData.some(g => g.item_code === item.code)) {
+ toast.error(`"${item.name}" 은(는) 이미 등록된 품목입니다.`);
+ return;
+ }
setForm(p => ({ ...p, item_code: item.code, item_name: item.name }));
setItemModalOpen(false);
+ loadProcessOptions(item.code);
};
/* ═══════════════════ 데이터 조회 ═══════════════════ */
@@ -242,6 +313,7 @@ export default function ItemInspectionInfoPage() {
if (!code) { toast.error("수정할 항목을 선택해주세요"); return; }
const group = groupedData.find(g => g.item_code === code);
if (!group) return;
+ loadProcessOptions(code);
const row = group.rows[0];
setForm({ ...row });
setEditMode(true);
@@ -269,6 +341,12 @@ export default function ItemInspectionInfoPage() {
if (!rowMap[typeKey]) rowMap[typeKey] = [];
const mCode = r.inspection_method || "";
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
+ // 판단기준/선택옵션/단위 resolve
+ const inspOpt = inspOptions.find(o => o.code === r.inspection_standard_id);
+ const jcCode = inspOpt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ const unitCode = inspOpt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
rowMap[typeKey].push({
id: r.id,
inspection_standard_id: r.inspection_standard_id || "",
@@ -277,6 +355,9 @@ export default function ItemInspectionInfoPage() {
apply_process: "",
acceptance_criteria: r.pass_criteria || "",
is_required: r.is_required === "true" || r.is_required === true,
+ judgment_criteria: jcLabel,
+ selection_options: inspOpt?.selection_options || "",
+ unit: unitLabel,
});
}
setInspectionRows(rowMap);
@@ -304,7 +385,22 @@ export default function ItemInspectionInfoPage() {
const opt = inspOptions.find(o => o.code === value);
const methodCode = opt?.method || "";
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
- return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
+ // 판단기준 라벨 resolve
+ const jcCode = opt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ // 단위 라벨 resolve
+ const unitCode = opt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
+ return {
+ ...r,
+ inspection_standard_id: value,
+ inspection_detail: opt?.detail || "",
+ inspection_method: methodLabel,
+ judgment_criteria: jcLabel,
+ selection_options: opt?.selection_options || "",
+ unit: unitLabel,
+ acceptance_criteria: "", // 판단기준 변경 시 초기화
+ };
}
return { ...r, [field]: value };
}),
@@ -561,9 +657,12 @@ export default function ItemInspectionInfoPage() {
{resolveMethodLabel(row.inspection_method)}
{row.apply_process || "-"}
- {row.judgment_criteria ? (
- {row.judgment_criteria}
- ) : "-"}
+ {(() => {
+ const insp = inspOptions.find(o => o.code === row.inspection_standard_id);
+ const jcCode = insp?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ return jcLabel ? {jcLabel} : "-";
+ })()}
{row.pass_criteria || "-"}
@@ -588,7 +687,7 @@ export default function ItemInspectionInfoPage() {
{/* ═══════════════════ 등록/수정 모달 ═══════════════════ */}
+ {/* 선택형 옵션 입력 (판단기준이 선택형일 때만) */}
+ {(catOptions[`${INSPECTION_TABLE}.judgment_criteria`] || []).find((o) => o.code === inspForm.judgment_criteria)?.label === "선택형" && (
+
+
+
+
+ {(inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : []).map((opt: string, idx: number) => (
+
+ {opt}
+
+
+ ))}
+
+
+ {
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
+ e.preventDefault();
+ const v = (e.target as HTMLInputElement).value.trim();
+ if (!v) return;
+ const existing = inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : [];
+ if (existing.includes(v)) { toast.error("이미 존재하는 옵션입니다."); return; }
+ setInspForm((p: any) => ({ ...p, selection_options: [...existing, v].join(",") }));
+ (e.target as HTMLInputElement).value = "";
+ }
+ }}
+ />
+
+
옵션명을 입력하고 Enter를 눌러 추가하세요. POP에서 이 옵션 중 선택하여 검사 결과를 입력합니다.
+
+
+ )}
{/* 단위 */}
diff --git a/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx b/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx
index d6aaaabe..d3b008d7 100644
--- a/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx
+++ b/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx
@@ -49,6 +49,9 @@ type InspectionRow = {
apply_process: string;
acceptance_criteria: string;
is_required: boolean;
+ judgment_criteria?: string; // 판단기준 라벨 (수치(범위)/텍스트입력/O·X/선택형)
+ selection_options?: string; // 선택형일 때 옵션 (콤마 구분)
+ unit?: string; // 검사 단위
};
export default function ItemInspectionInfoPage() {
@@ -74,15 +77,25 @@ export default function ItemInspectionInfoPage() {
// FK 옵션
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
- const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
+ const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; judgment_criteria: string; selection_options: string; unit: string; types: string[] }[]>([]);
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [judgmentCatOptions, setJudgmentCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [inspUnitCatOptions, setInspUnitCatOptions] = useState<{ code: string; label: string }[]>([]);
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
+ // 품목 카테고리 코드→라벨 (type, inventory_unit)
+ const [itemCatMap, setItemCatMap] = useState
>>({});
+ const itemCatMapRef = React.useRef(itemCatMap);
+ itemCatMapRef.current = itemCatMap;
+
// 검사유형별 검사항목 rows (모달용)
const [inspectionRows, setInspectionRows] = useState>({});
const [collapsedTypes, setCollapsedTypes] = useState>({});
+ // 기본 라우팅 공정 목록 (적용공정 Select용)
+ const [processOptions, setProcessOptions] = useState<{ code: string; name: string }[]>([]);
+
// 품목 선택 모달
const [itemModalOpen, setItemModalOpen] = useState(false);
const [itemSearchKeyword, setItemSearchKeyword] = useState("");
@@ -116,9 +129,26 @@ export default function ItemInspectionInfoPage() {
label: r.inspection_criteria || r.inspection_standard || r.id,
detail: r.inspection_item || r.inspection_criteria || "",
method: r.inspection_method || "",
+ judgment_criteria: r.judgment_criteria || "",
+ selection_options: r.selection_options || "",
+ unit: r.unit || "",
types: r.inspection_type ? r.inspection_type.split(",").filter(Boolean) : [],
})));
+ // 품목 카테고리 (type, unit)
+ const catMap: Record> = {};
+ for (const col of ["type", "unit", "inventory_unit"]) {
+ try {
+ const catRes = await apiClient.get(`/table-categories/item_info/${col}/values`);
+ if (catRes.data?.success && catRes.data.data?.length) {
+ catMap[col] = {};
+ const fl = (arr: any[]) => { for (const v of arr) { catMap[col][v.valueCode] = v.valueLabel; if (v.children?.length) fl(v.children); } };
+ fl(catRes.data.data);
+ }
+ } catch { /* skip */ }
+ }
+ setItemCatMap(catMap);
+
// 검사유형 카테고리
try {
const catRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_type/values`);
@@ -137,6 +167,24 @@ export default function ItemInspectionInfoPage() {
setInspMethodCatOptions(flatMethods);
} catch { /* skip */ }
+ // 판단기준 카테고리
+ try {
+ const jcRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/judgment_criteria/values`);
+ const flatJc: { code: string; label: string }[] = [];
+ const flattenJc = (arr: any[]) => { for (const v of arr) { flatJc.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenJc(v.children); } };
+ if (jcRes.data?.data?.length) flattenJc(jcRes.data.data);
+ setJudgmentCatOptions(flatJc);
+ } catch { /* skip */ }
+
+ // 검사 단위 카테고리
+ try {
+ const unitRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/unit/values`);
+ const flatUnit: { code: string; label: string }[] = [];
+ const flattenU = (arr: any[]) => { for (const v of arr) { flatUnit.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenU(v.children); } };
+ if (unitRes.data?.data?.length) flattenU(unitRes.data.data);
+ setInspUnitCatOptions(flatUnit);
+ } catch { /* skip */ }
+
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
setUserOptions(users.map((u: any) => ({
code: u.user_id || u.id,
@@ -147,6 +195,23 @@ export default function ItemInspectionInfoPage() {
loadOptions();
}, []);
+ // 품목별 기본 라우팅 공정 로드
+ const loadProcessOptions = async (itemCode: string) => {
+ try {
+ const res = await apiClient.get(`/work-instruction/__lookup__/routing-versions/${encodeURIComponent(itemCode)}`);
+ if (res.data?.success && res.data.data?.length > 0) {
+ const defaultVer = res.data.data.find((v: any) => v.is_default) || res.data.data[0];
+ const procs = (defaultVer.processes || []).map((p: any) => ({
+ code: p.process_code,
+ name: p.process_name || p.process_code,
+ }));
+ setProcessOptions(procs);
+ } else {
+ setProcessOptions([]);
+ }
+ } catch { setProcessOptions([]); }
+ };
+
/* ═══════════════════ 품목 선택 모달 ═══════════════════ */
const searchItemServer = async (page?: number) => {
const p = page ?? itemPage;
@@ -163,15 +228,21 @@ export default function ItemInspectionInfoPage() {
});
const resData = res.data?.data;
const rows = resData?.data || resData?.rows || [];
- setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: r.type || "", unit: r.inventory_unit || "" })));
+ const cm = itemCatMapRef.current;
+ setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: cm["type"]?.[r.type] || r.type || "", unit: cm["inventory_unit"]?.[r.inventory_unit] || r.inventory_unit || "" })));
setItemTotal(resData?.total || resData?.totalCount || rows.length);
} catch { /* skip */ } finally { setItemSearchLoading(false); }
};
const openItemModal = () => { setItemSearchKeyword(""); setItemPage(1); setItemModalOpen(true); searchItemServer(1); };
const handleItemSearch = () => { setItemPage(1); searchItemServer(1); };
const selectItem = (item: typeof itemOptions[0]) => {
+ if (groupedData.some(g => g.item_code === item.code)) {
+ toast.error(`"${item.name}" 은(는) 이미 등록된 품목입니다.`);
+ return;
+ }
setForm(p => ({ ...p, item_code: item.code, item_name: item.name }));
setItemModalOpen(false);
+ loadProcessOptions(item.code);
};
/* ═══════════════════ 데이터 조회 ═══════════════════ */
@@ -242,6 +313,7 @@ export default function ItemInspectionInfoPage() {
if (!code) { toast.error("수정할 항목을 선택해주세요"); return; }
const group = groupedData.find(g => g.item_code === code);
if (!group) return;
+ loadProcessOptions(code);
const row = group.rows[0];
setForm({ ...row });
setEditMode(true);
@@ -269,6 +341,12 @@ export default function ItemInspectionInfoPage() {
if (!rowMap[typeKey]) rowMap[typeKey] = [];
const mCode = r.inspection_method || "";
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
+ // 판단기준/선택옵션/단위 resolve
+ const inspOpt = inspOptions.find(o => o.code === r.inspection_standard_id);
+ const jcCode = inspOpt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ const unitCode = inspOpt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
rowMap[typeKey].push({
id: r.id,
inspection_standard_id: r.inspection_standard_id || "",
@@ -277,6 +355,9 @@ export default function ItemInspectionInfoPage() {
apply_process: "",
acceptance_criteria: r.pass_criteria || "",
is_required: r.is_required === "true" || r.is_required === true,
+ judgment_criteria: jcLabel,
+ selection_options: inspOpt?.selection_options || "",
+ unit: unitLabel,
});
}
setInspectionRows(rowMap);
@@ -304,7 +385,22 @@ export default function ItemInspectionInfoPage() {
const opt = inspOptions.find(o => o.code === value);
const methodCode = opt?.method || "";
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
- return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
+ // 판단기준 라벨 resolve
+ const jcCode = opt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ // 단위 라벨 resolve
+ const unitCode = opt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
+ return {
+ ...r,
+ inspection_standard_id: value,
+ inspection_detail: opt?.detail || "",
+ inspection_method: methodLabel,
+ judgment_criteria: jcLabel,
+ selection_options: opt?.selection_options || "",
+ unit: unitLabel,
+ acceptance_criteria: "", // 판단기준 변경 시 초기화
+ };
}
return { ...r, [field]: value };
}),
@@ -561,9 +657,12 @@ export default function ItemInspectionInfoPage() {
{resolveMethodLabel(row.inspection_method)}
{row.apply_process || "-"}
- {row.judgment_criteria ? (
- {row.judgment_criteria}
- ) : "-"}
+ {(() => {
+ const insp = inspOptions.find(o => o.code === row.inspection_standard_id);
+ const jcCode = insp?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ return jcLabel ? {jcLabel} : "-";
+ })()}
{row.pass_criteria || "-"}
@@ -588,7 +687,7 @@ export default function ItemInspectionInfoPage() {
{/* ═══════════════════ 등록/수정 모달 ═══════════════════ */}
+ {/* 선택형 옵션 입력 (판단기준이 선택형일 때만) */}
+ {(catOptions[`${INSPECTION_TABLE}.judgment_criteria`] || []).find((o) => o.code === inspForm.judgment_criteria)?.label === "선택형" && (
+
+
+
+
+ {(inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : []).map((opt: string, idx: number) => (
+
+ {opt}
+
+
+ ))}
+
+
+ {
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
+ e.preventDefault();
+ const v = (e.target as HTMLInputElement).value.trim();
+ if (!v) return;
+ const existing = inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : [];
+ if (existing.includes(v)) { toast.error("이미 존재하는 옵션입니다."); return; }
+ setInspForm((p: any) => ({ ...p, selection_options: [...existing, v].join(",") }));
+ (e.target as HTMLInputElement).value = "";
+ }
+ }}
+ />
+
+
옵션명을 입력하고 Enter를 눌러 추가하세요. POP에서 이 옵션 중 선택하여 검사 결과를 입력합니다.
+
+
+ )}
{/* 단위 */}
diff --git a/frontend/app/(main)/COMPANY_29/quality/item-inspection/page.tsx b/frontend/app/(main)/COMPANY_29/quality/item-inspection/page.tsx
index d6aaaabe..d3b008d7 100644
--- a/frontend/app/(main)/COMPANY_29/quality/item-inspection/page.tsx
+++ b/frontend/app/(main)/COMPANY_29/quality/item-inspection/page.tsx
@@ -49,6 +49,9 @@ type InspectionRow = {
apply_process: string;
acceptance_criteria: string;
is_required: boolean;
+ judgment_criteria?: string; // 판단기준 라벨 (수치(범위)/텍스트입력/O·X/선택형)
+ selection_options?: string; // 선택형일 때 옵션 (콤마 구분)
+ unit?: string; // 검사 단위
};
export default function ItemInspectionInfoPage() {
@@ -74,15 +77,25 @@ export default function ItemInspectionInfoPage() {
// FK 옵션
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
- const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
+ const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; judgment_criteria: string; selection_options: string; unit: string; types: string[] }[]>([]);
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [judgmentCatOptions, setJudgmentCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [inspUnitCatOptions, setInspUnitCatOptions] = useState<{ code: string; label: string }[]>([]);
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
+ // 품목 카테고리 코드→라벨 (type, inventory_unit)
+ const [itemCatMap, setItemCatMap] = useState
>>({});
+ const itemCatMapRef = React.useRef(itemCatMap);
+ itemCatMapRef.current = itemCatMap;
+
// 검사유형별 검사항목 rows (모달용)
const [inspectionRows, setInspectionRows] = useState>({});
const [collapsedTypes, setCollapsedTypes] = useState>({});
+ // 기본 라우팅 공정 목록 (적용공정 Select용)
+ const [processOptions, setProcessOptions] = useState<{ code: string; name: string }[]>([]);
+
// 품목 선택 모달
const [itemModalOpen, setItemModalOpen] = useState(false);
const [itemSearchKeyword, setItemSearchKeyword] = useState("");
@@ -116,9 +129,26 @@ export default function ItemInspectionInfoPage() {
label: r.inspection_criteria || r.inspection_standard || r.id,
detail: r.inspection_item || r.inspection_criteria || "",
method: r.inspection_method || "",
+ judgment_criteria: r.judgment_criteria || "",
+ selection_options: r.selection_options || "",
+ unit: r.unit || "",
types: r.inspection_type ? r.inspection_type.split(",").filter(Boolean) : [],
})));
+ // 품목 카테고리 (type, unit)
+ const catMap: Record> = {};
+ for (const col of ["type", "unit", "inventory_unit"]) {
+ try {
+ const catRes = await apiClient.get(`/table-categories/item_info/${col}/values`);
+ if (catRes.data?.success && catRes.data.data?.length) {
+ catMap[col] = {};
+ const fl = (arr: any[]) => { for (const v of arr) { catMap[col][v.valueCode] = v.valueLabel; if (v.children?.length) fl(v.children); } };
+ fl(catRes.data.data);
+ }
+ } catch { /* skip */ }
+ }
+ setItemCatMap(catMap);
+
// 검사유형 카테고리
try {
const catRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_type/values`);
@@ -137,6 +167,24 @@ export default function ItemInspectionInfoPage() {
setInspMethodCatOptions(flatMethods);
} catch { /* skip */ }
+ // 판단기준 카테고리
+ try {
+ const jcRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/judgment_criteria/values`);
+ const flatJc: { code: string; label: string }[] = [];
+ const flattenJc = (arr: any[]) => { for (const v of arr) { flatJc.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenJc(v.children); } };
+ if (jcRes.data?.data?.length) flattenJc(jcRes.data.data);
+ setJudgmentCatOptions(flatJc);
+ } catch { /* skip */ }
+
+ // 검사 단위 카테고리
+ try {
+ const unitRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/unit/values`);
+ const flatUnit: { code: string; label: string }[] = [];
+ const flattenU = (arr: any[]) => { for (const v of arr) { flatUnit.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenU(v.children); } };
+ if (unitRes.data?.data?.length) flattenU(unitRes.data.data);
+ setInspUnitCatOptions(flatUnit);
+ } catch { /* skip */ }
+
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
setUserOptions(users.map((u: any) => ({
code: u.user_id || u.id,
@@ -147,6 +195,23 @@ export default function ItemInspectionInfoPage() {
loadOptions();
}, []);
+ // 품목별 기본 라우팅 공정 로드
+ const loadProcessOptions = async (itemCode: string) => {
+ try {
+ const res = await apiClient.get(`/work-instruction/__lookup__/routing-versions/${encodeURIComponent(itemCode)}`);
+ if (res.data?.success && res.data.data?.length > 0) {
+ const defaultVer = res.data.data.find((v: any) => v.is_default) || res.data.data[0];
+ const procs = (defaultVer.processes || []).map((p: any) => ({
+ code: p.process_code,
+ name: p.process_name || p.process_code,
+ }));
+ setProcessOptions(procs);
+ } else {
+ setProcessOptions([]);
+ }
+ } catch { setProcessOptions([]); }
+ };
+
/* ═══════════════════ 품목 선택 모달 ═══════════════════ */
const searchItemServer = async (page?: number) => {
const p = page ?? itemPage;
@@ -163,15 +228,21 @@ export default function ItemInspectionInfoPage() {
});
const resData = res.data?.data;
const rows = resData?.data || resData?.rows || [];
- setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: r.type || "", unit: r.inventory_unit || "" })));
+ const cm = itemCatMapRef.current;
+ setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: cm["type"]?.[r.type] || r.type || "", unit: cm["inventory_unit"]?.[r.inventory_unit] || r.inventory_unit || "" })));
setItemTotal(resData?.total || resData?.totalCount || rows.length);
} catch { /* skip */ } finally { setItemSearchLoading(false); }
};
const openItemModal = () => { setItemSearchKeyword(""); setItemPage(1); setItemModalOpen(true); searchItemServer(1); };
const handleItemSearch = () => { setItemPage(1); searchItemServer(1); };
const selectItem = (item: typeof itemOptions[0]) => {
+ if (groupedData.some(g => g.item_code === item.code)) {
+ toast.error(`"${item.name}" 은(는) 이미 등록된 품목입니다.`);
+ return;
+ }
setForm(p => ({ ...p, item_code: item.code, item_name: item.name }));
setItemModalOpen(false);
+ loadProcessOptions(item.code);
};
/* ═══════════════════ 데이터 조회 ═══════════════════ */
@@ -242,6 +313,7 @@ export default function ItemInspectionInfoPage() {
if (!code) { toast.error("수정할 항목을 선택해주세요"); return; }
const group = groupedData.find(g => g.item_code === code);
if (!group) return;
+ loadProcessOptions(code);
const row = group.rows[0];
setForm({ ...row });
setEditMode(true);
@@ -269,6 +341,12 @@ export default function ItemInspectionInfoPage() {
if (!rowMap[typeKey]) rowMap[typeKey] = [];
const mCode = r.inspection_method || "";
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
+ // 판단기준/선택옵션/단위 resolve
+ const inspOpt = inspOptions.find(o => o.code === r.inspection_standard_id);
+ const jcCode = inspOpt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ const unitCode = inspOpt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
rowMap[typeKey].push({
id: r.id,
inspection_standard_id: r.inspection_standard_id || "",
@@ -277,6 +355,9 @@ export default function ItemInspectionInfoPage() {
apply_process: "",
acceptance_criteria: r.pass_criteria || "",
is_required: r.is_required === "true" || r.is_required === true,
+ judgment_criteria: jcLabel,
+ selection_options: inspOpt?.selection_options || "",
+ unit: unitLabel,
});
}
setInspectionRows(rowMap);
@@ -304,7 +385,22 @@ export default function ItemInspectionInfoPage() {
const opt = inspOptions.find(o => o.code === value);
const methodCode = opt?.method || "";
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
- return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
+ // 판단기준 라벨 resolve
+ const jcCode = opt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ // 단위 라벨 resolve
+ const unitCode = opt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
+ return {
+ ...r,
+ inspection_standard_id: value,
+ inspection_detail: opt?.detail || "",
+ inspection_method: methodLabel,
+ judgment_criteria: jcLabel,
+ selection_options: opt?.selection_options || "",
+ unit: unitLabel,
+ acceptance_criteria: "", // 판단기준 변경 시 초기화
+ };
}
return { ...r, [field]: value };
}),
@@ -561,9 +657,12 @@ export default function ItemInspectionInfoPage() {
{resolveMethodLabel(row.inspection_method)}
{row.apply_process || "-"}
- {row.judgment_criteria ? (
- {row.judgment_criteria}
- ) : "-"}
+ {(() => {
+ const insp = inspOptions.find(o => o.code === row.inspection_standard_id);
+ const jcCode = insp?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ return jcLabel ? {jcLabel} : "-";
+ })()}
{row.pass_criteria || "-"}
@@ -588,7 +687,7 @@ export default function ItemInspectionInfoPage() {
{/* ═══════════════════ 등록/수정 모달 ═══════════════════ */}
+ {/* 선택형 옵션 입력 (판단기준이 선택형일 때만) */}
+ {(catOptions[`${INSPECTION_TABLE}.judgment_criteria`] || []).find((o) => o.code === inspForm.judgment_criteria)?.label === "선택형" && (
+
+
+
+
+ {(inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : []).map((opt: string, idx: number) => (
+
+ {opt}
+
+
+ ))}
+
+
+ {
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
+ e.preventDefault();
+ const v = (e.target as HTMLInputElement).value.trim();
+ if (!v) return;
+ const existing = inspForm.selection_options ? inspForm.selection_options.split(",").filter(Boolean) : [];
+ if (existing.includes(v)) { toast.error("이미 존재하는 옵션입니다."); return; }
+ setInspForm((p: any) => ({ ...p, selection_options: [...existing, v].join(",") }));
+ (e.target as HTMLInputElement).value = "";
+ }
+ }}
+ />
+
+
옵션명을 입력하고 Enter를 눌러 추가하세요. POP에서 이 옵션 중 선택하여 검사 결과를 입력합니다.
+
+
+ )}
{/* 단위 */}
diff --git a/frontend/app/(main)/COMPANY_30/quality/item-inspection/page.tsx b/frontend/app/(main)/COMPANY_30/quality/item-inspection/page.tsx
index d6aaaabe..d3b008d7 100644
--- a/frontend/app/(main)/COMPANY_30/quality/item-inspection/page.tsx
+++ b/frontend/app/(main)/COMPANY_30/quality/item-inspection/page.tsx
@@ -49,6 +49,9 @@ type InspectionRow = {
apply_process: string;
acceptance_criteria: string;
is_required: boolean;
+ judgment_criteria?: string; // 판단기준 라벨 (수치(범위)/텍스트입력/O·X/선택형)
+ selection_options?: string; // 선택형일 때 옵션 (콤마 구분)
+ unit?: string; // 검사 단위
};
export default function ItemInspectionInfoPage() {
@@ -74,15 +77,25 @@ export default function ItemInspectionInfoPage() {
// FK 옵션
const [itemOptions, setItemOptions] = useState<{ code: string; name: string; item_type: string; unit: string }[]>([]);
- const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; types: string[] }[]>([]);
+ const [inspOptions, setInspOptions] = useState<{ code: string; label: string; detail: string; method: string; judgment_criteria: string; selection_options: string; unit: string; types: string[] }[]>([]);
const [inspTypeCatOptions, setInspTypeCatOptions] = useState<{ code: string; label: string }[]>([]);
const [inspMethodCatOptions, setInspMethodCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [judgmentCatOptions, setJudgmentCatOptions] = useState<{ code: string; label: string }[]>([]);
+ const [inspUnitCatOptions, setInspUnitCatOptions] = useState<{ code: string; label: string }[]>([]);
const [userOptions, setUserOptions] = useState<{ code: string; label: string }[]>([]);
+ // 품목 카테고리 코드→라벨 (type, inventory_unit)
+ const [itemCatMap, setItemCatMap] = useState
>>({});
+ const itemCatMapRef = React.useRef(itemCatMap);
+ itemCatMapRef.current = itemCatMap;
+
// 검사유형별 검사항목 rows (모달용)
const [inspectionRows, setInspectionRows] = useState>({});
const [collapsedTypes, setCollapsedTypes] = useState>({});
+ // 기본 라우팅 공정 목록 (적용공정 Select용)
+ const [processOptions, setProcessOptions] = useState<{ code: string; name: string }[]>([]);
+
// 품목 선택 모달
const [itemModalOpen, setItemModalOpen] = useState(false);
const [itemSearchKeyword, setItemSearchKeyword] = useState("");
@@ -116,9 +129,26 @@ export default function ItemInspectionInfoPage() {
label: r.inspection_criteria || r.inspection_standard || r.id,
detail: r.inspection_item || r.inspection_criteria || "",
method: r.inspection_method || "",
+ judgment_criteria: r.judgment_criteria || "",
+ selection_options: r.selection_options || "",
+ unit: r.unit || "",
types: r.inspection_type ? r.inspection_type.split(",").filter(Boolean) : [],
})));
+ // 품목 카테고리 (type, unit)
+ const catMap: Record> = {};
+ for (const col of ["type", "unit", "inventory_unit"]) {
+ try {
+ const catRes = await apiClient.get(`/table-categories/item_info/${col}/values`);
+ if (catRes.data?.success && catRes.data.data?.length) {
+ catMap[col] = {};
+ const fl = (arr: any[]) => { for (const v of arr) { catMap[col][v.valueCode] = v.valueLabel; if (v.children?.length) fl(v.children); } };
+ fl(catRes.data.data);
+ }
+ } catch { /* skip */ }
+ }
+ setItemCatMap(catMap);
+
// 검사유형 카테고리
try {
const catRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/inspection_type/values`);
@@ -137,6 +167,24 @@ export default function ItemInspectionInfoPage() {
setInspMethodCatOptions(flatMethods);
} catch { /* skip */ }
+ // 판단기준 카테고리
+ try {
+ const jcRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/judgment_criteria/values`);
+ const flatJc: { code: string; label: string }[] = [];
+ const flattenJc = (arr: any[]) => { for (const v of arr) { flatJc.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenJc(v.children); } };
+ if (jcRes.data?.data?.length) flattenJc(jcRes.data.data);
+ setJudgmentCatOptions(flatJc);
+ } catch { /* skip */ }
+
+ // 검사 단위 카테고리
+ try {
+ const unitRes = await apiClient.get(`/table-categories/${INSPECTION_TABLE}/unit/values`);
+ const flatUnit: { code: string; label: string }[] = [];
+ const flattenU = (arr: any[]) => { for (const v of arr) { flatUnit.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) flattenU(v.children); } };
+ if (unitRes.data?.data?.length) flattenU(unitRes.data.data);
+ setInspUnitCatOptions(flatUnit);
+ } catch { /* skip */ }
+
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
setUserOptions(users.map((u: any) => ({
code: u.user_id || u.id,
@@ -147,6 +195,23 @@ export default function ItemInspectionInfoPage() {
loadOptions();
}, []);
+ // 품목별 기본 라우팅 공정 로드
+ const loadProcessOptions = async (itemCode: string) => {
+ try {
+ const res = await apiClient.get(`/work-instruction/__lookup__/routing-versions/${encodeURIComponent(itemCode)}`);
+ if (res.data?.success && res.data.data?.length > 0) {
+ const defaultVer = res.data.data.find((v: any) => v.is_default) || res.data.data[0];
+ const procs = (defaultVer.processes || []).map((p: any) => ({
+ code: p.process_code,
+ name: p.process_name || p.process_code,
+ }));
+ setProcessOptions(procs);
+ } else {
+ setProcessOptions([]);
+ }
+ } catch { setProcessOptions([]); }
+ };
+
/* ═══════════════════ 품목 선택 모달 ═══════════════════ */
const searchItemServer = async (page?: number) => {
const p = page ?? itemPage;
@@ -163,15 +228,21 @@ export default function ItemInspectionInfoPage() {
});
const resData = res.data?.data;
const rows = resData?.data || resData?.rows || [];
- setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: r.type || "", unit: r.inventory_unit || "" })));
+ const cm = itemCatMapRef.current;
+ setFilteredItems(rows.map((r: any) => ({ code: r.item_number, name: r.item_name, item_type: cm["type"]?.[r.type] || r.type || "", unit: cm["inventory_unit"]?.[r.inventory_unit] || r.inventory_unit || "" })));
setItemTotal(resData?.total || resData?.totalCount || rows.length);
} catch { /* skip */ } finally { setItemSearchLoading(false); }
};
const openItemModal = () => { setItemSearchKeyword(""); setItemPage(1); setItemModalOpen(true); searchItemServer(1); };
const handleItemSearch = () => { setItemPage(1); searchItemServer(1); };
const selectItem = (item: typeof itemOptions[0]) => {
+ if (groupedData.some(g => g.item_code === item.code)) {
+ toast.error(`"${item.name}" 은(는) 이미 등록된 품목입니다.`);
+ return;
+ }
setForm(p => ({ ...p, item_code: item.code, item_name: item.name }));
setItemModalOpen(false);
+ loadProcessOptions(item.code);
};
/* ═══════════════════ 데이터 조회 ═══════════════════ */
@@ -242,6 +313,7 @@ export default function ItemInspectionInfoPage() {
if (!code) { toast.error("수정할 항목을 선택해주세요"); return; }
const group = groupedData.find(g => g.item_code === code);
if (!group) return;
+ loadProcessOptions(code);
const row = group.rows[0];
setForm({ ...row });
setEditMode(true);
@@ -269,6 +341,12 @@ export default function ItemInspectionInfoPage() {
if (!rowMap[typeKey]) rowMap[typeKey] = [];
const mCode = r.inspection_method || "";
const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
+ // 판단기준/선택옵션/단위 resolve
+ const inspOpt = inspOptions.find(o => o.code === r.inspection_standard_id);
+ const jcCode = inspOpt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ const unitCode = inspOpt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
rowMap[typeKey].push({
id: r.id,
inspection_standard_id: r.inspection_standard_id || "",
@@ -277,6 +355,9 @@ export default function ItemInspectionInfoPage() {
apply_process: "",
acceptance_criteria: r.pass_criteria || "",
is_required: r.is_required === "true" || r.is_required === true,
+ judgment_criteria: jcLabel,
+ selection_options: inspOpt?.selection_options || "",
+ unit: unitLabel,
});
}
setInspectionRows(rowMap);
@@ -304,7 +385,22 @@ export default function ItemInspectionInfoPage() {
const opt = inspOptions.find(o => o.code === value);
const methodCode = opt?.method || "";
const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
- return { ...r, inspection_standard_id: value, inspection_detail: opt?.detail || "", inspection_method: methodLabel };
+ // 판단기준 라벨 resolve
+ const jcCode = opt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ // 단위 라벨 resolve
+ const unitCode = opt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
+ return {
+ ...r,
+ inspection_standard_id: value,
+ inspection_detail: opt?.detail || "",
+ inspection_method: methodLabel,
+ judgment_criteria: jcLabel,
+ selection_options: opt?.selection_options || "",
+ unit: unitLabel,
+ acceptance_criteria: "", // 판단기준 변경 시 초기화
+ };
}
return { ...r, [field]: value };
}),
@@ -561,9 +657,12 @@ export default function ItemInspectionInfoPage() {
{resolveMethodLabel(row.inspection_method)}
{row.apply_process || "-"}
- {row.judgment_criteria ? (
- {row.judgment_criteria}
- ) : "-"}
+ {(() => {
+ const insp = inspOptions.find(o => o.code === row.inspection_standard_id);
+ const jcCode = insp?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ return jcLabel ? {jcLabel} : "-";
+ })()}
{row.pass_criteria || "-"}
@@ -588,7 +687,7 @@ export default function ItemInspectionInfoPage() {
{/* ═══════════════════ 등록/수정 모달 ═══════════════════ */}