This commit is contained in:
2026-10-15 15:42:10 +08:00
parent 095a95e64a
commit c6bd6af5ce
43 changed files with 1153 additions and 92 deletions
@@ -18,6 +18,16 @@ class WarehouseTransferCargoValidationTest {
}
}
@Test
void skuUnitsAndExpectedCartonsAreIndependent() {
try (var factory = Validation.buildDefaultValidatorFactory()) {
var validator = factory.getValidator();
assertTrue(validator.validate(new WarehouseTransferRequests.Item("SKU-A", "货物", 3, null, 120L)).isEmpty());
assertTrue(!validator.validate(new WarehouseTransferRequests.Item("SKU-A", "货物", 3, null, 0L)).isEmpty());
assertTrue(!validator.validate(new WarehouseTransferRequests.Item("SKU-A", "货物", 0, null, 120L)).isEmpty());
}
}
@Test
void cartonRelabelAllowsOriginalSkuToBeOmitted() {
try (var factory = Validation.buildDefaultValidatorFactory()) {
@@ -0,0 +1,108 @@
package com.baiyue.lastleg.transfer.service;
import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests;
import com.baiyue.lastleg.transfer.entity.*;
import com.baiyue.lastleg.transfer.mapper.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
class WarehouseTransferCartonCountTest {
WarehouseTransferService service;
WarehouseTransferOrderMapper orders = mock(WarehouseTransferOrderMapper.class);
WarehouseTransferCartonMapper cartons = mock(WarehouseTransferCartonMapper.class);
WarehouseTransferItemMapper items = mock(WarehouseTransferItemMapper.class);
WarehouseTransferPalletMapper palletMapper = mock(WarehouseTransferPalletMapper.class);
WarehouseTransferPallet pallet;
WarehouseTransferOrder order;
WarehouseTransferCarton a, b;
@BeforeEach void setup() {
service = spy(new WarehouseTransferService());
ReflectionTestUtils.setField(service, "transfers", orders);
ReflectionTestUtils.setField(service, "transferCartons", cartons);
ReflectionTestUtils.setField(service, "transferItems", items);
ReflectionTestUtils.setField(service, "transferPallets", palletMapper);
pallet = new WarehouseTransferPallet(); pallet.setId(200L); pallet.setStatus("GENERATED"); pallet.setWarehousePalletId(900L);
when(palletMapper.listOrder("C1", 1L)).thenReturn(List.of(pallet));
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
doReturn(null).when(service).detail(anyString(), anyString());
order = new WarehouseTransferOrder(); order.setId(1L); order.setCustomerId("C1"); order.setTransferNo("WT1"); order.setStatus("WAIT_PDA_RECEIVING"); order.setPackageCount(2);
a = box("BOX1", 10L); b = box("BOX2", 11L);
when(orders.lockForPda("WT1", "77")).thenReturn(order);
when(cartons.listOrder("C1", 1L)).thenReturn(List.of(a,b));
var ia = new WarehouseTransferItem(); ia.setId(10L); ia.setSkuCode("A");
var ib = new WarehouseTransferItem(); ib.setId(11L); ib.setSkuCode("B");
when(items.selectList(any())).thenReturn(List.of(ia,ib));
}
WarehouseTransferCarton box(String mark, long item) { var box = new WarehouseTransferCarton(); box.setCartonMark(mark); box.setCartonIndex((int)item); box.setTransferItemId(item); return box; }
WarehouseTransferRequests.CountCarton input(String mark, String sku, long quantity) { return new WarehouseTransferRequests.CountCarton(mark, null, 200L, new BigDecimal("20"),new BigDecimal("30"),new BigDecimal("40"),new BigDecimal("2.5"),sku,quantity); }
WarehouseTransferRequests.CountCartons request(String key, WarehouseTransferRequests.CountCarton... rows) { return new WarehouseTransferRequests.CountCartons(key,List.of(rows),null); }
@Test void multiSkuBoxesPersistDimensionsPiecesAndReplayWithoutWriting() {
var request = request("K1",input("BOX1","A",12),input("BOX2","B",7));
service.countCartons("OP1",77L,"WT1",request);
assertEquals(12L,a.getCountedQuantity()); assertEquals(7L,b.getCountedQuantity()); assertNotNull(a.getCountedAt()); assertEquals("OP1",a.getCountedBy()); assertEquals(new BigDecimal("2.5"),b.getGrossWeightKg());
assertEquals(200L,a.getTransferPalletId()); assertEquals(2,pallet.getCartonCount());
assertEquals(new BigDecimal("0.048000"),pallet.getVolumeCbm()); assertEquals(new BigDecimal("5.0"),pallet.getGrossWeightKg());
clearInvocations(cartons); order.setStatus("WAIT_CUSTOMER_CONFIRMATION");
service.countCartons("OP1",77L,"WT1",request);
verify(cartons,never()).updateById(any(WarehouseTransferCarton.class));
assertThrows(ResponseStatusException.class,()->service.countCartons("OP1",77L,"WT1",request("K1",input("BOX1","A",13),input("BOX2","B",7))));
}
@Test void missingDuplicateForeignSkuAndInvalidQuantityRejectBeforeWrites() {
var invalid = List.of(request("K",input("BOX1","A",1)),request("K",input("BOX1","A",1),input("BOX1","A",2)),request("K",input("OTHER","A",1),input("BOX2","B",2)),request("K",input("BOX1","B",1),input("BOX2","B",2)),request("K",input("BOX1","A",0),input("BOX2","B",2)));
for(var request:invalid) assertThrows(ResponseStatusException.class,()->service.countCartons("OP1",77L,"WT1",request));
verify(cartons,never()).updateById(any(WarehouseTransferCarton.class));
}
@Test void shortReceiptNeedsReasonAndPreservesMissingBoxFact() {
var request = new WarehouseTransferRequests.CountCartons("SHORT",List.of(input("BOX1","A",3)),"少到一箱");
service.countCartons("OP1",77L,"WT1",request);
assertEquals("COUNTED",a.getStatus()); assertEquals("NOT_RECEIVED",b.getStatus());
assertEquals("少到一箱",b.getDiscrepancyReason()); assertNull(b.getCountedQuantity());
}
@Test void extraReceiptGeneratesOwnedBoxAndReconcilesToDeclaredSku() {
var extra = new WarehouseTransferRequests.CountCarton(null,11L,200L,new BigDecimal("10"),new BigDecimal("20"),new BigDecimal("30"),BigDecimal.ONE,"B",4);
service.countCartons("OP1",77L,"WT1",new WarehouseTransferRequests.CountCartons("EXTRA",List.of(input("BOX1","A",1),input("BOX2","B",2),extra),"多到一箱"));
var captured = org.mockito.ArgumentCaptor.forClass(WarehouseTransferCarton.class);
verify(cartons).insert(captured.capture());
assertEquals("WT1#EX-12",captured.getValue().getCartonMark()); assertEquals("C1",captured.getValue().getCustomerId()); assertEquals(4L,captured.getValue().getCountedQuantity());
}
@Test void cartonsRequireGeneratedPalletOwnedByThisOrder() {
when(palletMapper.listOrder("C1",1L)).thenReturn(List.of());
assertThrows(ResponseStatusException.class,()->service.countCartons("OP1",77L,"WT1",request("K",input("BOX1","A",1),input("BOX2","B",2))));
verify(cartons,never()).updateById(any(WarehouseTransferCarton.class));
when(palletMapper.listOrder("C1",1L)).thenReturn(List.of(pallet)); pallet.setStatus("DRAFT");
assertThrows(ResponseStatusException.class,()->service.countCartons("OP1",77L,"WT1",request("K",input("BOX1","A",1),input("BOX2","B",2))));
}
@Test void multiSkuPalletCreatesOneInboundLineAllocationWithSummedBoxes() {
var plans = mock(com.baiyue.lastleg.inboundplan.mapper.InboundPlanMapper.class);
var lines = mock(com.baiyue.lastleg.inboundplan.mapper.InboundPlanLineMapper.class);
var pallets = mock(com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletMapper.class);
var allocations = mock(com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletAllocationMapper.class);
ReflectionTestUtils.setField(service,"inboundPlans",plans); ReflectionTestUtils.setField(service,"inboundPlanLines",lines);
ReflectionTestUtils.setField(service,"inboundPlanPallets",pallets); ReflectionTestUtils.setField(service,"inboundPlanAllocations",allocations);
order.setLastlegOrderId(9L); order.setInboundCode("WT1");
var plan = new com.baiyue.lastleg.inboundplan.entity.InboundPlan(); plan.setId(100L);
var line = new com.baiyue.lastleg.inboundplan.entity.InboundPlanLine(); line.setId(101L);
when(plans.findBySourceOrder("C1",9L)).thenReturn(plan); when(lines.selectOne(any())).thenReturn(line);
var pallet = new WarehouseTransferPallet(); pallet.setId(200L); pallet.setCartonCount(3); pallet.setVolumeCbm(BigDecimal.ONE); pallet.setSequenceNo(1);
var first = new WarehouseTransferPalletItem(); first.setTransferPalletId(200L); first.setTransferItemId(10L); first.setPackageCount(1);
var second = new WarehouseTransferPalletItem(); second.setTransferPalletId(200L); second.setTransferItemId(11L); second.setPackageCount(2);
ReflectionTestUtils.invokeMethod(service,"completeTransferInboundPlan","C1",order,List.of(pallet),List.of(first,second),java.time.LocalDateTime.now());
var result=org.mockito.ArgumentCaptor.forClass(com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation.class);
verify(allocations,times(1)).insert(result.capture()); assertEquals(3,result.getValue().getPickedPackageCount()); assertEquals(3,result.getValue().getCountedPackageCount());
}
@Test void otherWarehouseCannotReadOrWriteAndCompletedCountsCannotBeOverwritten() {
assertThrows(ResponseStatusException.class,()->service.countCartons("OP1",88L,"WT1",request("K",input("BOX1","A",1))));
service.countCartons("OP1",77L,"WT1",request("K",input("BOX1","A",1),input("BOX2","B",2)));
clearInvocations(cartons);
assertThrows(ResponseStatusException.class,()->service.countCartons("OP2",77L,"WT1",request("NEW",input("BOX1","A",9),input("BOX2","B",2))));
verify(cartons,never()).updateById(any(WarehouseTransferCarton.class));
}
}
@@ -48,19 +48,17 @@ class WarehouseTransferPalletAllocationTest {
}
@Test
void persistedOverReceiptUsesTheSameStableAllocationRule() {
WarehouseTransferPallet first = persistedPallet(4);
WarehouseTransferPallet second = persistedPallet(3);
var allocations = WarehouseTransferService.derivePersistedPalletAllocations(
List.of(item(11L, 3), item(12L, 2)), List.of(first, second));
assertEquals(7, allocations.stream().mapToInt(value -> value.packageCount()).sum());
assertEquals(3, allocations.stream().filter(value -> value.transferItemId() == 11L)
.mapToInt(value -> value.packageCount()).sum());
assertEquals(4, allocations.stream().filter(value -> value.transferItemId() == 12L)
.mapToInt(value -> value.packageCount()).sum());
assertEquals(List.of(0, 0, 1), allocations.stream().map(value -> value.palletIndex()).toList());
void countedBoxesDetermineSkuPlacementRatherThanDeclarationOrder() {
WarehouseTransferPallet first = persistedPallet(2); first.setId(100L);
WarehouseTransferPallet second = persistedPallet(1); second.setId(200L);
var a = new com.baiyue.lastleg.transfer.entity.WarehouseTransferCarton(); a.setTransferItemId(12L); a.setTransferPalletId(100L);
var b = new com.baiyue.lastleg.transfer.entity.WarehouseTransferCarton(); b.setTransferItemId(12L); b.setTransferPalletId(100L);
var c = new com.baiyue.lastleg.transfer.entity.WarehouseTransferCarton(); c.setTransferItemId(11L); c.setTransferPalletId(200L);
var allocations = WarehouseTransferService.deriveCountedPalletAllocations(List.of(a,b,c),List.of(first,second));
assertEquals(List.of(new WarehouseTransferService.DerivedPalletAllocation(0,12L,2),new WarehouseTransferService.DerivedPalletAllocation(1,11L,1)),allocations);
c.setTransferPalletId(999L);
org.junit.jupiter.api.Assertions.assertThrows(org.springframework.web.server.ResponseStatusException.class,
() -> WarehouseTransferService.deriveCountedPalletAllocations(List.of(a,b,c),List.of(first,second)));
}
@Test
@@ -322,6 +322,10 @@ class WarehouseTransferServiceTest {
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.WAIT_PDA_RECEIVING);
transfer.setOperatingWarehouseId("77");
WarehouseTransferPallet pallet = measuredPallet("GENERATED");
var counted = java.util.stream.IntStream.rangeClosed(1, 5).mapToObj(index -> {
var box = new WarehouseTransferCarton(); box.setStatus("COUNTED"); box.setCountedAt(LocalDateTime.now()); return box;
}).toList();
when(transferCartons.listOrder("TENANT-A", transfer.getId())).thenReturn(counted);
when(transfers.lockForPda("WTR-1", "77")).thenReturn(transfer);
when(transferItems.selectList(any())).thenReturn(List.of(transferItem(51L, "SKU-1", 5)));
WarehouseTransferMeasurementPhoto photo = new WarehouseTransferMeasurementPhoto();
@@ -366,6 +370,10 @@ class WarehouseTransferServiceTest {
new InventoryCommandResponse(1L, "STOCK_IN", 801L, 901L, 5, 0, 0, 0, false, false));
when(sourceOrders.selectList(any())).thenReturn(List.of());
var counted = java.util.stream.IntStream.rangeClosed(1, 5).mapToObj(index -> {
var box = new WarehouseTransferCarton(); box.setTransferPalletId(pallet.getId()); box.setTransferItemId(item.getId()); box.setCartonIndex(index); box.setTotalCartons(5); box.setCartonMark("BOX-" + index); box.setStatus("COUNTED"); box.setCountedAt(LocalDateTime.now()); return box;
}).toList();
when(transferCartons.listOrder("TENANT-A", transfer.getId())).thenReturn(counted);
var result = service.palletize("OP-1", 77L, "WTR-1",
new WarehouseTransferRequests.Palletize("DONE-1", List.of(9001L)));