Bläddra i källkod

imporve report speed

stockTakeUpdate
CANCERYS\kw093 1 vecka sedan
förälder
incheckning
33ee152b72
2 ändrade filer med 125 tillägg och 26 borttagningar
  1. +118
    -23
      src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt
  2. +7
    -3
      src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt

+ 118
- 23
src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt Visa fil

@@ -96,6 +96,46 @@ lot_root_origin AS (
THEN CONCAT('轉倉(原:', $LOT_ROOT_CATEGORY_SQL, ')')
ELSE $LOT_ROOT_CATEGORY_SQL
END"""

/** Excel/畫面顯示用:pending→待盤點、pass→待審核、completed→已審核 */
private const val STOCK_TAKE_STATUS_LABEL_SQL = """
CASE
WHEN COALESCE(stockTakeRecordStatus, '') = 'completed' THEN '已審核'
WHEN COALESCE(stockTakeRecordStatus, '') = 'pass' THEN '待審核'
WHEN COALESCE(stockTakeRecordStatus, '') IN ('pending', 'notMatch') THEN '待盤點'
ELSE COALESCE(stockTakeRecordStatus, '')
END"""
}

/**
* 報表狀態篩選:
* - pending:待盤點(pending / notMatch)
* - pass / approving:待審核(pass)
* - completed:已審核
*/
private fun buildStockTakeRecordStatusFilterSql(status: String?): String {
val statusNormalized = status?.trim()?.lowercase().orEmpty()
return when (statusNormalized) {
"pending" -> "AND str.status IN ('pending', 'notMatch')"
"pass", "approving" -> "AND str.status = 'pass'"
"completed" -> "AND str.status = 'completed'"
else -> ""
}
}

/**
* Overall/多輪「最新一筆」用的 status 條件。
* @param defaultToCompleted 未指定 status 時是否仍限制 completed(舊 overall/多輪預設)
*/
private fun buildOverallStatusPredicateSql(alias: String, status: String?, defaultToCompleted: Boolean): String {
val statusNormalized = status?.trim()?.lowercase().orEmpty()
val pred = when (statusNormalized) {
"pending" -> "$alias.status IN ('pending', 'notMatch')"
"pass", "approving" -> "$alias.status = 'pass'"
"completed" -> "$alias.status = 'completed'"
else -> if (defaultToCompleted) "$alias.status = 'completed'" else null
}
return if (pred == null) "" else "AND $pred"
}

/**
@@ -422,20 +462,7 @@ ORDER BY
val args = mutableMapOf<String, Any>()
args["stockTakeRoundId"] = stockTakeRoundId

val statusNormalized = status?.trim()?.lowercase().orEmpty()
// status 映射规则:
// - All/null:不过滤
// - pending:包含 pending/pass/notMatch
// - completed:只看 completed
val statusLatestSql = when (statusNormalized) {
"pending" -> """
AND str.status IN ('pending', 'pass', 'notMatch')
""".trimIndent()
"completed" -> """
AND str.status = 'completed'
""".trimIndent()
else -> ""
}
val statusLatestSql = buildStockTakeRecordStatusFilterSql(status)

val storeIdSql = run {
val normalized = storeId?.trim()
@@ -457,9 +484,9 @@ ORDER BY
)
val lotTypeFilterSql = buildLotTypeFilterClause(type)

// 效能:先取本輪 latest_str,再只對相關 lot / lot_line 做追溯與 in/out 聚合
val sql = """
WITH RECURSIVE
$LOT_ROOT_ORIGIN_CTE_SQL,
rb AS (
SELECT
COALESCE(MIN(s.date), CURRENT_DATE) AS fromDate,
@@ -484,6 +511,64 @@ latest_str AS (
AND str.stockTakeRoundId = :stockTakeRoundId
$statusLatestSql
),
lot_trace AS (
SELECT
il.id AS lotId,
il.stockInLineId AS silId,
0 AS depth
FROM inventory_lot il
INNER JOIN (SELECT DISTINCT lotId FROM latest_str) seed ON seed.lotId = il.id
WHERE il.deleted = 0
AND il.stockInLineId IS NOT NULL
UNION ALL
SELECT
lt.lotId AS lotId,
il_src.stockInLineId AS silId,
lt.depth + 1 AS depth
FROM lot_trace lt
INNER JOIN stock_in_line sil
ON sil.id = lt.silId
AND sil.deleted = 0
INNER JOIN stock_transfer_record tr
ON tr.id = sil.stockTransferId
AND tr.deleted = 0
INNER JOIN stock_out_line sol
ON sol.id = tr.stockOutLineId
AND sol.deleted = 0
INNER JOIN inventory_lot_line ill_src
ON ill_src.id = sol.inventoryLotLineId
AND ill_src.deleted = 0
INNER JOIN inventory_lot il_src
ON il_src.id = ill_src.inventoryLotId
AND il_src.deleted = 0
WHERE lt.depth < 8
AND il_src.stockInLineId IS NOT NULL
AND (
UPPER(TRIM(COALESCE(sil.type, ''))) = 'TRF'
OR sil.stockTransferId IS NOT NULL
)
),
lot_root_origin AS (
SELECT
lotId,
silId AS rootSilId
FROM (
SELECT
lotId,
silId,
ROW_NUMBER() OVER (PARTITION BY lotId ORDER BY depth DESC) AS rn
FROM lot_trace
) t
WHERE t.rn = 1
),
round_ill AS (
SELECT DISTINCT ill.id AS inventoryLotLineId
FROM latest_str ls
INNER JOIN inventory_lot_line ill
ON ill.inventoryLotId = ls.lotId
AND ill.warehouseId = ls.warehouseId
AND ill.deleted = 0
),
in_agg AS (
SELECT
ill.id AS inventoryLotLineId,
@@ -505,6 +590,7 @@ in_agg AS (
ELSE 0 END) AS inDuring,
MAX(CASE WHEN sil.receiptDate IS NOT NULL THEN DATE(sil.receiptDate) END) AS lastInDate
FROM inventory_lot_line ill
INNER JOIN round_ill ri ON ri.inventoryLotLineId = ill.id
CROSS JOIN rb
INNER JOIN inventory_lot il
ON ill.inventoryLotId = il.id
@@ -534,6 +620,7 @@ out_agg AS (
SUM(CASE WHEN DATE(sol.endTime) BETWEEN rb.fromDate AND rb.toDate THEN COALESCE(sol.qty, 0) ELSE 0 END) AS outDuring,
MAX(CASE WHEN sol.endTime IS NOT NULL THEN DATE(sol.endTime) END) AS lastOutDate
FROM inventory_lot_line ill
INNER JOIN round_ill ri ON ri.inventoryLotLineId = ill.id
CROSS JOIN rb
LEFT JOIN stock_out_line sol
ON sol.inventoryLotLineId = ill.id
@@ -660,7 +747,8 @@ SELECT
CASE WHEN SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCumStockOut,
CASE WHEN SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCurrentBalance,
CASE WHEN SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalStockTakeQty,
lotTypeRaw AS type
lotTypeRaw AS type,
$STOCK_TAKE_STATUS_LABEL_SQL AS stockTakeStatus

FROM data
ORDER BY
@@ -673,15 +761,16 @@ ORDER BY
}

/**
* V2 Overall:每個 (lotId, warehouseId) 取 **status=completed** 且 **stockTakeRoundId 最大** 之一筆盤點紀錄
* (同輪多筆時取 id 較大者)。期初/累計區間依該筆所屬輪次之 MIN~MAX(`date`)。
* @param limitedToRoundIds null=全系統所有輪次;非 null=僅在指定輪次內取最新已完成
* V2 Overall:每個 (lotId, warehouseId) 取符合 status 篩選且 **stockTakeRoundId 最大** 之一筆盤點紀錄
* (同輪多筆時取 id 較大者;未指定 status 時預設 completed)。期初/累計區間依該筆所屬輪次之 MIN~MAX(`date`)。
* @param limitedToRoundIds null=全系統所有輪次;非 null=僅在指定輪次內取最新
*/
fun searchStockTakeVarianceReportV2Overall(
itemCode: String?,
storeId: String?,
limitedToRoundIds: List<Long>? = null,
type: String? = null,
status: String? = null,
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()

@@ -696,6 +785,11 @@ ORDER BY
"AND s.stockTakeRoundId IN ($inClause)"
}

// 未指定 status 時維持舊行為(只取 completed)
val statusSSql = buildOverallStatusPredicateSql("s", status, defaultToCompleted = true)
val statusStrSql = buildOverallStatusPredicateSql("str", status, defaultToCompleted = true)
val statusStr2Sql = buildOverallStatusPredicateSql("str2", status, defaultToCompleted = true)

val storeIdSql = run {
val normalized = storeId?.trim()
if (normalized.isNullOrBlank() || normalized.equals("all", ignoreCase = true)) {
@@ -748,19 +842,19 @@ latest_str AS (
MAX(s.stockTakeRoundId) AS maxRound
FROM stocktakerecord s
WHERE s.deleted = 0
AND s.status = 'completed'
$statusSSql
$roundIdsFilterSql
GROUP BY s.lotId, s.warehouseId
) mx ON mx.lotId = str.lotId
AND mx.warehouseId = str.warehouseId
AND mx.maxRound = str.stockTakeRoundId
WHERE str.deleted = 0
AND str.status = 'completed'
$statusStrSql
AND NOT EXISTS (
SELECT 1
FROM stocktakerecord str2
WHERE str2.deleted = 0
AND str2.status = 'completed'
$statusStr2Sql
AND str2.lotId = str.lotId
AND str2.warehouseId = str.warehouseId
AND str2.stockTakeRoundId = str.stockTakeRoundId
@@ -950,7 +1044,8 @@ SELECT
CASE WHEN SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCumStockOut,
CASE WHEN SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(stkBookQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCurrentBalance,
CASE WHEN SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(stkApproverQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalStockTakeQty,
lotTypeRaw AS type
lotTypeRaw AS type,
$STOCK_TAKE_STATUS_LABEL_SQL AS stockTakeStatus

FROM data
ORDER BY


+ 7
- 3
src/main/java/com/ffii/fpsms/modules/report/web/StockTakeVarianceReportController.kt Visa fil

@@ -264,6 +264,7 @@ class StockTakeVarianceReportController(
"盤盈虧",
"盤盈虧百分比",
"類型",
"狀態",
"審核時間",
)
val totalColumns = headers.size
@@ -509,7 +510,8 @@ class StockTakeVarianceReportController(
setNumberCellFromFormatted(r, 8, rowMap["variance"], numberStyle, dashStyle)
setTextCell(r, 9, rowMap["variancePercentage"], dashStyle)
setTextCell(r, 10, rowMap["type"], centerStyle)
setTextCell(r, 11, rowMap["stockTakeDate"], centerStyle)
setTextCell(r, 11, rowMap["stockTakeStatus"], centerStyle)
setTextCell(r, 12, rowMap["stockTakeDate"], centerStyle)

currentItemNo = itemNo
currentItemName = itemName
@@ -526,7 +528,7 @@ class StockTakeVarianceReportController(
sheet.setAutoFilter(CellRangeAddress(headerRowIndex, lastRowIndex, 0, 0))
}

val widths = intArrayOf(14, 26, 10, 18, 14, 14, 14, 12, 12, 14, 18, 22)
val widths = intArrayOf(14, 26, 10, 18, 14, 14, 14, 12, 12, 14, 18, 12, 22)
widths.forEachIndexed { idx, w -> sheet.setColumnWidth(idx, w * 256) }

val output = ByteArrayOutputStream()
@@ -596,7 +598,7 @@ class StockTakeVarianceReportController(
?: emptyList()

/**
* rep-012:單輪=單輪查詢;多輪=已選輪次內各批號/倉最新已完成(status 固定 completed)。
* rep-012:單輪=單輪查詢;多輪=已選輪次內各批號/倉最新一筆(可依 status 篩;未指定則 completed)。
* overall=true 且未傳輪次時保留舊「全系統輪次」行為。
*/
private fun resolveRep012ReportData(
@@ -621,6 +623,7 @@ class StockTakeVarianceReportController(
itemCode = itemCode,
storeId = storeId,
type = type,
status = status,
),
)
}
@@ -648,6 +651,7 @@ class StockTakeVarianceReportController(
storeId = storeId,
limitedToRoundIds = roundIds,
type = type,
status = status,
),
)
}


Laddar…
Avbryt
Spara