From 6211230564d12182a3665ea9fae0b365e6441c43 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sun, 20 Sep 2026 19:11:53 +0800 Subject: [PATCH] fix(lastleg): preserve mixed SKU balances when splitting return relabel stock --- .../service/PdaPalletOperationService.java | 22 +++++++----- .../mapper/ReturnInventoryCartonMapper.java | 5 +-- .../PdaPalletOperationServiceTest.java | 35 ++++++++++++++++++- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java index 637626f9b..8fc1a31bb 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java @@ -375,14 +375,20 @@ public class PdaPalletOperationService { .eq("application_id", application.getId()).eq("return_carton_id", source.getId())).size() != 1) throw conflict("来源托盘包含多项换标任务,不能按单 SKU 拆分,请核对库存"); List lines = returnCartonItems.lockCarton(source.getId()); - if (lines.size() != 1 || !item.getSkuCode().equalsIgnoreCase(lines.get(0).getSkuCode()) - || value(lines.get(0).getQuantity()) != value(source.getTotalQuantity())) - throw conflict("来源托盘包含其他 SKU,不能按单 SKU 拆分,请核对库存"); + List matching = lines.stream() + .filter(line -> item.getSkuCode().equalsIgnoreCase(line.getSkuCode())).toList(); + if (matching.size() != 1 || quantity <= 0 || quantity > value(matching.get(0).getQuantity()) + || lines.stream().anyMatch(line -> value(line.getQuantity()) <= 0) + || lines.stream().mapToLong(line -> value(line.getQuantity())).sum() != value(source.getTotalQuantity())) + throw conflict("来源托盘SKU数量不一致,请核对库存"); long remaining = value(source.getTotalQuantity()) - quantity; - ReturnInventoryCartonItem sourceLine = lines.get(0); - sourceLine.setQuantity(remaining); - if (returnCartonItems.updateById(sourceLine) != 1 - || returnCartons.releasePartial(source.getId(), application.getCustomerId(), application.getId(), remaining) != 1) + ReturnInventoryCartonItem sourceLine = matching.get(0); + long skuRemaining = value(sourceLine.getQuantity()) - quantity; + int remainingSkuTypes = lines.size() - (skuRemaining == 0 ? 1 : 0); + sourceLine.setQuantity(skuRemaining); + int changed = skuRemaining == 0 ? returnCartonItems.deleteById(sourceLine.getId()) : returnCartonItems.updateById(sourceLine); + if (changed != 1 + || returnCartons.releasePartial(source.getId(), application.getCustomerId(), application.getId(), remaining, remainingSkuTypes) != 1) throw conflict("来源托盘数量已变化,请刷新后重试"); LocalDateTime now = LocalDateTime.now(); long targetId = IdWorker.getId(); @@ -403,7 +409,7 @@ public class PdaPalletOperationService { targetLine.setProductNameZh(sourceLine.getProductNameZh()); targetLine.setQuantity(quantity); if (returnCartonItems.insert(targetLine) != 1 || returnBalances.adjustCartonQuantity(source.getCustomerId(), source.getOverseasWarehouseId(), - sourceLine.getSkuCode(), 1) != 1) throw conflict("退货库存拆分记账失败"); + sourceLine.getSkuCode(), skuRemaining == 0 ? 0 : 1) != 1) throw conflict("退货库存拆分记账失败"); item.setReturnCartonId(targetId); if (relabelItems.updateById(item) != 1) throw conflict("换标单来源托盘更新失败"); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/returnreceipt/mapper/ReturnInventoryCartonMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/returnreceipt/mapper/ReturnInventoryCartonMapper.java index e636fb9fb..ac0540219 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/returnreceipt/mapper/ReturnInventoryCartonMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/returnreceipt/mapper/ReturnInventoryCartonMapper.java @@ -36,12 +36,13 @@ public interface ReturnInventoryCartonMapper extends BaseMapper#{remaining}") int releasePartial(@Param("id") long id, @Param("customerId") String customerId, - @Param("applicationId") long applicationId, @Param("remaining") long remaining); + @Param("applicationId") long applicationId, @Param("remaining") long remaining, + @Param("skuTypes") int skuTypes); @Select("SELECT * FROM ll_return_inventory_carton WHERE source_receipt_id=#{receiptId} LIMIT 1") ReturnInventoryCarton findReceipt(@Param("receiptId") long receiptId); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java index 6ff734011..2541b2c2d 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java @@ -102,7 +102,7 @@ class PdaPalletOperationServiceTest { when(relabelItems.selectList(any())).thenReturn(List.of(item)); when(returnCartonItems.lockCarton(801L)).thenReturn(List.of(originalLine)); when(returnCartonItems.updateById(any(ReturnInventoryCartonItem.class))).thenReturn(1); - when(returnCartons.releasePartial(801L, "100", 600L, 70L)).thenReturn(1); + when(returnCartons.releasePartial(801L, "100", 600L, 70L, 1)).thenReturn(1); when(returnCartons.insert(any(ReturnInventoryCarton.class))).thenReturn(1); when(returnCartonItems.insert(any(ReturnInventoryCartonItem.class))).thenReturn(1); when(returnBalances.adjustCartonQuantity("100", "88", "SKU-1", 1)).thenReturn(1); @@ -128,6 +128,39 @@ class PdaPalletOperationServiceTest { verifyNoInteractions(mapper); } + @Test void mixedSkuSplitMovesOnlySelectedSkuAndPreservesOtherSkuOnSource() { + RelabelApplication app = new RelabelApplication(); app.setId(600L); app.setCustomerId("100"); + RelabelItem item = new RelabelItem(); item.setId(701L); item.setSkuCode("SKU-A"); + item.setReturnCartonId(801L); item.setDestinationFbaWarehouseCode("SCK8"); + ReturnInventoryCarton original = new ReturnInventoryCarton(); original.setId(801L); + original.setCustomerId("100"); original.setOverseasWarehouseId("88"); original.setTotalQuantity(2L); + original.setSourceReturnNo("RT-1"); original.setSourceBatchNo("RB-1,RB-2"); original.setPalletCode("OLD"); + ReturnInventoryCartonItem a = new ReturnInventoryCartonItem(); a.setId(901L); a.setSkuCode("SKU-A"); a.setQuantity(1L); + ReturnInventoryCartonItem b = new ReturnInventoryCartonItem(); b.setId(902L); b.setSkuCode("SKU-B"); b.setQuantity(1L); + when(relabelItems.selectList(any())).thenReturn(List.of(item)); + when(returnCartonItems.lockCarton(801L)).thenReturn(List.of(a, b)); + when(returnCartonItems.deleteById(901L)).thenReturn(1); + when(returnCartons.releasePartial(801L, "100", 600L, 1L, 1)).thenReturn(1); + when(returnCartons.insert(any(ReturnInventoryCarton.class))).thenReturn(1); + when(returnCartonItems.insert(any(ReturnInventoryCartonItem.class))).thenReturn(1); + when(returnBalances.adjustCartonQuantity("100", "88", "SKU-A", 0)).thenReturn(1); + when(relabelItems.updateById(any(RelabelItem.class))).thenReturn(1); + ReflectionTestUtils.invokeMethod(service, "splitReturnRelabelCarton", app, item, original, + pallet(30L, "NEW", 88L, 0), 1L); + assertEquals(1L, b.getQuantity()); + verify(returnCartonItems, never()).updateById(b); + var moved = ArgumentCaptor.forClass(ReturnInventoryCartonItem.class); + verify(returnCartonItems).insert(moved.capture()); + assertEquals("SKU-A", moved.getValue().getSkuCode()); + assertEquals(1L, moved.getValue().getQuantity()); + var target = ArgumentCaptor.forClass(ReturnInventoryCarton.class); + verify(returnCartons).insert(target.capture()); + assertEquals("RT-1", target.getValue().getSourceReturnNo()); + assertEquals("RB-1,RB-2", target.getValue().getSourceBatchNo()); + assertEquals("NEW", target.getValue().getPalletCode()); + verify(returnBalances, never()).adjustCartonQuantity(eq("100"), eq("88"), eq("SKU-B"), anyLong()); + } + @Test void resolveReportsMissingOnSitePalletAsNotFound() { when(physical.resolve("MISSING")).thenReturn(null);