Files
vexplor/backend-node/src/controllers/itemInspectionController.ts
kjs 9f9be20e34 Enhance Item Inspection and Outbound Functionality
- Added `apply_process_name` to the item inspection controller, allowing for better clarity in process identification by joining with the `process_mng` table.
- Updated the outbound controller to include additional delivery details, such as `delivery_destination_name` and `customer_name`, with fallback logic for improved data accuracy.
- Enhanced the query logic to ensure proper handling of delivery addresses and customer information, improving the overall data retrieval process.

(TASK: ERP-XXX)
2026-05-22 09:59:20 +09:00

156 lines
5.2 KiB
TypeScript

/**
* 품목검사정보 컨트롤러
* - item_code 단위로 GROUP BY 한 후 페이징하여 좌측 품목 패널 데이터 제공
*/
import { Response } from "express";
import { AuthenticatedRequest } from "../types/auth";
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
interface InspectionRow {
id: string;
item_code: string;
item_name: string;
inspection_type: string | null;
inspection_standard: string | null;
inspection_standard_id: string | null;
inspection_item_name: string | null;
inspection_method: string | null;
apply_process: string | null;
apply_process_name: string | null;
classification: string | null;
pass_criteria: string | null;
upper_limit: string | null;
lower_limit: string | null;
is_required: string | null;
is_active: string | null;
manager_id: string | null;
memo: string | null;
sort_order: string | null;
change_record: string | null;
created_date: string | null;
updated_date: string | null;
}
export async function getGroupedList(req: AuthenticatedRequest, res: Response) {
try {
const companyCode = req.user!.companyCode;
const search = (req.query.search as string | undefined)?.trim() || "";
const page = Math.max(1, parseInt(String(req.query.page ?? "1"), 10) || 1);
const size = Math.max(1, Math.min(200, parseInt(String(req.query.size ?? "20"), 10) || 20));
const pool = getPool();
const searchPattern = search ? `%${search}%` : null;
// 1) total: DISTINCT item_code 수
const countQuery = `
SELECT COUNT(*)::int AS total FROM (
SELECT item_code
FROM item_inspection_info
WHERE company_code = $1
AND ($2::text IS NULL OR item_code ILIKE $2 OR item_name ILIKE $2)
GROUP BY item_code
) g;
`;
const countRes = await pool.query(countQuery, [companyCode, searchPattern]);
const total: number = countRes.rows[0]?.total ?? 0;
if (total === 0) {
return res.json({
success: true,
groups: [],
total: 0,
page,
size,
totalPages: 1,
});
}
// 2) 현재 페이지의 item_code 목록 (그룹 메타 포함)
const offset = (page - 1) * size;
const groupQuery = `
SELECT
item_code,
MIN(item_name) AS item_name,
MIN(is_active) AS is_active
FROM item_inspection_info
WHERE company_code = $1
AND ($2::text IS NULL OR item_code ILIKE $2 OR item_name ILIKE $2)
GROUP BY item_code
ORDER BY MIN(item_name) ASC NULLS LAST, item_code ASC
LIMIT $3 OFFSET $4;
`;
const groupRes = await pool.query(groupQuery, [companyCode, searchPattern, size, offset]);
const pageItems: { item_code: string; item_name: string | null; is_active: string | null }[] = groupRes.rows;
const itemCodes = pageItems.map((r) => r.item_code).filter(Boolean);
if (itemCodes.length === 0) {
return res.json({
success: true,
groups: [],
total,
page,
size,
totalPages: Math.max(1, Math.ceil(total / size)),
});
}
// 3) 페이지 item_code들의 모든 검사항목 row
// 적용공정(apply_process)은 공정 코드(P001 등)이므로 process_mng JOIN으로 공정명 해석.
// 공정 매핑이 깨졌거나 코드가 없으면 apply_process_name 은 NULL → 프론트에서 코드 fallback.
const detailQuery = `
SELECT
iii.id, iii.item_code, iii.item_name, iii.inspection_type, iii.inspection_standard,
iii.inspection_standard_id, iii.inspection_item_name, iii.inspection_method,
iii.apply_process,
pm.process_name AS apply_process_name,
iii.classification,
iii.pass_criteria, iii.upper_limit, iii.lower_limit, iii.is_required, iii.is_active,
iii.manager_id, iii.memo, iii.sort_order, iii.change_record,
iii.created_date, iii.updated_date
FROM item_inspection_info iii
LEFT JOIN process_mng pm
ON pm.process_code = iii.apply_process
AND pm.company_code = iii.company_code
WHERE iii.company_code = $1
AND iii.item_code = ANY($2::text[])
ORDER BY iii.item_code, iii.sort_order, iii.created_date;
`;
const detailRes = await pool.query(detailQuery, [companyCode, itemCodes]);
const rowsByItem: Record<string, InspectionRow[]> = {};
for (const row of detailRes.rows as InspectionRow[]) {
const key = row.item_code || "";
if (!rowsByItem[key]) rowsByItem[key] = [];
rowsByItem[key].push(row);
}
const groups = pageItems.map((g) => ({
item_code: g.item_code,
item_name: g.item_name || "",
is_active: g.is_active || "",
rows: rowsByItem[g.item_code] || [],
}));
logger.info("품목검사정보 그룹 페이징 조회", {
companyCode,
page,
size,
total,
groupCount: groups.length,
});
return res.json({
success: true,
groups,
total,
page,
size,
totalPages: Math.max(1, Math.ceil(total / size)),
});
} catch (error: any) {
logger.error("품목검사정보 그룹 페이징 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });
}
}