|
|
@@ -92,10 +92,18 @@ open class PickExecutionIssueService( |
|
|
println(" issueCategory: ${request.issueCategory}") |
|
|
println(" issueCategory: ${request.issueCategory}") |
|
|
println("========================================") |
|
|
println("========================================") |
|
|
|
|
|
|
|
|
// 1. 检查是否已经存在相同的 pick execution issue 记录 |
|
|
|
|
|
|
|
|
// 1. 解析 lot: |
|
|
|
|
|
// request.lotId 在前端目前传的是 inventory_lot_line.id(用于 SOL 关联/计算 bookQty 等) |
|
|
|
|
|
// 但 pick_execution_issue.lot_id 在 DB 上外键指向 inventory_lot.id |
|
|
|
|
|
val inventoryLotLine = request.lotId?.let { |
|
|
|
|
|
inventoryLotLineRepository.findById(it).orElse(null) |
|
|
|
|
|
} |
|
|
|
|
|
val inventoryLotIdForIssue = inventoryLotLine?.inventoryLot?.id |
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查是否已经存在相同的 pick execution issue 记录(以 inventory_lot.id 去重) |
|
|
val existingIssues = pickExecutionIssueRepository.findByPickOrderLineIdAndLotIdAndDeletedFalse( |
|
|
val existingIssues = pickExecutionIssueRepository.findByPickOrderLineIdAndLotIdAndDeletedFalse( |
|
|
request.pickOrderLineId, |
|
|
|
|
|
request.lotId ?: 0L |
|
|
|
|
|
|
|
|
request.pickOrderLineId, |
|
|
|
|
|
inventoryLotIdForIssue ?: 0L |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
println("Checking for existing issues...") |
|
|
println("Checking for existing issues...") |
|
|
@@ -119,12 +127,8 @@ open class PickExecutionIssueService( |
|
|
val pickOrder = pickOrderRepository.findById(request.pickOrderId).orElse(null) |
|
|
val pickOrder = pickOrderRepository.findById(request.pickOrderId).orElse(null) |
|
|
println("Pick order: id=${pickOrder?.id}, code=${pickOrder?.code}, type=${pickOrder?.type?.value}") |
|
|
println("Pick order: id=${pickOrder?.id}, code=${pickOrder?.code}, type=${pickOrder?.type?.value}") |
|
|
|
|
|
|
|
|
// 2. 获取 inventory_lot_line 并计算账面数量 (bookQty) |
|
|
|
|
|
val inventoryLotLine = request.lotId?.let { |
|
|
|
|
|
inventoryLotLineRepository.findById(it).orElse(null) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
println("Inventory lot line: id=${inventoryLotLine?.id}, lotNo=${inventoryLotLine?.inventoryLot?.lotNo}") |
|
|
|
|
|
|
|
|
// 3. 计算账面数量 (bookQty)(用 inventory_lot_line 快照) |
|
|
|
|
|
println("Inventory lot line: id=${inventoryLotLine?.id}, lotNo=${inventoryLotLine?.inventoryLot?.lotNo}, inventoryLotId=${inventoryLotIdForIssue}") |
|
|
|
|
|
|
|
|
// 计算账面数量(创建 issue 时的快照) |
|
|
// 计算账面数量(创建 issue 时的快照) |
|
|
val bookQty = if (inventoryLotLine != null) { |
|
|
val bookQty = if (inventoryLotLine != null) { |
|
|
@@ -138,19 +142,19 @@ open class PickExecutionIssueService( |
|
|
BigDecimal.ZERO |
|
|
BigDecimal.ZERO |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 3. 获取数量值 |
|
|
|
|
|
|
|
|
// 4. 获取数量值 |
|
|
val requiredQty = request.requiredQty ?: BigDecimal.ZERO |
|
|
val requiredQty = request.requiredQty ?: BigDecimal.ZERO |
|
|
val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO |
|
|
val actualPickQty = request.actualPickQty ?: BigDecimal.ZERO |
|
|
val missQty = request.missQty ?: BigDecimal.ZERO |
|
|
val missQty = request.missQty ?: BigDecimal.ZERO |
|
|
val badItemQty = request.badItemQty ?: BigDecimal.ZERO |
|
|
val badItemQty = request.badItemQty ?: BigDecimal.ZERO |
|
|
val badReason = request.badReason ?: "quantity_problem" |
|
|
val badReason = request.badReason ?: "quantity_problem" |
|
|
val stockOutLines = stockOutLineRepository |
|
|
|
|
|
|
|
|
val relatedStockOutLines = stockOutLineRepository |
|
|
.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( |
|
|
.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( |
|
|
request.pickOrderLineId, |
|
|
request.pickOrderLineId, |
|
|
request.lotId ?: 0L |
|
|
request.lotId ?: 0L |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
val currentStatus = stockOutLines.firstOrNull()?.status ?: "" |
|
|
|
|
|
|
|
|
val currentStatus = relatedStockOutLines.firstOrNull()?.status ?: "" |
|
|
|
|
|
|
|
|
if (currentStatus.equals("pending", ignoreCase = true) |
|
|
if (currentStatus.equals("pending", ignoreCase = true) |
|
|
&& actualPickQty > BigDecimal.ZERO |
|
|
&& actualPickQty > BigDecimal.ZERO |
|
|
@@ -187,7 +191,7 @@ open class PickExecutionIssueService( |
|
|
println(" Bad Reason: $badReason") |
|
|
println(" Bad Reason: $badReason") |
|
|
println(" Book Qty: $bookQty") |
|
|
println(" Book Qty: $bookQty") |
|
|
|
|
|
|
|
|
// 4. 计算 issueQty(实际的问题数量) |
|
|
|
|
|
|
|
|
// 5. 计算 issueQty(实际的问题数量) |
|
|
val issueQty = when { |
|
|
val issueQty = when { |
|
|
// Bad item 或 bad package:一律用用户输入的 bad 数量,不用 bookQty - actualPickQty |
|
|
// Bad item 或 bad package:一律用用户输入的 bad 数量,不用 bookQty - actualPickQty |
|
|
badItemQty > BigDecimal.ZERO -> { |
|
|
badItemQty > BigDecimal.ZERO -> { |
|
|
@@ -215,9 +219,10 @@ open class PickExecutionIssueService( |
|
|
println("================================================") |
|
|
println("================================================") |
|
|
println("=== Processing Logic Selection ===") |
|
|
println("=== Processing Logic Selection ===") |
|
|
|
|
|
|
|
|
// 5. 创建 pick execution issue 记录 |
|
|
|
|
|
|
|
|
// 6. 创建 pick execution issue 记录 |
|
|
val issueNo = generateIssueNo() |
|
|
val issueNo = generateIssueNo() |
|
|
println("Generated issue number: $issueNo") |
|
|
println("Generated issue number: $issueNo") |
|
|
|
|
|
val lotNoForIssue = request.lotNo ?: inventoryLotLine?.inventoryLot?.lotNo |
|
|
|
|
|
|
|
|
val pickExecutionIssue = PickExecutionIssue( |
|
|
val pickExecutionIssue = PickExecutionIssue( |
|
|
id = null, |
|
|
id = null, |
|
|
@@ -235,8 +240,8 @@ open class PickExecutionIssueService( |
|
|
itemId = request.itemId, |
|
|
itemId = request.itemId, |
|
|
itemCode = request.itemCode, |
|
|
itemCode = request.itemCode, |
|
|
itemDescription = request.itemDescription, |
|
|
itemDescription = request.itemDescription, |
|
|
lotId = request.lotId, |
|
|
|
|
|
lotNo = request.lotNo, |
|
|
|
|
|
|
|
|
lotId = inventoryLotIdForIssue, |
|
|
|
|
|
lotNo = lotNoForIssue, |
|
|
storeLocation = request.storeLocation, |
|
|
storeLocation = request.storeLocation, |
|
|
requiredQty = request.requiredQty, |
|
|
requiredQty = request.requiredQty, |
|
|
actualPickQty = request.actualPickQty, |
|
|
actualPickQty = request.actualPickQty, |
|
|
@@ -265,7 +270,7 @@ open class PickExecutionIssueService( |
|
|
println(" Handle Status: ${savedIssue.handleStatus}") |
|
|
println(" Handle Status: ${savedIssue.handleStatus}") |
|
|
println(" Issue Qty: ${savedIssue.issueQty}") |
|
|
println(" Issue Qty: ${savedIssue.issueQty}") |
|
|
|
|
|
|
|
|
// 6. NEW: Update inventory_lot_line.issueQty |
|
|
|
|
|
|
|
|
// 7. NEW: Update inventory_lot_line.issueQty(仍然用 lotLineId) |
|
|
if (request.lotId != null && inventoryLotLine != null) { |
|
|
if (request.lotId != null && inventoryLotLine != null) { |
|
|
println("Updating inventory_lot_line.issueQty...") |
|
|
println("Updating inventory_lot_line.issueQty...") |
|
|
// ✅ 修改:如果只有 missQty,不更新 issueQty |
|
|
// ✅ 修改:如果只有 missQty,不更新 issueQty |
|
|
@@ -305,100 +310,19 @@ open class PickExecutionIssueService( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 7. 获取相关数据用于后续处理 |
|
|
|
|
|
val actualPickQtyForProcessing = request.actualPickQty ?: BigDecimal.ZERO |
|
|
|
|
|
val missQtyForProcessing = request.missQty ?: BigDecimal.ZERO |
|
|
|
|
|
val badItemQtyForProcessing = request.badItemQty ?: BigDecimal.ZERO |
|
|
|
|
|
val lotId = request.lotId |
|
|
|
|
|
val itemId = request.itemId |
|
|
|
|
|
|
|
|
|
|
|
println("=== Processing Logic Selection ===") |
|
|
|
|
|
println("Actual Pick Qty: $actualPickQtyForProcessing") |
|
|
|
|
|
println("Miss Qty: $missQtyForProcessing") |
|
|
|
|
|
println("Bad Item Qty: $badItemQtyForProcessing") |
|
|
|
|
|
println("Bad Reason: ${request.badReason}") |
|
|
|
|
|
println("Lot ID: $lotId") |
|
|
|
|
|
println("Item ID: $itemId") |
|
|
|
|
|
println("================================================") |
|
|
|
|
|
|
|
|
|
|
|
// 8. 新的统一处理逻辑(根据 badReason 决定处理方式) |
|
|
|
|
|
when { |
|
|
|
|
|
// 情况1: 只有 miss item (actualPickQty = 0, missQty > 0, badItemQty = 0) |
|
|
|
|
|
actualPickQtyForProcessing == BigDecimal.ZERO && missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing == BigDecimal.ZERO -> { |
|
|
|
|
|
println("→ Handling: Miss Item Only") |
|
|
|
|
|
handleMissItemOnly(request, missQtyForProcessing) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 情况2: 只有 bad item (badItemQty > 0, missQty = 0) |
|
|
|
|
|
badItemQtyForProcessing > BigDecimal.ZERO && missQtyForProcessing == BigDecimal.ZERO -> { |
|
|
|
|
|
println("→ Handling: Bad Item Only") |
|
|
|
|
|
// NEW: Check bad reason |
|
|
|
|
|
if (request.badReason == "package_problem") { |
|
|
|
|
|
println(" Bad reason is 'package_problem' - calling handleBadItemPackageProblem") |
|
|
|
|
|
handleBadItemPackageProblem(request, badItemQtyForProcessing) |
|
|
|
|
|
} else { |
|
|
|
|
|
println(" Bad reason is 'quantity_problem' - calling handleBadItemOnly") |
|
|
|
|
|
// quantity_problem or default: handle as normal bad item |
|
|
|
|
|
handleBadItemOnly(request, badItemQtyForProcessing) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 情况3: 既有 miss item 又有 bad item |
|
|
|
|
|
missQtyForProcessing > BigDecimal.ZERO && badItemQtyForProcessing > BigDecimal.ZERO -> { |
|
|
|
|
|
println("→ Handling: Both Miss and Bad Item") |
|
|
|
|
|
// NEW: Check bad reason |
|
|
|
|
|
if (request.badReason == "package_problem") { |
|
|
|
|
|
println(" Bad reason is 'package_problem' - calling handleBothMissAndBadItemPackageProblem") |
|
|
|
|
|
handleBothMissAndBadItemPackageProblem(request, missQtyForProcessing, badItemQtyForProcessing) |
|
|
|
|
|
} else { |
|
|
|
|
|
println(" Bad reason is 'quantity_problem' - calling handleBothMissAndBadItem") |
|
|
|
|
|
handleBothMissAndBadItem(request, missQtyForProcessing, badItemQtyForProcessing) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 情况4: 有 miss item 的情况(无论 actualPickQty 是多少) |
|
|
|
|
|
missQtyForProcessing > BigDecimal.ZERO -> { |
|
|
|
|
|
println("→ Handling: Miss Item With Partial Pick") |
|
|
|
|
|
handleMissItemWithPartialPick(request, actualPickQtyForProcessing, missQtyForProcessing) |
|
|
|
|
|
} |
|
|
|
|
|
actualPickQtyForProcessing == BigDecimal.ZERO && |
|
|
|
|
|
missQtyForProcessing == BigDecimal.ZERO && |
|
|
|
|
|
badItemQtyForProcessing == BigDecimal.ZERO -> { |
|
|
|
|
|
println("→ Handling: All zero, mark stock out line as completed") |
|
|
|
|
|
handleAllZeroMarkCompleted(request) |
|
|
|
|
|
} |
|
|
|
|
|
// 情况5: 正常拣货 (actualPickQty > 0, 没有 miss 或 bad item) |
|
|
|
|
|
actualPickQtyForProcessing > BigDecimal.ZERO -> { |
|
|
|
|
|
println("→ Handling: Normal Pick") |
|
|
|
|
|
handleNormalPick(request, actualPickQtyForProcessing) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
else -> { |
|
|
|
|
|
println("⚠️ Unknown case: actualPickQty=$actualPickQtyForProcessing, missQty=$missQtyForProcessing, badItemQty=$badItemQtyForProcessing") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val pickOrderForCompletion = pickOrderRepository.findById(request.pickOrderId).orElse(null) |
|
|
|
|
|
val consoCode = pickOrderForCompletion?.consoCode |
|
|
|
|
|
|
|
|
|
|
|
if (!consoCode.isNullOrBlank()) { |
|
|
|
|
|
// 优先走原来的 consoCode 逻辑(兼容已有 DO 流程) |
|
|
|
|
|
println("🔍 Checking if pick order $consoCode should be completed after lot rejection...") |
|
|
|
|
|
try { |
|
|
|
|
|
checkAndCompletePickOrder(consoCode) |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
println("⚠️ Error checking pick order completion by consoCode: ${e.message}") |
|
|
|
|
|
} |
|
|
|
|
|
} else if (pickOrderForCompletion != null) { |
|
|
|
|
|
// 🔁 没有 consoCode 的情况:改用 pickOrderId 去检查是否可以完结 |
|
|
|
|
|
println("🔍 Checking if pick order ${pickOrderForCompletion.code} (ID=${pickOrderForCompletion.id}) " + |
|
|
|
|
|
"should be completed after lot rejection (no consoCode)...") |
|
|
|
|
|
try { |
|
|
|
|
|
checkAndCompletePickOrderByPickOrderId(pickOrderForCompletion.id!!) |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
println("⚠️ Error checking pick order completion by pickOrderId: ${e.message}") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 7. 按规则:issue form 只记录问题 +(可选)把 SOL 标记为 checked |
|
|
|
|
|
val stockOutLines = stockOutLineRepository |
|
|
|
|
|
.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( |
|
|
|
|
|
request.pickOrderLineId, |
|
|
|
|
|
request.lotId ?: 0L |
|
|
|
|
|
) |
|
|
|
|
|
stockOutLines.forEach { sol -> |
|
|
|
|
|
sol.status = "checked" |
|
|
|
|
|
sol.modified = LocalDateTime.now() |
|
|
|
|
|
sol.modifiedBy = "system" |
|
|
|
|
|
stockOutLineRepository.save(sol) |
|
|
} |
|
|
} |
|
|
|
|
|
stockOutLineRepository.flush() |
|
|
|
|
|
|
|
|
println("=== recordPickExecutionIssue: SUCCESS ===") |
|
|
println("=== recordPickExecutionIssue: SUCCESS ===") |
|
|
println("Issue ID: ${savedIssue.id}, Issue No: ${savedIssue.issueNo}") |
|
|
println("Issue ID: ${savedIssue.id}, Issue No: ${savedIssue.issueNo}") |
|
|
@@ -433,11 +357,12 @@ open class PickExecutionIssueService( |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
stockOutLines.forEach { sol -> |
|
|
stockOutLines.forEach { sol -> |
|
|
sol.status = "completed" |
|
|
|
|
|
|
|
|
// issue form 不完结,只标记 checked,让 submit/batch submit 决定 completed(允许 0) |
|
|
|
|
|
sol.status = "checked" |
|
|
sol.modified = LocalDateTime.now() |
|
|
sol.modified = LocalDateTime.now() |
|
|
sol.modifiedBy = "system" |
|
|
sol.modifiedBy = "system" |
|
|
stockOutLineRepository.save(sol) |
|
|
stockOutLineRepository.save(sol) |
|
|
println("All-zero case: mark stock out line ${sol.id} as completed (qty kept as ${sol.qty})") |
|
|
|
|
|
|
|
|
println("All-zero case: mark stock out line ${sol.id} as checked (qty kept as ${sol.qty})") |
|
|
} |
|
|
} |
|
|
stockOutLineRepository.flush() |
|
|
stockOutLineRepository.flush() |
|
|
} |
|
|
} |
|
|
@@ -773,34 +698,18 @@ private fun handleMissItemWithPartialPick(request: PickExecutionIssueRequest, ac |
|
|
|
|
|
|
|
|
// ✅ 修改:不更新 unavailableQty(因为不 reject lot) |
|
|
// ✅ 修改:不更新 unavailableQty(因为不 reject lot) |
|
|
|
|
|
|
|
|
// ✅ 修改:不 reject stock_out_line,根据 actualPickQty 设置状态 |
|
|
|
|
|
|
|
|
// ✅ 按规则:issue form 不负责完结/数量提交,只记录问题 +(可选)把 SOL 标记为 checked |
|
|
val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( |
|
|
val stockOutLines = stockOutLineRepository.findByPickOrderLineIdAndInventoryLotLineIdAndDeletedFalse( |
|
|
request.pickOrderLineId, |
|
|
request.pickOrderLineId, |
|
|
request.lotId ?: 0L |
|
|
request.lotId ?: 0L |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
stockOutLines.forEach { stockOutLine -> |
|
|
stockOutLines.forEach { stockOutLine -> |
|
|
val requiredQty = request.requiredQty?.toDouble() ?: 0.0 |
|
|
|
|
|
val actualPickQtyDouble = actualPickQty.toDouble() |
|
|
|
|
|
|
|
|
|
|
|
// 设置状态:如果 actualPickQty >= requiredQty,则为 completed,否则为 partially_completed |
|
|
|
|
|
val newStatus = if (actualPickQtyDouble >= requiredQty) { |
|
|
|
|
|
"completed" |
|
|
|
|
|
} else { |
|
|
|
|
|
"partially_completed" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
stockOutLine.status = newStatus |
|
|
|
|
|
stockOutLine.qty = actualPickQtyDouble |
|
|
|
|
|
|
|
|
stockOutLine.status = "checked" |
|
|
stockOutLine.modified = LocalDateTime.now() |
|
|
stockOutLine.modified = LocalDateTime.now() |
|
|
stockOutLine.modifiedBy = "system" |
|
|
stockOutLine.modifiedBy = "system" |
|
|
val savedStockOutLine = stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
|
|
|
|
|
|
println("Updated stock out line ${stockOutLine.id} status to: ${newStatus} (NOT rejected)") |
|
|
|
|
|
|
|
|
|
|
|
// ✅ 修复:使用更新前的 onHandQty 计算 balance |
|
|
|
|
|
val balance = onHandQtyBeforeUpdate - actualPickQtyDouble |
|
|
|
|
|
createStockLedgerForStockOut(savedStockOutLine, "Nor", balance) |
|
|
|
|
|
|
|
|
stockOutLineRepository.saveAndFlush(stockOutLine) |
|
|
|
|
|
println("Issue form: marked stock out line ${stockOutLine.id} as checked (no completion)") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// ✅ 修复:检查 pick order line 是否应该标记为完成 |
|
|
// ✅ 修复:检查 pick order line 是否应该标记为完成 |
|
|
|