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 32a0f09..c7d07e2 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 @@ -67,6 +67,7 @@ open class PickExecutionIssueService( @Transactional(rollbackFor = [Exception::class]) open fun recordPickExecutionIssue(request: PickExecutionIssueRequest): MessageResponse { try { + // 1. 检查是否已经存在相同的 pick execution issue 记录 val existingIssues = pickExecutionIssueRepository.findByPickOrderLineIdAndLotIdAndDeletedFalse( request.pickOrderLineId, @@ -184,14 +185,31 @@ open class PickExecutionIssueService( // 6. NEW: Update inventory_lot_line.issueQty if (request.lotId != null && inventoryLotLine != null) { - val currentIssueQty = inventoryLotLine.issueQty ?: BigDecimal.ZERO - val newIssueQty = currentIssueQty.add(issueQty) - inventoryLotLine.issueQty = newIssueQty - inventoryLotLine.modified = LocalDateTime.now() - inventoryLotLine.modifiedBy = "system" - inventoryLotLineRepository.saveAndFlush(inventoryLotLine) - println("Updated inventory_lot_line ${request.lotId} issueQty: ${currentIssueQty} -> ${newIssueQty}") + // ✅ 修改:如果只有 missQty,不更新 issueQty + val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO + val missQty = request.missQty ?: BigDecimal.ZERO + val badItemQty = request.badItemQty ?: BigDecimal.ZERO + + val isMissItemOnly = actualPickQty == BigDecimal.ZERO + && missQty > BigDecimal.ZERO + && badItemQty == BigDecimal.ZERO + val hasMissItemWithPartialPick = missQty > BigDecimal.ZERO + && actualPickQty > BigDecimal.ZERO + + if (!isMissItemOnly && !hasMissItemWithPartialPick) { + // 只有非 miss item 的情况才更新 issueQty + val currentIssueQty = inventoryLotLine.issueQty ?: BigDecimal.ZERO + val newIssueQty = currentIssueQty.add(issueQty) + inventoryLotLine.issueQty = newIssueQty + inventoryLotLine.modified = LocalDateTime.now() + inventoryLotLine.modifiedBy = "system" + inventoryLotLineRepository.saveAndFlush(inventoryLotLine) + println("Updated inventory_lot_line ${request.lotId} issueQty: ${currentIssueQty} -> ${newIssueQty}") + } else { + println("Skipped updating issueQty for miss item (lot ${request.lotId})") + } } + // 7. 获取相关数据用于后续处理 val actualPickQtyForProcessing = request.actualPickQty ?: BigDecimal.ZERO @@ -446,7 +464,7 @@ private fun checkAndCompletePickOrder(consoCode: String) { // 修复:处理有部分拣货但有 miss item 的情况 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, actualPickQty: BigDecimal, missQty: BigDecimal) { - println("=== HANDLING MISS ITEM WITH PARTIAL PICK (FIXED LOGIC) ===") + println("=== HANDLING MISS ITEM WITH PARTIAL PICK (NEW LOGIC: DON'T REJECT LOT) ===") println("Actual Pick Qty: ${actualPickQty}") println("Miss Qty: ${missQty}") @@ -455,75 +473,73 @@ private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, ac val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) if (inventoryLotLine != null) { - // 修复1:只处理已拣货的部分:更新 outQty + // ✅ 修改:更新 outQty 为 actualPickQty val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO val newOutQty = currentOutQty.add(actualPickQty) inventoryLotLine.outQty = newOutQty - // 修复2:Miss item 不减少 inQty,而是标记为 unavailable - // 因为 miss item 意味着这些物品实际上不存在或找不到 - // 所以应该标记整个批次为 unavailable,而不是减少 inQty + // ✅ 修改:释放 holdQty(减少 requiredQty) + val currentHoldQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO + val requiredQty = request.requiredQty ?: BigDecimal.ZERO + val newHoldQty = (currentHoldQty - requiredQty).coerceAtLeast(BigDecimal.ZERO) + inventoryLotLine.holdQty = newHoldQty - // 修复3:如果 missQty > 0,标记批次为 unavailable - if (missQty > BigDecimal.ZERO) { - inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE - } + // ✅ 修改:不设置 status 为 UNAVAILABLE + // if (missQty > BigDecimal.ZERO) { + // inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE + // } inventoryLotLine.modified = LocalDateTime.now() inventoryLotLine.modifiedBy = "system" - inventoryLotLineRepository.saveAndFlush(inventoryLotLine) // 使用 saveAndFlush + inventoryLotLineRepository.saveAndFlush(inventoryLotLine) println("Miss item with partial pick: Updated lot ${lotId}") - println(" - Added to outQty: ${actualPickQty} (${currentOutQty} -> ${newOutQty})") - println(" - Set status to UNAVAILABLE due to missQty: ${missQty}") + println(" - Updated outQty: ${currentOutQty} -> ${newOutQty} (actualPickQty: ${actualPickQty})") + println(" - Released holdQty: ${currentHoldQty} -> ${newHoldQty} (released: ${requiredQty})") + println(" - Did NOT set status to UNAVAILABLE") } - // 修复4:更新 inventory 表的 unavailableQty - // 对于 miss item,应该将 missQty 计入 unavailableQty - updateInventoryUnavailableQty(itemId, missQty) + // ✅ 修改:不更新 unavailableQty(因为不 reject lot) + // updateInventoryUnavailableQty(itemId, missQty) // 删除这行 - // 修复5:更新 stock_out_line 状态为 rejected(因为还有 miss item) - // 修复:同时创建 stock_ledger 记录 + // ✅ 修改:不 reject stock_out_line,根据 actualPickQty 设置状态 val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( request.pickOrderLineId, request.lotId ?: 0L ) stockOutLines.forEach { stockOutLine -> - stockOutLine.status = "rejected" + val requiredQty = request.requiredQty?.toDouble() ?: 0.0 + val actualPickQtyDouble = actualPickQty.toDouble() - // 更新 qty 为 actualPickQty - if (request.actualPickQty != null) { - stockOutLine.qty = request.actualPickQty.toDouble() - println("Updated stock out line ${stockOutLine.id} qty to: ${request.actualPickQty}") + // 设置状态:如果 actualPickQty >= requiredQty,则为 completed,否则为 partially_completed + val newStatus = if (actualPickQtyDouble >= requiredQty) { + "completed" + } else { + "partially_completed" } + stockOutLine.status = newStatus + stockOutLine.qty = actualPickQtyDouble stockOutLine.modified = LocalDateTime.now() stockOutLine.modifiedBy = "system" val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) - println("Updated stock out line ${stockOutLine.id} status to: rejected") + println("Updated stock out line ${stockOutLine.id} status to: ${newStatus} (NOT rejected)") - // 修复:为实际拣货的部分创建 stock_ledger 记录(即使状态是 rejected) - // 因为这部分确实从库存中出库了 - if (request.actualPickQty != null && request.actualPickQty > BigDecimal.ZERO) { - createStockLedgerForStockOut(savedStockOutLine, "Nor") // 实际拣货的部分 - } + // 创建 stock_ledger 记录 + createStockLedgerForStockOut(savedStockOutLine, "Nor") } - // 重新建议拣货批次(针对 miss 的数量) - try { - resuggestPickOrder(request.pickOrderId) - println("Resuggested pick order for miss qty: ${missQty}") - } catch (e: Exception) { - println("Error during resuggest in handleMissItemWithPartialPick: ${e.message}") - } + // ✅ 修改:不重新建议拣货批次(因为 lot 仍然可用) + // resuggestPickOrder(request.pickOrderId) // 删除这行 + println("Miss item with partial pick: Did NOT resuggest pick order (lot remains available)") } // 修复:Miss item 处理逻辑 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigDecimal) { - println("=== HANDLING MISS ITEM ONLY (FIXED LOGIC) ===") + println("=== HANDLING MISS ITEM ONLY (NEW LOGIC: DON'T REJECT LOT) ===") println("Miss Qty: ${missQty}") val lotId = request.lotId ?: return @@ -531,41 +547,69 @@ private fun handleMissItemOnly(request: PickExecutionIssueRequest, missQty: BigD val inventoryLotLine = inventoryLotLineRepository.findById(lotId).orElse(null) if (inventoryLotLine != null) { - - val currentInQty = inventoryLotLine.inQty ?: BigDecimal.ZERO - val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO - val remainingQty = currentInQty.minus(currentOutQty) - + // ✅ 修改:不设置 status 为 UNAVAILABLE + // ✅ 修改:释放 holdQty(减少 holdQty) + val currentHoldQty = inventoryLotLine.holdQty ?: BigDecimal.ZERO + val requiredQty = request.requiredQty ?: BigDecimal.ZERO + // 释放 holdQty:减少 requiredQty(因为已经处理完了) + val newHoldQty = (currentHoldQty - requiredQty).coerceAtLeast(BigDecimal.ZERO) + inventoryLotLine.holdQty = newHoldQty + + // ✅ 修改:如果有 actualPickQty,更新 outQty + val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO + if (actualPickQty > BigDecimal.ZERO) { + val currentOutQty = inventoryLotLine.outQty ?: BigDecimal.ZERO + val newOutQty = currentOutQty.add(actualPickQty) + inventoryLotLine.outQty = newOutQty + println(" - Updated outQty: ${currentOutQty} -> ${newOutQty} (actualPickQty: ${actualPickQty})") + } - inventoryLotLine.status = InventoryLotLineStatus.UNAVAILABLE inventoryLotLine.modified = LocalDateTime.now() inventoryLotLine.modifiedBy = "system" - inventoryLotLineRepository.save(inventoryLotLine) + inventoryLotLineRepository.saveAndFlush(inventoryLotLine) - println("Miss item only: Set lot ${lotId} status to UNAVAILABLE") - println(" - Remaining qty: ${remainingQty}") - println(" - Miss qty (user input): ${missQty}") - println(" - Unavailable qty (should be remaining qty): ${remainingQty}") + println("Miss item only: Updated lot ${lotId}") + println(" - Released holdQty: ${currentHoldQty} -> ${newHoldQty} (released: ${requiredQty})") + println(" - Did NOT set status to UNAVAILABLE") } - - val currentInQty = inventoryLotLine?.inQty ?: BigDecimal.ZERO - val currentOutQty = inventoryLotLine?.outQty ?: BigDecimal.ZERO - val remainingQty = currentInQty.minus(currentOutQty) - - - updateInventoryUnavailableQty(itemId, remainingQty) + // ✅ 修改:不更新 unavailableQty(因为不 reject lot) + // updateInventoryUnavailableQty(itemId, remainingQty) // 删除这行 - - updateStockOutLineStatus(request, "rejected") + // ✅ 修改:不 reject stock_out_line,根据 actualPickQty 设置状态 + val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( + request.pickOrderLineId, + request.lotId ?: 0L + ) - // 重新建议拣货批次 - try { - resuggestPickOrder(request.pickOrderId) - println("Resuggested pick order to find alternative lots for missing qty: ${missQty}") - } catch (e: Exception) { - println("Error during resuggest in handleMissItemOnly: ${e.message}") + stockOutLines.forEach { stockOutLine -> + val requiredQty = request.requiredQty?.toDouble() ?: 0.0 + val actualPickQtyDouble = request.actualPickQty?.toDouble() ?: 0.0 + + // 设置状态:如果 actualPickQty >= requiredQty,则为 completed,否则为 partially_completed + val newStatus = if (actualPickQtyDouble >= requiredQty) { + "completed" + } else { + "partially_completed" + } + + stockOutLine.status = newStatus + stockOutLine.qty = actualPickQtyDouble + stockOutLine.modified = LocalDateTime.now() + stockOutLine.modifiedBy = "system" + val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) + + println("Updated stock out line ${stockOutLine.id} status to: ${newStatus} (NOT rejected)") + + // 创建 stock_ledger 记录(如果有 actualPickQty) + if (actualPickQtyDouble > 0) { + createStockLedgerForStockOut(savedStockOutLine, "Nor") + } } + + // ✅ 修改:不重新建议拣货批次(因为 lot 仍然可用) + // resuggestPickOrder(request.pickOrderId) // 删除这行 + println("Miss item: Did NOT resuggest pick order (lot remains available)") } // 修复:Bad item 处理逻辑 @@ -729,6 +773,18 @@ private fun handleBothMissAndBadItem(request: PickExecutionIssueRequest, missQty println("Updated stock out line ${stockOutLine.id} status to: ${status}") } + try { + + stockOutLineRepository.flush() + + + stockOutLineService.checkIsStockOutLineCompleted(request.pickOrderLineId) + println("✅ Checked pick order line ${request.pickOrderLineId} completion status after updating stock out line") + } catch (e: Exception) { + println("⚠️ Error checking pick order line completion: ${e.message}") + e.printStackTrace() + // 不中断主流程,只记录错误 + } } // 修复:使用 REQUIRES_NEW 传播级别,避免事务冲突 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = [Exception::class]) 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 48e56ba..8914dec 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 @@ -66,7 +66,7 @@ import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecordReposito import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRecord import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLine import com.ffii.fpsms.modules.deliveryOrder.enums.DeliveryOrderStatus - +import com.ffii.fpsms.modules.stock.entity.InventoryLotRepository import com.ffii.fpsms.modules.stock.entity.SuggestPickLotRepository // 添加这行 import com.ffii.fpsms.modules.stock.entity.projection.StockOutLineInfo // 添加这行 import com.ffii.fpsms.modules.stock.entity.SuggestedPickLot @@ -98,6 +98,7 @@ open class PickOrderService( private val joPickOrderRepository: JoPickOrderRepository, // 添加这行 private val joPickOrderRecordRepository: JoPickOrderRecordRepository, private val doPickOrderLineRepository: DoPickOrderLineRepository, + private val inventoryLotRepository: InventoryLotRepository, ) : AbstractBaseEntityService(jdbcDao, pickOrderRepository) { @@ -4547,12 +4548,51 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto } // ✅ 根据 lotNo 和 itemId 查找新的 InventoryLotLine - val newIll = inventoryLotLineRepository.findByLotNoAndItemId(req.newInventoryLotNo, polItemId) - ?: return MessageResponse( - id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", - message = "Inventory lot line with lotNo '${req.newInventoryLotNo}' and itemId ${polItemId} not found", - errorPosition = null - ) + val newIll = when { + // 优先使用 stockInLineId(更可靠) + req.newStockInLineId != null && req.newStockInLineId > 0 -> { + // 通过 stockInLineId 查找 InventoryLot + val inventoryLot = inventoryLotRepository.findByStockInLineIdAndDeletedFalse(req.newStockInLineId) + ?: return MessageResponse( + id = null, name = "Inventory lot not found", code = "ERROR", type = "pickorder", + message = "Inventory lot with stockInLineId ${req.newStockInLineId} not found", + errorPosition = null + ) + + // 通过 InventoryLot 和 itemId 查找 InventoryLotLine + val lotLines = inventoryLotLineRepository.findAllByInventoryLotId(inventoryLot.id!!) + .filter { it.inventoryLot?.item?.id == polItemId && !it.deleted } + + if (lotLines.isEmpty()) { + return MessageResponse( + id = null, name = "Lot line not found", code = "ERROR", type = "pickorder", + message = "Inventory lot line with stockInLineId ${req.newStockInLineId} and itemId ${polItemId} not found", + errorPosition = null + ) + } + + // 如果有多个,取第一个(通常应该只有一个) + lotLines.first() + } + + // 兼容旧方式:使用 lotNo + req.newInventoryLotNo != null && req.newInventoryLotNo.isNotBlank() -> { + inventoryLotLineRepository.findByLotNoAndItemId(req.newInventoryLotNo, polItemId) + ?: return MessageResponse( + id = null, name = "New lot line not found", code = "ERROR", type = "pickorder", + message = "Inventory lot line with lotNo '${req.newInventoryLotNo}' and itemId ${polItemId} not found", + errorPosition = null + ) + } + + else -> { + return MessageResponse( + id = null, name = "Invalid request", code = "ERROR", type = "pickorder", + message = "Either newStockInLineId or newInventoryLotNo must be provided", + errorPosition = null + ) + } + } // Item consistency check (应该已经通过上面的查询保证了,但再次确认) val newItemId = newIll.inventoryLot?.item?.id @@ -4567,7 +4607,7 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto id = null, name = "Invalid lot line", code = "ERROR", type = "pickorder", message = "New inventory lot line has no ID", errorPosition = null ) - + val newLotNo = newIll.inventoryLot?.lotNo ?: req.newInventoryLotNo ?: "unknown" // 1) Update suggested pick lot (if provided): move holdQty from old ILL to new ILL and re-point the suggestion if (req.originalSuggestedPickLotId != null && req.originalSuggestedPickLotId > 0) { // ✅ 使用 repository 而不是 SQL @@ -4607,8 +4647,8 @@ println("DEBUG sol polIds in linesResults: " + linesResults.mapNotNull { it["sto name = "Lot substitution confirmed", code = "SUCCESS", type = "pickorder", - message = "Updated suggestion and stock out line to new lot line with lotNo '${req.newInventoryLotNo}'", - errorPosition = null + message = "Updated suggestion and stock out line to new lot line with lotNo '${newLotNo}'", + errorPosition = null ) } diff --git a/src/main/resources/db/changelog/changes/20260123_fai/01_m18.sql b/src/main/resources/db/changelog/changes/20260123_fai/01_m18.sql index 40e27fc..8ae8bad 100644 --- a/src/main/resources/db/changelog/changes/20260123_fai/01_m18.sql +++ b/src/main/resources/db/changelog/changes/20260123_fai/01_m18.sql @@ -1,5 +1,9 @@ --liquibase formatted sql --changeset author:vin m18 +--preconditions onFail:MARK_RAN +--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'delivery_order_line' AND COLUMN_NAME = 'qtyM18' +--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'delivery_order_line' AND COLUMN_NAME = 'delivery_order_linecol' +--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'delivery_order_line' AND COLUMN_NAME = 'uomIdM18' ALTER TABLE `fpsmsdb`.`delivery_order_line` ADD COLUMN `qtyM18` DECIMAL(14,2) NULL AFTER `qty`,