Преглед на файлове

chart improve

production
CANCERYS\kw093 преди 1 месец
родител
ревизия
b8c608c5b4
променени са 6 файла, в които са добавени 178 реда и са изтрити 103 реда
  1. +141
    -103
      src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt
  2. +6
    -0
      src/main/resources/db/changelog/changes/20260516_Enson/02_setting.sql
  3. +8
    -0
      src/main/resources/db/changelog/changes/20260517_Enson/02_setting.sql
  4. +6
    -0
      src/main/resources/db/changelog/changes/20260517_Enson/03_setting.sql
  5. +12
    -0
      src/main/resources/db/changelog/changes/20260519_01_Enson/01_chart_perf_indexes.sql
  6. +5
    -0
      src/main/resources/db/changelog/changes/20260519_01_Enson/02_chart_perf_indexes.sql

+ 141
- 103
src/main/java/com/ffii/fpsms/modules/chart/service/ChartService.kt Целия файл

@@ -3,6 +3,7 @@ package com.ffii.fpsms.modules.chart.service
import com.ffii.core.support.JdbcDao
import org.springframework.stereotype.Service
import java.time.LocalDate
import java.time.LocalDateTime

@Service
open class ChartService(
@@ -15,25 +16,18 @@ open class ChartService(
*/
fun getStockTransactionsByDate(startDate: LocalDate?, endDate: LocalDate?): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val startSql = if (startDate != null) {
args["startDate"] = startDate.toString()
"AND DATE(sl.date) >= :startDate"
} else ""
val endSql = if (endDate != null) {
args["endDate"] = endDate.toString()
"AND DATE(sl.date) <= :endDate"
} else ""
val rangeSql = ledgerDateTimeRangeSql(args, "sl.date", startDate, endDate)
val sql = """
SELECT
DATE_FORMAT(sl.date, '%Y-%m-%d') AS date,
COALESCE(SUM(sl.inQty), 0) AS inQty,
COALESCE(SUM(sl.outQty), 0) AS outQty,
COALESCE(SUM(COALESCE(sl.inQty, 0) + COALESCE(sl.outQty, 0)), 0) AS totalQty
FROM stock_ledger sl
FROM stock_ledger sl FORCE INDEX (idx_sl_deleted_date)
WHERE sl.deleted = 0 AND sl.date IS NOT NULL
$startSql $endSql
GROUP BY sl.date
ORDER BY sl.date
$rangeSql
GROUP BY DATE_FORMAT(sl.date, '%Y-%m-%d')
ORDER BY date
""".trimIndent()
return jdbcDao.queryForList(sql, args)
}
@@ -45,14 +39,7 @@ open class ChartService(
*/
fun getDeliveryOrderByDate(startDate: LocalDate?, endDate: LocalDate?): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val startSql = if (startDate != null) {
args["startDate"] = startDate.toString()
"AND DATE(do.estimatedArrivalDate) >= :startDate"
} else ""
val endSql = if (endDate != null) {
args["endDate"] = endDate.toString()
"AND DATE(do.estimatedArrivalDate) <= :endDate"
} else ""
val rangeSql = localDateRangeSql(args, "do.estimatedArrivalDate", startDate, endDate)
val sql = """
SELECT
DATE_FORMAT(do.estimatedArrivalDate, '%Y-%m-%d') AS date,
@@ -60,8 +47,9 @@ open class ChartService(
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 AND do.estimatedArrivalDate IS NOT NULL $startSql $endSql
GROUP BY DATE(do.estimatedArrivalDate)
WHERE do.deleted = 0 AND do.estimatedArrivalDate IS NOT NULL
$rangeSql
GROUP BY DATE_FORMAT(do.estimatedArrivalDate, '%Y-%m-%d')
ORDER BY date
""".trimIndent()
return jdbcDao.queryForList(sql, args)
@@ -536,46 +524,39 @@ open class ChartService(
*/
fun getStockInOutByDate(startDate: LocalDate?, endDate: LocalDate?): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
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 rangeStart = startDate?.atStartOfDay()
val rangeEndExclusive = endDate?.plusDays(1)?.atStartOfDay()
if (rangeStart != null) args["inOutRangeStart"] = rangeStart
if (rangeEndExclusive != null) args["inOutRangeEndExclusive"] = rangeEndExclusive
val inDateFilter = stockInOutCoalescedDateRangeSql(
"COALESCE(si.completeDate, sil.receiptDate, si.created)",
rangeStart,
rangeEndExclusive,
)
val outDateFilter = stockInOutCoalescedDateRangeSql(
"COALESCE(so.completeDate, so.created)",
rangeStart,
rangeEndExclusive,
)
val sql = """
SELECT DATE_FORMAT(u.dt, '%Y-%m-%d') AS date,
SELECT u.dt AS date,
COALESCE(SUM(u.inQty), 0) AS inQty,
COALESCE(SUM(u.outQty), 0) AS outQty
FROM (
SELECT DATE(COALESCE(si.completeDate, sil.receiptDate, si.created)) AS dt,
SELECT DATE_FORMAT(COALESCE(si.completeDate, sil.receiptDate, si.created), '%Y-%m-%d') AS dt,
SUM(COALESCE(sil.acceptedQty, 0)) AS inQty, 0 AS outQty
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))
GROUP BY DATE_FORMAT(COALESCE(si.completeDate, sil.receiptDate, si.created), '%Y-%m-%d')
UNION ALL
SELECT DATE(COALESCE(so.completeDate, so.created)) AS dt,
SELECT DATE_FORMAT(COALESCE(so.completeDate, so.created), '%Y-%m-%d') AS dt,
0 AS inQty, SUM(COALESCE(sol.qty, 0)) AS outQty
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))
GROUP BY DATE_FORMAT(COALESCE(so.completeDate, so.created), '%Y-%m-%d')
) u
WHERE 1=1 $startSql $endSql
GROUP BY u.dt
ORDER BY u.dt
""".trimIndent()
@@ -589,20 +570,14 @@ open class ChartService(
*/
fun getTopDeliveryItemsItemOptions(startDate: LocalDate?, endDate: LocalDate?): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val startSql = if (startDate != null) {
args["startDate"] = startDate.toString()
"AND DATE(do.estimatedArrivalDate) >= :startDate"
} else ""
val endSql = if (endDate != null) {
args["endDate"] = endDate.toString()
"AND DATE(do.estimatedArrivalDate) <= :endDate"
} else ""
val rangeSql = localDateRangeSql(args, "do.estimatedArrivalDate", startDate, endDate)
val sql = """
SELECT DISTINCT it.code AS itemCode, COALESCE(it.name, '') AS itemName
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
WHERE do.deleted = 0 AND do.estimatedArrivalDate IS NOT NULL
$rangeSql
ORDER BY it.code
""".trimIndent()
return jdbcDao.queryForList(sql, args)
@@ -620,14 +595,7 @@ open class ChartService(
itemCodes: List<String>?
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>("limit" to limit)
val startSql = if (startDate != null) {
args["startDate"] = startDate.toString()
"AND DATE(do.estimatedArrivalDate) >= :startDate"
} else ""
val endSql = if (endDate != null) {
args["endDate"] = endDate.toString()
"AND DATE(do.estimatedArrivalDate) <= :endDate"
} else ""
val rangeSql = localDateRangeSql(args, "do.estimatedArrivalDate", startDate, endDate)
val itemSql = if (!itemCodes.isNullOrEmpty()) {
val codes = itemCodes.map { it.trim() }.filter { it.isNotBlank() }
if (codes.isEmpty()) "" else {
@@ -643,7 +611,8 @@ open class ChartService(
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
WHERE do.deleted = 0 AND do.estimatedArrivalDate IS NOT NULL
$rangeSql $itemSql
GROUP BY dol.itemId, it.code, it.name
ORDER BY totalQty DESC
LIMIT :limit
@@ -661,26 +630,26 @@ open class ChartService(
itemCode: String?
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val startSql = if (startDate != null) {
args["startDate"] = startDate.toString()
"AND sl.date >= :startDate"
} else ""
val endSql = if (endDate != null) {
args["endDate"] = endDate.toString()
"AND sl.date <= :endDate"
} else ""
val itemSql = if (!itemCode.isNullOrBlank()) {
val rangeSql = ledgerDateTimeRangeSql(args, "sl.date", startDate, endDate)
val hasItemFilter = !itemCode.isNullOrBlank()
if (hasItemFilter) {
args["itemCode"] = "%$itemCode%"
"AND sl.itemCode LIKE :itemCode"
} else ""
}
val itemSql = if (hasItemFilter) "AND sl.itemCode LIKE :itemCode" else ""
val fromClause = if (hasItemFilter) {
"FROM stock_ledger sl"
} else {
"FROM stock_ledger sl FORCE INDEX (idx_sl_deleted_date)"
}
val sql = """
SELECT
DATE_FORMAT(sl.date, '%Y-%m-%d') AS date,
COALESCE(SUM(sl.balance), 0) AS balance
FROM stock_ledger sl
WHERE sl.deleted = 0 AND sl.date IS NOT NULL $startSql $endSql $itemSql
GROUP BY sl.date
ORDER BY sl.date
$fromClause
WHERE sl.deleted = 0 AND sl.date IS NOT NULL
$rangeSql $itemSql
GROUP BY DATE_FORMAT(sl.date, '%Y-%m-%d')
ORDER BY date
""".trimIndent()
return jdbcDao.queryForList(sql, args)
}
@@ -697,27 +666,35 @@ open class ChartService(
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val yearSql = if (year != null) {
args["year"] = year
"AND YEAR(sl.date) = :year"
} else ""
val startSql = if (startDate != null) {
args["startDate"] = startDate.toString()
"AND sl.date >= :startDate"
} else ""
val endSql = if (endDate != null) {
args["endDate"] = endDate.toString()
"AND sl.date <= :endDate"
args["consumptionYearStart"] = LocalDate.of(year, 1, 1).atStartOfDay()
args["consumptionYearEndExclusive"] = LocalDate.of(year + 1, 1, 1).atStartOfDay()
"AND sl.date >= :consumptionYearStart AND sl.date < :consumptionYearEndExclusive"
} else ""
val itemSql = if (!itemCode.isNullOrBlank()) {
val rangeSql = ledgerDateTimeRangeSql(
args,
"sl.date",
startDate,
endDate,
startArg = "consumptionRangeStart",
endArg = "consumptionRangeEndExclusive",
)
val hasItemFilter = !itemCode.isNullOrBlank()
if (hasItemFilter) {
args["itemCode"] = "%$itemCode%"
"AND sl.itemCode LIKE :itemCode"
} else ""
}
val itemSql = if (hasItemFilter) "AND sl.itemCode LIKE :itemCode" else ""
val fromClause = if (hasItemFilter) {
"FROM stock_ledger sl"
} else {
"FROM stock_ledger sl FORCE INDEX (idx_sl_deleted_date)"
}
val sql = """
SELECT
DATE_FORMAT(sl.date, '%Y-%m') AS month,
COALESCE(SUM(sl.outQty), 0) AS outQty
FROM stock_ledger sl
WHERE sl.deleted = 0 AND sl.date IS NOT NULL $yearSql $startSql $endSql $itemSql
$fromClause
WHERE sl.deleted = 0 AND sl.date IS NOT NULL
$yearSql $rangeSql $itemSql
GROUP BY DATE_FORMAT(sl.date, '%Y-%m')
ORDER BY month
""".trimIndent()
@@ -746,6 +723,8 @@ open class ChartService(
* staffNos: when non-empty, filter to these staff by user.staffNo (multi-select).
* storeIdNull: when true, only rows with dop.storeId IS NULL (takes precedence over storeId).
* storeId: when non-blank and storeIdNull is not true, filter dop.storeId equality (trimmed).
* When no store filter, FORCE INDEX (idx_dopo_staff_perf_complete) so the optimizer uses a
* ticketCompleteDateTime range scan instead of a less selective store composite index.
*/
fun getStaffDeliveryPerformance(
startDate: LocalDate?,
@@ -756,12 +735,12 @@ open class ChartService(
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
val startSql = if (startDate != null) {
args["startDate"] = startDate.toString()
"AND DATE(dop.ticketCompleteDateTime) >= :startDate"
args["startDate"] = startDate.atStartOfDay()
"AND dop.ticketCompleteDateTime >= :startDate"
} else ""
val endSql = if (endDate != null) {
args["endDate"] = endDate.toString()
"AND DATE(dop.ticketCompleteDateTime) <= :endDate"
args["endExclusive"] = endDate.plusDays(1).atStartOfDay()
"AND dop.ticketCompleteDateTime < :endExclusive"
} else ""
val staffSql = if (!staffNos.isNullOrEmpty()) {
val nos = staffNos.map { it.trim() }.filter { it.isNotBlank() }
@@ -778,6 +757,12 @@ open class ChartService(
}
else -> ""
}
val useStoreFilter = storeIdNull == true || !storeId.isNullOrBlank()
val fromClause = if (useStoreFilter) {
"FROM delivery_order_pick_order dop"
} else {
"FROM delivery_order_pick_order dop FORCE INDEX (idx_dopo_staff_perf_complete)"
}
val sql = """
SELECT
DATE_FORMAT(dop.ticketCompleteDateTime, '%Y-%m-%d') AS date,
@@ -790,13 +775,14 @@ open class ChartService(
ELSE 0
END
), 0) AS totalMinutes
FROM delivery_order_pick_order dop
$fromClause
LEFT JOIN user u ON dop.handledBy = u.id AND u.deleted = 0
WHERE dop.deleted = 0
AND LOWER(COALESCE(dop.ticketStatus, '')) = 'completed'
AND dop.ticketStatus = 'completed'
AND dop.ticketCompleteDateTime IS NOT NULL
$startSql $endSql $staffSql $storeSql
GROUP BY DATE(dop.ticketCompleteDateTime), dop.handledBy, u.name, dop.handlerName
GROUP BY DATE_FORMAT(dop.ticketCompleteDateTime, '%Y-%m-%d'),
dop.handledBy, u.name, dop.handlerName
ORDER BY date, orderCount DESC
""".trimIndent()
return jdbcDao.queryForList(sql, args)
@@ -1604,4 +1590,56 @@ open class ChartService(
""".trimIndent()
return jdbcDao.queryForList(sql, args)
}

/** Half-open [start, end+1 day) on a DATE/DATETIME column (no DATE() wrapper). */
private fun localDateRangeSql(
args: MutableMap<String, Any>,
column: String,
startDate: LocalDate?,
endDate: LocalDate?,
startArg: String = "chartRangeStart",
endArg: String = "chartRangeEndExclusive",
): String = buildString {
if (startDate != null) {
args[startArg] = startDate
append(" AND $column >= :$startArg")
}
if (endDate != null) {
args[endArg] = endDate.plusDays(1)
append(" AND $column < :$endArg")
}
}

/** Half-open range on stock_ledger.date (DATETIME). */
private fun ledgerDateTimeRangeSql(
args: MutableMap<String, Any>,
column: String,
startDate: LocalDate?,
endDate: LocalDate?,
startArg: String = "ledgerRangeStart",
endArg: String = "ledgerRangeEndExclusive",
): String = buildString {
if (startDate != null) {
args[startArg] = startDate.atStartOfDay()
append(" AND $column >= :$startArg")
}
if (endDate != null) {
args[endArg] = endDate.plusDays(1).atStartOfDay()
append(" AND $column < :$endArg")
}
}

/** COALESCE datetime expression; args [inOutRangeStart] / [inOutRangeEndExclusive] must already be in map when non-null. */
private fun stockInOutCoalescedDateRangeSql(
coalescedExpr: String,
rangeStart: LocalDateTime?,
rangeEndExclusive: LocalDateTime?,
): String = buildString {
if (rangeStart != null) {
append(" AND $coalescedExpr >= :inOutRangeStart")
}
if (rangeEndExclusive != null) {
append(" AND $coalescedExpr < :inOutRangeEndExclusive")
}
}
}

+ 6
- 0
src/main/resources/db/changelog/changes/20260516_Enson/02_setting.sql Целия файл

@@ -0,0 +1,6 @@
--liquibase formatted sql

-- 修改 stock_ledger 表的 date 欄位為 datetime
--changeset Enson:20260516-01
ALTER TABLE `stock_ledger`
MODIFY COLUMN `date` DATE;

+ 8
- 0
src/main/resources/db/changelog/changes/20260517_Enson/02_setting.sql Целия файл

@@ -0,0 +1,8 @@
--liquibase formatted sql

-- 修改 stock_ledger 表的 date 欄位為 datetime
--changeset Enson:20260517-02
CREATE INDEX idx_dopo_staff_perf_complete
ON fpsmsdb.delivery_order_pick_order (deleted, ticketStatus, ticketCompleteDateTime);
CREATE INDEX idx_dopo_staff_perf_store_complete
ON fpsmsdb.delivery_order_pick_order (deleted, ticketStatus, storeId, ticketCompleteDateTime);

+ 6
- 0
src/main/resources/db/changelog/changes/20260517_Enson/03_setting.sql Целия файл

@@ -0,0 +1,6 @@
--liquibase formatted sql

-- 修改 stock_ledger 表的 date 欄位為 datetime
--changeset Enson:20260519-03
CREATE INDEX idx_sl_deleted_date
ON stock_ledger (deleted, date);

+ 12
- 0
src/main/resources/db/changelog/changes/20260519_01_Enson/01_chart_perf_indexes.sql Целия файл

@@ -0,0 +1,12 @@
--liquibase formatted sql

--changeset Enson:20260519-01-chart-do-eta-idx
CREATE INDEX idx_do_deleted_eta
ON delivery_order (deleted, estimatedArrivalDate);

--changeset Enson:20260519-02-chart-stock-in-out-idx
CREATE INDEX idx_si_deleted_complete
ON stock_in (deleted, completeDate);

CREATE INDEX idx_so_deleted_complete
ON stock_out (deleted, completeDate);

+ 5
- 0
src/main/resources/db/changelog/changes/20260519_01_Enson/02_chart_perf_indexes.sql Целия файл

@@ -0,0 +1,5 @@
--liquibase formatted sql

--changeset Enson:20260519-04-chart-stock-ledger-idx
CREATE INDEX idx_sl_deleted_itemcode_created
ON stock_ledger (deleted, itemCode, created);

Зареждане…
Отказ
Запис