From 722e3a45c1e71cc4c5d79795399e7ef03ca38b33 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 8 Sep 2026 18:06:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BA=AB=E5=AD=98=E6=9F=A5=E8=A9=A2=20/=20?= =?UTF-8?q?=E7=9B=A4=E9=BB=9E=E8=AA=BF=E6=95=B4=E6=AC=8A=E9=99=90=20/=20?= =?UTF-8?q?=E6=89=B9=E6=AC=A1=E7=8F=BE=E6=B3=81=E5=A0=B1=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fpsms/config/security/SecurityConfig.java | 2 +- .../service/StockLotOnhandReportService.kt | 49 +++++++++++++++---- .../modules/report/web/ReportController.kt | 7 +-- .../web/StockLotOnhandReportController.kt | 7 ++- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java b/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java index 7dfc830..44c34b5 100644 --- a/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java +++ b/src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java @@ -77,7 +77,7 @@ public class SecurityConfig { /** * FP-MTMS Version Checklist | Functions Ref. No. 67 | v1.0.0 | 2026-08-13 * FP-MTMS Version Checklist | Functions Ref. No. 51 | v1.0.1 | 2026-08-06 - * FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.5 | 2026-08-05 + * FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.6 | 2026-09-07 * (stockAdjustment/submit → INVENTORY_ADJUST only) */ @Bean diff --git a/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt b/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt index aecdd1f..15fffb4 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt @@ -9,6 +9,8 @@ import java.time.format.DateTimeFormatter * 庫存批次現況(Stock Balance):永遠今天。 * 現存讀 [inventory_lot_line](available、未過期、in-out > 0)。 * 最後異動:有 inventoryLotLineId 的帳本用 MAX(id);缺的比 SIL/SOL 時間。 + * 單位均價:貨品 AverageUnitPrice(HKD/stock UOM)用 item_uom 比率轉成該 lot 單位; + * 同一貨品同一 UOM 共用同一均價。庫存總價值 = 該 UOM 均價 × lot 數量。 * 單位均價/庫存總價值只填 root PO 為 PP/PF 的批(TRF 往回走);其他來源空白。 */ @Service @@ -70,6 +72,19 @@ lot_root_origin AS ( WHERE t.rn = 1 )""" + /** One stock-unit item_uom per item (same pick as item average-price SQL). */ + private const val STOCK_ONE_CTE_SQL = """ +stock_one AS ( + SELECT iu.* + FROM item_uom iu + INNER JOIN ( + SELECT itemId, MIN(id) AS id + FROM item_uom + WHERE deleted = 0 AND stockUnit = 1 + GROUP BY itemId + ) x ON x.id = iu.id +)""" + private const val ROOT_STOCK_IN_JOIN_SQL = """ LEFT JOIN lot_root_origin lro ON lro.lotId = il.id @@ -85,7 +100,7 @@ lot_root_origin AS ( val stockDate: String, ) - /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.0 | 2026-08-31 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ fun search( itemCode: String?, storeId: String?, @@ -133,12 +148,13 @@ lot_root_origin AS ( } val lotOriginFilterSql = buildLotOriginFilterSql(lotOrigin) val originJoinSql = ROOT_STOCK_IN_JOIN_SQL - val ctePrefix = "WITH RECURSIVE\n$LOT_ROOT_ORIGIN_CTE_SQL\n" + val ctePrefix = "WITH RECURSIVE\n$LOT_ROOT_ORIGIN_CTE_SQL,\n$STOCK_ONE_CTE_SQL\n" val liveLots = jdbcDao.queryForList( """ ${ctePrefix}SELECT ill.id AS inventoryLotLineId, + COALESCE(NULLIF(TRIM(ill.remarks), ''), '') AS remarks, COALESCE(it.code, '') AS itemNo, COALESCE(it.name, '') AS itemName, COALESCE(uc.udfudesc, uc.code, '') AS unitOfMeasure, @@ -149,7 +165,19 @@ lot_root_origin AS ( COALESCE(wh.area, '') AS areaPart, COALESCE(wh.slot, '') AS slotPart, (COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0)) AS lotQtyRaw, - COALESCE(CAST(NULLIF(it.AverageUnitPrice, '') AS DECIMAL(14, 4)), 0) AS avgUnitPriceRaw, + CASE + WHEN iu.id IS NULL OR so.id IS NULL THEN + COALESCE(CAST(NULLIF(TRIM(it.AverageUnitPrice), '') AS DECIMAL(14, 4)), 0) + WHEN iu.uomId = so.uomId THEN + COALESCE(CAST(NULLIF(TRIM(it.AverageUnitPrice), '') AS DECIMAL(14, 4)), 0) + ELSE + ROUND( + COALESCE(CAST(NULLIF(TRIM(it.AverageUnitPrice), '') AS DECIMAL(14, 4)), 0) + * (COALESCE(iu.ratioN, 1) / COALESCE(NULLIF(iu.ratioD, 0), 1)) + * (COALESCE(so.ratioD, 1) / COALESCE(NULLIF(so.ratioN, 0), 1)), + 4 + ) + END AS avgUnitPriceRaw, UPPER(LEFT(TRIM(COALESCE(root_po.code, '')), 2)) AS rootPoPrefix FROM inventory_lot_line ill INNER JOIN inventory_lot il @@ -162,6 +190,8 @@ lot_root_origin AS ( ON iu.id = ill.stockItemUomId AND iu.deleted = 0 LEFT JOIN uom_conversion uc ON uc.id = iu.uomId + LEFT JOIN stock_one so + ON so.itemId = it.id $originJoinSql WHERE ill.deleted = 0 AND it.code IS NOT NULL AND it.code <> '' @@ -200,7 +230,7 @@ lot_root_origin AS ( private data class SilSolHit(val ts: String, val date: String, val kind: String) - /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.0 | 2026-08-31 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ private fun loadLastTrnFromLedger(lotIds: List, out: MutableMap>) { if (lotIds.isEmpty()) return // idx_ledger_lot_date_id is (lot, date, id) — bad for MAX(id); force the lot-only index @@ -233,7 +263,7 @@ lot_root_origin AS ( } } - /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.0 | 2026-08-31 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ private fun fillLastTrnFromLotHeaderSil(missing: List, hits: MutableMap): Int { var n = 0 for (chunk in missing.chunked(800)) { @@ -259,7 +289,7 @@ lot_root_origin AS ( return n } - /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.0 | 2026-08-31 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ private fun fillLastTrnFromSilLine(missing: List, hits: MutableMap): Int { var n = 0 for (chunk in missing.chunked(800)) { @@ -281,7 +311,7 @@ lot_root_origin AS ( return n } - /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.0 | 2026-08-31 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ private fun fillLastTrnFromSol(missing: List, hits: MutableMap): Int { var n = 0 for (chunk in missing.chunked(800)) { @@ -325,7 +355,7 @@ lot_root_origin AS ( else -> "" } - /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.0 | 2026-08-31 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ private fun assembleRows( liveLots: List>, lastTrnByLot: Map>, @@ -344,8 +374,9 @@ lot_root_origin AS ( val last = lastTrnByLot[lotId] val origin = r["rootPoPrefix"]?.toString().orEmpty().uppercase() val showPrice = origin == "PP" || origin == "PF" - val row = HashMap(22) + val row = HashMap(24) row["inventoryLotLineId"] = lotId.toString() + row["remarks"] = r["remarks"] ?: "" row["itemNo"] = r["itemNo"] ?: "" row["itemName"] = r["itemName"] ?: "" row["unitOfMeasure"] = r["unitOfMeasure"] ?: "" diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt index fc24362..dfcc5ba 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt @@ -540,11 +540,12 @@ class ReportController( numberStyle: XSSFCellStyle, dashStyle: XSSFCellStyle, preferInt: Boolean = false, + emptyAsBlank: Boolean = false, ) { val cell = row.createCell(col) val parsed = parseSignedNumber(value) if (parsed == null) { - cell.setCellValue("-") + cell.setCellValue(if (emptyAsBlank) "" else "-") cell.cellStyle = dashStyle return } @@ -914,8 +915,8 @@ class ReportController( setNumberCellFromFormatted(r, 7, m["totalVariance"], styles.int, styles.dash, preferInt = true) setNumberCellFromFormatted(r, 8, m["totalDefectiveGoods"], styles.int, styles.dash, preferInt = true) setNumberCellFromFormatted(r, 9, m["totalCurrentBalance"], styles.int, styles.dash, preferInt = true) - setNumberCellFromFormatted(r, 10, m["avgUnitPrice"], styles.number, styles.dash, preferInt = false) - setNumberCellFromFormatted(r, 11, m["totalStockBalance"], styles.number, styles.dash, preferInt = false) + setNumberCellFromFormatted(r, 10, m["avgUnitPrice"], styles.number, styles.dash, preferInt = false, emptyAsBlank = true) + setNumberCellFromFormatted(r, 11, m["totalStockBalance"], styles.number, styles.dash, preferInt = false, emptyAsBlank = true) } } diff --git a/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt b/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt index 7aac550..01efc8f 100644 --- a/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt +++ b/src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt @@ -43,7 +43,7 @@ class StockLotOnhandReportController( val number: CellStyle, ) - /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.0 | 2026-08-31 */ + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ @GetMapping("/print-stock-lot-onhand-excel") fun exportExcel( @RequestParam(required = false) itemCode: String?, @@ -191,6 +191,7 @@ class StockLotOnhandReportController( return v } + /** FP-MTMS Version Checklist | Functions Ref. No. 68 | v1.0.1 | 2026-09-07 */ private fun createExcel( dbData: List>, reportDate: String, @@ -215,6 +216,7 @@ class StockLotOnhandReportController( "樓層", "倉庫", "區域", "儲位", "最後異動日", "最後異動(入庫/出庫)", "批號結餘", "單位均價", "庫存總價值", + "備註", ) val totalColumns = headers.size var rowIndex = 0 @@ -299,6 +301,7 @@ class StockLotOnhandReportController( setNumberCell(r, 12, m["lotQtyRaw"], styles.number) setNumberCell(r, 13, m["avgUnitPriceRaw"], styles.number) setNumberCell(r, 14, m["stockValueRaw"], styles.number) + setTextCell(r, 15, m["remarks"], styles.text) } } @@ -307,7 +310,7 @@ class StockLotOnhandReportController( sheet.setAutoFilter(CellRangeAddress(headerRowIndex, lastRowIndex, 0, totalColumns - 1)) } sheet.createFreezePane(0, headerRowIndex + 1) - intArrayOf(14, 28, 14, 8, 20, 12, 10, 12, 10, 10, 12, 20, 12, 12, 14) + intArrayOf(14, 28, 14, 8, 20, 12, 10, 12, 10, 10, 12, 20, 12, 12, 14, 28) .forEachIndexed { i, w -> sheet.setColumnWidth(i, w * 256) } val out = ByteArrayOutputStream()