This commit is contained in:
2026-09-07 16:08:05 +08:00
parent 397e9f3b62
commit 2e7ab6f508
14 changed files with 320 additions and 67 deletions
@@ -128,12 +128,20 @@ public class ImportFailureExcelUtil {
} }
private static List<Field> excelFields(Class<?> excelClass) { private static List<Field> excelFields(Class<?> excelClass) {
return Arrays.stream(excelClass.getDeclaredFields()) List<Class<?>> classHierarchy = new ArrayList<>();
.filter(field -> field.getAnnotation(ExcelProperty.class) != null) for (Class<?> current = excelClass; current != null; current = current.getSuperclass()) {
.filter(field -> field.getAnnotation(ExcelIgnore.class) == null) classHierarchy.add(0, current);
.filter(field -> !Objects.equals(field.getName(), ERROR_MESSAGE_FIELD)) }
.filter(field -> !Objects.equals(field.getName(), FAILURE_REASON_FIELD)) List<Field> fields = new ArrayList<>();
.toList(); for (Class<?> current : classHierarchy) {
Arrays.stream(current.getDeclaredFields())
.filter(field -> field.getAnnotation(ExcelProperty.class) != null)
.filter(field -> field.getAnnotation(ExcelIgnore.class) == null)
.filter(field -> !Objects.equals(field.getName(), ERROR_MESSAGE_FIELD))
.filter(field -> !Objects.equals(field.getName(), FAILURE_REASON_FIELD))
.forEach(fields::add);
}
return fields;
} }
private static List<List<String>> buildHead(List<Field> excelFields) { private static List<List<String>> buildHead(List<Field> excelFields) {
@@ -36,6 +36,7 @@ public class FormalSettlementVO extends FormalSettlement {
@TableField(exist = false) @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate createStartDate; @TableField(exist = false) @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate createStartDate;
@TableField(exist = false) @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate createEndDate; @TableField(exist = false) @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate createEndDate;
@TableField(exist = false) private String createUserName; @TableField(exist = false) private String createUserName;
@TableField(exist = false) private String ids;
@TableField(exist = false) private String approvalStatusName; @TableField(exist = false) private String approvalStatusName;
@TableField(exist = false) private String settlementTypeName; @TableField(exist = false) private String settlementTypeName;
@TableField(exist = false) private String preSettlementNos; @TableField(exist = false) private String preSettlementNos;
@@ -69,6 +69,9 @@ public class PreSettlementVO extends PreSettlement {
@Schema(description = "创建人姓名") @Schema(description = "创建人姓名")
private String createUserName; private String createUserName;
@TableField(exist = false)
private String ids;
@TableField(exist = false) @TableField(exist = false)
@Schema(description = "更新人姓名") @Schema(description = "更新人姓名")
private String updateUserName; private String updateUserName;
@@ -18,6 +18,7 @@ import java.util.List;
public class TransportReconciliationVO extends TransportReconciliation { public class TransportReconciliationVO extends TransportReconciliation {
@Serial private static final long serialVersionUID = 1L; @Serial private static final long serialVersionUID = 1L;
@TableField(exist = false) private String createUserName; @TableField(exist = false) private String createUserName;
@TableField(exist = false) private String ids;
@TableField(exist = false) private String updateUserName; @TableField(exist = false) private String updateUserName;
@TableField(exist = false) private String reconciliationModeName; @TableField(exist = false) private String reconciliationModeName;
@TableField(exist = false) private String reconciliationStatusName; @TableField(exist = false) private String reconciliationStatusName;
@@ -10,14 +10,18 @@ package org.springblade.transport.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController; 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.Condition;
import org.springblade.core.mp.support.Query; import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tool.api.R; import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest; import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest; import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest; import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest;
@@ -26,6 +30,7 @@ import org.springblade.transport.pojo.dto.FormalSettlementInvoiceClaimRequest;
import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest; import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee; import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.vo.FormalSettlementVO; import org.springblade.transport.pojo.vo.FormalSettlementVO;
import org.springblade.transport.excel.FormalSettlementExcel;
import org.springblade.transport.pojo.vo.PreSettlementVO; import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.service.IFormalSettlementService; import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.service.IPreSettlementService; import org.springblade.transport.service.IPreSettlementService;
@@ -39,6 +44,8 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.math.BigDecimal;
import java.math.RoundingMode;
/** /**
* 正式结算单控制器 * 正式结算单控制器
@@ -62,6 +69,65 @@ public class FormalSettlementController extends BladeController {
return R.data(formalSettlementService.selectPage(Condition.getPage(pageQuery), query)); return R.data(formalSettlementService.selectPage(Condition.getPage(pageQuery), query));
} }
@GetMapping("/export")
@ApiOperationSupport(order = 20)
@Operation(summary = "导出正式结算单")
public void export(FormalSettlementVO query, @RequestParam(required = false) String ids,
HttpServletResponse response) {
query.setIds(ids);
IPage<FormalSettlementVO> page = formalSettlementService.selectPage(new Page<>(1, 100000), query);
List<FormalSettlementExcel> rows = page.getRecords().stream().map(this::toExcel).toList();
ExcelUtil.export(response, "正式结算单" + DateUtil.time(), "正式结算单", rows, FormalSettlementExcel.class);
}
private FormalSettlementExcel toExcel(FormalSettlementVO vo) {
FormalSettlementExcel excel = new FormalSettlementExcel();
excel.setFormalSettlementNo(vo.getFormalSettlementNo());
excel.setPreSettlementNos(vo.getPreSettlementNos());
excel.setSourceType(vo.getSourceType());
excel.setPayerName(vo.getPayerName());
excel.setPayeeName(vo.getPayeeName());
excel.setProjectName(vo.getProjectName());
excel.setDeptName(vo.getDeptName());
excel.setContractNo(vo.getContractNo());
excel.setContractName(vo.getContractName());
excel.setSettlementAmount(formatMoney(vo.getSettlementAmount(), vo.getCurrency()));
excel.setLocalSettlementAmount(formatMoney(vo.getLocalSettlementAmount(), vo.getLocalCurrency()));
excel.setExchangeRate(vo.getExchangeRate() == null ? "" : vo.getExchangeRate().stripTrailingZeros().toPlainString());
excel.setInvoiceStatusName(invoiceStatusName(vo.getInvoiceStatus(), vo.getSettlementType()));
excel.setPaymentStatusName(paymentStatusName(vo.getPaymentStatus()));
excel.setApprovalStatusName(vo.getApprovalStatusName());
excel.setKingdeeBillNo(vo.getKingdeeBillNo());
excel.setCreateUserName(vo.getCreateUserName());
excel.setCreateTime(vo.getCreateTime());
return excel;
}
private String formatMoney(BigDecimal value, String currency) {
if (value == null) return "";
return value.setScale(2, RoundingMode.HALF_UP).toPlainString() + " "
+ (currency == null || currency.isBlank() ? "RMB" : currency);
}
private String invoiceStatusName(String value, String settlementType) {
boolean payable = "payable".equals(settlementType);
return switch (value == null ? "" : value) {
case "unreceived" -> payable ? "未收票" : "未开票";
case "partial" -> payable ? "部分收票" : "部分开票";
case "completed" -> payable ? "已收票" : "已开票";
default -> value == null || value.isBlank() ? "-" : value;
};
}
private String paymentStatusName(String value) {
return switch (value == null ? "" : value) {
case "unpaid" -> "未收/付款";
case "partial" -> "部分收/付款";
case "paid" -> "已收/付款";
default -> value == null || value.isBlank() ? "-" : value;
};
}
@GetMapping("/detail") @GetMapping("/detail")
@ApiOperationSupport(order = 2) @ApiOperationSupport(order = 2)
@Operation(summary = "正式结算单详情") @Operation(summary = "正式结算单详情")
@@ -224,7 +224,9 @@ public class PreSettlementController extends BladeController {
@GetMapping("/export") @GetMapping("/export")
@ApiOperationSupport(order = 19) @ApiOperationSupport(order = 19)
@Operation(summary = "导出预结算单") @Operation(summary = "导出预结算单")
public void export(PreSettlementVO query, HttpServletResponse response) { public void export(PreSettlementVO query, @RequestParam(required = false) String ids,
HttpServletResponse response) {
query.setIds(ids);
IPage<PreSettlementVO> page = preSettlementService.selectPage(new Page<>(1, 100000), query); IPage<PreSettlementVO> page = preSettlementService.selectPage(new Page<>(1, 100000), query);
List<PreSettlementExcel> rows = page.getRecords().stream().map(this::toExcel).toList(); List<PreSettlementExcel> rows = page.getRecords().stream().map(this::toExcel).toList();
ExcelUtil.export(response, "预结算单" + DateUtil.time(), "预结算单", rows, PreSettlementExcel.class); ExcelUtil.export(response, "预结算单" + DateUtil.time(), "预结算单", rows, PreSettlementExcel.class);
@@ -7,6 +7,7 @@
package org.springblade.transport.controller; package org.springblade.transport.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -23,6 +24,7 @@ import org.springblade.transport.excel.CargoReconciliationExcel;
import org.springblade.transport.excel.CargoReconciliationFailureExcel; import org.springblade.transport.excel.CargoReconciliationFailureExcel;
import org.springblade.transport.excel.VehicleReconciliationExcel; import org.springblade.transport.excel.VehicleReconciliationExcel;
import org.springblade.transport.excel.VehicleReconciliationFailureExcel; import org.springblade.transport.excel.VehicleReconciliationFailureExcel;
import org.springblade.transport.excel.TransportReconciliationExportExcel;
import org.springblade.transport.pojo.dto.TransportReconciliationManualMatchRequest; import org.springblade.transport.pojo.dto.TransportReconciliationManualMatchRequest;
import org.springblade.transport.pojo.dto.TransportReconciliationSaveRequest; import org.springblade.transport.pojo.dto.TransportReconciliationSaveRequest;
import org.springblade.transport.pojo.entity.FormalSettlement; import org.springblade.transport.pojo.entity.FormalSettlement;
@@ -40,6 +42,8 @@ import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.math.BigDecimal;
import java.math.RoundingMode;
/** 运输对账单控制器。 @author Chill */ /** 运输对账单控制器。 @author Chill */
@RestController @RestController
@@ -57,6 +61,40 @@ public class TransportReconciliationController extends BladeController {
return R.data(reconciliationService.selectPage(Condition.getPage(pageQuery), query)); return R.data(reconciliationService.selectPage(Condition.getPage(pageQuery), query));
} }
@GetMapping("/export")
@ApiOperationSupport(order = 15)
@Operation(summary = "导出运输对账单")
public void export(TransportReconciliationVO query, @RequestParam(required = false) String ids,
HttpServletResponse response) {
query.setIds(ids);
IPage<TransportReconciliationVO> page = reconciliationService.selectPage(new Page<>(1, 100000), query);
List<TransportReconciliationExportExcel> rows = page.getRecords().stream().map(this::toExcel).toList();
ExcelUtil.export(response, "运输对账" + DateUtil.time(), "运输对账", rows, TransportReconciliationExportExcel.class);
}
private TransportReconciliationExportExcel toExcel(TransportReconciliationVO vo) {
TransportReconciliationExportExcel excel = new TransportReconciliationExportExcel();
excel.setReconciliationNo(vo.getReconciliationNo());
excel.setPayerName(vo.getPayerName());
excel.setPayeeName(vo.getPayeeName());
excel.setProjectName(vo.getProjectName());
excel.setDeptName(vo.getDeptName());
excel.setContractNo(vo.getContractNo());
excel.setContractName(vo.getContractName());
excel.setSettlementAmount(formatMoney(vo.getSettlementAmount()));
excel.setReconciliationModeName(vo.getReconciliationModeName());
excel.setExternalBillCount(vo.getExternalBillCount());
excel.setMatchedCount(vo.getMatchedCount());
excel.setReconciliationStatusName(vo.getReconciliationStatusName());
excel.setCreateUserName(vo.getCreateUserName());
excel.setCreateTime(vo.getCreateTime());
return excel;
}
private String formatMoney(BigDecimal value) {
return value == null ? "0.00" : value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
@GetMapping("/detail") @GetMapping("/detail")
@ApiOperationSupport(order = 2) @ApiOperationSupport(order = 2)
@Operation(summary = "运输对账详情") @Operation(summary = "运输对账详情")
@@ -0,0 +1,58 @@
package org.springblade.transport.excel;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
@Data
@ColumnWidth(22)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class FormalSettlementExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("结算单号")
private String formalSettlementNo;
@ExcelProperty("预结算单号")
private String preSettlementNos;
@ExcelProperty("来源")
private String sourceType;
@ExcelProperty("付款方")
private String payerName;
@ExcelProperty("收款方")
private String payeeName;
@ExcelProperty("项目名称")
private String projectName;
@ExcelProperty("所属组织")
private String deptName;
@ExcelProperty("合同编号")
private String contractNo;
@ExcelProperty("合同名称")
private String contractName;
@ExcelProperty("原币结算金额")
private String settlementAmount;
@ExcelProperty("本位币结算金额")
private String localSettlementAmount;
@ExcelProperty("结算汇率")
private String exchangeRate;
@ExcelProperty("发票状态")
private String invoiceStatusName;
@ExcelProperty("收付款状态")
private String paymentStatusName;
@ExcelProperty("审核状态")
private String approvalStatusName;
@ExcelProperty("金蝶单据号")
private String kingdeeBillNo;
@ExcelProperty("创建人")
private String createUserName;
@ExcelProperty("创建时间")
private Date createTime;
}
@@ -0,0 +1,50 @@
package org.springblade.transport.excel;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
@Data
@ColumnWidth(22)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class TransportReconciliationExportExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty("对账单号")
private String reconciliationNo;
@ExcelProperty("付款方")
private String payerName;
@ExcelProperty("收款方")
private String payeeName;
@ExcelProperty("项目名称")
private String projectName;
@ExcelProperty("所属组织")
private String deptName;
@ExcelProperty("合同编号")
private String contractNo;
@ExcelProperty("合同名称")
private String contractName;
@ExcelProperty("结算金额")
private String settlementAmount;
@ExcelProperty("对账模式")
private String reconciliationModeName;
@ExcelProperty("账单总数")
private Integer externalBillCount;
@ExcelProperty("匹配数")
private Integer matchedCount;
@ExcelProperty("对账状态")
private String reconciliationStatusName;
@ExcelProperty("创建人")
private String createUserName;
@ExcelProperty("创建时间")
private Date createTime;
}
@@ -153,6 +153,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
.eq(Func.isNotEmpty(query.getApprovalStatus()), FormalSettlement::getApprovalStatus, query.getApprovalStatus()) .eq(Func.isNotEmpty(query.getApprovalStatus()), FormalSettlement::getApprovalStatus, query.getApprovalStatus())
.ge(query.getCreateStartDate() != null, FormalSettlement::getCreateTime, query.getCreateStartDate() == null ? null : query.getCreateStartDate().atStartOfDay()) .ge(query.getCreateStartDate() != null, FormalSettlement::getCreateTime, query.getCreateStartDate() == null ? null : query.getCreateStartDate().atStartOfDay())
.lt(query.getCreateEndDate() != null, FormalSettlement::getCreateTime, query.getCreateEndDate() == null ? null : query.getCreateEndDate().plusDays(1).atStartOfDay()); .lt(query.getCreateEndDate() != null, FormalSettlement::getCreateTime, query.getCreateEndDate() == null ? null : query.getCreateEndDate().plusDays(1).atStartOfDay());
if (Func.isNotEmpty(query.getIds())) {
wrapper.in(FormalSettlement::getId, Func.toLongList(query.getIds()));
}
if (Func.isNotEmpty(query.getPreSettlementNo())) { if (Func.isNotEmpty(query.getPreSettlementNo())) {
List<Long> ids = sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery() List<Long> ids = sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.like(FormalSettlementSource::getPreSettlementNo, query.getPreSettlementNo())) .like(FormalSettlementSource::getPreSettlementNo, query.getPreSettlementNo()))
@@ -668,7 +668,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
} }
private LambdaQueryWrapper<PreSettlement> buildQuery(PreSettlementVO query) { private LambdaQueryWrapper<PreSettlement> buildQuery(PreSettlementVO query) {
return Wrappers.<PreSettlement>lambdaQuery() LambdaQueryWrapper<PreSettlement> wrapper = Wrappers.<PreSettlement>lambdaQuery()
.eq(PreSettlement::getIsDeleted, 0) .eq(PreSettlement::getIsDeleted, 0)
.like(Func.isNotEmpty(query.getPreSettlementNo()), PreSettlement::getPreSettlementNo, .like(Func.isNotEmpty(query.getPreSettlementNo()), PreSettlement::getPreSettlementNo,
query.getPreSettlementNo()) query.getPreSettlementNo())
@@ -688,6 +688,10 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
.le(query.getCreateEndDate() != null, PreSettlement::getCreateTime, .le(query.getCreateEndDate() != null, PreSettlement::getCreateTime,
query.getCreateEndDate() == null ? null : query.getCreateEndDate().plusDays(1).atStartOfDay()) query.getCreateEndDate() == null ? null : query.getCreateEndDate().plusDays(1).atStartOfDay())
.orderByDesc(PreSettlement::getCreateTime); .orderByDesc(PreSettlement::getCreateTime);
if (Func.isNotEmpty(query.getIds())) {
wrapper.in(PreSettlement::getId, Func.toLongList(query.getIds()));
}
return wrapper;
} }
private Map<String, Object> candidateMap(ReceivablePayableDetail source) { private Map<String, Object> candidateMap(ReceivablePayableDetail source) {
@@ -725,6 +729,9 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
result.put("unitPrice", source.getUnitPrice()); result.put("unitPrice", source.getUnitPrice());
result.put("freightAmount", money(source.getFreightAmount())); result.put("freightAmount", money(source.getFreightAmount()));
result.put("feeItemsJson", source.getFeeItemsJson()); result.put("feeItemsJson", source.getFeeItemsJson());
BigDecimal originalAmount = sourceOriginalAmount(source);
result.put("originalAmount", originalAmount);
result.put("adjustAmount", money(source.getTotalAmount()).subtract(originalAmount));
result.put("totalAmount", money(source.getTotalAmount())); result.put("totalAmount", money(source.getTotalAmount()));
result.put("currency", source.getCurrency()); result.put("currency", source.getCurrency());
result.put("settlementStatusName", "待结算"); result.put("settlementStatusName", "待结算");
@@ -768,6 +775,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
PreSettlementDetail detail = copySourceDetail(settlement, source); PreSettlementDetail detail = copySourceDetail(settlement, source);
detailMapper.insert(detail); detailMapper.insert(detail);
copySourceFees(detail, source); copySourceFees(detail, source);
refreshDetail(detail);
int affected = sourceDetailMapper.update(null, int affected = sourceDetailMapper.update(null,
Wrappers.<ReceivablePayableDetail>lambdaUpdate() Wrappers.<ReceivablePayableDetail>lambdaUpdate()
.set(ReceivablePayableDetail::getPreSettlementNo, settlement.getPreSettlementNo()) .set(ReceivablePayableDetail::getPreSettlementNo, settlement.getPreSettlementNo())
@@ -834,8 +842,9 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
detail.setUnitPrice(source.getUnitPrice()); detail.setUnitPrice(source.getUnitPrice());
detail.setFreightAmount(money(source.getFreightAmount())); detail.setFreightAmount(money(source.getFreightAmount()));
detail.setFeeItemsJson(source.getFeeItemsJson()); detail.setFeeItemsJson(source.getFeeItemsJson());
detail.setOriginalAmount(money(source.getTotalAmount())); BigDecimal originalAmount = sourceOriginalAmount(source);
detail.setAdjustAmount(BigDecimal.ZERO.setScale(2)); detail.setOriginalAmount(originalAmount);
detail.setAdjustAmount(money(source.getTotalAmount()).subtract(originalAmount));
detail.setSettlementAmountTax(money(source.getTotalAmount())); detail.setSettlementAmountTax(money(source.getTotalAmount()));
detail.setCurrency(Func.isEmpty(source.getCurrency()) ? LOCAL_CURRENCY : source.getCurrency()); detail.setCurrency(Func.isEmpty(source.getCurrency()) ? LOCAL_CURRENCY : source.getCurrency());
detail.setRemark(source.getRemark()); detail.setRemark(source.getRemark());
@@ -849,6 +858,18 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
return detail; return detail;
} }
private BigDecimal sourceOriginalAmount(ReceivablePayableDetail source) {
List<ReceivablePayableCargoFee> sourceFees = sourceFeeMapper.selectList(
Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
.eq(ReceivablePayableCargoFee::getDetailId, source.getId())
.eq(ReceivablePayableCargoFee::getIsDeleted, 0));
if (sourceFees.isEmpty() || sourceFees.stream().noneMatch(item -> item.getOriginalAmount() != null)) {
return money(source.getTotalAmount());
}
return sourceFees.stream().map(ReceivablePayableCargoFee::getOriginalAmount)
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
}
private void copySourceFees(PreSettlementDetail detail, ReceivablePayableDetail source) { private void copySourceFees(PreSettlementDetail detail, ReceivablePayableDetail source) {
List<ReceivablePayableCargoFee> sourceFees = sourceFeeMapper.selectList( List<ReceivablePayableCargoFee> sourceFees = sourceFeeMapper.selectList(
Wrappers.<ReceivablePayableCargoFee>lambdaQuery() Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
@@ -880,7 +901,18 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
fee.setId(null); fee.setId(null);
fee.setPreSettlementDetailId(detail.getId()); fee.setPreSettlementDetailId(detail.getId());
fee.setSourceFeeId(sourceFee.getId()); fee.setSourceFeeId(sourceFee.getId());
fee.setSettlementAmountTax(money(sourceFee.getAfterAmount())); // 历史应付费用可能没有 after_amount,不能在首次打开明细调整时被当成 0。
BigDecimal settlementAmount = sourceFee.getAfterAmount() != null
? money(sourceFee.getAfterAmount())
: sourceFees.size() == 1 && source.getTotalAmount() != null
? money(source.getTotalAmount())
: sourceFee.getOriginalAmount() != null
? money(sourceFee.getOriginalAmount()) : BigDecimal.ZERO.setScale(2);
BigDecimal originalAmount = sourceFee.getOriginalAmount() == null
? settlementAmount : money(sourceFee.getOriginalAmount());
fee.setOriginalAmount(originalAmount);
fee.setAdjustAmount(settlementAmount.subtract(originalAmount));
fee.setSettlementAmountTax(settlementAmount);
fee.setSettlementAmountNoTax(null); fee.setSettlementAmountNoTax(null);
detailFeeMapper.insert(fee); detailFeeMapper.insert(fee);
} }
@@ -938,10 +970,11 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
row.setLineNo(lineNo++); row.setLineNo(lineNo++);
row.setFeeType(keyParts[0]); row.setFeeType(keyParts[0]);
row.setFeeItem(keyParts.length > 1 ? keyParts[1] : ""); row.setFeeItem(keyParts.length > 1 ? keyParts[1] : "");
row.setOriginalAmount(money(entry.getValue())); // 原金额是明细首次生成时的快照,多次调整后不得被当前结算金额覆盖。
row.setAdjustAmount(preserveAdjustments && old != null ? money(old.getAdjustAmount()) // 当前费用聚合值代表调整后的结算金额,差额统一记录为调整金额。
: BigDecimal.ZERO.setScale(2)); row.setOriginalAmount(old == null ? money(entry.getValue()) : money(old.getOriginalAmount()));
row.setSettlementAmount(row.getOriginalAmount().add(row.getAdjustAmount())); row.setSettlementAmount(money(entry.getValue()));
row.setAdjustAmount(money(row.getSettlementAmount().subtract(row.getOriginalAmount())));
row.setRemark(old == null ? "" : old.getRemark()); row.setRemark(old == null ? "" : old.getRemark());
row.setManualFlag(0); row.setManualFlag(0);
summaryFeeMapper.insert(row); summaryFeeMapper.insert(row);
@@ -1023,11 +1056,12 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
} }
row.setFeeType(feeType); row.setFeeType(feeType);
row.setFeeItem(feeItem); row.setFeeItem(feeItem);
row.setOriginalAmount(requestRow.getOriginalAmount() == null // 生成费用行的三个金额必须来源于结算明细快照,不能接受前端回传值覆盖原金额。
? money(row.getOriginalAmount()) : money(requestRow.getOriginalAmount())); // rebuildSummaryFees 已按明细重建 original/adjust/settlement,此处仅保留备注。
BigDecimal before = money(row.getAdjustAmount()); BigDecimal before = money(row.getAdjustAmount());
row.setAdjustAmount(money(requestRow.getAdjustAmount())); row.setOriginalAmount(money(row.getOriginalAmount()));
row.setSettlementAmount(money(row.getOriginalAmount()).add(row.getAdjustAmount())); row.setSettlementAmount(money(row.getSettlementAmount()));
row.setAdjustAmount(money(row.getSettlementAmount().subtract(row.getOriginalAmount())));
row.setRemark(limitRemark(requestRow.getRemark(), 50)); row.setRemark(limitRemark(requestRow.getRemark(), 50));
if (row.getId() == null) summaryFeeMapper.insert(row); else summaryFeeMapper.updateById(row); if (row.getId() == null) summaryFeeMapper.insert(row); else summaryFeeMapper.updateById(row);
retainedGeneratedIds.add(row.getId()); retainedGeneratedIds.add(row.getId());
@@ -1071,8 +1105,8 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
} }
private void refreshSettlementAmount(PreSettlement settlement) { private void refreshSettlementAmount(PreSettlement settlement) {
BigDecimal settlementAmount = listSummaryFees(settlement.getId()).stream() BigDecimal settlementAmount = listDetails(settlement.getId()).stream()
.map(PreSettlementSummaryFee::getSettlementAmount).map(this::money) .map(PreSettlementDetail::getSettlementAmountTax).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
settlement.setSettlementAmount(money(settlementAmount)); settlement.setSettlementAmount(money(settlementAmount));
BigDecimal rate = normalizeRate(settlement.getExchangeRate(), settlement.getCurrency()); BigDecimal rate = normalizeRate(settlement.getExchangeRate(), settlement.getCurrency());
@@ -1523,13 +1523,8 @@ public class ReceivablePayableDetailServiceImpl
} }
private Map<String, Object> resolveBillingPlan(List<Map<String, Object>> plans, String planId) { private Map<String, Object> resolveBillingPlan(List<Map<String, Object>> plans, String planId) {
if ("__matched__".equals(planId)) { if ("__matched__".equals(planId) || Func.isEmpty(planId)) {
return plans.stream().filter(this::isDefaultPlan).findFirst() return resolveDefaultBillingPlan(plans, null);
.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) return plans.stream().filter(plan -> Objects.equals(stringValue(plan, "id"), planId)
|| Objects.equals(stringValue(plan, "planId"), planId) || Objects.equals(stringValue(plan, "planId"), planId)
@@ -1545,20 +1540,24 @@ public class ReceivablePayableDetailServiceImpl
return Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value)) || "1".equals(String.valueOf(value)); return Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value)) || "1".equals(String.valueOf(value));
} }
/**
* 解析合同生效计费方案。
* 合同存在勾选默认的计费方案时仅在默认方案内匹配计算,不再回退其他方案;
* 全部方案均未勾选默认时仅使用最新添加的计费方案。
*/
private Map<String, Object> resolveDefaultBillingPlan(List<Map<String, Object>> plans, String transportType) { private Map<String, Object> resolveDefaultBillingPlan(List<Map<String, Object>> plans, String transportType) {
if (plans.isEmpty()) return null; if (plans.isEmpty()) return null;
if (plans.size() == 1) return plans.get(0); List<Map<String, Object>> defaultPlans = plans.stream().filter(this::isDefaultPlan).toList();
Optional<Map<String, Object>> matchedDefaultPlan = plans.stream().filter(this::isDefaultPlan) if (defaultPlans.isEmpty()) return plans.get(plans.size() - 1);
Optional<Map<String, Object>> matchedDefaultPlan = defaultPlans.stream()
.filter(plan -> !isBlank(plan.get("transportMode"))) .filter(plan -> !isBlank(plan.get("transportMode")))
.filter(plan -> matchesCondition(plan.get("transportMode"), transportType)) .filter(plan -> matchesCondition(plan.get("transportMode"), transportType))
.findFirst(); .findFirst();
if (matchedDefaultPlan.isPresent()) return matchedDefaultPlan.get(); if (matchedDefaultPlan.isPresent()) return matchedDefaultPlan.get();
Optional<Map<String, Object>> defaultPlanWithoutTransportMode = plans.stream() return defaultPlans.stream()
.filter(this::isDefaultPlan)
.filter(plan -> isBlank(plan.get("transportMode"))) .filter(plan -> isBlank(plan.get("transportMode")))
.findFirst(); .findFirst()
if (defaultPlanWithoutTransportMode.isPresent()) return defaultPlanWithoutTransportMode.get(); .orElseGet(() -> defaultPlans.get(defaultPlans.size() - 1));
return plans.stream().noneMatch(this::isDefaultPlan) ? plans.get(plans.size() - 1) : null;
} }
private BigDecimal calculateRule(Map<String, Object> rule, Waybill waybill) { private BigDecimal calculateRule(Map<String, Object> rule, Waybill waybill) {
@@ -1937,42 +1936,28 @@ public class ReceivablePayableDetailServiceImpl
return waybill; return waybill;
} }
/**
* 匹配费用调整试算使用的合同计费规则,仅在合同生效计费方案内查找。
*/
private List<Map<String, Object>> matchingAdjustedRules(ContractManage contract, private List<Map<String, Object>> matchingAdjustedRules(ContractManage contract,
ReceivablePayableCargoFee fee, Waybill waybill) { ReceivablePayableCargoFee fee, Waybill waybill) {
Map<String, Object> plan = resolveDefaultBillingPlan(parseList(contract.getBillingPlanJson()),
waybill.getTransportType());
if (plan == null || !(plan.get("rules") instanceof List<?> rules)) return List.of();
Set<String> feeItemNames = parseMap(fee.getFeeItemsJson()).keySet(); Set<String> feeItemNames = parseMap(fee.getFeeItemsJson()).keySet();
List<Map<String, Object>> firstCandidates = List.of(); List<Map<String, Object>> feeItemCandidates = new ArrayList<>();
List<Map<String, Object>> defaultCandidates = List.of(); List<Map<String, Object>> candidates = new ArrayList<>();
List<Map<String, Object>> billingMatchedCandidates = List.of(); for (Object value : rules) {
List<Map<String, Object>> billingFieldCandidates = List.of(); if (!(value instanceof Map<?, ?> raw)) continue;
for (Map<String, Object> plan : parseList(contract.getBillingPlanJson())) { Map<String, Object> rule = new LinkedHashMap<>();
if (!(plan.get("rules") instanceof List<?> rules)) continue; raw.forEach((key, item) -> rule.put(String.valueOf(key), item));
List<Map<String, Object>> candidates = new ArrayList<>(); if (!feeItemNames.contains(stringValue(rule, "feeItem"))) continue;
List<Map<String, Object>> feeItemCandidates = new ArrayList<>(); feeItemCandidates.add(rule);
for (Object value : rules) { if (matchesRule(rule, waybill)) candidates.add(rule);
if (!(value instanceof Map<?, ?> raw)) continue;
Map<String, Object> rule = new LinkedHashMap<>();
raw.forEach((key, item) -> rule.put(String.valueOf(key), item));
if (!feeItemNames.contains(stringValue(rule, "feeItem"))) continue;
feeItemCandidates.add(rule);
if (matchesRule(rule, waybill)) candidates.add(rule);
}
if (feeItemCandidates.stream().anyMatch(rule -> matchesBillingFields(rule, fee))) {
if (billingFieldCandidates.isEmpty() || isDefaultPlan(plan)) {
billingFieldCandidates = feeItemCandidates;
}
}
if (candidates.isEmpty()) continue;
if (firstCandidates.isEmpty()) firstCandidates = candidates;
if (isDefaultPlan(plan)) defaultCandidates = candidates;
if (candidates.stream().anyMatch(rule -> matchesBillingFields(rule, fee))) {
if (billingMatchedCandidates.isEmpty() || isDefaultPlan(plan)) {
billingMatchedCandidates = candidates;
}
}
} }
if (!billingMatchedCandidates.isEmpty()) return billingMatchedCandidates; if (candidates.stream().anyMatch(rule -> matchesBillingFields(rule, fee))) return candidates;
if (!billingFieldCandidates.isEmpty()) return billingFieldCandidates; if (feeItemCandidates.stream().anyMatch(rule -> matchesBillingFields(rule, fee))) return feeItemCandidates;
return defaultCandidates.isEmpty() ? firstCandidates : defaultCandidates; return candidates;
} }
private boolean matchesBillingFields(Map<String, Object> rule, ReceivablePayableCargoFee fee) { private boolean matchesBillingFields(Map<String, Object> rule, ReceivablePayableCargoFee fee) {
@@ -116,6 +116,9 @@ public class TransportReconciliationServiceImpl
.eq(Func.isNotEmpty(query.getMatchStatus()), TransportReconciliation::getMatchStatus, query.getMatchStatus()) .eq(Func.isNotEmpty(query.getMatchStatus()), TransportReconciliation::getMatchStatus, query.getMatchStatus())
.eq(Func.isNotEmpty(query.getReconciliationStatus()), TransportReconciliation::getReconciliationStatus, query.getReconciliationStatus()) .eq(Func.isNotEmpty(query.getReconciliationStatus()), TransportReconciliation::getReconciliationStatus, query.getReconciliationStatus())
.orderByDesc(TransportReconciliation::getCreateTime); .orderByDesc(TransportReconciliation::getCreateTime);
if (Func.isNotEmpty(query.getIds())) {
wrapper.in(TransportReconciliation::getId, Func.toLongList(query.getIds()));
}
return page(page, wrapper).convert(item -> TransportReconciliationWrapper.build().entityVO(item)); return page(page, wrapper).convert(item -> TransportReconciliationWrapper.build().entityVO(item));
} }
+1
View File
@@ -417,6 +417,7 @@ INSERT IGNORE INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `pa
(2090000000000000612, 2090000000000000600, 'waybill_manage_template', '下载模板', 'waybill_manage_template', '', '', 12, 2, 0, 1, NULL, '', 0), (2090000000000000612, 2090000000000000600, 'waybill_manage_template', '下载模板', 'waybill_manage_template', '', '', 12, 2, 0, 1, NULL, '', 0),
(2090000000000000613, 2090000000000000600, 'waybill_manage_road_loading', '公路配载', 'waybill_manage_road_loading', '', '', 13, 2, 0, 1, NULL, '', 0), (2090000000000000613, 2090000000000000600, 'waybill_manage_road_loading', '公路配载', 'waybill_manage_road_loading', '', '', 13, 2, 0, 1, NULL, '', 0),
(2090000000000000614, 2090000000000000600, 'waybill_manage_mileage', '维护里程', 'waybill_manage_mileage', '', '', 14, 2, 0, 1, NULL, '', 0), (2090000000000000614, 2090000000000000600, 'waybill_manage_mileage', '维护里程', 'waybill_manage_mileage', '', '', 14, 2, 0, 1, NULL, '', 0),
(2090000000000000615, 2090000000000000000, 'waybill_import', '导入运单', 'waybill_import', '/business/waybill-import', 'iconfont icon-daoru', 65, 1, 0, 1, NULL, '', 0),
(2090000000000000700, 2090000000000000000, 'loading_manage', '配载管理', 'loading_manage', '/business/loading-manage', 'iconfont icon-caidanguanli', 70, 1, 0, 1, NULL, '', 0), (2090000000000000700, 2090000000000000000, 'loading_manage', '配载管理', 'loading_manage', '/business/loading-manage', 'iconfont icon-caidanguanli', 70, 1, 0, 1, NULL, '', 0),
(2090000000000000701, 2090000000000000700, 'loading_manage_view', '查看', 'loading_manage_view', '', '', 1, 2, 0, 1, NULL, '', 0), (2090000000000000701, 2090000000000000700, 'loading_manage_view', '查看', 'loading_manage_view', '', '', 1, 2, 0, 1, NULL, '', 0),
(2090000000000000702, 2090000000000000700, 'loading_manage_add', '新增', 'loading_manage_add', '', '', 2, 2, 0, 1, NULL, '', 0), (2090000000000000702, 2090000000000000700, 'loading_manage_add', '新增', 'loading_manage_add', '', '', 2, 2, 0, 1, NULL, '', 0),