feat: 입고/자재현황/분석리포트 컨트롤러 및 프론트엔드 개선

- receivingController: 헤더-디테일 JOIN 구조로 변경, 검색/조회 로직 개선
- materialStatusController: work_instruction 테이블 기반으로 쿼리 수정
- analyticsReportController: 구매 리포트 company_code 필터링 로직 개선
- material-status 페이지: COMPANY_29/COMPANY_7 프론트엔드 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kmh
2026-03-30 17:01:26 +09:00
parent f980bffed4
commit 4e4088eb71
6 changed files with 450 additions and 160 deletions

View File

@@ -218,37 +218,84 @@ export async function getPurchaseReportData(req: any, res: Response): Promise<vo
if (!companyCode) { res.status(401).json({ success: false, message: "인증 정보가 없습니다" }); return; }
const { startDate, endDate } = req.query;
const conditions: string[] = [];
const params: any[] = [];
let idx = 1;
// company_code 필터 파라미터 ($1 또는 없음)
const cf = buildCompanyFilter(companyCode, "po", idx);
if (cf.condition) { conditions.push(cf.condition); params.push(...cf.params); idx = cf.nextIdx; }
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 df = buildDateFilter(startDate, endDate, "COALESCE(po.order_date, po.created_date::date::text)", idx);
conditions.push(...df.conditions); params.push(...df.params); idx = df.nextIdx;
// 날짜 필터는 외부 쿼리에서 적용
const outerConditions: string[] = [];
const df = buildDateFilter(startDate, endDate, "date", idx);
outerConditions.push(...df.conditions);
params.push(...df.params);
idx = df.nextIdx;
const whereClause = buildWhereClause(conditions);
const outerWhereClause = buildWhereClause(outerConditions);
const dataQuery = `
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
${whereClause}
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(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(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
`;