fix(dropship): serialize outbound creation and pick exact carton subsets
This commit is contained in:
+2
@@ -25,6 +25,7 @@ class DeliverySchemaContractTest {
|
||||
assertTrue(schema.contains("create table if not exists ll_outbound_order"));
|
||||
assertTrue(schema.contains("status varchar(32) not null default 'wait_outbound'"));
|
||||
assertTrue(schema.contains("unique key uk_ll_outbound_idempotency (customer_id,idempotency_key)"));
|
||||
assertTrue(schema.contains("request_hash char(64) null"));
|
||||
assertTrue(schema.contains("unique key uk_ll_outbound_dispatch (customer_id,dispatch_order_id)"));
|
||||
assertTrue(schema.contains("create table if not exists ll_outbound_pallet_item"));
|
||||
assertTrue(schema.contains("unique key uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id,inbound_code,sku_code)"));
|
||||
@@ -66,6 +67,7 @@ class DeliverySchemaContractTest {
|
||||
StandardCharsets.UTF_8).toLowerCase();
|
||||
assertTrue(overseas.contains("begin inline empty-database baseline: lastleg"));
|
||||
assertTrue(overseas.contains(createToken));
|
||||
assertTrue(overseas.contains("request_hash char(64) null"));
|
||||
assertTrue(overseas.indexOf(createToken) < overseas.indexOf("assert_phase5_lastleg_identifiers_migratable"));
|
||||
assertTrue(overseas.contains("alter table ll_delivery_order add column overseas_warehouse_name"));
|
||||
}
|
||||
|
||||
+64
-5
@@ -91,6 +91,7 @@ class DeliveryTransferServiceTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new DeliveryTransferService();
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new com.fasterxml.jackson.databind.ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "dispatchOrders", dispatchOrders);
|
||||
ReflectionTestUtils.setField(service, "dispatchItems", dispatchItems);
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
@@ -440,6 +441,7 @@ class DeliveryTransferServiceTest {
|
||||
.thenReturn(List.of(new DropshipCarton()));
|
||||
InventoryPosition position = new InventoryPosition();
|
||||
position.setId(900L + i); position.setCustomerId("TENANT-A"); position.setBalanceId(800L + i);
|
||||
position.setOverseasWarehouseId("WH-1");
|
||||
position.setPalletId(501L); position.setPalletNo("DP-SHARED");
|
||||
InventoryBalance balance = new InventoryBalance();
|
||||
balance.setCustomerId("TENANT-A"); balance.setInboundCode("IN-" + i);
|
||||
@@ -460,6 +462,10 @@ class DeliveryTransferServiceTest {
|
||||
|
||||
@Test
|
||||
void oneClickOutboundRejectsSummarySkuWithoutAvailableDropshipCartons() {
|
||||
var generator = org.mockito.Mockito.mock(LastlegOrderNoGenerator.class);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", generator);
|
||||
InventoryPosition position = new InventoryPosition(); position.setCustomerId("TENANT-A"); position.setOverseasWarehouseId("WH-1");
|
||||
when(inventoryPositions.selectById(901L)).thenReturn(position);
|
||||
DropshipOutboundCandidate candidate = dropshipCandidate(901L, 1001L, "WH-1", "PLT-501", "MIXED", 1L);
|
||||
when(dropshipProfiles.lockOutboundCandidate("TENANT-A", 901L, "MIXED")).thenReturn(candidate);
|
||||
when(dropshipCartons.lockAvailable("TENANT-A", 1001L, "MIXED", 1L)).thenReturn(List.of());
|
||||
@@ -470,6 +476,45 @@ class DeliveryTransferServiceTest {
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
assertTrue(exception.getReason().contains("一件代发可用箱数不足"));
|
||||
verify(outboundPallets, never()).insert(any(OutboundPalletItem.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void concurrentOneClickCreateReplaysCurrentCommittedRowBeforeTakingInventoryLocks() {
|
||||
var generator = org.mockito.Mockito.mock(LastlegOrderNoGenerator.class);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", generator);
|
||||
var request = new DeliveryRequests.OneClickOutbound("recipient", List.of(
|
||||
new DeliveryRequests.OneClickItem(901L, 2L, "SKU-1", List.of("A", "B"))), "remark", "race-key");
|
||||
OutboundOrder winner = outbound(301L, null, null, "OUT-WINNER", "race-key");
|
||||
winner.setOutboundType("ONE_CLICK_DROPSHIP"); winner.setRequestHash(service.oneClickRequestHash(request));
|
||||
OutboundPdaTask task = new OutboundPdaTask(); task.setId(701L); task.setTaskNo("PDA-WINNER"); task.setStatus("PENDING");
|
||||
InventoryPosition position = new InventoryPosition(); position.setCustomerId("TENANT-A"); position.setOverseasWarehouseId("WH-1");
|
||||
when(inventoryPositions.selectById(901L)).thenReturn(position);
|
||||
when(outboundOrders.insert(any(OutboundOrder.class))).thenThrow(new org.springframework.dao.DuplicateKeyException("race"));
|
||||
when(outboundOrders.lockByIdempotency("TENANT-A", "race-key")).thenReturn(winner);
|
||||
when(pdaTasks.lockByOutbound("TENANT-A", 301L)).thenReturn(task);
|
||||
assertEquals("PDA-WINNER", service.createOneClick("TENANT-A", "U-1", request).taskNo());
|
||||
verify(dropshipProfiles, never()).lockOutboundCandidate(any(), any(), any());
|
||||
verify(outboundPallets, never()).insert(any(OutboundPalletItem.class));
|
||||
verify(pdaTasks, never()).insert(any(OutboundPdaTask.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneClickReplayRejectsChangedRecipientItemsMarksAndRemark() {
|
||||
var item = new DeliveryRequests.OneClickItem(901L, 2L, "SKU-1", List.of("A", "B"));
|
||||
var request = new DeliveryRequests.OneClickOutbound("recipient", List.of(item), "remark", "key");
|
||||
OutboundOrder existing = outbound(301L, null, null, "OUT-1", "key");
|
||||
existing.setOutboundType("ONE_CLICK_DROPSHIP"); existing.setRequestHash(service.oneClickRequestHash(request));
|
||||
when(outboundOrders.findByIdempotency("TENANT-A", "key")).thenReturn(existing);
|
||||
for (var changed : List.of(
|
||||
new DeliveryRequests.OneClickOutbound("changed", List.of(item), "remark", "key"),
|
||||
new DeliveryRequests.OneClickOutbound("recipient", List.of(item), "changed", "key"),
|
||||
new DeliveryRequests.OneClickOutbound("recipient", List.of(new DeliveryRequests.OneClickItem(902L, 2L, "SKU-1", List.of("A", "B"))), "remark", "key"),
|
||||
new DeliveryRequests.OneClickOutbound("recipient", List.of(new DeliveryRequests.OneClickItem(901L, 2L, "SKU-1", List.of("A", "C"))), "remark", "key"))) {
|
||||
assertEquals(409, assertThrows(ResponseStatusException.class,
|
||||
() -> service.createOneClick("TENANT-A", "U-1", changed)).getStatusCode().value());
|
||||
}
|
||||
verify(inventoryPositions, never()).selectById(any());
|
||||
verify(outboundOrders, never()).insert(any(OutboundOrder.class));
|
||||
}
|
||||
|
||||
@@ -493,7 +538,6 @@ class DeliveryTransferServiceTest {
|
||||
|
||||
@Test
|
||||
void oneClickCreationRejectsOrdinaryInventoryBeforeAnyWrite() {
|
||||
when(dropshipProfiles.lockOutboundCandidate("TENANT-A", 901L, "MIXED")).thenReturn(null);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> service.createOneClick(
|
||||
"TENANT-A", "U-1", new DeliveryRequests.OneClickOutbound("recipient", List.of(
|
||||
@@ -619,6 +663,12 @@ class DeliveryTransferServiceTest {
|
||||
assertEquals("SKU-1", projected.skuCode());
|
||||
assertEquals("IN-A", projected.inboundCode());
|
||||
assertEquals(1L, projected.availablePackages());
|
||||
|
||||
item.setCartonMarks("CTN-B");
|
||||
assertEquals(List.of("CTN-B"), service.pdaTaskDetail("WH-1", "PDA-701").items().get(0).positions()
|
||||
.stream().map(DeliveryResponses.PdaPickPosition::cartonMark).toList());
|
||||
second.setStatus("SHIPPED");
|
||||
assertTrue(service.pdaTaskDetail("WH-1", "PDA-701").items().get(0).positions().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -647,6 +697,7 @@ class DeliveryTransferServiceTest {
|
||||
DeliveryResponses.PdaTaskDetail detail = service.pdaTaskDetail("WH-1", "PDA-701");
|
||||
|
||||
assertEquals("CTN-A", detail.items().get(0).positions().get(0).cartonMark());
|
||||
assertEquals("SKU-1", detail.items().get(0).positions().get(0).skuCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -658,13 +709,15 @@ class DeliveryTransferServiceTest {
|
||||
OutboundPdaTask task = new OutboundPdaTask(); task.setId(701L); task.setCustomerId("TENANT-A");
|
||||
task.setOutboundOrderId(301L); task.setTaskNo("PDA-701"); task.setStatus("PENDING");
|
||||
OutboundPalletItem item = pallet(501L, "DP-1", "ONT8", "Ontario", "IN-A", 2);
|
||||
item.setSkuCode("SKU-1");
|
||||
item.setSkuCode("SKU-1"); item.setCartonMarks("BOX-A,BOX-B");
|
||||
InventoryPosition position = new InventoryPosition(); position.setId(901L); position.setCustomerId("TENANT-A");
|
||||
position.setBalanceId(801L); position.setPalletId(501L); position.setOverseasWarehouseId("WH-1");
|
||||
InventoryBalance balance = new InventoryBalance(); balance.setCustomerId("TENANT-A"); balance.setInboundCode("IN-A");
|
||||
DropshipOrderProfile profile = new DropshipOrderProfile(); profile.setId(1001L);
|
||||
DropshipCarton first = new DropshipCarton(); first.setId(1101L); first.setProductQuantity(7L);
|
||||
DropshipCarton second = new DropshipCarton(); second.setId(1102L); second.setProductQuantity(13L);
|
||||
first.setStatus("AVAILABLE"); first.setSkuCode("SKU-1"); first.setCartonMark("BOX-A");
|
||||
second.setStatus("AVAILABLE"); second.setSkuCode("SKU-1"); second.setCartonMark("BOX-B");
|
||||
when(pdaTasks.lockByNoAndWarehouse("PDA-701", "WH-1")).thenReturn(task);
|
||||
when(outboundOrders.selectById(301L)).thenReturn(outbound);
|
||||
when(outboundOrders.lockByNo("TENANT-A", "OUT-301")).thenReturn(outbound);
|
||||
@@ -672,12 +725,18 @@ class DeliveryTransferServiceTest {
|
||||
when(inventoryPositions.selectById(901L)).thenReturn(position);
|
||||
when(inventoryBalances.selectById(801L)).thenReturn(balance);
|
||||
when(dropshipProfiles.findPosition("TENANT-A", 901L)).thenReturn(profile);
|
||||
when(dropshipCartons.lockAvailable("TENANT-A", 1001L, "SKU-1", 1L)).thenReturn(List.of(first), List.of(second));
|
||||
when(dropshipCartons.findByProfileAndMark("TENANT-A", 1001L, "BOX-A")).thenReturn(first);
|
||||
when(dropshipCartons.findByProfileAndMark("TENANT-A", 1001L, "BOX-B")).thenReturn(second);
|
||||
when(dropshipSkuInventory.deduct("TENANT-A", 1001L, "SKU-1", 1L, 7L)).thenReturn(1);
|
||||
when(dropshipSkuInventory.deduct("TENANT-A", 1001L, "SKU-1", 1L, 13L)).thenReturn(1);
|
||||
when(outboundPallets.findByOutbound("TENANT-A", 301L)).thenReturn(List.of(item));
|
||||
service.pdaPick("WH-1", "U-1", "PDA-701", new DeliveryRequests.PdaPick(item.getId(), 901L, 1L, "pick-7"));
|
||||
service.pdaPick("WH-1", "U-1", "PDA-701", new DeliveryRequests.PdaPick(item.getId(), 901L, 1L, "pick-13"));
|
||||
assertThrows(ResponseStatusException.class, () -> service.pdaPick("WH-1", "U-1", "PDA-701",
|
||||
new DeliveryRequests.PdaPick(item.getId(), 901L, 1L, "wrong-mark", List.of("WRONG-BOX"))));
|
||||
service.pdaPick("WH-1", "U-1", "PDA-701", new DeliveryRequests.PdaPick(item.getId(), 901L, 1L, "pick-7", List.of("BOX-A")));
|
||||
assertEquals(1, item.getPickedPackageCount()); assertEquals("AVAILABLE", second.getStatus());
|
||||
assertThrows(ResponseStatusException.class, () -> service.pdaPick("WH-1", "U-1", "PDA-701",
|
||||
new DeliveryRequests.PdaPick(item.getId(), 901L, 1L, "repeat-box-new-key", List.of("BOX-A"))));
|
||||
service.pdaPick("WH-1", "U-1", "PDA-701", new DeliveryRequests.PdaPick(item.getId(), 901L, 1L, "pick-13", List.of("BOX-B")));
|
||||
verify(dropshipSkuInventory).deduct("TENANT-A", 1001L, "SKU-1", 1L, 7L);
|
||||
verify(dropshipSkuInventory).deduct("TENANT-A", 1001L, "SKU-1", 1L, 13L);
|
||||
assertEquals("SHIPPED", first.getStatus()); assertEquals("SHIPPED", second.getStatus());
|
||||
|
||||
Reference in New Issue
Block a user