shiyunteng
2026-05-13 fb9fca375c78c5b79acf6db990357816f86100fb
feat:合同、出库、资金明细、发票
19个文件已修改
19个文件已添加
1853 ■■■■■ 已修改文件
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractAddDTO.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractPaymentScheduleAddDTO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractPaymentScheduleProcessAddDTO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractSubjectMatterAddDTO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractUpdateDTO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/OutBoundAddDTO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/OutSubjectMatterAddDTO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/PaymentConfirmAddDTO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/Contract.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractPaymentSchedule.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractPaymentScheduleProcess.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractSubjectMatter.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/CurrentOverdue.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/HistoryOverdue.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/PaymentConfirm.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ContractDetailVo.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ContractPaymentScheduleVo.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ScheduleProcessVo.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractBillingController.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractController.java 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractPaymentScheduleProcessController.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/CurrentOverdueController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/HistoryOverdueController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/OutBoundController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/PaymentConfirmController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/ContractPaymentScheduleProcessMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractPaymentScheduleProcessService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/CurrentOverdueService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/HistoryOverdueService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/OutBoundService.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/PaymentConfirmService.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractPaymentScheduleProcessServiceImpl.java 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractServiceImpl.java 297 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/OutBoundServiceImpl.java 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/PaymentConfirmServiceImpl.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/utils/ContractNumberGenerator.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-biz/src/main/resources/mapper/ContractPaymentScheduleProcessMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractAddDTO.java
New file
@@ -0,0 +1,78 @@
package com.by4cloud.platformx.business.dto;
import com.by4cloud.platformx.business.entity.ContractPaymentSchedule;
import com.by4cloud.platformx.business.entity.ContractSubjectMatter;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.List;
@Data
public class ContractAddDTO {
    @Schema(description = "合同名称")
    private String contractName;
    @Schema(description = "甲方id")
    @JsonProperty("partyAId")
    private Long partyAId;
    @Schema(description = "甲方名称")
    @JsonProperty("partyA")
    private String partyA;
    @Schema(description = "乙方id")
    private Long partyBId;
    @Schema(description = "乙方名称")
    private String partyB;
    @Schema(description = "合同金额")
    private BigDecimal amount;
    @Schema(description = "币种(CNY/USD等)")
    private String currency;
    @Schema(description = "合同类型(0-生成式 1-备案式)")
    private Integer contractType;
    @Schema(description = "供应属性 0-成品 1-备件 2-大修 3-其他")
    private Integer supplyAttribute;
    @Schema(description = "合同状态(0-草稿 1-已生效 2-已终止 3-已过期)")
    private Integer contractStatus;
    @Schema(description = "合同服务属性(0-产品销售 1-无形服务)")
    private Integer contractAttribute;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "签署日期")
    private String signDate;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "生效日期")
    private String effectiveDate;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "到期日期")
    private String expirationDate;
    @Schema(description = "合同附件URL")
    private String attachmentUrl;
    @Schema(description = "备注")
    private String remark;
    private List<ContractPaymentScheduleAddDTO> contractPaymentSchedule;
    private List<ContractSubjectMatterAddDTO> contractSubjectMatter;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractPaymentScheduleAddDTO.java
New file
@@ -0,0 +1,20 @@
package com.by4cloud.platformx.business.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ContractPaymentScheduleAddDTO {
    @Schema(description = "收款阶段名称(显性要素,如:合同签订预付、发货前付款、货到签收付款、调试完成付款、质保金)")
    private String stageName;
    @Schema(description = "收款比例(%,如30表示30%)(显性要素)")
    private BigDecimal paymentRatio;
    @Schema(description = "约定天数(合同签订后X天内付款/货到后X天内付款等)")
    private Integer agreedDays;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractPaymentScheduleProcessAddDTO.java
New file
@@ -0,0 +1,22 @@
package com.by4cloud.platformx.business.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import lombok.Data;
import java.util.Date;
@Data
public class ContractPaymentScheduleProcessAddDTO {
    @Schema(description = "阶段ID")
    @Column(columnDefinition = "bigint comment '阶段ID'")
    private Long scheduleId;
    @Schema(description = "履约时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Column(columnDefinition = "date comment '履约时间'")
    private Date processDate;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractSubjectMatterAddDTO.java
New file
@@ -0,0 +1,30 @@
package com.by4cloud.platformx.business.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ContractSubjectMatterAddDTO {
    @Column(columnDefinition = "VARCHAR(200) comment '标的物名称'")
    private String materialName;
    @Schema(description = "标的物编码(内部唯一编码)")
    private String materialCode;
    @Schema(description = "标的物编码(内部唯一名称)")
    private String materialInternalName;
    @Schema(description = "标的物规格/型号")
    private String specification;
    @Schema(description = "数量")
    private BigDecimal quantity;
    @Schema(description = "单价")
    private BigDecimal unitPrice;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/ContractUpdateDTO.java
New file
@@ -0,0 +1,10 @@
package com.by4cloud.platformx.business.dto;
import lombok.Data;
@Data
public class ContractUpdateDTO extends ContractAddDTO{
    private Long id;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/OutBoundAddDTO.java
New file
@@ -0,0 +1,27 @@
package com.by4cloud.platformx.business.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class OutBoundAddDTO {
    @Schema(description = "客商名称")
    private String busGuestName;
    @Schema(description = "客商ID")
    private Long busGuestId;
    @Schema(description = "出库时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date outBoundTime;
    @Schema(description = "出库标的物")
    private List<OutSubjectMatterAddDTO> subjectMatterList;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/OutSubjectMatterAddDTO.java
New file
@@ -0,0 +1,26 @@
package com.by4cloud.platformx.business.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class OutSubjectMatterAddDTO {
    @Schema(description = "标的物名称")
    private String subjectMatterName;
    @Schema(description = "标的物编码")
    private String subjectMatterCode;
    @Schema(description = "出库数量")
    private BigDecimal outBoundNum;
    @Schema(description = "单据名称")
    private String outBoundAttNames;
    @Schema(description = "单据路径")
    private String outBoundAttPaths;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/dto/PaymentConfirmAddDTO.java
New file
@@ -0,0 +1,27 @@
package com.by4cloud.platformx.business.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class PaymentConfirmAddDTO {
    private String contractNo;
    private String busGuestId;
    private String busGuestName;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date confirmTime;
    /**
     * 转入 1 转出 2
     */
    private String inOrOut;
    private BigDecimal transationAmount;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/Contract.java
@@ -13,6 +13,8 @@
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
@@ -53,8 +55,8 @@
    private String partyB;
    @Schema(description = "合同金额")
    @Column(columnDefinition = "double comment '合同金额'")
    private Double amount;
    @Column(columnDefinition = "decimal(10,2) comment '合同金额'")
    private BigDecimal amount;
    @Schema(description = "币种(CNY/USD等)")
    @Column(columnDefinition = "VARCHAR(10) default 'CNY' comment '币种'")
@@ -79,20 +81,24 @@
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "签署日期")
    @Column(columnDefinition = "VARCHAR(64) comment '签署日期'")
    private String signDate;
    @Column(columnDefinition = "date comment '签署日期'")
    private Date signDate;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "生效日期")
    @Column(columnDefinition = "VARCHAR(64) comment '生效日期'")
    private String effectiveDate;
    @Column(columnDefinition = "date comment '生效日期'")
    private Date effectiveDate;
    @Schema(description = "交付周期")
    @Column(columnDefinition = "int default 0 comment '交付周期'")
    private Integer deliveryCycle;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "到期日期")
    @Column(columnDefinition = "VARCHAR(64) comment '到期日期'")
    private String expirationDate;
    @Column(columnDefinition = "date comment '到期日期'")
    private Date expirationDate;
    @Schema(description = "合同附件URL")
    @Column(columnDefinition = "VARCHAR(500) comment '合同附件URL'")
@@ -111,7 +117,27 @@
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "审批时间")
    @Column(columnDefinition = "datetime comment '审批时间'")
    private String approveTime;
    private Date approveTime;
    @Schema(description = "到货节点ID")
    @Column(columnDefinition = "bigint comment '到货节点ID'")
    private Long arrivalScheduleId;
    @Schema(description = "验收节点ID")
    @Column(columnDefinition = "bigint comment '验收节点ID'")
    private Long acceptScheduleId;
    @Schema(description = "erp推送标识")
    @Column(columnDefinition = "char comment 'erp推送标识 0 未推 1 已推'")
    private String erpPushFlag;
    @Schema(description = "开票状态")
    @Column(columnDefinition = "char comment '开票状态 0 不能开票 1 部分可开 2 待开 3 已开'")
    private String billingStatus;
    @Schema(description = "已开票金额")
    @Column(columnDefinition = "decimal(10,2) comment '已开票金额'")
    private BigDecimal billingAmout;
    /**
     * 临时字段 - 用于接收审批人名称等关联查询结果
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractPaymentSchedule.java
@@ -13,6 +13,9 @@
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
 * 合同收款计划/履约节点实体类
 * 统一管理合同的收款阶段、履约节点、收款比例及履约时间节点
@@ -70,16 +73,16 @@
    // ==================== 收款比例/金额 ====================
    @Schema(description = "收款比例(%,如30表示30%)(显性要素)")
    @Column(columnDefinition = "double comment '收款比例(%)'")
    private Double paymentRatio;
    @Column(columnDefinition = "decimal(10,2) comment '收款比例(%)'")
    private BigDecimal paymentRatio;
    @Schema(description = "计划收款金额(根据合同总金额*比例自动计算)")
    @Column(columnDefinition = "double comment '计划收款金额'")
    private Double plannedAmount;
    @Column(columnDefinition = "decimal(10,2) comment '计划收款金额'")
    private BigDecimal plannedAmount;
    @Schema(description = "实际收款金额")
    @Column(columnDefinition = "double default 0.00 comment '实际收款金额'")
    private Double actualAmount;
    @Column(columnDefinition = "decimal(10,2) comment '实际收款金额'")
    private BigDecimal actualAmount;
    // ==================== 时序控制 ====================
@@ -87,17 +90,29 @@
    @Column(columnDefinition = "int not null comment '阶段顺序'")
    private Integer stageOrder;
    @Schema(description = "计划收款日期(基于合同签订日期+约定天数计算)")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Column(columnDefinition = "VARCHAR(64) comment '计划收款日期'")
    private String plannedPaymentDate;
//    @Schema(description = "计划收款日期(基于合同签订日期+约定天数计算)")
//    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
//    @Column(columnDefinition = "date comment '计划收款日期'")
//    private Date plannedPaymentDate;
    @Schema(description = "收款日期")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Column(columnDefinition = "datetime comment '收款日期'")
    private Date paymentDate;
    @Schema(description = "收款阶段生效日期(隐性要素 - 履约时间起点,即该节点实际达成的日期)")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Column(columnDefinition = "VARCHAR(64) comment '收款阶段生效日期(履约节点实际达成日期)'")
    private String effectiveDate;
    @Column(columnDefinition = "date comment '收款阶段生效日期(履约节点实际达成日期)'")
    private Date effectiveDate;
    @Schema(description = "收款阶段截止日期")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Column(columnDefinition = "date comment '收款阶段截止日期'")
    private Date effectiveEndDate;
    @Schema(description = "约定天数(合同签订后X天内付款/货到后X天内付款等)")
    @Column(columnDefinition = "int default 0 comment '约定天数'")
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractPaymentScheduleProcess.java
New file
@@ -0,0 +1,59 @@
package com.by4cloud.platformx.business.entity;
import com.by4cloud.platformx.common.data.mybatis.BaseModel;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
 * 合同收款计划/履约节点实体类
 * 统一管理合同的收款阶段、履约节点、收款比例及履约时间节点
 *
 * @author xfei
 * @date 2024-01-15
 */
@Data
@Entity
@Table(name = "contract_payment_schedule_process")
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ContractPaymentScheduleProcess extends BaseModel<ContractPaymentScheduleProcess> {
    @Schema(description = "关联合同ID")
    @Column(columnDefinition = "bigint not null comment '关联合同ID'")
    private Long contractId;
    @Schema(description = "合同名称")
    @Column(columnDefinition = "VARCHAR(64) comment '合同名称'")
    private String contractName;
    @Schema(description = "阶段ID")
    @Column(columnDefinition = "bigint comment '阶段ID'")
    private Long scheduleId;
    @Schema(description = "阶段名称")
    @Column(columnDefinition = "VARCHAR(64) comment '阶段名称'")
    private String scheduleName;
    @Schema(description = "履约时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Column(columnDefinition = "date comment '履约时间'")
    private Date processDate;
    @Schema(description = "履约单据名称")
    @Column(columnDefinition="text comment '履约单据名称'")
    private String outBoundAttNames;
    @Schema(description = "履约单据路径")
    @Column(columnDefinition="text comment '履约单据路径'")
    private String outBoundAttPaths;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/ContractSubjectMatter.java
@@ -13,6 +13,8 @@
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
/**
 * 合同标的物明细实体类
 * @author xfei
@@ -66,32 +68,32 @@
    private Integer category;
    @Schema(description = "数量")
    @Column(columnDefinition = "double comment '数量'")
    private Double quantity;
    @Column(columnDefinition = "decimal(10,2) comment '数量'")
    private BigDecimal quantity;
    @Schema(description = "计量单位(个/台/吨/项/套等)")
    @Column(columnDefinition = "VARCHAR(20) comment '计量单位'")
    private String unit;
    @Schema(description = "单价")
    @Column(columnDefinition = "double comment '单价'")
    private Double unitPrice;
    @Column(columnDefinition = "decimal(10,2) comment '单价'")
    private BigDecimal unitPrice;
    @Schema(description = "税率(百分比,如13表示13%)")
    @Column(columnDefinition = "double default 0.00 comment '税率'")
    private Double taxRate;
    @Column(columnDefinition = "decimal(10,2) comment '税率'")
    private BigDecimal taxRate;
    @Schema(description = "税额")
    @Column(columnDefinition = "double default 0.00 comment '税额'")
    private Double taxAmount;
    @Column(columnDefinition = "decimal(10,2) comment '税额'")
    private BigDecimal taxAmount;
    @Schema(description = "含税总价")
    @Column(columnDefinition = "double comment '含税总价'")
    private Double totalAmount;
    @Column(columnDefinition = "decimal(10,2) comment '含税总价'")
    private BigDecimal totalAmount;
    @Schema(description = "不含税总价")
    @Column(columnDefinition = "double comment '不含税总价'")
    private Double totalAmountExcludingTax;
    @Column(columnDefinition = "decimal(10,2) comment '不含税总价'")
    private BigDecimal totalAmountExcludingTax;
    @Schema(description = "交货/交付地点")
    @Column(columnDefinition = "VARCHAR(500) comment '交货/交付地点'")
@@ -109,7 +111,7 @@
    @Column(columnDefinition = "VARCHAR(64) comment '实际交付日期'")
    private String actualDeliveryDate;
    @Schema(description = "交付状态(0-未交付 1-部分交付 2-已交付 3-逾期)")
    @Schema(description = "交付状态(0-未交付 1-部分交付 2-已交付)")
    @Column(columnDefinition = "tinyint(2) default 0 comment '交付状态'")
    private Integer deliveryStatus;
@@ -139,12 +141,19 @@
    @Column(columnDefinition = "VARCHAR(64) comment '验收时间'")
    private String acceptTime;
    @Schema(description = "已交付数量")
    @Column(columnDefinition = "decimal(10,2) comment '已交付数量'")
    private BigDecimal deliveredQuantity;
    /**
     * 临时字段 - 已交付数量(用于交付进度统计)
     */
    @Transient
    @TableField(exist = false)
    @Schema(description = "已交付数量(临时字段)")
    private Double deliveredQuantity;
    @Schema(description = "未交付数量")
    @Column(columnDefinition = "decimal(10,2) comment '未交付数量'")
    private BigDecimal remainingQuantity;
    @Schema(description = "折扣率")
    @Column(columnDefinition = "decimal(10,2) comment '折扣率'")
    private BigDecimal discountRate;
    @Schema(description = "最近一次交付数量")
    @Column(columnDefinition = "decimal(10,2) comment '最近一次交付数量'")
    private BigDecimal lastDeliveredQuantity;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/CurrentOverdue.java
@@ -41,7 +41,15 @@
    @Column(columnDefinition="decimal(10,2) comment '应收金额  /元 两位小数'")
    private BigDecimal receivableAmount;
    @Schema(description = "合同到期时间")
    @Schema(description = "阶段ID")
    @Column(columnDefinition = "bigint comment '阶段ID'")
    private Long scheduleId;
    @Schema(description = "阶段名称")
    @Column(columnDefinition = "VARCHAR(64) comment '阶段名称'")
    private String scheduleName;
    @Schema(description = "逾期时间")
    @Column(columnDefinition="datetime comment '合同到期时间'")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date contractExpirTime;
@@ -49,5 +57,4 @@
    @Schema(description = "逾期时长")
    @Column(columnDefinition="decimal(10,0) comment '逾期时长  /天'")
    private BigDecimal overdueDuration;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/HistoryOverdue.java
@@ -55,4 +55,12 @@
    @Column(columnDefinition="decimal(10,0) comment '逾期时长  /天'")
    private BigDecimal overdueDuration;
    @Schema(description = "阶段ID")
    @Column(columnDefinition = "bigint comment '阶段ID'")
    private Long scheduleId;
    @Schema(description = "阶段名称")
    @Column(columnDefinition = "VARCHAR(64) comment '阶段名称'")
    private String scheduleName;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/entity/PaymentConfirm.java
@@ -36,6 +36,10 @@
    @Column(columnDefinition="bigint comment '合同ID'")
    private Long contractId;
    @Schema(description = "合同编号")
    @Column(columnDefinition = "VARCHAR(64) comment '合同编号'")
    private String contractNo;
    @Schema(description = "收到总公司通知时间")
    @Column(columnDefinition="datetime comment '收到总公司通知时间'")
    private Date confirmTime;
@@ -63,4 +67,12 @@
    @Schema(description = "总额")
    @Column(columnDefinition="decimal(10,2) comment '总额  /元 两位小数'")
    private BigDecimal totalAmount;
    @Schema(description = "阶段ID")
    @Column(columnDefinition = "bigint comment '阶段ID'")
    private Long scheduleId;
    @Schema(description = "阶段名称")
    @Column(columnDefinition = "VARCHAR(64) comment '阶段名称'")
    private String scheduleName;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ContractDetailVo.java
New file
@@ -0,0 +1,76 @@
package com.by4cloud.platformx.business.vo;
import com.by4cloud.platformx.business.dto.ContractPaymentScheduleAddDTO;
import com.by4cloud.platformx.business.dto.ContractSubjectMatterAddDTO;
import com.by4cloud.platformx.business.entity.ContractPaymentSchedule;
import com.by4cloud.platformx.business.entity.ContractSubjectMatter;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.List;
@Data
public class ContractDetailVo {
    private Long id;
    @Schema(description = "合同名称")
    private String contractName;
    @Schema(description = "甲方id")
    private Long partyAId;
    @Schema(description = "甲方名称")
    private String partyA;
    @Schema(description = "乙方id")
    private Long partyBId;
    @Schema(description = "乙方名称")
    private String partyB;
    @Schema(description = "合同金额")
    private Double amount;
    @Schema(description = "币种(CNY/USD等)")
    private String currency;
    @Schema(description = "合同类型(0-生成式 1-备案式)")
    private Integer contractType;
    @Schema(description = "供应属性 0-成品 1-备件 2-大修 3-其他")
    private Integer supplyAttribute;
    @Schema(description = "合同状态(0-草稿 1-已生效 2-已终止 3-已过期)")
    private Integer contractStatus;
    @Schema(description = "合同服务属性(0-产品销售 1-无形服务)")
    private Integer contractAttribute;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "签署日期")
    private String signDate;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "生效日期")
    private String effectiveDate;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "到期日期")
    private String expirationDate;
    @Schema(description = "合同附件URL")
    private String attachmentUrl;
    @Schema(description = "备注")
    private String remark;
    private List<ContractPaymentSchedule> contractPaymentSchedule;
    private List<ContractSubjectMatter> contractSubjectMatter;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ContractPaymentScheduleVo.java
New file
@@ -0,0 +1,47 @@
package com.by4cloud.platformx.business.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Column;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class ContractPaymentScheduleVo {
    private Long id;
    @Schema(description = "收款阶段名称(显性要素,如:合同签订预付、发货前付款、货到签收付款、调试完成付款、质保金)")
    private String stageName;
    @Schema(description = "计划收款金额(根据合同总金额*比例自动计算)")
    private BigDecimal plannedAmount;
    @Schema(description = "实际收款金额")
    private BigDecimal actualAmount;
    @Schema(description = "收款阶段生效日期(隐性要素 - 履约时间起点,即该节点实际达成的日期)")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date effectiveDate;
    @Schema(description = "收款阶段截止日期")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date effectiveEndDate;
    @Schema(description = "收款日期")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date paymentDate;
    @Schema(description = "收款状态(0-未收款 1-部分收款 2-已收款 3-逾期)")
    private Integer paymentStatus;
    private List<ScheduleProcessVo> processVoList;
}
platformx-business-finance-api/src/main/java/com/by4cloud/platformx/business/vo/ScheduleProcessVo.java
New file
@@ -0,0 +1,24 @@
package com.by4cloud.platformx.business.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class ScheduleProcessVo {
    @Schema(description = "履约时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date processDate;
    @Schema(description = "履约单据名称")
    private String outBoundAttNames;
    @Schema(description = "履约单据路径")
    private String outBoundAttPaths;
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractBillingController.java
New file
@@ -0,0 +1,61 @@
package com.by4cloud.platformx.business.controller;
/**
 * 合同管理
 *
 * @author syt
 * @date 2026-04-29 16:46:26
 */
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.entity.Contract;
import com.by4cloud.platformx.business.service.ContractService;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.mybatis.BaseModel;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/contractBilling" )
@Tag(description = "contractBilling" , name = "合同开票管理" )
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class ContractBillingController {
    private final ContractService contractService;
    /**
     * 分页查询
     * @param page 分页对象
     * @param contract 合同管理
     * @return
     */
    @Operation(summary = "分页查询" , description = "分页查询" )
    @GetMapping("/page" )
    public R getContractPage(@ParameterObject Page page, @ParameterObject Contract contract) {
        LambdaQueryWrapper<Contract> wrapper = Wrappers.lambdaQuery();
        wrapper.like(StringUtils.isNotBlank(contract.getContractName()),Contract::getContractName,contract.getContractName());
        wrapper.like(StringUtils.isNotBlank(contract.getPartyA()),Contract::getPartyA,contract.getPartyA());
        wrapper.eq(Contract::getBillingStatus,contract.getBillingStatus());
        if (StrUtil.equals(contract.getBillingStatus(),"1")){
            wrapper.orderByDesc(BaseModel::getCreateTime);
        }else {
            wrapper.orderByAsc(BaseModel::getCreateTime);
        }
        return R.ok(contractService.pageByScope(page, wrapper));
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractController.java
@@ -5,11 +5,14 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.dto.ContractAddDTO;
import com.by4cloud.platformx.business.dto.ContractUpdateDTO;
import com.by4cloud.platformx.business.entity.Contract;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.mybatis.BaseModel;
import com.by4cloud.platformx.common.log.annotation.SysLog;
import com.by4cloud.platformx.business.service.ContractService;
import com.by4cloud.platformx.common.security.annotation.Inner;
import com.by4cloud.platformx.common.security.util.SecurityUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -67,36 +70,33 @@
    @GetMapping("/{id}" )
    @PreAuthorize("@pms.hasPermission('business_contract_view')" )
    public R getById(@PathVariable("id" ) Long id) {
        return R.ok(contractService.getById(id));
        return R.ok(contractService.detail(id));
    }
    /**
     * 新增合同管理
     * @param contract 合同管理
     * @param addDTO 合同管理
     * @return R
     */
    @Operation(summary = "新增合同管理" , description = "新增合同管理" )
    @SysLog("新增合同管理" )
    @PostMapping
    @PreAuthorize("@pms.hasPermission('business_contract_add')" )
    public R save(@RequestBody Contract contract) {
        Long compId = SecurityUtils.getUser().getCompId();
        contract.setPartyBId(contract.getCompId());
        contract.setPartyB(contract.getCompName());
        return R.ok(contractService.save(contract));
    public R save(@RequestBody ContractAddDTO addDTO) {
        return contractService.add(addDTO);
    }
    /**
     * 修改合同管理
     * @param contract 合同管理
     * @param updateDTO 合同管理
     * @return R
     */
    @Operation(summary = "修改合同管理" , description = "修改合同管理" )
    @SysLog("修改合同管理" )
    @PutMapping
    @PreAuthorize("@pms.hasPermission('business_contract_edit')" )
    public R updateById(@RequestBody Contract contract) {
        return R.ok(contractService.updateById(contract));
    public R updateById(@RequestBody ContractUpdateDTO updateDTO) {
        return contractService.edit(updateDTO);
    }
    /**
@@ -125,4 +125,27 @@
    public List<Contract> export(Contract contract,Long[] ids) {
        return contractService.list(Wrappers.lambdaQuery(contract).in(ArrayUtil.isNotEmpty(ids), Contract::getId, ids));
    }
    /**
     * 通过id合同生效
     * @param id id
     * @return R
     */
    @Operation(summary = "通过id合同生效" , description = "通过id合同生效" )
    @GetMapping("/takeEffect/{id}" )
    @PreAuthorize("@pms.hasPermission('business_contract_effect')" )
    public R takeEffect(@PathVariable("id" ) Long id) {
        return R.ok(contractService.takeEffect(id));
    }
    /**
     * 定时生成应收款账目
     * @return R
     */
    @GetMapping("/genCurrentOverdue" )
    @Inner(value = false)
    public R genCurrentOverdue() {
        return contractService.genCurrentOverdue();
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/ContractPaymentScheduleProcessController.java
New file
@@ -0,0 +1,129 @@
package com.by4cloud.platformx.business.controller;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.dto.ContractPaymentScheduleProcessAddDTO;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.log.annotation.SysLog;
import com.by4cloud.platformx.business.entity.ContractPaymentScheduleProcess;
import com.by4cloud.platformx.business.service.ContractPaymentScheduleProcessService;
import org.springframework.security.access.prepost.PreAuthorize;
import com.by4cloud.platformx.common.excel.annotation.ResponseExcel;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Objects;
/**
 * 履约节点
 *
 * @author syt
 * @date 2026-05-11 11:10:26
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/contractPaymentScheduleProcess" )
@Tag(description = "contractPaymentScheduleProcess" , name = "履约节点管理" )
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class ContractPaymentScheduleProcessController {
    private final  ContractPaymentScheduleProcessService contractPaymentScheduleProcessService;
    /**
     * 分页查询
     * @param page 分页对象
     * @param contractPaymentScheduleProcess 履约节点
     * @return
     */
    @Operation(summary = "分页查询" , description = "分页查询" )
    @GetMapping("/page" )
    @PreAuthorize("@pms.hasPermission('business_contractPaymentScheduleProcess_view')" )
    public R getContractPaymentScheduleProcessPage(@ParameterObject Page page, @ParameterObject ContractPaymentScheduleProcess contractPaymentScheduleProcess) {
        LambdaQueryWrapper<ContractPaymentScheduleProcess> wrapper = Wrappers.lambdaQuery();
        return R.ok(contractPaymentScheduleProcessService.page(page, wrapper));
    }
    /**
     * 通过id查询履约节点
     * @param id id
     * @return R
     */
    @Operation(summary = "通过id查询" , description = "通过id查询" )
    @GetMapping("/{id}" )
    @PreAuthorize("@pms.hasPermission('business_contractPaymentScheduleProcess_view')" )
    public R getById(@PathVariable("id" ) Long id) {
        return R.ok(contractPaymentScheduleProcessService.getById(id));
    }
    /**
     * 通过合同id查询履约节点
     * @param id id
     * @return R
     */
    @GetMapping("/selectScheduleProcess/{id}" )
    public R selectScheduleProcess(@PathVariable("id" ) Long id) {
        return contractPaymentScheduleProcessService.selectScheduleProcess(id);
    }
    /**
     * 新增履约节点
     * @param addDTO 履约节点
     * @return R
     */
    @Operation(summary = "新增履约节点" , description = "新增履约节点" )
    @SysLog("新增履约节点" )
    @PostMapping
    public R save(@RequestBody ContractPaymentScheduleProcessAddDTO addDTO) {
        return contractPaymentScheduleProcessService.add(addDTO);
    }
    /**
     * 修改履约节点
     * @param contractPaymentScheduleProcess 履约节点
     * @return R
     */
    @Operation(summary = "修改履约节点" , description = "修改履约节点" )
    @SysLog("修改履约节点" )
    @PutMapping
    @PreAuthorize("@pms.hasPermission('business_contractPaymentScheduleProcess_edit')" )
    public R updateById(@RequestBody ContractPaymentScheduleProcess contractPaymentScheduleProcess) {
        return R.ok(contractPaymentScheduleProcessService.updateById(contractPaymentScheduleProcess));
    }
    /**
     * 通过id删除履约节点
     * @param ids id列表
     * @return R
     */
    @Operation(summary = "通过id删除履约节点" , description = "通过id删除履约节点" )
    @SysLog("通过id删除履约节点" )
    @DeleteMapping
    @PreAuthorize("@pms.hasPermission('business_contractPaymentScheduleProcess_del')" )
    public R removeById(@RequestBody Long[] ids) {
        return R.ok(contractPaymentScheduleProcessService.removeBatchByIds(CollUtil.toList(ids)));
    }
    /**
     * 导出excel 表格
     * @param contractPaymentScheduleProcess 查询条件
        * @param ids 导出指定ID
     * @return excel 文件流
     */
    @ResponseExcel
    @GetMapping("/export")
    @PreAuthorize("@pms.hasPermission('business_contractPaymentScheduleProcess_export')" )
    public List<ContractPaymentScheduleProcess> export(ContractPaymentScheduleProcess contractPaymentScheduleProcess,Long[] ids) {
        return contractPaymentScheduleProcessService.list(Wrappers.lambdaQuery(contractPaymentScheduleProcess).in(ArrayUtil.isNotEmpty(ids), ContractPaymentScheduleProcess::getId, ids));
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/CurrentOverdueController.java
@@ -54,7 +54,7 @@
        wrapper.like(StrUtil.isNotBlank(currentOverdue.getBusGuestName()),CurrentOverdue::getBusGuestName,currentOverdue.getBusGuestName());
        wrapper.like(StrUtil.isNotBlank(currentOverdue.getContractName()),CurrentOverdue::getContractName,currentOverdue.getContractName());
        wrapper.orderByDesc(CurrentOverdue::getCreateTime);
        return R.ok(currentOverdueService.page(page, wrapper));
        return R.ok(currentOverdueService.pageByScope(page, wrapper));
    }
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/HistoryOverdueController.java
@@ -52,7 +52,7 @@
        wrapper.like(StrUtil.isNotBlank(historyOverdue.getBusGuestName()),HistoryOverdue::getBusGuestName,historyOverdue.getBusGuestName());
        wrapper.like(StrUtil.isNotBlank(historyOverdue.getContractName()),HistoryOverdue::getContractName,historyOverdue.getContractName());
        wrapper.orderByDesc(HistoryOverdue::getCreateTime);
        return R.ok(historyOverdueService.page(page, wrapper));
        return R.ok(historyOverdueService.pageByScope(page, wrapper));
    }
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/OutBoundController.java
@@ -6,6 +6,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.dto.OutBoundAddDTO;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.log.annotation.SysLog;
import com.by4cloud.platformx.business.entity.OutBound;
@@ -36,7 +37,7 @@
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class OutBoundController {
    private final  OutBoundService outBoundService;
    private final OutBoundService outBoundService;
    /**
     * 分页查询
@@ -52,7 +53,7 @@
        wrapper.like(StrUtil.isNotBlank(outBound.getBusGuestName()),OutBound::getBusGuestName,outBound.getBusGuestName());
        wrapper.like(StrUtil.isNotBlank(outBound.getSubjectMatterName()),OutBound::getSubjectMatterName,outBound.getSubjectMatterName());
        wrapper.orderByDesc(OutBound::getCreateTime);
        return R.ok(outBoundService.page(page, wrapper));
        return R.ok(outBoundService.pageByScope(page, wrapper));
    }
@@ -70,15 +71,15 @@
    /**
     * 新增EPR出库记录
     * @param outBound EPR出库记录
     * @param addDTO EPR出库记录
     * @return R
     */
    @Operation(summary = "新增EPR出库记录" , description = "新增EPR出库记录" )
    @SysLog("新增EPR出库记录" )
    @PostMapping
    @PreAuthorize("@pms.hasPermission('business_outBound_add')" )
    public R save(@RequestBody OutBound outBound) {
        return R.ok(outBoundService.save(outBound));
    public R save(@RequestBody OutBoundAddDTO addDTO) {
        return outBoundService.add(addDTO);
    }
    /**
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/controller/PaymentConfirmController.java
@@ -6,6 +6,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.by4cloud.platformx.business.dto.PaymentConfirmAddDTO;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.log.annotation.SysLog;
import com.by4cloud.platformx.business.entity.PaymentConfirm;
@@ -51,8 +52,9 @@
        LambdaQueryWrapper<PaymentConfirm> wrapper = Wrappers.lambdaQuery();
        wrapper.like(StrUtil.isNotBlank(paymentConfirm.getBusGuestName()),PaymentConfirm::getBusGuestName,paymentConfirm.getBusGuestName());
        wrapper.like(StrUtil.isNotBlank(paymentConfirm.getContractName()),PaymentConfirm::getContractName,paymentConfirm.getContractName());
        wrapper.like(StrUtil.isNotBlank(paymentConfirm.getBusinessType()),PaymentConfirm::getBusinessType,paymentConfirm.getBusinessType());
        wrapper.orderByDesc(PaymentConfirm::getCreateTime);
        return R.ok(paymentConfirmService.page(page, wrapper));
        return R.ok(paymentConfirmService.pageByScope(page, wrapper));
    }
@@ -70,15 +72,15 @@
    /**
     * 新增收款确认
     * @param paymentConfirm 收款确认
     * @param addDTO 收款确认
     * @return R
     */
    @Operation(summary = "新增收款确认" , description = "新增收款确认" )
    @SysLog("新增收款确认" )
    @PostMapping
    @PreAuthorize("@pms.hasPermission('business_paymentConfirm_add')" )
    public R save(@RequestBody PaymentConfirm paymentConfirm) {
        return R.ok(paymentConfirmService.save(paymentConfirm));
    public R save(@RequestBody PaymentConfirmAddDTO addDTO) {
        return paymentConfirmService.add(addDTO);
    }
    /**
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/mapper/ContractPaymentScheduleProcessMapper.java
New file
@@ -0,0 +1,11 @@
package com.by4cloud.platformx.business.mapper;
import com.by4cloud.platformx.common.data.datascope.PlatformxBaseMapper;
import com.by4cloud.platformx.business.entity.ContractPaymentScheduleProcess;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ContractPaymentScheduleProcessMapper extends PlatformxBaseMapper<ContractPaymentScheduleProcess> {
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractPaymentScheduleProcessService.java
New file
@@ -0,0 +1,13 @@
package com.by4cloud.platformx.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.by4cloud.platformx.business.dto.ContractPaymentScheduleProcessAddDTO;
import com.by4cloud.platformx.business.entity.ContractPaymentScheduleProcess;
import com.by4cloud.platformx.common.core.util.R;
public interface ContractPaymentScheduleProcessService extends IService<ContractPaymentScheduleProcess> {
    R add(ContractPaymentScheduleProcessAddDTO addDTO);
    R selectScheduleProcess(Long id);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/ContractService.java
@@ -1,7 +1,11 @@
package com.by4cloud.platformx.business.service;
import com.by4cloud.platformx.business.dto.ContractAddDTO;
import com.by4cloud.platformx.business.dto.ContractUpdateDTO;
import com.by4cloud.platformx.business.entity.Contract;
import com.by4cloud.platformx.business.vo.ContractDetailVo;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.mybatis.IIService;
/**
@@ -9,4 +13,13 @@
 */
public interface ContractService extends IIService<Contract> {
    R add(ContractAddDTO addDTO);
    R edit(ContractUpdateDTO updateDTO);
    ContractDetailVo detail(Long id);
    Boolean takeEffect(Long id);
    R genCurrentOverdue();
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/CurrentOverdueService.java
@@ -1,8 +1,8 @@
package com.by4cloud.platformx.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.by4cloud.platformx.business.entity.CurrentOverdue;
import com.by4cloud.platformx.common.data.mybatis.IIService;
public interface CurrentOverdueService extends IService<CurrentOverdue> {
public interface CurrentOverdueService extends IIService<CurrentOverdue> {
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/HistoryOverdueService.java
@@ -1,8 +1,8 @@
package com.by4cloud.platformx.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.by4cloud.platformx.business.entity.HistoryOverdue;
import com.by4cloud.platformx.common.data.mybatis.IIService;
public interface HistoryOverdueService extends IService<HistoryOverdue> {
public interface HistoryOverdueService extends IIService<HistoryOverdue> {
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/OutBoundService.java
@@ -1,8 +1,11 @@
package com.by4cloud.platformx.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.by4cloud.platformx.business.dto.OutBoundAddDTO;
import com.by4cloud.platformx.business.entity.OutBound;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.mybatis.IIService;
public interface OutBoundService extends IService<OutBound> {
public interface OutBoundService extends IIService<OutBound> {
    R add(OutBoundAddDTO addDTO);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/PaymentConfirmService.java
@@ -1,8 +1,11 @@
package com.by4cloud.platformx.business.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.by4cloud.platformx.business.dto.PaymentConfirmAddDTO;
import com.by4cloud.platformx.business.entity.PaymentConfirm;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.data.mybatis.IIService;
public interface PaymentConfirmService extends IService<PaymentConfirm> {
public interface PaymentConfirmService extends IIService<PaymentConfirm> {
    R add(PaymentConfirmAddDTO addDTO);
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractPaymentScheduleProcessServiceImpl.java
New file
@@ -0,0 +1,181 @@
package com.by4cloud.platformx.business.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.by4cloud.platformx.business.dto.ContractPaymentScheduleProcessAddDTO;
import com.by4cloud.platformx.business.entity.Contract;
import com.by4cloud.platformx.business.entity.ContractPaymentSchedule;
import com.by4cloud.platformx.business.entity.ContractPaymentScheduleProcess;
import com.by4cloud.platformx.business.entity.PaymentConfirm;
import com.by4cloud.platformx.business.mapper.ContractMapper;
import com.by4cloud.platformx.business.mapper.ContractPaymentScheduleMapper;
import com.by4cloud.platformx.business.mapper.ContractPaymentScheduleProcessMapper;
import com.by4cloud.platformx.business.mapper.PaymentConfirmMapper;
import com.by4cloud.platformx.business.service.ContractPaymentScheduleProcessService;
import com.by4cloud.platformx.business.vo.ContractPaymentScheduleVo;
import com.by4cloud.platformx.business.vo.ScheduleProcessVo;
import com.by4cloud.platformx.common.core.util.R;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
 * 履约节点
 *
 * @author syt
 * @date 2026-05-11 11:10:26
 */
@Service
@RequiredArgsConstructor
public class ContractPaymentScheduleProcessServiceImpl extends ServiceImpl<ContractPaymentScheduleProcessMapper, ContractPaymentScheduleProcess> implements ContractPaymentScheduleProcessService {
    private final ContractPaymentScheduleMapper contractPaymentScheduleMapper;
    private final ContractPaymentScheduleProcessMapper processMapper;
    private final ContractMapper contractMapper;
    private final PaymentConfirmMapper paymentConfirmMapper;
    @Override
    public R add(ContractPaymentScheduleProcessAddDTO addDTO) {
        ContractPaymentSchedule schedule = contractPaymentScheduleMapper.selectById(addDTO.getScheduleId());
        if (ObjUtil.isNull(schedule)){
            return R.failed("履约阶段与合同不一致,请联系技术人员");
        }
        //合同
        Contract contract = contractMapper.selectById(schedule.getContractId());
        //新增当前阶段应收
        PaymentConfirm currentConfim = new PaymentConfirm();
        currentConfim.setBusinessType(schedule.getStageName()+"应收");
        currentConfim.setBusGuestId(contract.getPartyAId());
        currentConfim.setBusGuestName(contract.getPartyA());
        currentConfim.setContractId(contract.getId());
        currentConfim.setContractName(contract.getContractName());
        currentConfim.setContractNo(contract.getContractNo());
        currentConfim.setScheduleId(schedule.getId());
        currentConfim.setScheduleName(schedule.getStageName());
        currentConfim.setConfirmTime(addDTO.getProcessDate());
        currentConfim.setTransationAmount(schedule.getPlannedAmount());
        currentConfim.setReceivableAmount(schedule.getPlannedAmount());
        PaymentConfirm lastConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId,contract.getId())
                .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
        BigDecimal lastTotal = new BigDecimal("0");
        if(ObjUtil.isNotNull(lastConfirm)){
            lastTotal = lastConfirm.getTotalAmount();
        }
        currentConfim.setTotalAmount(lastTotal.subtract(currentConfim.getReceivableAmount()));
        paymentConfirmMapper.insert(currentConfim);
        //履约
        ContractPaymentScheduleProcess contractPaymentScheduleProcess = BeanUtil.copyProperties(addDTO,ContractPaymentScheduleProcess.class);
        contractPaymentScheduleProcess.setContractId(schedule.getContractId());
        contractPaymentScheduleProcess.setContractName(schedule.getContractName());
        contractPaymentScheduleProcess.setScheduleId(schedule.getId());
        contractPaymentScheduleProcess.setScheduleName(schedule.getStageName());
        baseMapper.insert(contractPaymentScheduleProcess);
        //更新当前阶段
        if (ObjUtil.isNull(schedule.getEffectiveDate())) {
            schedule.setEffectiveDate(DateUtil.offsetDay(addDTO.getProcessDate(),schedule.getAgreedDays()));
            contractPaymentScheduleMapper.updateById(schedule);
        }
        //查询是否有之前阶段
        ContractPaymentSchedule beforeSchedule = contractPaymentScheduleMapper.selectOne(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId, schedule.getContractId())
                .lt(ContractPaymentSchedule::getStageOrder, schedule.getStageOrder()).orderByDesc(ContractPaymentSchedule::getCreateTime).last("limit 1"));
        if (ObjUtil.isNotNull(beforeSchedule)){
            beforeSchedule.setEffectiveEndDate(DateUtil.offsetDay(addDTO.getProcessDate(),beforeSchedule.getAgreedDays()));
            contractPaymentScheduleMapper.updateById(beforeSchedule);
            //之前阶段是否收款完成
//            if (beforeSchedule.getPaymentStatus()!=2){
//                //新增之前阶段超期
//                PaymentConfirm beforeConfim = new PaymentConfirm();
//                beforeConfim.setBusinessType("应收超期");
//                beforeConfim.setBusGuestId(contract.getPartyAId());
//                beforeConfim.setBusGuestName(contract.getPartyA());
//                beforeConfim.setContractId(contract.getId());
//                beforeConfim.setContractName(contract.getContractName());
//                beforeConfim.setContractNo(contract.getContractNo());
//                beforeConfim.setScheduleId(schedule.getId());
//                beforeConfim.setScheduleName(schedule.getStageName());
//                beforeConfim.setConfirmTime(addDTO.getProcessDate());
//                beforeConfim.setTransationAmount(schedule.getPlannedAmount());
//                PaymentConfirm newLastConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId,contract.getId())
//                        .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
//                beforeConfim.setOverdueAmount(StrUtil.equals(schedule.getPaymentStatus()+"","0")?
//                        schedule.getPlannedAmount():
//                        schedule.getPlannedAmount().subtract(schedule.getActualAmount()));
//                beforeConfim.setTotalAmount(newLastConfirm.getTotalAmount());
//                paymentConfirmMapper.insert(beforeConfim);
//            }
        }
        //查询是否有后续阶段
        List<ContractPaymentSchedule> afterSchedule = contractPaymentScheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery()
                .eq(ContractPaymentSchedule::getContractId, schedule.getContractId())
                .gt(ContractPaymentSchedule::getStageOrder, schedule.getStageOrder()));
        if (ArrayUtil.isNotEmpty(afterSchedule.toArray())&&afterSchedule.size()==1){
            //最后阶段生效时间
            ContractPaymentSchedule endSchedule = afterSchedule.get(0);
            endSchedule.setEffectiveDate(DateUtil.offsetDay(addDTO.getProcessDate(),endSchedule.getAgreedDays()));
            endSchedule.setEffectiveEndDate(contract.getExpirationDate());
            contractPaymentScheduleMapper.updateById(endSchedule);
            //当前阶段生效时间
            schedule.setEffectiveDate(DateUtil.offsetDay(addDTO.getProcessDate(),schedule.getAgreedDays()));
            schedule.setEffectiveEndDate(endSchedule.getEffectiveDate());
            contractPaymentScheduleMapper.updateById(schedule);
            //最后阶段应收
            PaymentConfirm newConfim = new PaymentConfirm();
            newConfim.setBusinessType(endSchedule.getStageName()+"应收");
            newConfim.setBusGuestId(contract.getPartyAId());
            newConfim.setBusGuestName(contract.getPartyA());
            newConfim.setContractId(contract.getId());
            newConfim.setContractName(contract.getContractName());
            newConfim.setContractNo(contract.getContractNo());
            newConfim.setScheduleId(endSchedule.getId());
            newConfim.setScheduleName(schedule.getStageName());
            newConfim.setConfirmTime(addDTO.getProcessDate());
            newConfim.setTransationAmount(endSchedule.getPlannedAmount());
            newConfim.setReceivableAmount(endSchedule.getPlannedAmount());
            PaymentConfirm lastNewConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId,contract.getId())
                    .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
            BigDecimal lastNewTotal = new BigDecimal("0");
            if(ObjUtil.isNotNull(lastNewConfirm)){
                lastNewTotal = lastNewConfirm.getTotalAmount();
            }
            newConfim.setTotalAmount(lastNewTotal.subtract(newConfim.getReceivableAmount()));
            paymentConfirmMapper.insert(newConfim);
        }else {
            schedule.setEffectiveEndDate(contract.getExpirationDate());
            contractPaymentScheduleMapper.updateById(schedule);
        }
        return R.ok();
    }
    @Override
    public R selectScheduleProcess(Long id) {
        MPJLambdaWrapper<ContractPaymentSchedule> wrapper = new MPJLambdaWrapper<ContractPaymentSchedule>()
                .select(ContractPaymentSchedule::getId,ContractPaymentSchedule::getStageName,ContractPaymentSchedule::getEffectiveDate,
                        ContractPaymentSchedule::getPlannedAmount,ContractPaymentSchedule::getPlannedAmount,
                        ContractPaymentSchedule::getEffectiveEndDate,ContractPaymentSchedule::getPaymentDate,
                        ContractPaymentSchedule::getPaymentStatus,ContractPaymentSchedule::getActualAmount)
                .eq(ContractPaymentSchedule::getContractId,id)
                .orderByAsc(ContractPaymentSchedule::getCreateTime);
        List<ContractPaymentScheduleVo> scheduleVoList = contractPaymentScheduleMapper.selectJoinList(ContractPaymentScheduleVo.class,wrapper);
        if (ArrayUtil.isEmpty(scheduleVoList.toArray())){
            scheduleVoList.stream().forEach(contractPaymentScheduleVo -> {
                List<ContractPaymentScheduleProcess> paymentScheduleProcessList = processMapper.selectList(Wrappers.<ContractPaymentScheduleProcess>lambdaQuery()
                        .eq(ContractPaymentScheduleProcess::getContractId,contractPaymentScheduleVo.getId())
                        .orderByAsc(ContractPaymentScheduleProcess::getCreateTime));
                if (ArrayUtil.isNotEmpty(paymentScheduleProcessList.toArray())){
                    contractPaymentScheduleVo.setProcessVoList(BeanUtil.copyToList(paymentScheduleProcessList, ScheduleProcessVo.class));
                }
            });
        }
        return R.ok(scheduleVoList);
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/ContractServiceImpl.java
@@ -1,10 +1,29 @@
package com.by4cloud.platformx.business.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.by4cloud.platformx.business.entity.Contract;
import com.by4cloud.platformx.business.mapper.ContractMapper;
import com.by4cloud.platformx.business.dto.ContractAddDTO;
import com.by4cloud.platformx.business.dto.ContractUpdateDTO;
import com.by4cloud.platformx.business.entity.*;
import com.by4cloud.platformx.business.mapper.*;
import com.by4cloud.platformx.business.service.ContractService;
import com.by4cloud.platformx.business.utils.ContractNumberGenerator;
import com.by4cloud.platformx.business.vo.ContractDetailVo;
import com.by4cloud.platformx.common.core.util.R;
import com.by4cloud.platformx.common.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @author cd
@@ -12,6 +31,280 @@
 * @date 2026/4/29 14:07
 **/
@Service
@RequiredArgsConstructor
public class ContractServiceImpl extends ServiceImpl<ContractMapper, Contract> implements ContractService {
    private final ContractSubjectMatterMapper contractSubjectMatterMapper;
    private final ContractPaymentScheduleMapper contractPaymentScheduleMapper;
    private final ContractPaymentScheduleProcessMapper contractPaymentScheduleProcessMapper;
    private final PaymentConfirmMapper paymentConfirmMapper;
    private final CurrentOverdueMapper currentOverdueMapper;
    @Override
    public R add(ContractAddDTO addDTO) {
        Contract contract = BeanUtil.copyProperties(addDTO,Contract.class);
        contract.setPartyBId(SecurityUtils.getUser().getCompId());
        contract.setPartyB(SecurityUtils.getUser().getCompName());
        contract.setContractNo(ContractNumberGenerator.generateContractNumber());
        contract.setBillingStatus("0");
        contract.setErpPushFlag("0");
        List<Contract> contracts;
        contracts = baseMapper.selectList(Wrappers.<Contract>lambdaQuery().eq(Contract::getContractNo,contract.getContractNo()));
        while (ArrayUtil.isNotEmpty(contracts.toArray())){
            contract.setContractNo(ContractNumberGenerator.generateContractNumber());
            contracts = baseMapper.selectList(Wrappers.<Contract>lambdaQuery().eq(Contract::getContractNo,contract.getContractNo()));
        }
        baseMapper.insert(contract);
        if (ArrayUtil.isNotEmpty(addDTO.getContractSubjectMatter())){
            addDTO.getContractSubjectMatter().stream().forEach(contractSubjectMatterAddDTO -> {
                ContractSubjectMatter subjectMatter = BeanUtil.copyProperties(contractSubjectMatterAddDTO, ContractSubjectMatter.class);
                subjectMatter.setContractId(contract.getId());
                subjectMatter.setContractName(contract.getContractName());
                subjectMatter.setDeliveredQuantity(new BigDecimal("0"));
                subjectMatter.setDeliveryStatus(0);
                subjectMatter.setTotalAmount(contractSubjectMatterAddDTO.getQuantity().multiply(contractSubjectMatterAddDTO.getUnitPrice()));
                contractSubjectMatterMapper.insert(subjectMatter);
            });
        }
        if (ArrayUtil.isNotEmpty(addDTO.getContractPaymentSchedule())){
            AtomicInteger index = new AtomicInteger(1);
            addDTO.getContractPaymentSchedule().stream().forEach(contractPaymentScheduleAddDTO -> {
                int currentIndex = index.getAndIncrement();
                ContractPaymentSchedule schedule = BeanUtil.copyProperties(contractPaymentScheduleAddDTO, ContractPaymentSchedule.class);
                if (contractPaymentScheduleAddDTO.getStageName().equals("合同签订")){
                    schedule.setEffectiveDate(DateUtil.offsetDay(contract.getSignDate(),contractPaymentScheduleAddDTO.getAgreedDays()));
                }
                if (ObjUtil.isNotNull(schedule.getEffectiveDate())) {
                    if (schedule.getEffectiveDate().before(DateUtil.date())) {
                        schedule.setFulfillmentStatus(1);
                    } else {
                        schedule.setFulfillmentStatus(0);
                    }
                }
                schedule.setPaymentStatus(0);
                schedule.setContractId(contract.getId());
                schedule.setContractName(contract.getContractName());
                schedule.setPlannedAmount(contract.getAmount().multiply(schedule.getPaymentRatio().divide(new BigDecimal("100"))));
                schedule.setStageOrder(currentIndex);
                contractPaymentScheduleMapper.insert(schedule);
                if (contractPaymentScheduleAddDTO.getStageName().equals("货到签收")){
                    contract.setArrivalScheduleId(schedule.getId());
                    baseMapper.updateById(contract);
                }
                if (contractPaymentScheduleAddDTO.getStageName().equals("调试完成或验收")){
                    contract.setAcceptScheduleId(schedule.getId());
                    baseMapper.updateById(contract);
                }
            });
        }
        return R.ok();
    }
    @Override
    public R edit(ContractUpdateDTO updateDTO) {
        Contract entity = baseMapper.selectById(updateDTO.getId());
        if (!StrUtil.equals(entity.getContractStatus()+"","0")){
            return R.failed("当前状态无法修改合同");
        }
        Contract contract = BeanUtil.copyProperties(updateDTO,Contract.class);
        contract.setContractNo(ContractNumberGenerator.generateContractNumber());
        baseMapper.updateById(contract);
        if (ArrayUtil.isNotEmpty(updateDTO.getContractSubjectMatter())){
            contractSubjectMatterMapper.delete(Wrappers.<ContractSubjectMatter>lambdaQuery().eq(ContractSubjectMatter::getContractId,contract.getId()));
            updateDTO.getContractSubjectMatter().stream().forEach(contractSubjectMatterAddDTO -> {
                ContractSubjectMatter subjectMatter = BeanUtil.copyProperties(contractSubjectMatterAddDTO, ContractSubjectMatter.class);
                subjectMatter.setContractId(contract.getId());
                subjectMatter.setContractName(contract.getContractName());
                subjectMatter.setDeliveredQuantity(new BigDecimal("0"));
                subjectMatter.setDeliveryStatus(0);
                contractSubjectMatterMapper.insert(subjectMatter);
            });
        }
        if (ArrayUtil.isNotEmpty(updateDTO.getContractPaymentSchedule())){
            contractPaymentScheduleMapper.delete(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId,contract.getId()));
            AtomicInteger index = new AtomicInteger(1);
            updateDTO.getContractPaymentSchedule().stream().forEach(contractPaymentScheduleAddDTO -> {
                int currentIndex = index.getAndIncrement();
                ContractPaymentSchedule subjectMatter = BeanUtil.copyProperties(contractPaymentScheduleAddDTO, ContractPaymentSchedule.class);
                subjectMatter.setContractId(contract.getId());
                subjectMatter.setContractName(contract.getContractName());
                subjectMatter.setPlannedAmount(contract.getAmount().multiply(subjectMatter.getPaymentRatio().divide(new BigDecimal("100"))));
                subjectMatter.setStageOrder(currentIndex);
                contractPaymentScheduleMapper.insert(subjectMatter);
            });
        }
        return R.ok();
    }
    @Override
    public ContractDetailVo detail(Long id) {
        Contract contract = baseMapper.selectById(id);
        ContractDetailVo detailVo = BeanUtil.copyProperties(contract,ContractDetailVo.class);
        List<ContractSubjectMatter> subjectMatterList = contractSubjectMatterMapper.selectList(Wrappers.<ContractSubjectMatter>lambdaQuery().eq(ContractSubjectMatter::getContractId,id));
        detailVo.setContractSubjectMatter(subjectMatterList);
        List<ContractPaymentSchedule> paymentScheduleList = contractPaymentScheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId,contract.getId()));
        detailVo.setContractPaymentSchedule(paymentScheduleList);
        return detailVo;
    }
    @Override
    public Boolean takeEffect(Long id) {
        Contract contract = baseMapper.selectById(id);
        contract.setContractStatus(1);
        baseMapper.updateById(contract);
        ContractPaymentSchedule fitstSchedule = contractPaymentScheduleMapper.selectOne(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId,contract.getId())
                .orderByAsc(ContractPaymentSchedule::getCreateTime).last("limit 1"));
//        PaymentConfirm entity = new PaymentConfirm();
//        entity.setContractId(contract.getId());
//        entity.setContractName(contract.getContractName());
//        entity.setContractNo(contract.getContractNo());
//        entity.setBusGuestId(contract.getPartyAId());
//        entity.setBusGuestName(contract.getPartyA());
//        entity.setBusinessType(fitstSchedule.getStageName()+"应收");
//        entity.setTransationAmount(new BigDecimal(fitstSchedule.getPlannedAmount()));
//        entity.setReceivableAmount(new BigDecimal(fitstSchedule.getPlannedAmount()));
//        entity.setTotalAmount(new BigDecimal("0").subtract(new BigDecimal(fitstSchedule.getPlannedAmount())));
//        entity.setConfirmTime(new Date());
//        paymentConfirmMapper.insert(entity);
        if (fitstSchedule.getStageName().equals("合同签订")){
            //新增应收
            PaymentConfirm paymentConfirm = new PaymentConfirm();
            paymentConfirm.setBusinessType("合同签订应收");
            paymentConfirm.setBusGuestId(contract.getPartyAId());
            paymentConfirm.setBusGuestName(contract.getPartyA());
            paymentConfirm.setContractId(contract.getId());
            paymentConfirm.setContractName(contract.getContractName());
            paymentConfirm.setContractNo(contract.getContractNo());
            paymentConfirm.setScheduleId(fitstSchedule.getId());
            paymentConfirm.setScheduleName(fitstSchedule.getStageName());
            paymentConfirm.setConfirmTime(new Date());
            paymentConfirm.setTransationAmount(fitstSchedule.getPlannedAmount());
            paymentConfirm.setReceivableAmount(fitstSchedule.getPlannedAmount());
            paymentConfirm.setTotalAmount(fitstSchedule.getPlannedAmount().multiply(new BigDecimal("-1")));
            paymentConfirmMapper.insert(paymentConfirm);
            //新增合同履约记录
            ContractPaymentScheduleProcess process = new ContractPaymentScheduleProcess();
            process.setContractId(contract.getId());
            process.setContractName(contract.getContractName());
            process.setScheduleId(fitstSchedule.getId());
            process.setScheduleName(fitstSchedule.getStageName());
            process.setProcessDate(contract.getSignDate());
            contractPaymentScheduleProcessMapper.insert(process);
        }
        //查询是否有后续阶段
        List<ContractPaymentSchedule> afterSchedule = contractPaymentScheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery()
                .eq(ContractPaymentSchedule::getContractId, fitstSchedule.getContractId())
                .gt(ContractPaymentSchedule::getStageOrder, fitstSchedule.getStageOrder()));
        if (ArrayUtil.isNotEmpty(afterSchedule.toArray())&&afterSchedule.size()==1){
            //最后阶段生效时间
            ContractPaymentSchedule endSchedule = afterSchedule.get(0);
            endSchedule.setEffectiveDate(DateUtil.offsetDay(new Date(),endSchedule.getAgreedDays()));
            endSchedule.setEffectiveEndDate(contract.getExpirationDate());
            contractPaymentScheduleMapper.updateById(endSchedule);
            //第一阶段生效时间
            fitstSchedule.setEffectiveDate(DateUtil.offsetDay(new Date(),fitstSchedule.getAgreedDays()));
            fitstSchedule.setEffectiveEndDate(endSchedule.getEffectiveDate());
            contractPaymentScheduleMapper.updateById(fitstSchedule);
            //最后阶段应收
            PaymentConfirm newConfim = new PaymentConfirm();
            newConfim.setBusinessType(endSchedule.getStageName()+"应收");
            newConfim.setBusGuestId(contract.getPartyAId());
            newConfim.setBusGuestName(contract.getPartyA());
            newConfim.setContractId(contract.getId());
            newConfim.setContractName(contract.getContractName());
            newConfim.setContractNo(contract.getContractNo());
            newConfim.setScheduleId(endSchedule.getId());
            newConfim.setScheduleName(endSchedule.getStageName());
            newConfim.setConfirmTime(new Date());
            newConfim.setTransationAmount(endSchedule.getPlannedAmount());
            newConfim.setReceivableAmount(endSchedule.getPlannedAmount());
            PaymentConfirm lastNewConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId,contract.getId())
                    .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
            BigDecimal lastNewTotal = new BigDecimal("0");
            if(ObjUtil.isNotNull(lastNewConfirm)){
                lastNewTotal = lastNewConfirm.getTotalAmount();
            }
            newConfim.setTotalAmount(lastNewTotal.subtract(newConfim.getReceivableAmount()));
            paymentConfirmMapper.insert(newConfim);
        }
        if(ArrayUtil.isEmpty(afterSchedule.toArray())){
            fitstSchedule.setEffectiveEndDate(contract.getExpirationDate());
            contractPaymentScheduleMapper.updateById(fitstSchedule);
        }
        return Boolean.TRUE;
    }
    @Override
    public R genCurrentOverdue() {
        List<ContractPaymentSchedule> scheduleList = contractPaymentScheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery()
                .lt(ContractPaymentSchedule::getEffectiveEndDate, DateUtil.today())
                .ne(ContractPaymentSchedule::getPaymentStatus,"2"));
        if (ArrayUtil.isNotEmpty(scheduleList.toArray())){
            scheduleList.stream().forEach(contractPaymentSchedule -> {
                Contract contract = baseMapper.selectById(contractPaymentSchedule.getContractId());
                //应收超期
                PaymentConfirm overdueConfirm = new PaymentConfirm();
                overdueConfirm.setBusinessType("应收超期");
                overdueConfirm.setBusGuestId(contract.getPartyAId());
                overdueConfirm.setBusGuestName(contract.getPartyA());
                overdueConfirm.setContractId(contract.getId());
                overdueConfirm.setContractName(contract.getContractName());
                overdueConfirm.setContractNo(contract.getContractNo());
                overdueConfirm.setConfirmTime(new Date());
                overdueConfirm.setScheduleId(contractPaymentSchedule.getId());
                overdueConfirm.setScheduleName(contractPaymentSchedule.getStageName());
                PaymentConfirm newLastConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId,contract.getId())
                        .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
                overdueConfirm.setTransationAmount(StrUtil.equals(contractPaymentSchedule.getPaymentStatus()+"","0")?
                        contractPaymentSchedule.getPlannedAmount():
                        contractPaymentSchedule.getPlannedAmount().subtract(contractPaymentSchedule.getActualAmount()));
                overdueConfirm.setReceivableAmount(newLastConfirm.getTotalAmount().multiply(new BigDecimal("-1")));
                overdueConfirm.setOverdueAmount(overdueConfirm.getTransationAmount());
                overdueConfirm.setTotalAmount(newLastConfirm.getTotalAmount());
                overdueConfirm.setCompId(contract.getCompId());
                //判断当前阶段是否已生成上阶段应收超期
                PaymentConfirm oiverdueConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getScheduleId, contractPaymentSchedule.getId())
                        .eq(PaymentConfirm::getBusinessType, "应收超期"));
                if (ObjUtil.isNull(oiverdueConfirm)) {
                    paymentConfirmMapper.insert(overdueConfirm);
                }
                //当前逾期
                CurrentOverdue currentOverdue = new CurrentOverdue();
                currentOverdue.setBusGuestId(contract.getPartyAId());
                currentOverdue.setBusGuestName(contract.getPartyA());
                currentOverdue.setContractId(contractPaymentSchedule.getContractId());
                currentOverdue.setContractName(contractPaymentSchedule.getContractName());
                currentOverdue.setScheduleId(contractPaymentSchedule.getId());
                currentOverdue.setScheduleName(contractPaymentSchedule.getStageName());
                currentOverdue.setCompId(contract.getCompId());
                currentOverdue.setContractExpirTime(contractPaymentSchedule.getEffectiveEndDate());
                currentOverdue.setReceivableAmount(StrUtil.equals(contractPaymentSchedule.getPaymentStatus()+"","0")?
                        contractPaymentSchedule.getPlannedAmount():contractPaymentSchedule.getPlannedAmount().subtract(contractPaymentSchedule.getActualAmount()));
                currentOverdue.setOverdueDuration(BigDecimal.valueOf(DateUtil.betweenDay(contractPaymentSchedule.getEffectiveEndDate(),new Date(),true)));
                CurrentOverdue overdue = currentOverdueMapper.selectOne(Wrappers.<CurrentOverdue>lambdaQuery().eq(CurrentOverdue::getContractId,currentOverdue.getContractId())
                        .eq(CurrentOverdue::getScheduleId,currentOverdue.getScheduleId()).last("limit 1"));
                if (ObjUtil.isNull(overdue)){
                    currentOverdueMapper.insert(currentOverdue);
                }else {
                    overdue.setOverdueDuration(BigDecimal.valueOf(DateUtil.betweenDay(contractPaymentSchedule.getEffectiveEndDate(),new Date(),true)));
                    currentOverdueMapper.updateById(overdue);
                }
            });
        }
        return R.ok();
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/OutBoundServiceImpl.java
@@ -1,12 +1,29 @@
package com.by4cloud.platformx.business.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.by4cloud.platformx.business.entity.OutBound;
import com.by4cloud.platformx.business.mapper.OutBoundMapper;
import com.by4cloud.platformx.business.dto.OutBoundAddDTO;
import com.by4cloud.platformx.business.dto.OutSubjectMatterAddDTO;
import com.by4cloud.platformx.business.entity.*;
import com.by4cloud.platformx.business.mapper.*;
import com.by4cloud.platformx.business.service.OutBoundService;
import com.by4cloud.platformx.common.core.util.R;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * EPR出库记录
@@ -15,11 +32,231 @@
 * @date 2026-04-29 11:32:05
 */
@Service
@RequiredArgsConstructor
public class OutBoundServiceImpl extends ServiceImpl<OutBoundMapper, OutBound> implements OutBoundService {
    private final ContractPaymentScheduleMapper scheduleMapper;
    private final ContractSubjectMatterMapper contractSubjectMatterMapper;
    private final PaymentConfirmMapper paymentConfirmMapper;
    private final ContractMapper contractMapper;
    private final ContractPaymentScheduleProcessMapper scheduleProcessMapper;
    @Override
    public boolean save(OutBound entity) {
        entity.setOutBoundTime(new Date());
        return super.save(entity);
    public R add(OutBoundAddDTO addDTO) {
        if (ArrayUtil.isEmpty(addDTO.getSubjectMatterList().toArray())) {
            return R.failed("出库标的物不能为空");
        }
        MPJLambdaWrapper<ContractSubjectMatter> wrapper = new MPJLambdaWrapper<ContractSubjectMatter>()
                .selectAll(ContractSubjectMatter.class)
                .ne(ContractSubjectMatter::getDeliveryStatus, 2)
                .exists(Contract.class, contractQuery ->
                        contractQuery.select(ContractSubjectMatter::getId)
                                .eq(Contract::getId, ContractSubjectMatter::getContractId)
                                .eq(StrUtil.isNotBlank(addDTO.getBusGuestName()), Contract::getPartyA, addDTO.getBusGuestName())
                                .eq(ObjUtil.isNotNull(addDTO.getBusGuestId()), Contract::getPartyAId, addDTO.getBusGuestId())
                )
                .orderByAsc(ContractSubjectMatter::getCreateTime);
        List<ContractSubjectMatter> subjectMatterList = contractSubjectMatterMapper.selectList(wrapper);
        if (ArrayUtil.isEmpty(subjectMatterList.toArray())) {
            return R.failed("没有查询到相关合同订单");
        }
        Double outTotal = subjectMatterList.stream().mapToDouble(item -> {
            // 优先判断 remainNum
            if (item.getRemainingQuantity() != null && item.getRemainingQuantity().compareTo(new BigDecimal("0")) > 0) {
                return item.getRemainingQuantity().doubleValue();
            } else {
                // remainNum 为 null 或 0 时,使用 quantity
                return item.getQuantity() == null ? 0.0 : item.getQuantity().doubleValue();
            }
        }).sum();
        BigDecimal orderNum = addDTO.getSubjectMatterList().stream().map(OutSubjectMatterAddDTO::getOutBoundNum)
                .filter(num -> num != null) // 过滤掉 null 值,防止 NullPointerException
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (new BigDecimal(outTotal).compareTo(orderNum) < 0) {
            return R.failed("出库数量超出合同订单数量");
        }
        //当前入库标的物
        List<ContractSubjectMatter> currentOutList = new ArrayList<>();
        addDTO.getSubjectMatterList().stream().forEach(outSubjectMatterAddDTO -> {
            BigDecimal remainNum = outSubjectMatterAddDTO.getOutBoundNum();
            for (ContractSubjectMatter subjectMatter : subjectMatterList) {
                if (subjectMatter.getDeliveredQuantity().compareTo(new BigDecimal("0")) > 0) {
                    remainNum = remainNum.subtract(subjectMatter.getRemainingQuantity());
                    //部分交付状态下 全部出库
                    if (remainNum.compareTo(new BigDecimal("0")) >= 0) {
                        BigDecimal currentOut = subjectMatter.getRemainingQuantity();
                        subjectMatter.setDeliveredQuantity(subjectMatter.getQuantity());
                        subjectMatter.setRemainingQuantity(new BigDecimal("0"));
                        subjectMatter.setActualDeliveryDate(DateUtil.today());
                        subjectMatter.setDeliveryStatus(2);
                        subjectMatter.setLastDeliveredQuantity(currentOut);
                        contractSubjectMatterMapper.updateById(subjectMatter);
                        currentOutList.add(subjectMatter);
                    } else {
                        BigDecimal currentOut = remainNum.add(subjectMatter.getRemainingQuantity());
                        subjectMatter.setDeliveredQuantity(subjectMatter.getQuantity().add(remainNum));
                        subjectMatter.setRemainingQuantity(remainNum.multiply(new BigDecimal("-1")));
                        subjectMatter.setActualDeliveryDate(DateUtil.today());
                        subjectMatter.setDeliveryStatus(1);
                        subjectMatter.setLastDeliveredQuantity(currentOut);
                        contractSubjectMatterMapper.updateById(subjectMatter);
                        currentOutList.add(subjectMatter);
                        break;
                    }
                } else {
                    //未交付状态下
                    remainNum = remainNum.subtract(subjectMatter.getQuantity());
                    if (remainNum.compareTo(new BigDecimal("0")) >= 0) {
                        subjectMatter.setDeliveredQuantity(subjectMatter.getQuantity());
                        subjectMatter.setRemainingQuantity(new BigDecimal("0"));
                        subjectMatter.setActualDeliveryDate(DateUtil.today());
                        subjectMatter.setDeliveryStatus(2);
                        contractSubjectMatterMapper.updateById(subjectMatter);
                        subjectMatter.setLastDeliveredQuantity(subjectMatter.getQuantity());
                        currentOutList.add(subjectMatter);
                    } else {
                        BigDecimal currentOut = remainNum.add(subjectMatter.getQuantity());
                        subjectMatter.setDeliveredQuantity(subjectMatter.getQuantity().add(remainNum));
                        subjectMatter.setRemainingQuantity(remainNum.multiply(new BigDecimal("-1")));
                        subjectMatter.setActualDeliveryDate(DateUtil.today());
                        subjectMatter.setDeliveryStatus(1);
                        subjectMatter.setLastDeliveredQuantity(currentOut);
                        contractSubjectMatterMapper.updateById(subjectMatter);
                        currentOutList.add(subjectMatter);
                        break;
                    }
                }
            }
            OutBound outBound = BeanUtil.copyProperties(outSubjectMatterAddDTO, OutBound.class);
            outBound.setBusGuestName(addDTO.getBusGuestName());
            outBound.setBusGuestId(addDTO.getBusGuestId());
            outBound.setOutBoundTime(addDTO.getOutBoundTime());
            baseMapper.insert(outBound);
        });
        //出库应收款明细
        if (ArrayUtil.isNotEmpty(currentOutList.toArray())) {
            //根据合同id拆分应收明细
            Map<Long, List<ContractSubjectMatter>> conSubMatMap = currentOutList.stream().collect(Collectors.groupingBy(ContractSubjectMatter::getContractId));
            conSubMatMap.forEach((key, value) -> {
                //查询合同是否有出库阶段
                ContractPaymentSchedule schedule = scheduleMapper.selectOne(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId, key)
                        .eq(ContractPaymentSchedule::getStageName, "发货前"));
                //当前出库金额
                BigDecimal outPirce = value.stream()
                        .filter(item -> item.getLastDeliveredQuantity() != null && item.getUnitPrice() != null)
                        .map(item -> item.getLastDeliveredQuantity().multiply(item.getUnitPrice()))
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                //合同金额
                Contract contract = contractMapper.selectById(key);
                //出库是否完成
                if (outPirce.compareTo(contract.getAmount())<0){
                    contract.setBillingStatus("1");
                    contractMapper.updateById(contract);
                }
                if (outPirce.compareTo(contract.getAmount())==0){
                    contract.setBillingStatus("2");
                    contractMapper.updateById(contract);
                }
                if (ObjUtil.isNotNull(schedule)) {
                    //按出库资金比例 计算发货前应收更新资金明细
                    PaymentConfirm paymentConfirm = new PaymentConfirm();
                    paymentConfirm.setBusinessType("发货前应收");
                    paymentConfirm.setBusGuestId(contract.getPartyAId());
                    paymentConfirm.setBusGuestName(contract.getPartyA());
                    paymentConfirm.setContractId(contract.getId());
                    paymentConfirm.setContractName(contract.getContractName());
                    paymentConfirm.setContractNo(contract.getContractNo());
                    paymentConfirm.setScheduleId(schedule.getId());
                    paymentConfirm.setScheduleName(schedule.getStageName());
                    paymentConfirm.setConfirmTime(addDTO.getOutBoundTime());
                    paymentConfirm.setTransationAmount(outPirce.divide(contract.getAmount(), 10, RoundingMode.HALF_UP).multiply(schedule.getPlannedAmount()));
                    paymentConfirm.setReceivableAmount(paymentConfirm.getTransationAmount());
                    paymentConfirm.setTotalAmount(paymentConfirm.getTransationAmount().multiply(new BigDecimal("-1")));
                    //最近应收
                    PaymentConfirm lastConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId, contract.getId())
                            .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
                    if (ObjUtil.isNotNull(lastConfirm)) {
                        paymentConfirm.setTotalAmount(paymentConfirm.getTotalAmount().add(lastConfirm.getTotalAmount()));
                        paymentConfirmMapper.insert(paymentConfirm);
                    } else {
                        paymentConfirmMapper.insert(paymentConfirm);
                    }
                    //更新发货前收款生效时间
                    if (ObjUtil.isNull(schedule.getEffectiveDate())){
                        schedule.setEffectiveDate(DateUtil.offsetDay(addDTO.getOutBoundTime(),schedule.getAgreedDays()));
                        //查询是否有后续阶段
                        List<ContractPaymentSchedule> afterSchedule = scheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId, key)
                                .gt(ContractPaymentSchedule::getStageOrder, schedule.getStageOrder()));
                        if (ArrayUtil.isEmpty(afterSchedule.toArray())){
                            schedule.setEffectiveEndDate(contract.getExpirationDate());
                        }
                        scheduleMapper.updateById(schedule);
                        //查询是否有之前阶段
                        ContractPaymentSchedule beforeSchedule = scheduleMapper.selectOne(Wrappers.<ContractPaymentSchedule>lambdaQuery().eq(ContractPaymentSchedule::getContractId, schedule.getContractId())
                                .lt(ContractPaymentSchedule::getStageOrder, schedule.getStageOrder()).orderByDesc(ContractPaymentSchedule::getCreateTime).last("limit 1"));
                        if (ObjUtil.isNotNull(beforeSchedule)){
                            beforeSchedule.setEffectiveEndDate(DateUtil.offsetDay(addDTO.getOutBoundTime(),beforeSchedule.getAgreedDays()));
                            scheduleMapper.updateById(beforeSchedule);
                        }
                    }
                    //更新履约
                    ContractPaymentScheduleProcess contractPaymentScheduleProcess = new ContractPaymentScheduleProcess();
                    contractPaymentScheduleProcess.setContractId(schedule.getContractId());
                    contractPaymentScheduleProcess.setContractName(schedule.getContractName());
                    contractPaymentScheduleProcess.setScheduleId(schedule.getId());
                    contractPaymentScheduleProcess.setScheduleName(schedule.getStageName());
                    contractPaymentScheduleProcess.setProcessDate(addDTO.getOutBoundTime());
                    scheduleProcessMapper.insert(contractPaymentScheduleProcess);
                    //查询是否有后续阶段
                    List<ContractPaymentSchedule> afterSchedule = scheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery()
                            .eq(ContractPaymentSchedule::getContractId, schedule.getContractId())
                            .gt(ContractPaymentSchedule::getStageOrder, schedule.getStageOrder()));
                    if (ArrayUtil.isNotEmpty(afterSchedule.toArray())&&afterSchedule.size()==1){
                        //最后阶段生效时间
                        ContractPaymentSchedule endSchedule = afterSchedule.get(0);
                        endSchedule.setEffectiveDate(DateUtil.offsetDay(addDTO.getOutBoundTime(),endSchedule.getAgreedDays()));
                        endSchedule.setEffectiveEndDate(contract.getExpirationDate());
                        scheduleMapper.updateById(endSchedule);
                        //当前阶段生效时间
                        schedule.setEffectiveDate(DateUtil.offsetDay(addDTO.getOutBoundTime(),schedule.getAgreedDays()));
                        schedule.setEffectiveEndDate(endSchedule.getEffectiveDate());
                        scheduleMapper.updateById(schedule);
                        //最后阶段应收
                        PaymentConfirm newConfim = new PaymentConfirm();
                        newConfim.setBusinessType(endSchedule.getStageName()+"应收");
                        newConfim.setBusGuestId(contract.getPartyAId());
                        newConfim.setBusGuestName(contract.getPartyA());
                        newConfim.setContractId(contract.getId());
                        newConfim.setContractName(contract.getContractName());
                        newConfim.setContractNo(contract.getContractNo());
                        newConfim.setScheduleId(endSchedule.getId());
                        newConfim.setScheduleName(schedule.getStageName());
                        newConfim.setConfirmTime(addDTO.getOutBoundTime());
                        newConfim.setTransationAmount(endSchedule.getPlannedAmount());
                        newConfim.setReceivableAmount(endSchedule.getPlannedAmount());
                        PaymentConfirm lastNewConfirm = paymentConfirmMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId,contract.getId())
                                .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
                        BigDecimal lastNewTotal = new BigDecimal("0");
                        if(ObjUtil.isNotNull(lastNewConfirm)){
                            lastNewTotal = lastNewConfirm.getTotalAmount();
                        }
                        newConfim.setTotalAmount(lastNewTotal.subtract(newConfim.getReceivableAmount()));
                        paymentConfirmMapper.insert(newConfim);
                    }
                    if(ArrayUtil.isEmpty(afterSchedule.toArray())){
                        schedule.setEffectiveEndDate(contract.getExpirationDate());
                        scheduleMapper.updateById(schedule);
                    }
                }
            });
        }
        return R.ok();
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/service/impl/PaymentConfirmServiceImpl.java
@@ -1,12 +1,28 @@
package com.by4cloud.platformx.business.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.by4cloud.platformx.business.entity.PaymentConfirm;
import com.by4cloud.platformx.business.dto.PaymentConfirmAddDTO;
import com.by4cloud.platformx.business.entity.*;
import com.by4cloud.platformx.business.mapper.ContractPaymentScheduleMapper;
import com.by4cloud.platformx.business.mapper.CurrentOverdueMapper;
import com.by4cloud.platformx.business.mapper.HistoryOverdueMapper;
import com.by4cloud.platformx.business.mapper.PaymentConfirmMapper;
import com.by4cloud.platformx.business.service.BusinessCustomerService;
import com.by4cloud.platformx.business.service.ContractPaymentScheduleService;
import com.by4cloud.platformx.business.service.ContractService;
import com.by4cloud.platformx.business.service.PaymentConfirmService;
import com.by4cloud.platformx.common.core.util.R;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
 * 收款确认
@@ -15,11 +31,125 @@
 * @date 2026-04-29 11:33:26
 */
@Service
@RequiredArgsConstructor
public class PaymentConfirmServiceImpl extends ServiceImpl<PaymentConfirmMapper, PaymentConfirm> implements PaymentConfirmService {
    private final ContractService contractService;
    private final BusinessCustomerService businessCustomerService;
    private final ContractPaymentScheduleMapper contractPaymentScheduleMapper;
    private final CurrentOverdueMapper currentOverdueMapper;
    private final HistoryOverdueMapper historyOverdueMapper;
    @Override
    public boolean save(PaymentConfirm entity) {
        entity.setConfirmTime(new Date());
        return super.save(entity);
    public R add(PaymentConfirmAddDTO addDTO) {
        Contract contract = contractService.getOne(Wrappers.<Contract>lambdaQuery().eq(Contract::getContractNo,addDTO.getContractNo()));
        if(ObjUtil.isNull(contract)){
            return R.failed("合同查询失败,请检查合同编号");
        }
        BusinessCustomer customer = businessCustomerService.getOne(Wrappers.<BusinessCustomer>lambdaQuery()
                .eq(StrUtil.isNotBlank(addDTO.getBusGuestName()),BusinessCustomer::getCompanyName,addDTO.getBusGuestName())
                .eq(StrUtil.isNotBlank(addDTO.getBusGuestId()),BusinessCustomer::getId,addDTO.getBusGuestId()));
        if(ObjUtil.isNull(customer)){
            return R.failed("客商查询失败,请检查客商");
        }
        PaymentConfirm lastConfirm = baseMapper.selectOne(Wrappers.<PaymentConfirm>lambdaQuery().eq(PaymentConfirm::getContractId,contract.getId())
                .orderByDesc(PaymentConfirm::getCreateTime).last("limit 1"));
        BigDecimal lastTotal = new BigDecimal("0");
        if(ObjUtil.isNotNull(lastConfirm)){
            lastTotal = lastConfirm.getTotalAmount();
        }
        //转入
        if (StrUtil.equals(addDTO.getInOrOut(),"1")){
            BigDecimal newtotal = addDTO.getTransationAmount().add(lastTotal);
            PaymentConfirm entity = new PaymentConfirm();
            entity.setContractId(contract.getId());
            entity.setContractName(contract.getContractName());
            entity.setContractNo(addDTO.getContractNo());
            if(ObjUtil.isNotNull(lastConfirm)){
                entity.setScheduleId(lastConfirm.getScheduleId());
                entity.setScheduleName(lastConfirm.getScheduleName());
            }
            entity.setBusGuestId(customer.getId());
            entity.setBusGuestName(customer.getCompanyName());
            entity.setBusinessType("客户付款");
            if (newtotal.compareTo(new BigDecimal("0"))>0){
                entity.setTransationAmount(addDTO.getTransationAmount());
                entity.setAdvanceAmount(newtotal);
                entity.setTotalAmount(newtotal);
                entity.setConfirmTime(new Date());
                baseMapper.insert(entity);
            }else if (newtotal.compareTo(new BigDecimal("0"))==0){
                entity.setTransationAmount(addDTO.getTransationAmount());
                entity.setTotalAmount(newtotal);
                entity.setConfirmTime(new Date());
                baseMapper.insert(entity);
            }else {
                entity.setTransationAmount(addDTO.getTransationAmount());
                entity.setReceivableAmount(newtotal.multiply(new BigDecimal("-1")));
                entity.setTotalAmount(newtotal);
                entity.setConfirmTime(new Date());
                baseMapper.insert(entity);
            }
            List<ContractPaymentSchedule> scheduleList = contractPaymentScheduleMapper.selectList(Wrappers.<ContractPaymentSchedule>lambdaQuery()
                    .eq(ContractPaymentSchedule::getContractId,contract.getId()).ne(ContractPaymentSchedule::getPaymentStatus,2)
                    .orderByAsc(ContractPaymentSchedule::getCreateTime));
            if (ArrayUtil.isNotEmpty(scheduleList.toArray())){
                BigDecimal remain = addDTO.getTransationAmount();
                for (ContractPaymentSchedule schedule:scheduleList) {
                    remain = remain.subtract(StrUtil.equals(schedule.getPaymentStatus()+"","0")?schedule.getPlannedAmount():
                            schedule.getPlannedAmount().subtract(schedule.getActualAmount()));
                    if (remain.compareTo(new BigDecimal("0"))>=0){
                        schedule.setActualAmount(schedule.getPlannedAmount());
                        schedule.setPaymentDate(addDTO.getConfirmTime());
                        schedule.setPaymentStatus(2);
                        contractPaymentScheduleMapper.updateById(schedule);
                        //查询是否有当前逾期
                        CurrentOverdue currentOverdue = currentOverdueMapper.selectOne(Wrappers.<CurrentOverdue>lambdaQuery().eq(CurrentOverdue::getScheduleId,schedule.getId())
                                .eq(CurrentOverdue::getContractId,schedule.getContractId()).last("limit 1"));
                        if (ObjUtil.isNotNull(currentOverdue)){
                            //新增逾期历史
                            HistoryOverdue historyOverdue = BeanUtil.copyProperties(currentOverdue,HistoryOverdue.class,"id");
                            historyOverdue.setPaymentTime(addDTO.getConfirmTime());
                            historyOverdueMapper.insert(historyOverdue);
                            //删除当前逾期
                            currentOverdueMapper.deleteById(currentOverdue);
                        }
                    }else {
                        schedule.setActualAmount(StrUtil.equals(schedule.getPaymentStatus()+"","0")?schedule.getPlannedAmount().add(remain):
                                schedule.getPlannedAmount().subtract(schedule.getActualAmount()).add(remain));
                        schedule.setPaymentDate(addDTO.getConfirmTime());
                        schedule.setPaymentStatus(1);
                        contractPaymentScheduleMapper.updateById(schedule);
                        break;
                    }
                }
            }
        }
        //转出
        if (StrUtil.equals(addDTO.getInOrOut(),"2")){
            PaymentConfirm entity = new PaymentConfirm();
            entity.setContractId(contract.getId());
            entity.setContractName(contract.getContractName());
            entity.setContractNo(addDTO.getContractNo());
            entity.setBusGuestId(customer.getId());
            entity.setBusGuestName(customer.getCompanyName());
            entity.setBusinessType("");
            entity.setTransationAmount(addDTO.getTransationAmount());
            BigDecimal newtotal = addDTO.getTransationAmount().multiply(new BigDecimal("-1")).add(lastConfirm.getTotalAmount());
            if (newtotal.compareTo(new BigDecimal("0"))>0){
                entity.setAdvanceAmount(newtotal);
                entity.setTotalAmount(newtotal);
            }else if (newtotal.compareTo(new BigDecimal("0"))==0){
                entity.setTotalAmount(newtotal);
            }else {
                return R.failed("当前客户余额为"+lastConfirm.getTotalAmount()+"不足以满足当前发生金额,无法操作");
            }
            entity.setConfirmTime(new Date());
            baseMapper.insert(entity);
        }
        return R.ok();
    }
}
platformx-business-finance-biz/src/main/java/com/by4cloud/platformx/business/utils/ContractNumberGenerator.java
New file
@@ -0,0 +1,40 @@
package com.by4cloud.platformx.business.utils;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class ContractNumberGenerator {
    // 用于存储每天的序列号计数器,Key为日期字符串,Value为原子整数
    private static final ConcurrentHashMap<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();
    // 日期格式化器,格式为 yyyyMMdd
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
    /**
     * 生成合同编号
     * 格式: yyyyMMdd-XXXX (XXXX为当日自增序列,不足4位补0)
     *
     * @return 合同编号字符串
     */
    public static String generateContractNumber() {
        // 1. 获取当前日期字符串
        String dateStr = LocalDate.now().format(DATE_FORMATTER);
        // 2. 获取或创建当天的计数器
        // computeIfAbsent 保证原子性,如果key不存在则创建新的AtomicInteger(0)
        AtomicInteger counter = counterMap.computeIfAbsent(dateStr, k -> new AtomicInteger(0));
        // 3. 递增并获取当前序列号
        int sequence = counter.incrementAndGet();
        // 4. 格式化序列号,例如 1 -> "0001"
        String formattedSequence = String.format("%04d", sequence);
        // 5. 组合返回
        return dateStr + "-" + formattedSequence;
    }
}
platformx-business-finance-biz/src/main/resources/mapper/ContractPaymentScheduleProcessMapper.xml
New file
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.by4cloud.platformx.business.mapper.ContractPaymentScheduleProcessMapper">
  <resultMap id="contractPaymentScheduleProcessMap" type="com.by4cloud.platformx.business.entity.ContractPaymentScheduleProcess">
        <id property="id" column="id"/>
        <result property="compId" column="comp_id"/>
        <result property="contractId" column="contract_id"/>
        <result property="contractName" column="contract_name"/>
        <result property="outBoundAttNames" column="out_bound_att_names"/>
        <result property="outBoundAttPaths" column="out_bound_att_paths"/>
        <result property="processDate" column="process_date"/>
        <result property="scheduleId" column="schedule_id"/>
        <result property="scheduleName" column="schedule_name"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="delFlag" column="del_flag"/>
  </resultMap>
</mapper>