完善付款

This commit is contained in:
2026-08-27 18:20:48 +08:00
parent 171887811c
commit cce9ac0f3d
20 changed files with 659 additions and 30 deletions
@@ -29,6 +29,7 @@ public class FormalSettlementSaveRequest implements Serializable {
private String attachmentsJson; private String attachmentsJson;
private String remark; private String remark;
private List<SummaryFee> summaryFees; private List<SummaryFee> summaryFees;
private List<Invoice> invoices;
@Data @Data
public static class SummaryFee implements Serializable { public static class SummaryFee implements Serializable {
@@ -40,4 +41,17 @@ public class FormalSettlementSaveRequest implements Serializable {
private String remark; private String remark;
private Integer manualFlag; private Integer manualFlag;
} }
@Data
public static class Invoice implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private String invoiceNo;
private LocalDate invoiceDate;
private String invoiceType;
private BigDecimal taxRate;
private BigDecimal invoiceAmount;
private BigDecimal availableInvoiceAmount;
private BigDecimal matchedAmount;
private String attachmentJson;
}
} }
@@ -0,0 +1,38 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
* Copyright of this software remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.pojo.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
/** 付款申请付款记录请求。 @author Chill */
@Data
public class PaymentApplicationRecordRequest implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private BigDecimal paidAmount;
private LocalDate paidDate;
private String paymentNo;
private String voucherJson;
private String kingdeeBillNo;
}
@@ -58,4 +58,5 @@ public class PaymentApplicationSaveRequest implements Serializable {
private String attachmentsJson; private String attachmentsJson;
private String remark; private String remark;
private List<PaymentApplicationInvoiceRequest> invoices; private List<PaymentApplicationInvoiceRequest> invoices;
private List<PaymentApplicationRecordRequest> paymentRecords;
} }
@@ -0,0 +1,31 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
import java.time.LocalDateTime;
/** 正式结算变更记录。 @author Chill */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("blade_formal_settlement_change_record")
public class FormalSettlementChangeRecord extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
private Long formalSettlementId;
private String changeType;
private Integer lineNo;
private String operationType;
private String changeContent;
private String changeReason;
private String operatorName;
private LocalDateTime changeTime;
}
@@ -0,0 +1,42 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 正式结算发票明细实体类
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("blade_formal_settlement_invoice")
@Schema(description = "正式结算发票明细")
public class FormalSettlementInvoice extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
private Long formalSettlementId;
private Integer lineNo;
private String invoiceNo;
private LocalDate invoiceDate;
private String invoiceType;
private BigDecimal taxRate;
private BigDecimal invoiceAmount;
private BigDecimal availableInvoiceAmount;
private BigDecimal matchedAmount;
private String attachmentJson;
}
@@ -12,7 +12,9 @@ import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springblade.transport.pojo.entity.FormalSettlement; import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementChangeRecord;
import org.springblade.transport.pojo.entity.FormalSettlementDetail; import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementInvoice;
import org.springblade.transport.pojo.entity.FormalSettlementSource; import org.springblade.transport.pojo.entity.FormalSettlementSource;
import org.springblade.transport.pojo.entity.FormalSettlementPayment; import org.springblade.transport.pojo.entity.FormalSettlementPayment;
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee; import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
@@ -42,4 +44,8 @@ public class FormalSettlementVO extends FormalSettlement {
@TableField(exist = false) private List<FormalSettlementDetail> details; @TableField(exist = false) private List<FormalSettlementDetail> details;
@TableField(exist = false) private List<FormalSettlementSummaryFee> summaryFees; @TableField(exist = false) private List<FormalSettlementSummaryFee> summaryFees;
@TableField(exist = false) private List<FormalSettlementPayment> payments; @TableField(exist = false) private List<FormalSettlementPayment> payments;
@TableField(exist = false) private List<FormalSettlementInvoice> invoices;
@TableField(exist = false) private List<PaymentApplicationVO> paymentApplications;
@TableField(exist = false) private List<SettlementAdjustmentVO> adjustments;
@TableField(exist = false) private List<FormalSettlementChangeRecord> changeRecords;
} }
@@ -0,0 +1,30 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 付款申请关联结算单金额视图
*
* @author Chill
*/
@Data
@Schema(description = "付款申请关联结算单金额")
public class PaymentApplicationReferenceAmountVO implements Serializable {
@Serial private static final long serialVersionUID = 1L;
@Schema(description = "结算金额") private BigDecimal settlementAmount;
@Schema(description = "累计申请付款金额") private BigDecimal cumulativeAppliedAmount;
@Schema(description = "可付款金额") private BigDecimal payableAmount;
}
@@ -17,6 +17,7 @@ public class SettlementAdjustmentVO extends SettlementAdjustment {
private String approvalStatusName; private String approvalStatusName;
private String settlementTypeName; private String settlementTypeName;
private String createUserName; private String createUserName;
private String kingdeeBillNo;
private List<SettlementAdjustmentDetail> details; private List<SettlementAdjustmentDetail> details;
private List<FormalSettlementDetail> formalDetails; private List<FormalSettlementDetail> formalDetails;
} }
@@ -31,6 +31,7 @@ import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tool.api.R; import org.springblade.core.tool.api.R;
import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest; import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest; import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
import org.springblade.transport.pojo.vo.PaymentApplicationVO; import org.springblade.transport.pojo.vo.PaymentApplicationVO;
import org.springblade.transport.service.IPaymentApplicationService; import org.springblade.transport.service.IPaymentApplicationService;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -56,32 +57,39 @@ public class PaymentApplicationController extends BladeController {
@ApiOperationSupport(order = 2) @ApiOperationSupport(order = 2)
@Operation(summary = "付款申请详情") @Operation(summary = "付款申请详情")
public R<PaymentApplicationVO> detail(@RequestParam Long id) { return R.data(paymentApplicationService.detail(id)); } public R<PaymentApplicationVO> detail(@RequestParam Long id) { return R.data(paymentApplicationService.detail(id)); }
@PostMapping("/save") @GetMapping("/reference-amount")
@ApiOperationSupport(order = 3) @ApiOperationSupport(order = 3)
@Operation(summary = "动态计算结算单可付款金额")
public R<PaymentApplicationReferenceAmountVO> referenceAmount(@RequestParam String paymentType,
@RequestParam Long referenceId, @RequestParam(required = false) Long excludeId) {
return R.data(paymentApplicationService.referenceAmount(paymentType, referenceId, excludeId));
}
@PostMapping("/save")
@ApiOperationSupport(order = 4)
@Operation(summary = "保存付款申请") @Operation(summary = "保存付款申请")
public R<Long> save(@RequestBody PaymentApplicationSaveRequest request) { return R.data(paymentApplicationService.saveDraft(request)); } public R<Long> save(@RequestBody PaymentApplicationSaveRequest request) { return R.data(paymentApplicationService.saveDraft(request)); }
@PostMapping("/remove") @PostMapping("/remove")
@ApiOperationSupport(order = 4) @ApiOperationSupport(order = 5)
@Operation(summary = "删除付款申请草稿") @Operation(summary = "删除付款申请草稿")
public R remove(@RequestParam Long id) { paymentApplicationService.removeDraft(id); return R.success("删除成功"); } public R remove(@RequestParam Long id) { paymentApplicationService.removeDraft(id); return R.success("删除成功"); }
@PostMapping("/submit") @PostMapping("/submit")
@ApiOperationSupport(order = 5) @ApiOperationSupport(order = 6)
@Operation(summary = "提交付款申请") @Operation(summary = "提交付款申请")
public R submit(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.submit(request); return R.success("提交成功"); } public R submit(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.submit(request); return R.success("提交成功"); }
@PostMapping("/approve") @PostMapping("/approve")
@ApiOperationSupport(order = 6) @ApiOperationSupport(order = 7)
@Operation(summary = "审批通过付款申请") @Operation(summary = "审批通过付款申请")
public R approve(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.approve(request); return R.success("审批通过"); } public R approve(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.approve(request); return R.success("审批通过"); }
@PostMapping("/return") @PostMapping("/return")
@ApiOperationSupport(order = 7) @ApiOperationSupport(order = 8)
@Operation(summary = "驳回付款申请") @Operation(summary = "驳回付款申请")
public R returnBill(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.returnBill(request); return R.success("已驳回"); } public R returnBill(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.returnBill(request); return R.success("已驳回"); }
@PostMapping("/void") @PostMapping("/void")
@ApiOperationSupport(order = 8) @ApiOperationSupport(order = 9)
@Operation(summary = "作废付款申请") @Operation(summary = "作废付款申请")
public R voidBill(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.voidBill(request); return R.success("作废成功"); } public R voidBill(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.voidBill(request); return R.success("作废成功"); }
@PostMapping("/sync-kingdee") @PostMapping("/sync-kingdee")
@ApiOperationSupport(order = 9) @ApiOperationSupport(order = 10)
@Operation(summary = "生成金蝶付款单") @Operation(summary = "生成金蝶付款单")
public R<String> syncKingdee(@RequestParam Long id) { return R.data(paymentApplicationService.syncKingdee(id)); } public R<String> syncKingdee(@RequestParam Long id) { return R.data(paymentApplicationService.syncKingdee(id)); }
} }
@@ -0,0 +1,16 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springblade.transport.pojo.entity.FormalSettlementChangeRecord;
/** 正式结算变更记录 Mapper。 @author Chill */
@Mapper
public interface FormalSettlementChangeRecordMapper extends BaseMapper<FormalSettlementChangeRecord> {
}
@@ -0,0 +1,22 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springblade.transport.pojo.entity.FormalSettlementInvoice;
/**
* 正式结算发票明细 Mapper
*
* @author Chill
*/
@Mapper
public interface FormalSettlementInvoiceMapper extends BaseMapper<FormalSettlementInvoice> {
}
@@ -24,12 +24,14 @@ import org.springblade.core.mp.base.BaseService;
import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest; import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest; import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
import org.springblade.transport.pojo.entity.PaymentApplication; import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
import org.springblade.transport.pojo.vo.PaymentApplicationVO; import org.springblade.transport.pojo.vo.PaymentApplicationVO;
/** 付款申请服务。 @author Chill */ /** 付款申请服务。 @author Chill */
public interface IPaymentApplicationService extends BaseService<PaymentApplication> { public interface IPaymentApplicationService extends BaseService<PaymentApplication> {
IPage<PaymentApplicationVO> selectPage(IPage<PaymentApplication> page, PaymentApplicationVO query); IPage<PaymentApplicationVO> selectPage(IPage<PaymentApplication> page, PaymentApplicationVO query);
PaymentApplicationVO detail(Long id); PaymentApplicationVO detail(Long id);
PaymentApplicationReferenceAmountVO referenceAmount(String paymentType, Long referenceId, Long excludeId);
Long saveDraft(PaymentApplicationSaveRequest request); Long saveDraft(PaymentApplicationSaveRequest request);
void removeDraft(Long id); void removeDraft(Long id);
void submit(PaymentApplicationStatusRequest request); void submit(PaymentApplicationStatusRequest request);
@@ -19,7 +19,9 @@ import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.Func;
import org.springblade.transport.mapper.FormalSettlementDetailMapper; import org.springblade.transport.mapper.FormalSettlementDetailMapper;
import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper; import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper;
import org.springblade.transport.mapper.FormalSettlementChangeRecordMapper;
import org.springblade.transport.mapper.FormalSettlementMapper; import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.mapper.FormalSettlementInvoiceMapper;
import org.springblade.transport.mapper.FormalSettlementSourceMapper; import org.springblade.transport.mapper.FormalSettlementSourceMapper;
import org.springblade.transport.mapper.FormalSettlementSummaryFeeMapper; import org.springblade.transport.mapper.FormalSettlementSummaryFeeMapper;
import org.springblade.transport.mapper.FormalSettlementPaymentMapper; import org.springblade.transport.mapper.FormalSettlementPaymentMapper;
@@ -27,15 +29,19 @@ import org.springblade.transport.mapper.PreSettlementDetailMapper;
import org.springblade.transport.mapper.PreSettlementDetailFeeMapper; import org.springblade.transport.mapper.PreSettlementDetailFeeMapper;
import org.springblade.transport.mapper.PreSettlementMapper; import org.springblade.transport.mapper.PreSettlementMapper;
import org.springblade.transport.mapper.PreSettlementSummaryFeeMapper; import org.springblade.transport.mapper.PreSettlementSummaryFeeMapper;
import org.springblade.transport.mapper.PaymentApplicationMapper;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper; import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper; import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.mapper.SettlementAdjustmentMapper;
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.FormalSettlementStatusRequest; import org.springblade.transport.pojo.dto.FormalSettlementStatusRequest;
import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest; import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest;
import org.springblade.transport.pojo.entity.FormalSettlement; import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementChangeRecord;
import org.springblade.transport.pojo.entity.FormalSettlementDetail; import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee; import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.entity.FormalSettlementInvoice;
import org.springblade.transport.pojo.entity.FormalSettlementSource; import org.springblade.transport.pojo.entity.FormalSettlementSource;
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee; import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
import org.springblade.transport.pojo.entity.FormalSettlementPayment; import org.springblade.transport.pojo.entity.FormalSettlementPayment;
@@ -44,11 +50,14 @@ import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.entity.PreSettlementDetail; import org.springblade.transport.pojo.entity.PreSettlementDetail;
import org.springblade.transport.pojo.entity.PreSettlementDetailFee; import org.springblade.transport.pojo.entity.PreSettlementDetailFee;
import org.springblade.transport.pojo.entity.PreSettlementSummaryFee; import org.springblade.transport.pojo.entity.PreSettlementSummaryFee;
import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail; import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee; import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
import org.springblade.transport.pojo.entity.SettlementAdjustment;
import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.FormalSettlementVO; import org.springblade.transport.pojo.vo.FormalSettlementVO;
import org.springblade.transport.pojo.vo.PreSettlementVO; import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.pojo.vo.SettlementAdjustmentVO;
import org.springblade.transport.service.IFormalSettlementService; import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.service.IContractManageService; import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.service.IPreSettlementService; import org.springblade.transport.service.IPreSettlementService;
@@ -56,6 +65,8 @@ import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.wrapper.PreSettlementWrapper; import org.springblade.transport.wrapper.PreSettlementWrapper;
import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.transport.wrapper.FormalSettlementWrapper; import org.springblade.transport.wrapper.FormalSettlementWrapper;
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
import org.springblade.system.cache.UserCache;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -91,6 +102,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private final FormalSettlementSourceMapper sourceMapper; private final FormalSettlementSourceMapper sourceMapper;
private final FormalSettlementSummaryFeeMapper summaryFeeMapper; private final FormalSettlementSummaryFeeMapper summaryFeeMapper;
private final FormalSettlementPaymentMapper paymentMapper; private final FormalSettlementPaymentMapper paymentMapper;
private final FormalSettlementInvoiceMapper invoiceMapper;
private final FormalSettlementChangeRecordMapper changeRecordMapper;
private final FormalSettlementDetailMapper detailMapper; private final FormalSettlementDetailMapper detailMapper;
private final FormalSettlementDetailFeeMapper detailFeeMapper; private final FormalSettlementDetailFeeMapper detailFeeMapper;
private final PreSettlementMapper preSettlementMapper; private final PreSettlementMapper preSettlementMapper;
@@ -99,6 +112,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private final PreSettlementDetailFeeMapper preDetailFeeMapper; private final PreSettlementDetailFeeMapper preDetailFeeMapper;
private final ReceivablePayableDetailMapper receivablePayableMapper; private final ReceivablePayableDetailMapper receivablePayableMapper;
private final ReceivablePayableCargoFeeMapper receivablePayableCargoFeeMapper; private final ReceivablePayableCargoFeeMapper receivablePayableCargoFeeMapper;
private final PaymentApplicationMapper paymentApplicationMapper;
private final SettlementAdjustmentMapper settlementAdjustmentMapper;
private final IContractManageService contractManageService; private final IContractManageService contractManageService;
private final IPreSettlementService preSettlementService; private final IPreSettlementService preSettlementService;
private final IWaybillService waybillService; private final IWaybillService waybillService;
@@ -172,13 +187,34 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
public FormalSettlementVO detail(Long id) { public FormalSettlementVO detail(Long id) {
FormalSettlement settlement = existing(id); FormalSettlement settlement = existing(id);
FormalSettlementVO vo = toVO(settlement); FormalSettlementVO vo = toVO(settlement);
vo.setSources(sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery() List<FormalSettlementSource> sources = sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, id).orderByAsc(FormalSettlementSource::getCreateTime))); .eq(FormalSettlementSource::getFormalSettlementId, id).orderByAsc(FormalSettlementSource::getCreateTime));
vo.setSources(sources);
vo.setDetails(detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery() vo.setDetails(detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.eq(FormalSettlementDetail::getFormalSettlementId, id).orderByAsc(FormalSettlementDetail::getLineNo))); .eq(FormalSettlementDetail::getFormalSettlementId, id).orderByAsc(FormalSettlementDetail::getLineNo)));
vo.setSummaryFees(listSummaryFees(id)); vo.setSummaryFees(listSummaryFees(id));
vo.setPayments(paymentMapper.selectList(Wrappers.<FormalSettlementPayment>lambdaQuery() vo.setPayments(paymentMapper.selectList(Wrappers.<FormalSettlementPayment>lambdaQuery()
.eq(FormalSettlementPayment::getFormalSettlementId, id).orderByDesc(FormalSettlementPayment::getCreateTime))); .eq(FormalSettlementPayment::getFormalSettlementId, id).orderByDesc(FormalSettlementPayment::getCreateTime)));
vo.setInvoices(invoiceMapper.selectList(Wrappers.<FormalSettlementInvoice>lambdaQuery()
.eq(FormalSettlementInvoice::getFormalSettlementId, id).orderByAsc(FormalSettlementInvoice::getLineNo)));
List<Long> preSettlementIds = sources.stream().map(FormalSettlementSource::getPreSettlementId).toList();
LambdaQueryWrapper<PaymentApplication> paymentApplicationQuery = Wrappers.<PaymentApplication>lambdaQuery();
if (preSettlementIds.isEmpty()) {
paymentApplicationQuery.eq(PaymentApplication::getSettlementId, id);
} else {
paymentApplicationQuery.and(query -> query.eq(PaymentApplication::getSettlementId, id)
.or().in(PaymentApplication::getPreSettlementId, preSettlementIds));
}
vo.setPaymentApplications(paymentApplicationMapper.selectList(paymentApplicationQuery
.orderByDesc(PaymentApplication::getCreateTime)).stream()
.map(item -> PaymentApplicationWrapper.build().entityVO(item)).toList());
vo.setAdjustments(settlementAdjustmentMapper.selectList(Wrappers.<SettlementAdjustment>lambdaQuery()
.eq(SettlementAdjustment::getFormalSettlementId, id)
.orderByDesc(SettlementAdjustment::getCreateTime)).stream()
.map(item -> toAdjustmentVO(item, settlement.getKingdeeBillNo())).toList());
vo.setChangeRecords(changeRecordMapper.selectList(Wrappers.<FormalSettlementChangeRecord>lambdaQuery()
.eq(FormalSettlementChangeRecord::getFormalSettlementId, id)
.orderByDesc(FormalSettlementChangeRecord::getChangeTime)));
return vo; return vo;
} }
@@ -189,6 +225,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
throw new ServiceException("请至少选择一张预结算单或一条应收应付明细"); throw new ServiceException("请至少选择一张预结算单或一条应收应付明细");
} }
FormalSettlement settlement = request.getId() == null ? new FormalSettlement() : editable(request.getId()); FormalSettlement settlement = request.getId() == null ? new FormalSettlement() : editable(request.getId());
boolean creating = settlement.getId() == null;
if (settlement.getId() != null) releaseSources(settlement); if (settlement.getId() != null) releaseSources(settlement);
List<PreSettlement> sources = Func.isEmpty(request.getSourcePreSettlementIds()) ? List.of() List<PreSettlement> sources = Func.isEmpty(request.getSourcePreSettlementIds()) ? List.of()
: request.getSourcePreSettlementIds().stream().distinct().map(this::availableSource).toList(); : request.getSourcePreSettlementIds().stream().distinct().map(this::availableSource).toList();
@@ -241,6 +278,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
rebuildSummaryFees(settlement.getId()); rebuildSummaryFees(settlement.getId());
applySummaryRequest(settlement.getId(), request.getSummaryFees()); applySummaryRequest(settlement.getId(), request.getSummaryFees());
refreshSettlementAmount(settlement); refreshSettlementAmount(settlement);
rebuildInvoices(settlement, request.getInvoices());
saveChange(settlement.getId(), "结算单基本信息", null, creating ? "新增" : "调整",
(creating ? "创建" : "保存") + "正式结算单" + settlement.getFormalSettlementNo(), request.getRemark());
return settlement.getId(); return settlement.getId();
} }
@@ -256,6 +296,10 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
detailMapper.delete(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, id)); detailMapper.delete(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, id));
summaryFeeMapper.delete(Wrappers.<FormalSettlementSummaryFee>lambdaQuery() summaryFeeMapper.delete(Wrappers.<FormalSettlementSummaryFee>lambdaQuery()
.eq(FormalSettlementSummaryFee::getFormalSettlementId, id)); .eq(FormalSettlementSummaryFee::getFormalSettlementId, id));
invoiceMapper.delete(Wrappers.<FormalSettlementInvoice>lambdaQuery()
.eq(FormalSettlementInvoice::getFormalSettlementId, id));
changeRecordMapper.delete(Wrappers.<FormalSettlementChangeRecord>lambdaQuery()
.eq(FormalSettlementChangeRecord::getFormalSettlementId, id));
removeById(id); removeById(id);
} }
@@ -271,6 +315,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setCurrentProcessor(AuthUtil.getUserName()); settlement.setCurrentProcessor(AuthUtil.getUserName());
settlement.setApprovedTime(LocalDateTime.now()); settlement.setApprovedTime(LocalDateTime.now());
updateById(settlement); updateById(settlement);
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "正式结算审批通过", request.getReason());
} }
@Override @Override
@@ -282,6 +327,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setCurrentNode("已作废"); settlement.setCurrentNode("已作废");
settlement.setVoidReason(required(limit(request.getReason(), 200), "作废原因")); settlement.setVoidReason(required(limit(request.getReason(), 200), "作废原因"));
updateById(settlement); updateById(settlement);
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "作废正式结算单", request.getReason());
} }
@Override @Override
@@ -294,6 +340,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setKingdeeSyncStatus("synced"); settlement.setKingdeeSyncStatus("synced");
settlement.setSyncedTime(LocalDateTime.now()); settlement.setSyncedTime(LocalDateTime.now());
updateById(settlement); updateById(settlement);
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "同步金蝶单据" + kingdeeNo, "");
return kingdeeNo; return kingdeeNo;
} }
@@ -340,9 +387,11 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
Map<Long, FormalSettlementDetailFee> existing = detailFees(detail.getId()).stream() Map<Long, FormalSettlementDetailFee> existing = detailFees(detail.getId()).stream()
.collect(Collectors.toMap(FormalSettlementDetailFee::getId, item -> item)); .collect(Collectors.toMap(FormalSettlementDetailFee::getId, item -> item));
if (existing.size() != request.getRows().size()) throw new ServiceException("费用调整行数据不完整"); if (existing.size() != request.getRows().size()) throw new ServiceException("费用调整行数据不完整");
List<String> changes = new java.util.ArrayList<>();
for (PreSettlementDetailAdjustRequest.FeeRow row : request.getRows()) { for (PreSettlementDetailAdjustRequest.FeeRow row : request.getRows()) {
FormalSettlementDetailFee fee = existing.get(row.getId()); FormalSettlementDetailFee fee = existing.get(row.getId());
if (fee == null) throw new ServiceException("存在无效的货物费用行"); if (fee == null) throw new ServiceException("存在无效的货物费用行");
BigDecimal beforeAmount = money(fee.getSettlementAmountTax());
fee.setTransportQuantity(nonNegative(row.getTransportQuantity(), "运输总量")); fee.setTransportQuantity(nonNegative(row.getTransportQuantity(), "运输总量"));
fee.setMileage(nonNegative(row.getMileage(), "里程")); fee.setMileage(nonNegative(row.getMileage(), "里程"));
fee.setUnitPrice(nonNegative(row.getUnitPrice(), "运输单价")); fee.setUnitPrice(nonNegative(row.getUnitPrice(), "运输单价"));
@@ -353,6 +402,11 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
fee.setAdjustAmount(fee.getSettlementAmountTax().subtract(money(fee.getOriginalAmount()))); fee.setAdjustAmount(fee.getSettlementAmountTax().subtract(money(fee.getOriginalAmount())));
fee.setRemark(limit(row.getRemark(), 200)); fee.setRemark(limit(row.getRemark(), 200));
detailFeeMapper.updateById(fee); detailFeeMapper.updateById(fee);
if (beforeAmount.compareTo(fee.getSettlementAmountTax()) != 0) {
changes.add("" + firstNotEmpty(fee.getCargoName(), fee.getLineNo())
+ "】结算金额(含税)从【" + beforeAmount + "】调整为【"
+ fee.getSettlementAmountTax() + "");
}
} }
List<FormalSettlementDetailFee> rows = detailFees(detail.getId()); List<FormalSettlementDetailFee> rows = detailFees(detail.getId());
detail.setTransportQuantity(rows.stream().map(FormalSettlementDetailFee::getTransportQuantity).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add)); detail.setTransportQuantity(rows.stream().map(FormalSettlementDetailFee::getTransportQuantity).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
@@ -365,6 +419,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
detailMapper.updateById(detail); detailMapper.updateById(detail);
rebuildSummaryFees(settlement.getId()); rebuildSummaryFees(settlement.getId());
refreshSettlementAmount(settlement); refreshSettlementAmount(settlement);
saveChange(settlement.getId(), "结算明细项", detail.getLineNo(), "调整",
changes.isEmpty() ? "调整结算明细费用" : String.join("", changes), reason);
} }
private void rebuildSummaryFees(Long settlementId) { private void rebuildSummaryFees(Long settlementId) {
@@ -512,6 +568,51 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
renumberSummaryFees(settlementId); renumberSummaryFees(settlementId);
} }
private void rebuildInvoices(FormalSettlement settlement, List<FormalSettlementSaveRequest.Invoice> requestRows) {
List<FormalSettlementSaveRequest.Invoice> invoices = requestRows == null ? List.of() : requestRows;
Set<String> invoiceNumbers = new LinkedHashSet<>();
BigDecimal matchedTotal = BigDecimal.ZERO;
for (FormalSettlementSaveRequest.Invoice requestRow : invoices) {
String invoiceNo = requiredText(requestRow.getInvoiceNo(), "发票号");
if (invoiceNo.length() > 32) throw new ServiceException("发票号不能超过32个字");
if (!invoiceNumbers.add(invoiceNo)) throw new ServiceException("发票号" + invoiceNo + "重复");
BigDecimal invoiceAmount = nonNegative(requestRow.getInvoiceAmount(), "发票金额");
BigDecimal availableAmount = nonNegative(requestRow.getAvailableInvoiceAmount(), "可匹配发票金额");
BigDecimal matchedAmount = nonNegative(requestRow.getMatchedAmount(), "匹配结算单金额");
if (availableAmount.compareTo(invoiceAmount) > 0) {
throw new ServiceException("发票" + invoiceNo + "的可匹配金额不能超过发票金额");
}
if (matchedAmount.compareTo(availableAmount) > 0) {
throw new ServiceException("发票" + invoiceNo + "的匹配结算单金额不能超过可匹配金额");
}
if (requestRow.getTaxRate() != null && (requestRow.getTaxRate().signum() < 0
|| requestRow.getTaxRate().compareTo(BigDecimal.valueOf(100)) > 0)) {
throw new ServiceException("发票" + invoiceNo + "的税率必须在0-100之间");
}
matchedTotal = matchedTotal.add(matchedAmount);
}
if (matchedTotal.compareTo(money(settlement.getSettlementAmount())) > 0) {
throw new ServiceException("发票匹配结算单金额合计不能超过结算金额");
}
invoiceMapper.delete(Wrappers.<FormalSettlementInvoice>lambdaQuery()
.eq(FormalSettlementInvoice::getFormalSettlementId, settlement.getId()));
int lineNo = 1;
for (FormalSettlementSaveRequest.Invoice requestRow : invoices) {
FormalSettlementInvoice invoice = new FormalSettlementInvoice();
invoice.setFormalSettlementId(settlement.getId());
invoice.setLineNo(lineNo++);
invoice.setInvoiceNo(requestRow.getInvoiceNo().trim());
invoice.setInvoiceDate(requestRow.getInvoiceDate());
invoice.setInvoiceType(limit(requestRow.getInvoiceType(), 50));
invoice.setTaxRate(requestRow.getTaxRate());
invoice.setInvoiceAmount(money(requestRow.getInvoiceAmount()));
invoice.setAvailableInvoiceAmount(money(requestRow.getAvailableInvoiceAmount()));
invoice.setMatchedAmount(money(requestRow.getMatchedAmount()));
invoice.setAttachmentJson(requestRow.getAttachmentJson());
invoiceMapper.insert(invoice);
}
}
private void refreshSettlementAmount(FormalSettlement settlement) { private void refreshSettlementAmount(FormalSettlement settlement) {
BigDecimal amount = listSummaryFees(settlement.getId()).stream() BigDecimal amount = listSummaryFees(settlement.getId()).stream()
.map(FormalSettlementSummaryFee::getSettlementAmount).map(this::money) .map(FormalSettlementSummaryFee::getSettlementAmount).map(this::money)
@@ -707,6 +808,27 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
return vo; return vo;
} }
private SettlementAdjustmentVO toAdjustmentVO(SettlementAdjustment entity, String kingdeeBillNo) {
SettlementAdjustmentVO vo = Objects.requireNonNull(
BeanUtil.copyProperties(entity, SettlementAdjustmentVO.class));
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser()));
vo.setApprovalStatusName(approvalStatusName(entity.getApprovalStatus()));
vo.setSettlementTypeName("receivable".equals(entity.getSettlementType()) ? "应收" : "应付");
vo.setKingdeeBillNo(kingdeeBillNo);
return vo;
}
private String approvalStatusName(String value) {
return switch (value == null ? "" : value) {
case DRAFT -> "草稿";
case REVIEWING -> "审批中";
case APPROVED -> "审批通过";
case RETURNED -> "已驳回";
case VOIDED -> "已作废";
default -> value;
};
}
private PreSettlement availableSource(Long id) { private PreSettlement availableSource(Long id) {
PreSettlement source = preSettlementMapper.selectById(id); PreSettlement source = preSettlementMapper.selectById(id);
if (source == null || Objects.equals(source.getIsDeleted(), 1)) throw new ServiceException("预结算单不存在"); if (source == null || Objects.equals(source.getIsDeleted(), 1)) throw new ServiceException("预结算单不存在");
@@ -742,6 +864,21 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setApprovalStatus(target); settlement.setCurrentNode(node); settlement.setCurrentProcessor(AuthUtil.getUserName()); settlement.setApprovalStatus(target); settlement.setCurrentNode(node); settlement.setCurrentProcessor(AuthUtil.getUserName());
if (RETURNED.equals(target)) settlement.setVoidReason(limit(reason, 200)); if (RETURNED.equals(target)) settlement.setVoidReason(limit(reason, 200));
updateById(settlement); updateById(settlement);
saveChange(settlement.getId(), "结算单基本信息", null, "调整", node, reason);
}
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
String content, String reason) {
FormalSettlementChangeRecord record = new FormalSettlementChangeRecord();
record.setFormalSettlementId(settlementId);
record.setChangeType(changeType);
record.setLineNo(lineNo);
record.setOperationType(operationType);
record.setChangeContent(content);
record.setChangeReason(reason);
record.setOperatorName(Func.isEmpty(AuthUtil.getUserName()) ? "system" : AuthUtil.getUserName());
record.setChangeTime(LocalDateTime.now());
changeRecordMapper.insert(record);
} }
private void copyHeader(PreSettlement source, FormalSettlement target) { private void copyHeader(PreSettlement source, FormalSettlement target) {
@@ -771,5 +908,6 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private BigDecimal nonNegative(BigDecimal value, String field) { if (value == null || value.signum() < 0) throw new ServiceException(field + "不能小于0"); return value; } private BigDecimal nonNegative(BigDecimal value, String field) { if (value == null || value.signum() < 0) throw new ServiceException(field + "不能小于0"); return value; }
private BigDecimal positive(BigDecimal value, String field) { if (value == null || value.signum() <= 0) throw new ServiceException(field + "必须大于0"); return value; } private BigDecimal positive(BigDecimal value, String field) { if (value == null || value.signum() <= 0) throw new ServiceException(field + "必须大于0"); return value; }
private String required(String value, String field) { if (Func.isEmpty(value)) throw new ServiceException("请填写" + field); return value; } private String required(String value, String field) { if (Func.isEmpty(value)) throw new ServiceException("请填写" + field); return value; }
private String firstNotEmpty(Object first, Object second) { return Func.isNotEmpty(first) ? String.valueOf(first) : String.valueOf(second); }
private String limit(String value, int max) { if (value != null && value.length() > max) throw new ServiceException("内容不能超过" + max + "个字"); return value; } private String limit(String value, int max) { if (value != null && value.length() > max) throw new ServiceException("内容不能超过" + max + "个字"); return value; }
} }
@@ -35,10 +35,13 @@ import org.springblade.transport.mapper.BillLedgerUsageMapper;
import org.springblade.transport.mapper.PreSettlementMapper; import org.springblade.transport.mapper.PreSettlementMapper;
import org.springblade.transport.mapper.ProjectApplyMapper; import org.springblade.transport.mapper.ProjectApplyMapper;
import org.springblade.transport.mapper.CustomerArchiveMapper; import org.springblade.transport.mapper.CustomerArchiveMapper;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.PaymentApplicationInvoiceMapper; import org.springblade.transport.mapper.PaymentApplicationInvoiceMapper;
import org.springblade.transport.mapper.PaymentApplicationMapper; import org.springblade.transport.mapper.PaymentApplicationMapper;
import org.springblade.transport.mapper.PaymentApplicationRecordMapper; import org.springblade.transport.mapper.PaymentApplicationRecordMapper;
import org.springblade.transport.mapper.TemporaryCreditLimitMapper;
import org.springblade.transport.pojo.dto.PaymentApplicationInvoiceRequest; import org.springblade.transport.pojo.dto.PaymentApplicationInvoiceRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationRecordRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest; import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest; import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
import org.springblade.transport.pojo.entity.FormalSettlement; import org.springblade.transport.pojo.entity.FormalSettlement;
@@ -47,9 +50,13 @@ import org.springblade.transport.pojo.entity.BillLedgerUsage;
import org.springblade.transport.pojo.entity.PreSettlement; import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.entity.ProjectApply; import org.springblade.transport.pojo.entity.ProjectApply;
import org.springblade.transport.pojo.entity.CustomerArchive; import org.springblade.transport.pojo.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.PaymentApplication; import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.pojo.entity.PaymentApplicationInvoice; import org.springblade.transport.pojo.entity.PaymentApplicationInvoice;
import org.springblade.transport.pojo.entity.PaymentApplicationRecord;
import org.springblade.transport.pojo.entity.TemporaryCreditLimit;
import org.springblade.transport.pojo.vo.PaymentApplicationVO; import org.springblade.transport.pojo.vo.PaymentApplicationVO;
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
import org.springblade.transport.service.IPaymentApplicationService; import org.springblade.transport.service.IPaymentApplicationService;
import org.springblade.transport.wrapper.PaymentApplicationWrapper; import org.springblade.transport.wrapper.PaymentApplicationWrapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -61,7 +68,9 @@ import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
/** 付款申请服务实现。 @author Chill */ /** 付款申请服务实现。 @author Chill */
@Service @Service
@@ -78,9 +87,11 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
private final PreSettlementMapper preSettlementMapper; private final PreSettlementMapper preSettlementMapper;
private final FormalSettlementMapper formalSettlementMapper; private final FormalSettlementMapper formalSettlementMapper;
private final ProjectApplyMapper projectApplyMapper; private final ProjectApplyMapper projectApplyMapper;
private final ContractManageMapper contractManageMapper;
private final CustomerArchiveMapper customerArchiveMapper; private final CustomerArchiveMapper customerArchiveMapper;
private final BillLedgerMapper billLedgerMapper; private final BillLedgerMapper billLedgerMapper;
private final BillLedgerUsageMapper billLedgerUsageMapper; private final BillLedgerUsageMapper billLedgerUsageMapper;
private final TemporaryCreditLimitMapper temporaryCreditLimitMapper;
@Override @Override
public IPage<PaymentApplicationVO> selectPage(IPage<PaymentApplication> page, PaymentApplicationVO query) { public IPage<PaymentApplicationVO> selectPage(IPage<PaymentApplication> page, PaymentApplicationVO query) {
@@ -114,6 +125,39 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
return vo; return vo;
} }
@Override
public PaymentApplicationReferenceAmountVO referenceAmount(String paymentType, Long referenceId, Long excludeId) {
if (referenceId == null) throw new ServiceException("结算单不能为空");
BigDecimal settlementAmount;
if ("settlement_payment".equals(paymentType)) {
FormalSettlement settlement = formalSettlementMapper.selectById(referenceId);
if (settlement == null || Objects.equals(settlement.getIsDeleted(), 1)) {
throw new ServiceException("正式结算单不存在");
}
if (!APPROVED.equals(settlement.getApprovalStatus())) {
throw new ServiceException("只能选择审批通过、未作废的正式结算单");
}
settlementAmount = money(settlement.getSettlementAmount());
} else if ("progress_advance".equals(paymentType)) {
PreSettlement settlement = preSettlementMapper.selectById(referenceId);
if (settlement == null || Objects.equals(settlement.getIsDeleted(), 1)) {
throw new ServiceException("预结算单不存在");
}
if (!APPROVED.equals(settlement.getApprovalStatus())) {
throw new ServiceException("只能选择审批通过、未作废的预结算单");
}
settlementAmount = money(settlement.getSettlementAmount());
} else {
throw new ServiceException("付款类型不支持关联结算单");
}
BigDecimal cumulativeAppliedAmount = cumulativeAppliedAmount(paymentType, referenceId, excludeId);
PaymentApplicationReferenceAmountVO amount = new PaymentApplicationReferenceAmountVO();
amount.setSettlementAmount(settlementAmount);
amount.setCumulativeAppliedAmount(cumulativeAppliedAmount);
amount.setPayableAmount(settlementAmount.subtract(cumulativeAppliedAmount));
return amount;
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Long saveDraft(PaymentApplicationSaveRequest request) { public Long saveDraft(PaymentApplicationSaveRequest request) {
@@ -139,6 +183,14 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
entity.setRemark(limit(request.getRemark(), 200)); entity.setRemark(limit(request.getRemark(), 200));
entity.setApplyDate(entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate()); entity.setApplyDate(entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate());
entity.setApplicantName(Func.isEmpty(entity.getApplicantName()) ? AuthUtil.getUserName() : entity.getApplicantName()); entity.setApplicantName(Func.isEmpty(entity.getApplicantName()) ? AuthUtil.getUserName() : entity.getApplicantName());
List<PaymentApplicationInvoiceRequest> invoiceRequests = ("project_advance".equals(request.getPaymentType())
|| request.getInvoices() == null) ? List.of() : request.getInvoices();
BigDecimal matchedInvoiceAmount = BigDecimal.ZERO;
for (PaymentApplicationInvoiceRequest item : invoiceRequests) {
validateInvoice(item);
matchedInvoiceAmount = matchedInvoiceAmount.add(money(item.getMatchedAmount()));
}
entity.setMatchedInvoiceAmount(matchedInvoiceAmount);
fillReference(entity, request); fillReference(entity, request);
fillBillLedger(entity, request); fillBillLedger(entity, request);
validateQuota(entity); validateQuota(entity);
@@ -146,27 +198,38 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
invoiceMapper.delete(Wrappers.<PaymentApplicationInvoice>lambdaQuery() invoiceMapper.delete(Wrappers.<PaymentApplicationInvoice>lambdaQuery()
.eq(PaymentApplicationInvoice::getPaymentApplicationId, entity.getId())); .eq(PaymentApplicationInvoice::getPaymentApplicationId, entity.getId()));
int lineNo = 1; int lineNo = 1;
BigDecimal matchedInvoiceAmount = BigDecimal.ZERO; for (PaymentApplicationInvoiceRequest item : invoiceRequests) {
for (PaymentApplicationInvoiceRequest item : request.getInvoices() == null ? List.<PaymentApplicationInvoiceRequest>of() : request.getInvoices()) {
validateInvoice(item);
PaymentApplicationInvoice invoice = Objects.requireNonNull(BeanUtil.copyProperties(item, PaymentApplicationInvoice.class)); PaymentApplicationInvoice invoice = Objects.requireNonNull(BeanUtil.copyProperties(item, PaymentApplicationInvoice.class));
invoice.setId(null); invoice.setId(null);
invoice.setPaymentApplicationId(entity.getId()); invoice.setPaymentApplicationId(entity.getId());
invoice.setLineNo(lineNo++); invoice.setLineNo(lineNo++);
invoiceMapper.insert(invoice); invoiceMapper.insert(invoice);
matchedInvoiceAmount = matchedInvoiceAmount.add(money(item.getMatchedAmount()));
} }
if (matchedInvoiceAmount.compareTo(money(entity.getAppliedAmount())) > 0) { recordMapper.delete(Wrappers.<PaymentApplicationRecord>lambdaQuery()
throw new ServiceException("发票匹配金额合计不能超过申请付款金额"); .eq(PaymentApplicationRecord::getPaymentApplicationId, entity.getId()));
BigDecimal paidAmount = BigDecimal.ZERO;
List<PaymentApplicationRecordRequest> recordRequests = ("project_advance".equals(request.getPaymentType())
|| request.getPaymentRecords() == null) ? List.of() : request.getPaymentRecords();
for (PaymentApplicationRecordRequest item : recordRequests) {
validatePaymentRecord(item);
PaymentApplicationRecord record = Objects.requireNonNull(BeanUtil.copyProperties(item, PaymentApplicationRecord.class));
record.setId(null);
record.setPaymentApplicationId(entity.getId());
recordMapper.insert(record);
paidAmount = paidAmount.add(money(item.getPaidAmount()));
}
if (paidAmount.compareTo(money(entity.getAppliedAmount())) > 0) {
throw new ServiceException("付款记录金额合计不能超过申请付款金额");
} }
entity.setMatchedInvoiceAmount(matchedInvoiceAmount); entity.setMatchedInvoiceAmount(matchedInvoiceAmount);
entity.setInvoiceStatus(matchedInvoiceAmount.compareTo(BigDecimal.ZERO) > 0 ? "matched" : "unmatched"); entity.setInvoiceStatus(matchedInvoiceAmount.compareTo(BigDecimal.ZERO) > 0 ? "matched" : "unmatched");
entity.setPaidAmount(paidAmount);
updateById(entity); updateById(entity);
return entity.getId(); return entity.getId();
} }
@Override @Transactional(rollbackFor = Exception.class) @Override @Transactional(rollbackFor = Exception.class)
public void removeDraft(Long id) { PaymentApplication entity = editable(id); invoiceMapper.delete(Wrappers.<PaymentApplicationInvoice>lambdaQuery().eq(PaymentApplicationInvoice::getPaymentApplicationId, id)); removeById(entity); } public void removeDraft(Long id) { PaymentApplication entity = editable(id); invoiceMapper.delete(Wrappers.<PaymentApplicationInvoice>lambdaQuery().eq(PaymentApplicationInvoice::getPaymentApplicationId, id)); recordMapper.delete(Wrappers.<PaymentApplicationRecord>lambdaQuery().eq(PaymentApplicationRecord::getPaymentApplicationId, id)); removeById(entity); }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void submit(PaymentApplicationStatusRequest request) { public void submit(PaymentApplicationStatusRequest request) {
@@ -174,7 +237,9 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
if (!List.of(DRAFT, RETURNED).contains(entity.getApprovalStatus())) { if (!List.of(DRAFT, RETURNED).contains(entity.getApprovalStatus())) {
throw new ServiceException("当前状态不允许提交"); throw new ServiceException("当前状态不允许提交");
} }
validateProjectAdvanceContractFile(entity);
validateSelectedBill(entity, false); validateSelectedBill(entity, false);
validateQuota(entity);
entity.setApprovalStatus(REVIEWING); entity.setApprovalStatus(REVIEWING);
entity.setCurrentNode("财务审核"); entity.setCurrentNode("财务审核");
entity.setCurrentProcessor(AuthUtil.getUserName()); entity.setCurrentProcessor(AuthUtil.getUserName());
@@ -228,6 +293,16 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
} }
private void validateInvoice(PaymentApplicationInvoiceRequest invoice) { private void validateInvoice(PaymentApplicationInvoiceRequest invoice) {
if (invoice == null) throw new ServiceException("发票信息不能为空");
if (invoice.getSettlementNo() != null && invoice.getSettlementNo().length() > 100) {
throw new ServiceException("结算单号不能超过100个字符");
}
if (invoice.getInvoiceNo() != null && invoice.getInvoiceNo().length() > 100) {
throw new ServiceException("发票号不能超过100个字符");
}
if (invoice.getInvoiceType() != null && invoice.getInvoiceType().length() > 30) {
throw new ServiceException("发票类型不能超过30个字符");
}
BigDecimal invoiceAmount = money(invoice.getInvoiceAmount()); BigDecimal invoiceAmount = money(invoice.getInvoiceAmount());
BigDecimal matchedAmount = money(invoice.getMatchedAmount()); BigDecimal matchedAmount = money(invoice.getMatchedAmount());
if (invoiceAmount.compareTo(BigDecimal.ZERO) < 0) { if (invoiceAmount.compareTo(BigDecimal.ZERO) < 0) {
@@ -245,18 +320,66 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
} }
} }
private void validatePaymentRecord(PaymentApplicationRecordRequest record) {
if (record == null) throw new ServiceException("付款记录不能为空");
if (record.getPaidAmount() == null || record.getPaidAmount().compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("付款记录金额不能小于0");
}
if (record.getPaymentNo() != null && record.getPaymentNo().length() > 100) {
throw new ServiceException("付款单号不能超过100个字符");
}
if (record.getKingdeeBillNo() != null && record.getKingdeeBillNo().length() > 100) {
throw new ServiceException("付款凭证不能超过100个字符");
}
}
private void validateProjectAdvanceContractFile(PaymentApplication entity) {
if (!"project_advance".equals(entity.getPaymentType())) return;
ContractManage contract = entity.getContractId() == null ? null : contractManageMapper.selectById(entity.getContractId());
if (contract == null || Objects.equals(contract.getIsDeleted(), 1)) {
throw new ServiceException("项目预付必须选择有效合同");
}
if (!hasDownloadableFile(contract.getContractFileJson())
&& !hasDownloadableFile(entity.getAttachmentsJson(), "contract")) {
throw new ServiceException("请上传合同签章文件");
}
}
private boolean hasDownloadableFile(String value) {
return hasDownloadableFile(value, null);
}
private boolean hasDownloadableFile(String value, String attachmentType) {
if (Func.isEmpty(value)) return false;
try {
Object parsed = JsonUtil.parse(value, List.class);
if (!(parsed instanceof List<?> files)) return false;
return files.stream().anyMatch(file -> file instanceof Map<?, ?> fields
&& (attachmentType == null || Objects.equals(attachmentType, fields.get("attachmentType")))
&& List.of("url", "link", "fileUrl", "downloadUrl", "src", "domain").stream()
.anyMatch(key -> Func.isNotEmpty(fields.get(key))));
} catch (Exception exception) {
return false;
}
}
private void fillReference(PaymentApplication entity, PaymentApplicationSaveRequest request) { private void fillReference(PaymentApplication entity, PaymentApplicationSaveRequest request) {
if (request.getSettlementId() != null) { if ("project_advance".equals(request.getPaymentType())) {
ProjectApply project = projectApplyMapper.selectById(request.getProjectId());
if (project == null || Objects.equals(project.getIsDeleted(), 1)) throw new ServiceException("所属项目不存在");
entity.setSettlementId(null); entity.setSettlementNo(null); entity.setPreSettlementId(null); entity.setPreSettlementNo(null);
entity.setProjectId(request.getProjectId()); entity.setProjectName(request.getProjectName()); entity.setDeptId(request.getDeptId()); entity.setDeptName(request.getDeptName()); entity.setContractId(request.getContractId()); entity.setContractNo(request.getContractNo()); entity.setContractName(request.getContractName()); entity.setPayerName(request.getPayerName()); entity.setPayeeName(request.getPayeeName()); entity.setSettlementAmount(request.getSettlementAmount()); entity.setPayableAmount(money(project.getFundDemand()).multiply(BigDecimal.valueOf(10000))); entity.setBillType(Func.isEmpty(project.getSettlementMode()) ? null : "项目预付");
} else if (request.getSettlementId() != null) {
FormalSettlement source = formalSettlementMapper.selectById(request.getSettlementId()); FormalSettlement source = formalSettlementMapper.selectById(request.getSettlementId());
if (source == null) throw new ServiceException("正式结算单不存在"); if (source == null) throw new ServiceException("正式结算单不存在");
if (!APPROVED.equals(source.getApprovalStatus())) throw new ServiceException("只能选择审批通过、未作废的正式结算单"); if (!APPROVED.equals(source.getApprovalStatus())) throw new ServiceException("只能选择审批通过、未作废的正式结算单");
entity.setSettlementId(source.getId()); entity.setSettlementNo(source.getFormalSettlementNo()); entity.setPreSettlementId(null); entity.setPreSettlementNo(null); entity.setSettlementId(source.getId()); entity.setSettlementNo(source.getFormalSettlementNo()); entity.setPreSettlementId(null); entity.setPreSettlementNo(null);
entity.setProjectId(source.getProjectId()); entity.setProjectName(source.getProjectName()); entity.setDeptId(source.getDeptId()); entity.setDeptName(source.getDeptName()); entity.setContractId(source.getContractId()); entity.setContractNo(source.getContractNo()); entity.setContractName(source.getContractName()); entity.setPayerName(source.getPayerName()); entity.setPayeeName(source.getPayeeName()); entity.setSettlementAmount(source.getSettlementAmount()); entity.setPayableAmount(money(source.getSettlementAmount()).subtract(money(source.getAppliedPaymentAmount()))); entity.setBillType("正式结算单"); entity.setProjectId(source.getProjectId()); entity.setProjectName(source.getProjectName()); entity.setDeptId(source.getDeptId()); entity.setDeptName(source.getDeptName()); entity.setContractId(source.getContractId()); entity.setContractNo(source.getContractNo()); entity.setContractName(source.getContractName()); entity.setPayerName(source.getPayerName()); entity.setPayeeName(source.getPayeeName()); entity.setSettlementAmount(source.getSettlementAmount()); entity.setPayableAmount(money(source.getSettlementAmount()).subtract(cumulativeAppliedAmount(request.getPaymentType(), source.getId(), entity.getId()))); entity.setBillType("正式结算单");
} else if (request.getPreSettlementId() != null) { } else if (request.getPreSettlementId() != null) {
PreSettlement source = preSettlementMapper.selectById(request.getPreSettlementId()); PreSettlement source = preSettlementMapper.selectById(request.getPreSettlementId());
if (source == null) throw new ServiceException("预结算单不存在"); if (source == null) throw new ServiceException("预结算单不存在");
if (!APPROVED.equals(source.getApprovalStatus())) throw new ServiceException("只能选择审批通过、未作废的预结算单"); if (!APPROVED.equals(source.getApprovalStatus())) throw new ServiceException("只能选择审批通过、未作废的预结算单");
entity.setPreSettlementId(source.getId()); entity.setPreSettlementNo(source.getPreSettlementNo()); entity.setSettlementId(null); entity.setSettlementNo(null); entity.setProjectId(source.getProjectId()); entity.setProjectName(source.getProjectName()); entity.setDeptId(source.getDeptId()); entity.setDeptName(source.getDeptName()); entity.setContractId(source.getContractId()); entity.setContractNo(source.getContractNo()); entity.setContractName(source.getContractName()); entity.setPayerName(source.getPayerName()); entity.setPayeeName(source.getPayeeName()); entity.setSettlementAmount(source.getSettlementAmount()); entity.setPayableAmount(money(source.getSettlementAmount()).subtract(money(source.getAdvanceAppliedAmount()))); entity.setBillType("预结算单"); entity.setPreSettlementId(source.getId()); entity.setPreSettlementNo(source.getPreSettlementNo()); entity.setSettlementId(null); entity.setSettlementNo(null); entity.setProjectId(source.getProjectId()); entity.setProjectName(source.getProjectName()); entity.setDeptId(source.getDeptId()); entity.setDeptName(source.getDeptName()); entity.setContractId(source.getContractId()); entity.setContractNo(source.getContractNo()); entity.setContractName(source.getContractName()); entity.setPayerName(source.getPayerName()); entity.setPayeeName(source.getPayeeName()); entity.setSettlementAmount(source.getSettlementAmount()); entity.setPayableAmount(money(source.getSettlementAmount()).subtract(cumulativeAppliedAmount(request.getPaymentType(), source.getId(), entity.getId()))); entity.setBillType("预结算单");
} else { } else {
entity.setProjectId(request.getProjectId()); entity.setProjectName(request.getProjectName()); entity.setDeptId(request.getDeptId()); entity.setDeptName(request.getDeptName()); entity.setContractId(request.getContractId()); entity.setContractNo(request.getContractNo()); entity.setContractName(request.getContractName()); entity.setPayerName(request.getPayerName()); entity.setPayeeName(request.getPayeeName()); entity.setSettlementAmount(request.getSettlementAmount()); entity.setPayableAmount(request.getPayableAmount()); entity.setBillType(request.getBillType()); entity.setProjectId(request.getProjectId()); entity.setProjectName(request.getProjectName()); entity.setDeptId(request.getDeptId()); entity.setDeptName(request.getDeptName()); entity.setContractId(request.getContractId()); entity.setContractNo(request.getContractNo()); entity.setContractName(request.getContractName()); entity.setPayerName(request.getPayerName()); entity.setPayeeName(request.getPayeeName()); entity.setSettlementAmount(request.getSettlementAmount()); entity.setPayableAmount(request.getPayableAmount()); entity.setBillType(request.getBillType());
} }
@@ -264,32 +387,73 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
} }
private void validateQuota(PaymentApplication entity) { private void validateQuota(PaymentApplication entity) {
BigDecimal usedByProject = sumApplied(Wrappers.<PaymentApplication>lambdaQuery() ProjectApply project = entity.getProjectId() == null ? null : projectApplyMapper.selectOne(
.eq(entity.getProjectId() != null, PaymentApplication::getProjectId, entity.getProjectId()) Wrappers.<ProjectApply>lambdaQuery().eq(ProjectApply::getId, entity.getProjectId()).last("FOR UPDATE"));
.ne(entity.getId() != null, PaymentApplication::getId, entity.getId()));
ProjectApply project = entity.getProjectId() == null ? null : projectApplyMapper.selectById(entity.getProjectId());
if (project != null && project.getFundLimit() != null) { if (project != null && project.getFundLimit() != null) {
BigDecimal projectLimit = project.getFundLimit().multiply(BigDecimal.valueOf(10000)); BigDecimal projectLimit = projectFundLimit(project);
if (usedByProject.add(money(entity.getAppliedAmount())).compareTo(projectLimit) > 0) throw new ServiceException("申请付款金额超过项目剩余资金使用额度"); BigDecimal projectOccupiedAmount = quotaOccupiedAmount(Wrappers.<PaymentApplication>lambdaQuery()
.eq(PaymentApplication::getProjectId, entity.getProjectId())
.ne(entity.getId() != null, PaymentApplication::getId, entity.getId()), entity);
if (projectOccupiedAmount.compareTo(projectLimit) > 0) {
throw new ServiceException("申请付款金额超过项目剩余资金使用额度(已扣减收票金额并包含在途申请)");
}
} }
if (Func.isNotEmpty(entity.getPayeeName())) { if (Func.isNotEmpty(entity.getPayeeName())) {
CustomerArchive customer = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery() CustomerArchive customer = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, entity.getPayeeName()).or().eq(CustomerArchive::getShortName, entity.getPayeeName())) .and(wrapper -> wrapper.eq(CustomerArchive::getFullName, entity.getPayeeName()).or().eq(CustomerArchive::getShortName, entity.getPayeeName()))
.eq(CustomerArchive::getIsDeleted, 0).last("limit 1")); .eq(CustomerArchive::getIsDeleted, 0).last("limit 1 FOR UPDATE"));
if (customer != null && customer.getMaxCreditLimit() != null) { if (customer != null && customer.getMaxCreditLimit() != null) {
BigDecimal customerLimit = customer.getMaxCreditLimit().multiply(BigDecimal.valueOf(10000)); BigDecimal customerLimit = customer.getMaxCreditLimit().multiply(BigDecimal.valueOf(10000));
BigDecimal usedByCustomer = sumApplied(Wrappers.<PaymentApplication>lambdaQuery() List<String> customerNames = Stream.of(customer.getFullName(), customer.getShortName())
.eq(PaymentApplication::getPayeeName, entity.getPayeeName()).ne(entity.getId() != null, PaymentApplication::getId, entity.getId())); .filter(Func::isNotEmpty).map(String::trim).distinct().toList();
if (usedByCustomer.add(money(entity.getAppliedAmount())).compareTo(customerLimit) > 0) throw new ServiceException("申请付款金额超过客户剩余资金使用额度"); BigDecimal customerOccupiedAmount = quotaOccupiedAmount(Wrappers.<PaymentApplication>lambdaQuery()
.in(PaymentApplication::getPayeeName, customerNames)
.ne(entity.getId() != null, PaymentApplication::getId, entity.getId()), entity);
if (customerOccupiedAmount.compareTo(customerLimit) > 0) {
throw new ServiceException("申请付款金额超过客户剩余资金使用额度(已扣减收票金额并包含在途申请)");
}
} }
} }
} }
private BigDecimal projectFundLimit(ProjectApply project) {
BigDecimal temporaryLimit = temporaryCreditLimitMapper.selectList(Wrappers.<TemporaryCreditLimit>lambdaQuery()
.eq(TemporaryCreditLimit::getProjectId, project.getId())
.eq(TemporaryCreditLimit::getApprovalStatus, APPROVED)
.ge(TemporaryCreditLimit::getValidUntil, LocalDate.now())
.eq(TemporaryCreditLimit::getStatus, 1)
.eq(TemporaryCreditLimit::getIsDeleted, 0)).stream()
.map(TemporaryCreditLimit::getApplyLimit).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return money(project.getFundLimit()).add(temporaryLimit).multiply(BigDecimal.valueOf(10000));
}
private BigDecimal quotaOccupiedAmount(LambdaQueryWrapper<PaymentApplication> wrapper,
PaymentApplication currentApplication) {
List<PaymentApplication> applications = list(wrapper.ne(PaymentApplication::getApprovalStatus, VOIDED)
.last("FOR UPDATE"));
BigDecimal appliedAmount = applications.stream().map(PaymentApplication::getAppliedAmount)
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add)
.add(money(currentApplication.getAppliedAmount()));
BigDecimal receivedInvoiceAmount = applications.stream().map(PaymentApplication::getMatchedInvoiceAmount)
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add)
.add(money(currentApplication.getMatchedInvoiceAmount()));
return appliedAmount.subtract(receivedInvoiceAmount).max(BigDecimal.ZERO);
}
private BigDecimal sumApplied(LambdaQueryWrapper<PaymentApplication> wrapper) { private BigDecimal sumApplied(LambdaQueryWrapper<PaymentApplication> wrapper) {
return list(wrapper.ne(PaymentApplication::getApprovalStatus, VOIDED)).stream() return list(wrapper.ne(PaymentApplication::getApprovalStatus, VOIDED)).stream()
.map(PaymentApplication::getAppliedAmount).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); .map(PaymentApplication::getAppliedAmount).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
} }
private BigDecimal cumulativeAppliedAmount(String paymentType, Long referenceId, Long excludeId) {
LambdaQueryWrapper<PaymentApplication> wrapper = Wrappers.<PaymentApplication>lambdaQuery()
.eq("settlement_payment".equals(paymentType), PaymentApplication::getSettlementId, referenceId)
.eq("progress_advance".equals(paymentType), PaymentApplication::getPreSettlementId, referenceId)
.ne(excludeId != null, PaymentApplication::getId, excludeId);
return sumApplied(wrapper);
}
private void fillBillLedger(PaymentApplication entity, PaymentApplicationSaveRequest request) { private void fillBillLedger(PaymentApplication entity, PaymentApplicationSaveRequest request) {
if (!isBillPayment(request.getPaymentMethod())) { if (!isBillPayment(request.getPaymentMethod())) {
entity.setBillLedgerId(null); entity.setBillLedgerId(null);
@@ -56,6 +56,7 @@ import org.springblade.transport.pojo.entity.PreSettlementChangeRecord;
import org.springblade.transport.pojo.entity.PreSettlementDetail; import org.springblade.transport.pojo.entity.PreSettlementDetail;
import org.springblade.transport.pojo.entity.PreSettlementDetailFee; import org.springblade.transport.pojo.entity.PreSettlementDetailFee;
import org.springblade.transport.pojo.entity.PreSettlementSummaryFee; import org.springblade.transport.pojo.entity.PreSettlementSummaryFee;
import org.springblade.transport.pojo.entity.ProjectApply;
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee; import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail; import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.entity.Waybill;
@@ -183,7 +184,15 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
.and(Func.isNotEmpty(keyword), query -> query.like(ContractManage::getContractName, keyword) .and(Func.isNotEmpty(keyword), query -> query.like(ContractManage::getContractName, keyword)
.or().like(ContractManage::getContractNo, keyword)) .or().like(ContractManage::getContractNo, keyword))
.orderByDesc(ContractManage::getCreateTime); .orderByDesc(ContractManage::getCreateTime);
return contractManageService.list(wrapper).stream().filter(this::isAvailableContract).map(contract -> { List<ContractManage> contracts = contractManageService.list(wrapper).stream()
.filter(this::isAvailableContract).toList();
Set<Long> projectIds = contracts.stream().map(ContractManage::getProjectId)
.filter(Objects::nonNull).collect(Collectors.toSet());
Map<Long, ProjectApply> projectById = Func.isEmpty(projectIds) ? Map.of()
: projectApplyService.listByIds(projectIds).stream()
.collect(Collectors.toMap(ProjectApply::getId, Function.identity()));
return contracts.stream().map(contract -> {
ProjectApply project = projectById.get(contract.getProjectId());
Map<String, Object> result = new LinkedHashMap<>(); Map<String, Object> result = new LinkedHashMap<>();
result.put("id", contract.getId()); result.put("id", contract.getId());
result.put("contractNo", contract.getContractNo()); result.put("contractNo", contract.getContractNo());
@@ -194,6 +203,8 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
result.put("deptName", contract.getOrganizationName()); result.put("deptName", contract.getOrganizationName());
result.put("partyA", contract.getPartyA()); result.put("partyA", contract.getPartyA());
result.put("partyB", contract.getPartyB()); result.put("partyB", contract.getPartyB());
result.put("fundDemand", project == null ? null : project.getFundDemand());
result.put("settlementMode", project == null ? null : project.getSettlementMode());
result.put("settlementType", contractSettlementType(contract)); result.put("settlementType", contractSettlementType(contract));
return result; return result;
}).toList(); }).toList();
@@ -11,12 +11,14 @@ import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserCache; import org.springblade.system.cache.UserCache;
import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper; import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper;
import org.springblade.transport.mapper.FormalSettlementDetailMapper; import org.springblade.transport.mapper.FormalSettlementDetailMapper;
import org.springblade.transport.mapper.FormalSettlementChangeRecordMapper;
import org.springblade.transport.mapper.FormalSettlementMapper; import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.mapper.SettlementAdjustmentDetailMapper; import org.springblade.transport.mapper.SettlementAdjustmentDetailMapper;
import org.springblade.transport.mapper.SettlementAdjustmentMapper; import org.springblade.transport.mapper.SettlementAdjustmentMapper;
import org.springblade.transport.pojo.dto.SettlementAdjustmentSaveRequest; import org.springblade.transport.pojo.dto.SettlementAdjustmentSaveRequest;
import org.springblade.transport.pojo.dto.SettlementAdjustmentStatusRequest; import org.springblade.transport.pojo.dto.SettlementAdjustmentStatusRequest;
import org.springblade.transport.pojo.entity.FormalSettlement; import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementChangeRecord;
import org.springblade.transport.pojo.entity.FormalSettlementDetail; import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee; import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.entity.SettlementAdjustment; import org.springblade.transport.pojo.entity.SettlementAdjustment;
@@ -48,6 +50,7 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
private final FormalSettlementMapper formalMapper; private final FormalSettlementMapper formalMapper;
private final FormalSettlementDetailMapper formalDetailMapper; private final FormalSettlementDetailMapper formalDetailMapper;
private final FormalSettlementDetailFeeMapper formalFeeMapper; private final FormalSettlementDetailFeeMapper formalFeeMapper;
private final FormalSettlementChangeRecordMapper formalChangeRecordMapper;
@Override @Override
public IPage<SettlementAdjustmentVO> selectPage(IPage<SettlementAdjustment> page, SettlementAdjustmentVO query) { public IPage<SettlementAdjustmentVO> selectPage(IPage<SettlementAdjustment> page, SettlementAdjustmentVO query) {
@@ -178,9 +181,15 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
if (formal == null || !APPROVED.equals(formal.getApprovalStatus())) throw new ServiceException("关联正式结算单状态已变化,无法审批"); if (formal == null || !APPROVED.equals(formal.getApprovalStatus())) throw new ServiceException("关联正式结算单状态已变化,无法审批");
for (SettlementAdjustmentDetail item : detailMapper.selectList(Wrappers.<SettlementAdjustmentDetail>lambdaQuery().eq(SettlementAdjustmentDetail::getAdjustmentId, adjustment.getId()))) { for (SettlementAdjustmentDetail item : detailMapper.selectList(Wrappers.<SettlementAdjustmentDetail>lambdaQuery().eq(SettlementAdjustmentDetail::getAdjustmentId, adjustment.getId()))) {
FormalSettlementDetailFee fee = validateFee(formal.getId(), item.getFormalSettlementDetailId(), item.getFormalSettlementDetailFeeId()); FormalSettlementDetailFee fee = validateFee(formal.getId(), item.getFormalSettlementDetailId(), item.getFormalSettlementDetailFeeId());
BigDecimal beforeAmount = money(fee.getSettlementAmountTax());
fee.setSettlementAmountTax(money(fee.getSettlementAmountTax()).add(money(item.getAdjustmentAmountTax()))); fee.setSettlementAmountTax(money(fee.getSettlementAmountTax()).add(money(item.getAdjustmentAmountTax())));
if (item.getAdjustmentAmountNoTax() != null) fee.setSettlementAmountNoTax(money(fee.getSettlementAmountNoTax()).add(item.getAdjustmentAmountNoTax())); if (item.getAdjustmentAmountNoTax() != null) fee.setSettlementAmountNoTax(money(fee.getSettlementAmountNoTax()).add(item.getAdjustmentAmountNoTax()));
fee.setAdjustAmount(money(fee.getAdjustAmount()).add(item.getAdjustmentAmountTax())); formalFeeMapper.updateById(fee); fee.setAdjustAmount(money(fee.getAdjustAmount()).add(item.getAdjustmentAmountTax())); formalFeeMapper.updateById(fee);
FormalSettlementDetail formalDetail = formalDetailMapper.selectById(item.getFormalSettlementDetailId());
saveFormalChange(formal.getId(), "结算明细项", formalDetail == null ? null : formalDetail.getLineNo(),
"调整", "" + (Func.isEmpty(item.getFeeItem()) ? "结算费用" : item.getFeeItem())
+ "】从【" + beforeAmount + "】调整为【" + fee.getSettlementAmountTax() + "",
Func.isEmpty(item.getRemark()) ? adjustment.getRemark() : item.getRemark());
} }
for (FormalSettlementDetail detail : formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, formal.getId()))) { for (FormalSettlementDetail detail : formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, formal.getId()))) {
List<FormalSettlementDetailFee> fees = formalFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery().eq(FormalSettlementDetailFee::getFormalSettlementDetailId, detail.getId())); List<FormalSettlementDetailFee> fees = formalFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery().eq(FormalSettlementDetailFee::getFormalSettlementDetailId, detail.getId()));
@@ -190,9 +199,26 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
} }
BigDecimal amount = formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, formal.getId())).stream().map(FormalSettlementDetail::getSettlementAmountTax).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal amount = formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, formal.getId())).stream().map(FormalSettlementDetail::getSettlementAmountTax).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
formal.setSettlementAmount(amount); formal.setLocalSettlementAmount(amount.multiply(formal.getExchangeRate() == null ? BigDecimal.ONE : formal.getExchangeRate())); formalMapper.updateById(formal); formal.setSettlementAmount(amount); formal.setLocalSettlementAmount(amount.multiply(formal.getExchangeRate() == null ? BigDecimal.ONE : formal.getExchangeRate())); formalMapper.updateById(formal);
saveFormalChange(formal.getId(), "合计费用项", null, "调整",
"调整单" + adjustment.getAdjustmentNo() + "调整金额【" + money(adjustment.getAdjustmentAmount()) + "",
adjustment.getRemark());
adjustment.setKingdeeSyncStatus(formal.getKingdeeSyncStatus()); adjustment.setApprovalStatus(APPROVED); adjustment.setCurrentNode("审批通过"); adjustment.setCurrentProcessor(AuthUtil.getUserName()); adjustment.setApprovedTime(LocalDateTime.now()); updateById(adjustment); adjustment.setKingdeeSyncStatus(formal.getKingdeeSyncStatus()); adjustment.setApprovalStatus(APPROVED); adjustment.setCurrentNode("审批通过"); adjustment.setCurrentProcessor(AuthUtil.getUserName()); adjustment.setApprovedTime(LocalDateTime.now()); updateById(adjustment);
} }
private void saveFormalChange(Long settlementId, String changeType, Integer lineNo,
String operationType, String content, String reason) {
FormalSettlementChangeRecord record = new FormalSettlementChangeRecord();
record.setFormalSettlementId(settlementId);
record.setChangeType(changeType);
record.setLineNo(lineNo);
record.setOperationType(operationType);
record.setChangeContent(content);
record.setChangeReason(reason);
record.setOperatorName(Func.isEmpty(AuthUtil.getUserName()) ? "system" : AuthUtil.getUserName());
record.setChangeTime(LocalDateTime.now());
formalChangeRecordMapper.insert(record);
}
private void changeStatus(Long id, String from, String to, String node, String reason) { SettlementAdjustment item = existing(id); if (!from.equals(item.getApprovalStatus())) throw new ServiceException("当前状态不允许此操作"); item.setApprovalStatus(to); item.setCurrentNode(node); if (reason != null) item.setRemark(reason); updateById(item); } private void changeStatus(Long id, String from, String to, String node, String reason) { SettlementAdjustment item = existing(id); if (!from.equals(item.getApprovalStatus())) throw new ServiceException("当前状态不允许此操作"); item.setApprovalStatus(to); item.setCurrentNode(node); if (reason != null) item.setRemark(reason); updateById(item); }
private SettlementAdjustment existing(Long id) { SettlementAdjustment item = getById(id); if (item == null || Objects.equals(item.getIsDeleted(), 1)) throw new ServiceException("结算调整单不存在"); return item; } private SettlementAdjustment existing(Long id) { SettlementAdjustment item = getById(id); if (item == null || Objects.equals(item.getIsDeleted(), 1)) throw new ServiceException("结算调整单不存在"); return item; }
private SettlementAdjustment editable(Long id) { SettlementAdjustment item = existing(id); if (!(DRAFT.equals(item.getApprovalStatus()) || RETURNED.equals(item.getApprovalStatus()))) throw new ServiceException("仅草稿或驳回的调整单可编辑"); return item; } private SettlementAdjustment editable(Long id) { SettlementAdjustment item = existing(id); if (!(DRAFT.equals(item.getApprovalStatus()) || RETURNED.equals(item.getApprovalStatus()))) throw new ServiceException("仅草稿或驳回的调整单可编辑"); return item; }
@@ -23,6 +23,33 @@ CREATE TABLE IF NOT EXISTS `blade_formal_settlement` (
KEY `idx_formal_contract` (`contract_id`), KEY `idx_formal_approval` (`approval_status`), KEY `idx_formal_create_time` (`create_time`) KEY `idx_formal_contract` (`contract_id`), KEY `idx_formal_approval` (`approval_status`), KEY `idx_formal_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算单'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算单';
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_invoice` (
`id` bigint(20) NOT NULL COMMENT '主键', `tenant_id` varchar(12) NOT NULL DEFAULT '000000' COMMENT '租户ID',
`create_user` bigint(20) DEFAULT NULL, `create_dept` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL,
`update_user` bigint(20) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT '1', `is_deleted` int(11) DEFAULT '0',
`formal_settlement_id` bigint(20) NOT NULL COMMENT '正式结算单ID', `line_no` int(11) NOT NULL COMMENT '行号',
`invoice_no` varchar(32) NOT NULL COMMENT '发票号', `invoice_date` date DEFAULT NULL COMMENT '开票日期',
`invoice_type` varchar(50) DEFAULT NULL COMMENT '发票类型', `tax_rate` decimal(8,4) DEFAULT NULL COMMENT '税率',
`invoice_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '发票金额(含税)',
`available_invoice_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '可匹配发票金额(含税)',
`matched_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '匹配结算单金额(含税)',
`attachment_json` longtext COMMENT '发票附件JSON', PRIMARY KEY (`id`),
UNIQUE KEY `uk_formal_settlement_invoice_no` (`tenant_id`,`formal_settlement_id`,`invoice_no`),
KEY `idx_formal_settlement_invoice` (`formal_settlement_id`,`line_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算发票明细';
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_change_record` (
`id` bigint(20) NOT NULL COMMENT '主键', `tenant_id` varchar(12) NOT NULL DEFAULT '000000' COMMENT '租户ID',
`create_user` bigint(20) DEFAULT NULL, `create_dept` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL,
`update_user` bigint(20) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT '1', `is_deleted` int(11) DEFAULT '0',
`formal_settlement_id` bigint(20) NOT NULL COMMENT '正式结算单ID', `change_type` varchar(50) NOT NULL COMMENT '变更类型',
`line_no` int(11) DEFAULT NULL COMMENT '行号', `operation_type` varchar(30) NOT NULL COMMENT '操作类型',
`change_content` text NOT NULL COMMENT '变更内容', `change_reason` varchar(200) DEFAULT NULL COMMENT '变更原因',
`operator_name` varchar(100) DEFAULT NULL COMMENT '操作人', `change_time` datetime NOT NULL COMMENT '变更时间',
PRIMARY KEY (`id`), KEY `idx_formal_settlement_change_bill` (`formal_settlement_id`),
KEY `idx_formal_settlement_change_time` (`change_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算变更记录';
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_source` ( CREATE TABLE IF NOT EXISTS `blade_formal_settlement_source` (
`id` bigint(20) NOT NULL, `tenant_id` varchar(12) NOT NULL DEFAULT '000000', `create_user` bigint(20) DEFAULT NULL, `id` bigint(20) NOT NULL, `tenant_id` varchar(12) NOT NULL DEFAULT '000000', `create_user` bigint(20) DEFAULT NULL,
`create_dept` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_user` bigint(20) DEFAULT NULL, `create_dept` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_user` bigint(20) DEFAULT NULL,
@@ -0,0 +1,23 @@
-- 正式结算详情增加变更记录模块。
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_change_record` (
`id` bigint(20) NOT NULL COMMENT '主键',
`tenant_id` varchar(12) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '000000' COMMENT '租户ID',
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`status` int(11) DEFAULT '1' COMMENT '状态',
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
`formal_settlement_id` bigint(20) NOT NULL COMMENT '正式结算单ID',
`change_type` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '变更类型',
`line_no` int(11) DEFAULT NULL COMMENT '行号',
`operation_type` varchar(30) COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作类型',
`change_content` text COLLATE utf8mb4_general_ci NOT NULL COMMENT '变更内容',
`change_reason` varchar(200) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '变更原因',
`operator_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作人',
`change_time` datetime NOT NULL COMMENT '变更时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_formal_settlement_change_bill` (`formal_settlement_id`) USING BTREE,
KEY `idx_formal_settlement_change_time` (`change_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算变更记录';
@@ -0,0 +1,26 @@
-- 正式结算单增加认领发票明细
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_invoice` (
`id` bigint(20) NOT NULL COMMENT '主键',
`tenant_id` varchar(12) NOT NULL DEFAULT '000000' COMMENT '租户ID',
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`status` int(11) DEFAULT '1' COMMENT '状态',
`is_deleted` int(11) DEFAULT '0' COMMENT '是否删除',
`formal_settlement_id` bigint(20) NOT NULL COMMENT '正式结算单ID',
`line_no` int(11) NOT NULL COMMENT '行号',
`invoice_no` varchar(32) NOT NULL COMMENT '发票号',
`invoice_date` date DEFAULT NULL COMMENT '开票日期',
`invoice_type` varchar(50) DEFAULT NULL COMMENT '发票类型',
`tax_rate` decimal(8,4) DEFAULT NULL COMMENT '税率',
`invoice_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '发票金额(含税)',
`available_invoice_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '可匹配发票金额(含税)',
`matched_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '匹配结算单金额(含税)',
`attachment_json` longtext COMMENT '发票附件JSON',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_formal_settlement_invoice_no` (`tenant_id`,`formal_settlement_id`,`invoice_no`),
KEY `idx_formal_settlement_invoice` (`formal_settlement_id`,`line_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算发票明细';
@@ -0,0 +1,3 @@
ALTER TABLE `blade_payment_application`
ADD KEY `idx_payment_application_project_quota` (`tenant_id`, `project_id`, `is_deleted`, `approval_status`),
ADD KEY `idx_payment_application_payee_quota` (`tenant_id`, `payee_name`(100), `is_deleted`, `approval_status`);