diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt index f2ea549..a8c0ca9 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt @@ -1536,7 +1536,7 @@ open class DeliveryOrderService( this.type = "do" this.consoPickOrderCode = consoCode this.status = StockOutStatus.PENDING.status - this.handler = request.userId + //this.handler = request.userId } val savedStockOut = stockOutRepository.saveAndFlush(stockOut) diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt index e0bf057..2711e91 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/JoPickOrderService.kt @@ -457,8 +457,8 @@ open class JoPickOrderService( val lotAvailability = when { il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" sol?.status == "rejected" -> "rejected" - availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" + availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" else -> "available" } @@ -685,8 +685,8 @@ open class JoPickOrderService( val lotAvailability = when { il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" sol?.status == "rejected" -> "rejected" - availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" + availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" else -> "available" } @@ -2175,8 +2175,8 @@ open fun getJobOrderLotsHierarchicalByPickOrderId(pickOrderId: Long): JobOrderLo val lotAvailability = when { il.expiryDate != null && il.expiryDate!!.isBefore(LocalDate.now()) -> "expired" sol?.status == "rejected" -> "rejected" - availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" ill.status == InventoryLotLineStatus.UNAVAILABLE -> "status_unavailable" + availableQty != null && availableQty <= BigDecimal.ZERO -> "insufficient_stock" else -> "available" } 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 e951daa..92c45b5 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 @@ -920,8 +920,8 @@ open class PickOrderService( val lotAvailability = when { isExpired -> "expired" sol?.status == "rejected" -> "rejected" - availableQty <= zero -> "insufficient_stock" ill.status?.value == "unavailable" -> "status_unavailable" + availableQty <= zero -> "insufficient_stock" else -> "available" } @@ -4061,6 +4061,17 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", message = "Cannot resolve new inventory lot line", errorPosition = null ) + + if (newIll.status == InventoryLotLineStatus.UNAVAILABLE) { + return MessageResponse( + id = null, + name = "Lot line unavailable", + code = "LOT_UNAVAILABLE", + type = "pickorder", + message = "Cannot switch to unavailable inventory lot line", + errorPosition = null + ) + } // Item consistency check val newItemId = newIll.inventoryLot?.item?.id @@ -4578,8 +4589,8 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto lotAvailability = when { isExpired -> "expired" stockOutLine?.status == "rejected" -> "rejected" - availableQty <= zero -> "insufficient_stock" illEntity.status?.value == "unavailable" -> "status_unavailable" + availableQty <= zero -> "insufficient_stock" else -> "available" }, processingStatus = when { diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt index 74e628e..7206d08 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryLotLineRepository.kt @@ -137,7 +137,7 @@ interface InventoryLotLineRepository : AbstractRepository coalesce(ill.outQty, 0) AND ill.deleted = false ORDER BY il.expiryDate ASC """) 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 c74b61a..d10496d 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 @@ -83,19 +83,34 @@ open class InventoryLotLineService( return RecordsRes(records, total.toInt()); } + /** + * Same rules as [saveInventoryLotLine]: only stay AVAILABLE if previously AVAILABLE and remaining > 0. + */ + open fun deriveInventoryLotLineStatus( + previousStatus: InventoryLotLineStatus?, + inQty: BigDecimal?, + outQty: BigDecimal?, + holdQty: BigDecimal? + ): InventoryLotLineStatus { + val remainingQty = + (inQty ?: BigDecimal.ZERO) - (outQty ?: BigDecimal.ZERO) - (holdQty ?: BigDecimal.ZERO) + val status = previousStatus + val qtyStatus = + if (remainingQty > BigDecimal.ZERO) InventoryLotLineStatus.AVAILABLE else InventoryLotLineStatus.UNAVAILABLE + return when { + status == InventoryLotLineStatus.AVAILABLE && qtyStatus == InventoryLotLineStatus.AVAILABLE -> + InventoryLotLineStatus.AVAILABLE + else -> InventoryLotLineStatus.UNAVAILABLE + } + } + open fun saveInventoryLotLine(request: SaveInventoryLotLineRequest): InventoryLotLine { val inventoryLotLine = request.id?.let { inventoryLotLineRepository.findById(it).getOrNull() } ?: 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() } - val remainingQty = - (request.inQty ?: BigDecimal(0)) - (request.outQty ?: BigDecimal(0)) - (request.holdQty ?: BigDecimal(0)) val status = request.status?.let { _status -> InventoryLotLineStatus.entries.find { it.value == _status } } - val qtyStatus = when (remainingQty > BigDecimal(0)) { - true -> InventoryLotLineStatus.AVAILABLE - else -> InventoryLotLineStatus.UNAVAILABLE - } println("status: ${request.status}") println("status123: ${status?.value}") @@ -107,11 +122,7 @@ open class InventoryLotLineService( outQty = request.outQty holdQty = request.holdQty this.stockUom = stockUom - this.status = - when (status == InventoryLotLineStatus.AVAILABLE && qtyStatus == InventoryLotLineStatus.AVAILABLE) { - true -> InventoryLotLineStatus.AVAILABLE - else -> InventoryLotLineStatus.UNAVAILABLE - } + this.status = deriveInventoryLotLineStatus(status, request.inQty, request.outQty, request.holdQty) remarks = request.remarks } 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 1f7d16f..597e784 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 @@ -1012,6 +1012,17 @@ open fun updateStockOutLineStatusByQRCodeAndLotNo(request: UpdateStockOutLineSta errorPosition = null ) } + if (resolved.status == InventoryLotLineStatus.UNAVAILABLE) { + println(" Reject noLot bind: resolved InventoryLotLine id=${resolved.id} is UNAVAILABLE") + return MessageResponse( + id = null, + name = "Lot line unavailable", + code = "LOT_UNAVAILABLE", + type = "error", + message = "Cannot confirm scan: target inventory lot line is unavailable", + errorPosition = null + ) + } // Bind the lot line to this stockOutLine so subsequent operations can proceed stockOutLine.inventoryLotLine = resolved stockOutLine.item = stockOutLine.item ?: resolved.inventoryLot?.item @@ -1487,19 +1498,29 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) { open fun createStockOut(request: StockOutRequest): StockOutLine { val inventoryLotLine = inventoryLotLineRepository.findById(request.inventoryLotLineId).orElseThrow() + val qtyBd = BigDecimal.valueOf(request.qty) + val oldHold = inventoryLotLine.holdQty ?: BigDecimal.ZERO - // Step 1: Increase outQty in inventory_lot_line table + // Step 1: Increase outQty in inventory_lot_line table and release hold (same idea as pick / QR scan) val updatedInventoryLotLine = inventoryLotLine.apply { val currentOutQty = this.outQty ?: BigDecimal.ZERO - val newOutQty = currentOutQty + BigDecimal.valueOf(request.qty) + val newOutQty = currentOutQty + qtyBd this.outQty = newOutQty - - val currentInQty = this.inQty ?: BigDecimal.ZERO - if (newOutQty.compareTo(currentInQty) == 0) { - this.status = InventoryLotLineStatus.UNAVAILABLE - } + val newHold = oldHold.subtract(qtyBd).coerceAtLeast(BigDecimal.ZERO) + this.holdQty = newHold + this.status = inventoryLotLineService.deriveInventoryLotLineStatus( + this.status, + this.inQty, + this.outQty, + this.holdQty + ) } inventoryLotLineRepository.save(updatedInventoryLotLine) + // inventory.onHandQty / onHoldQty / unavailableQty: 由 DB trigger `inventory_lot_line_AFTER_UPDATE` + // 依 old/new 的 in、out、hold、status 同步;此處勿再手動改 inventory.onHoldQty,否則與 trigger 重複扣會變負數。 + + val itemId = updatedInventoryLotLine.inventoryLot?.item?.id + ?: throw IllegalArgumentException("InventoryLotLine must have an associated item") // Step 2: Create a row of stock_out val currentUser = SecurityUtils.getUser().orElseThrow() @@ -1512,8 +1533,6 @@ if (submitQty > BigDecimal.ZERO && actualInventoryLotLineId != null) { val savedStockOut = stockOutRepository.save(stockOut) // Step 3: Create a row in stock_out_line table - val itemId = updatedInventoryLotLine.inventoryLot?.item?.id - ?: throw IllegalArgumentException("InventoryLotLine must have an associated item") val item = itemRepository.findById(itemId).orElseThrow() val stockOutLine = StockOutLine().apply { @@ -1917,10 +1936,17 @@ fun applyStockOutLineDelta( if (isIssuePosting) { val latestLotLine = inventoryLotLineRepository.findById(lotLine.id!!).orElse(null) if (latestLotLine != null) { + val prevStatus = latestLotLine.status val currentHoldQty = latestLotLine.holdQty ?: BigDecimal.ZERO val currentOutQty = latestLotLine.outQty ?: BigDecimal.ZERO latestLotLine.holdQty = currentHoldQty.subtract(deltaQty).coerceAtLeast(BigDecimal.ZERO) latestLotLine.outQty = currentOutQty.add(deltaQty) + latestLotLine.status = inventoryLotLineService.deriveInventoryLotLineStatus( + prevStatus, + latestLotLine.inQty, + latestLotLine.outQty, + latestLotLine.holdQty + ) latestLotLine.modified = eventTime if (!operator.isNullOrBlank()) { latestLotLine.modifiedBy = operator