From 4e9bb19b23f797bcfb976fbf9a65e0b8c718a88e Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 8 Sep 2026 07:56:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=BB=93=E7=AE=97=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pojo/vo/CommonCargoVO.java | 4 + .../excel/CargoReconciliationExcel.java | 4 +- .../service/impl/CommonCargoServiceImpl.java | 4 + .../TransportReconciliationServiceImpl.java | 120 +++++++++++++++--- .../service/impl/WaybillServiceImpl.java | 16 ++- 5 files changed, 122 insertions(+), 26 deletions(-) diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonCargoVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonCargoVO.java index b6bd631..8b30d95 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonCargoVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonCargoVO.java @@ -47,6 +47,10 @@ public class CommonCargoVO extends CommonCargo { @Schema(description = "是否查看全部组织") private Integer allDept; + @TableField(exist = false) + @Schema(description = "规格型号(匹配规格或型号)") + private String specificationModel; + @TableField(exist = false) @Schema(description = "是否只读") private Boolean readonly; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationExcel.java index a8f55a8..0c03d69 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CargoReconciliationExcel.java @@ -30,12 +30,12 @@ public class CargoReconciliationExcel implements Serializable { @ExcelProperty("货物类型") private String cargoType; @ExcelProperty("规格") private String specification; @ExcelProperty("型号") private String model; - @ExcelProperty("运输量") @NumberFormat("0.000000") private BigDecimal transportQuantity; + @ExcelProperty("运输总量") @NumberFormat("0.000000") private BigDecimal transportQuantity; @ExcelProperty("运输单价") @NumberFormat("0.00") private BigDecimal unitPrice; @ExcelProperty("里程(KM)") @NumberFormat("0.00") private BigDecimal mileage; @ExcelProperty("运输费") @NumberFormat("0.00") private BigDecimal freightAmount; @ExcelProperty("费用项目名称1") @NumberFormat("0.00") private BigDecimal feeItemOne; @ExcelProperty("费用项目名称2") @NumberFormat("0.00") private BigDecimal feeItemTwo; - @ExcelProperty("结算金额") @NumberFormat("0.00") private BigDecimal settlementAmount; + @ExcelProperty("结算费用合计") @NumberFormat("0.00") private BigDecimal settlementAmount; @ExcelIgnore private String errorMessage; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java index 8171531..f8b6b5c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java @@ -238,6 +238,10 @@ public class CommonCargoServiceImpl extends BaseServiceImpl wrapper.like(CommonCargo::getSpecification, commonCargo.getSpecificationModel()) + .or().like(CommonCargo::getModel, commonCargo.getSpecificationModel())); + } if (Func.isNotEmpty(commonCargo.getSpecification())) { queryWrapper.like(CommonCargo::getSpecification, commonCargo.getSpecification()); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportReconciliationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportReconciliationServiceImpl.java index a4e0bea..52601fe 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportReconciliationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportReconciliationServiceImpl.java @@ -52,18 +52,20 @@ import org.springblade.transport.pojo.vo.TransportReconciliationVO; import org.springblade.transport.service.ITransportReconciliationService; import org.springblade.transport.wrapper.TransportReconciliationWrapper; import org.springframework.stereotype.Service; -import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; +import java.text.Collator; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.function.Function; @@ -217,7 +219,7 @@ public class TransportReconciliationServiceImpl continue; } try { - validExternals.add(buildVehicleExternal(id, index, row)); + validExternals.add(buildVehicleExternal(id, index, row)); } catch (Exception exception) { VehicleReconciliationFailureExcel failure = new VehicleReconciliationFailureExcel(); BeanUtil.copyProperties(row, failure); @@ -226,10 +228,6 @@ public class TransportReconciliationServiceImpl failures.add(failure); } } - if (Func.isNotEmpty(failures)) { - TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); - return failures; - } for (TransportReconciliationExternal external : validExternals) { if (externalMapper.insert(external) <= 0) throw new ServiceException("外部账单保存失败"); } @@ -242,7 +240,7 @@ public class TransportReconciliationServiceImpl TransportReconciliationExternal external = new TransportReconciliationExternal(); BeanUtil.copyProperties(row, external); external.setReconciliationId(reconciliationId); - external.setExternalLineNo(index + 2); + external.setExternalLineNo(index + 1); external.setActualDepartureTime(parseTime(row.getActualDepartureTime(), "实际发货时间")); external.setActualCompletionTime(parseTimeNullable(row.getActualCompletionTime(), "实际完成时间")); external.setFeeItemsJson(JsonUtil.toJson(Map.of("费用项目1", money(row.getFeeItemOne())))); @@ -312,32 +310,95 @@ public class TransportReconciliationServiceImpl @Override @Transactional(rollbackFor = Exception.class) public List importCargoes(Long id, List rows) { + if (Func.isEmpty(rows)) throw new ServiceException("导入数据不能为空"); TransportReconciliation bill = editable(id); if (!CARGO.equals(bill.getReconciliationMode())) throw new ServiceException("当前对账模式不是货物明细对账"); resetExternal(id); List failures = new ArrayList<>(); + List validExternals = new ArrayList<>(); for (int index = 0; index < rows.size(); index++) { CargoReconciliationExcel row = rows.get(index); + List validationErrors = validateImportCargo(row); + if (Func.isNotEmpty(validationErrors)) { + CargoReconciliationFailureExcel failure = new CargoReconciliationFailureExcel(); + BeanUtil.copyProperties(row, failure); + failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(validationErrors)); + failures.add(failure); + continue; + } try { - validateExternal(row.getVehicleNo(), row.getCargoName(), row.getTransportQuantity(), row.getSettlementAmount()); - TransportReconciliationExternal external = new TransportReconciliationExternal(); - BeanUtil.copyProperties(row, external); - external.setReconciliationId(id); external.setExternalLineNo(index + 2); - external.setActualDepartureTime(parseTime(row.getActualDepartureTime(), "实际发货时间")); - external.setActualCompletionTime(parseTimeNullable(row.getActualCompletionTime(), "实际完成时间")); - external.setFeeItemsJson(JsonUtil.toJson(Map.of("费用项目名称1", money(row.getFeeItemOne()), "费用项目名称2", money(row.getFeeItemTwo())))); - external.setMatchStatus(UNMATCHED); external.setSuspectedDuplicate(false); - external.setRawDataJson(JsonUtil.toJson(row)); externalMapper.insert(external); + validExternals.add(buildCargoExternal(id, index, row)); } catch (Exception exception) { CargoReconciliationFailureExcel failure = new CargoReconciliationFailureExcel(); - BeanUtil.copyProperties(row, failure); failure.setErrorMessage(exception.getMessage()); + BeanUtil.copyProperties(row, failure); + failure.setErrorMessage(org.springblade.common.excel.ImportFailureExcelUtil.formatErrorMessage(List.of( + exception instanceof ServiceException ? exception.getMessage() : "导入失败"))); failures.add(failure); } } + for (TransportReconciliationExternal external : validExternals) { + if (externalMapper.insert(external) <= 0) throw new ServiceException("外部账单保存失败"); + } refreshStats(id); return failures; } + private TransportReconciliationExternal buildCargoExternal(Long reconciliationId, int index, + CargoReconciliationExcel row) { + TransportReconciliationExternal external = new TransportReconciliationExternal(); + BeanUtil.copyProperties(row, external); + external.setReconciliationId(reconciliationId); + external.setExternalLineNo(index + 1); + external.setActualDepartureTime(parseTime(row.getActualDepartureTime(), "实际发货时间")); + external.setActualCompletionTime(parseTimeNullable(row.getActualCompletionTime(), "实际完成时间")); + external.setFeeItemsJson(JsonUtil.toJson(Map.of("费用项目名称1", money(row.getFeeItemOne()), "费用项目名称2", money(row.getFeeItemTwo())))); + external.setMatchStatus(UNMATCHED); + external.setSuspectedDuplicate(false); + external.setRawDataJson(JsonUtil.toJson(row)); + return external; + } + + private List validateImportCargo(CargoReconciliationExcel row) { + List validationErrors = new ArrayList<>(); + if (row == null) { + validationErrors.add("导入数据不能为空"); + return validationErrors; + } + org.springblade.common.excel.ImportFailureExcelUtil.addValidationError(validationErrors, + Func.isEmpty(row.getVehicleNo()), "车牌号不能为空"); + org.springblade.common.excel.ImportFailureExcelUtil.addValidationError(validationErrors, + Func.isEmpty(row.getCargoName()), "货物名称不能为空"); + org.springblade.common.excel.ImportFailureExcelUtil.addValidationError(validationErrors, + Func.isEmpty(row.getActualDepartureTime()), "实际发货时间不能为空"); + org.springblade.common.excel.ImportFailureExcelUtil.addValidationError(validationErrors, + Func.isEmpty(row.getTransportQuantity()), "运输总量不能为空"); + org.springblade.common.excel.ImportFailureExcelUtil.addValidationError(validationErrors, + Func.isEmpty(row.getSettlementAmount()), "结算费用合计不能为空"); + + addImportLengthError(validationErrors, row.getVehicleNo(), 30, "车牌号不能超过30字"); + addImportLengthError(validationErrors, row.getDepartureAddress(), 500, "发货地址不能超过500字"); + addImportLengthError(validationErrors, row.getArrivalAddress(), 500, "到货地址不能超过500字"); + addImportLengthError(validationErrors, row.getCargoName(), 500, "货物名称不能超过500字"); + addImportLengthError(validationErrors, row.getCargoType(), 500, "货物类型不能超过500字"); + addImportLengthError(validationErrors, row.getSpecification(), 200, "规格不能超过200字"); + addImportLengthError(validationErrors, row.getModel(), 200, "型号不能超过200字"); + + addImportDecimalErrors(validationErrors, row.getTransportQuantity(), "运输总量", 6); + addImportDecimalErrors(validationErrors, row.getMileage(), "里程(KM)", 2); + addImportDecimalErrors(validationErrors, row.getUnitPrice(), "运输单价", 2); + addImportDecimalErrors(validationErrors, row.getFreightAmount(), "运输费", 2); + addImportDecimalErrors(validationErrors, row.getFeeItemOne(), "费用项目名称1", 2); + addImportDecimalErrors(validationErrors, row.getFeeItemTwo(), "费用项目名称2", 2); + addImportDecimalErrors(validationErrors, row.getSettlementAmount(), "结算费用合计", 2); + + LocalDateTime departureTime = parseImportTime(row.getActualDepartureTime(), "实际发货时间", validationErrors); + LocalDateTime completionTime = parseImportTime(row.getActualCompletionTime(), "实际完成时间", validationErrors); + org.springblade.common.excel.ImportFailureExcelUtil.addValidationError(validationErrors, + departureTime != null && completionTime != null && completionTime.isBefore(departureTime), + "实际完成时间不能早于实际发货时间"); + return validationErrors; + } + @Override @Transactional(rollbackFor = Exception.class) public void autoMatch(Long id) { @@ -685,7 +746,7 @@ public class TransportReconciliationServiceImpl private void validateExternal(String vehicleNo, String cargoName, BigDecimal quantity, BigDecimal amount) { if (Func.isEmpty(vehicleNo)) throw new ServiceException("车牌号不能为空"); if (Func.isEmpty(cargoName)) throw new ServiceException("货物名称不能为空"); - nonNegative(quantity, "运输量"); nonNegative(amount, "结算金额"); + nonNegative(quantity, "运输总量"); nonNegative(amount, "结算费用合计"); } private LocalDateTime parseTime(String value, String field) { @@ -707,12 +768,29 @@ public class TransportReconciliationServiceImpl throw new ServiceException(field + "格式应为yyyy-MM-dd HH:mm:ss或yyyy-MM-dd"); } - private String vehicleKey(TransportReconciliationInternal row) { return key(row.getVehicleNo(), row.getCargoName(), row.getActualDepartureTime(), row.getBatchNo(), row.getTransportQuantity()); } - private String vehicleKey(TransportReconciliationExternal row) { return key(row.getVehicleNo(), row.getCargoName(), row.getActualDepartureTime(), row.getBatchNo(), row.getTransportQuantity()); } + private String vehicleKey(TransportReconciliationInternal row) { return key(row.getVehicleNo(), sortedCargoNames(row.getCargoName()), row.getActualDepartureTime(), row.getBatchNo(), row.getTransportQuantity()); } + private String vehicleKey(TransportReconciliationExternal row) { return key(row.getVehicleNo(), sortedCargoNames(row.getCargoName()), row.getActualDepartureTime(), row.getBatchNo(), row.getTransportQuantity()); } private String cargoKey(TransportReconciliationInternal row) { return key(row.getVehicleNo(), row.getDepartureAddress(), row.getArrivalAddress(), row.getCargoName(), row.getActualDepartureTime(), row.getTransportQuantity()); } private String cargoKey(TransportReconciliationExternal row) { return key(row.getVehicleNo(), row.getDepartureAddress(), row.getArrivalAddress(), row.getCargoName(), row.getActualDepartureTime(), row.getTransportQuantity()); } private String key(Object... values) { StringBuilder builder = new StringBuilder(); for (Object value : values) builder.append(normal(value)).append('|'); return builder.toString(); } - private String normal(Object value) { if (value == null) return ""; if (value instanceof BigDecimal decimal) return decimal.stripTrailingZeros().toPlainString(); return value.toString().trim().replaceAll("\\s+", "").toLowerCase(); } + private String normal(Object value) { + if (value == null) return ""; + if (value instanceof BigDecimal decimal) return decimal.stripTrailingZeros().toPlainString(); + if (value instanceof List list) return JsonUtil.toJson(list); + return value.toString().trim().replaceAll("\\s+", "").toLowerCase(); + } + private List sortedCargoNames(String cargoName) { + if (Func.isEmpty(cargoName)) return List.of(); + Collator collator = Collator.getInstance(Locale.CHINA); + return Arrays.stream(cargoName.split("、")) + .map(String::trim) + .filter(Func::isNotEmpty) + .sorted((left, right) -> { + int initialResult = collator.compare(left.substring(0, 1), right.substring(0, 1)); + return initialResult == 0 ? collator.compare(left, right) : initialResult; + }) + .toList(); + } private String firstNotEmpty(String... values) { for (String value : values) if (Func.isNotEmpty(value)) return value; return null; 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 7363b9a..f1f22cf 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 @@ -233,9 +233,19 @@ public class WaybillServiceImpl extends BaseServiceImpl @Override public List exportWaybill(WaybillVO waybill, String ids) { - LambdaQueryWrapper queryWrapper = buildQuery(waybill); - if (Func.isNotEmpty(ids)) { - queryWrapper.in(Waybill::getId, Func.toLongList(ids)); + TransportBusinessSupport.validateAllDept(waybill.getAllDept(), "运单管理"); + List idList = Func.toLongList(ids); + LambdaQueryWrapper queryWrapper; + if (Func.isNotEmpty(idList)) { + queryWrapper = Wrappers.lambdaQuery().eq(Waybill::getIsDeleted, 0); + if (!Objects.equals(waybill.getAllDept(), 1)) { + queryWrapper.eq(Waybill::getDeptId, TransportBusinessSupport.currentDeptId("运单管理")); + } else if (Func.isNotEmpty(waybill.getDeptId())) { + queryWrapper.eq(Waybill::getDeptId, waybill.getDeptId()); + } + queryWrapper.in(Waybill::getId, idList).orderByDesc(Waybill::getCreateTime); + } else { + queryWrapper = buildQuery(waybill); } return list(queryWrapper).stream().map(record -> { WaybillExcel excel = new WaybillExcel();