fix(inventory): resolve returns from immutable shipment SKU facts
This commit is contained in:
+4
@@ -0,0 +1,4 @@
|
||||
package com.baiyue.lastleg.inventory.command.dto;
|
||||
|
||||
/** SKU and shipped quantity derived only from the selected immutable outbound shipment facts. */
|
||||
public record ShipmentSource(String skuCode, long shippedPackages) { }
|
||||
+7
-5
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.lastleg.inventory.command.mapper;
|
||||
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryBalance;
|
||||
import com.baiyue.lastleg.inventory.command.dto.ShipmentSource;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
@@ -47,11 +48,11 @@ public interface InventoryCommandMapper {
|
||||
"AND NOT EXISTS (SELECT 1 FROM ll_inventory_position p WHERE p.balance_id=ll_inventory_balance.id)")
|
||||
int deleteEmptyBalance(@Param("customerId") String customerId, @Param("balanceId") Long balanceId);
|
||||
|
||||
@Select("SELECT COALESCE(-SUM(l.delta_packages),0) FROM ll_inventory_ledger l " +
|
||||
@Select("SELECT l.sku_code AS skuCode, -SUM(l.delta_packages) AS shippedPackages FROM ll_inventory_ledger l " +
|
||||
"JOIN ll_outbound_order o ON o.customer_id=l.customer_id AND o.outbound_no=#{outboundNo} " +
|
||||
"AND o.overseas_warehouse_id=l.overseas_warehouse_id AND o.status='OUTBOUNDED' " +
|
||||
"WHERE l.customer_id=#{customerId} AND l.position_id=#{positionId} " +
|
||||
"AND l.sku_code=#{skuCode} AND l.business_type='SHIP' AND l.delta_packages<0 " +
|
||||
"AND l.business_type='SHIP' AND l.delta_packages<0 " +
|
||||
"AND ((o.outbound_type='DISPATCH_DELIVERY' AND l.business_order_no=o.dispatch_no " +
|
||||
"AND l.outbound_order_no=o.dispatch_no AND EXISTS (" +
|
||||
"SELECT 1 FROM ll_dispatch_pda_task t JOIN ll_dispatch_pda_pick p " +
|
||||
@@ -62,9 +63,10 @@ public interface InventoryCommandMapper {
|
||||
"WHERE t.customer_id=o.customer_id AND t.dispatch_order_id=o.dispatch_order_id " +
|
||||
"AND t.warehouse_id=o.overseas_warehouse_id AND p.inventory_position_id=l.position_id " +
|
||||
"AND p.packages=-l.delta_packages AND l.idempotency_key=CONCAT(op.idempotency_key,':',op.id))) " +
|
||||
"OR (o.outbound_type<>'DISPATCH_DELIVERY' AND l.outbound_order_no=o.outbound_no))")
|
||||
long shippedPackages(@Param("customerId") String customerId, @Param("positionId") Long positionId,
|
||||
@Param("outboundNo") String outboundNo, @Param("skuCode") String skuCode);
|
||||
"OR (o.outbound_type<>'DISPATCH_DELIVERY' AND l.outbound_order_no=o.outbound_no)) " +
|
||||
"GROUP BY l.sku_code")
|
||||
List<ShipmentSource> shipmentSources(@Param("customerId") String customerId, @Param("positionId") Long positionId,
|
||||
@Param("outboundNo") String outboundNo);
|
||||
|
||||
|
||||
@Select("SELECT COALESCE(SUM(delta_packages),0) FROM ll_inventory_ledger " +
|
||||
|
||||
+20
-5
@@ -294,7 +294,7 @@ public class InventoryCommandService {
|
||||
|| source.maxReturnPackages() <= 0) {
|
||||
throw badRequest("A valid returned inventory source is required");
|
||||
}
|
||||
String sourceSku = sku(source.skuCode());
|
||||
String sourceSku = trim(source.skuCode());
|
||||
ReturnedInventoryRestoreRequest.SourceItem normalized = new ReturnedInventoryRestoreRequest.SourceItem(
|
||||
source.outboundPalletItemId(), source.inventoryPositionId(), sourceSku, source.maxReturnPackages());
|
||||
if (sources.putIfAbsent(source.outboundPalletItemId(), normalized) != null) {
|
||||
@@ -351,6 +351,7 @@ public class InventoryCommandService {
|
||||
}
|
||||
|
||||
Map<Long, Locked> lockedSources = new LinkedHashMap<>();
|
||||
Map<Long, ReturnedInventoryRestoreRequest.SourceItem> resolvedSources = new LinkedHashMap<>();
|
||||
Map<Long, Long> balanceBefore = new LinkedHashMap<>();
|
||||
for (ReturnedInventoryRestoreRequest.SourceItem source : sources.values()) {
|
||||
Locked locked = locked(customerId, source.inventoryPositionId());
|
||||
@@ -361,22 +362,36 @@ public class InventoryCommandService {
|
||||
if (!warehouseId.equals(trim(balance.getOverseasWarehouseId()))) {
|
||||
throw conflict("Returned inventory source warehouse does not match");
|
||||
}
|
||||
long shipped = commands.shippedPackages(customerId, source.inventoryPositionId(), outboundNo,
|
||||
source.skuCode());
|
||||
long restored = commands.restoredPackages(customerId, returnSourceIdentity(source), source.skuCode());
|
||||
var shipmentSources = commands.shipmentSources(customerId, source.inventoryPositionId(), outboundNo);
|
||||
if (shipmentSources == null || shipmentSources.isEmpty()) {
|
||||
throw conflict("Immutable outbound shipment source was not found");
|
||||
}
|
||||
var matchingSources = source.skuCode() == null ? shipmentSources : shipmentSources.stream()
|
||||
.filter(shipment -> source.skuCode().equals(shipment.skuCode())).toList();
|
||||
if (matchingSources.size() != 1) {
|
||||
throw conflict(source.skuCode() == null ? "Immutable outbound shipment SKU source is ambiguous"
|
||||
: "Declared return SKU does not match an immutable shipment source");
|
||||
}
|
||||
var shipment = matchingSources.get(0);
|
||||
String actualSku = required(shipment.skuCode(), "Immutable outbound shipment SKU is missing");
|
||||
var resolved = new ReturnedInventoryRestoreRequest.SourceItem(source.outboundPalletItemId(),
|
||||
source.inventoryPositionId(), actualSku, source.maxReturnPackages());
|
||||
long shipped = shipment.shippedPackages();
|
||||
long restored = commands.restoredPackages(customerId, returnSourceIdentity(resolved), actualSku);
|
||||
long requested = requestedBySource.get(source.outboundPalletItemId());
|
||||
if (shipped <= 0) throw conflict("Immutable outbound shipment source was not found");
|
||||
if (restored + requested > shipped) {
|
||||
throw conflict("Returned packages exceed the remaining shipped packages");
|
||||
}
|
||||
lockedSources.put(source.outboundPalletItemId(), locked);
|
||||
resolvedSources.put(source.outboundPalletItemId(), resolved);
|
||||
balanceBefore.putIfAbsent(balance.getId(), value(balance.getAvailablePackages()));
|
||||
}
|
||||
|
||||
long restoredPackages = 0;
|
||||
List<Long> ledgerIds = new java.util.ArrayList<>();
|
||||
for (ReturnedInventoryRestoreRequest.Receipt receipt : request.receipts()) {
|
||||
ReturnedInventoryRestoreRequest.SourceItem source = sources.get(receipt.outboundPalletItemId());
|
||||
ReturnedInventoryRestoreRequest.SourceItem source = resolvedSources.get(receipt.outboundPalletItemId());
|
||||
Locked sourceLocked = lockedSources.get(receipt.outboundPalletItemId());
|
||||
InventoryBalance balance = sourceLocked.balance();
|
||||
WarehouseResourceGateway.WarehousePalletReference pallet = warehouseGateway.resolvePallet(
|
||||
|
||||
+45
-10
@@ -1,6 +1,9 @@
|
||||
package com.baiyue.lastleg.inventory.command;
|
||||
|
||||
import com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper;
|
||||
import com.baiyue.lastleg.inventory.command.dto.ShipmentSource;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -28,18 +31,26 @@ class DispatchShipmentSourceQueryTest {
|
||||
try (var statement = connection.createStatement()) { statement.execute(sql); }
|
||||
}
|
||||
|
||||
private long shipped(Connection connection, String customerId, long positionId, String outboundNo, String sku) throws Exception {
|
||||
String sql = String.join(" ", InventoryCommandMapper.class.getMethod("shippedPackages",
|
||||
String.class, Long.class, String.class, String.class).getAnnotation(Select.class).value())
|
||||
.replace("#{outboundNo}", "?").replace("#{customerId}", "?")
|
||||
.replace("#{positionId}", "?").replace("#{skuCode}", "?");
|
||||
private List<ShipmentSource> sources(Connection connection, String customerId, long positionId, String outboundNo) throws Exception {
|
||||
String sql = String.join(" ", InventoryCommandMapper.class.getMethod("shipmentSources",
|
||||
String.class, Long.class, String.class).getAnnotation(Select.class).value())
|
||||
.replace("#{outboundNo}", "?").replace("#{customerId}", "?").replace("#{positionId}", "?");
|
||||
try (var statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, outboundNo); statement.setString(2, customerId);
|
||||
statement.setLong(3, positionId); statement.setString(4, sku);
|
||||
try (var result = statement.executeQuery()) { result.next(); return result.getLong(1); }
|
||||
statement.setLong(3, positionId);
|
||||
try (var result = statement.executeQuery()) {
|
||||
List<ShipmentSource> rows = new ArrayList<>();
|
||||
while (result.next()) rows.add(new ShipmentSource(result.getString("skuCode"), result.getLong("shippedPackages")));
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long shipped(Connection connection, String customerId, long positionId, String outboundNo, String sku) throws Exception {
|
||||
return sources(connection, customerId, positionId, outboundNo).stream()
|
||||
.filter(source -> sku.equals(source.skuCode())).mapToLong(ShipmentSource::shippedPackages).sum();
|
||||
}
|
||||
|
||||
private void scan(Connection connection) throws Exception {
|
||||
execute(connection, "INSERT INTO ll_inventory_operation VALUES (71,'100','SHIP','COMPLETED','DISPATCH:DS-1:31:pick-1:SHIP')");
|
||||
execute(connection, "INSERT INTO ll_inventory_ledger VALUES (51,'100',41,'WH-1','SKU-1','SHIP',-8,'DS-1','DS-1','DISPATCH:DS-1:31:pick-1:SHIP:71')");
|
||||
@@ -104,16 +115,40 @@ class DispatchShipmentSourceQueryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void ordinaryFbaUnspecifiedSkuMatchesExactlyAndWrongSkuNeverFallsBack() throws Exception {
|
||||
void resolvesOnlyActualImmutableSkuFactsAndGroupsQuantitiesForTheExactSource() throws Exception {
|
||||
try (Connection connection = database()) {
|
||||
scan(connection); transfer(connection);
|
||||
execute(connection, "UPDATE ll_inventory_ledger SET sku_code='UNSPECIFIED'");
|
||||
assertEquals(8, shipped(connection, "100", 41, "OB-1", "UNSPECIFIED"));
|
||||
assertEquals(List.of(new ShipmentSource("SKU-1", 8L)), sources(connection, "100", 41, "OB-1"));
|
||||
execute(connection, "INSERT INTO ll_dispatch_pda_pick VALUES (22,'100',11,32,41,3,'pick-2')");
|
||||
execute(connection, "INSERT INTO ll_inventory_operation VALUES (72,'100','SHIP','COMPLETED','DISPATCH:DS-1:32:pick-2:SHIP')");
|
||||
execute(connection, "INSERT INTO ll_inventory_ledger VALUES (52,'100',41,'WH-1','SKU-1','SHIP',-3,'DS-1','DS-1','DISPATCH:DS-1:32:pick-2:SHIP:72')");
|
||||
assertEquals(List.of(new ShipmentSource("SKU-1", 11L)), sources(connection, "100", 41, "OB-1"));
|
||||
execute(connection, "UPDATE ll_inventory_ledger SET sku_code='MIXED' WHERE id=52");
|
||||
assertEquals(2, sources(connection, "100", 41, "OB-1").size());
|
||||
assertEquals(8, shipped(connection, "100", 41, "OB-1", "SKU-1"));
|
||||
assertEquals(3, shipped(connection, "100", 41, "OB-1", "MIXED"));
|
||||
assertEquals(0, shipped(connection, "100", 41, "OB-1", ""));
|
||||
assertEquals(0, shipped(connection, "100", 41, "OB-1", "UNSPECIFIED"));
|
||||
assertEquals(0, shipped(connection, "100", 41, "OB-1", "WRONG-SKU"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void productionMyBatisMapperMaterializesTheCanonicalShipmentSkuAndQuantity() throws Exception {
|
||||
try (Connection connection = database()) {
|
||||
scan(connection); transfer(connection);
|
||||
var configuration = new org.apache.ibatis.session.Configuration(new org.apache.ibatis.mapping.Environment(
|
||||
"source-sql", new org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory(),
|
||||
new org.apache.ibatis.datasource.unpooled.UnpooledDataSource("org.h2.Driver",
|
||||
connection.getMetaData().getURL(), "", "")));
|
||||
configuration.addMapper(InventoryCommandMapper.class);
|
||||
try (var session = new org.apache.ibatis.session.SqlSessionFactoryBuilder().build(configuration).openSession(connection)) {
|
||||
assertEquals(List.of(new ShipmentSource("SKU-1", 8L)),
|
||||
session.getMapper(InventoryCommandMapper.class).shipmentSources("100", 41L, "OB-1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shipmentLedgerRequiresTheActualCompletedOperationIdentityRatherThanAPrefix() throws Exception {
|
||||
try (Connection connection = database()) {
|
||||
|
||||
+43
-21
@@ -4,6 +4,7 @@ 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.dto.ShipmentSource;
|
||||
import com.baiyue.lastleg.inventory.command.mapper.InventoryCommandMapper;
|
||||
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryBalance;
|
||||
@@ -20,8 +21,8 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.NullAndEmptySource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
@@ -361,7 +362,7 @@ class InventoryCommandServiceTest {
|
||||
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.shipmentSources("100", 201L, "OUT-1")).thenReturn(List.of(new ShipmentSource("SKU-1", 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));
|
||||
@@ -393,7 +394,7 @@ class InventoryCommandServiceTest {
|
||||
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.shipmentSources("100", 201L, "OUT-1")).thenReturn(List.of(new ShipmentSource("SKU-1", 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));
|
||||
@@ -413,15 +414,14 @@ class InventoryCommandServiceTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NullAndEmptySource
|
||||
@ValueSource(strings = {" "})
|
||||
void ordinaryFbaReturnUsesTheStockInSkuNormalizationAndReplaysOnlyOnce(String sourceSku) {
|
||||
@MethodSource("missingSkuSources")
|
||||
void missingFbaSummarySkuUsesUniqueImmutableShipmentSkuAndReplaysOnlyOnce(String sourceSku, String actualSku) {
|
||||
InventoryBalance balance = balance(200L, 0);
|
||||
balance.setOverseasWarehouseId("900");
|
||||
balance.setSkuCode("UNSPECIFIED");
|
||||
balance.setSkuCode("MIXED");
|
||||
when(commands.lockPosition("100", 201L)).thenReturn(position(201L, 200L, 0));
|
||||
when(commands.lockBalance("100", 200L)).thenReturn(balance);
|
||||
when(commands.shippedPackages("100", 201L, "OUT-1", "UNSPECIFIED")).thenReturn(8L);
|
||||
when(commands.shipmentSources("100", 201L, "OUT-1")).thenReturn(List.of(new ShipmentSource(actualSku, 8L)));
|
||||
when(warehouseGateway.resolvePallet(400L)).thenReturn(new WarehouseResourceGateway.WarehousePalletReference(
|
||||
400L, "PLT-RETURN", 900L, 500L, "IDLE", null, 0));
|
||||
when(warehouseGateway.resolveLocation(900L, 500L)).thenReturn(
|
||||
@@ -438,31 +438,51 @@ class InventoryCommandServiceTest {
|
||||
verify(operations).insert(operation.capture());
|
||||
when(operations.findIdempotency("100", "return-1")).thenReturn(operation.getValue());
|
||||
assertTrue(service.restoreReturnedInventory("100", "7", request).replayed());
|
||||
verify(commands).shippedPackages("100", 201L, "OUT-1", "UNSPECIFIED");
|
||||
verify(commands).restoredPackages("100", "RETURN_SOURCE:301:201", "UNSPECIFIED");
|
||||
verify(commands).shipmentSources("100", 201L, "OUT-1");
|
||||
verify(commands).restoredPackages("100", "RETURN_SOURCE:301:201", actualSku);
|
||||
ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(ledgers, times(1)).insert(ledger.capture());
|
||||
assertEquals("UNSPECIFIED", ledger.getValue().getSkuCode());
|
||||
assertEquals(actualSku, ledger.getValue().getSkuCode());
|
||||
assertEquals(sourceSku, request.sources().get(0).skuCode());
|
||||
assertEquals("MIXED", balance.getSkuCode());
|
||||
verify(positions, times(1)).insert(any(InventoryPosition.class));
|
||||
verify(balances, times(1)).updateById(any(InventoryBalance.class));
|
||||
}
|
||||
|
||||
private static java.util.stream.Stream<Arguments> missingSkuSources() {
|
||||
return java.util.stream.Stream.of("SKU-1", "MIXED", "UNSPECIFIED").flatMap(actualSku ->
|
||||
java.util.stream.Stream.of(null, "", " ").map(sourceSku -> Arguments.of(sourceSku, actualSku)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonEmptyWrongReturnSkuIsRejectedWithoutSearchingAnotherSkuOrWritingInventory() {
|
||||
void nonEmptyWrongReturnSkuIsRejectedWithoutSearchingAnotherSourceOrWritingInventory() {
|
||||
rejectReturnSku("WRONG-SKU", List.of(new ShipmentSource("SKU-1", 8L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingSummarySkuWithMultipleImmutableSkuGroupsIsRejectedWithoutInventoryMutation() {
|
||||
rejectReturnSku(null, List.of(new ShipmentSource("SKU-1", 8L), new ShipmentSource("MIXED", 3L)));
|
||||
}
|
||||
|
||||
private void rejectReturnSku(String sourceSku, List<ShipmentSource> shipments) {
|
||||
InventoryBalance balance = balance(200L, 0);
|
||||
balance.setOverseasWarehouseId("900");
|
||||
balance.setSkuCode("UNSPECIFIED");
|
||||
balance.setSkuCode("MIXED");
|
||||
when(commands.lockPosition("100", 201L)).thenReturn(position(201L, 200L, 0));
|
||||
when(commands.lockBalance("100", 200L)).thenReturn(balance);
|
||||
when(commands.shipmentSources("100", 201L, "OUT-1")).thenReturn(shipments);
|
||||
ReturnedInventoryRestoreRequest base = returnRequest(1);
|
||||
ReturnedInventoryRestoreRequest request = new ReturnedInventoryRestoreRequest(base.exceptionNo(), base.outboundNo(),
|
||||
base.overseasWarehouseId(), base.inboundOrderId(),
|
||||
List.of(new ReturnedInventoryRestoreRequest.SourceItem(301L, 201L, "WRONG-SKU", 5)),
|
||||
List.of(new ReturnedInventoryRestoreRequest.SourceItem(301L, 201L, sourceSku, 5)),
|
||||
base.receipts(), base.idempotencyKey(), base.evidenceFileIds());
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.restoreReturnedInventory("100", "7", request));
|
||||
verify(commands).shippedPackages("100", 201L, "OUT-1", "WRONG-SKU");
|
||||
verify(commands, never()).shippedPackages("100", 201L, "OUT-1", "UNSPECIFIED");
|
||||
ResponseStatusException failure = assertThrows(ResponseStatusException.class,
|
||||
() -> service.restoreReturnedInventory("100", "7", request));
|
||||
assertEquals(sourceSku == null ? "Immutable outbound shipment SKU source is ambiguous"
|
||||
: "Declared return SKU does not match an immutable shipment source", failure.getReason());
|
||||
verify(commands).shipmentSources("100", 201L, "OUT-1");
|
||||
verify(commands, never()).restoredPackages(any(), any(), any());
|
||||
verify(positions, never()).insert(any(InventoryPosition.class));
|
||||
verify(positions, never()).updateById(any(InventoryPosition.class));
|
||||
verify(balances, never()).updateById(any(InventoryBalance.class));
|
||||
@@ -475,7 +495,7 @@ class InventoryCommandServiceTest {
|
||||
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.shipmentSources("100", 201L, "OUT-1")).thenReturn(List.of(new ShipmentSource("SKU-1", 4L)));
|
||||
when(commands.restoredPackages("100", "RETURN_SOURCE:301:201", "SKU-1")).thenReturn(2L);
|
||||
|
||||
assertThrows(ResponseStatusException.class,
|
||||
@@ -489,14 +509,16 @@ class InventoryCommandServiceTest {
|
||||
|
||||
@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());
|
||||
String shippedSql = String.join(" ", InventoryCommandMapper.class.getMethod("shipmentSources",
|
||||
String.class, Long.class, String.class).getAnnotation(Select.class).value());
|
||||
assertTrue(shippedSql.contains("position_id=#{positionId}"));
|
||||
assertTrue(shippedSql.contains("o.outbound_no=#{outboundNo}"));
|
||||
assertTrue(shippedSql.contains("p.inventory_position_id=l.position_id"));
|
||||
assertTrue(shippedSql.contains("p.idempotency_key,':SHIP'"));
|
||||
assertTrue(shippedSql.contains("l.idempotency_key=CONCAT(op.idempotency_key,':',op.id)"));
|
||||
assertTrue(shippedSql.contains("business_type='SHIP'"));
|
||||
assertTrue(shippedSql.contains("GROUP BY l.sku_code"));
|
||||
assertFalse(shippedSql.contains("#{skuCode}"));
|
||||
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}"));
|
||||
|
||||
Reference in New Issue
Block a user