fix: restore transfer creation replay before carton validation
This commit is contained in:
+10
-10
@@ -214,6 +214,16 @@ public class WarehouseTransferService {
|
||||
@Transactional
|
||||
public WarehouseTransferResponses.Detail create(String customerId, String operatorId,
|
||||
WarehouseTransferRequests.Create request) {
|
||||
// 已成功创建的命令先恢复原业务身份;不能用后来冻结或换标后的箱状态否定重放。
|
||||
String key = request.idempotencyKey().trim();
|
||||
String requestHash = hash(request);
|
||||
WarehouseTransferOrder replay = transfers.findKey(customerId, key);
|
||||
if (replay != null) {
|
||||
if (!requestHash.equals(replay.getRequestHash())) {
|
||||
throw conflict("Idempotency key was reused with different warehouse transfer data");
|
||||
}
|
||||
return detail(customerId, replay.getTransferNo());
|
||||
}
|
||||
if (!request.isInboundModeValid()) throw badRequest("外来货物须填写入仓信息,库存转运须选择FBA仓或私人仓");
|
||||
int itemPackages = request.items().stream().mapToInt(WarehouseTransferRequests.Item::packageCount).sum();
|
||||
if (itemPackages != request.packageCount()) {
|
||||
@@ -224,16 +234,6 @@ public class WarehouseTransferService {
|
||||
validateDestination(request.destinationType(), request.destinationWarehouseCode(), request.fbaWarehouseCode(),
|
||||
request.privateDestinationAddress(), request.privateDestinationPostalCode(),
|
||||
request.privateDestinationContactName(), request.privateDestinationContactPhone());
|
||||
String key = request.idempotencyKey().trim();
|
||||
String requestHash = hash(request);
|
||||
WarehouseTransferOrder replay = transfers.findKey(customerId, key);
|
||||
if (replay != null) {
|
||||
if (!requestHash.equals(replay.getRequestHash())) {
|
||||
throw conflict("Idempotency key was reused with different warehouse transfer data");
|
||||
}
|
||||
return detail(customerId, replay.getTransferNo());
|
||||
}
|
||||
|
||||
var customer = customerDirectory.find(customerId);
|
||||
if (customer == null || !customerId.equals(customer.id()) || !StringUtils.hasText(customer.name())) {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "客户名称暂不可用,请稍后重试");
|
||||
|
||||
+37
-10
@@ -75,6 +75,7 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -85,6 +86,24 @@ import static org.mockito.Mockito.times;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WarehouseTransferServiceTest {
|
||||
@Test
|
||||
void creationReplayRestoresOriginalTransferBeforeRecheckingChangedCartonAvailability() {
|
||||
var request = relabelCartonRequest("BOX-ORIGINAL");
|
||||
var existing = persistedTransfer();
|
||||
existing.setRequestHash(org.springframework.test.util.ReflectionTestUtils.invokeMethod(service,"hash",request));
|
||||
when(transfers.findKey("TENANT-A",request.idempotencyKey())).thenReturn(existing);
|
||||
when(transfers.findNo("TENANT-A",existing.getTransferNo())).thenReturn(existing);
|
||||
assertEquals(existing.getTransferNo(),service.create("TENANT-A","USER-1",request).transferNo());
|
||||
verifyNoInteractions(inboundSnapshots,customerDirectory,inboundOrders);
|
||||
verify(orders,never()).insert(any(LastlegOrder.class));
|
||||
verify(orders,never()).updateById(any(LastlegOrder.class));
|
||||
verify(transfers,never()).insert(any(WarehouseTransferOrder.class));
|
||||
clearInvocations(orders);
|
||||
existing.setRequestHash("different-original-payload");
|
||||
var conflict=assertThrows(ResponseStatusException.class,()->service.create("TENANT-A","USER-1",request));
|
||||
assertEquals(409,conflict.getStatusCode().value());
|
||||
verifyNoInteractions(inboundSnapshots,customerDirectory,inboundOrders,orders);
|
||||
}
|
||||
@Test
|
||||
void detailIncludesOnlyTheCoordinatorScopedDurableReceiptProjection() {
|
||||
var transfer=transfer(WarehouseTransferService.PARTIALLY_RECEIVED);
|
||||
@@ -488,18 +507,13 @@ class WarehouseTransferServiceTest {
|
||||
|
||||
assertEquals(400, exception.getStatusCode().value());
|
||||
assertEquals("Warehouse transfer relabel only supports one destination warehouse per order", exception.getReason());
|
||||
verifyNoInteractions(transfers);
|
||||
verify(transfers).findKey("TENANT-A",request.idempotencyKey());
|
||||
verifyNoMoreInteractions(transfers);
|
||||
}
|
||||
|
||||
@Test
|
||||
void transferRelabelRejectsUnknownAndForeignOrderCartonsBeforePersistence() {
|
||||
var request = new WarehouseTransferRequests.Create(
|
||||
"IN-1", List.of(11L), "WH-1", null, "FBA", null, "ONT8", null,
|
||||
null, null, null, null, null, null, null, 1,
|
||||
BigDecimal.ZERO, null, null, null, null, null, null, null, true,
|
||||
LocalDateTime.of(2026, 10, 16, 10, 0), "Contact", "123", null, "TRUCK",
|
||||
"carton-ownership", List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 1, null)),
|
||||
List.of(new WarehouseTransferRequests.RelabelItem("CARTON_LABEL", List.of("FOREIGN-BOX"), "SKU-1", null, 1, "FBA-1", "ONT8", null)));
|
||||
var request = relabelCartonRequest("FOREIGN-BOX");
|
||||
for (var cartons : List.of(List.<InboundSnapshotMapper.SourceCarton>of(),
|
||||
List.of(new InboundSnapshotMapper.SourceCarton("CURRENT-BOX", "SKU-1", null, null, null, null, null)))) {
|
||||
when(inboundSnapshots.sourceCartons("TENANT-A", "WH-1", "IN-1")).thenReturn(cartons);
|
||||
@@ -510,7 +524,8 @@ class WarehouseTransferServiceTest {
|
||||
.thenReturn(List.of(new InboundSnapshotMapper.SourceCarton("FOREIGN-BOX", "SKU-1", null, null, null, null, null)));
|
||||
org.junit.jupiter.api.Assertions.assertDoesNotThrow(() ->
|
||||
ReflectionTestUtils.invokeMethod(service, "validateRelabelRequest", "TENANT-A", request));
|
||||
verifyNoInteractions(transfers);
|
||||
verify(transfers,times(2)).findKey("TENANT-A",request.idempotencyKey());
|
||||
verifyNoMoreInteractions(transfers);
|
||||
}
|
||||
|
||||
/** 先按原订单来源快照拒绝外部换标箱,不进入后续选箱占用。 */
|
||||
@@ -546,7 +561,9 @@ class WarehouseTransferServiceTest {
|
||||
|
||||
var error = assertThrows(ResponseStatusException.class, () -> service.create("TENANT-A","USER-1",request));
|
||||
assertEquals(400,error.getStatusCode().value());
|
||||
verifyNoInteractions(transfers,orders);
|
||||
verify(transfers).findKey("TENANT-A",request.idempotencyKey());
|
||||
verifyNoMoreInteractions(transfers);
|
||||
verifyNoInteractions(orders);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1279,6 +1296,16 @@ class WarehouseTransferServiceTest {
|
||||
verify(transfers, never()).insert(any(WarehouseTransferOrder.class));
|
||||
}
|
||||
|
||||
private static WarehouseTransferRequests.Create relabelCartonRequest(String cartonMark) {
|
||||
return new WarehouseTransferRequests.Create(
|
||||
"IN-1", List.of(11L), "WH-1", null, "FBA", null, "ONT8", null,
|
||||
null, null, null, null, null, null, null, 1,
|
||||
BigDecimal.ZERO, null, null, null, null, null, null, null, true,
|
||||
LocalDateTime.of(2026, 10, 16, 10, 0), "Contact", "123", null, "TRUCK",
|
||||
"carton-ownership", List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 1, null)),
|
||||
List.of(new WarehouseTransferRequests.RelabelItem("CARTON_LABEL", List.of(cartonMark), "SKU-1", null, 1, "FBA-1", "ONT8", null)));
|
||||
}
|
||||
|
||||
private static WarehouseTransferRequests.Create request(boolean needRelabel) {
|
||||
if (!needRelabel) {
|
||||
return new WarehouseTransferRequests.Create(
|
||||
|
||||
Reference in New Issue
Block a user