feat(fleet): add internal capacity pool and warehouse fulfillment bridge

This commit is contained in:
2026-10-07 16:46:56 +08:00
parent 564cfa107c
commit 001219460f
57 changed files with 3820 additions and 38 deletions
@@ -40,6 +40,7 @@ class DeliverySourcePublicationPersistenceTest {
volatile Runnable beforeFirstSourceRead = () -> {};
final Map<String,List<Long>> sourceLocks=new ConcurrentHashMap<>();
Fixture() {
org.springframework.test.util.ReflectionTestUtils.setField(service,"fleetPool",mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge.class));
db.execute("CREATE TABLE ll_order(id BIGINT PRIMARY KEY,customer_id VARCHAR(20),source_region VARCHAR(20),source_inbound_id VARCHAR(30),package_count BIGINT,UNIQUE(customer_id,source_region,source_inbound_id))");
db.execute("CREATE TABLE ll_dispatch_order(id BIGINT,customer_id VARCHAR(20),depart_at TIMESTAMP)");
db.execute("CREATE TABLE ll_delivery_order(id BIGINT PRIMARY KEY,customer_id VARCHAR(20),dispatch_order_id BIGINT,delivery_group_key VARCHAR(100),package_count BIGINT,signed_packages BIGINT,signed_at TIMESTAMP,outbound_no VARCHAR(30))");
@@ -46,6 +46,7 @@ class DeliveryDocumentServiceTest {
DeliveryOrder order;
@BeforeEach void setUp() {
service = new DeliveryDocumentService();
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPool", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge.class));
ReflectionTestUtils.setField(service,"orders",orders);
ReflectionTestUtils.setField(service,"outboundOrders",outboundOrders);
ReflectionTestUtils.setField(service,"revisions",revisions);
@@ -66,6 +66,7 @@ class DispatchPdaWorkflowServiceTest {
@BeforeEach
void setUp() {
service = new DispatchPdaWorkflowService();
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPool", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge.class));
ReflectionTestUtils.setField(service, "fleetBindings", fleetBindings);
objectMapper = new ObjectMapper();
ReflectionTestUtils.setField(service, "orders", orders);
@@ -0,0 +1,159 @@
package com.baiyue.lastleg.fleet.business;
import com.baiyue.lastleg.delivery.entity.DeliveryOrder;
import com.baiyue.lastleg.delivery.mapper.DeliverySourceEvidenceMapper;
import com.baiyue.lastleg.delivery.model.SourceDeliveryCompletionPolicy;
import com.baiyue.lastleg.fleet.trip.FleetBookingIncomingGateway;
import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder;
import com.baiyue.lastleg.looseboard.mapper.*;
import com.baiyue.lastleg.trucking.entity.UsTruckOrder;
import com.baiyue.lastleg.trucking.entity.UsTruckOrderStatusHistory;
import com.baiyue.lastleg.trucking.mapper.*;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.*;
import org.springframework.transaction.annotation.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import static com.baiyue.lastleg.fleet.business.FleetPoolOrderModels.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
class FleetPoolOrderBridgeTest {
FleetPoolOrderMapper links=mock(FleetPoolOrderMapper.class);
LocalLooseboardOrderMapper loose=mock(LocalLooseboardOrderMapper.class);
LocalLooseboardHistoryMapper looseHistory=mock(LocalLooseboardHistoryMapper.class);
UsTruckOrderMapper trucks=mock(UsTruckOrderMapper.class);
UsTruckOrderStatusHistoryMapper truckHistory=mock(UsTruckOrderStatusHistoryMapper.class);
LastlegOrderNoGenerator numbers=mock(LastlegOrderNoGenerator.class);
DeliverySourceEvidenceMapper evidence=mock(DeliverySourceEvidenceMapper.class);
FleetPoolOrderBridge service;
FleetPoolOrderLink row; LocalLooseboardOrder looseOrder; UsTruckOrder truckOrder;
@BeforeEach void setup(){
service=new FleetPoolOrderBridge(links,loose,looseHistory,trucks,truckHistory,numbers,new ObjectMapper().findAndRegisterModules(),evidence);
when(evidence.dispatchIds("9",77)).thenReturn(List.of(70L));
when(evidence.returns("9",70)).thenReturn(List.of());
when(numbers.next(anyString(),any(),anyLong())).thenReturn("LOCAL-001");
when(links.lockBooking(7L)).thenAnswer(a->row);when(links.lockBookingNo("FB-7")).thenAnswer(a->row);
when(links.lockIncoming("9","WT-7")).thenAnswer(a->row);
when(links.lockDispatch("9","DISPATCH-7")).thenAnswer(a->row==null?List.of():List.of(row));
doAnswer(a->{row=a.getArgument(0);return 1;}).when(links).insert(any(FleetPoolOrderLink.class));
doAnswer(a->{looseOrder=a.getArgument(0);return 1;}).when(loose).insert(any(LocalLooseboardOrder.class));
doAnswer(a->{truckOrder=a.getArgument(0);return 1;}).when(trucks).insert(any(UsTruckOrder.class));
when(loose.lockNo("9","LOCAL-001")).thenAnswer(a->looseOrder);
when(trucks.lockNo("9","LOCAL-001")).thenAnswer(a->truckOrder);
}
ConfirmedBooking input(String type){return new ConfirmedBooking(7,"FB-7","9",type,"92","起运仓","FBA:ONT8","ONT8",
LocalDateTime.of(2030,1,1,12,0),"SELF_DELIVERY","客户自送",null,"货物",4,2,
BigDecimal.TEN,BigDecimal.ONE,"USD","联系人","123456","本轮池订单");}
void incoming(){service.bindIncoming("FB-7","9","WT-7",77,"92","FBA:ONT8","op");}
FleetBookingIncomingGateway.Evidence receipt(){return new FleetBookingIncomingGateway.Evidence("WT-7","WT-7","92","FBA:ONT8","READY_FOR_DISPATCH","hash",List.of(
new FleetBookingIncomingGateway.Pallet("101","PALLET-1",2,BigDecimal.TEN,BigDecimal.ONE),
new FleetBookingIncomingGateway.Pallet("102","PALLET-2",2,BigDecimal.TEN,BigDecimal.ONE)),4);}
void dispatch(String type){service.confirm(input(type),"op");incoming();service.receiving("9","WT-7","pda");
service.acceptedReceipt(7,receipt(),3,"op");service.boundDispatch(7,"9","DISPATCH-7","op");}
DeliveryOrder delivery(int signed){var d=new DeliveryOrder();d.setId(71L);d.setDispatchOrderId(70L);d.setDeliveryGroupKey("CODE:ONT8");d.setCustomerId("9");d.setDispatchNo("DISPATCH-7");d.setDeliveryNo("DELIVERY-7");
d.setStatus("DELIVERED");d.setPackageCount(4);d.setSignedPackages(signed);d.setSignedAt(LocalDateTime.now());
when(evidence.deliveries("9",70)).thenReturn(List.of(new SourceDeliveryCompletionPolicy.Delivery(71,70,"CODE:ONT8",4,(long)signed,d.getSignedAt(),d.getSignedAt().minusHours(1))));
when(evidence.picks("9",70)).thenReturn(List.of(new SourceDeliveryCompletionPolicy.Pick(1,70,"CODE:ONT8",77,4,1L,null,null,null)));
return d;}
@Test void looseboardConfirmationCreatesOnlyCommercialRecordAndReplaysWithoutAnotherOrder(){
var result=service.confirm(input("LOCAL_LOOSEBOARD"),"op");
assertThat(result.businessOrderNo()).isEqualTo("LOCAL-001");assertThat(looseOrder.getLastlegOrderId()).isNull();
assertThat(looseOrder.getBudgetAmount()).isNull();assertThat(row.getIncomingNo()).isNull();
assertThat(looseOrder.getStatus()).isEqualTo("WAITING_INBOUND");
assertThat(service.confirm(input("LOCAL_LOOSEBOARD"),"op")).isEqualTo(result);
verify(loose,times(1)).insert(any(LocalLooseboardOrder.class));verifyNoInteractions(trucks);
assertThatThrownBy(()->service.confirm(input("US_TRUCK"),"op")).hasMessageContaining("不同商业订单");
}
@Test void truckKeepsForecastPhysicalPalletsSeparateFromActualSpaces(){
dispatch("US_TRUCK");
assertThat(truckOrder.getPalletCount()).isEqualTo(2);assertThat(truckOrder.getReceivedPalletCount()).isEqualTo(2);
assertThat(truckOrder.getAllocatedPalletCount()).isEqualTo(2);assertThat(row.getActualPalletSpaces()).isEqualTo(3);
assertThat(truckOrder.getBudgetAmount()).isNull();assertThat(truckOrder.getStatus()).isEqualTo("PENDING_DISPATCH");
verifyNoInteractions(loose);
}
@Test void incomingMustMatchCustomerWarehouseAndDestinationAndNeverBindSecondTailOrder(){
service.confirm(input("LOCAL_LOOSEBOARD"),"op");
assertThatThrownBy(()->service.bindIncoming("FB-7","another","WT-7",77,"92","FBA:ONT8","op")).hasMessageContaining("客户");
assertThatThrownBy(()->service.bindIncoming("FB-7","9","WT-7",77,"93","FBA:ONT8","op")).hasMessageContaining("仓库");
assertThatThrownBy(()->service.bindIncoming("FB-7","9","WT-7",77,"92","FBA:LAX9","op")).hasMessageContaining("目的地");
incoming();incoming();assertThat(looseOrder.getLastlegOrderId()).isEqualTo(77L);
assertThatThrownBy(()->service.bindIncoming("FB-7","9","WT-8",78,"92","FBA:ONT8","op")).hasMessageContaining("其他来货");
}
@Test void cancellationOnlyBeforeIncomingAndIsIdempotent(){
service.confirm(input("US_TRUCK"),"op");service.cancelBeforeIncoming(7,"9","op","取消承运");
service.cancelBeforeIncoming(7,"9","op","取消承运");assertThat(truckOrder.getStatus()).isEqualTo("CANCELLED");
verify(truckHistory,times(2)).insert(any(UsTruckOrderStatusHistory.class));
assertThatThrownBy(this::incoming).hasMessageContaining("不能登记来货");
}
@Test void incomingBlocksCancellationAndWrongReceiptIdentityOrDuplicatePhysicalPallets(){
service.confirm(input("US_TRUCK"),"op");incoming();
assertThatThrownBy(()->service.cancelBeforeIncoming(7,"9","op","取消")).hasMessageContaining("已登记来货");
var r=receipt();var wrong=new FleetBookingIncomingGateway.Evidence("OTHER",r.inboundCode(),r.warehouseId(),r.destinationKey(),r.status(),r.receiptHash(),r.pallets(),4);
assertThatThrownBy(()->service.acceptedReceipt(7,wrong,3,"op")).hasMessageContaining("来源");
var duplicate=new FleetBookingIncomingGateway.Evidence(r.incomingNo(),r.inboundCode(),r.warehouseId(),r.destinationKey(),r.status(),r.receiptHash(),List.of(r.pallets().get(0),r.pallets().get(0)),4);
assertThatThrownBy(()->service.acceptedReceipt(7,duplicate,3,"op")).hasMessageContaining("重复");
}
@Test void actualDepartureAndFullSignatureDriveLooseboardExactlyOnce(){
dispatch("LOCAL_LOOSEBOARD");
assertThatThrownBy(()->service.departed("9","DISPATCH-7",Map.of(77L,3),"op",LocalDateTime.now())).hasMessageContaining("完整货量");
service.departed("9","DISPATCH-7",Map.of(77L,4),"op",LocalDateTime.now());
assertThat(looseOrder.getStatus()).isEqualTo("IN_TRANSIT");
service.signed(delivery(2),Map.of(77L,4L),"op",LocalDateTime.now());assertThat(looseOrder.getStatus()).isEqualTo("IN_TRANSIT");
service.signed(delivery(4),Map.of(77L,4L),"op",LocalDateTime.now());assertThat(looseOrder.getStatus()).isEqualTo("COMPLETED");
long version=row.getRecordVersion();service.signed(delivery(4),Map.of(77L,4L),"op",LocalDateTime.now());assertThat(row.getRecordVersion()).isEqualTo(version);
}
@Test void truckCompletesOnlyFromItsWholeActualShipment(){
dispatch("US_TRUCK");service.departed("9","DISPATCH-7",Map.of(77L,4),"op",LocalDateTime.now());
assertThatThrownBy(()->service.signed(delivery(4),Map.of(77L,3L),"op",LocalDateTime.now())).hasMessageContaining("来源数量不完整");
service.signed(delivery(4),Map.of(77L,4L),"op",LocalDateTime.now());assertThat(truckOrder.getStatus()).isEqualTo("COMPLETED");
long version=row.getRecordVersion();service.signed(delivery(4),Map.of(77L,4L),"op",LocalDateTime.now());assertThat(row.getRecordVersion()).isEqualTo(version);
}
@Test void truckRequiresActualForecastPalletCountWithoutSubstitutingSpacesOrOne(){
var p=input("US_TRUCK");var missing=new ConfirmedBooking(p.bookingId(),p.bookingNo(),p.customerId(),p.businessType(),p.warehouseId(),
p.warehouseName(),p.destinationKey(),p.destinationLabel(),p.plannedDepartureAt(),p.deliveryMode(),p.deliveryInstructions(),
p.estimatedArrivalAt(),p.cargoName(),p.packageCount(),null,p.weightKg(),p.volumeCbm(),p.currency(),p.contactName(),p.contactPhone(),p.remark());
assertThatThrownBy(()->service.confirm(missing,"op")).hasMessageContaining("独立实托数");
verifyNoInteractions(trucks,loose,links);
}
@Test void anotherCoShippedSourceReturnDoesNotBlockThisFullySignedCommercialOrder(){
dispatch("US_TRUCK");service.departed("9","DISPATCH-7",Map.of(77L,4),"op",LocalDateTime.now());
var d=delivery(6);d.setPackageCount(8);
when(evidence.deliveries("9",70)).thenReturn(List.of(new SourceDeliveryCompletionPolicy.Delivery(71,70,"CODE:ONT8",8,6L,d.getSignedAt(),d.getSignedAt().minusHours(1))));
when(evidence.picks("9",70)).thenReturn(List.of(new SourceDeliveryCompletionPolicy.Pick(1,70,"CODE:ONT8",77,4,1L,null,null,null),
new SourceDeliveryCompletionPolicy.Pick(2,70,"CODE:ONT8",78,4,2L,null,null,null)));
when(evidence.returns("9",70)).thenReturn(List.of(new SourceDeliveryCompletionPolicy.Returned(9,71,78,2,d.getSignedAt().minusMinutes(5))));
service.signed(d,Map.of(77L,4L,78L,4L),"op",LocalDateTime.now());assertThat(truckOrder.getStatus()).isEqualTo("COMPLETED");
}
@Test void thisSourceReturnOrMissingHistoryCannotBeCountedAsWholeSignature(){
dispatch("US_TRUCK");service.departed("9","DISPATCH-7",Map.of(77L,4),"op",LocalDateTime.now());
var d=delivery(2);
when(evidence.returns("9",70)).thenReturn(List.of(new SourceDeliveryCompletionPolicy.Returned(9,71,77,2,d.getSignedAt().minusMinutes(5))));
service.signed(d,Map.of(77L,4L),"op",LocalDateTime.now());assertThat(truckOrder.getStatus()).isEqualTo("IN_TRANSIT");
d=delivery(4);when(evidence.dispatchIds("9",77)).thenReturn(List.of());
service.signed(d,Map.of(77L,4L),"op",LocalDateTime.now());assertThat(truckOrder.getStatus()).isEqualTo("IN_TRANSIT");
}
@Test void bindingDoesNotMeanDepartureAndCannotCompleteBeforeActualDeparture(){
dispatch("US_TRUCK");assertThat(truckOrder.getStatus()).isEqualTo("PENDING_DISPATCH");
assertThatThrownBy(()->service.signed(delivery(4),Map.of(77L,4L),"op",LocalDateTime.now())).hasMessageContaining("实际出库");
service.unboundDispatch(7,"9","DISPATCH-7","op");assertThat(row.getDispatchNo()).isNull();
assertThat(truckOrder.getStatus()).isEqualTo("WAITING_CONSOLIDATION");
}
@Test void allLifecycleMethodsRequireCallersExistingTransactionAndNoParentServicesAreInjected(){
for(var method:FleetPoolOrderBridge.class.getDeclaredMethods())if(Set.of("confirm","bindIncoming","receiving","acceptedReceipt","boundDispatch","unboundDispatch","departed","signed","cancelBeforeIncoming").contains(method.getName()))
assertThat(method.getAnnotation(Transactional.class).propagation()).isEqualTo(Propagation.MANDATORY);
assertThat(Arrays.stream(FleetPoolOrderBridge.class.getDeclaredFields()).map(f->f.getType().getSimpleName()))
.noneMatch(name->name.contains("FleetTrip")||name.contains("FleetBooking")||name.contains("Fee")||name.contains("Inventory"));
}
@Test void poolOnlyCallbacksCannotSucceedWithoutTheirCommercialLink(){
assertThatThrownBy(()->service.acceptedReceipt(7,receipt(),1,"op")).hasMessageContaining("缺少唯一商业订单关联");
assertThatThrownBy(()->service.boundDispatch(7,"9","DISPATCH-7","op")).hasMessageContaining("缺少唯一商业订单关联");
assertThatThrownBy(()->service.unboundDispatch(7,"9","DISPATCH-7","op")).hasMessageContaining("缺少唯一商业订单关联");
assertThatThrownBy(()->service.cancelBeforeIncoming(7,"9","op","cancel")).hasMessageContaining("缺少唯一商业订单关联");
when(links.poolBooking("FB-7")).thenReturn(1);
assertThatThrownBy(this::incoming).hasMessageContaining("缺少唯一商业订单关联");
verifyNoInteractions(loose,trucks);
}
}
@@ -0,0 +1,54 @@
package com.baiyue.lastleg.fleet.business;
import org.junit.jupiter.api.Test;
import java.util.*;
import static com.baiyue.lastleg.fleet.business.FleetPoolOrderModels.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
class FleetPoolOrderQueriesTest {
FleetPoolArrangementRow row(){var r=new FleetPoolArrangementRow();r.setCustomerId("9");r.setBusinessType("US_TRUCK");r.setBusinessOrderId(1L);
r.setBusinessOrderNo("LOCAL-1");r.setBookingNo("FB-1");r.setActualPackageCount(4);r.setFulfillmentStage("IN_TRANSIT");return r;}
@Test void batchSummaryIsOneQueryAndRejectsForeignCustomerRows(){
var mapper=mock(FleetPoolOrderMapper.class);var service=new FleetPoolOrderQueries(mapper);
var keys=List.of(new OrderKey("9","US_TRUCK",1),new OrderKey("9","US_TRUCK",2));
when(mapper.arrangements(any())).thenReturn(List.of(row()));
assertThat(service.arrangements(keys)).hasSize(1);verify(mapper,times(1)).arrangements(any());
var foreign=row();foreign.setCustomerId("OTHER");when(mapper.arrangements(any())).thenReturn(List.of(foreign));
assertThatThrownBy(()->service.arrangements(keys)).hasMessageContaining("客户范围");
}
@Test void independentOrdersAndEmptyPagesRemainUnlinked(){
var mapper=mock(FleetPoolOrderMapper.class);var service=new FleetPoolOrderQueries(mapper);
assertThat(service.arrangements(List.of())).isEmpty();verifyNoInteractions(mapper);
assertThat(service.arrangement(new OrderKey("9","US_TRUCK",1))).isNull();
}
@Test void partialReturnsDoNotPretendAllSignedAndUnknownCountsStayUnknown(){
var r=row();r.setDeliveryStatus("DELIVERED");r.setDeliveryPackages(4);r.setSignedPackages(3);r.setReturnedPackages(1);r.setDeliveryReturnedPackages(1);
var result=FleetPoolOrderQueries.project(r);assertThat(result.signedPackages()).isEqualTo(3);assertThat(result.unsignedPackages()).isEqualTo(1);
assertThat(result.returnedPackages()).isEqualTo(1);assertThat(result.exceptionSummary()).isNotBlank();
r.setDeliveryReturnedPackages(0);assertThat(FleetPoolOrderQueries.project(r).signedPackages()).isNull();
r.setActualPackageCount(null);assertThat(FleetPoolOrderQueries.project(r).unsignedPackages()).isNull();
}
@Test void signedOrderRetainsHistoricalSignatureAfterLaterReturn(){
var r=row();r.setFulfillmentStage("COMPLETED");r.setReturnedPackages(1);
var result=FleetPoolOrderQueries.project(r);assertThat(result.signedPackages()).isEqualTo(4);assertThat(result.unsignedPackages()).isZero();
assertThat(result.exceptionSummary()).isNotBlank();
}
@Test void businessAndIncomingOldCommandsFailClosedForPoolSources(){
var mapper=mock(FleetPoolOrderMapper.class);var service=new FleetPoolOrderQueries(mapper);
when(mapper.linked("9","US_TRUCK",1)).thenReturn(1);when(mapper.linkedIncoming("9","WT-1")).thenReturn(1);
assertThatThrownBy(()->service.requireStandalone("9","US_TRUCK",1)).hasMessageContaining("承运安排");
assertThatThrownBy(()->service.requireIndependentIncoming("9","WT-1")).hasMessageContaining("不支持");
}
@Test void publicSummaryHasNoInternalIdsOrPrivateFleetAndCustomerFields(){
var names=Arrays.stream(Arrangement.class.getRecordComponents()).map(c->c.getName()).toList();
assertThat(names).doesNotContain("bookingId","offerId","customerId","driverPhone","purchasePrice","remainingCapacity");
assertThat(names).contains("bookingNo","businessOrderNo","signedPackages","returnedPackages","readOnlyReason");
}
@Test void plannedTripUtcAndLocalBusinessStageAreReturnedAsUnambiguousInstants(){
var r=row();r.setPlannedDepartureAt(java.time.LocalDateTime.of(2030,1,1,8,0));r.setStageOccurredAt(java.time.LocalDateTime.of(2030,1,1,9,0));
var result=FleetPoolOrderQueries.project(r);
assertThat(result.plannedDepartureAt()).isEqualTo(java.time.Instant.parse("2030-01-01T08:00:00Z"));
assertThat(result.stageOccurredAt()).isEqualTo(r.getStageOccurredAt().atZone(java.time.ZoneId.systemDefault()).toInstant());
}
}
@@ -0,0 +1,71 @@
package com.baiyue.lastleg.fleet.trip;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge;
import com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory;
import com.baiyue.lastleg.fleet.domain.TripCapacityPolicy;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.*;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import java.math.BigDecimal;
import java.time.*;
import java.util.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
class FleetCapacityOfferServiceTest {
private final FleetCapacityOfferMapper offers=mock(FleetCapacityOfferMapper.class);
private final FleetTripMapper trips=mock(FleetTripMapper.class);
private final FleetTripExecutionService execution=mock(FleetTripExecutionService.class);
private final FleetBookingReservations reservations=mock(FleetBookingReservations.class);
private final Instant now=Instant.parse("2026-10-07T01:00:00Z");
private FleetCapacityOfferService service;
@BeforeEach void setup(){
var permissions=Set.of(FleetCapacityOfferService.LIST);
var user=new AuthenticatedUser(101L,0L,0L,101L,"sales","ADMIN","WEB",permissions);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user,"",List.of(new SimpleGrantedAuthority(FleetCapacityOfferService.LIST))));
service=new FleetCapacityOfferService(offers,mock(FleetCapacityOfferCommandMapper.class),trips,mock(FleetBookingMapper.class),mock(FleetBookingService.class),reservations,
execution,mock(FleetCustomerDirectory.class),mock(FleetPoolOrderBridge.class),new ObjectMapper().registerModule(new JavaTimeModule()),Clock.fixed(now,ZoneOffset.UTC));
}
@AfterEach void clear(){SecurityContextHolder.clearContext();}
@Test void ordinaryPageOnlyCalculatesRequestedDatabasePageAndPreservesAccurateTotal(){
var rows=List.of(offer(1),offer(2));var page=new Page<FleetCapacityOfferEntity>(3,20,2000);page.setRecords(rows);
when(offers.pageOffers(any(),any(),eq(true))).thenReturn(page);
for(var row:rows){var trip=trip(row.getTripNo());when(trips.findByNo(row.getTripNo())).thenReturn(trip);
when(reservations.stops(trip)).thenReturn(List.of(new FleetTripModels.Stop("起点",null,null),new FleetTripModels.Stop("ONT8",null,"FBA:ONT8")));
when(execution.capacityPlan(row.getTripNo())).thenReturn(new FleetExecutionModels.Planning(row.getTripNo(),1L,List.of(),"hash",null,false,"计划待核对",List.of()));}
var query=new FleetCapacityOfferModels.Query(3,20,"FO",null,null,null,null,null,null,null);
var result=service.page(query);
assertThat(result.total()).isEqualTo(2000);assertThat(result.page()).isEqualTo(3);assertThat(result.records()).hasSize(2);
var captor=ArgumentCaptor.forClass(Page.class);verify(offers).pageOffers(captor.capture(),same(query),eq(true));
assertThat(captor.getValue().getCurrent()).isEqualTo(3);assertThat(captor.getValue().getSize()).isEqualTo(20);
verify(execution,times(2)).capacityPlan(anyString());verify(trips,times(2)).findByNo(anyString());verifyNoMoreInteractions(execution,trips);
}
@Test void broadDynamicAvailabilityFilterRejectsBeforeAnyPerTripCapacityQuery(){
when(offers.pageOffers(any(),any(),eq(true))).thenReturn(new Page<>(1,100,101));
assertThatThrownBy(()->service.page(new FleetCapacityOfferModels.Query(1,20,null,null,null,null,null,null,true,null))).hasMessageContaining("100");
verifyNoInteractions(execution,trips);
}
@Test void cutoffUsesStrictServerInstantAndRechecksChangedDeparture(){
var trip=trip("FT-A");var offer=offer(1);offer.setStatus("OPEN");
assertThat(FleetCapacityOfferService.openBlock(offer,trip,now)).isNull();
assertThat(FleetCapacityOfferService.openBlock(offer,trip,offer.getCutoffAt().toInstant(ZoneOffset.UTC))).contains("截止");
trip.setPlannedDepartureAt(offer.getCutoffAt().minusMinutes(1));
assertThat(FleetCapacityOfferService.openBlock(offer,trip,now)).contains("截止");
trip.setStatus("IN_TRANSIT");assertThat(FleetCapacityOfferService.openBlock(offer,trip,now)).contains("已发车");
}
@Test void destinationCapacityUsesIndependentMinimumForEveryDimension(){
var a=new TripCapacityPolicy.Load(12,new BigDecimal("600"),new BigDecimal("8"));
var b=new TripCapacityPolicy.Load(4,new BigDecimal("1000"),new BigDecimal("3"));
var result=FleetCapacityOfferService.min(a,b);assertThat(result.palletSpaces()).isEqualTo(4);
assertThat(result.weightKg()).isEqualByComparingTo("600");assertThat(result.volumeCbm()).isEqualByComparingTo("3");
}
private FleetCapacityOfferEntity offer(long id){var row=new FleetCapacityOfferEntity();row.setId(id);row.setOfferNo("FO-"+id);row.setTripNo("FT-"+id);row.setTripId(id);row.setStatus("OPEN");row.setRecordVersion(0L);row.setCutoffAt(LocalDateTime.ofInstant(now.plusSeconds(1800),ZoneOffset.UTC));return row;}
private FleetTripEntity trip(String number){var trip=new FleetTripEntity();trip.setId(1L);trip.setTripNo(number);trip.setStatus("PLANNED");trip.setWarehouseId(92L);trip.setWarehouseName("起点");trip.setRecordVersion(1L);trip.setPlannedDepartureAt(LocalDateTime.ofInstant(now.plusSeconds(3600),ZoneOffset.UTC));return trip;}
}
@@ -57,6 +57,7 @@ class FleetTripAuthorizationTest {
}
@Configuration @EnableMethodSecurity
static class Config {
@Bean com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge poolOrders() { return mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge.class); }
@Bean FleetBookingService bookingService() { return mock(FleetBookingService.class); }
@Bean com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory customers() { return mock(com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory.class); }
@Bean PlatformFleetBookingController booking(FleetBookingService service, com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory customers) { return new PlatformFleetBookingController(service,customers); }
@@ -64,6 +64,8 @@ class FleetTripPersistenceTest {
private FleetTripExecutionService execution;
private FleetTripCompletionService completion;
private FleetBookingService bookingService;
private FleetCapacityOfferService capacityOffers;
private com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge poolOrders;
private FleetBookingIncomingGateway incoming;
private FleetBookingQuoteContext quoteContext;
private com.baiyue.lastleg.fleet.quote.FleetQuoteCandidateService quoteCandidates;
@@ -87,7 +89,7 @@ class FleetTripPersistenceTest {
jdbc = new JdbcTemplate(dataSource); transactions = new DataSourceTransactionManager(dataSource);
String schema = Files.readString(Path.of("src/main/resources/db/lastleg.sql"));
for (String table : List.of("ll_fleet_driver", "ll_fleet_driver_history", "ll_vehicle", "ll_fleet_trip", "ll_fleet_resource_assignment", "ll_fleet_trip_history",
"ll_fleet_external_booking", "ll_fleet_trip_dispatch", "ll_dispatch_order", "ll_dispatch_pda_task", "ll_outbound_order", "ll_delivery_order",
"ll_fleet_capacity_offer", "ll_fleet_capacity_offer_command", "ll_fleet_external_booking", "ll_fleet_trip_dispatch", "ll_dispatch_order", "ll_dispatch_pda_task", "ll_outbound_order", "ll_delivery_order",
"ll_dispatch_item", "ll_dispatch_pda_pick", "ll_dispatch_pda_operation", "ll_dispatch_pallet_lock", "ll_delivery_operation",
"ll_outbound_pallet_item", "ll_exception_order", "ll_exception_return_item", "ll_inventory_ledger", "ll_dispatch_pda_pick_order")) {
var match = Pattern.compile("(?s)CREATE TABLE(?: IF NOT EXISTS)? " + table + " \\(.*?;").matcher(schema);
@@ -96,7 +98,7 @@ class FleetTripPersistenceTest {
}
var configuration = new MybatisConfiguration();
for (var mapper : List.of(DriverMapper.class, DriverHistoryMapper.class, VehicleMapper.class, FleetTripMapper.class, FleetResourceMapper.class,
FleetTripHistoryMapper.class, FleetTripDispatchMapper.class, FleetBookingMapper.class, DispatchOrderMapper.class, DispatchPdaTaskMapper.class, OutboundOrderMapper.class, DeliveryOrderMapper.class,
FleetTripHistoryMapper.class, FleetTripDispatchMapper.class, FleetBookingMapper.class, FleetCapacityOfferMapper.class, FleetCapacityOfferCommandMapper.class, DispatchOrderMapper.class, DispatchPdaTaskMapper.class, OutboundOrderMapper.class, DeliveryOrderMapper.class,
DispatchItemMapper.class, DispatchPdaPickMapper.class, DispatchPdaOperationMapper.class, DispatchPalletLockMapper.class, DeliveryOperationMapper.class)) configuration.addMapper(mapper);
var pagination = new com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor();
pagination.addInnerInterceptor(new com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor(com.baomidou.mybatisplus.annotation.DbType.H2));
@@ -115,6 +117,10 @@ class FleetTripPersistenceTest {
vehicleService = transactional(new VehicleApplicationService(vehicles, new FleetVehicleAssignmentGuard(resources)), VehicleApplicationService.class);
var bookingMapper = session.getMapper(FleetBookingMapper.class);
var reservations = new FleetBookingReservations(bookingMapper, json);
poolOrders=mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge.class);
when(poolOrders.businessOrderNumbers(anyCollection())).thenReturn(java.util.Map.of());
ReflectionTestUtils.setField(reservations,"offers",session.getMapper(FleetCapacityOfferMapper.class));
ReflectionTestUtils.setField(reservations,"poolOrders",poolOrders);
var bindingMapper = session.getMapper(FleetTripDispatchMapper.class);
bindingGuard = transactional(new FleetDispatchBindingGuard(bindingMapper, json), FleetDispatchBindingGuard.class);
var workflow = new DispatchPdaWorkflowService();
@@ -128,6 +134,7 @@ class FleetTripPersistenceTest {
ReflectionTestUtils.setField(workflow, "fleetBindings", bindingGuard);
ReflectionTestUtils.setField(workflow, "objectMapper", json);
ReflectionTestUtils.setField(workflow, "pickAttribution", mock(DispatchPickOrderAttributionService.class));
ReflectionTestUtils.setField(workflow,"fleetPool",poolOrders);
var dispatchGateway = transactional(new DispatchFleetAdapter(session.getMapper(DispatchOrderMapper.class), session.getMapper(DispatchPdaTaskMapper.class),
session.getMapper(OutboundOrderMapper.class), session.getMapper(DeliveryOrderMapper.class), session.getMapper(DispatchItemMapper.class), session.getMapper(DispatchPdaPickMapper.class),
transactional(workflow, DispatchPdaWorkflowService.class), session.getMapper(DeliveryOperationMapper.class)), DispatchFleetAdapter.class);
@@ -141,9 +148,111 @@ class FleetTripPersistenceTest {
quoteContext=mock(FleetBookingQuoteContext.class);
quoteCandidates=mock(com.baiyue.lastleg.fleet.quote.FleetQuoteCandidateService.class);
bookingService = transactional(new FleetBookingService(session.getMapper(FleetTripMapper.class), bookingMapper, reservations, service, execution, customerDirectory, json, incoming, quoteContext, quoteCandidates), FleetBookingService.class);
ReflectionTestUtils.setField(bookingService,"poolOrders",poolOrders);
capacityOffers=transactional(new FleetCapacityOfferService(session.getMapper(FleetCapacityOfferMapper.class),session.getMapper(FleetCapacityOfferCommandMapper.class),
session.getMapper(FleetTripMapper.class),bookingMapper,bookingService,reservations,execution,customerDirectory,poolOrders,json),FleetCapacityOfferService.class);
completion = transactional(new FleetTripCompletionService(session.getMapper(FleetTripMapper.class), bindingMapper, resources, dispatchGateway, service, vehicles, vehicleService, json), FleetTripCompletionService.class);
}
@Test void poolSalesAreIsolatedByUserIdAndCannotConfirmOrReadAllInquiries() {
var offer=publishedOffer();poolLogin(101,false);
var first=capacityOffers.createInquiry(offer.offerNo(),poolDraft(offer,2,"owner-first"));
poolLogin(102,false);
assertThatThrownBy(()->capacityOffers.inquiry(first.inquiry().bookingNo())).hasMessageContaining("无权");
assertThatThrownBy(()->capacityOffers.inquiries(new FleetCapacityOfferModels.InquiryQuery(1,20,null,null,null,"ALL"))).hasMessageContaining("无权");
assertThatThrownBy(()->capacityOffers.confirm(first.inquiry().bookingNo(),poolCommand(first.inquiry(),"sales-confirm"))).hasMessageContaining("无权");
poolLogin(101,false);
assertThat(capacityOffers.inquiries(new FleetCapacityOfferModels.InquiryQuery(1,20,null,null,null,"MINE")).records()).extracting(FleetCapacityOfferModels.Inquiry::bookingNo).containsExactly(first.inquiry().bookingNo());
assertThat(jdbc.queryForObject("SELECT sales_owner_user_id FROM ll_fleet_external_booking WHERE booking_no=?",String.class,first.inquiry().bookingNo())).isEqualTo("101");
}
@Test void poolConfirmSerializesLastSpaceAndOriginalSuccessfulCommandReplaysAfterClose() throws Exception {
var offer=publishedOffer();poolLogin(101,false);
var first=capacityOffers.createInquiry(offer.offerNo(),poolDraft(offer,8,"pool-first"));
poolLogin(102,false);
var second=capacityOffers.createInquiry(offer.offerNo(),poolDraft(first.offer(),8,"pool-second"));
poolLogin(1,true);
var firstCurrent=capacityOffers.inquiry(first.inquiry().bookingNo());
var secondCurrent=capacityOffers.inquiry(second.inquiry().bookingNo());
var firstRequest=poolCommand(firstCurrent,"pool-confirm-first");var secondRequest=poolCommand(secondCurrent,"pool-confirm-second");
var latch=new CountDownLatch(1);var workers=Executors.newFixedThreadPool(2);
java.util.concurrent.Callable<Boolean> a=()->poolConfirmAttempt(latch,firstCurrent.bookingNo(),firstRequest);
java.util.concurrent.Callable<Boolean> b=()->poolConfirmAttempt(latch,secondCurrent.bookingNo(),secondRequest);
try {var fa=workers.submit(a);var fb=workers.submit(b);latch.countDown();assertThat(List.of(fa.get(15,TimeUnit.SECONDS),fb.get(15,TimeUnit.SECONDS))).containsExactlyInAnyOrder(true,false);}
finally {workers.shutdownNow();}
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_fleet_external_booking WHERE status='CONFIRMED'",Integer.class)).isEqualTo(1);
var current=capacityOffers.detail(offer.offerNo()).offer();assertThat(current.destinations().get(0).remaining().palletSpaces()).isEqualTo(4);
String winner=jdbc.queryForObject("SELECT booking_no FROM ll_fleet_external_booking WHERE status='CONFIRMED'",String.class);
String loser=winner.equals(firstCurrent.bookingNo())?secondCurrent.bookingNo():firstCurrent.bookingNo();
var loserRow=capacityOffers.inquiry(loser);
assertThatThrownBy(()->capacityOffers.confirm(loser,poolCommand(loserRow,"retry-rejected-space"))).hasMessageContaining("超过车辆");
capacityOffers.command(offer.offerNo(),"CLOSE",new FleetCapacityOfferModels.Command(current.tripRecordVersion(),current.recordVersion(),"pool-close-test",null));
var original=winner.equals(firstCurrent.bookingNo())?firstRequest:secondRequest;
assertThat(capacityOffers.confirm(winner,original).replayed()).isTrue();
assertThatThrownBy(()->capacityOffers.confirm(winner,new FleetCapacityOfferModels.InquiryCommand(original.tripRecordVersion(),original.offerRecordVersion(),original.recordVersion(),original.idempotencyKey(),"changed"))).hasMessageContaining("不能更换");
verify(poolOrders,times(1)).confirm(any(),eq("1"));
poolLogin(winner.equals(firstCurrent.bookingNo())?101:102,false);
assertThat(capacityOffers.inquiry(winner).status()).isEqualTo("CONFIRMED");
}
@Test void poolCommercialFailureRollsBackReservationAndOldBookingEndpointCannotBypassPool() {
var offer=publishedOffer();var draft=capacityOffers.createInquiry(offer.offerNo(),poolDraft(offer,8,"bridge-rollback-create"));
doThrow(new IllegalStateException("bridge unavailable")).when(poolOrders).confirm(any(),anyString());
assertThatThrownBy(()->capacityOffers.confirm(draft.inquiry().bookingNo(),poolCommand(draft.inquiry(),"bridge-rollback-command"))).hasMessageContaining("bridge unavailable");
assertThat(capacityOffers.inquiry(draft.inquiry().bookingNo()).status()).isEqualTo("DRAFT");
assertThat(capacityOffers.detail(offer.offerNo()).offer().destinations().get(0).remaining().palletSpaces()).isEqualTo(12);
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_fleet_capacity_offer_command WHERE action='CONFIRM'",Integer.class)).isZero();
assertThatThrownBy(()->bookingService.confirm(offer.tripNo(),draft.inquiry().bookingNo(),"1",new FleetBookingModels.Command(draft.inquiry().tripRecordVersion(),0L,"old-pool-bypass",null))).hasMessageContaining("需求池");
}
@Test void liveVehicleChangesBlockCommitButDoNotFabricateDraftQuantityOrCapacity() {
var offer=publishedOffer();var currentTrip=service.detail(offer.tripNo()).trip();
jdbc.update("UPDATE ll_vehicle SET max_pallet_spaces=20 WHERE id=?",Long.parseLong(currentTrip.vehicleId()));
var current=capacityOffers.detail(offer.offerNo()).offer();
assertThat(current.capacityKnown()).isFalse();assertThat(current.canConfirm()).isFalse();assertThat(current.canCreateInquiry()).isTrue();
assertThat(current.destinations()).allMatch(row->row.remaining()==null);
var unknown=new FleetBookingModels.Cargo("FBA:ONT8","待核货物",null,null,null,null,"PALLET",null,"USD",null,null,null);
var draft=capacityOffers.createInquiry(offer.offerNo(),new FleetCapacityOfferModels.InquiryWrite(current.tripRecordVersion(),current.recordVersion(),null,9L,"US_TRUCK","SELF_DELIVERY",null,null,unknown,null,"unknown-pool-cargo"));
assertThat(draft.inquiry().cargo().packageCount()).isNull();assertThat(draft.inquiry().expectedPalletCount()).isNull();
assertThatThrownBy(()->capacityOffers.confirm(draft.inquiry().bookingNo(),poolCommand(draft.inquiry(),"changed-vehicle-confirm"))).hasMessageContaining("车辆容量");
}
@Test void poolCancellationReleasesOnlyUnstartedBookingAndPreservesActualIncoming(){
var offer=publishedOffer();var draft=capacityOffers.createInquiry(offer.offerNo(),poolDraft(offer,8,"cancel-pool-draft"));
var confirmed=capacityOffers.confirm(draft.inquiry().bookingNo(),poolCommand(draft.inquiry(),"cancel-pool-confirm"));
var cancelled=capacityOffers.cancel(confirmed.inquiry().bookingNo(),poolCommand(confirmed.inquiry(),"cancel-pool-safe"));
assertThat(cancelled.inquiry().status()).isEqualTo("CANCELLED");
assertThat(cancelled.offer().destinations().get(0).remaining().palletSpaces()).isEqualTo(12);
verify(poolOrders).cancelBeforeIncoming(anyLong(),eq("9"),eq("1"),isNull());
var next=capacityOffers.createInquiry(offer.offerNo(),poolDraft(cancelled.offer(),8,"cancel-incoming-draft"));
var reserved=capacityOffers.confirm(next.inquiry().bookingNo(),poolCommand(next.inquiry(),"cancel-incoming-confirm"));
jdbc.update("UPDATE ll_fleet_external_booking SET incoming_no='WT-STARTED' WHERE booking_no=?",reserved.inquiry().bookingNo());
assertThatThrownBy(()->capacityOffers.cancel(reserved.inquiry().bookingNo(),poolCommand(reserved.inquiry(),"cancel-incoming-refused"))).hasMessageContaining("已登记来货");
assertThat(jdbc.queryForObject("SELECT incoming_no FROM ll_fleet_external_booking WHERE booking_no=?",String.class,reserved.inquiry().bookingNo())).isEqualTo("WT-STARTED");
verify(poolOrders,times(1)).cancelBeforeIncoming(anyLong(),anyString(),anyString(),any());
}
private boolean poolConfirmAttempt(CountDownLatch latch,String booking,FleetCapacityOfferModels.InquiryCommand request) throws Exception {
poolLogin(1,true);try {latch.await(5,TimeUnit.SECONDS);capacityOffers.confirm(booking,request);return true;}catch(ResponseStatusException conflict){return false;}finally{SecurityContextHolder.clearContext();}
}
private FleetCapacityOfferModels.Offer publishedOffer(){
poolLogin(1,true);String trip=readyTrip();var plan=execution.capacityPlan(trip);
var measured=new FleetExecutionModels.Measurement("901",18,new BigDecimal("100"),new BigDecimal("1"),1);
var confirmed=execution.confirmCapacityPlan(trip,"1",new FleetExecutionModels.Confirm(plan.recordVersion(),plan.manifestHash(),List.of(measured),"自有18托位"));
var created=capacityOffers.create(new FleetCapacityOfferModels.Write(trip,confirmed.recordVersion(),null,departure.minusSeconds(60),"调度","10086",null,"offer-create-test"));
return capacityOffers.command(created.offer().offerNo(),"PUBLISH",new FleetCapacityOfferModels.Command(confirmed.recordVersion(),0L,"offer-publish-test",null)).offer();
}
private FleetCapacityOfferModels.InquiryWrite poolDraft(FleetCapacityOfferModels.Offer offer,int spaces,String key){
var cargo=new FleetBookingModels.Cargo("FBA:ONT8","外接货",20,spaces,new BigDecimal("1000"),new BigDecimal("10"),"PALLET",new BigDecimal("50.00"),"USD","验收联系人","10086",null);
return new FleetCapacityOfferModels.InquiryWrite(offer.tripRecordVersion(),offer.recordVersion(),null,9L,"LOCAL_LOOSEBOARD","SELF_DELIVERY",departure.minusSeconds(120),2,cargo,null,key);
}
private static FleetCapacityOfferModels.InquiryCommand poolCommand(FleetCapacityOfferModels.Inquiry row,String key){return new FleetCapacityOfferModels.InquiryCommand(row.tripRecordVersion(),row.offerRecordVersion(),row.recordVersion(),key,null);}
private static void poolLogin(long id,boolean manager){
var permissions=manager?java.util.Set.of(FleetCapacityOfferService.MANAGE):java.util.Set.of(FleetCapacityOfferService.LIST,"platform:fleet:capacity-offer:inquiry-create","platform:fleet:capacity-offer:inquiry-edit","platform:fleet:capacity-offer:inquiry-cancel");
var user=new com.baiyue.common.security.AuthenticatedUser(id,0L,0L,id,"same-display-name","ADMIN","WEB",permissions);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user,"unused",permissions.stream().map(SimpleGrantedAuthority::new).toList()));
}
@Test void creationReplayUsesSameBusinessNumberAndRejectsChangedPayload() {
var request = createRequest("creation-one");
var first = service.create("1", request);
@@ -53,6 +53,7 @@ class LocalLooseboardServiceTest {
@BeforeEach
void setUp() {
service = new LocalLooseboardService();
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPool", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderQueries.class));
ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "histories", histories);
ReflectionTestUtils.setField(service, "operations", operations);
@@ -15,7 +15,9 @@ import static org.assertj.core.api.Assertions.*;
class FleetIncomingAdapterTest {
@Test void createsRealCustomerDeliveryWithoutClaimingStockWasReceived() {
var service=mock(WarehouseTransferService.class);var adapter=new FleetIncomingAdapter(service,mock(WarehouseTransferOrderMapper.class),new ObjectMapper());
org.springframework.test.util.ReflectionTestUtils.setField(adapter,"fleetPool",mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge.class));
var detail=mock(WarehouseTransferResponses.Detail.class);
when(detail.lastlegOrderId()).thenReturn(71L);
when(detail.transferNo()).thenReturn("WT-1");when(detail.inboundCode()).thenReturn("WT-1");when(detail.operatingWarehouseId()).thenReturn("92");when(detail.fbaWarehouseCode()).thenReturn("ONT8");
when(detail.status()).thenReturn("WAIT_PDA_RECEIVING");when(detail.pallets()).thenReturn(List.of());
when(service.createAndSubmit(eq("9"),eq("1"),any())).thenReturn(detail);when(service.review(eq("9"),eq("WT-1"),eq("1"),any())).thenReturn(detail);
@@ -190,6 +190,8 @@ class WarehouseTransferServiceTest {
@BeforeEach
void setUp() {
service = new WarehouseTransferService();
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPool", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderBridge.class));
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPoolQueries", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderQueries.class));
ReflectionTestUtils.setField(service, "receiptCoordinator", receiptCoordinator);
lenient().when(receiptCoordinator.receive(anyString(), anyString(), anyString(), any(), any(), any()))
.thenAnswer(call -> {
@@ -53,6 +53,7 @@ class UsTruckOrderServiceTest {
@BeforeEach
void setUp() {
service = new UsTruckOrderService();
org.springframework.test.util.ReflectionTestUtils.setField(service, "fleetPool", org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.business.FleetPoolOrderQueries.class));
ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "pallets", pallets);
ReflectionTestUtils.setField(service, "histories", histories);