Implement KPI daily production feature

- Added a new KPI controller to handle daily production data retrieval.
- Created routes for accessing KPI data, specifically for daily production.
- Developed frontend components for displaying daily production metrics, including charts and summary cards.
- Implemented data fetching logic with date range filtering for production data.
- Ensured proper loading states and error handling in the UI.

This feature is part of TASK:ERP-022.
This commit is contained in:
kjs
2026-04-28 16:14:27 +09:00
parent 4afb0f5ca4
commit bf8d99ccf5
16 changed files with 716 additions and 88 deletions

View File

@@ -149,6 +149,7 @@ import cascadingHierarchyRoutes from "./routes/cascadingHierarchyRoutes"; // 다
import categoryValueCascadingRoutes from "./routes/categoryValueCascadingRoutes"; // 카테고리 값 연쇄관계
import categoryTreeRoutes from "./routes/categoryTreeRoutes"; // 카테고리 트리 (테스트)
import processWorkStandardRoutes from "./routes/processWorkStandardRoutes"; // 공정 작업기준
import kpiRoutes from "./routes/kpiRoutes"; // KPI (TASK:ERP-022)
import aiAssistantProxy from "./routes/aiAssistantProxy"; // AI 어시스턴트 API 프록시 (같은 포트로 서비스)
import auditLogRoutes from "./routes/auditLogRoutes"; // 통합 변경 이력
import moldRoutes from "./routes/moldRoutes"; // 금형 관리
@@ -378,6 +379,7 @@ app.use("/api/cascading-hierarchy", cascadingHierarchyRoutes); // 다단계 계
app.use("/api/category-value-cascading", categoryValueCascadingRoutes); // 카테고리 값 연쇄관계
app.use("/api/category-tree", categoryTreeRoutes); // 카테고리 트리 (테스트)
app.use("/api/process-work-standard", processWorkStandardRoutes); // 공정 작업기준
app.use("/api/kpi", kpiRoutes); // KPI (TASK:ERP-022)
app.use("/api/audit-log", auditLogRoutes); // 통합 변경 이력
app.use("/api/mold", moldRoutes); // 금형 관리
app.use("/api/shipping-plan", shippingPlanRoutes); // 출하계획 관리

View File

@@ -0,0 +1,48 @@
/**
* KPI 컨트롤러 — TASK:ERP-022
* 일별 생산량 등 KPI 지표 조회 전담
*/
import { Response } from "express";
import { AuthenticatedRequest } from "../types/auth";
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
/**
* GET /api/kpi/daily-production?from=YYYY-MM-DD&to=YYYY-MM-DD
* 회사별 일별 생산량 조회
*/
export async function getDailyProduction(req: AuthenticatedRequest, res: Response) {
try {
const companyCode = req.user?.companyCode;
if (!companyCode) {
return res.status(401).json({ success: false, message: "인증 필요" });
}
const from = (req.query.from as string) || "";
const to = (req.query.to as string) || "";
const params: any[] = [companyCode];
let where = "company_code = $1";
if (from) {
params.push(from);
where += ` AND prod_date >= $${params.length}`;
}
if (to) {
params.push(to);
where += ` AND prod_date <= $${params.length}`;
}
const result = await getPool().query(
`SELECT prod_date, production_qty, defect_qty, work_hours, remark
FROM kpi_daily_production
WHERE ${where}
ORDER BY prod_date`,
params
);
return res.json({ success: true, data: result.rows });
} catch (error: any) {
logger.error("KPI 일별 생산량 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });
}
}

View File

@@ -468,10 +468,10 @@ export async function saveRoutingDetails(req: AuthenticatedRequest, res: Respons
}
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)
`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, execution_type, writer)
VALUES (gen_random_uuid()::text, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
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", legacyCode, writer]
[companyCode, versionId, d.seq_no, d.process_code, d.is_required || "Y", d.is_fixed_order || "Y", d.work_type || "내부", d.standard_time || "0", legacyCode, d.execution_type || null, writer]
);
const newDetailId = insertRes.rows[0].id;

View File

@@ -0,0 +1,13 @@
/**
* KPI 라우트 — TASK:ERP-022
*/
import { Router } from "express";
import { authenticateToken } from "../middleware/authMiddleware";
import * as ctrl from "../controllers/kpiController";
const router = Router();
router.use(authenticateToken);
router.get("/daily-production", ctrl.getDailyProduction);
export default router;