调整结算模块

This commit is contained in:
2026-09-08 07:56:07 +08:00
parent 7afd243a80
commit 4e9bb19b23
5 changed files with 122 additions and 26 deletions
@@ -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;
}
@@ -238,6 +238,10 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
if (Func.isNotEmpty(commonCargo.getPackageType())) {
queryWrapper.like(CommonCargo::getPackageType, commonCargo.getPackageType());
}
if (Func.isNotEmpty(commonCargo.getSpecificationModel())) {
queryWrapper.and(wrapper -> wrapper.like(CommonCargo::getSpecification, commonCargo.getSpecificationModel())
.or().like(CommonCargo::getModel, commonCargo.getSpecificationModel()));
}
if (Func.isNotEmpty(commonCargo.getSpecification())) {
queryWrapper.like(CommonCargo::getSpecification, commonCargo.getSpecification());
}
@@ -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<CargoReconciliationFailureExcel> importCargoes(Long id, List<CargoReconciliationExcel> rows) {
if (Func.isEmpty(rows)) throw new ServiceException("导入数据不能为空");
TransportReconciliation bill = editable(id);
if (!CARGO.equals(bill.getReconciliationMode())) throw new ServiceException("当前对账模式不是货物明细对账");
resetExternal(id);
List<CargoReconciliationFailureExcel> failures = new ArrayList<>();
List<TransportReconciliationExternal> validExternals = new ArrayList<>();
for (int index = 0; index < rows.size(); index++) {
CargoReconciliationExcel row = rows.get(index);
List<String> 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<String> validateImportCargo(CargoReconciliationExcel row) {
List<String> 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<String> 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;
@@ -233,9 +233,19 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
@Override
public List<WaybillExcel> exportWaybill(WaybillVO waybill, String ids) {
LambdaQueryWrapper<Waybill> queryWrapper = buildQuery(waybill);
if (Func.isNotEmpty(ids)) {
queryWrapper.in(Waybill::getId, Func.toLongList(ids));
TransportBusinessSupport.validateAllDept(waybill.getAllDept(), "运单管理");
List<Long> idList = Func.toLongList(ids);
LambdaQueryWrapper<Waybill> queryWrapper;
if (Func.isNotEmpty(idList)) {
queryWrapper = Wrappers.<Waybill>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();