Implement Order Status Integration for Outsourcing Purchase Management

- Added a new endpoint `listOrderStatus` in the `outsourcePurchaseController` to retrieve integrated order status information, including filtering options for source type, order status, and date range.
- Updated the `outsourcePurchaseService` to handle the new order status retrieval logic, ensuring proper filtering and data aggregation.
- Introduced a new route for accessing the order status information in `outsourcePurchaseRoutes`.
- Created a detailed modal for viewing outsourcing purchase order details, enhancing the user interface for better data presentation.
- Developed a registration modal for creating and editing outsourcing purchase orders, featuring a tabbed interface for improved user experience.

(TASK: ERP-025, ERP-019)
This commit is contained in:
kjs
2026-05-07 18:33:38 +09:00
parent ec1c95b8c5
commit 2e686f059d
28 changed files with 13714 additions and 168 deletions

View File

@@ -1188,3 +1188,181 @@ async function mapToOpoMaterials(
};
});
}
// ─────────────────────────────────────────────────────────────────────────────
// 외주발주현황 통합 조회 (TASK:ERP-025)
// 발주 × 공정 × 자재 × 사급출고 × 입고 단위로 펼친 행 반환
// ─────────────────────────────────────────────────────────────────────────────
export interface OrderStatusFilter {
keyword?: string;
source_type?: string;
order_status?: string;
release_status?: string; // '출고완료' | '입고완료'
date_from?: string;
date_to?: string;
}
export async function listOrderStatus(
companyCode: string,
opts: OrderStatusFilter,
) {
const pool = getPool();
const params: any[] = [companyCode];
let where = `o.company_code = $1`;
if (opts.source_type) {
params.push(opts.source_type);
where += ` AND o.source_type = $${params.length}`;
}
if (opts.order_status) {
params.push(opts.order_status);
where += ` AND o.status = $${params.length}`;
}
if (opts.date_from) {
params.push(opts.date_from);
where += ` AND o.order_date >= $${params.length}`;
}
if (opts.date_to) {
params.push(opts.date_to);
where += ` AND o.order_date <= $${params.length}`;
}
// 출고: outsource_purchase_order_material.outbound_id → outbound_mng.id 정참조
// 입고: inbound_mng.source_table='outbound_mng' AND source_id=outbound_mng.id 역참조 합산
const sql = `
SELECT
o.id AS order_id,
o.order_no,
COALESCE(o.source_type, '') AS source_type,
COALESCE(o.source_no, '') AS source_no,
COALESCE(o.item_code, '') AS item_code,
COALESCE(o.item_name, '') AS item_name,
COALESCE(o.spec, '') AS spec,
COALESCE(o.material, '') AS material,
COALESCE(NULLIF(o.quantity::text, '')::numeric, 0) AS quantity,
o.order_date,
o.due_date,
COALESCE(o.status, '') AS order_status,
COALESCE(o.manager, '') AS manager,
p.id AS process_id,
p.seq,
COALESCE(p.process_name, '') AS process_name,
COALESCE(p.vendor_code, '') AS vendor_code,
COALESCE(p.vendor_name, '') AS vendor_name,
COALESCE(p.material_needed, false) AS material_needed,
m.id AS material_id,
COALESCE(m.item_code, '') AS material_item_code,
COALESCE(m.item_name, '') AS material_item_name,
COALESCE(NULLIF(m.qty::text, '')::numeric, 0) AS material_qty,
COALESCE(m.unit, '') AS material_unit,
COALESCE(m.release_status, '') AS material_release_status,
om.id AS outbound_id,
COALESCE(om.outbound_number, '') AS outbound_number,
COALESCE(om.outbound_status, '') AS outbound_status,
COALESCE(NULLIF(om.outbound_qty, '')::numeric, 0) AS released_qty,
COALESCE(om.outbound_date, '') AS request_date,
COALESCE(in_sum.received_qty, 0)::numeric AS received_qty
FROM outsource_purchase_order o
LEFT JOIN outsource_purchase_order_process p
ON p.opo_id = o.id
LEFT JOIN outsource_purchase_order_material m
ON m.opo_process_id = p.id
LEFT JOIN outbound_mng om
ON om.id = m.outbound_id
AND om.company_code = o.company_code
LEFT JOIN (
SELECT company_code, source_id, SUM(inbound_qty)::numeric AS received_qty
FROM inbound_mng
WHERE source_table = 'outbound_mng'
GROUP BY company_code, source_id
) in_sum
ON in_sum.source_id = om.id
AND in_sum.company_code = o.company_code
WHERE ${where}
ORDER BY o.order_date DESC NULLS LAST, o.order_no DESC, p.seq NULLS LAST
`;
const r = await pool.query(sql, params);
// 행 단위 파생 필드 + 키워드/출고상태 필터
let rows = r.rows.map((row: any) => {
const releasedQty = Number(row.released_qty || 0);
const receivedQty = Number(row.received_qty || 0);
const remainQty = Math.max(0, releasedQty - receivedQty);
const ratePct =
releasedQty > 0 ? Math.round((receivedQty / releasedQty) * 100) : 0;
// 화면 표기용 통합 출고상태
let release_status = "";
if (releasedQty > 0) {
release_status = receivedQty >= releasedQty ? "입고완료" : "출고완료";
}
return {
order_id: row.order_id,
order_no: row.order_no,
source_type: row.source_type,
source_no: row.source_no,
item_code: row.item_code,
item_name: row.item_name,
spec: row.spec,
material: row.material,
quantity: Number(row.quantity || 0),
order_date: row.order_date || "",
due_date: row.due_date || "",
order_status: row.order_status,
manager: row.manager,
process_id: row.process_id || "",
seq: row.seq != null ? Number(row.seq) : null,
process_name: row.process_name,
vendor_code: row.vendor_code,
vendor_name: row.vendor_name,
material_needed: !!row.material_needed,
material_id: row.material_id || "",
material_item_code: row.material_item_code,
material_item_name: row.material_item_name,
material_qty: Number(row.material_qty || 0),
material_unit: row.material_unit,
material_release_status: row.material_release_status,
outbound_id: row.outbound_id || "",
outbound_number: row.outbound_number,
outbound_status: row.outbound_status,
released_qty: releasedQty,
request_date: row.request_date,
received_qty: receivedQty,
remain_qty: remainQty,
rate_pct: ratePct,
release_status,
};
});
if (opts.keyword) {
const kw = opts.keyword.toLowerCase();
rows = rows.filter((row: any) => {
const hay = [
row.order_no,
row.source_type,
row.source_no,
row.item_code,
row.item_name,
row.spec,
row.material,
row.process_name,
row.vendor_name,
row.material_item_code,
row.material_item_name,
row.manager,
]
.join(" ")
.toLowerCase();
return hay.includes(kw);
});
}
if (opts.release_status) {
rows = rows.filter((row: any) => row.release_status === opts.release_status);
}
return { rows, total: rows.length };
}