🐛 未配载运次的计划时间取运单自身的值,详情与列表同源

运次读服务拼未配载运次时从不给 estimatedStartDate/estimatedEndDate 赋值,
而配载运次取配载单、是有值的。于是所有按运次读的出口里,普通运输一律没有计划时间:
调度端详情页「计划时间」显示 —,司机端列表卡片同理,而同一张运单在列表卡片上有值。

- 段一 select 补 estimated_start_time/estimated_end_time(不取这两列则仍为空)
- waybillTrip 从运单补计划时间,与 loadingTrip 取配载单同一口径
- 调度端、司机端卡片改为直接取运次字段,删掉未配载运单独走一次批量补查的分叉
- 司机端卡片的计划时间不再以「有配载单」为前提;运次级为空时不覆盖卡片已有值

库里本就为空的配载单(如未填日期的草稿)仍不显示,不编造值。
This commit is contained in:
2026-10-18 19:40:19 +08:00
parent d3705efa70
commit 04389cd9d5
5 changed files with 50 additions and 36 deletions
@@ -1086,10 +1086,16 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
card.setVehicleNo(Func.toStr(trip.getVehicleNo(), ""));
card.setCreateTime(formatDateTime(trip.getSortTime()));
card.setPublishTime(card.getCreateTime());
// 计划时间与备注:配载运次取配载单,未配载的照运单卡片自己的值,不回填空
if (Func.isNotEmpty(card.getLoadingNo())) {
// 计划时间两条路同源:配载运次取配载单,未配载运次取那张运单自己的。
// 未配载详情进来时卡片已照运单填过,运次级为空就保留它,不覆盖成空
if (trip.getEstimatedStartDate() != null) {
card.setPlanShipTime(formatLocalDateYmd(trip.getEstimatedStartDate()));
}
if (trip.getEstimatedEndDate() != null) {
card.setPlanFinishTime(formatLocalDateYmd(trip.getEstimatedEndDate()));
}
// 备注只有配载运次有运次级的值,未配载的保留运单卡片自己带来的备注
if (Func.isNotEmpty(card.getLoadingNo())) {
card.setRemark(Func.toStr(trip.getTaskRemark(), ""));
}
card.setFromName(Func.toStrWithEmpty(trip.getDepartureName(), Func.toStr(trip.getDepartureAddress(), "")));
@@ -246,7 +246,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
/**
* 运次卡片页:一次算清这一页的重派标记、异常与未配载运次的计划时间,再逐条拼卡片
* 运次卡片页:一次算清这一页的重派标记与异常,再逐条拼卡片
*/
private Page<AdminWaybillCardVO> tripCardPage(List<TripVO> trips, int pageNo, int pageSize, long total,
Set<Long> exceptionWaybillIds) {
@@ -254,9 +254,8 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
if (!trips.isEmpty()) {
Set<String> reassignKeys = loadReassignTripKeys(
trips.stream().map(this::tripKeyOf).collect(Collectors.toCollection(LinkedHashSet::new)));
Map<Long, Waybill> loneWaybills = loadLoneWaybills(trips);
for (TripVO trip : trips) {
cards.add(toTripCard(trip, exceptionWaybillIds, reassignKeys, loneWaybills));
cards.add(toTripCard(trip, exceptionWaybillIds, reassignKeys));
}
}
Page<AdminWaybillCardVO> page = new Page<>(pageNo, pageSize, total);
@@ -265,12 +264,12 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
}
/**
* 运次卡片:展示单号、整趟车起/经/终与站点数、货物汇总都取运次字段。
* 运次卡片:展示单号、整趟车起/经/终与站点数、货物汇总、计划时间都取运次字段。
* 旧字段 id / waybillNo 保留运单语义(未配载就是这张运单,配载取第一张可见子运单),
* 打卡订正、重新派单等按运单进入的入口仍靠它们。
*/
private AdminWaybillCardVO toTripCard(TripVO trip, Set<Long> exceptionWaybillIds,
Set<String> reassignTripIds, Map<Long, Waybill> loneWaybills) {
Set<String> reassignTripIds) {
AdminWaybillCardVO card = new AdminWaybillCardVO();
List<TripVO.TripWaybillVO> children = trip.getWaybills() == null ? List.of() : trip.getWaybills();
Long firstChildId = children.isEmpty() ? null : parseId(children.get(0).getId());
@@ -293,15 +292,9 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
String weight = formatWeight(trip.getTotalQuantity(), trip.getQuantityUnit());
card.setWeight(weight);
card.setTotalWeight(weight);
if (loaded) {
card.setPlanTime(formatLocalDate(trip.getEstimatedStartDate()));
card.setPlanTimeEnd(formatLocalDate(trip.getEstimatedEndDate()));
} else {
// 未配载运次不带计划时间,按运单主键补一次;缺失时留空,不与其他运次串值
Waybill waybill = loneWaybills.get(parseId(trip.getId()));
card.setPlanTime(formatLocalDate(waybill == null ? null : waybill.getEstimatedStartTime()));
card.setPlanTimeEnd(formatLocalDate(waybill == null ? null : waybill.getEstimatedEndTime()));
}
// 计划时间两条路同源:配载运次取配载单,未配载运次取那张运单自己的
card.setPlanTime(formatLocalDate(trip.getEstimatedStartDate()));
card.setPlanTimeEnd(formatLocalDate(trip.getEstimatedEndDate()));
card.setStatus(trip.getStatus());
card.setDriverName(Func.toStr(trip.getDriverName(), ""));
card.setVehicleNo(Func.toStr(trip.getVehicleNo(), ""));
@@ -312,25 +305,6 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
return card;
}
/**
* 未配载运次(每张运单自成运次)的计划时间:运次读服务不下发它,一页一次批量补查
*/
private Map<Long, Waybill> loadLoneWaybills(List<TripVO> trips) {
Set<Long> loneIds = trips.stream()
.filter(trip -> Func.isEmpty(trip.getLoadingNo()))
.map(trip -> parseId(trip.getId()))
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (loneIds.isEmpty()) {
return Map.of();
}
return waybillService.list(Wrappers.<Waybill>lambdaQuery()
.select(Waybill::getId, Waybill::getEstimatedStartTime, Waybill::getEstimatedEndTime)
.in(Waybill::getId, loneIds))
.stream()
.collect(Collectors.toMap(Waybill::getId, Function.identity(), (first, ignored) -> first));
}
/** 这一页里带「子单被拒」标记的运次主键:判据与重派待办一致(已拒单 + 未取消未完成) */
private Set<String> loadReassignTripKeys(Set<String> tripIds) {
if (Func.isEmpty(tripIds)) {
@@ -231,7 +231,9 @@ public class TripQueryServiceImpl implements ITripQueryService {
Waybill::getCargoName, Waybill::getCargoType, Waybill::getQuantity, Waybill::getQuantityUnit,
Waybill::getDepartureName, Waybill::getDepartureAddress, Waybill::getArrivalName,
Waybill::getArrivalAddress, Waybill::getDriverName, Waybill::getDriverPhone, Waybill::getVehicleNo,
Waybill::getCustomerName, Waybill::getTransportType, Waybill::getTaskRemark, Waybill::getCreateTime)
Waybill::getCustomerName, Waybill::getTransportType, Waybill::getTaskRemark,
// 未配载运次没有配载单可依,计划时间要取运单自己的,故必须随行取回
Waybill::getEstimatedStartTime, Waybill::getEstimatedEndTime, Waybill::getCreateTime)
.in(Waybill::getBusinessStatus, TripAggregation.CHILD_STATUSES)
.and(visible -> visible.isNull(Waybill::getDriverAcceptStatus)
.or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED));
@@ -545,6 +547,9 @@ public class TripQueryServiceImpl implements ITripQueryService {
trip.setCargoType(Func.toStr(waybill.getCargoType(), ""));
trip.setDepartureAddress(Func.toStr(waybill.getDepartureAddress(), ""));
trip.setArrivalAddress(Func.toStr(waybill.getArrivalAddress(), ""));
// 未配载没有配载单可依,计划时间只能取这张运单自己的
trip.setEstimatedStartDate(waybill.getEstimatedStartTime());
trip.setEstimatedEndDate(waybill.getEstimatedEndTime());
trip.setTaskRemark(Func.toStr(waybill.getTaskRemark(), ""));
if (withPayload) {
trip.setRouteJson(waybill.getRouteJson());
@@ -191,6 +191,8 @@ class DriverTripCardTest {
assertEquals("", card.getLoadingNo());
assertEquals("YD-20260920-0003", card.getTripNo());
assertEquals("common", card.getTransportOrgType());
assertEquals("2026-09-20", card.getPlanShipTime(), "未配载运次的卡片也要带计划时间");
assertEquals("2026-09-22", card.getPlanFinishTime());
}
@Test
@@ -400,6 +402,8 @@ class DriverTripCardTest {
trip.setRequireAccept(true);
trip.setAcceptStatus("pending");
trip.setSortTime(new Date(1787629629000L));
trip.setEstimatedStartDate(java.time.LocalDate.of(2026, 9, 20));
trip.setEstimatedEndDate(java.time.LocalDate.of(2026, 9, 22));
trip.setDepartureName("南宁武鸣");
trip.setArrivalName("钦州港");
trip.setStationCount(2);
@@ -23,6 +23,7 @@ import org.springblade.transport.service.ILoadingManageService;
import org.springblade.transport.service.IProcessConfigService;
import org.springblade.transport.service.IWaybillService;
import java.time.LocalDate;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@@ -112,6 +113,8 @@ class TripQueryServiceImplTest {
String select = String.valueOf(capturedRowQuery().getSqlSelect());
assertTrue(select.contains("process_json"), "分档要按过程配置推导,必须取:" + select);
assertTrue(select.contains("create_time"), "排序键要用:" + select);
assertTrue(select.contains("estimated_start_time"), "未配载运次的计划时间取自运单,必须取:" + select);
assertTrue(select.contains("estimated_end_time"), select);
assertFalse(select.contains("route_json"), "运单表上的大 JSON 列不必拖回:" + select);
assertFalse(select.contains("goods_json"), select);
assertFalse(select.contains("task_info_json"), select);
@@ -258,6 +261,28 @@ class TripQueryServiceImplTest {
assertEquals("YD-20260920-0003", trip.getTripNo());
}
@Test
@DisplayName("未配载运次的计划时间取自这张运单:没有配载单可依,不能留空")
void unloadedTripPlanTimeComesFromWaybill() {
Waybill row = waybill(WAYBILL_ID, "YD-20260920-0003", null, "running", null, at(1000L), ACCEPT_NO);
row.setEstimatedStartTime(LocalDate.of(2026, 8, 7));
row.setEstimatedEndTime(LocalDate.of(2026, 8, 24));
stubRows(row);
TripVO listed = service.page(TripQueryVO.forDriver(PHONE)).getRecords().get(0);
assertEquals(LocalDate.of(2026, 8, 7), listed.getEstimatedStartDate(), "列表卡片与详情同源,不能只在列表补");
assertEquals(LocalDate.of(2026, 8, 24), listed.getEstimatedEndDate());
when(loadingManageService.getById(WAYBILL_ID)).thenReturn(null);
when(waybillService.getById(WAYBILL_ID)).thenReturn(row);
TripVO detailed = service.detail(tripQuery(String.valueOf(WAYBILL_ID)));
assertEquals(LocalDate.of(2026, 8, 7), detailed.getEstimatedStartDate(), "管理端详情走这条路,不留空");
assertEquals(LocalDate.of(2026, 8, 24), detailed.getEstimatedEndDate());
}
@Test
@DisplayName("配载单在两次查询之间被删掉时,不给出没有主键的运次")
void loadingMissingIsDropped() {