품질관리 4메뉴 — wace_plm JSP + quality.xml MyBatis 1:1 재정합
Build and Push Images / build-and-push (push) Has been cancelled

수입검사 요청 (incoming-request):
 - 필터 12종 (품의서/발주서/프로젝트/품번/품명/공급업체/입고결과/제품구분/
   검사여부/요청현황/요청자/요청일범위)
 - 그리드 12컬럼 (proposal_no, purchase_order_no, project_no, product_name,
   part_no, part_name, partner_name, delivery_status, request_date,
   request_user_name, inspection_yn, request_status)
 - 백엔드: purchase_order_master + incoming_inspection_detail LEFT JOIN
   + sales_request_master + contract_mgmt + part_mng + user_info

수입검사 진행 (incoming-mgmt):
 - 필터 11종 (수입요청과 유사 + 검사일범위 + 검사현황)
 - 그리드 19컬럼 (검사일/검사자/품의서/발주서/프로젝트/제품구분/품명모델/
   부품품번/부품명/공급업체/입고일/입고수량/입고결과/검사수량/불량수량/
   불량률/검사현황/검사성적서)
 - SUM(defect_qty) 서브쿼리로 불량률 자동 계산
 - 하단 요약: 총 입고수량/검사수량/불량수량

공정검사 관리 (process-inspection):
 - 필터 10종 (프로젝트/제품구분/품번/품명/작업환경/측정기/검사일범위/
   검사자/검사결과/진행공정)
 - 그리드 9컬럼 (검사일/검사자/프로젝트/제품구분/품번/품명/검사수량합계/
   검사결과/첨부파일)
 - master/detail 집계 + EXISTS 필터로 wace 1:1

반제품검사 관리 (semi-product-inspection):
 - 필터 8종 (모델명/작업지시번호/부품품번/부품명/검사일범위/검사자/
   불량유형/귀책부서)
 - 그리드 14컬럼 (검사일/검사자/제품구분/모델명/작업지시번호/부품품번/
   부품명/입고수량/양품수량/불량수량/불량률/재생수량/최종양품수량)
 - data_type='GOOD' 마스터 + 동일 inspection_group_id 의 'DEFECT' SUM
 - 재생수량(disposition_type='수정완료') + 최종양품수량 자동 산정
 - 하단 요약: 5개 합계 카드

frontend/lib/api/quality.ts 타입 1:1 정합, 모든 필터 파라미터 직렬화.
This commit is contained in:
chpark
2026-05-15 11:22:48 +09:00
parent e785cd8a98
commit c74e742b6f
6 changed files with 500 additions and 328 deletions
+145 -152
View File
@@ -1,15 +1,14 @@
/**
* 품질관리 — 4개 신규 메뉴 라우트.
* 품질관리 — wace_plm 의 QualityController + quality.xml 을 1:1 이식.
*
* GET /api/quality/incoming-request → 수입검사 요청
* GET /api/quality/incoming-mgmt → 수입검사 관리
* GET /api/quality/process-inspection → 공정검사 관리
* GET /api/quality/semi-product-inspection → 반제품검사 관리
* GET /api/quality/incoming-request → 수입검사 요청 (incomingInspectionGridList)
* GET /api/quality/incoming-mgmt → 수입검사 진행 (incomingInspectionProgressGridList)
* GET /api/quality/process-inspection → 공정검사 관리 (processInspectionGridList)
* GET /api/quality/semi-product-inspection → 반제품검사 관리 (semiProductInspectionGridList)
*
* 데이터 소스:
* - incoming_* : purchase_order_master + incoming_inspection_detail/defect (wace_plm 스타일 5개 신규 테이블, docs/migration/quality/02_*.sql)
* - process_* : process_inspection_master + process_inspection_detail
* - semi_* : pms_quality_semi_product_inspection (DATA_TYPE='GOOD' 마스터 행)
* 컬럼/필터/조인은 wace_plm 의 JSP + MyBatis 와 동일하게 구성한다.
* 일부 조인 테이블(arrival_plan, inventory_mgmt_in 등)이 vexplor_rps 에 없을 수 있어
* LEFT JOIN 으로만 묶고, 미존재 시 SQL 실패해도 빈 응답으로 fallback.
*/
import { Router, Request, Response } from "express";
@@ -28,126 +27,143 @@ function emptyList(res: Response) {
}
// ─── 1. 수입검사 요청 ──────────────────────────────────────────
// 발주서 마스터 + 검사 디테일 LEFT JOIN. 디테일이 없으면 미요청 상태로 노출.
// wace quality.xml getIncomingInspectionList — 발주서 기반.
router.get("/incoming-request", async (req: Request, res: Response) => {
try {
const pool = getPool();
const { project_no, partner_objid, request_user_id } = req.query as Record<string, string>;
const q = req.query as Record<string, string>;
const where: string[] = ["1=1"];
const params: any[] = [];
if (project_no) {
params.push(`%${project_no}%`);
where.push(`COALESCE(cm.project_no, '') ILIKE $${params.length}`);
}
if (partner_objid) {
params.push(partner_objid);
where.push(`pom.partner_objid = $${params.length}`);
}
if (request_user_id) {
params.push(request_user_id);
where.push(`iid.request_user_id = $${params.length}`);
const add = (sql: string, ...v: any[]) => { v.forEach(x => params.push(x)); where.push(sql.replace(/\?/g, () => "$" + params.length)); };
if (q.search_proposal_no) add("COALESCE(srm.proposal_no, '') ILIKE ?", `%${q.search_proposal_no}%`);
if (q.search_purchase_order_no) add("pom.purchase_order_no ILIKE ?", `%${q.search_purchase_order_no}%`);
if (q.project_no) add("COALESCE(cm.project_no, '') ILIKE ?", `%${q.project_no}%`);
if (q.search_part_no) add("COALESCE(pm.part_no, '') ILIKE ?", `%${q.search_part_no}%`);
if (q.search_part_name) add("COALESCE(pm.part_name, '') ILIKE ?", `%${q.search_part_name}%`);
if (q.search_partner) add("pom.partner_objid::text = ?", q.search_partner);
if (q.search_product_cd) add("pom.product_cd::text = ?", q.search_product_cd);
if (q.search_inspection_yn) add("COALESCE(iid.inspection_yn, '미요청') = ?", q.search_inspection_yn);
if (q.request_user_id) add("COALESCE(iid.request_user_id, pom.writer) = ?", q.request_user_id);
if (q.request_start_date) add("COALESCE(iid.request_date, pom.regdate::date) >= ?", q.request_start_date);
if (q.request_end_date) add("COALESCE(iid.request_date, pom.regdate::date) <= ?", q.request_end_date);
if (q.search_request_status) {
const s = q.search_request_status;
if (s === "미요청") where.push("iid.objid IS NULL");
else if (s === "요청중") where.push("iid.objid IS NOT NULL AND iid.inspection_date IS NULL");
else if (s === "요청완료") where.push("iid.inspection_date IS NOT NULL");
}
const sql = `
SELECT
pom.objid::text AS objid,
pom.purchase_order_no AS purchase_order_no,
COALESCE(srm.proposal_no, '') AS proposal_no,
COALESCE(cm.project_no, '') AS project_no,
COALESCE(ci.code_name, '') AS product_name,
'' AS part_no,
'' AS part_name,
COALESCE(client.partner_name, '') AS partner_name,
'-' AS delivery_status,
pom.objid::text AS objid,
COALESCE(srm.proposal_no, '') AS proposal_no,
pom.purchase_order_no AS purchase_order_no,
COALESCE(cm.project_no, '') AS project_no,
COALESCE(ci.code_name, '') AS product_name,
COALESCE(pm.part_no, '') AS part_no,
COALESCE(pm.part_name, '') AS part_name,
COALESCE(client.partner_name, '') AS partner_name,
'-' AS delivery_status,
TO_CHAR(COALESCE(iid.request_date, pom.regdate::date), 'YYYY-MM-DD') AS request_date,
COALESCE(ui_req.user_name, ui_w.user_name, '') AS request_user_name,
COALESCE(iid.inspection_yn, '-') AS inspection_yn,
COALESCE(ui.user_name, '') AS request_user_name,
COALESCE(iid.inspection_yn, '-') AS inspection_yn,
CASE
WHEN iid.objid IS NULL THEN '미요청'
WHEN iid.inspection_date IS NOT NULL THEN '검사완료'
WHEN iid.inspection_date IS NOT NULL THEN '요청완료'
ELSE '요청중'
END AS request_status
END AS request_status
FROM purchase_order_master pom
LEFT JOIN sales_request_master srm ON srm.objid = pom.sales_request_objid
LEFT JOIN contract_mgmt cm ON cm.objid = pom.contract_objid
LEFT JOIN code_info ci ON ci.objid = pom.product_cd
LEFT JOIN client_mng client ON client.objid = pom.partner_objid
LEFT JOIN user_info ui_w ON ui_w.user_id = pom.writer
LEFT JOIN sales_request_master srm ON srm.objid = pom.sales_request_objid
LEFT JOIN contract_mgmt cm ON cm.objid = pom.contract_objid
LEFT JOIN code_info ci ON ci.objid = pom.product_cd
LEFT JOIN client_mng client ON client.objid = pom.partner_objid
LEFT JOIN part_mng pm ON pm.objid = pom.part_objid
LEFT JOIN incoming_inspection_detail iid ON iid.purchase_order_master_objid = pom.objid::text
LEFT JOIN user_info ui_req ON ui_req.user_id = iid.request_user_id
LEFT JOIN user_info ui ON ui.user_id = COALESCE(iid.request_user_id, pom.writer)
WHERE ${where.join(" AND ")}
ORDER BY pom.regdate DESC NULLS LAST
ORDER BY pom.regdate DESC NULLS LAST, pom.objid DESC
LIMIT 500
`;
const r = await pool.query(sql, params);
res.json({
success: true,
list: r.rows,
pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 },
});
res.json({ success: true, list: r.rows, pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 } });
} catch (err: any) {
console.warn("[quality/incoming-request] fallback empty:", err?.message);
emptyList(res);
}
});
// ─── 2. 수입검사 관리 ──────────────────────────────────────────
// ─── 2. 수입검사 진행 ──────────────────────────────────────────
// wace quality.xml getIncomingInspectionProgressList — INCOMING_INSPECTION_DETAIL 기반,
// 검사된 행만 (INSPECTION_YN = '검사'). DEFECT 합계로 불량률 계산.
router.get("/incoming-mgmt", async (req: Request, res: Response) => {
try {
const pool = getPool();
const { project_no, partner_objid, inspector_id } = req.query as Record<string, string>;
const q = req.query as Record<string, string>;
const where: string[] = ["iid.objid IS NOT NULL"];
const where: string[] = ["iid.inspection_yn = '검사'"];
const params: any[] = [];
if (project_no) {
params.push(`%${project_no}%`);
where.push(`COALESCE(cm.project_no, '') ILIKE $${params.length}`);
}
if (partner_objid) {
params.push(partner_objid);
where.push(`pom.partner_objid = $${params.length}`);
}
if (inspector_id) {
params.push(inspector_id);
where.push(`iid.inspector_id = $${params.length}`);
const add = (sql: string, ...v: any[]) => { v.forEach(x => params.push(x)); where.push(sql.replace(/\?/g, () => "$" + params.length)); };
if (q.search_proposal_no) add("COALESCE(srm.proposal_no, '') ILIKE ?", `%${q.search_proposal_no}%`);
if (q.search_purchase_order_no) add("pom.purchase_order_no ILIKE ?", `%${q.search_purchase_order_no}%`);
if (q.project_no) add("COALESCE(cm.project_no, '') ILIKE ?", `%${q.project_no}%`);
if (q.search_part_no) add("COALESCE(pm.part_no, '') ILIKE ?", `%${q.search_part_no}%`);
if (q.search_part_name) add("COALESCE(pm.part_name, '') ILIKE ?", `%${q.search_part_name}%`);
if (q.search_partner) add("pom.partner_objid::text = ?", q.search_partner);
if (q.search_product_cd) add("pom.product_cd::text = ?", q.search_product_cd);
if (q.inspector_id) add("iid.inspector_id = ?", q.inspector_id);
if (q.inspection_start_date) add("iid.inspection_date >= ?", q.inspection_start_date);
if (q.inspection_end_date) add("iid.inspection_date <= ?", q.inspection_end_date);
if (q.search_inspection_status) {
const s = q.search_inspection_status;
if (s === "완료") where.push("iid.inspection_date IS NOT NULL AND COALESCE(iid.inspection_result, '') <> ''");
else if (s === "진행중") where.push("iid.inspection_date IS NULL");
}
const sql = `
SELECT
iid.objid::text AS objid,
TO_CHAR(iid.inspection_date, 'YYYY-MM-DD') AS inspection_date,
COALESCE(ui_ins.user_name, '') AS inspector_name,
COALESCE(srm.proposal_no, '') AS proposal_no,
pom.purchase_order_no AS purchase_order_no,
COALESCE(cm.project_no, '') AS project_no,
COALESCE(ci.code_name, '') AS product_name,
'' AS part_no,
'' AS part_name,
COALESCE(pm.product_name, '') AS model_name,
COALESCE(pm.part_no, '') AS part_no,
COALESCE(pm.part_name, '') AS part_name,
COALESCE(client.partner_name, '') AS partner_name,
TO_CHAR(iid.inspection_date, 'YYYY-MM-DD') AS inspection_date,
COALESCE(ui.user_name, '') AS inspector_name,
iid.inspection_qty AS total_qty,
(iid.inspection_qty - COALESCE(iid.defect_qty, 0)) AS good_qty,
iid.defect_qty AS bad_qty,
COALESCE(iid.inspection_result, '') AS inspection_result,
TO_CHAR(iid.reg_date, 'YYYY-MM-DD') AS delivery_date,
iid.inspection_qty AS delivery_qty,
'-' AS delivery_status,
iid.inspection_qty AS inspection_qty,
COALESCE((SELECT SUM(d.defect_qty) FROM incoming_inspection_defect d WHERE d.inspection_detail_objid = iid.objid), 0) AS defect_qty_sum,
CASE
WHEN iid.inspection_date IS NOT NULL THEN '검사완료'
ELSE '요청중'
END AS request_status
WHEN iid.inspection_qty IS NULL OR iid.inspection_qty = 0 THEN 0
ELSE ROUND(COALESCE((SELECT SUM(d.defect_qty) FROM incoming_inspection_defect d WHERE d.inspection_detail_objid = iid.objid), 0) / iid.inspection_qty * 100, 2)
END AS defect_rate,
CASE
WHEN iid.inspection_date IS NULL THEN '미검사'
WHEN COALESCE(iid.inspection_result, '') = '' THEN '진행중'
ELSE '완료'
END AS inspection_result,
0 AS inspection_file_cnt
FROM incoming_inspection_detail iid
LEFT JOIN purchase_order_master pom ON pom.objid::text = iid.purchase_order_master_objid
LEFT JOIN contract_mgmt cm ON cm.objid = pom.contract_objid
LEFT JOIN code_info ci ON ci.objid = pom.product_cd
LEFT JOIN client_mng client ON client.objid = pom.partner_objid
LEFT JOIN user_info ui ON ui.user_id = iid.inspector_id
LEFT JOIN purchase_order_master pom ON pom.objid::text = iid.purchase_order_master_objid
LEFT JOIN sales_request_master srm ON srm.objid = pom.sales_request_objid
LEFT JOIN contract_mgmt cm ON cm.objid = pom.contract_objid
LEFT JOIN code_info ci ON ci.objid = pom.product_cd
LEFT JOIN client_mng client ON client.objid = pom.partner_objid
LEFT JOIN part_mng pm ON pm.objid = pom.part_objid
LEFT JOIN user_info ui_ins ON ui_ins.user_id = iid.inspector_id
WHERE ${where.join(" AND ")}
ORDER BY iid.reg_date DESC
ORDER BY iid.inspection_date DESC NULLS LAST, iid.objid DESC
LIMIT 500
`;
const r = await pool.query(sql, params);
res.json({
success: true,
list: r.rows,
pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 },
});
res.json({ success: true, list: r.rows, pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 } });
} catch (err: any) {
console.warn("[quality/incoming-mgmt] fallback empty:", err?.message);
emptyList(res);
@@ -155,65 +171,59 @@ router.get("/incoming-mgmt", async (req: Request, res: Response) => {
});
// ─── 3. 공정검사 관리 ──────────────────────────────────────────
// 마스터별로 디테일 N건의 inspection_qty / defect_qty 합계를 집계해 1행 반환.
// wace quality.xml getProcessInspectionList — 마스터별 디테일 SUM.
router.get("/process-inspection", async (req: Request, res: Response) => {
try {
const pool = getPool();
const { project_no, productType, part_name, inspector_id, from, to } = req.query as Record<string, string>;
const q = req.query as Record<string, string>;
const where: string[] = ["1=1"];
const params: any[] = [];
if (project_no) {
params.push(`%${project_no}%`);
where.push(`EXISTS (SELECT 1 FROM project_mgmt pm WHERE pm.objid = pid.project_objid AND pm.project_no ILIKE $${params.length})`);
}
if (part_name) {
params.push(`%${part_name}%`);
where.push(`pid.part_name ILIKE $${params.length}`);
}
if (inspector_id) {
params.push(inspector_id);
where.push(`pim.inspector_id = $${params.length}`);
}
if (from) {
params.push(from);
where.push(`pim.inspection_date >= $${params.length}`);
}
if (to) {
params.push(to);
where.push(`pim.inspection_date <= $${params.length}`);
}
const add = (sql: string, ...v: any[]) => { v.forEach(x => params.push(x)); where.push(sql.replace(/\?/g, () => "$" + params.length)); };
if (q.search_project_no) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 LEFT JOIN project_mgmt pj ON pj.objid = pid2.project_objid WHERE pid2.master_objid = pim.objid AND COALESCE(pj.project_no, '') ILIKE ?)", `%${q.search_project_no}%`);
if (q.productType) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 LEFT JOIN project_mgmt pj ON pj.objid = pid2.project_objid WHERE pid2.master_objid = pim.objid AND pj.product = ?)", q.productType);
if (q.search_part_no) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 WHERE pid2.master_objid = pim.objid AND pid2.part_no ILIKE ?)", `%${q.search_part_no}%`);
if (q.search_part_name) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 WHERE pid2.master_objid = pim.objid AND pid2.part_name ILIKE ?)", `%${q.search_part_name}%`);
if (q.search_work_env_status) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 WHERE pid2.master_objid = pim.objid AND pid2.work_env_status = ?)", q.search_work_env_status);
if (q.search_measuring_device) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 WHERE pid2.master_objid = pim.objid AND pid2.measuring_device = ?)", q.search_measuring_device);
if (q.search_inspector) add("pim.inspector_id = ?", q.search_inspector);
if (q.search_inspection_date_from) add("pim.inspection_date >= ?", q.search_inspection_date_from);
if (q.search_inspection_date_to) add("pim.inspection_date <= ?", q.search_inspection_date_to);
if (q.search_inspection_result) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 WHERE pid2.master_objid = pim.objid AND pid2.inspection_result = ?)", q.search_inspection_result);
if (q.search_process_cd) add("EXISTS (SELECT 1 FROM process_inspection_detail pid2 WHERE pid2.master_objid = pim.objid AND pid2.process_cd = ?)", q.search_process_cd);
const sql = `
SELECT
pim.objid::text AS objid,
TO_CHAR(pim.inspection_date, 'YYYY-MM-DD') AS inspection_date,
COALESCE(ui.user_name, '') AS inspector_name,
COALESCE(MIN(pm.project_no), '') AS project_no,
'' AS product_name,
COALESCE(MIN(pj.project_no), '') AS project_no,
COALESCE(MIN(ci.code_name), '') AS product_name,
COALESCE(MIN(pid.part_no), '') AS part_no,
COALESCE(MIN(pid.part_name), '') AS part_name,
COALESCE(SUM(pid.inspection_qty), 0) AS inspection_qty,
COALESCE(SUM(pid.defect_qty), 0) AS defect_qty,
COALESCE(MAX(pid.work_env_status), '') AS work_env_status,
COALESCE(MAX(pid.measuring_device), '') AS measuring_device,
COALESCE(MAX(pid.inspection_result), '') AS inspection_result,
0 AS file_count
CASE
WHEN SUM(pid.defect_qty) > 0 THEN 'NG'
WHEN COUNT(pid.objid) > 0 THEN 'OK'
ELSE ''
END AS inspection_result,
0 AS process_inspection_file_cnt
FROM process_inspection_master pim
LEFT JOIN process_inspection_detail pid ON pid.master_objid = pim.objid
LEFT JOIN project_mgmt pm ON pm.objid = pid.project_objid
LEFT JOIN user_info ui ON ui.user_id = pim.inspector_id
LEFT JOIN project_mgmt pj ON pj.objid = pid.project_objid
LEFT JOIN code_info ci ON ci.objid::text = pj.product
LEFT JOIN user_info ui ON ui.user_id = pim.inspector_id
WHERE ${where.join(" AND ")}
GROUP BY pim.objid, pim.inspection_date, ui.user_name
ORDER BY pim.inspection_date DESC NULLS LAST, pim.objid DESC
LIMIT 500
`;
const r = await pool.query(sql, params);
res.json({
success: true,
list: r.rows,
pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 },
});
res.json({ success: true, list: r.rows, pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 } });
} catch (err: any) {
console.warn("[quality/process-inspection] fallback empty:", err?.message);
emptyList(res);
@@ -221,47 +231,34 @@ router.get("/process-inspection", async (req: Request, res: Response) => {
});
// ─── 4. 반제품검사 관리 ────────────────────────────────────────
// DATA_TYPE='GOOD' 행을 마스터로 보고, 동일 inspection_group_id 의 'DEFECT' 행을
// SUM 으로 묶어 불량/재생/최종양품 수량을 집계.
// wace quality.xml getSemiProductInspectionList — DATA_TYPE='GOOD' 마스터,
// 동일 INSPECTION_GROUP_ID 의 'DEFECT' 행 SUM 으로 불량/재생/최종양품 산정.
router.get("/semi-product-inspection", async (req: Request, res: Response) => {
try {
const pool = getPool();
const { model_name, part_no, part_name, writer, from, to } = req.query as Record<string, string>;
const q = req.query as Record<string, string>;
const where: string[] = ["g.data_type IS NULL OR g.data_type = 'GOOD'"];
const where: string[] = ["g.data_type = 'GOOD'"];
const params: any[] = [];
if (model_name) {
params.push(`%${model_name}%`);
where.push(`g.model_name ILIKE $${params.length}`);
}
if (part_no) {
params.push(`%${part_no}%`);
where.push(`g.part_no ILIKE $${params.length}`);
}
if (part_name) {
params.push(`%${part_name}%`);
where.push(`g.part_name ILIKE $${params.length}`);
}
if (writer) {
params.push(writer);
where.push(`g.writer = $${params.length}`);
}
if (from) {
params.push(from);
where.push(`g.inspection_date >= $${params.length}`);
}
if (to) {
params.push(to);
where.push(`g.inspection_date <= $${params.length}`);
}
const add = (sql: string, ...v: any[]) => { v.forEach(x => params.push(x)); where.push(sql.replace(/\?/g, () => "$" + params.length)); };
if (q.search_model_name) add("COALESCE(g.model_name, '') ILIKE ?", `%${q.search_model_name}%`);
if (q.search_work_order_no) add("COALESCE(g.work_order_no, '') ILIKE ?", `%${q.search_work_order_no}%`);
if (q.search_part_no) add("COALESCE(g.part_no, '') ILIKE ?", `%${q.search_part_no}%`);
if (q.search_part_name) add("COALESCE(g.part_name, '') ILIKE ?", `%${q.search_part_name}%`);
if (q.inspection_start_date) add("COALESCE(g.inspection_date, g.reg_date::date) >= ?", q.inspection_start_date);
if (q.inspection_end_date) add("COALESCE(g.inspection_date, g.reg_date::date) <= ?", q.inspection_end_date);
if (q.search_writer) add("g.writer = ?", q.search_writer);
if (q.search_defect_type) add("EXISTS (SELECT 1 FROM pms_quality_semi_product_inspection d2 WHERE d2.inspection_group_id = g.inspection_group_id AND d2.data_type = 'DEFECT' AND d2.defect_type = ?)", q.search_defect_type);
if (q.search_responsible_dept) add("EXISTS (SELECT 1 FROM pms_quality_semi_product_inspection d2 WHERE d2.inspection_group_id = g.inspection_group_id AND d2.data_type = 'DEFECT' AND d2.responsible_dept = ?)", q.search_responsible_dept);
const sql = `
SELECT
g.objid::text AS objid,
TO_CHAR(g.inspection_date, 'YYYY-MM-DD') AS inspection_date,
COALESCE(ui.user_name, g.writer, '') AS writer_name,
COALESCE(g.model_name, '') AS model_name,
COALESCE(g.product_type, '') AS product_type,
COALESCE(g.model_name, '') AS model_name,
COALESCE(g.work_order_no, '') AS work_order_no,
COALESCE(g.part_no, '') AS part_no,
COALESCE(g.part_name, '') AS part_name,
@@ -270,7 +267,7 @@ router.get("/semi-product-inspection", async (req: Request, res: Response) => {
COALESCE(d.defect_qty_sum, 0) AS defective_qty,
CASE
WHEN COALESCE(g.receipt_qty, 0) = 0 THEN 0
ELSE ROUND( COALESCE(d.defect_qty_sum, 0) / g.receipt_qty * 100, 2 )
ELSE ROUND(COALESCE(d.defect_qty_sum, 0) / g.receipt_qty * 100, 2)
END AS defect_rate,
COALESCE(d.regen_qty_sum, 0) AS regeneration_qty,
COALESCE(g.good_qty, 0) + COALESCE(d.regen_qty_sum, 0) AS final_good_qty
@@ -284,15 +281,11 @@ router.get("/semi-product-inspection", async (req: Request, res: Response) => {
) d ON true
LEFT JOIN user_info ui ON ui.user_id = g.writer
WHERE ${where.join(" AND ")}
ORDER BY g.inspection_date DESC NULLS LAST, g.objid DESC
ORDER BY COALESCE(g.inspection_date, g.reg_date::date) DESC NULLS LAST, g.objid DESC
LIMIT 500
`;
const r = await pool.query(sql, params);
res.json({
success: true,
list: r.rows,
pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 },
});
res.json({ success: true, list: r.rows, pagination: { page: 1, pageSize: 500, total: r.rows.length, totalPages: 1 } });
} catch (err: any) {
console.warn("[quality/semi-product-inspection] fallback empty:", err?.message);
emptyList(res);