diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt index e8d5d96..e9e9035 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt @@ -166,7 +166,12 @@ open class JobOrderService( val inventoriesMap = if (allItemIds.isNotEmpty()) { inventoryRepository.findInventoryInfoByItemIdInAndDeletedIsFalse(allItemIds) - .associateBy { it.itemId } + .mapNotNull { info -> + val itemId = info.itemId ?: return@mapNotNull null + val stockUomId = info.stockUomId ?: return@mapNotNull null + (itemId to stockUomId) to info + } + .toMap() } else { emptyMap() } @@ -228,7 +233,7 @@ open class JobOrderService( private fun calculateStockCounts( jobOrder: JobOrder, - inventoriesMap: Map + inventoriesMap: Map, com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo> ): Pair { // 过滤掉 consumables 和 CMB 类型的物料 val nonConsumablesJobms = jobOrder.jobms.filter { jobm -> @@ -243,36 +248,22 @@ open class JobOrderService( var sufficientCount = 0 var insufficientCount = 0 - //println("=== JobOrderService.calculateStockCounts for JobOrder: ${jobOrder.code} ===") - nonConsumablesJobms.forEach { jobm -> val itemId = jobm.item?.id - val itemCode = jobm.item?.code ?: "N/A" - val itemName = jobm.item?.name ?: "N/A" - if (itemId != null) { - val inventory = inventoriesMap[itemId] - val availableQty = if (inventory != null) { - inventory.availableQty ?: ( - (inventory.onHandQty ?: BigDecimal.ZERO) - - (inventory.onHoldQty ?: BigDecimal.ZERO) - - (inventory.unavailableQty ?: BigDecimal.ZERO) - ) - } else { - BigDecimal.ZERO - } + val jobmUomId = jobm.uom?.id + val inventory = resolveInventoryForJobm(itemId, jobmUomId, inventoriesMap) + val availableQty = inventory?.availableQty + ?: (inventory?.let { + (it.onHandQty ?: BigDecimal.ZERO) - (it.unavailableQty ?: BigDecimal.ZERO) + } ?: BigDecimal.ZERO) - // ✅ 获取 reqQty 和 availableQty 的单位信息 val reqQty = jobm.reqQty ?: BigDecimal.ZERO val reqUomId = jobm.uom?.id ?: 0L - val reqUomName = jobm.uom?.udfudesc ?: "N/A" - // ✅ 修复:使用 itemUomService 获取 stockUomId(与 ProductProcessService 保持一致) val stockUnitItemUom = itemUomService.findStockUnitByItemId(itemId) - val stockUomId = stockUnitItemUom?.uom?.id ?: 0L - val stockUomName = stockUnitItemUom?.uom?.udfudesc ?: "N/A" + val availableUomId = inventory?.stockUomId ?: stockUnitItemUom?.uom?.id ?: 0L - // ✅ 转换为 base unit 进行比较(与 ProductProcessService 保持一致) val baseReqQtyResult = if (reqUomId > 0 && reqQty > BigDecimal.ZERO) { try { itemUomService.convertUomByItem( @@ -291,13 +282,13 @@ open class JobOrderService( null } - val baseAvailableQtyResult = if (stockUomId > 0 && availableQty > BigDecimal.ZERO) { + val baseAvailableQtyResult = if (availableUomId > 0 && availableQty > BigDecimal.ZERO) { try { itemUomService.convertUomByItem( ConvertUomByItemRequest( itemId = itemId, qty = availableQty, - uomId = stockUomId, + uomId = availableUomId, targetUnit = "baseUnit" ) ) @@ -311,27 +302,32 @@ open class JobOrderService( val baseReqQty = baseReqQtyResult?.newQty ?: BigDecimal.ZERO val baseAvailableQty = baseAvailableQtyResult?.newQty ?: BigDecimal.ZERO - val baseUomName = baseReqQtyResult?.udfudesc ?: baseAvailableQtyResult?.udfudesc ?: "N/A" - // ✅ 使用 base unit 进行比较 if (baseAvailableQty >= baseReqQty) { sufficientCount++ - //println("✅ SUFFICIENT - Item: $itemCode ($itemName) - reqQty: $reqQty ($reqUomName) = $baseReqQty ($baseUomName), availableQty: $availableQty ($stockUomName) = $baseAvailableQty ($baseUomName)") } else { insufficientCount++ - //println("❌ INSUFFICIENT - Item: $itemCode ($itemName) - reqQty: $reqQty ($reqUomName) = $baseReqQty ($baseUomName), availableQty: $availableQty ($stockUomName) = $baseAvailableQty ($baseUomName)") } } else { - // 如果没有 itemId,视为不足 insufficientCount++ - //println("❌ INSUFFICIENT - Item: $itemCode ($itemName) - No itemId") } } - //println("=== Result: sufficient=$sufficientCount, insufficient=$insufficientCount ===") - return Pair(sufficientCount, insufficientCount) } + + private fun resolveInventoryForJobm( + itemId: Long, + jobmUomId: Long?, + inventoriesMap: Map, com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo>, + ): com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo? { + if (jobmUomId != null) { + inventoriesMap[itemId to jobmUomId]?.let { return it } + } + val stockUnitUomId = itemUomService.findStockUnitByItemId(itemId)?.uom?.id ?: return null + return inventoriesMap[itemId to stockUnitUomId] + } + open fun jobOrderDetail(id: Long): JobOrderDetail { val sqlResult = jobOrderRepository.findJobOrderDetailById(id) ?: throw NoSuchElementException(); diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt index 120ab5a..f4cba6b 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PSService.kt @@ -168,8 +168,8 @@ open class PSService( /** Set or clear coffee_or_tea for itemCode + systemType (coffee / tea / lemon). */ /** - * Recalculate [inventory.onHandQty] / hold / unavailable from [inventory_lot_line] for FG BOM items. - * Same aggregation as pick-issue manual inventory sync. + * Recalculate [inventory.onHandQty] / unavailable from [inventory_lot_line] for FG BOM items. + * Per (itemId, stockUomId) bucket. Does not write onHoldQty. */ fun refreshInventoryOnHandForFgBomItems(): Int { val sql = """ @@ -182,24 +182,28 @@ open class PSService( LEFT JOIN ( SELECT il.itemId, + iu.uomId AS stockUomId, SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0)) AS totalOnHandQty, - SUM(COALESCE(ill.holdQty, 0)) AS totalOnHoldQty, SUM(CASE - WHEN ill.status = 'unavailable' - THEN COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0) + WHEN LOWER(ill.status) = 'unavailable' + THEN COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) ELSE 0 END) AS totalUnavailableQty FROM inventory_lot_line ill - INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId AND il.deleted = 0 - WHERE ill.deleted = 0 - GROUP BY il.itemId + INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId AND IFNULL(il.deleted, 0) = 0 + INNER JOIN item_uom iu ON iu.id = ill.stockItemUomId AND IFNULL(iu.deleted, 0) = 0 + WHERE IFNULL(ill.deleted, 0) = 0 + GROUP BY il.itemId, iu.uomId ) calc ON calc.itemId = i.itemId + AND ( + i.stockUomId = calc.stockUomId + OR (i.stockUomId IS NULL AND i.uomId = calc.stockUomId) + ) SET i.onHandQty = COALESCE(calc.totalOnHandQty, 0), - i.onHoldQty = COALESCE(calc.totalOnHoldQty, 0), i.unavailableQty = COALESCE(calc.totalUnavailableQty, 0), i.status = IF( - COALESCE(calc.totalOnHandQty, 0) - COALESCE(calc.totalOnHoldQty, 0) - COALESCE(calc.totalUnavailableQty, 0) > 0, + COALESCE(calc.totalOnHandQty, 0) - COALESCE(calc.totalUnavailableQty, 0) > 0, 'available', 'unavailable' ), diff --git a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt index 2c2f932..43919b4 100644 --- a/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/master/entity/ProductionScheduleLineRepository.kt @@ -27,14 +27,25 @@ interface ProductionScheduleLineRepository : AbstractRepository= DATE_ADD(:fromDate, INTERVAL 1 DAY) AND DATE(ps.produceAt) < DATE_ADD(:fromDate, INTERVAL 8 DAY) AND ps.id = ( @@ -1968,5 +2005,16 @@ open class ProductionScheduleService( // Optional: log the action (if you have logging setup) // logger.info("Cleared all production schedules with produceAt >= today") } - + + private fun availableQtyWithoutHold( + inventories: List?, + stockUomId: Long?, + zero: BigDecimal, + ): BigDecimal { + if (inventories.isNullOrEmpty() || stockUomId == null) return zero + val bucket = inventories.firstOrNull { inv -> + inv.stockUom?.id == stockUomId || (inv.stockUom == null && inv.uom?.id == stockUomId) + } ?: return zero + return (bucket.onHandQty ?: zero) - (bucket.unavailableQty ?: zero) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt index 5fd8fcb..7321893 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt @@ -2313,71 +2313,6 @@ private fun updateLotLineAfterIssue(lotLineId: Long, qty: BigDecimal, isMissItem lotLine.modifiedBy = "system" // 修复:使用 saveAndFlush 确保立即提交到数据库,触发触发器 inventoryLotLineRepository.saveAndFlush(lotLine) - updateInventoryAfterLotLineChange(lotLine) - } -} -private fun updateInventoryAfterLotLineChange(lotLine: InventoryLotLine) { - try { - val item = lotLine.inventoryLot?.item ?: return - val inventory = inventoryRepository.findByItemId(item.id!!).orElse(null) ?: return - - // 使用 SQL 查询计算所有相关 lot lines 的总和 - val sql = """ - SELECT - SUM(COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0)) as totalOnHandQty, - SUM(COALESCE(ill.holdQty, 0)) as totalOnHoldQty, - SUM(CASE - WHEN ill.status = 'unavailable' - THEN COALESCE(ill.inQty, 0) - COALESCE(ill.outQty, 0) - COALESCE(ill.holdQty, 0) - ELSE 0 - END) as totalUnavailableQty - FROM inventory_lot_line ill - INNER JOIN inventory_lot il ON il.id = ill.inventoryLotId - WHERE il.itemId = :itemId - AND ill.deleted = 0 - """.trimIndent() - - val result = jdbcDao.queryForMap(sql, mapOf("itemId" to item.id!!)).orElse(null) - - if (result != null) { - val totalOnHandQty = (result["totalOnHandQty"] as? Number)?.let { - BigDecimal(it.toString()) - } ?: BigDecimal.ZERO - - val totalOnHoldQty = (result["totalOnHoldQty"] as? Number)?.let { - BigDecimal(it.toString()) - } ?: BigDecimal.ZERO - - val totalUnavailableQty = (result["totalUnavailableQty"] as? Number)?.let { - BigDecimal(it.toString()) - } ?: BigDecimal.ZERO - - // 更新 inventory - inventory.onHandQty = totalOnHandQty - inventory.onHoldQty = totalOnHoldQty - inventory.unavailableQty = totalUnavailableQty - inventory.status = if (totalOnHandQty.subtract(totalOnHoldQty).subtract(totalUnavailableQty) > BigDecimal.ZERO) { - "available" - } else { - "unavailable" - } - inventory.modified = LocalDateTime.now() - inventory.modifiedBy = "system" - - inventoryRepository.saveAndFlush(inventory) - - println("=== MANUALLY UPDATED INVENTORY ===") - println("Item ID: ${item.id}") - println("Total OnHandQty: ${totalOnHandQty}") - println("Total OnHoldQty: ${totalOnHoldQty}") - println("Total UnavailableQty: ${totalUnavailableQty}") - println("Status: ${inventory.status}") - println("==================================") - } - - } catch (e: Exception) { - println("Error updating inventory manually: ${e.message}") - e.printStackTrace() } } /** FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 */ @@ -2768,7 +2703,6 @@ open fun submitIssueWithQty(request: SubmitIssueWithQtyRequest): MessageResponse lotLine.modified = LocalDateTime.now() lotLine.modifiedBy = "system" inventoryLotLineRepository.saveAndFlush(lotLine) - updateInventoryAfterLotLineChange(lotLine) println("✅ Reset lot ${request.lotId}: issueQty=0, status=AVAILABLE") } val rejectedLines = stockOutLineRepository diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt index 1856056..a6cf73f 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt @@ -31,6 +31,7 @@ import com.ffii.fpsms.modules.stock.entity.StockOutRepository import com.ffii.fpsms.modules.stock.entity.enum.InventoryLotLineStatus import com.ffii.fpsms.modules.stock.entity.projection.CurrentInventoryItemInfo import com.ffii.fpsms.modules.stock.service.InventoryLotLineService +import com.ffii.fpsms.modules.stock.entity.projection.InventoryLotLineInfo import com.ffii.fpsms.modules.stock.service.InventoryService import com.ffii.fpsms.modules.stock.service.StockOutLineService import com.ffii.fpsms.modules.stock.service.SuggestedPickLotService @@ -215,28 +216,17 @@ open class PickOrderService( val today = LocalDate.now() val zero = BigDecimal.ZERO - val inventories = if (itemIds.isNotEmpty()) { - inventoryLotLineService - .allInventoryLotLinesByItemIdIn(itemIds) - .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } - .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero } - .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } - .groupBy { it.item?.id } - } else { - emptyMap() - } + val inventories = pickableLotLinesByItemAndStockUom(itemIds, today, zero) // Build response (removed suggestions) val pickOrderInfos = fullPickOrders.map { po -> val pickOrderLineInfos = po.pickOrderLines.map { pol -> - val inventory = pol.item?.id?.let { inventories[it] } - GetPickOrderLineInfo( id = pol.id, itemId = pol.item?.id, itemCode = pol.item?.code, itemName = pol.item?.name, - availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) }, + availableQty = pickLineAvailableQty(pol.item?.id, pol.uom?.id, inventories, zero), requiredQty = pol.qty, uomCode = pol.uom?.code, uomDesc = pol.uom?.udfudesc, @@ -660,7 +650,7 @@ open class PickOrderService( // Get Inventory Data val requiredItems = pos .flatMap { it.pickOrderLines } - .groupBy { it.item?.id } + .groupBy { it.item?.id to it.uom?.id } .map { (key, value) -> key to object : CurrentInventoryItemInfo { override val id: Long? = value[0].item?.id @@ -671,42 +661,25 @@ open class PickOrderService( override val requiredQty: BigDecimal = value.sumOf { it.qty ?: zero } } - } // itemId - requiredQty + } // itemId+uom - requiredQty - val itemIds = requiredItems.mapNotNull { it.first } -// val inventories = inventoryLotLineRepository.findCurrentInventoryByItems(itemIds) -// val inventories = inventoryService.allInventoriesByItemIds(itemIds) - val inventories = inventoryLotLineService - .allInventoryLotLinesByItemIdIn(itemIds) - .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } - .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero } - .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } - .sortedBy { it.expiryDate } - .groupBy { it.item?.id } + val itemIds = requiredItems.mapNotNull { it.first.first } + val inventories = pickableLotLinesByItemAndStockUom(itemIds, today, zero) // Pick Orders val releasePickOrderInfos = pos .map { po -> val releasePickOrderLineInfos = po.pickOrderLines.map { pol -> -// if (pol.item?.id != null && pol.item!!.id!! > 0) { - val inventory = pol.item?.id?.let { inventories[it] } - val itemUom = pol.item?.id?.let { itemUomService.findSalesUnitByItemId(it) } -// val inventory = inventories.find { it.itemId == pol.item?.id } - - // Return ReleasePickOrderLineInfo( id = pol.id, itemId = pol.item?.id, itemCode = pol.item?.code, itemName = pol.item?.name, -// availableQty = inventory?.availableQty, - availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) }, -// availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) * (itemUom?.ratioN ?: one) * (itemUom?.ratioD ?: one) }, + availableQty = pickLineAvailableQty(pol.item?.id, pol.uom?.id, inventories, zero), requiredQty = pol.qty, uomCode = pol.uom?.code, uomDesc = pol.uom?.udfudesc, ) -// } } // Return @@ -725,16 +698,12 @@ open class PickOrderService( // val inventory = inventories // .find { it.itemId == item.first } - val inventory = item.first?.let { inventories[it] } - val itemUom = item.first?.let { itemUomService.findSalesUnitByItemId(it) } + val inventory = item.first.let { (itemId, uomId) -> + if (itemId != null && uomId != null) inventories[itemId to uomId] else null + } item.second.let { -// it.availableQty = inventory?.availableQty - - it.availableQty = inventory?.sumOf { i -> i.availableQty ?: zero } -// it.availableQty = inventory?.sumOf { i -> (i.availableQty ?: zero) * (itemUom?.ratioN ?: one) * (itemUom?.ratioD ?: one) } - - // return + it.availableQty = inventory?.sumOf { i -> (i.inQty ?: zero) - (i.outQty ?: zero) } it } } @@ -759,7 +728,7 @@ open class PickOrderService( // Get Inventory Data val requiredItems = pos .flatMap { it.pickOrderLines } - .groupBy { it.item?.id } + .groupBy { it.item?.id to it.uom?.id } .map { (key, value) -> key to object : CurrentInventoryItemInfo { override val id: Long? = value[0].item?.id @@ -769,16 +738,10 @@ open class PickOrderService( override var availableQty: BigDecimal? = zero override val requiredQty: BigDecimal = value.sumOf { it.qty ?: zero } } - } // itemId - requiredQty + } - val itemIds = requiredItems.mapNotNull { it.first } - val inventories = inventoryLotLineService - .allInventoryLotLinesByItemIdIn(itemIds) - .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } - .filter { (it.inQty ?: zero).minus(it.outQty ?: zero).minus(it.holdQty ?: zero) > zero } - .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } - .sortedBy { it.expiryDate } - .groupBy { it.item?.id } + val itemIds = requiredItems.mapNotNull { it.first.first } + val inventories = pickableLotLinesByItemAndStockUom(itemIds, today, zero) val suggestions = suggestedPickLotService.suggestionForPickOrders(SuggestedPickLotForPoRequest(pickOrders = pos)) @@ -793,24 +756,10 @@ open class PickOrderService( emptyMap() } - // Pre-calculate available quantities for each item ONCE - val itemAvailableQtyMap = mutableMapOf() - itemIds.forEach { itemId -> - val inventory = inventories[itemId] - val totalAvailableQty = inventory?.sumOf { i -> - val inQty = i.inQty ?: zero - val outQty = i.outQty ?: zero - val holdQty = i.holdQty ?: zero - inQty.minus(outQty).minus(holdQty) - } ?: zero - itemAvailableQtyMap[itemId] = totalAvailableQty - } - val releasePickOrderLineInfos = pos .map { po -> val releasePickOrderLineInfos = po.pickOrderLines.map { pol -> - val itemId = pol.item?.id - val availableQty = itemId?.let { itemAvailableQtyMap[it] } ?: zero + val availableQty = pickLineAvailableQty(pol.item?.id, pol.uom?.id, inventories, zero) // 取得這一行的所有 stock_out_line val stockOutLines = stockOutLinesByPickOrderLineId[pol.id] ?: emptyList() @@ -871,16 +820,11 @@ open class PickOrderService( } // Items val currentInventoryInfos = requiredItems.map { item -> - val inventory = item.first?.let { inventories[it] } - val itemUom = item.first?.let { itemUomService.findSalesUnitByItemId(it) } - + val inventory = item.first.let { (itemId, uomId) -> + if (itemId != null && uomId != null) inventories[itemId to uomId] else null + } item.second.let { - val convertedAvailableQty = inventory?.sumOf { i -> - val baseQty = (i.availableQty ?: zero) - baseQty - } - - it.availableQty = convertedAvailableQty + it.availableQty = inventory?.sumOf { i -> (i.inQty ?: zero) - (i.outQty ?: zero) } it } } @@ -2344,19 +2288,16 @@ open fun checkAndCompletePickOrderByConsoCode(consoCode: String): MessageRespons -- Calculate available quantity from inventory COALESCE(( - SELECT SUM( - COALESCE(inv.onHandQty, 0) - - COALESCE(inv.onHoldQty, 0) - - COALESCE(inv.unavailableQty, 0) - ) + SELECT COALESCE(inv.onHandQty, 0) - COALESCE(inv.unavailableQty, 0) FROM fpsmsdb.inventory inv - JOIN fpsmsdb.item_uom iu - ON iu.itemId = inv.itemId - AND iu.uomId = inv.uomId - AND iu.baseUnit = 1 - AND iu.deleted = false WHERE inv.itemId = i.id AND inv.deleted = false + AND ( + inv.stockUomId = pol.uomId + OR (inv.stockUomId IS NULL AND inv.uomId = pol.uomId) + ) + ORDER BY CASE WHEN inv.stockUomId IS NULL THEN 1 ELSE 0 END, inv.id + LIMIT 1 ), 0) as availableQty, -- Check if all stock out lines for this pick order line are completed @@ -5201,5 +5142,36 @@ open fun getAllPickOrderLotsWithDetailsHierarchical(userId: Long): Map, + today: LocalDate, + zero: BigDecimal, + ): Map, List> { + if (itemIds.isEmpty()) return emptyMap() + return inventoryLotLineService + .allInventoryLotLinesByItemIdIn(itemIds) + .filter { it.status == InventoryLotLineStatus.AVAILABLE.value } + .filter { ((it.inQty ?: zero) - (it.outQty ?: zero)) > zero } + .filter { it.expiryDate.isAfter(today) || it.expiryDate.isEqual(today) } + .mapNotNull { line -> + val itemId = line.item?.id ?: return@mapNotNull null + val stockUomId = line.uomId ?: return@mapNotNull null + (itemId to stockUomId) to line + } + .groupBy({ it.first }, { it.second }) + } + + private fun pickLineAvailableQty( + itemId: Long?, + polUomId: Long?, + lotsByBucket: Map, List>, + zero: BigDecimal, + ): BigDecimal { + if (itemId == null || polUomId == null) return zero + return lotsByBucket[itemId to polUomId] + ?.sumOf { (it.inQty ?: zero) - (it.outQty ?: zero) } + ?: zero + } + } diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt index 78494b9..f4903d8 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryLotLineService.kt @@ -35,7 +35,6 @@ import java.util.Optional import kotlin.jvm.optionals.getOrNull import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.stock.web.model.UpdateInventoryLotLineQuantitiesRequest -import com.ffii.fpsms.modules.stock.entity.InventoryRepository import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisRequest import com.ffii.fpsms.modules.stock.web.model.QrCodeAnalysisResponse import com.ffii.fpsms.modules.stock.web.model.ScannedLotInfo @@ -58,7 +57,6 @@ open class InventoryLotLineService( private val warehouseRepository: WarehouseRepository, private val itemUomRespository: ItemUomRespository, private val stockInLineRepository: StockInLineRepository, - private val inventoryRepository: InventoryRepository, private val printerService: PrinterService, @Lazy private val jobOrderService: JobOrderService @@ -150,11 +148,15 @@ open class InventoryLotLineService( } } open fun saveInventoryLotLine(request: SaveInventoryLotLineRequest): InventoryLotLine { - val inventoryLotLine = - request.id?.let { inventoryLotLineRepository.findById(it).getOrNull() } ?: InventoryLotLine() + val existing = request.id?.let { inventoryLotLineRepository.findById(it).getOrNull() } + val inventoryLotLine = existing ?: InventoryLotLine() val inventoryLot = request.inventoryLotId?.let { inventoryLotRepository.findById(it).getOrNull() } val warehouse = request.warehouseId?.let { warehouseRepository.findById(it).getOrNull() } val stockUom = request.stockUomId?.let { itemUomRespository.findById(it).getOrNull() } + ?: existing?.stockUom + if (stockUom == null) { + throw IllegalArgumentException("stockItemUomId is required for inventory_lot_line") + } val status = request.status?.let { _status -> InventoryLotLineStatus.entries.find { it.value == _status } } inventoryLotLine.apply { @@ -191,9 +193,6 @@ open class InventoryLotLineService( val updatedLotLine = saveInventoryLotLine(updateRequest) - // ADD THIS: Update inventory table after lot line status change - updateInventoryTable(updatedLotLine) - return MessageResponse( id = updatedLotLine.id, name = updatedLotLine.id.toString(), @@ -204,45 +203,6 @@ open class InventoryLotLineService( ) } - // ADD THIS: New method to update inventory table - private fun updateInventoryTable(inventoryLotLine: InventoryLotLine) { - try { - // Get the item ID from the inventory lot - val itemId = inventoryLotLine.inventoryLot?.item?.id - if (itemId == null) { - println("Cannot update inventory table: itemId is null for lot line ${inventoryLotLine.id}") - return - } - - // Calculate onHoldQty (sum of holdQty from available lots only) - val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE) - .sumOf { it.holdQty ?: BigDecimal.ZERO } - - // Calculate unavailableQty (sum of inQty from unavailable lots only) - val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE) - .sumOf { - val inQty = it.inQty ?: BigDecimal.ZERO - val outQty = it.outQty ?: BigDecimal.ZERO - val remainingQty = inQty.minus(outQty) - remainingQty - } - - // Update the inventory table - val inventory = inventoryRepository.findByItemId(itemId).orElse(null) - if (inventory != null) { - inventory.onHoldQty = onHoldQty - inventory.unavailableQty = unavailableQty - inventoryRepository.save(inventory) - - println("Updated inventory for item $itemId: onHoldQty=$onHoldQty, unavailableQty=$unavailableQty") - } else { - println("Inventory not found for item $itemId") - } - } catch (e: Exception) { - println("Error updating inventory table for lot line ${inventoryLotLine.id}: ${e.message}") - e.printStackTrace() - } - } @Throws(IOException::class) @Transactional open fun exportStockInLineQrcode(request: LotLineToQrcode): Map { diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt index ae0e4cc..411380b 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt @@ -338,6 +338,9 @@ open class InventoryService( val warehouse = warehouseRepository.findAll().find { it.code == request.warehouseCode }!! // val salesUnit = itemUomService.findSalesUnitByItemId(itemId = inventoryLot.item!!.id!!) val stockUnit = itemUomService.findStockUnitByItemId(itemId = inventoryLot.item!!.id!!) + ?: throw IllegalArgumentException( + "stockItemUomId is required: no stockUnit item_uom for itemId=${inventoryLot.item!!.id}", + ) // ChangeList #8: lot-line stockItemUomId must be set so trigger writes inventory.stockUomId. InventoryLotLine().apply { this.inventoryLot = inventoryLot diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt index ebae233..b143d78 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockInLineService.kt @@ -538,6 +538,9 @@ open class StockInLineService( this.inQty = convertedBaseQty this.status = InventoryLotLineStatus.AVAILABLE this.stockUom = stockItemUom + ?: throw IllegalArgumentException( + "stockItemUomId is required: no stockUnit item_uom for itemId=${request.itemId}", + ) } saveLines.add(inventoryLotLine) } diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt index 759abbb..73237b5 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockOutLineService.kt @@ -43,7 +43,6 @@ import com.ffii.fpsms.modules.pickOrder.service.PickOrderService import com.ffii.fpsms.modules.master.service.ItemUomService import com.ffii.fpsms.modules.common.SecurityUtils import com.ffii.fpsms.modules.stock.entity.StockLedgerRepository -import com.ffii.fpsms.modules.stock.entity.InventoryRepository import com.ffii.fpsms.modules.pickOrder.entity.PickExecutionIssueRepository import java.time.LocalTime import com.ffii.fpsms.modules.stock.entity.StockInLineRepository @@ -78,7 +77,6 @@ private val inventoryLotLineService: InventoryLotLineService, private val bagService: BagService, private val pickOrderService: PickOrderService, private val stockLedgerRepository: StockLedgerRepository, - private val inventoryRepository: InventoryRepository, private val pickExecutionIssueRepository: PickExecutionIssueRepository, private val itemUomService: ItemUomService, private val inventoryBucketResolver: InventoryBucketResolver, @@ -266,13 +264,6 @@ fun handleQc(stockOutLine: StockOutLine, request: UpdateStockOutLineRequest): Li this.holdQty = this.holdQty!!.minus((request.qty.div(ratio)).toBigDecimal()) } inventoryLotLineRepository.save(targetLotLineEntry) - // update inventory - val inventory = inventoryRepository.findByItemId(request.itemId).orElseThrow() - val inventoryEntry = inventory.apply { - this.onHandQty = this.onHandQty!!.minus((request.qty.div(ratio)).toBigDecimal()) - this.onHoldQty = this.onHoldQty!!.minus((request.qty.div(ratio)).toBigDecimal()) - } - inventoryRepository.save(inventoryEntry) return listOf(stockOutLine, newStockOutLine) } @Transactional @@ -817,10 +808,6 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { val savedLotLine = inventoryLotLineRepository.save(inventoryLotLine) println("Saved lot line: ${savedLotLine.id}, status: ${savedLotLine.status}, holdQty: ${savedLotLine.holdQty}") - // Step 3: Update inventory table - println("Updating inventory table...") - updateInventoryTableAfterLotRejection(inventoryLotLine) - // Step 4: Trigger resuggest val pickOrderLine = stockOutLine.pickOrderLine if (pickOrderLine?.pickOrder?.id != null) { @@ -839,51 +826,6 @@ private fun getStockOutIdFromPickOrderLine(pickOrderLineId: Long): Long { } } -// ADD THIS: Update inventory table after lot rejection -private fun updateInventoryTableAfterLotRejection(inventoryLotLine: InventoryLotLine) { - try { - println("=== UPDATING INVENTORY TABLE ===") - val itemId = inventoryLotLine.inventoryLot?.item?.id - println("Item ID: $itemId") - - if (itemId != null) { - // Calculate onHoldQty (sum of holdQty from available lots only) - val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE) - .sumOf { it.holdQty ?: BigDecimal.ZERO } - - // Calculate unavailableQty (sum of inQty from unavailable lots only) - val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId,InventoryLotLineStatus.UNAVAILABLE) - .sumOf { - val inQty = it.inQty ?: BigDecimal.ZERO - val outQty = it.outQty ?: BigDecimal.ZERO - val remainingQty = inQty.minus(outQty) - remainingQty - } - - println("Calculated onHoldQty: $onHoldQty") - println("Calculated unavailableQty: $unavailableQty") - - // Update the inventory table - val inventory = inventoryRepository.findByItemId(itemId).orElse(null) - if (inventory != null) { - println("Found inventory record: ${inventory.id}") - println("Current inventory - onHoldQty: ${inventory.onHoldQty}, unavailableQty: ${inventory.unavailableQty}") - - inventory.onHoldQty = onHoldQty - inventory.unavailableQty = unavailableQty - val savedInventory = inventoryRepository.save(inventory) - - println("Updated inventory - onHoldQty: ${savedInventory.onHoldQty}, unavailableQty: ${savedInventory.unavailableQty}") - } else { - println("No inventory record found for item $itemId") - } - } - } catch (e: Exception) { - println("Error updating inventory table after lot rejection: ${e.message}") - e.printStackTrace() - } -} - @Transactional(rollbackFor = [Exception::class]) open fun batchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { val startTime = System.currentTimeMillis() @@ -955,14 +897,6 @@ open fun batchSubmit(request: QrPickBatchSubmitRequest): MessageResponse { this.holdQty = (this.holdQty ?: zero) - baseQty } inventoryLotLineRepository.save(lotLine) - - // 更新 inventory - val inv = inventoryRepository.findByItemId(lotLine.inventoryLot?.item?.id!!).orElseThrow() - inv.apply { - this.onHandQty = (this.onHandQty ?: zero) - baseQty - this.onHoldQty = (this.onHoldQty ?: zero) - baseQty - } - inventoryRepository.save(inv) } processedIds += line.stockOutLineId @@ -2156,7 +2090,7 @@ fun applyStockOutLineDelta( val postingType = (typeOverride ?: savedSol.type ?: "").trim().lowercase() val isIssuePosting = postingType == "miss" || postingType == "bad" || postingType == "expiry" - // 2) inventory_lot_line + inventory + // 2) inventory_lot_line; inventory onHand/unavailable is owned by the lot-line trigger if (!skipInventoryWrite) { val lotLine = savedSol.inventoryLotLine if (lotLine != null) { @@ -2196,19 +2130,6 @@ fun applyStockOutLineDelta( } } } - - val itemId = savedSol.item?.id - if (itemId != null) { - val inv = inventoryBucketResolver.findInventoryBucket(itemId, savedSol.inventoryLotLine) - if (inv != null) { - val zero = BigDecimal.ZERO - inv.onHandQty = (inv.onHandQty ?: zero).minus(deltaQty) - if (!isIssuePosting) { - inv.onHoldQty = (inv.onHoldQty ?: zero).minus(deltaQty) - } - inventoryRepository.save(inv) - } - } } // 3) stock_ledger (same ledger shape as createStockLedgerForStockOut / completeStockOutAfterLotLineSave) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt index 3332710..86e8570 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/StockTakeRecordService.kt @@ -65,7 +65,8 @@ open class StockTakeRecordService( val stockLedgerRepository: StockLedgerRepository, val inventoryRepository: InventoryRepository, val uomConversionRepository: UomConversionRepository, - val itemsRepository: ItemsRepository + val itemsRepository: ItemsRepository, + val inventoryBucketResolver: InventoryBucketResolver, ) { private val logger: Logger = LoggerFactory.getLogger(StockTakeRecordService::class.java) @@ -2125,7 +2126,7 @@ open fun batchSaveApproverStockTakeRecordsByIds( "batchSaveApproverStockTakeRecordsByIds cache build completed: lotLinePairs={}, lots={}, inventories={}, stockTakeLines={}, cacheBuildMs={}", adjustmentCache.inventoryLotLineByWarehouseLot.size, adjustmentCache.inventoryLotById.size, - adjustmentCache.inventoryByItemId.size, + adjustmentCache.inventoryByItemAndStockUom.size, adjustmentCache.stockTakeLineByRecordId.size, elapsedMs(cacheBuildStartNs) ) @@ -2213,7 +2214,7 @@ open fun batchSaveApproverStockTakeRecordsByIds( val runtimeCache = BatchAdjustmentRuntimeCache( stockOutByStockTakeId = mutableMapOf(), stockInByStockTakeId = mutableMapOf(), - runningLedgerBalanceByItemId = mutableMapOf() + runningLedgerBalanceByBucket = mutableMapOf() ) val adjustmentContext = StockTakeAdjustmentBatchContext() var varianceCount = 0 @@ -2278,7 +2279,7 @@ open fun batchSaveApproverStockTakeRecordsByIds( "batchSaveApproverStockTakeRecordsByIds runtime cache stats: stockOutHeads={}, stockInHeads={}, ledgerItems={}, batchedStockTakeLines={}, batchedOutLines={}, batchedInLines={}, batchedInventoryLotLines={}, batchedLedgers={}", runtimeCache.stockOutByStockTakeId.size, runtimeCache.stockInByStockTakeId.size, - runtimeCache.runningLedgerBalanceByItemId.size, + runtimeCache.runningLedgerBalanceByBucket.size, adjustmentContext.stockTakeLineByRecordId.size, adjustmentContext.stockOutLines.size, adjustmentContext.stockInLines.size, @@ -2398,9 +2399,7 @@ private fun applyVarianceAdjustment( ?: throw IllegalArgumentException("Inventory lot not found") val itemId = inventoryLot.item?.id ?: throw IllegalArgumentException("Item ID not found") - val inventory = cache?.inventoryByItemId?.get(itemId) - ?: inventoryRepository.findByItemId(itemId).orElse(null) - ?: throw IllegalArgumentException("Inventory not found for item") + val inventory = resolveInventoryForStockTake(itemId, inventoryLotLine, cache) // 1. 更新開輪預建的 StockTakeLine,或舊資料無預建時新建 val stockTakeLine = stockTakeRecord.id?.let { rid -> @@ -2481,11 +2480,9 @@ private fun applyVarianceAdjustment( // 避免同一批多筆盤虧時每筆都用同一個 inventory.onHandQty 導致 balance 錯誤。 val itemIdForLedger = inventoryLot.item?.id ?: throw IllegalArgumentException("Item ID not found for stock take ledger") - val previousBalance = runtimeCache?.runningLedgerBalanceByItemId?.get(itemIdForLedger) - ?: run { - val latestLedger = stockLedgerRepository.findFirstByItemIdAndDeletedFalseOrderByDateDescIdDesc(itemIdForLedger) - latestLedger?.balance ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble() - } + val ledgerKey = stockTakeLedgerBucketKey(itemIdForLedger, latestLine, inventory) + val previousBalance = runtimeCache?.runningLedgerBalanceByBucket?.get(ledgerKey) + ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble() val newBalance = previousBalance - qtyToRemove.toDouble() // FP-MTMS Version Checklist | Functions Ref. No. 46 | v1.0.1 | 2026-08-05 @@ -2519,7 +2516,7 @@ private fun applyVarianceAdjustment( } else { stockLedgerRepository.save(stockLedger) } - runtimeCache?.runningLedgerBalanceByItemId?.put(itemIdForLedger, newBalance) + runtimeCache?.runningLedgerBalanceByBucket?.put(ledgerKey, newBalance) val newOutQty = (latestLine.outQty ?: zero).add(qtyToRemove) latestLine.outQty = newOutQty @@ -2619,11 +2616,9 @@ private fun applyVarianceAdjustment( val itemIdForLedger = inventoryLot.item?.id ?: throw IllegalArgumentException("Item ID not found for stock take ledger (in)") - val previousBalance = runtimeCache?.runningLedgerBalanceByItemId?.get(itemIdForLedger) - ?: run { - val latestLedger = stockLedgerRepository.findFirstByItemIdAndDeletedFalseOrderByDateDescIdDesc(itemIdForLedger) - latestLedger?.balance ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble() - } + val ledgerKey = stockTakeLedgerBucketKey(itemIdForLedger, latestLine, inventory) + val previousBalance = runtimeCache?.runningLedgerBalanceByBucket?.get(ledgerKey) + ?: (inventory.onHandQty ?: BigDecimal.ZERO).toDouble() val newBalance = previousBalance + plusQty.toDouble() // latestLine.inQty already includes plusQty above — snapshot live lot line after move. @@ -2656,21 +2651,46 @@ private fun applyVarianceAdjustment( } else { stockLedgerRepository.save(stockLedger) } - runtimeCache?.runningLedgerBalanceByItemId?.put(itemIdForLedger, newBalance) + runtimeCache?.runningLedgerBalanceByBucket?.put(ledgerKey, newBalance) } } +private fun resolveInventoryForStockTake( + itemId: Long, + lotLine: InventoryLotLine, + cache: BatchAdjustmentCache?, +): com.ffii.fpsms.modules.stock.entity.Inventory { + val stockUomId = inventoryBucketResolver.resolveStockUomId(lotLine) + if (stockUomId != null) { + cache?.inventoryByItemAndStockUom?.get(itemId to stockUomId)?.let { return it } + inventoryBucketResolver.findInventoryBucket(itemId, stockUomId)?.let { return it } + } + inventoryBucketResolver.findInventoryBucket(itemId, lotLine)?.let { return it } + throw IllegalArgumentException("Inventory not found for item $itemId stock UOM $stockUomId") +} + +private fun stockTakeLedgerBucketKey( + itemId: Long, + lotLine: InventoryLotLine, + inventory: com.ffii.fpsms.modules.stock.entity.Inventory, +): Pair { + val stockUomId = inventoryBucketResolver.resolveStockUomId(lotLine) + ?: inventory.stockUom?.id + ?: 0L + return itemId to stockUomId +} + private data class BatchAdjustmentCache( val inventoryLotLineByWarehouseLot: Map, InventoryLotLine>, val inventoryLotById: Map, - val inventoryByItemId: Map, + val inventoryByItemAndStockUom: Map, com.ffii.fpsms.modules.stock.entity.Inventory>, val stockTakeLineByRecordId: Map ) private data class BatchAdjustmentRuntimeCache( val stockOutByStockTakeId: MutableMap, val stockInByStockTakeId: MutableMap, - val runningLedgerBalanceByItemId: MutableMap + val runningLedgerBalanceByBucket: MutableMap, Double> ) private data class StockTakeAdjustmentBatchContext( @@ -2725,10 +2745,15 @@ private fun buildBatchAdjustmentCache(records: List): BatchAdju emptyMap() } val itemIds = inventoryLotById.values.mapNotNull { it.item?.id }.distinct() - val inventoryByItemId = + val inventoryByItemAndStockUom = if (itemIds.isNotEmpty()) { inventoryRepository.findAllByItemIdInAndDeletedIsFalse(itemIds) - .groupBy { it.item?.id ?: 0L } + .mapNotNull { inv -> + val itemId = inv.item?.id ?: return@mapNotNull null + val stockUomId = inv.stockUom?.id ?: inv.uom?.id ?: return@mapNotNull null + (itemId to stockUomId) to inv + } + .groupBy({ it.first }, { it.second }) .mapValues { (_, list) -> list.minByOrNull { it.id ?: Long.MAX_VALUE }!! } } else { emptyMap() @@ -2745,7 +2770,7 @@ private fun buildBatchAdjustmentCache(records: List): BatchAdju return BatchAdjustmentCache( inventoryLotLineByWarehouseLot = inventoryLotLineByWarehouseLot, inventoryLotById = inventoryLotById, - inventoryByItemId = inventoryByItemId, + inventoryByItemAndStockUom = inventoryByItemAndStockUom, stockTakeLineByRecordId = stockTakeLineByRecordId ) } diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt index 507b100..2f0b535 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/SuggestedPickLotService.kt @@ -28,8 +28,7 @@ import kotlin.jvm.optionals.getOrNull import org.springframework.transaction.annotation.Transactional import com.ffii.fpsms.modules.master.web.models.MessageResponse import com.ffii.fpsms.modules.pickOrder.entity.PickOrderRepository -import java.math.RoundingMode -import com.ffii.fpsms.modules.stock.entity.InventoryRepository +import java.math.RoundingMode import com.ffii.fpsms.modules.stock.web.model.StockOutLineStatus import com.ffii.fpsms.modules.stock.web.model.PickAnotherLotRequest import com.ffii.fpsms.modules.stock.service.InventoryLotLineService @@ -54,7 +53,6 @@ open class SuggestedPickLotService( val itemUomService: ItemUomService, val pickExecutionIssueRepository: PickExecutionIssueRepository, // 添加逗号 val pickOrderRepository: PickOrderRepository, - val inventoryRepository: InventoryRepository, val failInventoryLotLineRepository: FailInventoryLotLineRepository, val stockOutRepository: StockOutRepository, val itemRepository: ItemsRepository, @@ -1073,38 +1071,6 @@ val basePickOrders = (listOf(pickOrder) + allCompetingPickOrders).distinctBy { i } } - // ✅ 优化:批量更新 inventory table(收集所有 item IDs,只更新一次) - val allItemIdsToUpdate = allPickOrdersToResuggest - .flatMap { it.pickOrderLines } - .mapNotNull { it.item?.id } - .distinct() - - if (allItemIdsToUpdate.isNotEmpty()) { - println("=== Batch updating inventory table for ${allItemIdsToUpdate.size} items ===") - allItemIdsToUpdate.forEach { itemId -> - // Calculate onHoldQty for ALL pick orders that use this item - // ✅ FIX: Use enum directly, not .value - val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE) - .sumOf { it.holdQty ?: BigDecimal.ZERO } - - // ✅ FIX: Use enum directly, not .value - val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE) - .sumOf { - val inQty = it.inQty ?: BigDecimal.ZERO - val outQty = it.outQty ?: BigDecimal.ZERO - val remainingQty = inQty.minus(outQty) - remainingQty - } - - val inventory = inventoryRepository.findByItemId(itemId).orElse(null) - if (inventory != null) { - inventory.onHoldQty = onHoldQty - inventory.unavailableQty = unavailableQty - inventoryRepository.save(inventory) - } - } - println("✅ Batch updated inventory table for ${allItemIdsToUpdate.size} items") - } println("=== RESUGGEST DEBUG END ===") return MessageResponse( @@ -1345,40 +1311,6 @@ private fun generateOptimalSuggestionsForAllPickOrders( return suggestions } -private fun updateInventoryTableAfterResuggest(pickOrder: PickOrder) { - try { - // Get all item IDs from the pick order - val itemIds = pickOrder.pickOrderLines.mapNotNull { it.item?.id }.distinct() - - itemIds.forEach { itemId -> - // FIX: Calculate onHoldQty for ALL pick orders that use this item, not just the current one - val onHoldQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.AVAILABLE) - .sumOf { it.holdQty ?: BigDecimal.ZERO } - - // FIX: Use enum method instead of string method - val unavailableQty = inventoryLotLineRepository.findAllByInventoryLotItemIdAndStatus(itemId, InventoryLotLineStatus.UNAVAILABLE) - .sumOf { - val inQty = it.inQty ?: BigDecimal.ZERO - val outQty = it.outQty ?: BigDecimal.ZERO - val remainingQty = inQty.minus(outQty) - remainingQty - } - - // Update the inventory table - val inventory = inventoryRepository.findByItemId(itemId).orElse(null) - if (inventory != null) { - inventory.onHoldQty = onHoldQty - inventory.unavailableQty = unavailableQty - inventoryRepository.save(inventory) - - println("Updated inventory for item $itemId: onHoldQty=$onHoldQty, unavailableQty=$unavailableQty") - } - } - } catch (e: Exception) { - println("Error updating inventory table after resuggest: ${e.message}") - e.printStackTrace() - } -} private fun findAllSuggestionsForPickOrder(pickOrderId: Long): List { val pickOrderLines = pickOrderRepository.findById(pickOrderId) .orElseThrow() @@ -1802,27 +1734,9 @@ private fun handleMissingItem(inventoryLotLine: InventoryLotLine, failQty: BigDe // 处理损坏物品的情况 private fun handleBrokenItem(inventoryLotLine: InventoryLotLine, failQty: BigDecimal) { try { - // 1. 将库存批次行标记为不可用 inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE - - // 2. 更新库存表的不可用数量 - val itemId = inventoryLotLine.inventoryLot?.item?.id - if (itemId != null) { - val inventory = inventoryRepository.findByItemId(itemId).orElse(null) - if (inventory != null) { - val currentUnavailableQty = inventory.unavailableQty ?: BigDecimal.ZERO - inventory.unavailableQty = currentUnavailableQty.plus(failQty) - inventoryRepository.save(inventory) - - println("Broken item handled: Updated unavailableQty by $failQty for item $itemId") - } - } - - // 3. 保存库存批次行的状态变更 inventoryLotLineRepository.save(inventoryLotLine) - println("Broken item handled: Marked lot line ${inventoryLotLine.id} as unavailable") - } catch (e: Exception) { println("Error handling broken item: ${e.message}") throw e