diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java index dfbd4de..d956a87 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -174,6 +174,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl waybills) { + // 按顺序收集全部节点:首票发货地、每票到货地;节点与相邻前一点相同则重合跳过 + List nodes = new ArrayList<>(); + for (Waybill waybill : waybills) { + String departure = TransportBusinessSupport.trimToNull(waybill.getDepartureAddress()); + String arrival = TransportBusinessSupport.trimToNull(waybill.getArrivalAddress()); + appendRouteNode(nodes, departure); + appendRouteNode(nodes, arrival); + } + // 途经点 = 去掉首尾(首票发货地、末票到货地)后的中间节点 + List transitNodes = nodes.size() > 2 ? nodes.subList(1, nodes.size() - 1) : List.of(); + if (Func.isEmpty(transitNodes)) { + return null; + } + return String.join(" - ", transitNodes); + } + + private void appendRouteNode(List nodes, String address) { + if (Func.isEmpty(address)) { + return; + } + if (!nodes.isEmpty() && nodes.get(nodes.size() - 1).equals(address)) { + // 与上一节点相同视为同一地点,重合不重复 + return; + } + nodes.add(address); + } + @Override @Transactional(rollbackFor = Exception.class) public BusinessRemoveResultVO removeLoadingManage(String ids) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java index f944cb1..9f0259d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java @@ -19,6 +19,7 @@ import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.WebUtil; +import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.system.cache.DictCache; import org.springblade.system.cache.DictBizCache; import org.springblade.system.cache.UserCache; @@ -55,6 +56,7 @@ import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -184,20 +186,36 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl> rows = Func.isEmpty(request.getRows()) ? List.of() : request.getRows(); + // 逐行构建运单(未落库),构建失败时报出对应行号 + List rowWaybills = new ArrayList<>(); + for (int index = 0; index < rows.size(); index++) { + try { + rowWaybills.add(buildWaybill(rows.get(index), batch, request.getCarrierContractId(), draft)); + } catch (Exception exception) { + throw new ServiceException("第" + (index + 1) + "行" + (draft ? "保存" : "导入") + "失败:" + exception.getMessage()); + } + } + // 同一运单标识号的多货物行合并为一条运单,各行转为货物明细 + Map waybillByRow = new HashMap<>(); + Set skipRows = new HashSet<>(); + mergeMultiCargoRows(rows, rowWaybills, waybillByRow, skipRows); + List waybills = new ArrayList<>(); Map> loadingWaybills = new TreeMap<>(); for (int index = 0; index < rows.size(); index++) { + if (skipRows.contains(index)) continue; + Waybill waybill = waybillByRow.getOrDefault(index, rowWaybills.get(index)); + // 配载标识号必须取自行数据:运单落库(prepareForSave)会把新建运单的 loadingNo 置空 + String loadingIdentifier = stringValue(rows.get(index), "loadingIdentifier", "配载标识号"); try { - String loadingIdentifier = stringValue(rows.get(index), "loadingIdentifier", "配载标识号"); - Waybill waybill = buildWaybill(rows.get(index), batch, request.getCarrierContractId(), draft); if (draft) waybillService.saveDraft(waybill); else waybillService.submit(waybill); - waybills.add(waybill); - if (!draft && IMPORT_TYPE_WAYBILL.equals(batch.getImportType()) && Func.isNotEmpty(loadingIdentifier)) { - loadingWaybills.computeIfAbsent(loadingIdentifier, key -> new ArrayList<>()).add(waybill); - } } catch (Exception exception) { throw new ServiceException("第" + (index + 1) + "行" + (draft ? "保存" : "导入") + "失败:" + exception.getMessage()); } + waybills.add(waybill); + if (!draft && IMPORT_TYPE_WAYBILL.equals(batch.getImportType()) && Func.isNotEmpty(loadingIdentifier)) { + loadingWaybills.computeIfAbsent(loadingIdentifier, key -> new ArrayList<>()).add(waybill); + } } loadingWaybills.forEach(loadingManageService::createFromImportedWaybills); batch.setWaybillCount(waybills.size()); @@ -218,6 +236,69 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl> rows, List rowWaybills, + Map waybillByRow, Set skipRows) { + Map> groups = new LinkedHashMap<>(); + for (int index = 0; index < rowWaybills.size(); index++) { + String relationNo = rowWaybills.get(index).getRelationNo(); + if (Func.isEmpty(relationNo)) continue; + groups.computeIfAbsent(relationNo, key -> new ArrayList<>()).add(index); + } + for (List groupRows : groups.values()) { + if (groupRows.size() <= 1) continue; + Waybill primary = rowWaybills.get(groupRows.get(0)); + List> goodsList = new ArrayList<>(); + for (Integer rowIndex : groupRows) { + Waybill current = rowWaybills.get(rowIndex); + if (!rowIndex.equals(groupRows.get(0))) { + // 主行字段保留首行值,数量等数值字段累加同组其余各行 + mergeNumericFields(primary, current); + skipRows.add(rowIndex); + waybillByRow.remove(rowIndex); + } + goodsList.add(buildGoodsItem(rows.get(rowIndex), current)); + } + primary.setGoodsJson(JsonUtil.toJson(goodsList)); + } + } + + /** 合并多货物行时累加数量、其他费用等可加数值字段;为空的字段跳过。 */ + private void mergeNumericFields(Waybill primary, Waybill current) { + primary.setQuantity(sumNullable(primary.getQuantity(), current.getQuantity())); + primary.setOtherFeeTotal(sumNullable(primary.getOtherFeeTotal(), current.getOtherFeeTotal())); + } + + private BigDecimal sumNullable(BigDecimal first, BigDecimal second) { + if (first == null) return second; + if (second == null) return first; + return first.add(second); + } + + /** 由导入行构建一条货物明细,字段与运单详情货物编辑结构保持一致。 */ + private Map buildGoodsItem(Map row, Waybill waybill) { + Map goods = new LinkedHashMap<>(); + putIfNotEmpty(goods, "cargoName", waybill.getCargoName()); + putIfNotEmpty(goods, "cargoType", waybill.getCargoType()); + putIfNotEmpty(goods, "specification", waybill.getSpecification()); + putIfNotEmpty(goods, "model", waybill.getModel()); + putIfNotEmpty(goods, "packageType", stringValue(row, "packageType", "包装")); + if (waybill.getQuantity() != null) { + goods.put("quantity", waybill.getQuantity().toPlainString()); + } + putIfNotEmpty(goods, "quantityUnit", waybill.getQuantityUnit()); + putIfNotEmpty(goods, "remark", waybill.getRemark()); + return goods; + } + + private void putIfNotEmpty(Map goods, String key, String value) { + if (Func.isNotEmpty(value)) goods.put(key, value); + } + @Override public IPage page(IPage page, WaybillImportBatchRequest request) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() @@ -523,6 +604,9 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl row, int rowIndex, + Map> waybillIdentifierMap, List> allRows, List errors) { + String waybillIdentifier = stringValue(row, "waybillIdentifier", "同一运单标识号"); + if (Func.isEmpty(waybillIdentifier)) { + return; + } + List sameIdentifierRows = waybillIdentifierMap.get(waybillIdentifier); + if (sameIdentifierRows == null || sameIdentifierRows.size() <= 1) { + return; + } + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "departureAddress", "发货地址", errors); + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "arrivalAddress", "到货地址", errors); + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "quantityUnit", "数量单位", errors); + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "loadingIdentifier", "配载标识号", errors); + } + + /** 比较同一运单标识号组内其余行的字段值,不一致时记录错误;空值不参与比较。 */ + private void compareGroupValue(int rowIndex, List sameIdentifierRows, List> allRows, + String field, String fieldName, List errors) { + String currentValue = normalizeCompareValue(stringValue(allRows.get(rowIndex), field, fieldName)); + if (Func.isEmpty(currentValue)) { + return; + } + for (Integer otherRowIndex : sameIdentifierRows) { + if (otherRowIndex <= rowIndex) { + continue; + } + String otherValue = normalizeCompareValue(stringValue(allRows.get(otherRowIndex), field, fieldName)); + if (Func.isNotEmpty(otherValue) && !currentValue.equals(otherValue)) { + errors.add("同一运单标识号下," + fieldName + "必须一致"); + return; + } + } + } + + private String normalizeCompareValue(String value) { + return Func.isEmpty(value) ? null : value.replaceAll("\\s+", ""); + } + private LocalDate parseDateForValidation(Object value, String fieldName, List errors) { if (value == null || String.valueOf(value).isBlank()) { return null;