- 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)
156 lines
5.2 KiB
TypeScript
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 });
|
|
}
|
|
}
|