釐清 inventoryLotLineId=實際批,對齊 actualPickLotLineId(無行為變更) tab 數量 0 不顯示 (0);Badge→文字 (n);欄位「工單編號/貨品編號」;中文 i18n(工單流程、預計生產日期、含未按規劃完成…)production
| @@ -2370,7 +2370,7 @@ private fun updateInventoryAfterLotLineChange(lotLine: InventoryLotLine) { | |||||
| e.printStackTrace() | e.printStackTrace() | ||||
| } | } | ||||
| } | } | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 */ | |||||
| private fun createStockLedgerForStockOut( | private fun createStockLedgerForStockOut( | ||||
| stockOutLine: StockOutLine, | stockOutLine: StockOutLine, | ||||
| ledgerType: String? = null, | ledgerType: String? = null, | ||||
| @@ -2875,7 +2875,7 @@ open fun submitIssueWithQty(request: SubmitIssueWithQtyRequest): MessageResponse | |||||
| } | } | ||||
| // New: Create stock ledger with explicit balance (to avoid double subtraction) | // New: Create stock ledger with explicit balance (to avoid double subtraction) | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 */ | |||||
| private fun createStockLedgerForStockOutWithBalance( | private fun createStockLedgerForStockOutWithBalance( | ||||
| stockOutLine: StockOutLine, | stockOutLine: StockOutLine, | ||||
| balance: Double, | balance: Double, | ||||
| @@ -8,10 +8,10 @@ class DoUserPickAuditReportService( | |||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| ) { | ) { | ||||
| /** | /** | ||||
| * FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.1 | 2026-08-05 | |||||
| * DO workbench user-audit rows (Just issue only). | * DO workbench user-audit rows (Just issue only). | ||||
| * issueTypes may combine: 不正常已完成+超出提料+未完全提料+不正常換批. | |||||
| * 未完全提料/超出提料 use POL-level SUM(stock_out_line.qty) vs pol.qty (multi-lot split OK if total matches). | |||||
| * issueTypes may combine: 跳過提料(使用「已完成」)+多提+少提+擅自換批/揀錯批. | |||||
| * 少提/多提 use POL-level SUM(stock_out_line.qty) vs pol.qty (multi-lot split OK if total matches). | |||||
| * Only completed tickets (`dop.ticketStatus = completed`) are included. | * Only completed tickets (`dop.ticketStatus = completed`) are included. | ||||
| * | * | ||||
| * Perf: filter day/completed SOL first (`day_sol`) by pickTime only (idx_sol_pickTime_deleted), | * Perf: filter day/completed SOL first (`day_sol`) by pickTime only (idx_sol_pickTime_deleted), | ||||
| @@ -120,14 +120,14 @@ class DoUserPickAuditReportService( | |||||
| IFNULL(d.isIssueJustComplete, 0) AS issueJustComplete, | IFNULL(d.isIssueJustComplete, 0) AS issueJustComplete, | ||||
| COALESCE(u.name, d.handlerName, '') AS user, | COALESCE(u.name, d.handlerName, '') AS user, | ||||
| TRIM(BOTH '+' FROM CONCAT_WS('+', | TRIM(BOTH '+' FROM CONCAT_WS('+', | ||||
| CASE WHEN IFNULL(d.isIssueJustComplete, 0) = 1 THEN '不正常已完成' ELSE NULL END, | |||||
| CASE WHEN IFNULL(d.isIssueJustComplete, 0) = 1 THEN '跳過提料(使用「已完成」)' ELSE NULL END, | |||||
| CASE WHEN d.polQty IS NOT NULL | CASE WHEN d.polQty IS NOT NULL | ||||
| AND COALESCE(pp.pickedTotal, 0) > d.polQty THEN '超出提料' ELSE NULL END, | |||||
| AND COALESCE(pp.pickedTotal, 0) > d.polQty THEN '多提' ELSE NULL END, | |||||
| CASE WHEN d.polQty IS NOT NULL | CASE WHEN d.polQty IS NOT NULL | ||||
| AND COALESCE(pp.pickedTotal, 0) < d.polQty | AND COALESCE(pp.pickedTotal, 0) < d.polQty | ||||
| AND d.inventoryLotLineId IS NOT NULL THEN '未完全提料' ELSE NULL END, | |||||
| AND d.inventoryLotLineId IS NOT NULL THEN '少提' ELSE NULL END, | |||||
| CASE WHEN sug_lot.lotNo IS NOT NULL AND act_lot.lotNo IS NOT NULL | CASE WHEN sug_lot.lotNo IS NOT NULL AND act_lot.lotNo IS NOT NULL | ||||
| AND sug_lot.lotNo <> act_lot.lotNo THEN '不正常換批' ELSE NULL END | |||||
| AND sug_lot.lotNo <> act_lot.lotNo THEN '擅自換批/揀錯批' ELSE NULL END | |||||
| )) AS issueTypes, | )) AS issueTypes, | ||||
| d.solId AS stockOutLineId | d.solId AS stockOutLineId | ||||
| FROM day_sol d | FROM day_sol d | ||||
| @@ -183,7 +183,7 @@ class DoUserPickAuditReportService( | |||||
| return jdbcDao.queryForList(sql, args) as List<Map<String, Any?>> | return jdbcDao.queryForList(sql, args) as List<Map<String, Any?>> | ||||
| } | } | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.1 | 2026-08-05 */ | |||||
| fun getDistinctHandlers(): List<String> { | fun getDistinctHandlers(): List<String> { | ||||
| val sql = """ | val sql = """ | ||||
| SELECT DISTINCT TRIM(COALESCE(u.name, dop.handlerName, '')) AS handler | SELECT DISTINCT TRIM(COALESCE(u.name, dop.handlerName, '')) AS handler | ||||
| @@ -23,11 +23,11 @@ import java.io.ByteArrayOutputStream | |||||
| class DoUserPickAuditReportController( | class DoUserPickAuditReportController( | ||||
| private val doUserPickAuditReportService: DoUserPickAuditReportService, | private val doUserPickAuditReportService: DoUserPickAuditReportService, | ||||
| ) { | ) { | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.1 | 2026-08-05 */ | |||||
| @GetMapping("/do-user-pick-audit-handlers") | @GetMapping("/do-user-pick-audit-handlers") | ||||
| fun handlers(): List<String> = doUserPickAuditReportService.getDistinctHandlers() | fun handlers(): List<String> = doUserPickAuditReportService.getDistinctHandlers() | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 39 | v1.0.1 | 2026-08-05 */ | |||||
| @GetMapping("/print-do-user-pick-audit-excel") | @GetMapping("/print-do-user-pick-audit-excel") | ||||
| fun exportExcel( | fun exportExcel( | ||||
| @RequestParam(required = false) dateStart: String?, | @RequestParam(required = false) dateStart: String?, | ||||
| @@ -93,9 +93,9 @@ class DoUserPickAuditReportController( | |||||
| "actualQty" to "實際提料數量", | "actualQty" to "實際提料數量", | ||||
| "lotBeforeQty" to "提料前庫存", | "lotBeforeQty" to "提料前庫存", | ||||
| "lotAfterQty" to "提料後庫存", | "lotAfterQty" to "提料後庫存", | ||||
| "issueJustComplete" to "不正常已完成", | |||||
| "issueJustComplete" to "跳過提料(使用「已完成」)", | |||||
| "user" to "提料人", | "user" to "提料人", | ||||
| "issueTypes" to "問題類型", | |||||
| "issueTypes" to "偏離類型", | |||||
| ) | ) | ||||
| var r = 0 | var r = 0 | ||||
| @@ -21,7 +21,20 @@ open class StockLedger: BaseEntity<Long>() { | |||||
| @JoinColumn(name = "stockOutLineId") | @JoinColumn(name = "stockOutLineId") | ||||
| open var stockOutLine: StockOutLine? = null | open var stockOutLine: StockOutLine? = null | ||||
| /** Lot line for this movement; prefer this over resolving via stock in/out line. */ | |||||
| /** | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 | |||||
| * | |||||
| * stock_ledger.inventoryLotLineId = the **actual** inventory_lot_line.id this | |||||
| * in/out movement posted against (the lot whose qty changed). | |||||
| * | |||||
| * Not a suggested lot. For pick-order flows (FG / JO / PO), this must align with | |||||
| * suggested_pick_lot.actualPickLotLineId (and stock_out_line.inventoryLotLineId), | |||||
| * NOT suggested_pick_lot.suggestedLotLineId. | |||||
| * If the picker switches lot (suggested A, scan B), ledger points to B. | |||||
| * | |||||
| * Prefer reading this column directly; do not infer lot only via stockInLine / | |||||
| * stockOutLine when this is populated. | |||||
| */ | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "inventoryLotLineId") | @JoinColumn(name = "inventoryLotLineId") | ||||
| open var inventoryLotLine: InventoryLotLine? = null | open var inventoryLotLine: InventoryLotLine? = null | ||||
| @@ -40,11 +53,17 @@ open class StockLedger: BaseEntity<Long>() { | |||||
| @Column(name = "balance") | @Column(name = "balance") | ||||
| open var balance: Double? = null | open var balance: Double? = null | ||||
| /** Lot available qty (in−out) before this movement; null for pre-V2 rows. */ | |||||
| /** | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 | |||||
| * Available qty of that same actual lot (in−out) **before** this movement; null for pre-V2 rows. | |||||
| */ | |||||
| @Column(name = "lotQtyBefore", precision = 14, scale = 2) | @Column(name = "lotQtyBefore", precision = 14, scale = 2) | ||||
| open var lotQtyBefore: BigDecimal? = null | open var lotQtyBefore: BigDecimal? = null | ||||
| /** Lot available qty (in−out) after this movement; null for pre-V2 rows. */ | |||||
| /** | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 | |||||
| * Available qty of that same actual lot (in−out) **after** this movement; null for pre-V2 rows. | |||||
| */ | |||||
| @Column(name = "lotQtyAfter", precision = 14, scale = 2) | @Column(name = "lotQtyAfter", precision = 14, scale = 2) | ||||
| open var lotQtyAfter: BigDecimal? = null | open var lotQtyAfter: BigDecimal? = null | ||||
| @@ -29,7 +29,14 @@ open class SuggestedPickLot: BaseEntity<Long>() { | |||||
| @JoinColumn(name = "suggestedLotLineId") | @JoinColumn(name = "suggestedLotLineId") | ||||
| open var suggestedLotLine: InventoryLotLine? = null | open var suggestedLotLine: InventoryLotLine? = null | ||||
| /** Actual picked lot line; switch/scan updates this without changing [suggestedLotLine]. */ | |||||
| /** | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 33 | v1.0.2 | 2026-08-03 | |||||
| * | |||||
| * Actual picked inventory_lot_line.id. | |||||
| * On create: init = suggestedLotLineId; lot switch/scan updates this only (suggested stays). | |||||
| * Downstream (display, COALESCE reads, stock_ledger.inventoryLotLineId / SOL.inventoryLotLineId) | |||||
| * must use this, not [suggestedLotLine]. | |||||
| */ | |||||
| @ManyToOne | @ManyToOne | ||||
| @JoinColumn(name = "actualPickLotLineId") | @JoinColumn(name = "actualPickLotLineId") | ||||
| open var actualPickLotLine: InventoryLotLine? = null | open var actualPickLotLine: InventoryLotLine? = null | ||||
| @@ -1378,7 +1378,7 @@ open class StockInLineService( | |||||
| } | } | ||||
| @Transactional | @Transactional | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 */ | |||||
| private fun createStockLedgerForStockIn(stockInLine: StockInLine, inQty: Double) { | private fun createStockLedgerForStockIn(stockInLine: StockInLine, inQty: Double) { | ||||
| val _t0 = System.nanoTime() | val _t0 = System.nanoTime() | ||||
| fun _msSince(t: Long): Double = (System.nanoTime() - t) / 1_000_000.0 | fun _msSince(t: Long): Double = (System.nanoTime() - t) / 1_000_000.0 | ||||
| @@ -5,15 +5,28 @@ import com.ffii.fpsms.modules.stock.entity.StockLedger | |||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| /** | /** | ||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 | |||||
| * Fills [StockLedger.inventoryLotLine], [lotQtyBefore], [lotQtyAfter]. | |||||
| * Prefer explicit [inventoryLotLine]; else fall back to stockOutLine / stockInLine related lot. | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 | |||||
| * | * | ||||
| * [lotAvailableAfterMove] = lot (in−out) **after** this movement has already been applied on the lot line. | |||||
| * [signedDelta] = +inQty or −outQty for this ledger row (stock-in positive, stock-out negative of posted qty). | |||||
| * Fills [StockLedger.inventoryLotLine], [lotQtyBefore], [lotQtyAfter] for each movement. | |||||
| * | |||||
| * [StockLedger.inventoryLotLine] / inventoryLotLineId = **actual** moved lot line id | |||||
| * (the lot whose qty changed). Prefer explicit [inventoryLotLine]; else fall back to | |||||
| * stockOutLine / stockInLine related lot. | |||||
| * | |||||
| * On pick/suggestion flows: pass/resolve the lot that was actually picked/posted | |||||
| * (= suggested_pick_lot.actualPickLotLineId / stock_out_line.inventoryLotLineId), | |||||
| * never suggested_pick_lot.suggestedLotLineId alone. Lot switch (suggested A, scan B) | |||||
| * → ledger must point to B. | |||||
| * | |||||
| * [lotQtyBefore]/[lotQtyAfter] are snapshots of that same actual lot's available qty. | |||||
| * [lotAvailableAfterMove] = lot (in−out) **after** this movement has already been applied. | |||||
| * [signedDelta] = +inQty or −outQty for this ledger row (stock-in positive, stock-out negative). | |||||
| */ | */ | ||||
| object StockLedgerLotSnapshot { | object StockLedgerLotSnapshot { | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 */ | |||||
| /** | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 | |||||
| * Sets ledger.inventoryLotLine to the actual lot, then lotQtyBefore/After from that lot. | |||||
| */ | |||||
| fun apply( | fun apply( | ||||
| ledger: StockLedger, | ledger: StockLedger, | ||||
| inventoryLotLine: InventoryLotLine? = null, | inventoryLotLine: InventoryLotLine? = null, | ||||
| @@ -34,12 +47,12 @@ object StockLedgerLotSnapshot { | |||||
| ledger.lotQtyBefore = lotAvailableAfterMove.subtract(signedDelta) | ledger.lotQtyBefore = lotAvailableAfterMove.subtract(signedDelta) | ||||
| } | } | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 */ | |||||
| fun availableQty(ill: InventoryLotLine): BigDecimal = | fun availableQty(ill: InventoryLotLine): BigDecimal = | ||||
| (ill.inQty ?: BigDecimal.ZERO).subtract(ill.outQty ?: BigDecimal.ZERO) | (ill.inQty ?: BigDecimal.ZERO).subtract(ill.outQty ?: BigDecimal.ZERO) | ||||
| /** | /** | ||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 | |||||
| * FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 | |||||
| * Ledger UOM must follow the lot line's stock ItemUom → uom.id | * Ledger UOM must follow the lot line's stock ItemUom → uom.id | ||||
| * (same item may have lots with different UOMs; item stockUnit flag can change later). | * (same item may have lots with different UOMs; item stockUnit flag can change later). | ||||
| */ | */ | ||||
| @@ -1509,7 +1509,7 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { | |||||
| } | } | ||||
| } | } | ||||
| @Transactional | @Transactional | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 */ | |||||
| private fun createStockLedgerForStockOut(stockOutLine: StockOutLine) { | private fun createStockLedgerForStockOut(stockOutLine: StockOutLine) { | ||||
| val item = stockOutLine.item ?: return | val item = stockOutLine.item ?: return | ||||
| val inventory = itemUomService.findInventoryForItemBaseUom(item.id!!) ?: return | val inventory = itemUomService.findInventoryForItemBaseUom(item.id!!) ?: return | ||||
| @@ -1757,7 +1757,7 @@ open fun newBatchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { | |||||
| } | } | ||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 */ | |||||
| /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 */ | |||||
| private fun createStockLedgerForPickDelta( | private fun createStockLedgerForPickDelta( | ||||
| stockOutLine: StockOutLine, | stockOutLine: StockOutLine, | ||||
| deltaQty: BigDecimal, | deltaQty: BigDecimal, | ||||
| @@ -2488,7 +2488,7 @@ private fun applyVarianceAdjustment( | |||||
| } | } | ||||
| val newBalance = previousBalance - qtyToRemove.toDouble() | val newBalance = previousBalance - qtyToRemove.toDouble() | ||||
| // FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.0 | 2026-08-03 | |||||
| // FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 | |||||
| // Snapshot from live lot line (in−out) at approve time — not stock_take_record.bookQty. | // Snapshot from live lot line (in−out) at approve time — not stock_take_record.bookQty. | ||||
| val lotAfterAvail = StockLedgerLotSnapshot.availableQty(latestLine).subtract(qtyToRemove) | val lotAfterAvail = StockLedgerLotSnapshot.availableQty(latestLine).subtract(qtyToRemove) | ||||