From b9c6c4f27d96e9cd1cf20c5d76114ad55c283119 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 9 Oct 2026 15:26:02 +0800 Subject: [PATCH] 2.7 fix be 2.7 a 2.7 b --- .../master/service/StockLedgerFixService.kt | 3 + .../ledgerfix/StockLedgerFixAdjService.kt | 130 +++++++++++++++++- .../ledgerfix/StockLedgerFixExportService.kt | 35 +++++ .../master/web/StockLedgerFixController.kt | 5 +- .../master/web/models/StockLedgerFixModels.kt | 2 + 5 files changed, 173 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/StockLedgerFixService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/StockLedgerFixService.kt index a55c197d..3d132841 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/StockLedgerFixService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/StockLedgerFixService.kt @@ -61,6 +61,9 @@ open class StockLedgerFixService( open fun applyAdjAlign(adjDateRaw: String? = null): StockLedgerFixAdjResponse = adj.applyAdjAlign(adjDateRaw) + open fun applyAdjLotLineIn(adjDateRaw: String? = null): StockLedgerFixAdjResponse = + adj.applyAdjLotLineIn(adjDateRaw) + open fun fixDay(dateRaw: String, stepsRaw: List? = null): StockLedgerFixRunResponse = backfill.fixDay(dateRaw, stepsRaw) diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixAdjService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixAdjService.kt index b5a2c61e..daede986 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixAdjService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixAdjService.kt @@ -20,7 +20,7 @@ open class StockLedgerFixAdjService( private val log = LoggerFactory.getLogger(StockLedgerFixAdjService::class.java) /** - * One ADJ per inventory bucket on [adjDate] (default yesterday; pass today on freeze-night dump). + * 2.7b. One ADJ per inventory bucket on [adjDate] (default yesterday; pass today on freeze-night dump). * Quantity is onHandQty minus the bucket ledger net, including rows with no lot. * The row is attached to the lot line whose own net is furthest from its ledger. * Lot lines and inventory are not updated, so the lot-line trigger does not run. @@ -150,6 +150,133 @@ open class StockLedgerFixAdjService( } } + /** + * 2.7a. Lot lines with outQty greater than inQty get inQty raised to outQty, + * and one inbound ADJ on that same lot for the gap. A second run finds no gap. + * The lot-line update fires the inventory trigger, so the ledger in must be the same qty. + */ + open fun applyAdjLotLineIn(adjDateRaw: String? = null): StockLedgerFixAdjResponse { + if (!support.tryLock(StockLedgerFixSupport.RUN_LOCK)) { + throw IllegalStateException("fix already running") + } + val (adjDate, asOfExclusive) = resolveAdjWindow(adjDateRaw) + val t0 = System.nanoTime() + try { + val gaps = loadLotLineInGaps() + var insertedIn = 0 + val lotIds = linkedSetOf() + if (gaps.isNotEmpty()) { + val insTx = TransactionTemplate(support.transactionManager) + insTx.timeout = 300 + insTx.execute { + var stockInId: Long? = null + fun inHeader(): Long = + stockInId ?: ensureAdjStockInHeader(adjDate).also { stockInId = it } + gaps.forEach { g -> + val raised = support.jdbcDao.executeUpdate( + """ + UPDATE inventory_lot_line + SET inQty = outQty, + modified = NOW(), + modifiedBy = 'stock-ledger-fix' + WHERE id = :id + AND IFNULL(deleted, 0) = 0 + AND COALESCE(outQty, 0) > COALESCE(inQty, 0) + """.trimIndent(), + mapOf("id" to g.lotLineId), + ) + if (raised == 0) return@forEach + insertAdjLedger( + g, adjDate, inQty = g.missIn, outQty = null, + remarks = ADJ_LOT_LINE_REMARKS, stockInId = inHeader(), + ) + insertedIn++ + lotIds.add(g.lotLineId) + } + } + } + val adjustmentRecords = TransactionTemplate(support.transactionManager).let { tx -> + tx.timeout = 300 + tx.execute { insertMissingAdjustmentRecords() } ?: 0 + } + val filledLotQty = support.timed("adj 2.7a 2.4") { + backfill.fillLotQtyForLotIds(lotIds.toList(), asOfExclusive) + } + val filledBalance = support.timed("adj 2.7a 2.5") { + backfill.fillBalanceOnNewAdjRows(adjDate, asOfExclusive) + } + val range = if (lotIds.isEmpty()) null else backfill.dateRangeForFilter( + "inventoryLotLineId IN (:lotIds)", + mapOf("today" to asOfExclusive, "lotIds" to lotIds.toList()), + ) + val dayRows = if (range != null && lotIds.isNotEmpty()) { + support.timed("adj 2.7a 2.6") { + backfill.upsertStockLotDayRange(lotIds.toList(), range.first, range.second) + } + } else 0 + log.info( + "stock-ledger-fix 2.7a {} lines={} adjRecord={} lotQty={} bal={} day={} total={}ms", + adjDate, insertedIn, adjustmentRecords, filledLotQty, filledBalance, dayRows, + (System.nanoTime() - t0) / 1_000_000, + ) + return StockLedgerFixAdjResponse( + adjDate = adjDate.toString(), + insertedIn = insertedIn, + insertedOut = 0, + overIssuePatched = insertedIn, + filledLotQty = filledLotQty, + filledBalance = filledBalance, + dayRowsWritten = dayRows, + adjustmentRecords = adjustmentRecords, + ) + } catch (e: Exception) { + log.warn("stock-ledger-fix 2.7a FAILED after {}ms: {}", (System.nanoTime() - t0) / 1_000_000, e.message) + throw e + } finally { + support.unlock(StockLedgerFixSupport.RUN_LOCK) + } + } + + private fun loadLotLineInGaps(): List { + return support.jdbcDao.queryForList( + """ + SELECT ill.id AS lotLineId, + il.itemId AS itemId, + it.code AS itemCode, + iu.uomId AS uomId, + i.id AS inventoryId, + CAST(COALESCE(ill.outQty, 0) - COALESCE(ill.inQty, 0) AS DECIMAL(14,2)) AS gap + FROM inventory_lot_line ill + INNER JOIN inventory_lot il + ON il.id = ill.inventoryLotId AND IFNULL(il.deleted, 0) = 0 + INNER JOIN items it ON it.id = il.itemId + INNER JOIN item_uom iu ON iu.id = ill.stockItemUomId + LEFT JOIN inventory i + ON i.itemId = il.itemId + AND i.stockUomId = iu.uomId + AND IFNULL(i.deleted, 0) = 0 + WHERE IFNULL(ill.deleted, 0) = 0 + AND COALESCE(ill.outQty, 0) > COALESCE(ill.inQty, 0) + """.trimIndent(), + ).mapNotNull { row -> + val gap = support.toDecimal(row["gap"]) + if (gap.compareTo(ADJ_TOLERANCE) <= 0) return@mapNotNull null + AdjGap( + lotLineId = support.toLong(row["lotLineId"]), + inventoryId = support.toLongOrNull(row["inventoryId"]), + itemId = support.toLongOrNull(row["itemId"]), + itemCode = row["itemCode"]?.toString(), + uomId = support.toLongOrNull(row["uomId"]), + lineIn = BigDecimal.ZERO, + lineOut = BigDecimal.ZERO, + ledgerIn = BigDecimal.ZERO, + ledgerOut = BigDecimal.ZERO, + missIn = gap, + missOut = BigDecimal.ZERO, + ) + } + } + /** * ADJ window: ledger date = [adjDate], gap sums use `date < adjDate+1`. * Default adjDate = yesterday. Pass today for freeze-night dump (e.g. 31/8). @@ -680,6 +807,7 @@ open class StockLedgerFixAdjService( companion object { private val ADJ_TOLERANCE = BigDecimal("0.01") private const val ADJ_DOC_REMARKS = "stock-ledger-fix bucket ADJ (lot line unchanged)" + private const val ADJ_LOT_LINE_REMARKS = "stock-ledger-fix 2.7a ADJ" private const val ADJ_HEADER_REMARKS = "stock-ledger-fix 2.7 ADJ" } } diff --git a/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixExportService.kt b/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixExportService.kt index baa95d32..2903c475 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixExportService.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/service/ledgerfix/StockLedgerFixExportService.kt @@ -436,6 +436,41 @@ open class StockLedgerFixExportService( jdbc: NamedParameterJdbcTemplate, args: Map, ) { + writeTempUpdate( + writer, + jdbc, + args, + title = "-- === 2.7a lot line inQty (out was greater; trigger refreshes onHand) ===\n", + selectSql = """ + SELECT ill.id, ill.inQty, ill.modified, ill.modifiedBy + FROM inventory_lot_line ill + WHERE ill.modifiedBy = 'stock-ledger-fix' + AND IFNULL(ill.deleted, 0) = 0 + AND EXISTS ( + SELECT 1 + FROM stock_ledger sl + INNER JOIN stock_in_line sil ON sil.id = sl.stockInLineId + WHERE sl.inventoryLotLineId = ill.id + AND sl.deleted = 0 + AND sl.type = 'ADJ' + AND sl.createdBy = 'stock-ledger-fix' + AND sl.date >= :from AND sl.date < :toExclusive + AND sil.remarks = 'stock-ledger-fix 2.7a ADJ' + ) + ORDER BY ill.id + """.trimIndent(), + tempName = "tmp_slf_ill_in", + columnDefs = "inQty DECIMAL(14,2) NULL, modified DATETIME NULL, modifiedBy VARCHAR(50) NULL", + columns = listOf("id", "inQty", "modified", "modifiedBy"), + updateSql = """ + UPDATE inventory_lot_line ill + INNER JOIN tmp_slf_ill_in t ON t.id = ill.id + SET ill.inQty = t.inQty, + ill.modified = t.modified, + ill.modifiedBy = t.modifiedBy + """.trimIndent(), + emptyComment = "-- (no 2.7a lot-line inQty in range)", + ) writer.write("-- === 2.7 ADJ stock_in / stock_out (1 header + many lines; apply before ledger ADJ FKs) ===\n") writeExportRows( writer, diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/StockLedgerFixController.kt b/src/main/java/com/ffii/fpsms/modules/master/web/StockLedgerFixController.kt index b255f47a..a5540f1a 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/StockLedgerFixController.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/StockLedgerFixController.kt @@ -98,7 +98,10 @@ open class StockLedgerFixController( @PreAuthorize("hasAnyAuthority('ADMIN','TESTING')") @PostMapping("/adj") open fun adjApply(@RequestBody(required = false) body: StockLedgerFixAdjRequest?) = - stockLedgerFixService.applyAdjAlign(body?.adjDate) + when (body?.step?.trim()?.lowercase()) { + "2.7a", "27a" -> stockLedgerFixService.applyAdjLotLineIn(body?.adjDate) + else -> stockLedgerFixService.applyAdjAlign(body?.adjDate) + } @PreAuthorize("hasAnyAuthority('ADMIN','TESTING')") @PostMapping("/run") diff --git a/src/main/java/com/ffii/fpsms/modules/master/web/models/StockLedgerFixModels.kt b/src/main/java/com/ffii/fpsms/modules/master/web/models/StockLedgerFixModels.kt index fd2a8a46..45f53287 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/web/models/StockLedgerFixModels.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/web/models/StockLedgerFixModels.kt @@ -101,6 +101,8 @@ data class StockLedgerFixSearchLotHit( data class StockLedgerFixAdjRequest( /** Optional ADJ ledger date (yyyy-MM-dd). Default = yesterday. Use today on freeze-night dump. */ val adjDate: String? = null, + /** `2.7a` raises lot-line inQty where outQty is greater. Empty = 2.7b bucket align. */ + val step: String? = null, ) data class StockLedgerFixAdjRow(