Implement server-side pagination for order summaries, plans, and shipping orders

- Added pagination parameters (page and size) to the getOrderSummary and getPlans functions in the productionController.
- Updated the getList function in shippingOrderController and shippingPlanController to support server-side pagination.
- Modified frontend components to handle pagination state and display total counts for orders and plans.
- Ensured compatibility with existing functionality by maintaining behavior when pagination is not used.
This commit is contained in:
kjs
2026-04-28 13:59:34 +09:00
parent 8bfa1f9838
commit 4afb0f5ca4
39 changed files with 1158 additions and 330 deletions

View File

@@ -14,13 +14,23 @@ export async function getOrderSummary(req: AuthenticatedRequest, res: Response)
const companyCode = req.user!.companyCode;
const { excludePlanned, itemCode, itemName } = req.query;
const data = await productionService.getOrderSummary(companyCode, {
// 서버 페이징 (size 미지정 시 기존 동작 유지: 전체 반환)
const page = parseInt(String(req.query.page ?? "1"), 10) || 1;
const size = parseInt(String(req.query.size ?? "0"), 10) || 0;
const result = await productionService.getOrderSummary(companyCode, {
excludePlanned: excludePlanned === "true",
itemCode: itemCode as string,
itemName: itemName as string,
page,
size,
});
return res.json({ success: true, data });
// 페이징 사용 시 result는 { data, total, page, size, totalPages } 객체
if (size > 0 && !Array.isArray(result)) {
return res.json({ success: true, ...result });
}
return res.json({ success: true, data: result });
} catch (error: any) {
logger.error("수주 데이터 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });
@@ -47,15 +57,23 @@ export async function getPlans(req: AuthenticatedRequest, res: Response) {
const companyCode = req.user!.companyCode;
const { productType, status, startDate, endDate, itemCode } = req.query;
const data = await productionService.getPlans(companyCode, {
const page = parseInt(String(req.query.page ?? "1"), 10) || 1;
const size = parseInt(String(req.query.size ?? "0"), 10) || 0;
const result = await productionService.getPlans(companyCode, {
productType: productType as string,
status: status as string,
startDate: startDate as string,
endDate: endDate as string,
itemCode: itemCode as string,
page,
size,
});
return res.json({ success: true, data });
if (size > 0 && !Array.isArray(result)) {
return res.json({ success: true, ...result });
}
return res.json({ success: true, data: result });
} catch (error: any) {
logger.error("생산계획 목록 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });

View File

@@ -13,6 +13,11 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
const companyCode = req.user!.companyCode;
const { dateFrom, dateTo, status, customer, keyword } = req.query;
// 서버 페이징 파라미터 (없으면 기존 동작 유지: 전체 조회)
const page = parseInt(String(req.query.page ?? "1"), 10) || 1;
const size = parseInt(String(req.query.size ?? "0"), 10) || 0;
const usePaging = size > 0;
const conditions: string[] = [];
const params: any[] = [];
let idx = 1;
@@ -89,10 +94,41 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
`;
const pool = getPool();
if (usePaging) {
// total 카운트 — JOIN/GROUP 없이 si 기준 distinct count
const countQuery = `
SELECT COUNT(DISTINCT si.id)::int AS total
FROM shipment_instruction si
LEFT JOIN customer_mng c
ON si.partner_id = c.customer_code AND si.company_code = c.company_code
${where}
`;
const countResult = await pool.query(countQuery, params);
const total = countResult.rows[0]?.total ?? 0;
const offset = (page - 1) * size;
const pagedQuery = `${query} LIMIT $${idx} OFFSET $${idx + 1}`;
const pagedResult = await pool.query(pagedQuery, [...params, size, offset]);
logger.info("출하지시 목록 조회 (페이징)", {
companyCode, page, size, total, count: pagedResult.rowCount,
});
return res.json({
success: true,
data: pagedResult.rows,
total,
page,
size,
totalPages: Math.max(1, Math.ceil(total / size)),
});
}
const result = await pool.query(query, params);
logger.info("출하지시 목록 조회", { companyCode, count: result.rowCount });
return res.json({ success: true, data: result.rows });
return res.json({ success: true, data: result.rows, total: result.rowCount });
} catch (error: any) {
logger.error("출하지시 목록 조회 실패", { error: error.message });
return res.status(500).json({ success: false, message: error.message });

View File

@@ -151,6 +151,11 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
const companyCode = req.user!.companyCode;
const { dateFrom, dateTo, status, customer, keyword } = req.query;
// 서버 페이징 파라미터 (없으면 기존 동작 유지: 전체 조회)
const page = parseInt(String(req.query.page ?? "1"), 10) || 1;
const size = parseInt(String(req.query.size ?? "0"), 10) || 0;
const usePaging = size > 0;
const conditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;
@@ -239,6 +244,53 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
`;
const pool = getPool();
// 서버 페이징 적용 시: COUNT + LIMIT/OFFSET
if (usePaging) {
const countQuery = `
SELECT COUNT(*)::int AS total
FROM shipment_plan sp
LEFT JOIN sales_order_detail d
ON sp.detail_id = d.id AND sp.company_code = d.company_code
LEFT JOIN sales_order_mng m
ON sp.sales_order_id = m.id AND sp.company_code = m.company_code
LEFT JOIN LATERAL (
SELECT item_name FROM item_info
WHERE item_number = COALESCE(d.part_code, m.part_code)
AND company_code = sp.company_code
LIMIT 1
) i ON true
LEFT JOIN customer_mng c
ON COALESCE(NULLIF(m.partner_id, ''), NULLIF(d.delivery_partner_code, '')) = c.customer_code
AND sp.company_code = c.company_code
${whereClause}
`;
const countResult = await pool.query(countQuery, params);
const total = countResult.rows[0]?.total ?? 0;
const offset = (page - 1) * size;
const pagedQuery = `${query} LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
const pagedResult = await pool.query(pagedQuery, [...params, size, offset]);
logger.info("출하계획 목록 조회 (페이징)", {
companyCode,
page,
size,
total,
rowCount: pagedResult.rowCount,
});
return res.json({
success: true,
data: pagedResult.rows,
total,
page,
size,
totalPages: Math.max(1, Math.ceil(total / size)),
});
}
// 페이징 미사용: 기존 동작 (전체 조회)
const result = await pool.query(query, params);
logger.info("출하계획 목록 조회", {
@@ -246,7 +298,7 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
rowCount: result.rowCount,
});
return res.json({ success: true, data: result.rows });
return res.json({ success: true, data: result.rows, total: result.rowCount });
} catch (error: any) {
logger.error("출하계획 목록 조회 실패", {
error: error.message,