From 8075d3bf4d61fe76f376cbc3d5ddd2a05e5fccda Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 25 Aug 2026 22:34:49 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=BB=93=E7=AE=97=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReceivablePayableAdjustFeeRequest.java | 33 +++- .../dto/SettlementAdjustmentSaveRequest.java | 1 + .../pojo/entity/FormalSettlement.java | 2 + .../entity/ReceivablePayableCargoFee.java | 7 +- .../pojo/entity/SettlementAdjustment.java | 1 + .../pojo/vo/ReceivablePayableDetailVO.java | 4 + .../FormalSettlementController.java | 4 +- .../service/IFormalSettlementService.java | 2 +- .../impl/FormalSettlementServiceImpl.java | 17 +- .../impl/InvoiceApplicationServiceImpl.java | 1 + .../impl/InvoiceReceiptServiceImpl.java | 26 +++ .../impl/ReceiptClaimRecordServiceImpl.java | 2 + .../service/impl/ReceiptFlowServiceImpl.java | 2 + .../ReceivablePayableDetailServiceImpl.java | 161 +++++++++++++----- .../impl/SettlementAdjustmentServiceImpl.java | 1 + .../wrapper/FormalSettlementWrapper.java | 5 + .../blade_formal_settlement_20260818.sql | 4 +- ...formal_settlement_list_fields_20260825.sql | 34 ++++ ...payable_cargo_fee_data_source_20260825.sql | 35 ++++ ...ade_receivable_payable_detail_20260812.sql | 5 +- .../blade_settlement_adjustment_20260818.sql | 3 +- ...lement_adjustment_attachments_20260825.sql | 3 + 22 files changed, 287 insertions(+), 66 deletions(-) create mode 100644 doc/sql/transport/blade_formal_settlement_list_fields_20260825.sql create mode 100644 doc/sql/transport/blade_receivable_payable_cargo_fee_data_source_20260825.sql create mode 100644 doc/sql/transport/blade_settlement_adjustment_attachments_20260825.sql 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 25fa656..e463b7b 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 @@ -44,9 +44,33 @@ public class ReceivablePayableAdjustFeeRequest implements Serializable { @Schema(description = "费用行ID") private Long id; + @Schema(description = "货物名称") + private String cargoName; + + @Schema(description = "货物类型") + private String cargoType; + + @Schema(description = "规格") + private String specification; + + @Schema(description = "型号") + private String model; + + @Schema(description = "计费要素") + private String billingFactor; + + @Schema(description = "计费类型") + private String billingType; + @Schema(description = "运输量") private BigDecimal transportQuantity; + @Schema(description = "运费计算单位") + private String priceUnit; + + @Schema(description = "运输单价") + private BigDecimal unitPrice; + @Schema(description = "里程") private BigDecimal mileage; @@ -59,15 +83,6 @@ public class ReceivablePayableAdjustFeeRequest implements Serializable { @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-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/SettlementAdjustmentSaveRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/SettlementAdjustmentSaveRequest.java index bf7577f..9aa016c 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/SettlementAdjustmentSaveRequest.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/SettlementAdjustmentSaveRequest.java @@ -11,6 +11,7 @@ public class SettlementAdjustmentSaveRequest implements Serializable { @Serial private static final long serialVersionUID = 1L; private Long id; private Long formalSettlementId; + private String attachmentsJson; private String remark; private List details; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java index 29e9a11..107b7a8 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java @@ -48,6 +48,8 @@ public class FormalSettlement extends TenantEntity { private BigDecimal localSettlementAmount; private BigDecimal appliedPaymentAmount; private BigDecimal paidAmount; + private BigDecimal remainingPayableAmount; + private BigDecimal invoiceAmount; private LocalDate exchangeRateDate; private BigDecimal exchangeRate; private String invoiceStatus; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceivablePayableCargoFee.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceivablePayableCargoFee.java index 9c626d6..a812b0c 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceivablePayableCargoFee.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceivablePayableCargoFee.java @@ -54,6 +54,9 @@ public class ReceivablePayableCargoFee extends TenantEntity { @Schema(description = "行号") private String lineNo; + @Schema(description = "来源:自动生成/手工录入") + private String dataSource; + @Schema(description = "货物名称") private String cargoName; @@ -66,10 +69,10 @@ public class ReceivablePayableCargoFee extends TenantEntity { @Schema(description = "型号") private String model; - @Schema(description = "运费计费要素") + @Schema(description = "计费要素") private String billingFactor; - @Schema(description = "运费计费类型") + @Schema(description = "计费类型") private String billingType; @Schema(description = "运输量") diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/SettlementAdjustment.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/SettlementAdjustment.java index 08e5977..2b7834a 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/SettlementAdjustment.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/SettlementAdjustment.java @@ -30,6 +30,7 @@ public class SettlementAdjustment extends TenantEntity { private String currentNode; private String currentProcessor; private String kingdeeSyncStatus; + private String attachmentsJson; private String remark; private LocalDateTime approvedTime; } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceivablePayableDetailVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceivablePayableDetailVO.java index c49f3b7..4485762 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceivablePayableDetailVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceivablePayableDetailVO.java @@ -47,6 +47,10 @@ public class ReceivablePayableDetailVO extends ReceivablePayableDetail { @Serial private static final long serialVersionUID = 1L; + @TableField(exist = false) + @Schema(description = "指定明细ID,多个使用逗号分隔") + private String ids; + @TableField(exist = false) @Schema(description = "生成开始日期") @DateTimeFormat(pattern = "yyyy-MM-dd") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java index b40f671..8604023 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java @@ -81,8 +81,8 @@ public class FormalSettlementController extends BladeController { @GetMapping("/next-no") @ApiOperationSupport(order = 5) @Operation(summary = "获取最新正式结算单号") - public R nextNo() { - return R.data(formalSettlementService.nextNo()); + public R nextNo(@RequestParam String settlementType) { + return R.data(formalSettlementService.nextNo(settlementType)); } @GetMapping("/fee-options") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java index cc65b20..abb997c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java @@ -29,7 +29,7 @@ import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest; public interface IFormalSettlementService extends BaseService { IPage selectPage(IPage page, FormalSettlementVO query); IPage candidatePreSettlements(IPage page, PreSettlementVO query); - String nextNo(); + String nextNo(String settlementType); FormalSettlementVO detail(Long id); Long saveDraft(FormalSettlementSaveRequest request); void removeDraft(Long id); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java index 9b0af80..3181c02 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java @@ -144,8 +144,13 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl "SJS"; + case "payable" -> "FJS"; + default -> throw new ServiceException("结算类型不正确"); + }; + String prefix = code + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); List numbers = list(Wrappers.lambdaQuery() .select(FormalSettlement::getFormalSettlementNo) .likeRight(FormalSettlement::getFormalSettlementNo, prefix)) @@ -160,7 +165,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl= 0 ? "completed" : "partial"; + settlement.setInvoiceAmount(allocated); settlement.setInvoiceStatus(status); formalSettlementMapper.updateById(settlement); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java index 2d7e310..43d1152 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/InvoiceReceiptServiceImpl.java @@ -57,9 +57,11 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -208,6 +210,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl oldSettlementIds = entity.getId() == null ? List.of() : relationSettlementIds(entity.getId()); KingdeeInvoicePool invoice = lockedInvoice(request.getKingdeeInvoicePoolId()); assertInvoiceUnused(invoice, entity.getId()); @@ -260,6 +263,9 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpllambdaQuery() .eq(InvoiceReceiptSettlement::getInvoiceReceiptId, entity.getId())); saveRelations(entity.getId(), requestedRows, settlementMap); + Set refreshIds = new LinkedHashSet<>(oldSettlementIds); + refreshIds.addAll(settlementMap.keySet()); + refreshIds.forEach(this::refreshSettlementInvoiceStatus); record(entity.getId(), creating ? "create" : "save", creating ? "创建草稿" : "保存草稿", entity.getApprovalStatus(), entity.getApprovalStatus(), null, entity.getKingdeeBillNo()); return entity.getId(); @@ -272,11 +278,13 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl settlementIds = relationSettlementIds(id); settlementRelationMapper.delete(Wrappers.lambdaQuery() .eq(InvoiceReceiptSettlement::getInvoiceReceiptId, id)); recordMapper.delete(Wrappers.lambdaQuery() .eq(InvoiceReceiptRecord::getInvoiceReceiptId, id)); removeById(entity); + settlementIds.forEach(this::refreshSettlementInvoiceStatus); } @Override @@ -466,6 +474,23 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl relationSettlementIds(Long receiptId) { + return settlementRelationMapper.selectList(Wrappers.lambdaQuery() + .eq(InvoiceReceiptSettlement::getInvoiceReceiptId, receiptId)).stream() + .map(InvoiceReceiptSettlement::getFormalSettlementId).distinct().toList(); + } + + private void refreshSettlementInvoiceStatus(Long settlementId) { + FormalSettlement settlement = formalSettlementMapper.selectById(settlementId); + if (settlement == null) return; + BigDecimal invoiceAmount = receivedAmount(settlementId, null); + String invoiceStatus = invoiceAmount.compareTo(BigDecimal.ZERO) <= 0 ? "unreceived" + : invoiceAmount.compareTo(money(settlement.getSettlementAmount())) >= 0 ? "completed" : "partial"; + settlement.setInvoiceAmount(invoiceAmount); + settlement.setInvoiceStatus(invoiceStatus); + formalSettlementMapper.updateById(settlement); + } + private List activeRelations(Long settlementId, Long excludeReceiptId) { List relations = settlementRelationMapper.selectList( Wrappers.lambdaQuery() @@ -568,6 +593,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl { + if (Func.isEmpty(row.getDataSource())) { + row.setDataSource("手工调整".equals(row.getBillingFactor()) + ? FEE_SOURCE_MANUAL : FEE_SOURCE_AUTO); + } + }); ReceivablePayableFeeDetailVO result = buildFeeDetail(rows); LinkedHashSet feeItemNames = new LinkedHashSet<>(contractFeeItemNames(detail.getContractId())); feeItemNames.addAll(result.getFeeItemNames()); @@ -274,37 +282,34 @@ public class ReceivablePayableDetailServiceImpl throw new ServiceException("变更原因不能超过300个字"); } if (Boolean.TRUE.equals(adjusted.getManualFee())) { - if (Func.isEmpty(adjusted.getFeeItemName())) { - throw new ServiceException("请填写手工费用项目"); + if (existing == null && !"receivable".equals(detail.getSettlementType())) { + throw new ServiceException("仅应收明细允许新增手工费用"); } - if (!List.of("charge", "deduct").contains(adjusted.getFeeType())) { - throw new ServiceException("手工费用类型不正确"); + if (existing != null && !isManualFee(existing)) { + 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); + validateAdjustRow(adjusted, true); + Map manualItems = validatedFeeItems(adjusted.getFeeItems(), allowedFeeItems); + BigDecimal freightAmount = money(adjusted.getFreightAmount()); + BigDecimal afterAmount = adjustedAfterAmount(freightAmount, manualItems); boolean newManualRow = existing == null; if (newManualRow) { existing = new ReceivablePayableCargoFee(); existing.setDetailId(detail.getId()); + existing.setWaybillId(detail.getWaybillId()); existing.setLineNo("ADJ-" + System.currentTimeMillis()); existing.setOriginalAmount(BigDecimal.ZERO); } - String oldName = existing.getCargoName(); + String oldCargoName = 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); + applyEditableFields(existing, adjusted, true); + existing.setDataSource(FEE_SOURCE_MANUAL); + existing.setBillingFactor("-"); + existing.setBillingType("-"); + existing.setFreightAmount(freightAmount); existing.setFeeItemsJson(JsonUtil.toJson(manualItems)); - existing.setAdjustAmount(signedAmount.subtract(money(existing.getOriginalAmount()))); - existing.setAfterAmount(signedAmount); + existing.setAdjustAmount(afterAmount.subtract(money(existing.getOriginalAmount()))); + existing.setAfterAmount(afterAmount); existing.setRemark(adjusted.getRemark()); existing.setChangeReason(adjusted.getChangeReason()); if (newManualRow) { @@ -313,39 +318,30 @@ public class ReceivablePayableDetailServiceImpl } else { cargoFeeMapper.updateById(existing); } - changes.add("【手工费用】从[" + oldName + " " + formatValue(oldAmount) + "]调整为[" - + existing.getBillingType() + " " + existing.getCargoName() + " " + formatValue(signedAmount) + "]"); + changes.add("【手工费用】从[" + Objects.toString(oldCargoName, "") + " " + + formatValue(oldAmount) + "]调整为[" + Objects.toString(existing.getCargoName(), "") + + " " + formatValue(afterAmount) + "]"); continue; } if (existing == null) { throw new ServiceException("存在无效的费用调整行"); } - validateNonNegative(adjusted.getTransportQuantity(), "运输量"); - validateNonNegative(adjusted.getMileage(), "里程"); - validateNonNegative(adjusted.getFreightAmount(), "运输费"); - Map feeItems = new LinkedHashMap<>(); - if (adjusted.getFeeItems() != null) { - adjusted.getFeeItems().forEach((name, amount) -> { - if (!allowedFeeItems.contains(name)) { - throw new ServiceException("费用项目不存在:" + name); - } - validateNonNegative(amount, name); - feeItems.put(name, money(amount)); - }); + if (isManualFee(existing)) { + throw new ServiceException("手工录入费用行不能变更为自动生成"); } + validateAdjustRow(adjusted, false); + Map feeItems = validatedFeeItems(adjusted.getFeeItems(), allowedFeeItems); BigDecimal transportQuantity = money(adjusted.getTransportQuantity()); BigDecimal mileage = money(adjusted.getMileage()); BigDecimal freightAmount = money(adjusted.getFreightAmount()); Map effectiveFeeItems = feeItems; - boolean ruleInputChanged = money(existing.getTransportQuantity()).compareTo(transportQuantity) != 0 - || money(existing.getMileage()).compareTo(mileage) != 0; - if (ruleInputChanged) { - AdjustedFeeCalculation calculation = calculateAdjustedFee(detail, existing, transportQuantity, - mileage, freightAmount, effectiveFeeItems); - freightAmount = calculation.freightAmount(); - effectiveFeeItems = calculation.feeItems(); - } + appendChange(changes, "规格", existing.getSpecification(), adjusted.getSpecification()); + appendChange(changes, "型号", existing.getModel(), adjusted.getModel()); + appendChange(changes, "计费要素", existing.getBillingFactor(), adjusted.getBillingFactor()); + appendChange(changes, "计费类型", existing.getBillingType(), adjusted.getBillingType()); appendChange(changes, "计费数量", existing.getTransportQuantity(), transportQuantity); + appendChange(changes, "运费计算单位", existing.getPriceUnit(), adjusted.getPriceUnit()); + appendChange(changes, "运输单价", existing.getUnitPrice(), adjusted.getUnitPrice()); appendChange(changes, "里程", existing.getMileage(), mileage); appendChange(changes, "运输费", existing.getFreightAmount(), freightAmount); if (!Objects.equals(existing.getRemark(), adjusted.getRemark())) { @@ -361,6 +357,10 @@ public class ReceivablePayableDetailServiceImpl appendChange(changes, name, decimal(oldFeeItems.get(name)), money(effectiveFeeItems.get(name))); } BigDecimal afterAmount = adjustedAfterAmount(freightAmount, effectiveFeeItems); + applyEditableFields(existing, adjusted, false); + if (Func.isEmpty(existing.getDataSource())) { + existing.setDataSource(FEE_SOURCE_AUTO); + } existing.setTransportQuantity(transportQuantity); existing.setMileage(mileage); existing.setFreightAmount(freightAmount); @@ -889,6 +889,9 @@ public class ReceivablePayableDetailServiceImpl .like(Func.isNotEmpty(query.getBatchNo()), ReceivablePayableDetail::getBatchNo, query.getBatchNo()) .like(Func.isNotEmpty(query.getVehicleNo()), ReceivablePayableDetail::getVehicleNo, query.getVehicleNo()) .eq(Func.isNotEmpty(query.getSettlementStatus()), ReceivablePayableDetail::getSettlementStatus, query.getSettlementStatus()); + if (Func.isNotEmpty(query.getIds())) { + wrapper.in(ReceivablePayableDetail::getId, Func.toLongList(query.getIds())); + } wrapper.eq(Func.isNotEmpty(query.getSettlementType()), ReceivablePayableDetail::getSettlementType, query.getSettlementType()); return wrapper.orderByDesc(ReceivablePayableDetail::getCreateTime); } @@ -1146,6 +1149,7 @@ public class ReceivablePayableDetailServiceImpl cargoFee.setDetailId(detailId); cargoFee.setWaybillId(waybill.getId()); cargoFee.setLineNo("0001"); + cargoFee.setDataSource(FEE_SOURCE_AUTO); cargoFee.setCargoName(waybill.getCargoName()); cargoFee.setCargoType(waybill.getCargoType()); cargoFee.setSpecification(waybill.getSpecification()); @@ -1225,6 +1229,7 @@ public class ReceivablePayableDetailServiceImpl Map feeGoods, Map rule) { ReceivablePayableCargoFee fee = new ReceivablePayableCargoFee(); fee.setWaybillId(waybill.getId()); + fee.setDataSource(FEE_SOURCE_AUTO); fee.setCargoName(feeGoods.getOrDefault("cargoName", feeWaybill.getCargoName())); fee.setCargoType(feeGoods.getOrDefault("cargoType", feeWaybill.getCargoType())); fee.setSpecification(feeGoods.getOrDefault("specification", feeWaybill.getSpecification())); @@ -1519,6 +1524,9 @@ public class ReceivablePayableDetailServiceImpl Set feeItemNames = new LinkedHashSet<>(); List records = rows.stream().map(row -> { ReceivablePayableCargoFeeVO vo = Objects.requireNonNull(BeanUtil.copyProperties(row, ReceivablePayableCargoFeeVO.class)); + if (Func.isEmpty(vo.getDataSource())) { + vo.setDataSource(isManualFee(row) ? FEE_SOURCE_MANUAL : FEE_SOURCE_AUTO); + } Map feeItems = parseMap(row.getFeeItemsJson()); feeItemNames.addAll(feeItems.keySet()); vo.setFeeItems(feeItems); @@ -1552,6 +1560,63 @@ public class ReceivablePayableDetailServiceImpl return new ArrayList<>(names); } + private boolean isManualFee(ReceivablePayableCargoFee fee) { + return FEE_SOURCE_MANUAL.equals(fee.getDataSource()) || "手工调整".equals(fee.getBillingFactor()); + } + + private void validateAdjustRow(ReceivablePayableAdjustFeeRequest.AdjustRow row, boolean manualFee) { + if (manualFee) { + validateLength(row.getCargoName(), 100, "货物名称"); + validateLength(row.getCargoType(), 100, "货物类型"); + } + validateLength(row.getSpecification(), 255, "规格"); + validateLength(row.getModel(), 255, "型号"); + validateLength(row.getBillingFactor(), 100, "计费要素"); + validateLength(row.getBillingType(), 100, "计费类型"); + validateLength(row.getPriceUnit(), 50, "运费计算单位"); + validateNonNegative(row.getTransportQuantity(), "运输量"); + validateNonNegative(row.getUnitPrice(), "运输单价"); + validateNonNegative(row.getMileage(), "里程"); + validateNonNegative(row.getFreightAmount(), "运输费"); + } + + private void validateLength(String value, int maxLength, String field) { + if (value != null && value.length() > maxLength) { + throw new ServiceException(field + "不能超过" + maxLength + "个字"); + } + } + + private Map validatedFeeItems(Map feeItems, + Set allowedFeeItems) { + Map result = new LinkedHashMap<>(); + if (feeItems == null) return result; + feeItems.forEach((name, amount) -> { + if (!allowedFeeItems.contains(name)) { + throw new ServiceException("费用项目不存在:" + name); + } + validateNonNegative(amount, name); + result.put(name, money(amount)); + }); + return result; + } + + private void applyEditableFields(ReceivablePayableCargoFee fee, + ReceivablePayableAdjustFeeRequest.AdjustRow adjusted, + boolean manualFee) { + if (manualFee) { + fee.setCargoName(adjusted.getCargoName()); + fee.setCargoType(adjusted.getCargoType()); + } + fee.setSpecification(adjusted.getSpecification()); + fee.setModel(adjusted.getModel()); + fee.setBillingFactor(adjusted.getBillingFactor()); + fee.setBillingType(adjusted.getBillingType()); + fee.setTransportQuantity(money(adjusted.getTransportQuantity())); + fee.setPriceUnit(adjusted.getPriceUnit()); + fee.setUnitPrice(money(adjusted.getUnitPrice())); + fee.setMileage(money(adjusted.getMileage())); + } + private void validateNonNegative(BigDecimal value, String field) { if (value != null && value.compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException(field + "不能小于0"); @@ -1572,7 +1637,7 @@ public class ReceivablePayableDetailServiceImpl ReceivablePayableCargoFee fee, BigDecimal transportQuantity, BigDecimal mileage, BigDecimal freightAmount, Map feeItems) { - if ("手工调整".equals(fee.getBillingFactor())) { + if (isManualFee(fee)) { throw new ServiceException("手工费用不支持按合同计费规则试算"); } ContractManage contract = contractManageService.getById(detail.getContractId()); @@ -1691,6 +1756,13 @@ public class ReceivablePayableDetailServiceImpl } } + private void appendChange(List changes, String field, String before, String after) { + if (!Objects.equals(before, after)) { + changes.add("【" + field + "】从[" + Objects.toString(before, "") + "]调整为[" + + Objects.toString(after, "") + "]"); + } + } + private String formatValue(BigDecimal value) { return money(value).stripTrailingZeros().toPlainString(); } @@ -1883,7 +1955,8 @@ public class ReceivablePayableDetailServiceImpl private void applyManualAdjustment(ReceivablePayableDetail detail, BigDecimal amount, String feeItem, String reason) { ReceivablePayableCargoFee fee = new ReceivablePayableCargoFee(); fee.setDetailId(detail.getId()); fee.setWaybillId(detail.getWaybillId()); fee.setLineNo("ADJ-" + System.currentTimeMillis()); - fee.setCargoName(Func.isEmpty(feeItem) ? "手工调差" : feeItem); fee.setBillingFactor("手工调整"); fee.setBillingType("手工调差"); + fee.setDataSource(FEE_SOURCE_MANUAL); fee.setCargoName(Func.isEmpty(feeItem) ? "手工调差" : feeItem); + fee.setBillingFactor("-"); fee.setBillingType("-"); fee.setOriginalAmount(BigDecimal.ZERO); fee.setAdjustAmount(money(amount)); fee.setAfterAmount(money(amount)); fee.setFeeItemsJson(JsonUtil.toJson(Map.of(fee.getCargoName(), money(amount)))); fee.setRemark(reason); cargoFeeMapper.insert(fee); detail.setOtherFeeAmount(money(detail.getOtherFeeAmount()).add(money(amount))); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/SettlementAdjustmentServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/SettlementAdjustmentServiceImpl.java index 761ce51..87e430c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/SettlementAdjustmentServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/SettlementAdjustmentServiceImpl.java @@ -136,6 +136,7 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl 'voided' + WHERE relation.`is_deleted` = 0 + UNION ALL + SELECT relation.`formal_settlement_id`, relation.`allocated_invoice_amount` + FROM `blade_invoice_receipt_settlement` relation + INNER JOIN `blade_invoice_receipt` receipt + ON receipt.`id` = relation.`invoice_receipt_id` + AND receipt.`is_deleted` = 0 + AND receipt.`approval_status` <> 'voided' + WHERE relation.`is_deleted` = 0 + ) invoice_relation + GROUP BY invoice_relation.`formal_settlement_id` +) invoice_summary ON invoice_summary.`formal_settlement_id` = settlement.`id` +SET settlement.`invoice_amount` = COALESCE(invoice_summary.`invoice_amount`, 0) +WHERE settlement.`is_deleted` = 0; diff --git a/doc/sql/transport/blade_receivable_payable_cargo_fee_data_source_20260825.sql b/doc/sql/transport/blade_receivable_payable_cargo_fee_data_source_20260825.sql new file mode 100644 index 0000000..d368df3 --- /dev/null +++ b/doc/sql/transport/blade_receivable_payable_cargo_fee_data_source_20260825.sql @@ -0,0 +1,35 @@ +-- MySQL 5.7+ 兼容:为应收应付货物费用明细增加来源。 +-- 请在 transport 数据库执行。本脚本使用 information_schema 判断,可重复执行。 + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS `upgrade_receivable_payable_cargo_fee_data_source_20260825`$$ +CREATE PROCEDURE `upgrade_receivable_payable_cargo_fee_data_source_20260825`() +BEGIN + DECLARE db_name varchar(128); + SET db_name = DATABASE(); + + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = db_name + AND table_name = 'blade_receivable_payable_cargo_fee' + AND column_name = 'data_source' + ) THEN + ALTER TABLE `blade_receivable_payable_cargo_fee` + ADD COLUMN `data_source` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci + NOT NULL DEFAULT '自动生成' + COMMENT '来源:自动生成/手工录入' AFTER `line_no`; + + END IF; + + UPDATE `blade_receivable_payable_cargo_fee` + SET `data_source` = '手工录入' + WHERE `billing_factor` = '手工调整' + AND `data_source` <> '手工录入'; +END$$ + +CALL `upgrade_receivable_payable_cargo_fee_data_source_20260825`()$$ +DROP PROCEDURE `upgrade_receivable_payable_cargo_fee_data_source_20260825`$$ + +DELIMITER ; diff --git a/doc/sql/transport/blade_receivable_payable_detail_20260812.sql b/doc/sql/transport/blade_receivable_payable_detail_20260812.sql index b9d639e..5e77871 100644 --- a/doc/sql/transport/blade_receivable_payable_detail_20260812.sql +++ b/doc/sql/transport/blade_receivable_payable_detail_20260812.sql @@ -64,12 +64,13 @@ CREATE TABLE IF NOT EXISTS `blade_receivable_payable_cargo_fee` ( `detail_id` bigint(20) NOT NULL COMMENT '应收应付明细ID', `waybill_id` bigint(20) DEFAULT NULL COMMENT '运单ID', `line_no` varchar(30) DEFAULT NULL COMMENT '行号', + `data_source` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '自动生成' COMMENT '来源:自动生成/手工录入', `cargo_name` varchar(100) DEFAULT NULL COMMENT '货物名称', `cargo_type` varchar(100) DEFAULT NULL COMMENT '货物类型', `specification` varchar(255) DEFAULT NULL COMMENT '规格', `model` varchar(255) DEFAULT NULL COMMENT '型号', - `billing_factor` varchar(100) DEFAULT NULL COMMENT '运费计费要素', - `billing_type` varchar(100) DEFAULT NULL COMMENT '运费计费类型', + `billing_factor` varchar(100) DEFAULT NULL COMMENT '计费要素', + `billing_type` varchar(100) DEFAULT NULL COMMENT '计费类型', `transport_quantity` decimal(18,6) DEFAULT NULL COMMENT '运输量', `quantity_unit` varchar(50) DEFAULT NULL COMMENT '数量单位', `price_unit` varchar(50) DEFAULT NULL COMMENT '运费计算单位', diff --git a/doc/sql/transport/blade_settlement_adjustment_20260818.sql b/doc/sql/transport/blade_settlement_adjustment_20260818.sql index 5c63d71..71c66db 100644 --- a/doc/sql/transport/blade_settlement_adjustment_20260818.sql +++ b/doc/sql/transport/blade_settlement_adjustment_20260818.sql @@ -11,7 +11,8 @@ CREATE TABLE IF NOT EXISTS `blade_settlement_adjustment` ( `original_settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '原结算金额', `adjusted_settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '调整后结算金额', `approval_status` varchar(30) NOT NULL DEFAULT 'draft', `current_node` varchar(100) DEFAULT NULL, - `current_processor` varchar(200) DEFAULT NULL, `kingdee_sync_status` varchar(30) DEFAULT NULL COMMENT '关联正式单金蝶状态', `remark` varchar(200) DEFAULT NULL, `approved_time` datetime DEFAULT NULL, + `current_processor` varchar(200) DEFAULT NULL, `kingdee_sync_status` varchar(30) DEFAULT NULL COMMENT '关联正式单金蝶状态', + `attachments_json` longtext COLLATE utf8mb4_general_ci COMMENT '附件材料JSON', `remark` varchar(200) DEFAULT NULL, `approved_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_settlement_adjustment_no` (`tenant_id`,`adjustment_no`), KEY `idx_adjustment_formal` (`formal_settlement_id`), KEY `idx_adjustment_status` (`approval_status`), KEY `idx_adjustment_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='结算调整单'; diff --git a/doc/sql/transport/blade_settlement_adjustment_attachments_20260825.sql b/doc/sql/transport/blade_settlement_adjustment_attachments_20260825.sql new file mode 100644 index 0000000..4e2198e --- /dev/null +++ b/doc/sql/transport/blade_settlement_adjustment_attachments_20260825.sql @@ -0,0 +1,3 @@ +-- 结算调整单增加附件材料字段 +ALTER TABLE `blade_settlement_adjustment` + ADD COLUMN `attachments_json` longtext COLLATE utf8mb4_general_ci NULL COMMENT '附件材料JSON' AFTER `kingdee_sync_status`;