diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt index b61de1c..dce6b60 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt @@ -818,7 +818,9 @@ fun searchMaterialStockOutTraceabilityReport( /** * Queries the database for Stock Balance Report data (one summarized row per item). * Uses stock_ledger with report period (fromDate/toDate): opening = before fromDate, cum in/out = in period, current = up to toDate. - * totalStockBalance = SUM(pol.qty * pol.up) per item via purchase_order_line; avgUnitPrice = totalStockBalance / totalCurrentBalance. + * Price per stock unit = (pol.qty * pol.up) / sil.acceptedQty (PO unit price is per purchase unit; acceptedQty is in stock unit). + * Total balance = sum over movements of (inQty * price_per_stock_in - outQty * price_per_stock_out) per item. + * 現存存貨 = totalCurrentBalance; avg unit price = total_balance / 現存存貨 (0 when current stock is 0). */ fun searchStockBalanceReport( stockCategory: String?, @@ -874,10 +876,10 @@ fun searchMaterialStockOutTraceabilityReport( CASE WHEN COALESCE(item_agg.totalMisInputAndLost, 0) < 0 THEN CONCAT('(', FORMAT(-item_agg.totalMisInputAndLost, 0), ')') ELSE FORMAT(COALESCE(item_agg.totalMisInputAndLost, 0), 0) END as totalMisInputAndLost, CASE WHEN COALESCE(item_agg.totalVariance, 0) < 0 THEN CONCAT('(', FORMAT(-item_agg.totalVariance, 0), ')') ELSE FORMAT(COALESCE(item_agg.totalVariance, 0), 0) END as totalVariance, CASE WHEN COALESCE(item_agg.totalDefectiveGoods, 0) < 0 THEN CONCAT('(', FORMAT(-item_agg.totalDefectiveGoods, 0), ')') ELSE FORMAT(COALESCE(item_agg.totalDefectiveGoods, 0), 0) END as totalDefectiveGoods, - FORMAT(ROUND(COALESCE(ipv.totalStockBalanceRaw, 0), 2), 2) as totalStockBalance, + FORMAT(ROUND(COALESCE(item_agg.total_in_value, 0) - COALESCE(item_agg.total_out_value, 0), 2), 2) as totalStockBalance, CASE - WHEN COALESCE(item_agg.totalCurrentBalance, 0) > 0 AND ipv.totalStockBalanceRaw IS NOT NULL AND ipv.totalStockBalanceRaw <> 0 - THEN FORMAT(ROUND(ipv.totalStockBalanceRaw / item_agg.totalCurrentBalance, 2), 2) + WHEN COALESCE(item_agg.totalCurrentBalance, 0) > 0 + THEN FORMAT(ROUND((COALESCE(item_agg.total_in_value, 0) - COALESCE(item_agg.total_out_value, 0)) / item_agg.totalCurrentBalance, 2), 2) ELSE '0.00' END as avgUnitPrice FROM ( @@ -895,7 +897,9 @@ fun searchMaterialStockOutTraceabilityReport( SUM(agg.cumStockOutBad) AS totalDefectiveGoods, MAX(agg.storeLocation) AS storeLocation, MAX(agg.lastInDate) AS lastInDate, - MAX(agg.lastOutDate) AS lastOutDate + MAX(agg.lastOutDate) AS lastOutDate, + SUM(agg.total_in_value) AS total_in_value, + SUM(agg.total_out_value) AS total_out_value FROM ( SELECT sl.itemCode, @@ -935,7 +939,9 @@ fun searchMaterialStockOutTraceabilityReport( MAX(lot_wh.storeLocation) AS storeLocation, SUM(CASE WHEN DATE(sl.date) BETWEEN :fromDate AND :toDate AND COALESCE(sl.inQty, 0) > 0 AND (LOWER(TRIM(COALESCE(sl.type, ''))) = 'miss' OR LOWER(TRIM(COALESCE(sol.type, ''))) = 'miss') THEN sl.outQty ELSE 0 END) AS cumStockOutMiss, SUM(CASE WHEN DATE(sl.date) BETWEEN :fromDate AND :toDate AND COALESCE(sl.outQty, 0) > 0 AND (LOWER(TRIM(COALESCE(sl.type, ''))) = 'bad' OR LOWER(TRIM(COALESCE(sol.type, ''))) = 'bad') THEN sl.outQty ELSE 0 END) AS cumStockOutBad, - SUM(CASE WHEN DATE(sl.date) BETWEEN :fromDate AND :toDate AND COALESCE(sl.outQty, 0) > 0 AND LOWER(TRIM(COALESCE(sl.type, ''))) = 'adj' AND (sol.stockTransferId IS NULL OR sol.id IS NULL) THEN sl.outQty ELSE 0 END) AS cumStockOutAdjStockTake + SUM(CASE WHEN DATE(sl.date) BETWEEN :fromDate AND :toDate AND COALESCE(sl.outQty, 0) > 0 AND LOWER(TRIM(COALESCE(sl.type, ''))) = 'adj' AND (sol.stockTransferId IS NULL OR sol.id IS NULL) THEN sl.outQty ELSE 0 END) AS cumStockOutAdjStockTake, + SUM(COALESCE(sl.inQty, 0) * CASE WHEN pol_in.id IS NOT NULL AND iu_stock.id IS NOT NULL AND iu_purchase.id IS NOT NULL AND COALESCE(iu_purchase.ratioN, 0) > 0 THEN COALESCE(pol_in.up, 0) * (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0)) / (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) ELSE 0 END) AS total_in_value, + SUM(COALESCE(sl.outQty, 0) * CASE WHEN pol_out.id IS NOT NULL AND iu_stock.id IS NOT NULL AND iu_purchase.id IS NOT NULL AND COALESCE(iu_purchase.ratioN, 0) > 0 THEN COALESCE(pol_out.up, 0) * (iu_stock.ratioN / NULLIF(iu_stock.ratioD, 0)) / (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) ELSE 0 END) AS total_out_value FROM stock_ledger sl LEFT JOIN stock_in_line sil ON sl.stockInLineId = sil.id AND sil.deleted = 0 LEFT JOIN inventory_lot il_in ON sil.inventoryLotId = il_in.id AND il_in.deleted = 0 @@ -949,6 +955,11 @@ fun searchMaterialStockOutTraceabilityReport( LEFT JOIN warehouse wh ON ill.warehouseId = wh.id AND wh.deleted = 0 GROUP BY il.id ) lot_wh ON lot_wh.lotId = COALESCE(il_in.id, il_out.id) + LEFT JOIN purchase_order_line pol_in ON sil.purchaseOrderLineId = pol_in.id AND pol_in.deleted = 0 + LEFT JOIN stock_in_line sil_out ON il_out.stockInLineId = sil_out.id AND sil_out.deleted = 0 + LEFT JOIN purchase_order_line pol_out ON sil_out.purchaseOrderLineId = pol_out.id AND pol_out.deleted = 0 + LEFT JOIN item_uom iu_stock ON sl.itemId = iu_stock.itemId AND iu_stock.stockUnit = 1 AND iu_stock.deleted = 0 + LEFT JOIN item_uom iu_purchase ON sl.itemId = iu_purchase.itemId AND iu_purchase.purchaseUnit = 1 AND iu_purchase.deleted = 0 WHERE sl.deleted = 0 AND sl.itemCode IS NOT NULL AND sl.itemCode <> '' AND DATE(sl.date) <= :toDate @@ -965,17 +976,6 @@ fun searchMaterialStockOutTraceabilityReport( GROUP BY agg.itemCode, agg.itemId, it.id, it.code, it.name, uc.udfudesc, uc.code HAVING 1=1 ) item_agg - LEFT JOIN ( - SELECT itemId, SUM(pol_qty_up) AS totalStockBalanceRaw - FROM ( - SELECT sil.itemId, MAX(COALESCE(pol.qty, 0) * COALESCE(pol.up, 0)) AS pol_qty_up - FROM stock_in_line sil - LEFT JOIN purchase_order_line pol ON sil.purchaseOrderLineId = pol.id AND pol.deleted = 0 - WHERE sil.deleted = 0 - GROUP BY sil.itemId, pol.id - ) t - GROUP BY itemId - ) ipv ON item_agg.itemId = ipv.itemId WHERE 1=1 """.trimIndent() diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt index 9b83af9..27ea738 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/StockLedgerReportController.kt @@ -20,8 +20,8 @@ fun generateStockLedgerReport( @RequestParam(required = false) itemCode: String?, @RequestParam(required = false) storeLocation: String?, // URL 參數名仍然是 lastInDateStart / lastInDateEnd - @RequestParam(name = "startDateStart", required = false) reportPeriodStart: String?, - @RequestParam(name = "startDateEnd", required = false) reportPeriodEnd: String?, + @RequestParam(name = "lastInDateStart", required = false) reportPeriodStart: String?, + @RequestParam(name = "lastInDateEnd", required = false) reportPeriodEnd: String?, ): ResponseEntity { val parameters = mutableMapOf()