| @@ -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( | |||
| stockCategory: String?, | |||
| stockSubCategory: String?, | |||
| itemCode: String?, | |||
| year: String?, | |||
| lastOutDateStart: String?, | |||
| lastOutDateEnd: String? | |||
| lastOutDateEnd: String?, | |||
| handler: String? | |||
| ): List<Map<String, Any>> { | |||
| val args = mutableMapOf<String, Any>() | |||
| @@ -316,6 +348,13 @@ return result | |||
| "AND DATE(IFNULL(dpor.RequiredDeliveryDate, do.estimatedArrivalDate)) <= DATE(:lastOutDateEnd)" | |||
| } else "" | |||
| val handlerSql = buildMultiValueExactClause( | |||
| handler, | |||
| "COALESCE(picker_user.name, modified_user.name, '')", | |||
| "handler", | |||
| args | |||
| ) | |||
| val sql = """ | |||
| SELECT | |||
| IFNULL(DATE_FORMAT( | |||
| @@ -416,6 +455,7 @@ return result | |||
| $yearSql | |||
| $lastOutDateStartSql | |||
| $lastOutDateEndSql | |||
| $handlerSql | |||
| GROUP BY | |||
| sol.id, | |||
| dpor.RequiredDeliveryDate, | |||
| @@ -469,7 +509,8 @@ fun searchMaterialStockOutTraceabilityReport( | |||
| itemCode: String?, | |||
| year: String?, | |||
| lastOutDateStart: String?, | |||
| lastOutDateEnd: String? | |||
| lastOutDateEnd: String?, | |||
| handler: String? | |||
| ): List<Map<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 = """ | |||
| SELECT | |||
| IFNULL(it.code, '') AS itemNo, | |||
| @@ -597,6 +645,7 @@ fun searchMaterialStockOutTraceabilityReport( | |||
| $yearSql | |||
| $lastOutDateStartSql | |||
| $lastOutDateEndSql | |||
| $handlerSql | |||
| ORDER BY | |||
| it.code, | |||
| il.lotNo, | |||
| @@ -706,7 +755,6 @@ fun searchMaterialStockOutTraceabilityReport( | |||
| } else "" | |||
| val sql = """ | |||
| SELECT | |||
| COALESCE(it.code, '') as itemNo, | |||
| COALESCE(it.name, '') as itemName, | |||
| @@ -715,25 +763,13 @@ fun searchMaterialStockOutTraceabilityReport( | |||
| COALESCE(sil.lotNo, il.lotNo, '') as lotNo, | |||
| COALESCE(DATE_FORMAT(COALESCE(sil.expiryDate, il.expiryDate), '%Y-%m-%d'), '') as expiryDate, | |||
| 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( | |||
| 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 | |||
| END, 2))) as iqcDefectPercentage, | |||
| 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(sp_si.code, sp_po.code, '') as supplierID, | |||
| 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 | |||
| LEFT JOIN stock_in si ON sil.stockInId = si.id | |||
| LEFT JOIN purchase_order po ON sil.purchaseOrderId = po.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 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_line ill ON il.id = ill.inventoryLotId | |||
| LEFT JOIN warehouse wh ON ill.warehouseId = wh.id | |||
| @@ -838,17 +864,17 @@ fun searchMaterialStockOutTraceabilityReport( | |||
| COALESCE(item_agg.storeLocation, '') as storeLocation, | |||
| COALESCE(DATE_FORMAT(item_agg.lastInDate, '%Y-%m-%d'), '') as lastInDate, | |||
| 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 defectiveGoods, | |||
| '' 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 | |||
| 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) | |||
| @@ -255,35 +255,27 @@ SELECT | |||
| reOrderLevel, | |||
| 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 分區) | |||
| 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) | |||
| 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 | |||
| ORDER BY | |||
| @@ -248,41 +248,42 @@ SELECT | |||
| expiryDate, | |||
| 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(lastOutDateRaw, '%Y-%m-%d'), '') AS lastOutDate, | |||
| COALESCE(DATE_FORMAT(stockTakeDateRaw, '%Y-%m-%d'), '') AS stockTakeDate, | |||
| /* 取貨量 = approverStockTakeQty(無盤點紀錄就空白) */ | |||
| /* 取貨量 = approverStockTakeQty(無盤點紀錄顯示 0) */ | |||
| 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, | |||
| /* 超撿量 = varianceQty(無盤點紀錄就空白) */ | |||
| /* 超撿量 = varianceQty(無盤點紀錄顯示 0) */ | |||
| 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, | |||
| /* 盤盈虧百分比(無盤點或 bookQty=0 顯示 0%) */ | |||
| 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, | |||
| /* 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 | |||
| ORDER BY | |||
| @@ -164,6 +164,14 @@ class ReportController( | |||
| 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") | |||
| fun generateFGStockOutTraceabilityReport( | |||
| @RequestParam(required = false) stockCategory: String?, | |||
| @@ -171,7 +179,8 @@ class ReportController( | |||
| @RequestParam(required = false) itemCode: String?, | |||
| @RequestParam(required = false) year: String?, | |||
| @RequestParam(required = false) lastOutDateStart: String?, | |||
| @RequestParam(required = false) lastOutDateEnd: String? | |||
| @RequestParam(required = false) lastOutDateEnd: String?, | |||
| @RequestParam(required = false) handler: String? | |||
| ): ResponseEntity<ByteArray> { | |||
| val parameters = mutableMapOf<String, Any>() | |||
| @@ -193,7 +202,8 @@ class ReportController( | |||
| itemCode, | |||
| year, | |||
| lastOutDateStart, | |||
| lastOutDateEnd | |||
| lastOutDateEnd, | |||
| handler | |||
| ) | |||
| val pdfBytes = reportService.createPdfResponse( | |||
| @@ -217,7 +227,8 @@ class ReportController( | |||
| @RequestParam(required = false) itemCode: String?, | |||
| @RequestParam(required = false) year: String?, | |||
| @RequestParam(required = false) lastOutDateStart: String?, | |||
| @RequestParam(required = false) lastOutDateEnd: String? | |||
| @RequestParam(required = false) lastOutDateEnd: String?, | |||
| @RequestParam(required = false) handler: String? | |||
| ): ResponseEntity<ByteArray> { | |||
| val parameters = mutableMapOf<String, Any>() | |||
| @@ -239,7 +250,8 @@ class ReportController( | |||
| itemCode, | |||
| year, | |||
| lastOutDateStart, | |||
| lastOutDateEnd | |||
| lastOutDateEnd, | |||
| handler | |||
| ) | |||
| val pdfBytes = reportService.createPdfResponse( | |||
| @@ -57,10 +57,10 @@ | |||
| <field name="cumStockIn" class="java.lang.String"/> | |||
| <field name="cumStockOut" 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="lastInDate" class="java.lang.String"/> | |||
| <field name="reOrderLevel" class="java.lang.String"/> | |||
| @@ -100,32 +100,32 @@ | |||
| </textElement> | |||
| <text><![CDATA[貨品總量:]]></text> | |||
| </staticText> | |||
| <textField pattern="#,##0"> | |||
| <textField> | |||
| <reportElement x="416" y="0" width="50" height="18" uuid="6c141d7d-766e-49bf-b987-e790312573a5"> | |||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | |||
| </reportElement> | |||
| <textElement textAlignment="Right" verticalAlignment="Middle"> | |||
| <font fontName="微軟正黑體"/> | |||
| </textElement> | |||
| <textFieldExpression><![CDATA[$F{totalStockIn}]]></textFieldExpression> | |||
| <textFieldExpression><![CDATA[$F{totalStockIn} != null ? $F{totalStockIn} : ""]]></textFieldExpression> | |||
| </textField> | |||
| <textField textAdjust="StretchHeight" pattern="#,##0"> | |||
| <textField textAdjust="StretchHeight"> | |||
| <reportElement x="466" y="0" width="50" height="18" uuid="f6be72ca-650f-4c37-a149-26d5502be17a"> | |||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | |||
| </reportElement> | |||
| <textElement textAlignment="Right" verticalAlignment="Middle"> | |||
| <font fontName="微軟正黑體"/> | |||
| </textElement> | |||
| <textFieldExpression><![CDATA[$F{totalStockOut}]]></textFieldExpression> | |||
| <textFieldExpression><![CDATA[$F{totalStockOut} != null ? $F{totalStockOut} : ""]]></textFieldExpression> | |||
| </textField> | |||
| <textField pattern="#,##0"> | |||
| <textField> | |||
| <reportElement x="524" y="0" width="66" height="18" uuid="636bf782-3020-4b49-837e-53ede91d1443"> | |||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | |||
| </reportElement> | |||
| <textElement textAlignment="Right" verticalAlignment="Middle"> | |||
| <font fontName="微軟正黑體"/> | |||
| </textElement> | |||
| <textFieldExpression><![CDATA[$F{totalCumBalance}]]></textFieldExpression> | |||
| <textFieldExpression><![CDATA[$F{totalCumBalance} != null ? $F{totalCumBalance} : ""]]></textFieldExpression> | |||
| </textField> | |||
| <line> | |||
| <reportElement x="2" y="18" width="799" height="1" uuid="c31c2ebe-c7c9-4ad1-abc2-fa0ba99dd77f"> | |||
| @@ -381,20 +381,14 @@ | |||
| <textElement textAlignment="Right" verticalAlignment="Top"> | |||
| <font fontName="微軟正黑體" size="10"/> | |||
| </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 textAdjust="StretchHeight"> | |||
| <reportElement x="466" y="0" width="50" height="16" uuid="ca781a08-f648-41bc-a44f-b4d3fc05bf24"/> | |||
| <textElement textAlignment="Right" verticalAlignment="Top"> | |||
| <font fontName="微軟正黑體" size="10"/> | |||
| </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 textAdjust="StretchHeight"> | |||
| <reportElement x="524" y="0" width="66" height="16" uuid="0011e1ab-a161-46d8-af84-629e955e5461"> | |||
| @@ -403,10 +397,7 @@ | |||
| <textElement textAlignment="Right" verticalAlignment="Top"> | |||
| <font fontName="微軟正黑體" size="10"/> | |||
| </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 textAdjust="StretchHeight"> | |||
| <reportElement x="592" y="0" width="112" height="16" uuid="abcf9c0b-8e93-44b0-af99-8b7fe7812ff8"> | |||
| @@ -452,10 +443,7 @@ | |||
| <textElement textAlignment="Right" verticalAlignment="Top"> | |||
| <font fontName="微軟正黑體" size="10"/> | |||
| </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> | |||
| </band> | |||
| </detail> | |||