贴标申请,仓储转运
This commit is contained in:
+22
@@ -11,6 +11,8 @@ class WarehouseTransferSchemaContractTest {
|
||||
void baselineAndIncrementalContainTransferAndHistoricalOrderBindingTables() throws Exception {
|
||||
String baseline = Files.readString(Path.of("src", "main", "resources", "db", "lastleg.sql"), StandardCharsets.UTF_8).toLowerCase();
|
||||
String migration = Files.readString(Path.of("..", "scripts", "db", "incremental.sql"), StandardCharsets.UTF_8).toLowerCase();
|
||||
String deployment = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"),
|
||||
StandardCharsets.UTF_8).toLowerCase();
|
||||
for (String table : new String[]{"ll_warehouse_transfer_order", "ll_warehouse_transfer_source_order",
|
||||
"ll_warehouse_transfer_item", "ll_warehouse_transfer_inventory_allocation",
|
||||
"ll_warehouse_transfer_receipt"}) {
|
||||
@@ -21,5 +23,25 @@ class WarehouseTransferSchemaContractTest {
|
||||
assertTrue(migration.contains("uk_ll_wh_transfer_source"));
|
||||
assertTrue(migration.contains("uk_ll_wh_transfer_active_position"));
|
||||
assertTrue(migration.contains("uk_ll_wh_transfer_receipt_source"));
|
||||
assertTrue(baseline.contains("pickup_contact_name varchar(128) null"));
|
||||
assertTrue(baseline.contains("pickup_contact_phone varchar(64) null"));
|
||||
assertTrue(migration.contains("column_name='pickup_contact_name'"));
|
||||
assertTrue(migration.contains("column_name='pickup_contact_phone'"));
|
||||
assertTrue(baseline.contains("create table if not exists ll_warehouse_transfer_measurement_photo"));
|
||||
assertTrue(migration.contains("create table if not exists ll_warehouse_transfer_measurement_photo"));
|
||||
assertTrue(baseline.contains("received_package_count int null comment 'pda实际入库箱数'"));
|
||||
assertTrue(migration.contains("column_name='received_package_count'"));
|
||||
assertTrue(migration.contains("o.current_status='inbound_completed'"));
|
||||
for (String field : new String[]{"pickup_contact_name", "review_decision", "destination_type",
|
||||
"destination_binding_idempotency_key", "private_destination_address",
|
||||
"private_destination_postal_code", "draft_idempotency_key", "bind_idempotency_key"}) {
|
||||
assertTrue(deployment.contains(field), () -> "deployment SQL must include " + field);
|
||||
}
|
||||
for (String table : new String[]{"ll_warehouse_transfer_measurement_photo", "ll_inbound_plan_line",
|
||||
"ll_inbound_plan_pallet", "ll_dispatch_item", "ll_delivery_order"}) {
|
||||
assertTrue(deployment.contains(table), () -> "deployment SQL must include " + table);
|
||||
}
|
||||
assertTrue(deployment.contains("assert_relabel_transfer_latest_schema_ready"));
|
||||
assertTrue(deployment.contains("贴标申请与仓储转运最新表结构已完整部署"));
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -31,6 +31,25 @@ class MerchantWarehouseTransferControllerTest {
|
||||
verify(service).detail("100", "TR001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantSubmitUsesOnlyAuthenticatedTenant() {
|
||||
controller.submit(user(100L, "MERCHANT"), "TR001");
|
||||
verify(service).submit("100", "TR001", "1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantUpdateUsesOnlyAuthenticatedTenant() {
|
||||
WarehouseTransferRequests.Create request = mock(WarehouseTransferRequests.Create.class);
|
||||
controller.updateDraft(user(100L, "MERCHANT"), "TR001", request);
|
||||
verify(service).updateDraft("100", "TR001", "1", request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantPrintsSubmittedCartonLabelsWithinAuthenticatedTenant() {
|
||||
controller.cartonLabel(user(100L, "MERCHANT"), "TR001");
|
||||
verify(service).cartonLabel("100", "TR001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantEndpointRejectsPlatformIdentity() {
|
||||
assertThrows(ResponseStatusException.class,
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.baiyue.lastleg.transfer.controller;
|
||||
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PdaWarehouseTransferControllerContractTest {
|
||||
@Test
|
||||
void detailUsesOnlyAuthenticatedPdaWarehouseScopeAndTransferNumber() throws Exception {
|
||||
var method = PdaWarehouseTransferController.class
|
||||
.getMethod("detail", AuthenticatedUser.class, String.class);
|
||||
|
||||
assertThat(method.getAnnotation(GetMapping.class).value()).containsExactly("/{transferNo}");
|
||||
assertThat(method.getParameterCount()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void measurementPhotoUploadUsesThePublicPdaRouteAndFileMultipartPart() throws Exception {
|
||||
var method = PdaWarehouseTransferController.class.getMethod("uploadMeasurementPhoto",
|
||||
AuthenticatedUser.class, String.class, MultipartFile.class);
|
||||
|
||||
var mapping = method.getAnnotation(PostMapping.class);
|
||||
assertThat(mapping.value()).containsExactly("/{transferNo}/measurement-photos/files");
|
||||
assertThat(mapping.consumes()).containsExactly("multipart/form-data");
|
||||
assertThat(method.getParameters()[2].getAnnotation(RequestParam.class).value()).isEqualTo("file");
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletLabelUsesThePdaScopedTransferAndGeneratedWarehousePallet() throws Exception {
|
||||
var method = PdaWarehouseTransferController.class.getMethod("palletLabel",
|
||||
AuthenticatedUser.class, String.class, long.class, String.class);
|
||||
|
||||
assertThat(method.getAnnotation(GetMapping.class).value())
|
||||
.containsExactly("/{transferNo}/pallets/{warehousePalletId}/label");
|
||||
assertThat(method.getParameters()[3].getAnnotation(RequestHeader.class).value()).isEqualTo("X-Request-Id");
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletLifecycleExposesDraftGenerateBindAndDraftDeleteRoutes() throws Exception {
|
||||
var draft = PdaWarehouseTransferController.class.getMethod("savePalletDraft",
|
||||
AuthenticatedUser.class, String.class, WarehouseTransferRequests.PalletDraft.class);
|
||||
var generate = PdaWarehouseTransferController.class.getMethod("generatePallet",
|
||||
AuthenticatedUser.class, String.class, long.class, WarehouseTransferRequests.PalletAction.class);
|
||||
var bind = PdaWarehouseTransferController.class.getMethod("bindPallet",
|
||||
AuthenticatedUser.class, String.class, long.class, WarehouseTransferRequests.PalletBind.class);
|
||||
var delete = PdaWarehouseTransferController.class.getMethod("deletePalletDraft",
|
||||
AuthenticatedUser.class, String.class, long.class);
|
||||
|
||||
assertThat(draft.getAnnotation(PostMapping.class).value()).containsExactly("/{transferNo}/pallets/drafts");
|
||||
assertThat(generate.getAnnotation(PostMapping.class).value()).containsExactly("/{transferNo}/pallets/{palletId}/generate");
|
||||
assertThat(bind.getAnnotation(PostMapping.class).value()).containsExactly("/{transferNo}/pallets/{palletId}/bind");
|
||||
assertThat(delete.getAnnotation(DeleteMapping.class).value()).containsExactly("/{transferNo}/pallets/{palletId}");
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.baiyue.lastleg.transfer.dto;
|
||||
|
||||
import jakarta.validation.Validation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class WarehouseTransferCargoValidationTest {
|
||||
@Test
|
||||
void transferCargoAllowsSkuAndProductNameToBeOmitted() {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
var violations = factory.getValidator().validate(
|
||||
new WarehouseTransferRequests.Item(null, null, 1, null));
|
||||
|
||||
assertTrue(violations.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cartonRelabelAllowsOriginalSkuToBeOmitted() {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
var violations = factory.getValidator().validate(new WarehouseTransferRequests.RelabelItem(
|
||||
"CARTON_LABEL", List.of("CUSTOMER-CARTON-1"), null, null, 1,
|
||||
"FBA-CARTON-1", "ONT8", null));
|
||||
|
||||
assertTrue(violations.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-7
@@ -11,12 +11,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class WarehouseTransferPalletValidationTest {
|
||||
@Test
|
||||
void palletAcceptsDirectTotalVolumeAndWeightWithoutCartonDimensions() {
|
||||
void palletAcceptsMeasuredDimensionsVolumeAndWeight() {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
var validator = factory.getValidator();
|
||||
var pallet = new WarehouseTransferRequests.Pallet("PALLET-1", 5,
|
||||
new BigDecimal("1.250000"), new BigDecimal("420.50"),
|
||||
List.of(new WarehouseTransferRequests.PalletItem(101L, 5)));
|
||||
var pallet = new WarehouseTransferRequests.Pallet(5,
|
||||
new BigDecimal("100"), new BigDecimal("100"), new BigDecimal("125"),
|
||||
new BigDecimal("1.250000"), new BigDecimal("420.50"));
|
||||
|
||||
assertTrue(validator.validate(pallet).isEmpty());
|
||||
assertEquals(new BigDecimal("1.250000"), pallet.volumeCbm());
|
||||
@@ -28,11 +28,23 @@ class WarehouseTransferPalletValidationTest {
|
||||
void palletRejectsNonPositiveTotalVolume() {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
var validator = factory.getValidator();
|
||||
var pallet = new WarehouseTransferRequests.Pallet("PALLET-1", 5,
|
||||
BigDecimal.ZERO, new BigDecimal("420.50"),
|
||||
List.of(new WarehouseTransferRequests.PalletItem(101L, 5)));
|
||||
var pallet = new WarehouseTransferRequests.Pallet(5,
|
||||
new BigDecimal("100"), new BigDecimal("100"), new BigDecimal("125"),
|
||||
BigDecimal.ZERO, new BigDecimal("420.50"));
|
||||
|
||||
assertEquals(1, validator.validate(pallet).size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletRejectsMissingOrNonPositiveDimensions() {
|
||||
try (var factory = Validation.buildDefaultValidatorFactory()) {
|
||||
var validator = factory.getValidator();
|
||||
var pallet = new WarehouseTransferRequests.Pallet(5,
|
||||
BigDecimal.ZERO, null, new BigDecimal("125"),
|
||||
new BigDecimal("1.250000"), new BigDecimal("420.50"));
|
||||
|
||||
assertEquals(2, validator.validate(pallet).size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.lastleg.transfer.gateway;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HttpTransferMeasurementPhotoFileGatewayContractTest {
|
||||
@Test
|
||||
void defaultsToTheFileServicesActualLocalPort() throws Exception {
|
||||
var field = HttpTransferMeasurementPhotoFileGateway.class.getDeclaredField("baseUrl");
|
||||
|
||||
assertThat(field.getAnnotation(Value.class).value())
|
||||
.isEqualTo("${baiyue.clients.file.url:http://127.0.0.1:9206}");
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package com.baiyue.lastleg.transfer.service;
|
||||
|
||||
import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferItem;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferPallet;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class WarehouseTransferPalletAllocationTest {
|
||||
@Test
|
||||
void allocatesMultipleDeclaredSkusAcrossPalletsInStableDeclaredOrder() {
|
||||
WarehouseTransferItem first = item(11L, 3);
|
||||
WarehouseTransferItem second = item(12L, 4);
|
||||
var allocations = WarehouseTransferService.derivePalletAllocations(
|
||||
List.of(first, second), List.of(pallet(2), pallet(5)));
|
||||
|
||||
assertEquals(List.of(
|
||||
new WarehouseTransferService.DerivedPalletAllocation(0, 11L, 2),
|
||||
new WarehouseTransferService.DerivedPalletAllocation(1, 11L, 1),
|
||||
new WarehouseTransferService.DerivedPalletAllocation(1, 12L, 4)), allocations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void underReceiptConsumesDeclaredSkusInStableOrder() {
|
||||
var allocations = WarehouseTransferService.derivePalletAllocations(
|
||||
List.of(item(11L, 3), item(12L, 2)), List.of(pallet(4)));
|
||||
|
||||
assertEquals(List.of(
|
||||
new WarehouseTransferService.DerivedPalletAllocation(0, 11L, 3),
|
||||
new WarehouseTransferService.DerivedPalletAllocation(0, 12L, 1)), allocations);
|
||||
assertEquals(4, allocations.stream().mapToInt(value -> value.packageCount()).sum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void overReceiptAssignsExcessToFinalDeclaredSkuAcrossPallets() {
|
||||
var allocations = WarehouseTransferService.derivePalletAllocations(
|
||||
List.of(item(11L, 3), item(12L, 2)), List.of(pallet(4), pallet(3)));
|
||||
|
||||
assertEquals(List.of(
|
||||
new WarehouseTransferService.DerivedPalletAllocation(0, 11L, 3),
|
||||
new WarehouseTransferService.DerivedPalletAllocation(0, 12L, 1),
|
||||
new WarehouseTransferService.DerivedPalletAllocation(1, 12L, 3)), allocations);
|
||||
assertEquals(7, allocations.stream().mapToInt(value -> value.packageCount()).sum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistedOverReceiptUsesTheSameStableAllocationRule() {
|
||||
WarehouseTransferPallet first = persistedPallet(4);
|
||||
WarehouseTransferPallet second = persistedPallet(3);
|
||||
|
||||
var allocations = WarehouseTransferService.derivePersistedPalletAllocations(
|
||||
List.of(item(11L, 3), item(12L, 2)), List.of(first, second));
|
||||
|
||||
assertEquals(7, allocations.stream().mapToInt(value -> value.packageCount()).sum());
|
||||
assertEquals(3, allocations.stream().filter(value -> value.transferItemId() == 11L)
|
||||
.mapToInt(value -> value.packageCount()).sum());
|
||||
assertEquals(4, allocations.stream().filter(value -> value.transferItemId() == 12L)
|
||||
.mapToInt(value -> value.packageCount()).sum());
|
||||
assertEquals(List.of(0, 0, 1), allocations.stream().map(value -> value.palletIndex()).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestsFreshPalletNumbersAndCarriesOnlyTheKnownFbaDestination() {
|
||||
var known = WarehouseTransferService.newTransferPalletRequests(81L, "WTR-1", "ONT8", 2);
|
||||
var unknown = WarehouseTransferService.newTransferPalletRequests(81L, "WTR-2", null, 1);
|
||||
|
||||
assertEquals(List.of("WTR-1:1", "WTR-1:2"), known.stream().map(value -> value.businessKey()).toList());
|
||||
assertEquals(List.of("ONT8", "ONT8"), known.stream().map(value -> value.fbaWarehouseCode()).toList());
|
||||
assertEquals(true, known.stream().allMatch(value -> value.palletCode() == null));
|
||||
assertEquals(null, unknown.get(0).fbaWarehouseCode());
|
||||
assertEquals(null, unknown.get(0).palletCode());
|
||||
}
|
||||
|
||||
private static WarehouseTransferItem item(long id, int packages) {
|
||||
WarehouseTransferItem item = new WarehouseTransferItem();
|
||||
item.setId(id); item.setPackageCount(packages);
|
||||
return item;
|
||||
}
|
||||
|
||||
private static WarehouseTransferRequests.Pallet pallet(int cartons) {
|
||||
return new WarehouseTransferRequests.Pallet(cartons, BigDecimal.ONE, BigDecimal.ONE,
|
||||
BigDecimal.ONE, new BigDecimal("0.000001"), BigDecimal.ONE);
|
||||
}
|
||||
|
||||
private static com.baiyue.lastleg.transfer.entity.WarehouseTransferPallet persistedPallet(int cartons) {
|
||||
var pallet = new com.baiyue.lastleg.transfer.entity.WarehouseTransferPallet();
|
||||
pallet.setCartonCount(cartons);
|
||||
return pallet;
|
||||
}
|
||||
}
|
||||
+393
-12
@@ -1,6 +1,7 @@
|
||||
package com.baiyue.lastleg.transfer.service;
|
||||
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderDetail;
|
||||
import com.baiyue.lastleg.entity.LastlegInboundOrder;
|
||||
import com.baiyue.lastleg.relabel.entity.RelabelApplication;
|
||||
import com.baiyue.lastleg.inventory.command.dto.InventoryCommandResponse;
|
||||
@@ -19,11 +20,27 @@ import com.baiyue.lastleg.transfer.entity.WarehouseTransferOrder;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferCarton;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferInventoryAllocation;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferSourceOrder;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferMeasurementPhoto;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferPallet;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferItem;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferPalletMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferPalletItemMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferRelabelBindingMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.mapper.InboundStockReceiptMapper;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlan;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanLine;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanMapper;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanLineMapper;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletMapper;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletAllocationMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.InboundSnapshotMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferItemMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferCartonMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferInventoryAllocationMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferOrderMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferMeasurementPhotoMapper;
|
||||
import com.baiyue.lastleg.transfer.gateway.TransferMeasurementPhotoFileGateway;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferReceiptMapper;
|
||||
import com.baiyue.lastleg.transfer.mapper.WarehouseTransferSourceOrderMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -62,14 +79,36 @@ import static org.mockito.Mockito.times;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class WarehouseTransferServiceTest {
|
||||
@Test
|
||||
void customerMainStatusFiltersExpandToInternalWorkflowStates() {
|
||||
assertEquals(List.of(WarehouseTransferService.APPROVED, WarehouseTransferService.WAIT_PDA_RECEIVING),
|
||||
WarehouseTransferService.customerMainStatuses(WarehouseTransferService.INBOUND_MEASURING));
|
||||
assertTrue(WarehouseTransferService.customerMainStatuses(WarehouseTransferService.INBOUND_COMPLETED)
|
||||
.containsAll(List.of(WarehouseTransferService.WAIT_CUSTOMER_CONFIRMATION,
|
||||
WarehouseTransferService.WAIT_FBA_DESTINATION, WarehouseTransferService.WAIT_RELABEL,
|
||||
WarehouseTransferService.READY_FOR_DISPATCH, WarehouseTransferService.OUTBOUNDED)));
|
||||
assertNull(WarehouseTransferService.customerMainStatuses(WarehouseTransferService.DRAFT));
|
||||
}
|
||||
|
||||
@Mock WarehouseTransferOrderMapper transfers;
|
||||
@Mock WarehouseTransferItemMapper transferItems;
|
||||
@Mock WarehouseTransferCartonMapper transferCartons;
|
||||
@Mock WarehouseTransferSourceOrderMapper sourceOrders;
|
||||
@Mock WarehouseTransferInventoryAllocationMapper inventoryAllocations;
|
||||
@Mock WarehouseTransferReceiptMapper transferReceipts;
|
||||
@Mock WarehouseTransferMeasurementPhotoMapper measurementPhotos;
|
||||
@Mock WarehouseTransferPalletMapper transferPallets;
|
||||
@Mock WarehouseTransferPalletItemMapper transferPalletItems;
|
||||
@Mock InboundStockReceiptMapper stockReceipts;
|
||||
@Mock WarehouseTransferRelabelBindingMapper relabelBindings;
|
||||
@Mock WarehouseResourceGateway warehouseResources;
|
||||
@Mock TransferMeasurementPhotoFileGateway measurementPhotoFiles;
|
||||
@Mock InboundSnapshotMapper inboundSnapshots;
|
||||
@Mock LastlegInboundOrderMapper inboundOrders;
|
||||
@Mock InboundPlanMapper inboundPlans;
|
||||
@Mock InboundPlanLineMapper inboundPlanLines;
|
||||
@Mock InboundPlanPalletMapper inboundPlanPallets;
|
||||
@Mock InboundPlanPalletAllocationMapper inboundPlanAllocations;
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderDetailMapper orderDetails;
|
||||
@Mock LastlegOrderStatusHistoryMapper orderHistories;
|
||||
@@ -88,8 +127,19 @@ class WarehouseTransferServiceTest {
|
||||
ReflectionTestUtils.setField(service, "sourceOrders", sourceOrders);
|
||||
ReflectionTestUtils.setField(service, "inventoryAllocations", inventoryAllocations);
|
||||
ReflectionTestUtils.setField(service, "transferReceipts", transferReceipts);
|
||||
ReflectionTestUtils.setField(service, "measurementPhotos", measurementPhotos);
|
||||
ReflectionTestUtils.setField(service, "transferPallets", transferPallets);
|
||||
ReflectionTestUtils.setField(service, "transferPalletItems", transferPalletItems);
|
||||
ReflectionTestUtils.setField(service, "stockReceipts", stockReceipts);
|
||||
ReflectionTestUtils.setField(service, "relabelBindings", relabelBindings);
|
||||
ReflectionTestUtils.setField(service, "warehouseResources", warehouseResources);
|
||||
ReflectionTestUtils.setField(service, "measurementPhotoFiles", measurementPhotoFiles);
|
||||
ReflectionTestUtils.setField(service, "inboundSnapshots", inboundSnapshots);
|
||||
ReflectionTestUtils.setField(service, "inboundOrders", inboundOrders);
|
||||
ReflectionTestUtils.setField(service, "inboundPlans", inboundPlans);
|
||||
ReflectionTestUtils.setField(service, "inboundPlanLines", inboundPlanLines);
|
||||
ReflectionTestUtils.setField(service, "inboundPlanPallets", inboundPlanPallets);
|
||||
ReflectionTestUtils.setField(service, "inboundPlanAllocations", inboundPlanAllocations);
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "orderDetails", orderDetails);
|
||||
ReflectionTestUtils.setField(service, "orderHistories", orderHistories);
|
||||
@@ -103,6 +153,116 @@ class WarehouseTransferServiceTest {
|
||||
lenient().when(inventoryAllocations.selectList(any())).thenReturn(List.of());
|
||||
lenient().when(transferReceipts.selectList(any())).thenReturn(List.of());
|
||||
lenient().when(transferCartons.listOrder(anyString(), anyLong())).thenReturn(List.of());
|
||||
lenient().when(measurementPhotos.listOrder(anyString(), anyLong())).thenReturn(List.of());
|
||||
lenient().when(measurementPhotos.listSubmitted(anyString(), anyLong())).thenReturn(List.of());
|
||||
lenient().when(transferPalletItems.listOrder(anyString(), anyLong())).thenReturn(List.of());
|
||||
lenient().when(relabelBindings.listOrder(anyString(), anyLong())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatedPalletRetryWithSameKeyDoesNotGenerateAnotherPhysicalPallet() {
|
||||
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.WAIT_PDA_RECEIVING);
|
||||
transfer.setOperatingWarehouseId("77");
|
||||
WarehouseTransferPallet pallet = measuredPallet("GENERATED");
|
||||
pallet.setGenerateIdempotencyKey("GEN-1");
|
||||
when(transfers.lockForPda("WTR-1", "77")).thenReturn(transfer);
|
||||
when(transferPallets.lockOne("TENANT-A", transfer.getId(), pallet.getId())).thenReturn(pallet);
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
when(transferPallets.listOrder("TENANT-A", transfer.getId())).thenReturn(List.of(pallet));
|
||||
|
||||
service.generatePallet(77L, "WTR-1", pallet.getId(), new WarehouseTransferRequests.PalletAction("GEN-1"));
|
||||
|
||||
verify(warehouseResources, never()).ensurePalletBatch(any());
|
||||
verify(warehouseResources, never()).confirmPalletBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanBindingRejectsCodeOtherThanThisGeneratedPallet() {
|
||||
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.WAIT_PDA_RECEIVING);
|
||||
WarehouseTransferPallet pallet = measuredPallet("GENERATED");
|
||||
when(transfers.lockForPda("WTR-1", "77")).thenReturn(transfer);
|
||||
when(transferPallets.lockOne("TENANT-A", transfer.getId(), pallet.getId())).thenReturn(pallet);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> service.bindPallet(
|
||||
77L, "WTR-1", pallet.getId(), new WarehouseTransferRequests.PalletBind("OTHER-PALLET", "BIND-1")));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(warehouseResources, never()).resolvePallet(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletizeRejectsWhenAnyPersistedPalletIsNotBound() {
|
||||
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.WAIT_PDA_RECEIVING);
|
||||
transfer.setOperatingWarehouseId("77");
|
||||
WarehouseTransferPallet pallet = measuredPallet("GENERATED");
|
||||
when(transfers.lockForPda("WTR-1", "77")).thenReturn(transfer);
|
||||
when(transferItems.selectList(any())).thenReturn(List.of(transferItem(51L, "SKU-1", 5)));
|
||||
WarehouseTransferMeasurementPhoto photo = new WarehouseTransferMeasurementPhoto();
|
||||
photo.setFileId(9001L);
|
||||
when(measurementPhotos.findFile("TENANT-A", transfer.getId(), 9001L)).thenReturn(photo);
|
||||
when(transferPallets.listOrder("TENANT-A", transfer.getId())).thenReturn(List.of(pallet));
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> service.palletize(
|
||||
"OP-1", 77L, "WTR-1", new WarehouseTransferRequests.Palletize("DONE-1", List.of(9001L))));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(warehouseResources, never()).ensurePalletBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletizeCompletesAfterEveryPalletIsBoundAndDoesNotGenerateAgain() {
|
||||
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.WAIT_PDA_RECEIVING);
|
||||
transfer.setOperatingWarehouseId("77");
|
||||
transfer.setInboundCode("WTR-1");
|
||||
WarehouseTransferPallet pallet = measuredPallet("BOUND");
|
||||
WarehouseTransferItem item = transferItem(51L, "SKU-1", 5);
|
||||
LastlegOrder tail = tailOrder();
|
||||
LastlegInboundOrder inbound = new LastlegInboundOrder();
|
||||
inbound.setId(transfer.getInboundOrderId()); inbound.setCustomerId("TENANT-A");
|
||||
InboundPlan inboundPlan = new InboundPlan(); inboundPlan.setId(201L); inboundPlan.setStatus("PDA_PENDING");
|
||||
InboundPlanLine inboundLine = new InboundPlanLine(); inboundLine.setId(202L);
|
||||
when(transfers.lockForPda("WTR-1", "77")).thenReturn(transfer);
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(transferItems.selectList(any())).thenReturn(List.of(item));
|
||||
when(transferPallets.listOrder("TENANT-A", transfer.getId())).thenReturn(List.of(pallet));
|
||||
WarehouseTransferMeasurementPhoto photo = new WarehouseTransferMeasurementPhoto();
|
||||
photo.setFileId(9001L);
|
||||
when(measurementPhotos.findFile("TENANT-A", transfer.getId(), 9001L)).thenReturn(photo);
|
||||
when(measurementPhotos.markSubmitted(eq("TENANT-A"), eq(transfer.getId()), any(), any())).thenReturn(1);
|
||||
when(orders.selectById(transfer.getLastlegOrderId())).thenReturn(tail);
|
||||
when(inboundOrders.selectById(transfer.getInboundOrderId())).thenReturn(inbound);
|
||||
when(inboundPlans.findBySourceOrder("TENANT-A", transfer.getLastlegOrderId())).thenReturn(inboundPlan);
|
||||
when(inboundPlanLines.selectOne(any())).thenReturn(inboundLine);
|
||||
when(warehouseResources.requireActiveSharedWarehouse(77L)).thenReturn(
|
||||
new WarehouseResourceGateway.WarehouseReference(77L, "WH-77", "Warehouse 77", "ACTIVE"));
|
||||
when(inventory.stockIn(eq("TENANT-A"), eq("OP-1"), any())).thenReturn(
|
||||
new InventoryCommandResponse(1L, "STOCK_IN", 801L, 901L, 5, 0, 0, 0, false, false));
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
var result = service.palletize("OP-1", 77L, "WTR-1",
|
||||
new WarehouseTransferRequests.Palletize("DONE-1", List.of(9001L)));
|
||||
|
||||
assertEquals(WarehouseTransferService.WAIT_CUSTOMER_CONFIRMATION, result.status());
|
||||
assertEquals("COMPLETED", pallet.getStatus());
|
||||
assertEquals(WarehouseTransferService.INBOUND_COMPLETED, tail.getCurrentStatus());
|
||||
assertEquals(5, tail.getReceivedPackageCount());
|
||||
assertEquals("RECEIVED", inbound.getStatus());
|
||||
assertEquals(5, inbound.getPackageCount());
|
||||
assertEquals("COMPLETED", inboundPlan.getStatus());
|
||||
verify(transferPalletItems).insert(any(com.baiyue.lastleg.transfer.entity.WarehouseTransferPalletItem.class));
|
||||
verify(inventory).stockIn(eq("TENANT-A"), eq("OP-1"), any());
|
||||
verify(stockReceipts).insert(any(com.baiyue.lastleg.inboundplan.pda.entity.InboundStockReceipt.class));
|
||||
verify(inboundOrders).updateById(inbound);
|
||||
verify(inboundPlans).updateById(inboundPlan);
|
||||
verify(inboundPlanPallets).insert(any(com.baiyue.lastleg.inboundplan.entity.InboundPlanPallet.class));
|
||||
ArgumentCaptor<com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation> allocation =
|
||||
ArgumentCaptor.forClass(com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation.class);
|
||||
verify(inboundPlanAllocations).insert(allocation.capture());
|
||||
assertEquals(5, allocation.getValue().getPickedPackageCount());
|
||||
verify(warehouseResources, never()).ensurePalletBatch(any());
|
||||
verify(warehouseResources, never()).confirmPalletBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,7 +271,7 @@ class WarehouseTransferServiceTest {
|
||||
LastlegOrder historical = historicalOrder();
|
||||
when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L);
|
||||
when(orders.selectList(any())).thenReturn(List.of(historical));
|
||||
when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> persistedTransfer());
|
||||
when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> transfer(WarehouseTransferService.DRAFT));
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
@@ -120,23 +280,23 @@ class WarehouseTransferServiceTest {
|
||||
ArgumentCaptor<LastlegOrder> tailOrder = ArgumentCaptor.forClass(LastlegOrder.class);
|
||||
verify(orders).insert(tailOrder.capture());
|
||||
assertEquals(WarehouseTransferService.ORDER_TYPE, tailOrder.getValue().getOrderType());
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, tailOrder.getValue().getCurrentStatus());
|
||||
assertEquals("WAREHOUSE_TRANSFER:PENDING_REVIEW;RELABEL:PENDING_BINDING", tailOrder.getValue().getSubOrderStatus());
|
||||
assertEquals(WarehouseTransferService.DRAFT, tailOrder.getValue().getCurrentStatus());
|
||||
assertEquals("WAREHOUSE_TRANSFER:DRAFT;RELABEL:PENDING_BINDING", tailOrder.getValue().getSubOrderStatus());
|
||||
assertTrue(tailOrder.getValue().getOrderNo().matches("WT-WH-\\d{8}-\\d{6}"));
|
||||
assertNull(tailOrder.getValue().getTransferWarehouseId());
|
||||
verify(sourceOrders).insert(any(WarehouseTransferSourceOrder.class));
|
||||
verify(relabelApplications, never()).insert(any(RelabelApplication.class));
|
||||
assertNull(tailOrder.getValue().getRelabelOrderNo());
|
||||
assertEquals("PENDING_BINDING", tailOrder.getValue().getRelabelStatus());
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, result.status());
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, result.orderStatus());
|
||||
assertEquals(WarehouseTransferService.DRAFT, result.status());
|
||||
assertEquals(WarehouseTransferService.DRAFT, result.orderStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void transferWithoutRelabelDoesNotCreateRelabelDocument() {
|
||||
when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L);
|
||||
when(orders.selectList(any())).thenReturn(List.of(historicalOrder()));
|
||||
when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> persistedTransfer());
|
||||
when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> transfer(WarehouseTransferService.DRAFT));
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
@@ -146,6 +306,27 @@ class WarehouseTransferServiceTest {
|
||||
assertNull(result.relabelNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
void transferRelabelRejectsMultipleDestinationWarehouses() {
|
||||
WarehouseTransferRequests.Create request = new WarehouseTransferRequests.Create(
|
||||
null, null, "WH-1", null, "NONE", null, null, null,
|
||||
null, null, null, null, "123 Pickup Road", null, null, 2,
|
||||
new BigDecimal("0.1000"), "CUSTOMER_DELIVERY", LocalDateTime.of(2026, 9, 18, 10, 0),
|
||||
null, null, null, "BOX-1,BOX-2", null, true,
|
||||
LocalDateTime.of(2026, 9, 19, 10, 0), "Contact", "13800000000", null, "TRUCK",
|
||||
"single-destination-check", List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 2, null)),
|
||||
List.of(
|
||||
new WarehouseTransferRequests.RelabelItem("CARTON_LABEL", List.of("BOX-1"), "SKU-1", null, 1, "FBA-1", "ONT8", null),
|
||||
new WarehouseTransferRequests.RelabelItem("CARTON_LABEL", List.of("BOX-2"), "SKU-1", null, 1, "FBA-2", "ACY2", null)));
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.create("TENANT-A", "USER-1", request));
|
||||
|
||||
assertEquals(400, exception.getStatusCode().value());
|
||||
assertEquals("Warehouse transfer relabel only supports one destination warehouse per order", exception.getReason());
|
||||
verifyNoInteractions(transfers);
|
||||
}
|
||||
|
||||
@Test
|
||||
void transferCanEnterTheTransitWarehouseBeforeFbaDestinationIsKnown() {
|
||||
WarehouseTransferRequests.Create request = new WarehouseTransferRequests.Create(
|
||||
@@ -155,7 +336,7 @@ class WarehouseTransferServiceTest {
|
||||
List.of(new WarehouseTransferRequests.Item("SKU-1", "Product", 5, "MARK-1")));
|
||||
when(inboundSnapshots.findId("TENANT-A", "IN-1")).thenReturn(101L);
|
||||
when(orders.selectList(any())).thenReturn(List.of(historicalOrder()));
|
||||
when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> persistedTransfer());
|
||||
when(transfers.findNo(eq("TENANT-A"), org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> transfer(WarehouseTransferService.DRAFT));
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
@@ -179,7 +360,7 @@ class WarehouseTransferServiceTest {
|
||||
null, "TRACK-MUST-NOT-BE-SAVED", null, "Incoming transit cargo", true, "key-incoming",
|
||||
List.of(new WarehouseTransferRequests.Item("SKU-1", "Product A", 2, null),
|
||||
new WarehouseTransferRequests.Item("SKU-2", "Product B", 3, null)));
|
||||
when(transfers.findNo(eq("TENANT-A"), anyString())).thenAnswer(invocation -> persistedTransfer());
|
||||
when(transfers.findNo(eq("TENANT-A"), anyString())).thenAnswer(invocation -> transfer(WarehouseTransferService.DRAFT));
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
@@ -199,7 +380,7 @@ class WarehouseTransferServiceTest {
|
||||
assertEquals(transfer.getValue().getTransferNo(), transfer.getValue().getInboundCode());
|
||||
assertEquals(transfer.getValue().getTransferNo(), inbound.getValue().getInboundCode());
|
||||
assertEquals("OVERSEAS", inbound.getValue().getDestinationType());
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, inbound.getValue().getStatus());
|
||||
assertEquals(WarehouseTransferService.DRAFT, inbound.getValue().getStatus());
|
||||
assertNull(transfer.getValue().getTrackingNo());
|
||||
assertEquals("PENDING_BINDING", tailOrder.getValue().getRelabelStatus());
|
||||
assertEquals(List.of(1, 2, 3, 4, 5), cartons.getAllValues().stream()
|
||||
@@ -209,7 +390,7 @@ class WarehouseTransferServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void approvedTransferWithoutRelabelEntersPendingAssignment() {
|
||||
void approvedTransferWithoutRelabelEntersInboundMeasuring() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
transfer.setStatus(WarehouseTransferService.PENDING_REVIEW);
|
||||
transfer.setNeedRelabel(false);
|
||||
@@ -225,14 +406,134 @@ class WarehouseTransferServiceTest {
|
||||
new WarehouseTransferRequests.Review("APPROVE", null));
|
||||
|
||||
assertEquals(WarehouseTransferService.APPROVED, transfer.getStatus());
|
||||
assertEquals(WarehouseTransferService.PENDING_ASSIGNMENT, tailOrder.getCurrentStatus());
|
||||
assertEquals(WarehouseTransferService.INBOUND_MEASURING, tailOrder.getCurrentStatus());
|
||||
assertEquals(WarehouseTransferService.APPROVED, result.status());
|
||||
assertEquals(WarehouseTransferService.PENDING_ASSIGNMENT, result.orderStatus());
|
||||
assertEquals(WarehouseTransferService.INBOUND_MEASURING, result.orderStatus());
|
||||
verify(transfers).updateById(transfer);
|
||||
verify(orders).updateById(tailOrder);
|
||||
verifyNoInteractions(inventory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void approvedIncomingTransferCreatesPendingInboundPlan() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
transfer.setInboundCode("WTR-1"); transfer.setOperatingWarehouseId("77");
|
||||
transfer.setEstimatedArrivalAt(LocalDateTime.of(2026, 9, 17, 10, 0));
|
||||
LastlegOrder tailOrder = tailOrder(); tailOrder.setCurrentStatus(WarehouseTransferService.PENDING_REVIEW);
|
||||
LastlegInboundOrder inbound = new LastlegInboundOrder();
|
||||
inbound.setId(101L); inbound.setCustomerId("TENANT-A"); inbound.setStatus(WarehouseTransferService.PENDING_REVIEW);
|
||||
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(orders.selectById(31L)).thenReturn(tailOrder);
|
||||
when(inboundOrders.selectById(101L)).thenReturn(inbound);
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
service.review("TENANT-A", "WTR-1", "USER-1", new WarehouseTransferRequests.Review("APPROVE", null));
|
||||
|
||||
ArgumentCaptor<InboundPlan> plan = ArgumentCaptor.forClass(InboundPlan.class);
|
||||
ArgumentCaptor<InboundPlanLine> line = ArgumentCaptor.forClass(InboundPlanLine.class);
|
||||
verify(inboundPlans).insert(plan.capture());
|
||||
verify(inboundPlanLines).insert(line.capture());
|
||||
assertEquals("PDA_PENDING", plan.getValue().getStatus());
|
||||
assertEquals("WT-WTR-1", plan.getValue().getPlanNo());
|
||||
assertEquals("WTR-1", line.getValue().getInboundCode());
|
||||
assertEquals(5, line.getValue().getRequiredPackageCount());
|
||||
assertEquals("PENDING_INBOUND", inbound.getStatus());
|
||||
assertEquals(WarehouseTransferService.WAIT_PDA_RECEIVING, transfer.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantSubmitsDraftForReviewBeforePrintingLabels() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
transfer.setStatus(WarehouseTransferService.DRAFT);
|
||||
transfer.setInboundCode("WTR-1");
|
||||
LastlegOrder tailOrder = historicalOrder();
|
||||
tailOrder.setId(31L); tailOrder.setCustomerId("TENANT-A"); tailOrder.setRelabelStatus("NOT_REQUIRED");
|
||||
LastlegInboundOrder inbound = new LastlegInboundOrder();
|
||||
inbound.setId(101L); inbound.setCustomerId("TENANT-A"); inbound.setStatus(WarehouseTransferService.DRAFT);
|
||||
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(orders.selectById(31L)).thenReturn(tailOrder);
|
||||
when(inboundOrders.selectById(101L)).thenReturn(inbound);
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
var result = service.submit("TENANT-A", "WTR-1", "USER-1");
|
||||
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, transfer.getStatus());
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, tailOrder.getCurrentStatus());
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, inbound.getStatus());
|
||||
assertEquals(WarehouseTransferService.PENDING_REVIEW, result.status());
|
||||
verify(transfers).updateById(transfer);
|
||||
verify(orders).updateById(tailOrder);
|
||||
verify(inboundOrders).updateById(inbound);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantUpdatesDraftAndRebuildsEditableSnapshots() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
transfer.setStatus(WarehouseTransferService.DRAFT);
|
||||
LastlegOrder tailOrder = historicalOrder();
|
||||
tailOrder.setId(31L); tailOrder.setCustomerId("TENANT-A"); tailOrder.setCurrentStatus(WarehouseTransferService.DRAFT);
|
||||
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(orders.selectById(31L)).thenReturn(tailOrder);
|
||||
when(transferItems.selectList(any())).thenReturn(List.of());
|
||||
when(sourceOrders.selectList(any())).thenReturn(List.of());
|
||||
|
||||
var result = service.updateDraft("TENANT-A", "WTR-1", "USER-1", request(false));
|
||||
|
||||
assertEquals(WarehouseTransferService.DRAFT, result.status());
|
||||
assertEquals("WH-1", transfer.getOperatingWarehouseId());
|
||||
assertEquals(new BigDecimal("0.2500"), tailOrder.getVolumeCbm());
|
||||
verify(transfers).updateById(transfer);
|
||||
verify(orders).updateById(tailOrder);
|
||||
verify(transferCartons).delete(any());
|
||||
verify(transferItems).delete(any());
|
||||
verify(relabelBindings).delete(any());
|
||||
verify(orderDetails).delete(any());
|
||||
verify(transferItems).insert(any(WarehouseTransferItem.class));
|
||||
verify(transferCartons, times(5)).insert(any(WarehouseTransferCarton.class));
|
||||
verify(orderDetails).insert(any(LastlegOrderDetail.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantCannotUpdateSubmittedTransfer() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class,
|
||||
() -> service.updateDraft("TENANT-A", "WTR-1", "USER-1", request(false)));
|
||||
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
verify(transfers, never()).updateById(any(WarehouseTransferOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantDraftUpdateIsTenantScoped() {
|
||||
when(transfers.lockNo("TENANT-B", "WTR-1")).thenReturn(null);
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class,
|
||||
() -> service.updateDraft("TENANT-B", "WTR-1", "USER-2", request(false)));
|
||||
|
||||
assertEquals(404, error.getStatusCode().value());
|
||||
verify(transfers, never()).updateById(any(WarehouseTransferOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void draftCannotPrintCartonLabelsBeforeSubmission() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
transfer.setStatus(WarehouseTransferService.DRAFT);
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class,
|
||||
() -> service.cartonLabel("TENANT-A", "WTR-1"));
|
||||
|
||||
assertEquals(409, error.getStatusCode().value());
|
||||
verify(transferCartons, never()).listOrder(anyString(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectionRequiresNonBlankReasonBeforeAnyStateOrInventoryChange() {
|
||||
for (String decision : List.of("REJECT", "REJECTED")) {
|
||||
@@ -378,6 +679,67 @@ class WarehouseTransferServiceTest {
|
||||
assertEquals("RESERVED", allocation.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundRejectsTransferWithoutFbaDestinationBeforeInventoryMutation() {
|
||||
WarehouseTransferOrder transfer = transfer(WarehouseTransferService.ALLOCATED);
|
||||
transfer.setFbaWarehouseCode(null);
|
||||
when(transfers.lockNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
||||
service.outbound("TENANT-A", "WTR-1", "USER-1",
|
||||
new WarehouseTransferRequests.Outbound("OUT-NO-FBA", null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verifyNoInteractions(inventory);
|
||||
verify(inventoryAllocations, never()).lockTransfer(anyString(), anyLong());
|
||||
verify(transfers, never()).updateById(any(WarehouseTransferOrder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantMeasurementPhotosExposeOnlySubmittedSelection() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
WarehouseTransferMeasurementPhoto selected = new WarehouseTransferMeasurementPhoto();
|
||||
selected.setFileId(101L); selected.setFileName("selected.jpg"); selected.setContentType("image/jpeg");
|
||||
selected.setSizeBytes(12L); selected.setUploadedAt(LocalDateTime.of(2026, 9, 16, 10, 0));
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(measurementPhotos.listSubmitted("TENANT-A", 21L)).thenReturn(List.of(selected));
|
||||
|
||||
var result = service.measurementPhotos("TENANT-A", "WTR-1");
|
||||
|
||||
assertEquals(List.of(101L), result.stream().map(value -> value.fileId()).toList());
|
||||
verify(measurementPhotos, never()).listOrder("TENANT-A", 21L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantCannotDownloadUploadedButUnsubmittedMeasurementPhoto() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
when(transfers.findNo("TENANT-A", "WTR-1")).thenReturn(transfer);
|
||||
when(measurementPhotos.findSubmittedFile("TENANT-A", 21L, 102L)).thenReturn(null);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.downloadMeasurementPhoto("TENANT-A", "WTR-1", 102L));
|
||||
|
||||
assertEquals(404, exception.getStatusCode().value());
|
||||
verifyNoInteractions(measurementPhotoFiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaCanDownloadOwnWarehouseUploadedPhotoBeforeSubmission() {
|
||||
WarehouseTransferOrder transfer = persistedTransfer();
|
||||
WarehouseTransferMeasurementPhoto draft = new WarehouseTransferMeasurementPhoto();
|
||||
draft.setFileId(102L);
|
||||
var downloaded = new TransferMeasurementPhotoFileGateway.DownloadedFile(
|
||||
new byte[]{1}, "draft.jpg", "image/jpeg");
|
||||
when(transfers.findNoByWarehouse("WTR-1", "92")).thenReturn(transfer);
|
||||
when(measurementPhotos.findFile("TENANT-A", 21L, 102L)).thenReturn(draft);
|
||||
when(measurementPhotoFiles.download("TENANT-A", 21L, 102L)).thenReturn(downloaded);
|
||||
|
||||
var result = service.downloadPdaMeasurementPhoto(92L, "WTR-1", 102L);
|
||||
|
||||
assertEquals("draft.jpg", result.fileName());
|
||||
verify(measurementPhotos, never()).findSubmittedFile(anyString(), anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void allocationRejectsPositionOutsideSelectedInboundOrder() {
|
||||
WarehouseTransferOrder transfer = transfer("APPROVED");
|
||||
@@ -427,6 +789,7 @@ class WarehouseTransferServiceTest {
|
||||
transfer.setInboundOrderId(101L); transfer.setInboundCode("IN-1"); transfer.setLastlegOrderId(31L);
|
||||
transfer.setLastlegOrderNo("LMO-TRANSFER-1"); transfer.setNeedRelabel(false);
|
||||
transfer.setStatus(WarehouseTransferService.PENDING_REVIEW); transfer.setPackageCount(5);
|
||||
transfer.setFbaWarehouseCode("ONT8");
|
||||
return transfer;
|
||||
}
|
||||
|
||||
@@ -436,6 +799,24 @@ class WarehouseTransferServiceTest {
|
||||
return transfer;
|
||||
}
|
||||
|
||||
private static WarehouseTransferPallet measuredPallet(String status) {
|
||||
WarehouseTransferPallet pallet = new WarehouseTransferPallet();
|
||||
pallet.setId(61L); pallet.setTransferOrderId(21L); pallet.setCustomerId("TENANT-A");
|
||||
pallet.setSequenceNo(1); pallet.setWarehousePalletId(701L); pallet.setPalletNo("NEW-PALLET-1");
|
||||
pallet.setCartonCount(5); pallet.setLengthCm(new BigDecimal("100.00"));
|
||||
pallet.setWidthCm(new BigDecimal("100.00")); pallet.setHeightCm(new BigDecimal("100.00"));
|
||||
pallet.setVolumeCbm(new BigDecimal("1.000000")); pallet.setGrossWeightKg(new BigDecimal("50.000"));
|
||||
pallet.setStatus(status);
|
||||
return pallet;
|
||||
}
|
||||
|
||||
private static WarehouseTransferItem transferItem(long id, String sku, int packages) {
|
||||
WarehouseTransferItem item = new WarehouseTransferItem();
|
||||
item.setId(id); item.setTransferOrderId(21L); item.setCustomerId("TENANT-A");
|
||||
item.setSkuCode(sku); item.setProductNameZh("Product"); item.setPackageCount(packages);
|
||||
return item;
|
||||
}
|
||||
|
||||
private static LastlegOrder tailOrder() {
|
||||
LastlegOrder order = historicalOrder();
|
||||
order.setId(31L); order.setCurrentStatus("PENDING_ASSIGNMENT");
|
||||
|
||||
Reference in New Issue
Block a user