✨ 异常详情下发配载单、子运单与可调整站点

This commit is contained in:
2026-10-23 01:10:25 +08:00
parent b2addfc01e
commit ba2b0a3a8b
3 changed files with 567 additions and 4 deletions
@@ -61,10 +61,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -605,8 +607,7 @@ public class ExceptionDisposalServiceImpl
int childCount = childWaybillCount(loading.getWaybillIdsJson());
for (Station station : stations) {
for (String tag : station.tags()) {
String digits = tag.replaceAll("\\D+", "");
int order = Func.isEmpty(digits) ? 0 : Func.toInt(digits, 0);
int order = tagOrder(tag);
if (order < 1 || order > childCount) {
throw new ServiceException("路线节点未关联子运单");
}
@@ -631,7 +632,7 @@ public class ExceptionDisposalServiceImpl
/** 配载单当前路线的站点地址:优先站点行,没有站点行时回退到发货 / 途经 / 到货地址三列 */
private List<String> loadingStationAddresses(LoadingManage loading) {
List<String> addresses = new ArrayList<>();
for (Map<String, Object> row : jsonObjectList(loading.getRouteJson())) {
for (Map<String, Object> row : loadingStationRows(loading)) {
addAddress(addresses, text(row.get("address")));
}
if (!addresses.isEmpty()) {
@@ -978,7 +979,7 @@ public class ExceptionDisposalServiceImpl
return vo;
}
/** 按关联运单补齐详情页路线 / 货物展示字段 */
/** 按关联运单补齐详情页路线 / 货物 / 配载单展示字段 */
private void fillRouteAndCargo(ExceptionDisposalVO vo, Long waybillId) {
if (vo == null || waybillId == null) {
return;
@@ -996,6 +997,206 @@ public class ExceptionDisposalServiceImpl
cargo.put("name", Func.toStr(waybill.getCargoName(), ""));
cargo.put("weight", formatWeight(waybill.getQuantity(), waybill.getQuantityUnit()));
vo.setCargo(cargo);
fillRouteNodes(vo, waybill);
}
/**
* 详情页的可编辑站点与配载单信息。
* <p>
* 配载单是这趟车的执行主体,站点取它的站点行;运单没有配载单号、或配载单已被删除 / 解绑时退回
* 运单自己的路线段——历史异常仍要能打开,不能因为配载单不在了就打不开详情。
*/
private void fillRouteNodes(ExceptionDisposalVO vo, Waybill waybill) {
LoadingManage loading = findLoading(waybill.getLoadingNo());
if (loading == null) {
vo.setChildWaybillNos(new ArrayList<>());
vo.setRouteNodes(waybillRouteNodes(waybill));
return;
}
Map<Integer, Waybill> childWaybills = childWaybillsByOrder(loading);
vo.setLoadingNo(loading.getLoadingNo());
vo.setChildWaybillNos(childWaybillNos(childWaybills));
List<Map<String, Object>> rows = loadingStationRows(loading);
vo.setRouteNodes(loadingRouteNodes(rows, childWaybills));
vo.setRoute(loadingRoute(rows, loading, waybill));
}
/** 配载单的站点行:只有带地址的行才算一个站,缺地址的历史行不参与 */
private List<Map<String, Object>> loadingStationRows(LoadingManage loading) {
List<Map<String, Object>> rows = new ArrayList<>();
for (Map<String, Object> row : jsonObjectList(loading.getRouteJson())) {
if (Func.isNotBlank(text(row.get("address")))) {
rows.add(row);
}
}
return rows;
}
private List<ExceptionDisposalVO.RouteNode> loadingRouteNodes(List<Map<String, Object>> rows,
Map<Integer, Waybill> childWaybills) {
List<ExceptionDisposalVO.RouteNode> nodes = new ArrayList<>(rows.size());
for (Map<String, Object> row : rows) {
List<String> tags = stringList(row.get("tags"));
ExceptionDisposalVO.RouteNode node = new ExceptionDisposalVO.RouteNode();
node.setAddress(text(row.get("address")));
node.setTags(tags);
node.setProjects(stationProjects(tags, childWaybills));
nodes.add(node);
}
return stationKinds(nodes);
}
/**
* 没有配载单时从路线段推导站点:首段出发地是首站,其后每段的到达地各成一站。
* 地址口径与改路线时的比对一致(地址优先、站名兜底),否则用户什么都没改直接提交也会被判成改过地址。
*/
private List<ExceptionDisposalVO.RouteNode> waybillRouteNodes(Waybill waybill) {
List<ExceptionDisposalVO.RouteNode> nodes = new ArrayList<>();
for (String address : waybillStationAddresses(waybill)) {
if (Func.isBlank(address)) {
continue;
}
ExceptionDisposalVO.RouteNode node = new ExceptionDisposalVO.RouteNode();
node.setAddress(address);
node.setTags(new ArrayList<>());
node.setProjects(new ArrayList<>());
nodes.add(node);
}
return stationKinds(nodes);
}
/** 站序声明:首站 start、末站 end、其余 transit;不足两个站就没有首末可言,不给前端调整入口 */
private List<ExceptionDisposalVO.RouteNode> stationKinds(List<ExceptionDisposalVO.RouteNode> nodes) {
if (nodes.size() < 2) {
return new ArrayList<>();
}
for (int index = 0; index < nodes.size(); index++) {
nodes.get(index).setKind(index == 0 ? NODE_KIND_START
: (index == nodes.size() - 1 ? NODE_KIND_END : NODE_KIND_TRANSIT));
}
return nodes;
}
/**
* 站点标记指向配载单运单集合里的第 N 张子运单,取其项目名去重。
* 标记对不上子运单(越界、子运单已不存在)时跳过,一个坏标记不该让整个详情打不开。
*/
private List<String> stationProjects(List<String> tags, Map<Integer, Waybill> childWaybills) {
Set<String> projects = new LinkedHashSet<>();
for (String tag : tags) {
Waybill child = childWaybills.get(tagOrder(tag) - 1);
if (child == null) {
continue;
}
String projectName = Func.toStr(child.getProjectName(), "").trim();
if (Func.isNotBlank(projectName)) {
projects.add(projectName);
}
}
return new ArrayList<>(projects);
}
/**
* 按配载单运单集合的位次取子运单,位次从 0 起(站点标记里的 N 减一)。
* 位次不能因为某张子运单查不到而前移,否则标记会指到别的子运单上。
*/
private Map<Integer, Waybill> childWaybillsByOrder(LoadingManage loading) {
Map<Integer, Waybill> childWaybills = new LinkedHashMap<>();
List<Long> waybillIds = waybillIds(loading.getWaybillIdsJson());
List<Long> queryIds = waybillIds.stream().filter(Objects::nonNull).distinct().toList();
if (queryIds.isEmpty()) {
return childWaybills;
}
Map<Long, Waybill> waybillsById = new HashMap<>();
List<Waybill> found = waybillService.listByIds(queryIds);
if (found != null) {
for (Waybill child : found) {
if (child != null && !Objects.equals(child.getIsDeleted(), 1)) {
waybillsById.put(child.getId(), child);
}
}
}
for (int order = 0; order < waybillIds.size(); order++) {
Waybill child = waybillsById.get(waybillIds.get(order));
if (child != null) {
childWaybills.put(order, child);
}
}
return childWaybills;
}
/** 配载单运单集合的元素是运单主键;元素不是主键时留空占位,后面的位次不能因此前移 */
private List<Long> waybillIds(String waybillIdsJson) {
List<Long> waybillIds = new ArrayList<>();
if (Func.isBlank(waybillIdsJson)) {
return waybillIds;
}
try {
Object parsed = JsonUtil.parse(waybillIdsJson, List.class);
if (parsed instanceof List<?> list) {
for (Object item : list) {
long waybillId = Func.toLong(text(item), 0L);
waybillIds.add(waybillId > 0 ? waybillId : null);
}
}
} catch (Exception ignored) {
// 运单集合坏了就没有子运单可言,详情退回运单自己的路线,不影响打开
return new ArrayList<>();
}
return waybillIds;
}
/** 子运单号按配载单运单集合的顺序给出;查不到的子运单跳过,不占位 */
private List<String> childWaybillNos(Map<Integer, Waybill> childWaybills) {
List<String> childWaybillNos = new ArrayList<>(childWaybills.size());
for (Waybill child : childWaybills.values()) {
String waybillNo = Func.toStr(child.getWaybillNo(), "").trim();
if (Func.isNotBlank(waybillNo)) {
childWaybillNos.add(waybillNo);
}
}
return childWaybillNos;
}
/** 有配载单时路线摘要取这趟车自己的首末站:站点行的展示地址优先,没有站点行回落到地址列 */
private Map<String, String> loadingRoute(List<Map<String, Object>> rows, LoadingManage loading, Waybill waybill) {
String start;
String end;
if (rows.isEmpty()) {
start = firstNonBlank(loading.getDepartureAddress(), waybill.getDepartureName(), "");
end = firstNonBlank(loading.getArrivalAddress(), waybill.getArrivalName(), "");
} else {
start = stationDisplayAddress(rows.get(0));
end = stationDisplayAddress(rows.get(rows.size() - 1));
}
Map<String, String> route = new HashMap<>(2);
route.put("start", start);
route.put("end", end);
return route;
}
private String stationDisplayAddress(Map<String, Object> row) {
return firstNonBlank(text(row.get("displayAddress")), text(row.get("address")), "");
}
/** 站点标记里的数字是配载单运单集合里的位次(1 起),取不到数字时返回 0 */
private int tagOrder(String tag) {
String digits = Func.toStr(tag, "").replaceAll("\\D+", "");
return Func.isEmpty(digits) ? 0 : Func.toInt(digits, 0);
}
private List<String> stringList(Object value) {
List<String> texts = new ArrayList<>();
if (value instanceof List<?> list) {
for (Object item : list) {
String text = text(item);
if (Func.isNotBlank(text)) {
texts.add(text);
}
}
}
return texts;
}
private String formatWeight(BigDecimal quantity, String unit) {