瀏覽代碼

update

undefined
CANCERYS\kw093 1 周之前
父節點
當前提交
5facc1c50c
共有 11 個檔案被更改,包括 501 行新增28 行删除
  1. +6
    -3
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt
  2. +28
    -0
      src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt
  3. +29
    -22
      src/main/java/com/ffii/fpsms/modules/master/service/StockLedgerFixService.kt
  4. +5
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/Inventory.kt
  5. +3
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt
  6. +4
    -0
      src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt
  7. +6
    -3
      src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineWorkbenchService.kt
  8. +44
    -0
      src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/01_add_inventory_stock_uom_id.sql
  9. +8
    -0
      src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/02_drop_inventory_lot_line_triggers.sql
  10. +170
    -0
      src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/03_inventory_lot_line_trigger_insert.sql
  11. +198
    -0
      src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/04_inventory_lot_line_trigger_update.sql

+ 6
- 3
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoWorkbenchMainService.kt 查看文件

@@ -2595,15 +2595,18 @@ return MessageResponse(
private fun createWorkbenchPickLedger(sol: StockOutLine, deltaQty: BigDecimal) {
if (deltaQty <= BigDecimal.ZERO) return
val solItem = sol.item ?: return
val inventory = itemUomService.findInventoryForItemBaseUom(solItem.id!!) ?: return
val ill = sol.inventoryLotLine?.id?.let { inventoryLotLineRepository.findById(it).orElse(sol.inventoryLotLine) }
?: sol.inventoryLotLine
val inventory = (
ill?.let { itemUomService.resolveInventoryForLot(it) }
?: itemUomService.findInventoryForItemBaseUom(solItem.id!!)
) ?: return
// Fast path (batch-submit style): avoid querying latest ledger.
// At this point inventory.onHandQty has already been updated by DB triggers after lot outQty change.
// So previousBalance can be reconstructed as (onHandAfter + deltaQty).
val onHandAfter = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
val previousBalance = onHandAfter + deltaQty.toDouble()
val newBalance = previousBalance - deltaQty.toDouble()
val ill = sol.inventoryLotLine?.id?.let { inventoryLotLineRepository.findById(it).orElse(sol.inventoryLotLine) }
?: sol.inventoryLotLine
val lotAfter = ill?.let { StockLedgerLotSnapshot.availableQty(it) }
val ledger = StockLedger().apply {
this.stockOutLine = sol


+ 28
- 0
src/main/java/com/ffii/fpsms/modules/master/service/ItemUomService.kt 查看文件

@@ -13,6 +13,7 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import com.ffii.fpsms.modules.master.entity.ItemsRepository
import com.ffii.fpsms.modules.stock.entity.Inventory
import com.ffii.fpsms.modules.stock.entity.InventoryLotLine
import com.ffii.fpsms.modules.stock.entity.InventoryRepository

@Service
@@ -86,6 +87,33 @@ open class ItemUomService(
return inventoryRepository.findFirstByItemIdAndDeletedIsFalseOrderByIdAsc(itemId)
}

/**
* Qty bucket for a lot line (ChangeList No.34). Same key as lot-line triggers:
* `ill.stockItemUomId` → `item_uom.uomId` = `inventory.stockUomId`.
* Does not create inventory; trigger is the writer.
*/
open fun resolveInventoryForLot(lotLine: InventoryLotLine): Inventory? {
val itemId = lotLine.inventoryLot?.item?.id ?: return null
val stockUomId = resolveLotStockUomId(lotLine, itemId)
?: return findInventoryForItemBaseUom(itemId)
return findInventoryForItemStockUom(itemId, stockUomId)
}

open fun findInventoryForItemStockUom(itemId: Long, stockUomId: Long): Inventory? {
inventoryRepository.findFirstByItemIdAndStockUomIdAndDeletedIsFalseOrderByIdAsc(itemId, stockUomId)
?.let { return it }
return inventoryRepository.findFirstByItemIdAndUomIdAndDeletedIsFalseOrderByIdAsc(itemId, stockUomId)
}

private fun resolveLotStockUomId(lotLine: InventoryLotLine, itemId: Long): Long? {
lotLine.stockUom?.uom?.id?.let { return it }
val itemUomId = lotLine.stockUom?.id
if (itemUomId != null) {
itemUomRespository.findById(itemUomId).getOrNull()?.uom?.id?.let { return it }
}
return findStockUnitByItemId(itemId)?.uom?.id
}

open fun findPickingUnitByItemId(itemId: Long): ItemUom? {
return itemUomRespository.findByItemIdAndPickingUnitIsTrueAndDeletedIsFalse(itemId)
}


+ 29
- 22
src/main/java/com/ffii/fpsms/modules/master/service/StockLedgerFixService.kt 查看文件

@@ -190,7 +190,7 @@ open class StockLedgerFixService(
WHERE inv.id = sl.inventoryId
AND IFNULL(inv.deleted, 0) = 0
AND inv.itemId = sl.itemId
AND inv.uomId = sl.uomId
AND COALESCE(inv.stockUomId, inv.uomId) = sl.uomId
)
) AS invIncorrect,
SUM(sl.lotQtyAfter IS NULL OR sl.lotQtyBefore IS NULL) AS lotQtyMiss,
@@ -964,13 +964,13 @@ open class StockLedgerFixService(
)
}

/** 2.3 inventory by (itemId, uomId) from 1.0 MySQL step. */
/** 2.3 inventory by (itemId, stockUomId bucket) from 1.0 MySQL step. */
private fun fillInventoryId(args: Map<String, Any>): Int {
return jdbcDao.executeUpdate(
"""
UPDATE stock_ledger sl
INNER JOIN (
SELECT i.itemId, i.uomId, MIN(i.id) AS inventoryId
SELECT i.itemId, COALESCE(i.stockUomId, i.uomId) AS bucketUomId, MIN(i.id) AS inventoryId
FROM inventory i
INNER JOIN (
SELECT DISTINCT itemId, uomId
@@ -979,10 +979,10 @@ open class StockLedgerFixService(
AND date >= :day AND date < :dayNext
AND itemId IS NOT NULL
AND uomId IS NOT NULL
) t ON t.itemId = i.itemId AND t.uomId = i.uomId
) t ON t.itemId = i.itemId AND t.uomId = COALESCE(i.stockUomId, i.uomId)
WHERE IFNULL(i.deleted, 0) = 0
GROUP BY i.itemId, i.uomId
) inv ON inv.itemId = sl.itemId AND inv.uomId = sl.uomId
GROUP BY i.itemId, COALESCE(i.stockUomId, i.uomId)
) inv ON inv.itemId = sl.itemId AND inv.bucketUomId = sl.uomId
SET sl.inventoryId = inv.inventoryId
WHERE sl.deleted = 0
AND sl.date >= :day AND sl.date < :dayNext
@@ -1556,11 +1556,11 @@ open class StockLedgerFixService(
"""
UPDATE stock_ledger sl
INNER JOIN (
SELECT i.itemId, i.uomId, MIN(i.id) AS inventoryId
SELECT i.itemId, COALESCE(i.stockUomId, i.uomId) AS bucketUomId, MIN(i.id) AS inventoryId
FROM inventory i
WHERE IFNULL(i.deleted, 0) = 0
GROUP BY i.itemId, i.uomId
) inv ON inv.itemId = sl.itemId AND inv.uomId = sl.uomId
GROUP BY i.itemId, COALESCE(i.stockUomId, i.uomId)
) inv ON inv.itemId = sl.itemId AND inv.bucketUomId = sl.uomId
SET sl.inventoryId = inv.inventoryId
WHERE sl.deleted = 0
AND sl.date < :today
@@ -1612,11 +1612,11 @@ open class StockLedgerFixService(
UPDATE stock_ledger sl
INNER JOIN tmp_sl_fix_inv_ids t ON t.id = sl.id
INNER JOIN (
SELECT i.itemId, i.uomId, MIN(i.id) AS inventoryId
SELECT i.itemId, COALESCE(i.stockUomId, i.uomId) AS bucketUomId, MIN(i.id) AS inventoryId
FROM inventory i
WHERE IFNULL(i.deleted, 0) = 0
GROUP BY i.itemId, i.uomId
) inv ON inv.itemId = sl.itemId AND inv.uomId = sl.uomId
GROUP BY i.itemId, COALESCE(i.stockUomId, i.uomId)
) inv ON inv.itemId = sl.itemId AND inv.bucketUomId = sl.uomId
SET sl.inventoryId = inv.inventoryId
""".trimIndent(),
)
@@ -2193,7 +2193,7 @@ open class StockLedgerFixService(
jdbc.query(
"""
SELECT id, created, createdBy, version, modified, modifiedBy, deleted,
itemId, uomId, onHandQty, onHoldQty, unavailableQty,
itemId, uomId, stockUomId, onHandQty, onHoldQty, unavailableQty,
price, currencyId, cpu, cpuUnit, cpm, cpmUnit, status
FROM inventory
WHERE IFNULL(deleted, 0) = 0
@@ -2205,7 +2205,7 @@ open class StockLedgerFixService(
writer.write(
"INSERT INTO inventory (\n" +
" id, created, createdBy, version, modified, modifiedBy, deleted,\n" +
" itemId, uomId, onHandQty, onHoldQty, unavailableQty,\n" +
" itemId, uomId, stockUomId, onHandQty, onHoldQty, unavailableQty,\n" +
" price, currencyId, cpu, cpuUnit, cpm, cpmUnit, status\n" +
") VALUES\n",
)
@@ -2215,7 +2215,7 @@ open class StockLedgerFixService(
writer.write(
"(${rsSql(rs, "id")}, ${rsSql(rs, "created")}, ${rsSql(rs, "createdBy")}, ${rsSql(rs, "version")}, " +
"${rsSql(rs, "modified")}, ${rsSql(rs, "modifiedBy")}, ${rsSql(rs, "deleted")}, " +
"${rsSql(rs, "itemId")}, ${rsSql(rs, "uomId")}, ${rsSql(rs, "onHandQty")}, " +
"${rsSql(rs, "itemId")}, ${rsSql(rs, "uomId")}, ${rsSql(rs, "stockUomId")}, ${rsSql(rs, "onHandQty")}, " +
"${rsSql(rs, "onHoldQty")}, ${rsSql(rs, "unavailableQty")}, ${rsSql(rs, "price")}, " +
"${rsSql(rs, "currencyId")}, ${rsSql(rs, "cpu")}, ${rsSql(rs, "cpuUnit")}, " +
"${rsSql(rs, "cpm")}, ${rsSql(rs, "cpmUnit")}, ${rsSql(rs, "status")})",
@@ -2226,6 +2226,7 @@ open class StockLedgerFixService(
"\nON DUPLICATE KEY UPDATE " +
"onHandQty=VALUES(onHandQty), onHoldQty=VALUES(onHoldQty), " +
"unavailableQty=VALUES(unavailableQty), status=VALUES(status), " +
"uomId=VALUES(uomId), stockUomId=VALUES(stockUomId), " +
"price=VALUES(price), currencyId=VALUES(currencyId), " +
"cpu=VALUES(cpu), cpuUnit=VALUES(cpuUnit), cpm=VALUES(cpm), cpmUnit=VALUES(cpmUnit), " +
"modified=VALUES(modified), modifiedBy=VALUES(modifiedBy), " +
@@ -2240,6 +2241,7 @@ open class StockLedgerFixService(
"\nON DUPLICATE KEY UPDATE " +
"onHandQty=VALUES(onHandQty), onHoldQty=VALUES(onHoldQty), " +
"unavailableQty=VALUES(unavailableQty), status=VALUES(status), " +
"uomId=VALUES(uomId), stockUomId=VALUES(stockUomId), " +
"price=VALUES(price), currencyId=VALUES(currencyId), " +
"cpu=VALUES(cpu), cpuUnit=VALUES(cpuUnit), cpm=VALUES(cpm), cpmUnit=VALUES(cpmUnit), " +
"modified=VALUES(modified), modifiedBy=VALUES(modifiedBy), " +
@@ -2652,11 +2654,11 @@ open class StockLedgerFixService(
LEFT JOIN items it ON it.id = il.itemId
LEFT JOIN item_uom iu ON iu.id = ill.stockItemUomId
LEFT JOIN (
SELECT itemId, uomId, MIN(id) AS inventoryId
SELECT itemId, COALESCE(stockUomId, uomId) AS bucketUomId, MIN(id) AS inventoryId
FROM inventory
WHERE IFNULL(deleted, 0) = 0
GROUP BY itemId, uomId
) inv ON inv.itemId = il.itemId AND inv.uomId = iu.uomId
GROUP BY itemId, COALESCE(stockUomId, uomId)
) inv ON inv.itemId = il.itemId AND inv.bucketUomId = iu.uomId
WHERE IFNULL(ill.deleted, 0) = 0
AND (
COALESCE(ill.inQty, 0) <> 0
@@ -2982,7 +2984,7 @@ open class StockLedgerFixService(
ON iu.id = ill.stockItemUomId
LEFT JOIN inventory inv
ON inv.itemId = il.itemId
AND inv.uomId = iu.uomId
AND COALESCE(inv.stockUomId, inv.uomId) = iu.uomId
AND IFNULL(inv.deleted, 0) = 0
WHERE IFNULL(ill.deleted, 0) = 0
AND il.itemId IS NOT NULL
@@ -2994,12 +2996,13 @@ open class StockLedgerFixService(

private const val INSERT_MISSING_INVENTORY_SQL = """
INSERT INTO inventory (
itemId, uomId, onHandQty, onHoldQty, unavailableQty,
itemId, uomId, stockUomId, onHandQty, onHoldQty, unavailableQty,
price, currencyId, cpu, cpuUnit, cpm, cpmUnit, status,
created, createdBy, modified, modifiedBy, version, deleted
)
SELECT
src.itemId,
COALESCE(baseIu.uomId, src.uomId),
src.uomId,
0, 0, 0,
COALESCE(tpl.price, 0),
@@ -3022,9 +3025,13 @@ open class StockLedgerFixService(
AND iu.uomId IS NOT NULL
GROUP BY il.itemId, iu.uomId
) src
LEFT JOIN item_uom baseIu
ON baseIu.itemId = src.itemId
AND baseIu.baseUnit = 1
AND IFNULL(baseIu.deleted, 0) = 0
LEFT JOIN inventory existing
ON existing.itemId = src.itemId
AND existing.uomId = src.uomId
AND COALESCE(existing.stockUomId, existing.uomId) = src.uomId
AND IFNULL(existing.deleted, 0) = 0
LEFT JOIN inventory tpl
ON tpl.id = (
@@ -3060,7 +3067,7 @@ open class StockLedgerFixService(
AND il.itemId IS NOT NULL
AND iu.uomId IS NOT NULL
GROUP BY il.itemId, iu.uomId
) agg ON agg.itemId = i.itemId AND agg.uomId = i.uomId
) agg ON agg.itemId = i.itemId AND agg.uomId = COALESCE(i.stockUomId, i.uomId)
SET
i.onHandQty = COALESCE(agg.onHand, 0),
i.onHoldQty = COALESCE(agg.onHold, 0),


+ 5
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/Inventory.kt 查看文件

@@ -58,6 +58,11 @@ open class Inventory: BaseEntity<Long>(){
@JoinColumn(name = "uomId")
open var uom: UomConversion? = null

/** Qty bucket key (lot stock UOM → uom_conversion.id). [uom] is base and is not overwritten by triggers. */
@ManyToOne
@JoinColumn(name = "stockUomId")
open var stockUom: UomConversion? = null

// @NotNull
@Column(name = "status")
open var status: String? = null

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt 查看文件

@@ -112,4 +112,7 @@ interface InventoryRepository: AbstractRepository<Inventory, Long> {

/** 與 item_uom 中 baseUnit=true 之 uomId 對齊時使用,避免同一 item 多筆 inventory 時 NonUniqueResult */
fun findFirstByItemIdAndUomIdAndDeletedIsFalseOrderByIdAsc(itemId: Long, uomId: Long): Inventory?

/** Qty bucket: inventory.stockUomId = lot stock UOM (uom_conversion.id). */
fun findFirstByItemIdAndStockUomIdAndDeletedIsFalseOrderByIdAsc(itemId: Long, stockUomId: Long): Inventory?
}

+ 4
- 0
src/main/java/com/ffii/fpsms/modules/stock/entity/projection/InventoryInfo.kt 查看文件

@@ -27,6 +27,10 @@ interface InventoryInfo{
// @get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty) / (target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioN / target.item.itemUoms.^[stockUnit == true && deleted == false]?.ratioD)}")
@get:Value("#{(target.onHandQty - target.onHoldQty - target.unavailableQty)}")
val availableQty: BigDecimal?
@get:Value("#{target.stockUom?.id}")
val stockUomId: Long?
@get:Value("#{target.stockUom?.code}")
val stockUomCode: String?
@get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom.code}")
val uomCode: String?
@get:Value("#{target.item.itemUoms.^[stockUnit == true && deleted == false]?.uom?.udfudesc}")


+ 6
- 3
src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineWorkbenchService.kt 查看文件

@@ -512,13 +512,16 @@ if (updated == 0) {
if (deltaQty <= BigDecimal.ZERO) return
val sol = stockOutLIneRepository.findById(stockOutLineId).orElse(null) ?: return
val solItem = sol.item ?: return
val inventory = itemUomService.findInventoryForItemBaseUom(solItem.id!!) ?: return
val ill = sol.inventoryLotLine?.id?.let { inventoryLotLineRepository.findById(it).orElse(sol.inventoryLotLine) }
?: sol.inventoryLotLine
val inventory = (
ill?.let { itemUomService.resolveInventoryForLot(it) }
?: itemUomService.findInventoryForItemBaseUom(solItem.id!!)
) ?: return
// Fast path: use inventory onHand after update + delta to reconstruct previous balance.
val onHandAfter = (inventory.onHandQty ?: BigDecimal.ZERO).toDouble()
val previousBalance = onHandAfter + deltaQty.toDouble()
val newBalance = previousBalance - deltaQty.toDouble()
val ill = sol.inventoryLotLine?.id?.let { inventoryLotLineRepository.findById(it).orElse(sol.inventoryLotLine) }
?: sol.inventoryLotLine
val lotAfter = ill?.let { StockLedgerLotSnapshot.availableQty(it) }
val ledger = StockLedger().apply {
this.stockOutLine = sol


+ 44
- 0
src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/01_add_inventory_stock_uom_id.sql 查看文件

@@ -0,0 +1,44 @@
--liquibase formatted sql

--changeset fpsms:20260911_inventory_add_stock_uom_id
--comment: No.1 inventory.stockUomId is the qty bucket; uomId stays base. No.35 unique among non-deleted rows.
--precondition onFail:MARK_RAN
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'inventory' AND COLUMN_NAME = 'stockUomId'

ALTER TABLE `inventory`
ADD COLUMN `stockUomId` INT NULL AFTER `uomId`;

UPDATE `inventory` i
INNER JOIN (
SELECT `itemId`, `uomId`, MIN(`id`) AS keepId
FROM `inventory`
WHERE IFNULL(`deleted`, 0) = 0
AND `uomId` IS NOT NULL
GROUP BY `itemId`, `uomId`
) k ON k.keepId = i.`id`
SET i.`stockUomId` = i.`uomId`
WHERE i.`stockUomId` IS NULL
AND i.`uomId` IS NOT NULL;

UPDATE `inventory` i
INNER JOIN `item_uom` iu
ON iu.`itemId` = i.`itemId`
AND iu.`baseUnit` = 1
AND IFNULL(iu.`deleted`, 0) = 0
SET i.`uomId` = iu.`uomId`
WHERE IFNULL(i.`deleted`, 0) = 0
AND iu.`uomId` IS NOT NULL;

ALTER TABLE `inventory`
ADD COLUMN `stockUomActiveKey` VARCHAR(40)
GENERATED ALWAYS AS (
IF(IFNULL(`deleted`, 0) = 0 AND `stockUomId` IS NOT NULL,
CONCAT(`itemId`, ':', `stockUomId`),
NULL)
) STORED;

CREATE UNIQUE INDEX `uk_inventory_item_stock_uom_active`
ON `inventory` (`stockUomActiveKey`);

CREATE INDEX `idx_inventory_item_stock_uom`
ON `inventory` (`itemId`, `stockUomId`);

+ 8
- 0
src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/02_drop_inventory_lot_line_triggers.sql 查看文件

@@ -0,0 +1,8 @@
--liquibase formatted sql

--changeset fpsms:20260911_drop_inventory_lot_line_triggers
--comment: Replace lot-line inventory triggers with stockUomId bucket lookup.

DROP TRIGGER IF EXISTS `inventory_lot_line_AFTER_INSERT`;

DROP TRIGGER IF EXISTS `inventory_lot_line_AFTER_UPDATE`;

+ 170
- 0
src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/03_inventory_lot_line_trigger_insert.sql 查看文件

@@ -0,0 +1,170 @@
-- liquibase formatted sql

-- changeset fpsms:20260911_inventory_lot_line_trigger_after_insert splitStatements:false
-- comment: No.2 AFTER INSERT: bucket by lot stock UOM (stockItemUomId → inventory.stockUomId). New rows set stockUomId + base uomId. UPDATE existing qty only.

create definer = current_user trigger `inventory_lot_line_AFTER_insert`
after insert
on `inventory_lot_line`
for each row
begin
declare inventoryId int default -1;
declare itemId int default null;
declare stockUomId int default null;
declare baseUomId int default null;
declare error_msg TEXT default null;
declare continue handler for sqlexception, sqlwarning
begin
get diagnostics condition 1 error_msg = message_text;
set error_msg = coalesce(error_msg, 'Unknown error occurred');
end;

select il.itemId
into itemId
from `inventory_lot` il
where il.id = new.inventoryLotId;

select iu.uomId
into stockUomId
from `item_uom` iu
where iu.id = new.stockItemUomId
limit 1;

if stockUomId is null and itemId is not null then
select iu.uomId
into stockUomId
from `item_uom` iu
where iu.itemId = itemId
and iu.stockUnit = true
and ifnull(iu.deleted, 0) = 0
limit 1;
end if;

if itemId is not null then
select iu.uomId
into baseUomId
from `item_uom` iu
where iu.itemId = itemId
and iu.baseUnit = true
and ifnull(iu.deleted, 0) = 0
limit 1;
end if;

if itemId is not null and stockUomId is not null then
select coalesce(
(select i.id
from `inventory` i
where i.itemId = itemId
and i.stockUomId = stockUomId
and ifnull(i.deleted, 0) = 0
order by i.id
limit 1),
(select i.id
from `inventory` i
where i.itemId = itemId
and i.uomId = stockUomId
and i.stockUomId is null
and ifnull(i.deleted, 0) = 0
order by i.id
limit 1),
-1
)
into inventoryId;
end if;

drop temporary table if exists `temp_inventory`;

create temporary table `temp_inventory`
(
onHandQty decimal(14, 2),
onHoldQty decimal(14, 2),
unavailableQty decimal(14, 2),
currencyId int,
currencyName varchar(30),
itemId int,
price decimal(14, 2)
);

insert into `temp_inventory` (onHandQty, onHoldQty, unavailableQty, currencyId, currencyName, itemId, price)
select coalesce(new.inQty, 0) - coalesce(new.outQty, 0) AS onHandQty,
coalesce(new.holdQty, 0) AS onHoldQty,
IF(
new.status = 'unavailable',
coalesce(new.inQty, 0) - coalesce(new.outQty, 0) - coalesce(new.holdQty, 0),
0
) AS unavailableQty,
c.id AS currencyId,
coalesce(c.name, 'HKD') AS currencyName,
il.itemId AS itemId,
coalesce(pql.price, 0) AS price
from `inventory_lot` il
left join `stock_in_line` sil on sil.id = il.stockInLineId
left join `stock_in` si on si.id = sil.stockInId
left join `purchase_order_line` pol on pol.id = sil.purchaseOrderLineId
left join `purchase_order` po on po.id = pol.purchaseOrderId
left join `purchase_quotation_line` pql on pql.itemId = il.itemId and pql.uomId = pol.uomId
left join `purchase_quotation` pq on pq.id = pql.purchaseQuotationId
left join `currency` c on c.id = pq.currencyId
where il.id = new.inventoryLotId;

begin
declare inventoryErrorMsg text default null;
declare continue handler for sqlexception, sqlwarning
begin
get diagnostics condition 1 inventoryErrorMsg = message_text;
set inventoryErrorMsg = coalesce(inventoryErrorMsg, 'Unknown error occurred');
end;

if inventoryId < 0 and stockUomId is not null then
insert into `inventory` (itemId, onHandQty, onHoldQty, unavailableQty, price, currencyId, cpu, cpuUnit, cpm,
cpmUnit, uomId, stockUomId, status)
select ti.itemId AS itemId,
ti.onHandQty AS onHandQty,
ti.onHoldQty AS onHoldQty,
ti.unavailableQty AS unavailableQty,
ti.price AS price,
ti.currencyId AS currencyId,
0 AS cpu,
ti.currencyName AS cpuUnit,
0 AS cpm,
ti.currencyName AS cpmUnit,
coalesce(baseUomId, stockUomId) AS uomId,
stockUomId AS stockUomId,
if(coalesce(ti.onHandQty - ti.onHoldQty - ti.unavailableQty, 0) > 0, 'available',
'unavailable') AS status
from `temp_inventory` ti;
else
update `inventory` i
join `temp_inventory` ti on i.id = inventoryId
set i.onHandQty = (i.onHandQty + ti.onHandQty),
i.onHoldQty = (i.onHoldQty + ti.onHoldQty),
i.unavailableQty = (i.unavailableQty + ti.unavailableQty),
i.price = ti.price,
i.currencyId = ti.currencyId,
i.cpu = 0,
i.cpuUnit = ti.currencyName,
i.cpm = 0,
i.cpmUnit = ti.currencyName,
i.stockUomId = coalesce(i.stockUomId, stockUomId),
i.status = if(coalesce((i.onHandQty + ti.onHandQty) - (i.onHoldQty + ti.onHoldQty) -
(i.unavailableQty + ti.unavailableQty), 0) > 0, 'available',
'unavailable')
where i.id = inventoryId;
end if;

if inventoryErrorMsg is null then
insert into `trigger_debug_log` (tableName, triggerName, description, status)
VALUES ('inventory_lot_line', 'after insert', 'Insert / Update Inventory Table', 'success');
ELSE
insert into `trigger_debug_log` (tableName, triggerName, description, errorMessages, status)
VALUES ('inventory_lot_line', 'after insert', 'Insert / Update Inventory Table', inventoryErrorMsg, 'fail');
end if;
end;

drop temporary table if exists `temp_inventory`;

if error_msg is NOT null then
insert into `trigger_debug_log` (tableName, triggerName, `description`, errorMessages, `status`)
VALUES ('inventory_lot_line', 'after insert', 'Unhandled error in trigger', error_msg, 'fail');
end if;
end;

+ 198
- 0
src/main/resources/db/changelog/changes/20260911_inventory_stock_uom/04_inventory_lot_line_trigger_update.sql 查看文件

@@ -0,0 +1,198 @@
-- liquibase formatted sql

-- changeset fpsms:20260911_inventory_lot_line_trigger_after_update splitStatements:false
-- comment: No.3 AFTER UPDATE: same stockUomId bucket as INSERT. Qty/hold/unavailable only; do not overwrite uomId.

create definer = current_user trigger `inventory_lot_line_AFTER_UPDATE`
after update
on `inventory_lot_line`
for each row
begin
declare inventoryId int default -1;
declare itemId int default null;
declare stockUomId int default null;
declare baseUomId int default null;
declare error_msg text default null;
declare continue handler for sqlexception, sqlwarning
begin
get diagnostics condition 1 error_msg = message_text;
SET error_msg = coalesce(error_msg, 'Unknown error occurred');
end;

select il.itemId
into itemId
from `inventory_lot` il
where il.id = new.inventoryLotId;

select iu.uomId
into stockUomId
from `item_uom` iu
where iu.id = new.stockItemUomId
limit 1;

if stockUomId is null and itemId is not null then
select iu.uomId
into stockUomId
from `item_uom` iu
where iu.itemId = itemId
and iu.stockUnit = true
and ifnull(iu.deleted, 0) = 0
limit 1;
end if;

if itemId is not null then
select iu.uomId
into baseUomId
from `item_uom` iu
where iu.itemId = itemId
and iu.baseUnit = true
and ifnull(iu.deleted, 0) = 0
limit 1;
end if;

if itemId is not null and stockUomId is not null then
select coalesce(
(select i.id
from `inventory` i
where i.itemId = itemId
and i.stockUomId = stockUomId
and ifnull(i.deleted, 0) = 0
order by i.id
limit 1),
(select i.id
from `inventory` i
where i.itemId = itemId
and i.uomId = stockUomId
and i.stockUomId is null
and ifnull(i.deleted, 0) = 0
order by i.id
limit 1),
-1
)
into inventoryId;
end if;

drop temporary table if exists temp_inventory;

create temporary table temp_inventory
(
oldOnHandQty decimal(14, 2),
currentOnHandQty decimal(14, 2),
oldOnHoldQty decimal(14, 2),
currentOnHoldQty decimal(14, 2),
oldUnavailableQty decimal(14, 2),
currentUnavailableQty decimal(14, 2),
currencyId INT,
currencyName varchar(30),
itemId INT,
price decimal(14, 2)
);

insert into temp_inventory (oldOnHandQty, currentOnHandQty, oldOnHoldQty, currentOnHoldQty, oldUnavailableQty,
currentUnavailableQty, currencyId, currencyName, itemId, price)
select coalesce(coalesce(old.inQty, 0) - coalesce(old.outQty, 0), 0) as oldOnHandQty,
coalesce(coalesce(new.inQty, 0) - coalesce(new.outQty, 0), 0) as currentOnHandQty,
coalesce(old.holdQty, 0) as oldOnHoldQty,
coalesce(new.holdQty, 0) as currentOnHoldQty,
IF(
old.status = 'unavailable',
coalesce(old.inQty, 0) - coalesce(old.outQty, 0) - coalesce(old.holdQty, 0),
0
) as oldUnavailableQty,
IF(
new.status = 'unavailable',
coalesce(new.inQty, 0) - coalesce(new.outQty, 0) - coalesce(new.holdQty, 0),
0
) as currentUnavailableQty,
c.id as currencyId,
coalesce(c.name, 'HKD') as currencyName,
il.itemId as itemId,
coalesce(pql.price, 0) as price
from `inventory_lot` il
left join `stock_in_line` sil on sil.id = il.stockInLineId
left join `stock_in` si on si.id = sil.stockInId
left join `purchase_order_line` pol on pol.id = sil.purchaseOrderLineId
left join `purchase_order` po on po.id = pol.purchaseOrderId
left join `purchase_quotation_line` pql on pql.itemId = il.itemId and pql.uomId = pol.uomId
left join `purchase_quotation` pq on pq.id = pql.purchaseQuotationId
left join `currency` c on c.id = pq.currencyId
where il.id = new.inventoryLotId;

begin
declare inventoryErrorMsg text default null;
declare continue handler for sqlexception, sqlwarning
begin
GET diagnostics condition 1 inventoryErrorMsg = message_text;
SET inventoryErrorMsg = coalesce(inventoryErrorMsg, 'Unknown error occurred');
end;

if inventoryId < 0 and stockUomId is not null then
insert into `inventory` (itemId, onHandQty, onHoldQty, unavailableQty, price, currencyId, cpu, cpuUnit, cpm,
cpmUnit, uomId, stockUomId, status)
select ti.itemId as itemId,
ti.currentOnHandQty as onHandQty,
ti.currentOnHoldQty as onHoldQty,
ti.currentUnavailableQty as unavailableQty,
ti.price as price,
ti.currencyId as currencyId,
0 as cpu,
ti.currencyName as cpuUnit,
0 as cpm,
ti.currencyName as cpmUnit,
coalesce(baseUomId, stockUomId) as uomId,
stockUomId as stockUomId,
if(coalesce(ti.currentOnHandQty - ti.currentOnHoldQty - ti.currentUnavailableQty, 0) > 0,
'available', 'unavailable') as status
from `temp_inventory` ti;
else
update `inventory` i
join `temp_inventory` ti on i.id = inventoryId
set i.onHandQty = i.onHandQty - (ti.oldOnHandQty - ti.currentOnHandQty),
i.onHoldQty = i.onHoldQty - (ti.oldOnHoldQty - ti.currentOnHoldQty),
i.unavailableQty = case
when old.status = 'available' and new.status = 'unavailable'
then i.unavailableQty + ti.currentUnavailableQty
when old.status = 'unavailable' and new.status = 'available'
then i.unavailableQty - ti.oldUnavailableQty
when old.status = 'unavailable' and new.status = 'unavailable'
then i.unavailableQty + (ti.currentUnavailableQty - ti.oldUnavailableQty)
else i.unavailableQty
end,
i.price = ti.price,
i.currencyId = ti.currencyId,
i.cpu = 0,
i.cpuUnit = ti.currencyName,
i.cpm = 0,
i.cpmUnit = ti.currencyName,
i.stockUomId = coalesce(i.stockUomId, stockUomId),
i.status = if((i.onHandQty - (ti.oldOnHandQty - ti.currentOnHandQty)) - (i.onHoldQty -
(ti.oldOnHoldQty - ti.currentOnHoldQty))
- (case
when old.status = 'available' and new.status = 'unavailable'
then i.unavailableQty + ti.currentUnavailableQty
when old.status = 'unavailable' and new.status = 'available'
then i.unavailableQty - ti.oldUnavailableQty
when old.status = 'unavailable' and new.status = 'unavailable'
then i.unavailableQty + (ti.currentUnavailableQty - ti.oldUnavailableQty)
else i.unavailableQty
end) > 0,
'available', 'unavailable')
where i.id = inventoryId;
end if;

if inventoryErrorMsg is null then
insert into `trigger_debug_log` (tableName, triggerName, description, status)
values ('inventory_lot_line', 'after update', 'Insert / Update Inventory Table', 'success');
else
insert into `trigger_debug_log` (tableName, triggerName, description, errorMessages, status)
values ('inventory_lot_line', 'after update', 'Insert / Update Inventory Table', inventoryErrorMsg, 'fail');
end if;
end;

drop temporary table if exists `temp_inventory`;

if error_msg is NOT null then
insert into `trigger_debug_log` (tableName, triggerName, `description`, errorMessages, `status`)
VALUES ('inventory_lot_line', 'after update', 'Unhandled error in trigger', error_msg, 'fail');
end if;
end;

Loading…
取消
儲存