fix(lastleg): restore returned inventory from shipment source
This commit is contained in:
+103
-1
@@ -2,6 +2,8 @@ package com.baiyue.lastleg.inventory.command;
|
||||
|
||||
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests;
|
||||
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandResponse;
|
||||
import com.baiyue.lastleg.inventory.command.dto.ReturnedInventoryRestoreRequest;
|
||||
import com.baiyue.lastleg.inventory.command.dto.ReturnedInventoryRestoreResponse;
|
||||
import com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper;
|
||||
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryBalance;
|
||||
@@ -17,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@@ -57,7 +60,7 @@ class InventoryCommandServiceTest {
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
ReflectionTestUtils.setField(service, "commands", commands);
|
||||
ReflectionTestUtils.setField(service, "warehouseGateway", warehouseGateway);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
lenient().when(operations.findIdempotency(any(), any())).thenReturn(null);
|
||||
lenient().when(commands.lockInboundTransitBalances(any(), any(), any())).thenReturn(List.of());
|
||||
}
|
||||
@@ -329,6 +332,97 @@ class InventoryCommandServiceTest {
|
||||
verify(operations).findIdempotency("200", "platform-hold-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void restoresReturnedInventoryFromImmutableShipmentSourceIntoVerifiedTarget() throws Exception {
|
||||
assertTrue(InventoryCommandService.class.getMethod("restoreReturnedInventory", String.class, String.class,
|
||||
ReturnedInventoryRestoreRequest.class).getAnnotation(Transactional.class) != null);
|
||||
InventoryBalance balance = balance(200L, 2);
|
||||
balance.setOverseasWarehouseId("900");
|
||||
InventoryPosition source = position(201L, 200L, 0);
|
||||
when(commands.lockPosition("100", 201L)).thenReturn(source);
|
||||
when(commands.lockBalance("100", 200L)).thenReturn(balance);
|
||||
when(commands.shippedPackages("100", 201L, "OUT-1", "SKU-1")).thenReturn(8L);
|
||||
when(commands.restoredPackages("100", "RETURN_SOURCE:301:201", "SKU-1")).thenReturn(1L);
|
||||
when(warehouseGateway.resolvePallet(400L)).thenReturn(new WarehouseResourceGateway.WarehousePalletReference(
|
||||
400L, "PLT-RETURN", 900L, 500L, "IDLE", null, 0));
|
||||
when(warehouseGateway.resolveLocation(900L, 500L)).thenReturn(
|
||||
new WarehouseResourceGateway.WarehouseLocationReference(500L, "LOC-RETURN", "STORAGE", null, "ACTIVE"));
|
||||
|
||||
ReturnedInventoryRestoreResponse result = service.restoreReturnedInventory("100", "7", returnRequest(3));
|
||||
|
||||
assertEquals(2, result.beforePackages());
|
||||
assertEquals(3, result.restoredPackages());
|
||||
assertEquals(5, result.afterPackages());
|
||||
assertFalse(result.replayed());
|
||||
ArgumentCaptor<InventoryPosition> target = ArgumentCaptor.forClass(InventoryPosition.class);
|
||||
verify(positions).insert(target.capture());
|
||||
assertEquals("RETURN:EX-1", target.getValue().getBatchNo());
|
||||
assertEquals("LOC-RETURN", target.getValue().getWarehouseLocationCode());
|
||||
assertEquals(3L, target.getValue().getAvailablePackages());
|
||||
ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(ledgers).insert(ledger.capture());
|
||||
assertEquals("RETURN_STOCK_IN", ledger.getValue().getBusinessType());
|
||||
assertEquals("RETURN_SOURCE:301:201", ledger.getValue().getSourceEventId());
|
||||
assertEquals("OUT-1", ledger.getValue().getOutboundOrderNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnedInventoryReplayDoesNotMutateInventoryTwice() {
|
||||
InventoryBalance balance = balance(200L, 0);
|
||||
balance.setOverseasWarehouseId("900");
|
||||
InventoryPosition source = position(201L, 200L, 0);
|
||||
when(commands.lockPosition("100", 201L)).thenReturn(source);
|
||||
when(commands.lockBalance("100", 200L)).thenReturn(balance);
|
||||
when(commands.shippedPackages("100", 201L, "OUT-1", "SKU-1")).thenReturn(8L);
|
||||
when(commands.restoredPackages("100", "RETURN_SOURCE:301:201", "SKU-1")).thenReturn(0L);
|
||||
when(warehouseGateway.resolvePallet(400L)).thenReturn(new WarehouseResourceGateway.WarehousePalletReference(
|
||||
400L, "PLT-RETURN", 900L, 500L, "IDLE", null, 0));
|
||||
when(warehouseGateway.resolveLocation(900L, 500L)).thenReturn(
|
||||
new WarehouseResourceGateway.WarehouseLocationReference(500L, "LOC-RETURN", "STORAGE", null, "ACTIVE"));
|
||||
ReturnedInventoryRestoreRequest request = returnRequest(3);
|
||||
service.restoreReturnedInventory("100", "7", request);
|
||||
ArgumentCaptor<InventoryOperation> operation = ArgumentCaptor.forClass(InventoryOperation.class);
|
||||
verify(operations).insert(operation.capture());
|
||||
when(operations.findIdempotency("100", "return-1")).thenReturn(operation.getValue());
|
||||
|
||||
ReturnedInventoryRestoreResponse replay = service.restoreReturnedInventory("100", "7", request);
|
||||
|
||||
assertTrue(replay.replayed());
|
||||
verify(positions, times(1)).insert(any(InventoryPosition.class));
|
||||
verify(ledgers, times(1)).insert(any(InventoryLedger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsReturnBeyondRemainingImmutableShipmentWithoutInventoryMutation() {
|
||||
InventoryBalance balance = balance(200L, 0);
|
||||
balance.setOverseasWarehouseId("900");
|
||||
when(commands.lockPosition("100", 201L)).thenReturn(position(201L, 200L, 0));
|
||||
when(commands.lockBalance("100", 200L)).thenReturn(balance);
|
||||
when(commands.shippedPackages("100", 201L, "OUT-1", "SKU-1")).thenReturn(4L);
|
||||
when(commands.restoredPackages("100", "RETURN_SOURCE:301:201", "SKU-1")).thenReturn(2L);
|
||||
|
||||
assertThrows(ResponseStatusException.class,
|
||||
() -> service.restoreReturnedInventory("100", "7", returnRequest(3)));
|
||||
|
||||
verify(positions, never()).insert(any(InventoryPosition.class));
|
||||
verify(positions, never()).updateById(any(InventoryPosition.class));
|
||||
verify(balances, never()).updateById(any(InventoryBalance.class));
|
||||
verify(ledgers, never()).insert(any(InventoryLedger.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnRestoreSqlIsBoundToImmutableShipLedgerAndSourceIdentity() throws Exception {
|
||||
String shippedSql = String.join(" ", InventoryCommandMapper.class.getMethod("shippedPackages",
|
||||
String.class, Long.class, String.class, String.class).getAnnotation(Select.class).value());
|
||||
assertTrue(shippedSql.contains("position_id=#{positionId}"));
|
||||
assertTrue(shippedSql.contains("outbound_order_no=#{outboundNo}"));
|
||||
assertTrue(shippedSql.contains("business_type='SHIP'"));
|
||||
String restoredSql = String.join(" ", InventoryCommandMapper.class.getMethod("restoredPackages",
|
||||
String.class, String.class, String.class).getAnnotation(Select.class).value());
|
||||
assertTrue(restoredSql.contains("source_event_id=#{sourceIdentity}"));
|
||||
assertTrue(restoredSql.contains("business_type='RETURN_STOCK_IN'"));
|
||||
}
|
||||
|
||||
private InventoryCommandRequests.StockIn stockIn(String inbound, String key, long packages, long quantity) {
|
||||
return new InventoryCommandRequests.StockIn("100", "Customer 100", "WH-1", "WH1", "Warehouse 1",
|
||||
"DEST-1", "ONT8", "ONT8", 101L, inbound, "IN-CODE-1", "SKU-1", "Product 1", null,
|
||||
@@ -342,6 +436,14 @@ class InventoryCommandServiceTest {
|
||||
packages, "ORD-1", "EVT-1", key, null);
|
||||
}
|
||||
|
||||
private ReturnedInventoryRestoreRequest returnRequest(long packages) {
|
||||
return new ReturnedInventoryRestoreRequest("EX-1", "OUT-1", "900", 101L,
|
||||
List.of(new ReturnedInventoryRestoreRequest.SourceItem(301L, 201L, "SKU-1", 5)),
|
||||
List.of(new ReturnedInventoryRestoreRequest.Receipt(301L, 201L, packages,
|
||||
400L, "PLT-RETURN", 500L, "LOC-RETURN")),
|
||||
"return-1", List.of("FILE-1"));
|
||||
}
|
||||
|
||||
private InventoryBalance balance(Long id, long available) {
|
||||
InventoryBalance value = new InventoryBalance();
|
||||
value.setId(id); value.setCustomerId("100"); value.setCustomerName("Customer 100");
|
||||
|
||||
Reference in New Issue
Block a user