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:
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user