From 199fa60ef5a2b9e8b0f4ae0021f2ca7c9b7d054a Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 20 Mar 2026 13:46:30 +0900 Subject: [PATCH] feat: add BOM materials retrieval functionality - Implemented a new API endpoint for retrieving BOM materials based on item codes, enhancing the ability to manage and view component materials. - Added necessary SQL queries to fetch BOM details, ensuring that the data is filtered by company code for multi-tenancy support. - Updated frontend components to integrate BOM materials fetching, allowing for better visibility and management of materials in the process workflow. These changes aim to streamline the management of BOM materials, facilitating better tracking and organization within the application. --- .../src/controllers/processInfoController.ts | 41 + backend-node/src/routes/processInfoRoutes.ts | 3 + frontend/lib/api/processInfo.ts | 23 + .../ProcessWorkStandardComponent.tsx | 1 + .../components/DetailFormModal.tsx | 774 ++++++++++++++---- .../components/WorkItemDetailList.tsx | 34 +- .../components/WorkPhaseSection.tsx | 3 + .../v2-process-work-standard/config.ts | 9 +- .../v2-process-work-standard/types.ts | 42 +- 9 files changed, 737 insertions(+), 193 deletions(-) diff --git a/backend-node/src/controllers/processInfoController.ts b/backend-node/src/controllers/processInfoController.ts index 5869b112..a8d99fb1 100644 --- a/backend-node/src/controllers/processInfoController.ts +++ b/backend-node/src/controllers/processInfoController.ts @@ -420,3 +420,44 @@ export async function saveRoutingDetails(req: AuthenticatedRequest, res: Respons return res.status(500).json({ success: false, message: error.message }); } } + +// ═══════════════════════════════════════════ +// BOM 구성 자재 조회 (품목코드 기반) +// ═══════════════════════════════════════════ + +export async function getBomMaterials(req: AuthenticatedRequest, res: Response) { + try { + const companyCode = req.user!.companyCode; + const { itemCode } = req.params; + + if (!itemCode) { + return res.status(400).json({ success: false, message: "itemCode는 필수입니다" }); + } + + const query = ` + SELECT + bd.id, + bd.child_item_id, + bd.quantity, + bd.unit as detail_unit, + bd.process_type, + i.item_name as child_item_name, + i.item_number as child_item_code, + i.type as child_item_type, + i.unit as item_unit + FROM bom b + JOIN bom_detail bd ON b.id = bd.bom_id AND b.company_code = bd.company_code + LEFT JOIN item_info i ON bd.child_item_id = i.id AND bd.company_code = i.company_code + WHERE b.item_code = $1 AND b.company_code = $2 + ORDER BY bd.seq_no ASC, bd.created_date ASC + `; + + const result = await pool.query(query, [itemCode, companyCode]); + + logger.info("BOM 자재 조회 성공", { companyCode, itemCode, count: result.rowCount }); + return res.json({ success: true, data: result.rows }); + } catch (error: any) { + logger.error("BOM 자재 조회 실패", { error: error.message }); + return res.status(500).json({ success: false, message: error.message }); + } +} diff --git a/backend-node/src/routes/processInfoRoutes.ts b/backend-node/src/routes/processInfoRoutes.ts index 30fb9479..3507707e 100644 --- a/backend-node/src/routes/processInfoRoutes.ts +++ b/backend-node/src/routes/processInfoRoutes.ts @@ -39,4 +39,7 @@ router.delete("/routing-versions/:id", ctrl.deleteRoutingVersion); router.get("/routing-details/:versionId", ctrl.getRoutingDetails); router.put("/routing-details/:versionId", ctrl.saveRoutingDetails); +// BOM 구성 자재 조회 +router.get("/bom-materials/:itemCode", ctrl.getBomMaterials); + export default router; diff --git a/frontend/lib/api/processInfo.ts b/frontend/lib/api/processInfo.ts index 4cc565e2..b52272a5 100644 --- a/frontend/lib/api/processInfo.ts +++ b/frontend/lib/api/processInfo.ts @@ -274,3 +274,26 @@ export async function saveRoutingDetails( return { success: false, message: e.message }; } } + +// ═══ BOM 구성 자재 조회 ═══ + +export interface BomMaterial { + id: string; + child_item_id: string; + quantity: string; + detail_unit: string | null; + process_type: string | null; + child_item_name: string | null; + child_item_code: string | null; + child_item_type: string | null; + item_unit: string | null; +} + +export async function getBomMaterials(itemCode: string): Promise> { + try { + const res = await apiClient.get(`${BASE}/bom-materials/${encodeURIComponent(itemCode)}`); + return res.data; + } catch (e: any) { + return { success: false, message: e.message }; + } +} diff --git a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx index ad169acd..f5c7c794 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/ProcessWorkStandardComponent.tsx @@ -208,6 +208,7 @@ export function ProcessWorkStandardComponent({ selectedWorkItemDetails={selectedDetailsByPhase[phase.key] || []} detailTypes={config.detailTypes} readonly={config.readonly} + selectedItemCode={selection.itemCode || undefined} onSelectWorkItem={handleSelectWorkItem} onAddWorkItem={handleAddWorkItem} onEditWorkItem={handleEditWorkItem} diff --git a/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx b/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx index ec22d200..eaeb7262 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/components/DetailFormModal.tsx @@ -1,10 +1,11 @@ "use client"; -import React, { useState, useEffect } from "react"; -import { Search } from "lucide-react"; +import React, { useState, useEffect, useCallback } from "react"; +import { Search, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, @@ -22,6 +23,7 @@ import { } from "@/components/ui/dialog"; import { WorkItemDetail, DetailTypeDefinition, InspectionStandard } from "../types"; import { InspectionStandardLookup } from "./InspectionStandardLookup"; +import { getBomMaterials, BomMaterial } from "@/lib/api/processInfo"; interface DetailFormModalProps { open: boolean; @@ -30,24 +32,53 @@ interface DetailFormModalProps { detailTypes: DetailTypeDefinition[]; editData?: WorkItemDetail | null; mode: "add" | "edit"; + selectedItemCode?: string; } -const LOOKUP_TARGETS = [ - { value: "equipment", label: "설비정보" }, - { value: "material", label: "자재정보" }, - { value: "worker", label: "작업자정보" }, - { value: "tool", label: "공구정보" }, - { value: "document", label: "문서정보" }, -]; - const INPUT_TYPES = [ { value: "text", label: "텍스트" }, { value: "number", label: "숫자" }, { value: "date", label: "날짜" }, - { value: "textarea", label: "장문텍스트" }, - { value: "select", label: "선택형" }, + { value: "textarea", label: "장문 텍스트" }, ]; +const UNIT_OPTIONS = [ + "mm", "cm", "m", "μm", "℃", "℉", "bar", "Pa", "MPa", "psi", + "RPM", "kg", "N", "N·m", "m/s", "m/min", "A", "V", "kW", "%", + "L/min", "Hz", "dB", "ea", "g", "mg", "ml", "L", +]; + +const PLC_DATA_OPTIONS = [ + { value: "PLC_TEMP_01", label: "온도 (PLC_TEMP_01)" }, + { value: "PLC_PRES_01", label: "압력 (PLC_PRES_01)" }, + { value: "PLC_RPM_01", label: "회전수 (PLC_RPM_01)" }, + { value: "PLC_TORQ_01", label: "토크 (PLC_TORQ_01)" }, + { value: "PLC_SPD_01", label: "속도 (PLC_SPD_01)" }, + { value: "PLC_CUR_01", label: "전류 (PLC_CUR_01)" }, + { value: "PLC_VOLT_01", label: "전압 (PLC_VOLT_01)" }, + { value: "PLC_VIB_01", label: "진동 (PLC_VIB_01)" }, + { value: "PLC_HUM_01", label: "습도 (PLC_HUM_01)" }, + { value: "PLC_FLOW_01", label: "유량 (PLC_FLOW_01)" }, +]; + +const PLC_PRODUCTION_OPTIONS = { + work_qty: [ + { value: "PLC_CNT_01", label: "생산카운터 (PLC_CNT_01)" }, + { value: "PLC_CNT_02", label: "완료카운터 (PLC_CNT_02)" }, + { value: "PLC_QTY_01", label: "작업수량 (PLC_QTY_01)" }, + ], + defect_qty: [ + { value: "PLC_NG_01", label: "불량카운터 (PLC_NG_01)" }, + { value: "PLC_NG_02", label: "NG감지기 (PLC_NG_02)" }, + { value: "PLC_REJ_01", label: "리젝트수 (PLC_REJ_01)" }, + ], + good_qty: [ + { value: "PLC_OK_01", label: "양품카운터 (PLC_OK_01)" }, + { value: "PLC_OK_02", label: "합격카운터 (PLC_OK_02)" }, + { value: "PLC_GOOD_01", label: "양품수량 (PLC_GOOD_01)" }, + ], +}; + export function DetailFormModal({ open, onClose, @@ -55,10 +86,35 @@ export function DetailFormModal({ detailTypes, editData, mode, + selectedItemCode, }: DetailFormModalProps) { const [formData, setFormData] = useState>({}); const [inspectionLookupOpen, setInspectionLookupOpen] = useState(false); const [selectedInspection, setSelectedInspection] = useState(null); + const [bomMaterials, setBomMaterials] = useState([]); + const [bomLoading, setBomLoading] = useState(false); + const [bomChecked, setBomChecked] = useState>(new Set()); + + const loadBomMaterials = useCallback(async () => { + if (!selectedItemCode) { + setBomMaterials([]); + return; + } + setBomLoading(true); + try { + const res = await getBomMaterials(selectedItemCode); + if (res.success && res.data) { + setBomMaterials(res.data); + setBomChecked(new Set(res.data.map((m) => m.child_item_id))); + } else { + setBomMaterials([]); + } + } catch { + setBomMaterials([]); + } finally { + setBomLoading(false); + } + }, [selectedItemCode]); useEffect(() => { if (open) { @@ -86,6 +142,12 @@ export function DetailFormModal({ } }, [open, mode, editData, detailTypes]); + useEffect(() => { + if (open && formData.detail_type === "material_input") { + loadBomMaterials(); + } + }, [open, formData.detail_type, loadBomMaterials]); + const updateField = (field: string, value: any) => { setFormData((prev) => ({ ...prev, [field]: value })); }; @@ -108,17 +170,33 @@ export function DetailFormModal({ const type = formData.detail_type; - if (type === "check" && !formData.content?.trim()) return; - if (type === "inspect" && !formData.content?.trim()) return; + if (type === "checklist" && !formData.content?.trim()) return; + if (type === "inspection") { + if (!formData.process_inspection_apply) return; + if (formData.process_inspection_apply === "none" && !formData.content?.trim()) return; + } if (type === "procedure" && !formData.content?.trim()) return; if (type === "input" && !formData.content?.trim()) return; - if (type === "info" && !formData.lookup_target) return; + if (type === "equip_inspection" && !formData.equip_inspection_apply) return; + if (type === "equip_condition" && !formData.content?.trim()) return; const submitData = { ...formData }; - if (type === "info" && !submitData.content?.trim()) { - const targetLabel = LOOKUP_TARGETS.find(t => t.value === submitData.lookup_target)?.label || submitData.lookup_target; - submitData.content = `${targetLabel} 조회`; + // content 자동 설정 (UI에서 직접 입력이 없는 유형들) + if (type === "inspection" && submitData.process_inspection_apply === "apply") { + submitData.content = submitData.content || "품목별 검사정보 (자동 연동)"; + } + if (type === "lookup") { + submitData.content = submitData.content || "품목 등록 문서 (자동 연동)"; + } + if (type === "equip_inspection" && submitData.equip_inspection_apply === "apply") { + submitData.content = submitData.content || "설비 점검항목 (설비정보 연동)"; + } + if (type === "production_result") { + submitData.content = submitData.content || "작업수량 / 불량수량 / 양품수량"; + } + if (type === "material_input") { + submitData.content = submitData.content || "BOM 구성 자재 (자동 연동)"; } onSubmit(submitData); @@ -130,7 +208,7 @@ export function DetailFormModal({ return ( <> !v && onClose()}> - + 상세 항목 {mode === "add" ? "추가" : "수정"} @@ -149,12 +227,11 @@ export function DetailFormModal({ - {/* 체크리스트 */} - {currentType === "check" && ( - <> -
- - updateField("content", e.target.value)} - placeholder="예: 전원 상태 확인" - className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" - /> -
- + {/* ============ 체크리스트 ============ */} + {currentType === "checklist" && ( +
+ + updateField("content", e.target.value)} + placeholder="예: 전원 상태 확인" + className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" + /> +
)} - {/* 검사항목 */} - {currentType === "inspect" && ( + {/* ============ 검사항목 ============ */} + {currentType === "inspection" && ( <> + {/* 공정검사 적용 여부 */}
-
- - +
+ +
- {selectedInspection && ( -
-

- 선택된 검사기준 정보 + {/* 적용 시: 품목별 검사정보 자동 연동 안내 */} + {formData.process_inspection_apply === "apply" && ( +

+

+ 품목별 검사정보 (자동 연동) +

+

+ 품목에 등록된 검사기준이 자동으로 적용됩니다.

-
-

- 검사코드: {selectedInspection.inspection_code} -

-

- 검사항목: {selectedInspection.inspection_item} -

-

- 검사방법: {selectedInspection.inspection_method || "-"} -

-

- 단위: {selectedInspection.unit || "-"} -

-

- 하한값: {selectedInspection.lower_limit || "-"} -

-

- 상한값: {selectedInspection.upper_limit || "-"} -

-
)} -
- - updateField("content", e.target.value)} - placeholder="예: 외경 치수" - className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" - /> -
+ {/* 미적용 시: 수동 입력 */} + {formData.process_inspection_apply === "none" && ( + <> +
+ + updateField("content", e.target.value)} + placeholder="예: 외경 치수" + className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" + /> +
+
+
+ + updateField("inspection_method", e.target.value)} + placeholder="예: 마이크로미터" + className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" + /> +
+
+ + +
+
-
-
- - updateField("inspection_method", e.target.value)} - placeholder="예: 마이크로미터" - className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" - /> -
-
- - updateField("unit", e.target.value)} - placeholder="예: mm" - className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" - /> -
-
+ {/* 기준값 ± 오차범위 */} +
+
+
+ updateField("base_value", e.target.value)} + placeholder="기준값" + className="h-8 text-xs sm:h-10 sm:text-sm" + /> +
+ ± +
+ updateField("tolerance", e.target.value)} + placeholder="오차범위" + className="h-8 text-xs sm:h-10 sm:text-sm" + /> +
+
-
-
- - updateField("lower_limit", e.target.value)} - placeholder="예: 7.95" - className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" - /> -
-
- - updateField("upper_limit", e.target.value)} - placeholder="예: 8.05" - className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" - /> -
-
+ {/* 자동수집 */} +
+ + +
+
+ + )} )} - {/* 작업절차 */} + {/* ============ 작업절차 ============ */} {currentType === "procedure" && ( <>
@@ -322,10 +427,7 @@ export function DetailFormModal({ type="number" value={formData.duration_minutes ?? ""} onChange={(e) => - updateField( - "duration_minutes", - e.target.value ? Number(e.target.value) : undefined - ) + updateField("duration_minutes", e.target.value ? Number(e.target.value) : undefined) } placeholder="예: 5" className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" @@ -334,7 +436,7 @@ export function DetailFormModal({ )} - {/* 직접입력 */} + {/* ============ 직접입력 ============ */} {currentType === "input" && ( <>
@@ -359,9 +461,7 @@ export function DetailFormModal({ {INPUT_TYPES.map((t) => ( - - {t.label} - + {t.label} ))} @@ -369,41 +469,367 @@ export function DetailFormModal({ )} - {/* 정보조회 */} - {currentType === "info" && ( + {/* ============ 문서참조 ============ */} + {currentType === "lookup" && ( <> -
- - +
+ 📄 + + 해당 품목에 등록된 문서를 자동으로 불러옵니다. +
- - updateField("display_fields", e.target.value)} - placeholder="예: 설비명, 설비코드" - className="mt-1 h-8 text-xs sm:h-10 sm:text-sm" - /> + +
+

+ 품목이 선택되지 않았습니다. +

+
)} + {/* ============ 설비점검 ============ */} + {currentType === "equip_inspection" && ( + <> +
+ 🏭 + + 공정에 지정된 설비를 자동 참조합니다. + +
+ +
+ +
+ + +
+
+ + {/* 적용 시: 설비 점검항목 자동 연동 */} + {formData.equip_inspection_apply === "apply" && ( +
+

+ 설비 점검항목 (설비정보 연동) +

+

+ 공정에 지정된 설비의 점검항목이 자동으로 적용됩니다. +

+
+ )} + + )} + + {/* ============ 설비조건 ============ */} + {currentType === "equip_condition" && ( + <> +
+ 🏭 + + 공정에 지정된 설비를 자동 참조합니다. + +
+ +
+ +
+ {/* 조건명 + 단위 */} +
+
+ updateField("content", e.target.value)} + placeholder="조건명 (예: 온도, 압력, RPM)" + className="h-8 text-xs sm:h-10 sm:text-sm" + /> +
+
+ +
+
+ + {/* 기준값 ± 오차범위 */} +
+
+ updateField("condition_base_value", e.target.value)} + placeholder="기준값" + className="h-8 text-xs sm:h-10 sm:text-sm" + /> +
+ ± +
+ updateField("condition_tolerance", e.target.value)} + placeholder="오차범위" + className="h-8 text-xs sm:h-10 sm:text-sm" + /> +
+
+ + {/* 자동수집 */} +
+ + +
+
+
+ + )} + + {/* ============ 실적등록 ============ */} + {currentType === "production_result" && ( +
+ +
+ {/* 작업수량 */} +
+
+ 📦 작업수량 + 생산된 전체 수량 +
+
+ + +
+
+ + {/* 불량수량 */} +
+
+ 🚫 불량수량 + 불량으로 판정된 수량 +
+
+ + +
+
+ + {/* 양품수량 */} +
+
+ ✅ 양품수량 + 작업수량 - 불량수량 +
+
+ + +
+
+
+
+ )} + + {/* ============ 자재투입 ============ */} + {currentType === "material_input" && ( +
+ +
+
+

+ 📦 BOM 구성 자재 (자동 연동) +

+ + {bomMaterials.length > 0 ? `${bomMaterials.length}건` : ""} + +
+
+ {bomLoading ? ( +
+ + BOM 데이터를 불러오는 중... +
+ ) : !selectedItemCode ? ( +
+ 품목을 먼저 선택하세요. +
+ ) : bomMaterials.length === 0 ? ( +
+ 해당 품목의 BOM 구성 자재가 없습니다. +
+ ) : ( + bomMaterials.map((mat) => { + const typeColor = + mat.child_item_type === "원자재" ? "#16a34a" + : mat.child_item_type === "반제품" ? "#2563eb" + : "#6b7280"; + return ( +
+ { + setBomChecked((prev) => { + const next = new Set(prev); + if (checked) next.add(mat.child_item_id); + else next.delete(mat.child_item_id); + return next; + }); + }} + /> +
+
+ {mat.child_item_type && ( + + {mat.child_item_type} + + )} + + {mat.child_item_code || "-"} + + + {mat.child_item_name || "(이름 없음)"} + +
+
+ 소요량: {mat.quantity || "0"} {mat.detail_unit || mat.item_unit || ""} + {mat.process_type ? ` | 공정: ${mat.process_type}` : ""} +
+
+
+ ); + }) + )} +
+
+
+ )} + {/* 필수 여부 (모든 유형 공통) */} {currentType && (
diff --git a/frontend/lib/registry/components/v2-process-work-standard/components/WorkItemDetailList.tsx b/frontend/lib/registry/components/v2-process-work-standard/components/WorkItemDetailList.tsx index 89d1b464..7b2979e1 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/components/WorkItemDetailList.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/components/WorkItemDetailList.tsx @@ -13,6 +13,7 @@ interface WorkItemDetailListProps { details: WorkItemDetail[]; detailTypes: DetailTypeDefinition[]; readonly?: boolean; + selectedItemCode?: string; onCreateDetail: (data: Partial) => void; onUpdateDetail: (id: string, data: Partial) => void; onDeleteDetail: (id: string) => void; @@ -23,6 +24,7 @@ export function WorkItemDetailList({ details, detailTypes, readonly, + selectedItemCode, onCreateDetail, onUpdateDetail, onDeleteDetail, @@ -66,11 +68,12 @@ export function WorkItemDetailList({ const getContentSummary = (detail: WorkItemDetail): string => { const type = detail.detail_type; - if (type === "inspect" && detail.inspection_code) { + if (type === "inspection") { + if (detail.process_inspection_apply === "apply") return "품목별 검사정보 (자동 연동)"; const parts = [detail.content]; if (detail.inspection_method) parts.push(`[${detail.inspection_method}]`); - if (detail.lower_limit || detail.upper_limit) { - parts.push(`(${detail.lower_limit || "-"} ~ ${detail.upper_limit || "-"} ${detail.unit || ""})`); + if (detail.base_value) { + parts.push(`(기준: ${detail.base_value}${detail.tolerance ? ` ±${detail.tolerance}` : ""} ${detail.unit || ""})`); } return parts.join(" "); } @@ -83,20 +86,24 @@ export function WorkItemDetailList({ number: "숫자", date: "날짜", textarea: "장문", - select: "선택형", }; return `${detail.content} [${typeMap[detail.input_type] || detail.input_type}]`; } - if (type === "info" && detail.lookup_target) { - const targetMap: Record = { - equipment: "설비정보", - material: "자재정보", - worker: "작업자정보", - tool: "공구정보", - document: "문서정보", - }; - return `${targetMap[detail.lookup_target] || detail.lookup_target} 조회`; + if (type === "lookup") return "품목 등록 문서 (자동 연동)"; + if (type === "equip_inspection") { + return detail.equip_inspection_apply === "apply" + ? "설비 점검항목 (설비정보 연동)" + : detail.content || "설비점검"; } + if (type === "equip_condition") { + const parts = [detail.content]; + if (detail.condition_base_value) { + parts.push(`(기준: ${detail.condition_base_value}${detail.condition_tolerance ? ` ±${detail.condition_tolerance}` : ""} ${detail.condition_unit || ""})`); + } + return parts.join(" "); + } + if (type === "production_result") return "작업수량 / 불량수량 / 양품수량"; + if (type === "material_input") return "BOM 구성 자재 (자동 연동)"; return detail.content || "-"; }; @@ -214,6 +221,7 @@ export function WorkItemDetailList({ detailTypes={detailTypes} editData={editTarget} mode={modalMode} + selectedItemCode={selectedItemCode} />
); diff --git a/frontend/lib/registry/components/v2-process-work-standard/components/WorkPhaseSection.tsx b/frontend/lib/registry/components/v2-process-work-standard/components/WorkPhaseSection.tsx index 1f2e7e4a..dcfc2a96 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/components/WorkPhaseSection.tsx +++ b/frontend/lib/registry/components/v2-process-work-standard/components/WorkPhaseSection.tsx @@ -20,6 +20,7 @@ interface WorkPhaseSectionProps { selectedWorkItemDetails: WorkItemDetail[]; detailTypes: DetailTypeDefinition[]; readonly?: boolean; + selectedItemCode?: string; onSelectWorkItem: (workItemId: string, phaseKey: string) => void; onAddWorkItem: (phase: string) => void; onEditWorkItem: (item: WorkItem) => void; @@ -36,6 +37,7 @@ export function WorkPhaseSection({ selectedWorkItemDetails, detailTypes, readonly, + selectedItemCode, onSelectWorkItem, onAddWorkItem, onEditWorkItem, @@ -107,6 +109,7 @@ export function WorkPhaseSection({ details={selectedWorkItemDetails} detailTypes={detailTypes} readonly={readonly} + selectedItemCode={selectedItemCode} onCreateDetail={(data) => selectedWorkItemId && onCreateDetail(selectedWorkItemId, data, phase.key) } diff --git a/frontend/lib/registry/components/v2-process-work-standard/config.ts b/frontend/lib/registry/components/v2-process-work-standard/config.ts index 22b36ea3..4a13fa56 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/config.ts +++ b/frontend/lib/registry/components/v2-process-work-standard/config.ts @@ -23,10 +23,15 @@ export const defaultConfig: ProcessWorkStandardConfig = { { key: "POST", label: "작업 후 (Post-Work)", sortOrder: 3 }, ], detailTypes: [ - { value: "check", label: "체크리스트" }, - { value: "inspect", label: "검사항목" }, + { value: "checklist", label: "체크리스트" }, + { value: "inspection", label: "검사항목" }, { value: "procedure", label: "작업절차" }, { value: "input", label: "직접입력" }, + { value: "lookup", label: "문서참조" }, + { value: "equip_inspection", label: "설비점검" }, + { value: "equip_condition", label: "설비조건" }, + { value: "production_result", label: "실적등록" }, + { value: "material_input", label: "자재투입" }, ], splitRatio: 30, leftPanelTitle: "품목 및 공정 선택", diff --git a/frontend/lib/registry/components/v2-process-work-standard/types.ts b/frontend/lib/registry/components/v2-process-work-standard/types.ts index 7185429b..457fb999 100644 --- a/frontend/lib/registry/components/v2-process-work-standard/types.ts +++ b/frontend/lib/registry/components/v2-process-work-standard/types.ts @@ -91,19 +91,53 @@ export interface WorkItemDetail { sort_order: number; remark?: string; created_date?: string; - // 검사항목 전용 + + // 검사항목(inspection) 전용 + process_inspection_apply?: string; // "apply" | "none" inspection_code?: string; inspection_method?: string; unit?: string; + base_value?: string; + tolerance?: string; lower_limit?: string; upper_limit?: string; - // 작업절차 전용 + auto_collect?: string; // "Y" | "N" + plc_data?: string; + + // 작업절차(procedure) 전용 duration_minutes?: number; - // 직접입력 전용 + + // 직접입력(input) 전용 input_type?: string; - // 정보조회 전용 + + // 문서참조(lookup) 전용 lookup_target?: string; display_fields?: string; + + // 설비점검(equip_inspection) 전용 + equip_inspection_apply?: string; // "apply" | "none" + + // 설비조건(equip_condition) 전용 + condition_name?: string; + condition_unit?: string; + condition_base_value?: string; + condition_tolerance?: string; + condition_auto_collect?: string; + condition_plc_data?: string; + + // 실적등록(production_result) 전용 + work_qty_auto_collect?: string; + work_qty_plc_data?: string; + defect_qty_auto_collect?: string; + defect_qty_plc_data?: string; + good_qty_auto_collect?: string; + good_qty_plc_data?: string; + + // 자재투입(material_input) 전용 - BOM 자동연동 + material_code?: string; + material_name?: string; + quantity?: string; + material_unit?: string; } export interface InspectionStandard {