From 8beb542c9881afaafa316f9e83ab64a730214ede Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Thu, 20 Aug 2026 06:30:50 +0800 Subject: [PATCH] fix bug --- .../ReceivablePayableAdjustFeeRequest.java | 15 + .../ReceivablePayableDetailController.java | 62 ++- .../service/ILoadingManageService.java | 3 + .../IReceivablePayableDetailService.java | 6 + .../impl/LoadingManageServiceImpl.java | 45 ++- .../service/impl/MasterOrderServiceImpl.java | 11 +- .../impl/PreSettlementServiceImpl.java | 19 +- .../ReceivablePayableDetailServiceImpl.java | 373 +++++++++++++++++- .../impl/ShippingTemplateServiceImpl.java | 2 +- .../service/impl/WaybillServiceImpl.java | 54 ++- 10 files changed, 558 insertions(+), 32 deletions(-) diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ReceivablePayableAdjustFeeRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ReceivablePayableAdjustFeeRequest.java index e434b2a..e34efbc 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ReceivablePayableAdjustFeeRequest.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ReceivablePayableAdjustFeeRequest.java @@ -55,5 +55,20 @@ public class ReceivablePayableAdjustFeeRequest implements Serializable { @Schema(description = "动态费用项目") private Map feeItems; + + @Schema(description = "是否手工费用行") + private Boolean manualFee; + + @Schema(description = "手工费用项目名称") + private String feeItemName; + + @Schema(description = "手工费用类型:charge/deduct") + private String feeType; + + @Schema(description = "手工费用金额") + private BigDecimal amount; + + @Schema(description = "备注") + private String remark; } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java index c0bd427..f72c499 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java @@ -24,12 +24,12 @@ package org.springblade.transport.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import cn.idev.excel.FastExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import org.springblade.core.boot.ctrl.BladeController; -import org.springblade.core.excel.util.ExcelUtil; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; @@ -43,6 +43,7 @@ import org.springblade.transport.pojo.vo.ReceivablePayableChangeRecordVO; import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO; import org.springblade.transport.pojo.vo.ReceivablePayableFeeDetailVO; import org.springblade.transport.service.IReceivablePayableDetailService; +import org.springblade.system.cache.DictCache; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -51,7 +52,13 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; /** * 应收应付明细控制器 @@ -160,7 +167,56 @@ public class ReceivablePayableDetailController extends BladeController { @ApiOperationSupport(order = 10) @Operation(summary = "导出应收应付明细") public void exportReceivablePayableDetail(ReceivablePayableDetailVO query, HttpServletResponse response) { - IPage page = detailService.selectPage(Condition.getPage(new Query()), query); - ExcelUtil.export(response, "应收应付明细" + DateUtil.time(), "应收应付明细", page.getRecords(), ReceivablePayableDetailVO.class); + List records = detailService.selectList(query); + Set feeItemNames = new LinkedHashSet<>(); + records.forEach(row -> { + if (row.getFeeItems() != null) feeItemNames.addAll(row.getFeeItems().keySet()); + }); + + List> head = new ArrayList<>(); + String[] baseHeaders = {"单据号", "项目名称", "所属组织", "费用日期", "客商名称", "合同编号", "合同名称", "来源", + "预结算单号", "正式结算单号", "运单号", "车号", "运输类型", "货物名称", "货物类型", "运输总量", "里程(KM)", + "批次号", "运输单价"}; + for (String header : baseHeaders) head.add(List.of(header)); + feeItemNames.forEach(name -> head.add(List.of(name))); + for (String header : new String[] {"费用合计", "状态", "创建人", "创建时间"}) head.add(List.of(header)); + + List> rows = records.stream().map(row -> { + List values = new ArrayList<>(); + values.add(row.getDocumentNo()); values.add(row.getProjectName()); values.add(row.getDeptName()); values.add(row.getFeeDate()); + values.add(row.getCustomerName()); values.add(row.getContractNo()); values.add(row.getContractName()); values.add(row.getSourceType()); + values.add(row.getPreSettlementNo()); values.add(row.getFormalSettlementNo()); values.add(row.getWaybillNo()); values.add(row.getVehicleNo()); + values.add(transportTypeName(row.getTransportType())); values.add(row.getCargoName()); values.add(row.getCargoType()); + values.add(row.getTransportQuantity()); values.add(row.getMileage() != null && row.getMileage().compareTo(BigDecimal.valueOf(-1)) == 0 ? null : row.getMileage()); + values.add(row.getBatchNo()); values.add(money(row.getUnitPrice(), row.getCurrency())); + feeItemNames.forEach(name -> values.add(money(decimal(row.getFeeItems() == null ? null : row.getFeeItems().get(name)), row.getCurrency()))); + values.add(money(row.getTotalAmount(), row.getCurrency())); values.add(row.getSettlementStatusName()); values.add(row.getCreateUserName()); values.add(row.getCreateTime()); + return values; + }).toList(); + + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode("应收应付明细" + DateUtil.time(), StandardCharsets.UTF_8) + ".xlsx"); + try { + FastExcel.write(response.getOutputStream()).head(head).sheet("应收应付明细").doWrite(rows); + } catch (Exception exception) { + throw new IllegalStateException("导出应收应付明细失败", exception); + } + } + + private String transportTypeName(String value) { + if (value == null || value.isBlank()) return value; + String name = DictCache.getValue("transport_type", value); + return name == null || name.isBlank() ? value : name; + } + + private String money(BigDecimal value, String currency) { + if (value == null) return "-"; + return value.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString() + " " + (currency == null || currency.isBlank() ? "RMB" : currency); + } + + private BigDecimal decimal(Object value) { + if (value == null || String.valueOf(value).isBlank()) return null; + try { return new BigDecimal(String.valueOf(value)); } catch (NumberFormatException ignored) { return null; } } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java index 43bfaa4..50452e8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java @@ -42,6 +42,9 @@ public interface ILoadingManageService extends BaseService { boolean complete(Long id); + /** 运单完成后检查关联运单状态,全部完成时自动完成配载单。 */ + boolean completeIfAllWaybillsCompleted(String loadingNo); + BusinessRemoveResultVO batchComplete(String ids); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java index bb9aa65..1dc0ab8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java @@ -28,6 +28,7 @@ import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest; import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest; import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest; import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest; +import org.springblade.transport.pojo.entity.MasterOrder; import org.springblade.transport.pojo.entity.ReceivablePayableDetail; import org.springblade.transport.pojo.vo.ReceivablePayableChangeRecordVO; import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO; @@ -45,6 +46,8 @@ public interface IReceivablePayableDetailService extends BaseService selectPage(IPage page, ReceivablePayableDetailVO query); + List selectList(ReceivablePayableDetailVO query); + ReceivablePayableFeeDetailVO feeDetail(Long id); IPage changeRecords(IPage page, Long detailId); @@ -69,4 +72,7 @@ public interface IReceivablePayableDetailService extends BaseService waybillIds); + + /** 关闭总单调度后按合同系统计费模式自动生成总单应收、应付明细。 */ + void generateForClosedMasterOrder(MasterOrder masterOrder); } 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 09396f5..436e050 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 @@ -263,8 +263,8 @@ public class LoadingManageServiceImpl extends BaseServiceImpllambdaQuery() + .eq(LoadingManage::getLoadingNo, loadingNo) + .eq(LoadingManage::getIsDeleted, 0) + .last("FOR UPDATE"), false); + if (loadingManage == null + || Objects.equals(loadingManage.getBusinessStatus(), STATUS_COMPLETED) + || Objects.equals(loadingManage.getBusinessStatus(), STATUS_CANCELLED) + || Objects.equals(loadingManage.getBusinessStatus(), STATUS_DRAFT)) { + return false; + } + List waybillIdList = waybillIds(loadingManage.getWaybillIdsJson()); + if (Func.isEmpty(waybillIdList)) { + return false; + } + List waybillList = waybillMapper.selectList(Wrappers.lambdaQuery() + .eq(Waybill::getIsDeleted, 0) + .in(Waybill::getId, waybillIdList)); + boolean allCompleted = waybillList.size() == waybillIdList.size() + && waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_COMPLETED)); + if (!allCompleted) { + return false; + } + loadingManage.setBusinessStatus(STATUS_COMPLETED); + return updateById(loadingManage); + } + @Override @Transactional(rollbackFor = Exception.class) public BusinessRemoveResultVO batchComplete(String ids) { @@ -292,7 +324,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl waybillIdList = waybillIds(loadingManage.getWaybillIdsJson()); if (Func.isEmpty(waybillIdList)) { return false; @@ -300,8 +332,11 @@ public class LoadingManageServiceImpl extends BaseServiceImpl waybillList = waybillMapper.selectList(Wrappers.lambdaQuery() .eq(Waybill::getIsDeleted, 0) .in(Waybill::getId, waybillIdList)); - return waybillList.size() == waybillIdList.size() - && waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_RUNNING)); + if (waybillList.size() != waybillIdList.size()) { + return false; + } + return waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_RUNNING)) + || waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_COMPLETED)); } private LambdaQueryWrapper buildQuery(LoadingManageVO loadingManage) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java index 3a15e5e..8ec2c59 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java @@ -24,6 +24,7 @@ import org.springblade.transport.pojo.vo.MasterOrderVO; import org.springblade.transport.service.IMasterOrderService; import org.springblade.transport.service.IContractManageService; import org.springblade.transport.service.IProjectApplyService; +import org.springblade.transport.service.IReceivablePayableDetailService; import org.springblade.transport.service.ITransportPlanService; import org.springblade.transport.service.IWaybillService; import org.springblade.transport.support.TransportBusinessSupport; @@ -53,13 +54,15 @@ public class MasterOrderServiceImpl extends BaseServiceImpl existingSourceIds = existingDetails.stream().map(PreSettlementDetail::getSourceDetailId) .collect(Collectors.toSet()); List addedIds = distinctIds.stream().filter(id -> !existingSourceIds.contains(id)).toList(); + List addedSources = new ArrayList<>(); if (!addedIds.isEmpty()) { List sources = sourceDetailMapper.selectBatchIds(addedIds); if (sources.size() != addedIds.size()) { @@ -683,12 +684,20 @@ public class PreSettlementServiceImpl extends BaseServiceImpl detailLineMap = listDetails(settlement.getId()).stream() + .collect(Collectors.toMap(PreSettlementDetail::getSourceDetailId, PreSettlementDetail::getLineNo, + (left, right) -> left)); + for (ReceivablePayableDetail source : addedSources) { + saveChange(settlement.getId(), "结算明细项", detailLineMap.get(source.getId()), "新增", + "新增单据号" + source.getDocumentNo(), ""); + } + } } private void validateCandidate(PreSettlement settlement, ReceivablePayableDetail source, @@ -883,7 +892,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl implements IReceivablePayableDetailService { + private static final String SOURCE_MASTER_ORDER = "总单系统生成"; + private final ReceivablePayableCargoFeeMapper cargoFeeMapper; private final ReceivablePayableChangeRecordMapper changeRecordMapper; + private final MasterOrderMapper masterOrderMapper; private final IWaybillService waybillService; private final IContractManageService contractManageService; private final ICommonAddressService commonAddressService; @@ -101,6 +106,7 @@ public class ReceivablePayableDetailServiceImpl public ReceivablePayableDetailServiceImpl(ReceivablePayableCargoFeeMapper cargoFeeMapper, ReceivablePayableChangeRecordMapper changeRecordMapper, + MasterOrderMapper masterOrderMapper, IWaybillService waybillService, IContractManageService contractManageService, ICommonAddressService commonAddressService, @@ -108,6 +114,7 @@ public class ReceivablePayableDetailServiceImpl @Lazy IFormalSettlementService formalSettlementService) { this.cargoFeeMapper = cargoFeeMapper; this.changeRecordMapper = changeRecordMapper; + this.masterOrderMapper = masterOrderMapper; this.waybillService = waybillService; this.contractManageService = contractManageService; this.commonAddressService = commonAddressService; @@ -120,6 +127,11 @@ public class ReceivablePayableDetailServiceImpl return ReceivablePayableDetailWrapper.build().pageVO(page(page, buildQuery(query))); } + @Override + public List selectList(ReceivablePayableDetailVO query) { + return ReceivablePayableDetailWrapper.build().listVO(list(buildQuery(query))); + } + @Override public ReceivablePayableFeeDetailVO feeDetail(Long id) { ReceivablePayableDetail detail = getExisting(id); @@ -127,6 +139,10 @@ public class ReceivablePayableDetailServiceImpl .eq(ReceivablePayableCargoFee::getDetailId, detail.getId()) .eq(ReceivablePayableCargoFee::getIsDeleted, 0) .orderByAsc(ReceivablePayableCargoFee::getCreateTime)); + // 运输量的单位取明细对应运单单位,兼容历史费用行误存计费单位的情况。 + if (Func.isNotEmpty(detail.getQuantityUnit())) { + rows.forEach(row -> row.setQuantityUnit(detail.getQuantityUnit())); + } ReceivablePayableFeeDetailVO result = buildFeeDetail(rows); LinkedHashSet feeItemNames = new LinkedHashSet<>(contractFeeItemNames(detail.getContractId())); feeItemNames.addAll(result.getFeeItemNames()); @@ -157,6 +173,9 @@ public class ReceivablePayableDetailServiceImpl if (Func.isEmpty(request.getContractId())) { throw new ServiceException("请选择需要更新费用的合同"); } + if (Func.isEmpty(request.getBillingPlanId())) { + throw new ServiceException("请选择需要更新的合同计费方案"); + } List details = list(buildUpdateQuery(request)); if (Func.isEmpty(details)) { throw new ServiceException("没有可更新的待结算明细"); @@ -217,14 +236,65 @@ public class ReceivablePayableDetailServiceImpl .eq(ReceivablePayableCargoFee::getIsDeleted, 0)); Map existingMap = existingRows.stream() .collect(java.util.stream.Collectors.toMap(ReceivablePayableCargoFee::getId, row -> row)); - if (request.getRows().size() != existingRows.size()) { + Set submittedExistingIds = request.getRows().stream() + .map(ReceivablePayableAdjustFeeRequest.AdjustRow::getId) + .filter(Objects::nonNull) + .collect(java.util.stream.Collectors.toSet()); + if (!submittedExistingIds.equals(existingMap.keySet())) { throw new ServiceException("费用调整行数据不完整"); } Set allowedFeeItems = new LinkedHashSet<>(contractFeeItemNames(detail.getContractId())); existingRows.forEach(row -> allowedFeeItems.addAll(parseMap(row.getFeeItemsJson()).keySet())); List changes = new ArrayList<>(); + List allRows = new ArrayList<>(existingRows); for (ReceivablePayableAdjustFeeRequest.AdjustRow adjusted : request.getRows()) { ReceivablePayableCargoFee existing = existingMap.get(adjusted.getId()); + if (adjusted.getRemark() != null && adjusted.getRemark().length() > 200) { + throw new ServiceException("备注不能超过200个字"); + } + if (Boolean.TRUE.equals(adjusted.getManualFee())) { + if (Func.isEmpty(adjusted.getFeeItemName())) { + throw new ServiceException("请填写手工费用项目"); + } + if (!List.of("charge", "deduct").contains(adjusted.getFeeType())) { + throw new ServiceException("手工费用类型不正确"); + } + validateNonNegative(adjusted.getAmount(), "手工费用金额"); + BigDecimal signedAmount = money(adjusted.getAmount()) + .multiply("deduct".equals(adjusted.getFeeType()) ? BigDecimal.valueOf(-1) : BigDecimal.ONE); + Map manualItems = new LinkedHashMap<>(); + manualItems.put(adjusted.getFeeItemName().trim(), signedAmount); + boolean newManualRow = existing == null; + if (newManualRow) { + existing = new ReceivablePayableCargoFee(); + existing.setDetailId(detail.getId()); + existing.setLineNo("ADJ-" + System.currentTimeMillis()); + existing.setOriginalAmount(BigDecimal.ZERO); + } + String oldName = existing.getCargoName(); + BigDecimal oldAmount = money(existing.getAfterAmount()); + // cargoName 表示运单货物名称,手工收费项名称仅保存到费用项目 JSON。 + existing.setCargoName(detail.getCargoName()); + existing.setBillingFactor("手工调整"); + existing.setBillingType("deduct".equals(adjusted.getFeeType()) ? "手工扣费" : "手工收费"); + existing.setTransportQuantity(detail.getTransportQuantity()); + existing.setQuantityUnit(detail.getQuantityUnit()); + existing.setMileage(detail.getMileage()); + existing.setFreightAmount(BigDecimal.ZERO); + existing.setFeeItemsJson(JsonUtil.toJson(manualItems)); + existing.setAdjustAmount(signedAmount.subtract(money(existing.getOriginalAmount()))); + existing.setAfterAmount(signedAmount); + existing.setRemark(adjusted.getRemark()); + if (newManualRow) { + cargoFeeMapper.insert(existing); + allRows.add(existing); + } else { + cargoFeeMapper.updateById(existing); + } + changes.add("【手工费用】从[" + oldName + " " + formatValue(oldAmount) + "]调整为[" + + existing.getBillingType() + " " + existing.getCargoName() + " " + formatValue(signedAmount) + "]"); + continue; + } if (existing == null) { throw new ServiceException("存在无效的费用调整行"); } @@ -244,6 +314,10 @@ public class ReceivablePayableDetailServiceImpl appendChange(changes, "计费数量", existing.getTransportQuantity(), adjusted.getTransportQuantity()); appendChange(changes, "里程", existing.getMileage(), adjusted.getMileage()); appendChange(changes, "运输费", existing.getFreightAmount(), adjusted.getFreightAmount()); + if (!Objects.equals(existing.getRemark(), adjusted.getRemark())) { + changes.add("【备注】从[" + Objects.toString(existing.getRemark(), "") + "]调整为[" + + Objects.toString(adjusted.getRemark(), "") + "]"); + } Map oldFeeItems = parseMap(existing.getFeeItemsJson()); for (String name : allowedFeeItems) { appendChange(changes, name, decimal(oldFeeItems.get(name)), money(feeItems.get(name))); @@ -254,6 +328,7 @@ public class ReceivablePayableDetailServiceImpl existing.setMileage(money(adjusted.getMileage())); existing.setFreightAmount(freightAmount); existing.setFeeItemsJson(JsonUtil.toJson(feeItems)); + existing.setRemark(adjusted.getRemark()); existing.setAdjustAmount(afterAmount.subtract(money(existing.getOriginalAmount()))); existing.setAfterAmount(afterAmount); cargoFeeMapper.updateById(existing); @@ -264,7 +339,7 @@ public class ReceivablePayableDetailServiceImpl for (int i = 0; i < changes.size(); i++) { saveChangeRecord(detail, changes.get(i), request.getAdjustReason(), String.format("%04d", i + 1)); } - refreshAdjustedDetail(detail, existingRows); + refreshAdjustedDetail(detail, allRows); } @Override @@ -420,6 +495,50 @@ public class ReceivablePayableDetailServiceImpl } } + @Override + @Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW, + rollbackFor = Exception.class) + public void generateForClosedMasterOrder(MasterOrder masterOrder) { + if (masterOrder == null || Func.isEmpty(masterOrder.getId()) || Func.isEmpty(masterOrder.getContractId())) { + return; + } + ContractManage contract = contractManageService.getById(masterOrder.getContractId()); + if (contract == null || !isSystemGeneration(contract)) return; + try { + List masterGoods = masterOrderGoods(masterOrder); + if (masterGoods.isEmpty()) return; + List matchedFees = new ArrayList<>(); + for (Waybill masterGoodsItem : masterGoods) { + String planId = matchedPlanId(masterGoodsItem, contract); + if (Func.isEmpty(planId)) continue; + matchedFees.addAll(calculatedFees(masterGoodsItem, contract, planId, true)); + } + if (matchedFees.isEmpty()) return; + normalizeMasterFeeLines(matchedFees); + BigDecimal contractUnitPrice = resolveContractUnitPrice(matchedFees); + for (String settlementType : List.of("payable", "receivable")) { + if (existsByMasterOrder(masterOrder.getMasterNo(), settlementType)) continue; + ReceivablePayableDetail detail = buildMasterOrderDetail(masterOrder, contract, masterGoods, + settlementType, matchedFees, contractUnitPrice); + save(detail); + for (ReceivablePayableCargoFee fee : matchedFees) { + ReceivablePayableCargoFee copy = BeanUtil.copyProperties(fee, ReceivablePayableCargoFee.class); + copy.setId(null); + copy.setDetailId(detail.getId()); + copy.setWaybillId(null); + cargoFeeMapper.insert(copy); + } + } + } catch (Exception exception) { + log.error("自动生成总单费用明细失败,masterOrderId:{}, masterNo:{}, contractId:{}, failureReason:{}", + masterOrder.getId(), masterOrder.getMasterNo(), masterOrder.getContractId(), exception.getMessage(), exception); + if (exception instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw new RuntimeException(exception); + } + } + private boolean isSystemGeneration(ContractManage contract) { if (Func.isNotEmpty(contract.getFeeGenerationMode())) { return "system".equalsIgnoreCase(contract.getFeeGenerationMode()) || "系统生成".equals(contract.getFeeGenerationMode()); @@ -525,9 +644,12 @@ public class ReceivablePayableDetailServiceImpl LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .eq(Waybill::getIsDeleted, 0) .eq(Waybill::getContractId, request.getContractId()) - .eq(Waybill::getBusinessStatus, "completed") - .notInSql(Waybill::getId, "select waybill_id from blade_receivable_payable_detail where is_deleted = 0" - + (Func.isNotEmpty(request.getSettlementType()) ? " and settlement_type = '" + settlementType(request.getSettlementType()) + "'" : "")); + .eq(Waybill::getBusinessStatus, "completed"); + String targetSettlementType = settlementType(request.getSettlementType()); + wrapper.notInSql(Waybill::getId, + "select waybill_id from blade_receivable_payable_detail" + + " where is_deleted = 0 and waybill_id is not null and settlement_type = '" + + targetSettlementType + "'"); if (Func.isNotEmpty(request.getBatchNo())) { wrapper.like(Waybill::getBatchNo, request.getBatchNo()); } @@ -553,7 +675,7 @@ public class ReceivablePayableDetailServiceImpl } private ReceivablePayableDetail buildDetail(Waybill waybill, ContractManage contract, String settlementType, - List fees, BigDecimal unitPrice) { + List fees, BigDecimal unitPrice) { BigDecimal freight = fees.stream().filter(this::isFreight).map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal total = fees.stream().map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal other = total.subtract(freight).setScale(2, RoundingMode.HALF_UP); @@ -580,7 +702,7 @@ public class ReceivablePayableDetailServiceImpl detail.setCargoType(waybill.getCargoType()); detail.setTransportQuantity(waybill.getQuantity()); detail.setQuantityUnit(waybill.getQuantityUnit()); - detail.setMileage(waybill.getMileage()); + detail.setMileage(normalizeGeneratedMileage(waybill.getMileage())); detail.setBatchNo(waybill.getBatchNo()); detail.setUnitPrice(unitPrice); detail.setCurrency("RMB"); @@ -593,6 +715,80 @@ public class ReceivablePayableDetailServiceImpl return detail; } + private ReceivablePayableDetail buildMasterOrderDetail(MasterOrder masterOrder, ContractManage contract, + List masterGoods, String settlementType, + List fees, BigDecimal unitPrice) { + Waybill masterWaybill = masterGoods.get(0); + ReceivablePayableDetail detail = buildDetail(masterWaybill, contract, settlementType, fees, unitPrice); + detail.setSourceType(SOURCE_MASTER_ORDER); + detail.setWaybillId(null); + detail.setWaybillNo(masterOrder.getMasterNo()); + detail.setVehicleNo(null); + detail.setTransportType(masterOrder.getTransportOrganizationType()); + detail.setCargoName(joinMasterGoodsField(masterGoods, Waybill::getCargoName)); + detail.setCargoType(joinMasterGoodsField(masterGoods, Waybill::getCargoType)); + detail.setTransportQuantity(masterGoods.stream().map(Waybill::getQuantity) + .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); + detail.setQuantityUnit(commonMasterGoodsValue(masterGoods, Waybill::getQuantityUnit)); + detail.setMileage(null); + detail.setBatchNo(null); + detail.setRemark(masterOrder.getRemark()); + return detail; + } + + private List masterOrderGoods(MasterOrder masterOrder) { + List result = new ArrayList<>(); + for (Map goods : parseList(masterOrder.getGoodsJson())) { + BigDecimal quantity = decimal(goods.get("quantity")); + if (quantity.compareTo(BigDecimal.ZERO) <= 0) continue; + Waybill masterGoodsItem = new Waybill(); + masterGoodsItem.setProjectId(masterOrder.getProjectId()); + masterGoodsItem.setProjectName(masterOrder.getProjectName()); + masterGoodsItem.setContractId(masterOrder.getContractId()); + masterGoodsItem.setContractName(masterOrder.getContractName()); + masterGoodsItem.setCustomerName(masterOrder.getCustomerName()); + masterGoodsItem.setTransportType(masterOrder.getTransportOrganizationType()); + masterGoodsItem.setCargoName(stringValue(goods, "cargoName")); + masterGoodsItem.setCargoType(stringValue(goods, "cargoType")); + masterGoodsItem.setSpecification(stringValue(goods, "specification")); + masterGoodsItem.setModel(stringValue(goods, "model")); + masterGoodsItem.setQuantity(quantity); + masterGoodsItem.setQuantityUnit(stringValue(goods, "quantityUnit")); + masterGoodsItem.setDepartureName(masterOrder.getDepartureName()); + masterGoodsItem.setDepartureAddress(masterOrder.getDepartureAddress()); + masterGoodsItem.setArrivalName(masterOrder.getArrivalName()); + masterGoodsItem.setArrivalAddress(masterOrder.getArrivalAddress()); + masterGoodsItem.setEndDate(masterOrder.getPlanEndTime() == null ? LocalDate.now() + : masterOrder.getPlanEndTime().toLocalDate()); + masterGoodsItem.setMasterNo(masterOrder.getMasterNo()); + masterGoodsItem.setWaybillNo(masterOrder.getMasterNo()); + masterGoodsItem.setGoodsJson(JsonUtil.toJson(goods)); + masterGoodsItem.setRemark(masterOrder.getRemark()); + result.add(masterGoodsItem); + } + return result; + } + + private void normalizeMasterFeeLines(List fees) { + for (int index = 0; index < fees.size(); index++) { + ReceivablePayableCargoFee fee = fees.get(index); + fee.setWaybillId(null); + fee.setLineNo(String.format("%04d", index + 1)); + } + } + + private String joinMasterGoodsField(List masterGoods, + java.util.function.Function getter) { + return masterGoods.stream().map(getter).filter(Func::isNotEmpty).distinct() + .collect(java.util.stream.Collectors.joining(",")); + } + + private String commonMasterGoodsValue(List masterGoods, + java.util.function.Function getter) { + List values = masterGoods.stream().map(getter).filter(Func::isNotEmpty).distinct().toList(); + return values.size() == 1 ? values.get(0) : ""; + } + private BigDecimal resolveContractUnitPrice(List fees) { return fees.stream() .filter(this::isFreight) @@ -631,7 +827,7 @@ public class ReceivablePayableDetailServiceImpl cargoFee.setQuantityUnit(waybill.getQuantityUnit()); cargoFee.setPriceUnit(waybill.getPriceUnit()); cargoFee.setUnitPrice(unitPrice); - cargoFee.setMileage(waybill.getMileage()); + cargoFee.setMileage(normalizeGeneratedMileage(waybill.getMileage())); cargoFee.setFreightAmount(freightAmount); cargoFee.setFeeItemsJson(JsonUtil.toJson(feeItems)); cargoFee.setOriginalAmount(total); @@ -647,9 +843,7 @@ public class ReceivablePayableDetailServiceImpl private List calculatedFees(Waybill waybill, ContractManage contract, String planId, boolean matchOnly) { List> plans = parseList(contract == null ? null : contract.getBillingPlanJson()); - Map plan = "__matched__".equals(planId) ? plans.stream().filter(this::isDefaultPlan).findFirst().orElseGet(() -> plans.isEmpty() ? null : plans.get(plans.size() - 1)) : plans.stream().filter(item -> Objects.equals(stringValue(item, "id"), planId) - || Objects.equals(stringValue(item, "planId"), planId)).findFirst() - .orElseGet(() -> plans.stream().filter(this::isDefaultPlan).findFirst().orElse(null)); + Map plan = resolveBillingPlan(plans, planId); if (plan == null || !(plan.get("rules") instanceof List)) return matchOnly ? List.of() : List.of(buildCargoFee(null, waybill)); List result = new ArrayList<>(); int line = 1; @@ -664,9 +858,9 @@ public class ReceivablePayableDetailServiceImpl fee.setWaybillId(waybill.getId()); fee.setLineNo(String.format("%04d", line++)); fee.setCargoName(stringValue(rule, "feeItem", "费用")); fee.setCargoType(waybill.getCargoType()); fee.setBillingFactor(stringValue(rule, "billingElement", "")); fee.setBillingType(stringValue(rule, "billingType", "")); - fee.setTransportQuantity(measure(rule, waybill)); fee.setQuantityUnit(stringValue(rule, "billingUnit", waybill.getQuantityUnit())); + fee.setTransportQuantity(measure(rule, waybill)); fee.setQuantityUnit(waybill.getQuantityUnit()); fee.setPriceUnit(stringValue(rule, "billingUnit", waybill.getPriceUnit())); fee.setUnitPrice(decimal(rule.get("unitPrice"))); - fee.setMileage(waybill.getMileage()); fee.setFreightAmount(isFreightRule(rule) ? amount : BigDecimal.ZERO); + fee.setMileage(normalizeGeneratedMileage(waybill.getMileage())); fee.setFreightAmount(isFreightRule(rule) ? amount : BigDecimal.ZERO); fee.setFeeItemsJson(JsonUtil.toJson(Map.of(fee.getCargoName(), amount))); fee.setOriginalAmount(amount); fee.setAdjustAmount(BigDecimal.ZERO); fee.setAfterAmount(amount); fee.setRemark(stringValue(rule, "remark", waybill.getRemark())); result.add(fee); @@ -674,6 +868,24 @@ public class ReceivablePayableDetailServiceImpl return result.isEmpty() && !matchOnly ? List.of(buildCargoFee(null, waybill)) : result; } + private Map resolveBillingPlan(List> plans, String planId) { + if ("__matched__".equals(planId)) { + return plans.stream().filter(this::isDefaultPlan).findFirst() + .orElseGet(() -> plans.isEmpty() ? null : plans.get(plans.size() - 1)); + } + if (Func.isEmpty(planId)) { + return plans.stream().filter(this::isDefaultPlan).findFirst() + .orElseGet(() -> plans.isEmpty() ? null : plans.get(plans.size() - 1)); + } + return plans.stream().filter(plan -> Objects.equals(stringValue(plan, "id"), planId) + || Objects.equals(stringValue(plan, "planId"), planId) + || Objects.equals(stringValue(plan, "name"), planId) + || Objects.equals(stringValue(plan, "planName"), planId) + || Objects.equals(stringValue(plan, "billingPlanName"), planId)) + .findFirst() + .orElseThrow(() -> new ServiceException("合同计费方案不存在或已变更,请重新选择")); + } + private boolean isDefaultPlan(Map plan) { Object value = plan.get("defaultPlan"); return Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value)); @@ -851,6 +1063,10 @@ public class ReceivablePayableDetailServiceImpl } private void rebuildDetailFee(ReceivablePayableDetail detail, String billingPlanId) { + if (SOURCE_MASTER_ORDER.equals(detail.getSourceType())) { + rebuildMasterOrderDetailFee(detail, billingPlanId); + return; + } Waybill waybill = waybillService.getById(detail.getWaybillId()); if (waybill == null) { throw new ServiceException("关联运单不存在"); @@ -869,6 +1085,38 @@ public class ReceivablePayableDetailServiceImpl updateById(detail); } + private void rebuildMasterOrderDetailFee(ReceivablePayableDetail detail, String billingPlanId) { + MasterOrder masterOrder = masterOrderMapper.selectOne(Wrappers.lambdaQuery() + .eq(MasterOrder::getMasterNo, detail.getWaybillNo()) + .eq(MasterOrder::getIsDeleted, 0)); + if (masterOrder == null) { + throw new ServiceException("关联总单不存在"); + } + ContractManage contract = contractManageService.getById(detail.getContractId()); + List masterGoods = masterOrderGoods(masterOrder); + List fees = masterGoods.stream() + .flatMap(goods -> calculatedFees(goods, contract, billingPlanId).stream()).toList(); + normalizeMasterFeeLines(fees); + cargoFeeMapper.delete(Wrappers.lambdaQuery() + .eq(ReceivablePayableCargoFee::getDetailId, detail.getId())); + fees.forEach(fee -> { + fee.setDetailId(detail.getId()); + fee.setWaybillId(null); + cargoFeeMapper.insert(fee); + }); + BigDecimal freight = fees.stream().filter(this::isFreight) + .map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add); + BigDecimal total = fees.stream().map(ReceivablePayableCargoFee::getAfterAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + detail.setFreightAmount(freight); + detail.setOtherFeeAmount(total.subtract(freight)); + detail.setTotalAmount(total); + detail.setUnitPrice(resolveContractUnitPrice(fees)); + detail.setFeeItemsJson(JsonUtil.toJson(fees.stream().collect(HashMap::new, + (map, fee) -> map.put(fee.getCargoName(), fee.getAfterAmount()), HashMap::putAll))); + updateById(detail); + } + private void closeDetails(List ids, String settlementType) { if (Func.isEmpty(ids)) { throw new ServiceException("请选择需要关闭的明细"); @@ -927,6 +1175,14 @@ public class ReceivablePayableDetailServiceImpl .eq(ReceivablePayableDetail::getIsDeleted, 0)) > 0; } + private boolean existsByMasterOrder(String masterNo, String settlementType) { + return count(Wrappers.lambdaQuery() + .eq(ReceivablePayableDetail::getSourceType, SOURCE_MASTER_ORDER) + .eq(ReceivablePayableDetail::getWaybillNo, masterNo) + .eq(ReceivablePayableDetail::getSettlementType, settlementType) + .eq(ReceivablePayableDetail::getIsDeleted, 0)) > 0; + } + private String settlementType(String value) { if (Func.isEmpty(value)) return "receivable"; if (!List.of("receivable", "payable").contains(value)) { @@ -960,9 +1216,96 @@ public class ReceivablePayableDetailServiceImpl map.put("transportType", waybill.getTransportType()); map.put("carrierType", waybill.getCarrierType()); map.put("cargoInfo", waybill.getCargoName()); + map.put("departureAddress", waybill.getDepartureAddress()); + map.put("departureContact", waybill.getDepartureContact()); + map.put("arrivalAddress", waybill.getArrivalAddress()); + map.put("arrivalContact", waybill.getArrivalContact()); + map.put("unitPrice", waybill.getUnitPrice()); + map.put("freight", waybillFreight(waybill)); + map.put("otherFeeTotal", waybillOtherFee(waybill)); + map.put("freightTotal", waybillFreightTotal(waybill)); + map.put("contractName", waybill.getContractName()); + map.put("planName", waybill.getPlanName()); + map.put("batchNo", waybill.getBatchNo()); + map.put("originalNo", waybill.getOriginalNo()); + map.put("masterNo", waybill.getMasterNo()); + map.put("remark", Func.isNotEmpty(waybill.getRemark()) ? waybill.getRemark() : waybill.getTaskRemark()); + map.put("createTime", waybill.getCreateTime()); + map.put("updateTime", waybill.getUpdateTime()); + map.put("businessStatusName", waybillStatusName(waybill.getBusinessStatus())); return map; } + private BigDecimal waybillFreight(Waybill waybill) { + Map freight = waybillFreightMap(waybill); + Object value = firstValue(freight, "freight", "freightAmount", "transportFee"); + if (value != null) return decimal(value); + if (freight.get("freightItems") instanceof List items) { + BigDecimal total = BigDecimal.ZERO; + boolean hasAmount = false; + for (Object item : items) { + if (!(item instanceof Map source)) continue; + Object amount = firstValue(stringMap(source), "freightAmount", "amount", "totalAmount"); + if (amount == null) continue; + total = total.add(decimal(amount)); + hasAmount = true; + } + if (hasAmount) return total; + } + return null; + } + + private BigDecimal waybillOtherFee(Waybill waybill) { + Map freight = waybillFreightMap(waybill); + Object value = firstValue(freight, "otherFeeTotal", "otherFreightAmount", "otherAmount"); + return value == null ? waybill.getOtherFeeTotal() : decimal(value); + } + + private BigDecimal waybillFreightTotal(Waybill waybill) { + Map freight = waybillFreightMap(waybill); + Object total = firstValue(freight, "freightTotal", "totalFreight", "totalFreightAmount", "totalAmount"); + if (total != null) return decimal(total); + BigDecimal freightAmount = waybillFreight(waybill); + BigDecimal otherFeeAmount = waybillOtherFee(waybill); + if (freightAmount == null && otherFeeAmount == null) return null; + return money(freightAmount).add(money(otherFeeAmount)); + } + + private Map waybillFreightMap(Waybill waybill) { + if (Func.isEmpty(waybill.getFreightJson())) return new LinkedHashMap<>(); + try { + Object parsed = JsonUtil.parse(waybill.getFreightJson(), Object.class); + if (parsed instanceof Map source) return stringMap(source); + if (parsed instanceof List list && !list.isEmpty() && list.get(0) instanceof Map source) { + return stringMap(source); + } + } catch (Exception ignored) { + // 兼容历史费用 JSON 异常数据。 + } + return new LinkedHashMap<>(); + } + + private Map stringMap(Map source) { + Map result = new LinkedHashMap<>(); + source.forEach((key, value) -> result.put(String.valueOf(key), value)); + return result; + } + + private Object firstValue(Map map, String... keys) { + for (String key : keys) if (map.get(key) != null && !String.valueOf(map.get(key)).isBlank()) return map.get(key); + return null; + } + + private String waybillStatusName(String status) { + return switch (status == null ? "" : status) { + case "completed" -> "已完成"; + case "processing", "running", "in_progress", "inProgress" -> "进行中"; + case "pending", "created" -> "待执行"; + case "cancelled", "canceled" -> "已取消"; + default -> status; + }; + } + private Map candidateMap(ReceivablePayableDetailVO detail) { Map map = new LinkedHashMap<>(); map.put("id", detail.getId()); @@ -1038,6 +1381,10 @@ public class ReceivablePayableDetailServiceImpl return value == null ? BigDecimal.ZERO : value.setScale(2, RoundingMode.HALF_UP); } + private BigDecimal normalizeGeneratedMileage(BigDecimal mileage) { + return mileage != null && mileage.compareTo(BigDecimal.valueOf(-1)) == 0 ? null : mileage; + } + private String formatMoney(BigDecimal value) { return money(value).toPlainString(); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ShippingTemplateServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ShippingTemplateServiceImpl.java index 79baf7e..be33891 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ShippingTemplateServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ShippingTemplateServiceImpl.java @@ -425,7 +425,7 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl latestList = list(Wrappers.lambdaQuery() .select(ShippingTemplate::getTemplateCode) .likeRight(ShippingTemplate::getTemplateCode, prefix) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 60b3984..b9df29f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -37,11 +37,13 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import org.springblade.transport.mapper.WaybillMapper; import org.springblade.transport.pojo.entity.LoadingManage; +import org.springblade.transport.pojo.entity.ProcessConfig; import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.LoadingManageVO; import org.springblade.transport.pojo.vo.WaybillVO; import org.springblade.transport.service.ILoadingManageService; +import org.springblade.transport.service.IProcessConfigService; import org.springblade.transport.service.IReceivablePayableDetailService; import org.springblade.transport.service.IWaybillService; import org.springblade.transport.support.TransportBusinessSupport; @@ -72,6 +74,9 @@ public class WaybillServiceImpl extends BaseServiceImpl @jakarta.annotation.Resource private ILoadingManageService loadingManageService; + @jakarta.annotation.Resource + private IProcessConfigService processConfigService; + @jakarta.annotation.Resource @org.springframework.context.annotation.Lazy private IReceivablePayableDetailService receivablePayableDetailService; @@ -93,9 +98,14 @@ public class WaybillServiceImpl extends BaseServiceImpl boolean created = Func.isEmpty(waybill.getId()); if (!created) { Waybill oldRecord = loadEditable(waybill.getId(), true); + assertNotLoaded(oldRecord); waybill.setWaybillNo(oldRecord.getWaybillNo()); + waybill.setLoadingNo(oldRecord.getLoadingNo()); waybill.setDeptId(oldRecord.getDeptId()); waybill.setDeptName(oldRecord.getDeptName()); + } else { + waybill.setLoadingNo(null); + fillProjectProcessConfig(waybill); } prepare(waybill); if (created && Func.isEmpty(waybill.getWaybillNo())) { @@ -105,6 +115,33 @@ public class WaybillServiceImpl extends BaseServiceImpl return saveOrUpdate(waybill); } + private void fillProjectProcessConfig(Waybill waybill) { + if (Func.isNotEmpty(waybill.getProcessJson()) || Func.isEmpty(waybill.getProjectId())) { + return; + } + String projectId = String.valueOf(waybill.getProjectId()); + processConfigService.list(Wrappers.lambdaQuery() + .eq(ProcessConfig::getStatus, 1) + .eq(ProcessConfig::getIsDeleted, 0) + .like(ProcessConfig::getProjectIds, projectId) + .orderByDesc(ProcessConfig::getCreateTime)) + .stream() + .filter(processConfig -> containsProjectId(processConfig.getProjectIds(), projectId)) + .map(ProcessConfig::getNodeConfigJson) + .filter(Func::isNotEmpty) + .findFirst() + .ifPresent(waybill::setProcessJson); + } + + private boolean containsProjectId(String projectIds, String projectId) { + if (Func.isEmpty(projectIds)) { + return false; + } + return List.of(projectIds.split(",")).stream() + .map(String::trim) + .anyMatch(projectId::equals); + } + @Override @Transactional(rollbackFor = Exception.class) public BusinessRemoveResultVO removeWaybill(String ids) { @@ -223,20 +260,21 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setPlanId(source.getPlanId()); target.setPlanName(source.getPlanName()); target.setMasterNo(source.getMasterNo()); - target.setLoadingNo(source.getLoadingNo()); + target.setLoadingNo(null); target.setBatchNo(source.getBatchNo()); target.setRelationNo(source.getRelationNo()); target.setCurrentProcessNode(source.getCurrentProcessNode()); target.setGoodsJson(source.getGoodsJson()); target.setCarrierJson(source.getCarrierJson()); target.setTaskInfoJson(source.getTaskInfoJson()); - target.setProcessJson(source.getProcessJson()); + target.setProcessJson(null); target.setRouteJson(source.getRouteJson()); target.setFreightJson(source.getFreightJson()); target.setAttachmentsJson(source.getAttachmentsJson()); target.setRemark(source.getRemark()); target.setBusinessStatus("pending"); target.setWaybillNo(nextCode()); + fillProjectProcessConfig(target); prepare(target); validate(target); save(target); @@ -247,6 +285,7 @@ public class WaybillServiceImpl extends BaseServiceImpl @Transactional(rollbackFor = Exception.class) public boolean changeRoute(Waybill waybill) { Waybill oldRecord = loadEditable(waybill.getId(), true); + assertNotLoaded(oldRecord); if ("completed".equals(oldRecord.getBusinessStatus()) || "cancelled".equals(oldRecord.getBusinessStatus())) { throw new ServiceException("当前运单状态不允许变更运输路线"); } @@ -265,6 +304,7 @@ public class WaybillServiceImpl extends BaseServiceImpl @Transactional(rollbackFor = Exception.class) public boolean cancel(Long id) { Waybill waybill = loadEditable(id, true); + assertNotLoaded(waybill); if ("completed".equals(waybill.getBusinessStatus()) || "cancelled".equals(waybill.getBusinessStatus())) { throw new ServiceException("当前状态不允许取消"); } @@ -276,6 +316,7 @@ public class WaybillServiceImpl extends BaseServiceImpl @Transactional(rollbackFor = Exception.class) public boolean reassign(Long id) { Waybill waybill = loadEditable(id, true); + assertNotLoaded(waybill); if (!"pending".equals(waybill.getBusinessStatus())) { throw new ServiceException("仅待执行运单允许重新派单"); } @@ -294,6 +335,7 @@ public class WaybillServiceImpl extends BaseServiceImpl boolean updated = updateById(waybill); if (updated) { receivablePayableDetailService.generateForCompletedWaybills(List.of(waybill.getId())); + loadingManageService.completeIfAllWaybillsCompleted(waybill.getLoadingNo()); } return updated; } @@ -636,7 +678,13 @@ public class WaybillServiceImpl extends BaseServiceImpl } private boolean shouldSkipDelete(Waybill waybill) { - return false; + return Func.isNotEmpty(waybill.getLoadingNo()); + } + + private void assertNotLoaded(Waybill waybill) { + if (Func.isNotEmpty(waybill.getLoadingNo())) { + throw new ServiceException("运单已关联配载单,请在配载单中修改"); + } } private void validateRoadTaskInfo(Waybill waybill) {