- Refactored date handling in the InventoryStatusPage to use `toLocaleString` for transaction dates and last in dates, ensuring correct timezone formatting. - Introduced FormDatePicker in SalesOrderPage for date inputs, enhancing user experience with automatic formatting and improved date handling. - Added a checkbox for filtering items by customer in SalesOrderPage, allowing users to view only items registered for the selected customer. This update improves date accuracy and user interaction in the inventory and sales order modules.
117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
/**
|
|
* 품질 모니터링 데이터 조회 (서버 페이징 + 통계 합산)
|
|
* work_order_process(공정 메타) + work_order_process_result(실적) JOIN
|
|
* - 페이지: 화면 표 표시용
|
|
* - summary: KPI 카드용 (전체 합산)
|
|
*/
|
|
|
|
import { Response } from "express";
|
|
import { AuthenticatedRequest } from "../types/auth";
|
|
import { getPool } from "../database/db";
|
|
import { logger } from "../utils/logger";
|
|
|
|
export async function getQualityMonitoringData(req: AuthenticatedRequest, res: Response) {
|
|
try {
|
|
const companyCode = req.user!.companyCode;
|
|
const from = (req.query.from as string | undefined)?.trim() || "";
|
|
const to = (req.query.to as string | undefined)?.trim() || "";
|
|
const page = Math.max(1, parseInt(String(req.query.page ?? "1"), 10) || 1);
|
|
const size = Math.max(1, Math.min(500, parseInt(String(req.query.size ?? "50"), 10) || 50));
|
|
const offset = (page - 1) * size;
|
|
|
|
const params: any[] = [companyCode];
|
|
const conds: string[] = ["wopr.company_code = $1"];
|
|
if (from) {
|
|
params.push(`${from} 00:00:00`);
|
|
conds.push(`wopr.created_date >= $${params.length}::timestamp`);
|
|
}
|
|
if (to) {
|
|
params.push(`${to} 23:59:59`);
|
|
conds.push(`wopr.created_date <= $${params.length}::timestamp`);
|
|
}
|
|
const whereClause = `WHERE ${conds.join(" AND ")}`;
|
|
|
|
const pool = getPool();
|
|
|
|
// 1) total + summary (KPI 카드)
|
|
const summaryQuery = `
|
|
SELECT
|
|
COUNT(*)::int AS total,
|
|
SUM(CASE WHEN wopr.status = 'completed' AND COALESCE(CAST(NULLIF(wopr.defect_qty, '') AS numeric), 0) = 0 THEN 1 ELSE 0 END)::int AS passed,
|
|
SUM(CASE WHEN wopr.status = 'completed' AND COALESCE(CAST(NULLIF(wopr.defect_qty, '') AS numeric), 0) > 0 THEN 1 ELSE 0 END)::int AS failed,
|
|
SUM(CASE WHEN wopr.status <> 'completed' OR wopr.status IS NULL THEN 1 ELSE 0 END)::int AS pending
|
|
FROM work_order_process_result wopr
|
|
${whereClause}
|
|
`;
|
|
const summaryRes = await pool.query(summaryQuery, params);
|
|
const summaryRow = summaryRes.rows[0] || { total: 0, passed: 0, failed: 0, pending: 0 };
|
|
const total = summaryRow.total || 0;
|
|
const passRate = total > 0 ? Math.round((summaryRow.passed / total) * 1000) / 10 : 0;
|
|
|
|
// 2) 페이지 데이터
|
|
const pageParams = [...params, size, offset];
|
|
const dataQuery = `
|
|
SELECT
|
|
wopr.id,
|
|
wopr.wop_id,
|
|
wopr.status,
|
|
wopr.input_qty,
|
|
wopr.good_qty,
|
|
wopr.defect_qty,
|
|
wopr.started_at,
|
|
wopr.completed_at,
|
|
wopr.completed_by,
|
|
wopr.accepted_by,
|
|
wop.wo_id,
|
|
wop.process_code,
|
|
wop.process_name,
|
|
wop.plan_qty
|
|
FROM work_order_process_result wopr
|
|
LEFT JOIN work_order_process wop
|
|
ON wop.id = wopr.wop_id AND wop.company_code = wopr.company_code
|
|
${whereClause}
|
|
ORDER BY wopr.created_date DESC
|
|
LIMIT $${params.length + 1} OFFSET $${params.length + 2}
|
|
`;
|
|
const dataRes = await pool.query(dataQuery, pageParams);
|
|
|
|
const rows = dataRes.rows.map((r: any) => ({
|
|
id: r.id,
|
|
wo_id: r.wo_id,
|
|
process_code: r.process_code || "",
|
|
process_name: r.process_name || "",
|
|
status: r.status || "",
|
|
plan_qty: Number(r.plan_qty) || 0,
|
|
input_qty: Number(r.input_qty) || 0,
|
|
good_qty: Number(r.good_qty) || 0,
|
|
defect_qty: Number(r.defect_qty) || 0,
|
|
started_at: r.started_at || null,
|
|
completed_at: r.completed_at || null,
|
|
worker_name: r.completed_by || r.accepted_by || "",
|
|
}));
|
|
|
|
logger.info("품질 모니터링 조회", {
|
|
companyCode, from, to, page, size, total,
|
|
});
|
|
|
|
return res.json({
|
|
success: true,
|
|
rows,
|
|
total,
|
|
page,
|
|
size,
|
|
totalPages: Math.max(1, Math.ceil(total / size)),
|
|
summary: {
|
|
total,
|
|
passed: summaryRow.passed || 0,
|
|
failed: summaryRow.failed || 0,
|
|
pending: summaryRow.pending || 0,
|
|
passRate,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("품질 모니터링 조회 실패", { error: error.message });
|
|
return res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
}
|