|
|
@@ -20,7 +20,7 @@ open class StockLedgerFixAdjService( |
|
|
private val log = LoggerFactory.getLogger(StockLedgerFixAdjService::class.java) |
|
|
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. |
|
|
* 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. |
|
|
* 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. |
|
|
* 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<Long>() |
|
|
|
|
|
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<AdjGap> { |
|
|
|
|
|
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`. |
|
|
* ADJ window: ledger date = [adjDate], gap sums use `date < adjDate+1`. |
|
|
* Default adjDate = yesterday. Pass today for freeze-night dump (e.g. 31/8). |
|
|
* Default adjDate = yesterday. Pass today for freeze-night dump (e.g. 31/8). |
|
|
@@ -680,6 +807,7 @@ open class StockLedgerFixAdjService( |
|
|
companion object { |
|
|
companion object { |
|
|
private val ADJ_TOLERANCE = BigDecimal("0.01") |
|
|
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_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" |
|
|
private const val ADJ_HEADER_REMARKS = "stock-ledger-fix 2.7 ADJ" |
|
|
} |
|
|
} |
|
|
} |
|
|
} |