♻️ 整理异常处置路线调整的注释、参数命名与终态守卫
注释只留状态与约束,去掉推理与取舍表述;firstNonBlank / rewriteSegmentPoint 参数改为有语义的名字;reject / revoke / adjustRoute 共用 assertNotSettled,文案沿用「该异常已处理」。
This commit is contained in:
+46
-49
@@ -440,10 +440,7 @@ public class ExceptionDisposalServiceImpl
|
||||
throw new ServiceException("请选择需要拒绝的异常");
|
||||
}
|
||||
ExceptionDisposal disposal = getExisting(request.getId());
|
||||
// 已完成、已撤销都算已了结:再拒绝一次会覆盖最新处置结论,并往处置记录里多添一条
|
||||
if (WaybillProcessSupport.SETTLED_DISPOSAL_STATUSES.contains(disposal.getDisposalStatus())) {
|
||||
throw new ServiceException("该异常已处理");
|
||||
}
|
||||
assertNotSettled(disposal);
|
||||
String rejectReason = Func.toStr(request.getRejectReason(), "").trim();
|
||||
if (Func.isBlank(rejectReason)) {
|
||||
throw new ServiceException("请填写拒绝原因");
|
||||
@@ -467,14 +464,12 @@ public class ExceptionDisposalServiceImpl
|
||||
public void revoke(Long id) {
|
||||
ExceptionDisposal disposal = getExisting(id);
|
||||
Long currentUserId = AuthUtil.getUserId();
|
||||
// 撤销是上报人收回自己的上报,不是一次处置:别的账号(包括调度员)要推翻上报只能走拒绝
|
||||
// 撤销是上报人收回自己的上报,不是一次处置
|
||||
if (currentUserId == null || !Objects.equals(disposal.getReporterId(), currentUserId)) {
|
||||
throw new ServiceException("只能撤销本人上报的异常");
|
||||
}
|
||||
if (WaybillProcessSupport.SETTLED_DISPOSAL_STATUSES.contains(disposal.getDisposalStatus())) {
|
||||
throw new ServiceException("该异常已处理");
|
||||
}
|
||||
// 不写跟进记录,也不动 latest_follow_content:处置时间线里不该出现一条由上报人「处置」的记录
|
||||
assertNotSettled(disposal);
|
||||
// 不写跟进记录,也不动 latest_follow_content
|
||||
disposal.setDisposalStatus(STATUS_REVOKED);
|
||||
updateById(disposal);
|
||||
}
|
||||
@@ -486,25 +481,23 @@ public class ExceptionDisposalServiceImpl
|
||||
throw new ServiceException("请选择需要调整路线的异常");
|
||||
}
|
||||
ExceptionDisposal disposal = getExisting(request.getId());
|
||||
if (WaybillProcessSupport.SETTLED_DISPOSAL_STATUSES.contains(disposal.getDisposalStatus())) {
|
||||
throw new ServiceException("该异常已处理");
|
||||
}
|
||||
assertNotSettled(disposal);
|
||||
Waybill waybill = disposal.getWaybillId() == null ? null : waybillService.getById(disposal.getWaybillId());
|
||||
if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("关联运单不存在");
|
||||
}
|
||||
// 路线改在谁身上跟着配载单走:配载单是这趟车的执行主体,司机端打卡读的就是它的路线
|
||||
// 路线改在谁身上跟着配载单走:配载单是这趟车的执行主体
|
||||
LoadingManage loading = findLoading(waybill.getLoadingNo());
|
||||
if (loading == null && Func.isNotBlank(waybill.getLoadingNo())) {
|
||||
throw new ServiceException("运单已关联配载单,请在配载单中修改");
|
||||
}
|
||||
// 校验与拼装全部在写入之前完成:任何一条不通过都不该留下半条处置记录
|
||||
// 校验与拼装全部在写入之前完成
|
||||
List<Station> stations = validateRouteNodes(request.getRouteNodes(), loading);
|
||||
List<String> sourceAddresses = loading != null
|
||||
? loadingStationAddresses(loading)
|
||||
: waybillStationAddresses(waybill);
|
||||
List<String> targetAddresses = stations.stream().map(Station::address).toList();
|
||||
// 首站地址只读;原数据里首站地址缺失时无从比对,放行而不是拒绝整次调整
|
||||
// 首站地址不可修改;原数据首站地址为空时跳过比对
|
||||
if (!sourceAddresses.isEmpty() && Func.isNotBlank(sourceAddresses.get(0))
|
||||
&& !Objects.equals(sourceAddresses.get(0), targetAddresses.get(0))) {
|
||||
throw new ServiceException("起始地址不允许修改");
|
||||
@@ -515,8 +508,7 @@ public class ExceptionDisposalServiceImpl
|
||||
}
|
||||
RouteChangePlan plan = planRouteChange(loading, waybill, stations, sourceAddresses, targetAddresses);
|
||||
|
||||
// 事务内顺序固定:跟进记录 → 载体路线 → 异常状态。
|
||||
// 少了任何一步都读不出「谁在什么时候把路线改成了什么」,所以任一步失败都整体回滚
|
||||
// 事务内顺序固定:跟进记录 → 载体路线 → 异常状态
|
||||
insertFollowRecord(disposal.getId(), plan.followContent());
|
||||
if (loading != null) {
|
||||
applyLoadingRoute(loading, plan);
|
||||
@@ -528,6 +520,13 @@ public class ExceptionDisposalServiceImpl
|
||||
updateById(disposal);
|
||||
}
|
||||
|
||||
/** 已了结(已完成 / 已撤销)的异常不允许再次处置 */
|
||||
private void assertNotSettled(ExceptionDisposal disposal) {
|
||||
if (WaybillProcessSupport.SETTLED_DISPOSAL_STATUSES.contains(disposal.getDisposalStatus())) {
|
||||
throw new ServiceException("该异常已处理");
|
||||
}
|
||||
}
|
||||
|
||||
/** 一条跟进记录:处置记录里「先写记录再改状态」的顺序由调用方的事务保证 */
|
||||
private void insertFollowRecord(Long disposalId, String content) {
|
||||
ExceptionDisposalFollowRecord record = new ExceptionDisposalFollowRecord();
|
||||
@@ -553,7 +552,7 @@ public class ExceptionDisposalServiceImpl
|
||||
/**
|
||||
* 校验并规范化站点:站数至少两个、首站 start、末站 end、其余 transit,地址都不能为空。
|
||||
* <p>
|
||||
* kind 不额外接受「按位置猜」的写法:站序就是落库顺序,kind 与位置不一致时写下去的首末站地址会是错的。
|
||||
* kind 由位置决定,必须与站序一致。
|
||||
*/
|
||||
private List<Station> validateRouteNodes(List<ExceptionDisposalRouteAdjustRequest.RouteNode> routeNodes,
|
||||
LoadingManage loading) {
|
||||
@@ -597,10 +596,9 @@ public class ExceptionDisposalServiceImpl
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点标记里的数字是配载单运单集合里的位次(1 起),越界的标记会让司机端找不到对应子运单,
|
||||
* 那样的路线读不出装卸与货物,宁可报错也不落库。
|
||||
* 站点标记里的数字是配载单运单集合里的位次(1 起),越界时拒绝。
|
||||
* <p>
|
||||
* 没有配载单就没有子运单序号可言:运单的路线段没有装卸归属,站点标记不参与落库。
|
||||
* 没有配载单时没有子运单序号,站点标记不参与落库。
|
||||
*/
|
||||
private void assertTagsMatchChildWaybills(List<Station> stations, LoadingManage loading) {
|
||||
if (loading == null) {
|
||||
@@ -626,7 +624,7 @@ public class ExceptionDisposalServiceImpl
|
||||
List<?> waybillIds = JsonUtil.parse(waybillIdsJson, List.class);
|
||||
return waybillIds == null ? 0 : waybillIds.size();
|
||||
} catch (Exception ignored) {
|
||||
// 运单集合不是 JSON 数组时按没有子运单处理,站点标记一律对不上
|
||||
// 运单集合不是 JSON 数组时按没有子运单处理
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -645,7 +643,7 @@ public class ExceptionDisposalServiceImpl
|
||||
|
||||
/**
|
||||
* 运单当前路线的站点地址:首段出发地是首站,其后每段的到达地各成一站。
|
||||
* 段里缺地址时退化用段名——运单详情与调整页用的也是这两列。
|
||||
* 段里缺地址时用站名替代。
|
||||
*/
|
||||
private List<String> waybillStationAddresses(Waybill waybill) {
|
||||
List<Map<String, Object>> segments = routeSegments(waybill.getRouteJson());
|
||||
@@ -739,7 +737,7 @@ public class ExceptionDisposalServiceImpl
|
||||
parts.add("到货地 " + displayAddress(sourceEnd) + " → " + displayAddress(targetEnd));
|
||||
}
|
||||
if (parts.isEmpty()) {
|
||||
// 首末站都没动,变化只能落在途经站:地址细节塞不进 500 字的跟进记录,按站数说明
|
||||
// 首末站未变时按途经站数量说明变化
|
||||
parts.add("途经站由 " + transitCount(sourceAddresses) + " 个改为 " + transitCount(targetAddresses) + " 个");
|
||||
}
|
||||
return ROUTE_CHANGE_PREFIX + String.join(";", parts);
|
||||
@@ -770,7 +768,7 @@ public class ExceptionDisposalServiceImpl
|
||||
// 站点行没有独立的站名,展示地址与地址取同一份文本
|
||||
row.put("displayAddress", station.address());
|
||||
row.put("tags", station.tags());
|
||||
// locked 只影响 PC 编辑弹窗能否拖动,司机端不读;这里写入的路线始终可继续调整
|
||||
// locked 只影响 PC 编辑弹窗能否拖动,司机端不读
|
||||
row.put("locked", false);
|
||||
rows.add(row);
|
||||
}
|
||||
@@ -798,20 +796,20 @@ public class ExceptionDisposalServiceImpl
|
||||
return JsonUtil.toJson(rewritten);
|
||||
}
|
||||
|
||||
private void rewriteSegmentPoint(Map<String, Object> segment, String point, String targetAddress) {
|
||||
String currentAddress = firstNonBlank(text(segment.get(point + "Address")),
|
||||
text(segment.get(point + "Name")), "");
|
||||
/** 站点地址与站名写同一份文本 */
|
||||
private void rewriteSegmentPoint(Map<String, Object> segment, String pointKey, String targetAddress) {
|
||||
String currentAddress = firstNonBlank(text(segment.get(pointKey + "Address")),
|
||||
text(segment.get(pointKey + "Name")), "");
|
||||
if (Objects.equals(currentAddress, targetAddress)) {
|
||||
return;
|
||||
}
|
||||
segment.put(point + "Address", targetAddress);
|
||||
segment.put(point + "Name", targetAddress);
|
||||
segment.put(pointKey + "Address", targetAddress);
|
||||
segment.put(pointKey + "Name", targetAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别路线段形状的 route_json。运单历史上存在两种形状,另一种是 PC 写下的节点行
|
||||
* (address + tags + 运单号),司机端按段解析读不出来,因此这里只认段:元素带段字段、
|
||||
* 且不带 address / tags。
|
||||
* 识别路线段形状的 route_json:元素带段字段,且不带 address / tags。
|
||||
* 另一形状是 PC 写下的节点行(address + tags + 运单号),司机端按段解析读不出来。
|
||||
*/
|
||||
private List<Map<String, Object>> routeSegments(String routeJson) {
|
||||
List<Map<String, Object>> elements = jsonObjectList(routeJson);
|
||||
@@ -829,8 +827,7 @@ public class ExceptionDisposalServiceImpl
|
||||
|
||||
/**
|
||||
* 任务信息是整列覆盖:读出原对象,只把本次变更记录插到最前面,其余键原样写回。
|
||||
* 原来的内容不是 JSON 对象(坏数据或历史数组)时按空对象重建,只丢这一列的无法解析部分,
|
||||
* 不能让一次路线调整因为附件信息列而失败。
|
||||
* 原来的内容不是 JSON 对象(坏数据或历史数组)时按空对象重建。
|
||||
*/
|
||||
private String appendRouteChangeRecord(String taskInfoJson, String content) {
|
||||
Map<String, Object> taskInfo = parseTaskInfo(taskInfoJson);
|
||||
@@ -849,7 +846,7 @@ public class ExceptionDisposalServiceImpl
|
||||
return JsonUtil.toJson(taskInfo);
|
||||
}
|
||||
|
||||
/** 任务信息只认 JSON 对象:数组与坏数据都按空对象重建,这一列不能挡住整次路线调整 */
|
||||
/** 任务信息只认 JSON 对象:数组与坏数据都按空对象重建 */
|
||||
private Map<String, Object> parseTaskInfo(String taskInfoJson) {
|
||||
Map<String, Object> taskInfo = new LinkedHashMap<>();
|
||||
if (Func.isBlank(taskInfoJson)) {
|
||||
@@ -889,7 +886,7 @@ public class ExceptionDisposalServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
/** 解析成对象数组;坏 JSON 与空值都按空数组处理,路线坏了不该让整次操作崩掉 */
|
||||
/** 解析成对象数组;坏 JSON 与空值都按空数组处理 */
|
||||
private List<Map<String, Object>> jsonObjectList(String json) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
if (Func.isBlank(json)) {
|
||||
@@ -916,18 +913,18 @@ public class ExceptionDisposalServiceImpl
|
||||
return value == null ? "" : String.valueOf(value).trim();
|
||||
}
|
||||
|
||||
private String firstNonBlank(String first, String second, String fallback) {
|
||||
if (Func.isNotBlank(first)) {
|
||||
return first;
|
||||
private String firstNonBlank(String addressValue, String nameValue, String fallbackValue) {
|
||||
if (Func.isNotBlank(addressValue)) {
|
||||
return addressValue;
|
||||
}
|
||||
return Func.isNotBlank(second) ? second : fallback;
|
||||
return Func.isNotBlank(nameValue) ? nameValue : fallbackValue;
|
||||
}
|
||||
|
||||
/** 规范化后的一个站点:地址 + 该站的装卸标记 */
|
||||
private record Station(String address, List<String> tags) {
|
||||
}
|
||||
|
||||
/** 一次路线调整要落库的全部内容,写入阶段只负责 set 与 update */
|
||||
/** 一次路线调整要落库的全部内容 */
|
||||
private record RouteChangePlan(String followContent,
|
||||
String routeJson,
|
||||
String departureAddress,
|
||||
@@ -1008,8 +1005,8 @@ public class ExceptionDisposalServiceImpl
|
||||
/**
|
||||
* 详情页的可编辑站点与配载单信息。
|
||||
* <p>
|
||||
* 配载单是这趟车的执行主体,站点取它的站点行;运单没有配载单号、或配载单已被删除 / 解绑时退回
|
||||
* 运单自己的路线段——历史异常仍要能打开,不能因为配载单不在了就打不开详情。
|
||||
* 站点取配载单的站点行;运单没有配载单号,或配载单已删除时退回运单的路线段。
|
||||
* 运单不存在时三项各给空值。
|
||||
*/
|
||||
private void fillRouteNodes(ExceptionDisposalVO vo, Waybill waybill) {
|
||||
if (vo == null) {
|
||||
@@ -1061,7 +1058,7 @@ public class ExceptionDisposalServiceImpl
|
||||
|
||||
/**
|
||||
* 没有配载单时从路线段推导站点:首段出发地是首站,其后每段的到达地各成一站。
|
||||
* 地址口径与改路线时的比对一致(地址优先、站名兜底),否则用户什么都没改直接提交也会被判成改过地址。
|
||||
* 地址口径与改路线时的比对一致(地址优先、站名兜底)。
|
||||
*/
|
||||
private List<ExceptionDisposalVO.RouteNode> waybillRouteNodes(Waybill waybill) {
|
||||
List<ExceptionDisposalVO.RouteNode> nodes = new ArrayList<>();
|
||||
@@ -1078,7 +1075,7 @@ public class ExceptionDisposalServiceImpl
|
||||
return stationKinds(nodes);
|
||||
}
|
||||
|
||||
/** 站序声明:首站 start、末站 end、其余 transit;不足两个站就没有首末可言,不给前端调整入口 */
|
||||
/** 站序声明:首站 start、末站 end、其余 transit;不足两个站时返回空列表 */
|
||||
private List<ExceptionDisposalVO.RouteNode> stationKinds(List<ExceptionDisposalVO.RouteNode> nodes) {
|
||||
if (nodes.size() < 2) {
|
||||
return new ArrayList<>();
|
||||
@@ -1092,7 +1089,7 @@ public class ExceptionDisposalServiceImpl
|
||||
|
||||
/**
|
||||
* 站点标记指向配载单运单集合里的第 N 张子运单,取其项目名去重。
|
||||
* 标记对不上子运单(越界、子运单已不存在)时跳过,一个坏标记不该让整个详情打不开。
|
||||
* 标记对不上子运单(越界、子运单已不存在)时跳过。
|
||||
*/
|
||||
private List<String> stationProjects(List<String> tags, Map<Integer, Waybill> childWaybills) {
|
||||
Set<String> projects = new LinkedHashSet<>();
|
||||
@@ -1111,7 +1108,7 @@ public class ExceptionDisposalServiceImpl
|
||||
|
||||
/**
|
||||
* 按配载单运单集合的位次取子运单,位次从 0 起(站点标记里的 N 减一)。
|
||||
* 位次不能因为某张子运单查不到而前移,否则标记会指到别的子运单上。
|
||||
* 位次不因缺子运单而前移。
|
||||
*/
|
||||
private Map<Integer, Waybill> childWaybillsByOrder(LoadingManage loading) {
|
||||
Map<Integer, Waybill> childWaybills = new LinkedHashMap<>();
|
||||
@@ -1153,7 +1150,7 @@ public class ExceptionDisposalServiceImpl
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 运单集合坏了就没有子运单可言,详情退回运单自己的路线,不影响打开
|
||||
// 运单集合不是 JSON 数组时按没有子运单处理
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return waybillIds;
|
||||
|
||||
Reference in New Issue
Block a user