From 142eee384b82c13a86b0e5841f74b01ed87d63ab Mon Sep 17 00:00:00 2001 From: weicw1996 Date: Tue, 20 Oct 2026 22:20:43 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E8=BF=90=E6=AC=A1=E7=BA=A7?= =?UTF-8?q?=E4=B8=8B=E5=8F=91=E8=AE=A1=E9=87=8D=E5=90=88=E8=AE=A1=E4=B8=8E?= =?UTF-8?q?=E8=B4=A7=E7=89=A9=E6=98=8E=E7=BB=86=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pojo/vo/CargoLineVO.java | 48 +++++++++++ .../pojo/vo/DriverWaybillCardVO.java | 3 + .../springblade/transport/pojo/vo/TripVO.java | 9 ++ .../impl/DriverWaybillServiceImpl.java | 51 +++++------- .../service/impl/TripQueryServiceImpl.java | 29 +++++++ .../support/WaybillCargoSupport.java | 83 +++++++++++++++++++ .../service/impl/DriverTripCardTest.java | 4 + .../impl/TripReadServiceBoundaryTest.java | 35 ++++++++ 8 files changed, 232 insertions(+), 30 deletions(-) create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CargoLineVO.java 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")