✅ 补运次读服务的边界单测与管理端运次列表/详情用例
运次聚合五条边界、排序键、空子单集合、字符串主键、跨项目接单需求 OR; 管理端运次列表与详情的用例(此前该实现零覆盖)。
This commit is contained in:
+272
@@ -0,0 +1,272 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.AdminWaybillCardVO;
|
||||
import org.springblade.transport.pojo.vo.AdminWaybillDetailVO;
|
||||
import org.springblade.transport.pojo.vo.TripStationVO;
|
||||
import org.springblade.transport.pojo.vo.TripVO;
|
||||
import org.springblade.transport.service.IDriverService;
|
||||
import org.springblade.transport.service.IDriverWaybillService;
|
||||
import org.springblade.transport.service.IExceptionDisposalService;
|
||||
import org.springblade.transport.service.ILoadingManageService;
|
||||
import org.springblade.transport.service.IRiskDisposalService;
|
||||
import org.springblade.transport.service.ITripQueryService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 调度端列表与详情的运次口径:一卡一运次、整趟车的站点与货物。
|
||||
* <p>
|
||||
* 卡片上的状态、站点与货物汇总都出自共享运次读服务,本类只做字段裁剪;这里钉住的是
|
||||
* 「运次字段有没有被摆到卡片与详情上」,以及取不到运次时按运单主键与配载单主键的回落路径。
|
||||
*/
|
||||
class ManageTripListDetailTest {
|
||||
|
||||
private static final String LOADING_NO = "PZ-20261017-001";
|
||||
private static final Long LOADING_ID = 2111350784070275074L;
|
||||
private static final Long CHILD_A = 2111350784070275123L;
|
||||
private static final Long CHILD_B = 2102780956493299713L;
|
||||
|
||||
private ManageWaybillServiceImpl service;
|
||||
private IWaybillService waybillService;
|
||||
private IExceptionDisposalService exceptionDisposalService;
|
||||
private ITripQueryService tripQueryService;
|
||||
private ILoadingManageService loadingManageService;
|
||||
private IDriverWaybillService driverWaybillService;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Waybill.class);
|
||||
TableInfoHelper.initTableInfo(assistant, ExceptionDisposal.class);
|
||||
TableInfoHelper.initTableInfo(assistant, LoadingManage.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
waybillService = mock(IWaybillService.class);
|
||||
exceptionDisposalService = mock(IExceptionDisposalService.class);
|
||||
tripQueryService = mock(ITripQueryService.class);
|
||||
loadingManageService = mock(ILoadingManageService.class);
|
||||
driverWaybillService = mock(IDriverWaybillService.class);
|
||||
service = new ManageWaybillServiceImpl(
|
||||
waybillService,
|
||||
mock(IDriverService.class),
|
||||
exceptionDisposalService,
|
||||
mock(IRiskDisposalService.class),
|
||||
driverWaybillService,
|
||||
tripQueryService,
|
||||
loadingManageService);
|
||||
when(exceptionDisposalService.list(ArgumentMatchers.<Wrapper<ExceptionDisposal>>any()))
|
||||
.thenReturn(new ArrayList<>());
|
||||
when(waybillService.list(ArgumentMatchers.<Wrapper<Waybill>>any())).thenReturn(new ArrayList<>());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("列表一卡一运次:单号、组织类型、起经终、站点数、货物汇总都取运次字段")
|
||||
void pageListShowsOneCardPerTrip() {
|
||||
when(tripQueryService.page(any())).thenReturn(pageOf(1, trip()));
|
||||
|
||||
IPage<AdminWaybillCardVO> page = service.pageList(1, 10, null, null, null, null, null, null);
|
||||
|
||||
assertEquals(1, page.getTotal());
|
||||
assertEquals(1, page.getRecords().size(), "四张子单只出一条卡");
|
||||
AdminWaybillCardVO card = page.getRecords().get(0);
|
||||
assertEquals(String.valueOf(LOADING_ID), card.getTripId());
|
||||
assertEquals(LOADING_NO, card.getLoadingNo());
|
||||
assertEquals(LOADING_NO, card.getTripNo());
|
||||
assertEquals("load", card.getTransportType(), "有配载单即为公路配载");
|
||||
assertEquals(2, card.getStatus());
|
||||
assertEquals(4, card.getWaybillCount());
|
||||
assertEquals(3, card.getStationCount());
|
||||
assertEquals("南宁吴圩机场", card.getFromName());
|
||||
assertEquals("新福兴硅科技有限公司", card.getViaName());
|
||||
assertEquals("宾阳", card.getToName());
|
||||
assertEquals("石英砂A 131.84吨、技嘉4070显卡 30吨", card.getCargoSummary());
|
||||
assertEquals("161.84吨", card.getTotalWeight());
|
||||
assertEquals("2026-09-23 23:23:42", card.getCreateTime(), "卡片时间取运次排序键");
|
||||
assertEquals(Long.valueOf(CHILD_A), card.getId(), "旧 id 仍是运单主键,打卡订正等按它进入");
|
||||
assertEquals("YD-20260825-0006", card.getWaybillNo());
|
||||
assertFalse(card.getNeedReassign());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("详情按运次主键取:配载单号、组织类型、站点 ①②③ 与每站货物都带运单号")
|
||||
void detailShowsWholeTripStations() {
|
||||
when(tripQueryService.detail(any())).thenReturn(trip());
|
||||
|
||||
AdminWaybillDetailVO detail = service.detail(String.valueOf(LOADING_ID));
|
||||
|
||||
assertEquals(String.valueOf(LOADING_ID), detail.getTripId());
|
||||
assertEquals(LOADING_NO, detail.getLoadingNo());
|
||||
assertEquals(LOADING_NO, detail.getTripNo());
|
||||
assertEquals("load", detail.getTransportOrgType());
|
||||
assertEquals(2, detail.getStatus());
|
||||
assertEquals(3, detail.getStationCount());
|
||||
assertEquals(3, detail.getStations().size());
|
||||
assertEquals(3, detail.getRoutePoints().size(), "点位列与站点同源");
|
||||
assertEquals("161.84吨", detail.getTotalWeight());
|
||||
|
||||
TripStationVO first = detail.getStations().get(0);
|
||||
assertEquals(1, first.getIndex());
|
||||
assertEquals("装货", first.getOperationTypeLabel());
|
||||
assertEquals("2102780956493299713", first.getCargos().get(0).getWaybillId(), "货物条目带所属子运单主键");
|
||||
assertEquals("YD-20260923-0008", first.getCargos().get(0).getWaybillNo());
|
||||
|
||||
TripStationVO second = detail.getStations().get(1);
|
||||
assertEquals("装卸", second.getOperationTypeLabel());
|
||||
assertEquals(2, second.getCargos().size());
|
||||
assertEquals("YD-20260923-0008", second.getCargos().get(0).getWaybillNo(), "同一站可卸一张、装另一张");
|
||||
assertEquals("2131150784070275011", second.getCargos().get(1).getWaybillId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("取不到运次时按运单主键与配载单主键回落:整单被拒的配载单仍能打开并重新派单")
|
||||
void detailFallsBackWhenTripIsNotReachable() {
|
||||
when(tripQueryService.detail(any())).thenThrow(new ServiceException("运次不存在或无权操作"));
|
||||
when(waybillService.getById(CHILD_A)).thenReturn(null);
|
||||
when(loadingManageService.getById(LOADING_ID)).thenReturn(loading());
|
||||
|
||||
AdminWaybillDetailVO detail = service.detail(String.valueOf(LOADING_ID));
|
||||
|
||||
assertEquals(String.valueOf(LOADING_ID), detail.getTripId());
|
||||
assertEquals(LOADING_NO, detail.getLoadingNo());
|
||||
assertEquals("load", detail.getTransportOrgType());
|
||||
assertTrue(detail.getNeedReassign(), "整单被拒时按配载单回落,标记待重新派单");
|
||||
assertEquals(3, detail.getStationCount(), "子单为空时站点退回配载单自己的发货/途经/到货");
|
||||
assertEquals("南宁吴圩机场", detail.getFromName());
|
||||
assertEquals("宾阳", detail.getToName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("运次主键不是数字时直接拒绝,不去查库")
|
||||
void detailRefusesUnparsableTripId() {
|
||||
assertThrows(ServiceException.class, () -> service.detail("not-a-number"));
|
||||
assertThrows(ServiceException.class, () -> service.detail(null));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 夹具
|
||||
|
||||
private Page<TripVO> pageOf(long total, TripVO... trips) {
|
||||
Page<TripVO> page = new Page<>(1, 10, total);
|
||||
page.setRecords(List.of(trips));
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一趟 4 张子单的配载运次:站点与货物按本库真实样本同形(三站,第二站装卸)
|
||||
*/
|
||||
private TripVO trip() {
|
||||
TripVO trip = new TripVO();
|
||||
trip.setId(String.valueOf(LOADING_ID));
|
||||
trip.setTripNo(LOADING_NO);
|
||||
trip.setLoadingNo(LOADING_NO);
|
||||
trip.setTransportOrgType("load");
|
||||
trip.setStatus(2);
|
||||
trip.setRequireAccept(false);
|
||||
trip.setWaybillCount(4);
|
||||
trip.setDriverName("黄宇富");
|
||||
trip.setVehicleNo("桂A32523");
|
||||
trip.setDepartureName("南宁吴圩机场");
|
||||
trip.setTransitName("新福兴硅科技有限公司");
|
||||
trip.setArrivalName("宾阳");
|
||||
trip.setStationCount(3);
|
||||
trip.setCargoSummary("石英砂A 131.84吨、技嘉4070显卡 30吨");
|
||||
trip.setTotalQuantity(new BigDecimal("161.84"));
|
||||
trip.setQuantityUnit("吨");
|
||||
trip.setSortTime(at(1790177022000L));
|
||||
trip.setEstimatedStartDate(LocalDate.of(2026, 8, 25));
|
||||
trip.setEstimatedEndDate(LocalDate.of(2026, 9, 23));
|
||||
trip.setWaybills(List.of(
|
||||
tripWaybill(CHILD_A, "YD-20260825-0006"),
|
||||
tripWaybill(2111350784070275124L, "YD-20260825-0007"),
|
||||
tripWaybill(2111350784070275125L, "YD-20260825-0008"),
|
||||
tripWaybill(CHILD_B, "YD-20260923-0008")));
|
||||
trip.setStations(List.of(
|
||||
station(1, "南宁吴圩机场", "load", "装货", cargo("2102780956493299713", "YD-20260923-0008", "技嘉4070显卡", "30")),
|
||||
station(2, "武宣", "load_unload", "装卸",
|
||||
cargo("2102780956493299713", "YD-20260923-0008", "技嘉4070显卡", "30"),
|
||||
cargo("2131150784070275011", "YD-20260825-0006", "石英砂A", "42.10")),
|
||||
station(3, "宾阳", "unload", "卸货", cargo("2131150784070275011", "YD-20260825-0006", "石英砂A", "42.10"))));
|
||||
return trip;
|
||||
}
|
||||
|
||||
private TripVO.TripWaybillVO tripWaybill(Long id, String waybillNo) {
|
||||
TripVO.TripWaybillVO child = new TripVO.TripWaybillVO();
|
||||
child.setId(String.valueOf(id));
|
||||
child.setWaybillNo(waybillNo);
|
||||
child.setBusinessStatus("running");
|
||||
return child;
|
||||
}
|
||||
|
||||
private TripStationVO station(int index, String name, String operationType, String label,
|
||||
TripStationVO.TripCargoVO... cargos) {
|
||||
TripStationVO station = new TripStationVO();
|
||||
station.setIndex(index);
|
||||
station.setName(name);
|
||||
station.setAddress(name);
|
||||
station.setOperationType(operationType);
|
||||
station.setOperationTypeLabel(label);
|
||||
station.setTags(new ArrayList<>());
|
||||
station.setCargos(List.of(cargos));
|
||||
return station;
|
||||
}
|
||||
|
||||
private TripStationVO.TripCargoVO cargo(String waybillId, String waybillNo, String cargoName, String quantity) {
|
||||
TripStationVO.TripCargoVO cargo = new TripStationVO.TripCargoVO();
|
||||
cargo.setWaybillId(waybillId);
|
||||
cargo.setWaybillNo(waybillNo);
|
||||
cargo.setCargoName(cargoName);
|
||||
cargo.setQuantity(new BigDecimal(quantity));
|
||||
cargo.setQuantityUnit("吨");
|
||||
return cargo;
|
||||
}
|
||||
|
||||
private LoadingManage loading() {
|
||||
LoadingManage loading = new LoadingManage();
|
||||
loading.setId(LOADING_ID);
|
||||
loading.setLoadingNo(LOADING_NO);
|
||||
loading.setDriverName("黄宇富");
|
||||
loading.setDriverPhone("17587421208");
|
||||
loading.setVehicleNo("桂A32523");
|
||||
loading.setDepartureAddress("南宁吴圩机场");
|
||||
loading.setTransitAddress("武宣");
|
||||
loading.setArrivalAddress("宾阳");
|
||||
loading.setCargoName("石英砂A");
|
||||
return loading;
|
||||
}
|
||||
|
||||
private Date at(long millis) {
|
||||
return new Date(millis);
|
||||
}
|
||||
|
||||
}
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.TripQueryVO;
|
||||
import org.springblade.transport.pojo.vo.TripVO;
|
||||
import org.springblade.transport.service.ILoadingManageService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 运次读服务的聚合边界:四档状态、排序键、空子单集合与主键下发形态。
|
||||
* <p>
|
||||
* 断言都从服务出口读({@code page} / {@code detail}),不直接调判定函数:随卡片下发的
|
||||
* {@code status} 与 {@code requireAccept} 是两个字段,只验静态判定看不出服务有没有把子单真身取回来——
|
||||
* 库层分组行只投影主键、分组键与排序键,business_status 是 NULL。
|
||||
*/
|
||||
class TripReadServiceBoundaryTest {
|
||||
|
||||
private static final String PHONE = "17587421208";
|
||||
private static final String LOADING_NO = "PZ-20261017-001";
|
||||
/** 19 位雪花号,按数字下发会被双精度取整 */
|
||||
private static final Long LOADING_ID = 2111350784070275074L;
|
||||
private static final Long CHILD_A = 2111350784070275123L;
|
||||
private static final Long CHILD_B = 2102780956493299713L;
|
||||
private static final String ACCEPT_YES = "[{\"key\":\"accept\",\"name\":\"接单\",\"enabled\":true,\"confirmMode\":\"yes\"}]";
|
||||
private static final String ACCEPT_NO = "[{\"key\":\"accept\",\"name\":\"接单\",\"enabled\":true,\"confirmMode\":\"no_confirm_accept\"}]";
|
||||
|
||||
private IWaybillService waybillService;
|
||||
private ILoadingManageService loadingManageService;
|
||||
private TripQueryServiceImpl service;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, Waybill.class);
|
||||
TableInfoHelper.initTableInfo(assistant, LoadingManage.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
waybillService = mock(IWaybillService.class);
|
||||
loadingManageService = mock(ILoadingManageService.class);
|
||||
service = new TripQueryServiceImpl(waybillService, loadingManageService);
|
||||
lenient().when(waybillService.list(anyWaybillWrapper())).thenReturn(List.of());
|
||||
lenient().when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("子单全部完成 → 运次已完成(2)")
|
||||
void allChildrenCompleted() {
|
||||
stubLoadedTrip(
|
||||
child(CHILD_A, "completed", null, ACCEPT_NO, at(1787657033000L)),
|
||||
child(CHILD_B, "completed", "accepted", ACCEPT_NO, at(1787671422000L)));
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(2, trip.getStatus(), "终态且含已完成为已完成档");
|
||||
assertEquals(2, trip.getWaybillCount());
|
||||
assertEquals("accepted", trip.getAcceptStatus(), "任一子单已接单即整单已接单");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("子单全部取消 → 运次已取消(3),不是运输中")
|
||||
void allChildrenCancelled() {
|
||||
stubLoadedTrip(
|
||||
child(CHILD_A, "cancelled", null, ACCEPT_NO, at(1787657033000L)),
|
||||
child(CHILD_B, "cancelled", null, ACCEPT_NO, at(1787671422000L)));
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(3, trip.getStatus(), "已取消子单必须进运次,否则全取消会被算成运输中");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("混合终态且含 completed → 运次已完成(2),不看取消的那张")
|
||||
void mixedTerminalWithCompleted() {
|
||||
stubLoadedTrip(
|
||||
child(CHILD_A, "completed", null, ACCEPT_NO, at(1787657033000L)),
|
||||
child(CHILD_B, "cancelled", null, ACCEPT_NO, at(1787671422000L)));
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(2, trip.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("需接单且未接单 → 待接单(0)")
|
||||
void requireAcceptWithoutAcceptance() {
|
||||
Waybill required = child(CHILD_A, "pending", "pending", ACCEPT_YES, at(1787657033000L));
|
||||
required.setProjectId(1L);
|
||||
required.setProjectName("MK项目测试-1");
|
||||
stubLoadedTrip(required);
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(Boolean.TRUE, trip.getRequireAccept());
|
||||
assertEquals("pending", trip.getAcceptStatus());
|
||||
assertEquals(0, trip.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("不需接单 → 运输中(1)")
|
||||
void withoutAcceptRequirement() {
|
||||
Waybill notRequired = child(CHILD_A, "pending", null, ACCEPT_NO, at(1787657033000L));
|
||||
notRequired.setProjectId(2L);
|
||||
notRequired.setProjectName("武宣建筑原料运输项目");
|
||||
stubLoadedTrip(notRequired);
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(Boolean.FALSE, trip.getRequireAccept());
|
||||
assertEquals(1, trip.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("跨项目子单的接单需求按 OR 合并:一张要接单整趟车都要接单")
|
||||
void crossProjectRequirementMergesWithOr() {
|
||||
Waybill required = child(CHILD_A, "running", null, ACCEPT_YES, at(1787657033000L));
|
||||
required.setProjectId(1L);
|
||||
required.setProjectName("MK项目测试-1");
|
||||
Waybill notRequired = child(CHILD_B, "running", null, ACCEPT_NO, at(1787671422000L));
|
||||
notRequired.setProjectId(2L);
|
||||
notRequired.setProjectName("武宣建筑原料运输项目");
|
||||
stubLoadedTrip(required, notRequired);
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(Boolean.TRUE, trip.getRequireAccept());
|
||||
assertEquals(0, trip.getStatus());
|
||||
assertEquals(Boolean.TRUE, trip.getWaybills().get(0).getRequireAccept());
|
||||
assertEquals(Boolean.FALSE, trip.getWaybills().get(1).getRequireAccept());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("跨项目但都没接单节点 → 整趟车不需要接单")
|
||||
void crossProjectWithoutAnyRequirement() {
|
||||
Waybill first = child(CHILD_A, "running", null, null, at(1787657033000L));
|
||||
first.setProjectId(1L);
|
||||
Waybill second = child(CHILD_B, "running", null, ACCEPT_NO, at(1787671422000L));
|
||||
second.setProjectId(2L);
|
||||
stubLoadedTrip(first, second);
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(Boolean.FALSE, trip.getRequireAccept());
|
||||
assertEquals(1, trip.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("排序键取子单 create_time 的最大值,不取第一张也不取最后一张")
|
||||
void sortTimeIsMaxChildCreateTime() {
|
||||
Date earliest = at(1787657033000L);
|
||||
Date latest = at(1787671422000L);
|
||||
Date middle = at(1787663000000L);
|
||||
stubLoadedTrip(
|
||||
child(CHILD_A, "running", null, ACCEPT_NO, middle),
|
||||
child(2111350784070275999L, "running", null, ACCEPT_NO, latest),
|
||||
child(CHILD_B, "running", null, ACCEPT_NO, earliest));
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(latest, trip.getSortTime());
|
||||
assertEquals(3, trip.getWaybillCount());
|
||||
assertEquals(earliest, trip.getWaybills().get(0).getCreateTime(), "子单按创建时间升序摆");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("子单集合为空不抛异常:列表不给这张卡,详情直接拒绝而不是 NPE")
|
||||
void emptyChildrenDoNotThrow() {
|
||||
stubLoadedTrip();
|
||||
|
||||
IPage<TripVO> page = service.page(TripQueryVO.forDriver(PHONE));
|
||||
|
||||
assertTrue(page.getRecords().isEmpty(), "配载单在、可见子单为空时不给没有子单的运次");
|
||||
|
||||
when(loadingManageService.getById(LOADING_ID)).thenReturn(loading());
|
||||
assertThrows(ServiceException.class, () -> service.detail(detailQuery()),
|
||||
"子单全被拒单的配载运次对司机不可见");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("子单状态为空时不抛异常:读不出状态按没走完兜底,不崩也不误判成终态")
|
||||
void nullBusinessStatusDoesNotThrow() {
|
||||
Waybill blank = new Waybill();
|
||||
blank.setId(CHILD_A);
|
||||
blank.setWaybillNo("YD-20260920-0003");
|
||||
blank.setLoadingNo(LOADING_NO);
|
||||
blank.setBusinessStatus(null);
|
||||
stubLoadedTrip(blank);
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals(1, trip.getStatus(), "读不出状态按没走完处理,不是终态也不抛异常");
|
||||
assertEquals(1, trip.getWaybillCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("未配载运次按主键回查真身:分组行没有状态列,拿它当子单会让接单需求恒为 false")
|
||||
void loneTripReadsStateFromFetchedWaybill() {
|
||||
// 段一给的行只有主键、分组键与排序键,真身要靠段二回查
|
||||
Waybill row = new Waybill();
|
||||
row.setId(CHILD_A);
|
||||
row.setCreateTime(at(1787657033000L));
|
||||
stubPage(1, row);
|
||||
Waybill bare = new Waybill();
|
||||
bare.setId(CHILD_A);
|
||||
bare.setWaybillNo("YD-20260920-0003");
|
||||
bare.setBusinessStatus("pending");
|
||||
bare.setDriverAcceptStatus("pending");
|
||||
bare.setProcessJson(ACCEPT_YES);
|
||||
bare.setDriverPhone(PHONE);
|
||||
when(waybillService.list(anyWaybillWrapper())).thenReturn(List.of(bare));
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals("2111350784070275123", trip.getId());
|
||||
assertNull(trip.getLoadingNo());
|
||||
assertEquals(19, trip.getId().length(), "19 位雪花号的末位不被取整");
|
||||
assertEquals(Boolean.TRUE, trip.getRequireAccept(), "接单需求取自回查到的真身");
|
||||
assertEquals(0, trip.getStatus());
|
||||
assertEquals(1, trip.getWaybillCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("运次主键按字符串下发:19 位雪花号的末位不被双精度取整")
|
||||
void tripIdIsSentAsString() {
|
||||
stubLoadedTrip(child(CHILD_A, "running", null, ACCEPT_NO, at(1787657033000L)));
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertNotNull(trip.getId());
|
||||
assertEquals("2111350784070275074", trip.getId());
|
||||
assertEquals(19, trip.getId().length());
|
||||
assertEquals("2111350784070275123", trip.getWaybills().get(0).getId());
|
||||
assertEquals(19, trip.getWaybills().get(0).getId().length());
|
||||
assertTrue(trip.getId() instanceof String);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("货物与合计重量按整趟车算:同名同单位合并,单位不一致时合计留空")
|
||||
void cargoSummarySpansWholeTrip() {
|
||||
Waybill first = child(CHILD_A, "running", null, ACCEPT_NO, at(1787657033000L));
|
||||
first.setCargoName("石英砂A");
|
||||
first.setQuantity(new BigDecimal("42.10"));
|
||||
first.setQuantityUnit("吨");
|
||||
Waybill second = child(CHILD_B, "running", null, ACCEPT_NO, at(1787671422000L));
|
||||
second.setCargoName("石英砂A");
|
||||
second.setQuantity(new BigDecimal("47.57"));
|
||||
second.setQuantityUnit("吨");
|
||||
stubLoadedTrip(first, second);
|
||||
|
||||
TripVO trip = onlyTrip();
|
||||
|
||||
assertEquals("石英砂A 89.67吨", trip.getCargoSummary());
|
||||
assertEquals(0, new BigDecimal("89.67").compareTo(trip.getTotalQuantity()));
|
||||
assertEquals("吨", trip.getQuantityUnit());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 夹具
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Wrapper<Waybill> anyWaybillWrapper() {
|
||||
return ArgumentMatchers.<Wrapper<Waybill>>any();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Wrapper<LoadingManage> anyLoadingWrapper() {
|
||||
return ArgumentMatchers.<Wrapper<LoadingManage>>any();
|
||||
}
|
||||
|
||||
/** 配载运次:段一给一行分组行,段二给配载单与这一组的子单 */
|
||||
private void stubLoadedTrip(Waybill... children) {
|
||||
Waybill row = new Waybill();
|
||||
row.setId(children.length == 0 ? CHILD_A : children[0].getId());
|
||||
row.setLoadingNo(LOADING_NO);
|
||||
stubPage(1, row);
|
||||
when(loadingManageService.list(anyLoadingWrapper())).thenReturn(List.of(loading()));
|
||||
when(waybillService.list(anyWaybillWrapper())).thenReturn(List.of(children));
|
||||
}
|
||||
|
||||
private void stubPage(long total, Waybill... rows) {
|
||||
when(waybillService.page(any(), any())).thenAnswer(invocation -> {
|
||||
Page<Waybill> page = invocation.getArgument(0);
|
||||
page.setRecords(List.of(rows));
|
||||
page.setTotal(total);
|
||||
return page;
|
||||
});
|
||||
}
|
||||
|
||||
private TripVO onlyTrip() {
|
||||
IPage<TripVO> page = service.page(TripQueryVO.forDriver(PHONE));
|
||||
assertEquals(1, page.getRecords().size(), "本页应只有一个运次");
|
||||
return page.getRecords().get(0);
|
||||
}
|
||||
|
||||
private TripQueryVO detailQuery() {
|
||||
TripQueryVO query = TripQueryVO.forDriver(PHONE);
|
||||
query.setTripId(String.valueOf(LOADING_ID));
|
||||
return query;
|
||||
}
|
||||
|
||||
private LoadingManage loading() {
|
||||
LoadingManage loading = new LoadingManage();
|
||||
loading.setId(LOADING_ID);
|
||||
loading.setLoadingNo(LOADING_NO);
|
||||
loading.setDriverPhone(PHONE);
|
||||
loading.setDriverName("黄宇富");
|
||||
loading.setVehicleNo("桂A32523");
|
||||
loading.setProjectName("武宣建筑原料运输项目");
|
||||
return loading;
|
||||
}
|
||||
|
||||
private Waybill child(Long id, String businessStatus, String acceptStatus, String processJson, Date createTime) {
|
||||
Waybill waybill = new Waybill();
|
||||
waybill.setId(id);
|
||||
waybill.setWaybillNo("YD-" + id);
|
||||
waybill.setLoadingNo(LOADING_NO);
|
||||
waybill.setBusinessStatus(businessStatus);
|
||||
waybill.setDriverAcceptStatus(acceptStatus);
|
||||
waybill.setDriverPhone(PHONE);
|
||||
waybill.setProcessJson(processJson);
|
||||
waybill.setCreateTime(createTime);
|
||||
return waybill;
|
||||
}
|
||||
|
||||
private Date at(long millis) {
|
||||
return new Date(millis);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user