✨ 调度首页把司机已拒单的运单列为重新派单待处理,并在司机端让该运单脱离

This commit is contained in:
2026-10-10 01:48:49 +08:00
parent b66984d3a1
commit 4fe0c8a968
7 changed files with 560 additions and 11 deletions
@@ -72,7 +72,7 @@ public class ManageWaybillController extends BladeController {
@GetMapping("/home")
@ApiOperationSupport(order = 2)
@Operation(summary = "首页聚合", description = "统计 + 异常/风险角标 + 待处理事项(异常处置≠已完成)+ 当前用户名")
@Operation(summary = "首页聚合", description = "统计 + 异常/风险/重新派单角标 + 待处理事项(异常处置≠已完成,或司机已拒单待重新派单)+ 当前用户名")
public R<AdminHomeVO> home() {
return R.data(manageWaybillService.home());
}
@@ -262,7 +262,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
throw new ServiceException("运单ID不能为空");
}
Driver driver = requireCurrentDriver();
Waybill waybill = loadDriverWaybill(dto.getWaybillId(), currentBoundPlates(driver));
// 在途打卡单独保留原取单判据,不排除已拒单(见 scopedQuery 的重载说明)
Waybill waybill = loadDriverWaybill(dto.getWaybillId(), currentBoundPlates(driver), true);
waybill = normalizeAcceptStatus(waybill);
String processJson = resolveProcessJson(waybill);
@@ -483,6 +484,10 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
if (WaybillProcessSupport.isAccepted(waybill.getDriverAcceptStatus())) {
throw new ServiceException("该运单已接单");
}
// 拒单后运单已对本人脱离,不给自己接回来的入口;要改主意只能由调度重新派单
if (WaybillProcessSupport.isRejected(waybill.getDriverAcceptStatus())) {
throw new ServiceException("该运单已拒单,请联系调度重新派单");
}
}
private void assertRejectable(Waybill waybill) {
@@ -498,13 +503,19 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
}
private Waybill loadDriverWaybill(Long id, List<String> plates) {
return loadDriverWaybill(id, plates, false);
}
/** @param includeRejected 仅 submitEnroute 传 true,见 scopedQuery 的重载说明 */
private Waybill loadDriverWaybill(Long id, List<String> plates, boolean includeRejected) {
if (id == null) {
throw new ServiceException("运单ID不能为空");
}
if (plates.isEmpty()) {
throw new ServiceException("当前司机未绑定车辆");
}
Waybill waybill = waybillService.getOne(scopedQuery(plates).eq(Waybill::getId, id).last("LIMIT 1"));
Waybill waybill = waybillService.getOne(scopedQuery(plates, includeRejected)
.eq(Waybill::getId, id).last("LIMIT 1"));
if (waybill == null) {
throw new ServiceException("运单不存在或无权操作");
}
@@ -560,11 +571,23 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
}
/**
* 司机可见运单范围:运单车牌(主车/挂车)落在当前司机绑定车牌内。
* 绑定来源:blade_transport_driver.driving_vehicle
* 司机可见运单范围:运单车牌(主车/挂车)落在当前司机绑定车牌内,
* 且该运单未被这个司机拒单。绑定来源:blade_transport_driver.driving_vehicle
* <p>
* 拒单后运单对司机彻底脱离——列表、计数、详情、打卡、接单一律不可达,等管理员重新派单。
* 运单上的司机姓名/手机/车牌不清空,调度重新派单时要知道原来派给谁。
*/
private LambdaQueryWrapper<Waybill> scopedQuery(List<String> plates) {
return Wrappers.<Waybill>lambdaQuery().and(w -> {
return scopedQuery(plates, false);
}
/**
* @param includeRejected true 时不排除已拒单的运单,仅限在途打卡使用:
* 拒单的运单不可能处于运输中,收口拦不到合法操作,
* 而保留原判据可避免「已拒单 + 进行中」的历史脏数据被误伤
*/
private LambdaQueryWrapper<Waybill> scopedQuery(List<String> plates, boolean includeRejected) {
LambdaQueryWrapper<Waybill> wrapper = Wrappers.<Waybill>lambdaQuery().and(w -> {
w.in(Waybill::getVehicleNo, plates)
.or().in(Waybill::getTrailerVehicleNo, plates);
for (String plate : plates) {
@@ -578,6 +601,11 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
);
}
});
if (!includeRejected) {
wrapper.and(w -> w.isNull(Waybill::getDriverAcceptStatus)
.or().ne(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED));
}
return wrapper;
}
private void applyKeyword(LambdaQueryWrapper<Waybill> wrapper, String keyword) {
@@ -57,6 +57,7 @@ import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
@@ -85,6 +86,13 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
private static final String DISPOSAL_PROCESSING = "processing";
private static final String RISK_PENDING = "pending";
/** 小程序首页待处理事项的类型:异常待处置 */
private static final String TODO_EXCEPTION = "exception";
/** 小程序首页待处理事项的类型:重新派单待处理 */
private static final String TODO_REASSIGN = "reassign";
/** 重派条目跳转页码:参数 id 是运单主键(不是运单号),该页据此回显运单;buyerPaid 恒为 0,与列表卡片一致 */
private static final String REASSIGN_TARGET_URL = "/pages/waybill/reassign?id=%s&buyerPaid=0";
private static final String EXCEPTION_YES = "exception";
private static final String EXCEPTION_NO = "normal";
private static final String TRANSPORT_COMMON = "common";
@@ -122,8 +130,9 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
AdminHomeBadgesVO badges = new AdminHomeBadgesVO();
badges.setException(home.getStats().getException());
badges.setRisk(countPendingRisks());
badges.setPendingReassign(countPendingReassignments());
home.setBadges(badges);
home.setFeed(buildExceptionFeed());
home.setFeed(buildAdminFeed());
return home;
}
@@ -561,18 +570,66 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
.eq(RiskDisposal::getDisposalStatus, RISK_PENDING));
}
private List<AdminTodoItemVO> buildExceptionFeed() {
/** 待重新派单:司机已拒单,且运单尚未取消/完成 */
private long countPendingReassignments() {
return waybillService.count(pendingReassignQuery());
}
private LambdaQueryWrapper<Waybill> pendingReassignQuery() {
return Wrappers.<Waybill>lambdaQuery()
.eq(Waybill::getDriverAcceptStatus, ACCEPT_REJECTED)
.in(Waybill::getBusinessStatus, STATUS_PENDING, STATUS_RUNNING);
}
/**
* 首页待处理事项:异常待处置 + 重新派单待处理。
* 两类各按自己的业务发生时间(上报时刻 / 拒单时刻)倒序,合并后再整体倒序取前 N 条。
* 排序时间随条目一起携带,不塞进 id 字段——id 在两型下各自是处置主键与运单主键。
*/
List<AdminTodoItemVO> buildAdminFeed() {
List<TimedTodo> merged = new ArrayList<>();
merged.addAll(buildExceptionFeed());
merged.addAll(buildReassignFeed());
merged.sort(java.util.Comparator.comparing(
(TimedTodo entry) -> entry.time, java.util.Comparator.nullsFirst(java.util.Comparator.naturalOrder())));
java.util.Collections.reverse(merged);
int to = Math.min(merged.size(), FEED_LIMIT);
return merged.subList(0, to).stream().map(TimedTodo::item).collect(Collectors.toList());
}
/** 待处理事项 + 它的业务发生时间(仅用于排序) */
private record TimedTodo(AdminTodoItemVO item, LocalDateTime time) {
}
private List<TimedTodo> buildExceptionFeed() {
List<ExceptionDisposal> list = exceptionDisposalService.list(Wrappers.<ExceptionDisposal>lambdaQuery()
.in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)
.orderByDesc(ExceptionDisposal::getReportTime)
.last("LIMIT " + FEED_LIMIT));
return list.stream().map(this::toTodoItem).collect(Collectors.toList());
return list.stream()
.map(disposal -> new TimedTodo(toTodoItem(disposal), exceptionTimeOf(disposal)))
.collect(Collectors.toList());
}
private List<TimedTodo> buildReassignFeed() {
List<Waybill> list = waybillService.list(pendingReassignQuery()
.orderByDesc(Waybill::getDriverRejectTime)
.last("LIMIT " + FEED_LIMIT));
return list.stream()
.map(waybill -> new TimedTodo(toReassignTodoItem(waybill), rejectTimeOf(waybill)))
.collect(Collectors.toList());
}
private LocalDateTime exceptionTimeOf(ExceptionDisposal disposal) {
return disposal.getReportTime() != null
? disposal.getReportTime()
: toLocalDateTime(disposal.getCreateTime());
}
private AdminTodoItemVO toTodoItem(ExceptionDisposal disposal) {
AdminTodoItemVO item = new AdminTodoItemVO();
item.setId(disposal.getId());
item.setType("exception");
item.setType(TODO_EXCEPTION);
item.setTitle("异常待处置");
item.setTimeAgo(formatTimeAgo(disposal.getReportTime() != null
? disposal.getReportTime()
@@ -585,6 +642,38 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
return item;
}
private AdminTodoItemVO toReassignTodoItem(Waybill waybill) {
AdminTodoItemVO item = new AdminTodoItemVO();
item.setId(waybill.getId());
item.setType(TODO_REASSIGN);
item.setTitle("重新派单待处理");
item.setTimeAgo(formatTimeAgo(rejectTimeOf(waybill)));
item.setDesc(buildReassignDesc(waybill));
item.setWaybillNo(Func.toStr(waybill.getWaybillNo(), ""));
item.setActionLabel("重新派单");
item.setTargetUrl(String.format(REASSIGN_TARGET_URL, waybill.getId()));
return item;
}
/** 拒单时刻是「被拒了多久」的唯一依据;为空(历史数据)才回退到最后修改时间 */
private LocalDateTime rejectTimeOf(Waybill waybill) {
if (waybill.getDriverRejectTime() != null) {
return toLocalDateTime(waybill.getDriverRejectTime());
}
return toLocalDateTime(waybill.getUpdateTime());
}
/** 拒单原因的输入是可选的,没填时正文不能留下空的「:」 */
private String buildReassignDesc(Waybill waybill) {
String driver = Func.toStr(waybill.getDriverName(), "原司机");
String text = "运单已指派" + driver + ",被拒绝,请重新派单";
String reason = Func.toStr(waybill.getDriverRejectReason(), "").trim();
if (Func.isNotBlank(reason)) {
text = text + ":" + reason;
}
return text.length() > 80 ? text.substring(0, 80) + "…" : text;
}
private String buildExceptionDesc(ExceptionDisposal disposal) {
String reporter = Func.toStr(disposal.getReporterName(), "司机");
String type = Func.toStr(disposal.getExceptionType(), "异常");
@@ -606,6 +695,7 @@ public class ManageWaybillServiceImpl implements IManageWaybillService {
return Func.toStr(AuthUtil.getUserName(), "");
}
/** 相对时间文案;时间取不到时留空,避免把「现在」当成发生时刻显示成「刚刚」 */
private String formatTimeAgo(LocalDateTime time) {
if (time == null) {
return "";