feat(lastleg): connect appointment followups and fleet intake
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
package com.baiyue.lastleg.fleet.trip;
|
||||
|
||||
import com.baiyue.lastleg.fleet.domain.TripCapacityPolicy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.*;
|
||||
import java.util.List;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
class FleetInquiryProgressTest {
|
||||
private final Instant cutoff=Instant.parse("2026-10-20T10:00:00Z");
|
||||
private FleetCapacityOfferModels.Offer offer() {
|
||||
return new FleetCapacityOfferModels.Offer("FO-1","TR-1","OPEN",1,1,"1","仓库",cutoff,cutoff,null,null,null,
|
||||
List.of(new FleetCapacityOfferModels.Destination("FBA:ACY2","ACY2",1,new TripCapacityPolicy.Load(4,new BigDecimal("1000"),new BigDecimal("10")))),List.of(),true,true,true,null,null);
|
||||
}
|
||||
private FleetBookingModels.Cargo cargo(Integer spaces) {
|
||||
return new FleetBookingModels.Cargo("FBA:ACY2","货物",1,spaces,new BigDecimal("100"),BigDecimal.ONE,"PALLET",BigDecimal.ZERO,"USD","客户联系人","联系信息",null);
|
||||
}
|
||||
private FleetBookingEntity booking(String status) {
|
||||
var row=new FleetBookingEntity();row.setStatus(status);row.setBusinessType("LOCAL_LOOSEBOARD");row.setEstimatedArrivalAt(LocalDateTime.ofInstant(cutoff.minusSeconds(60),ZoneOffset.UTC));return row;
|
||||
}
|
||||
@Test void unknownQuantityIsMissingAndTruckNeedsPhysicalPalletCount() {
|
||||
var row=booking("DRAFT");row.setBusinessType("US_TRUCK");
|
||||
assertThat(FleetInquiryProgress.missing(row,cargo(null),offer())).containsExactly("占用标准托位","预计实托数");
|
||||
row.setExpectedPalletCount(1);
|
||||
assertThat(FleetInquiryProgress.missing(row,cargo(1),offer())).isEmpty();
|
||||
}
|
||||
@Test void capacityIsCheckedAgainstThisDestinationAndThisShipment() {
|
||||
assertThat(FleetInquiryProgress.capacityBlock(cargo(5),offer())).contains("超过");
|
||||
assertThat(FleetInquiryProgress.capacityBlock(cargo(4),offer())).isNull();
|
||||
}
|
||||
@Test void expiryDoesNotTellSalesToEditAnUnsavableDraft() {
|
||||
var p=FleetInquiryProgress.project(booking("DRAFT"),List.of("预计重量"),"已截止",false,false,null,null);
|
||||
assertThat(p.primaryAction()).isEqualTo("VIEW");assertThat(p.reservation()).isEqualTo("尚未保位");
|
||||
}
|
||||
@Test void receivingIsNotDispatchWorkUntilActualMeasurementCompletes() {
|
||||
var row=booking("CONFIRMED");row.setIncomingNo("WT-1");
|
||||
var waiting=FleetInquiryProgress.project(row,List.of(),null,false,false,"WAIT_PDA_RECEIVING",null);
|
||||
assertThat(waiting.owner()).isEqualTo("仓库");assertThat(waiting.primaryAction()).isEqualTo("VIEW");
|
||||
var received=FleetInquiryProgress.project(row,List.of(),null,false,false,"WAIT_CUSTOMER_CONFIRMATION",null);
|
||||
assertThat(received.owner()).isEqualTo("调度");assertThat(received.primaryAction()).isEqualTo("RECEIPT");
|
||||
}
|
||||
@Test void bindingIsNotDepartureAndOnlyActualFulfillmentAdvancesTracking() {
|
||||
assertThat(FleetInquiryProgress.project(booking("BOUND"),List.of(),null,false,false,null,"PENDING_DISPATCH").label()).isEqualTo("已排入本车");
|
||||
assertThat(FleetInquiryProgress.project(booking("BOUND"),List.of(),null,false,false,null,"IN_TRANSIT").label()).isEqualTo("已发车");
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -13,11 +13,15 @@ import static org.assertj.core.api.Assertions.*;
|
||||
class FleetTripApiContractTest {
|
||||
@Test void allTripOperationsHaveOpenApiAndRealPermissions() throws Exception {
|
||||
Map<String, Object> document = new Yaml().load(Files.readString(Path.of("src/main/resources/openapi/fleet-trips-v1.yaml")));
|
||||
Map<?, ?> paths = (Map<?, ?>) document.get("paths");
|
||||
Map<String, Object> preparation = new Yaml().load(Files.readString(Path.of("src/main/resources/openapi/fleet-preparation-v1.yaml")));
|
||||
Map<String, Object> pool = new Yaml().load(Files.readString(Path.of("src/main/resources/openapi/fleet-capacity-offers-v1.yaml")));
|
||||
var paths = new java.util.HashMap<Object,Object>();
|
||||
for(var spec:java.util.List.of(document,preparation,pool))paths.putAll((Map<?,?>)spec.get("paths"));
|
||||
String module = Files.readString(Path.of("../iam/src/main/resources/db/lastleg-permissions.sql"));
|
||||
module += Files.readString(Path.of("../iam/src/main/resources/db/fleet-capacity-offer-permissions.sql"));
|
||||
String deployed = Files.readString(Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql"));
|
||||
int count = 0;
|
||||
for (var controller : java.util.List.of(PlatformFleetTripController.class, PlatformFleetBookingController.class)) {
|
||||
for (var controller : java.util.List.of(PlatformFleetTripController.class, PlatformFleetBookingController.class, PlatformFleetCapacityOfferController.class)) {
|
||||
String base = controller.getAnnotation(RequestMapping.class).value()[0];
|
||||
for (var method : controller.getDeclaredMethods()) {
|
||||
String verb = null; String[] suffix = null;
|
||||
@@ -42,8 +46,10 @@ class FleetTripApiContractTest {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
assertThat(count).isEqualTo(29);
|
||||
assertThat(count).isEqualTo(52);
|
||||
verifyRefs(document, document);
|
||||
verifyRefs(preparation, preparation);
|
||||
verifyRefs(pool, pool);
|
||||
}
|
||||
|
||||
@Test void schemasMatchAndUseUniqueResourceClaimsAndVersionedHistory() throws Exception {
|
||||
@@ -68,7 +74,7 @@ class FleetTripApiContractTest {
|
||||
if(!reference.startsWith("#/")) {
|
||||
String[] parts=reference.split("#",2);
|
||||
assertThat(parts).hasSize(2);
|
||||
assertThat(parts[0]).isEqualTo("fleet-quote-candidates-v1.yaml");
|
||||
assertThat(Path.of(parts[0]).getFileName().toString()).isIn("fleet-quote-candidates-v1.yaml","fleet-trips-v1.yaml","fleet-capacity-offers-v1.yaml");
|
||||
try { value=new Yaml().load(Files.readString(Path.of("src/main/resources/openapi",parts[0]))); }
|
||||
catch(java.io.IOException error) { throw new java.io.UncheckedIOException(error); }
|
||||
pointer="#"+parts[1];
|
||||
|
||||
+18
@@ -36,6 +36,7 @@ class FleetTripAuthorizationTest {
|
||||
assertThatThrownBy(() -> controller.cancel(null, "TR-TEST", null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(() -> controller.plan(null, "TR-TEST", null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(() -> controller.bind(null, "TR-TEST", null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(() -> controller.prepare(null,null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(() -> controller.unbind(null, "TR-TEST", null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(() -> controller.confirmLoading(null, "TR-TEST", null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(() -> controller.reviewDispatch(null, "TR-TEST", "DP-1", null)).isInstanceOf(AccessDeniedException.class);
|
||||
@@ -55,8 +56,25 @@ class FleetTripAuthorizationTest {
|
||||
verifyNoMoreInteractions(service);
|
||||
} finally { SecurityContextHolder.clearContext(); }
|
||||
}
|
||||
@Test void poolReadDoesNotGrantIncomingReceiptOrDispatchCommands() {
|
||||
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("sales","unused",List.of(new SimpleGrantedAuthority(FleetCapacityOfferService.LIST))));
|
||||
var pool=context.getBean(PlatformFleetCapacityOfferController.class);
|
||||
pool.fulfillment("FB-1");verify(context.getBean(FleetCapacityOfferService.class)).fulfillment("FB-1");
|
||||
assertThatThrownBy(()->pool.incoming("FB-1",null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(()->pool.receipt("FB-1",null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(()->pool.arrange("FB-1",null)).isInstanceOf(AccessDeniedException.class);
|
||||
assertThatThrownBy(()->pool.saveAndConfirm("FB-1",null)).isInstanceOf(AccessDeniedException.class);
|
||||
} finally {SecurityContextHolder.clearContext();}
|
||||
}
|
||||
@Configuration @EnableMethodSecurity
|
||||
static class Config {
|
||||
@Bean FleetCapacityOfferCommandMapper commands() { return mock(FleetCapacityOfferCommandMapper.class); }
|
||||
@Bean FleetDispatchPlanningGateway planning() { return mock(FleetDispatchPlanningGateway.class); }
|
||||
@Bean FleetTripPreparationService preparation() { return mock(FleetTripPreparationService.class); }
|
||||
@Bean com.baiyue.lastleg.appointmentplanning.AppointmentCoverageService appointmentCoverage() { return mock(com.baiyue.lastleg.appointmentplanning.AppointmentCoverageService.class); }
|
||||
@Bean FleetCapacityOfferService capacityOffers() { return mock(FleetCapacityOfferService.class); }
|
||||
@Bean PlatformFleetCapacityOfferController poolController(FleetCapacityOfferService service) { return new PlatformFleetCapacityOfferController(service); }
|
||||
@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); }
|
||||
|
||||
+55
-4
@@ -56,6 +56,8 @@ import static org.mockito.Mockito.*;
|
||||
class FleetTripPersistenceTest {
|
||||
private JdbcTemplate jdbc;
|
||||
private FleetTripService service;
|
||||
private FleetTripPreparationService preparation;
|
||||
private FleetDispatchPlanningGateway planning;
|
||||
private DriverService drivers;
|
||||
private VehicleRepository vehicles;
|
||||
private VehicleApplicationService vehicleService;
|
||||
@@ -141,6 +143,9 @@ class FleetTripPersistenceTest {
|
||||
transactional(workflow, DispatchPdaWorkflowService.class), session.getMapper(DeliveryOperationMapper.class)), DispatchFleetAdapter.class);
|
||||
service = transactional(new FleetTripService(session.getMapper(FleetTripMapper.class), resources, session.getMapper(FleetTripHistoryMapper.class), vehicles, drivers, directory, json,
|
||||
bindingMapper, dispatchGateway, reservations), FleetTripService.class);
|
||||
preparation = transactional(new FleetTripPreparationService(service,session.getMapper(FleetTripMapper.class),session.getMapper(FleetCapacityOfferCommandMapper.class),json),FleetTripPreparationService.class);
|
||||
planning=mock(FleetDispatchPlanningGateway.class);
|
||||
ReflectionTestUtils.setField(service,"planning",planning);
|
||||
execution = transactional(new FleetTripExecutionService(session.getMapper(FleetTripMapper.class), bindingMapper, resources, dispatchGateway, service, drivers, vehicles, json, reservations), FleetTripExecutionService.class);
|
||||
var customerDirectory = mock(com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory.class);
|
||||
when(customerDirectory.required(anyLong())).thenAnswer(call -> new com.baiyue.lastleg.fleet.customer.FleetCustomerDirectory.Customer(
|
||||
@@ -150,11 +155,47 @@ class FleetTripPersistenceTest {
|
||||
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);
|
||||
ReflectionTestUtils.setField(bookingService,"planning",planning);
|
||||
ReflectionTestUtils.setField(bookingService,"commands",session.getMapper(FleetCapacityOfferCommandMapper.class));
|
||||
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);
|
||||
ReflectionTestUtils.setField(capacityOffers,"appointmentCoverage",mock(com.baiyue.lastleg.appointmentplanning.AppointmentCoverageService.class));
|
||||
completion = transactional(new FleetTripCompletionService(session.getMapper(FleetTripMapper.class), bindingMapper, resources, dispatchGateway, service, vehicles, vehicleService, json), FleetTripCompletionService.class);
|
||||
}
|
||||
|
||||
@Test void preparationRollsBackNewTripAndResourcesIfAnyBindingFailsThenReplaysSuccess() {
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("1","unused",List.of("platform:fleet:trip:create","platform:fleet:trip:assign","platform:fleet:trip:edit").stream().map(SimpleGrantedAuthority::new).toList()));
|
||||
long vehicle=vehicle(true), driver=driver(true);dispatch(21,"A","92");dispatch(22,"B","93");
|
||||
var request=new FleetTripPreparationService.Prepare(null,null,createRequest("prepare-atomic"),vehicle,driver,List.of("DP-21","DP-22"),"prepare-atomic");
|
||||
assertThatThrownBy(()->preparation.prepare("1",request)).hasMessageContaining("同一起运仓");
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_fleet_trip",Integer.class)).isZero();
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_fleet_trip_dispatch",Integer.class)).isZero();
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_fleet_resource_assignment",Integer.class)).isZero();
|
||||
jdbc.update("UPDATE ll_dispatch_order SET overseas_warehouse_id='92' WHERE id=22");
|
||||
jdbc.update("UPDATE ll_dispatch_pda_task SET warehouse_id='92' WHERE dispatch_order_id=22");
|
||||
var saved=preparation.prepare("1",request);
|
||||
assertThat(saved.dispatches()).hasSize(2);
|
||||
assertThat(preparation.prepare("1",request)).isEqualTo(saved);
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_fleet_trip",Integer.class)).isEqualTo(1);
|
||||
assertThatThrownBy(()->preparation.prepare("1",new FleetTripPreparationService.Prepare(null,null,request.plan(),vehicle,driver,List.of("DP-21"),request.idempotencyKey()))).hasMessageContaining("不能更换内容");
|
||||
}
|
||||
|
||||
@Test void sameWindowSaveAndConfirmIsAtomicAndReplaysOriginalResult() {
|
||||
var offer=publishedOffer();var draft=capacityOffers.createInquiry(offer.offerNo(),poolDraft(offer,2,"prepare-register"));
|
||||
var input=poolDraft(draft.offer(),2,"same-window-confirm");
|
||||
var update=new FleetCapacityOfferModels.InquiryWrite(input.tripRecordVersion(),input.offerRecordVersion(),draft.inquiry().recordVersion(),input.customerId(),input.businessType(),input.deliveryMode(),input.estimatedArrivalAt(),input.expectedPalletCount(),input.cargo(),input.quoteRef(),input.idempotencyKey());
|
||||
doThrow(new IllegalStateException("bridge failed")).when(poolOrders).confirm(any(),anyString());
|
||||
assertThatThrownBy(()->capacityOffers.saveAndConfirm(draft.inquiry().bookingNo(),update)).hasMessageContaining("bridge failed");
|
||||
assertThat(capacityOffers.inquiry(draft.inquiry().bookingNo()).recordVersion()).isEqualTo(draft.inquiry().recordVersion());
|
||||
assertThat(capacityOffers.inquiry(draft.inquiry().bookingNo()).status()).isEqualTo("DRAFT");
|
||||
reset(poolOrders);
|
||||
var confirmed=capacityOffers.saveAndConfirm(draft.inquiry().bookingNo(),update);
|
||||
assertThat(confirmed.inquiry().status()).isEqualTo("CONFIRMED");
|
||||
assertThat(confirmed.inquiry().progress().reservation()).isEqualTo("已保留货位");
|
||||
assertThat(capacityOffers.saveAndConfirm(draft.inquiry().bookingNo(),update).replayed()).isTrue();
|
||||
verify(poolOrders,times(1)).confirm(any(),anyString());
|
||||
}
|
||||
|
||||
@Test void poolSalesAreIsolatedByUserIdAndCannotConfirmOrReadAllInquiries() {
|
||||
var offer=publishedOffer();poolLogin(101,false);
|
||||
var first=capacityOffers.createInquiry(offer.offerNo(),poolDraft(offer,2,"owner-first"));
|
||||
@@ -443,7 +484,8 @@ class FleetTripPersistenceTest {
|
||||
} finally { executor.shutdownNow(); }
|
||||
}
|
||||
|
||||
@Test void externalReceiptChangesReservationOnlyAfterExplicitActualQuantityAndPriceConfirmation() {
|
||||
@org.junit.jupiter.params.ParameterizedTest @org.junit.jupiter.params.provider.ValueSource(booleans={false,true})
|
||||
void externalReceiptChangesReservationOnlyAfterExplicitActualQuantityAndPriceConfirmation(boolean automatic) {
|
||||
var source=enableQuote();
|
||||
String number=readyTrip();var basis=execution.capacityPlan(number);
|
||||
execution.confirmCapacityPlan(number,"1",new FleetExecutionModels.Confirm(basis.recordVersion(),basis.manifestHash(),List.of(measurement()),"计划"));
|
||||
@@ -451,7 +493,7 @@ class FleetTripPersistenceTest {
|
||||
var confirmed=bookingService.confirm(number,draft.booking().bookingNo(),"1",new FleetBookingModels.Command(draft.tripRecordVersion(),0L,"receipt-reserve",null));
|
||||
var awaiting=new FleetBookingIncomingGateway.Evidence("WT-EXT","WT-EXT","92","FBA:ONT8","WAIT_PDA_RECEIVING","empty",List.of(),0);
|
||||
when(incoming.create(eq("1"),any())).thenReturn(awaiting);
|
||||
var request=new FleetBookingModels.StartIncoming(confirmed.tripRecordVersion(),confirmed.booking().recordVersion(),null,null,null,null,null,null,"incoming-start");
|
||||
var request=new FleetBookingModels.StartIncoming(confirmed.tripRecordVersion(),confirmed.booking().recordVersion(),departure.minusSeconds(120),null,null,null,null,null,"incoming-start");
|
||||
var started=bookingService.startIncoming(number,draft.booking().bookingNo(),"1",request);
|
||||
assertThat(bookingService.startIncoming(number,draft.booking().bookingNo(),"1",request).replayed()).isTrue();
|
||||
verify(incoming,times(1)).create(eq("1"),any());
|
||||
@@ -473,11 +515,20 @@ class FleetTripPersistenceTest {
|
||||
assertThat(execution.capacityPlan(number).availableSegments().get(0).remaining().palletSpaces()).isEqualTo(28);
|
||||
dispatch(13,"9","92");
|
||||
jdbc.update("INSERT INTO ll_dispatch_item(id,dispatch_order_id,customer_id,warehouse_pallet_id,pallet_no,package_count,source_stage,destination_warehouse_code,overseas_warehouse_id,inbound_code) VALUES(3013,13,'9',902,'PALLET-902',22,'RECEIVED','ONT8','92','WT-EXT')");
|
||||
var linked=bookingService.bindDispatch(number,draft.booking().bookingNo(),"1",new FleetBookingModels.BindDispatch(accepted.tripRecordVersion(),accepted.booking().recordVersion(),"DP-13","link-external"));
|
||||
when(incoming.inspect("9","WT-EXT",true)).thenReturn(new FleetBookingIncomingGateway.Evidence(actual.incomingNo(),actual.inboundCode(),actual.warehouseId(),actual.destinationKey(),"READY_FOR_DISPATCH",actual.receiptHash(),actual.pallets(),actual.receivedPackages()));
|
||||
when(planning.resolveIncoming(eq("9"),any(),eq("1"))).thenReturn("DP-13");
|
||||
var arrange=new FleetBookingModels.Command(accepted.tripRecordVersion(),accepted.booking().recordVersion(),"auto-link-external",null);
|
||||
var linked=automatic?bookingService.arrangeDispatch(number,draft.booking().bookingNo(),"1",arrange):bookingService.bindDispatch(number,draft.booking().bookingNo(),"1",new FleetBookingModels.BindDispatch(accepted.tripRecordVersion(),accepted.booking().recordVersion(),"DP-13","link-external"));
|
||||
if(automatic){assertThat(bookingService.arrangeDispatch(number,draft.booking().bookingNo(),"1",arrange).replayed()).isTrue();verify(planning,times(1)).resolveIncoming(eq("9"),any(),eq("1"));}
|
||||
assertThat(linked.booking().status()).isEqualTo("BOUND");assertThat(linked.booking().dispatchNo()).isEqualTo("DP-13");
|
||||
assertThat(execution.capacityPlan(number).planValid()).isFalse();
|
||||
assertThat(service.detail(number).dispatches()).hasSize(3);
|
||||
service.unbindDispatch(number,"1",new FleetTripModels.BindingCommand(linked.tripRecordVersion(),"DP-13","撤销未开工关联"));
|
||||
var recheck=execution.capacityPlan(number);
|
||||
var planned=execution.confirmCapacityPlan(number,"1",new FleetExecutionModels.Confirm(recheck.recordVersion(),recheck.manifestHash(),List.of(measurement(),new FleetExecutionModels.Measurement("902",1,new BigDecimal("150"),new BigDecimal("1.5"),1)),"关联实收后核对"));
|
||||
assertThat(planned.capacityPlan().segments().get(0).occupied().palletSpaces()).isEqualTo(2);
|
||||
assertThat(planned.availableSegments().get(0).remaining().palletSpaces()).isEqualTo(28);
|
||||
if(automatic){var replay=bookingService.arrangeDispatch(number,draft.booking().bookingNo(),"1",arrange);assertThat(replay.tripRecordVersion()).isEqualTo(linked.tripRecordVersion());assertThat(replay.booking()).isEqualTo(linked.booking());}
|
||||
service.unbindDispatch(number,"1",new FleetTripModels.BindingCommand(planned.recordVersion(),"DP-13","撤销未开工关联"));
|
||||
assertThat(bookingService.list(number).records().get(0).status()).isEqualTo("RECEIVED");
|
||||
assertThat(bookingService.list(number).records().get(0).dispatchNo()).isNull();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user