feat: Enhance work item detail management with additional inspection fields

- Updated the processWorkStandardController and workInstructionController to include new fields for process_inspection_apply and equip_inspection_apply in SQL queries and data handling.
- Modified the DetailFormModal and WorkItemDetailList components to support individual registration of inspection items and equipment inspections, improving the flexibility of the inspection process.
- Implemented logic to handle automatic content generation for inspection and equipment inspection types, enhancing user experience and data accuracy.
- These changes aim to improve the management of work item details and streamline the inspection process across multiple company implementations.
This commit is contained in:
kjs
2026-04-14 17:51:47 +09:00
parent 3c526f0b35
commit 7aaf264661
4 changed files with 72 additions and 20 deletions
@@ -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) {
@@ -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]
);
}
}
@@ -270,14 +270,40 @@ export function DetailFormModal({
const submitData = { ...formData };
// content 자동 설정 (UI에서 직접 입력이 없는 유형들)
// 검사항목 적용 → 품목검사정보 각각 개별 등록
if (type === "inspection" && submitData.process_inspection_apply === "apply") {
if (itemInspections.length > 0) {
for (const insp of itemInspections) {
onSubmit({
...submitData,
detail_type: "inspection",
content: `${insp.inspection_item_name || insp.inspection_standard || "-"} | ${insp.pass_criteria || ""}`.trim(),
is_required: submitData.is_required || "Y",
});
}
onClose();
return;
}
submitData.content = submitData.content || "품목별 검사정보 (자동 연동)";
}
if (type === "lookup") {
submitData.content = submitData.content || "품목 등록 문서 (자동 연동)";
}
// 설비점검 적용 → 점검항목 각각 개별 등록
if (type === "equip_inspection" && submitData.equip_inspection_apply === "apply") {
if (equipInspItems.length > 0) {
for (const item of equipInspItems) {
const range = (item.lower_limit || item.upper_limit) ? `${item.lower_limit || ""} ~ ${item.upper_limit || ""}${item.unit ? ` ${item.unit}` : ""}` : "";
onSubmit({
...submitData,
detail_type: "equip_inspection",
content: `${item.inspection_item || "-"}${range ? ` | ${range}` : ""}`.trim(),
is_required: submitData.is_required || "Y",
});
}
onClose();
return;
}
submitData.content = submitData.content || "설비 점검항목 (설비정보 연동)";
}
if (type === "production_result") {
@@ -1,6 +1,6 @@
"use client";
import React, { useState } from "react";
import React, { useState, useRef } from "react";
import { Plus, Pencil, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -34,6 +34,7 @@ export function WorkItemDetailList({
const [modalOpen, setModalOpen] = useState(false);
const [modalMode, setModalMode] = useState<"add" | "edit">("add");
const [editTarget, setEditTarget] = useState<WorkItemDetail | null>(null);
const editFirstRef = useRef(false);
if (!workItem) {
return (
@@ -57,6 +58,7 @@ export function WorkItemDetailList({
const handleOpenEdit = (detail: WorkItemDetail) => {
setModalMode("edit");
setEditTarget(detail);
editFirstRef.current = false;
setModalOpen(true);
};
@@ -64,14 +66,31 @@ export function WorkItemDetailList({
if (modalMode === "add") {
onCreateDetail({ ...data, sort_order: details.length + 1 });
} else if (editTarget) {
onUpdateDetail(editTarget.id, data);
const isApplyGroup = editTarget.detail_type === "inspection" && editTarget.process_inspection_apply === "apply"
|| editTarget.detail_type === "equip_inspection" && editTarget.equip_inspection_apply === "apply"
|| editTarget.detail_type === "material_input";
if (isApplyGroup && data.is_required !== undefined) {
// 같은 유형의 apply 그룹 전체 일괄 업데이트 (필수여부 등)
const siblings = details.filter(d =>
d.detail_type === editTarget.detail_type &&
(d.detail_type === "inspection" ? d.process_inspection_apply === "apply" :
d.detail_type === "equip_inspection" ? d.equip_inspection_apply === "apply" :
d.detail_type === "material_input")
);
for (const sib of siblings) {
onUpdateDetail(sib.id, { ...sib, is_required: data.is_required });
}
} else {
onUpdateDetail(editTarget.id, data);
}
}
};
const getContentSummary = (detail: WorkItemDetail): string => {
const type = detail.detail_type;
if (type === "inspection") {
if (detail.process_inspection_apply === "apply") return "품목별 검사정보 (자동 연동)";
if (detail.process_inspection_apply === "apply") return detail.content || "품목별 검사정보 (자동 연동)";
const parts = [detail.content];
if (detail.inspection_method) parts.push(`[${detail.inspection_method}]`);
if (detail.base_value) {
@@ -93,9 +112,7 @@ export function WorkItemDetailList({
}
if (type === "lookup") return "품목 등록 문서 (자동 연동)";
if (type === "equip_inspection") {
return detail.equip_inspection_apply === "apply"
? "설비 점검항목 (설비정보 연동)"
: detail.content || "설비점검";
return detail.content || (detail.equip_inspection_apply === "apply" ? "설비 점검항목 (설비정보 연동)" : "설비점검");
}
if (type === "equip_condition") {
const parts = [detail.content];