✨ 运次级下发计重合计与货物明细行
This commit is contained in:
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 货物明细行:一条货物一行
|
||||
* <p>
|
||||
* 详情页逐行展示「货物名称 + 数量单位」,名称与数量分开给,前端不需要再从汇总串里拆。
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
+3
@@ -67,6 +67,9 @@ public class DriverWaybillCardVO implements Serializable {
|
||||
@Schema(description = "货物类别")
|
||||
private String cargoCategory;
|
||||
|
||||
@Schema(description = "货物明细(详情返回):一条一行,同名同单位的货物已合并数量")
|
||||
private List<CargoLineVO> cargos;
|
||||
|
||||
@Schema(description = "重量(带单位)")
|
||||
private String weight;
|
||||
|
||||
|
||||
+9
@@ -129,12 +129,21 @@ public class TripVO implements Serializable {
|
||||
@Schema(description = "货物汇总:按货物名合并数量,同名同单位取合计")
|
||||
private String cargoSummary;
|
||||
|
||||
@Schema(description = "货物明细:一条货物一行,同名同单位的货物已合并数量")
|
||||
private List<CargoLineVO> 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;
|
||||
|
||||
|
||||
+21
-30
@@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 重量文案:全部货物同单位时按货物行相加,单位不一致时不出合计
|
||||
* <p>
|
||||
* 主表 quantity 是各行数量的合计、quantity_unit 只是首行的单位,「20堆 + 50方」会变成「70堆」。
|
||||
* 拿 goodsJson 算不出合计(有货物没填数量或单位)时,退回主表那份汇总值——宁可显示一个来源不明的数,
|
||||
* 也不要让卡片上的重量凭空消失。
|
||||
* 货物明细行:一条货物一行,数量带单位
|
||||
*/
|
||||
private String formatCargoWeight(List<WaybillCargoSupport.Cargo> cargos, String fallback) {
|
||||
if (cargos.isEmpty()) {
|
||||
return fallback;
|
||||
private List<CargoLineVO> cargoLines(List<WaybillCargoSupport.Cargo> cargos) {
|
||||
List<CargoLineVO> 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) {
|
||||
|
||||
+29
@@ -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<CargoLineVO> cargoLines(List<WaybillCargoSupport.Cargo> cargos) {
|
||||
List<CargoLineVO> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合计重量:只有全部货物同单位时才能相加,单位不一致时留空(汇总文案里仍分单位列出)
|
||||
* <p>
|
||||
@@ -671,6 +688,18 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
trip.setQuantityUnit(unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计重合计:卡片上那个「货物重量」,口径见 WaybillCargoSupport#weighing
|
||||
*/
|
||||
private void fillWeighing(TripVO trip, List<WaybillCargoSupport.Cargo> 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;
|
||||
|
||||
+83
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计重合计:卡片上那个「货物重量」
|
||||
* <p>
|
||||
* 全部货物同单位时就是该单位合计;单位不一致时只合计「吨」项,其余单位不参与——
|
||||
* 「100吨 + 20堆」报 100 吨,「20堆 + 50方」没有吨项就不出合计。
|
||||
* 有货物缺数量时该单位合计不完整,宁可不报也不报一个偏小的数。
|
||||
*
|
||||
* @return 有可用合计时返回数量与单位,否则 null
|
||||
*/
|
||||
public static Quantity weighing(List<Cargo> cargos) {
|
||||
Map<String, BigDecimal> totals = new LinkedHashMap<>();
|
||||
Set<String> 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) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 货物明细行:一条货物一行,同名同单位的合并数量,按录入顺序
|
||||
* <p>
|
||||
* 与 {@link #cargoSummary(List)} 的合并口径一致,但数量与单位保持分开,供详情页逐行展示。
|
||||
* 数量为空的货物只留名称,不按 0 参与合并。
|
||||
*/
|
||||
public static List<Line> lines(List<Cargo> cargos) {
|
||||
Map<String, Integer> index = new LinkedHashMap<>();
|
||||
List<Line> 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) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 主表货名按顿号、逗号摊平;空格与空段去掉,全空时给空集合
|
||||
*/
|
||||
|
||||
+4
@@ -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");
|
||||
|
||||
+35
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user