CANCERYS\kw093 пре 1 недеља
родитељ
комит
55a3b2779f
3 измењених фајлова са 179 додато и 79 уклоњено
  1. +125
    -69
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
  2. +50
    -10
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickOrderService.kt
  3. +4
    -0
      src/main/resources/db/changelog/changes/20260123_fai/01_m18.sql

+ 125
- 69
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])


+ 50
- 10
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<PickOrder, Long, PickOrderRepository>(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
)
}



+ 4
- 0
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`,


Loading…
Откажи
Сачувај