From 7141c0f6b4ab32246840d5038f4ae984acd81ba3 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Thu, 14 May 2026 22:46:44 +0800 Subject: [PATCH] chart sql improt --- .../modules/chart/service/ChartService.kt | 84 ++++++++++++------- .../modules/chart/web/ChartController.kt | 6 +- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt b/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt index 90b6f5d..e46c5a4 100644 --- a/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt +++ b/src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt @@ -40,27 +40,28 @@ open class ChartService( /** * Delivery orders: order count and total line qty by date. - * Uses delivery_order.completeDate or estimatedArrivalDate for date. + * X-axis date: [delivery_order.estimatedArrivalDate] only (no completeDate/orderDate fallback). + * Rows without estimatedArrivalDate are excluded. */ fun getDeliveryOrderByDate(startDate: LocalDate?, endDate: LocalDate?): List> { val args = mutableMapOf() val startSql = if (startDate != null) { args["startDate"] = startDate.toString() - "AND DATE(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate)) >= :startDate" + "AND DATE(do.estimatedArrivalDate) >= :startDate" } else "" val endSql = if (endDate != null) { args["endDate"] = endDate.toString() - "AND DATE(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate)) <= :endDate" + "AND DATE(do.estimatedArrivalDate) <= :endDate" } else "" val sql = """ SELECT - DATE_FORMAT(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate), '%Y-%m-%d') AS date, + DATE_FORMAT(do.estimatedArrivalDate, '%Y-%m-%d') AS date, COUNT(DISTINCT do.id) AS orderCount, COALESCE(SUM(dol.qty), 0) AS totalQty FROM delivery_order do LEFT JOIN delivery_order_line dol ON dol.deliveryOrderId = do.id AND dol.deleted = 0 - WHERE do.deleted = 0 $startSql $endSql - GROUP BY DATE(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate)) + WHERE do.deleted = 0 AND do.estimatedArrivalDate IS NOT NULL $startSql $endSql + GROUP BY DATE(do.estimatedArrivalDate) ORDER BY date """.trimIndent() return jdbcDao.queryForList(sql, args) @@ -529,17 +530,32 @@ open class ChartService( * Stock in vs stock out by date. * Stock in: stock_in_line.acceptedQty, date from stock_in.completeDate or receiptDate/created. * Stock out: stock_out_line.qty, date from stock_out.completeDate or created. + * + * Date range is applied inside each UNION branch (predicate pushdown) so we do not aggregate + * all history before filtering. Reads filtered headers first via STRAIGHT_JOIN (si/so then lines). */ fun getStockInOutByDate(startDate: LocalDate?, endDate: LocalDate?): List> { val args = mutableMapOf() - val startSql = if (startDate != null) { - args["startDate"] = startDate.toString() - "AND u.dt >= :startDate" - } else "" - val endSql = if (endDate != null) { - args["endDate"] = endDate.toString() - "AND u.dt <= :endDate" - } else "" + if (startDate != null) args["startDate"] = startDate.toString() + if (endDate != null) args["endDate"] = endDate.toString() + val inDateFilter = buildString { + if (startDate != null) { + append(" AND DATE(COALESCE(si.completeDate, sil.receiptDate, si.created)) >= :startDate") + } + if (endDate != null) { + append(" AND DATE(COALESCE(si.completeDate, sil.receiptDate, si.created)) <= :endDate") + } + } + val outDateFilter = buildString { + if (startDate != null) { + append(" AND DATE(COALESCE(so.completeDate, so.created)) >= :startDate") + } + if (endDate != null) { + append(" AND DATE(COALESCE(so.completeDate, so.created)) <= :endDate") + } + } + val startSql = if (startDate != null) "AND u.dt >= :startDate" else "" + val endSql = if (endDate != null) "AND u.dt <= :endDate" else "" val sql = """ SELECT DATE_FORMAT(u.dt, '%Y-%m-%d') AS date, COALESCE(SUM(u.inQty), 0) AS inQty, @@ -547,16 +563,16 @@ open class ChartService( FROM ( SELECT DATE(COALESCE(si.completeDate, sil.receiptDate, si.created)) AS dt, SUM(COALESCE(sil.acceptedQty, 0)) AS inQty, 0 AS outQty - FROM stock_in_line sil - INNER JOIN stock_in si ON sil.stockInId = si.id AND si.deleted = 0 - WHERE sil.deleted = 0 + FROM stock_in si + STRAIGHT_JOIN stock_in_line sil ON sil.stockInId = si.id AND sil.deleted = 0 + WHERE si.deleted = 0$inDateFilter GROUP BY DATE(COALESCE(si.completeDate, sil.receiptDate, si.created)) UNION ALL SELECT DATE(COALESCE(so.completeDate, so.created)) AS dt, 0 AS inQty, SUM(COALESCE(sol.qty, 0)) AS outQty - FROM stock_out_line sol - INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0 - WHERE sol.deleted = 0 + FROM stock_out so + STRAIGHT_JOIN stock_out_line sol ON sol.stockOutId = so.id AND sol.deleted = 0 + WHERE so.deleted = 0$outDateFilter GROUP BY DATE(COALESCE(so.completeDate, so.created)) ) u WHERE 1=1 $startSql $endSql @@ -568,23 +584,25 @@ open class ChartService( /** * Distinct items that appear in delivery_order_line in the period (for multi-select options). + * Period filter: [delivery_order.estimatedArrivalDate] only; null ETA excluded. + * Uses STRAIGHT_JOIN so MySQL reads filtered `delivery_order` first (avoids full scan on `delivery_order_line`). */ fun getTopDeliveryItemsItemOptions(startDate: LocalDate?, endDate: LocalDate?): List> { val args = mutableMapOf() val startSql = if (startDate != null) { args["startDate"] = startDate.toString() - "AND DATE(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate)) >= :startDate" + "AND DATE(do.estimatedArrivalDate) >= :startDate" } else "" val endSql = if (endDate != null) { args["endDate"] = endDate.toString() - "AND DATE(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate)) <= :endDate" + "AND DATE(do.estimatedArrivalDate) <= :endDate" } else "" val sql = """ SELECT DISTINCT it.code AS itemCode, COALESCE(it.name, '') AS itemName - FROM delivery_order_line dol - INNER JOIN delivery_order do ON dol.deliveryOrderId = do.id AND do.deleted = 0 - INNER JOIN items it ON dol.itemId = it.id AND it.deleted = 0 - WHERE dol.deleted = 0 $startSql $endSql + FROM delivery_order do + STRAIGHT_JOIN delivery_order_line dol ON dol.deliveryOrderId = do.id AND dol.deleted = 0 + STRAIGHT_JOIN items it ON it.id = dol.itemId AND it.deleted = 0 + WHERE do.deleted = 0 AND do.estimatedArrivalDate IS NOT NULL $startSql $endSql ORDER BY it.code """.trimIndent() return jdbcDao.queryForList(sql, args) @@ -592,6 +610,8 @@ open class ChartService( /** * Top delivery items by total qty in the period. When itemCodes is non-empty, only those items (still ordered by totalQty, limit applied). + * Period filter: [delivery_order.estimatedArrivalDate] only; null ETA excluded. + * Uses STRAIGHT_JOIN so MySQL reads filtered `delivery_order` first (avoids full scan on `delivery_order_line`). */ fun getTopDeliveryItems( startDate: LocalDate?, @@ -602,11 +622,11 @@ open class ChartService( val args = mutableMapOf("limit" to limit) val startSql = if (startDate != null) { args["startDate"] = startDate.toString() - "AND DATE(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate)) >= :startDate" + "AND DATE(do.estimatedArrivalDate) >= :startDate" } else "" val endSql = if (endDate != null) { args["endDate"] = endDate.toString() - "AND DATE(COALESCE(do.completeDate, do.estimatedArrivalDate, do.orderDate)) <= :endDate" + "AND DATE(do.estimatedArrivalDate) <= :endDate" } else "" val itemSql = if (!itemCodes.isNullOrEmpty()) { val codes = itemCodes.map { it.trim() }.filter { it.isNotBlank() } @@ -620,10 +640,10 @@ open class ChartService( it.code AS itemCode, it.name AS itemName, SUM(COALESCE(dol.qty, 0)) AS totalQty - FROM delivery_order_line dol - INNER JOIN delivery_order do ON dol.deliveryOrderId = do.id AND do.deleted = 0 - INNER JOIN items it ON dol.itemId = it.id AND it.deleted = 0 - WHERE dol.deleted = 0 $startSql $endSql $itemSql + FROM delivery_order do + STRAIGHT_JOIN delivery_order_line dol ON dol.deliveryOrderId = do.id AND dol.deleted = 0 + STRAIGHT_JOIN items it ON it.id = dol.itemId AND it.deleted = 0 + WHERE do.deleted = 0 AND do.estimatedArrivalDate IS NOT NULL $startSql $endSql $itemSql GROUP BY dol.itemId, it.code, it.name ORDER BY totalQty DESC LIMIT :limit diff --git a/src/main/java/com/ffii/fpsms/modules/chart/web/ChartController.kt b/src/main/java/com/ffii/fpsms/modules/chart/web/ChartController.kt index 3de7d68..7d568ec 100644 --- a/src/main/java/com/ffii/fpsms/modules/chart/web/ChartController.kt +++ b/src/main/java/com/ffii/fpsms/modules/chart/web/ChartController.kt @@ -26,7 +26,7 @@ class ChartController( /** * GET /chart/delivery-order-by-date?startDate=&endDate= - * Returns [{ date, orderCount, totalQty }] + * Returns [{ date, orderCount, totalQty }]. Date axis: delivery_order.estimatedArrivalDate only (null ETA excluded). */ @GetMapping("/delivery-order-by-date") fun getDeliveryOrderByDate( @@ -129,7 +129,7 @@ class ChartController( /** * GET /chart/stock-in-out-by-date?startDate=&endDate= - * Returns [{ date, inQty, outQty }] + * Returns [{ date, inQty, outQty }]. Date range pushed into each UNION branch; si/so read before lines. */ @GetMapping("/stock-in-out-by-date") fun getStockInOutByDate( @@ -140,6 +140,7 @@ class ChartController( /** * GET /chart/top-delivery-items-item-options?startDate=&endDate= * Returns [{ itemCode, itemName }] — distinct items in delivery lines in the period (for multi-select). + * Period: delivery_order.estimatedArrivalDate only (null ETA excluded). */ @GetMapping("/top-delivery-items-item-options") fun getTopDeliveryItemsItemOptions( @@ -150,6 +151,7 @@ class ChartController( /** * GET /chart/top-delivery-items?startDate=&endDate=&limit=20&itemCode=A&itemCode=B * Returns [{ itemCode, itemName, totalQty }]. When itemCode present, only those items (still by totalQty, limit). + * Period: delivery_order.estimatedArrivalDate only (null ETA excluded). */ @GetMapping("/top-delivery-items") fun getTopDeliveryItems(