Implement process materials auto-fill functionality for outsource purchase orders

- Added a new endpoint to retrieve process materials based on routing details and work order ID.
- Introduced the `getProcessMaterials` function in the `outsourcePurchaseController` to handle the logic for fetching materials.
- Updated the `outsourcePurchaseRoutes` to include the new route for process materials.
- Enhanced the `RegistrationModal` component to toggle material needs and automatically fill materials when required.

(TASK:ERP-019)
This commit is contained in:
kjs
2026-05-06 18:09:23 +09:00
parent bd182386e6
commit 970a8f708a
10 changed files with 467 additions and 243 deletions

View File

@@ -182,6 +182,32 @@ export async function autoProcesses(
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 공정 자재투입(material_input) 자동 채움
// — 사급자재 체크 시 해당 공정의 자재 목록을 외주발주 자재 형식으로 반환
// ─────────────────────────────────────────────────────────────────────────────
export async function getProcessMaterials(
req: AuthenticatedRequest,
res: Response,
) {
try {
const companyCode = req.user!.companyCode;
const workOrderId = req.query.work_order_id as string | undefined;
const routingDetailId = req.query.routing_detail_id as string | undefined;
if (!routingDetailId) {
return fail(res, 400, "routing_detail_id는 필수입니다");
}
const data = await svc.getProcessMaterialInputs(
companyCode,
workOrderId,
routingDetailId,
);
return ok(res, data);
} catch (e: any) {
return fail(res, 500, e?.message || "공정 자재투입 조회 실패", e);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// 외주발주 가능 작업지시 목록
// (TASK:ERP-019 재구현 — 좌측 리스트 필터)

View File

@@ -466,6 +466,7 @@ export async function getWorkItemDetails(req: AuthenticatedRequest, res: Respons
selected_bom_items, process_inspection_apply, equip_inspection_apply,
condition_unit, condition_base_value, condition_tolerance,
condition_auto_collect, condition_plc_data,
bom_item_id, bom_item_name, bom_qty, bom_unit,
created_date
FROM process_work_item_detail
WHERE work_item_id = $1 AND company_code = $2
@@ -499,6 +500,8 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
// 설비조건(equip_condition) 전용 5개 필드 — TASK:ERP-015
condition_unit, condition_base_value, condition_tolerance,
condition_auto_collect, condition_plc_data,
// 자재투입(material_input) 전용 4필드 — 외주발주 사급자재 자동 채움 연동
bom_item_id, bom_item_name, bom_qty, bom_unit,
} = req.body;
if (!work_item_id || !content) {
@@ -524,9 +527,10 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
duration_minutes, input_type, lookup_target, display_fields, selected_bom_items,
process_inspection_apply, equip_inspection_apply,
condition_unit, condition_base_value, condition_tolerance,
condition_auto_collect, condition_plc_data)
condition_auto_collect, condition_plc_data,
bom_item_id, bom_item_name, bom_qty, bom_unit)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20,
$21, $22, $23, $24, $25)
$21, $22, $23, $24, $25, $26, $27, $28, $29)
RETURNING *
`;
@@ -559,6 +563,10 @@ export async function createWorkItemDetail(req: AuthenticatedRequest, res: Respo
condition_tolerance || null,
condition_auto_collect || null,
condition_plc_data || null,
bom_item_id || null,
bom_item_name || null,
bom_qty != null && bom_qty !== "" ? String(bom_qty) : null,
bom_unit || null,
]);
logger.info("작업 항목 상세 생성", { companyCode, id: result.rows[0].id });
@@ -588,6 +596,8 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
// 설비조건(equip_condition) 전용 5개 필드 — TASK:ERP-015
condition_unit, condition_base_value, condition_tolerance,
condition_auto_collect, condition_plc_data,
// 자재투입(material_input) 전용 4필드 — 외주발주 사급자재 자동 채움 연동
bom_item_id, bom_item_name, bom_qty, bom_unit,
} = req.body;
const bomItemsJson = Array.isArray(selected_bom_items) ? JSON.stringify(selected_bom_items) : selected_bom_items ?? null;
@@ -616,6 +626,10 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
condition_tolerance = $22,
condition_auto_collect = $23,
condition_plc_data = $24,
bom_item_id = $25,
bom_item_name = $26,
bom_qty = $27,
bom_unit = $28,
updated_date = NOW()
WHERE id = $6 AND company_code = $7
RETURNING *
@@ -646,6 +660,10 @@ export async function updateWorkItemDetail(req: AuthenticatedRequest, res: Respo
condition_tolerance ?? null,
condition_auto_collect ?? null,
condition_plc_data ?? null,
bom_item_id ?? null,
bom_item_name ?? null,
bom_qty != null && bom_qty !== "" ? String(bom_qty) : null,
bom_unit ?? null,
]);
if (result.rowCount === 0) {
@@ -762,9 +780,10 @@ export async function saveAll(req: AuthenticatedRequest, res: Response) {
inspection_code, inspection_method, unit, lower_limit, upper_limit,
duration_minutes, input_type, lookup_target, display_fields,
condition_unit, condition_base_value, condition_tolerance,
condition_auto_collect, condition_plc_data)
condition_auto_collect, condition_plc_data,
bom_item_id, bom_item_name, bom_qty, bom_unit)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17,
$18, $19, $20, $21, $22)`,
$18, $19, $20, $21, $22, $23, $24, $25, $26)`,
[
companyCode,
workItemId,
@@ -789,6 +808,11 @@ export async function saveAll(req: AuthenticatedRequest, res: Response) {
detail.condition_tolerance || null,
detail.condition_auto_collect || null,
detail.condition_plc_data || null,
// 자재투입(material_input) 전용 4필드 — 외주발주 사급자재 자동 채움 연동
detail.bom_item_id || null,
detail.bom_item_name || null,
detail.bom_qty != null && detail.bom_qty !== "" ? String(detail.bom_qty) : null,
detail.bom_unit || null,
]
);
}

View File

@@ -668,7 +668,8 @@ export async function getWorkStandard(req: AuthenticatedRequest, res: Response)
duration_minutes, input_type, lookup_target, display_fields,
process_inspection_apply, equip_inspection_apply,
condition_unit, condition_base_value, condition_tolerance,
condition_auto_collect, condition_plc_data
condition_auto_collect, condition_plc_data,
bom_item_id, bom_item_name, bom_qty, bom_unit
FROM wi_process_work_item_detail
WHERE wi_work_item_id = $1 AND company_code = $2
ORDER BY sort_order`,
@@ -695,7 +696,8 @@ export async function getWorkStandard(req: AuthenticatedRequest, res: Response)
duration_minutes, input_type, lookup_target, display_fields,
process_inspection_apply, equip_inspection_apply,
condition_unit, condition_base_value, condition_tolerance,
condition_auto_collect, condition_plc_data
condition_auto_collect, condition_plc_data,
bom_item_id, bom_item_name, bom_qty, bom_unit
FROM process_work_item_detail
WHERE work_item_id = $1 AND company_code = $2
ORDER BY sort_order`,
@@ -751,7 +753,7 @@ async function syncMasterChecklistFromWi(
// 3. 접수 건수 확인
const acceptCount = await client.query(
`SELECT COUNT(*)::int AS cnt FROM work_order_process_result wopr
JOIN work_order_process wop ON wop.id = wopr.work_order_process_id
JOIN work_order_process wop ON wop.id = wopr.wop_id
WHERE wop.wo_id = $1 AND wop.company_code = $2 AND wopr.company_code = $2`,
[wiId, companyCode],
);
@@ -850,9 +852,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 (id, 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, condition_unit, condition_base_value, condition_tolerance, condition_auto_collect, condition_plc_data, writer)
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)`,
[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, origDetail.condition_unit || null, origDetail.condition_base_value || null, origDetail.condition_tolerance || null, origDetail.condition_auto_collect || null, origDetail.condition_plc_data || null, userId]
`INSERT INTO wi_process_work_item_detail (id, 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, condition_unit, condition_base_value, condition_tolerance, condition_auto_collect, condition_plc_data, bom_item_id, bom_item_name, bom_qty, bom_unit, writer)
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)`,
[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, origDetail.condition_unit || null, origDetail.condition_base_value || null, origDetail.condition_tolerance || null, origDetail.condition_auto_collect || null, origDetail.condition_plc_data || null, origDetail.bom_item_id || null, origDetail.bom_item_name || null, origDetail.bom_qty || null, origDetail.bom_unit || null, userId]
);
}
}
@@ -919,9 +921,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 (id, 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, condition_unit, condition_base_value, condition_tolerance, condition_auto_collect, condition_plc_data, writer)
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)`,
[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, d.condition_unit || null, d.condition_base_value || null, d.condition_tolerance || null, d.condition_auto_collect || null, d.condition_plc_data || null, userId]
`INSERT INTO wi_process_work_item_detail (id, 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, condition_unit, condition_base_value, condition_tolerance, condition_auto_collect, condition_plc_data, bom_item_id, bom_item_name, bom_qty, bom_unit, writer)
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)`,
[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, d.condition_unit || null, d.condition_base_value || null, d.condition_tolerance || null, d.condition_auto_collect || null, d.condition_plc_data || null, d.bom_item_id || null, d.bom_item_name || null, d.bom_qty != null && d.bom_qty !== "" ? String(d.bom_qty) : null, d.bom_unit || null, userId]
);
}
}
@@ -939,7 +941,13 @@ export async function saveWorkStandard(req: AuthenticatedRequest, res: Response)
client.release();
}
} catch (error: any) {
logger.error("작업지시 공정작업기준 저장 실패", { error: error.message });
logger.error("작업지시 공정작업기준 저장 실패", {
message: error?.message,
code: error?.code,
detail: error?.detail,
where: error?.where,
stack: error?.stack,
});
return res.status(500).json({ success: false, message: error.message });
}
}