feat: Implement pagination and enhanced keyword search in work instruction retrieval

- Added pagination support to the `getList` function in the work instruction controller, allowing for efficient data retrieval with `page` and `pageSize` parameters.
- Enhanced the keyword search functionality to include checks for item numbers in the work instruction details, improving search accuracy.
- Updated the frontend components to utilize the new `SmartSelect` component for supplier and partner selection, enhancing user experience.
- Adjusted the `EDataTable` component to support server-side pagination, ensuring better performance with large datasets.
This commit is contained in:
kjs
2026-04-20 14:51:32 +09:00
parent 377e3e51e8
commit 68bc857eae
19 changed files with 260 additions and 158 deletions

View File

@@ -23,7 +23,12 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
try {
await ensureDetailRoutingColumn();
const companyCode = req.user!.companyCode;
const { dateFrom, dateTo, status, progressStatus, keyword } = req.query;
const { dateFrom, dateTo, status, progressStatus, keyword, page, pageSize } = req.query;
// 페이지네이션 파라미터 파싱 (page 없으면 전체 반환 — 하위호환)
const pageNum = page ? Math.max(1, parseInt(page as string, 10) || 1) : null;
const sizeNum = pageSize ? Math.max(1, Math.min(1000, parseInt(pageSize as string, 10) || 20)) : null;
const paginated = pageNum !== null && sizeNum !== null;
const conditions: string[] = [];
const params: any[] = [];
@@ -54,14 +59,110 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
params.push(progressStatus);
idx++;
}
// keyword 검색: wi 자체 필드 + detail.item_number 존재 여부로 EXISTS
if (keyword) {
conditions.push(`(wi.work_instruction_no ILIKE $${idx} OR wi.worker ILIKE $${idx} OR COALESCE(itm.item_name,'') ILIKE $${idx} OR COALESCE(d.item_number,'') ILIKE $${idx})`);
conditions.push(`(
wi.work_instruction_no ILIKE $${idx}
OR wi.worker ILIKE $${idx}
OR EXISTS (
SELECT 1 FROM work_instruction_detail dd
LEFT JOIN item_info ii ON ii.item_number = dd.item_number AND ii.company_code = wi.company_code
WHERE dd.work_instruction_id = wi.id
AND (dd.item_number ILIKE $${idx} OR COALESCE(ii.item_name,'') ILIKE $${idx})
)
)`);
params.push(`%${keyword}%`);
idx++;
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
const pool = getPool();
// 페이지네이션 모드: WI 단위로 페이지 잘라낸 뒤 detail과 JOIN
if (paginated) {
// 1) 총 WI 개수 카운트
const countSql = `
SELECT COUNT(*)::int AS cnt
FROM work_instruction wi
${whereClause}
`;
const countRes = await pool.query(countSql, params);
const totalCount = countRes.rows[0]?.cnt ?? 0;
// 2) 현재 페이지 WI id 목록
const offset = (pageNum! - 1) * sizeNum!;
const pageSql = `
SELECT wi.id
FROM work_instruction wi
${whereClause}
ORDER BY wi.created_date DESC, wi.id DESC
LIMIT ${sizeNum} OFFSET ${offset}
`;
const pageRes = await pool.query(pageSql, params);
const wiIds = pageRes.rows.map((r) => r.id);
if (wiIds.length === 0) {
return res.json({ success: true, data: [], totalCount, page: pageNum, pageSize: sizeNum });
}
// 3) 해당 WI들의 detail + 품목/설비/라우팅 JOIN
const dataSql = `
SELECT
wi.id AS wi_id,
wi.work_instruction_no,
wi.status,
wi.progress_status,
wi.qty AS total_qty,
wi.completed_qty,
wi.start_date,
wi.end_date,
wi.equipment_id,
wi.work_team,
wi.worker,
wi.remark AS wi_remark,
wi.created_date,
d.id AS detail_id,
d.item_number,
d.qty AS detail_qty,
d.remark AS detail_remark,
d.part_code,
d.source_table,
d.source_id,
d.routing_version_id AS detail_routing_version_id,
COALESCE(itm.item_name, '') AS item_name,
COALESCE(itm.type, '') AS item_type,
COALESCE(itm.size, '') AS item_spec,
COALESCE(e.equipment_name, '') AS equipment_name,
COALESCE(e.equipment_code, '') AS equipment_code,
wi.routing AS routing_version_id,
COALESCE(rv.version_name, '') AS routing_name,
ROW_NUMBER() OVER (PARTITION BY wi.work_instruction_no ORDER BY d.created_date) AS detail_seq,
COUNT(*) OVER (PARTITION BY wi.work_instruction_no) AS detail_count
FROM work_instruction wi
INNER JOIN work_instruction_detail d
ON d.work_instruction_id = wi.id
LEFT JOIN item_info itm
ON itm.item_number = d.item_number AND itm.company_code = wi.company_code
LEFT JOIN equipment_mng e
ON wi.equipment_id = e.id AND wi.company_code = e.company_code
LEFT JOIN item_routing_version rv
ON wi.routing = rv.id AND rv.company_code = wi.company_code
WHERE wi.id = ANY($1::varchar[])
ORDER BY wi.created_date DESC, wi.id DESC, d.created_date ASC
`;
const dataRes = await pool.query(dataSql, [wiIds]);
return res.json({
success: true,
data: dataRes.rows,
totalCount,
page: pageNum,
pageSize: sizeNum,
});
}
// 비페이지 모드 (하위호환): 기존 방식 유지, LATERAL만 LEFT JOIN으로 교체
const query = `
SELECT
wi.id AS wi_id,
@@ -97,17 +198,14 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
FROM work_instruction wi
INNER JOIN work_instruction_detail d
ON d.work_instruction_id = wi.id
LEFT JOIN LATERAL (
SELECT item_name, size, type FROM item_info
WHERE item_number = d.item_number AND company_code = wi.company_code LIMIT 1
) itm ON true
LEFT JOIN item_info itm
ON itm.item_number = d.item_number AND itm.company_code = wi.company_code
LEFT JOIN equipment_mng e ON wi.equipment_id = e.id AND wi.company_code = e.company_code
LEFT JOIN item_routing_version rv ON wi.routing = rv.id AND rv.company_code = wi.company_code
${whereClause}
ORDER BY wi.created_date DESC, d.created_date ASC
`;
const pool = getPool();
const result = await pool.query(query, params);
return res.json({ success: true, data: result.rows });
} catch (error: any) {