✨ 待回单确认拆出独立 tab 档位,进行中不再包含

This commit is contained in:
2026-10-25 16:20:36 +08:00
parent 2c4ba589b8
commit c950f13c55
9 changed files with 114 additions and 30 deletions
@@ -40,7 +40,7 @@ public class DriverWaybillTabCountsVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "全部(待接单+运输中+已完成)")
@Schema(description = "全部(待接单+运输中+待确认+已完成)")
private long all;
@Schema(description = "待接单")
@@ -49,6 +49,9 @@ public class DriverWaybillTabCountsVO implements Serializable {
@Schema(description = "进行中(运输中)")
private long doing;
@Schema(description = "待确认(待回单确认)")
private long confirm;
@Schema(description = "已完成")
private long done;
@@ -40,7 +40,7 @@ public class TripTabCountsVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "全部(待接单+进行中+已完成,不含已取消)")
@Schema(description = "全部(待接单+进行中+待确认+已完成,不含已取消)")
private long all;
@Schema(description = "待接单")
@@ -49,6 +49,9 @@ public class TripTabCountsVO implements Serializable {
@Schema(description = "进行中")
private long doing;
@Schema(description = "待确认(待回单确认)")
private long confirm;
@Schema(description = "已完成")
private long done;
@@ -107,7 +107,7 @@ public class DriverWaybillController extends BladeController {
@GetMapping("/task/current")
@ApiOperationSupport(order = 21)
@Operation(summary = "首页:当前运输中运次", description = "当前用户手机号可见的运输中运次(有配载单的一趟车算一个)最新一条")
@Operation(summary = "首页:当前运输中运次", description = "当前用户手机号可见的运输中或待确认(待回单确认)运次(有配载单的一趟车算一个)最新一条")
public R<DriverWaybillCardVO> tripCurrentTask() {
return R.data(driverWaybillService.tripCurrentTask());
}
@@ -122,18 +122,18 @@ public class DriverWaybillController extends BladeController {
@GetMapping("/task/counts")
@ApiOperationSupport(order = 23)
@Operation(summary = "运次 Tab 统计", description = "按运次统计:全部 = 待接单 + 进行中 + 已完成(不含已取消)")
@Operation(summary = "运次 Tab 统计", description = "按运次统计:全部 = 待接单 + 进行中 + 待确认 + 已完成(不含已取消);confirm 为待确认(待回单确认)")
public R<DriverWaybillTabCountsVO> tripCounts() {
return R.data(driverWaybillService.tripTabCounts());
}
@GetMapping("/task/page")
@ApiOperationSupport(order = 24)
@Operation(summary = "运次分页列表", description = "一页即若干个运次;status:空=全部,0待接单,1运输中,2已完成;keyword 支持运单号/配载单号/项目/司机/车牌/起终点/地址")
@Operation(summary = "运次分页列表", description = "一页即若干个运次;status:空=全部,0待接单,1运输中(不含待回单确认),2已完成,4待确认(待回单确认);keyword 支持运单号/配载单号/项目/司机/车牌/起终点/地址")
public R<IPage<DriverWaybillCardVO>> tripPage(
@Parameter(description = "当前页") @RequestParam(required = false) Integer current,
@Parameter(description = "每页运次数") @RequestParam(required = false) Integer size,
@Parameter(description = "状态:0待接单/1运输中/2已完成,不传为全部") @RequestParam(required = false) String status,
@Parameter(description = "状态:0待接单/1运输中/2已完成/4待确认,不传为全部") @RequestParam(required = false) String status,
@Parameter(description = "关键字:运单号/配载单号/项目/司机/车牌/起终点/地址") @RequestParam(required = false) String keyword) {
Integer statusCode = parseStatus(status);
return R.data(driverWaybillService.tripPage(current, size, statusCode, keyword));
@@ -84,12 +84,12 @@ public class ManageWaybillController extends BladeController {
@GetMapping("/list")
@ApiOperationSupport(order = 3)
@Operation(summary = "运单分页列表", description = "当前组织运单;status:0待接单/1运输中/2已完成;exception:exception/normal;transportType:common/load")
@Operation(summary = "运单分页列表", description = "当前组织运单;status:0待接单/1运输中(不含待回单确认)/2已完成/4待确认(待回单确认);exception:exception/normal;transportType:common/load")
public R<IPage<AdminWaybillCardVO>> list(
@Parameter(description = "当前页") @RequestParam(required = false) Integer current,
@Parameter(description = "每页条数") @RequestParam(required = false) Integer size,
@Parameter(description = "关键字:运单号/配载单号/项目/司机/车牌/起终点/地址") @RequestParam(required = false) String keyword,
@Parameter(description = "状态:0待接单/1运输中/2已完成,不传为全部") @RequestParam(required = false) String status,
@Parameter(description = "状态:0待接单/1运输中/2已完成/4待确认,不传为全部") @RequestParam(required = false) String status,
@Parameter(description = "异常:exception有异常/normal无异常") @RequestParam(required = false) String exception,
@Parameter(description = "运输组织:common普通/load配载") @RequestParam(required = false) String transportType,
@Parameter(description = "创建日起 YYYY-MM-DD") @RequestParam(required = false) String startDate,
@@ -108,7 +108,7 @@ public class ManageWaybillController extends BladeController {
@GetMapping("/pending")
@ApiOperationSupport(order = 5)
@Operation(summary = "待处理运单", description = "待接单/运输中;needReassign=true 仅司机已拒单")
@Operation(summary = "待处理运单", description = "待接单/运输中/待确认;needReassign=true 仅司机已拒单")
public R<IPage<AdminWaybillCardVO>> pending(
@Parameter(description = "当前页") @RequestParam(required = false) Integer current,
@Parameter(description = "每页条数") @RequestParam(required = false) Integer size,
@@ -149,6 +149,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
private static final int TRIP_STATUS_PENDING = 0;
private static final int TRIP_STATUS_RUNNING = 1;
/** 运次列表档位:4 待确认(待回单确认)。卡片状态码仍按 1 展示,只有列表 Tab 用它分档 */
private static final int TRIP_STATUS_PENDING_RETURN_CONFIRM = 4;
private final IWaybillService waybillService;
private final IDriverService driverService;
private final IMpUserClient mpUserClient;
@@ -280,7 +283,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
return null;
}
TripQueryVO query = TripQueryVO.forDriver(phone);
query.setStatus(TRIP_STATUS_RUNNING);
// 待确认(待回单确认)的趟车司机仍需回到卡片上继续操作,与进行中并列作为当前任务
query.setStatuses(List.of(TRIP_STATUS_RUNNING, TRIP_STATUS_PENDING_RETURN_CONFIRM));
query.setCurrent(1);
query.setSize(1);
IPage<TripVO> page = tripQueryService.page(query);
@@ -316,6 +320,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
TripTabCountsVO tripCounts = tripQueryService.tabCounts(TripQueryVO.forDriver(phone));
counts.setPending(tripCounts.getPending());
counts.setDoing(tripCounts.getDoing());
counts.setConfirm(tripCounts.getConfirm());
counts.setDone(tripCounts.getDone());
counts.setAll(tripCounts.getAll());
return counts;
@@ -236,8 +236,8 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
TripQueryVO query = TripQueryVO.forAdmin();
applyAdminDataScope(query);
query.setKeyword(keyword);
// 待处理只要待接单与进行中两档;「未拒单」也是交给运次读服务按子单行筛
query.setStatuses(List.of(0, 1));
// 待处理含待接单、进行中与待确认(待回单确认原本就算进行中,拆档后不能从待处理里消失)
query.setStatuses(List.of(0, 1, 4));
query.setExcludeRejectedTrips(Boolean.FALSE.equals(needReassign));
query.setCurrent(pageNo);
query.setSize(pageSize);
@@ -269,6 +269,19 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
return page;
}
/**
* 卡片展示用的原始业务状态:整趟车子单同一业务状态时取它,混着多种状态时为空,卡片按状态码展示。
* <p>
* 待回单确认的趟车状态码仍是 1(运输中),只有这个字段能把它区分开。
*/
private String uniqueChildBusinessStatus(List<TripVO.TripWaybillVO> children) {
Set<String> statuses = children.stream()
.map(TripVO.TripWaybillVO::getBusinessStatus)
.filter(Func::isNotBlank)
.collect(Collectors.toCollection(LinkedHashSet::new));
return statuses.size() == 1 ? statuses.iterator().next() : null;
}
/**
* 运次卡片:展示单号、整趟车起/经/终与站点数、货物汇总、计划时间都取运次字段。
* 旧字段 id / waybillNo 保留运单语义(未配载就是这张运单,配载取第一张可见子运单),
@@ -302,6 +315,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
card.setPlanTime(formatLocalDate(trip.getEstimatedStartDate()));
card.setPlanTimeEnd(formatLocalDate(trip.getEstimatedEndDate()));
card.setStatus(trip.getStatus());
card.setBusinessStatus(uniqueChildBusinessStatus(children));
card.setDriverName(Func.toStr(trip.getDriverName(), ""));
card.setVehicleNo(Func.toStr(trip.getVehicleNo(), ""));
card.setCreateTime(formatDateTime(trip.getSortTime()));
@@ -336,7 +350,11 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
return childIds.stream().map(this::parseId).filter(Objects::nonNull).anyMatch(exceptionWaybillIds::contains);
}
/** 小程序 status → 运次状态:0待接单 / 1进行中 / 2已完成 */
/**
* 小程序 status → 运次可筛选档位:0待接单 / 1进行中 / 2已完成 / 3已取消 / 4待确认(待回单确认)。
* <p>
* 1 不含待回单确认——待回单确认单独成档,卡片状态码仍是 1。
*/
private Integer toTripStatus(String status) {
if (Func.isBlank(status)) {
return null;
@@ -346,6 +364,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
case "1", STATUS_RUNNING, "transporting", "doing" -> 1;
case "2", STATUS_COMPLETED, "done" -> 2;
case "3", STATUS_CANCELLED -> 3;
case "4", WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, "confirm" -> 4;
default -> null;
};
}
@@ -515,6 +534,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
detail.setId(head == null ? loading.getId() : head.getId());
detail.setWaybillNo(head == null ? loading.getLoadingNo() : Func.toStr(head.getWaybillNo(), loading.getLoadingNo()));
detail.setStatus(TripAggregation.appStatus(tripBusinessStatus(children)));
detail.setBusinessStatus(tripBusinessStatus(children));
detail.setTransportMode(Func.toStr(loading.getTransportType(), ""));
detail.setTransportType(toTransportTypeLabel(Func.toStr(loading.getTransportType(), "")));
detail.setTransportOrgType(TRANSPORT_LOAD);
@@ -82,21 +82,41 @@ final class TripAggregation {
return switch (Func.toStr(status, "")) {
case PENDING -> 0;
case RUNNING -> 1;
case PENDING_RETURN_CONFIRM -> 1;
case COMPLETED -> 2;
case CANCELLED -> 3;
default -> null;
};
}
/**
* 运次的列表档位:0 待接单 / 1 进行中 / 2 已完成 / 3 已取消 / 4 待确认(待回单确认)。
* <p>
* 与 {@link #appStatus(String)} 的区别只在待回单确认:卡片状态仍按 1(运输中)展示,
* 列表档位单独成档,待回单确认不再落进行中。
*/
static Integer tabOf(String status) {
return switch (Func.toStr(status, "")) {
case PENDING -> 0;
case RUNNING -> 1;
case COMPLETED -> 2;
case CANCELLED -> 3;
case PENDING_RETURN_CONFIRM -> 4;
default -> null;
};
}
/**
* 运次状态:终态优先——子单全为终态时有已完成取已完成、否则已取消;
* 否则需接单且未接单取待接单,其余取运输中。
* 其次待接单档优先;再次有在跑的子单取运输中;只剩待回单确认的取待回单确认。
* <p>
* 待回单确认不受接单需求影响:需要接单的趟车走到回单确认时司机已接过单,不再回退成待接单。
* <p>
* 子单集合为空时无从判定,返回空。
*/
static String statusOf(List<Waybill> children, boolean requireAccept, boolean accepted) {
String status = classify(count(children));
if (status == null || TERMINAL_STATUSES.contains(status)) {
if (status == null || TERMINAL_STATUSES.contains(status) || PENDING_RETURN_CONFIRM.equals(status)) {
return status;
}
if (!requireAccept) {
@@ -106,13 +126,15 @@ final class TripAggregation {
}
/**
* 子单聚合结果:子单总数、终态数、已完成数、待接单档数
* 子单聚合结果:子单总数、终态数、已完成数、待接单档数、运输中数、待回单确认数
*/
static Counters count(List<Waybill> children) {
int total = 0;
int terminal = 0;
int completed = 0;
int pending = 0;
int running = 0;
int returnConfirm = 0;
for (Waybill child : children) {
String status = child == null ? null : child.getBusinessStatus();
total++;
@@ -125,10 +147,20 @@ final class TripAggregation {
if (status != null && PENDING_STATUSES.contains(status)) {
pending++;
}
if (RUNNING.equals(status)) {
running++;
}
if (PENDING_RETURN_CONFIRM.equals(status)) {
returnConfirm++;
}
}
return new Counters(total, terminal, completed, pending);
return new Counters(total, terminal, completed, pending, running, returnConfirm);
}
/**
* 待接单档优先,其次运输中,最后只剩待回单确认——整趟车都打完卡等回单确认才单独成档,
* 混着在跑的子单时仍算运输中。
*/
private static String classify(Counters counters) {
if (counters.total() == 0) {
return null;
@@ -136,7 +168,16 @@ final class TripAggregation {
if (counters.terminal() == counters.total()) {
return counters.completed() > 0 ? COMPLETED : CANCELLED;
}
return counters.pending() > 0 ? PENDING : RUNNING;
if (counters.pending() > 0) {
return PENDING;
}
if (counters.running() > 0) {
return RUNNING;
}
if (counters.returnConfirm() > 0) {
return PENDING_RETURN_CONFIRM;
}
return RUNNING;
}
/**
@@ -169,7 +210,7 @@ final class TripAggregation {
}
/** 子单聚合计数 */
record Counters(int total, int terminal, int completed, int pending) {
record Counters(int total, int terminal, int completed, int pending, int running, int returnConfirm) {
}
}
@@ -128,9 +128,10 @@ public class TripQueryServiceImpl implements ITripQueryService {
if (!scoped(condition)) {
return counts;
}
// 一次取数算清四档:分档口径与列表、卡片同一份推导,不再按库里的业务状态分批计数
// 一次取数算清各档:分档口径与列表、卡片同一份推导,不再按库里的业务状态分批计数
long pending = 0;
long doing = 0;
long confirm = 0;
long done = 0;
for (TripRows trip : trips(withoutTab(condition))) {
Integer tier = trip.tier();
@@ -141,14 +142,16 @@ public class TripQueryServiceImpl implements ITripQueryService {
case 0 -> pending++;
case 1 -> doing++;
case 2 -> done++;
case 4 -> confirm++;
default -> {
}
}
}
counts.setPending(pending);
counts.setDoing(doing);
counts.setConfirm(confirm);
counts.setDone(done);
counts.setAll(pending + doing + done);
counts.setAll(pending + doing + confirm + done);
return counts;
}
@@ -205,7 +208,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
boolean requireAccept = TripAggregation.requiresAccept(children);
boolean accepted = WaybillProcessSupport.ACCEPT_ACCEPTED.equals(TripAggregation.acceptStatus(children));
String status = TripAggregation.statusOf(children, requireAccept, accepted);
Integer tier = TripAggregation.appStatus(status);
Integer tier = TripAggregation.tabOf(status);
if (tier == null || !tiers.contains(tier)) {
return;
}
@@ -361,7 +364,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
}
/**
* 要哪几档:多档优先,其次单档,都不传按「全部」=待接单/进行中/已完成(不含已取消)
* 要哪几档:多档优先,其次单档,都不传按「全部」=待接单/进行中/待确认/已完成(不含已取消)
*/
private Set<Integer> tiers(TripQueryVO condition) {
if (Func.isNotEmpty(condition.getStatuses())) {
@@ -370,7 +373,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
if (condition.getStatus() != null) {
return Set.of(condition.getStatus());
}
return Set.of(0, 1, 2);
return Set.of(0, 1, 2, 4);
}
/**
@@ -140,7 +140,7 @@ class TripAggregationTest {
}
@Test
@DisplayName("四档都由同一份推导给出:待接单 / 进行中 / 已完成 / 已取消")
@DisplayName("五档都由同一份推导给出:待接单 / 进行中 / 待确认 / 已完成 / 已取消")
void tiersComeFromTheSameDerivation() {
// 需接单且未接单 → 待接单档
assertEquals(0, tier(List.of(waybill("pending", "pending", ACCEPT_YES))));
@@ -150,6 +150,9 @@ class TripAggregationTest {
assertEquals(2, tier(List.of(waybill("completed", "accepted", ACCEPT_YES))));
// 子单全取消 → 已取消档
assertEquals(3, tier(List.of(waybill("cancelled", null, ACCEPT_YES))));
// 待回单确认独立成档,即便项目要接单也不回落到待接单
assertEquals(4, tier(List.of(waybill(
org.springblade.transport.support.WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM, "pending", ACCEPT_YES))));
// 子单无从判定(空集合)时不落任何档,不会混进「全部」
assertNull(tier(List.of()));
}
@@ -182,7 +185,7 @@ class TripAggregationTest {
boolean requireAccept = TripAggregation.requiresAccept(children);
boolean accepted = org.springblade.transport.support.WaybillProcessSupport.ACCEPT_ACCEPTED
.equals(TripAggregation.acceptStatus(children));
return TripAggregation.appStatus(TripAggregation.statusOf(children, requireAccept, accepted));
return TripAggregation.tabOf(TripAggregation.statusOf(children, requireAccept, accepted));
}
@Test
@@ -194,20 +197,26 @@ class TripAggregationTest {
}
@Test
@DisplayName("待回单确认的子单仍进运次,且不会把运次判成已完成")
@DisplayName("待回单确认的子单仍进运次,独占一档且不会被判成已完成")
void pendingReturnConfirmChildKeepsTripRunning() {
String pendingReturnConfirm = org.springblade.transport.support.WaybillProcessSupport.STATUS_PENDING_RETURN_CONFIRM;
assertTrue(TripAggregation.CHILD_STATUSES.contains(pendingReturnConfirm),
"漏掉这个状态,子单会从运次读服务消失,配载单被当成全部完成");
// 与一张已完成的子单拼载:还有子单没走完,运次只能是运输中
// 与一张已完成的子单拼载:还有子单没走完,运次只能是待回单确认
List<Waybill> children = List.of(
waybill("completed", null, null),
waybill(pendingReturnConfirm, null, null));
String status = TripAggregation.statusOf(children, false, false);
assertEquals("running", status);
assertEquals(1, TripAggregation.appStatus(status), "运次不进已完成档");
assertEquals(pendingReturnConfirm, status);
assertEquals(4, TripAggregation.tabOf(status), "待回单确认单独一档");
assertEquals(1, TripAggregation.appStatus(status), "卡片状态码仍是运输中,不进已完成档");
// 还挂着在途子单时仍算进行中:待确认只在整趟车没有司机待办时成立
assertEquals("running", TripAggregation.statusOf(List.of(
waybill("running", null, null),
waybill(pendingReturnConfirm, null, null)), false, false));
}
private Waybill waybill(String businessStatus, String acceptStatus, String processJson) {