| @@ -266,13 +266,45 @@ return result | |||||
| } | } | ||||
| fun getDistinctHandlersForFGStockOutTraceability(): List<String> { | |||||
| val sql = """ | |||||
| SELECT DISTINCT COALESCE(picker_user.name, modified_user.name, '') AS handler | |||||
| FROM stock_out_line sol | |||||
| INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0 AND so.type = 'do' | |||||
| LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0 | |||||
| LEFT JOIN user modified_user ON sol.modifiedBy = modified_user.staffNo AND modified_user.deleted = 0 AND sol.handled_by IS NULL | |||||
| WHERE sol.deleted = 0 | |||||
| ORDER BY handler | |||||
| """.trimIndent() | |||||
| return jdbcDao.queryForList(sql, emptyMap<String, Any>()).map { row -> (row["handler"]?.toString() ?: "").trim() }.filter { it.isNotBlank() } | |||||
| } | |||||
| fun getDistinctHandlersForMaterialStockOutTraceability(): List<String> { | |||||
| val sql = """ | |||||
| SELECT DISTINCT COALESCE(picker_user.name, jpo_handler_user.name, created_user.name, modified_user.name, '') AS handler | |||||
| FROM stock_out_line sol | |||||
| INNER JOIN stock_out so ON sol.stockOutId = so.id AND so.deleted = 0 AND so.type = 'job' | |||||
| INNER JOIN pick_order_line pol ON sol.pickOrderLineId = pol.id AND pol.deleted = 0 | |||||
| INNER JOIN pick_order po ON pol.poId = po.id AND po.deleted = 0 AND po.type IN ('jo', 'JOB_ORDER') AND po.joId IS NOT NULL | |||||
| LEFT JOIN user picker_user ON sol.handled_by = picker_user.id AND picker_user.deleted = 0 | |||||
| LEFT JOIN jo_pick_order jpo ON po.id = jpo.pick_order_id AND pol.itemId = jpo.item_id AND jpo.deleted = 0 | |||||
| LEFT JOIN user jpo_handler_user ON jpo.handled_by = jpo_handler_user.id AND jpo_handler_user.deleted = 0 | |||||
| LEFT JOIN user created_user ON sol.createdBy = created_user.username AND created_user.deleted = 0 | |||||
| LEFT JOIN user modified_user ON sol.modifiedBy = modified_user.staffNo AND modified_user.deleted = 0 AND sol.handled_by IS NULL | |||||
| WHERE sol.deleted = 0 | |||||
| ORDER BY handler | |||||
| """.trimIndent() | |||||
| return jdbcDao.queryForList(sql, emptyMap<String, Any>()).map { row -> (row["handler"]?.toString() ?: "").trim() }.filter { it.isNotBlank() } | |||||
| } | |||||
| fun searchFGStockOutTraceabilityReport( | fun searchFGStockOutTraceabilityReport( | ||||
| stockCategory: String?, | stockCategory: String?, | ||||
| stockSubCategory: String?, | stockSubCategory: String?, | ||||
| itemCode: String?, | itemCode: String?, | ||||
| year: String?, | year: String?, | ||||
| lastOutDateStart: String?, | lastOutDateStart: String?, | ||||
| lastOutDateEnd: String? | |||||
| lastOutDateEnd: String?, | |||||
| handler: String? | |||||
| ): List<Map<String, Any>> { | ): List<Map<String, Any>> { | ||||
| val args = mutableMapOf<String, Any>() | val args = mutableMapOf<String, Any>() | ||||
| @@ -316,6 +348,13 @@ return result | |||||
| "AND DATE(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) <= DATE(:lastOutDateEnd)" | "AND DATE(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) <= DATE(:lastOutDateEnd)" | ||||
| } else "" | } else "" | ||||
| val handlerSql = buildMultiValueExactClause( | |||||
| handler, | |||||
| "COALESCE(picker_user.name, modified_user.name, '')", | |||||
| "handler", | |||||
| args | |||||
| ) | |||||
| val sql = """ | val sql = """ | ||||
| SELECT | SELECT | ||||
| IFNULL(DATE_FORMAT( | IFNULL(DATE_FORMAT( | ||||
| @@ -416,6 +455,7 @@ return result | |||||
| $yearSql | $yearSql | ||||
| $lastOutDateStartSql | $lastOutDateStartSql | ||||
| $lastOutDateEndSql | $lastOutDateEndSql | ||||
| $handlerSql | |||||
| GROUP BY | GROUP BY | ||||
| sol.id, | sol.id, | ||||
| dpor.RequiredDeliveryDate, | dpor.RequiredDeliveryDate, | ||||
| @@ -469,7 +509,8 @@ fun searchMaterialStockOutTraceabilityReport( | |||||
| itemCode: String?, | itemCode: String?, | ||||
| year: String?, | year: String?, | ||||
| lastOutDateStart: String?, | lastOutDateStart: String?, | ||||
| lastOutDateEnd: String? | |||||
| lastOutDateEnd: String?, | |||||
| handler: String? | |||||
| ): List<Map<String, Any>> { | ): List<Map<String, Any>> { | ||||
| val args = mutableMapOf<String, Any>() | val args = mutableMapOf<String, Any>() | ||||
| @@ -514,6 +555,13 @@ fun searchMaterialStockOutTraceabilityReport( | |||||
| "" | "" | ||||
| } | } | ||||
| val handlerSql = buildMultiValueExactClause( | |||||
| handler, | |||||
| "COALESCE(picker_user.name, jpo_handler_user.name, created_user.name, modified_user.name, '')", | |||||
| "handler", | |||||
| args | |||||
| ) | |||||
| val sql = """ | val sql = """ | ||||
| SELECT | SELECT | ||||
| IFNULL(it.code, '') AS itemNo, | IFNULL(it.code, '') AS itemNo, | ||||
| @@ -597,6 +645,7 @@ fun searchMaterialStockOutTraceabilityReport( | |||||
| $yearSql | $yearSql | ||||
| $lastOutDateStartSql | $lastOutDateStartSql | ||||
| $lastOutDateEndSql | $lastOutDateEndSql | ||||
| $handlerSql | |||||
| ORDER BY | ORDER BY | ||||
| it.code, | it.code, | ||||
| il.lotNo, | il.lotNo, | ||||
| @@ -706,7 +755,6 @@ fun searchMaterialStockOutTraceabilityReport( | |||||
| } else "" | } else "" | ||||
| val sql = """ | val sql = """ | ||||
| SELECT | SELECT | ||||
| COALESCE(it.code, '') as itemNo, | COALESCE(it.code, '') as itemNo, | ||||
| COALESCE(it.name, '') as itemName, | COALESCE(it.name, '') as itemName, | ||||
| @@ -715,25 +763,13 @@ fun searchMaterialStockOutTraceabilityReport( | |||||
| COALESCE(sil.lotNo, il.lotNo, '') as lotNo, | COALESCE(sil.lotNo, il.lotNo, '') as lotNo, | ||||
| COALESCE(DATE_FORMAT(COALESCE(sil.expiryDate, il.expiryDate), '%Y-%m-%d'), '') as expiryDate, | COALESCE(DATE_FORMAT(COALESCE(sil.expiryDate, il.expiryDate), '%Y-%m-%d'), '') as expiryDate, | ||||
| CASE WHEN COALESCE(qr_agg.qcFailed, 0) = 1 THEN '0' | CASE WHEN COALESCE(qr_agg.qcFailed, 0) = 1 THEN '0' | ||||
| ELSE TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE( | |||||
| CASE WHEN sil.purchaseOrderId IS NOT NULL AND iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL | |||||
| THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0)) | |||||
| ELSE sil.acceptedQty END, 0), 2))) | |||||
| END as stockInQty, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE( | |||||
| CASE WHEN sil.purchaseOrderId IS NOT NULL AND iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL | |||||
| THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0)) | |||||
| ELSE sil.acceptedQty END, 0), 2))) as iqcSampleQty, | |||||
| ELSE TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(sil.acceptedQty, 0), 2))) | |||||
| END as stockInQty, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(sil.acceptedQty, 0), 2))) as iqcSampleQty, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(qr_agg.failQtySum, 0), 2))) as iqcDefectQty, | TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(qr_agg.failQtySum, 0), 2))) as iqcDefectQty, | ||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT( | TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT( | ||||
| CASE WHEN COALESCE( | |||||
| CASE WHEN sil.purchaseOrderId IS NOT NULL AND iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL | |||||
| THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0)) | |||||
| ELSE sil.acceptedQty END, 0) > 0 | |||||
| THEN COALESCE(qr_agg.failQtySum, 0) / | |||||
| (CASE WHEN sil.purchaseOrderId IS NOT NULL AND iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL | |||||
| THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0)) | |||||
| ELSE sil.acceptedQty END) * 100 | |||||
| CASE WHEN COALESCE(sil.acceptedQty, 0) > 0 | |||||
| THEN COALESCE(qr_agg.failQtySum, 0) / sil.acceptedQty * 100 | |||||
| ELSE 0 | ELSE 0 | ||||
| END, 2))) as iqcDefectPercentage, | END, 2))) as iqcDefectPercentage, | ||||
| CASE WHEN COALESCE(qr_agg.qcFailed, 0) = 1 THEN '不合格' ELSE '已接受' END as iqcResult, | CASE WHEN COALESCE(qr_agg.qcFailed, 0) = 1 THEN '不合格' ELSE '已接受' END as iqcResult, | ||||
| @@ -742,24 +778,14 @@ fun searchMaterialStockOutTraceabilityReport( | |||||
| COALESCE(wh.code, '') as storeLocation, | COALESCE(wh.code, '') as storeLocation, | ||||
| COALESCE(sp_si.code, sp_po.code, '') as supplierID, | COALESCE(sp_si.code, sp_po.code, '') as supplierID, | ||||
| COALESCE(sp_si.name, sp_po.name, '') as supplierName, | COALESCE(sp_si.name, sp_po.name, '') as supplierName, | ||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE( | |||||
| CASE WHEN sil.purchaseOrderId IS NOT NULL AND iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL | |||||
| THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0)) | |||||
| ELSE sil.acceptedQty END, 0)) OVER (PARTITION BY it.id), 2))) as totalStockInQty, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE( | |||||
| CASE WHEN sil.purchaseOrderId IS NOT NULL AND iu_purchase.id IS NOT NULL AND iu.id IS NOT NULL | |||||
| THEN sil.acceptedQty * (iu_purchase.ratioN / NULLIF(iu_purchase.ratioD, 0)) / (iu.ratioN / NULLIF(iu.ratioD, 0)) | |||||
| ELSE sil.acceptedQty END, 0)) OVER (PARTITION BY it.id), 2))) as totalIqcSampleQty | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(sil.acceptedQty, 0)) OVER (PARTITION BY it.id), 2))) as totalStockInQty, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(sil.acceptedQty, 0)) OVER (PARTITION BY it.id), 2))) as totalIqcSampleQty | |||||
| FROM stock_in_line sil | FROM stock_in_line sil | ||||
| LEFT JOIN stock_in si ON sil.stockInId = si.id | LEFT JOIN stock_in si ON sil.stockInId = si.id | ||||
| LEFT JOIN purchase_order po ON sil.purchaseOrderId = po.id | LEFT JOIN purchase_order po ON sil.purchaseOrderId = po.id | ||||
| LEFT JOIN items it ON sil.itemId = it.id | LEFT JOIN items it ON sil.itemId = it.id | ||||
| LEFT JOIN item_uom iu ON it.id = iu.itemId AND iu.stockUnit = true | LEFT JOIN item_uom iu ON it.id = iu.itemId AND iu.stockUnit = true | ||||
| LEFT JOIN uom_conversion uc ON iu.uomId = uc.id | LEFT JOIN uom_conversion uc ON iu.uomId = uc.id | ||||
| LEFT JOIN item_uom iu_purchase | |||||
| ON it.id = iu_purchase.itemId | |||||
| AND iu_purchase.purchaseUnit = 1 | |||||
| AND iu_purchase.deleted = 0 | |||||
| LEFT JOIN inventory_lot il ON sil.inventoryLotId = il.id | LEFT JOIN inventory_lot il ON sil.inventoryLotId = il.id | ||||
| LEFT JOIN inventory_lot_line ill ON il.id = ill.inventoryLotId | LEFT JOIN inventory_lot_line ill ON il.id = ill.inventoryLotId | ||||
| LEFT JOIN warehouse wh ON ill.warehouseId = wh.id | LEFT JOIN warehouse wh ON ill.warehouseId = wh.id | ||||
| @@ -838,17 +864,17 @@ fun searchMaterialStockOutTraceabilityReport( | |||||
| COALESCE(item_agg.storeLocation, '') as storeLocation, | COALESCE(item_agg.storeLocation, '') as storeLocation, | ||||
| COALESCE(DATE_FORMAT(item_agg.lastInDate, '%Y-%m-%d'), '') as lastInDate, | COALESCE(DATE_FORMAT(item_agg.lastInDate, '%Y-%m-%d'), '') as lastInDate, | ||||
| COALESCE(DATE_FORMAT(item_agg.lastOutDate, '%Y-%m-%d'), '') as lastOutDate, | COALESCE(DATE_FORMAT(item_agg.lastOutDate, '%Y-%m-%d'), '') as lastOutDate, | ||||
| FORMAT(ROUND(COALESCE(item_agg.totalOpeningBalance, 0), 0), 0) as totalOpeningBalance, | |||||
| FORMAT(ROUND(COALESCE(item_agg.totalCumStockIn, 0), 0), 0) as totalCumStockIn, | |||||
| FORMAT(ROUND(COALESCE(item_agg.totalCumStockOut, 0), 0), 0) as totalCumStockOut, | |||||
| FORMAT(ROUND(COALESCE(item_agg.totalCurrentBalance, 0), 0), 0) as totalCurrentBalance, | |||||
| CASE WHEN COALESCE(item_agg.totalOpeningBalance, 0) < 0 THEN CONCAT('(', FORMAT(-item_agg.totalOpeningBalance, 0), ')') ELSE FORMAT(COALESCE(item_agg.totalOpeningBalance, 0), 0) END as totalOpeningBalance, | |||||
| CASE WHEN COALESCE(item_agg.totalCumStockIn, 0) < 0 THEN CONCAT('(', FORMAT(-item_agg.totalCumStockIn, 0), ')') ELSE FORMAT(COALESCE(item_agg.totalCumStockIn, 0), 0) END as totalCumStockIn, | |||||
| CASE WHEN COALESCE(item_agg.totalCumStockOut, 0) < 0 THEN CONCAT('(', FORMAT(-item_agg.totalCumStockOut, 0), ')') ELSE FORMAT(COALESCE(item_agg.totalCumStockOut, 0), 0) END as totalCumStockOut, | |||||
| CASE WHEN COALESCE(item_agg.totalCurrentBalance, 0) < 0 THEN CONCAT('(', FORMAT(-item_agg.totalCurrentBalance, 0), ')') ELSE FORMAT(COALESCE(item_agg.totalCurrentBalance, 0), 0) END as totalCurrentBalance, | |||||
| '' as misInputAndLost, | '' as misInputAndLost, | ||||
| '' as defectiveGoods, | '' as defectiveGoods, | ||||
| '' as variance, | '' as variance, | ||||
| FORMAT(ROUND(COALESCE(item_agg.totalMisInputAndLost, 0), 0), 0) as totalMisInputAndLost, | |||||
| FORMAT(ROUND(COALESCE(item_agg.totalVariance, 0), 0), 0) as totalVariance, | |||||
| FORMAT(ROUND(COALESCE(item_agg.totalDefectiveGoods, 0), 0), 0) as totalDefectiveGoods, | |||||
| FORMAT(ROUND(COALESCE(ipv.totalStockBalanceRaw, 0), 2), 0) as totalStockBalance, | |||||
| 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, | |||||
| CASE | CASE | ||||
| WHEN COALESCE(item_agg.totalCurrentBalance, 0) > 0 AND ipv.totalStockBalanceRaw IS NOT NULL AND ipv.totalStockBalanceRaw <> 0 | 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) | THEN FORMAT(ROUND(ipv.totalStockBalanceRaw / item_agg.totalCurrentBalance, 2), 2) | ||||
| @@ -255,35 +255,27 @@ SELECT | |||||
| reOrderLevel, | reOrderLevel, | ||||
| reOrderQty, | reOrderQty, | ||||
| -- jrxml 需要 String,避免 BigDecimal cast/trim 爆炸 | |||||
| CAST(CAST(inQty AS DECIMAL(20,0)) AS CHAR) AS stockIn, | |||||
| CAST(CAST(outQty AS DECIMAL(20,0)) AS CHAR) AS stockOut, | |||||
| -- jrxml 需要 String;負數括號顯示,無小數 | |||||
| CASE WHEN COALESCE(inQty, 0) < 0 THEN CONCAT('(', FORMAT(-inQty, 0), ')') ELSE FORMAT(COALESCE(inQty, 0), 0) END AS stockIn, | |||||
| CASE WHEN COALESCE(outQty, 0) < 0 THEN CONCAT('(', FORMAT(-outQty, 0), ')') ELSE FORMAT(COALESCE(outQty, 0), 0) END AS stockOut, | |||||
| -- 累計存量(跨 lot:只用 itemCode 分區) | -- 累計存量(跨 lot:只用 itemCode 分區) | ||||
| CAST(CAST( | |||||
| (openingBeforeStart | |||||
| + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId) | |||||
| ) AS DECIMAL(20,0) | |||||
| ) AS CHAR) AS cumBalance, | |||||
| CASE WHEN (openingBeforeStart + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId)) < 0 | |||||
| THEN CONCAT('(', FORMAT(-(openingBeforeStart + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId)), 0), ')') | |||||
| ELSE FORMAT(openingBeforeStart + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId), 0) END AS cumBalance, | |||||
| -- 累計期初存量 = 本行累計 - 本行異動 | -- 累計期初存量 = 本行累計 - 本行異動 | ||||
| CAST(CAST( | |||||
| (openingBeforeStart | |||||
| + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId) | |||||
| - delta | |||||
| ) AS DECIMAL(20,0) | |||||
| ) AS CHAR) AS cumOpeningBal, | |||||
| CASE WHEN (openingBeforeStart + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId) - delta) < 0 | |||||
| THEN CONCAT('(', FORMAT(-(openingBeforeStart + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId) - delta), 0), ')') | |||||
| ELSE FORMAT(openingBeforeStart + SUM(delta) OVER (PARTITION BY itemCode ORDER BY trnDateRaw, slId) - delta, 0) END AS cumOpeningBal, | |||||
| -- footer totals(同樣輸出 String) | -- footer totals(同樣輸出 String) | ||||
| CAST(openingBeforeStart AS DECIMAL(20,0)) AS totalCumOpeningBal, | |||||
| CAST(SUM(inQty) OVER (PARTITION BY itemCode) AS DECIMAL(20,0)) AS totalStockIn, | |||||
| CAST(SUM(outQty) OVER (PARTITION BY itemCode) AS DECIMAL(20,0)) AS totalStockOut, | |||||
| CAST( | |||||
| (openingBeforeStart | |||||
| + SUM(inQty) OVER (PARTITION BY itemCode) | |||||
| - SUM(outQty) OVER (PARTITION BY itemCode) | |||||
| ) AS DECIMAL(20,0) | |||||
| ) AS totalCumBalance | |||||
| CASE WHEN COALESCE(openingBeforeStart, 0) < 0 THEN CONCAT('(', FORMAT(-openingBeforeStart, 0), ')') ELSE FORMAT(COALESCE(openingBeforeStart, 0), 0) END AS totalCumOpeningBal, | |||||
| CASE WHEN SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemCode) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemCode), 0), ')') ELSE FORMAT(SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemCode), 0) END AS totalStockIn, | |||||
| CASE WHEN SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemCode) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemCode), 0), ')') ELSE FORMAT(SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemCode), 0) END AS totalStockOut, | |||||
| CASE WHEN (openingBeforeStart + SUM(inQty) OVER (PARTITION BY itemCode) - SUM(outQty) OVER (PARTITION BY itemCode)) < 0 | |||||
| THEN CONCAT('(', FORMAT(-(openingBeforeStart + SUM(inQty) OVER (PARTITION BY itemCode) - SUM(outQty) OVER (PARTITION BY itemCode)), 0), ')') | |||||
| ELSE FORMAT(openingBeforeStart + SUM(inQty) OVER (PARTITION BY itemCode) - SUM(outQty) OVER (PARTITION BY itemCode), 0) END AS totalCumBalance | |||||
| FROM period | FROM period | ||||
| ORDER BY | ORDER BY | ||||
| @@ -248,41 +248,42 @@ SELECT | |||||
| expiryDate, | expiryDate, | ||||
| storeLocation, | storeLocation, | ||||
| /* 期初/累計/現存:jrxml 欄位全是 String */ | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(openingQty, 0), 2))) AS openingBalance, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(inQty, 0), 2))) AS cumStockIn, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(outQty, 0), 2))) AS cumStockOut, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(currentQty, 0), 2))) AS currentBookBalance, | |||||
| /* 期初/累計/現存:jrxml 欄位全是 String;負數括號顯示,無小數 */ | |||||
| CASE WHEN COALESCE(openingQty, 0) < 0 THEN CONCAT('(', FORMAT(-openingQty, 0), ')') ELSE FORMAT(COALESCE(openingQty, 0), 0) END AS openingBalance, | |||||
| CASE WHEN COALESCE(inQty, 0) < 0 THEN CONCAT('(', FORMAT(-inQty, 0), ')') ELSE FORMAT(COALESCE(inQty, 0), 0) END AS cumStockIn, | |||||
| CASE WHEN COALESCE(outQty, 0) < 0 THEN CONCAT('(', FORMAT(-outQty, 0), ')') ELSE FORMAT(COALESCE(outQty, 0), 0) END AS cumStockOut, | |||||
| CASE WHEN COALESCE(currentQty, 0) < 0 THEN CONCAT('(', FORMAT(-currentQty, 0), ')') ELSE FORMAT(COALESCE(currentQty, 0), 0) END AS currentBookBalance, | |||||
| COALESCE(DATE_FORMAT(lastInDateRaw, '%Y-%m-%d'), '') AS lastInDate, | COALESCE(DATE_FORMAT(lastInDateRaw, '%Y-%m-%d'), '') AS lastInDate, | ||||
| COALESCE(DATE_FORMAT(lastOutDateRaw, '%Y-%m-%d'), '') AS lastOutDate, | COALESCE(DATE_FORMAT(lastOutDateRaw, '%Y-%m-%d'), '') AS lastOutDate, | ||||
| COALESCE(DATE_FORMAT(stockTakeDateRaw, '%Y-%m-%d'), '') AS stockTakeDate, | COALESCE(DATE_FORMAT(stockTakeDateRaw, '%Y-%m-%d'), '') AS stockTakeDate, | ||||
| /* 取貨量 = approverStockTakeQty(無盤點紀錄就空白) */ | |||||
| /* 取貨量 = approverStockTakeQty(無盤點紀錄顯示 0) */ | |||||
| CASE | CASE | ||||
| WHEN stkApproverQty IS NULL THEN '' | |||||
| ELSE TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(stkApproverQty, 0), 2))) | |||||
| WHEN stkApproverQty IS NULL THEN '0' | |||||
| WHEN COALESCE(stkApproverQty, 0) < 0 THEN CONCAT('(', FORMAT(-stkApproverQty, 0), ')') | |||||
| ELSE FORMAT(COALESCE(stkApproverQty, 0), 0) | |||||
| END AS stockTakeQty, | END AS stockTakeQty, | ||||
| /* 超撿量 = varianceQty(無盤點紀錄就空白) */ | |||||
| /* 超撿量 = varianceQty(無盤點紀錄顯示 0) */ | |||||
| CASE | CASE | ||||
| WHEN stkVarianceQty IS NULL THEN '' | |||||
| ELSE TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(COALESCE(stkVarianceQty, 0), 2))) | |||||
| WHEN stkVarianceQty IS NULL THEN '0' | |||||
| WHEN COALESCE(stkVarianceQty, 0) < 0 THEN CONCAT('(', FORMAT(-stkVarianceQty, 0), ')') | |||||
| ELSE FORMAT(COALESCE(stkVarianceQty, 0), 0) | |||||
| END AS variance, | END AS variance, | ||||
| /* 盤盈虧百分比(無盤點或 bookQty=0 顯示 0%) */ | |||||
| CASE | CASE | ||||
| WHEN stkVarianceQty IS NULL THEN '' | |||||
| WHEN COALESCE(stkBookQty, 0) = 0 THEN '' | |||||
| ELSE CONCAT( | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT((COALESCE(stkVarianceQty, 0) / stkBookQty) * 100, 2))), | |||||
| '%' | |||||
| ) | |||||
| WHEN stkVarianceQty IS NULL THEN '0%' | |||||
| WHEN COALESCE(stkBookQty, 0) = 0 THEN '0%' | |||||
| WHEN (COALESCE(stkVarianceQty, 0) / stkBookQty) * 100 < 0 THEN CONCAT('(', FORMAT(-(COALESCE(stkVarianceQty, 0) / stkBookQty) * 100, 0), '%)') | |||||
| ELSE CONCAT(FORMAT((COALESCE(stkVarianceQty, 0) / stkBookQty) * 100, 0), '%') | |||||
| END AS variancePercentage, | END AS variancePercentage, | ||||
| /* item totals(footer 用) */ | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(openingQty, 0)) OVER (PARTITION BY itemNo), 2))) AS totalOpeningBalance, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(inQty, 0)) OVER (PARTITION BY itemNo), 2))) AS totalCumStockIn, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(outQty, 0)) OVER (PARTITION BY itemNo), 2))) AS totalCumStockOut, | |||||
| TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(currentQty, 0)) OVER (PARTITION BY itemNo), 2))) AS totalCurrentBalance | |||||
| /* item totals(footer 用);負數括號顯示,無小數 */ | |||||
| CASE WHEN SUM(COALESCE(openingQty, 0)) OVER (PARTITION BY itemNo) < 0 THEN CONCAT('(', FORMAT(-SUM(COALESCE(openingQty, 0)) OVER (PARTITION BY itemNo), 0), ')') ELSE FORMAT(SUM(COALESCE(openingQty, 0)) OVER (PARTITION BY itemNo), 0) END AS totalOpeningBalance, | |||||
| 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 | |||||
| FROM data | FROM data | ||||
| ORDER BY | ORDER BY | ||||
| @@ -164,6 +164,14 @@ class ReportController( | |||||
| return ResponseEntity(pdfBytes, headers, HttpStatus.OK) | return ResponseEntity(pdfBytes, headers, HttpStatus.OK) | ||||
| } | } | ||||
| @GetMapping("/fg-stock-out-traceability-handlers") | |||||
| fun getFGStockOutTraceabilityHandlers(): List<String> = | |||||
| reportService.getDistinctHandlersForFGStockOutTraceability() | |||||
| @GetMapping("/material-stock-out-traceability-handlers") | |||||
| fun getMaterialStockOutTraceabilityHandlers(): List<String> = | |||||
| reportService.getDistinctHandlersForMaterialStockOutTraceability() | |||||
| @GetMapping("/print-fg-stock-out-traceability") | @GetMapping("/print-fg-stock-out-traceability") | ||||
| fun generateFGStockOutTraceabilityReport( | fun generateFGStockOutTraceabilityReport( | ||||
| @RequestParam(required = false) stockCategory: String?, | @RequestParam(required = false) stockCategory: String?, | ||||
| @@ -171,7 +179,8 @@ class ReportController( | |||||
| @RequestParam(required = false) itemCode: String?, | @RequestParam(required = false) itemCode: String?, | ||||
| @RequestParam(required = false) year: String?, | @RequestParam(required = false) year: String?, | ||||
| @RequestParam(required = false) lastOutDateStart: String?, | @RequestParam(required = false) lastOutDateStart: String?, | ||||
| @RequestParam(required = false) lastOutDateEnd: String? | |||||
| @RequestParam(required = false) lastOutDateEnd: String?, | |||||
| @RequestParam(required = false) handler: String? | |||||
| ): ResponseEntity<ByteArray> { | ): ResponseEntity<ByteArray> { | ||||
| val parameters = mutableMapOf<String, Any>() | val parameters = mutableMapOf<String, Any>() | ||||
| @@ -193,7 +202,8 @@ class ReportController( | |||||
| itemCode, | itemCode, | ||||
| year, | year, | ||||
| lastOutDateStart, | lastOutDateStart, | ||||
| lastOutDateEnd | |||||
| lastOutDateEnd, | |||||
| handler | |||||
| ) | ) | ||||
| val pdfBytes = reportService.createPdfResponse( | val pdfBytes = reportService.createPdfResponse( | ||||
| @@ -217,7 +227,8 @@ class ReportController( | |||||
| @RequestParam(required = false) itemCode: String?, | @RequestParam(required = false) itemCode: String?, | ||||
| @RequestParam(required = false) year: String?, | @RequestParam(required = false) year: String?, | ||||
| @RequestParam(required = false) lastOutDateStart: String?, | @RequestParam(required = false) lastOutDateStart: String?, | ||||
| @RequestParam(required = false) lastOutDateEnd: String? | |||||
| @RequestParam(required = false) lastOutDateEnd: String?, | |||||
| @RequestParam(required = false) handler: String? | |||||
| ): ResponseEntity<ByteArray> { | ): ResponseEntity<ByteArray> { | ||||
| val parameters = mutableMapOf<String, Any>() | val parameters = mutableMapOf<String, Any>() | ||||
| @@ -239,7 +250,8 @@ class ReportController( | |||||
| itemCode, | itemCode, | ||||
| year, | year, | ||||
| lastOutDateStart, | lastOutDateStart, | ||||
| lastOutDateEnd | |||||
| lastOutDateEnd, | |||||
| handler | |||||
| ) | ) | ||||
| val pdfBytes = reportService.createPdfResponse( | val pdfBytes = reportService.createPdfResponse( | ||||
| @@ -57,10 +57,10 @@ | |||||
| <field name="cumStockIn" class="java.lang.String"/> | <field name="cumStockIn" class="java.lang.String"/> | ||||
| <field name="cumStockOut" class="java.lang.String"/> | <field name="cumStockOut" class="java.lang.String"/> | ||||
| <field name="currentBalance" class="java.lang.String"/> | <field name="currentBalance" class="java.lang.String"/> | ||||
| <field name="totalStockIn" class="java.math.BigDecimal"/> | |||||
| <field name="totalCumOpeningBal" class="java.math.BigDecimal"/> | |||||
| <field name="totalStockOut" class="java.math.BigDecimal"/> | |||||
| <field name="totalCumBalance" class="java.math.BigDecimal"/> | |||||
| <field name="totalStockIn" class="java.lang.String"/> | |||||
| <field name="totalCumOpeningBal" class="java.lang.String"/> | |||||
| <field name="totalStockOut" class="java.lang.String"/> | |||||
| <field name="totalCumBalance" class="java.lang.String"/> | |||||
| <field name="lastOutDate" class="java.lang.String"/> | <field name="lastOutDate" class="java.lang.String"/> | ||||
| <field name="lastInDate" class="java.lang.String"/> | <field name="lastInDate" class="java.lang.String"/> | ||||
| <field name="reOrderLevel" class="java.lang.String"/> | <field name="reOrderLevel" class="java.lang.String"/> | ||||
| @@ -100,32 +100,32 @@ | |||||
| </textElement> | </textElement> | ||||
| <text><![CDATA[貨品總量:]]></text> | <text><![CDATA[貨品總量:]]></text> | ||||
| </staticText> | </staticText> | ||||
| <textField pattern="#,##0"> | |||||
| <textField> | |||||
| <reportElement x="416" y="0" width="50" height="18" uuid="6c141d7d-766e-49bf-b987-e790312573a5"> | <reportElement x="416" y="0" width="50" height="18" uuid="6c141d7d-766e-49bf-b987-e790312573a5"> | ||||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | <property name="com.jaspersoft.studio.unit.width" value="px"/> | ||||
| </reportElement> | </reportElement> | ||||
| <textElement textAlignment="Right" verticalAlignment="Middle"> | <textElement textAlignment="Right" verticalAlignment="Middle"> | ||||
| <font fontName="微軟正黑體"/> | <font fontName="微軟正黑體"/> | ||||
| </textElement> | </textElement> | ||||
| <textFieldExpression><![CDATA[$F{totalStockIn}]]></textFieldExpression> | |||||
| <textFieldExpression><![CDATA[$F{totalStockIn} != null ? $F{totalStockIn} : ""]]></textFieldExpression> | |||||
| </textField> | </textField> | ||||
| <textField textAdjust="StretchHeight" pattern="#,##0"> | |||||
| <textField textAdjust="StretchHeight"> | |||||
| <reportElement x="466" y="0" width="50" height="18" uuid="f6be72ca-650f-4c37-a149-26d5502be17a"> | <reportElement x="466" y="0" width="50" height="18" uuid="f6be72ca-650f-4c37-a149-26d5502be17a"> | ||||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | <property name="com.jaspersoft.studio.unit.width" value="px"/> | ||||
| </reportElement> | </reportElement> | ||||
| <textElement textAlignment="Right" verticalAlignment="Middle"> | <textElement textAlignment="Right" verticalAlignment="Middle"> | ||||
| <font fontName="微軟正黑體"/> | <font fontName="微軟正黑體"/> | ||||
| </textElement> | </textElement> | ||||
| <textFieldExpression><![CDATA[$F{totalStockOut}]]></textFieldExpression> | |||||
| <textFieldExpression><![CDATA[$F{totalStockOut} != null ? $F{totalStockOut} : ""]]></textFieldExpression> | |||||
| </textField> | </textField> | ||||
| <textField pattern="#,##0"> | |||||
| <textField> | |||||
| <reportElement x="524" y="0" width="66" height="18" uuid="636bf782-3020-4b49-837e-53ede91d1443"> | <reportElement x="524" y="0" width="66" height="18" uuid="636bf782-3020-4b49-837e-53ede91d1443"> | ||||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | <property name="com.jaspersoft.studio.unit.width" value="px"/> | ||||
| </reportElement> | </reportElement> | ||||
| <textElement textAlignment="Right" verticalAlignment="Middle"> | <textElement textAlignment="Right" verticalAlignment="Middle"> | ||||
| <font fontName="微軟正黑體"/> | <font fontName="微軟正黑體"/> | ||||
| </textElement> | </textElement> | ||||
| <textFieldExpression><![CDATA[$F{totalCumBalance}]]></textFieldExpression> | |||||
| <textFieldExpression><![CDATA[$F{totalCumBalance} != null ? $F{totalCumBalance} : ""]]></textFieldExpression> | |||||
| </textField> | </textField> | ||||
| <line> | <line> | ||||
| <reportElement x="2" y="18" width="799" height="1" uuid="c31c2ebe-c7c9-4ad1-abc2-fa0ba99dd77f"> | <reportElement x="2" y="18" width="799" height="1" uuid="c31c2ebe-c7c9-4ad1-abc2-fa0ba99dd77f"> | ||||
| @@ -381,20 +381,14 @@ | |||||
| <textElement textAlignment="Right" verticalAlignment="Top"> | <textElement textAlignment="Right" verticalAlignment="Top"> | ||||
| <font fontName="微軟正黑體" size="10"/> | <font fontName="微軟正黑體" size="10"/> | ||||
| </textElement> | </textElement> | ||||
| <textFieldExpression><![CDATA[$F{cumOpeningBal} == null || $F{cumOpeningBal}.trim().length() == 0 | |||||
| ? "" | |||||
| : new java.text.DecimalFormat("#,##0") | |||||
| .format(new java.math.BigDecimal($F{cumOpeningBal}))]]></textFieldExpression> | |||||
| <textFieldExpression><![CDATA[$F{cumOpeningBal} == null || $F{cumOpeningBal}.trim().length() == 0 ? "" : $F{cumOpeningBal}]]></textFieldExpression> | |||||
| </textField> | </textField> | ||||
| <textField textAdjust="StretchHeight"> | <textField textAdjust="StretchHeight"> | ||||
| <reportElement x="466" y="0" width="50" height="16" uuid="ca781a08-f648-41bc-a44f-b4d3fc05bf24"/> | <reportElement x="466" y="0" width="50" height="16" uuid="ca781a08-f648-41bc-a44f-b4d3fc05bf24"/> | ||||
| <textElement textAlignment="Right" verticalAlignment="Top"> | <textElement textAlignment="Right" verticalAlignment="Top"> | ||||
| <font fontName="微軟正黑體" size="10"/> | <font fontName="微軟正黑體" size="10"/> | ||||
| </textElement> | </textElement> | ||||
| <textFieldExpression><![CDATA[$F{stockOut} == null || $F{stockOut}.trim().length() == 0 | |||||
| ? "" | |||||
| : new java.text.DecimalFormat("#,##0") | |||||
| .format(new java.math.BigDecimal($F{stockOut}))]]></textFieldExpression> | |||||
| <textFieldExpression><![CDATA[$F{stockOut} == null || $F{stockOut}.trim().length() == 0 ? "" : $F{stockOut}]]></textFieldExpression> | |||||
| </textField> | </textField> | ||||
| <textField textAdjust="StretchHeight"> | <textField textAdjust="StretchHeight"> | ||||
| <reportElement x="524" y="0" width="66" height="16" uuid="0011e1ab-a161-46d8-af84-629e955e5461"> | <reportElement x="524" y="0" width="66" height="16" uuid="0011e1ab-a161-46d8-af84-629e955e5461"> | ||||
| @@ -403,10 +397,7 @@ | |||||
| <textElement textAlignment="Right" verticalAlignment="Top"> | <textElement textAlignment="Right" verticalAlignment="Top"> | ||||
| <font fontName="微軟正黑體" size="10"/> | <font fontName="微軟正黑體" size="10"/> | ||||
| </textElement> | </textElement> | ||||
| <textFieldExpression><![CDATA[$F{cumBalance} == null || $F{cumBalance}.trim().length() == 0 | |||||
| ? "" | |||||
| : new java.text.DecimalFormat("#,##0") | |||||
| .format(new java.math.BigDecimal($F{cumBalance}))]]></textFieldExpression> | |||||
| <textFieldExpression><![CDATA[$F{cumBalance} == null || $F{cumBalance}.trim().length() == 0 ? "" : $F{cumBalance}]]></textFieldExpression> | |||||
| </textField> | </textField> | ||||
| <textField textAdjust="StretchHeight"> | <textField textAdjust="StretchHeight"> | ||||
| <reportElement x="592" y="0" width="112" height="16" uuid="abcf9c0b-8e93-44b0-af99-8b7fe7812ff8"> | <reportElement x="592" y="0" width="112" height="16" uuid="abcf9c0b-8e93-44b0-af99-8b7fe7812ff8"> | ||||
| @@ -452,10 +443,7 @@ | |||||
| <textElement textAlignment="Right" verticalAlignment="Top"> | <textElement textAlignment="Right" verticalAlignment="Top"> | ||||
| <font fontName="微軟正黑體" size="10"/> | <font fontName="微軟正黑體" size="10"/> | ||||
| </textElement> | </textElement> | ||||
| <textFieldExpression><![CDATA[$F{stockIn} == null || $F{stockIn}.trim().length() == 0 | |||||
| ? "" | |||||
| : new java.text.DecimalFormat("#,##0") | |||||
| .format(new java.math.BigDecimal($F{stockIn}))]]></textFieldExpression> | |||||
| <textFieldExpression><![CDATA[$F{stockIn} == null || $F{stockIn}.trim().length() == 0 ? "" : $F{stockIn}]]></textFieldExpression> | |||||
| </textField> | </textField> | ||||
| </band> | </band> | ||||
| </detail> | </detail> | ||||