Преглед изворни кода

庫存查詢 / 盤點調整權限 / 批次現況報表

production
CANCERYS\kw093 пре 1 недеља
родитељ
комит
722e3a45c1
4 измењених фајлова са 50 додато и 15 уклоњено
  1. +1
    -1
      src/main/java/com/ffii/fpsms/config/security/SecurityConfig.java
  2. +40
    -9
      src/main/java/com/ffii/fpsms/modules/report/service/StockLotOnhandReportService.kt
  3. +4
    -3
      src/main/java/com/ffii/fpsms/modules/report/web/ReportController.kt
  4. +5
    -2
      src/main/java/com/ffii/fpsms/modules/report/web/StockLotOnhandReportController.kt

+ 1
- 1
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


+ 40
- 9
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<Long>, out: MutableMap<Long, Pair<String, String>>) {
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<Long>, hits: MutableMap<Long, SilSolHit>): 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<Long>, hits: MutableMap<Long, SilSolHit>): 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<Long>, hits: MutableMap<Long, SilSolHit>): 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<Map<String, Any>>,
lastTrnByLot: Map<Long, Pair<String, String>>,
@@ -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<String, Any>(22)
val row = HashMap<String, Any>(24)
row["inventoryLotLineId"] = lotId.toString()
row["remarks"] = r["remarks"] ?: ""
row["itemNo"] = r["itemNo"] ?: ""
row["itemName"] = r["itemName"] ?: ""
row["unitOfMeasure"] = r["unitOfMeasure"] ?: ""


+ 4
- 3
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)
}
}



+ 5
- 2
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<Map<String, Any>>,
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()


Loading…
Откажи
Сачувај