✨ 完善配载草稿确认与运单数量/里程落库

配载暂存免校验且确认生成待执行正式单,草稿仅预占运单不改其状态;里程 -1 对外置空;多货物运单按 goodsJson 合计回写数量并用于应收应付。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-23 18:35:28 +08:00
parent 29b846017b
commit f0732765cf
4 changed files with 90 additions and 4 deletions
@@ -109,7 +109,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
@Transactional(rollbackFor = Exception.class)
public boolean saveDraft(LoadingManage loadingManage) {
prepare(loadingManage);
validate(loadingManage, true);
// 暂存不做任何业务校验,允许空运单、缺字段直接落草稿
List<Long> oldWaybillIds = new ArrayList<>();
// 新建暂存为草稿;编辑已有单据时保持原业务状态(如待执行),避免被改回草稿
String businessStatus = STATUS_DRAFT;
@@ -128,10 +128,14 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
if (Func.isEmpty(loadingManage.getLoadingNo())) {
loadingManage.setLoadingNo(nextCode());
}
validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson()));
validateCarrierContract(loadingManage, false);
boolean result = saveOrUpdate(loadingManage);
syncAssociatedWaybills(loadingManage, businessStatus, oldWaybillIds);
// 草稿仅预占运单号,不把运单业务状态改成 draft;正式确认时再完整同步为待执行
if (STATUS_DRAFT.equals(businessStatus)) {
syncDraftWaybillReservation(loadingManage, oldWaybillIds);
} else {
syncAssociatedWaybills(loadingManage, businessStatus, oldWaybillIds);
}
return result;
}
@@ -149,6 +153,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
loadingManage.setDeptName(oldRecord.getDeptName());
}
prepareCreateOrUpdate(loadingManage);
// 确认提交:草稿/新建统一生成正式配载单(待执行)
loadingManage.setBusinessStatus(STATUS_PENDING);
if (Func.isEmpty(loadingManage.getLoadingNo())) {
loadingManage.setLoadingNo(nextCode());
@@ -616,6 +621,11 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
loadingManage.setRouteJson(TransportBusinessSupport.trimToNull(loadingManage.getRouteJson()));
loadingManage.setTaskInfoJson(TransportBusinessSupport.trimToNull(loadingManage.getTaskInfoJson()));
loadingManage.setDeptName(TransportBusinessSupport.trimToNull(loadingManage.getDeptName()));
// 未填写里程禁止落库 -1 占位值,统一存 null
if (loadingManage.getMileage() != null
&& loadingManage.getMileage().compareTo(BigDecimal.valueOf(-1)) == 0) {
loadingManage.setMileage(null);
}
if (Func.isEmpty(loadingManage.getDeptId())) {
Dept dept = TransportBusinessSupport.currentDept("配载管理");
loadingManage.setDeptId(dept.getId());
@@ -813,6 +823,25 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
.set(Waybill::getCurrentProcessNode, loadingManage.getCurrentProcessNode()));
}
/**
* 草稿阶段仅预占运单(写入 loadingNo),不改写运单业务状态。
* 避免暂存把运单打成 draft,导致确认生成正式配载单时状态异常。
*/
private void syncDraftWaybillReservation(LoadingManage loadingManage, List<Long> oldWaybillIds) {
List<Long> newWaybillIds = waybillIds(loadingManage.getWaybillIdsJson());
if (Func.isNotEmpty(oldWaybillIds)) {
List<Long> removedIds = new ArrayList<>(oldWaybillIds);
removedIds.removeAll(newWaybillIds);
releaseAssociatedWaybills(removedIds, STATUS_PENDING);
}
if (Func.isEmpty(newWaybillIds)) {
return;
}
waybillMapper.update(null, Wrappers.<Waybill>lambdaUpdate()
.in(Waybill::getId, newWaybillIds)
.set(Waybill::getLoadingNo, loadingManage.getLoadingNo()));
}
private void releaseAssociatedWaybills(List<Long> waybillIds, String status) {
if (Func.isEmpty(waybillIds)) {
return;
@@ -1463,7 +1463,7 @@ public class ReceivablePayableDetailServiceImpl
detail.setTransportType(waybill.getTransportType());
detail.setCargoName(waybill.getCargoName());
detail.setCargoType(waybill.getCargoType());
detail.setTransportQuantity(waybill.getQuantity());
detail.setTransportQuantity(resolveWaybillTransportQuantity(waybill));
detail.setQuantityUnit(waybill.getQuantityUnit());
detail.setMileage(normalizeGeneratedMileage(waybill.getMileage()));
detail.setBatchNo(waybill.getBatchNo());
@@ -1477,6 +1477,21 @@ public class ReceivablePayableDetailServiceImpl
return detail;
}
/**
* 多货物运单优先按 goodsJson 数量合计作为运输总量,避免主表 quantity 只落了首行。
*/
private BigDecimal resolveWaybillTransportQuantity(Waybill waybill) {
List<Map<String, Object>> goods = parseList(waybill.getGoodsJson());
if (goods.size() > 1) {
BigDecimal total = goods.stream().map(item -> decimal(item.get("quantity")))
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (total.signum() > 0) {
return total;
}
}
return money(waybill.getQuantity());
}
private ReceivablePayableDetail buildMasterOrderDetail(MasterOrder masterOrder, ContractManage contract,
List<Waybill> masterGoods, String settlementType,
List<ReceivablePayableCargoFee> fees, BigDecimal unitPrice) {
@@ -1358,6 +1358,8 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
if (isSentinelMinusOne(waybill.getQuantity())) waybill.setQuantity(null);
if (isSentinelMinusOne(waybill.getMileage())) waybill.setMileage(null);
if (isSentinelMinusOne(waybill.getUnitPrice())) waybill.setUnitPrice(null);
if (isSentinelMinusOne(waybill.getOtherFeeTotal())) waybill.setOtherFeeTotal(null);
syncQuantityFromGoodsJson(waybill);
waybill.setWaybillNo(TransportBusinessSupport.trimToNull(waybill.getWaybillNo()));
waybill.setProjectName(TransportBusinessSupport.trimToNull(waybill.getProjectName()));
waybill.setContractName(TransportBusinessSupport.trimToNull(waybill.getContractName()));
@@ -1425,6 +1427,40 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
return value != null && value.compareTo(BigDecimal.ONE.negate()) == 0;
}
/**
* 完整录入多货物时,运单主表 quantity 按 goodsJson 数量合计回写,避免只落首行数量。
*/
private void syncQuantityFromGoodsJson(Waybill waybill) {
if (waybill == null || Func.isEmpty(waybill.getGoodsJson())) {
return;
}
List<Map> goodsList;
try {
goodsList = JsonUtil.parseArray(waybill.getGoodsJson(), Map.class);
} catch (Exception exception) {
return;
}
if (Func.isEmpty(goodsList) || goodsList.size() <= 1) {
return;
}
BigDecimal total = BigDecimal.ZERO;
boolean hasQuantity = false;
for (Map goods : goodsList) {
if (goods == null || goods.get("quantity") == null) {
continue;
}
BigDecimal quantity = new BigDecimal(String.valueOf(goods.get("quantity")).trim());
if (isSentinelMinusOne(quantity)) {
continue;
}
hasQuantity = true;
total = total.add(quantity);
}
if (hasQuantity) {
waybill.setQuantity(total);
}
}
private void validate(Waybill waybill) {
TransportBusinessSupport.validateRequired(waybill.getProjectName(), "项目不能为空");
TransportBusinessSupport.validateRequired(waybill.getContractName(), "客户合同不能为空");
@@ -12,6 +12,7 @@ import org.springblade.system.cache.UserCache;
import org.springblade.transport.pojo.entity.LoadingManage;
import org.springblade.transport.pojo.vo.LoadingManageVO;
import java.math.BigDecimal;
import java.util.Objects;
/**
@@ -33,6 +34,11 @@ public class LoadingManageWrapper extends BaseEntityWrapper<LoadingManage, Loadi
Long currentDeptId = Func.firstLong(AuthUtil.getDeptId());
loadingManageVO.setReadonly(currentDeptId != null && !Objects.equals(loadingManage.getDeptId(), currentDeptId));
loadingManageVO.setBusinessStatusName(businessStatusName(loadingManage.getBusinessStatus()));
// 未维护里程的占位值 -1 对外展示为空
if (loadingManageVO.getMileage() != null
&& loadingManageVO.getMileage().compareTo(BigDecimal.valueOf(-1)) == 0) {
loadingManageVO.setMileage(null);
}
return loadingManageVO;
}