Update date handling in inventory and sales order pages

- 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.
This commit is contained in:
kjs
2026-04-29 18:20:01 +09:00
parent ef11b4d83b
commit 6ddc84f285
35 changed files with 813 additions and 370 deletions

View File

@@ -0,0 +1,116 @@
/**
* 품질 모니터링 데이터 조회 (서버 페이징 + 통계 합산)
* 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 });
}
}