Files
vexplor/backend-node/src/controllers/itemInspectionController.ts

144 lines
4.5 KiB
TypeScript
Raw Normal View History

/**
*
* - 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;
classification: string | null;
pass_criteria: 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
const detailQuery = `
SELECT
id, item_code, item_name, inspection_type, inspection_standard, inspection_standard_id,
inspection_item_name, inspection_method, apply_process, classification,
pass_criteria, is_required, is_active, manager_id, memo,
sort_order, change_record, created_date, updated_date
FROM item_inspection_info
WHERE company_code = $1
AND item_code = ANY($2::text[])
ORDER BY item_code, sort_order, 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 });
}
}