Merge branch 'dev-lyhfive' of http://git.buggoglobal.com/java-project/baiyue-firstleg into dev
This commit is contained in:
+29
-1
@@ -18,6 +18,8 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
@@ -31,7 +33,7 @@ class LastlegOrderControllerTest {
|
||||
private final LastlegOrderService service = mock(LastlegOrderService.class);
|
||||
private final LastlegOrderController controller = controller();
|
||||
private final LastlegOrderRequests.Query query = new LastlegOrderRequests.Query(
|
||||
1, 20, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
|
||||
@Test
|
||||
void platformEndpointRejectsMerchantIdentity() {
|
||||
@@ -76,6 +78,32 @@ class LastlegOrderControllerTest {
|
||||
verify(service).containerProgressDetail("BOOKING-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exportBuildsCsvBeforeReturningTheResponse() {
|
||||
byte[] csv = "csv".getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
when(service.exportCsv("200", query)).thenReturn(csv);
|
||||
|
||||
var response = controller.export(user(0L, "PLATFORM"), "200", query);
|
||||
|
||||
assertArrayEquals(csv, response.getBody());
|
||||
verify(service).exportCsv("200", query);
|
||||
}
|
||||
|
||||
@Test
|
||||
void platformOperatorCanExportAllCustomersWithoutFilters() {
|
||||
byte[] csv = "all customers".getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
LastlegOrderRequests.Query emptyQuery = new LastlegOrderRequests.Query(
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
when(service.exportCsv(null, emptyQuery)).thenReturn(csv);
|
||||
|
||||
var response = controller.export(user(0L, "PLATFORM"), null, emptyQuery);
|
||||
|
||||
assertEquals(Long.valueOf(1), emptyQuery.page());
|
||||
assertEquals(Long.valueOf(20), emptyQuery.size());
|
||||
assertArrayEquals(csv, response.getBody());
|
||||
verify(service).exportCsv(isNull(), eq(emptyQuery));
|
||||
}
|
||||
|
||||
@Test
|
||||
void iamSecurityEventsAreRejectedBeforeLoadingCompletedConsumer() {
|
||||
EventEnvelope event = mock(EventEnvelope.class);
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ class MerchantLastlegOrderControllerTest {
|
||||
private final LastlegOrderService service = mock(LastlegOrderService.class);
|
||||
private final MerchantLastlegOrderController controller = controller();
|
||||
private final LastlegOrderRequests.Query query = new LastlegOrderRequests.Query(
|
||||
1, 20, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||
|
||||
@Test
|
||||
void merchantListUsesOnlyAuthenticatedTenant() {
|
||||
|
||||
+8
-8
@@ -1,10 +1,13 @@
|
||||
package com.baiyue.lastleg.controller;
|
||||
|
||||
import com.baiyue.lastleg.delivery.controller.DeliveryTransferController;
|
||||
import com.baiyue.lastleg.delivery.controller.OutboundPdaController;
|
||||
import com.baiyue.lastleg.dispatch.controller.DispatchController;
|
||||
import com.baiyue.lastleg.dispatch.controller.DispatchQueryController;
|
||||
import com.baiyue.lastleg.dispatch.controller.SmartDispatchController;
|
||||
import com.baiyue.lastleg.dispatch.pda.controller.DispatchPdaController;
|
||||
import com.baiyue.lastleg.customervisit.controller.CustomerVisitController;
|
||||
import com.baiyue.lastleg.exceptionorder.controller.ExceptionOrderController;
|
||||
import com.baiyue.lastleg.inboundplan.pda.controller.ReceivingContainerController;
|
||||
import com.baiyue.lastleg.inventory.command.controller.InventoryCommandController;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -39,14 +42,12 @@ class Phase4BrowserApiBoundaryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void excludedOutboundControllersAreDisabledUnlessExplicitlyEnabledLater() {
|
||||
void outboundControllersAreNotPropertyGated() {
|
||||
for (Class<?> type : new Class<?>[] {
|
||||
DispatchController.class, DeliveryTransferController.class, DispatchPdaController.class
|
||||
DispatchController.class, DeliveryTransferController.class, DispatchPdaController.class,
|
||||
OutboundPdaController.class, CustomerVisitController.class, ExceptionOrderController.class
|
||||
}) {
|
||||
ConditionalOnProperty condition = type.getAnnotation(ConditionalOnProperty.class);
|
||||
assertTrue(condition != null, type.getSimpleName());
|
||||
assertTrue(Arrays.asList(condition.name()).contains("lastleg.future-outbound-api.enabled"));
|
||||
assertFalse(condition.matchIfMissing());
|
||||
assertFalse(type.isAnnotationPresent(ConditionalOnProperty.class), type.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +66,10 @@ class Phase4BrowserApiBoundaryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchQueryIsEnabledByDefaultWithoutEnablingOutboundCommands() throws Exception {
|
||||
void dispatchQueryAndSmartDispatchAreEnabledByDefault() throws Exception {
|
||||
String yaml = Files.readString(Path.of("src/main/resources/application.yml"));
|
||||
assertTrue(yaml.contains("enabled: ${LASTLEG_DISPATCH_QUERY_API_ENABLED:true}"));
|
||||
assertTrue(yaml.contains("enabled: ${LASTLEG_SMART_DISPATCH_API_ENABLED:true}"));
|
||||
assertFalse(yaml.contains("future-outbound-api:"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.baiyue.lastleg.delivery;
|
||||
|
||||
import com.baiyue.common.web.LongIdJacksonConfiguration;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryRequests;
|
||||
import com.baiyue.lastleg.delivery.dto.DeliveryResponses;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("派送单长整型 ID JSON 契约")
|
||||
class DeliveryLongIdJsonContractTest {
|
||||
|
||||
private static final long DELIVERY_ID = 19_000_000_000_000_001L;
|
||||
private static final long FEE_ID = 19_000_000_000_000_002L;
|
||||
private static final long OPERATION_ID = 19_000_000_000_000_003L;
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(
|
||||
JacksonAutoConfiguration.class, LongIdJacksonConfiguration.class));
|
||||
|
||||
@Test
|
||||
void deliveryResponseIdsShouldBeSerializedAsJsonStrings() {
|
||||
contextRunner.run(context -> {
|
||||
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
|
||||
DeliveryResponses.DeliveryDetail detail = new DeliveryResponses.DeliveryDetail(
|
||||
deliveryRow(),
|
||||
List.of(),
|
||||
List.of(new DeliveryResponses.DeliveryFeeRow(
|
||||
FEE_ID, "RECEIVABLE", "DELIVERY_FEE", "USD", null, null)),
|
||||
List.of(new DeliveryResponses.DeliveryOperationRow(
|
||||
OPERATION_ID, null, "CREATED", "tester", "success")));
|
||||
|
||||
JsonNode json = objectMapper.readTree(objectMapper.writeValueAsString(detail));
|
||||
|
||||
assertThat(json.at("/order/id").isTextual()).isTrue();
|
||||
assertThat(json.at("/order/id").textValue()).isEqualTo(Long.toString(DELIVERY_ID));
|
||||
assertThat(json.at("/fees/0/id").textValue()).isEqualTo(Long.toString(FEE_ID));
|
||||
assertThat(json.at("/operations/0/id").textValue()).isEqualTo(Long.toString(OPERATION_ID));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void quotedRequestIdShouldBeDeserializedToLong() {
|
||||
contextRunner.run(context -> {
|
||||
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
|
||||
|
||||
DeliveryRequests.OneClickItem item = objectMapper.readValue(
|
||||
"{\"inventoryPositionId\":\"19000000000000001\",\"packages\":\"2\",\"sku\":\"SKU-1\"}",
|
||||
DeliveryRequests.OneClickItem.class);
|
||||
|
||||
assertThat(item.inventoryPositionId()).isEqualTo(DELIVERY_ID);
|
||||
assertThat(item.packages()).isEqualTo(2L);
|
||||
});
|
||||
}
|
||||
|
||||
private DeliveryResponses.DeliveryRow deliveryRow() {
|
||||
return new DeliveryResponses.DeliveryRow(
|
||||
DELIVERY_ID,
|
||||
null, null, "PS-1", null, null, null, null, "CREATED", null,
|
||||
null, null, null, null, null, null, null, null, null, null, null,
|
||||
0, 0, null, null, null, null, null, null, null, null);
|
||||
}
|
||||
}
|
||||
+29
-9
@@ -7,6 +7,8 @@ import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundOrder;
|
||||
import com.baiyue.lastleg.delivery.entity.OutboundPalletItem;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryFeeMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.DeliveryOperationMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
@@ -19,6 +21,8 @@ import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -28,6 +32,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@@ -47,10 +52,13 @@ class DeliveryTransferServiceTest {
|
||||
@Mock OutboundOrderMapper outboundOrders;
|
||||
@Mock OutboundPalletItemMapper outboundPallets;
|
||||
@Mock DeliveryOrderMapper deliveryOrders;
|
||||
@Mock DeliveryFeeMapper deliveryFees;
|
||||
@Mock DeliveryOperationMapper deliveryOperations;
|
||||
@Mock InboundPlanPalletAllocationMapper planAllocations;
|
||||
@Mock InboundPlanLineMapper planLines;
|
||||
@Mock LastlegOrderMapper tailOrders;
|
||||
@Mock LastlegOrderStatusHistoryMapper histories;
|
||||
@Mock LastlegOrderDetailMapper tailOrderDetails;
|
||||
|
||||
private DeliveryTransferService service;
|
||||
|
||||
@@ -62,10 +70,14 @@ class DeliveryTransferServiceTest {
|
||||
ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders);
|
||||
ReflectionTestUtils.setField(service, "outboundPallets", outboundPallets);
|
||||
ReflectionTestUtils.setField(service, "deliveryOrders", deliveryOrders);
|
||||
ReflectionTestUtils.setField(service, "deliveryFees", deliveryFees);
|
||||
ReflectionTestUtils.setField(service, "deliveryOperations", deliveryOperations);
|
||||
ReflectionTestUtils.setField(service, "planAllocations", planAllocations);
|
||||
ReflectionTestUtils.setField(service, "planLines", planLines);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", new LastlegOrderNoGenerator());
|
||||
ReflectionTestUtils.setField(service, "tailOrders", tailOrders);
|
||||
ReflectionTestUtils.setField(service, "histories", histories);
|
||||
ReflectionTestUtils.setField(service, "tailOrderDetails", tailOrderDetails);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,7 +91,7 @@ class DeliveryTransferServiceTest {
|
||||
));
|
||||
|
||||
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
||||
new DeliveryRequests.Transfer("transfer-101", "ready"));
|
||||
transferRequest("transfer-101", "ready"));
|
||||
|
||||
ArgumentCaptor<OutboundOrder> outbound = ArgumentCaptor.forClass(OutboundOrder.class);
|
||||
ArgumentCaptor<DeliveryOrder> delivery = ArgumentCaptor.forClass(DeliveryOrder.class);
|
||||
@@ -92,10 +104,10 @@ class DeliveryTransferServiceTest {
|
||||
assertEquals(2, outbound.getValue().getPalletCount());
|
||||
assertEquals(20, outbound.getValue().getPackageCount());
|
||||
assertEquals("transfer-101", outbound.getValue().getIdempotencyKey());
|
||||
assertTrue(outbound.getValue().getOutboundNo().startsWith("OUT"));
|
||||
assertTrue(outbound.getValue().getOutboundNo().matches("OB-WH-\\d{8}-\\d{6}"));
|
||||
assertEquals(outbound.getValue().getId(), delivery.getValue().getOutboundOrderId());
|
||||
assertEquals("WAIT_DELIVERY", delivery.getValue().getStatus());
|
||||
assertTrue(delivery.getValue().getDeliveryNo().startsWith("DEL"));
|
||||
assertTrue(delivery.getValue().getDeliveryNo().matches("DL-WH-\\d{8}-\\d{6}"));
|
||||
assertEquals(List.of("ONT8", "LAX9"), pallets.getAllValues().stream()
|
||||
.map(OutboundPalletItem::getDestinationWarehouseCode).toList());
|
||||
assertEquals(List.of("IN-A", "IN-B"), pallets.getAllValues().stream()
|
||||
@@ -116,7 +128,7 @@ class DeliveryTransferServiceTest {
|
||||
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
||||
|
||||
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
||||
new DeliveryRequests.Transfer(" transfer-101 ", null));
|
||||
transferRequest(" transfer-101 ", null));
|
||||
|
||||
assertTrue(result.replayed());
|
||||
assertEquals("OUT-301", result.outboundNo());
|
||||
@@ -135,7 +147,7 @@ class DeliveryTransferServiceTest {
|
||||
when(outboundOrders.findByIdempotency("TENANT-A", "shared-key")).thenReturn(other);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
||||
service.transfer("TENANT-A", "DSP-102", "U-1", new DeliveryRequests.Transfer("shared-key", null)));
|
||||
service.transfer("TENANT-A", "DSP-102", "U-1", transferRequest("shared-key", null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(outboundOrders, never()).insert((OutboundOrder) any());
|
||||
@@ -155,7 +167,7 @@ class DeliveryTransferServiceTest {
|
||||
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
||||
|
||||
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
||||
new DeliveryRequests.Transfer("transfer-101", null));
|
||||
transferRequest("transfer-101", null));
|
||||
|
||||
assertTrue(result.replayed());
|
||||
assertEquals("OUT-301", result.outboundNo());
|
||||
@@ -176,13 +188,15 @@ class DeliveryTransferServiceTest {
|
||||
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(source));
|
||||
when(tailOrders.selectList(any())).thenReturn(List.of(tailOrder));
|
||||
|
||||
service.transfer("TENANT-A", "DSP-101", "U-1", new DeliveryRequests.Transfer("direct-order", null));
|
||||
service.transfer("TENANT-A", "DSP-101", "U-1", transferRequest("direct-order", null));
|
||||
|
||||
assertEquals("WAIT_OUTBOUND", tailOrder.getCurrentStatus());
|
||||
assertNotNull(tailOrder.getOutboundOrderNo());
|
||||
assertNotNull(tailOrder.getDeliveryOrderNo());
|
||||
verify(tailOrders).updateById(tailOrder);
|
||||
verify(histories).insert(any(LastlegOrderStatusHistory.class));
|
||||
ArgumentCaptor<LastlegOrderStatusHistory> history = ArgumentCaptor.forClass(LastlegOrderStatusHistory.class);
|
||||
verify(histories).insert(history.capture());
|
||||
assertEquals("TENANT-A", history.getValue().getCustomerId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -195,7 +209,7 @@ class DeliveryTransferServiceTest {
|
||||
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(planned));
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
||||
service.transfer("TENANT-A", "DSP-101", "U-1", new DeliveryRequests.Transfer("transfer-101", null)));
|
||||
service.transfer("TENANT-A", "DSP-101", "U-1", transferRequest("transfer-101", null)));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
assertTrue(exception.getReason().contains("received"));
|
||||
@@ -239,6 +253,12 @@ class DeliveryTransferServiceTest {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static DeliveryRequests.Transfer transferRequest(String idempotencyKey, String remark) {
|
||||
return new DeliveryRequests.Transfer(idempotencyKey, LocalDateTime.now(), "SELF_DELIVERY", null, null,
|
||||
"ONE_PICKUP_ONE_DROP", "PALLET", null, null, null, null, null, null, null, remark,
|
||||
List.of(new DeliveryRequests.FeeItem("DELIVERY_FEE", "USD", BigDecimal.ZERO, null)));
|
||||
}
|
||||
|
||||
private static DispatchItem item(Long id, Long warehousePalletId, String palletNo, String destinationCode,
|
||||
String destinationName, String inboundCode, int packages) {
|
||||
DispatchItem value = new DispatchItem();
|
||||
|
||||
+2
@@ -14,6 +14,7 @@ import com.baiyue.lastleg.dropship.entity.DropshipCarton;
|
||||
import com.baiyue.lastleg.dropship.entity.DropshipSkuInventory;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -63,6 +64,7 @@ class CargoDiscardServiceTest {
|
||||
ReflectionTestUtils.setField(service, "histories", histories);
|
||||
ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
ReflectionTestUtils.setField(service, "adjustments", adjustments);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", new LastlegOrderNoGenerator());
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "feePerPackage", new BigDecimal("5.00"));
|
||||
|
||||
+49
@@ -2,11 +2,17 @@ package com.baiyue.lastleg.dispatch.pda.service;
|
||||
|
||||
import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchOrder;
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchItem;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper;
|
||||
import com.baiyue.lastleg.dispatch.mapper.DispatchOrderMapper;
|
||||
import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaRequests;
|
||||
import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaResponses;
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaOperation;
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaPick;
|
||||
import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaTask;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaOperationMapper;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickMapper;
|
||||
import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaTaskMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -15,7 +21,10 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -24,6 +33,16 @@ class DispatchPdaWorkflowServiceTest {
|
||||
@Mock private DispatchOrderMapper orders;
|
||||
@Mock private DispatchPdaOperationMapper operations;
|
||||
@Mock private OutboundOrderMapper outboundOrders;
|
||||
@Mock private DispatchItemMapper items;
|
||||
@Mock private DispatchPdaTaskMapper tasks;
|
||||
@Mock private DispatchPdaPickMapper picks;
|
||||
|
||||
@Test
|
||||
void labelsTransferredPalletsWithOriginalAndCurrentFbaWarehouses() {
|
||||
assertEquals("ONT8 -> ONT", DispatchPdaWorkflowService.warehouseRouteLabel("ONT8", "ONT"));
|
||||
assertNull(DispatchPdaWorkflowService.warehouseRouteLabel("ONT8", "ont8"));
|
||||
assertNull(DispatchPdaWorkflowService.warehouseRouteLabel(null, "ONT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushRequiresVehiclePlateBeforeCreatingPdaTask() {
|
||||
@@ -118,4 +137,34 @@ class DispatchPdaWorkflowServiceTest {
|
||||
assertEquals("DEPARTED", result.status());
|
||||
assertEquals(true, result.replayed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void completingAnAutomaticallyCompletedTaskReturnsIdempotentSuccess() {
|
||||
DispatchPdaWorkflowService service = new DispatchPdaWorkflowService();
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "operations", operations);
|
||||
ReflectionTestUtils.setField(service, "items", items);
|
||||
ReflectionTestUtils.setField(service, "tasks", tasks);
|
||||
ReflectionTestUtils.setField(service, "picks", picks);
|
||||
DispatchOrder order = new DispatchOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setDispatchNo("PC1");
|
||||
order.setStatus("READY_TO_DEPART"); order.setVehiclePlate("CA-100");
|
||||
DispatchPdaTask task = new DispatchPdaTask();
|
||||
task.setId(2L); task.setStatus("COMPLETED");
|
||||
DispatchItem item = new DispatchItem(); item.setPackageCount(6);
|
||||
DispatchPdaPick pick = new DispatchPdaPick(); pick.setPackages(6L);
|
||||
DispatchPdaRequests.CompletePicking request = new DispatchPdaRequests.CompletePicking("complete-1", "normal");
|
||||
when(orders.findByDispatchNo("100", "PC1")).thenReturn(order);
|
||||
when(orders.lockById("100", 1L)).thenReturn(order);
|
||||
when(operations.findKey("100", "complete-1")).thenReturn(null);
|
||||
when(tasks.lockByDispatch("100", 1L)).thenReturn(task);
|
||||
when(items.findByDispatchOrder("100", 1L)).thenReturn(List.of(item));
|
||||
when(picks.listTask("100", 2L)).thenReturn(List.of(pick));
|
||||
|
||||
DispatchPdaResponses.PickingCompleted result = service.completePicking("100", "PC1", request);
|
||||
|
||||
assertEquals("READY_TO_DEPART", result.status());
|
||||
assertEquals(6, result.pickedPackages());
|
||||
assertEquals(true, result.replayed());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -75,8 +75,8 @@ class DispatchPickOrderAttributionServiceTest {
|
||||
"t1", "OUT1", "D1", List.of(pick), LocalDateTime.of(2026, 8, 17, 15, 0));
|
||||
|
||||
assertEquals(List.of(new DispatchPickOrderAttributionService.OrderPackages(101L, 4L)), result);
|
||||
verify(history).insert(argThat((LastlegOrderStatusHistory value) -> "PARTIALLY_SHIPPED".equals(value.getToStatus())
|
||||
&& value.getRemark().contains("6/10")));
|
||||
verify(history).insert(argThat((LastlegOrderStatusHistory value) -> "t1".equals(value.getCustomerId())
|
||||
&& "PARTIALLY_SHIPPED".equals(value.getToStatus()) && value.getRemark().contains("6/10")));
|
||||
}
|
||||
|
||||
private DispatchPickOrderAttributionService service() {
|
||||
|
||||
+1
@@ -56,6 +56,7 @@ class DispatchServiceTest {
|
||||
ReflectionTestUtils.setField(service, "candidates", candidates);
|
||||
ReflectionTestUtils.setField(service, "drafts", drafts);
|
||||
ReflectionTestUtils.setField(service, "draftContainers", draftContainers);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator());
|
||||
lenient().when(items.findByDispatchOrder(any(), any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ class DropshipOrderServiceTest {
|
||||
when(cartons.listProfile("C-1", 2L)).thenReturn(List.of(secondCarton));
|
||||
|
||||
service.activateAfterStockIn("C-1", 10L, 50L, List.of(
|
||||
receipt(101L, "WH-P-1", 201L, 1), receipt(102L, "WH-P-2", 202L, 1)));
|
||||
receipt(101L, "SOURCE-P-1", 201L, 1), receipt(102L, "SOURCE-P-2", 202L, 1)));
|
||||
|
||||
assertThat(first.getStatus()).isEqualTo("ACTIVE");
|
||||
assertThat(first.getWarehousePalletId()).isEqualTo(101L);
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.baiyue.lastleg.exceptionorder.service;
|
||||
|
||||
import com.baiyue.lastleg.entity.LastlegInboundOrder;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.exceptionorder.mapper.ExceptionOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegInboundOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ExceptionOrderServiceTest {
|
||||
@Mock private ExceptionOrderMapper exceptionOrders;
|
||||
@Mock private LastlegOrderMapper lastMileOrders;
|
||||
@Mock private LastlegInboundOrderMapper inboundOrders;
|
||||
private ExceptionOrderService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new ExceptionOrderService();
|
||||
ReflectionTestUtils.setField(service, "exceptionOrders", exceptionOrders);
|
||||
ReflectionTestUtils.setField(service, "lastMileOrders", lastMileOrders);
|
||||
ReflectionTestUtils.setField(service, "inboundOrders", inboundOrders);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesTheOneToOneInboundAndLastMileRelation() {
|
||||
LastlegOrder tail = tail("LM-1", "IN-1");
|
||||
LastlegInboundOrder inbound = inbound("IN-1");
|
||||
when(lastMileOrders.selectList(any())).thenReturn(List.of(tail));
|
||||
when(inboundOrders.selectList(any())).thenReturn(List.of(inbound));
|
||||
|
||||
var relation = service.resolve(null, null, "IN-1", "LM-1");
|
||||
|
||||
assertThat(relation.lastMileOrderNo()).isEqualTo("LM-1");
|
||||
assertThat(relation.inboundCode()).isEqualTo("IN-1");
|
||||
assertThat(relation.customerId()).isEqualTo("1001");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsInputsThatExistButDoNotBelongTogether() {
|
||||
when(lastMileOrders.selectList(any())).thenReturn(List.of(tail("LM-1", "IN-1")));
|
||||
when(inboundOrders.selectList(any())).thenReturn(List.of(inbound("IN-2")));
|
||||
|
||||
assertThatThrownBy(() -> service.resolve(null, null, "IN-2", "LM-1"))
|
||||
.isInstanceOf(ResponseStatusException.class)
|
||||
.hasMessageContaining("不是同一关联关系");
|
||||
}
|
||||
|
||||
private LastlegOrder tail(String orderNo, String inboundCode) {
|
||||
LastlegOrder value = new LastlegOrder();
|
||||
value.setId(12L); value.setCustomerId("1001"); value.setOrderNo(orderNo); value.setInboundCode(inboundCode);
|
||||
value.setCustomerName("测试客户"); value.setPackageCount(10);
|
||||
return value;
|
||||
}
|
||||
|
||||
private LastlegInboundOrder inbound(String inboundCode) {
|
||||
LastlegInboundOrder value = new LastlegInboundOrder();
|
||||
value.setId(11L); value.setCustomerId("1001"); value.setInboundCode(inboundCode); value.setPackageCount(10);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.baiyue.lastleg.inboundplan.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class InboundPlanOrderMapperContractTest {
|
||||
@Test
|
||||
void transferWarehouseIsTheEffectivePalletDestination() throws Exception {
|
||||
Select select = InboundPlanOrderMapper.class
|
||||
.getMethod("lockOrder", String.class, Long.class)
|
||||
.getAnnotation(Select.class);
|
||||
String sql = String.join(" ", select.value());
|
||||
|
||||
assertTrue(sql.contains("COALESCE(NULLIF(o.transfer_warehouse_id,''),MIN(NULLIF(d.destination_warehouse_id,'')))"));
|
||||
assertTrue(sql.contains("COALESCE(NULLIF(o.transfer_warehouse_code,''),MIN(NULLIF(d.destination_warehouse_code,'')))"));
|
||||
assertTrue(sql.contains("THEN 1 ELSE COUNT(DISTINCT"));
|
||||
assertTrue(sql.contains("0 AS destinationMismatchCount"));
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.baiyue.lastleg.inboundplan.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class InboundPlanQueryMapperContractTest {
|
||||
@Test
|
||||
void platformListReadsVersionAndDoesNotRequireANullDispatchCustomer() throws Exception {
|
||||
Select select = InboundPlanQueryMapper.class
|
||||
.getMethod("rows", String.class,
|
||||
com.baiyue.lastleg.inboundplan.dto.InboundPlanRequests.Query.class,
|
||||
long.class, long.class)
|
||||
.getAnnotation(Select.class);
|
||||
String sql = String.join(" ", select.value());
|
||||
|
||||
assertTrue(sql.contains("p.version version"));
|
||||
assertTrue(sql.contains("WHERE <if test='customerId != null and customerId != \"\"'>di.customer_id=#{customerId} AND </if>di.order_id=l.order_id"));
|
||||
}
|
||||
}
|
||||
+111
@@ -12,6 +12,8 @@ import com.baiyue.lastleg.inboundplan.pda.entity.InboundPdaTask;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.gateway.UnloadPhotoFileGateway;
|
||||
import com.baiyue.lastleg.inboundplan.pda.repository.PdaWorkflowRepository;
|
||||
import com.baiyue.lastleg.dropship.service.DropshipOrderService;
|
||||
import com.baiyue.lastleg.dropship.model.DropshipPalletSnapshot;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -51,6 +53,7 @@ class InboundPdaWorkflowServiceTest {
|
||||
@Mock InboundUnloadCompletedOutboxService unloadCompletedOutbox;
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock UnloadPhotoFileGateway unloadPhotoFiles;
|
||||
@Mock DropshipOrderService dropshipOrders;
|
||||
private InboundPdaWorkflowService service;
|
||||
|
||||
private InboundPlan plan;
|
||||
@@ -73,6 +76,7 @@ class InboundPdaWorkflowServiceTest {
|
||||
ReflectionTestUtils.setField(service, "unloadCompletedOutbox", unloadCompletedOutbox);
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "unloadPhotoFiles", unloadPhotoFiles);
|
||||
ReflectionTestUtils.setField(service, "dropshipOrders", dropshipOrders);
|
||||
ReflectionTestUtils.setField(service, "sourceObjectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
service.initializeObjectMapper();
|
||||
plan = plan();
|
||||
@@ -189,6 +193,20 @@ class InboundPdaWorkflowServiceTest {
|
||||
assertEquals(3, targetPallet.getActualPackageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cartonScanRejectsBatchQuantityAndRequiresOneRealCartonPerScan() {
|
||||
InboundPlanCarton first = carton("BOX-BATCH-1", "UNASSIGNED", targetLine.getId(), "WH-A");
|
||||
InboundPlanCarton second = carton("BOX-BATCH-2", "UNASSIGNED", targetLine.getId(), "WH-A");
|
||||
second.setId(42L);
|
||||
activeTask();
|
||||
when(repository.lockCarton("100", "BOX-BATCH-1")).thenReturn(first);
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.scanCarton("100", "LIP1",
|
||||
new PdaRequests.ScanCarton("BOX-BATCH-1", null, 2, "scan-batch", "7", "worker")));
|
||||
assertEquals("UNASSIGNED", first.getStatus());
|
||||
assertEquals("UNASSIGNED", second.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cartonScanRejectsDuplicateAndWrongDestination() {
|
||||
InboundPlanCarton repeated = carton("BOX-002", "ON_PALLET", targetLine.getId(), "WH-A");
|
||||
@@ -225,7 +243,9 @@ class InboundPdaWorkflowServiceTest {
|
||||
|
||||
@Test
|
||||
void countMustPrecedePickAndPickedBoxesDriveActualVolume() {
|
||||
targetLine.setFbaWarehouseCode("ONT8");
|
||||
activeTask();
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
target.setCountedPackageCount(0);
|
||||
when(repository.lockAllocation(plan.getId(), target.getId())).thenReturn(target);
|
||||
when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine);
|
||||
@@ -248,7 +268,9 @@ class InboundPdaWorkflowServiceTest {
|
||||
|
||||
@Test
|
||||
void overPickReturnsConfirmationWithoutChangingAllocationOrPallet() {
|
||||
targetLine.setFbaWarehouseCode("ONT8");
|
||||
activeTask();
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
when(repository.lockAllocation(plan.getId(), target.getId())).thenReturn(target);
|
||||
when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine);
|
||||
candidateQueries();
|
||||
@@ -459,8 +481,95 @@ class InboundPdaWorkflowServiceTest {
|
||||
verify(repository, never()).update(task);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialCompleteStocksOnlyActualPickedPackagesAndKeepsPartialStatus() {
|
||||
target.setPickedPackageCount(2);
|
||||
activeTask();
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
when(repository.allocations(plan.getId())).thenReturn(List.of(target));
|
||||
when(repository.pallets(plan.getId())).thenReturn(List.of(targetPallet));
|
||||
when(repository.cartons(plan.getId())).thenReturn(List.of());
|
||||
|
||||
var result = service.partialComplete("100", "LIP1",
|
||||
new PdaRequests.ForceComplete("partial-1", "7", "worker"));
|
||||
|
||||
assertEquals("PARTIAL_INBOUND", result.status());
|
||||
assertEquals(5, result.expectedPackageCount());
|
||||
assertEquals(2, result.pickedPackageCount());
|
||||
assertEquals("PARTIAL_INBOUND", plan.getStatus());
|
||||
verify(stockReceipts).stockIn(eq("100"), eq(plan), eq(task), eq(List.of(targetLine)),
|
||||
eq(List.of(target)), eq(List.of(targetPallet)), any(), eq(true));
|
||||
verify(unloadCompletedOutbox).publish(eq(plan), eq(task), eq(List.of(101L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialCompleteRequiresUnloadPhotosBeforeStockIn() {
|
||||
target.setPickedPackageCount(2);
|
||||
task.setUnloadPhotoFileIds(null);
|
||||
task.setUnloadPhotoUploadedAt(null);
|
||||
task.setEstimatedEmptyReturnAt(null);
|
||||
activeTask();
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.partialComplete("100", "LIP1",
|
||||
new PdaRequests.ForceComplete("partial-no-photo", "7", "worker")));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(stockReceipts, never()).stockIn(any(), any(), any(), any(), any(), any(), any(), eq(true));
|
||||
verify(unloadCompletedOutbox, never()).publish(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropshipTaskIsMarkedAndWholePalletPickReceivesThePlannedPalletAtOnce() {
|
||||
plan.setSourceOrderId(101L);
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setId(101L); order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(repository.order(101L)).thenReturn(order);
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(
|
||||
new DropshipPalletSnapshot("PLT-21", 3,
|
||||
List.of(new DropshipPalletSnapshot.Sku("SKU-A", "Product A", 3, 30)))));
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
when(repository.allocations(plan.getId())).thenReturn(List.of(target));
|
||||
when(repository.pallets(plan.getId())).thenReturn(List.of(targetPallet));
|
||||
when(repository.task("100", plan.getId())).thenReturn(task);
|
||||
activeTask();
|
||||
when(repository.lockAllocation(plan.getId(), target.getId())).thenReturn(target);
|
||||
when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine);
|
||||
when(repository.lockPallet(plan.getId(), targetPallet.getId())).thenReturn(targetPallet);
|
||||
|
||||
var taskResult = service.task("100", "LIP1");
|
||||
var pickResult = service.pickWholePallet("100", "LIP1", target.getId(),
|
||||
new PdaRequests.WholePalletOperation("whole-1", "7", "worker"));
|
||||
|
||||
assertEquals("ONE_CLICK_DROPSHIP", taskResult.orderType());
|
||||
assertTrue(taskResult.wholePalletInbound());
|
||||
assertEquals("SKU-A", taskResult.dropshipPallets().get(0).skus().get(0).skuCode());
|
||||
assertEquals(targetPallet.getId(), taskResult.dropshipPallets().get(0).planPalletId());
|
||||
assertEquals(target.getId(), taskResult.dropshipPallets().get(0).allocationId());
|
||||
assertEquals("WHOLE_PALLET_PICKED", pickResult.result());
|
||||
assertEquals(3, target.getPickedPackageCount());
|
||||
assertEquals(3, targetPallet.getActualPackageCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dropshipPlanRejectsOrdinaryPackagePicking() {
|
||||
plan.setSourceOrderId(101L);
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setId(101L); order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(repository.order(101L)).thenReturn(order);
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.pick("100", "LIP1", target.getId(),
|
||||
new PdaRequests.PackageOperation(1, "wrong-mode", "7", "worker")));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(repository, never()).lockAllocation(plan.getId(), target.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsReuseOfIdempotencyKeyWithDifferentActionOrPayload() {
|
||||
targetLine.setFbaWarehouseCode("ONT8");
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
InboundPdaOperation existing = new InboundPdaOperation();
|
||||
existing.setAction("COUNT");
|
||||
existing.setRequestHash("different");
|
||||
@@ -472,7 +581,9 @@ class InboundPdaWorkflowServiceTest {
|
||||
|
||||
@Test
|
||||
void completedTaskStillReplaysAnAlreadyPersistedPick() {
|
||||
targetLine.setFbaWarehouseCode("ONT8");
|
||||
activeTask();
|
||||
when(repository.lines(plan.getId())).thenReturn(List.of(targetLine));
|
||||
when(repository.lockAllocation(plan.getId(), target.getId())).thenReturn(target);
|
||||
when(repository.line(plan.getId(), targetLine.getId())).thenReturn(targetLine);
|
||||
when(repository.lockPallet(plan.getId(), targetPallet.getId())).thenReturn(targetPallet);
|
||||
|
||||
+56
@@ -19,6 +19,7 @@ import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.dropship.service.DropshipOrderService;
|
||||
import com.baiyue.lastleg.dropship.model.DropshipPalletSnapshot;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -157,6 +158,21 @@ class InboundStockReceiptServiceTest {
|
||||
assertEquals(5L, requests.getAllValues().stream().mapToLong(InventoryCommandRequests.StockIn::packages).sum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stocksInToHeldInventoryWhenInboundOrderIsDetained() {
|
||||
Fixture fixture = fixture(5, 5, 5, 10);
|
||||
fixture.order.setDetained(true);
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(inventory.stockInHeld(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
|
||||
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 5, 0, 0, 0, false, false));
|
||||
|
||||
service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
|
||||
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now());
|
||||
|
||||
verify(inventory).stockInHeld(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class));
|
||||
verify(inventory, never()).stockIn(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void existingTaskReceiptMakesStockInReplayANoOp() {
|
||||
Fixture fixture = fixture(10, 5, 5, 10);
|
||||
@@ -200,6 +216,7 @@ class InboundStockReceiptServiceTest {
|
||||
void activatesDropshipAfterGenericStockInReceiptIsCreated() {
|
||||
Fixture fixture = fixture(5, 5, 5, 10);
|
||||
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(dropshipPallet("PLT-21", 5)));
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(inventory.stockIn(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
|
||||
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 5, 0, 0, 0, false, false));
|
||||
@@ -227,6 +244,45 @@ class InboundStockReceiptServiceTest {
|
||||
verify(receipts, never()).insert(any(InboundStockReceipt.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialStockInSynchronizesOrderWithoutActivatingDropship() {
|
||||
Fixture fixture = fixture(5, 2, 2, 10);
|
||||
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(dropshipPallet("PLT-21", 2)));
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(inventory.stockIn(eq("100"), eq("operator"), any(InventoryCommandRequests.StockIn.class)))
|
||||
.thenReturn(new InventoryCommandResponse(8L, "STOCK_IN", 9L, 10L, 2, 0, 0, 0, false, false));
|
||||
|
||||
service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
|
||||
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now(), true);
|
||||
|
||||
assertEquals("INBOUND_COMPLETED", fixture.order.getCurrentStatus());
|
||||
assertEquals("INBOUND_PLAN:PARTIAL_INBOUND", fixture.order.getSubOrderStatus());
|
||||
verify(orders).updateById(fixture.order);
|
||||
verify(orderHistories).insert(any(LastlegOrderStatusHistory.class));
|
||||
verify(dropshipOrders, never()).activateAfterStockIn(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsPartiallyPickedDropshipSourcePallet() {
|
||||
Fixture fixture = fixture(5, 5, 2, 10);
|
||||
fixture.order.setOrderType("ONE_CLICK_DROPSHIP");
|
||||
when(orders.selectBatchIds(any())).thenReturn(List.of(fixture.order));
|
||||
when(dropshipOrders.palletSnapshots("100", 101L)).thenReturn(List.of(dropshipPallet("PLT-21", 5)));
|
||||
|
||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
|
||||
() -> service.stockIn("100", fixture.plan, fixture.task, List.of(fixture.line),
|
||||
List.of(fixture.allocation), List.of(fixture.pallet), LocalDateTime.now(), true));
|
||||
|
||||
assertEquals(409, exception.getStatusCode().value());
|
||||
verify(inventory, never()).stockIn(any(), any(), any());
|
||||
}
|
||||
|
||||
private DropshipPalletSnapshot dropshipPallet(String palletNo, long packages) {
|
||||
return new DropshipPalletSnapshot(palletNo, packages,
|
||||
List.of(new DropshipPalletSnapshot.Sku("SKU-1", "Product", packages, packages * 10)));
|
||||
}
|
||||
|
||||
private Fixture fixture(int required, int planned, int picked, int quantity) {
|
||||
InboundPlan plan = new InboundPlan(); plan.setId(1L); plan.setPlanNo("IP-1"); plan.setOverseasWarehouseId(900L);
|
||||
InboundPdaTask task = new InboundPdaTask(); task.setId(2L); task.setOperatorId("operator");
|
||||
|
||||
+14
@@ -502,6 +502,20 @@ class ReceivingContainerServiceBehaviorTest {
|
||||
verifyNoInteractions(workflow, operations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cartonResolutionReturnsInboundAndPalletMergeReminderFromTailLegOrder() {
|
||||
stubCurrent("FBA", "CONT-A");
|
||||
LastlegOrder marked = order("FBA");
|
||||
marked.setPalletMergeFlag("REQUIRED");
|
||||
when(orders.selectById(1L)).thenReturn(marked);
|
||||
|
||||
var resolved = service.resolveCarton("7", new ReceivingContainerRequests.CartonResolve("IN-1#2-1"));
|
||||
|
||||
assertThat(resolved.inboundCode()).isEqualTo("IN-1");
|
||||
assertThat(resolved.palletMergeRequired()).isTrue();
|
||||
assertThat(resolved.palletMergeHint()).contains("合托");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fbaCartonResolutionRejectsCrossContainerAndInvalidPrefixesAsConflicts() {
|
||||
stubCurrent("FBA", "CONT-A");
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ class InboundPlanPaginationTest {
|
||||
.getMethod("rows", String.class, InboundPlanRequests.Query.class, long.class, long.class)
|
||||
.getAnnotation(org.apache.ibatis.annotations.Select.class).value()) + InboundPlanQueryMapper.FROM_AND_FILTER;
|
||||
assertThat(sql).contains("a2.customer_id=#{customerId}", "ORDER BY a2.created_at DESC,ai2.id DESC LIMIT 1");
|
||||
assertThat(sql).contains("di.customer_id=#{customerId} AND di.order_id=l.order_id");
|
||||
assertThat(sql).contains("di.customer_id=#{customerId} AND </if>di.order_id=l.order_id");
|
||||
assertThat(sql).contains("q.planNo", "q.inboundCode", "q.status", "q.overseasWarehouseId",
|
||||
"q.destinationWarehouseCode", "q.warehouse",
|
||||
"q.fbaWarehouseCode", "q.appointmentStatus", "q.dispatchStatus", "q.createdFrom", "q.createdTo");
|
||||
|
||||
+87
-5
@@ -2,6 +2,7 @@ package com.baiyue.lastleg.inboundplan.service;
|
||||
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlan;
|
||||
import com.baiyue.lastleg.inboundplan.dto.InboundPlanRequests;
|
||||
import com.baiyue.lastleg.inboundplan.dto.UpdateInboundPlanRequest;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanLine;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanPallet;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanPalletAllocation;
|
||||
@@ -55,6 +56,7 @@ class InboundPlanServiceTest {
|
||||
ReflectionTestUtils.setField(service, "allocationMapper", allocationMapper);
|
||||
ReflectionTestUtils.setField(service, "orderMapper", orderMapper);
|
||||
ReflectionTestUtils.setField(service, "inventoryBalanceMapper", inventoryBalanceMapper);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator());
|
||||
org.mockito.Mockito.lenient().when(lineMapper.selectList(any())).thenReturn(List.of());
|
||||
org.mockito.Mockito.lenient().when(palletMapper.selectList(any())).thenReturn(List.of());
|
||||
org.mockito.Mockito.lenient().when(allocationMapper.selectList(any())).thenReturn(List.of());
|
||||
@@ -182,13 +184,93 @@ class InboundPlanServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsTransferWarehouseThatDiffersFromCargoSnapshots() {
|
||||
SourceOrderSnapshot invalid = new SourceOrderSnapshot(1L, "100", "MSCU6639870", "RK-1",
|
||||
void transferWarehouseOverridesOriginalCargoDestination() {
|
||||
SourceOrderSnapshot transferred = new SourceOrderSnapshot(1L, "100", "MSCU6639870", "RK-1",
|
||||
"DESTINATION_ASSIGNED", null, BigDecimal.ONE, 1, "WH-B", "WH-B", "Warehouse B", 1, 1);
|
||||
when(orderMapper.lockOrder("100", 1L)).thenReturn(List.of(invalid));
|
||||
when(orderMapper.lockOrder("100", 1L)).thenReturn(List.of(transferred));
|
||||
when(orderMapper.markPlanned(any(), any(), any(), org.mockito.ArgumentMatchers.anyInt(), any()))
|
||||
.thenReturn(1);
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.convert("100", 1L));
|
||||
verify(planMapper, never()).insert(any(InboundPlan.class));
|
||||
service.convert("100", 1L);
|
||||
|
||||
ArgumentCaptor<InboundPlanPallet> pallet = ArgumentCaptor.forClass(InboundPlanPallet.class);
|
||||
verify(palletMapper).insert(pallet.capture());
|
||||
assertEquals("WH-B", pallet.getValue().getDestinationWarehouseId());
|
||||
assertEquals("WH-B", pallet.getValue().getDestinationWarehouseCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertsDropshipSourcePalletsWithoutRedistributingTheirPackages() {
|
||||
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(order(2L, "RK-2", "1.0000", "WH-A")));
|
||||
when(orderMapper.markPlanned(any(), any(), any(), org.mockito.ArgumentMatchers.anyInt(), any()))
|
||||
.thenReturn(1);
|
||||
|
||||
service.convertDropship("100", 2L, List.of(
|
||||
new InboundPlanService.WholePalletInput("SOURCE-P-1", 3),
|
||||
new InboundPlanService.WholePalletInput("SOURCE-P-2", 7)));
|
||||
|
||||
ArgumentCaptor<InboundPlanPallet> pallets = ArgumentCaptor.forClass(InboundPlanPallet.class);
|
||||
verify(palletMapper, times(2)).insert(pallets.capture());
|
||||
assertEquals(List.of("SOURCE-P-1", "SOURCE-P-2"),
|
||||
pallets.getAllValues().stream().map(InboundPlanPallet::getPalletNo).toList());
|
||||
ArgumentCaptor<InboundPlanPalletAllocation> allocations =
|
||||
ArgumentCaptor.forClass(InboundPlanPalletAllocation.class);
|
||||
verify(allocationMapper, times(2)).insert(allocations.capture());
|
||||
assertEquals(List.of(3, 7), allocations.getAllValues().stream()
|
||||
.map(InboundPlanPalletAllocation::getPlannedPackageCount).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSynchronizesDestinationSnapshotToLinesAndPallets() {
|
||||
InboundPlan plan = new InboundPlan();
|
||||
plan.setId(20L);
|
||||
plan.setPlanNo("IP-20");
|
||||
plan.setCustomerId("100");
|
||||
plan.setStatus("PENDING_ASSIGNMENT");
|
||||
plan.setLineCount(1);
|
||||
plan.setPalletCount(1);
|
||||
plan.setVersion(3);
|
||||
|
||||
InboundPlanLine line = new InboundPlanLine();
|
||||
line.setId(21L);
|
||||
line.setCustomerId("100");
|
||||
line.setPlanId(20L);
|
||||
line.setRequiredPackageCount(1);
|
||||
line.setPlannedPalletCount(1);
|
||||
line.setDestinationWarehouseId("OLD-WH");
|
||||
line.setDestinationWarehouseCode("OLD-CODE");
|
||||
line.setDestinationWarehouseName("Old warehouse");
|
||||
|
||||
InboundPlanPallet pallet = new InboundPlanPallet();
|
||||
pallet.setId(22L);
|
||||
pallet.setCustomerId("100");
|
||||
pallet.setPlanId(20L);
|
||||
pallet.setSequenceNo(1);
|
||||
pallet.setActualPackageCount(0);
|
||||
pallet.setDestinationWarehouseId("OLD-WH");
|
||||
pallet.setDestinationWarehouseCode("OLD-CODE");
|
||||
pallet.setDestinationWarehouseName("Old warehouse");
|
||||
|
||||
when(planMapper.selectOne(any())).thenReturn(plan);
|
||||
when(lineMapper.selectList(any())).thenReturn(List.of(line), List.of(line));
|
||||
when(palletMapper.selectList(any())).thenReturn(List.of(pallet), List.of(pallet));
|
||||
when(allocationMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
service.update("100", "IP-20",
|
||||
new UpdateInboundPlanRequest("100", 193000000000200003L,
|
||||
" US-LAX-01 ", " Los Angeles Warehouse ", 3));
|
||||
|
||||
ArgumentCaptor<InboundPlanLine> updatedLine = ArgumentCaptor.forClass(InboundPlanLine.class);
|
||||
verify(lineMapper).updateById(updatedLine.capture());
|
||||
assertEquals("193000000000200003", updatedLine.getValue().getDestinationWarehouseId());
|
||||
assertEquals("US-LAX-01", updatedLine.getValue().getDestinationWarehouseCode());
|
||||
assertEquals("Los Angeles Warehouse", updatedLine.getValue().getDestinationWarehouseName());
|
||||
|
||||
ArgumentCaptor<InboundPlanPallet> updatedPallet = ArgumentCaptor.forClass(InboundPlanPallet.class);
|
||||
verify(palletMapper).updateById(updatedPallet.capture());
|
||||
assertEquals("193000000000200003", updatedPallet.getValue().getDestinationWarehouseId());
|
||||
assertEquals("US-LAX-01", updatedPallet.getValue().getDestinationWarehouseCode());
|
||||
assertEquals("Los Angeles Warehouse", updatedPallet.getValue().getDestinationWarehouseName());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+10
@@ -49,6 +49,16 @@ class PalletAllocatorTest {
|
||||
.map(PalletAllocator.AllocationDraft::plannedPackageCount).toList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsAtLeastTheFirstLegRecommendedPalletCount() {
|
||||
PalletAllocator.Result result = PalletAllocator.allocate(List.of(
|
||||
new PalletAllocator.LineInput(1L, "WH-A", new BigDecimal("1.5000"), 6, 3)));
|
||||
|
||||
assertEquals(3, result.pallets().size());
|
||||
assertEquals(6, result.allocations().stream()
|
||||
.mapToInt(PalletAllocator.AllocationDraft::plannedPackageCount).sum());
|
||||
}
|
||||
|
||||
private PalletAllocator.Result allocate(String volume) {
|
||||
return PalletAllocator.allocate(List.of(
|
||||
new PalletAllocator.LineInput(1L, "WH-A", new BigDecimal(volume),
|
||||
|
||||
+46
@@ -24,6 +24,8 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -57,6 +59,7 @@ class InventoryCommandServiceTest {
|
||||
ReflectionTestUtils.setField(service, "warehouseGateway", warehouseGateway);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
lenient().when(operations.findIdempotency(any(), any())).thenReturn(null);
|
||||
lenient().when(commands.lockInboundTransitBalances(any(), any(), any())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,6 +120,7 @@ class InventoryCommandServiceTest {
|
||||
balance.setSkuCode("SKU-1");
|
||||
balance.setInTransitPackages(12L);
|
||||
when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(balance);
|
||||
when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(balance));
|
||||
|
||||
InventoryCommandResponse result = service.stockIn("100", "7", stockIn("IN-1", "stock-in-transit", 12, 24));
|
||||
|
||||
@@ -125,6 +129,48 @@ class InventoryCommandServiceTest {
|
||||
assertEquals(12L, balance.getAvailablePackages());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockInTransfersInTransitFromTheOriginalOverseasWarehouse() {
|
||||
InventoryBalance target = balance(200L, 0);
|
||||
target.setSkuCode("SKU-1");
|
||||
target.setInTransitPackages(0L);
|
||||
InventoryBalance source = balance(201L, 0);
|
||||
source.setOverseasWarehouseId("WH-OLD");
|
||||
source.setSkuCode("SKU-1");
|
||||
source.setInTransitPackages(12L);
|
||||
when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(target);
|
||||
when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(source));
|
||||
|
||||
InventoryCommandResponse result = service.stockIn("100", "7",
|
||||
stockIn("IN-1", "stock-in-cross-warehouse", 12, 24));
|
||||
|
||||
assertEquals(12, result.availablePackages());
|
||||
assertEquals(0L, source.getInTransitPackages());
|
||||
ArgumentCaptor<InventoryLedger> written = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(ledgers, times(2)).insert(written.capture());
|
||||
assertEquals("STOCK_IN_TRANSIT_RELEASE", written.getAllValues().get(0).getBusinessType());
|
||||
assertEquals(-12L, written.getAllValues().get(0).getDeltaPackages());
|
||||
assertEquals("STOCK_IN", written.getAllValues().get(1).getBusinessType());
|
||||
assertEquals("IN_TRANSIT", written.getAllValues().get(1).getStatusFrom());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stockInKeepsOutsideSourceWhenInTransitIsInsufficient() {
|
||||
InventoryBalance target = balance(200L, 0);
|
||||
target.setSkuCode("SKU-1");
|
||||
target.setInTransitPackages(2L);
|
||||
when(balances.lockDimension("100", "WH-1", "ONT8", 101L, "IN-CODE-1")).thenReturn(target);
|
||||
when(commands.lockInboundTransitBalances("100", 101L, "IN-CODE-1")).thenReturn(List.of(target));
|
||||
|
||||
service.stockIn("100", "7", stockIn("IN-1", "stock-in-partial-transit", 5, 10));
|
||||
|
||||
ArgumentCaptor<InventoryLedger> ledger = ArgumentCaptor.forClass(InventoryLedger.class);
|
||||
verify(ledgers).insert(ledger.capture());
|
||||
assertEquals("OUTSIDE", ledger.getValue().getStatusFrom());
|
||||
assertEquals(0L, target.getInTransitPackages());
|
||||
assertEquals(5L, target.getAvailablePackages());
|
||||
}
|
||||
|
||||
@Test
|
||||
void differentSkusShareInboundBalanceAndKeepSkuInLedger() {
|
||||
InventoryBalance balance = balance(200L, 4);
|
||||
|
||||
+5
-2
@@ -16,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
@@ -31,6 +32,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -43,7 +45,7 @@ class LocalLooseboardServiceTest {
|
||||
@Mock LocalLooseboardOperationMapper operations;
|
||||
@Mock LastlegOrderMapper lastlegOrders;
|
||||
@Mock LastlegOrderStatusHistoryMapper lastlegHistories;
|
||||
@Mock LastlegOrderNoGenerator lastlegOrderNoGenerator;
|
||||
@Spy LastlegOrderNoGenerator lastlegOrderNoGenerator = new LastlegOrderNoGenerator();
|
||||
private LocalLooseboardService service;
|
||||
private final Map<String, LocalLooseboardOperation> operationRows = new HashMap<>();
|
||||
|
||||
@@ -57,7 +59,8 @@ class LocalLooseboardServiceTest {
|
||||
ReflectionTestUtils.setField(service, "lastlegHistories", lastlegHistories);
|
||||
ReflectionTestUtils.setField(service, "lastlegOrderNoGenerator", lastlegOrderNoGenerator);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
org.mockito.Mockito.lenient().when(lastlegOrderNoGenerator.next()).thenReturn("LMO-LOOSE-1");
|
||||
org.mockito.Mockito.lenient().when(lastlegOrderNoGenerator.next(anyString(), any(), anyLong()))
|
||||
.thenReturn("LB-WH-20260901-000001");
|
||||
org.mockito.Mockito.lenient().when(operations.findKey(anyString(), any(), anyString()))
|
||||
.thenAnswer(call -> operationRows.get(call.getArgument(2)));
|
||||
org.mockito.Mockito.lenient().when(operations.insert(any(LocalLooseboardOperation.class))).thenAnswer(call -> {
|
||||
|
||||
+4
-1
@@ -23,6 +23,9 @@ class OverseasProcurementInventoryBoundaryTest {
|
||||
assertFalse(source.contains("availablePackages"));
|
||||
assertFalse(source.contains("addOutbound"));
|
||||
assertFalse(source.contains("OverseasProcurementOutbound"));
|
||||
assertFalse(source.contains("outboundQuantity"));
|
||||
String responses = Files.readString(Path.of(
|
||||
"src/main/java/com/baiyue/lastleg/procurement/dto/OverseasProcurementResponses.java"),
|
||||
StandardCharsets.UTF_8);
|
||||
assertTrue(responses.contains("outboundQuantity"));
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -20,6 +20,7 @@ class OverseasProcurementSchemaContractTest {
|
||||
assertTrue(sql.contains("operation_type VARCHAR(32) NOT NULL"));
|
||||
assertTrue(sql.contains("review_idempotency_key VARCHAR(255) NULL"));
|
||||
assertTrue(sql.contains("review_request_hash CHAR(64) NULL"));
|
||||
assertTrue(sql.contains("outbound_details JSON NULL COMMENT '出库明细'"));
|
||||
assertTrue(sql.contains("idempotency_key VARCHAR(255) NULL COMMENT '操作幂等键'"));
|
||||
assertTrue(sql.contains("change_detail JSON NULL"));
|
||||
org.junit.jupiter.api.Assertions.assertFalse(sql.contains("CREATE TABLE IF NOT EXISTS ll_overseas_procurement_outbound"));
|
||||
@@ -39,6 +40,8 @@ class OverseasProcurementSchemaContractTest {
|
||||
"ALTER TABLE ll_overseas_procurement_order ADD COLUMN review_idempotency_key VARCHAR(255) NULL"));
|
||||
assertTrue(sql.contains(
|
||||
"ALTER TABLE ll_overseas_procurement_order ADD COLUMN review_request_hash CHAR(64) NULL"));
|
||||
assertTrue(sql.contains(
|
||||
"ALTER TABLE ll_overseas_procurement_order ADD COLUMN outbound_details JSON NULL"));
|
||||
assertTrue(sql.contains(
|
||||
"table_name='ll_overseas_procurement_status_history' AND column_name='idempotency_key'"));
|
||||
}
|
||||
|
||||
+24
@@ -45,6 +45,7 @@ class OverseasProcurementServiceTest {
|
||||
ReflectionTestUtils.setField(service, "histories", histories);
|
||||
ReflectionTestUtils.setField(service, "warehouseDirectory", warehouseDirectory);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", new com.baiyue.lastleg.service.LastlegOrderNoGenerator());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,9 +64,32 @@ class OverseasProcurementServiceTest {
|
||||
|
||||
assertEquals(OverseasProcurementService.PENDING_REVIEW, detail.summary().status());
|
||||
assertEquals(0, detail.summary().inboundQuantity());
|
||||
assertEquals(0, detail.summary().outboundQuantity());
|
||||
assertEquals(0, detail.summary().inventoryQuantity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundQuantityAndDisplayStatusFollowOutboundDetails() {
|
||||
OverseasProcurementOrder order = order(OverseasProcurementService.APPROVED, 90, 0);
|
||||
when(orders.findNo("100", "HC1")).thenReturn(order);
|
||||
when(histories.listOrder("100", 1L)).thenReturn(List.of());
|
||||
|
||||
order.setOutboundDetails("[]");
|
||||
assertEquals("PENDING_OUTBOUND", service.detail("100", "HC1").summary().status());
|
||||
|
||||
order.setOutboundDetails("[{\"id\":\"1\",\"quantity\":1}]");
|
||||
var partial = service.detail("100", "HC1").summary();
|
||||
assertEquals(1, partial.outboundQuantity());
|
||||
assertEquals(89, partial.inventoryQuantity());
|
||||
assertEquals("PARTIALLY_OUTBOUND", partial.status());
|
||||
|
||||
order.setOutboundDetails("[{\"id\":\"1\",\"quantity\":90}]");
|
||||
var completed = service.detail("100", "HC1").summary();
|
||||
assertEquals(90, completed.outboundQuantity());
|
||||
assertEquals(0, completed.inventoryQuantity());
|
||||
assertEquals("OUTBOUNDED", completed.status());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingOrderCannotReceiveInventory() {
|
||||
OverseasProcurementOrder order = order(OverseasProcurementService.PENDING_REVIEW, 0, 0);
|
||||
|
||||
+4
-4
@@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
@@ -44,7 +45,7 @@ class RelabelApplicationServiceTest {
|
||||
@Mock RelabelStatusHistoryMapper histories;
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderStatusHistoryMapper orderHistories;
|
||||
@Mock LastlegOrderNoGenerator orderNoGenerator;
|
||||
@Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator();
|
||||
|
||||
private RelabelApplicationService service;
|
||||
|
||||
@@ -58,7 +59,6 @@ class RelabelApplicationServiceTest {
|
||||
ReflectionTestUtils.setField(service, "orderHistories", orderHistories);
|
||||
ReflectionTestUtils.setField(service, "orderNoGenerator", orderNoGenerator);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", mapper());
|
||||
lenient().when(orderNoGenerator.next()).thenReturn("LMO-RELABEL-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,10 +91,10 @@ class RelabelApplicationServiceTest {
|
||||
assertEquals("PENDING_ASSIGNMENT", tailOrder.getValue().getCurrentStatus());
|
||||
assertEquals("RELABEL:PENDING_PROCESSING", tailOrder.getValue().getSubOrderStatus());
|
||||
assertEquals("CUSTOMER", tailOrder.getValue().getSourceRegion());
|
||||
assertEquals("LMO-RELABEL-1", tailOrder.getValue().getOrderNo());
|
||||
assertTrue(tailOrder.getValue().getOrderNo().matches("RL-WH-\\d{8}-\\d{6}"));
|
||||
assertEquals(tailOrder.getValue().getInboundCode(), tailOrder.getValue().getSourceInboundId());
|
||||
assertEquals(tailOrder.getValue().getInboundCode(), application.getValue().getRelabelNo());
|
||||
assertTrue(tailOrder.getValue().getInboundCode().startsWith("FBR"));
|
||||
assertTrue(tailOrder.getValue().getInboundCode().startsWith("RL-"));
|
||||
assertEquals(10, tailOrder.getValue().getPackageCount());
|
||||
assertEquals(6, application.getValue().getNotifiedPackageCount());
|
||||
assertEquals("SKU-A", relabelItem.getValue().getSkuCode());
|
||||
|
||||
+16
@@ -12,6 +12,7 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -53,4 +54,19 @@ class LastlegOrderNoGeneratorTest {
|
||||
when(sequenceMapper.currentValue()).thenReturn(10_000L);
|
||||
assertThrows(IllegalStateException.class, () -> generator.next(LocalDate.of(2026, 8, 27)));
|
||||
}
|
||||
|
||||
@Test void actualContainerNumberIsStableAndShared() {
|
||||
LastlegOrderNoGenerator generator = new LastlegOrderNoGenerator();
|
||||
assertTrue(generator.forContainerNo("2087379674555461633").matches("FL-WH-\\d{8}-\\d{6}"));
|
||||
assertEquals(generator.forContainerNo("MSCU1"), new LastlegOrderNoGenerator().forContainerNo("MSCU1"));
|
||||
assertNotEquals(generator.forContainerNo("MSCU1"), generator.forContainerNo("CAB1"));
|
||||
assertTrue(generator.forContainerNo("MSCU1234567890").length() <= 32);
|
||||
assertThrows(IllegalArgumentException.class, () -> generator.forContainerNo(" "));
|
||||
}
|
||||
|
||||
@Test void formatsBusinessWarehouseAndDate() {
|
||||
String value = new LastlegOrderNoGenerator().next("OUTBOUND", "warehouse-LAX-01", 123L);
|
||||
assertTrue(value.matches("OB-LAX-\\d{8}-000123"), value);
|
||||
assertTrue(value.length() <= 24);
|
||||
}
|
||||
}
|
||||
|
||||
+122
-1
@@ -8,9 +8,12 @@ import com.baiyue.lastleg.entity.LastlegOrderDetail;
|
||||
import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlanCarton;
|
||||
import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper;
|
||||
import com.baiyue.lastleg.inventory.command.service.InventoryCommandService;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderFeeMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderOperationLogMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -22,11 +25,16 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.time.LocalDateTime;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -38,8 +46,12 @@ class LastlegOrderServiceTest {
|
||||
@Mock LastlegOrderMapper orders;
|
||||
@Mock LastlegOrderDetailMapper details;
|
||||
@Mock LastlegOrderStatusHistoryMapper histories;
|
||||
@Mock LastlegOrderFeeMapper fees;
|
||||
@Mock LastlegOrderOperationLogMapper operations;
|
||||
@Mock InboundPlanCartonMapper cartons;
|
||||
@Mock FbaWarehouseValidator fbaWarehouses;
|
||||
@Mock InventoryCommandService inventory;
|
||||
@Mock InboundOrderDetainedOutboxService detainedOutbox;
|
||||
LastlegOrderService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -48,12 +60,16 @@ class LastlegOrderServiceTest {
|
||||
ReflectionTestUtils.setField(service, "orderMapper", orders);
|
||||
ReflectionTestUtils.setField(service, "detailMapper", details);
|
||||
ReflectionTestUtils.setField(service, "historyMapper", histories);
|
||||
ReflectionTestUtils.setField(service, "feeMapper", fees);
|
||||
ReflectionTestUtils.setField(service, "operationLogMapper", operations);
|
||||
ReflectionTestUtils.setField(service, "cartonMapper", cartons);
|
||||
ReflectionTestUtils.setField(service, "fbaWarehouseValidator", fbaWarehouses);
|
||||
lenient().when(fbaWarehouses.required(any(), any(), any())).thenAnswer(invocation ->
|
||||
new FbaWarehouseValidator.VerifiedFbaWarehouse(
|
||||
Long.parseLong(invocation.getArgument(0)), invocation.getArgument(1), invocation.getArgument(2),
|
||||
"US", "CA"));
|
||||
ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
ReflectionTestUtils.setField(service, "detainedOutbox", detainedOutbox);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,6 +79,8 @@ class LastlegOrderServiceTest {
|
||||
when(orders.selectOne(any())).thenReturn(order);
|
||||
when(details.selectList(any())).thenReturn(List.of());
|
||||
when(histories.selectList(any())).thenReturn(List.of());
|
||||
when(fees.selectList(any())).thenReturn(List.of());
|
||||
when(operations.selectList(any())).thenReturn(List.of());
|
||||
|
||||
var response = service.detail("100", 11L);
|
||||
|
||||
@@ -102,6 +120,52 @@ class LastlegOrderServiceTest {
|
||||
assertEquals("DESTINATION_ASSIGNED", history.getValue().getToStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFeePersistsFeeAndOperationForOwnedOrder() {
|
||||
when(orders.selectOne(any())).thenReturn(order(11L, "100", "PENDING_ASSIGNMENT", null));
|
||||
var request = new LastlegOrderRequests.CreateFee(
|
||||
"清关费", "USD", new BigDecimal("420.00"), "PENDING_REVIEW", "报关代理");
|
||||
|
||||
var fee = service.createFee("100", 11L, request, "platform_admin");
|
||||
|
||||
assertEquals("100", fee.getCustomerId());
|
||||
assertEquals("清关费", fee.getFeeItem());
|
||||
assertEquals(new BigDecimal("420.00"), fee.getAmount());
|
||||
verify(fees).insert(any(com.baiyue.lastleg.entity.LastlegOrderFee.class));
|
||||
verify(operations).insert(any(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletMergeMarkIsPersistedAndAuditedForPdaReminder() {
|
||||
LastlegOrder order = order(11L, "100", "INBOUND_PLANNED", "IB-1");
|
||||
when(orders.selectOne(any())).thenReturn(order);
|
||||
when(orders.lockById("100", 11L)).thenReturn(order);
|
||||
when(orders.updatePalletMergeFlag(any(), anyLong(), any(), any())).thenReturn(1);
|
||||
|
||||
service.setPalletMergeRequired("100", 11L,
|
||||
new LastlegOrderRequests.SetPalletMergeRequired(true), "platform_admin");
|
||||
|
||||
verify(orders).updatePalletMergeFlag(org.mockito.ArgumentMatchers.eq("100"),
|
||||
org.mockito.ArgumentMatchers.eq(11L), org.mockito.ArgumentMatchers.eq("REQUIRED"), any());
|
||||
ArgumentCaptor<com.baiyue.lastleg.entity.LastlegOrderOperationLog> operation =
|
||||
ArgumentCaptor.forClass(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class);
|
||||
verify(operations).insert(operation.capture());
|
||||
assertEquals("标记合托", operation.getValue().getOperationNode());
|
||||
assertTrue(operation.getValue().getResult().contains("PDA"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completedPhysicalMergeCannotBeUnmarked() {
|
||||
LastlegOrder order = order(11L, "100", "INBOUND_PLANNED", "IB-1");
|
||||
order.setPalletMergeFlag("MERGED");
|
||||
when(orders.selectOne(any())).thenReturn(order);
|
||||
when(orders.lockById("100", 11L)).thenReturn(order);
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> service.setPalletMergeRequired("100", 11L,
|
||||
new LastlegOrderRequests.SetPalletMergeRequired(false), "platform_admin"));
|
||||
verify(orders, never()).updatePalletMergeFlag(any(), anyLong(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void transferRejectsAnOrderAlreadyBoundToInboundPlan() {
|
||||
LastlegOrder order = order(11L, "100", "DESTINATION_ASSIGNED", "IB-1");
|
||||
@@ -145,7 +209,7 @@ class LastlegOrderServiceTest {
|
||||
|
||||
@Test
|
||||
void pageUsesPaginationAndPlatformFiltersWithoutCustomerScope() {
|
||||
var query = new LastlegOrderRequests.Query(3, 25, "LL-", "FIRST_LEG", "IN-", null,
|
||||
var query = new LastlegOrderRequests.Query(3L, 25L, "LL-", "FIRST_LEG", "IN-", null,
|
||||
"", "PENDING_ASSIGNMENT", null, "LAX", null, null, null, null, null);
|
||||
var resultPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<LastlegOrder>(3, 25);
|
||||
resultPage.setTotal(0);
|
||||
@@ -222,12 +286,69 @@ class LastlegOrderServiceTest {
|
||||
15 - pending, 15 - pending, pending, LocalDateTime.of(2026, 10, 3, 9, 0), ready, reason);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageReturnsPalletMergeRelatedInboundNumbers() {
|
||||
LastlegOrder order = order(11L, "100", "INBOUND_COMPLETED", "IB-1");
|
||||
order.setPalletMergeFlag("MERGED");
|
||||
var resultPage = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<LastlegOrder>(1, 10);
|
||||
resultPage.setRecords(List.of(order));
|
||||
resultPage.setTotal(1);
|
||||
when(orders.selectPage(any(), any())).thenReturn(resultPage);
|
||||
when(orders.findPalletMergeRelations(List.of(11L))).thenReturn(List.of(
|
||||
new LastlegOrderMapper.PalletMergeRelationRow(11L, "INBOUND-2", "PMG-1"),
|
||||
new LastlegOrderMapper.PalletMergeRelationRow(11L, "INBOUND-3", "PMG-1")));
|
||||
var query = new LastlegOrderRequests.Query(1L, 10L, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null);
|
||||
|
||||
var result = service.page(null, query);
|
||||
|
||||
assertEquals("PMG-1", result.records().get(0).palletMergeBatchId());
|
||||
assertEquals(List.of("INBOUND-2", "INBOUND-3"), result.records().get(0).palletMergeInboundNos());
|
||||
}
|
||||
|
||||
@Test
|
||||
void holdingBeforeInboundPersistsTheOrderMarkerAndOperationRemark() {
|
||||
LastlegOrder order = order(11L, "100", "PENDING_INBOUND", null);
|
||||
when(orders.selectOne(any())).thenReturn(order);
|
||||
when(orders.lockById("100", 11L)).thenReturn(order);
|
||||
|
||||
service.setDetained("100", 11L, new LastlegOrderRequests.SetDetained(true, "等待质检"), "operator");
|
||||
|
||||
assertTrue(order.getDetained());
|
||||
verify(orders).updateById(order);
|
||||
ArgumentCaptor<com.baiyue.lastleg.entity.LastlegOrderOperationLog> operation =
|
||||
ArgumentCaptor.forClass(com.baiyue.lastleg.entity.LastlegOrderOperationLog.class);
|
||||
verify(operations).insert(operation.capture());
|
||||
assertEquals("暂扣", operation.getValue().getOperationNode());
|
||||
assertEquals("等待质检", operation.getValue().getResult());
|
||||
verify(detainedOutbox).publish(eq(order), eq(true), eq("等待质检"), isNull(), eq("operator"), any(), anyLong());
|
||||
verify(inventory, never()).releaseInboundOrderHold(any(), any(), anyLong(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void releasingAfterInboundMovesAllOrderHeldInventoryToAvailable() {
|
||||
LastlegOrder order = order(11L, "100", "INBOUND_COMPLETED", "IP-1");
|
||||
order.setOrderNo("LM-1");
|
||||
order.setDetained(true);
|
||||
when(orders.selectOne(any())).thenReturn(order);
|
||||
when(orders.lockById("100", 11L)).thenReturn(order);
|
||||
|
||||
service.setDetained("100", 11L, new LastlegOrderRequests.SetDetained(false, "质检通过"), "operator");
|
||||
|
||||
verify(inventory).releaseInboundOrderHold("100", "operator", 11L, "LM-1", "质检通过");
|
||||
assertEquals(false, order.getDetained());
|
||||
verify(detainedOutbox).publish(eq(order), eq(false), eq("质检通过"), isNull(), eq("operator"), any(), anyLong());
|
||||
}
|
||||
|
||||
private static LastlegOrder order(long id, String customerId, String status, String inboundOrderNo) {
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setId(id);
|
||||
order.setCustomerId(customerId);
|
||||
order.setCurrentStatus(status);
|
||||
order.setInboundOrderNo(inboundOrderNo);
|
||||
order.setOrderNo("LM-" + id);
|
||||
order.setSourceInboundId(String.valueOf(1000 + id));
|
||||
order.setInboundCode("IN-" + id);
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ class LoadingCompletedConsumerTest {
|
||||
private final Map<String,String> frozenContainerNos=new HashMap<>();
|
||||
@Mock LastlegEventInboxMapper inbox; @Mock LastlegOrderMapper orders; @Mock LastlegInboundOrderMapper inboundOrders; @Mock LastlegOrderDetailMapper details; @Mock LastlegOrderStatusHistoryMapper histories; @Mock InboundPlanCartonMapper cartons; @Mock DropshipOrderService dropshipOrders; @Mock InventoryCommandService inventory; @Mock com.baiyue.lastleg.shipping.mapper.ContainerShippingGateMapper shippingGates; @Mock LastlegOrderNoDailySequenceMapper sequences; ObjectMapper json=new ObjectMapper().findAndRegisterModules(); LoadingCompletedConsumer service;
|
||||
@BeforeEach void setup(){ service=new LoadingCompletedConsumer(); ReflectionTestUtils.setField(service,"inboxMapper",inbox); ReflectionTestUtils.setField(service,"orderMapper",orders); ReflectionTestUtils.setField(service,"inboundOrderMapper",inboundOrders); ReflectionTestUtils.setField(service,"detailMapper",details); ReflectionTestUtils.setField(service,"historyMapper",histories); ReflectionTestUtils.setField(service,"cartonMapper",cartons); ReflectionTestUtils.setField(service,"dropshipOrders",dropshipOrders); ReflectionTestUtils.setField(service,"inventory",inventory); ReflectionTestUtils.setField(service,"shippingGates",shippingGates); ReflectionTestUtils.setField(service,"objectMapper",json); LastlegOrderNoGenerator generator=new LastlegOrderNoGenerator(); ReflectionTestUtils.setField(generator,"sequenceMapper",sequences); ReflectionTestUtils.setField(service,"orderNoGenerator",generator); lenient().when(sequences.currentValue()).thenReturn(1L); lenient().doAnswer(i->{ LastlegOrder o=i.getArgument(0); o.setId(Math.abs(o.getSourceInboundId().hashCode())+1L); return 1; }).when(orders).insert(any(LastlegOrder.class)); lenient().when(orders.lockContainerNumbers(anyString())).thenReturn(List.of()); lenient().when(shippingGates.initialize(anyString(),anyString(),anyString(),anyString(),anyLong(),anyLong())).thenAnswer(i->{ String key=i.getArgument(0); String value=i.getArgument(3); return frozenContainerNos.putIfAbsent(key,value)==null?1:0; }); lenient().when(shippingGates.lock(anyString())).thenAnswer(i->{ String key=i.getArgument(0); String value=frozenContainerNos.get(key); if(value==null)return null; var gate=new com.baiyue.lastleg.shipping.entity.ContainerShippingGate(); gate.setContainerBookingId(key); gate.setContainerNo(value); return gate; }); }
|
||||
@Test void createsPendingOrdersAndInTransitSnapshotsWithoutInboundPlans(){ EventEnvelope event=event(); assertEquals(2,service.consume(event)); assertThrows(NoSuchFieldException.class,()->LoadingCompletedConsumer.class.getDeclaredField("inboundPlans")); verify(orders,times(2)).insert(any(LastlegOrder.class)); ArgumentCaptor<InventoryCommandRequests.InTransit> inTransit=ArgumentCaptor.forClass(InventoryCommandRequests.InTransit.class); verify(inventory,times(2)).registerInTransit(eq("100"),eq("SYSTEM"),inTransit.capture()); assertEquals(Set.of("RK1","RK2"),inTransit.getAllValues().stream().map(InventoryCommandRequests.InTransit::inboundOrderNo).collect(java.util.stream.Collectors.toSet())); verify(details,times(2)).insert(any(LastlegOrderDetail.class)); verify(histories,times(2)).insert(any(LastlegOrderStatusHistory.class)); ArgumentCaptor<LastlegOrder> c=ArgumentCaptor.forClass(LastlegOrder.class); verify(orders,times(2)).insert(c.capture()); assertEquals(Set.of("in-1","in-2"),c.getAllValues().stream().map(LastlegOrder::getSourceInboundId).collect(java.util.stream.Collectors.toSet())); assertTrue(c.getAllValues().stream().allMatch(o->"PENDING_ASSIGNMENT".equals(o.getCurrentStatus()))); assertTrue(c.getAllValues().stream().allMatch(o->o.getInboundOrderNo()==null)); assertTrue(c.getAllValues().stream().allMatch(o->o.getId()!=null)); assertTrue(c.getAllValues().stream().allMatch(o->"MSCU6639870".equals(o.getContainerNo()))); assertTrue(c.getAllValues().stream().allMatch(o->"FBA".equals(o.getDestinationType()))); assertTrue(c.getAllValues().stream().allMatch(o->"Customer 100".equals(o.getCustomerName()))); assertTrue(c.getAllValues().stream().allMatch(o->"ow-1".equals(o.getSourceWarehouseId())&&o.getOverseasWarehouseId()==null)); ArgumentCaptor<LastlegInboundOrder> snapshots=ArgumentCaptor.forClass(LastlegInboundOrder.class); verify(inboundOrders,times(2)).insert(snapshots.capture()); assertTrue(snapshots.getAllValues().stream().allMatch(o->"100".equals(o.getCustomerId())&&"FBA".equals(o.getDestinationType()))); assertEquals(1,c.getAllValues().stream().map(LastlegOrder::getOrderNo).distinct().count()); assertTrue(c.getAllValues().get(0).getOrderNo().matches("LMO-[0-9]{6}-0001")); }
|
||||
@Test void createsPendingOrdersAndInTransitSnapshotsWithoutInboundPlans(){ EventEnvelope event=event(); assertEquals(2,service.consume(event)); assertThrows(NoSuchFieldException.class,()->LoadingCompletedConsumer.class.getDeclaredField("inboundPlans")); verify(orders,times(2)).insert(any(LastlegOrder.class)); ArgumentCaptor<InventoryCommandRequests.InTransit> inTransit=ArgumentCaptor.forClass(InventoryCommandRequests.InTransit.class); verify(inventory,times(2)).registerInTransit(eq("100"),eq("SYSTEM"),inTransit.capture()); assertEquals(Set.of("RK1","RK2"),inTransit.getAllValues().stream().map(InventoryCommandRequests.InTransit::inboundOrderNo).collect(java.util.stream.Collectors.toSet())); verify(details,times(2)).insert(any(LastlegOrderDetail.class)); verify(histories,times(2)).insert(any(LastlegOrderStatusHistory.class)); ArgumentCaptor<LastlegOrder> c=ArgumentCaptor.forClass(LastlegOrder.class); verify(orders,times(2)).insert(c.capture()); assertEquals(Set.of("in-1","in-2"),c.getAllValues().stream().map(LastlegOrder::getSourceInboundId).collect(java.util.stream.Collectors.toSet())); assertTrue(c.getAllValues().stream().allMatch(o->"PENDING_ASSIGNMENT".equals(o.getCurrentStatus()))); assertTrue(c.getAllValues().stream().allMatch(o->o.getInboundOrderNo()==null)); assertTrue(c.getAllValues().stream().allMatch(o->o.getId()!=null)); assertTrue(c.getAllValues().stream().allMatch(o->"MSCU6639870".equals(o.getContainerNo()))); assertTrue(c.getAllValues().stream().allMatch(o->"FBA".equals(o.getDestinationType()))); assertTrue(c.getAllValues().stream().allMatch(o->"Customer 100".equals(o.getCustomerName()))); assertTrue(c.getAllValues().stream().allMatch(o->"ow-1".equals(o.getSourceWarehouseId())&&o.getOverseasWarehouseId()==null)); ArgumentCaptor<LastlegInboundOrder> snapshots=ArgumentCaptor.forClass(LastlegInboundOrder.class); verify(inboundOrders,times(2)).insert(snapshots.capture()); assertTrue(snapshots.getAllValues().stream().allMatch(o->"100".equals(o.getCustomerId())&&"FBA".equals(o.getDestinationType()))); assertEquals(1,c.getAllValues().stream().map(LastlegOrder::getOrderNo).distinct().count()); assertTrue(c.getAllValues().get(0).getOrderNo().matches("FL-WH-[0-9]{8}-[0-9]{6}")); }
|
||||
@Test void freezesFormalPlannedWarehouseArrivalFromLoadingEvent(){ EventEnvelope source=event(); JsonNode node=source.payload().deepCopy(); ((com.fasterxml.jackson.databind.node.ObjectNode)node).put("estimatedWarehouseArrivalAt","2026-10-03T09:00"); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); EventEnvelope changed=new EventEnvelope(source.eventId(),source.eventType(),source.eventVersion(),source.occurredAt(),source.sourceRegion(),source.targetRegion(),source.tenantId(),source.aggregateType(),source.aggregateId(),source.aggregateVersion(),source.handoverVersion(),source.traceId(),source.correlationId(),source.causationId(),node,hash); service.consume(changed); ArgumentCaptor<LastlegOrder> captor=ArgumentCaptor.forClass(LastlegOrder.class); verify(orders,times(2)).insert(captor.capture()); assertTrue(captor.getAllValues().stream().allMatch(order->java.time.LocalDateTime.of(2026,10,3,9,0).equals(order.getEstimatedWarehouseAt()))); }
|
||||
@Test void loadingCreatesAwaitingPickupReadyGateWithoutPollutingShippingVersion(){ service.consume(event()); verify(shippingGates).initialize("book-1","100","","MSCU6639870",0L,1L); }
|
||||
@Test void rejectsUnnormalizedActualContainerNumberInsteadOfUsingCabinetNumber(){ EventEnvelope source=event(); JsonNode node=source.payload().deepCopy(); ((com.fasterxml.jackson.databind.node.ObjectNode)node).put("containerNo"," mscu6639870 ").put("cabinetNo","FALLBACK-CABINET"); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); EventEnvelope changed=new EventEnvelope(source.eventId(),source.eventType(),source.eventVersion(),source.occurredAt(),source.sourceRegion(),source.targetRegion(),source.tenantId(),source.aggregateType(),source.aggregateId(),source.aggregateVersion(),source.handoverVersion(),source.traceId(),source.correlationId(),source.causationId(),node,hash); assertThrows(IllegalArgumentException.class,()->service.consume(changed)); verify(inbox,never()).insert(any(LastlegEventInbox.class)); verify(orders,never()).insert(any(LastlegOrder.class)); }
|
||||
|
||||
+6
@@ -22,6 +22,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import com.baiyue.lastleg.shipping.mapper.ContainerShippingGateMapper;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
@@ -64,6 +66,10 @@ class TaillegRealConsumerAcceptanceTest {
|
||||
"com.baiyue.lastleg.inventory.command.mapper", "com.baiyue.lastleg.relabel.mapper",
|
||||
"com.baiyue.lastleg.transfer.mapper", "com.baiyue.lastleg.looseboard.mapper"})
|
||||
static class AcceptanceApplication {
|
||||
@Bean ContainerShippingGateMapper containerShippingGateMapper() {
|
||||
return Mockito.mock(ContainerShippingGateMapper.class);
|
||||
}
|
||||
|
||||
@Bean WarehouseResourceGateway warehouseResourceGateway() {
|
||||
return new WarehouseResourceGateway() {
|
||||
public WarehousePalletReference ensureDropshipPallet(String customerId, long warehouseId, String businessId) {
|
||||
|
||||
+7
-3
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
@@ -46,6 +47,8 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
@@ -70,7 +73,7 @@ class WarehouseTransferServiceTest {
|
||||
@Mock RelabelApplicationMapper relabelApplications;
|
||||
@Mock RelabelItemMapper relabelItems;
|
||||
@Mock RelabelStatusHistoryMapper relabelHistories;
|
||||
@Mock LastlegOrderNoGenerator orderNoGenerator;
|
||||
@Spy LastlegOrderNoGenerator orderNoGenerator = new LastlegOrderNoGenerator();
|
||||
@Mock InventoryCommandService inventory;
|
||||
@Mock InventoryCommandMapper inventoryCommands;
|
||||
private WarehouseTransferService service;
|
||||
@@ -94,7 +97,8 @@ class WarehouseTransferServiceTest {
|
||||
ReflectionTestUtils.setField(service, "inventory", inventory);
|
||||
ReflectionTestUtils.setField(service, "inventoryCommands", inventoryCommands);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
lenient().when(orderNoGenerator.next()).thenReturn("LMO-TRANSFER-1");
|
||||
lenient().when(orderNoGenerator.next(anyString(), any(), anyLong()))
|
||||
.thenReturn("WT-WH-20260901-000001");
|
||||
lenient().when(inventoryAllocations.selectList(any())).thenReturn(List.of());
|
||||
lenient().when(transferReceipts.selectList(any())).thenReturn(List.of());
|
||||
}
|
||||
@@ -116,7 +120,7 @@ class WarehouseTransferServiceTest {
|
||||
assertEquals(WarehouseTransferService.RETURN_RELABEL_ORDER_TYPE, tailOrder.getValue().getOrderType());
|
||||
assertEquals("PENDING_ASSIGNMENT", tailOrder.getValue().getCurrentStatus());
|
||||
assertEquals("WAREHOUSE_TRANSFER:PENDING_REVIEW;RELABEL:PENDING_PROCESSING", tailOrder.getValue().getSubOrderStatus());
|
||||
assertEquals("LMO-TRANSFER-1", tailOrder.getValue().getOrderNo());
|
||||
assertTrue(tailOrder.getValue().getOrderNo().matches("WT-WH-\\d{8}-\\d{6}"));
|
||||
assertNull(tailOrder.getValue().getTransferWarehouseId());
|
||||
verify(sourceOrders).insert(any(WarehouseTransferSourceOrder.class));
|
||||
verify(relabelApplications).insert(any(RelabelApplication.class));
|
||||
|
||||
Reference in New Issue
Block a user