fix(lastleg): restore returned inventory from shipment source
This commit is contained in:
+231
-1
@@ -3,6 +3,8 @@ package com.baiyue.lastleg.inventory.command.service;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
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.entity.InventoryBalance;
|
||||
import com.baiyue.lastleg.inventory.entity.InventoryLedger;
|
||||
@@ -27,7 +29,9 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@@ -245,6 +249,175 @@ public class InventoryCommandService {
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ReturnedInventoryRestoreResponse restoreReturnedInventory(String customerId, String operatorId,
|
||||
ReturnedInventoryRestoreRequest request) {
|
||||
if (request == null) throw badRequest("Returned inventory restore request is required");
|
||||
String exceptionNo = required(request.exceptionNo(), "Exception number is required");
|
||||
String outboundNo = required(request.outboundNo(), "Outbound number is required");
|
||||
String warehouseId = required(request.overseasWarehouseId(), "Overseas warehouse ID is required");
|
||||
long warehouseResourceId;
|
||||
try {
|
||||
warehouseResourceId = Long.parseLong(warehouseId);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw badRequest("Overseas warehouse ID must be numeric");
|
||||
}
|
||||
if (request.inboundOrderId() == null || request.inboundOrderId() <= 0) {
|
||||
throw badRequest("Inbound order ID is required");
|
||||
}
|
||||
if (request.sources() == null || request.sources().isEmpty()) {
|
||||
throw badRequest("Returned inventory sources are required");
|
||||
}
|
||||
if (request.receipts() == null || request.receipts().isEmpty()) {
|
||||
throw badRequest("Returned inventory receipts are required");
|
||||
}
|
||||
if (request.evidenceFileIds() == null || request.evidenceFileIds().isEmpty()
|
||||
|| request.evidenceFileIds().stream().anyMatch(value -> trim(value) == null)) {
|
||||
throw badRequest("Return evidence file IDs are required");
|
||||
}
|
||||
|
||||
Map<Long, ReturnedInventoryRestoreRequest.SourceItem> sources = new LinkedHashMap<>();
|
||||
for (ReturnedInventoryRestoreRequest.SourceItem source : request.sources()) {
|
||||
if (source == null || source.outboundPalletItemId() == null || source.outboundPalletItemId() <= 0
|
||||
|| source.inventoryPositionId() == null || source.inventoryPositionId() <= 0
|
||||
|| source.maxReturnPackages() <= 0) {
|
||||
throw badRequest("A valid returned inventory source is required");
|
||||
}
|
||||
String sourceSku = required(source.skuCode(), "Returned inventory source SKU is required");
|
||||
ReturnedInventoryRestoreRequest.SourceItem normalized = new ReturnedInventoryRestoreRequest.SourceItem(
|
||||
source.outboundPalletItemId(), source.inventoryPositionId(), sourceSku, source.maxReturnPackages());
|
||||
if (sources.putIfAbsent(source.outboundPalletItemId(), normalized) != null) {
|
||||
throw badRequest("Returned inventory source must be unique");
|
||||
}
|
||||
}
|
||||
Map<Long, Long> requestedBySource = new LinkedHashMap<>();
|
||||
Map<String, Boolean> receiptTargets = new LinkedHashMap<>();
|
||||
for (ReturnedInventoryRestoreRequest.Receipt receipt : request.receipts()) {
|
||||
ReturnedInventoryRestoreRequest.SourceItem source = receipt == null ? null
|
||||
: sources.get(receipt.outboundPalletItemId());
|
||||
if (source == null || !Objects.equals(source.inventoryPositionId(), receipt.inventoryPositionId())) {
|
||||
throw badRequest("Return receipt must match an immutable outbound inventory source");
|
||||
}
|
||||
if (receipt.receivedPackages() <= 0 || receipt.targetPalletId() == null || receipt.targetPalletId() <= 0
|
||||
|| receipt.targetLocationId() == null || receipt.targetLocationId() <= 0) {
|
||||
throw badRequest("A valid returned inventory receipt is required");
|
||||
}
|
||||
required(receipt.targetPalletNo(), "Target pallet number is required");
|
||||
required(receipt.targetLocationCode(), "Target location code is required");
|
||||
String receiptTarget = receipt.outboundPalletItemId() + ":" + receipt.targetPalletId();
|
||||
if (receiptTargets.putIfAbsent(receiptTarget, Boolean.TRUE) != null) {
|
||||
throw badRequest("Return receipt source and target pallet must be unique");
|
||||
}
|
||||
requestedBySource.merge(source.outboundPalletItemId(), receipt.receivedPackages(), Long::sum);
|
||||
}
|
||||
for (ReturnedInventoryRestoreRequest.SourceItem source : sources.values()) {
|
||||
long requested = requestedBySource.getOrDefault(source.outboundPalletItemId(), 0L);
|
||||
if (requested <= 0 || requested > source.maxReturnPackages()) {
|
||||
throw conflict("Returned packages exceed the approved source limit");
|
||||
}
|
||||
}
|
||||
|
||||
String normalizedKey = required(request.idempotencyKey(), "Idempotency key is required");
|
||||
String requestHash = hash(request);
|
||||
InventoryOperation existing = operations.findIdempotency(customerId, normalizedKey);
|
||||
if (existing != null) return replayReturned(existing, requestHash);
|
||||
|
||||
InventoryOperation operation = new InventoryOperation();
|
||||
operation.setId(IdWorker.getId());
|
||||
operation.setCustomerId(customerId);
|
||||
operation.setIdempotencyKey(normalizedKey);
|
||||
operation.setOperationType("RETURN_STOCK_IN");
|
||||
operation.setRequestHash(requestHash);
|
||||
operation.setStatus("PROCESSING");
|
||||
operation.setCreatedAt(LocalDateTime.now());
|
||||
operation.setUpdatedAt(operation.getCreatedAt());
|
||||
try {
|
||||
operations.insert(operation);
|
||||
} catch (DuplicateKeyException exception) {
|
||||
InventoryOperation concurrent = operations.findIdempotency(customerId, normalizedKey);
|
||||
if (concurrent != null) return replayReturned(concurrent, requestHash);
|
||||
throw exception;
|
||||
}
|
||||
|
||||
Map<Long, Locked> lockedSources = new LinkedHashMap<>();
|
||||
Map<Long, Long> balanceBefore = new LinkedHashMap<>();
|
||||
for (ReturnedInventoryRestoreRequest.SourceItem source : sources.values()) {
|
||||
Locked locked = locked(customerId, source.inventoryPositionId());
|
||||
InventoryBalance balance = locked.balance();
|
||||
if (!Objects.equals(balance.getInboundOrderId(), request.inboundOrderId())) {
|
||||
throw conflict("Returned inventory source inbound order does not match");
|
||||
}
|
||||
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());
|
||||
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);
|
||||
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());
|
||||
Locked sourceLocked = lockedSources.get(receipt.outboundPalletItemId());
|
||||
InventoryBalance balance = sourceLocked.balance();
|
||||
WarehouseResourceGateway.WarehousePalletReference pallet = warehouseGateway.resolvePallet(
|
||||
receipt.targetPalletId());
|
||||
if (pallet == null || pallet.palletId() != receipt.targetPalletId()
|
||||
|| pallet.warehouseId() != warehouseResourceId
|
||||
|| pallet.locationId() != receipt.targetLocationId()
|
||||
|| !required(receipt.targetPalletNo(), "Target pallet number is required").equals(pallet.palletNo())) {
|
||||
throw conflict("Target pallet does not match the returned inventory warehouse and location");
|
||||
}
|
||||
WarehouseResourceGateway.WarehouseLocationReference location = warehouseGateway.resolveLocation(
|
||||
warehouseResourceId, receipt.targetLocationId());
|
||||
if (location == null || location.locationId() != receipt.targetLocationId()
|
||||
|| !required(receipt.targetLocationCode(), "Target location code is required")
|
||||
.equals(location.locationCode())) {
|
||||
throw conflict("Target location does not match the returned inventory warehouse");
|
||||
}
|
||||
|
||||
InventoryPosition target = returnedPosition(customerId, balance, exceptionNo, receipt, location);
|
||||
long before = value(target.getAvailablePackages());
|
||||
target.setAvailablePackages(before + receipt.receivedPackages());
|
||||
target.setStatus("OCCUPIED");
|
||||
target.setVersion(value(target.getVersion()) + 1);
|
||||
target.setUpdatedAt(LocalDateTime.now());
|
||||
positions.updateById(target);
|
||||
|
||||
balance.setAvailablePackages(value(balance.getAvailablePackages()) + receipt.receivedPackages());
|
||||
touch(balance);
|
||||
balances.updateById(balance);
|
||||
InventoryLedger ledger = ledger(customerId, operatorId, operation.getId(), balance, target,
|
||||
source.skuCode(), "RETURN_STOCK_IN", "SHIPPED", "AVAILABLE", before,
|
||||
receipt.receivedPackages(), before + receipt.receivedPackages(), exceptionNo,
|
||||
balance.getInboundOrderNo(), outboundNo, returnSourceIdentity(source),
|
||||
normalizedKey + ":" + receipt.outboundPalletItemId() + ":" + target.getId(),
|
||||
"Exception return received with evidence " + request.evidenceFileIds().size());
|
||||
ledgerIds.add(ledger.getId());
|
||||
restoredPackages += receipt.receivedPackages();
|
||||
}
|
||||
long beforePackages = balanceBefore.values().stream().mapToLong(Long::longValue).sum();
|
||||
long afterPackages = balanceBefore.keySet().stream()
|
||||
.mapToLong(balanceId -> value(lockedSources.values().stream()
|
||||
.map(Locked::balance).filter(balance -> balance.getId().equals(balanceId)).findFirst().orElseThrow()
|
||||
.getAvailablePackages())).sum();
|
||||
ReturnedInventoryRestoreResponse response = new ReturnedInventoryRestoreResponse(operation.getId(),
|
||||
List.copyOf(ledgerIds), beforePackages, restoredPackages, afterPackages, false, LocalDateTime.now());
|
||||
operation.setStatus("COMPLETED");
|
||||
operation.setResultJson(write(response));
|
||||
operation.setUpdatedAt(response.completedAt());
|
||||
operations.updateById(operation);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InventoryCommandResponse adjust(String customerId, String operatorId,
|
||||
InventoryCommandRequests.Adjustment request) {
|
||||
@@ -483,7 +656,63 @@ public class InventoryCommandService {
|
||||
}
|
||||
}
|
||||
|
||||
private void ledger(String customerId, String operatorId, Long operationId, InventoryBalance balance,
|
||||
private InventoryPosition returnedPosition(String customerId, InventoryBalance balance, String exceptionNo,
|
||||
ReturnedInventoryRestoreRequest.Receipt receipt,
|
||||
WarehouseResourceGateway.WarehouseLocationReference location) {
|
||||
String batchNo = "RETURN:" + exceptionNo;
|
||||
InventoryPosition position = positions.lockPosition(customerId, balance.getId(), receipt.targetPalletId(), batchNo);
|
||||
if (position != null) {
|
||||
if (!Objects.equals(position.getWarehouseLocationId(), location.locationId())
|
||||
|| !Objects.equals(position.getWarehouseLocationCode(), location.locationCode())
|
||||
|| !Objects.equals(position.getPalletNo(), receipt.targetPalletNo())) {
|
||||
throw conflict("Returned inventory target position snapshot does not match");
|
||||
}
|
||||
return position;
|
||||
}
|
||||
position = new InventoryPosition();
|
||||
position.setId(IdWorker.getId());
|
||||
position.setCustomerId(customerId);
|
||||
position.setBalanceId(balance.getId());
|
||||
position.setOverseasWarehouseId(balance.getOverseasWarehouseId());
|
||||
position.setDestinationWarehouseCode(balance.getDestinationWarehouseCode());
|
||||
position.setPalletId(receipt.targetPalletId());
|
||||
position.setPalletNo(receipt.targetPalletNo().trim());
|
||||
position.setBatchNo(batchNo);
|
||||
position.setWarehouseLocationId(location.locationId());
|
||||
position.setWarehouseLocationCode(location.locationCode());
|
||||
position.setAvailablePackages(0L);
|
||||
position.setHoldPackages(0L);
|
||||
position.setAllocatedPackages(0L);
|
||||
position.setPickedPackages(0L);
|
||||
position.setReceivingPackages(0L);
|
||||
position.setStatus("OCCUPIED");
|
||||
position.setVersion(0L);
|
||||
position.setCreatedAt(LocalDateTime.now());
|
||||
position.setUpdatedAt(position.getCreatedAt());
|
||||
positions.insert(position);
|
||||
return position;
|
||||
}
|
||||
|
||||
private ReturnedInventoryRestoreResponse replayReturned(InventoryOperation operation, String requestHash) {
|
||||
if (!"RETURN_STOCK_IN".equals(operation.getOperationType())
|
||||
|| !requestHash.equals(operation.getRequestHash())) {
|
||||
throw conflict("Idempotency key was already used with a different inventory request");
|
||||
}
|
||||
if (!"COMPLETED".equals(operation.getStatus()) || trim(operation.getResultJson()) == null) {
|
||||
throw conflict("Inventory operation is still processing");
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(operation.getResultJson(), ReturnedInventoryRestoreResponse.class).asReplay();
|
||||
} catch (JsonProcessingException exception) {
|
||||
throw new IllegalStateException("Cannot read returned inventory idempotency result", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static String returnSourceIdentity(ReturnedInventoryRestoreRequest.SourceItem source) {
|
||||
return "RETURN_SOURCE:" + source.outboundPalletItemId() + ":" + source.inventoryPositionId();
|
||||
}
|
||||
|
||||
private InventoryLedger ledger(String customerId, String operatorId, Long operationId, InventoryBalance balance,
|
||||
InventoryPosition position, String skuCode, String businessType, String from, String to,
|
||||
long beforePackages, long deltaPackages, long afterPackages,
|
||||
String businessOrderNo, String inboundOrderNo, String outboundOrderNo,
|
||||
@@ -524,6 +753,7 @@ public class InventoryCommandService {
|
||||
ledger.setOccurredAt(LocalDateTime.now());
|
||||
ledger.setCreatedAt(ledger.getOccurredAt());
|
||||
ledgers.insert(ledger);
|
||||
return ledger;
|
||||
}
|
||||
|
||||
private static void mergeSkuSnapshot(InventoryBalance balance, String skuCode) {
|
||||
|
||||
+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