diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CargoLineVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CargoLineVO.java
new file mode 100644
index 00000000..831e6313
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CargoLineVO.java
@@ -0,0 +1,48 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 货物明细行:一条货物一行
+ *
+ * 详情页逐行展示「货物名称 + 数量单位」,名称与数量分开给,前端不需要再从汇总串里拆。
+ */
+@Data
+@Schema(description = "货物明细行")
+public class CargoLineVO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "货物名称")
+ private String cargoName;
+
+ @Schema(description = "数量(含单位,如 1000吨);录入时只填了名称则为空")
+ private String quantity;
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java
index cf2c7652..23432c6a 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java
@@ -67,6 +67,9 @@ public class DriverWaybillCardVO implements Serializable {
@Schema(description = "货物类别")
private String cargoCategory;
+ @Schema(description = "货物明细(详情返回):一条一行,同名同单位的货物已合并数量")
+ private List cargos;
+
@Schema(description = "重量(带单位)")
private String weight;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripVO.java
index e3c388a2..a6523dfb 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TripVO.java
@@ -129,12 +129,21 @@ public class TripVO implements Serializable {
@Schema(description = "货物汇总:按货物名合并数量,同名同单位取合计")
private String cargoSummary;
+ @Schema(description = "货物明细:一条货物一行,同名同单位的货物已合并数量")
+ private List cargos;
+
@Schema(description = "合计重量:全部子运单同单位时为数量合计,单位不一致时为空")
private BigDecimal totalQuantity;
@Schema(description = "合计重量的单位,与 totalQuantity 同时为空或同时有值")
private String quantityUnit;
+ @Schema(description = "计重合计:全部货物同单位时取该单位合计,单位不一致时只合计「吨」项(其余单位不参与);无可用合计时为空")
+ private BigDecimal weighingQuantity;
+
+ @Schema(description = "计重合计的单位,与 weighingQuantity 同时为空或同时有值")
+ private String weighingUnit;
+
@Schema(description = "计划发货日期(配载单,未配载为空)")
private LocalDate estimatedStartDate;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java
index c7331afd..e132f332 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java
@@ -58,6 +58,7 @@ import org.springblade.transport.pojo.vo.DriverEnrouteRecordVO;
import org.springblade.transport.pojo.vo.DriverNodePunchVO;
import org.springblade.transport.pojo.vo.DriverPunchNodeVO;
import org.springblade.transport.pojo.vo.DriverPunchPhotoVO;
+import org.springblade.transport.pojo.vo.CargoLineVO;
import org.springblade.transport.pojo.vo.DriverWaybillCardVO;
import org.springblade.transport.pojo.vo.DriverWaybillPreviewVO;
import org.springblade.transport.pojo.vo.DriverWaybillTabCountsVO;
@@ -981,7 +982,7 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
return toCard(waybill, false);
}
- private DriverWaybillCardVO toCard(Waybill waybill, boolean withEnrouteRecords) {
+ private DriverWaybillCardVO toCard(Waybill waybill, boolean withDetail) {
DriverWaybillCardVO card = new DriverWaybillCardVO();
card.setId(waybill.getId());
card.setWaybillNo(waybill.getWaybillNo());
@@ -1000,7 +1001,9 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
.filter(Func::isNotEmpty)
.distinct()
.collect(Collectors.joining("、")));
- card.setWeight(formatCargoWeight(cargos, formatWeight(waybill.getQuantity(), waybill.getQuantityUnit())));
+ // 计重合计:全部货物同单位时就是该单位合计,单位不一致时只有吨项参与(口径见 WaybillCargoSupport#weighing)
+ WaybillCargoSupport.Quantity weighing = WaybillCargoSupport.weighing(cargos);
+ card.setWeight(weighing == null ? "" : formatWeight(weighing.value(), weighing.unit()));
card.setStatus(toAppStatus(waybill.getBusinessStatus()));
String createTime = formatDateTime(waybill.getCreateTime());
card.setCreateTime(createTime);
@@ -1024,13 +1027,14 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
card.setUnloadAddress(card.getToAddress());
card.setCargoQuantity(weightText);
card.setTotalWeight(weightText);
+ card.setCargos(withDetail ? cargoLines(cargos) : null);
card.setTransportType(toTransportTypeLabel(waybill.getTransportType()));
card.setTransportOrgType(Func.isNotBlank(waybill.getLoadingNo()) ? TRANSPORT_LOAD : TRANSPORT_COMMON);
card.setPlanShipTime(formatLocalDateYmd(waybill.getEstimatedStartTime()));
card.setPlanFinishTime(formatLocalDateYmd(waybill.getEstimatedEndTime()));
card.setRemark(Func.toStr(waybill.getRemark(), ""));
- if (withEnrouteRecords) {
+ if (withDetail) {
Date lastPunchAt = findLastPunchTime(waybill.getId());
WaybillProcessSupport.TransitCheckinDecision transit = WaybillProcessSupport.evaluateTransitCheckin(
processJson, waybill.getBusinessStatus(), lastPunchAt, LocalDateTime.now());
@@ -1152,17 +1156,18 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
}
card.setCargoCategory(String.join("、", cargoTypes));
card.setCargoSummary(Func.toStr(trip.getCargoSummary(), ""));
- // 合计重量只在全部货物同单位时才有值;单位不一致时留空,卡片上已经由货物汇总逐条列出,
- // 拿汇总串冒充重量会让「货名 + 重量」两栏把同一段文字打印两遍
- String weight = trip.getTotalQuantity() == null
+ // 计重合计:同单位时就是该单位合计,单位不一致时只有吨项参与(见 TripQueryServiceImpl#fillWeighing);
+ // 都没有就留空,卡片上只剩货名,不拿汇总串冒充重量
+ String weight = trip.getWeighingQuantity() == null
? ""
- : formatWeight(trip.getTotalQuantity(), trip.getQuantityUnit());
+ : formatWeight(trip.getWeighingQuantity(), trip.getWeighingUnit());
card.setWeight(weight);
card.setCargoQuantity(weight);
card.setTotalWeight(weight);
if (withDetail) {
card.setStations(trip.getStations());
card.setRoutePoints(toTripRoutePoints(trip.getStations()));
+ card.setCargos(trip.getCargos());
}
}
@@ -2008,31 +2013,17 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
}
/**
- * 重量文案:全部货物同单位时按货物行相加,单位不一致时不出合计
- *
- * 主表 quantity 是各行数量的合计、quantity_unit 只是首行的单位,「20堆 + 50方」会变成「70堆」。
- * 拿 goodsJson 算不出合计(有货物没填数量或单位)时,退回主表那份汇总值——宁可显示一个来源不明的数,
- * 也不要让卡片上的重量凭空消失。
+ * 货物明细行:一条货物一行,数量带单位
*/
- private String formatCargoWeight(List cargos, String fallback) {
- if (cargos.isEmpty()) {
- return fallback;
+ private List cargoLines(List cargos) {
+ List lines = new ArrayList<>();
+ for (WaybillCargoSupport.Line line : WaybillCargoSupport.lines(cargos)) {
+ CargoLineVO vo = new CargoLineVO();
+ vo.setCargoName(line.name());
+ vo.setQuantity(line.quantity() == null ? "" : formatWeight(line.quantity(), line.unit()));
+ lines.add(vo);
}
- String unit = null;
- BigDecimal total = BigDecimal.ZERO;
- for (WaybillCargoSupport.Cargo cargo : cargos) {
- String cargoUnit = Func.toStr(cargo.quantityUnit(), "");
- if (cargo.quantity() == null || Func.isEmpty(cargoUnit)) {
- return fallback;
- }
- if (unit == null) {
- unit = cargoUnit;
- } else if (!unit.equals(cargoUnit)) {
- return "";
- }
- total = total.add(cargo.quantity());
- }
- return formatWeight(total, unit);
+ return lines;
}
private String formatWeight(BigDecimal quantity, String unit) {
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java
index 8a831a4e..3a3de257 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TripQueryServiceImpl.java
@@ -31,6 +31,7 @@ import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.utils.Func;
import org.springblade.transport.pojo.entity.LoadingManage;
import org.springblade.transport.pojo.entity.Waybill;
+import org.springblade.transport.pojo.vo.CargoLineVO;
import org.springblade.transport.pojo.vo.TripQueryVO;
import org.springblade.transport.pojo.vo.TripStationVO;
import org.springblade.transport.pojo.vo.TripTabCountsVO;
@@ -610,7 +611,9 @@ public class TripQueryServiceImpl implements ITripQueryService {
trip.setCargoType(String.join("、", cargoTypes));
}
trip.setCargoSummary(cargoSummary(children));
+ trip.setCargos(cargoLines(cargos));
fillTotalQuantity(trip, children);
+ fillWeighing(trip, cargos);
}
/**
@@ -639,6 +642,20 @@ public class TripQueryServiceImpl implements ITripQueryService {
return String.join("、", parts);
}
+ /**
+ * 货物明细行:与 cargoSummary 同一套合并口径,但数量与单位分开留,供详情页逐行展示
+ */
+ private List cargoLines(List cargos) {
+ List lines = new ArrayList<>();
+ for (WaybillCargoSupport.Line line : WaybillCargoSupport.lines(cargos)) {
+ CargoLineVO vo = new CargoLineVO();
+ vo.setCargoName(line.name());
+ vo.setQuantity(line.quantity() == null ? "" : formatQuantity(line.quantity(), line.unit()));
+ lines.add(vo);
+ }
+ return lines;
+ }
+
/**
* 合计重量:只有全部货物同单位时才能相加,单位不一致时留空(汇总文案里仍分单位列出)
*
@@ -671,6 +688,18 @@ public class TripQueryServiceImpl implements ITripQueryService {
trip.setQuantityUnit(unit);
}
+ /**
+ * 计重合计:卡片上那个「货物重量」,口径见 WaybillCargoSupport#weighing
+ */
+ private void fillWeighing(TripVO trip, List cargos) {
+ WaybillCargoSupport.Quantity weighing = WaybillCargoSupport.weighing(cargos);
+ if (weighing == null) {
+ return;
+ }
+ trip.setWeighingQuantity(weighing.value());
+ trip.setWeighingUnit(weighing.unit());
+ }
+
private String formatQuantity(BigDecimal quantity, String unit) {
String value = quantity.stripTrailingZeros().toPlainString();
return Func.isEmpty(unit) ? value : value + unit;
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillCargoSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillCargoSupport.java
index afe71bc6..c486b558 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillCargoSupport.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/WaybillCargoSupport.java
@@ -28,8 +28,11 @@ import org.springblade.transport.pojo.entity.Waybill;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* 运单货物明细的取法,只此一处。
@@ -42,6 +45,9 @@ import java.util.Map;
*/
public final class WaybillCargoSupport {
+ /** 计重时唯一允许跨货物相加的单位:其他单位只在「全部货物同单位」时才合计 */
+ private static final String UNIT_TON = "吨";
+
/** 货物没有填名称时的展示名:宁可让司机看到「货物」,也不要出现空行 */
private static final String FALLBACK_NAME = "货物";
@@ -146,6 +152,83 @@ public final class WaybillCargoSupport {
return cargos;
}
+ /**
+ * 计重合计:卡片上那个「货物重量」
+ *
+ * 全部货物同单位时就是该单位合计;单位不一致时只合计「吨」项,其余单位不参与——
+ * 「100吨 + 20堆」报 100 吨,「20堆 + 50方」没有吨项就不出合计。
+ * 有货物缺数量时该单位合计不完整,宁可不报也不报一个偏小的数。
+ *
+ * @return 有可用合计时返回数量与单位,否则 null
+ */
+ public static Quantity weighing(List cargos) {
+ Map totals = new LinkedHashMap<>();
+ Set incomplete = new HashSet<>();
+ for (Cargo cargo : cargos) {
+ String unit = Func.toStr(cargo.quantityUnit(), "");
+ if (Func.isEmpty(unit)) {
+ continue;
+ }
+ if (cargo.quantity() == null) {
+ incomplete.add(unit);
+ continue;
+ }
+ totals.merge(unit, cargo.quantity(), BigDecimal::add);
+ }
+ if (totals.isEmpty()) {
+ return null;
+ }
+ String unit;
+ if (totals.size() == 1) {
+ unit = totals.keySet().iterator().next();
+ } else {
+ unit = totals.containsKey(UNIT_TON) ? UNIT_TON : null;
+ }
+ if (unit == null || incomplete.contains(unit)) {
+ return null;
+ }
+ return new Quantity(totals.get(unit), unit);
+ }
+
+ /** 计重合计的数量与单位 */
+ public record Quantity(BigDecimal value, String unit) {
+ }
+
+ /**
+ * 货物明细行:一条货物一行,同名同单位的合并数量,按录入顺序
+ *
+ * 与 {@link #cargoSummary(List)} 的合并口径一致,但数量与单位保持分开,供详情页逐行展示。
+ * 数量为空的货物只留名称,不按 0 参与合并。
+ */
+ public static List lines(List cargos) {
+ Map index = new LinkedHashMap<>();
+ List lines = new ArrayList<>();
+ for (Cargo cargo : cargos) {
+ String name = cargo.displayName();
+ String unit = Func.toStr(cargo.quantityUnit(), "");
+ String key = name + '\u0000' + unit;
+ Integer at = index.get(key);
+ if (at == null) {
+ index.put(key, lines.size());
+ lines.add(new Line(name, cargo.quantity(), unit));
+ continue;
+ }
+ Line merged = lines.get(at);
+ if (cargo.quantity() == null) {
+ continue;
+ }
+ BigDecimal quantity = merged.quantity() == null
+ ? cargo.quantity()
+ : merged.quantity().add(cargo.quantity());
+ lines.set(at, new Line(name, quantity, unit));
+ }
+ return lines;
+ }
+
+ /** 一条货物明细:数量可能为空(录入时只填了名称) */
+ public record Line(String name, BigDecimal quantity, String unit) {
+ }
+
/**
* 主表货名按顿号、逗号摊平;空格与空段去掉,全空时给空集合
*/
diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java
index 966ac370..7bdcbba1 100644
--- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/DriverTripCardTest.java
@@ -383,6 +383,8 @@ class DriverTripCardTest {
trip.setCargoSummary("石英砂A 131.84吨、技嘉4070显卡 30吨");
trip.setTotalQuantity(new BigDecimal("161.84"));
trip.setQuantityUnit("吨");
+ trip.setWeighingQuantity(new BigDecimal("161.84"));
+ trip.setWeighingUnit("吨");
trip.setDriverName("黄宇富");
trip.setDriverPhone(PHONE);
trip.setVehicleNo("桂A32523");
@@ -410,6 +412,8 @@ class DriverTripCardTest {
trip.setCargoSummary("石灰石 30吨");
trip.setTotalQuantity(new BigDecimal("30"));
trip.setQuantityUnit("吨");
+ trip.setWeighingQuantity(new BigDecimal("30"));
+ trip.setWeighingUnit("吨");
trip.setDriverName("黄宇富");
trip.setDriverPhone(PHONE);
trip.setVehicleNo("桂A32523");
diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java
index b4aac8d2..ec37714d 100644
--- a/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java
+++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/service/impl/TripReadServiceBoundaryTest.java
@@ -309,6 +309,41 @@ class TripReadServiceBoundaryTest {
assertNull(trip.getQuantityUnit());
}
+ @Test
+ @DisplayName("计重合计:单位不一致时只合计吨项,堆方不参与;全是吨时就是吨合计")
+ void weighingOnlyCountsTonsWhenUnitsDiffer() {
+ Waybill mixed = child(CHILD_A, "running", null, ACCEPT_NO, at(1787657033000L));
+ mixed.setCargoName("阿什顿");
+ mixed.setQuantity(new BigDecimal("100"));
+ mixed.setQuantityUnit("吨");
+ mixed.setGoodsJson("[{\"cargoName\":\"阿什顿\",\"quantity\":\"100\",\"quantityUnit\":\"吨\"},"
+ + "{\"cargoName\":\"显卡050\",\"quantity\":\"20\",\"quantityUnit\":\"堆\"}]");
+ stubLoadedTrip(mixed);
+
+ TripVO trip = onlyTrip();
+
+ assertNull(trip.getTotalQuantity(), "单位不一致,合计重量仍然为空");
+ assertEquals(0, new BigDecimal("100").compareTo(trip.getWeighingQuantity()), "计重合计只算吨项");
+ assertEquals("吨", trip.getWeighingUnit());
+ }
+
+ @Test
+ @DisplayName("计重合计:没有吨项且单位不一致时不出合计,合计重量同样为空")
+ void weighingEmptyWhenNoTonAndUnitsDiffer() {
+ Waybill mixed = child(CHILD_A, "running", null, ACCEPT_NO, at(1787657033000L));
+ mixed.setCargoName("阿什顿");
+ mixed.setQuantity(new BigDecimal("70.00"));
+ mixed.setQuantityUnit("堆");
+ mixed.setGoodsJson("[{\"cargoName\":\"阿什顿\",\"quantity\":\"20\",\"quantityUnit\":\"堆\"},"
+ + "{\"cargoName\":\"显卡050\",\"quantity\":\"50\",\"quantityUnit\":\"方\"}]");
+ stubLoadedTrip(mixed);
+
+ TripVO trip = onlyTrip();
+
+ assertNull(trip.getWeighingQuantity(), "没有吨项就不出计重合计");
+ assertNull(trip.getWeighingUnit());
+ }
+
// ------------------------------------------------------------ 夹具
@SuppressWarnings("unchecked")