src/main/java/com/ffii/fpsms/modules/report/service/StockTakeVarianceReportService.kt파일 보기
@@ -9,6 +9,95 @@ open class StockTakeVarianceReportService(
private val jdbcDao: JdbcDao,
) {
companion object {
private const val ORIGIN_STOCK_IN_JOIN_SQL = """
LEFT JOIN stock_in_line origin_sil
ON origin_sil.id = il.stockInLineId AND origin_sil.deleted = 0
LEFT JOIN purchase_order po
ON po.id = origin_sil.purchaseOrderId AND po.deleted = 0
"""
private const val LOT_ROOT_ORIGIN_CTE_SQL = """
lot_trace AS (
SELECT
il.id AS lotId,
il.stockInLineId AS silId,
0 AS depth
FROM inventory_lot il
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
)"""
private const val ROOT_STOCK_IN_JOIN_SQL = """
LEFT JOIN lot_root_origin lro
ON lro.lotId = il.id
LEFT JOIN stock_in_line root_sil
ON root_sil.id = lro.rootSilId AND root_sil.deleted = 0
LEFT JOIN purchase_order root_po
ON root_po.id = root_sil.purchaseOrderId AND root_po.deleted = 0
"""
private const val LOT_ROOT_CATEGORY_SQL = """
CASE
WHEN root_sil.jobOrderId IS NOT NULL THEN '工廠生產'
WHEN UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'OPEN' THEN '期初存貨'
WHEN UPPER(TRIM(COALESCE(root_sil.type, ''))) = 'ADJ' THEN '倉存調整'
WHEN UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PP' THEN 'PP'
WHEN UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'PF' THEN 'PF'
WHEN UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) = 'TOA' THEN 'TOA'
WHEN root_sil.purchaseOrderId IS NOT NULL THEN '採購入倉'
ELSE '其他入倉'
END"""
private const val LOT_TYPE_DISPLAY_SQL = """
CASE
WHEN UPPER(TRIM(COALESCE(origin_sil.type, ''))) = 'TRF'
OR origin_sil.stockTransferId IS NOT NULL
THEN CONCAT('轉倉(原:', $LOT_ROOT_CATEGORY_SQL, ')')
ELSE $LOT_ROOT_CATEGORY_SQL
END"""
}
/**
* Stock Take Variance 報表查詢
*
@@ -40,6 +129,7 @@ open class StockTakeVarianceReportService(
storeLocation: String?,
stockTakeDateStart: String?,
stockTakeDateEnd: String?,
type: String? = null,
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
@@ -62,6 +152,7 @@ open class StockTakeVarianceReportService(
} else {
""
}
val lotTypeFilterSql = buildLotTypeFilterClause(type)
// 1) toDate:有填用傳入,沒填用今天
val toDate = (stockTakeDateEnd?.replace("/", "-")
@@ -87,7 +178,9 @@ open class StockTakeVarianceReportService(
args["toDate"] = toDate
val sql = """
WITH latest_str AS (
WITH RECURSIVE
$LOT_ROOT_ORIGIN_CTE_SQL,
latest_str AS (
SELECT
s1.lotId,
s1.warehouseId,
@@ -207,7 +300,8 @@ data AS (
ls.bookQty AS stkBookQty,
ls.approverStockTakeQty AS stkApproverQty,
ls.varianceQty AS stkVarianceQty,
ls.date AS stockTakeDateRaw
ls.date AS stockTakeDateRaw,
$LOT_TYPE_DISPLAY_SQL AS lotTypeRaw
FROM inventory_lot_line ill
INNER JOIN inventory_lot il
ON ill.inventoryLotId = il.id
@@ -218,6 +312,8 @@ data AS (
INNER JOIN warehouse wh
ON ill.warehouseId = wh.id
AND wh.deleted = 0
$ORIGIN_STOCK_IN_JOIN_SQL
$ROOT_STOCK_IN_JOIN_SQL
LEFT JOIN item_uom iu
ON it.id = iu.itemId
@@ -237,6 +333,7 @@ data AS (
$stockCategorySql
$itemCodeSql
$storeLocationSql
$lotTypeFilterSql
)
SELECT
@@ -284,7 +381,8 @@ SELECT
CASE WHEN SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCumStockIn,
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(currentQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(currentQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(currentQty, 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
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
FROM data
ORDER BY
@@ -307,6 +405,7 @@ ORDER BY
itemCode: String?,
storeId: String?,
status: String?,
type: String? = null,
): List<Map<String, Any>> {
val countSql = """
SELECT COUNT(*) AS c FROM stocktakerecord s
@@ -356,9 +455,12 @@ ORDER BY
"itemCode",
args
)
val lotTypeFilterSql = buildLotTypeFilterClause(type)
val sql = """
WITH rb AS (
WITH RECURSIVE
$LOT_ROOT_ORIGIN_CTE_SQL,
rb AS (
SELECT
COALESCE(MIN(s.date), CURRENT_DATE) AS fromDate,
COALESCE(MAX(s.date), CURRENT_DATE) AS toDate
@@ -476,7 +578,8 @@ data AS (
ls.varianceQty AS stkVarianceQty,
ls.strDate AS stockTakeDateRaw,
ls.approverTime AS approvalDateTimeRaw,
ls.stockTakeRecordStatus AS stockTakeRecordStatus
ls.stockTakeRecordStatus AS stockTakeRecordStatus,
$LOT_TYPE_DISPLAY_SQL AS lotTypeRaw
FROM latest_str ls
INNER JOIN inventory_lot il
ON ls.lotId = il.id
@@ -491,6 +594,8 @@ data AS (
INNER JOIN warehouse wh
ON wh.id = ls.warehouseId
AND wh.deleted = 0
$ORIGIN_STOCK_IN_JOIN_SQL
$ROOT_STOCK_IN_JOIN_SQL
LEFT JOIN item_uom iu
ON it.id = iu.itemId
@@ -505,6 +610,7 @@ data AS (
WHERE 1=1
$itemCodeSql
$storeIdSql
$lotTypeFilterSql
)
SELECT
@@ -553,7 +659,8 @@ SELECT
CASE WHEN SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCumStockIn,
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
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
FROM data
ORDER BY
@@ -574,6 +681,7 @@ ORDER BY
itemCode: String?,
storeId: String?,
limitedToRoundIds: List<Long>? = null,
type: String? = null,
): List<Map<String, Any>> {
val args = mutableMapOf<String, Any>()
@@ -605,9 +713,12 @@ ORDER BY
"itemCode",
args
)
val lotTypeFilterSql = buildLotTypeFilterClause(type)
val sql = """
WITH round_bounds AS (
WITH RECURSIVE
$LOT_ROOT_ORIGIN_CTE_SQL,
round_bounds AS (
SELECT
s.stockTakeRoundId,
COALESCE(MIN(s.date), CURRENT_DATE) AS fromDate,
@@ -766,12 +877,15 @@ data AS (
lb.varianceQty AS stkVarianceQty,
lb.strDate AS stockTakeDateRaw,
lb.approverTime AS approvalDateTimeRaw,
lb.stockTakeRecordStatus AS stockTakeRecordStatus
lb.stockTakeRecordStatus AS stockTakeRecordStatus,
$LOT_TYPE_DISPLAY_SQL AS lotTypeRaw
FROM line_bounds lb
INNER JOIN inventory_lot_line ill ON ill.id = lb.inventoryLotLineId
INNER JOIN inventory_lot il ON ill.inventoryLotId = il.id AND il.deleted = 0
INNER JOIN items it ON il.itemId = it.id AND it.deleted = 0
INNER JOIN warehouse wh ON wh.id = lb.warehouseId AND wh.deleted = 0
$ORIGIN_STOCK_IN_JOIN_SQL
$ROOT_STOCK_IN_JOIN_SQL
LEFT JOIN item_uom iu
ON it.id = iu.itemId
@@ -786,6 +900,7 @@ data AS (
WHERE 1=1
$itemCodeSql
$storeIdSql
$lotTypeFilterSql
)
SELECT
@@ -834,7 +949,8 @@ SELECT
CASE WHEN SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalCumStockIn,
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
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
FROM data
ORDER BY
@@ -883,6 +999,36 @@ ORDER BY
return if (!cap.isNullOrBlank()) cap else "盤點輪次$stockTakeRoundId"