This commit is contained in:
2026-09-15 11:54:14 +08:00
parent 3fb9909dd3
commit c1f01e1e87
56 changed files with 973 additions and 231 deletions
@@ -6,7 +6,6 @@ import com.baiyue.lastleg.customervisit.mapper.CustomerVisitMapper;
import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
import com.baiyue.lastleg.delivery.mapper.DeliveryOperationMapper;
import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -56,22 +55,12 @@ class CustomerVisitServiceTest {
}
@Test
void replaysDeliveredToDeliveredWithoutChangingDeliveryOrAddingHistory() {
DeliveryOrder delivery = delivery("DELIVERED");
CustomerVisit visit = new CustomerVisit();
visit.setId(33L);
visit.setStatus("PENDING");
when(deliveryOrders.lockByNo("1001", "DL-1")).thenReturn(delivery);
when(visits.findByDelivery("1001", 11L)).thenReturn(visit);
void rejectsManualDeliveryCompletionBeforeLoadingTheOrder() {
assertThatThrownBy(() -> service.updateDeliveryStatus("1001", "9001", "DL-1",
new CustomerVisitRequests.DeliveryStatus("DELIVERED", 0L, null)))
.isInstanceOf(ResponseStatusException.class).hasMessageContaining("不支持");
var result = service.updateDeliveryStatus("1001", "9001", "DL-1",
new CustomerVisitRequests.DeliveryStatus("DELIVERED", 0L, null));
assertThat(result.replayed()).isTrue();
assertThat(result.deliveryStatus()).isEqualTo("DELIVERED");
assertThat(result.visitId()).isEqualTo(33L);
verify(deliveryOrders, never()).updateById(delivery);
verifyNoInteractions(deliveryOperations);
verifyNoInteractions(deliveryOrders, deliveryOperations);
}
@Test
@@ -89,26 +78,7 @@ class CustomerVisitServiceTest {
}
@Test
void completingDeliveryUpdatesOnlySubOrderStatusAndKeepsMainFlowStatus() {
DeliveryOrder delivery = delivery("DELIVERING");
delivery.setVersion(1L);
LastlegOrder tail = new LastlegOrder();
tail.setId(21L); tail.setCustomerId("1001"); tail.setCurrentStatus("INBOUND_COMPLETED");
CustomerVisit visit = new CustomerVisit(); visit.setId(33L); visit.setStatus("PENDING");
when(deliveryOrders.lockByNo("1001", "DL-1")).thenReturn(delivery);
when(lastMileOrders.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(java.util.List.of(tail));
when(visits.findByDelivery("1001", 11L)).thenReturn(visit);
var result = service.updateDeliveryStatus("1001", "9001", "DL-1",
new CustomerVisitRequests.DeliveryStatus("DELIVERED", 1L, null));
assertThat(result.replayed()).isFalse();
assertThat(tail.getCurrentStatus()).isEqualTo("INBOUND_COMPLETED");
assertThat(tail.getSubOrderStatus()).isEqualTo("DELIVERY:DELIVERED");
}
@Test
void startMeansDeliveringAndOnlyCompleteSignsAndCreatesVisit() {
void startMeansDeliveringWithoutSigningOrCreatingVisit() {
DeliveryOrder delivery = delivery("WAIT_DELIVERY"); delivery.setVersion(0L);
when(deliveryOrders.lockByNo("1001", "DL-1")).thenReturn(delivery);
var started = service.updateDeliveryStatus("1001", "9001", "DL-1",
@@ -118,17 +88,7 @@ class CustomerVisitServiceTest {
assertThat(delivery.getDispatchedAt()).isNotNull();
assertThat(delivery.getSignedAt()).isNull();
verify(visits, never()).insert(org.mockito.ArgumentMatchers.any(CustomerVisit.class));
CustomerVisit visit = new CustomerVisit(); visit.setId(33L); visit.setStatus("PENDING");
when(visits.findByDelivery("1001", 11L)).thenReturn(visit);
var completed = service.updateDeliveryStatus("1001", "9001", "DL-1",
new CustomerVisitRequests.DeliveryStatus("DELIVERED", 1L, null));
assertThat(completed.deliveryStatus()).isEqualTo("DELIVERED");
assertThat(delivery.getSignedAt()).isNotNull();
assertThat(delivery.getVersion()).isEqualTo(2);
assertThat(completed.visitId()).isEqualTo(33);
assertThat(service.updateDeliveryStatus("1001", "9001", "DL-1",
new CustomerVisitRequests.DeliveryStatus("DELIVERED", 1L, null)).replayed()).isTrue();
verify(deliveryOrders, org.mockito.Mockito.times(2)).updateById(delivery);
verify(deliveryOrders).updateById(delivery);
}
private DeliveryOrder delivery(String status) {
@@ -67,6 +67,6 @@ class DeliveryLongIdJsonContractTest {
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, 0, java.util.List.of(), null,
com.baiyue.lastleg.delivery.model.DeliveryDocumentSnapshot.summary(null));
com.baiyue.lastleg.delivery.model.DeliveryDocumentSnapshot.summary(null), null, null, null);
}
}
@@ -35,7 +35,9 @@ class DeliverySchemaContractTest {
assertTrue(schema.contains("create table if not exists ll_delivery_order"));
assertTrue(schema.contains("status varchar(32) not null default 'wait_delivery'"));
assertTrue(schema.contains("unique key uk_ll_delivery_idempotency (customer_id,idempotency_key)"));
assertTrue(schema.contains("unique key uk_ll_delivery_outbound (customer_id,outbound_order_id)"));
assertTrue(schema.contains("unique key uk_ll_delivery_outbound_destination (customer_id,outbound_order_id,delivery_group_key)"));
assertTrue(schema.contains("destination_warehouse_code varchar(128) not null"));
assertTrue(schema.contains("delivery_group_key varchar(255) not null"));
}
@Test
@@ -53,7 +55,8 @@ class DeliverySchemaContractTest {
assertTrue(createEnd > create);
String definition = schema.substring(create, createEnd);
for (String column : new String[] {
"overseas_warehouse_name", "customer_name", "waybill_no", "delivery_method",
"overseas_warehouse_name", "customer_name", "destination_warehouse_id",
"destination_warehouse_code", "destination_warehouse_name", "delivery_group_key", "waybill_no", "delivery_method",
"carrier_name", "pickup_method", "loading_type", "vehicle_type", "vehicle_size",
"vehicle_plate", "driver_name", "driver_phone", "appointment_account", "seller_name",
"total_weight_kg", "total_volume_cbm", "planned_delivery_at", "dispatched_at",
@@ -8,10 +8,11 @@ class DeliveryStatusPolicyTest {
@Test
void exposesOnlyServerAuthorizedActionsForEachState() {
assertThat(DeliveryStatusPolicy.allowedActions("WAIT_DELIVERY"))
.containsExactly("START_DELIVERY", "COMPLETE_DELIVERY", "REJECT_DELIVERY", "CANCEL_DELIVERY");
.containsExactly("START_DELIVERY", "REJECT_DELIVERY", "CANCEL_DELIVERY");
assertThat(DeliveryStatusPolicy.allowedActions("DELIVERING"))
.containsExactly("COMPLETE_DELIVERY", "REJECT_DELIVERY", "MARK_RETURN");
.containsExactly("REJECT_DELIVERY", "MARK_RETURN");
assertThat(DeliveryStatusPolicy.allowedActions("DELIVERED")).containsExactly("MARK_RETURN");
assertThat(DeliveryStatusPolicy.isEditableTarget("DELIVERED")).isFalse();
assertThat(DeliveryStatusPolicy.allowedActions("RETURN_PENDING")).isEmpty();
assertThat(DeliveryStatusPolicy.allowedActions("CANCELLED")).isEmpty();
}
@@ -0,0 +1,59 @@
package com.baiyue.lastleg.delivery.service;
import com.baiyue.common.event.EventEnvelope;
import com.baiyue.common.event.OverseasDeliveryCompletedV1;
import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
import com.baiyue.lastleg.entity.LastlegEventOutbox;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.mapper.LastlegEventOutboxMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.Instant;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
class DeliveryCompletedOutboxServiceTest {
@Test
void emitsOneEventPerSourceInboundAcrossFbaWarehouses() throws Exception {
LastlegEventOutboxMapper mapper = mock(LastlegEventOutboxMapper.class);
ObjectMapper json = new ObjectMapper().registerModule(new JavaTimeModule());
DeliveryCompletedOutboxService service = new DeliveryCompletedOutboxService();
ReflectionTestUtils.setField(service, "outbox", mapper);
ReflectionTestUtils.setField(service, "objectMapper", json);
DeliveryOrder delivery = new DeliveryOrder();
delivery.setId(1L); delivery.setDeliveryNo("DL-1"); delivery.setVersion(2L);
service.publish(delivery, List.of(order(11, "101", "201", "IN-1", "ONT8"),
order(12, "102", "202", "IN-2", "ACY2")), Instant.parse("2026-09-15T08:00:00Z"));
ArgumentCaptor<LastlegEventOutbox> rows = ArgumentCaptor.forClass(LastlegEventOutbox.class);
verify(mapper, times(2)).insert(rows.capture());
List<OverseasDeliveryCompletedV1> payloads = rows.getAllValues().stream().map(row -> {
try {
EventEnvelope envelope = json.readValue(row.getPayloadJson(), EventEnvelope.class);
return json.treeToValue(envelope.payload(), OverseasDeliveryCompletedV1.class);
} catch (Exception exception) { throw new RuntimeException(exception); }
}).toList();
assertThat(payloads).extracting(OverseasDeliveryCompletedV1::sourceInboundId)
.containsExactly("101", "102");
assertThat(payloads).extracting(OverseasDeliveryCompletedV1::fbaWarehouseCode)
.containsExactly("ONT8", "ACY2");
}
private LastlegOrder order(long id, String inboundId, String orderId, String inboundCode, String fba) {
LastlegOrder value = new LastlegOrder();
value.setId(id); value.setOrderNo("LM-" + id); value.setCustomerId("88");
value.setSourceInboundId(inboundId); value.setInboundCode(inboundCode);
value.setSourceOrderId(orderId); value.setSourceOrderNo("FL-" + orderId);
value.setRootOrderId(orderId); value.setFbaWarehouseCode(fba);
return value;
}
}
@@ -5,6 +5,9 @@ import com.baiyue.lastleg.delivery.entity.*;
import com.baiyue.lastleg.delivery.gateway.DeliveryDocumentFileGateway;
import com.baiyue.lastleg.delivery.mapper.*;
import com.baiyue.lastleg.delivery.model.*;
import com.baiyue.lastleg.customervisit.service.CustomerVisitService;
import com.baiyue.lastleg.entity.LastlegOrder;
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -26,6 +29,9 @@ class DeliveryDocumentServiceTest {
@Mock DeliveryDocumentRevisionMapper revisions;
@Mock DeliveryOperationMapper operations;
@Mock DeliveryDocumentFileGateway files;
@Mock LastlegOrderMapper lastlegOrders;
@Mock DeliveryCompletedOutboxService deliveryCompletedOutbox;
@Mock CustomerVisitService customerVisits;
DeliveryDocumentService service;
DeliveryOrder order;
@BeforeEach void setUp() {
@@ -35,6 +41,9 @@ class DeliveryDocumentServiceTest {
ReflectionTestUtils.setField(service,"operations",operations);
ReflectionTestUtils.setField(service,"files",files);
ReflectionTestUtils.setField(service,"objectMapper",new ObjectMapper().findAndRegisterModules());
ReflectionTestUtils.setField(service,"lastlegOrders",lastlegOrders);
ReflectionTestUtils.setField(service,"deliveryCompletedOutbox",deliveryCompletedOutbox);
ReflectionTestUtils.setField(service,"customerVisits",customerVisits);
order = new DeliveryOrder(); order.setId(10L); order.setCustomerId("100");
order.setDeliveryNo("DL-1"); order.setVersion(0L); order.setStatus("DELIVERED");
}
@@ -121,6 +130,27 @@ class DeliveryDocumentServiceTest {
assertThat(result.uploadedCategoryCount()).isEqualTo(3); assertThat(result.totalCategoryCount()).isEqualTo(3);
assertThat(result.categories()).allMatch(c->c.status().equals("UPLOADED"));
}
@Test void savingReceiptAutomaticallyCompletesOnlyThisDeliveryAndItsBoundOrders() {
order.setStatus("WAIT_DELIVERY");
LastlegOrder related = new LastlegOrder(); related.setId(20L); related.setCustomerId("100");
related.setDeliveryOrderNo("DL-1"); related.setSubOrderStatus("DELIVERY:WAIT_DELIVERY");
when(orders.lockByNo("100","DL-1")).thenReturn(order);
when(files.metadata("100",10,RECEIPT,13)).thenReturn(file(13));
when(lastlegOrders.selectList(any())).thenReturn(List.of(related));
service.save("100","DL-1","9",request(0,"receipt",List.of(new Binding(RECEIPT,List.of(13L)))));
assertThat(order.getStatus()).isEqualTo("DELIVERED");
assertThat(order.getSignedAt()).isNotNull();
assertThat(related.getSubOrderStatus()).isEqualTo("DELIVERY:DELIVERED");
verify(lastlegOrders).updateById(related);
verify(deliveryCompletedOutbox).publish(eq(order), eq(List.of(related)), any());
verify(customerVisits).ensureForCompletedDelivery(eq(order), any());
ArgumentCaptor<DeliveryOperation> operation = ArgumentCaptor.forClass(DeliveryOperation.class);
verify(operations, times(2)).insert(operation.capture());
assertThat(operation.getAllValues()).extracting(DeliveryOperation::getOperationNode)
.containsExactly("SAVE_DOCUMENTS", "RECEIPT_AUTO_COMPLETE");
}
private Save request(long version,String key,List<Binding> bindings) { return new Save(version,key,"BOL-1",bindings); }
private File file(long id) { return new File(id,"a.pdf","application/pdf",1,LocalDateTime.of(2026,9,9,8,0)); }
}
@@ -117,7 +117,7 @@ class DeliveryTransferServiceTest {
}
@Test
void firstTransferCreatesOneOutboundOneDeliveryAndAllPalletSnapshots() {
void firstTransferCreatesOneOutboundAndOneDeliveryPerDestinationFbaWarehouse() {
DispatchOrder dispatch = dispatch(101L, "DSP-101", "TENANT-A");
when(dispatchOrders.lockGlobalByDispatchNo("DSP-101")).thenReturn(dispatch);
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(
@@ -132,7 +132,7 @@ class DeliveryTransferServiceTest {
ArgumentCaptor<DeliveryOrder> delivery = ArgumentCaptor.forClass(DeliveryOrder.class);
ArgumentCaptor<OutboundPalletItem> pallets = ArgumentCaptor.forClass(OutboundPalletItem.class);
verify(outboundOrders).insert(outbound.capture());
verify(deliveryOrders).insert(delivery.capture());
verify(deliveryOrders, org.mockito.Mockito.times(2)).insert(delivery.capture());
verify(outboundPallets, org.mockito.Mockito.times(2)).insert(pallets.capture());
assertEquals("WAIT_OUTBOUND", outbound.getValue().getStatus());
@@ -140,15 +140,18 @@ class DeliveryTransferServiceTest {
assertEquals(20, outbound.getValue().getPackageCount());
assertEquals("transfer-101", outbound.getValue().getIdempotencyKey());
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().matches("DL-WH-\\d{8}-\\d{6}"));
assertTrue(delivery.getAllValues().stream().allMatch(value -> outbound.getValue().getId().equals(value.getOutboundOrderId())));
assertEquals(List.of("ONT8", "LAX9"), delivery.getAllValues().stream().map(DeliveryOrder::getDestinationWarehouseCode).toList());
assertEquals(List.of(12, 8), delivery.getAllValues().stream().map(DeliveryOrder::getPackageCount).toList());
assertTrue(delivery.getAllValues().stream().allMatch(value -> "WAIT_DELIVERY".equals(value.getStatus())));
assertEquals(List.of("ONT8", "LAX9"), pallets.getAllValues().stream()
.map(OutboundPalletItem::getDestinationWarehouseCode).toList());
assertEquals(List.of("IN-A", "IN-B"), pallets.getAllValues().stream()
.map(OutboundPalletItem::getInboundCode).toList());
assertEquals(20, result.packageCount());
assertEquals(2, result.palletCount());
assertEquals(2, result.deliveries().size());
verify(feeSources, org.mockito.Mockito.times(1)).publishApproved(any(), any(), any());
assertTrue(!result.replayed());
}
@@ -193,14 +196,14 @@ class DeliveryTransferServiceTest {
DeliveryOrder delivery = delivery(401L, outbound, "DEL-401");
when(dispatchOrders.lockGlobalByDispatchNo("DSP-101")).thenReturn(dispatch);
when(outboundOrders.findByIdempotency("TENANT-A", "transfer-101")).thenReturn(outbound);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(List.of(delivery));
DeliveryResponses.TransferResult result = service.platformTransfer("DSP-101", "U-1",
transferRequest(" transfer-101 ", null));
assertTrue(result.replayed());
assertEquals("OUT-301", result.outboundNo());
assertEquals("DEL-401", result.deliveryNo());
assertEquals("DEL-401", result.deliveries().get(0).deliveryNo());
verify(outboundOrders, never()).insert((OutboundOrder) any());
verify(outboundPallets, never()).insert((OutboundPalletItem) any());
verify(deliveryOrders, never()).insert((DeliveryOrder) any());
@@ -230,14 +233,14 @@ class DeliveryTransferServiceTest {
item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 12)));
when(outboundOrders.insert(any(OutboundOrder.class))).thenThrow(new org.springframework.dao.DuplicateKeyException("duplicate"));
when(outboundOrders.findByIdempotency("TENANT-A", "transfer-101")).thenReturn(null, winner);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(List.of(delivery));
DeliveryResponses.TransferResult result = service.platformTransfer("DSP-101", "U-1",
transferRequest("transfer-101", null));
assertTrue(result.replayed());
assertEquals("OUT-301", result.outboundNo());
assertEquals("DEL-401", result.deliveryNo());
assertEquals("DEL-401", result.deliveries().get(0).deliveryNo());
verify(outboundPallets, never()).insert((OutboundPalletItem) any());
verify(deliveryOrders, never()).insert((DeliveryOrder) any());
}
@@ -292,7 +295,7 @@ class DeliveryTransferServiceTest {
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
assertEquals("OUTBOUNDED", detail.outbound().status());
org.junit.jupiter.api.Assertions.assertNull(detail.delivery());
org.junit.jupiter.api.Assertions.assertTrue(detail.deliveries().isEmpty());
assertEquals("IN-A", detail.pallets().get(0).inboundCode());
assertEquals(4, detail.pallets().get(0).packageCount());
}
@@ -312,13 +315,13 @@ class DeliveryTransferServiceTest {
OutboundPalletItem pallet = pallet(501L, "IP-ONT8-20260907-000001-P001", "ONT8", "Ontario", "IN-A", 12);
pallet.setSkuCode("SKU-1");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(List.of(delivery));
when(outboundPallets.findByOutbound("TENANT-A", 301L)).thenReturn(List.of(pallet));
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
assertEquals("OUT-301", detail.outbound().outboundNo());
assertEquals("DEL-401", detail.delivery().deliveryNo());
assertEquals("DEL-401", detail.deliveries().get(0).deliveryNo());
assertEquals(1, detail.pallets().size());
assertEquals("IP-ONT8-20260907-000001-P001", detail.pallets().get(0).palletNo());
assertEquals("IN-A", detail.pallets().get(0).inboundCode());
@@ -333,7 +336,7 @@ class DeliveryTransferServiceTest {
OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101");
DeliveryOrder delivery = delivery(401L, outbound, "DEL-401");
when(outboundOrders.findGlobalByNo("OUT-301")).thenReturn(outbound);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(List.of(delivery));
DeliveryResponses.OutboundDetail detail = service.outboundDetail("OUT-301");
@@ -361,13 +364,13 @@ class DeliveryTransferServiceTest {
when(dispatchOrders.findByDispatchNo("TENANT-A", "DSP-101")).thenReturn(dispatch);
when(dispatchOrders.lockById("TENANT-A", 101L)).thenReturn(dispatch);
when(outboundOrders.findByIdempotency("TENANT-A", "merchant-key")).thenReturn(outbound);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(List.of(delivery));
DeliveryResponses.TransferResult result = service.merchantTransfer("TENANT-A", "DSP-101", "U-2",
transferRequest(" merchant-key ", null));
assertTrue(result.replayed());
assertEquals("DEL-401", result.deliveryNo());
assertEquals("DEL-401", result.deliveries().get(0).deliveryNo());
verify(outboundOrders, never()).insert((OutboundOrder) any());
}
@@ -396,6 +399,24 @@ class DeliveryTransferServiceTest {
verify(dispatchItems, never()).findByDispatchOrder(any(), any());
}
@Test
void deliveryDetailAndBolSourceRowsContainOnlyItsDestinationFbaWarehouse() {
OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101");
DeliveryOrder ont8 = delivery(401L, outbound, "DEL-ONT8");
DispatchItem ont8Item = item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 12);
ont8Item.setOrderId(901L);
DispatchItem lax9Item = item(202L, 502L, "PLT-502", "LAX9", "Los Angeles", "IN-B", 8);
lax9Item.setOrderId(902L);
when(deliveryOrders.findByNo("TENANT-A", "DEL-ONT8")).thenReturn(ont8);
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(ont8Item, lax9Item));
when(tailOrders.selectBatchIds(List.of(901L))).thenReturn(List.of());
DeliveryResponses.DeliveryDetail detail = service.deliveryDetail("TENANT-A", "DEL-ONT8");
assertEquals("ONT8", detail.order().destinationWarehouseCode());
verify(tailOrders).selectBatchIds(List.of(901L));
}
@Test
void globalOutboundCancelIsIdempotentForAnUnexecutedOrder() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");
@@ -838,6 +859,8 @@ class DeliveryTransferServiceTest {
value.setOutboundOrderId(outbound.getId()); value.setOutboundNo(outbound.getOutboundNo());
value.setDispatchOrderId(outbound.getDispatchOrderId()); value.setDispatchNo(outbound.getDispatchNo());
value.setOverseasWarehouseId(outbound.getOverseasWarehouseId()); value.setStatus("WAIT_DELIVERY");
value.setDestinationWarehouseId("DEST-ONT8"); value.setDestinationWarehouseCode("ONT8");
value.setDestinationWarehouseName("Ontario"); value.setDeliveryGroupKey("CODE:ONT8");
value.setPalletCount(outbound.getPalletCount()); value.setPackageCount(outbound.getPackageCount());
value.setCreatedAt(LocalDateTime.now());
return value;
@@ -75,4 +75,34 @@ class DispatchBolPrintServiceTest {
assertThat(document.shipFromAddress()).isEqualTo(
"10767 7th Street, Rancho Cucamonga, California 91730, US"));
}
@Test
void createsOneBolDocumentPerFbaWarehouse() {
String warehouseId = "193000000000200003";
DispatchResponse detail = new DispatchResponse(1L, "DS-WH-20260911-624449", "DEV-23653906",
null, null, "五月开发验收车队", null, null, warehouseId, "洛杉矶仓", null,
null, null, null, LocalDateTime.of(2026, 9, 12, 2, 40, 20), "PENDING_DEPARTURE",
2, 2, 2, 0, 0, 2, null, LocalDateTime.of(2026, 9, 11, 10, 0), List.of(),
List.of(
new DispatchResponse.BolLine("TSTU3539519", "DP-001", "ABE2", "ABE2 Warehouse",
"NYEXX2609110214", null, null, 1),
new DispatchResponse.BolLine("TSTU3539519", "DP-002", "ONT8", "ONT8 Warehouse",
"NYEXX2609110215", null, null, 1)));
var warehouse = new DomesticWarehouseDirectoryClient.Warehouse(Long.parseLong(warehouseId), 0L,
"US-LAX", "洛杉矶仓", "OVERSEAS", "US", "ACTIVE");
when(dispatches.platformDetail(detail.dispatchNo())).thenReturn(detail);
when(warehouses.activeWarehouse(Long.parseLong(warehouseId), "internal-test-token"))
.thenReturn(ApiResponse.success(new DomesticWarehouseDirectoryClient.Detail(warehouse, List.of())));
when(print.generateDispatchBol(eq("internal-test-token"), eq("request-2"), any()))
.thenReturn(ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF)
.header("X-Print-Page-Count", "2").body("%PDF-".getBytes(StandardCharsets.US_ASCII)));
service.generate(detail.dispatchNo(), "request-2");
ArgumentCaptor<PrintPdfClient.Request> request = ArgumentCaptor.forClass(PrintPdfClient.Request.class);
verify(print).generateDispatchBol(eq("internal-test-token"), eq("request-2"), request.capture());
assertThat(request.getValue().documents().stream()
.map(PrintPdfClient.Document::fbaWarehouseCode).toList())
.containsExactly("ABE2", "ONT8");
}
}