提交
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
package com.baiyue.lastleg.fleet.pickup;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
class FleetPickupMapperTest {
|
||||
JdbcTemplate db;FleetPickupMapper mapper;
|
||||
@BeforeEach void setup() throws Exception {
|
||||
var ds=new JdbcDataSource();ds.setURL("jdbc:h2:mem:pickup_"+System.nanoTime()+";MODE=MySQL;DB_CLOSE_DELAY=-1");db=new JdbcTemplate(ds);
|
||||
db.execute("CREATE TABLE ll_warehouse_transfer_order(id BIGINT PRIMARY KEY,customer_id VARCHAR(64),transfer_no VARCHAR(64),inbound_code VARCHAR(64),lastleg_order_no VARCHAR(64),operating_warehouse_id VARCHAR(64),delivery_address VARCHAR(500),pickup_contact_name VARCHAR(100),pickup_contact_phone VARCHAR(40),package_count INT,estimated_arrival_at TIMESTAMP,status VARCHAR(32),delivery_method VARCHAR(40),created_at TIMESTAMP)");
|
||||
db.execute("CREATE TABLE ll_fleet_pickup_task(transfer_order_id BIGINT PRIMARY KEY,customer_id VARCHAR(64),transfer_no VARCHAR(64),trip_no VARCHAR(64),status VARCHAR(32),updated_at TIMESTAMP)");
|
||||
for(int i=1;i<=6;i++)db.update("INSERT INTO ll_warehouse_transfer_order VALUES(?,? ,? ,? ,? ,'100','Ontario Test Address','Contact','0000000000',5,CURRENT_TIMESTAMP,'WAIT_PDA_RECEIVING','WAREHOUSE_PICKUP',CURRENT_TIMESTAMP)",i,"C"+i,"WT-"+i,"WT-"+i,"LL-"+i);
|
||||
db.update("UPDATE ll_warehouse_transfer_order SET delivery_method='CUSTOMER_DELIVERY' WHERE id=2");
|
||||
db.update("UPDATE ll_warehouse_transfer_order SET status='PENDING_REVIEW' WHERE id=3");
|
||||
db.update("UPDATE ll_warehouse_transfer_order SET status='WAIT_CUSTOMER_CONFIRMATION' WHERE id=4");
|
||||
db.update("UPDATE ll_warehouse_transfer_order SET inbound_code='OLD-INBOUND' WHERE id=5");
|
||||
var config=new MybatisConfiguration();config.addMapper(FleetPickupMapper.class);
|
||||
var factory=new MybatisSqlSessionFactoryBean();factory.setDataSource(ds);factory.setConfiguration(config);
|
||||
mapper=new SqlSessionTemplate(factory.getObject()).getMapper(FleetPickupMapper.class);
|
||||
}
|
||||
@Test void candidateQualificationPaginationAndCustomerScopeUseActualSql() {
|
||||
assertThat(mapper.countCandidates(null)).isEqualTo(2);
|
||||
assertThat(mapper.candidates(null,1,0)).extracting(FleetPickupModels.Candidate::transferNo).containsExactly("WT-1");
|
||||
assertThat(mapper.candidates(null,1,1)).extracting(FleetPickupModels.Candidate::transferNo).containsExactly("WT-6");
|
||||
assertThat(mapper.countCandidates("WT-6")).isEqualTo(1);
|
||||
assertThat(mapper.source("C2","WT-1")).isNull();assertThat(mapper.source("C1","WT-1").getPackageCount()).isEqualTo(5);
|
||||
}
|
||||
@Test void assignmentDisappearsFromPoolAndCancellationRestoresIt() {
|
||||
db.update("INSERT INTO ll_fleet_pickup_task VALUES(1,'C1','WT-1','TR-1','ASSIGNED',CURRENT_TIMESTAMP)");
|
||||
assertThat(mapper.countCandidates(null)).isEqualTo(1);assertThat(mapper.unassign("TR-1")).isEqualTo(1);
|
||||
assertThat(mapper.countCandidates(null)).isEqualTo(2);assertThat(mapper.task(1L).getTripNo()).isNull();
|
||||
}
|
||||
@Test void pickedCargoCannotBeReleasedBackToPendingByCancellationSql() {
|
||||
db.update("INSERT INTO ll_fleet_pickup_task VALUES(1,'C1','WT-1','TR-1','PICKED',CURRENT_TIMESTAMP)");
|
||||
assertThat(mapper.unassign("TR-1")).isZero();assertThat(mapper.task(1L).getTripNo()).isEqualTo("TR-1");
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.baiyue.lastleg.fleet.pickup;
|
||||
|
||||
import com.baiyue.lastleg.fleet.driver.*;
|
||||
import com.baiyue.lastleg.fleet.reporting.*;
|
||||
import com.baiyue.lastleg.fleet.trip.*;
|
||||
import com.baiyue.lastleg.masterdata.vehicle.application.port.out.VehicleRepository;
|
||||
import com.baiyue.lastleg.transfer.entity.WarehouseTransferOrder;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
|
||||
class FleetPickupServiceTest {
|
||||
FleetPickupRepository repository;FleetTripService trips;FleetPickupService service;
|
||||
TripOperationsEvidenceGateway files;FleetTripEntity trip;WarehouseTransferOrder source;FleetPickupTask task;
|
||||
final Map<String,String> commands=new HashMap<>();
|
||||
@BeforeEach void setup() {
|
||||
repository=mock(FleetPickupRepository.class);trips=mock(FleetTripService.class);files=mock(TripOperationsEvidenceGateway.class);
|
||||
service=new FleetPickupService(repository,trips,mock(FleetWarehouseDirectory.class),mock(DriverService.class),mock(VehicleRepository.class),files,new ObjectMapper().findAndRegisterModules());
|
||||
trip=new FleetTripEntity();trip.setId(9L);trip.setTripNo("TR-1");trip.setTripPurpose("PICKUP");trip.setStatus("IN_TRANSIT");trip.setWarehouseId(100L);trip.setRecordVersion(2L);
|
||||
source=source(1L,"C1","WT-1","100");task=new FleetPickupTask();task.setTransferOrderId(1L);task.setCustomerId("C1");task.setTransferNo("WT-1");task.setTripNo("TR-1");task.setStatus("ASSIGNED");
|
||||
when(repository.trip("TR-1",true)).thenReturn(trip);when(repository.trip("TR-1",false)).thenReturn(trip);
|
||||
when(repository.source(eq("C1"),eq("WT-1"),anyBoolean())).thenReturn(source);
|
||||
when(repository.task(1L)).thenReturn(task);when(repository.lockTask(1L)).thenReturn(task);when(repository.tasks("TR-1")).thenAnswer(c->List.of(task));
|
||||
when(repository.commandHash(eq("TR-1"),anyString())).thenAnswer(c->commands.get(c.getArgument(1)));
|
||||
doAnswer(c->{commands.put(c.getArgument(1),c.getArgument(2));return null;}).when(repository).command(anyString(),anyString(),anyString());
|
||||
doAnswer(c->{trip.setRecordVersion(trip.getRecordVersion()+1);return null;}).when(trips).recordExecution(any(),anyLong(),anyString(),anyString(),anyString());
|
||||
}
|
||||
@Test void pickupMustArriveBeforePdaAndWrongWarehouseCannotRead() {
|
||||
when(repository.pdaSource("100","WT-1")).thenReturn(source);
|
||||
assertThatThrownBy(()->service.requireMeasuringSource("100","WT-1")).hasMessageContaining("先完成车辆取货送仓");
|
||||
task.setStatus("ARRIVED");assertThat(service.requireMeasuringSource("100","WT-1")).isSameAs(source);
|
||||
assertThatThrownBy(()->service.requireMeasuringSource("200","WT-1")).isInstanceOf(ResponseStatusException.class);
|
||||
}
|
||||
@Test void selfDeliveryKeepsOriginalMeasurementPath() {
|
||||
source.setDeliveryMethod("CUSTOMER_DELIVERY");when(repository.pdaSource("100","WT-1")).thenReturn(source);
|
||||
assertThat(service.requireMeasuringSource("100","WT-1")).isSameAs(source);verify(repository,never()).task(anyLong());
|
||||
}
|
||||
@Test void cannotScheduleSelfDeliveryOrAnotherWarehouseOrDuplicateSource() {
|
||||
var plan=new FleetPickupModels.Create(List.of(new FleetPickupModels.Source("C1","WT-1")),Instant.now(),Instant.now().plusSeconds(7200),null,"create-key-1");
|
||||
source.setDeliveryMethod("CUSTOMER_DELIVERY");assertThatThrownBy(()->service.create("OP",plan)).hasMessageContaining("不是已审核");
|
||||
source.setDeliveryMethod("WAREHOUSE_PICKUP");when(repository.task(1L)).thenReturn(null);when(repository.lockTask(1L)).thenReturn(null);
|
||||
var other=source(2L,"C2","WT-2","200");when(repository.source("C2","WT-2",true)).thenReturn(other);
|
||||
var mixed=new FleetPickupModels.Create(List.of(new FleetPickupModels.Source("C1","WT-1"),new FleetPickupModels.Source("C2","WT-2")),plan.plannedDepartureAt(),plan.expectedDriverEndAt(),null,"create-key-2");
|
||||
assertThatThrownBy(()->service.create("OP",mixed)).hasMessageContaining("同一海外仓");verify(trips,never()).create(anyString(),any());
|
||||
var duplicate=new FleetPickupModels.Create(List.of(plan.sources().get(0),plan.sources().get(0)),plan.plannedDepartureAt(),plan.expectedDriverEndAt(),null,"create-key-3");
|
||||
assertThatThrownBy(()->service.create("OP",duplicate)).hasMessageContaining("重复选择");
|
||||
}
|
||||
@Test void alreadyScheduledSourceIsNotScheduledAgain() {
|
||||
var plan=new FleetPickupModels.Create(List.of(new FleetPickupModels.Source("C1","WT-1")),Instant.now(),Instant.now().plusSeconds(7200),null,"create-key-1");
|
||||
assertThatThrownBy(()->service.create("OP",plan)).hasMessageContaining("已安排车次");verify(trips,never()).create(anyString(),any());
|
||||
}
|
||||
@Test void pickupQuantityDoesNotOverwriteForecastAndReplayDoesNotWriteTwice() {
|
||||
when(repository.ownsUpload(1L,"TR-1",88L)).thenReturn(true);
|
||||
when(files.metadata(9L,88L)).thenReturn(new TripOperationsRecordModels.EvidenceFile("88","取货现场.jpg","image/jpeg",500));
|
||||
var request=new FleetPickupModels.Pickup(2L,"pickup-key-1",7,List.of(88L),"现场多提两箱,等待仓库核实");
|
||||
var result=service.picked("TR-1","C1","WT-1","OP",request);
|
||||
assertThat(task.getActualPackages()).isEqualTo(7);assertThat(source.getPackageCount()).isEqualTo(5);assertThat(source.getStatus()).isEqualTo("WAIT_PDA_RECEIVING");
|
||||
assertThat(result.get(0).status()).isEqualTo("PICKED");assertThat(result.get(0).evidence()).hasSize(1);
|
||||
service.picked("TR-1","C1","WT-1","OP",request);verify(repository,times(1)).update(task);
|
||||
var changed=new FleetPickupModels.Pickup(2L,"pickup-key-1",9,List.of(88L),null);
|
||||
assertThatThrownBy(()->service.picked("TR-1","C1","WT-1","OP",changed)).hasMessageContaining("不能提交不同");
|
||||
}
|
||||
@Test void evidenceCannotCrossCustomerEvenWithinTheSameTrip() {
|
||||
assertThatThrownBy(()->service.picked("TR-1","C1","WT-1","OP",new FleetPickupModels.Pickup(2L,"pickup-key-1",5,List.of(99L),null))).hasMessageContaining("不属于当前订单");
|
||||
assertThatThrownBy(()->service.evidence("C1","WT-1",99L)).hasMessageContaining("没有该取货凭证");
|
||||
verifyNoInteractions(files);verify(repository,never()).update(any());
|
||||
}
|
||||
@Test void cannotArriveBeforePickupAndCannotCompleteWithPendingMembers() {
|
||||
var command=new FleetPickupModels.Command(2L,"arrive-key-1","确认送仓");
|
||||
assertThatThrownBy(()->service.arrive("TR-1","C1","WT-1","OP",command)).hasMessageContaining("先确认该单已取货");
|
||||
assertThatThrownBy(()->service.complete("TR-1","OP",command)).hasMessageContaining("全部取货订单");
|
||||
}
|
||||
@Test void arrivalOpensPdaAndCompletionKeepsWarehouseOrderInMeasurement() {
|
||||
task.setStatus("PICKED");task.setActualPackages(5);task.setPickedAt(LocalDateTime.now(ZoneOffset.UTC));
|
||||
service.arrive("TR-1","C1","WT-1","OP",new FleetPickupModels.Command(2L,"arrive-key-1","已送海外仓月台"));
|
||||
assertThat(task.getStatus()).isEqualTo("ARRIVED");assertThat(source.getStatus()).isEqualTo("WAIT_PDA_RECEIVING");
|
||||
service.complete("TR-1","OP",new FleetPickupModels.Command(3L,"complete-key-1","全部送仓"));
|
||||
assertThat(trip.getStatus()).isEqualTo("COMPLETED");assertThat(trip.getCompletionJson()).contains("complete-key-1");assertThat(source.getStatus()).isEqualTo("WAIT_PDA_RECEIVING");
|
||||
}
|
||||
@Test void staleVersionAndWrongTripPurposeAreRejected() {
|
||||
assertThatThrownBy(()->service.arrive("TR-1","C1","WT-1","OP",new FleetPickupModels.Command(1L,"arrive-key-1","送仓"))).hasMessageContaining("车次已变化");
|
||||
trip.setTripPurpose("DELIVERY");assertThatThrownBy(()->service.tasks("TR-1")).hasMessageContaining("不是入仓取货");
|
||||
}
|
||||
/** 车次展示的资源不能代替真实占用事实。 */
|
||||
@Test void departureRequiresBothResourcesAndTheirCurrentOwnership() {
|
||||
trip.setStatus("PLANNED");
|
||||
var command=new FleetPickupModels.Command(2L,"depart-key-1","开始取货");
|
||||
assertThatThrownBy(()->service.depart("TR-1","OP",command)).hasMessageContaining("先分配车辆和司机");
|
||||
trip.setVehicleId(20L);trip.setDriverId(30L);
|
||||
when(repository.ownsResources(9L,20L,30L)).thenReturn(false);
|
||||
assertThatThrownBy(()->service.depart("TR-1","OP",command)).hasMessageContaining("占用与当前车次不一致");
|
||||
assertThat(trip.getStatus()).isEqualTo("PLANNED");
|
||||
verify(trips,never()).recordExecution(any(),anyLong(),anyString(),anyString(),anyString());
|
||||
}
|
||||
/** 构造有业务含义的原转运单,箱唛与库存由原链路持有。 */
|
||||
static WarehouseTransferOrder source(long id,String customer,String number,String warehouse) {
|
||||
var row=new WarehouseTransferOrder();row.setId(id);row.setCustomerId(customer);row.setTransferNo(number);row.setInboundCode(number);
|
||||
row.setOperatingWarehouseId(warehouse);row.setDeliveryMethod("WAREHOUSE_PICKUP");row.setStatus("WAIT_PDA_RECEIVING");row.setPackageCount(5);
|
||||
row.setDeliveryAddress("1580 S Grove Ave, Ontario, CA 91761");row.setPickupContactName("取货测试联系人");row.setPickupContactPhone("0000000000");return row;
|
||||
}
|
||||
}
|
||||
+21
-2
@@ -186,8 +186,8 @@ class FleetTripPersistenceTest {
|
||||
ReflectionTestUtils.setField(dispatchGateway, "deliveryExecution", deliveryExecution);
|
||||
ReflectionTestUtils.setField(dispatchGateway, "cargoSources", new com.baiyue.lastleg.delivery.service.DispatchCargoSourceQuery(
|
||||
new com.baiyue.lastleg.delivery.repository.DispatchCargoSourceRepository(session.getMapper(com.baiyue.lastleg.delivery.mapper.DispatchFeeCargoMapper.class))));
|
||||
service = transactional(new FleetTripService(session.getMapper(FleetTripMapper.class), resources, session.getMapper(FleetTripHistoryMapper.class), vehicles, drivers, directory, json,
|
||||
bindingMapper, dispatchGateway, reservations), FleetTripService.class);
|
||||
service = transactional(new FleetTripService(new FleetTripRepository(session.getMapper(FleetTripMapper.class), resources, session.getMapper(FleetTripHistoryMapper.class), bindingMapper), vehicles, drivers, directory, json,
|
||||
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);
|
||||
@@ -368,6 +368,25 @@ class FleetTripPersistenceTest {
|
||||
assertThat(service.assign(second, "1", new FleetTripModels.Assign(0L, vehicle, driver, "重新安排")).trip().vehicleId()).isEqualTo(Long.toString(vehicle));
|
||||
}
|
||||
|
||||
/** 下拉占用来自真实资源表,本车可保留、别车不可选,查询不预留资源。 */
|
||||
@Test void candidateOccupancySeparatesResourceKindAndReleasesAfterCancellation() {
|
||||
long occupiedVehicle=vehicle(true), freeVehicle=vehicle(true), occupiedDriver=driver(true);
|
||||
String first=trip(), second=trip();
|
||||
service.assign(first,"1",new FleetTripModels.Assign(0L,occupiedVehicle,occupiedDriver,"候选占用验收"));
|
||||
var query=new FleetTripModels.ResourceQuery("VEHICLE",List.of(occupiedVehicle,freeVehicle,occupiedVehicle));
|
||||
var other=service.resourceOccupancy(second,query);
|
||||
assertThat(other).hasSize(2);
|
||||
assertThat(other.get(0)).isEqualTo(new FleetTripModels.ResourceOccupancy(Long.toString(occupiedVehicle),first,false));
|
||||
assertThat(other.get(1)).isEqualTo(new FleetTripModels.ResourceOccupancy(Long.toString(freeVehicle),null,true));
|
||||
assertThat(service.resourceOccupancy(first,query).get(0).available()).isTrue();
|
||||
assertThat(service.resourceOccupancy(second,new FleetTripModels.ResourceQuery("DRIVER",List.of(occupiedDriver))).get(0).available()).isFalse();
|
||||
assertThat(service.resourceOccupancy(second,new FleetTripModels.ResourceQuery("VEHICLE",List.of(occupiedDriver))).get(0).available()).isTrue();
|
||||
assertThatThrownBy(()->service.resourceOccupancy(second,new FleetTripModels.ResourceQuery("VEHICLE",List.of()))).hasMessageContaining("有效的车辆或司机候选");
|
||||
service.cancel(first,"1",new FleetTripModels.Cancel(1L,"取消释放候选资源"));
|
||||
assertThat(service.resourceOccupancy(second,query)).allMatch(FleetTripModels.ResourceOccupancy::available);
|
||||
assertThat(jdbc.queryForObject("SELECT COUNT(*) FROM ll_fleet_resource_assignment",Integer.class)).isZero();
|
||||
}
|
||||
|
||||
@Test void failedReassignmentKeepsOldResourcesAndRollsBackTheNewVehicleClaim() {
|
||||
long oldVehicle = vehicle(true), occupiedVehicle = vehicle(true), replacement = vehicle(true);
|
||||
long oldDriver = driver(true), occupiedDriver = driver(true);
|
||||
|
||||
+3
@@ -26,6 +26,9 @@ class WarehouseTransferCartonCountTest {
|
||||
@BeforeEach void setup() {
|
||||
service = spy(new WarehouseTransferService());
|
||||
ReflectionTestUtils.setField(service, "transfers", orders);
|
||||
var pickupRepository = org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.pickup.FleetPickupRepository.class);
|
||||
org.mockito.Mockito.lenient().when(pickupRepository.pdaSource(anyString(), anyString())).thenAnswer(call -> orders.lockForPda(call.getArgument(1), call.getArgument(0)));
|
||||
ReflectionTestUtils.setField(service, "pickupService", new com.baiyue.lastleg.fleet.pickup.FleetPickupService(pickupRepository,null,null,null,null,null,new ObjectMapper()));
|
||||
ReflectionTestUtils.setField(service, "transferCartons", cartons);
|
||||
ReflectionTestUtils.setField(service, "transferItems", items);
|
||||
ReflectionTestUtils.setField(service, "transferPallets", palletMapper);
|
||||
|
||||
+3
@@ -231,6 +231,9 @@ class WarehouseTransferServiceTest {
|
||||
return work.apply(new WarehouseTransferReceiptCoordinator.Context(target, 51L, 61L));
|
||||
});
|
||||
ReflectionTestUtils.setField(service, "transfers", transfers);
|
||||
var pickupRepository = org.mockito.Mockito.mock(com.baiyue.lastleg.fleet.pickup.FleetPickupRepository.class);
|
||||
org.mockito.Mockito.lenient().when(pickupRepository.pdaSource(anyString(), anyString())).thenAnswer(call -> transfers.lockForPda(call.getArgument(1), call.getArgument(0)));
|
||||
ReflectionTestUtils.setField(service, "pickupService", new com.baiyue.lastleg.fleet.pickup.FleetPickupService(pickupRepository,null,null,null,null,null,new ObjectMapper()));
|
||||
ReflectionTestUtils.setField(service, "transferItems", transferItems);
|
||||
ReflectionTestUtils.setField(service, "transferCartons", transferCartons);
|
||||
ReflectionTestUtils.setField(service, "sourceOrders", sourceOrders);
|
||||
|
||||
Reference in New Issue
Block a user