feat: Enhance routing details and subcontractor mapping functionality

- Updated the `getRoutingDetails` function to enrich routing details with subcontractor codes, improving data retrieval for routing information.
- Implemented a mapping mechanism to associate subcontractor codes with routing details, ensuring accurate representation of outsourcing suppliers.
- Enhanced the `saveRoutingDetails` function to handle subcontractor mappings during the save operation, ensuring data integrity and consistency.
- Updated the BOM service to improve unit handling and added inventory unit information for better clarity in item representation.
- Refactored production plan management to streamline modal handling and improve error messaging for better user feedback.
This commit is contained in:
kjs
2026-04-17 18:25:35 +09:00
parent 78e102fd45
commit 48b9ba3d2a
31 changed files with 2234 additions and 546 deletions

View File

@@ -382,7 +382,31 @@ export async function getRoutingDetails(req: AuthenticatedRequest, res: Response
[versionId, companyCode]
);
return res.json({ success: true, data: result.rows });
const rows = result.rows;
const detailIds = rows.map((r: any) => r.id).filter(Boolean);
let mappingByDetail: Record<string, string[]> = {};
if (detailIds.length > 0) {
const mapRes = await pool.query(
`SELECT routing_detail_id, subcontractor_code
FROM item_routing_subcontractor
WHERE routing_detail_id = ANY($1::uuid[])
ORDER BY seq_order`,
[detailIds]
);
for (const m of mapRes.rows) {
const key = String(m.routing_detail_id);
if (!mappingByDetail[key]) mappingByDetail[key] = [];
mappingByDetail[key].push(m.subcontractor_code);
}
}
const enriched = rows.map((r: any) => {
const list = mappingByDetail[String(r.id)] || [];
// 레거시 폴백: 매핑이 비어있고 legacy 단일 컬럼에 값이 있으면 배열로 포장
if (list.length === 0 && r.outsource_supplier) list.push(r.outsource_supplier);
return { ...r, outsource_supplier_list: list };
});
return res.json({ success: true, data: enriched });
} catch (error: any) {
logger.error("라우팅 상세 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });
@@ -400,6 +424,15 @@ export async function saveRoutingDetails(req: AuthenticatedRequest, res: Respons
try {
await client.query("BEGIN");
// 기존 상세의 외주업체 매핑을 먼저 제거
await client.query(
`DELETE FROM item_routing_subcontractor
WHERE routing_detail_id IN (
SELECT id FROM item_routing_detail WHERE routing_version_id=$1 AND company_code=$2
)`,
[versionId, companyCode]
);
// 기존 상세 삭제 후 재입력
await client.query(
`DELETE FROM item_routing_detail WHERE routing_version_id=$1 AND company_code=$2`,
@@ -407,11 +440,26 @@ export async function saveRoutingDetails(req: AuthenticatedRequest, res: Respons
);
for (const d of details) {
await client.query(
const suppliers: string[] = Array.isArray(d.outsource_supplier_list)
? d.outsource_supplier_list.filter((s: any) => typeof s === "string" && s.trim() !== "")
: (d.outsource_supplier ? [d.outsource_supplier] : []);
const primaryLegacy = suppliers[0] || d.outsource_supplier || "";
const insertRes = await client.query(
`INSERT INTO item_routing_detail (id, company_code, routing_version_id, seq_no, process_code, is_required, is_fixed_order, work_type, standard_time, outsource_supplier, writer)
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
[companyCode, versionId, d.seq_no, d.process_code, d.is_required || "Y", d.is_fixed_order || "Y", d.work_type || "내부", d.standard_time || "0", d.outsource_supplier || "", writer]
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id`,
[companyCode, versionId, d.seq_no, d.process_code, d.is_required || "Y", d.is_fixed_order || "Y", d.work_type || "내부", d.standard_time || "0", primaryLegacy, writer]
);
const newDetailId = insertRes.rows[0].id;
for (let i = 0; i < suppliers.length; i++) {
await client.query(
`INSERT INTO item_routing_subcontractor (id, company_code, routing_detail_id, subcontractor_code, seq_order)
VALUES (gen_random_uuid(), $1, $2, $3, $4)`,
[companyCode, newDetailId, suppliers[i], i]
);
}
}
await client.query("COMMIT");

View File

@@ -60,8 +60,9 @@ export async function getBomHeader(bomId: string, tableName?: string) {
const sql = `
SELECT b.*,
i.item_name, i.item_number, i.division as item_type,
COALESCE(b.unit, i.unit) as unit,
COALESCE(NULLIF(b.unit, ''), NULLIF(i.unit, ''), NULLIF(i.inventory_unit, '')) as unit,
i.unit as item_unit,
i.inventory_unit as item_inventory_unit,
i.division, i.size, i.material
FROM ${table} b
LEFT JOIN item_info i ON b.item_id = i.id

View File

@@ -694,13 +694,16 @@ export async function mergeSchedules(
[companyCode, ...scheduleIds]
);
// 병합된 스케줄 생성
// 병합된 스케줄 생성 (PP-YYYYMMDD-NNNN 형식)
const todayStr = new Date().toISOString().split("T")[0].replace(/-/g, "");
const planNoResult = await client.query(
`SELECT COALESCE(MAX(CAST(REPLACE(plan_no, 'PP-', '') AS INTEGER)), 0) + 1 AS next_no
FROM production_plan_mng WHERE company_code = $1`,
[companyCode]
`SELECT COUNT(*) + 1 AS next_no
FROM production_plan_mng
WHERE company_code = $1 AND plan_no LIKE $2`,
[companyCode, `PP-${todayStr}-%`]
);
const planNo = `PP-${String(planNoResult.rows[0].next_no || 1).padStart(6, "0")}`;
const nextNo = parseInt(planNoResult.rows[0].next_no, 10) || 1;
const planNo = `PP-${todayStr}-${String(nextNo).padStart(4, "0")}`;
const insertResult = await client.query(
`INSERT INTO production_plan_mng (
@@ -1017,13 +1020,16 @@ export async function splitSchedule(
[originalQty - splitQty, splitBy, planId, companyCode]
);
// 분할된 새 계획 생성
// 분할된 새 계획 생성 (PP-YYYYMMDD-NNNN 형식)
const todayStr = new Date().toISOString().split("T")[0].replace(/-/g, "");
const planNoResult = await client.query(
`SELECT COALESCE(MAX(CAST(REPLACE(plan_no, 'PP-', '') AS INTEGER)), 0) + 1 AS next_no
FROM production_plan_mng WHERE company_code = $1`,
[companyCode]
`SELECT COUNT(*) + 1 AS next_no
FROM production_plan_mng
WHERE company_code = $1 AND plan_no LIKE $2`,
[companyCode, `PP-${todayStr}-%`]
);
const planNo = `PP-${String(planNoResult.rows[0].next_no || 1).padStart(6, "0")}`;
const nextNo = parseInt(planNoResult.rows[0].next_no, 10) || 1;
const planNo = `PP-${todayStr}-${String(nextNo).padStart(4, "0")}`;
const insertResult = await client.query(
`INSERT INTO production_plan_mng (