Merge branch 'test' of http://8.163.30.128:10082/java-project/baiyue-firstleg into test
This commit is contained in:
+19
@@ -442,6 +442,23 @@ public class DispatchAggregatePersistenceTest {
|
||||
assertThat(f.tx.execute(s->f.service.detail(f.actor,original.dispatchNo())).counts().packageCount()).isEqualTo(10);
|
||||
}
|
||||
|
||||
/** 到仓取货、本土卡车不制造货物明细,但仍须按原客户出现在现有整车排车列表。 */
|
||||
@Test void customerFilterFindsPickupAndLocalTruckByTheirRealSourceLinks() throws Exception {
|
||||
var f=new Fixture();
|
||||
var pickup=f.tx.execute(s->f.service.create(f.actor,f.create("transport-pickup-list",List.of())));
|
||||
var truck=f.tx.execute(s->f.service.create(f.actor,f.create("transport-truck-list",List.of())));
|
||||
String pickupTrip=f.db.queryForObject("SELECT trip_no FROM ll_fleet_trip WHERE id=(SELECT trip_id FROM ll_dispatch_order WHERE dispatch_no=?)",String.class,pickup.dispatchNo());
|
||||
String truckTrip=f.db.queryForObject("SELECT trip_no FROM ll_fleet_trip WHERE id=(SELECT trip_id FROM ll_dispatch_order WHERE dispatch_no=?)",String.class,truck.dispatchNo());
|
||||
f.db.update("UPDATE ll_dispatch_order SET task_type='PICKUP' WHERE dispatch_no=?",pickup.dispatchNo());
|
||||
f.db.update("UPDATE ll_dispatch_order SET task_type='LOCAL_TRUCK' WHERE dispatch_no=?",truck.dispatchNo());
|
||||
f.db.update("INSERT INTO ll_fleet_pickup_task(customer_id,trip_no) VALUES('PICKUP-CUSTOMER',?)",pickupTrip);
|
||||
f.db.update("INSERT INTO ll_dispatch_truck_task(dispatch_no,customer_id) VALUES(?,'TRUCK-CUSTOMER')",truck.dispatchNo());
|
||||
var pickupPage=f.tx.execute(s->f.service.page(f.actor,new DispatchViews.Query(1,20,null,null,null,"PICKUP-CUSTOMER",null,StatusFilter.ACTIVE,Stage.BEFORE_LOADING,null,null)));
|
||||
var truckPage=f.tx.execute(s->f.service.page(f.actor,new DispatchViews.Query(1,20,null,null,null,"TRUCK-CUSTOMER",null,StatusFilter.ACTIVE,Stage.BEFORE_LOADING,null,null)));
|
||||
assertThat(pickupPage.total()).isOne();assertThat(pickupPage.records().get(0).dispatchNo()).isEqualTo(pickup.dispatchNo());assertThat(pickupPage.records().get(0).taskType()).isEqualTo("PICKUP");
|
||||
assertThat(truckPage.total()).isOne();assertThat(truckPage.records().get(0).dispatchNo()).isEqualTo(truck.dispatchNo());assertThat(truckPage.records().get(0).taskType()).isEqualTo("LOCAL_TRUCK");
|
||||
}
|
||||
|
||||
/** 第N项已被另一车占用时,早先本车/来源/资源/账本全部回滚。 */
|
||||
@Test void laterCargoConflictRollsBackTruckResourcesEarlierLocksAndItems() throws Exception {
|
||||
var f=new Fixture(); var a=f.source(1,"101",501,"ONT8",3); var b=f.source(2,"202",502,"LAX9",5);
|
||||
@@ -492,6 +509,8 @@ public class DispatchAggregatePersistenceTest {
|
||||
db.execute("ALTER TABLE ll_fleet_trip_dispatch ADD UNIQUE(trip_id)");
|
||||
db.execute("ALTER TABLE ll_dispatch_physical_pallet_lock ADD UNIQUE(active_warehouse_pallet_id)");
|
||||
db.execute("ALTER TABLE ll_dispatch_pallet_lock ADD UNIQUE(customer_id,active_key)");
|
||||
db.execute("CREATE TABLE ll_dispatch_truck_task(dispatch_no VARCHAR(64),customer_id VARCHAR(128))");
|
||||
db.execute("CREATE TABLE ll_fleet_pickup_task(customer_id VARCHAR(128),trip_no VARCHAR(64))");
|
||||
db.execute("CREATE TABLE ll_dispatch_command(id BIGINT PRIMARY KEY,dispatch_id BIGINT,dispatch_no VARCHAR(64),action VARCHAR(40),operator_id VARCHAR(128),idempotency_key VARCHAR(255) UNIQUE,record_version BIGINT,command_hash CHAR(64),result_json CLOB,created_at TIMESTAMP(3))");
|
||||
db.execute("CREATE TABLE ll_dispatch_history(id BIGINT PRIMARY KEY,dispatch_id BIGINT,record_version BIGINT,action VARCHAR(40),operator_id VARCHAR(128),reason VARCHAR(1000),snapshot_json CLOB,occurred_at TIMESTAMP(3))");
|
||||
db.execute("CREATE TABLE ll_dispatch_pda_pick(id BIGINT,customer_id VARCHAR(64),dispatch_item_id BIGINT,inventory_position_id BIGINT,packages BIGINT,product_quantity BIGINT)");
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.baiyue.lastleg.dispatch.transport;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchAggregate;
|
||||
import com.baiyue.lastleg.dispatch.repository.DispatchAggregateRepository;
|
||||
import com.baiyue.lastleg.dispatch.service.DispatchNumberGenerator;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrder;
|
||||
import com.baiyue.lastleg.trucking.repository.UsTruckOrderRepository;
|
||||
import com.baiyue.lastleg.fleet.trip.FleetTripEntity;
|
||||
import com.baiyue.lastleg.fleet.trip.TransportTripChanged;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.time.LocalDateTime;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
class TransportTaskProjectionTest {
|
||||
/** 旧取消入口产生的运输事实也必须撤销原单安排和资源投影。 */
|
||||
@Test void legacyCancellationSynchronizesDispatchAndUnlocksSource(){
|
||||
var roots=mock(DispatchAggregateRepository.class);var links=mock(TransportTaskRepository.class);var trucks=mock(UsTruckOrderRepository.class);
|
||||
var projection=new TransportTaskProjection(roots,links,trucks,mock(DispatchNumberGenerator.class),new ObjectMapper().findAndRegisterModules());
|
||||
var root=new DispatchAggregate();root.setId(2L);root.setDispatchNo("PC1");root.setRecordVersion(3L);root.setTaskType("LOCAL_TRUCK");
|
||||
when(links.byTrip(1L)).thenReturn(root);when(roots.lock("PC1")).thenReturn(root);when(roots.saveState(root,3L)).thenReturn(1);
|
||||
when(links.truck("PC1")).thenReturn(new TransportTaskModels.TruckLink("PC1","TR1","C1","U1"));
|
||||
var order=new UsTruckOrder();order.setId(9L);order.setStatus("DISPATCHED");order.setVersion(5L);when(trucks.lockNo("C1","U1")).thenReturn(order);
|
||||
var trip=new FleetTripEntity();trip.setId(1L);trip.setTripPurpose("LOCAL_TRUCK");trip.setStatus("CANCELLED");trip.setRecordVersion(4L);trip.setUpdatedAt(LocalDateTime.now());
|
||||
projection.changed(new TransportTripChanged(trip,"CANCEL","取消运输","OP"));
|
||||
assertThat(root.getStatus()).isEqualTo("CANCELLED");assertThat(root.getRecordVersion()).isEqualTo(4L);assertThat(order.getStatus()).isEqualTo("PENDING_DISPATCH");assertThat(order.getVersion()).isEqualTo(6L);
|
||||
verify(links).cancel("PC1");verify(trucks).insertHistory(argThat(row->"DISPATCHED".equals(row.getFromStatus())&&"PENDING_DISPATCH".equals(row.getToStatus())&&"C1".equals(row.getCustomerId())));
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.baiyue.lastleg.dispatch.transport;
|
||||
|
||||
import com.baiyue.lastleg.dispatch.entity.DispatchAggregate;
|
||||
import com.baiyue.lastleg.dispatch.repository.DispatchAggregateRepository;
|
||||
import com.baiyue.lastleg.fleet.pickup.*;
|
||||
import com.baiyue.lastleg.fleet.trip.*;
|
||||
import com.baiyue.lastleg.fleet.driver.DriverService;
|
||||
import com.baiyue.lastleg.fleet.reporting.TripOperationsEvidenceGateway;
|
||||
import com.baiyue.lastleg.fleet.business.FleetPoolOrderQueries;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrder;
|
||||
import com.baiyue.lastleg.trucking.repository.UsTruckOrderRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.*;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
|
||||
/** 验证原单承运执行权、凭证隔离和成功请求重放,不能仅靠页面按钮门禁。 */
|
||||
class TransportTaskServiceTest {
|
||||
DispatchAggregateRepository roots;TransportTaskRepository links;FleetTripRepository tripRepo;FleetTripService trips;
|
||||
UsTruckOrderRepository trucks;TripOperationsEvidenceGateway files;TransportTaskService service;FleetTripEntity trip;UsTruckOrder order;
|
||||
final Map<String,String> commands=new HashMap<>();
|
||||
@BeforeEach void setup(){
|
||||
roots=mock(DispatchAggregateRepository.class);links=mock(TransportTaskRepository.class);tripRepo=mock(FleetTripRepository.class);trips=mock(FleetTripService.class);
|
||||
trucks=mock(UsTruckOrderRepository.class);files=mock(TripOperationsEvidenceGateway.class);
|
||||
service=new TransportTaskService(roots,links,mock(TransportTaskProjection.class),tripRepo,trips,mock(FleetPickupService.class),mock(FleetPickupRepository.class),trucks,mock(FleetPoolOrderQueries.class),mock(DriverService.class),mock(FleetTransportVehicleGuard.class),files,new ObjectMapper().findAndRegisterModules());
|
||||
var root=new DispatchAggregate();root.setDispatchNo("PC1");root.setTaskType("LOCAL_TRUCK");root.setTripId(1L);
|
||||
when(roots.find("PC1")).thenReturn(root);when(links.byTrip(1L)).thenReturn(root);when(links.tripNo(1L)).thenReturn("TR1");
|
||||
when(links.truck("PC1")).thenReturn(new TransportTaskModels.TruckLink("PC1","TR1","C1","U1"));when(links.evidence("PC1")).thenReturn(List.of());
|
||||
trip=new FleetTripEntity();trip.setId(1L);trip.setTripNo("TR1");trip.setTripPurpose("LOCAL_TRUCK");trip.setStatus("IN_TRANSIT");trip.setRecordVersion(3L);
|
||||
when(tripRepo.lockTrip("TR1")).thenReturn(trip);when(tripRepo.findTrip("TR1")).thenReturn(trip);
|
||||
order=new UsTruckOrder();order.setStatus("IN_TRANSIT");when(trucks.lockNo("C1","U1")).thenReturn(order);
|
||||
when(links.commandHash(eq("PC1"),anyString())).thenAnswer(call->commands.get(call.getArgument(1)));
|
||||
doAnswer(call->{commands.put(call.getArgument(1),call.getArgument(2));return null;}).when(links).command(eq("PC1"),anyString(),anyString(),anyString());
|
||||
doAnswer(call->{trip.setRecordVersion(trip.getRecordVersion()+1);return null;}).when(trips).recordExecution(any(),anyLong(),anyString(),anyString(),anyString());
|
||||
}
|
||||
/** 无凭证或跨任务文件均不能结束运输,也不能写成功账本。 */
|
||||
@Test void completionRequiresOwnedEvidence(){
|
||||
assertThatThrownBy(()->service.truckCommand("PC1","OP","complete",command("key-missing",List.of()))).hasMessageContaining("凭证");
|
||||
when(files.metadata(1L,99L)).thenThrow(new IllegalArgumentException("跨任务凭证"));
|
||||
assertThatThrownBy(()->service.truckCommand("PC1","OP","complete",command("key-foreign",List.of(99L)))).hasMessageContaining("跨任务");
|
||||
verify(trips,never()).recordExecution(any(),anyLong(),anyString(),anyString(),anyString());verify(links,never()).command(anyString(),anyString(),anyString(),anyString());
|
||||
}
|
||||
/** 同请求在版本变化后仍恢复原成功结果,不重复推进原单。 */
|
||||
@Test void replayUsesOriginalSuccessfulCommandBeforeCurrentVersion(){
|
||||
var body=command("complete-key",List.of(77L));service.truckCommand("PC1","OP","complete",body);service.truckCommand("PC1","OP","complete",body);
|
||||
assertThat(trip.getStatus()).isEqualTo("COMPLETED");verify(trips,times(1)).recordExecution(any(),eq(3L),eq("LOCAL_TRUCK_COMPLETE"),anyString(),eq("OP"));
|
||||
assertThatThrownBy(()->service.truckCommand("PC1","OP","complete",command("complete-key",List.of(88L)))).hasMessageContaining("不同内容");
|
||||
}
|
||||
/** 旧版本和未发车原单均不能利用完成接口跳过真实执行。 */
|
||||
@Test void staleVersionAndWrongOriginalStatusCannotComplete(){
|
||||
assertThatThrownBy(()->service.truckCommand("PC1","OP","complete",new TransportTaskModels.Command(2L,"stale-key","结束运输",List.of(77L)))).hasMessageContaining("刷新");
|
||||
order.setStatus("DISPATCHED");assertThatThrownBy(()->service.truckCommand("PC1","OP","complete",command("wrong-state",List.of(77L)))).hasMessageContaining("先确认发车");verifyNoInteractions(files);
|
||||
}
|
||||
/** 下载只允许已完成命令持久化的文件引用,不能凭共享文件 ID 访问。 */
|
||||
@Test void evidenceDownloadRequiresPersistedConfirmation(){
|
||||
assertThatThrownBy(()->service.evidence("PC1",77L)).hasMessageContaining("没有该完成凭证");verifyNoInteractions(files);
|
||||
}
|
||||
/** 构造固定版本的测试执行命令。 */
|
||||
private TransportTaskModels.Command command(String key,List<Long> files){return new TransportTaskModels.Command(3L,key,"结束运输",files);}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.baiyue.lastleg.fleet.business;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class FleetLooseboardDemandPersistenceTest {
|
||||
/** 真实 SQL 验证同客户关联、唯一来源、取消恢复候选和跨客户隔离。 */
|
||||
@Test void originalDemandLeavesPoolUntilDraftIsCancelled() throws Exception {
|
||||
var ds=new DriverManagerDataSource("jdbc:h2:mem:"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1","sa","");
|
||||
var jdbc=new JdbcTemplate(ds);
|
||||
jdbc.execute("CREATE TABLE ll_local_looseboard_order(id BIGINT PRIMARY KEY,customer_id VARCHAR(128),order_no VARCHAR(64),status VARCHAR(32),lastleg_order_id BIGINT,origin VARCHAR(64),destination VARCHAR(64),created_at TIMESTAMP)");
|
||||
jdbc.execute("CREATE TABLE ll_fleet_external_booking(id BIGINT PRIMARY KEY,customer_id VARCHAR(128),source_looseboard_order_id BIGINT,status VARCHAR(32))");
|
||||
jdbc.execute("CREATE TABLE ll_fleet_pool_order_link(customer_id VARCHAR(128),business_type VARCHAR(32),business_order_id BIGINT)");
|
||||
jdbc.update("INSERT INTO ll_local_looseboard_order VALUES(1,'C-1','LB-1','PENDING_ACCEPTANCE',NULL,'LA','Ontario',CURRENT_TIMESTAMP)");
|
||||
jdbc.update("INSERT INTO ll_local_looseboard_order VALUES(2,'C-1','OLD','PENDING_ACCEPTANCE',99,'LA','Ontario',CURRENT_TIMESTAMP)");
|
||||
jdbc.update("INSERT INTO ll_fleet_external_booking VALUES(10,'C-1',NULL,'DRAFT'),(11,'C-2',NULL,'DRAFT')");
|
||||
var config=new MybatisConfiguration();config.setMapUnderscoreToCamelCase(true);
|
||||
config.addMapper(FleetLooseboardDemandMapper.class);config.addMapper(FleetPoolOrderMapper.class);
|
||||
var factory=new MybatisSqlSessionFactoryBean();factory.setDataSource(ds);factory.setConfiguration(config);
|
||||
var session=new SqlSessionTemplate(factory.getObject());
|
||||
var mapper=session.getMapper(FleetLooseboardDemandMapper.class);
|
||||
var links=session.getMapper(FleetPoolOrderMapper.class);
|
||||
assertThat(mapper.pending(Page.of(1,20),"Ontario").getRecords()).extracting("orderNo").containsExactly("LB-1");
|
||||
assertThat(mapper.attach(10,"C-2",1)).isZero();
|
||||
assertThat(mapper.attach(10,"C-1",1)).isEqualTo(1);
|
||||
assertThat(mapper.attach(10,"C-1",2)).isZero();
|
||||
assertThat(mapper.active("C-1",1)).isEqualTo(1);
|
||||
assertThat(mapper.active("C-2",1)).isNull();
|
||||
assertThat(links.linked("C-1","LOCAL_LOOSEBOARD",1)).isEqualTo(1);
|
||||
assertThat(mapper.source(10,"C-2")).isNull();
|
||||
assertThat(mapper.source(10,"C-1").getOrderNo()).isEqualTo("LB-1");
|
||||
assertThat(mapper.pending(Page.of(1,20),null).getRecords()).isEmpty();
|
||||
jdbc.update("UPDATE ll_fleet_external_booking SET status='CANCELLED' WHERE id=10");
|
||||
assertThat(mapper.active("C-1",1)).isNull();
|
||||
assertThat(links.linked("C-1","LOCAL_LOOSEBOARD",1)).isZero();
|
||||
assertThat(mapper.pending(Page.of(1,20),null).getRecords()).extracting("orderNo").containsExactly("LB-1");
|
||||
assertThat(mapper.sourceNumbers(List.of(10L,11L))).extracting(FleetPoolOrderLink::getBusinessOrderNo).containsExactly("LB-1");
|
||||
jdbc.update("UPDATE ll_fleet_external_booking SET source_looseboard_order_id=1 WHERE id=11");
|
||||
assertThat(mapper.source(11,"C-2")).isNull();
|
||||
assertThat(mapper.sourceNumbers(List.of(11L))).isEmpty();
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.baiyue.lastleg.fleet.business;
|
||||
|
||||
import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class FleetLooseboardDemandServiceTest {
|
||||
final FleetLooseboardDemandRepository repository=mock(FleetLooseboardDemandRepository.class);
|
||||
final FleetLooseboardDemandService service=new FleetLooseboardDemandService(repository);
|
||||
/** 校验客户隔离、版本、已有匹配与独立履约都阻止重复接单。 */
|
||||
@Test void rejectsMissingStaleMatchedAndPhysicalSources() {
|
||||
assertThatThrownBy(()->service.prepare("9","LOCAL_LOOSEBOARD","LB-1",0L)).hasMessageContaining("不存在");
|
||||
var row=source();when(repository.lock("9","LB-1")).thenReturn(row);
|
||||
assertThatThrownBy(()->service.prepare("9","LOCAL_LOOSEBOARD","LB-1",1L)).hasMessageContaining("变化");
|
||||
assertThatThrownBy(()->service.prepare("9","US_TRUCK","LB-1",0L)).hasMessageContaining("散板");
|
||||
when(repository.active("9",1)).thenReturn(true);
|
||||
assertThatThrownBy(()->service.prepare("9","LOCAL_LOOSEBOARD","LB-1",0L)).hasMessageContaining("已有承运");
|
||||
when(repository.active("9",1)).thenReturn(false);row.setLastlegOrderId(7L);
|
||||
assertThatThrownBy(()->service.prepare("9","LOCAL_LOOSEBOARD","LB-1",0L)).hasMessageContaining("独立履约");
|
||||
verify(repository,never()).attach(anyLong(),anyString(),anyLong());
|
||||
}
|
||||
/** 取消草稿使生效匹配清空,同张原单可重新选车且没有商业复制。 */
|
||||
@Test void allowsRematchAfterDraftCancellationAndKeepsSourceImmutable() {
|
||||
var row=source();when(repository.lock("9","LB-1")).thenReturn(row);
|
||||
when(repository.active("9",1)).thenReturn(true,false);
|
||||
assertThatThrownBy(()->service.prepare("9","LOCAL_LOOSEBOARD","LB-1",0L)).hasMessageContaining("已有承运");
|
||||
assertThat(service.prepare("9","LOCAL_LOOSEBOARD","LB-1",0L)).isSameAs(row);
|
||||
when(repository.attach(7,"9",1)).thenReturn(1);service.attach(7,row);
|
||||
when(repository.source(7,"9")).thenReturn(row);
|
||||
service.validateEdit(7,"9","LOCAL_LOOSEBOARD",null);
|
||||
assertThatThrownBy(()->service.validateEdit(7,"9","US_TRUCK",null)).hasMessageContaining("不能替换");
|
||||
assertThatThrownBy(()->service.validateEdit(7,"9","LOCAL_LOOSEBOARD","LB-2")).hasMessageContaining("不能替换");
|
||||
}
|
||||
/** 构造待匹配原单。 */
|
||||
private LocalLooseboardOrder source() {
|
||||
var row=new LocalLooseboardOrder();row.setId(1L);row.setCustomerId("9");row.setOrderNo("LB-1");row.setVersion(0L);row.setStatus("PENDING_ACCEPTANCE");return row;
|
||||
}
|
||||
}
|
||||
+19
@@ -25,11 +25,13 @@ class FleetPoolOrderBridgeTest {
|
||||
FleetPoolOrderNumberGenerator numbers=mock(FleetPoolOrderNumberGenerator.class);
|
||||
FleetPoolDeliveryEvidence evidence=mock(FleetPoolDeliveryEvidence.class);
|
||||
com.baiyue.lastleg.appointmentplanning.AppointmentSourceLinker appointmentSources=mock(com.baiyue.lastleg.appointmentplanning.AppointmentSourceLinker.class);
|
||||
FleetLooseboardDemandRepository demands=mock(FleetLooseboardDemandRepository.class);
|
||||
FleetPoolOrderBridge service;
|
||||
FleetPoolOrderLink row; LocalLooseboardOrder looseOrder; UsTruckOrder truckOrder;
|
||||
@BeforeEach void setup(){
|
||||
service=new FleetPoolOrderBridge(new FleetPoolOrderRepository(links,loose,looseHistory,trucks,truckHistory),numbers,new ObjectMapper().findAndRegisterModules(),evidence,appointmentSources);
|
||||
|
||||
org.springframework.test.util.ReflectionTestUtils.setField(service,"demands",demands);
|
||||
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);
|
||||
@@ -45,6 +47,23 @@ class FleetPoolOrderBridgeTest {
|
||||
BigDecimal.TEN,BigDecimal.ONE,"USD","联系人","123456","本轮池订单");}
|
||||
void incoming(){service.bindIncoming("FB-7","9","WT-7",77,"92","FBA:ONT8","op");}
|
||||
|
||||
/** 原单匹配后全链复用客户商业单,实收到签收均不另建商业单。 */
|
||||
@Test void matchedSourceRemainsTheSameCommercialOrderThroughDelivery() {
|
||||
looseOrder=new LocalLooseboardOrder();looseOrder.setId(31L);looseOrder.setCustomerId("9");looseOrder.setOrderNo("LB-ORIGINAL");looseOrder.setVersion(4L);looseOrder.setStatus("PENDING_ACCEPTANCE");
|
||||
looseOrder.setOrigin("客户原始提货地址");looseOrder.setDestinationContactName("原始收货人");
|
||||
when(demands.source(7,"9")).thenReturn(looseOrder);when(loose.lockNo("9","LB-ORIGINAL")).thenAnswer(a->looseOrder);
|
||||
var original=looseOrder;
|
||||
var confirmed=service.confirm(input("LOCAL_LOOSEBOARD"),"op");
|
||||
assertThat(confirmed.businessOrderNo()).isEqualTo("LB-ORIGINAL");assertThat(row.getBusinessOrderId()).isEqualTo(31L);
|
||||
assertThat(service.confirm(input("LOCAL_LOOSEBOARD"),"op")).isEqualTo(confirmed);
|
||||
incoming();service.receiving("9","WT-7","pda");service.acceptedReceipt(7,receipt(),3,"op");service.boundDispatch(7,"9","DISPATCH-7","op");
|
||||
service.departed("9","DISPATCH-7",Map.of(77L,4),"op",LocalDateTime.now());
|
||||
service.signed(delivery(4),Map.of(77L,4L),"op",LocalDateTime.now());
|
||||
assertThat(looseOrder).isSameAs(original);assertThat(original.getStatus()).isEqualTo("COMPLETED");
|
||||
assertThat(original.getLastlegOrderId()).isEqualTo(77L);assertThat(original.getDestinationContactName()).isEqualTo("原始收货人");
|
||||
verify(loose,never()).insert(any(LocalLooseboardOrder.class));
|
||||
}
|
||||
|
||||
@Test void materializesTheOriginalAppointmentAfterActualIncomingAssociationAndNeverOnReplay() {
|
||||
service.confirm(input("LOCAL_LOOSEBOARD"),"op");
|
||||
incoming();
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ class FleetTripApiContractTest {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
assertThat(count).isEqualTo(46);
|
||||
assertThat(count).isEqualTo(47);
|
||||
verifyRefs(document, document);
|
||||
verifyRefs(preparation, preparation);
|
||||
verifyRefs(pool, pool);
|
||||
|
||||
+16
@@ -81,6 +81,18 @@ class FleetTripAuthorizationTest {
|
||||
assertThatThrownBy(()->pool.saveAndConfirm("FB-1",null)).isInstanceOf(AccessDeniedException.class);
|
||||
} finally {SecurityContextHolder.clearContext();}
|
||||
}
|
||||
/** 需求含客户原单资料,仅列表权限不能读取待匹配需求。 */
|
||||
@Test void pendingDemandRequiresManageOrListAndCreate() {
|
||||
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
|
||||
var controller=context.getBean(PlatformFleetCapacityOfferController.class);
|
||||
var service=context.getBean(FleetCapacityOfferService.class);
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("sales","unused",List.of(new SimpleGrantedAuthority(FleetCapacityOfferService.LIST))));
|
||||
assertThatThrownBy(()->controller.pendingDemands(null)).isInstanceOf(AccessDeniedException.class);
|
||||
verifyNoInteractions(service);
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("sales","unused",List.of(new SimpleGrantedAuthority(FleetCapacityOfferService.LIST),new SimpleGrantedAuthority("platform:fleet:capacity-offer:inquiry-create"))));
|
||||
controller.pendingDemands(null);verify(service).pendingDemands(null);
|
||||
} finally {SecurityContextHolder.clearContext();}
|
||||
}
|
||||
@Test void externalArrangeRequiresCurrentCreateAndAllIndependentPermissions() {
|
||||
var required=List.of("platform:fleet:capacity-offer:manage","platform:fleet:trip:booking-edit","platform:fleet:trip:edit","platform:overseas-warehouse:dispatch:create");
|
||||
try(var context=new AnnotationConfigApplicationContext(Config.class)) {
|
||||
@@ -100,6 +112,10 @@ class FleetTripAuthorizationTest {
|
||||
}
|
||||
@Configuration @EnableMethodSecurity
|
||||
static class Config {
|
||||
/** 原单需求依赖使用替身,权限测试不访问数据库。 */
|
||||
@Bean com.baiyue.lastleg.fleet.business.FleetLooseboardDemandService looseboardDemands() { return mock(com.baiyue.lastleg.fleet.business.FleetLooseboardDemandService.class); }
|
||||
/** 桥接器的原单查询使用仓储替身。 */
|
||||
@Bean com.baiyue.lastleg.fleet.business.FleetLooseboardDemandRepository looseboardDemandRepository() { return mock(com.baiyue.lastleg.fleet.business.FleetLooseboardDemandRepository.class); }
|
||||
/** 既有取货能力依赖原仓储引用;权限测试使用只读替身,不连接数据库。 */
|
||||
@Bean com.baiyue.lastleg.fleet.pickup.FleetPickupRepository pickups() { return mock(com.baiyue.lastleg.fleet.pickup.FleetPickupRepository.class); }
|
||||
@Bean com.baiyue.lastleg.fleet.trip.work.AppointmentWorkTripChanges workTripChanges() { return mock(com.baiyue.lastleg.fleet.trip.work.AppointmentWorkTripChanges.class); }
|
||||
|
||||
+6
-7
@@ -64,6 +64,9 @@ class LocalLooseboardServiceTest {
|
||||
ReflectionTestUtils.setField(repository, "lastlegHistories", lastlegHistories);
|
||||
ReflectionTestUtils.setField(service, "lastlegOrderNoGenerator", lastlegOrderNoGenerator);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
// 旧独立履约用例保留真实关联尾程,新增需求不创建该来源。
|
||||
var legacyTail = new LastlegOrder(); legacyTail.setId(99L); legacyTail.setCustomerId("100");
|
||||
org.mockito.Mockito.lenient().when(lastlegOrders.selectById(99L)).thenReturn(legacyTail);
|
||||
org.mockito.Mockito.lenient().when(lastlegOrderNoGenerator.next(anyString(), any(), anyLong()))
|
||||
.thenReturn("LB-WH-20260901-000001");
|
||||
org.mockito.Mockito.lenient().when(operations.findKey(anyString(), any(), anyString()))
|
||||
@@ -94,12 +97,8 @@ class LocalLooseboardServiceTest {
|
||||
assertEquals("家居用品", detail.summary().productName());
|
||||
assertEquals(3, detail.summary().estimatedPalletCount());
|
||||
assertEquals(LocalDateTime.of(2030, 1, 2, 9, 30), detail.summary().pickupAt());
|
||||
verify(lastlegOrders).insert(org.mockito.ArgumentMatchers.<LastlegOrder>argThat(value ->
|
||||
"LOCAL_LOOSEBOARD".equals(value.getOrderType())
|
||||
&& value.getSourceOrderNo().equals("LB-WH-20260901-000001")
|
||||
&& value.getInboundCode() == null
|
||||
&& value.getPalletCount() == 0
|
||||
&& "LOCAL_LOOSEBOARD:PENDING_ACCEPTANCE".equals(value.getSubOrderStatus())));
|
||||
org.junit.jupiter.api.Assertions.assertNull(saved.get().getLastlegOrderId());
|
||||
verify(lastlegOrders, org.mockito.Mockito.never()).insert(org.mockito.ArgumentMatchers.<LastlegOrder>any());
|
||||
verify(histories).insert(org.mockito.ArgumentMatchers.<LocalLooseboardHistory>argThat(value ->
|
||||
"CREATE".equals(value.getOperationType())));
|
||||
}
|
||||
@@ -208,7 +207,7 @@ class LocalLooseboardServiceTest {
|
||||
|
||||
private LocalLooseboardOrder order(String status, long version) {
|
||||
LocalLooseboardOrder order = new LocalLooseboardOrder();
|
||||
order.setId(1L); order.setCustomerId("100"); order.setOrderNo("LB1");
|
||||
order.setId(1L); order.setCustomerId("100"); order.setOrderNo("LB1"); order.setLastlegOrderId(99L);
|
||||
order.setPalletRequired("YES");
|
||||
order.setTransportType("WAREHOUSE_TO_WAREHOUSE"); order.setOrigin("Los Angeles");
|
||||
order.setDestination("Ontario"); order.setPackageCount(12); order.setCurrency("USD");
|
||||
|
||||
Reference in New Issue
Block a user