feat(fleet): add internal capacity pool and warehouse fulfillment bridge
This commit is contained in:
+1
@@ -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))");
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
+159
@@ -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);
|
||||
}
|
||||
}
|
||||
+54
@@ -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());
|
||||
}
|
||||
}
|
||||
+71
@@ -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;}
|
||||
}
|
||||
+1
@@ -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); }
|
||||
|
||||
+111
-2
@@ -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);
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
+2
@@ -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 -> {
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user