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:
@@ -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); // 출하계획 관리
|
||||
|
||||
48
backend-node/src/controllers/kpiController.ts
Normal file
48
backend-node/src/controllers/kpiController.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
13
backend-node/src/routes/kpiRoutes.ts
Normal file
13
backend-node/src/routes/kpiRoutes.ts
Normal 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;
|
||||
Reference in New Issue
Block a user