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

@@ -1,6 +1,6 @@
/**
* 자재현황 컨트롤러
* - 생산계획(작업지시) 조회
* - 작업지시(work_instruction + work_instruction_detail) 조회
* - 선택된 작업지시의 BOM 기반 자재소요량 + 재고 현황 조회
* - 창고 목록 조회
*/
@@ -10,7 +10,7 @@ import { AuthenticatedRequest } from "../types/auth";
import { pool } from "../database/db";
import { logger } from "../utils/logger";
// ─── 생산계획(작업지시) 조회 ───
// ─── 작업지시 조회 (work_instruction + work_instruction_detail) ───
export async function getWorkOrders(
req: AuthenticatedRequest,
@@ -27,31 +27,31 @@ export async function getWorkOrders(
if (companyCode === "*") {
logger.info("최고 관리자 전체 작업지시 조회");
} else {
conditions.push(`p.company_code = $${paramIndex}`);
conditions.push(`wi.company_code = $${paramIndex}`);
params.push(companyCode);
paramIndex++;
}
if (dateFrom) {
conditions.push(`p.plan_date >= $${paramIndex}::date`);
conditions.push(`wi.start_date::date >= $${paramIndex}::date`);
params.push(dateFrom);
paramIndex++;
}
if (dateTo) {
conditions.push(`p.plan_date <= $${paramIndex}::date`);
conditions.push(`wi.start_date::date <= $${paramIndex}::date`);
params.push(dateTo);
paramIndex++;
}
if (itemCode) {
conditions.push(`p.item_code ILIKE $${paramIndex}`);
conditions.push(`d.item_number ILIKE $${paramIndex}`);
params.push(`%${itemCode}%`);
paramIndex++;
}
if (itemName) {
conditions.push(`p.item_name ILIKE $${paramIndex}`);
conditions.push(`COALESCE(itm.item_name, '') ILIKE $${paramIndex}`);
params.push(`%${itemName}%`);
paramIndex++;
}
@@ -60,22 +60,28 @@ export async function getWorkOrders(
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
const query = `
SELECT
p.id,
p.plan_no,
p.item_code,
p.item_name,
p.plan_qty,
p.completed_qty,
p.plan_date,
p.start_date,
p.end_date,
p.status,
p.work_order_no,
p.company_code
FROM production_plan_mng p
SELECT
d.id,
wi.work_instruction_no AS plan_no,
d.item_number AS item_code,
COALESCE(itm.item_name, '') AS item_name,
d.qty AS plan_qty,
wi.completed_qty,
wi.start_date AS plan_date,
wi.start_date,
wi.end_date,
wi.status,
wi.work_instruction_no AS work_order_no,
wi.company_code
FROM work_instruction wi
INNER JOIN work_instruction_detail d
ON d.work_instruction_no = wi.work_instruction_no AND d.company_code = wi.company_code
LEFT JOIN LATERAL (
SELECT item_name FROM item_info
WHERE item_number = d.item_number AND company_code = wi.company_code LIMIT 1
) itm ON true
${whereClause}
ORDER BY p.plan_date DESC, p.created_date DESC
ORDER BY wi.start_date DESC, wi.created_date DESC
`;
const result = await pool.query(query, params);
@@ -108,14 +114,14 @@ export async function getMaterialStatus(
.json({ success: false, message: "작업지시를 선택해주세요." });
}
// 1) 선택된 작업지시의 품목코드 + 수량 조회
// 1) 선택된 작업지시 상세의 품목코드 + 수량 조회
const planPlaceholders = planIds
.map((_, i) => `$${i + 1}`)
.join(",");
let paramIndex = planIds.length + 1;
const companyCondition =
companyCode === "*" ? "" : `AND p.company_code = $${paramIndex}`;
companyCode === "*" ? "" : `AND d.company_code = $${paramIndex}`;
const planParams: any[] = [...planIds];
if (companyCode !== "*") {
planParams.push(companyCode);
@@ -123,9 +129,13 @@ export async function getMaterialStatus(
}
const planQuery = `
SELECT p.item_code, p.item_name, p.plan_qty
FROM production_plan_mng p
WHERE p.id IN (${planPlaceholders})
SELECT d.item_number AS item_code, COALESCE(itm.item_name, '') AS item_name, d.qty AS plan_qty
FROM work_instruction_detail d
LEFT JOIN LATERAL (
SELECT item_name FROM item_info
WHERE item_number = d.item_number AND company_code = d.company_code LIMIT 1
) itm ON true
WHERE d.id IN (${planPlaceholders})
${companyCondition}
`;