- Added `end_date` field to user management for better tracking of user status. - Updated SQL queries in `adminController` to include `end_date` during user save operations. - Improved purchase report data handling by refining the logic for received quantities. - Enhanced file preview functionality to streamline file path handling. - Updated outbound and receiving controllers to ensure accurate updates to shipment and purchase order details. These changes aim to improve the overall functionality and user experience in managing user data and reporting processes.
536 lines
22 KiB
TypeScript
536 lines
22 KiB
TypeScript
import { Response } from "express";
|
|
import { query } from "../database/db";
|
|
import { logger } from "../utils/logger";
|
|
|
|
function buildCompanyFilter(companyCode: string, alias: string, paramIdx: number) {
|
|
if (companyCode === "*") return { condition: "", params: [] as any[], nextIdx: paramIdx };
|
|
return {
|
|
condition: `${alias}.company_code = $${paramIdx}`,
|
|
params: [companyCode],
|
|
nextIdx: paramIdx + 1,
|
|
};
|
|
}
|
|
|
|
function buildDateFilter(startDate: string | undefined, endDate: string | undefined, dateExpr: string, paramIdx: number) {
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let idx = paramIdx;
|
|
|
|
if (startDate) {
|
|
conditions.push(`${dateExpr} >= $${idx}`);
|
|
params.push(startDate);
|
|
idx++;
|
|
}
|
|
if (endDate) {
|
|
conditions.push(`${dateExpr} <= $${idx}`);
|
|
params.push(endDate);
|
|
idx++;
|
|
}
|
|
|
|
return { conditions, params, nextIdx: idx };
|
|
}
|
|
|
|
function buildWhereClause(conditions: string[]): string {
|
|
return conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
}
|
|
|
|
function extractFilterSet(rows: any[], field: string, labelField?: string): { value: string; label: string }[] {
|
|
const set = new Map<string, string>();
|
|
rows.forEach((r: any) => {
|
|
const val = r[field];
|
|
if (val && val !== "미지정") set.set(val, r[labelField || field] || val);
|
|
});
|
|
return [...set.entries()].map(([value, label]) => ({ value, label }));
|
|
}
|
|
|
|
// ============================================
|
|
// 생산 리포트
|
|
// ============================================
|
|
export async function getProductionReportData(req: any, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다" }); return; }
|
|
|
|
const { startDate, endDate } = req.query;
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let idx = 1;
|
|
|
|
const cf = buildCompanyFilter(companyCode, "wi", idx);
|
|
if (cf.condition) { conditions.push(cf.condition); params.push(...cf.params); idx = cf.nextIdx; }
|
|
|
|
const df = buildDateFilter(startDate, endDate, "COALESCE(wi.start_date, wi.created_date::date::text)", idx);
|
|
conditions.push(...df.conditions); params.push(...df.params); idx = df.nextIdx;
|
|
|
|
const whereClause = buildWhereClause(conditions);
|
|
|
|
const dataQuery = `
|
|
SELECT
|
|
COALESCE(wi.start_date, wi.created_date::date::text) as date,
|
|
COALESCE(wi.routing, '미지정') as process,
|
|
COALESCE(ei.equipment_name, wi.equipment_id, '미지정') as equipment,
|
|
COALESCE(ii.item_name, wi.item_id, '미지정') as item,
|
|
COALESCE(wi.worker, '미지정') as worker,
|
|
CAST(COALESCE(NULLIF(wi.qty, ''), '0') AS numeric) as "planQty",
|
|
COALESCE(pr.production_qty, 0) as "prodQty",
|
|
COALESCE(pr.defect_qty, 0) as "defectQty",
|
|
0 as "runTime",
|
|
0 as "downTime",
|
|
wi.status,
|
|
wi.company_code
|
|
FROM work_instruction wi
|
|
LEFT JOIN (
|
|
SELECT wo_id, company_code,
|
|
SUM(CAST(COALESCE(NULLIF(production_qty, ''), '0') AS numeric)) as production_qty,
|
|
SUM(CAST(COALESCE(NULLIF(defect_qty, ''), '0') AS numeric)) as defect_qty
|
|
FROM production_record GROUP BY wo_id, company_code
|
|
) pr ON wi.id = pr.wo_id AND wi.company_code = pr.company_code
|
|
LEFT JOIN (
|
|
SELECT DISTINCT ON (equipment_code, company_code)
|
|
equipment_code, equipment_name, equipment_type, company_code
|
|
FROM equipment_info ORDER BY equipment_code, company_code, created_date DESC
|
|
) ei ON wi.equipment_id = ei.equipment_code AND wi.company_code = ei.company_code
|
|
LEFT JOIN (
|
|
SELECT DISTINCT ON (item_number, company_code)
|
|
item_number, item_name, company_code
|
|
FROM item_info ORDER BY item_number, company_code, created_date DESC
|
|
) ii ON wi.item_id = ii.item_number AND wi.company_code = ii.company_code
|
|
${whereClause}
|
|
ORDER BY date DESC NULLS LAST
|
|
`;
|
|
|
|
const dataRows = await query(dataQuery, params);
|
|
|
|
logger.info("생산 리포트 데이터 조회", { companyCode, rowCount: dataRows.length });
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
rows: dataRows,
|
|
filterOptions: {
|
|
processes: extractFilterSet(dataRows, "process"),
|
|
equipment: extractFilterSet(dataRows, "equipment"),
|
|
items: extractFilterSet(dataRows, "item"),
|
|
workers: extractFilterSet(dataRows, "worker"),
|
|
},
|
|
totalCount: dataRows.length,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("생산 리포트 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: "생산 리포트 데이터 조회에 실패했습니다", error: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 재고 리포트
|
|
// ============================================
|
|
export async function getInventoryReportData(req: any, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다" }); return; }
|
|
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let idx = 1;
|
|
|
|
const cf = buildCompanyFilter(companyCode, "ist", idx);
|
|
if (cf.condition) { conditions.push(cf.condition); params.push(...cf.params); idx = cf.nextIdx; }
|
|
|
|
const whereClause = buildWhereClause(conditions);
|
|
|
|
const dataQuery = `
|
|
SELECT
|
|
COALESCE(ist.updated_date, ist.created_date)::date::text as date,
|
|
ist.item_code,
|
|
COALESCE(ii.item_name, ist.item_code, '미지정') as item,
|
|
COALESCE(wi.warehouse_name, ist.warehouse_code, '미지정') as warehouse,
|
|
'일반' as category,
|
|
CAST(COALESCE(NULLIF(ist.current_qty::text, ''), '0') AS numeric) as "currentQty",
|
|
CAST(COALESCE(NULLIF(ist.safety_qty::text, ''), '0') AS numeric) as "safetyQty",
|
|
COALESCE(ih_in.in_qty, 0) as "inQty",
|
|
COALESCE(ih_out.out_qty, 0) as "outQty",
|
|
0 as "stockValue",
|
|
GREATEST(CAST(COALESCE(NULLIF(ist.safety_qty::text, ''), '0') AS numeric)
|
|
- CAST(COALESCE(NULLIF(ist.current_qty::text, ''), '0') AS numeric), 0) as "shortageQty",
|
|
CASE WHEN CAST(COALESCE(NULLIF(ist.current_qty::text, ''), '0') AS numeric) > 0
|
|
AND COALESCE(ih_out.out_qty, 0) > 0
|
|
THEN ROUND(COALESCE(ih_out.out_qty, 0)::numeric
|
|
/ CAST(COALESCE(NULLIF(ist.current_qty::text, ''), '1') AS numeric), 2)
|
|
ELSE 0 END as "turnover",
|
|
ist.company_code
|
|
FROM inventory_stock ist
|
|
LEFT JOIN (
|
|
SELECT DISTINCT ON (item_number, company_code)
|
|
item_number, item_name, company_code
|
|
FROM item_info ORDER BY item_number, company_code, created_date DESC
|
|
) ii ON ist.item_code = ii.item_number AND ist.company_code = ii.company_code
|
|
LEFT JOIN warehouse_info wi ON ist.warehouse_code = wi.warehouse_code
|
|
AND ist.company_code = wi.company_code
|
|
LEFT JOIN (
|
|
SELECT item_code, company_code,
|
|
SUM(CAST(COALESCE(NULLIF(quantity::text, ''), '0') AS numeric)) as in_qty
|
|
FROM inventory_history WHERE transaction_type = 'IN'
|
|
GROUP BY item_code, company_code
|
|
) ih_in ON ist.item_code = ih_in.item_code AND ist.company_code = ih_in.company_code
|
|
LEFT JOIN (
|
|
SELECT item_code, company_code,
|
|
SUM(CAST(COALESCE(NULLIF(quantity::text, ''), '0') AS numeric)) as out_qty
|
|
FROM inventory_history WHERE transaction_type = 'OUT'
|
|
GROUP BY item_code, company_code
|
|
) ih_out ON ist.item_code = ih_out.item_code AND ist.company_code = ih_out.company_code
|
|
${whereClause}
|
|
ORDER BY date DESC NULLS LAST
|
|
`;
|
|
|
|
const dataRows = await query(dataQuery, params);
|
|
|
|
logger.info("재고 리포트 데이터 조회", { companyCode, rowCount: dataRows.length });
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
rows: dataRows,
|
|
filterOptions: {
|
|
items: extractFilterSet(dataRows, "item"),
|
|
warehouses: extractFilterSet(dataRows, "warehouse"),
|
|
categories: [
|
|
{ value: "원자재", label: "원자재" }, { value: "부자재", label: "부자재" },
|
|
{ value: "반제품", label: "반제품" }, { value: "완제품", label: "완제품" },
|
|
{ value: "일반", label: "일반" },
|
|
],
|
|
},
|
|
totalCount: dataRows.length,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("재고 리포트 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: "재고 리포트 데이터 조회에 실패했습니다", error: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 구매 리포트
|
|
// ============================================
|
|
export async function getPurchaseReportData(req: any, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다" }); return; }
|
|
|
|
const { startDate, endDate } = req.query;
|
|
const params: any[] = [];
|
|
let idx = 1;
|
|
|
|
// company_code 필터 파라미터 ($1 또는 없음)
|
|
const cf = buildCompanyFilter(companyCode, "po", idx);
|
|
let companyConditionDetail = "";
|
|
let companyConditionLegacy = "";
|
|
if (cf.condition) {
|
|
// purchase_detail 쪽: pd.company_code
|
|
companyConditionDetail = `pd.company_code = $${idx}`;
|
|
// purchase_order_mng 쪽: po.company_code
|
|
companyConditionLegacy = `po.company_code = $${idx}`;
|
|
// NOT EXISTS 내부에서도 동일 파라미터 재사용
|
|
params.push(...cf.params);
|
|
idx = cf.nextIdx;
|
|
}
|
|
|
|
// 날짜 필터는 외부 쿼리에서 적용
|
|
const outerConditions: string[] = [];
|
|
const df = buildDateFilter(startDate, endDate, "date", idx);
|
|
outerConditions.push(...df.conditions);
|
|
params.push(...df.params);
|
|
idx = df.nextIdx;
|
|
|
|
const outerWhereClause = buildWhereClause(outerConditions);
|
|
|
|
const dataQuery = `
|
|
WITH combined AS (
|
|
-- 신규: purchase_detail 기반 (헤더는 purchase_order_mng LEFT JOIN)
|
|
SELECT
|
|
COALESCE(po.order_date, po.created_date::date::text, pd.created_date::date::text) as date,
|
|
COALESCE(po.purchase_no, pd.purchase_no) as purchase_no,
|
|
COALESCE(pd.supplier_name, pd.supplier_code, po.supplier_name, po.supplier_code, '미지정') as supplier,
|
|
COALESCE(NULLIF(pd.item_name, ''), po.item_name, NULLIF(pd.item_code, ''), po.item_code, '미지정') as item,
|
|
COALESCE(NULLIF(pd.item_code, ''), po.item_code) as item_code,
|
|
COALESCE(po.manager, '미지정') as manager,
|
|
COALESCE(po.status, '') as status,
|
|
CAST(COALESCE(NULLIF(pd.order_qty, ''), '0') AS numeric) as "orderQty",
|
|
CAST(COALESCE(NULLIF(pd.received_qty, ''), NULLIF(po.received_qty, ''), '0') AS numeric) as "receiveQty",
|
|
CAST(COALESCE(NULLIF(pd.unit_price, ''), '0') AS numeric) as "unitPrice",
|
|
CAST(COALESCE(NULLIF(pd.order_qty, ''), '0') AS numeric)
|
|
* CAST(COALESCE(NULLIF(pd.unit_price, ''), '0') AS numeric) as "orderAmt",
|
|
CAST(COALESCE(NULLIF(pd.received_qty, ''), NULLIF(po.received_qty, ''), '0') AS numeric)
|
|
* CAST(COALESCE(NULLIF(pd.unit_price, ''), '0') AS numeric) as "receiveAmt",
|
|
1 as "orderCnt",
|
|
pd.company_code
|
|
FROM purchase_detail pd
|
|
LEFT JOIN purchase_order_mng po
|
|
ON pd.purchase_no = po.purchase_no AND pd.company_code = po.company_code
|
|
${companyConditionDetail ? `WHERE ${companyConditionDetail}` : ""}
|
|
|
|
UNION ALL
|
|
|
|
-- 레거시: purchase_detail에 없는 purchase_order_mng 데이터
|
|
SELECT
|
|
COALESCE(po.order_date, po.created_date::date::text) as date,
|
|
po.purchase_no,
|
|
COALESCE(po.supplier_name, po.supplier_code, '미지정') as supplier,
|
|
COALESCE(po.item_name, po.item_code, '미지정') as item,
|
|
po.item_code,
|
|
COALESCE(po.manager, '미지정') as manager,
|
|
po.status,
|
|
CAST(COALESCE(NULLIF(po.order_qty, ''), '0') AS numeric) as "orderQty",
|
|
CAST(COALESCE(NULLIF(po.received_qty, ''), '0') AS numeric) as "receiveQty",
|
|
CAST(COALESCE(NULLIF(po.unit_price, ''), '0') AS numeric) as "unitPrice",
|
|
CAST(COALESCE(NULLIF(po.amount, ''), '0') AS numeric) as "orderAmt",
|
|
CAST(COALESCE(NULLIF(po.received_qty, ''), '0') AS numeric)
|
|
* CAST(COALESCE(NULLIF(po.unit_price, ''), '0') AS numeric) as "receiveAmt",
|
|
1 as "orderCnt",
|
|
po.company_code
|
|
FROM purchase_order_mng po
|
|
WHERE ${companyConditionLegacy ? `${companyConditionLegacy} AND ` : ""}NOT EXISTS (
|
|
SELECT 1 FROM purchase_detail pd
|
|
WHERE pd.purchase_no = po.purchase_no AND pd.company_code = po.company_code
|
|
)
|
|
)
|
|
SELECT * FROM combined
|
|
${outerWhereClause}
|
|
ORDER BY date DESC NULLS LAST
|
|
`;
|
|
|
|
const dataRows = await query(dataQuery, params);
|
|
|
|
logger.info("구매 리포트 데이터 조회", { companyCode, rowCount: dataRows.length });
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
rows: dataRows,
|
|
filterOptions: {
|
|
suppliers: extractFilterSet(dataRows, "supplier"),
|
|
items: extractFilterSet(dataRows, "item"),
|
|
managers: extractFilterSet(dataRows, "manager"),
|
|
statuses: extractFilterSet(dataRows, "status"),
|
|
},
|
|
totalCount: dataRows.length,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("구매 리포트 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: "구매 리포트 데이터 조회에 실패했습니다", error: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 품질 리포트
|
|
// ============================================
|
|
export async function getQualityReportData(req: any, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다" }); return; }
|
|
|
|
const { startDate, endDate } = req.query;
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let idx = 1;
|
|
|
|
const cf = buildCompanyFilter(companyCode, "pr", idx);
|
|
if (cf.condition) { conditions.push(cf.condition); params.push(...cf.params); idx = cf.nextIdx; }
|
|
|
|
const df = buildDateFilter(startDate, endDate, "COALESCE(pr.production_date, pr.created_date::date::text)", idx);
|
|
conditions.push(...df.conditions); params.push(...df.params); idx = df.nextIdx;
|
|
|
|
const whereClause = buildWhereClause(conditions);
|
|
|
|
const dataQuery = `
|
|
SELECT
|
|
COALESCE(pr.production_date, pr.created_date::date::text) as date,
|
|
COALESCE(ii.item_name, wi.item_id, '미지정') as item,
|
|
'일반검사' as "defectType",
|
|
COALESCE(wi.routing, '미지정') as process,
|
|
COALESCE(pr.worker_name, '미지정') as inspector,
|
|
CAST(COALESCE(NULLIF(pr.production_qty, ''), '0') AS numeric) as "inspQty",
|
|
CAST(COALESCE(NULLIF(pr.production_qty, ''), '0') AS numeric)
|
|
- CAST(COALESCE(NULLIF(pr.defect_qty, ''), '0') AS numeric) as "passQty",
|
|
CAST(COALESCE(NULLIF(pr.defect_qty, ''), '0') AS numeric) as "defectQty",
|
|
0 as "reworkQty",
|
|
0 as "scrapQty",
|
|
0 as "claimCnt",
|
|
pr.company_code
|
|
FROM production_record pr
|
|
LEFT JOIN work_instruction wi ON pr.wo_id = wi.id AND pr.company_code = wi.company_code
|
|
LEFT JOIN (
|
|
SELECT DISTINCT ON (item_number, company_code)
|
|
item_number, item_name, company_code
|
|
FROM item_info ORDER BY item_number, company_code, created_date DESC
|
|
) ii ON wi.item_id = ii.item_number AND wi.company_code = ii.company_code
|
|
${whereClause}
|
|
ORDER BY date DESC NULLS LAST
|
|
`;
|
|
|
|
const dataRows = await query(dataQuery, params);
|
|
|
|
logger.info("품질 리포트 데이터 조회", { companyCode, rowCount: dataRows.length });
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
rows: dataRows,
|
|
filterOptions: {
|
|
items: extractFilterSet(dataRows, "item"),
|
|
defectTypes: [
|
|
{ value: "외관불량", label: "외관불량" }, { value: "치수불량", label: "치수불량" },
|
|
{ value: "기능불량", label: "기능불량" }, { value: "재질불량", label: "재질불량" },
|
|
{ value: "일반검사", label: "일반검사" },
|
|
],
|
|
processes: extractFilterSet(dataRows, "process"),
|
|
inspectors: extractFilterSet(dataRows, "inspector"),
|
|
},
|
|
totalCount: dataRows.length,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("품질 리포트 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: "품질 리포트 데이터 조회에 실패했습니다", error: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 설비 리포트
|
|
// ============================================
|
|
export async function getEquipmentReportData(req: any, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다" }); return; }
|
|
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let idx = 1;
|
|
|
|
const cf = buildCompanyFilter(companyCode, "ei", idx);
|
|
if (cf.condition) { conditions.push(cf.condition); params.push(...cf.params); idx = cf.nextIdx; }
|
|
|
|
const whereClause = buildWhereClause(conditions);
|
|
|
|
const dataQuery = `
|
|
SELECT
|
|
COALESCE(ei.updated_date, ei.created_date)::date::text as date,
|
|
ei.equipment_code,
|
|
COALESCE(ei.equipment_name, ei.equipment_code) as equipment,
|
|
COALESCE(ei.equipment_type, '미지정') as "equipType",
|
|
COALESCE(ei.location, '미지정') as line,
|
|
COALESCE(ui.user_name, ei.manager_id, '미지정') as manager,
|
|
ei.status,
|
|
CAST(COALESCE(NULLIF(ei.capacity_per_day::text, ''), '0') AS numeric) as "runTime",
|
|
0 as "downTime",
|
|
100 as "opRate",
|
|
0 as "faultCnt",
|
|
0 as "mtbf",
|
|
0 as "mttr",
|
|
0 as "maintCost",
|
|
CAST(COALESCE(NULLIF(ei.capacity_per_day::text, ''), '0') AS numeric) as "prodQty",
|
|
ei.company_code
|
|
FROM equipment_info ei
|
|
LEFT JOIN (
|
|
SELECT DISTINCT ON (user_id) user_id, user_name FROM user_info
|
|
) ui ON ei.manager_id = ui.user_id
|
|
${whereClause}
|
|
ORDER BY equipment ASC
|
|
`;
|
|
|
|
const dataRows = await query(dataQuery, params);
|
|
|
|
logger.info("설비 리포트 데이터 조회", { companyCode, rowCount: dataRows.length });
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
rows: dataRows,
|
|
filterOptions: {
|
|
equipment: extractFilterSet(dataRows, "equipment"),
|
|
equipTypes: extractFilterSet(dataRows, "equipType"),
|
|
lines: extractFilterSet(dataRows, "line"),
|
|
managers: extractFilterSet(dataRows, "manager"),
|
|
},
|
|
totalCount: dataRows.length,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("설비 리포트 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: "설비 리포트 데이터 조회에 실패했습니다", error: error.message });
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 금형 리포트
|
|
// ============================================
|
|
export async function getMoldReportData(req: any, res: Response): Promise<void> {
|
|
try {
|
|
const companyCode = req.user?.companyCode;
|
|
if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다" }); return; }
|
|
|
|
const conditions: string[] = [];
|
|
const params: any[] = [];
|
|
let idx = 1;
|
|
|
|
const cf = buildCompanyFilter(companyCode, "mm", idx);
|
|
if (cf.condition) { conditions.push(cf.condition); params.push(...cf.params); idx = cf.nextIdx; }
|
|
|
|
const whereClause = buildWhereClause(conditions);
|
|
|
|
const dataQuery = `
|
|
SELECT
|
|
COALESCE(mm.updated_date, mm.created_date)::date::text as date,
|
|
mm.mold_code,
|
|
COALESCE(mm.mold_name, mm.mold_code) as mold,
|
|
COALESCE(mm.mold_type, mm.category, '미지정') as "moldType",
|
|
COALESCE(ii.item_name, '미지정') as item,
|
|
COALESCE(mm.manufacturer, '미지정') as maker,
|
|
mm.operation_status as status,
|
|
CAST(COALESCE(NULLIF(mm.shot_count::text, ''), '0') AS numeric) as "shotCnt",
|
|
CAST(COALESCE(NULLIF(mm.warranty_shot_count::text, ''), '0') AS numeric) as "guaranteeShot",
|
|
CASE WHEN CAST(COALESCE(NULLIF(mm.warranty_shot_count::text, ''), '0') AS numeric) > 0
|
|
THEN ROUND(
|
|
CAST(COALESCE(NULLIF(mm.shot_count::text, ''), '0') AS numeric) * 100.0
|
|
/ CAST(COALESCE(NULLIF(mm.warranty_shot_count::text, ''), '1') AS numeric), 1)
|
|
ELSE 0 END as "lifeRate",
|
|
0 as "repairCnt",
|
|
0 as "repairCost",
|
|
0 as "prodQty",
|
|
0 as "defectRate",
|
|
CAST(COALESCE(NULLIF(mm.cavity_count::text, ''), '0') AS numeric) as "cavityUse",
|
|
mm.company_code
|
|
FROM mold_mng mm
|
|
LEFT JOIN (
|
|
SELECT DISTINCT ON (item_number, company_code)
|
|
item_number, item_name, company_code
|
|
FROM item_info ORDER BY item_number, company_code, created_date DESC
|
|
) ii ON mm.mold_code = ii.item_number AND mm.company_code = ii.company_code
|
|
${whereClause}
|
|
ORDER BY mold ASC
|
|
`;
|
|
|
|
const dataRows = await query(dataQuery, params);
|
|
|
|
logger.info("금형 리포트 데이터 조회", { companyCode, rowCount: dataRows.length });
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {
|
|
rows: dataRows,
|
|
filterOptions: {
|
|
molds: extractFilterSet(dataRows, "mold"),
|
|
moldTypes: extractFilterSet(dataRows, "moldType"),
|
|
items: extractFilterSet(dataRows, "item"),
|
|
makers: extractFilterSet(dataRows, "maker"),
|
|
},
|
|
totalCount: dataRows.length,
|
|
},
|
|
});
|
|
} catch (error: any) {
|
|
logger.error("금형 리포트 조회 실패", { error: error.message });
|
|
res.status(500).json({ success: false, message: "금형 리포트 데이터 조회에 실패했습니다", error: error.message });
|
|
}
|
|
}
|