!6 fix: 项目+合同对接合同系统
Merge pull request !6 from mindless/feature/hcs-api-p0
This commit is contained in:
+49
@@ -220,4 +220,53 @@ public class ContractManage extends TenantEntity {
|
||||
@Schema(description = "其他事项说明")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "合规系统单据ID(fdContractId)")
|
||||
private String hcsFdId;
|
||||
|
||||
@Schema(description = "合规同步状态 unsynced/synced/failed")
|
||||
private String hcsSyncStatus;
|
||||
|
||||
@Schema(description = "合规推送失败重试次数")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private Integer hcsRetryCount;
|
||||
|
||||
@Schema(description = "合规最近同步时间")
|
||||
private LocalDateTime hcsSyncTime;
|
||||
|
||||
@Schema(description = "合规最近同步结果信息/失败原因")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String hcsSyncMsg;
|
||||
|
||||
@Schema(description = "合规履约计划ID(C103-9 返回 fdExeplanId)")
|
||||
private String hcsExeplanId;
|
||||
|
||||
@Schema(description = "合规客商风险等级(C101-1,甲方/乙方取高)")
|
||||
private String hcsRiskLevel;
|
||||
|
||||
@Schema(description = "合规客商黑名单状态(C101-1,甲方/乙方取是)")
|
||||
private String hcsBlackStatus;
|
||||
|
||||
@Schema(description = "合规风险校验时间")
|
||||
private LocalDateTime hcsRiskTime;
|
||||
|
||||
@Schema(description = "合规特批流程ID(C103-8 返回)")
|
||||
private String hcsSpecFdId;
|
||||
|
||||
@Schema(description = "合规特批状态 none/need/processing/approved/rejected")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String hcsSpecStatus;
|
||||
|
||||
@Schema(description = "合规最近特批动作时间")
|
||||
private LocalDateTime hcsSpecTime;
|
||||
|
||||
@Schema(description = "合规特批结果/发起失败原因")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String hcsSpecMsg;
|
||||
|
||||
@Schema(description = "特批申请材料+内部审批附件JSON(含C103-2 合规附件ID)")
|
||||
private String hcsSpecFilesJson;
|
||||
|
||||
@Schema(description = "金蝶合同ID(contractLegal/result 合同特批金蝶同步结果回调回填)")
|
||||
private String kingdeeId;
|
||||
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* HCS 附件转传映射实体类(C103-2 转传取得合规附件ID,单附件行级,照金蝶附件同步表设计)
|
||||
* <p>
|
||||
* 仅落成功记录:HCS 转传任一附件失败即抛异常整单重试(失败原因落单据 hcs_sync_msg),故无失败态字段。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_hcs_attachment_sync")
|
||||
@Schema(description = "HCS附件转传映射")
|
||||
public class HcsAttachmentSync extends TenantEntity {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 业务单据类型(contract/payment/invoice-application/invoice-receipt)
|
||||
*/
|
||||
private String bizType;
|
||||
/**
|
||||
* 业务单据id
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long bizId;
|
||||
/**
|
||||
* 附件幂等键(url|link|fileUrl|downloadUrl|uid|originalName|name 依次取首个非空)
|
||||
*/
|
||||
private String attachmentKey;
|
||||
/**
|
||||
* 附件名称(展示/排查用)
|
||||
*/
|
||||
private String attachmentName;
|
||||
/**
|
||||
* 合规附件ID(C103-2 返回 fdId)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 最近转传时间
|
||||
*/
|
||||
private LocalDateTime syncTime;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* HCS 风险/额度校验留痕实体类(照 ops 日志惯例,仅插入不改)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@TableName("blade_hcs_check_log")
|
||||
@Schema(description = "HCS 风险/额度校验留痕")
|
||||
public class HcsContractCheckLog implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private String tenantId;
|
||||
|
||||
@Schema(description = "业务类型 project/contract/payment")
|
||||
private String bizType;
|
||||
|
||||
@Schema(description = "业务单据ID")
|
||||
private Long bizId;
|
||||
|
||||
@Schema(description = "校验类型 risk=客商风险/credit=额度")
|
||||
private String checkType;
|
||||
|
||||
@Schema(description = "校验结果 pass=通过/fail=不通过/skip=跳过")
|
||||
private String result;
|
||||
|
||||
@Schema(description = "校验说明")
|
||||
private String msg;
|
||||
|
||||
@Schema(description = "合规系统链路追踪ID")
|
||||
private String traceId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.tenant.mp.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 合同履约规则实体类(HCS 履约阶段,设计文档 §4.1,ADR-0002)
|
||||
* <p>
|
||||
* 合同维度的资金计划阶段,C103-9 行级同步的数据源:合同审批通过置 row_op=add,
|
||||
* 后续编辑按行差异置 add/update/delete,由扫描任务随合同链路同步;
|
||||
* 合规返回的阶段ID 必须持久化(hcsStageFdId),行更新/删除全靠它。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("blade_contract_exe_stage")
|
||||
@Schema(description = "合同履约规则(HCS 履约阶段)")
|
||||
public class HcsContractExeStage extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@Schema(description = "合同ID")
|
||||
private Long contractId;
|
||||
|
||||
@Schema(description = "履约类型 payment=付款/collection=收款")
|
||||
private String performType;
|
||||
|
||||
@Schema(description = "款项性质 prepayment/progress/acceptance/retention")
|
||||
private String fundNature;
|
||||
|
||||
@Schema(description = "款项金额(元,付款审批通过按申请付款金额累加更新)")
|
||||
private BigDecimal fundAmount;
|
||||
|
||||
@Schema(description = "计划资金日期")
|
||||
private LocalDateTime fundDate;
|
||||
|
||||
@Schema(description = "开票规则(如 payFirst)")
|
||||
private String invoiceRule;
|
||||
|
||||
@Schema(description = "账期")
|
||||
private Integer invoiceCycle;
|
||||
|
||||
@Schema(description = "金额类型")
|
||||
private String fundType;
|
||||
|
||||
@Schema(description = "是否完成")
|
||||
private Integer isFinish;
|
||||
|
||||
@Schema(description = "行级同步操作 none/add/update/delete")
|
||||
private String rowOp;
|
||||
|
||||
@Schema(description = "合规阶段ID(C103-9 返回,update/delete 必用)")
|
||||
private String hcsStageFdId;
|
||||
|
||||
@Schema(description = "同步状态 unsynced/synced/failed")
|
||||
private String hcsSyncStatus;
|
||||
|
||||
@Schema(description = "同步结果信息/失败原因")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String hcsSyncMsg;
|
||||
|
||||
}
|
||||
+10
@@ -183,4 +183,14 @@ public class InvoiceApplication extends TenantEntity {
|
||||
* 作废原因,驳回时记录驳回原因
|
||||
*/
|
||||
private String voidReason;
|
||||
/** 合规系统单据ID(fdId) */
|
||||
private String hcsFdId;
|
||||
/** 合规同步状态:unsynced 待同步 / synced 已同步 / failed 同步失败 */
|
||||
private String hcsSyncStatus;
|
||||
/** 合规推送失败重试次数 */
|
||||
private Integer hcsRetryCount;
|
||||
/** 合规最近同步时间 */
|
||||
private LocalDateTime hcsSyncTime;
|
||||
/** 合规最近同步结果信息/失败原因 */
|
||||
private String hcsSyncMsg;
|
||||
}
|
||||
|
||||
+14
@@ -180,4 +180,18 @@ public class InvoiceReceipt extends TenantEntity {
|
||||
* 作废原因
|
||||
*/
|
||||
private String voidReason;
|
||||
/** 合规系统单据ID(fdId) */
|
||||
private String hcsFdId;
|
||||
/** 合规同步状态:unsynced 待同步 / synced 已同步 / failed 同步失败 */
|
||||
private String hcsSyncStatus;
|
||||
/** 合规推送失败重试次数 */
|
||||
private Integer hcsRetryCount;
|
||||
/** 合规最近同步时间 */
|
||||
private LocalDateTime hcsSyncTime;
|
||||
/** 合规最近同步结果信息/失败原因 */
|
||||
private String hcsSyncMsg;
|
||||
/** 合规推送合同ID(应付结算回溯不到时人工补录,D13) */
|
||||
private Long hcsContractId;
|
||||
/** 合规推送合同编号(应付结算回溯不到时人工补录,D13) */
|
||||
private String hcsContractNo;
|
||||
}
|
||||
|
||||
+23
@@ -33,6 +33,7 @@ import org.springblade.core.tenant.mp.TenantEntity;
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 付款申请实体。 @author Chill */
|
||||
@Data
|
||||
@@ -120,4 +121,26 @@ public class PaymentApplication extends TenantEntity {
|
||||
private String attachmentsJson;
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
/** 合规系统单据ID(fdId) */
|
||||
private String hcsFdId;
|
||||
/** 合规同步状态:unsynced 待同步 / synced 已同步 / failed 同步失败 */
|
||||
private String hcsSyncStatus;
|
||||
/** 合规推送失败重试次数 */
|
||||
private Integer hcsRetryCount;
|
||||
/** 合规最近同步时间 */
|
||||
private LocalDateTime hcsSyncTime;
|
||||
/** 合规最近同步结果信息/失败原因 */
|
||||
private String hcsSyncMsg;
|
||||
/** 额度校验结果:pass 通过 / over 超额(C106-1) */
|
||||
private String hcsCreditStatus;
|
||||
/** 额度校验说明(超额原因 fdMessage) */
|
||||
private String hcsCreditMsg;
|
||||
/** 额度校验时间 */
|
||||
private LocalDateTime hcsCreditTime;
|
||||
/** 结算付款特批流程ID(C106-3 返回,C106-4 必传) */
|
||||
private String hcsSpecFdId;
|
||||
/** 特批状态:none/need/pushing/processing/approved/rejected */
|
||||
private String hcsSpecStatus;
|
||||
/** 履约记录ID(C108-1 返回,C108-2 更新必传) */
|
||||
private String hcsPerfFdId;
|
||||
}
|
||||
|
||||
+23
@@ -198,4 +198,27 @@ public class ProjectApply extends TenantEntity {
|
||||
@Schema(description = "作废原因")
|
||||
private String voidReason;
|
||||
|
||||
@Schema(description = "合规系统单据ID(fdId)")
|
||||
private String hcsFdId;
|
||||
|
||||
@Schema(description = "合规同步状态 unsynced/synced/failed")
|
||||
private String hcsSyncStatus;
|
||||
|
||||
@Schema(description = "合规推送失败重试次数")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private Integer hcsRetryCount;
|
||||
|
||||
@Schema(description = "合规最近同步时间")
|
||||
private LocalDateTime hcsSyncTime;
|
||||
|
||||
@Schema(description = "合规最近同步结果信息/失败原因")
|
||||
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String hcsSyncMsg;
|
||||
|
||||
@Schema(description = "金蝶项目ID(D902 项目金蝶同步结果回调回填)")
|
||||
private String kingdeeId;
|
||||
|
||||
@Schema(description = "金蝶项目编号(D902 项目金蝶同步结果回调回填)")
|
||||
private String kingdeeNumber;
|
||||
|
||||
}
|
||||
|
||||
+8
@@ -55,6 +55,14 @@ public class ContractManageVO extends ContractManage {
|
||||
@Schema(description = "到期快捷筛选")
|
||||
private String expireScope;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否已同步金蝶(金蝶合同ID是否已回填)")
|
||||
private Boolean kingdeeSynced;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否已同步合规(合规同步状态是否为 synced)")
|
||||
private Boolean hcsSynced;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "生效类型筛选")
|
||||
private String effectiveType;
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 客商风险展示 VO(C101-1 查询结果 + TMS 侧组装提示,前端接入设计 §3.3)
|
||||
* <p>
|
||||
* 内部组织类型客商默认无风险(不调 C101-1);外部客商高风险/黑名单时 risky=true 并返回 riskTip
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "客商风险展示")
|
||||
public class CustomerRiskVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "客商编号")
|
||||
private String customerCode;
|
||||
|
||||
@Schema(description = "客商类型:internal内部组织(默认无风险)/external外部客商")
|
||||
private String customerKind;
|
||||
|
||||
@Schema(description = "客商名称")
|
||||
private String customerName;
|
||||
|
||||
@Schema(description = "黑名单状态:yes/no")
|
||||
private String fdBlackStatus;
|
||||
|
||||
@Schema(description = "风险等级:0=低风险,1=中风险,2=高风险")
|
||||
private String fdRiskLevel;
|
||||
|
||||
@Schema(description = "风险等级名称(服务端转义:黑名单/高风险/中风险/低风险,黑名单优先;无等级为空)")
|
||||
private String riskLevelName;
|
||||
|
||||
@Schema(description = "是否高风险/黑名单客商(外部客商专用,内部组织恒为 false)")
|
||||
private Boolean risky;
|
||||
|
||||
@Schema(description = "风险客商提示文案(基本信息备注后展示,安全客商为空)")
|
||||
private String riskTip;
|
||||
|
||||
}
|
||||
+8
@@ -86,6 +86,14 @@ public class ProjectApplyVO extends ProjectApply {
|
||||
@Schema(description = "是否仅查询可用于合同选择的项目(含临时项目与正式审批通过项目)")
|
||||
private Boolean contractSelectable;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否已同步金蝶(金蝶项目ID是否已回填)")
|
||||
private Boolean kingdeeSynced;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否已同步合规(合规同步状态是否为 synced)")
|
||||
private Boolean hcsSynced;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "资金使用风险等级:high、medium、none")
|
||||
private String fundUseRisk;
|
||||
|
||||
@@ -51,6 +51,10 @@
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-bfi-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-hcs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
|
||||
+2
@@ -28,6 +28,7 @@ package org.springblade.transport.config;
|
||||
import lombok.Data;
|
||||
import org.springblade.thirdparty.bfi.constant.BfiConstant;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -45,6 +46,7 @@ import java.util.Map;
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@RefreshScope
|
||||
@ConfigurationProperties(prefix = "thirdparty.bfi")
|
||||
public class BfiProperties {
|
||||
|
||||
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.constant;
|
||||
|
||||
/**
|
||||
* HCS(合同合规系统)C103-1/C103-11 合同发起 xformFields 字段名常量
|
||||
* <p>
|
||||
* 字段名取自 v4 接口文档「合同字段说明」,fdSepcailContract 为文档原文拼写(Special 笔误,必须沿用);
|
||||
* 命名照 {@link HcsSyncConstant} 惯例。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class HcsContractFieldConstant {
|
||||
|
||||
// ==================== 合同发起 xformFields 字段名(fdXformField.name) ====================
|
||||
|
||||
/**
|
||||
* 合同ID(更新时传 C103-1 返回的 fdContractId)
|
||||
*/
|
||||
public static final String FIELD_FD_ID = "fdId";
|
||||
|
||||
/**
|
||||
* 合同编号(fd 模板字段)
|
||||
*/
|
||||
public static final String FIELD_FD_NUMBER = "fdNumber";
|
||||
|
||||
/**
|
||||
* 合同编号(CONTRACT 模板字段)
|
||||
*/
|
||||
public static final String FIELD_CONTRACT_NO = "CONTRACT_NO";
|
||||
|
||||
/**
|
||||
* 合同标题(fd 模板字段)
|
||||
*/
|
||||
public static final String FIELD_FD_SUBJECT = "fdSubject";
|
||||
|
||||
/**
|
||||
* 合同名称(CONTRACT 模板字段)
|
||||
*/
|
||||
public static final String FIELD_CONTRACT_NAME = "CONTRACT_NAME";
|
||||
|
||||
/**
|
||||
* 合同金额(fd 模板字段)
|
||||
*/
|
||||
public static final String FIELD_FD_AMOUNT = "fdAmount";
|
||||
|
||||
/**
|
||||
* 合同金额(AMOUNT 模板字段)
|
||||
*/
|
||||
public static final String FIELD_AMOUNT = "AMOUNT";
|
||||
|
||||
/**
|
||||
* 币种(fd 模板字段)
|
||||
*/
|
||||
public static final String FIELD_FD_CURRENCY_ID = "fdCurrencyId";
|
||||
|
||||
/**
|
||||
* 币种(CURRENCY 模板字段)
|
||||
*/
|
||||
public static final String FIELD_CURRENCY = "CURRENCY";
|
||||
|
||||
/**
|
||||
* 合同结算类型:payment=付款,payment_collection=收支双向,collection=收款,none=不结算
|
||||
*/
|
||||
public static final String FIELD_FD_SETTLEMENT_TYPE = "fdSettlementType";
|
||||
|
||||
/**
|
||||
* 特批流程(文档原文拼写笔误,必须沿用)
|
||||
*/
|
||||
public static final String FIELD_FD_SEPCAIL_CONTRACT = "fdSepcailContract";
|
||||
|
||||
/**
|
||||
* 合同类型
|
||||
*/
|
||||
public static final String FIELD_CONTRACT_TYPE = "CONTRACT_TYPE";
|
||||
|
||||
/**
|
||||
* 甲方名称
|
||||
*/
|
||||
public static final String FIELD_PARTY_A = "PARTY_A";
|
||||
|
||||
/**
|
||||
* 乙方名称
|
||||
*/
|
||||
public static final String FIELD_PARTY_B = "PARTY_B";
|
||||
|
||||
/**
|
||||
* 签订日期(yyyy-MM-dd,fdType=timestamp)
|
||||
*/
|
||||
public static final String FIELD_SIGN_DATE = "SIGN_DATE";
|
||||
|
||||
/**
|
||||
* 预计开始时间(合同开始期限为指定日期时必填)
|
||||
*/
|
||||
public static final String FIELD_FD_ESTIMATE_START_TIME = "fdEstimateStartTime";
|
||||
|
||||
/**
|
||||
* 预计结束时间(合同结束期限为指定日期时必填)
|
||||
*/
|
||||
public static final String FIELD_FD_ESTIMATE_END_TIME = "fdEstimateEndTime";
|
||||
|
||||
/**
|
||||
* 合同开始期限:signDate=签订之日起算,specifiedDate=指定日期
|
||||
*/
|
||||
public static final String FIELD_FD_START_TERM = "fdStartTerm";
|
||||
|
||||
/**
|
||||
* 合同结束期限:signTerm=按签订后期限,specifiedDate=指定日期,noTerm=无固定期限
|
||||
*/
|
||||
public static final String FIELD_FD_END_TERM = "fdEndTerm";
|
||||
|
||||
/**
|
||||
* 创建时间(yyyy-MM-dd,fdType=timestamp)
|
||||
*/
|
||||
public static final String FIELD_CREATE_TIME = "CREATE_TIME";
|
||||
|
||||
/**
|
||||
* 创建时间(fd 模板字段,yyyy-MM-dd,fdType=timestamp)
|
||||
*/
|
||||
public static final String FIELD_FD_CREATE_TIME = "fdCreateTime";
|
||||
|
||||
/**
|
||||
* 同步时间(yyyy-MM-dd,fdType=timestamp)
|
||||
*/
|
||||
public static final String FIELD_FD_SYN_TIME = "fd_syn_time";
|
||||
|
||||
/**
|
||||
* 承办人名称
|
||||
*/
|
||||
public static final String FIELD_CREATE_USER_NAME = "CREATE_USER_NAME";
|
||||
|
||||
/**
|
||||
* 合同状态:draft/approving/approved 等(v4 必填)
|
||||
*/
|
||||
public static final String FIELD_CONTRACT_STATUS = "CONTRACT_STATUS";
|
||||
|
||||
/**
|
||||
* 合同份数(v4 必填)
|
||||
*/
|
||||
public static final String FIELD_FD_SERVINGS = "fdServings";
|
||||
|
||||
/**
|
||||
* 合同说明(选填)
|
||||
*/
|
||||
public static final String FIELD_FD_DESC = "fdDesc";
|
||||
|
||||
/**
|
||||
* 关联项目(选填,对象传值如 {"fdId": "项目id"},传合规项目ID)
|
||||
*/
|
||||
public static final String FIELD_FD_PROJECT = "fdProject";
|
||||
|
||||
/**
|
||||
* 合同结算模式:open=开口合同,close=闭口合同
|
||||
*/
|
||||
public static final String FIELD_FD_CONTRACT_GW_NATURE = "fdContractGwNature";
|
||||
|
||||
// ==================== 结算类型枚举值 ====================
|
||||
|
||||
/**
|
||||
* 合同结算类型取值:付款(fdSettlementType,承运商合同固定取此值;合规系统字典中文名,2026-10-17 确认)
|
||||
*/
|
||||
public static final String SETTLEMENT_TYPE_PAYMENT = "付款";
|
||||
|
||||
/**
|
||||
* 合同结算类型取值:收支双向(fdSettlementType,合规系统字典中文名)
|
||||
*/
|
||||
public static final String SETTLEMENT_TYPE_PAYMENT_COLLECTION = "收支双向";
|
||||
|
||||
/**
|
||||
* 合同结算类型取值:收款(fdSettlementType,客户合同固定取此值;合规系统字典中文名)
|
||||
*/
|
||||
public static final String SETTLEMENT_TYPE_COLLECTION = "收款";
|
||||
|
||||
/**
|
||||
* 合同结算类型取值:不结算(fdSettlementType,合规系统字典中文名)
|
||||
*/
|
||||
public static final String SETTLEMENT_TYPE_NONE = "不结算";
|
||||
|
||||
// ==================== 结算模式枚举值 ====================
|
||||
|
||||
/**
|
||||
* 合同结算模式取值:开口合同(fdContractGwNature,TMS 固定取此值)
|
||||
*/
|
||||
public static final String CONTRACT_GW_NATURE_OPEN = "open";
|
||||
|
||||
/**
|
||||
* 合同结算模式取值:闭口合同(fdContractGwNature)
|
||||
*/
|
||||
public static final String CONTRACT_GW_NATURE_CLOSE = "close";
|
||||
|
||||
// ==================== 期限枚举值 ====================
|
||||
|
||||
/**
|
||||
* 期限取值:指定日期(fdStartTerm/fdEndTerm,TMS 固定按指定日期起算)
|
||||
*/
|
||||
public static final String TERM_SPECIFIED_DATE = "specifiedDate";
|
||||
|
||||
private HcsContractFieldConstant() {
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.constant;
|
||||
|
||||
/**
|
||||
* HCS(合同合规系统)列表查询 / 回调字段名常量(TMS 业务侧)
|
||||
* <p>
|
||||
* 与 {@link HcsContractFieldConstant}(C103-1/C103-11 表单字段)区分;
|
||||
* 项目立项查询用下划线字段名,合同/开票查询用驼峰字段名,以实际接口为准。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class HcsFieldConstant {
|
||||
|
||||
// ==================== C102-3 项目立项列表查询(下划线字段名) ====================
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
public static final String PROJ_FD_ID = "fd_id";
|
||||
|
||||
/**
|
||||
* 项目编号(判重条件键)
|
||||
*/
|
||||
public static final String PROJ_FD_NUM = "fd_num";
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
public static final String PROJ_FD_PRO_NAME = "fd_pro_name";
|
||||
|
||||
/**
|
||||
* 创建时间(排序字段)
|
||||
*/
|
||||
public static final String PROJ_FD_CREATE_TIME = "fd_create_time";
|
||||
|
||||
// ==================== C103-3 合同列表查询(驼峰字段名) ====================
|
||||
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
public static final String CONTRACT_FD_ID = "fdId";
|
||||
|
||||
/**
|
||||
* 合同标题
|
||||
*/
|
||||
public static final String CONTRACT_FD_SUBJECT = "fdSubject";
|
||||
|
||||
/**
|
||||
* 合同编号(查询条件键)
|
||||
*/
|
||||
public static final String CONTRACT_FD_NUMBER = "fdNumber";
|
||||
|
||||
/**
|
||||
* 合同分类层级ID
|
||||
*/
|
||||
public static final String CONTRACT_FD_CATE_HIERARCHY_ID = "fdCateHierarchyId";
|
||||
|
||||
// ==================== C111-1 开票登记列表查询(驼峰字段名) ====================
|
||||
|
||||
/**
|
||||
* 单据ID
|
||||
*/
|
||||
public static final String INVOICE_FD_ID = "fdId";
|
||||
|
||||
/**
|
||||
* 单据编号(查询条件键)
|
||||
*/
|
||||
public static final String INVOICE_FD_NUMBER = "fdNumber";
|
||||
|
||||
/**
|
||||
* 开票日期
|
||||
*/
|
||||
public static final String INVOICE_FD_BILL_DATE = "fdBillDate";
|
||||
|
||||
/**
|
||||
* 合同编号
|
||||
*/
|
||||
public static final String INVOICE_FD_CONTRACT_NUMBER = "fdContractNumber";
|
||||
|
||||
/**
|
||||
* 开票金额
|
||||
*/
|
||||
public static final String INVOICE_FD_INVOICE_AMOUNT = "fdInvoiceAmount";
|
||||
|
||||
/**
|
||||
* 单据状态
|
||||
*/
|
||||
public static final String INVOICE_FD_STATUS = "fdStatus";
|
||||
|
||||
private HcsFieldConstant() {
|
||||
}
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.constant;
|
||||
|
||||
/**
|
||||
* HCS(合同合规系统)同步常量(TMS 业务侧)
|
||||
* <p>
|
||||
* 命名照 {@link KingdeeBfiConstant} 惯例;与客户端模块 org.springblade.hcs.constant.HcsConstant 区分。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public final class HcsSyncConstant {
|
||||
|
||||
/**
|
||||
* 同步状态:待同步
|
||||
*/
|
||||
public static final String HCS_STATUS_UNSYNCED = "unsynced";
|
||||
|
||||
/**
|
||||
* 同步状态:已同步
|
||||
*/
|
||||
public static final String HCS_STATUS_SYNCED = "synced";
|
||||
|
||||
/**
|
||||
* 同步状态:同步失败
|
||||
*/
|
||||
public static final String HCS_STATUS_FAILED = "failed";
|
||||
|
||||
/**
|
||||
* 推送业务类型:项目立项(C102)
|
||||
*/
|
||||
public static final String BIZ_TYPE_PROJECT = "project";
|
||||
|
||||
/**
|
||||
* 推送业务类型:合同(C103)
|
||||
*/
|
||||
public static final String BIZ_TYPE_CONTRACT = "contract";
|
||||
|
||||
/**
|
||||
* 推送业务类型:付款申请(C106 特批 / C108 履约记录)
|
||||
*/
|
||||
public static final String BIZ_TYPE_PAYMENT = "payment";
|
||||
|
||||
/**
|
||||
* 推送业务类型:开票申请(C111 开票登记)
|
||||
*/
|
||||
public static final String BIZ_TYPE_INVOICE_APPLICATION = "invoice-application";
|
||||
|
||||
/**
|
||||
* 推送业务类型:收票单(C109)
|
||||
*/
|
||||
public static final String BIZ_TYPE_INVOICE_RECEIPT = "invoice-receipt";
|
||||
|
||||
/**
|
||||
* 校验类型:客商风险(C101-1)
|
||||
*/
|
||||
public static final String CHECK_TYPE_RISK = "risk";
|
||||
|
||||
/**
|
||||
* 校验类型:额度(C106-1,P3 付款链使用)
|
||||
*/
|
||||
public static final String CHECK_TYPE_CREDIT = "credit";
|
||||
|
||||
/**
|
||||
* 校验类型:合同特批(合规特批结果推送/查询联动留痕;原 C103-8 发起留痕随接口删除)
|
||||
*/
|
||||
public static final String CHECK_TYPE_SPEC = "spec";
|
||||
|
||||
/**
|
||||
* 校验类型:项目金蝶同步结果回调(D902,projectLegal/result 推送留痕)
|
||||
*/
|
||||
public static final String CHECK_TYPE_KINGDEE = "kingdee";
|
||||
|
||||
/**
|
||||
* 校验结果:通过
|
||||
*/
|
||||
public static final String CHECK_RESULT_PASS = "pass";
|
||||
|
||||
/**
|
||||
* 校验结果:不通过(黑名单)
|
||||
*/
|
||||
public static final String CHECK_RESULT_FAIL = "fail";
|
||||
|
||||
/**
|
||||
* 校验结果:跳过(客商编号反查不到等)
|
||||
*/
|
||||
public static final String CHECK_RESULT_SKIP = "skip";
|
||||
|
||||
// ==================== 额度校验结果(C106-1,D15) ====================
|
||||
|
||||
/**
|
||||
* 额度校验通过
|
||||
*/
|
||||
public static final String CREDIT_STATUS_PASS = "pass";
|
||||
|
||||
/**
|
||||
* 额度校验超额(不拦截提交,转入特批链路)
|
||||
*/
|
||||
public static final String CREDIT_STATUS_OVER = "over";
|
||||
|
||||
// ==================== 特批状态(hcs_spec_status,D15) ====================
|
||||
|
||||
/**
|
||||
* 特批状态:无需特批
|
||||
*/
|
||||
public static final String SPEC_STATUS_NONE = "none";
|
||||
|
||||
/**
|
||||
* 特批状态:待特批(高风险/黑名单或额度超额标记,扫描任务据此发起 C106-3)
|
||||
*/
|
||||
public static final String SPEC_STATUS_NEED = "need";
|
||||
|
||||
/**
|
||||
* 特批状态:发起中
|
||||
*/
|
||||
public static final String SPEC_STATUS_PUSHING = "pushing";
|
||||
|
||||
/**
|
||||
* 特批状态:特批中(C106-3 已发起,TMS 挂起「特批审核」,等待合规 paymentLegal/result 回调)
|
||||
*/
|
||||
public static final String SPEC_STATUS_PROCESSING = "processing";
|
||||
|
||||
/**
|
||||
* 特批状态:特批通过(合规回调/人工确认,联动恢复"审批通过"放行)
|
||||
*/
|
||||
public static final String SPEC_STATUS_APPROVED = "approved";
|
||||
|
||||
/**
|
||||
* 特批状态:特批驳回(合规回调/人工确认,联动付款审批驳回走重新提交闭环)
|
||||
*/
|
||||
public static final String SPEC_STATUS_REJECTED = "rejected";
|
||||
|
||||
/**
|
||||
* 合规侧特批结果回传状态:通过(contractLegal/paymentLegal result 接口 status,D15 回传机制)
|
||||
*/
|
||||
public static final String LEGAL_RESULT_APPROVED = "APPROVED";
|
||||
|
||||
/**
|
||||
* 合规侧特批结果回传状态:驳回
|
||||
*/
|
||||
public static final String LEGAL_RESULT_REJECTED = "REJECTED";
|
||||
|
||||
// ==================== 客商风险(C101-1,外部客商高风险/黑名单管控) ====================
|
||||
|
||||
/**
|
||||
* C101-1 风险等级:高风险(0=低风险,1=中风险,2=高风险)
|
||||
*/
|
||||
public static final String RISK_LEVEL_HIGH = "2";
|
||||
|
||||
/**
|
||||
* 客商类型:内部组织(默认无风险,不查 C101-1)
|
||||
*/
|
||||
public static final String CUSTOMER_KIND_INTERNAL = "internal";
|
||||
|
||||
/**
|
||||
* 合同附件类型字段名(与前端附件元数据 fileType 字段对齐,值取业务字典 contract_attachment_type)
|
||||
*/
|
||||
public static final String ATTACHMENT_TYPE_FIELD = "fileType";
|
||||
|
||||
/**
|
||||
* 附件类型:特批附件(高风险/黑名单客商合同必须上传;值取业务字典 contract_attachment_type,中文匹配沿用 FILE_TYPE_SEAL_ARCHIVE="双章归档文件" 惯例)
|
||||
*/
|
||||
public static final String ATTACHMENT_TYPE_SPEC_EXPLANATION = "特批附件";
|
||||
|
||||
/**
|
||||
* 付款申请附件类型值:特批附件(前端付款表单附件类型枚举 attachmentType,付款附件结构与合同 fileType 结构不同)
|
||||
*/
|
||||
public static final String PAYMENT_ATTACH_TYPE_SPECIAL = "special_approval";
|
||||
|
||||
/**
|
||||
* 附件类型:用印附件(双章归档文件,取值沿用 ContractManageServiceImpl#FILE_TYPE_SEAL_ARCHIVE)
|
||||
*/
|
||||
public static final String ATTACHMENT_TYPE_SEAL = "双章归档文件";
|
||||
|
||||
// ==================== 合同附件挂载 fdEntityKey(eopCdraft 附件面板字段 key,C103-1) ====================
|
||||
|
||||
/**
|
||||
* 合同附件字段 key:特批附件
|
||||
*/
|
||||
public static final String CONTRACT_ATTACH_KEY_SPECIAL = "eopCdraftSpecial";
|
||||
|
||||
/**
|
||||
* 合同附件字段 key:用印附件
|
||||
*/
|
||||
public static final String CONTRACT_ATTACH_KEY_SEAL = "eopCdraftSign";
|
||||
|
||||
/**
|
||||
* 合同附件字段 key:其他附件(含合同主文件)
|
||||
*/
|
||||
public static final String CONTRACT_ATTACH_KEY_OTHER = "eopCdraftOther";
|
||||
|
||||
/**
|
||||
* 特批审核挂起节点展示名(currentNode,特批发起后挂起展示,结果回传后恢复)
|
||||
*/
|
||||
public static final String SPEC_REVIEW_NODE = "特批审核";
|
||||
|
||||
// ==================== 履约计划行(blade_contract_exe_stage,C103-9) ====================
|
||||
|
||||
/**
|
||||
* 行级同步操作:无(同步成功后清除标记)
|
||||
*/
|
||||
public static final String ROW_OP_NONE = "none";
|
||||
|
||||
/**
|
||||
* 行级同步操作:新增
|
||||
*/
|
||||
public static final String ROW_OP_ADD = "add";
|
||||
|
||||
/**
|
||||
* 行级同步操作:更新
|
||||
*/
|
||||
public static final String ROW_OP_UPDATE = "update";
|
||||
|
||||
/**
|
||||
* 行级同步操作:删除
|
||||
*/
|
||||
public static final String ROW_OP_DELETE = "delete";
|
||||
|
||||
/**
|
||||
* 履约类型:付款
|
||||
*/
|
||||
public static final String PERFORM_TYPE_PAYMENT = "payment";
|
||||
|
||||
/**
|
||||
* 履约类型:收款
|
||||
*/
|
||||
public static final String PERFORM_TYPE_COLLECTION = "collection";
|
||||
|
||||
/**
|
||||
* 款项性质:进度款
|
||||
*/
|
||||
public static final String FUND_NATURE_PROGRESS = "progress";
|
||||
|
||||
/**
|
||||
* 款项性质:预付款
|
||||
*/
|
||||
public static final String FUND_NATURE_PREPAYMENT = "prepayment";
|
||||
|
||||
/**
|
||||
* 付款类型→款项性质映射:正式结算付款对应进度款计划行,预付款(进度预付/项目预付)对应预付款计划行
|
||||
*
|
||||
* @param paymentType 付款申请付款类型
|
||||
* @return 款项性质
|
||||
*/
|
||||
public static String fundNatureOfPayment(String paymentType) {
|
||||
return TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(paymentType) ? FUND_NATURE_PROGRESS : FUND_NATURE_PREPAYMENT;
|
||||
}
|
||||
|
||||
// ==================== Redis 缓存 key(照 BFI blade:third: 前缀惯例) ====================
|
||||
|
||||
/**
|
||||
* 主数据缓存:合同模板列表
|
||||
*/
|
||||
public static final String MASTER_CACHE_TEMPLATES = "blade:third:hcs:master:templates";
|
||||
|
||||
/**
|
||||
* 主数据缓存:合同分类列表
|
||||
*/
|
||||
public static final String MASTER_CACHE_CATEGORIES = "blade:third:hcs:master:categories";
|
||||
|
||||
/**
|
||||
* 主数据缓存:币种列表
|
||||
*/
|
||||
public static final String MASTER_CACHE_CURRENCIES = "blade:third:hcs:master:currencies";
|
||||
|
||||
/**
|
||||
* 推送任务分布式锁 key 前缀(多实例部署防重扫,完整 key = 前缀 + 业务类型,任务间互不阻塞)
|
||||
*/
|
||||
public static final String PUSH_TASK_LOCK_KEY_PREFIX = "blade:third:hcs:push:task:lock:";
|
||||
|
||||
/**
|
||||
* 即时推送单条锁 key 前缀(审批通过即时推送防与扫描任务并发双推,完整 key = 前缀 + 业务类型 + ":" + 单据ID)
|
||||
*/
|
||||
public static final String PUSH_SINGLE_LOCK_KEY_PREFIX = "blade:third:hcs:push:lock:";
|
||||
|
||||
/**
|
||||
* 即时推送单条锁 TTL(秒):大于单条推送预期时长,异常残留锁自动过期后由扫描兜底
|
||||
*/
|
||||
public static final long PUSH_SINGLE_LOCK_TTL_SECONDS = 120L;
|
||||
|
||||
/**
|
||||
* hcs_sync_msg 字段安全截断长度(列宽 varchar(512),留余量防远程错误报文超长导致落库失败)
|
||||
*/
|
||||
public static final int SYNC_MSG_MAX_LENGTH = 500;
|
||||
|
||||
/**
|
||||
* 截断超长的推送结果/失败原因(如 Feign 异常携带的远程 404 响应体),防止 Data too long 落库失败
|
||||
*/
|
||||
public static String truncateMsg(String message) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
return message.length() > SYNC_MSG_MAX_LENGTH ? message.substring(0, SYNC_MSG_MAX_LENGTH) : message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批状态中文名(变更记录展示用,金蝶同步结果回调等系统链路共用;未匹配回退原值)
|
||||
*/
|
||||
public static String approvalStatusName(String approvalStatus) {
|
||||
if (approvalStatus == null || approvalStatus.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return switch (approvalStatus) {
|
||||
case "draft" -> "草稿";
|
||||
case "reviewing" -> "审批中";
|
||||
case "withdrawn" -> "已撤回";
|
||||
case "approved" -> "审批通过";
|
||||
case "rejected" -> "已驳回";
|
||||
case "change_reviewing" -> "变更审批中";
|
||||
case "change_rejected" -> "变更驳回";
|
||||
case "change_approved" -> "变更审批通过";
|
||||
case "voided" -> "已作废";
|
||||
default -> approvalStatus;
|
||||
};
|
||||
}
|
||||
|
||||
private HcsSyncConstant() {
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.controller;
|
||||
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.service.IContractExeStageService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同履约规则维护端点(合同表单"履约规则"页签,设计文档 §4.1,ADR-0002)
|
||||
* <p>
|
||||
* 行级差异(add/update/delete)由本端点落库标记,行级同步由扫描任务随合同链路调 C103-9 完成;
|
||||
* 行删除为软删+标记,不可物理删除。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("contract-exe-stage")
|
||||
@Tag(name = "合同履约规则", description = "合同履约规则维护(HCS 履约阶段同步数据源)")
|
||||
public class ContractExeStageController {
|
||||
|
||||
private final IContractExeStageService contractExeStageService;
|
||||
|
||||
/**
|
||||
* 按合同查询履约规则行
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "按合同查询履约规则", description = "传入合同ID,返回规则行列表")
|
||||
public R<List<HcsContractExeStage>> list(@Parameter(description = "合同ID", required = true) @RequestParam Long contractId) {
|
||||
return R.data(contractExeStageService.listByContractId(contractId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存履约规则行(整批提交,按行差异置同步标记)
|
||||
*/
|
||||
@PostMapping("/submit")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "保存履约规则", description = "传入合同ID与规则行列表,新行/未同步行标记 add、已同步行标记 update")
|
||||
public R<Boolean> submit(@Parameter(description = "合同ID", required = true) @RequestParam Long contractId,
|
||||
@RequestBody List<HcsContractExeStage> rows) {
|
||||
return R.status(contractExeStageService.submitRows(contractId, rows));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除履约规则行(软删+标记,已同步行待 C103-9 上报删除)
|
||||
*/
|
||||
@PostMapping("/remove")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "删除履约规则行", description = "传入规则行ID,软删并标记 delete 待同步")
|
||||
public R<Boolean> remove(@Parameter(description = "规则行ID", required = true) @RequestParam Long id) {
|
||||
return R.status(contractExeStageService.removeRow(id));
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -40,6 +40,7 @@ import org.springblade.transport.excel.ContractManageExcel;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.vo.ContractManageVO;
|
||||
import org.springblade.transport.service.IContractManageService;
|
||||
import org.springblade.transport.support.hcs.HcsImmediatePushSupport;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -63,6 +64,7 @@ import java.util.Map;
|
||||
public class ContractManageController extends BladeController {
|
||||
|
||||
private final IContractManageService contractManageService;
|
||||
private final HcsImmediatePushSupport hcsImmediatePushSupport;
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@@ -110,7 +112,14 @@ public class ContractManageController extends BladeController {
|
||||
@ApiOperationSupport(order = 7)
|
||||
@Operation(summary = "审批通过", description = "传入id")
|
||||
public R approve(@Parameter(description = "主键", required = true) @RequestParam Long id) {
|
||||
return R.status(contractManageService.approve(id));
|
||||
contractManageService.approve(id);
|
||||
// 审批事务已提交,再同步推送合规;失败原因以警告文案返回(data=false 供前端区分提示样式),照正式结算单审批接口
|
||||
hcsImmediatePushSupport.pushContractAndWait(id);
|
||||
String syncFailure = HcsImmediatePushSupport.pollSyncFailure();
|
||||
if (syncFailure != null) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但合规推送失败:" + syncFailure);
|
||||
}
|
||||
return R.success("审批通过");
|
||||
}
|
||||
|
||||
@PostMapping("/reject")
|
||||
|
||||
+5
-1
@@ -204,12 +204,16 @@ public class FormalSettlementController extends BladeController {
|
||||
return approveResponse();
|
||||
}
|
||||
|
||||
/** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
|
||||
/** 审批接口统一响应:审批已生效,自动推送结果(成功单据号/失败原因)一并返回;失败用 data=false 供前端区分提示样式 */
|
||||
private R approveResponse() {
|
||||
String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
|
||||
String syncResult = KingdeeApprovalSyncListener.pollSyncResult();
|
||||
if (syncFailure != null) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
|
||||
}
|
||||
if (syncResult != null) {
|
||||
return R.success("审批通过,已同步金蝶:" + syncResult);
|
||||
}
|
||||
return R.success("审批通过");
|
||||
}
|
||||
|
||||
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tenant.annotation.TenantIgnore;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 合规回调 Mock 端点
|
||||
* <p>
|
||||
* 合同法务审批结果回调(D103)、付款法务审批结果回调(D104)、项目金蝶同步结果回调(D902)、
|
||||
* 合同金蝶同步结果回传接收(D903)四个入站回调的纯 mock 实现:仅校验来源系统标识后返回固定结果,
|
||||
* 不读写业务数据、不联动状态。真实处理逻辑保留在对应 Support 中,联调时把端点切回
|
||||
* {@link } 并调用 Support 方法即可。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@TenantIgnore
|
||||
@RequestMapping("hcs")
|
||||
@Tag(name = "HCS合规回调Mock", description = "HCS合规回调Mock端点(仅自测,返回固定 mock 数据)")
|
||||
public class HcsMockController {
|
||||
|
||||
/**
|
||||
* 来源系统标识(合规方约定值,与 third-party.hcs.src-system 默认值一致)
|
||||
*/
|
||||
private static final String SOURCE_SYSTEM = "WLTMS";
|
||||
|
||||
/**
|
||||
* 合同法务审批结果回调(D103)
|
||||
*/
|
||||
@PostMapping("/contractLegal/result")
|
||||
@ApiOperationSupport(order = 15)
|
||||
@Operation(summary = "合同法务审批结果回调Mock", description = "D103 合规推送合同特批(金蝶同步)结果,仅校验来源系统并返回 mock 数据")
|
||||
public R<String> contractLegalResult(@RequestBody(required = false) Map<String, Object> param) {
|
||||
this.checkSourceSystem("合同法务审批结果", param);
|
||||
return R.data("mock特批结果已处理:approved");
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款法务审批结果回调(D104)
|
||||
*/
|
||||
@PostMapping("/paymentLegal/result")
|
||||
@ApiOperationSupport(order = 16)
|
||||
@Operation(summary = "付款法务审批结果回调Mock", description = "D104 合规推送付款特批结果,仅校验来源系统并返回 mock 数据")
|
||||
public R<String> paymentLegalResult(@RequestBody(required = false) Map<String, Object> param) {
|
||||
this.checkSourceSystem("付款法务审批结果", param);
|
||||
return R.data("mock付款特批结果接收已处理:approved");
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目金蝶同步结果回调(D902)
|
||||
*/
|
||||
@PostMapping("/projectLegal/result")
|
||||
@ApiOperationSupport(order = 18)
|
||||
@Operation(summary = "项目金蝶同步结果回调Mock", description = "D902 合规推送项目金蝶同步结果,仅校验来源系统并返回 mock 数据")
|
||||
public R<String> projectLegalResult(@RequestBody(required = false) Map<String, Object> param) {
|
||||
this.checkSourceSystem("项目金蝶同步结果", param);
|
||||
return R.data("mock项目金蝶同步结果接收已处理:approved");
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同金蝶同步结果回传接收(D903)
|
||||
*/
|
||||
@PostMapping("/contractKingdee/result")
|
||||
@ApiOperationSupport(order = 19)
|
||||
@Operation(summary = "合同金蝶同步结果回传接收Mock", description = "D903 合规推送合同金蝶最终同步结果,仅校验来源系统并返回 mock 数据")
|
||||
public R<String> contractKingdeeResult(@RequestBody(required = false) Map<String, Object> param) {
|
||||
this.checkSourceSystem("合同金蝶同步结果", param);
|
||||
return R.data("mock合同金蝶同步结果接收处理:approved");
|
||||
}
|
||||
|
||||
/**
|
||||
* 来源系统校验:仅接受合规方约定的来源标识,防伪造回传
|
||||
*
|
||||
* @param bizName 业务名称(日志用)
|
||||
* @param param 回调报文
|
||||
*/
|
||||
private void checkSourceSystem(String bizName, Map<String, Object> param) {
|
||||
String sourceSystem = param == null ? null : Objects.toString(param.get("sourceSystem"), null);
|
||||
if (!StrUtil.equalsIgnoreCase(sourceSystem, SOURCE_SYSTEM)) {
|
||||
log.warn("{}推送来源系统标识错误:sourceSystem={}", bizName, sourceSystem);
|
||||
throw new ServiceException("来源系统标识错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.controller;
|
||||
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import feign.Response;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tenant.annotation.TenantIgnore;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.dto.HcsContractLegalResultDTO;
|
||||
import org.springblade.hcs.dto.HcsCurrencyVO;
|
||||
import org.springblade.hcs.dto.HcsLegalResultDTO;
|
||||
import org.springblade.hcs.dto.HcsProjectLegalResultDTO;
|
||||
import org.springblade.hcs.dto.HcsTemplateVO;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.pojo.vo.CustomerRiskVO;
|
||||
import org.springblade.transport.support.hcs.HcsAttachmentSupport;
|
||||
import org.springblade.transport.support.hcs.HcsContractSpecSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsContractSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsCreditCheckSupport;
|
||||
import org.springblade.transport.support.hcs.HcsInvoiceRegSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsMasterDataSupport;
|
||||
import org.springblade.transport.support.hcs.HcsPaymentSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsProjectSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsReceiptBillSyncSupport;
|
||||
import org.springblade.transport.task.HcsPushTask;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HCS 合规推送端点(设计文档 §5.0 手动入口)
|
||||
* <p>
|
||||
* 前端列表页"推送合规/重推"按钮(照"同步金蝶"按钮模式):
|
||||
* 单条重推重置 hcs_sync_status=unsynced 并清零重试计数后立即推送;批量端点复用定时扫描逻辑。
|
||||
* 排查推送问题以 hcs_sync_status/hcs_sync_msg 为准,不以审批日志为准(ADR-0001)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@TenantIgnore
|
||||
@RequestMapping("hcs")
|
||||
@Tag(name = "HCS合规推送", description = "HCS合规推送端点")
|
||||
public class HcsSyncController {
|
||||
|
||||
private final HcsPushTask hcsPushTask;
|
||||
private final HcsProjectSyncSupport hcsProjectSyncSupport;
|
||||
private final HcsContractSyncSupport hcsContractSyncSupport;
|
||||
private final HcsContractSpecSyncSupport hcsContractSpecSyncSupport;
|
||||
private final HcsCreditCheckSupport hcsCreditCheckSupport;
|
||||
private final HcsAttachmentSupport hcsAttachmentSupport;
|
||||
private final HcsPaymentSyncSupport hcsPaymentSyncSupport;
|
||||
private final HcsInvoiceRegSyncSupport hcsInvoiceRegSyncSupport;
|
||||
private final HcsReceiptBillSyncSupport hcsReceiptBillSyncSupport;
|
||||
private final HcsMasterDataSupport hcsMasterDataSupport;
|
||||
|
||||
/**
|
||||
* 单条重推(bizType:project=项目立项 / contract=合同 / payment=付款申请 / invoice-application=开票申请 / invoice-receipt=收票单)
|
||||
*/
|
||||
@PostMapping("/sync/{bizType}/{id}")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "单条推送/重推合规", description = "重置待同步状态后立即推送,bizType:project、contract、payment、invoice-application、invoice-receipt")
|
||||
public R<String> repush(@Parameter(description = "业务类型", required = true) @PathVariable String bizType,
|
||||
@Parameter(description = "单据ID", required = true) @PathVariable Long id) {
|
||||
String result = switch (bizType) {
|
||||
case HcsSyncConstant.BIZ_TYPE_PROJECT -> hcsProjectSyncSupport.repushProject(id);
|
||||
case HcsSyncConstant.BIZ_TYPE_CONTRACT -> hcsContractSyncSupport.repushContract(id);
|
||||
case HcsSyncConstant.BIZ_TYPE_PAYMENT -> hcsPaymentSyncSupport.repushPayment(id);
|
||||
case HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION -> hcsInvoiceRegSyncSupport.repushInvoiceRegistration(id);
|
||||
case HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT -> hcsReceiptBillSyncSupport.repushReceiptBill(id);
|
||||
default -> throw new IllegalArgumentException("不支持的HCS推送业务类型:" + bizType);
|
||||
};
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量推送(复用定时扫描逻辑,单轮批量上限 push.batch-size)
|
||||
*/
|
||||
@PostMapping("/sync/{bizType}")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@Operation(summary = "批量推送合规", description = "扫描待同步/待补偿单据推送一轮,bizType:project、contract、payment、invoice-application、invoice-receipt")
|
||||
public R<String> pushBatch(@Parameter(description = "业务类型", required = true) @PathVariable String bizType) {
|
||||
String result = switch (bizType) {
|
||||
case HcsSyncConstant.BIZ_TYPE_PROJECT -> hcsPushTask.pushUnsyncedProjects();
|
||||
case HcsSyncConstant.BIZ_TYPE_CONTRACT -> hcsPushTask.pushUnsyncedContracts();
|
||||
case HcsSyncConstant.BIZ_TYPE_PAYMENT -> hcsPushTask.pushUnsyncedPayments();
|
||||
case HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION -> hcsPushTask.pushUnsyncedInvoiceApplications();
|
||||
case HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT -> hcsPushTask.pushUnsyncedInvoiceReceipts();
|
||||
default -> throw new IllegalArgumentException("不支持的HCS推送业务类型:" + bizType);
|
||||
};
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面勾选批量重推(项目立项/合同列表勾选行后批量推送,逐条推送互不影响,单条失败计入失败明细)
|
||||
*/
|
||||
@PostMapping("/sync/{bizType}/batch")
|
||||
@ApiOperationSupport(order = 17)
|
||||
@Operation(summary = "按勾选ID批量推送/重推合规", description = "仅支持 project(项目立项)、contract(合同),逐条重置待同步后立即推送,单条失败不影响其余,返回成功数与失败明细")
|
||||
public R<String> repushBatch(@Parameter(description = "业务类型 project/contract", required = true) @PathVariable String bizType,
|
||||
@Parameter(description = "单据ID列表", required = true) @RequestBody List<Long> ids) {
|
||||
if (!List.of(HcsSyncConstant.BIZ_TYPE_PROJECT, HcsSyncConstant.BIZ_TYPE_CONTRACT).contains(bizType)) {
|
||||
throw new IllegalArgumentException("不支持的HCS批量推送业务类型:" + bizType);
|
||||
}
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return R.fail("请先勾选要推送的单据");
|
||||
}
|
||||
int successCount = 0;
|
||||
List<String> failureList = new ArrayList<>();
|
||||
for (Long id : ids) {
|
||||
try {
|
||||
if (HcsSyncConstant.BIZ_TYPE_PROJECT.equals(bizType)) {
|
||||
hcsProjectSyncSupport.repushProject(id);
|
||||
} else {
|
||||
hcsContractSyncSupport.repushContract(id);
|
||||
}
|
||||
successCount++;
|
||||
} catch (Exception exception) {
|
||||
failureList.add(id + ":" + exception.getMessage());
|
||||
}
|
||||
}
|
||||
String summary = "批量推送完成:成功 " + successCount + " 条,失败 " + failureList.size() + " 条";
|
||||
if (!failureList.isEmpty()) {
|
||||
summary += ";失败明细:" + String.join(";", failureList);
|
||||
}
|
||||
return R.data(summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批结果人工确认(§9-U1 过渡兜底:与合规 paymentLegal/result 回调等效,均联动付款审批状态)
|
||||
*/
|
||||
@PostMapping("/spec-pay/confirm")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "特批结果人工确认", description = "传入付款申请ID与结果(approved/rejected),仅特批中(processing)允许确认;approved 恢复审批通过放行,rejected 联动付款审批驳回")
|
||||
public R<String> confirmSpecResult(@Parameter(description = "付款申请ID", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "确认结果 approved/rejected", required = true) @RequestParam String result) {
|
||||
return R.data(hcsPaymentSyncSupport.confirmSpecResult(id, result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新主数据缓存(C103-6 模板 / C103-7 分类 / C103-12 币种)
|
||||
*/
|
||||
@PostMapping("/master-data/refresh")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "刷新HCS主数据缓存", description = "全量重拉模板/分类/币种列表并缓存,同时校验模板映射配置")
|
||||
public R<String> refreshMasterData() {
|
||||
return R.data(hcsMasterDataSupport.refreshAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* 合规模板列表(C103-6,Redis 缓存优先,前端接入设计 §3.1)
|
||||
*/
|
||||
@GetMapping("/master-data/template")
|
||||
@ApiOperationSupport(order = 9)
|
||||
@Operation(summary = "合规模板列表查询", description = "C103-6 模板列表,走服务端 Redis 缓存,未命中自动全量拉取")
|
||||
public R<List<HcsTemplateVO>> templates() {
|
||||
return R.data(hcsMasterDataSupport.getTemplates());
|
||||
}
|
||||
|
||||
/**
|
||||
* 合规币种列表(C103-12,Redis 缓存优先,前端接入设计 §3.1)
|
||||
*/
|
||||
@GetMapping("/master-data/currency")
|
||||
@ApiOperationSupport(order = 16)
|
||||
@Operation(summary = "合规币种列表查询", description = "C103-12 币种列表,走服务端 Redis 缓存,未命中自动全量拉取")
|
||||
public R<List<HcsCurrencyVO>> currencies() {
|
||||
return R.data(hcsMasterDataSupport.getCurrencies());
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商风险等级查询(C101-1,展示用,前端接入设计 §3.3;强制校验仍在推送/付款链路):
|
||||
* 内部组织类型客商默认无风险不调合规,外部客商高风险/黑名单返回提示文案
|
||||
*/
|
||||
@GetMapping("/risk")
|
||||
@ApiOperationSupport(order = 10)
|
||||
@Operation(summary = "客商风险等级查询", description = "C101-1 按客商编号查询风险等级与黑名单状态(展示用),内部客商默认无风险,高风险/黑名单返回风险提示文案,结果留痕")
|
||||
public R<CustomerRiskVO> risk(@Parameter(description = "客商编号", required = true) @RequestParam String customerCode) {
|
||||
return R.data(hcsCreditCheckSupport.queryCustomerRisk(customerCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商风险批量查询(C101-1,客商列表页展示用,前端接入设计 §3.3):
|
||||
* 按客商编号集合逐个查询(口径同 /risk),展示用不作为安全边界,单个失败跳过不阻断整页
|
||||
*/
|
||||
@GetMapping("/risk/batch")
|
||||
@ApiOperationSupport(order = 13)
|
||||
@Operation(summary = "客商风险批量查询", description = "C101-1 按客商编号集合批量查询风险等级与黑名单状态(展示用),口径同单个查询,单个失败跳过不阻断")
|
||||
public R<List<CustomerRiskVO>> riskBatch(@Parameter(description = "客商编号集合,逗号分隔", required = true) @RequestParam String customerCodes) {
|
||||
return R.data(hcsCreditCheckSupport.queryCustomerRiskBatch(Func.toStrList(customerCodes)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商风险按名称查询(C101-1,客商档案表单展示用,前端接入设计 §3.3):
|
||||
* 按名称反查客商编号后口径同 /risk;反查不到客商编号不阻断(risky=false,§9-U6)
|
||||
*/
|
||||
@GetMapping("/risk/by-name")
|
||||
@ApiOperationSupport(order = 14)
|
||||
@Operation(summary = "客商风险按名称查询", description = "C101-1 按客商名称查询风险(展示用),名称反查编号后口径同编号查询,反查不到默认无风险")
|
||||
public R<CustomerRiskVO> riskByName(@Parameter(description = "客商名称", required = true) @RequestParam String customerName) {
|
||||
return R.data(hcsCreditCheckSupport.queryCustomerRiskByName(customerName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件上传到合同系统(C103-2 代理,前端接入设计 §3.2),返回合规附件ID
|
||||
*/
|
||||
@PostMapping(value = "/attachment/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@ApiOperationSupport(order = 11)
|
||||
@Operation(summary = "附件上传到合同系统", description = "C103-2 代理:单文件上传至合规,返回合规附件ID(fdId)")
|
||||
public R<String> uploadAttachment(@Parameter(description = "附件文件", required = true) @RequestParam("fdFile") MultipartFile fdFile) {
|
||||
return R.data(hcsAttachmentSupport.uploadOne(fdFile, AuthUtil.getUserAccount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同附件下载(C103-5 代理,前端接入设计 §3.2):文件流透传浏览器
|
||||
*/
|
||||
@GetMapping("/attachment/download")
|
||||
@ApiOperationSupport(order = 12)
|
||||
@Operation(summary = "合规附件下载", description = "C103-5 代理:按合规附件ID 下载文件流,name 参数可选用于指定下载文件名")
|
||||
public void downloadAttachment(@Parameter(description = "合规附件ID", required = true) @RequestParam String fdId,
|
||||
@Parameter(description = "下载文件名(可选)") @RequestParam(required = false) String name,
|
||||
HttpServletResponse response) throws Exception {
|
||||
String fileName = StringUtil.isBlank(name) ? "hcs-attachment-" + fdId : name;
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"));
|
||||
try (Response hcsResponse = hcsAttachmentSupport.downloadRaw(fdId);
|
||||
InputStream inputStream = hcsResponse.body().asInputStream();
|
||||
OutputStream outputStream = response.getOutputStream()) {
|
||||
inputStream.transferTo(outputStream);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同特批结果人工确认(§6-U1 过渡方案:与合规推送等效)
|
||||
*/
|
||||
@PostMapping("/contract/spec/confirm")
|
||||
@ApiOperationSupport(order = 14)
|
||||
@Operation(summary = "合同特批结果人工确认", description = "传入合同ID 与结果(approved/rejected),仅特批中(processing)允许确认")
|
||||
public R<String> confirmContractSpec(@Parameter(description = "合同ID", required = true) @RequestParam Long id,
|
||||
@Parameter(description = "确认结果 approved/rejected", required = true) @RequestParam String result) {
|
||||
return R.data(hcsContractSpecSyncSupport.confirmSpecResult(id, result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同特批结果回传接收(合规系统 contractLegal/result 推送,替代未建设的 D103,原文留痕)
|
||||
* <p>
|
||||
* 免登免授权:blade.secure skip-url + auth permitAll 放行(doc/nacos/blade.yaml);
|
||||
* iPaaS 网关注册映射:/gwzh/HTHG/contractLegal/result → /hcs/contractLegal/result;
|
||||
* 报文 {sourceSystem, requestId=合规合同ID(hcs_fd_id), id, status=APPROVED/REJECTED, reason},
|
||||
* 按 requestId=hcs_fd_id 定位合同,approved 恢复流程展示,rejected 联动合同审批驳回。
|
||||
*/
|
||||
@PostMapping("/contractLegal/result")
|
||||
@ApiOperationSupport(order = 15)
|
||||
@Operation(summary = "合同特批结果回传接收", description = "合规系统推送合同特批结果,按合规合同ID(hcs_fd_id=requestId)定位并联动特批状态,原文留痕")
|
||||
public R<String> contractLegalResult(@RequestBody(required = false) HcsLegalResultDTO param) {
|
||||
return R.data(hcsContractSpecSyncSupport.handleLegalResult(param));
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款特批结果回传接收(合规系统 paymentLegal/result 推送)
|
||||
* <p>
|
||||
* 免登免授权同合同接口(blade.secure 放行);
|
||||
* iPaaS 网关注册映射:/gwzh/HTHG/paymentLegal/result → /hcs/paymentLegal/result;
|
||||
* 报文 {sourceSystem, requestId=TMS付款申请ID, status=APPROVED/REJECTED, reason},
|
||||
* approved 恢复"审批通过"放行,rejected 联动付款审批驳回走重新提交闭环。
|
||||
*/
|
||||
@PostMapping("/paymentLegal/result")
|
||||
@ApiOperationSupport(order = 16)
|
||||
@Operation(summary = "付款特批结果回传接收", description = "合规系统推送付款特批结果,按付款申请ID定位并联动特批状态与付款审批状态,原文留痕")
|
||||
public R<String> paymentLegalResult(@RequestBody(required = false) HcsLegalResultDTO param) {
|
||||
return R.data(hcsPaymentSyncSupport.handleLegalResult(param));
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目金蝶同步结果回传接收(合规系统 projectLegal/result 推送,D902,原文留痕)
|
||||
* <p>
|
||||
* 免登免授权:blade.secure skip-url + auth permitAll 放行(doc/nacos/blade.yaml);
|
||||
* iPaaS 网关注册映射:/gwzh/HTHG/projectLegal/result → /hcs/projectLegal/result;
|
||||
* 报文 {sourceSystem, projectCode, requestId, success, kingdeeId, projectNumber, reason},
|
||||
* 按 projectCode=TMS 项目编号定位,success=true 回填金蝶项目ID/编号,success=false 记录失败原因。
|
||||
*/
|
||||
@PostMapping("/projectLegal/result")
|
||||
@ApiOperationSupport(order = 18)
|
||||
@Operation(summary = "项目金蝶同步结果回传接收", description = "合规系统推送项目金蝶同步结果(D902),按项目编号定位并回填金蝶ID,原文留痕")
|
||||
public R<String> projectLegalResult(@RequestBody(required = false) HcsProjectLegalResultDTO param) {
|
||||
return R.data(hcsProjectSyncSupport.handleLegalResult(param));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同金蝶同步结果回传接收(合规系统 contractKingdee/result 推送,D903,投送人/钢厂/结算中心合同共用,原文留痕)
|
||||
* <p>
|
||||
* 免登免授权:blade.secure skip-url + auth permitAll 放行(doc/nacos/blade.yaml);
|
||||
* iPaaS 网关注册映射:/gwzh/HTHG/contractKingdee/result → /hcs/contractKingdee/result;
|
||||
* 报文 {contractType?, contractCode, requestId, success, reason?, kingdeeId?, sourceSystem?},
|
||||
* 本接口不处理法务审批结论,仅按 contractCode=TMS 合同编号定位回填金蝶合同ID/记录失败原因。
|
||||
*/
|
||||
@PostMapping("/contractKingdee/result")
|
||||
@ApiOperationSupport(order = 19)
|
||||
@Operation(summary = "合同金蝶同步结果回传接收", description = "合规系统推送合同金蝶最终同步结果(D903,投送人/钢厂/结算中心共用),按合同编号定位回填金蝶ID,不处理法务审批结论,原文留痕")
|
||||
public R<String> contractKingdeeResult(@RequestBody(required = false) HcsContractLegalResultDTO param) {
|
||||
return R.data(hcsContractSyncSupport.handleKingdeeResult(param));
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -134,12 +134,16 @@ public class InvoiceApplicationController extends BladeController {
|
||||
return approveResponse();
|
||||
}
|
||||
|
||||
/** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
|
||||
/** 审批接口统一响应:审批已生效,自动推送结果(成功单据号/失败原因)一并返回;失败用 data=false 供前端区分提示样式 */
|
||||
private R approveResponse() {
|
||||
String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
|
||||
String syncResult = KingdeeApprovalSyncListener.pollSyncResult();
|
||||
if (syncFailure != null) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
|
||||
}
|
||||
if (syncResult != null) {
|
||||
return R.success("审批通过,已同步金蝶:" + syncResult);
|
||||
}
|
||||
return R.success("审批通过");
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -150,12 +150,16 @@ public class InvoiceReceiptController extends BladeController {
|
||||
return approveResponse();
|
||||
}
|
||||
|
||||
/** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
|
||||
/** 审批接口统一响应:审批已生效,自动推送结果(成功单据号/失败原因)一并返回;失败用 data=false 供前端区分提示样式 */
|
||||
private R approveResponse() {
|
||||
String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
|
||||
String syncResult = KingdeeApprovalSyncListener.pollSyncResult();
|
||||
if (syncFailure != null) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
|
||||
}
|
||||
if (syncResult != null) {
|
||||
return R.success("审批通过,已同步金蝶:" + syncResult);
|
||||
}
|
||||
return R.success("审批通过");
|
||||
}
|
||||
|
||||
|
||||
+26
-3
@@ -29,13 +29,16 @@ import org.springblade.core.mp.support.Condition;
|
||||
import org.springblade.core.mp.support.Query;
|
||||
import org.springblade.core.secure.annotation.PreAuth;
|
||||
import org.springblade.core.tool.api.R;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.listener.KingdeeApprovalSyncListener;
|
||||
import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest;
|
||||
import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
|
||||
import org.springblade.transport.pojo.vo.ProjectAdvanceWriteOffOptionVO;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springblade.transport.support.hcs.HcsImmediatePushSupport;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -43,6 +46,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/** 付款申请控制器。 @author Chill */
|
||||
@@ -53,6 +57,7 @@ import java.util.List;
|
||||
@Tag(name = "付款管理", description = "付款申请管理")
|
||||
public class PaymentApplicationController extends BladeController {
|
||||
private final IPaymentApplicationService paymentApplicationService;
|
||||
private final HcsImmediatePushSupport hcsImmediatePushSupport;
|
||||
@GetMapping("/list")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@Operation(summary = "付款申请分页")
|
||||
@@ -96,15 +101,33 @@ public class PaymentApplicationController extends BladeController {
|
||||
@Operation(summary = "审批通过付款申请")
|
||||
public R approve(@RequestBody PaymentApplicationStatusRequest request) {
|
||||
paymentApplicationService.approve(request);
|
||||
return approveResponse();
|
||||
// 审批生效后立即发起付款特批(C106-3,不受即时推送开关控制);失败仅告警不阻断审批结果,保留重推
|
||||
hcsImmediatePushSupport.pushPaymentAndWait(request.getId());
|
||||
return approveResponse(request.getId());
|
||||
}
|
||||
|
||||
/** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
|
||||
private R approveResponse() {
|
||||
/** 审批接口统一响应:审批已生效,付款特批发起/自动同步结果(成功单据号/失败原因)一并返回;失败用 data=false 供前端区分提示样式 */
|
||||
private R approveResponse(Long id) {
|
||||
String hcsFailure = HcsImmediatePushSupport.pollSyncFailure();
|
||||
String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
|
||||
String syncResult = KingdeeApprovalSyncListener.pollSyncResult();
|
||||
if (hcsFailure != null) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但发起付款特批/推送合规失败:" + hcsFailure + ",可稍后重推合规");
|
||||
}
|
||||
PaymentApplication payment = paymentApplicationService.getById(id);
|
||||
String specStatus = payment == null ? null : payment.getHcsSpecStatus();
|
||||
if (HcsSyncConstant.SPEC_STATUS_NEED.equals(specStatus)) {
|
||||
return R.success("审批通过,付款特批待发起,可在详情页重推合规");
|
||||
}
|
||||
if (Arrays.asList(HcsSyncConstant.SPEC_STATUS_PUSHING, HcsSyncConstant.SPEC_STATUS_PROCESSING).contains(specStatus)) {
|
||||
return R.success("审批通过,已发起付款特批,特批通过后将自动推送履约数据并同步金蝶");
|
||||
}
|
||||
if (syncFailure != null) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
|
||||
}
|
||||
if (syncResult != null) {
|
||||
return R.success("审批通过,已同步金蝶:" + syncResult);
|
||||
}
|
||||
return R.success("审批通过");
|
||||
}
|
||||
@PostMapping("/return")
|
||||
|
||||
+10
-1
@@ -40,6 +40,7 @@ import org.springblade.transport.excel.ProjectApplyExcel;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springblade.transport.pojo.vo.ProjectApplyVO;
|
||||
import org.springblade.transport.service.IProjectApplyService;
|
||||
import org.springblade.transport.support.hcs.HcsImmediatePushSupport;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -63,6 +64,7 @@ import java.util.Map;
|
||||
public class ProjectApplyController extends BladeController {
|
||||
|
||||
private final IProjectApplyService projectApplyService;
|
||||
private final HcsImmediatePushSupport hcsImmediatePushSupport;
|
||||
|
||||
@GetMapping("/detail")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@@ -119,7 +121,14 @@ public class ProjectApplyController extends BladeController {
|
||||
@ApiOperationSupport(order = 6)
|
||||
@Operation(summary = "审批通过", description = "传入id")
|
||||
public R approve(@Parameter(description = "主键", required = true) @RequestParam Long id) {
|
||||
return R.status(projectApplyService.approve(id));
|
||||
projectApplyService.approve(id);
|
||||
// 审批事务已提交,再同步推送合规;失败原因以警告文案返回(data=false 供前端区分提示样式),照正式结算单审批接口
|
||||
hcsImmediatePushSupport.pushProjectAndWait(id);
|
||||
String syncFailure = HcsImmediatePushSupport.pollSyncFailure();
|
||||
if (syncFailure != null) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但合规推送失败:" + syncFailure);
|
||||
}
|
||||
return R.success("审批通过");
|
||||
}
|
||||
|
||||
@PostMapping("/reject")
|
||||
|
||||
+4
@@ -39,12 +39,16 @@ public class SettlementAdjustmentController extends BladeController {
|
||||
String hint = service.approve(request);
|
||||
// 未推送场景自动推送失败原因以警告文案返回(data=false 供前端区分提示样式)
|
||||
String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
|
||||
String syncResult = KingdeeApprovalSyncListener.pollSyncResult();
|
||||
if (Func.isNotBlank(syncFailure)) {
|
||||
return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
|
||||
}
|
||||
if (Func.isNotBlank(hint)) {
|
||||
return R.data(Boolean.TRUE, "审批通过;" + hint);
|
||||
}
|
||||
if (Func.isNotBlank(syncResult)) {
|
||||
return R.success("审批通过,已同步金蝶:" + syncResult);
|
||||
}
|
||||
return R.success("审批通过");
|
||||
}
|
||||
@PostMapping("/return") public R returnBill(@RequestBody SettlementAdjustmentStatusRequest request) { service.returnBill(request); return R.success("已驳回"); }
|
||||
|
||||
+37
@@ -22,7 +22,9 @@ package org.springblade.transport.listener;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.event.KingdeeBillApprovedEvent;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IInvoiceApplicationService;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
@@ -66,6 +68,9 @@ public class KingdeeApprovalSyncListener {
|
||||
/** 当前审批请求线程的自动推送失败原因,供 Controller 取走提示前端(见类注释线程说明) */
|
||||
private static final ThreadLocal<String> SYNC_FAILURE = new ThreadLocal<>();
|
||||
|
||||
/** 当前审批请求线程的自动推送成功结果(金蝶单据号/推送摘要),供 Controller 拼进提示文案 */
|
||||
private static final ThreadLocal<String> SYNC_RESULT = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 取走并清除当前审批请求的自动推送失败原因;null 表示推送成功或未触发推送。
|
||||
* 须在 service.approve 返回后立即调用(监听器在审批事务提交时同步执行完毕)。
|
||||
@@ -76,6 +81,23 @@ public class KingdeeApprovalSyncListener {
|
||||
return failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取走并清除当前审批请求的自动推送成功结果;null 表示推送失败或未触发推送。
|
||||
* 与 {@link #pollSyncFailure()} 成对调用,避免线程复用导致结果串单。
|
||||
*/
|
||||
public static String pollSyncResult() {
|
||||
String result = SYNC_RESULT.get();
|
||||
SYNC_RESULT.remove();
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 记录本次自动推送的成功结果,空值不记录(未触发推送/接口无返回) */
|
||||
private static void recordResult(String result) {
|
||||
if (result != null && !result.isBlank()) {
|
||||
SYNC_RESULT.set(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务提交后同步推送。fallbackExecution=true 兜底:approve 未开启事务时事件不再被默认丢弃。
|
||||
*
|
||||
@@ -84,6 +106,7 @@ public class KingdeeApprovalSyncListener {
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
|
||||
public void onBillApproved(KingdeeBillApprovedEvent event) {
|
||||
SYNC_FAILURE.remove();
|
||||
SYNC_RESULT.remove();
|
||||
try {
|
||||
switch (event.bizType()) {
|
||||
case KingdeeBillApprovedEvent.BIZ_FORMAL_SETTLEMENT -> pushSettlement(event.id());
|
||||
@@ -95,6 +118,7 @@ public class KingdeeApprovalSyncListener {
|
||||
} catch (Exception exception) {
|
||||
// 推送失败只记日志(失败状态已在推送方法内独立事务落库),不影响审批结果,交给重推任务与手动按钮兜底;
|
||||
// 原因暂存线程变量,由审批 Controller 取走提示前端
|
||||
SYNC_RESULT.remove();
|
||||
log.error("审批通过自动同步金蝶失败 bizType={} id={}", event.bizType(), event.id(), exception);
|
||||
String reason = exception.getMessage() == null ? "金蝶接口调用异常" : exception.getMessage();
|
||||
// 手动按钮同文案已带"推送金蝶失败:"前缀,拼提示时去掉避免重复
|
||||
@@ -108,6 +132,7 @@ public class KingdeeApprovalSyncListener {
|
||||
}
|
||||
log.info("正式结算单[{}]审批通过自动同步金蝶开始", id);
|
||||
String billNo = formalSettlementService.syncKingdee(id);
|
||||
recordResult(billNo);
|
||||
log.info("正式结算单[{}]审批通过自动同步金蝶完成:billNo={}", id, billNo);
|
||||
}
|
||||
|
||||
@@ -115,8 +140,18 @@ public class KingdeeApprovalSyncListener {
|
||||
if (!bfiProperties.getAutoSync().isPayment()) {
|
||||
return;
|
||||
}
|
||||
// 付款特批未完成(高风险/黑名单/额度超额)时金蝶必然拒推:跳过而非置失败,
|
||||
// 特批通过后由 HcsPaymentSyncSupport 重新发布本事件自动补推
|
||||
PaymentApplication payment = paymentApplicationService.getById(id);
|
||||
if (payment != null && payment.getHcsSpecStatus() != null
|
||||
&& List.of(HcsSyncConstant.SPEC_STATUS_NEED, HcsSyncConstant.SPEC_STATUS_PUSHING,
|
||||
HcsSyncConstant.SPEC_STATUS_PROCESSING).contains(payment.getHcsSpecStatus())) {
|
||||
log.info("付款申请[{}]审批通过自动同步金蝶跳过(付款特批未完成):hcsSpecStatus={}", id, payment.getHcsSpecStatus());
|
||||
return;
|
||||
}
|
||||
log.info("付款申请[{}]审批通过自动同步金蝶开始", id);
|
||||
String kingdeeBillId = paymentApplicationService.syncKingdee(id);
|
||||
recordResult(kingdeeBillId);
|
||||
log.info("付款申请[{}]审批通过自动同步金蝶完成:kingdeeBillId={}", id, kingdeeBillId);
|
||||
}
|
||||
|
||||
@@ -126,6 +161,7 @@ public class KingdeeApprovalSyncListener {
|
||||
}
|
||||
log.info("开票申请[{}]审批通过自动同步金蝶开始", id);
|
||||
String billNo = invoiceApplicationService.syncKingdee(id);
|
||||
recordResult(billNo);
|
||||
log.info("开票申请[{}]审批通过自动同步金蝶完成:billNo={}", id, billNo);
|
||||
}
|
||||
|
||||
@@ -135,6 +171,7 @@ public class KingdeeApprovalSyncListener {
|
||||
}
|
||||
log.info("收票登记[{}]审批通过自动推送金蝶收票单开始", id);
|
||||
String summary = invoiceReceiptService.pushKingdeeReceivedInvoice(List.of(id), true);
|
||||
recordResult(summary);
|
||||
log.info("收票登记[{}]审批通过自动推送金蝶收票单完成:{}", id, summary);
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springblade.transport.pojo.entity.HcsAttachmentSync;
|
||||
|
||||
/**
|
||||
* HCS 附件转传映射 Mapper
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Mapper
|
||||
public interface HcsAttachmentSyncMapper extends BaseMapper<HcsAttachmentSync> {
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springblade.transport.pojo.entity.HcsContractCheckLog;
|
||||
|
||||
/**
|
||||
* HCS 风险/额度校验留痕 Mapper
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Mapper
|
||||
public interface HcsContractCheckLogMapper extends BaseMapper<HcsContractCheckLog> {
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同履约规则 Mapper
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Mapper
|
||||
public interface HcsContractExeStageMapper extends BaseMapper<HcsContractExeStage> {
|
||||
|
||||
/**
|
||||
* 查询合同的待同步行(含软删行:C103-9 的 delete 行已逻辑删除但须随行级同步上报,
|
||||
* 原生 SQL 绕过 @TableLogic 过滤)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @return 行级同步操作非 none 的规则行
|
||||
*/
|
||||
@Select("select * from blade_contract_exe_stage where contract_id = #{contractId} and row_op <> 'none' order by id")
|
||||
List<HcsContractExeStage> selectSyncRows(@Param("contractId") Long contractId);
|
||||
}
|
||||
+11
@@ -1,10 +1,21 @@
|
||||
package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplicationSettlement;
|
||||
|
||||
/** 付款申请关联正式结算单 Mapper。 */
|
||||
@Mapper
|
||||
public interface PaymentApplicationSettlementMapper extends BaseMapper<PaymentApplicationSettlement> {
|
||||
|
||||
/**
|
||||
* 按付款申请主键物理删除关联记录(表存在不含逻辑删除字段的关系唯一索引,重存前必须物理清理)
|
||||
*
|
||||
* @param paymentApplicationId 付款申请主键
|
||||
* @return 删除行数
|
||||
*/
|
||||
@Delete("DELETE FROM blade_payment_application_settlement WHERE payment_application_id = #{paymentApplicationId}")
|
||||
int deleteByPaymentApplicationId(@Param("paymentApplicationId") Long paymentApplicationId);
|
||||
}
|
||||
|
||||
+24
-2
@@ -25,7 +25,10 @@ import org.springblade.transport.service.IInvoiceApplicationService;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springblade.transport.service.IPreSettlementService;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.service.IProjectApplyService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.hcs.HcsImmediatePushSupport;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -75,6 +78,7 @@ public final class MkProcessBizHandlers {
|
||||
@RequiredArgsConstructor
|
||||
public static class ProjectHandler implements IMkProcessBizHandler {
|
||||
private final IProjectApplyService service;
|
||||
private final HcsImmediatePushSupport hcsImmediatePushSupport;
|
||||
|
||||
@Override
|
||||
public String bizType() {
|
||||
@@ -122,7 +126,13 @@ public final class MkProcessBizHandlers {
|
||||
entity.setCurrentNode("审核通过");
|
||||
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
|
||||
entity.setApprovedTime(LocalDateTime.now());
|
||||
return service.updateById(entity);
|
||||
// HCS 推送:审批通过置待同步状态,更新成功后即时推送,扫描任务保留兜底(ADR-0001 修订,前端接入设计 §6)
|
||||
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
boolean updated = service.updateById(entity);
|
||||
if (updated) {
|
||||
hcsImmediatePushSupport.pushProjectAfterCommit(id);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,6 +155,7 @@ public final class MkProcessBizHandlers {
|
||||
@RequiredArgsConstructor
|
||||
public static class ContractHandler implements IMkProcessBizHandler {
|
||||
private final IContractManageService service;
|
||||
private final HcsImmediatePushSupport hcsImmediatePushSupport;
|
||||
|
||||
@Override
|
||||
public String bizType() {
|
||||
@@ -190,7 +201,13 @@ public final class MkProcessBizHandlers {
|
||||
entity.setApprovalStatus(change ? "change_approved" : "approved");
|
||||
entity.setCurrentNode("审核通过");
|
||||
entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
|
||||
return service.updateById(entity);
|
||||
// HCS 推送:审批通过置待同步状态,更新成功后即时推送,扫描任务保留兜底(ADR-0001 修订,前端接入设计 §6)
|
||||
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
boolean updated = service.updateById(entity);
|
||||
if (updated) {
|
||||
hcsImmediatePushSupport.pushContractAfterCommit(id);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -361,6 +378,7 @@ public final class MkProcessBizHandlers {
|
||||
@RequiredArgsConstructor
|
||||
public static class PaymentHandler implements IMkProcessBizHandler {
|
||||
private final IPaymentApplicationService service;
|
||||
private final HcsImmediatePushSupport hcsImmediatePushSupport;
|
||||
|
||||
@Override
|
||||
public String bizType() {
|
||||
@@ -408,8 +426,12 @@ public final class MkProcessBizHandlers {
|
||||
if (updated != null) {
|
||||
updated.setCurrentNode("审核通过");
|
||||
updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
|
||||
// HCS 推送零埋点:MK 审批回调仅置待同步状态,与 /approve 入口收敛于同一权威状态(ADR-0001)
|
||||
updated.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
service.updateById(updated);
|
||||
}
|
||||
// 审批通过立即发起付款特批(C106-3,不受即时推送开关控制;事务提交后异步执行,失败留待重推兜底)
|
||||
hcsImmediatePushSupport.pushPaymentAfterCommit(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.service;
|
||||
|
||||
import org.springblade.core.mp.base.BaseService;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同履约规则 服务接口
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface IContractExeStageService extends BaseService<HcsContractExeStage> {
|
||||
|
||||
/**
|
||||
* 按合同查询履约规则行(表单维护用,不含软删行)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @return 规则行列表
|
||||
*/
|
||||
List<HcsContractExeStage> listByContractId(Long contractId);
|
||||
|
||||
/**
|
||||
* 匹配合同的付款履约计划行(已同步,优先按款项性质精确匹配,无则取首条付款行兜底)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @param fundNature 款项性质(progress/prepayment 等,空则直接取首条付款行)
|
||||
* @return 付款履约计划行,无已同步付款行时返回 null
|
||||
*/
|
||||
HcsContractExeStage findPaymentStageRow(Long contractId, String fundNature);
|
||||
|
||||
/**
|
||||
* 匹配合同的收款履约计划行(已同步,优先按款项性质精确匹配,无则取首条收款行兜底)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @param fundNature 款项性质(progress/prepayment 等,空则直接取首条收款行)
|
||||
* @return 收款履约计划行,无已同步收款行时返回 null
|
||||
*/
|
||||
HcsContractExeStage findCollectionStageRow(Long contractId, String fundNature);
|
||||
|
||||
/**
|
||||
* 金蝶认领推送完成后累加收款履约计划行款项金额(认领金额累加,多次认领累加更新):
|
||||
* 原子累加 fund_amount;已同步行置 row_op=update 凭已存合规阶段ID 随 C103-9 重推,
|
||||
* 未同步行保持 add 待合同链路首推(首推即携带累加后金额)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @param claimedAmount 本次累加的认领金额(须大于0)
|
||||
*/
|
||||
void accumulateClaimedAmountOnPush(Long contractId, BigDecimal claimedAmount);
|
||||
|
||||
/**
|
||||
* 付款审批通过累加履约计划行款项金额(款项金额=申请付款金额,多次付款累加更新):
|
||||
* 按款项性质匹配付款计划行,原子累加 fund_amount;已同步行置 row_op=update 凭已存合规阶段ID 随 C103-9 重推,
|
||||
* 未同步行保持 add 待合同链路首推(首推即携带累加后金额)
|
||||
*
|
||||
* @param payment 付款申请单(审批通过)
|
||||
*/
|
||||
void accumulatePaidAmountOnApprove(PaymentApplication payment);
|
||||
|
||||
/**
|
||||
* 审批通过自动创建默认履约计划行(合同下无规则行时):承运商合同=付款进度款(收票规则取合同结算模式),
|
||||
* 客户合同=收款进度款;金额/资金日期留空,随合同链路推 C103-9
|
||||
*
|
||||
* @param contractManage 合同单(审批通过)
|
||||
*/
|
||||
void createDefaultRowOnApprove(ContractManage contractManage);
|
||||
|
||||
/**
|
||||
* 保存合同履约规则行(按行差异置同步标记:新行/未同步行 add,已同步行 update)
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @param rows 规则行
|
||||
* @return 保存结果
|
||||
*/
|
||||
boolean submitRows(Long contractId, List<HcsContractExeStage> rows);
|
||||
|
||||
/**
|
||||
* 删除履约规则行(软删+标记:已同步行置 row_op=delete 待 C103-9 上报;未同步行直接软删)
|
||||
*
|
||||
* @param id 规则行ID
|
||||
* @return 删除结果
|
||||
*/
|
||||
boolean removeRow(Long id);
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.HcsContractExeStageMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.service.IContractExeStageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同履约规则 服务实现类(设计文档 §4.1,ADR-0002)
|
||||
* <p>
|
||||
* 行级差异标记:新行/未同步行 add;已同步行编辑 update;已同步行删除 delete(软删+标记,不可物理删除)。
|
||||
* 行级同步由扫描任务随合同链路调 C103-9 完成(HcsExeStageSyncSupport)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ContractExeStageServiceImpl extends BaseServiceImpl<HcsContractExeStageMapper, HcsContractExeStage> implements IContractExeStageService {
|
||||
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final HcsProperties hcsProperties;
|
||||
|
||||
@Override
|
||||
public List<HcsContractExeStage> listByContractId(Long contractId) {
|
||||
return list(Wrappers.<HcsContractExeStage>lambdaQuery()
|
||||
.eq(HcsContractExeStage::getContractId, contractId)
|
||||
.orderByAsc(HcsContractExeStage::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HcsContractExeStage findPaymentStageRow(Long contractId, String fundNature) {
|
||||
if (contractId == null) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtil.isNotBlank(fundNature)) {
|
||||
HcsContractExeStage natureRow = getOne(Wrappers.<HcsContractExeStage>lambdaQuery()
|
||||
.eq(HcsContractExeStage::getContractId, contractId)
|
||||
.eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_PAYMENT)
|
||||
.eq(HcsContractExeStage::getFundNature, fundNature)
|
||||
.isNotNull(HcsContractExeStage::getHcsStageFdId)
|
||||
.orderByAsc(HcsContractExeStage::getId)
|
||||
.last("limit 1"), false);
|
||||
if (natureRow != null) {
|
||||
return natureRow;
|
||||
}
|
||||
}
|
||||
// 兜底:无该款项性质已同步行时取首条已同步付款行(与 C108 履约记录历史口径一致)
|
||||
return getOne(Wrappers.<HcsContractExeStage>lambdaQuery()
|
||||
.eq(HcsContractExeStage::getContractId, contractId)
|
||||
.eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_PAYMENT)
|
||||
.isNotNull(HcsContractExeStage::getHcsStageFdId)
|
||||
.orderByAsc(HcsContractExeStage::getId)
|
||||
.last("limit 1"), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accumulatePaidAmountOnApprove(PaymentApplication payment) {
|
||||
if (payment == null || payment.getContractId() == null || payment.getAppliedAmount() == null) {
|
||||
return;
|
||||
}
|
||||
String fundNature = HcsSyncConstant.fundNatureOfPayment(payment.getPaymentType());
|
||||
HcsContractExeStage stageRow = this.findPaymentStageRow(payment.getContractId(), fundNature);
|
||||
// 仅精确匹配款项性质的计划行累加,兜底行款项性质不一致时不累加(避免预付款金额计入进度款计划)
|
||||
if (stageRow == null || !fundNature.equals(stageRow.getFundNature())) {
|
||||
log.info("付款审批未匹配到款项性质[{}]的履约计划行,跳过款项金额累加:paymentNo={},contractId={}",
|
||||
fundNature, payment.getPaymentNo(), payment.getContractId());
|
||||
return;
|
||||
}
|
||||
// 原子累加防并发付款审批丢失更新(coalesce 兼容多数据库)
|
||||
update(Wrappers.<HcsContractExeStage>lambdaUpdate()
|
||||
.eq(HcsContractExeStage::getId, stageRow.getId())
|
||||
.setSql("fund_amount = coalesce(fund_amount, 0) + " + payment.getAppliedAmount().toPlainString()));
|
||||
boolean resyncNeeded = StringUtil.isNotBlank(stageRow.getHcsStageFdId());
|
||||
if (resyncNeeded) {
|
||||
// 已同步行置 update 凭已存合规阶段ID 随 C103-9 重推;未同步行保持 add 待合同链路首推
|
||||
update(Wrappers.<HcsContractExeStage>lambdaUpdate()
|
||||
.eq(HcsContractExeStage::getId, stageRow.getId())
|
||||
.set(HcsContractExeStage::getRowOp, HcsSyncConstant.ROW_OP_UPDATE)
|
||||
.set(HcsContractExeStage::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_UNSYNCED)
|
||||
.set(HcsContractExeStage::getHcsSyncMsg, "付款审批款项金额累加,待重推C103-9"));
|
||||
}
|
||||
log.info("付款审批履约计划款项金额累加:paymentNo={},stageRowId={},累加金额={},rowOp={}",
|
||||
payment.getPaymentNo(), stageRow.getId(), payment.getAppliedAmount(), resyncNeeded ? "update" : "add(待合同链路首推)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HcsContractExeStage findCollectionStageRow(Long contractId, String fundNature) {
|
||||
if (contractId == null) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtil.isNotBlank(fundNature)) {
|
||||
HcsContractExeStage natureRow = getOne(Wrappers.<HcsContractExeStage>lambdaQuery()
|
||||
.eq(HcsContractExeStage::getContractId, contractId)
|
||||
.eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_COLLECTION)
|
||||
.eq(HcsContractExeStage::getFundNature, fundNature)
|
||||
.isNotNull(HcsContractExeStage::getHcsStageFdId)
|
||||
.orderByAsc(HcsContractExeStage::getId)
|
||||
.last("limit 1"), false);
|
||||
if (natureRow != null) {
|
||||
return natureRow;
|
||||
}
|
||||
}
|
||||
// 兜底:无该款项性质已同步行时取首条已同步收款行
|
||||
return getOne(Wrappers.<HcsContractExeStage>lambdaQuery()
|
||||
.eq(HcsContractExeStage::getContractId, contractId)
|
||||
.eq(HcsContractExeStage::getPerformType, HcsSyncConstant.PERFORM_TYPE_COLLECTION)
|
||||
.isNotNull(HcsContractExeStage::getHcsStageFdId)
|
||||
.orderByAsc(HcsContractExeStage::getId)
|
||||
.last("limit 1"), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accumulateClaimedAmountOnPush(Long contractId, BigDecimal claimedAmount) {
|
||||
if (contractId == null || claimedAmount == null || claimedAmount.signum() <= 0) {
|
||||
return;
|
||||
}
|
||||
HcsContractExeStage stageRow = this.findCollectionStageRow(contractId, HcsSyncConstant.FUND_NATURE_PROGRESS);
|
||||
if (stageRow == null) {
|
||||
log.info("收款认领未匹配到已同步收款履约计划行,跳过款项金额累加:contractId={}", contractId);
|
||||
return;
|
||||
}
|
||||
// 原子累加防并发认领推送丢失更新(coalesce 兼容多数据库)
|
||||
update(Wrappers.<HcsContractExeStage>lambdaUpdate()
|
||||
.eq(HcsContractExeStage::getId, stageRow.getId())
|
||||
.setSql("fund_amount = coalesce(fund_amount, 0) + " + claimedAmount.toPlainString()));
|
||||
boolean resyncNeeded = StringUtil.isNotBlank(stageRow.getHcsStageFdId());
|
||||
if (resyncNeeded) {
|
||||
// 已同步行置 update 凭已存合规阶段ID 随 C103-9 重推;未同步行保持 add 待合同链路首推
|
||||
update(Wrappers.<HcsContractExeStage>lambdaUpdate()
|
||||
.eq(HcsContractExeStage::getId, stageRow.getId())
|
||||
.set(HcsContractExeStage::getRowOp, HcsSyncConstant.ROW_OP_UPDATE)
|
||||
.set(HcsContractExeStage::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_UNSYNCED)
|
||||
.set(HcsContractExeStage::getHcsSyncMsg, "收款认领金额累加,待重推C103-9"));
|
||||
}
|
||||
log.info("收款认领履约计划款项金额累加:contractId={},stageRowId={},累加金额={},rowOp={}",
|
||||
contractId, stageRow.getId(), claimedAmount, resyncNeeded ? "update" : "add(待合同链路首推)");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean submitRows(Long contractId, List<HcsContractExeStage> rows) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(contractId);
|
||||
if (contractManage == null) {
|
||||
throw new ServiceException("合同不存在:" + contractId);
|
||||
}
|
||||
if (Func.isEmpty(rows)) {
|
||||
throw new ServiceException("履约规则行不能为空");
|
||||
}
|
||||
for (HcsContractExeStage row : rows) {
|
||||
validateRow(row);
|
||||
row.setContractId(contractId);
|
||||
if (row.getId() == null) {
|
||||
// 新行:待随合同链路首推(row_op=add)
|
||||
row.setRowOp(HcsSyncConstant.ROW_OP_ADD);
|
||||
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
row.setHcsSyncMsg("待随合同链路同步");
|
||||
row.setFundType(StringUtil.isBlank(row.getFundType()) ? hcsProperties.getExeStage().getFundType() : row.getFundType());
|
||||
row.setIsFinish(row.getIsFinish() == null ? 0 : row.getIsFinish());
|
||||
save(row);
|
||||
} else {
|
||||
HcsContractExeStage existing = getById(row.getId());
|
||||
if (existing == null || !contractId.equals(existing.getContractId())) {
|
||||
throw new ServiceException("履约规则行不属于该合同:" + row.getId());
|
||||
}
|
||||
// 已同步行编辑 → update;未同步行保持 add
|
||||
String rowOp = StringUtil.isBlank(existing.getHcsStageFdId()) ? HcsSyncConstant.ROW_OP_ADD : HcsSyncConstant.ROW_OP_UPDATE;
|
||||
existing.setPerformType(row.getPerformType());
|
||||
existing.setFundNature(row.getFundNature());
|
||||
existing.setFundAmount(row.getFundAmount());
|
||||
existing.setFundDate(row.getFundDate());
|
||||
existing.setInvoiceRule(row.getInvoiceRule());
|
||||
existing.setInvoiceCycle(row.getInvoiceCycle());
|
||||
existing.setFundType(StringUtil.isBlank(row.getFundType()) ? hcsProperties.getExeStage().getFundType() : row.getFundType());
|
||||
existing.setIsFinish(row.getIsFinish() == null ? 0 : row.getIsFinish());
|
||||
existing.setRowOp(rowOp);
|
||||
existing.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
existing.setHcsSyncMsg("待随合同链路同步(" + rowOp + ")");
|
||||
updateById(existing);
|
||||
}
|
||||
}
|
||||
log.info("合同履约规则保存:contractId={},共 {} 行", contractId, rows.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean removeRow(Long id) {
|
||||
HcsContractExeStage row = getById(id);
|
||||
if (row == null) {
|
||||
throw new ServiceException("履约规则行不存在:" + id);
|
||||
}
|
||||
if (StringUtil.isNotBlank(row.getHcsStageFdId())) {
|
||||
// 已同步行:软删+标记 delete,由 C103-9 凭合规阶段ID 上报删除(ADR-0002,不可物理删除)
|
||||
row.setRowOp(HcsSyncConstant.ROW_OP_DELETE);
|
||||
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
row.setHcsSyncMsg("待随合同链路同步(delete)");
|
||||
updateById(row);
|
||||
}
|
||||
removeById(id);
|
||||
log.info("合同履约规则行删除:id={},contractId={},rowOp={}", id, row.getContractId(), row.getRowOp());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createDefaultRowOnApprove(ContractManage contractManage) {
|
||||
Long contractId = contractManage.getId();
|
||||
if (count(Wrappers.<HcsContractExeStage>lambdaQuery().eq(HcsContractExeStage::getContractId, contractId)) > 0) {
|
||||
return;
|
||||
}
|
||||
HcsContractExeStage row = new HcsContractExeStage();
|
||||
row.setContractId(contractId);
|
||||
if ("承运商合同".equals(contractManage.getContractCategory())) {
|
||||
row.setPerformType(HcsSyncConstant.PERFORM_TYPE_PAYMENT);
|
||||
row.setInvoiceRule(mapInvoiceRule(contractManage.getSettlementMode()));
|
||||
} else if ("客户合同".equals(contractManage.getContractCategory())) {
|
||||
row.setPerformType(HcsSyncConstant.PERFORM_TYPE_COLLECTION);
|
||||
} else {
|
||||
log.info("合同类别非承运商/客户,不自动创建履约计划行:contractNo={},contractCategory={}", contractManage.getContractNo(), contractManage.getContractCategory());
|
||||
return;
|
||||
}
|
||||
// 默认进度款计划行:金额/资金日期留空,随合同链路推 C103-9
|
||||
row.setFundNature(HcsSyncConstant.FUND_NATURE_PROGRESS);
|
||||
row.setFundType(hcsProperties.getExeStage().getFundType());
|
||||
row.setIsFinish(0);
|
||||
row.setRowOp(HcsSyncConstant.ROW_OP_ADD);
|
||||
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
row.setHcsSyncMsg("审批通过自动创建,待随合同链路同步");
|
||||
save(row);
|
||||
log.info("审批通过自动创建履约计划行:contractNo={},contractCategory={},performType={},invoiceRule={}",
|
||||
contractManage.getContractNo(), contractManage.getContractCategory(), row.getPerformType(), row.getInvoiceRule());
|
||||
}
|
||||
|
||||
/**
|
||||
* 结算模式文本映射 C103-9 开票规则:先款后票=payFirst、先票后款=invoiceFirst,其余(月结/现结等)不传
|
||||
*/
|
||||
private String mapInvoiceRule(String settlementMode) {
|
||||
if ("先款后票".equals(settlementMode)) {
|
||||
return "payFirst";
|
||||
}
|
||||
if ("先票后款".equals(settlementMode)) {
|
||||
return "invoiceFirst";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 行字段校验
|
||||
*/
|
||||
private void validateRow(HcsContractExeStage row) {
|
||||
if (StringUtil.isBlank(row.getPerformType())) {
|
||||
throw new ServiceException("履约类型不能为空(payment/collection)");
|
||||
}
|
||||
if (!List.of(HcsSyncConstant.PERFORM_TYPE_PAYMENT, HcsSyncConstant.PERFORM_TYPE_COLLECTION).contains(row.getPerformType())) {
|
||||
throw new ServiceException("履约类型非法:" + row.getPerformType());
|
||||
}
|
||||
if (row.getFundAmount() != null && row.getFundAmount().signum() < 0) {
|
||||
throw new ServiceException("计划金额不能为负数");
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -25,6 +25,8 @@ package org.springblade.transport.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
@@ -35,12 +37,17 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.excel.ContractManageExcel;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.HcsContractExeStageMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.pojo.vo.ContractManageVO;
|
||||
import org.springblade.transport.service.IContractExeStageService;
|
||||
import org.springblade.transport.service.IContractManageService;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.hcs.HcsCreditCheckSupport;
|
||||
import org.springblade.transport.wrapper.ContractManageWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -65,9 +72,15 @@ import java.util.regex.Pattern;
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMapper, ContractManage> implements IContractManageService {
|
||||
|
||||
private final HcsContractExeStageMapper hcsContractExeStageMapper;
|
||||
private final HcsCreditCheckSupport hcsCreditCheckSupport;
|
||||
private final IContractExeStageService contractExeStageService;
|
||||
|
||||
private static final String STAGE_DRAFT = "draft";
|
||||
private static final String STAGE_TEMPORARY = "temporary";
|
||||
private static final String STAGE_FORMAL = "formal";
|
||||
@@ -150,6 +163,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
prepare(contractManage);
|
||||
validateDraft(contractManage);
|
||||
validateTemporary(contractManage);
|
||||
// 高风险/黑名单客商管控:必须上传特批附件(外部客商,内部组织默认无风险)
|
||||
validateSpecExplanationOnSubmit(contractManage);
|
||||
if (Func.isNotEmpty(contractManage.getId())) {
|
||||
validateContractPartyCombinationUnique(contractManage);
|
||||
}
|
||||
@@ -172,6 +187,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
throw new ServiceException("仅草稿或已撤回/已驳回的临时合同允许提交临时合同审批");
|
||||
}
|
||||
validateTemporary(contractManage);
|
||||
// 高风险/黑名单客商管控:必须上传特批附件(外部客商,内部组织默认无风险)
|
||||
validateSpecExplanationOnSubmit(contractManage);
|
||||
// 临时放开新增合同从草稿转临时合同时的组合重复校验。
|
||||
// validateContractPartyCombinationUnique(contractManage);
|
||||
contractManage.setContractStage(STAGE_TEMPORARY);
|
||||
@@ -190,6 +207,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
throw new ServiceException("仅临时合同或已撤回/已驳回的正式合同允许提交正式合同审批");
|
||||
}
|
||||
validateFormal(contractManage);
|
||||
// 高风险/黑名单客商管控:必须上传特批附件(外部客商,内部组织默认无风险)
|
||||
validateSpecExplanationOnSubmit(contractManage);
|
||||
// 临时放开新增合同从临时转正式合同时的组合重复校验。
|
||||
// validateContractPartyCombinationUnique(contractManage);
|
||||
contractManage.setContractStage(STAGE_FORMAL);
|
||||
@@ -222,6 +241,15 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
contractManage.setContractFileJson(markContractFilesApproved(contractManage.getContractFileJson()));
|
||||
refreshArchiveStatusOnApprove(contractManage);
|
||||
updateLatestReviewingChangeRecord(contractManage, contractManage.getApprovalStatus(), "审核通过");
|
||||
// HCS 推送:审批通过(含变更通过)置待同步状态,事务提交后由 Controller 同步推送并回传失败原因,扫描任务保留兜底
|
||||
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
// 合同审批通过:履约规则未同步行统一置待同步标记,随合同链路调 C103-9(ADR-0002)
|
||||
// 审批通过自动创建默认履约计划行(承运商=付款进度款/客户=收款进度款),已有规则行时不重复创建
|
||||
contractExeStageService.createDefaultRowOnApprove(contractManage);
|
||||
hcsContractExeStageMapper.update(null, Wrappers.<HcsContractExeStage>lambdaUpdate()
|
||||
.eq(HcsContractExeStage::getContractId, contractManage.getId())
|
||||
.isNull(HcsContractExeStage::getHcsStageFdId)
|
||||
.set(HcsContractExeStage::getRowOp, "add"));
|
||||
return updateById(contractManage);
|
||||
}
|
||||
|
||||
@@ -371,6 +399,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
normalizeOptionalIntegerFields(source);
|
||||
validateContractAmountRequired(source);
|
||||
validateAdditionalFields(source);
|
||||
source.setBillingPlanJson(request.getBillingPlanJson()); source.setSettlementRuleJson(request.getSettlementRuleJson()); source.setPreSettlementConfigJson(request.getPreSettlementConfigJson()); source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson()); source.setPaymentRatioJson(request.getPaymentRatioJson());
|
||||
source.setContractFileJson(request.getContractFileJson()); source.setAttachmentsJson(request.getAttachmentsJson());
|
||||
source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson()));
|
||||
// 高风险/黑名单客商管控:变更提交同样必须携带特批附件(与新增/暂存/转正式一致,外部客商,内部组织默认无风险)
|
||||
validateSpecExplanationOnSubmit(source);
|
||||
source.setBillingPlanJson(request.getBillingPlanJson());
|
||||
source.setSettlementRuleJson(request.getSettlementRuleJson());
|
||||
source.setPreSettlementConfigJson(request.getPreSettlementConfigJson());
|
||||
@@ -508,9 +541,29 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
.orderByDesc(ContractManage::getCreateTime);
|
||||
applyEffectiveType(queryWrapper, contractManage.getEffectiveType());
|
||||
applyExpireScope(queryWrapper, contractManage.getExpireScope());
|
||||
// 是否同步金蝶(kingdee_id 是否已回填)/ 是否同步合规(hcs_sync_status 是否 synced)
|
||||
applySyncFlagFilter(queryWrapper, contractManage.getKingdeeSynced(), contractManage.getHcsSynced());
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶/合规同步与否筛选(列表搜索:kingdeeSynced 按 kingdee_id 空值判断,hcsSynced 按 hcs_sync_status=synced 判断)
|
||||
*/
|
||||
private void applySyncFlagFilter(LambdaQueryWrapper<ContractManage> queryWrapper, Boolean kingdeeSynced, Boolean hcsSynced) {
|
||||
if (Boolean.TRUE.equals(kingdeeSynced)) {
|
||||
queryWrapper.isNotNull(ContractManage::getKingdeeId).ne(ContractManage::getKingdeeId, "");
|
||||
} else if (Boolean.FALSE.equals(kingdeeSynced)) {
|
||||
queryWrapper.and(wrapper -> wrapper.isNull(ContractManage::getKingdeeId)
|
||||
.or().eq(ContractManage::getKingdeeId, ""));
|
||||
}
|
||||
if (Boolean.TRUE.equals(hcsSynced)) {
|
||||
queryWrapper.eq(ContractManage::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
} else if (Boolean.FALSE.equals(hcsSynced)) {
|
||||
queryWrapper.and(wrapper -> wrapper.isNull(ContractManage::getHcsSyncStatus)
|
||||
.or().ne(ContractManage::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyEffectiveType(LambdaQueryWrapper<ContractManage> queryWrapper, String effectiveType) {
|
||||
if (Func.isEmpty(effectiveType)) {
|
||||
return;
|
||||
@@ -1205,6 +1258,49 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 高风险/黑名单客商提交校验:甲/乙方为外部客商且高风险(C101-1 风险等级=2)或黑名单时,
|
||||
* 必须已上传「特批附件」类型附件(fileType=特批附件,业务字典 contract_supporting_attachment_type),
|
||||
* 归口「附件(佐证材料等)」(attachmentsJson)区上传;用印材料(contractFileJson)历史已传行兼容放行;内部组织类型客商默认无风险
|
||||
*
|
||||
* @param contractManage 合同单
|
||||
*/
|
||||
private void validateSpecExplanationOnSubmit(ContractManage contractManage) {
|
||||
boolean risky = isRiskyCustomer(contractManage.getPartyA()) || isRiskyCustomer(contractManage.getPartyB());
|
||||
if (!risky) {
|
||||
return;
|
||||
}
|
||||
boolean hasSpecExplanation = this.hasSpecAttachment(contractManage.getAttachmentsJson())
|
||||
|| this.hasSpecAttachment(contractManage.getContractFileJson());
|
||||
if (!hasSpecExplanation) {
|
||||
throw new ServiceException("合同相对方存在高风险/黑名单客商,必须上传「特批附件」类型附件后再提交审批");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件 JSON 中是否已存在特批附件(fileType=特批附件)
|
||||
*
|
||||
* @param attachmentsJson 附件元数据 JSON 数组
|
||||
* @return 是否存在特批附件
|
||||
*/
|
||||
private boolean hasSpecAttachment(String attachmentsJson) {
|
||||
return parseJsonArray(attachmentsJson).stream()
|
||||
.filter(Map.class::isInstance)
|
||||
.map(Map.class::cast)
|
||||
.anyMatch(attachment -> HcsSyncConstant.ATTACHMENT_TYPE_SPEC_EXPLANATION
|
||||
.equals(Objects.toString(attachment.get(HcsSyncConstant.ATTACHMENT_TYPE_FIELD), null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 单方客商是否高风险/黑名单(C101-1 实时查询,名称反查不到客商编号不阻断)
|
||||
*/
|
||||
private boolean isRiskyCustomer(String partyName) {
|
||||
if (Func.isEmpty(partyName)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean.TRUE.equals(hcsCreditCheckSupport.queryCustomerRiskByName(partyName).getRisky());
|
||||
}
|
||||
|
||||
private String mergeAttachmentJson(String existingJson, String pendingJson) {
|
||||
List<Object> merged = parseJsonArray(existingJson);
|
||||
List<Object> pending = parseJsonArray(pendingJson);
|
||||
|
||||
+7
@@ -52,6 +52,7 @@ import org.springblade.thirdparty.bfi.pojo.vo.BfiPageVO;
|
||||
import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.KingdeeAttachmentConstant;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.constant.KingdeeLockConstant;
|
||||
import org.springblade.transport.pojo.entity.KingdeeBillLock;
|
||||
@@ -1271,6 +1272,8 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
||||
invoiceWritten += inserted;
|
||||
// 查到发票:开票申请单推进开票完成(终态)
|
||||
application.setKingdeeStatus(KingdeeBfiConstant.KINGDEE_STATUS_INVOICED);
|
||||
// HCS 开票登记待更新:扫描任务凭 hcs_fd_id 走 C111-3 回填票据日期(D12)
|
||||
application.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
toUpdateApplications.add(application);
|
||||
invoicedCount++;
|
||||
log.info("金蝶开票结果回写:开票申请[{}]状态推进为invoiced,本次写入发票{}张", application.getApplicationNo(), inserted);
|
||||
@@ -1671,6 +1674,10 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
||||
InvoiceApplication entity = existing(id);
|
||||
if (!from.equals(entity.getApprovalStatus())) throw new ServiceException("当前状态不允许此操作");
|
||||
entity.setApprovalStatus(to);
|
||||
if (TransportBusiConstant.APPROVED.equals(to)) {
|
||||
// HCS 推送零埋点:审批通过仅置待同步状态,扫描任务据此推开票登记(ADR-0001/D12)
|
||||
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
}
|
||||
entity.setCurrentNode(node);
|
||||
entity.setCurrentProcessor(AuthUtil.getUserName());
|
||||
updateById(entity);
|
||||
|
||||
+8
@@ -50,6 +50,7 @@ import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoicePoolVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiPageVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiReceivedInvoiceVO;
|
||||
import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.constant.KingdeeAttachmentConstant;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
@@ -87,6 +88,8 @@ import org.springblade.transport.support.KingdeeAttachmentSyncSupport;
|
||||
import org.springblade.transport.support.KingdeeReceivedInvoiceSyncSupport;
|
||||
import org.springblade.transport.support.KingdeeSyncLockSupport;
|
||||
import org.springblade.transport.wrapper.InvoiceReceiptWrapper;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -121,6 +124,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RefreshScope
|
||||
@RequiredArgsConstructor
|
||||
public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMapper, InvoiceReceipt>
|
||||
implements IInvoiceReceiptService {
|
||||
@@ -2073,6 +2077,10 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
|
||||
String actionName, String reason) {
|
||||
String fromStatus = entity.getApprovalStatus();
|
||||
entity.setApprovalStatus(toStatus);
|
||||
if (TransportBusiConstant.APPROVED.equals(toStatus)) {
|
||||
// HCS 推送零埋点:审批通过仅置待同步状态,扫描任务据此推收票单(ADR-0001)
|
||||
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
}
|
||||
entity.setCurrentNode(actionName);
|
||||
entity.setCurrentProcessor(AuthUtil.getUserName());
|
||||
updateById(entity);
|
||||
|
||||
+53
-7
@@ -50,6 +50,7 @@ import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.thirdparty.bfi.util.BfiUtil;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.KingdeeAttachmentConstant;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.constant.KingdeeLockConstant;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
@@ -85,6 +86,7 @@ import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementPayment;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementSource;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.pojo.entity.KingdeeBillLock;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplicationInvoice;
|
||||
@@ -97,6 +99,7 @@ import org.springblade.transport.pojo.entity.TemporaryCreditLimit;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IContractExeStageService;
|
||||
import org.springblade.transport.service.IKingdeeBillLockService;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springblade.transport.service.ISettlementProjectAdvanceWriteOffService;
|
||||
@@ -105,6 +108,7 @@ import org.springblade.transport.pojo.vo.ProjectAdvanceWriteOffOptionVO;
|
||||
import org.springblade.transport.support.KingdeeApBillSyncSupport;
|
||||
import org.springblade.transport.support.KingdeeAttachmentSyncSupport;
|
||||
import org.springblade.transport.support.KingdeeSyncLockSupport;
|
||||
import org.springblade.transport.support.hcs.HcsCreditCheckSupport;
|
||||
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -174,6 +178,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
|
||||
private final BladeRedis bladeRedis;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final HcsCreditCheckSupport hcsCreditCheckSupport;
|
||||
private final IContractExeStageService contractExeStageService;
|
||||
|
||||
@Override
|
||||
public IPage<PaymentApplicationVO> selectPage(IPage<PaymentApplication> page, PaymentApplicationVO query) {
|
||||
@@ -460,6 +466,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
if (isBillPayment(entity.getPaymentMethod()) && activeBillUsage(entity.getId()) == null) {
|
||||
syncBillOccupation(entity);
|
||||
}
|
||||
// HCS 提交同步校验(事务内查询类同步,D15/R2-Q3):C101-1 风险(收款方外部客商高风险/黑名单须特批附件并标记待特批)+ C106-1 额度(超额转特批)
|
||||
hcsCreditCheckSupport.checkOnSubmit(entity);
|
||||
entity.setApprovalStatus(TransportBusiConstant.REVIEWING);
|
||||
entity.setCurrentNode("财务审核");
|
||||
entity.setCurrentProcessor(AuthUtil.getUserName());
|
||||
@@ -516,7 +524,11 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
useBillBalance(entity);
|
||||
}
|
||||
entity.setApprovalStatus(TransportBusiConstant.APPROVED); entity.setCurrentNode("审批通过"); entity.setCurrentProcessor(AuthUtil.getUserName());
|
||||
// HCS 推送零埋点:审批通过仅置待同步状态;特批发起由 /approve 与 MK 审批回调经 HcsImmediatePushSupport 即时触发,扫描任务兜底(ADR-0001)
|
||||
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
updateById(entity);
|
||||
// 履约计划款项金额累加(款项金额=申请付款金额,多次付款累加),已同步计划行置 update 待随付款链重推 C103-9
|
||||
this.contractExeStageService.accumulatePaidAmountOnApprove(entity);
|
||||
refreshFormalSettlement(entity);
|
||||
// 审批事务提交后自动推送金蝶(AFTER_COMMIT 监听器消费,推送失败不影响审批结果)
|
||||
eventPublisher.publishEvent(new KingdeeBillApprovedEvent(KingdeeBillApprovedEvent.BIZ_PAYMENT_APPLICATION, entity.getId()));
|
||||
@@ -601,6 +613,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
// 单据号入上下文:批量同步失败提示用单号定位单据(雪花id用户无法对应),不再额外查库
|
||||
context.paymentNoCache().put(id, entity.getPaymentNo());
|
||||
if (!TransportBusiConstant.APPROVED.equals(entity.getApprovalStatus())) throw new ServiceException("仅审批通过的付款申请允许生成金蝶单据");
|
||||
// 高风险/黑名单客商付款特批门禁:待特批/特批审核中(挂起"特批审核")禁止推送金蝶,
|
||||
// 特批通过(合规 paymentLegal/result 回调恢复"审批通过")后放行
|
||||
if (entity.getHcsSpecStatus() != null && List.of(HcsSyncConstant.SPEC_STATUS_NEED, HcsSyncConstant.SPEC_STATUS_PUSHING,
|
||||
HcsSyncConstant.SPEC_STATUS_PROCESSING).contains(entity.getHcsSpecStatus())) {
|
||||
throw new ServiceException("收款方客商为高风险/黑名单,付款特批审核中(" + entity.getCurrentNode() + "),特批完成后方可推送金蝶");
|
||||
}
|
||||
if (KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(entity.getKingdeeStatus())
|
||||
|| KingdeeBfiConstant.KINGDEE_STATUS_PAYING.equals(entity.getKingdeeStatus())
|
||||
|| KingdeeBfiConstant.KINGDEE_STATUS_PAID.equals(entity.getKingdeeStatus())
|
||||
@@ -954,6 +972,10 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
} else if (KingdeeBfiConstant.KINGDEE_STATUS_PAID.equals(kingdeeStatus)) {
|
||||
entity.setPaidAmount(money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
// HCS 履约记录推进:实际付款变化后置待同步,扫描任务凭 hcs_perf_fd_id 走 C108-2 更新(D11)
|
||||
if (entity.getHcsPerfFdId() != null) {
|
||||
entity.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
}
|
||||
updateById(entity);
|
||||
if (KingdeeBfiConstant.KINGDEE_STATUS_PAID.equals(kingdeeStatus)) {
|
||||
// 付款完成:联动履约付款数据(预结算预付记录、正式结算付款记录)
|
||||
@@ -1288,14 +1310,38 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entry.setProjectNumber(KingdeeBfiConstant.BFI_AP_BILL_PROJECT_NUMBER_TEST);
|
||||
// 【必填】对方系统唯一行id(不能重复):付款申请号-1
|
||||
entry.setAwrpEntryCode(bfiEntryCode(entity.getPaymentNo(), 1));
|
||||
// 备注(非必传)
|
||||
entry.setERemark(entity.getRemark());
|
||||
// 备注(非必传):携带关联履约计划id(进度款计划行合规阶段ID),用于合同系统关联履约付款数据与履约计划
|
||||
entry.setERemark(this.bfiExeStageRemark(entity));
|
||||
param.setEntry(List.of(entry));
|
||||
|
||||
BfiBillSaveResultVO saveResult = bfiService.savePrePayApply(param);
|
||||
return requireBfiBillIdAndFillNo(entity, saveResult, "推送金蝶付款申请");
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶付款申请备注:携带关联履约计划id(进度款计划行合规阶段ID hcs_stage_fd_id),
|
||||
* 用于合同系统关联履约付款数据与履约计划;无关联计划行时不携带(与 C108 履约记录"无关联则不传"口径一致),
|
||||
* 计划行查询异常不阻断金蝶推送
|
||||
*
|
||||
* @param entity 付款申请实体
|
||||
* @return 备注文本(履约计划id + 原备注)
|
||||
*/
|
||||
private String bfiExeStageRemark(PaymentApplication entity) {
|
||||
String stageFdId = null;
|
||||
try {
|
||||
HcsContractExeStage stageRow = this.contractExeStageService.findPaymentStageRow(entity.getContractId(),
|
||||
HcsSyncConstant.fundNatureOfPayment(entity.getPaymentType()));
|
||||
stageFdId = stageRow == null ? null : stageRow.getHcsStageFdId();
|
||||
} catch (Exception exception) {
|
||||
log.warn("付款申请[{}]查询关联履约计划行异常,金蝶备注不携带履约计划id", entity.getPaymentNo(), exception);
|
||||
}
|
||||
String planPart = Func.isBlank(stageFdId) ? "" : "履约计划id:" + stageFdId;
|
||||
if (Func.isBlank(entity.getRemark())) {
|
||||
return planPart;
|
||||
}
|
||||
return planPart.isEmpty() ? entity.getRemark() : planPart + ";" + entity.getRemark();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验正式结算付款对应的结算单结算类型:应付(财务应付单 A102-2)直接放行,
|
||||
* 应收(对应财务应收单 A302-2)推送规则待业务确认,暂拦截报错。
|
||||
@@ -1372,8 +1418,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entry.setProjectNumber(KingdeeBfiConstant.BFI_AP_BILL_PROJECT_NUMBER_TEST);
|
||||
// 期望付款日(非必传,不传时根据应付单自动带出)
|
||||
// entry.setEExpaydate(applyDateText);
|
||||
// 备注(非必传)
|
||||
// entry.setERemark(entity.getRemark());
|
||||
// 备注(非必传):携带关联履约计划id(进度款计划行合规阶段ID),用于合同系统关联履约付款数据与履约计划
|
||||
entry.setERemark(this.bfiExeStageRemark(entity));
|
||||
// 【必填】外部单据类型 todo
|
||||
entry.setAwrpSrcType(bfiProperties.getPayApply().getSrcType());
|
||||
param.setEntry(List.of(entry));
|
||||
@@ -2129,9 +2175,9 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
private void saveSettlementRelations(PaymentApplication entity, List<FormalSettlement> settlements,
|
||||
List<PaymentApplicationSettlementLineRequest> lines) {
|
||||
if (settlements.isEmpty()) return;
|
||||
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId()));
|
||||
boolean customAmounts = this.useSpecifiedLineAmounts(lines);
|
||||
// 关系唯一索引不含 is_deleted,逻辑删除会残留旧行导致重存冲突,此处必须物理删除
|
||||
settlementRelationMapper.deleteByPaymentApplicationId(entity.getId());
|
||||
boolean customAmounts = settlements.size() > 1 && lines != null && !lines.isEmpty();
|
||||
BigDecimal totalBase = settlements.stream()
|
||||
.map(item -> payableOfFormalSettlement(item.getId(), item.getSettlementAmount(), entity.getId()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
+23
@@ -37,6 +37,7 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.excel.ProjectApplyExcel;
|
||||
import org.springblade.transport.mapper.ProjectApplyMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementMapper;
|
||||
@@ -213,6 +214,8 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
|
||||
projectApply.setCurrentNode("审批通过");
|
||||
projectApply.setCurrentProcessor(AuthUtil.getUserName());
|
||||
projectApply.setApprovedTime(LocalDateTime.now());
|
||||
// HCS 推送:审批通过置待同步状态,事务提交后由 Controller 同步推送并回传失败原因,扫描任务保留兜底
|
||||
projectApply.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
return updateById(projectApply);
|
||||
}
|
||||
|
||||
@@ -404,9 +407,29 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
|
||||
.toList();
|
||||
queryWrapper.in(ProjectApply::getId, matchedIds.isEmpty() ? List.of(-1L) : matchedIds);
|
||||
}
|
||||
// 是否同步金蝶(kingdee_id 是否已回填)/ 是否同步合规(hcs_sync_status 是否 synced)
|
||||
applySyncFlagFilter(queryWrapper, projectApply.getKingdeeSynced(), projectApply.getHcsSynced());
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶/合规同步与否筛选(列表搜索:kingdeeSynced 按 kingdee_id 空值判断,hcsSynced 按 hcs_sync_status=synced 判断)
|
||||
*/
|
||||
private void applySyncFlagFilter(LambdaQueryWrapper<ProjectApply> queryWrapper, Boolean kingdeeSynced, Boolean hcsSynced) {
|
||||
if (Boolean.TRUE.equals(kingdeeSynced)) {
|
||||
queryWrapper.isNotNull(ProjectApply::getKingdeeId).ne(ProjectApply::getKingdeeId, "");
|
||||
} else if (Boolean.FALSE.equals(kingdeeSynced)) {
|
||||
queryWrapper.and(wrapper -> wrapper.isNull(ProjectApply::getKingdeeId)
|
||||
.or().eq(ProjectApply::getKingdeeId, ""));
|
||||
}
|
||||
if (Boolean.TRUE.equals(hcsSynced)) {
|
||||
queryWrapper.eq(ProjectApply::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
} else if (Boolean.FALSE.equals(hcsSynced)) {
|
||||
queryWrapper.and(wrapper -> wrapper.isNull(ProjectApply::getHcsSyncStatus)
|
||||
.or().ne(ProjectApply::getHcsSyncStatus, HcsSyncConstant.HCS_STATUS_SYNCED));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillFundUseRisk(List<ProjectApplyVO> records) {
|
||||
if (records == null || records.isEmpty()) {
|
||||
return;
|
||||
|
||||
+9
-2
@@ -65,6 +65,7 @@ import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IReceiptFlowService;
|
||||
import org.springblade.transport.support.KingdeeSyncLockSupport;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.hcs.HcsClaimSyncSupport;
|
||||
import org.springblade.transport.wrapper.ReceiptFlowWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -130,6 +131,7 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
private final BfiProperties bfiProperties;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
private final KingdeeSyncLockSupport kingdeeSyncLockSupport;
|
||||
private final HcsClaimSyncSupport hcsClaimSyncSupport;
|
||||
|
||||
@Override
|
||||
public IPage<ReceiptFlowVO> selectPage(IPage<KingdeeReceiptFlow> page, ReceiptFlowVO query) {
|
||||
@@ -904,10 +906,15 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
|
||||
|
||||
/**
|
||||
* 事务内推送:流水行锁覆盖闸门校验、对账、推送与状态落库全过程,
|
||||
* 防定时任务与手动按钮并发对同一流水双推
|
||||
* 防定时任务与手动按钮并发对同一流水双推;
|
||||
* 推送成功后触发合同合规认领链推送(C103-9/C107-1/C108-1,事务提交后执行,失败不影响金蝶推送结果)
|
||||
*/
|
||||
private ReceiptClaimPushResult pushClaimLocked(Long flowId, BfiClaimAnnounceVO announce) {
|
||||
return transactionTemplate.execute(status -> doPushClaimToKingdee(flowId, announce));
|
||||
ReceiptClaimPushResult result = transactionTemplate.execute(status -> doPushClaimToKingdee(flowId, announce));
|
||||
if (result != null && result.isSuccess()) {
|
||||
hcsClaimSyncSupport.pushClaimToHcs(flowId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
@@ -282,6 +282,8 @@ public class KingdeeApBillSyncSupport {
|
||||
param.setCurrencyNumber(currency);
|
||||
// 【必填】付款组织.编码
|
||||
param.setPayorgNumber(orgNumber);
|
||||
// 部门.编码:项目业务部门自身的金蝶编码(结算/付款组织仍传上溯平台公司编码,口径与付款申请 A103-3 一致)
|
||||
param.setDepartmentNumber(this.kingdeeDeptNumber(contract.getProjectId(), context));
|
||||
// 采购组织.编码(非必传)
|
||||
// param.setPurorgNumber(orgNumber);
|
||||
// 【必填】款项性质.编码
|
||||
|
||||
+2
@@ -218,6 +218,8 @@ public class KingdeeArBillSyncSupport {
|
||||
param.setOrgNumber(orgNumber);
|
||||
// 收款组织.编码:同结算组织
|
||||
param.setRecorgNumber(orgNumber);
|
||||
// 部门.编码:项目业务部门自身的金蝶编码(结算/收款组织仍传上溯平台公司编码,口径与付款申请/应付单一致)
|
||||
param.setDepartmentNumber(kingdeeApBillSyncSupport.kingdeeDeptNumber(contract.getProjectId(), context));
|
||||
// 【必填】单据日期
|
||||
param.setBizdate(billDateText);
|
||||
// 【必填】往来类型:统一固定客户(KingdeeApBillSyncSupport#bfiAsstactType,与应付单/付款申请同口径)
|
||||
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsAttachmentDownloadDTO;
|
||||
import org.springblade.hcs.dto.HcsAttachmentRefDTO;
|
||||
import org.springblade.hcs.dto.HcsAttachmentVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.HcsAttachmentSyncMapper;
|
||||
import org.springblade.transport.pojo.entity.HcsAttachmentSync;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 附件转传支撑组件(C103-2 multipart 上传,设计文档 §6)
|
||||
* <p>
|
||||
* 流程:解析单据附件 JSON 元素(去重键照 ContractManage#attachmentKey 惯例)→
|
||||
* 按元数据 URL 从文件服务拉取流(基址可配 attachment.file-download-base)→
|
||||
* C103-2 上传取得合规附件ID → 落映射同步表(blade_hcs_attachment_sync,照金蝶附件同步表设计:
|
||||
* biz_type+biz_id+attachment_key 唯一,转传前查命中即复用,仅落成功记录)。
|
||||
* 任一附件转传失败 → 抛出异常由整单推送失败进补偿重试(失败原因落单据 hcs_sync_msg)。
|
||||
* <p>
|
||||
* 合同链路挂载字段 fdEntityKey(v4 枚举):主文件 approved=true → eopCdraftSign(用印)、
|
||||
* 特批附件(fileType=特批附件)→ eopCdraftSpecial(随 C103-1 fdSepcailContract=true 特批流程)、其它附件 → eopCdraftOther。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsAttachmentSupport {
|
||||
|
||||
/**
|
||||
* 附件元数据去重键候选字段(照 ContractManage#attachmentKey 既有惯例)
|
||||
*/
|
||||
private static final List<String> KEY_FIELDS = List.of("url", "link", "fileUrl", "downloadUrl", "uid", "originalName", "name");
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final HcsAttachmentSyncMapper hcsAttachmentSyncMapper;
|
||||
|
||||
/**
|
||||
* 解析单据附件 JSON 并转传,返回合规附件引用列表(保持行序)
|
||||
*
|
||||
* @param bizType 业务单据类型(HcsSyncConstant.BIZ_TYPE_*)
|
||||
* @param bizId 业务单据id
|
||||
* @param attachmentsJson 附件元数据 JSON 数组
|
||||
* @param operatorLoginName 操作人登录名(C103-2 fdLoginName)
|
||||
* @return 合规附件引用列表(fdId + fdEntityKey)
|
||||
*/
|
||||
public List<HcsAttachmentRefDTO> transferAttachments(String bizType, Long bizId, String attachmentsJson, String operatorLoginName) {
|
||||
if (StringUtil.isBlank(attachmentsJson)) {
|
||||
return List.of();
|
||||
}
|
||||
List<Map<?, ?>> attachments = parseJsonArray(attachmentsJson);
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = new ArrayList<>();
|
||||
for (Map<?, ?> attachment : attachments) {
|
||||
attachmentRefs.add(this.transferOne(bizType, bizId, attachment, operatorLoginName));
|
||||
}
|
||||
return attachmentRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析合同主文件 JSON(仅 approved=true 的文件,挂用印字段)与其它附件 JSON(挂其他附件字段)并转传
|
||||
*
|
||||
* @param bizType 业务单据类型(HcsSyncConstant.BIZ_TYPE_*)
|
||||
* @param bizId 业务单据id
|
||||
* @param contractFileJson 合同主文件 JSON
|
||||
* @param attachmentsJson 其它附件 JSON
|
||||
* @param operatorLoginName 操作人登录名
|
||||
* @return 合规附件引用列表(fdId + fdEntityKey)
|
||||
*/
|
||||
public List<HcsAttachmentRefDTO> transferContractAttachments(String bizType, Long bizId, String contractFileJson, String attachmentsJson, String operatorLoginName) {
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = new ArrayList<>();
|
||||
for (Map<?, ?> file : parseJsonArray(contractFileJson)) {
|
||||
if (isApproved(file)) {
|
||||
attachmentRefs.add(this.transferOne(bizType, bizId, file, operatorLoginName,
|
||||
HcsSyncConstant.CONTRACT_ATTACH_KEY_SEAL));
|
||||
}
|
||||
}
|
||||
for (Map<?, ?> attachment : parseJsonArray(attachmentsJson)) {
|
||||
// 特批附件(fileType=特批附件)挂特批面板字段,其余挂其他附件字段(2026-10-13:特批改随 C103-1 fdSepcailContract 发起)
|
||||
String entityKey = HcsSyncConstant.ATTACHMENT_TYPE_SPEC_EXPLANATION.equals(attachment.get(HcsSyncConstant.ATTACHMENT_TYPE_FIELD))
|
||||
? HcsSyncConstant.CONTRACT_ATTACH_KEY_SPECIAL : HcsSyncConstant.CONTRACT_ATTACH_KEY_OTHER;
|
||||
attachmentRefs.add(this.transferOne(bizType, bizId, attachment, operatorLoginName, entityKey));
|
||||
}
|
||||
return attachmentRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端单文件直传合规(C103-2 代理,前端接入设计 §3.2):返回合规附件ID;
|
||||
* 不写映射缓存(无 TMS 附件元数据键),调用方自行持久化 fdId(如特批材料 hcs_spec_files_json)
|
||||
*
|
||||
* @param file 上传文件
|
||||
* @param loginName 操作人登录名(fdLoginName)
|
||||
* @return 合规附件ID
|
||||
*/
|
||||
public String uploadOne(MultipartFile file, String loginName) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new HcsException("HCS附件上传失败:文件为空");
|
||||
}
|
||||
HcsResult<HcsAttachmentVO> result = hcsClient.uploadAttachment(file, "false", loginName);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdId())) {
|
||||
log.error("HCS附件上传失败响应:{}", result == null ? "无响应" : String.format("code=%s, msg=%s, traceId=%s",
|
||||
result.getCode(), result.getMsg(), result.getTraceId()));
|
||||
throw new HcsException("HCS附件上传失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 合规附件下载(C103-5 代理,前端接入设计 §3.2):返回原始文件流响应,由控制器透传浏览器
|
||||
*
|
||||
* @param fdId 合规附件ID
|
||||
* @return Feign 文件流响应(调用方负责关闭)
|
||||
*/
|
||||
public feign.Response downloadRaw(String fdId) {
|
||||
HcsAttachmentDownloadDTO param = new HcsAttachmentDownloadDTO();
|
||||
// 【必填】附件ID:上传时返回的 fdId
|
||||
param.setFdId(fdId);
|
||||
feign.Response response = hcsClient.downloadAttachment(param);
|
||||
if (response == null || response.body() == null) {
|
||||
throw new HcsException("HCS附件下载失败:响应为空(fdId=" + fdId + ")");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转传单个附件:映射同步表命中直接复用合规附件ID,未命中下载上传后落成功记录;
|
||||
* 返回引用默认不带 fdEntityKey,由调用方(或合同主文件/其他附件重载)指定挂载字段
|
||||
*/
|
||||
private HcsAttachmentRefDTO transferOne(String bizType, Long bizId, Map<?, ?> attachment, String operatorLoginName) {
|
||||
return this.transferOne(bizType, bizId, attachment, operatorLoginName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转传单个附件并指定挂载字段 key(fdEntityKey,如合同 eopCdraftSpecial/eopCdraftSign/eopCdraftOther)
|
||||
*/
|
||||
private HcsAttachmentRefDTO transferOne(String bizType, Long bizId, Map<?, ?> attachment, String operatorLoginName, String fdEntityKey) {
|
||||
String attachmentKey = attachmentKey(attachment);
|
||||
HcsAttachmentSync existing = hcsAttachmentSyncMapper.selectOne(Wrappers.<HcsAttachmentSync>lambdaQuery()
|
||||
.eq(HcsAttachmentSync::getBizType, bizType)
|
||||
.eq(HcsAttachmentSync::getBizId, bizId)
|
||||
.eq(HcsAttachmentSync::getAttachmentKey, attachmentKey)
|
||||
.last("limit 1"));
|
||||
if (existing != null && StringUtil.isNotBlank(existing.getFdId())) {
|
||||
String url = resolveUrl(attachment);
|
||||
return buildRef(existing.getFdId(), resolveFileName(attachment, url == null ? "" : url), fdEntityKey);
|
||||
}
|
||||
String url = resolveUrl(attachment);
|
||||
if (StringUtil.isBlank(url)) {
|
||||
throw new HcsException("HCS附件转传失败,附件元数据缺少下载地址(attachmentKey=" + attachmentKey + ")");
|
||||
}
|
||||
byte[] fileBytes = downloadFile(url);
|
||||
String fileName = resolveFileName(attachment, url);
|
||||
log.info("HCS附件转传开始:{},大小 {} 字节", fileName, fileBytes.length);
|
||||
HcsResult<HcsAttachmentVO> result = hcsClient.uploadAttachment(
|
||||
new InMemoryMultipartFile("fdFile", fileName, fileBytes), "false", operatorLoginName);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdId())) {
|
||||
log.error("HCS附件转传失败响应:{}", result == null ? "无响应" : String.format("code=%s, msg=%s, traceId=%s",
|
||||
result.getCode(), result.getMsg(), result.getTraceId()));
|
||||
throw new HcsException("HCS附件转传失败[" + fileName + "]:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
String attachmentFdId = result.getData().getFdId();
|
||||
HcsAttachmentSync record = new HcsAttachmentSync();
|
||||
record.setBizType(bizType);
|
||||
record.setBizId(bizId);
|
||||
record.setAttachmentKey(attachmentKey);
|
||||
record.setAttachmentName(fileName);
|
||||
record.setFdId(attachmentFdId);
|
||||
record.setSyncTime(LocalDateTime.now());
|
||||
hcsAttachmentSyncMapper.insert(record);
|
||||
log.info("HCS附件转传完成:{} → 合规附件ID {}(业务单据[{}:{}],fdEntityKey={})", fileName, attachmentFdId, bizType, bizId, fdEntityKey);
|
||||
return buildRef(attachmentFdId, fileName, fdEntityKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合规附件引用(fdId + 挂载字段 key)
|
||||
*/
|
||||
private HcsAttachmentRefDTO buildRef(String fdId, String fileName, String fdEntityKey) {
|
||||
HcsAttachmentRefDTO ref = new HcsAttachmentRefDTO();
|
||||
ref.setFdId(fdId);
|
||||
ref.setFdFileName(fileName);
|
||||
ref.setFdEntityKey(fdEntityKey);
|
||||
return ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按元数据 URL 从文件服务拉取流:完整 URL 直接下载,相对路径拼可配基址
|
||||
*/
|
||||
private byte[] downloadFile(String url) {
|
||||
String target = url;
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
String base = hcsProperties.getAttachment().getFileDownloadBase();
|
||||
if (StringUtil.isBlank(base)) {
|
||||
throw new HcsException("HCS附件转传失败,文件服务下载基址未配置(thirdParty.hcs.attachment.file-download-base)");
|
||||
}
|
||||
target = base + url;
|
||||
}
|
||||
try {
|
||||
return HttpUtil.downloadBytes(target);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS附件转传下载失败:{}", target, exception);
|
||||
throw new HcsException("HCS附件转传下载失败:" + target, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件元数据去重键(url|link|fileUrl|downloadUrl|uid|originalName|name 依次取首个非空)
|
||||
*/
|
||||
private String attachmentKey(Map<?, ?> attachment) {
|
||||
for (String field : KEY_FIELDS) {
|
||||
Object value = attachment.get(field);
|
||||
if (value != null && !String.valueOf(value).isBlank()) {
|
||||
return field + ":" + value;
|
||||
}
|
||||
}
|
||||
return String.valueOf(attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取附件下载地址
|
||||
*/
|
||||
private String resolveUrl(Map<?, ?> attachment) {
|
||||
for (String field : List.of("url", "link", "fileUrl", "downloadUrl", "path")) {
|
||||
Object value = attachment.get(field);
|
||||
if (value != null && !String.valueOf(value).isBlank()) {
|
||||
return String.valueOf(value).trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取附件文件名(name/originalName 优先,缺失时取 URL 末段)
|
||||
*/
|
||||
private String resolveFileName(Map<?, ?> attachment, String url) {
|
||||
for (String field : List.of("name", "originalName", "fileName")) {
|
||||
Object value = attachment.get(field);
|
||||
if (value != null && !String.valueOf(value).isBlank()) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
String lastSegment = url.substring(url.lastIndexOf('/') + 1);
|
||||
return StringUtil.isBlank(lastSegment) ? "hcs-attachment" : lastSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主文件审批标记判定(照 ContractManage#isAttachmentApproved 惯例)
|
||||
*/
|
||||
private boolean isApproved(Map<?, ?> attachment) {
|
||||
Object approved = attachment.get("approved");
|
||||
if (approved == null) {
|
||||
return false;
|
||||
}
|
||||
return Boolean.TRUE.equals(approved) || Objects.equals(approved, 1)
|
||||
|| "1".equals(String.valueOf(approved)) || "true".equalsIgnoreCase(String.valueOf(approved));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 数组(照 ContractManage#parseJsonArray 惯例,异常返回空列表)
|
||||
*/
|
||||
private List<Map<?, ?>> parseJsonArray(String json) {
|
||||
if (StringUtil.isBlank(json)) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
return JsonUtil.parse(json, List.class);
|
||||
} catch (Exception exception) {
|
||||
log.warn("HCS附件元数据解析失败,按无附件处理:{}", json);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存 MultipartFile(下载字节流转传 C103-2 用)
|
||||
*/
|
||||
private record InMemoryMultipartFile(String name, String originalFilename, byte[] bytes) implements MultipartFile {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalFilename() {
|
||||
return originalFilename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return bytes == null || bytes.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return bytes == null ? 0 : bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferTo(File dest) throws IOException {
|
||||
try (FileOutputStream outputStream = new FileOutputStream(dest)) {
|
||||
outputStream.write(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for their own commercial purposes.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this license agreement and terms of use,
|
||||
* and you expressly agree to be bound by the terms of use of this software.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsClaimRecordAddDTO;
|
||||
import org.springblade.hcs.dto.HcsCollectionFlowVO;
|
||||
import org.springblade.hcs.dto.HcsFormSaveVO;
|
||||
import org.springblade.hcs.dto.HcsIdVO;
|
||||
import org.springblade.hcs.dto.HcsMechanismsDTO;
|
||||
import org.springblade.hcs.dto.HcsPageQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsPageVO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordFormDataDTO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordPropsDTO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordSaveDTO;
|
||||
import org.springblade.hcs.dto.HcsRefVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsUserRefVO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementMapper;
|
||||
import org.springblade.transport.mapper.KingdeeReceiptFlowMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.pojo.entity.KingdeeReceiptFlow;
|
||||
import org.springblade.transport.pojo.entity.ReceiptClaim;
|
||||
import org.springblade.transport.pojo.entity.ReceiptClaimSettlement;
|
||||
import org.springblade.transport.service.IContractExeStageService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* HCS 收款认领链推送编排支撑组件(C103-9 更新履约计划 + C107-1 认领记录 + C108-1 履约流水)
|
||||
* <p>
|
||||
* 触发点:金蝶认领推送(A301-3)成功之后(手动按钮与定时任务共用钩子)。
|
||||
* 开关复用既有配置(不单独新增):exe-stage.sync-enabled 门禁 C103-9/C107-1(与合同链履约计划同步同开关),
|
||||
* perf-record.sync-enabled 门禁 C108-1(与付款链履约记录同步同开关)。
|
||||
* 编排顺序:先 C103-9(认领金额累加到收款履约计划行并重推,C107-1/C108-1 引用其返回的阶段ID),
|
||||
* 再按「认领主单 × 合同」推 C107-1(合规流水ID 经 C104-2 按明细流水号定位),最后按合同推 C108-1 履约流水。
|
||||
* 合同未推送合规(hcs_fd_id 空)时仅累加计划行金额,随合同链路首推。
|
||||
* 推送失败仅记日志不回滚金蝶推送结果(单合同失败不影响其余合同)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsClaimSyncSupport {
|
||||
|
||||
/**
|
||||
* 认领完成状态(与 ReceiptFlowServiceImpl.CLAIMED 同值)
|
||||
*/
|
||||
private static final String CLAIMED = "claimed";
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final KingdeeReceiptFlowMapper receiptFlowMapper;
|
||||
private final ReceiptClaimMapper claimMapper;
|
||||
private final ReceiptClaimSettlementMapper claimSettlementMapper;
|
||||
private final FormalSettlementMapper formalSettlementMapper;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final IContractExeStageService contractExeStageService;
|
||||
private final HcsExeStageSyncSupport hcsExeStageSyncSupport;
|
||||
|
||||
/**
|
||||
* 金蝶认领推送成功后推送合同合规(C103-9 → C107-1 → C108-1)
|
||||
* <p>
|
||||
* 复用既有开关:exe-stage.sync-enabled(履约计划同步)门禁 C103-9/C107-1,
|
||||
* perf-record.sync-enabled(履约记录同步)门禁 C108-1;开关关闭或推送异常均静默返回,
|
||||
* 不影响金蝶认领推送主流程与事务。
|
||||
*
|
||||
* @param flowId 收款流水ID
|
||||
*/
|
||||
public void pushClaimToHcs(Long flowId) {
|
||||
if (!hcsProperties.getExeStage().isSyncEnabled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
doPushClaimToHcs(flowId);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS收款认领链推送异常 flowId:{}", flowId, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送主体:归集有效认领分摊(按合同分组)→ 逐合同推送(单合同失败不影响其余合同)
|
||||
*/
|
||||
private void doPushClaimToHcs(Long flowId) {
|
||||
KingdeeReceiptFlow flow = receiptFlowMapper.selectById(flowId);
|
||||
if (flow == null) {
|
||||
return;
|
||||
}
|
||||
// 未作废认领主单(作废认领不同步合规)
|
||||
List<ReceiptClaim> claims = claimMapper.selectList(Wrappers.<ReceiptClaim>lambdaQuery()
|
||||
.eq(ReceiptClaim::getReceiptFlowId, flowId)
|
||||
.eq(ReceiptClaim::getClaimStatus, CLAIMED));
|
||||
if (claims.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 有效分摊行(作废时已置 status=0)
|
||||
List<ReceiptClaimSettlement> allocations = claimSettlementMapper.selectList(
|
||||
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
|
||||
.eq(ReceiptClaimSettlement::getReceiptFlowId, flowId)
|
||||
.eq(ReceiptClaimSettlement::getStatus, 1)
|
||||
.eq(ReceiptClaimSettlement::getIsDeleted, 0));
|
||||
if (allocations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 结算单批量定位合同(一次 IN 查询,避免逐行 N+1)
|
||||
Map<Long, FormalSettlement> settlements = formalSettlementMapper.selectBatchIds(
|
||||
allocations.stream().map(ReceiptClaimSettlement::getFormalSettlementId)
|
||||
.filter(Objects::nonNull).distinct().toList()).stream()
|
||||
.collect(Collectors.toMap(FormalSettlement::getId, item -> item, (first, second) -> first));
|
||||
// 合同id → 认领主单id → 分摊金额
|
||||
Map<Long, Map<Long, BigDecimal>> amountByContractByClaim = new LinkedHashMap<>();
|
||||
for (ReceiptClaimSettlement allocation : allocations) {
|
||||
FormalSettlement settlement = settlements.get(allocation.getFormalSettlementId());
|
||||
if (settlement == null || settlement.getContractId() == null
|
||||
|| allocation.getReceiptClaimId() == null || allocation.getAllocatedReceiptAmount() == null) {
|
||||
continue;
|
||||
}
|
||||
amountByContractByClaim
|
||||
.computeIfAbsent(settlement.getContractId(), key -> new LinkedHashMap<>())
|
||||
.merge(allocation.getReceiptClaimId(), allocation.getAllocatedReceiptAmount(), BigDecimal::add);
|
||||
}
|
||||
if (amountByContractByClaim.isEmpty()) {
|
||||
log.info("HCS收款认领链推送跳过:分摊行未关联合同 flowId={}", flowId);
|
||||
return;
|
||||
}
|
||||
// 合规侧收款流水ID(C107-1 必填引用,一次定位全部合同共用;定位失败仅跳过 C107-1,不阻塞履约计划更新)
|
||||
String hcsCollectionFlowId = null;
|
||||
try {
|
||||
hcsCollectionFlowId = locateHcsCollectionFlowId(flow);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS收款流水定位失败 flowId:{},跳过C107-1认领记录推送", flowId, exception);
|
||||
}
|
||||
for (Map.Entry<Long, Map<Long, BigDecimal>> contractEntry : amountByContractByClaim.entrySet()) {
|
||||
try {
|
||||
pushOneContract(flow, claims, contractEntry.getKey(), contractEntry.getValue(), hcsCollectionFlowId);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS收款认领链推送失败 flowId:{},contractId:{}", flowId, contractEntry.getKey(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送单个合同:① C103-9 更新履约计划(认领金额累加重推)→ ② C107-1 认领记录(按认领主单分组)→ ③ C108-1 履约流水
|
||||
*/
|
||||
private void pushOneContract(KingdeeReceiptFlow flow, List<ReceiptClaim> claims, Long contractId,
|
||||
Map<Long, BigDecimal> amountByClaim, String hcsCollectionFlowId) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(contractId);
|
||||
if (contractManage == null) {
|
||||
log.warn("HCS收款认领链推送跳过:合同不存在 contractId={}", contractId);
|
||||
return;
|
||||
}
|
||||
BigDecimal claimedTotal = amountByClaim.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
// ① C103-9 更新合同履约计划:认领金额累加到收款计划行;已同步行置 update 凭阶段ID 重推
|
||||
contractExeStageService.accumulateClaimedAmountOnPush(contractId, claimedTotal);
|
||||
HcsContractExeStage stageRow = contractExeStageService.findCollectionStageRow(contractId, HcsSyncConstant.FUND_NATURE_PROGRESS);
|
||||
if (StringUtil.isNotBlank(contractManage.getHcsFdId())) {
|
||||
// 行级重推(无待同步行返回提示,不视为失败);合同未推送合规时随合同链路首推
|
||||
hcsExeStageSyncSupport.syncExeStages(contractManage);
|
||||
}
|
||||
// ② C107-1 认领记录:按「认领主单 × 合同」推送,金额为该认领主单分摊到本合同的金额
|
||||
if (StringUtil.isBlank(hcsCollectionFlowId)) {
|
||||
log.warn("HCS认领记录推送跳过:未定位到合规收款流水 flowId={},detailSerialNo={}", flow.getId(), flow.getDetailSerialNo());
|
||||
} else {
|
||||
Map<Long, ReceiptClaim> claimsById = claims.stream()
|
||||
.collect(Collectors.toMap(ReceiptClaim::getId, item -> item, (first, second) -> first));
|
||||
for (Map.Entry<Long, BigDecimal> claimEntry : amountByClaim.entrySet()) {
|
||||
pushClaimRecord(flow, claimsById.get(claimEntry.getKey()), contractManage, stageRow, claimEntry.getValue(), hcsCollectionFlowId);
|
||||
}
|
||||
}
|
||||
// ③ C108-1 履约流水:按合同一条(开关 perf-record.sync-enabled,与付款链履约记录同步同开关),
|
||||
// 须已推送合规且存在已同步收款计划行(fd_plan_id 必填)
|
||||
if (!hcsProperties.getPerfRecord().isSyncEnabled()) {
|
||||
log.info("HCS履约流水推送跳过:履约记录同步开关未开启 contractNo={}", contractManage.getContractNo());
|
||||
return;
|
||||
}
|
||||
if (StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
log.info("HCS履约流水推送跳过:合同尚未推送合规 contractNo={}", contractManage.getContractNo());
|
||||
return;
|
||||
}
|
||||
if (stageRow == null || StringUtil.isBlank(stageRow.getHcsStageFdId())) {
|
||||
log.info("HCS履约流水推送跳过:无已同步收款履约计划行 contractNo={}", contractManage.getContractNo());
|
||||
return;
|
||||
}
|
||||
pushPerfRecord(flow, contractManage, stageRow, claimedTotal, latestClaimDate(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* C107-1 认领记录推送(单认领主单 × 单合同)
|
||||
*/
|
||||
private void pushClaimRecord(KingdeeReceiptFlow flow, ReceiptClaim claim, ContractManage contractManage,
|
||||
HcsContractExeStage stageRow, BigDecimal claimAmount, String hcsCollectionFlowId) {
|
||||
if (claim == null || claimAmount == null || claimAmount.signum() <= 0) {
|
||||
return;
|
||||
}
|
||||
HcsClaimRecordAddDTO param = new HcsClaimRecordAddDTO();
|
||||
// 【必填】收款流水引用(合规侧流水ID,C104-2 按明细流水号定位)
|
||||
HcsRefVO collectionFlow = new HcsRefVO();
|
||||
collectionFlow.setFdId(hcsCollectionFlowId);
|
||||
param.setFdCollectionFlow(collectionFlow);
|
||||
// 认领合同引用(fdId 为合规合同ID)
|
||||
HcsRefVO contractRef = new HcsRefVO();
|
||||
contractRef.setFdId(contractManage.getHcsFdId());
|
||||
contractRef.setFdName(contractManage.getContractName());
|
||||
param.setFdContract(contractRef);
|
||||
// 【必填】认领合同编号(须在合规系统本地存在)
|
||||
param.setFdContractNumber(contractManage.getContractNo());
|
||||
// 对应履约项ID 与 认领款项类型(取合同收款计划行,无已同步行不传)
|
||||
if (stageRow != null) {
|
||||
param.setFdExePlanStageId(stageRow.getHcsStageFdId());
|
||||
param.setFdFundNature(stageRow.getFundNature());
|
||||
}
|
||||
// 认领人/部门:TMS 与合规用户不同源,仅传名称(§9-U2)
|
||||
param.setFdClaimer(HcsRefVO.ofName(claim.getClaimerName()));
|
||||
param.setFdClaimerName(claim.getClaimerName());
|
||||
param.setFdClaimerDept(HcsRefVO.ofName(claim.getClaimerDeptName()));
|
||||
param.setFdClaimerDeptName(claim.getClaimerDeptName());
|
||||
// 【必填】认领金额
|
||||
param.setFdClaimAmount(claimAmount);
|
||||
param.setFdClaimDate(toMillis(claim.getClaimDate()));
|
||||
param.setFdRemark(claim.getRemark());
|
||||
// 状态 0=已认领(仅推送未作废认领)
|
||||
param.setFdStatus(0);
|
||||
log.info("HCS认领记录推送开始 flowId:{},claimId:{},contractNo:{},认领金额 {}",
|
||||
flow.getId(), claim.getId(), contractManage.getContractNo(), claimAmount);
|
||||
HcsResult<HcsIdVO> result = hcsClient.claimRecordAdd(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS认领记录推送失败响应:{},claimId={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), claim.getId());
|
||||
throw new HcsException("C107-1 认领记录推送失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
// shortcut: C107-1 返回 fdId 未持久化(认领作废同步 C107-3 未实现,重推会重复);需作废联动时先落库
|
||||
log.info("HCS认领记录推送成功 flowId:{},claimId:{},contractNo:{},claimRecordFdId:{}",
|
||||
flow.getId(), claim.getId(), contractManage.getContractNo(),
|
||||
result.getData() == null ? "响应未返回" : result.getData().getFdId());
|
||||
}
|
||||
|
||||
/**
|
||||
* C108-1 履约流水推送(单合同一条:申请金额=实际金额=该合同累计认领金额)
|
||||
*/
|
||||
private void pushPerfRecord(KingdeeReceiptFlow flow, ContractManage contractManage,
|
||||
HcsContractExeStage stageRow, BigDecimal claimedTotal, LocalDate planDate) {
|
||||
// 表单模板ID(配置 perf-record.xform-id,环境值 §9-U9,未配置不传)
|
||||
String xformId = StringUtil.isBlank(hcsProperties.getPerfRecord().getXformId()) ? null : hcsProperties.getPerfRecord().getXformId();
|
||||
HcsPerfRecordPropsDTO props = new HcsPerfRecordPropsDTO();
|
||||
// 【必填】申请编号:收款通知单号,兜底明细流水号
|
||||
props.setFdApplyNo(firstNonBlank(flow.getReceiptNoticeNo(), flow.getDetailSerialNo()));
|
||||
// 申请金额 / 实际金额:该合同累计认领金额
|
||||
props.setFdApplyAmount(claimedTotal.toPlainString());
|
||||
props.setFdFactAmount(claimedTotal.toPlainString());
|
||||
// 【必填】履约日期:最近认领日期(毫秒时间戳)
|
||||
props.setFdPlanDate(toMillis(planDate));
|
||||
// 履约类型:收款
|
||||
props.setFdPalnType(HcsSyncConstant.PERFORM_TYPE_COLLECTION);
|
||||
// 履约性质:收款计划行款项性质
|
||||
props.setFdProperty(stageRow.getFundNature());
|
||||
// 履约计划id / 来源履约计划项ID:收款计划行合规阶段ID(HCS 只认自己的阶段ID)
|
||||
props.setFdId(stageRow.getHcsStageFdId());
|
||||
props.setFdPlanId(stageRow.getHcsStageFdId());
|
||||
// 金蝶单据号:A301-3 推送返回的金蝶收款单号
|
||||
props.setFdJdDocNo(flow.getKingdeeBillNo());
|
||||
// 【必填】合同ID:HCS 合同ID
|
||||
props.setFdContractId(contractManage.getHcsFdId());
|
||||
// 业务ID:TMS 收款流水ID(溯源)
|
||||
props.setFdBusinessId(String.valueOf(flow.getId()));
|
||||
props.setFdCreateTime(System.currentTimeMillis());
|
||||
props.setFdXformId(xformId);
|
||||
HcsPerfRecordFormDataDTO formData = new HcsPerfRecordFormDataDTO();
|
||||
formData.setFdXformId(xformId);
|
||||
formData.setDynamicProps(props);
|
||||
// 机制数据(C108-1 必填):认领附件暂不转传,传空列表
|
||||
HcsMechanismsDTO mechanisms = new HcsMechanismsDTO();
|
||||
mechanisms.setAttachment(List.of());
|
||||
HcsPerfRecordSaveDTO param = new HcsPerfRecordSaveDTO();
|
||||
param.setCurUser(HcsUserRefVO.ofLoginName(hcsProperties.getOperatorLoginName()));
|
||||
param.setFormData(formData);
|
||||
param.setMechanisms(mechanisms);
|
||||
log.info("HCS履约流水新建开始 flowId:{},contractNo:{},认领金额 {}", flow.getId(), contractManage.getContractNo(), claimedTotal);
|
||||
HcsResult<HcsFormSaveVO> result = hcsClient.perfRecordAdd(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdId())) {
|
||||
log.error("HCS履约流水新建失败响应:{},contractNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo());
|
||||
throw new HcsException("C108-1 履约流水新建失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
// shortcut: 履约流水 fdId 未持久化(认领链无 C108-2 更新场景);需推进实际金额更新时先落库
|
||||
log.info("HCS履约流水新建成功 flowId:{},contractNo:{},perfFdId:{}",
|
||||
flow.getId(), contractManage.getContractNo(), result.getData().getFdId());
|
||||
}
|
||||
|
||||
/**
|
||||
* C104-2 定位合规侧收款流水ID:按明细流水号精确查询(合规流水由合规自 ERP 同步,TMS 镜像同源)
|
||||
*/
|
||||
private String locateHcsCollectionFlowId(KingdeeReceiptFlow flow) {
|
||||
if (StringUtil.isBlank(flow.getDetailSerialNo())) {
|
||||
log.warn("HCS收款流水定位跳过:流水缺少明细流水号 flowId={}", flow.getId());
|
||||
return null;
|
||||
}
|
||||
HcsPageQueryDTO param = new HcsPageQueryDTO();
|
||||
param.setColumns(List.of("fdId", "fdClaimNotice", "fdDetailSerialNumber", "fdCollectionAmount", "fdClaimStatus", "fdClaimedAmount"));
|
||||
param.setConditions(Map.of("fdDetailSerialNumber", flow.getDetailSerialNo()));
|
||||
param.setOffset(0);
|
||||
param.setPageSize(10);
|
||||
HcsResult<HcsPageVO<HcsCollectionFlowVO>> result = hcsClient.collectionFlowList(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
throw new HcsException("C104-2 收款流水查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getContent().stream()
|
||||
.filter(item -> flow.getDetailSerialNo().equals(item.getFdDetailSerialNumber()))
|
||||
.map(HcsCollectionFlowVO::getFdId)
|
||||
.filter(StringUtil::isNotBlank)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 最近认领日期(C108-1 履约日期)
|
||||
*/
|
||||
private LocalDate latestClaimDate(List<ReceiptClaim> claims) {
|
||||
return claims.stream()
|
||||
.map(ReceiptClaim::getClaimDate)
|
||||
.filter(Objects::nonNull)
|
||||
.max(LocalDate::compareTo)
|
||||
.orElse(LocalDate.now());
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
for (String value : values) {
|
||||
if (Func.isNotEmpty(value)) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Long toMillis(LocalDate localDate) {
|
||||
return localDate == null ? null : localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsContractDetailQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsContractDetailVO;
|
||||
import org.springblade.hcs.dto.HcsLegalResultDTO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.HcsContractCheckLogMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.HcsContractCheckLog;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HCS 合同特批同步支撑组件(合规特批结果接收:contractLegal/result 推送 + C103-4 定时查询兜底 + 人工确认)。
|
||||
* <p>
|
||||
* 业务闭环(2026-10-13 起):高风险/黑名单客商合同随 C103-1/C103-11 报文 fdSepcailContract=true 在合规侧走特批流程
|
||||
* (C103-8 独立发起接口合规未提供,已删除),TMS 挂起"特批审核"(processing)→
|
||||
* 集团审批结果经合规 contractLegal/result 接口推送 TMS(替代未建设的 D103),或定时 C103-4 查询兜底联动 →
|
||||
* approved 恢复流程,rejected 联动合同审批驳回。
|
||||
* <p>
|
||||
* 结果推送报文 {sourceSystem, requestId=合规合同ID, id, status=APPROVED/REJECTED, reason},原文留痕 blade_hcs_check_log。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsContractSpecSyncSupport {
|
||||
|
||||
/**
|
||||
* C103-4 合同审批状态:已审批(fdApprovalStatus,小写枚举)
|
||||
*/
|
||||
private static final String LEGAL_STATUS_APPROVED = "approved";
|
||||
|
||||
/**
|
||||
* C103-4 合同审批状态:审批作废(特批驳回映射待联调确认,暂按作废联动驳回)
|
||||
*/
|
||||
private static final String LEGAL_STATUS_INVALID = "approvalinvalid";
|
||||
|
||||
/**
|
||||
* 结果推送原文留痕最大长度(blade_hcs_check_log.msg 为 varchar(512))
|
||||
*/
|
||||
private static final int CALLBACK_MSG_MAX_LENGTH = 500;
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final HcsContractCheckLogMapper hcsContractCheckLogMapper;
|
||||
private final HcsProperties hcsProperties;
|
||||
|
||||
/**
|
||||
* 特批结果定时查询兜底(合规推送 contractLegal/result 未达/未配置时的补偿,2026-10-13):
|
||||
* 扫描特批中(processing)的合同,按 C103-4 合同详情 fdApprovalStatus 联动——
|
||||
* approved 恢复"审批通过"、approvalinvalid 联动审批驳回(特批驳回的合规侧状态待联调确认,暂按作废映射)、其余等待中跳过;
|
||||
* 与结果推送互不冲突(推送先到则状态已非 processing,查询自然跳过)
|
||||
*
|
||||
* @return 结果汇总(供任务日志)
|
||||
*/
|
||||
public String syncProcessingSpecResults() {
|
||||
List<ContractManage> contracts = contractManageMapper.selectList(Wrappers.<ContractManage>lambdaQuery()
|
||||
.eq(ContractManage::getHcsSpecStatus, HcsSyncConstant.SPEC_STATUS_PROCESSING)
|
||||
.isNotNull(ContractManage::getHcsFdId)
|
||||
.last("limit 50"));
|
||||
if (contracts.isEmpty()) {
|
||||
return "特批结果查询:无特批中合同";
|
||||
}
|
||||
int approvedCount = 0;
|
||||
int rejectedCount = 0;
|
||||
for (ContractManage contractManage : contracts) {
|
||||
try {
|
||||
HcsContractDetailQueryDTO query = new HcsContractDetailQueryDTO();
|
||||
// 【定位】合规合同ID:C103-4 fdId 与 fdNumber 二选一
|
||||
query.setFdId(contractManage.getHcsFdId());
|
||||
HcsResult<HcsContractDetailVO> result = hcsClient.contractDetail(query);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
log.warn("HCS特批结果查询失败响应:contractNo={},{}", contractManage.getContractNo(),
|
||||
result == null ? "无响应" : result.getMsg());
|
||||
continue;
|
||||
}
|
||||
String approvalStatus = result.getData().getFdApprovalStatus();
|
||||
if (LEGAL_STATUS_APPROVED.equalsIgnoreCase(approvalStatus)) {
|
||||
syncContractOnSpecResult(contractManage, true);
|
||||
markSpec(contractManage, HcsSyncConstant.SPEC_STATUS_APPROVED, "特批结果查询联动:fdApprovalStatus=approved");
|
||||
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_PASS, "特批结果查询联动:fdApprovalStatus=approved", null);
|
||||
approvedCount++;
|
||||
} else if (LEGAL_STATUS_INVALID.equalsIgnoreCase(approvalStatus)) {
|
||||
syncContractOnSpecResult(contractManage, false);
|
||||
markSpec(contractManage, HcsSyncConstant.SPEC_STATUS_REJECTED, "特批结果查询联动:fdApprovalStatus=approvalinvalid");
|
||||
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_FAIL, "特批结果查询联动:fdApprovalStatus=approvalinvalid", null);
|
||||
rejectedCount++;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
// 单条失败不影响其余,下轮扫描自动重试
|
||||
log.warn("HCS特批结果查询单条失败(下轮重试):contractNo={},{}", contractManage.getContractNo(), exception.getMessage());
|
||||
}
|
||||
}
|
||||
return "特批结果查询:扫描 " + contracts.size() + " 单,联动通过 " + approvedCount + ",联动驳回 " + rejectedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批结果人工确认(§6-U1 过渡方案:页面确认与合规推送等效)
|
||||
*
|
||||
* @param id 合同ID
|
||||
* @param result approved/rejected
|
||||
* @return 结果描述
|
||||
*/
|
||||
public String confirmSpecResult(Long id, String result) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(id);
|
||||
if (contractManage == null) {
|
||||
throw new ServiceException("合同单不存在:" + id);
|
||||
}
|
||||
if (!Arrays.asList(HcsSyncConstant.SPEC_STATUS_APPROVED, HcsSyncConstant.SPEC_STATUS_REJECTED).contains(result)) {
|
||||
throw new ServiceException("特批结果仅支持 approved/rejected");
|
||||
}
|
||||
if (!HcsSyncConstant.SPEC_STATUS_PROCESSING.equals(contractManage.getHcsSpecStatus())) {
|
||||
throw new ServiceException("仅特批中的合同允许确认结果(当前状态:" + contractManage.getHcsSpecStatus() + ")");
|
||||
}
|
||||
// 特批结果联动合同审批状态(通过恢复展示,驳回联动审批驳回),随 markSpec 一并落库
|
||||
syncContractOnSpecResult(contractManage, HcsSyncConstant.SPEC_STATUS_APPROVED.equals(result));
|
||||
markSpec(contractManage, result, "特批结果人工确认:" + result);
|
||||
writeCheckLog(contractManage, HcsSyncConstant.SPEC_STATUS_APPROVED.equals(result)
|
||||
? HcsSyncConstant.CHECK_RESULT_PASS : HcsSyncConstant.CHECK_RESULT_FAIL, "特批结果人工确认:" + result, null);
|
||||
log.info("HCS合同特批结果人工确认:contractNo={},specFdId={},结果={}", contractManage.getContractNo(), contractManage.getHcsSpecFdId(), result);
|
||||
return "特批结果已确认:" + result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同特批结果接收处理(合规 contractLegal/result 推送,2026-10-23 按联调实际报文修正):
|
||||
* 报文 {sourceSystem, requestId=合规合同ID, id, status=APPROVED/REJECTED, reason};
|
||||
* sourceSystem 须与 third-party.hcs.src-system 一致(不符抛"来源系统标识错误"异常);
|
||||
* requestId 即合规合同ID(与 id 同值),按 blade_contract_manage.hcs_fd_id 定位合同;
|
||||
* approved 联动恢复"审批通过",rejected 联动合同审批驳回走重新提交闭环(reason 随原文留痕)
|
||||
*
|
||||
* @param param 合规推送报文
|
||||
* @return 处理结果描述
|
||||
*/
|
||||
public String handleLegalResult(HcsLegalResultDTO param) {
|
||||
// 来源系统校验:仅接受合规方约定的来源标识,防伪造回传
|
||||
if (!StrUtil.equalsIgnoreCase(param == null ? null : param.getSourceSystem(), hcsProperties.getSrcSystem())) {
|
||||
log.warn("合同特批结果推送来源系统标识错误:sourceSystem={}", param == null ? null : param.getSourceSystem());
|
||||
throw new ServiceException("来源系统标识错误");
|
||||
}
|
||||
String raw = JSONUtil.toJsonStr(param);
|
||||
String requestId = param.getRequestId();
|
||||
ContractManage contractManage = locateContractByFdId(requestId);
|
||||
if (contractManage == null) {
|
||||
log.warn("合同特批结果推送未定位到合同:requestId={},报文已留痕", requestId);
|
||||
writeCallbackLog(null, HcsSyncConstant.CHECK_RESULT_SKIP, "合同特批结果推送未定位到合同:" + raw);
|
||||
return "已留痕:未定位到合同(requestId=" + requestId + ")";
|
||||
}
|
||||
String normalizedStatus = param.getStatus() == null ? "" : param.getStatus().trim();
|
||||
if (!HcsSyncConstant.LEGAL_RESULT_APPROVED.equalsIgnoreCase(normalizedStatus)
|
||||
&& !HcsSyncConstant.LEGAL_RESULT_REJECTED.equalsIgnoreCase(normalizedStatus)) {
|
||||
log.warn("合同特批结果推送状态未识别:contractNo={},status={},报文已留痕", contractManage.getContractNo(), param.getStatus());
|
||||
writeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP, "合同特批结果推送状态未识别:" + raw);
|
||||
return "已留痕:状态字段未识别(contractNo=" + contractManage.getContractNo() + ")";
|
||||
}
|
||||
boolean approved = HcsSyncConstant.LEGAL_RESULT_APPROVED.equalsIgnoreCase(normalizedStatus);
|
||||
// 特批结果联动合同审批状态(通过恢复展示,驳回联动审批驳回),随 markSpec 一并落库
|
||||
syncContractOnSpecResult(contractManage, approved);
|
||||
markSpec(contractManage, approved ? HcsSyncConstant.SPEC_STATUS_APPROVED : HcsSyncConstant.SPEC_STATUS_REJECTED,
|
||||
"合同特批结果推送:" + normalizedStatus + (StringUtil.isBlank(param.getReason()) ? "" : ",reason=" + param.getReason()));
|
||||
writeCallbackLog(contractManage, approved ? HcsSyncConstant.CHECK_RESULT_PASS : HcsSyncConstant.CHECK_RESULT_FAIL,
|
||||
"合同特批结果推送:" + raw);
|
||||
log.info("合同特批结果推送处理完成:contractNo={},结果={},reason={}", contractManage.getContractNo(), normalizedStatus, param.getReason());
|
||||
return "特批结果已处理:" + (approved ? "approved" : "rejected");
|
||||
}
|
||||
|
||||
/**
|
||||
* 按合规合同ID定位合同(合规推送 requestId/id,即 blade_contract_manage.hcs_fd_id)
|
||||
*/
|
||||
public ContractManage locateContractByFdId(String requestId) {
|
||||
if (StringUtil.isBlank(requestId)) {
|
||||
return null;
|
||||
}
|
||||
return contractManageMapper.selectOne(Wrappers.<ContractManage>lambdaQuery()
|
||||
.eq(ContractManage::getHcsFdId, requestId.trim())
|
||||
.last("limit 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批结果联动合同审批状态(handleLegalResult 与 confirmSpecResult 共用,仅改字段,随 markSpec 落库):
|
||||
* approved 恢复"审批通过"展示(C103-1 已起草完成,hcs_sync_status 保持 synced,无需再推送);
|
||||
* rejected 联动合同审批驳回(approvalStatus=rejected),走重新编辑→提交→审批→特批闭环
|
||||
*/
|
||||
private void syncContractOnSpecResult(ContractManage contractManage, boolean approved) {
|
||||
if (approved) {
|
||||
contractManage.setCurrentNode("审批通过");
|
||||
} else {
|
||||
contractManage.setApprovalStatus("rejected");
|
||||
contractManage.setCurrentNode("已驳回");
|
||||
contractManage.setCurrentProcessor("待处理");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回写特批状态与动作时间
|
||||
*/
|
||||
private void markSpec(ContractManage contractManage, String status, String message) {
|
||||
contractManage.setHcsSpecStatus(status);
|
||||
contractManage.setHcsSpecTime(LocalDateTime.now());
|
||||
contractManage.setHcsSpecMsg(StringUtil.left(message, 500));
|
||||
contractManageMapper.updateById(contractManage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批留痕(blade_hcs_check_log,biz_type=contract,check_type=spec,异常不影响主流程)
|
||||
*/
|
||||
private void writeCheckLog(ContractManage contractManage, String result, String msg, String traceId) {
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
checkLog.setTenantId(contractManage.getTenantId());
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT);
|
||||
checkLog.setBizId(contractManage.getId());
|
||||
checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_SPEC);
|
||||
checkLog.setResult(result);
|
||||
checkLog.setMsg(StringUtil.left(msg, CALLBACK_MSG_MAX_LENGTH));
|
||||
checkLog.setTraceId(traceId);
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS合同特批留痕写入失败:contractNo={}", contractManage.getContractNo(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结果推送留痕(合同未定位到时 bizId 为空,原文留痕便于联调解析)
|
||||
*/
|
||||
private void writeCallbackLog(ContractManage contractManage, String result, String msg) {
|
||||
if (contractManage == null) {
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT);
|
||||
checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_SPEC);
|
||||
checkLog.setResult(result);
|
||||
checkLog.setMsg(StringUtil.left(msg, CALLBACK_MSG_MAX_LENGTH));
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("合同特批结果推送留痕写入失败", exception);
|
||||
}
|
||||
return;
|
||||
}
|
||||
writeCheckLog(contractManage, result, msg, null);
|
||||
}
|
||||
}
|
||||
+787
@@ -0,0 +1,787 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.constant.HcsConstant;
|
||||
import org.springblade.hcs.dto.HcsAttachmentRefDTO;
|
||||
import org.springblade.hcs.dto.HcsContractDraftDTO;
|
||||
import org.springblade.hcs.dto.HcsContractDraftVO;
|
||||
import org.springblade.hcs.dto.HcsContractFieldJsonDTO;
|
||||
import org.springblade.hcs.dto.HcsContractLegalResultDTO;
|
||||
import org.springblade.hcs.dto.HcsContractVO;
|
||||
import org.springblade.hcs.dto.HcsPageQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsPageVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsRiskQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsRiskVO;
|
||||
import org.springblade.hcs.dto.HcsSignerDTO;
|
||||
import org.springblade.hcs.dto.HcsXformFieldDTO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsContractFieldConstant;
|
||||
import org.springblade.transport.constant.HcsFieldConstant;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.CustomerArchiveMapper;
|
||||
import org.springblade.transport.mapper.HcsContractCheckLogMapper;
|
||||
import org.springblade.transport.mapper.ProjectApplyMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.CustomerArchive;
|
||||
import org.springblade.transport.pojo.entity.HcsContractCheckLog;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 合同同步支撑组件(C103-1 发起 / C103-11 更新,设计文档 §5.2)
|
||||
* <p>
|
||||
* 扫描任务按序执行:⓪ 特批挂起门禁 → ① 客商风险校验(可配开关,仅留痕,高风险不再阻断)→
|
||||
* ② 附件转传(挂载点为与 fdFieldJson 同级的顶层 attachment,随 C103-1 挂载)→ ③ C103-3 判重(§9-U4 查询能力联调确认)→
|
||||
* ④ C103-1 起草 / 变更单 C103-11 更新(§9-U5 定位方式待联调)→ ⑤ 履约计划 C103-9 行级同步({@link HcsExeStageSyncSupport},
|
||||
* 仅依赖 hcs_fd_id,先于特批挂起执行,失败补偿不被特批门禁阻塞)→ ⑥ 高风险/黑名单挂起"特批审核"
|
||||
* (外部客商管控:报文随 fdSepcailContract=true 在合规侧走特批,{@link HcsContractSpecSyncSupport} 接收结果)。
|
||||
* 结果留痕:风险校验写 blade_hcs_check_log 与合同 hcs_risk_* 字段;推送结果回填 hcs_fd_id/hcs_sync_status。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsContractSyncSupport {
|
||||
|
||||
/**
|
||||
* 合同金额等 xformFields 值一律字符串传值;时间字段 v4 要求 yyyy-MM-dd(fdType=timestamp)
|
||||
*/
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* C101-1 黑名单状态取值(示例 no 表示非黑名单,§9-U3 字典值联调确认)
|
||||
*/
|
||||
private static final String BLACK_STATUS_YES = "yes";
|
||||
|
||||
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final CustomerArchiveMapper customerArchiveMapper;
|
||||
private final ProjectApplyMapper projectApplyMapper;
|
||||
private final HcsContractCheckLogMapper hcsContractCheckLogMapper;
|
||||
private final HcsAttachmentSupport hcsAttachmentSupport;
|
||||
private final HcsExeStageSyncSupport hcsExeStageSyncSupport;
|
||||
private final HcsContractSpecSyncSupport hcsContractSpecSyncSupport;
|
||||
|
||||
/**
|
||||
* 合同金蝶同步结果接收处理(合规 contractKingdee/result 推送,D903,投送人/钢厂/结算中心合同共用):
|
||||
* 合规侧收到金蝶最终同步结果后通知 TMS 更新同步状态,本接口不处理法务审批结论(特批联动走 contractLegal/result + C103-4 兜底);
|
||||
* contractType(deliverer/gc/sett_center)非必填仅随原文留痕;sourceSystem 非必填,传值时须为 ;
|
||||
* contractCode=TMS 合同编号定位(requestId 仅 X-ERP-Request-Id 回显不可定位);
|
||||
* success=true 回填金蝶合同ID(kingdeeId),success=false 记录失败原因;均追加变更记录并原文留痕 blade_hcs_check_log
|
||||
*
|
||||
* @param param 合规推送报文
|
||||
* @return 处理结果描述
|
||||
*/
|
||||
public String handleKingdeeResult(HcsContractLegalResultDTO param) {
|
||||
// sourceSystem 非必填(D903),传值时仅接受合规方约定的 ,防伪造回传
|
||||
String sourceSystem = param == null ? null : param.getSourceSystem();
|
||||
if (!StrUtil.equalsIgnoreCase(param == null ? null : param.getSourceSystem(), hcsProperties.getSrcSystem())) {
|
||||
log.warn("合同金蝶同步结果推送来源系统标识错误:sourceSystem={}", sourceSystem);
|
||||
throw new ServiceException("来源系统标识错误");
|
||||
}
|
||||
String raw = JSONUtil.toJsonStr(param);
|
||||
String requestId = param.getRequestId();
|
||||
ContractManage contractManage = hcsContractSpecSyncSupport.locateContractByFdId(requestId);
|
||||
if (contractManage == null) {
|
||||
log.warn("合同金蝶同步结果推送未定位到合同:contractCode={},报文已留痕", param.getContractCode());
|
||||
writeKingdeeCallbackLog(null, HcsSyncConstant.CHECK_RESULT_SKIP, "合同金蝶同步结果推送未定位到合同:" + raw);
|
||||
return "已留痕:未定位到合同(contractCode=" + param.getContractCode() + ")";
|
||||
}
|
||||
if (param.getSuccess() == null) {
|
||||
log.warn("合同金蝶同步结果推送 success 字段缺失:contractNo={},报文已留痕", contractManage.getContractNo());
|
||||
writeKingdeeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP, "合同金蝶同步结果推送 success 字段缺失:" + raw);
|
||||
return "已留痕:success 字段缺失(contractNo=" + contractManage.getContractNo() + ")";
|
||||
}
|
||||
if (param.getSuccess()) {
|
||||
// 成功:回填金蝶合同ID,同步结果信息随 hcs_sync_msg 留痕,不联动审批/特批状态
|
||||
contractManage.setKingdeeId(param.getKingdeeId());
|
||||
contractManage.setHcsSyncMsg(HcsSyncConstant.truncateMsg("合同金蝶同步结果回调:金蝶同步成功,kingdeeId=" + param.getKingdeeId()));
|
||||
appendKingdeeResultRecord(contractManage, "金蝶同步成功:金蝶合同ID=" + param.getKingdeeId());
|
||||
contractManageMapper.updateById(contractManage);
|
||||
writeKingdeeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_PASS, "合同金蝶同步结果推送:" + raw);
|
||||
log.info("合同金蝶同步结果推送处理完成:contractNo={},结果=success,kingdeeId={}", contractManage.getContractNo(), param.getKingdeeId());
|
||||
return "金蝶同步结果已处理:success";
|
||||
}
|
||||
// 失败:记录失败原因(金蝶同步在合规侧完成,TMS 不重推,失败重试由合规侧负责)
|
||||
contractManage.setHcsSyncMsg(HcsSyncConstant.truncateMsg("合同金蝶同步结果回调:金蝶同步失败:" + param.getReason()));
|
||||
appendKingdeeResultRecord(contractManage, "金蝶同步失败:" + param.getReason());
|
||||
contractManageMapper.updateById(contractManage);
|
||||
writeKingdeeCallbackLog(contractManage, HcsSyncConstant.CHECK_RESULT_FAIL, "合同金蝶同步结果推送:" + raw);
|
||||
log.info("合同金蝶同步结果推送处理完成:contractNo={},结果=fail,reason={}", contractManage.getContractNo(), param.getReason());
|
||||
return "金蝶同步结果已处理:fail";
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ERP 合同编号定位合同(合规推送 contractCode,合同编号唯一)
|
||||
*/
|
||||
private ContractManage locateContractByNo(String contractCode) {
|
||||
if (StringUtil.isBlank(contractCode)) {
|
||||
return null;
|
||||
}
|
||||
return contractManageMapper.selectOne(Wrappers.<ContractManage>lambdaQuery()
|
||||
.eq(ContractManage::getContractNo, contractCode.trim())
|
||||
.last("limit 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同金蝶同步结果推送留痕(blade_hcs_check_log,biz_type=contract,合同未定位到时 bizId 为空,异常不影响主流程)
|
||||
*/
|
||||
private void writeKingdeeCallbackLog(ContractManage contractManage, String result, String msg) {
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
if (contractManage != null) {
|
||||
checkLog.setTenantId(contractManage.getTenantId());
|
||||
checkLog.setBizId(contractManage.getId());
|
||||
}
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT);
|
||||
checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_KINGDEE);
|
||||
checkLog.setResult(result);
|
||||
checkLog.setMsg(StringUtil.left(msg, HcsSyncConstant.SYNC_MSG_MAX_LENGTH));
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("合同金蝶同步结果推送留痕写入失败:contractNo={}", contractManage == null ? null : contractManage.getContractNo(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶同步结果回填变更记录(changeRecordJson 追加一条;系统回调无登录用户,操作人记来源标识)
|
||||
*/
|
||||
private void appendKingdeeResultRecord(ContractManage contractManage, String changeContent) {
|
||||
List<Object> records = StringUtil.isBlank(contractManage.getChangeRecordJson())
|
||||
? new ArrayList<>() : new ArrayList<>(JSONUtil.toList(contractManage.getChangeRecordJson(), Object.class));
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("changeDate", LocalDate.now().toString());
|
||||
record.put("handlerUserName", "合规系统回调");
|
||||
record.put("changeType", "金蝶同步结果");
|
||||
record.put("changeContent", changeContent);
|
||||
record.put("status", contractManage.getApprovalStatus());
|
||||
record.put("statusName", HcsSyncConstant.approvalStatusName(contractManage.getApprovalStatus()));
|
||||
records.add(record);
|
||||
contractManage.setChangeRecordJson(JSONUtil.toJsonStr(records));
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送合同到 HCS:特批挂起门禁 → 风险校验(仅留痕,高风险不再阻断)→ 附件转传 → 判重 → 起草/更新 → 回填合规 fdId 置 synced
|
||||
* → 履约计划行级同步(C103-9,先于特批挂起)→ 高风险/黑名单客商合同挂起"特批审核"等待合规特批结果(报文随 fdSepcailContract=true,外部客商管控)
|
||||
*
|
||||
* @param contractManage 合同单(approvalStatus 须为 approved/change_approved)
|
||||
* @return 推送结果描述(供任务汇总与 hcs_sync_msg 落库)
|
||||
*/
|
||||
public String pushContract(ContractManage contractManage) {
|
||||
log.info("HCS合同推送开始:contractNo={},approvalStatus={},首推={},hcsSpecStatus={}",
|
||||
contractManage.getContractNo(), contractManage.getApprovalStatus(),
|
||||
StringUtil.isBlank(contractManage.getHcsFdId()), contractManage.getHcsSpecStatus());
|
||||
// ⓪ 特批挂起门禁:特批中(processing)的合同跳过推送,等待合规推送/定时查询特批结果后恢复;
|
||||
// need(存量待特批/历史卡单)不拦截——重推时随起草报文带 fdSepcailContract=true 自愈转特批
|
||||
if (HcsSyncConstant.SPEC_STATUS_PROCESSING.equals(contractManage.getHcsSpecStatus())) {
|
||||
log.info("HCS合同推送跳过(特批挂起中):contractNo={},hcsSpecStatus={}", contractManage.getContractNo(), contractManage.getHcsSpecStatus());
|
||||
return "合同特批挂起中(processing),跳过推送";
|
||||
}
|
||||
// ① 客商风险校验(可配开关):留痕 hcs_risk_* 字段并返回高风险/黑名单标志,不再阻断推送(高风险合同起草后自动转特批)
|
||||
boolean risky = false;
|
||||
if (hcsProperties.getContract().isRequireRiskCheck()) {
|
||||
risky = checkRelativeRisk(contractManage);
|
||||
}
|
||||
// ② 附件转传(v4 已明确挂载点:C103-1 顶层 attachment,与 fdFieldJson 同级,转传取得合规附件引用后随发起报文挂载)
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = transferAttachmentsIfEnabled(contractManage);
|
||||
// ③④ 首推判重后起草(C103-1);已有合规ID(变更单)走更新(C103-11)
|
||||
String resultMessage;
|
||||
if (StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
String existFdId = findExistFdId(contractManage);
|
||||
if (StringUtil.isNotBlank(existFdId)) {
|
||||
markSynced(contractManage, existFdId, "判重命中,回填已有合规合同ID");
|
||||
resultMessage = "判重命中,回填已有合规合同ID";
|
||||
} else {
|
||||
String fdContractId = draftContract(contractManage, attachmentRefs, risky);
|
||||
// 先持久化合规合同ID(调用方必须保存),履约规则同步失败时下轮凭其走更新补偿
|
||||
resultMessage = "合同发起成功:" + contractManage.getContractNo();
|
||||
markSynced(contractManage, fdContractId, resultMessage);
|
||||
}
|
||||
} else {
|
||||
updateContract(contractManage, risky);
|
||||
resultMessage = "合同更新成功:" + contractManage.getContractNo();
|
||||
markSynced(contractManage, contractManage.getHcsFdId(), resultMessage);
|
||||
}
|
||||
// ⑤ 履约规则 C103-9 行级同步(随合同链路,§5.3;仅依赖 hcs_fd_id 与特批无关,先于特批挂起执行——
|
||||
// 失败抛出落 failed 时特批未挂起,修复后可直接重推补偿,不被特批门禁阻塞)
|
||||
if (hcsProperties.getExeStage().isSyncEnabled()) {
|
||||
resultMessage = resultMessage + ";" + hcsExeStageSyncSupport.syncExeStages(contractManage);
|
||||
}
|
||||
// ⑥ 高风险/黑名单客商(外部客商管控):起草/更新报文已随 fdSepcailContract=true 在合规侧发起特批流程,
|
||||
// 此处挂起"特批审核"等待结果(合规推送 contractLegal/result 或定时 C103-4 查询联动恢复/驳回)
|
||||
if (risky) {
|
||||
hangSpecReview(contractManage);
|
||||
}
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* C101-1 客商风险校验:甲乙双方各查一次,留痕 check_log 与合同 hcs_risk_* 字段;
|
||||
* 返回是否高风险/黑名单(风险等级=2 或任一方黑名单),不再阻断推送——
|
||||
* 推送成功后由自动特批链路挂起处理(外部客商管控,见 {@link #})
|
||||
*
|
||||
* @return true=高风险或黑名单客商(触发自动特批)
|
||||
*/
|
||||
private boolean checkRelativeRisk(ContractManage contractManage) {
|
||||
RiskResult riskPartyA = queryRisk(contractManage, "甲方", contractManage.getPartyA());
|
||||
RiskResult riskPartyB = queryRisk(contractManage, "乙方", contractManage.getPartyB());
|
||||
// 风险等级:甲乙双方取先查到的非空值(无法量化比较,留痕双方原始值,联调后细化)
|
||||
String riskLevel = StringUtil.isNotBlank(riskPartyA.riskLevel()) ? riskPartyA.riskLevel() : riskPartyB.riskLevel();
|
||||
// 黑名单状态:任一方黑名单即为是
|
||||
boolean blacklist = riskPartyA.blacklist() || riskPartyB.blacklist();
|
||||
contractManage.setHcsRiskLevel(riskLevel);
|
||||
contractManage.setHcsBlackStatus(blacklist ? BLACK_STATUS_YES : "no");
|
||||
contractManage.setHcsRiskTime(LocalDateTime.now());
|
||||
contractManageMapper.updateById(contractManage);
|
||||
boolean risky = HcsSyncConstant.RISK_LEVEL_HIGH.equals(riskLevel) || blacklist;
|
||||
log.info("HCS合同风险校验完成:contractNo={},甲方[{}={}/{}],乙方[{}={}/{}],高风险/黑名单={}",
|
||||
contractManage.getContractNo(), contractManage.getPartyA(), riskPartyA.blackStatus(), riskPartyA.riskLevel(),
|
||||
contractManage.getPartyB(), riskPartyB.blackStatus(), riskPartyB.riskLevel(), risky);
|
||||
return risky;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高风险/黑名单客商合同挂起"特批审核":合规侧特批流程已随 C103-1/C103-11 报文 fdSepcailContract=true 发起
|
||||
* (C103-8 独立发起接口合规未提供,2026-10-13 联调确认),TMS 挂起展示"特批审核"(currentNode),
|
||||
* 结果经合规推送(contractLegal/result)或定时 C103-4 查询联动后恢复或驳回;特批已通过(单次有效)不再重复挂起
|
||||
*/
|
||||
private void hangSpecReview(ContractManage contractManage) {
|
||||
// 终态守卫:合规回调(秒批/秒驳)先于挂起执行时,不得覆盖已落地的结果
|
||||
// Arrays.asList 允许 null 元素:List.of 的不可变列表 contains(null) 会抛 NPE,而首次推送时 hcsSpecStatus 为 null
|
||||
if (Arrays.asList(HcsSyncConstant.SPEC_STATUS_APPROVED, HcsSyncConstant.SPEC_STATUS_REJECTED).contains(contractManage.getHcsSpecStatus())) {
|
||||
log.info("HCS合同高风险/黑名单但特批已有终态(单次有效),跳过挂起:contractNo={},hcsSpecStatus={}",
|
||||
contractManage.getContractNo(), contractManage.getHcsSpecStatus());
|
||||
return;
|
||||
}
|
||||
contractManage.setHcsSpecStatus(HcsSyncConstant.SPEC_STATUS_PROCESSING);
|
||||
contractManage.setHcsSpecTime(LocalDateTime.now());
|
||||
contractManage.setHcsSpecMsg("高风险/黑名单客商,合同报文随 fdSepcailContract=true 走合规特批流程");
|
||||
// TMS 流程挂起为"特批审核",特批通过恢复展示、驳回联动合同审批驳回
|
||||
contractManage.setCurrentNode(HcsSyncConstant.SPEC_REVIEW_NODE);
|
||||
contractManageMapper.updateById(contractManage);
|
||||
log.info("HCS合同高风险/黑名单挂起特批审核:contractNo={},riskLevel={},blackStatus={},挂起节点={}",
|
||||
contractManage.getContractNo(), contractManage.getHcsRiskLevel(), contractManage.getHcsBlackStatus(),
|
||||
contractManage.getCurrentNode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 数组(照 ContractManage#parseJsonArray 惯例,异常返回空列表)
|
||||
*/
|
||||
private List<Map<?, ?>> parseJsonArray(String json) {
|
||||
if (StringUtil.isBlank(json)) {
|
||||
return List.of();
|
||||
}
|
||||
try {
|
||||
List<Map<?, ?>> result = new ArrayList<>();
|
||||
for (Object item : JsonUtil.parse(json, List.class)) {
|
||||
if (item instanceof Map<?, ?> map) {
|
||||
result.add(map);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (Exception exception) {
|
||||
log.warn("合同附件元数据解析失败,按无附件处理:{}", json);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单方客商风险:按名称反查客商档案(§9-U6 编码体系联调确认),反查不到记 skip 留痕不阻断;
|
||||
* 内部组织客商默认无风险跳过(与展示/提交/付款链路口径一致,不调 C101-1)
|
||||
*/
|
||||
private RiskResult queryRisk(ContractManage contractManage, String standing, String partyName) {
|
||||
CustomerArchive customerArchive = queryCustomerArchive(partyName);
|
||||
if (customerArchive == null) {
|
||||
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP,
|
||||
standing + "方[" + partyName + "]反查不到客商编号,跳过风险校验(§9-U6)", null);
|
||||
return new RiskResult(null, null, false);
|
||||
}
|
||||
// 内部组织类型客商默认无风险,不调 C101-1(与展示/提交/付款链路口径一致)
|
||||
if (HcsCreditCheckSupport.isInternalCustomer(customerArchive)) {
|
||||
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP,
|
||||
standing + "方[" + partyName + "]为内部组织客商,默认无风险跳过校验", null);
|
||||
return new RiskResult(null, null, false);
|
||||
}
|
||||
HcsRiskQueryDTO param = new HcsRiskQueryDTO();
|
||||
// 【必填】客商编号:按甲乙方名称反查 CustomerArchive 客商编码(§9-U6)
|
||||
param.setFdNumber(customerArchive.getCustomerCode());
|
||||
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS客商风险查询失败响应:{},contractNo={},standing={},fdNumber={}",
|
||||
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo(), standing, customerArchive.getCustomerCode());
|
||||
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsRiskVO risk = result.getData();
|
||||
String blackStatus = risk == null ? null : risk.getFdBlackStatus();
|
||||
String riskLevel = risk == null ? null : risk.getFdRiskLevel();
|
||||
boolean blacklist = BLACK_STATUS_YES.equalsIgnoreCase(blackStatus);
|
||||
writeCheckLog(contractManage, blacklist ? HcsSyncConstant.CHECK_RESULT_FAIL : HcsSyncConstant.CHECK_RESULT_PASS,
|
||||
standing + "方[" + partyName + "]黑名单=" + blackStatus + ",风险等级=" + riskLevel, result.getTraceId());
|
||||
return new RiskResult(blackStatus, riskLevel, blacklist);
|
||||
}
|
||||
|
||||
/**
|
||||
* C103-3 合同列表判重(conditions={"fdNumber": contractNo},§9-U4 查询能力联调确认)
|
||||
*/
|
||||
private String findExistFdId(ContractManage contractManage) {
|
||||
HcsPageQueryDTO query = new HcsPageQueryDTO();
|
||||
query.setColumns(List.of(HcsFieldConstant.CONTRACT_FD_ID, HcsFieldConstant.CONTRACT_FD_NUMBER));
|
||||
// 判重条件:合同编号精确匹配
|
||||
query.setConditions(Map.of(HcsFieldConstant.CONTRACT_FD_NUMBER, Objects.toString(contractManage.getContractNo(), "")));
|
||||
query.setOffset(0);
|
||||
query.setPageSize(10);
|
||||
HcsResult<HcsPageVO<HcsContractVO>> result = hcsClient.contractList(query);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS合同判重查询失败响应:{},contractNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo());
|
||||
throw new HcsException("C103-3 合同判重查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsPageVO<HcsContractVO> page = result.getData();
|
||||
if (page == null || page.getContent() == null || page.getContent().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.getContent().get(0).getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* C103-1 合同发起,返回合规合同 fdContractId;risky=true 时报文带 fdSepcailContract=true 走合规特批流程
|
||||
*/
|
||||
private String draftContract(ContractManage contractManage, List<HcsAttachmentRefDTO> attachmentRefs, boolean risky) {
|
||||
HcsContractDraftDTO param = buildDraftParam(contractManage, attachmentRefs, risky);
|
||||
log.info("HCS合同推送开始:{}(C103-1 发起,挂载附件 {} 个,特批流程={})", contractManage.getContractNo(), attachmentRefs.size(), risky);
|
||||
HcsResult<HcsContractDraftVO> result = hcsClient.contractDraft(param);
|
||||
return validateDraftResult(contractManage, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* C103-11 合同更新(入参同发起,v4 新增顶层 fdId 定位已有合同);risky=true 时报文带 fdSepcailContract=true
|
||||
*/
|
||||
private void updateContract(ContractManage contractManage, boolean risky) {
|
||||
HcsContractDraftDTO param = buildDraftParam(contractManage, null, risky);
|
||||
log.info("HCS合同推送开始:{}(C103-11 更新,附件不重复挂载待联调确认,特批流程={})", contractManage.getContractNo(), risky);
|
||||
HcsResult<Void> result = hcsClient.contractUpdate(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS合同更新失败响应:{},contractNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo());
|
||||
throw new HcsException("C103-11 合同更新失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 C103-1/C103-11 入参(字段标注照 §3.4 规范,映射表见设计文档 §5.2);
|
||||
* attachmentRefs 新建传合规附件引用列表(顶层 attachment,fdId+fdEntityKey,必填,无附件传空列表),更新传 null 不挂载
|
||||
*/
|
||||
private HcsContractDraftDTO buildDraftParam(ContractManage contractManage, List<HcsAttachmentRefDTO> attachmentRefs, boolean risky) {
|
||||
HcsContractDraftDTO param = new HcsContractDraftDTO();
|
||||
// 【必填】合同ID:C103-11 更新顶层必填(传 C103-1 返回的 fdContractId),C103-1 新建不传(v4 新增)
|
||||
param.setFdId(contractManage.getHcsFdId());
|
||||
// 【必填】合同模板ID:template-mapping[contractCategory](§9-U7 待合规方提供模板ID清单)
|
||||
param.setFdTemplateId(requireTemplateId(contractManage));
|
||||
// 【必填】外部系统标识:fd_sources,配置 thirdParty.hcs.src-system(D14)
|
||||
param.setFdOuterSystem(hcsProperties.getSrcSystem());
|
||||
// 【必填】操作人登录名:配置 thirdParty.hcs.operator-login-name(默认 srvadmin,§9-U2)
|
||||
param.setFdLoginName(hcsProperties.getOperatorLoginName());
|
||||
// 【必填】保存类型:submit(提交)
|
||||
param.setSaveType("submit");
|
||||
HcsContractFieldJsonDTO fieldJson = new HcsContractFieldJsonDTO();
|
||||
fieldJson.setXformFields(buildXformFields(contractManage, risky));
|
||||
// 相对方(签署方):甲方/乙方,编号按名称反查 CustomerArchive(§9-U6)
|
||||
fieldJson.setSigner(buildSigners(contractManage));
|
||||
// 【必填】附件数据:与 fdFieldJson 同级的顶层 attachment,C103-1 必填(无附件传空列表),fdEntityKey=特批 eopCdraftSpecial/用印 eopCdraftSign/其他 eopCdraftOther,
|
||||
// fdBindType=ADD;更新不挂载(传 null)
|
||||
if (attachmentRefs != null) {
|
||||
param.setAttachment(new ArrayList<>(attachmentRefs));
|
||||
for (HcsAttachmentRefDTO attachment : param.getAttachment()) {
|
||||
// 【必填】操作类型:ADD 新增
|
||||
attachment.setFdBindType("ADD");
|
||||
}
|
||||
}
|
||||
param.setFdFieldJson(fieldJson);
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* xformFields 模板字段(字段名取 {@link HcsContractFieldConstant},中文名取自《桂物合同集成接口逻辑梳理》§5.1 合同字段说明)
|
||||
*/
|
||||
private List<HcsXformFieldDTO> buildXformFields(ContractManage contractManage, boolean risky) {
|
||||
List<HcsXformFieldDTO> fields = new ArrayList<>();
|
||||
// 【必填】合同ID:更新时传合同发起返回的 fdContractId 的值(v4 合同字段说明)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_ID, "合同ID", contractManage.getHcsFdId()));
|
||||
// 【必填】合同编号:ContractManage.contractNo
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_NUMBER, "合同编号", contractManage.getContractNo()));
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_CONTRACT_NO, "合同编号", contractManage.getContractNo()));
|
||||
// 【必填】合同标题:ContractManage.contractName
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_SUBJECT, "合同标题", contractManage.getContractName()));
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_CONTRACT_NAME, "合同名称", contractManage.getContractName()));
|
||||
// 【必填】合同金额:contractAmount(v4 字段表 fdAmount=Double,数值传值;AMOUNT 模板字段无类型说明照示例字符串传值)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_AMOUNT, "合同金额", contractManage.getContractAmount()));
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_AMOUNT, "合同金额", toPlainString(contractManage.getContractAmount())));
|
||||
// 币种:TMS 结算币种经 currency-name-mapping 映射(默认 RMB→人民币,照金蝶惯例)
|
||||
// fields.add(field(HcsContractFieldConstant.FIELD_FD_CURRENCY_ID, "币种", mapCurrencyName(contractManage.getSettlementCurrency())));
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_CURRENCY, "币种", mapCurrencyName(contractManage.getSettlementCurrency())));
|
||||
// 合同结算类型:按合同类别固定规则——承运商合同=付款,客户合同=收款
|
||||
// 传中文字典名(2026-10-17 确认;此前传枚举键 collection+fdIsSystem 合规侧未命中字典落为"不结算")
|
||||
String settlementType = null;
|
||||
if ("承运商合同".equals(contractManage.getContractCategory())) {
|
||||
settlementType = HcsContractFieldConstant.SETTLEMENT_TYPE_PAYMENT;
|
||||
} else if ("客户合同".equals(contractManage.getContractCategory())) {
|
||||
settlementType = HcsContractFieldConstant.SETTLEMENT_TYPE_COLLECTION;
|
||||
}
|
||||
if (StringUtil.isNotBlank(settlementType)) {
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_SETTLEMENT_TYPE, "合同结算类型", settlementType));
|
||||
} else {
|
||||
log.warn("HCS合同结算类型无法确定:contractCategory=[{}] 非承运商合同/客户合同,fdSettlementType 不传", contractManage.getContractCategory());
|
||||
}
|
||||
// 结算模式:TMS 合同固定开口合同(open,v4 字段说明)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_CONTRACT_GW_NATURE, "结算模式", HcsContractFieldConstant.CONTRACT_GW_NATURE_OPEN));
|
||||
// 特批流程:高风险/黑名单客商报文带 true(合规侧随起草自动走特批流程,C103-8 独立接口未提供);
|
||||
// 配置项 contract.special-contract-enabled 可强制全量走特批(原文拼写笔误必须沿用,D15)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_SEPCAIL_CONTRACT, "特批流程", risky || hcsProperties.getContract().isSpecialContractEnabled() ? "true" : "false"));
|
||||
// 合同类型:ContractManage.contractCategory
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_CONTRACT_TYPE, "合同类型", contractManage.getContractCategory()));
|
||||
// 甲方名称/乙方名称:TMS 存名称字符串
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_PARTY_A, "甲方名称", contractManage.getPartyA()));
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_PARTY_B, "乙方名称", contractManage.getPartyB()));
|
||||
// 【必填】签订日期:signDate(yyyy-MM-dd,fdType=timestamp)
|
||||
fields.add(timestampField(HcsContractFieldConstant.FIELD_SIGN_DATE, "签订日期", contractManage.getSignDate()));
|
||||
// 预计开始/结束时间:startDate/endDate(fdType=timestamp,期限为指定日期时必填)
|
||||
fields.add(timestampField(HcsContractFieldConstant.FIELD_FD_ESTIMATE_START_TIME, "预计开始时间", contractManage.getStartDate()));
|
||||
fields.add(timestampField(HcsContractFieldConstant.FIELD_FD_ESTIMATE_END_TIME, "预计结束时间", contractManage.getEndDate()));
|
||||
// 【必填】合同开始期限:TMS 固定指定日期起算,日期取 fdEstimateStartTime(v4 合同字段说明)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_START_TERM, "合同开始期限", contractManage.getStartDate() == null ? null : HcsContractFieldConstant.TERM_SPECIFIED_DATE));
|
||||
// 【必填】合同结束期限:TMS 固定指定日期,日期取 fdEstimateEndTime(v4 合同字段说明)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_END_TERM, "合同结束期限", contractManage.getEndDate() == null ? null : HcsContractFieldConstant.TERM_SPECIFIED_DATE));
|
||||
// 【必填】创建时间:createTime(yyyy-MM-dd,fdType=timestamp)
|
||||
fields.add(timestampField(HcsContractFieldConstant.FIELD_CREATE_TIME, "创建时间", contractManage.getCreateTime()));
|
||||
fields.add(timestampField(HcsContractFieldConstant.FIELD_FD_CREATE_TIME, "创建时间", contractManage.getCreateTime()));
|
||||
// 【必填】同步时间:推送时刻(yyyy-MM-dd,fdType=timestamp)
|
||||
fields.add(timestampField(HcsContractFieldConstant.FIELD_FD_SYN_TIME, "同步时间", LocalDateTime.now()));
|
||||
// 【必填】承办人名称:handlerUserName
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_CREATE_USER_NAME, "承办人名称", contractManage.getHandlerUserName()));
|
||||
// 【必填】合同状态:status-mapping[approvalStatus](v4 必填,draft/approving/approved 等,§9-U3 待联调后配置,未配置不传)
|
||||
String contractStatus = hcsProperties.getContract().getStatusMapping().get(contractManage.getApprovalStatus());
|
||||
if (StringUtil.isNotBlank(contractStatus)) {
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_CONTRACT_STATUS, "合同状态", contractStatus));
|
||||
} else {
|
||||
log.warn("HCS合同状态映射缺失:approvalStatus=[{}] 不在 contract.status-mapping,CONTRACT_STATUS 不传(§9-U3)", contractManage.getApprovalStatus());
|
||||
}
|
||||
// 【必填】合同份数:copyCount(v4 字段表 fdServings=Integer,数值传值;空值默认 1)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_SERVINGS, "合同份数", contractManage.getCopyCount() == null ? 1 : contractManage.getCopyCount()));
|
||||
// 合同说明:remark(选填)
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_DESC, "合同说明", contractManage.getRemark()));
|
||||
// 关联项目(选填,对象传值):合同所选项目对应合同系统项目ID(blade_project_apply.hcs_fd_id,C102-1 立项同步回填),
|
||||
// 未选择项目或项目未立项同步时不传
|
||||
Map<String, String> fdProject = queryProjectFdId(contractManage.getProjectId());
|
||||
if (fdProject != null) {
|
||||
fields.add(field(HcsContractFieldConstant.FIELD_FD_PROJECT, "关联项目", fdProject));
|
||||
}
|
||||
// TODO 待联调(v4 选填字段,暂不传):fdCoTakers 协办人(TMS 无对应)、fdUnderTaker/fdUnderTakerDept 承办人及部门(组织架构对象传值方式待确认)、
|
||||
// fdRelations 关联合同(数组)、
|
||||
// fdContractAttribute 合同属性(枚举值文档未给出,TMS 无对应)、fdUrgencyLevel 紧急程度(TMS 无对应)
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* signer 相对方(签署方):甲/乙双方。fdType 档案优先:客商档案为内部组织(customerKind=internal/内部组织)
|
||||
* 的一方为我方签约主体传 inner,另一方传 customer(外部相对方,2026-10-14 联调口径);
|
||||
* 双方均非内部组织或查不到档案时按合同类别推定(承运商合同甲=inner,客户合同乙=inner,其余 customer);
|
||||
* 丙方(三方合同)TMS 无签约主体字段来源,暂不支持
|
||||
*/
|
||||
private List<HcsSignerDTO> buildSigners(ContractManage contractManage) {
|
||||
List<HcsSignerDTO> signers = new ArrayList<>();
|
||||
// 甲乙方各反查一次客商档案,编号与内部组织判定共用查询结果
|
||||
CustomerArchive partyAArchive = queryCustomerArchive(contractManage.getPartyA());
|
||||
CustomerArchive partyBArchive = queryCustomerArchive(contractManage.getPartyB());
|
||||
boolean partyAInternal = HcsCreditCheckSupport.isInternalCustomer(partyAArchive);
|
||||
boolean partyBInternal = HcsCreditCheckSupport.isInternalCustomer(partyBArchive);
|
||||
String partyAType;
|
||||
String partyBType;
|
||||
if (partyAInternal || partyBInternal) {
|
||||
// 档案命中:内部组织一方=我方签约主体(inner),外部一方统一 customer
|
||||
partyAType = partyAInternal ? HcsConstant.SIGNER_FD_TYPE_INNER : HcsConstant.SIGNER_FD_TYPE_CUSTOMER;
|
||||
partyBType = partyBInternal ? HcsConstant.SIGNER_FD_TYPE_INNER : HcsConstant.SIGNER_FD_TYPE_CUSTOMER;
|
||||
} else if ("承运商合同".equals(contractManage.getContractCategory())) {
|
||||
// 兜底:档案未命中内部组织,按类别推定——我方为付款/发包方,签约主体在甲方
|
||||
partyAType = HcsConstant.SIGNER_FD_TYPE_INNER;
|
||||
partyBType = HcsConstant.SIGNER_FD_TYPE_CUSTOMER;
|
||||
} else if ("客户合同".equals(contractManage.getContractCategory())) {
|
||||
// 兜底:我方为收款/承运方,签约主体在乙方
|
||||
partyAType = HcsConstant.SIGNER_FD_TYPE_CUSTOMER;
|
||||
partyBType = HcsConstant.SIGNER_FD_TYPE_INNER;
|
||||
} else {
|
||||
// 兜底:其他类别双方统一 customer(2026-10-14 联调口径)
|
||||
partyAType = HcsConstant.SIGNER_FD_TYPE_CUSTOMER;
|
||||
partyBType = HcsConstant.SIGNER_FD_TYPE_CUSTOMER;
|
||||
}
|
||||
// 甲方相对方:fdStanding=甲(编号按名称反查,反查不到留空,§9-U6)
|
||||
signers.add(signer("甲", partyAType, contractManage.getPartyA(), partyAArchive));
|
||||
// 乙方相对方:fdStanding=乙
|
||||
signers.add(signer("乙", partyBType, contractManage.getPartyB(), partyBArchive));
|
||||
return signers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个相对方:fdType 由签约主体落位规则决定(我方主体 inner,外部相对方 customer)
|
||||
*/
|
||||
private HcsSignerDTO signer(String fdStanding, String fdType, String partyName, CustomerArchive customerArchive) {
|
||||
HcsSignerDTO signer = new HcsSignerDTO();
|
||||
// 签署地位:甲 / 乙
|
||||
signer.setFdStanding(fdStanding);
|
||||
// 相对方名称:TMS 甲乙方名称
|
||||
signer.setFdName(partyName);
|
||||
// 相对方编号:按名称反查 CustomerArchive 客商编码,反查不到留空(§9-U6)
|
||||
signer.setFdNumber(customerArchive == null ? null : customerArchive.getCustomerCode());
|
||||
// 单位类型:inner 内部主体(我方签约主体)/ customer 外部相对方
|
||||
signer.setFdType(fdType);
|
||||
return signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件转传(可配开关):合同主文件 approved=true(挂 eopCdraftSign)+ 其它附件(挂 eopCdraftOther),
|
||||
* 返回合规附件引用列表(挂载 C103-1 顶层 attachment,与 fdFieldJson 同级)
|
||||
*/
|
||||
private List<HcsAttachmentRefDTO> transferAttachmentsIfEnabled(ContractManage contractManage) {
|
||||
if (!hcsProperties.getAttachment().isTransferEnabled()) {
|
||||
return List.of();
|
||||
}
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = hcsAttachmentSupport.transferContractAttachments(
|
||||
HcsSyncConstant.BIZ_TYPE_CONTRACT, contractManage.getId(),
|
||||
contractManage.getContractFileJson(), contractManage.getAttachmentsJson(), hcsProperties.getOperatorLoginName());
|
||||
log.info("HCS合同附件转传完成:contractNo={},共 {} 个附件(随 C103-1 挂载)", contractManage.getContractNo(), attachmentRefs.size());
|
||||
return attachmentRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 C103-1 返回并取合同 fdContractId(必须持久化)
|
||||
*/
|
||||
private String validateDraftResult(ContractManage contractManage, HcsResult<HcsContractDraftVO> result) {
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdContractId())) {
|
||||
log.error("HCS合同发起失败响应:{},contractNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo());
|
||||
throw new HcsException("C103-1 合同发起失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getFdContractId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条手动重推:重置为待同步并清零重试计数后立即推送(页面"推送合规/重推"按钮入口)
|
||||
*
|
||||
* @param id 合同单ID
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String repushContract(Long id) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(id);
|
||||
if (contractManage == null) {
|
||||
throw new ServiceException("合同单不存在:" + id);
|
||||
}
|
||||
if (!List.of("approved", "change_approved").contains(contractManage.getApprovalStatus())) {
|
||||
throw new ServiceException("仅审批通过的合同可推送合规(当前状态:" + contractManage.getApprovalStatus() + ")");
|
||||
}
|
||||
if (HcsSyncConstant.SPEC_STATUS_PROCESSING.equals(contractManage.getHcsSpecStatus())) {
|
||||
throw new ServiceException("合同特批审核中,禁止重推(等待合规特批结果后恢复)");
|
||||
}
|
||||
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
contractManage.setHcsRetryCount(0);
|
||||
contractManageMapper.updateById(contractManage);
|
||||
try {
|
||||
return pushContract(contractManage);
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
markFailed(contractManage, message);
|
||||
log.error("HCS合同推送失败:{}", message, exception);
|
||||
throw new ServiceException("HCS合同推送失败:" + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填合规 fdId 并置 synced
|
||||
*/
|
||||
public void markSynced(ContractManage contractManage, String fdId, String message) {
|
||||
contractManage.setHcsFdId(fdId);
|
||||
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
contractManage.setHcsRetryCount(0);
|
||||
contractManage.setHcsSyncTime(LocalDateTime.now());
|
||||
contractManage.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
contractManageMapper.updateById(contractManage);
|
||||
log.info("HCS合同推送成功:contractNo={},fdContractId={},{}", contractManage.getContractNo(), fdId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送失败回写:failed + 重试次数+1 + 失败原因
|
||||
*/
|
||||
public void markFailed(ContractManage contractManage, String message) {
|
||||
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED);
|
||||
contractManage.setHcsRetryCount(contractManage.getHcsRetryCount() == null ? 1 : contractManage.getHcsRetryCount() + 1);
|
||||
contractManage.setHcsSyncTime(LocalDateTime.now());
|
||||
contractManage.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
contractManageMapper.updateById(contractManage);
|
||||
log.error("HCS合同推送失败:contractNo={},retryCount={},{}", contractManage.getContractNo(), contractManage.getHcsRetryCount(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板映射(template-mapping[contractCategory],未配置/无映射直接失败提示,§9-U7)
|
||||
*/
|
||||
private String requireTemplateId(ContractManage contractManage) {
|
||||
Map<String, String> mapping = hcsProperties.getContract().getTemplateMapping();
|
||||
String templateId = mapping.get(contractManage.getContractCategory());
|
||||
if (StringUtil.isBlank(templateId)) {
|
||||
throw new HcsException("HCS合同推送失败,合同类别 [" + contractManage.getContractCategory()
|
||||
+ "] 未配置合规模板ID(thirdParty.hcs.contract.template-mapping,§9-U7)");
|
||||
}
|
||||
return templateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按名称反查客商档案(全称或简称精确匹配,查不到返回 null)
|
||||
*/
|
||||
private CustomerArchive queryCustomerArchive(String partyName) {
|
||||
if (StringUtil.isBlank(partyName)) {
|
||||
return null;
|
||||
}
|
||||
return customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
|
||||
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, partyName).or().eq(CustomerArchive::getShortName, partyName))
|
||||
.eq(CustomerArchive::getIsDeleted, 0)
|
||||
.last("limit 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联项目取值:合同所选项目对应合同系统项目ID(blade_project_apply.hcs_fd_id,C102-1 立项同步回填),
|
||||
* 未选择项目、立项单不存在或未同步合规时返回 null 不传(fdProject 选填)
|
||||
*/
|
||||
private Map<String, String> queryProjectFdId(Long projectId) {
|
||||
if (projectId == null) {
|
||||
return null;
|
||||
}
|
||||
ProjectApply projectApply = projectApplyMapper.selectById(projectId);
|
||||
if (projectApply == null || StringUtil.isBlank(projectApply.getHcsFdId())) {
|
||||
log.warn("HCS合同关联项目缺失合规项目ID:projectId=[{}] 未立项或未同步合规,fdProject 不传", projectId);
|
||||
return null;
|
||||
}
|
||||
return Map.of("fdId", projectApply.getHcsFdId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 币种名称映射:currency-name-mapping 命中取映射值,未命中传原值并告警(§9-U3)
|
||||
*/
|
||||
private String mapCurrencyName(String currency) { if (StringUtil.isBlank(currency)) {
|
||||
return null;
|
||||
}
|
||||
return hcsProperties.getContract().getCurrencyNameMapping().getOrDefault(currency, currency);
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通模板字段(字符串/对象传值)
|
||||
*/
|
||||
private HcsXformFieldDTO field(String name, String label, Object value) {
|
||||
HcsXformFieldDTO field = new HcsXformFieldDTO();
|
||||
field.setName(name);
|
||||
field.setLabel(label);
|
||||
field.setValue(value);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间模板字段(fdType=timestamp,v4 要求传 yyyy-MM-dd)
|
||||
*/
|
||||
private HcsXformFieldDTO timestampField(String name, String label, Object value) {
|
||||
if (value == null) {
|
||||
return field(name, label, null);
|
||||
}
|
||||
String text;
|
||||
if (value instanceof LocalDate localDate) {
|
||||
text = localDate.format(DATE_FORMATTER);
|
||||
} else if (value instanceof LocalDateTime localDateTime) {
|
||||
text = localDateTime.format(DATE_FORMATTER);
|
||||
} else {
|
||||
// BaseEntity.createTime 为 Date 类型
|
||||
text = DATE_FORMATTER.format(((java.util.Date) value).toInstant()
|
||||
.atZone(java.time.ZoneId.systemDefault()).toLocalDate());
|
||||
}
|
||||
HcsXformFieldDTO field = field(name, label, text);
|
||||
// 时间字段需带 fdType=timestamp
|
||||
field.setFdType("timestamp");
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额字符串(空值安全)
|
||||
*/
|
||||
private String toPlainString(BigDecimal amount) {
|
||||
return amount == null ? null : amount.toPlainString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 风险校验留痕(blade_hcs_check_log,异常不影响主流程)
|
||||
*/
|
||||
private void writeCheckLog(ContractManage contractManage, String result, String msg, String traceId) {
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
checkLog.setTenantId(contractManage.getTenantId());
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT);
|
||||
checkLog.setBizId(contractManage.getId());
|
||||
checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_RISK);
|
||||
checkLog.setResult(result);
|
||||
checkLog.setMsg(msg);
|
||||
checkLog.setTraceId(traceId);
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS风险校验留痕写入失败:contractNo={}", contractManage.getContractNo(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单方风险查询结果
|
||||
*/
|
||||
private record RiskResult(String blackStatus, String riskLevel, boolean blacklist) {
|
||||
}
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsCreditCheckDTO;
|
||||
import org.springblade.hcs.dto.HcsCreditCheckVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsRiskQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsRiskVO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.CustomerArchiveMapper;
|
||||
import org.springblade.transport.mapper.HcsContractCheckLogMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.CustomerArchive;
|
||||
import org.springblade.transport.pojo.entity.HcsContractCheckLog;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.pojo.vo.CustomerRiskVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 付款提交校验支撑组件(C101-1 客商风险 + C106-1 额度校验,设计文档 §5.4,D15/R2-Q3)
|
||||
* <p>
|
||||
* 付款申请提交(submit)事务内同步调用(查询类同步):
|
||||
* ① C101-1 收款方为外部客商时查风险,高风险(风险等级=2)/黑名单提示且必须上传「特批附件」,
|
||||
* 并标记 hcs_spec_status=need 转付款特批链路(审批通过后扫描任务发起 C106-3,合规 paymentLegal/result 回调联动);
|
||||
* 内部组织客商默认无风险不查询;
|
||||
* ② C106-1 校验申请金额是否超出合同额度,超额不拦截提交,置 hcs_credit_status=over 并标记
|
||||
* hcs_spec_status=need 转特批链路(D15/R2-Q2)。
|
||||
* 校验依赖合同已推送(hcs_fd_id 存在),未推送时按 credit.require-contract-synced 配置阻断或跳过。
|
||||
* 结果留痕 blade_hcs_check_log(biz_type=payment)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsCreditCheckSupport {
|
||||
|
||||
/**
|
||||
* C101-1 黑名单状态取值(示例 no 表示非黑名单,§9-U3 字典值联调确认)
|
||||
*/
|
||||
private static final String BLACK_STATUS_YES = "yes";
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final CustomerArchiveMapper customerArchiveMapper;
|
||||
private final HcsContractCheckLogMapper hcsContractCheckLogMapper;
|
||||
|
||||
/**
|
||||
* 付款提交同步校验入口(submit 事务内调用,校验结果字段随提交一并落库)
|
||||
*
|
||||
* @param payment 付款申请单(须含合同与收款方信息)
|
||||
*/
|
||||
public void checkOnSubmit(PaymentApplication payment) {
|
||||
// ① 客商风险校验(可配开关):外部客商高风险/黑名单须特批附件并标记待特批
|
||||
if (hcsProperties.getRisk().isCheckOnPayment()) {
|
||||
checkRelativeRisk(payment);
|
||||
}
|
||||
// ② 额度校验(超额不拦截,转特批);一期暂不对接默认关,关闭后特批无自动触发
|
||||
if (!hcsProperties.getCredit().isCheckEnabled()) {
|
||||
return;
|
||||
}
|
||||
checkCredit(payment);
|
||||
}
|
||||
|
||||
/**
|
||||
* C101-1 收款方客商风险校验:仅外部客商查询,高风险/黑名单必须上传特批附件,
|
||||
* 并标记 hcs_spec_status=need 转付款特批链路(不再直接阻断)
|
||||
*/
|
||||
private void checkRelativeRisk(PaymentApplication payment) {
|
||||
CustomerArchive customerArchive = queryCustomerArchive(payment.getPayeeName());
|
||||
if (customerArchive == null) {
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, HcsSyncConstant.CHECK_RESULT_SKIP,
|
||||
"收款方[" + payment.getPayeeName() + "]反查不到客商编号,跳过风险校验(§9-U6)", null);
|
||||
log.warn("HCS付款提交风险校验跳过:paymentNo={},收款方反查不到客商编号", payment.getPaymentNo());
|
||||
return;
|
||||
}
|
||||
// 内部组织类型客商默认无风险,不查 C101-1(与 queryCustomerRisk 展示口径一致)
|
||||
if (isInternalCustomer(customerArchive)) {
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, HcsSyncConstant.CHECK_RESULT_SKIP,
|
||||
"收款方[" + payment.getPayeeName() + "]为内部组织客商,默认无风险跳过校验", null);
|
||||
return;
|
||||
}
|
||||
HcsRiskQueryDTO param = new HcsRiskQueryDTO();
|
||||
// 【必填】客商编号:按收款方名称反查 CustomerArchive 客商编码(用哪个编码待联调,§9-U6)
|
||||
param.setFdNumber(customerArchive.getCustomerCode());
|
||||
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS客商风险查询失败响应:{},paymentNo={},fdNumber={}",
|
||||
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo(), customerArchive.getCustomerCode());
|
||||
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsRiskVO risk = result.getData();
|
||||
String blackStatus = risk == null ? null : risk.getFdBlackStatus();
|
||||
String riskLevel = risk == null ? null : risk.getFdRiskLevel();
|
||||
boolean risky = HcsSyncConstant.RISK_LEVEL_HIGH.equals(riskLevel) || BLACK_STATUS_YES.equalsIgnoreCase(blackStatus);
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_RISK, risky ? HcsSyncConstant.CHECK_RESULT_FAIL : HcsSyncConstant.CHECK_RESULT_PASS,
|
||||
"收款方[" + payment.getPayeeName() + "]黑名单=" + blackStatus + ",风险等级=" + riskLevel + ",高风险/黑名单=" + risky,
|
||||
result.getTraceId());
|
||||
if (!risky) {
|
||||
return;
|
||||
}
|
||||
// 高风险/黑名单:必须上传「特批附件」类型附件(提示阻断),并标记待特批(审批通过后推送 C106-3);
|
||||
// 清空旧特批流程ID:特批单次有效,重新提交须重新发起 C106-3(否则会 C106-4 更新旧驳回单)
|
||||
if (!hasSpecAttachment(payment.getAttachmentsJson())) {
|
||||
throw new ServiceException("收款方[" + payment.getPayeeName() + "]为高风险/黑名单客商,必须上传「特批附件」类型附件后再提交审批");
|
||||
}
|
||||
payment.setHcsSpecFdId(null);
|
||||
payment.setHcsSpecStatus(HcsSyncConstant.SPEC_STATUS_NEED);
|
||||
}
|
||||
|
||||
/**
|
||||
* C106-1 额度校验:通过置 pass;超额置 over 并标记待特批(不拦截提交,D15/R2-Q2)
|
||||
*/
|
||||
private void checkCredit(PaymentApplication payment) {
|
||||
ContractManage contractManage = payment.getContractId() == null ? null : contractManageMapper.selectById(payment.getContractId());
|
||||
if (contractManage == null || StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
if (hcsProperties.getCredit().isRequireContractSynced()) {
|
||||
throw new ServiceException("关联合同尚未推送合规,无法校验额度(paymentNo=" + payment.getPaymentNo() + ")");
|
||||
}
|
||||
log.warn("HCS付款提交额度校验跳过:paymentNo={},合同未推送合规(require-contract-synced=false)", payment.getPaymentNo());
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_CREDIT, HcsSyncConstant.CHECK_RESULT_SKIP, "合同未推送合规,跳过额度校验", null);
|
||||
return;
|
||||
}
|
||||
HcsCreditCheckDTO param = new HcsCreditCheckDTO();
|
||||
// 【必填】合同ID:HCS 合同ID(blade_contract_manage.hcs_fd_id)
|
||||
param.setFdContractId(contractManage.getHcsFdId());
|
||||
// 【必填】申请金额:PaymentApplication.appliedAmount(元)
|
||||
param.setFdApplyAmount(payment.getAppliedAmount() == null ? BigDecimal.ZERO : payment.getAppliedAmount());
|
||||
HcsResult<HcsCreditCheckVO> result = hcsClient.contractCreditCheck(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS额度校验失败响应:{},paymentNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo());
|
||||
throw new HcsException("C106-1 额度校验失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsCreditCheckVO credit = result.getData();
|
||||
payment.setHcsCreditTime(LocalDateTime.now());
|
||||
if (credit != null && Boolean.TRUE.equals(credit.getFdIsCredit())) {
|
||||
// 额度通过:正常提交
|
||||
payment.setHcsCreditStatus(HcsSyncConstant.CREDIT_STATUS_PASS);
|
||||
payment.setHcsCreditMsg(null);
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_CREDIT, HcsSyncConstant.CHECK_RESULT_PASS, "额度校验通过", result.getTraceId());
|
||||
return;
|
||||
}
|
||||
// 超额:success 仍为 true,必须以 fdIsCredit 为准;不拦截提交,转入特批链路
|
||||
String message = credit == null ? "响应数据为空" : credit.getFdMessage();
|
||||
payment.setHcsCreditStatus(HcsSyncConstant.CREDIT_STATUS_OVER);
|
||||
payment.setHcsCreditMsg(message);
|
||||
payment.setHcsSpecStatus(HcsSyncConstant.SPEC_STATUS_NEED);
|
||||
writeCheckLog(payment, HcsSyncConstant.CHECK_TYPE_CREDIT, HcsSyncConstant.CHECK_RESULT_FAIL, "额度超额:" + message, result.getTraceId());
|
||||
log.warn("HCS付款提交额度超额(转特批):paymentNo={},{}", payment.getPaymentNo(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商风险查询(C101-1,展示与提交校验共用,前端接入设计 §3.3):
|
||||
* 内部组织类型客商默认无风险(不调 C101-1);外部客商按编号查询风险等级与黑名单状态并留痕;
|
||||
* 高风险/黑名单时组装风险提示文案(风险项明细 C101-1 未返回,固定文案展示)
|
||||
*
|
||||
* @param customerCode 客商编号
|
||||
* @return 风险展示信息(customerKind/fdBlackStatus/fdRiskLevel/risky/riskTip)
|
||||
*/
|
||||
public CustomerRiskVO queryCustomerRisk(String customerCode) {
|
||||
if (StringUtil.isBlank(customerCode)) {
|
||||
throw new ServiceException("客商编号不能为空");
|
||||
}
|
||||
CustomerArchive customerArchive = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
|
||||
.eq(CustomerArchive::getCustomerCode, customerCode)
|
||||
.eq(CustomerArchive::getIsDeleted, 0)
|
||||
.last("limit 1"));
|
||||
CustomerRiskVO riskVO = new CustomerRiskVO();
|
||||
riskVO.setCustomerCode(customerCode);
|
||||
riskVO.setCustomerName(customerArchive == null ? customerCode : customerArchive.getFullName());
|
||||
// 内部组织类型客商默认无风险;档案不存在按外部客商查询(不静默放行)
|
||||
boolean internal = isInternalCustomer(customerArchive);
|
||||
riskVO.setCustomerKind(internal ? HcsSyncConstant.CUSTOMER_KIND_INTERNAL : "external");
|
||||
if (internal) {
|
||||
riskVO.setRisky(Boolean.FALSE);
|
||||
return riskVO;
|
||||
}
|
||||
HcsRiskQueryDTO param = new HcsRiskQueryDTO();
|
||||
// 【必填】客商编号(用哪个编码待联调,§9-U6)
|
||||
param.setFdNumber(customerCode);
|
||||
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS客商风险查询失败响应:{},fdNumber={}",
|
||||
result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), customerCode);
|
||||
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsRiskVO risk = result.getData();
|
||||
String blackStatus = risk == null ? null : risk.getFdBlackStatus();
|
||||
String riskLevel = risk == null ? null : risk.getFdRiskLevel();
|
||||
boolean risky = HcsSyncConstant.RISK_LEVEL_HIGH.equals(riskLevel) || BLACK_STATUS_YES.equalsIgnoreCase(blackStatus);
|
||||
riskVO.setFdBlackStatus(blackStatus);
|
||||
riskVO.setFdRiskLevel(riskLevel);
|
||||
riskVO.setRiskLevelName(riskLevelName(blackStatus, riskLevel));
|
||||
riskVO.setRisky(risky);
|
||||
if (risky) {
|
||||
// 风险提示固定短文案(C101-1 未返回风险项明细,不拼接枚举清单):黑名单与高风险区分表述
|
||||
riskVO.setRiskTip("【" + riskVO.getCustomerName() + "】:"
|
||||
+ (BLACK_STATUS_YES.equalsIgnoreCase(blackStatus) ? "被列入外部黑名单" : "为高风险客商"));
|
||||
}
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT);
|
||||
checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_RISK);
|
||||
checkLog.setResult(risky ? HcsSyncConstant.CHECK_RESULT_FAIL : HcsSyncConstant.CHECK_RESULT_PASS);
|
||||
checkLog.setMsg("客商风险查询[" + customerCode + "]黑名单=" + blackStatus + ",风险等级=" + riskLevel + ",高风险/黑名单=" + risky);
|
||||
checkLog.setTraceId(result.getTraceId());
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS客商风险查询留痕写入失败:fdNumber={}", customerCode, exception);
|
||||
}
|
||||
return riskVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按客商名称查询风险(合同提交校验用):按名称反查客商编号后转 {@link #queryCustomerRisk};
|
||||
* 反查不到客商编号时不阻断(risky=false),与推送链路 skip 口径一致(§9-U6)
|
||||
*
|
||||
* @param customerName 客商名称(合同甲/乙方名称)
|
||||
* @return 风险展示信息
|
||||
*/
|
||||
public CustomerRiskVO queryCustomerRiskByName(String customerName) {
|
||||
String customerCode = queryCustomerCode(customerName);
|
||||
if (StringUtil.isBlank(customerCode)) {
|
||||
CustomerRiskVO riskVO = new CustomerRiskVO();
|
||||
riskVO.setCustomerName(customerName);
|
||||
riskVO.setRisky(Boolean.FALSE);
|
||||
return riskVO;
|
||||
}
|
||||
return queryCustomerRisk(customerCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部组织客商判定(集团内我方签约主体,档案登记 customerKind=internal/内部组织);
|
||||
* 展示、提交校验、付款校验、合同推送、signer 单位类型各链路统一复用本判定
|
||||
*
|
||||
* @param customerArchive 客商档案
|
||||
* @return 是否内部组织客商
|
||||
*/
|
||||
public static boolean isInternalCustomer(CustomerArchive customerArchive) {
|
||||
return customerArchive != null
|
||||
&& (HcsSyncConstant.CUSTOMER_KIND_INTERNAL.equals(customerArchive.getCustomerKind())
|
||||
|| "内部组织".equals(customerArchive.getCustomerKind()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 风险等级转义(C101-1 枚举 0=低风险/1=中风险/2=高风险):黑名单状态优先展示,无等级返回空
|
||||
*/
|
||||
private String riskLevelName(String blackStatus, String riskLevel) {
|
||||
if (BLACK_STATUS_YES.equalsIgnoreCase(blackStatus)) {
|
||||
return "黑名单";
|
||||
}
|
||||
return switch (Objects.toString(riskLevel, "")) {
|
||||
case "2" -> "高风险";
|
||||
case "1" -> "中风险";
|
||||
case "0" -> "低风险";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商风险批量查询(C101-1,客商列表页展示用,前端接入设计 §3.3):
|
||||
* 逐个复用 {@link #queryCustomerRisk}(内部客商默认无风险,外部客商查 C101-1,与付款页面展示口径一致);
|
||||
* C101-1 仅支持单编号查询,逐个串行调用;展示场景单个失败跳过不阻断整页(强制校验仍在合同/付款链路)
|
||||
*
|
||||
* @param customerCodes 客商编号集合
|
||||
* @return 风险展示信息列表(查询失败的客商不在结果中)
|
||||
*/
|
||||
public List<CustomerRiskVO> queryCustomerRiskBatch(List<String> customerCodes) {
|
||||
List<CustomerRiskVO> riskList = new ArrayList<>();
|
||||
if (Func.isEmpty(customerCodes)) {
|
||||
return riskList;
|
||||
}
|
||||
for (String customerCode : customerCodes) {
|
||||
try {
|
||||
riskList.add(this.queryCustomerRisk(customerCode));
|
||||
} catch (Exception exception) {
|
||||
log.error("客商风险批量查询单个失败,跳过:customerCode={}", customerCode, exception);
|
||||
}
|
||||
}
|
||||
return riskList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按名称反查客商档案(内部/外部类型判断与客商编码获取共用;查不到返回 null)
|
||||
*/
|
||||
private CustomerArchive queryCustomerArchive(String customerName) {
|
||||
if (StringUtil.isBlank(customerName)) {
|
||||
return null;
|
||||
}
|
||||
return customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
|
||||
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, customerName).or().eq(CustomerArchive::getShortName, customerName))
|
||||
.eq(CustomerArchive::getIsDeleted, 0)
|
||||
.last("limit 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按名称反查客商编码(照 KingdeeApBillSyncSupport#queryCustomerCode 惯例,查不到返回 null)
|
||||
*/
|
||||
private String queryCustomerCode(String customerName) {
|
||||
CustomerArchive customerArchive = queryCustomerArchive(customerName);
|
||||
return customerArchive == null ? null : customerArchive.getCustomerCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件中是否已上传特批附件(付款附件结构 attachmentType=special_approval,即前端付款表单「特批附件」类型;
|
||||
* 兼容 fileType=特批附件 结构;口径同合同提交校验 validateSpecExplanationOnSubmit)
|
||||
*/
|
||||
private boolean hasSpecAttachment(String attachmentsJson) {
|
||||
if (StringUtil.isBlank(attachmentsJson)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(attachmentsJson, List.class);
|
||||
if (!(parsed instanceof List<?> attachments)) {
|
||||
return false;
|
||||
}
|
||||
return attachments.stream().filter(Map.class::isInstance).map(Map.class::cast)
|
||||
.anyMatch(attachment -> HcsSyncConstant.PAYMENT_ATTACH_TYPE_SPECIAL
|
||||
.equals(Objects.toString(attachment.get("attachmentType"), null))
|
||||
|| HcsSyncConstant.ATTACHMENT_TYPE_SPEC_EXPLANATION
|
||||
.equals(Objects.toString(attachment.get(HcsSyncConstant.ATTACHMENT_TYPE_FIELD), null)));
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS付款提交附件JSON解析失败:按未上传特批附件处理", exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验留痕(blade_hcs_check_log,异常不影响主流程)
|
||||
*/
|
||||
private void writeCheckLog(PaymentApplication payment, String checkType, String result, String msg, String traceId) {
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
checkLog.setTenantId(payment.getTenantId());
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_PAYMENT);
|
||||
checkLog.setBizId(payment.getId());
|
||||
checkLog.setCheckType(checkType);
|
||||
checkLog.setResult(result);
|
||||
checkLog.setMsg(msg);
|
||||
checkLog.setTraceId(traceId);
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS付款校验留痕写入失败:paymentNo={}", payment.getPaymentNo(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsExeplanStageDTO;
|
||||
import org.springblade.hcs.dto.HcsExeplanStageVO;
|
||||
import org.springblade.hcs.dto.HcsExeplanSyncDTO;
|
||||
import org.springblade.hcs.dto.HcsExeplanVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.HcsContractExeStageMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 履约规则同步支撑组件(C103-9 新版行级同步,设计文档 §5.3,ADR-0002)
|
||||
* <p>
|
||||
* 随合同推送链路执行(合同须已同步、hcs_fd_id 存在):聚合 row_op 非 none 的规则行
|
||||
* (add 行不带 fdId,update/delete 行必须带合规阶段ID),整体 fdEopExeplanOperateType
|
||||
* 首次为 add、后续为 update;返回的 fdExeplanId 回填 blade_contract_manage.hcs_exeplan_id,
|
||||
* 变更阶段 fdId 按行回填 hcs_stage_fd_id(合规返回的 fdId 必须持久化,后续行更新/删除全靠它)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsExeStageSyncSupport {
|
||||
|
||||
/**
|
||||
* 行级扩展内容固定空 JSON
|
||||
*/
|
||||
private static final String EXTEND_CONTENT_EMPTY = "{}";
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final HcsContractExeStageMapper hcsContractExeStageMapper;
|
||||
|
||||
/**
|
||||
* 同步合同履约规则到 HCS(C103-9),成功后回填 fdExeplanId 与各阶段 fdId 并清除行级操作标记
|
||||
*
|
||||
* @param contractManage 合同(须已同步,hcs_fd_id 非空)
|
||||
* @return 同步结果描述(无待同步行时返回提示)
|
||||
*/
|
||||
public String syncExeStages(ContractManage contractManage) {
|
||||
if (StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
throw new HcsException("C103-9 履约规则同步失败,合同尚未推送合规(contractNo=" + contractManage.getContractNo() + ")");
|
||||
}
|
||||
// 含软删行:delete 行已逻辑删除但须凭合规阶段ID 上报(原生 SQL 绕过逻辑删除过滤)
|
||||
List<HcsContractExeStage> pendingRows = hcsContractExeStageMapper.selectSyncRows(contractManage.getId());
|
||||
if (pendingRows.isEmpty()) {
|
||||
return "履约规则无待同步行";
|
||||
}
|
||||
HcsExeplanSyncDTO param = buildSyncParam(contractManage, pendingRows);
|
||||
log.info("HCS履约规则同步开始:contractNo={},整体操作 {},{} 行", contractManage.getContractNo(),
|
||||
param.getFdEopExeplanOperateType(), pendingRows.size());
|
||||
HcsResult<HcsExeplanVO> result = hcsClient.exeplanAdd(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
log.error("HCS履约规则同步失败响应:{},contractNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), contractManage.getContractNo());
|
||||
throw new HcsException("C103-9 履约规则同步失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsExeplanVO exeplan = result.getData();
|
||||
backfill(contractManage, pendingRows, param.getFdStages(), exeplan);
|
||||
return String.format("履约规则同步成功(%s,%d 行)", param.getFdEopExeplanOperateType(), pendingRows.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 C103-9 入参(字段标注照 §3.4 规范,映射见设计文档 §5.3)
|
||||
*/
|
||||
private HcsExeplanSyncDTO buildSyncParam(ContractManage contractManage, List<HcsContractExeStage> pendingRows) {
|
||||
HcsExeplanSyncDTO param = new HcsExeplanSyncDTO();
|
||||
// 【必填】合同ID:HCS 合同ID(blade_contract_manage.hcs_fd_id,非 TMS 主键)
|
||||
param.setFdContractId(contractManage.getHcsFdId());
|
||||
// 【必填】外部系统标识:fd_sources,配置 thirdParty.hcs.src-system(D14)
|
||||
param.setFdOuterSystem(hcsProperties.getSrcSystem());
|
||||
// 【必填】操作人登录名:配置 thirdParty.hcs.operator-login-name(默认 srvadmin,§9-U2)
|
||||
param.setFdLoginName(hcsProperties.getOperatorLoginName());
|
||||
// 【必填】计划整体操作类型:首次同步 add,已同步过(有履约计划ID)update
|
||||
param.setFdEopExeplanOperateType(StringUtil.isBlank(contractManage.getHcsExeplanId()) ? "add" : "update");
|
||||
// 履约计划ID:已同步过则传(回填自首次同步返回)
|
||||
param.setFdExeplanId(contractManage.getHcsExeplanId());
|
||||
List<HcsExeplanStageDTO> stages = new ArrayList<>();
|
||||
for (HcsContractExeStage row : pendingRows) {
|
||||
HcsExeplanStageDTO stage = new HcsExeplanStageDTO();
|
||||
// 阶段项ID:行级 update/delete 必传(取 hcs_stage_fd_id),add 行不带
|
||||
if (!"add".equals(row.getRowOp())) {
|
||||
stage.setFdId(row.getHcsStageFdId());
|
||||
}
|
||||
// 【必填】行级操作:add / update / delete
|
||||
stage.setFdEopExeplanOperateType(row.getRowOp());
|
||||
// 【必填】履约类型:payment=付款 / collection=收款
|
||||
stage.setFdPerformType(row.getPerformType());
|
||||
// 【必填】款项性质:prepayment/progress/acceptance/retention
|
||||
stage.setFdFundNature(row.getFundNature());
|
||||
// 【必填】款项金额(元,付款审批通过按申请付款金额累加更新)
|
||||
stage.setFdFundAmount(row.getFundAmount());
|
||||
// 【必填】资金日期(毫秒时间戳)
|
||||
stage.setFdFundDate(toMillis(row.getFundDate()));
|
||||
// 【必填】开票规则:payFirst=先款后票 / invoiceFirst=先票后款
|
||||
stage.setFdInvoiceRule(row.getInvoiceRule());
|
||||
// 【必填】账期(天)
|
||||
stage.setFdInvoiceCycle(row.getInvoiceCycle());
|
||||
// 金额类型:行维护值,空取配置 thirdParty.hcs.exe-stage.fund-type(默认 byAmount)
|
||||
stage.setFdFundType(StringUtil.isBlank(row.getFundType()) ? hcsProperties.getExeStage().getFundType() : row.getFundType());
|
||||
// 是否完成
|
||||
stage.setFdIsFinish(row.getIsFinish() != null && row.getIsFinish() > 0);
|
||||
// 扩展内容 JSON 字符串:固定 "{}"
|
||||
stage.setFdExtendContent(EXTEND_CONTENT_EMPTY);
|
||||
stages.add(stage);
|
||||
}
|
||||
param.setFdStages(stages);
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填:fdExeplanId → 合同 hcs_exeplan_id;变更阶段 fdId 按行回填 hcs_stage_fd_id 并清除行级操作标记
|
||||
* <p>
|
||||
* TODO 待联调确认:fdChangeStages 与请求行的对应关系 v4 未明确,优先按请求行序对应(首次全 add 场景),
|
||||
* 行数不一致时按业务属性组合键匹配,仍匹配不到的 add 行保持待同步(下轮重试)。
|
||||
*/
|
||||
private void backfill(ContractManage contractManage, List<HcsContractExeStage> pendingRows,
|
||||
List<HcsExeplanStageDTO> requestStages, HcsExeplanVO exeplan) {
|
||||
// 履约计划ID 回填(后续行更新/删除依赖,必须持久化)
|
||||
if (StringUtil.isNotBlank(exeplan.getFdExeplanId())
|
||||
&& !exeplan.getFdExeplanId().equals(contractManage.getHcsExeplanId())) {
|
||||
contractManage.setHcsExeplanId(exeplan.getFdExeplanId());
|
||||
}
|
||||
backfillExeplanId(contractManage);
|
||||
List<HcsExeplanStageVO> changeStages = exeplan.getFdChangeStages() == null ? List.of() : exeplan.getFdChangeStages();
|
||||
// 行数一致时按请求行序一一对应;不一致时按业务属性组合键匹配
|
||||
boolean positional = changeStages.size() == requestStages.size();
|
||||
Map<String, HcsExeplanStageVO> byAttribute = positional ? Map.of() : mapByAttribute(changeStages);
|
||||
int addRowCount = 0;
|
||||
int unmatchedCount = 0;
|
||||
for (int index = 0; index < pendingRows.size(); index++) {
|
||||
HcsContractExeStage row = pendingRows.get(index);
|
||||
String stageFdId = row.getHcsStageFdId();
|
||||
if ("add".equals(row.getRowOp())) {
|
||||
addRowCount++;
|
||||
HcsExeplanStageVO matched = positional ? changeStages.get(index) : byAttribute.get(attributeKey(requestStages.get(index)));
|
||||
stageFdId = matched == null ? null : matched.getFdId();
|
||||
if (StringUtil.isBlank(stageFdId)) {
|
||||
unmatchedCount++;
|
||||
}
|
||||
}
|
||||
markRowSynced(row, stageFdId);
|
||||
}
|
||||
log.info("HCS履约规则阶段ID回填完成:contractNo={},add {} 行,未匹配 {} 行(模式:{})",
|
||||
contractManage.getContractNo(), addRowCount, unmatchedCount, positional ? "行序对应" : "属性匹配");
|
||||
}
|
||||
|
||||
/**
|
||||
* 行同步成功回写:回填合规阶段ID、清除行级操作标记并置 synced
|
||||
*/
|
||||
private void markRowSynced(HcsContractExeStage row, String stageFdId) {
|
||||
if (StringUtil.isNotBlank(stageFdId)) {
|
||||
row.setHcsStageFdId(stageFdId);
|
||||
} else if ("add".equals(row.getRowOp())) {
|
||||
// add 行未取得阶段ID:保持待同步,下轮重试(ADR-0002:阶段ID 必须持久化)
|
||||
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED);
|
||||
row.setHcsSyncMsg("返回阶段ID未能匹配,待重试");
|
||||
hcsContractExeStageMapper.updateById(row);
|
||||
log.warn("HCS履约规则 add 行未匹配到返回阶段ID:rowId={},contractId={}", row.getId(), row.getContractId());
|
||||
return;
|
||||
}
|
||||
row.setRowOp("none");
|
||||
row.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
row.setHcsSyncMsg("同步成功");
|
||||
hcsContractExeStageMapper.updateById(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按业务属性组合键索引返回阶段(行数不一致时的兜底匹配)
|
||||
*/
|
||||
private Map<String, HcsExeplanStageVO> mapByAttribute(List<HcsExeplanStageVO> changeStages) {
|
||||
Map<String, HcsExeplanStageVO> byAttribute = new HashMap<>();
|
||||
for (HcsExeplanStageVO stage : changeStages) {
|
||||
byAttribute.putIfAbsent(String.join("|", Objects.toString(stage.getFdPerformType(), ""),
|
||||
Objects.toString(stage.getFdFundNature(), ""),
|
||||
Objects.toString(stage.getFdFundAmount(), ""),
|
||||
Objects.toString(stage.getFdFundDate(), "")), stage);
|
||||
}
|
||||
return byAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求行的业务属性组合键
|
||||
*/
|
||||
private String attributeKey(HcsExeplanStageDTO stage) {
|
||||
return String.join("|", Objects.toString(stage.getFdPerformType(), ""),
|
||||
Objects.toString(stage.getFdFundNature(), ""),
|
||||
Objects.toString(stage.getFdFundAmount(), ""),
|
||||
Objects.toString(stage.getFdFundDate(), ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同履约计划ID 回写(fdExeplanId 必须持久化;失败仅告警不阻断行回填,下轮同步会再次携带)
|
||||
*/
|
||||
private void backfillExeplanId(ContractManage contractManage) {
|
||||
try {
|
||||
contractManageMapper.updateById(contractManage);
|
||||
log.info("HCS履约计划ID已回填:contractNo={},fdExeplanId={}", contractManage.getContractNo(), contractManage.getHcsExeplanId());
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS履约计划ID回写失败:contractNo={}", contractManage.getContractNo(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime → 毫秒时间戳
|
||||
*/
|
||||
private Long toMillis(LocalDateTime localDateTime) {
|
||||
return localDateTime == null ? null : localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.mapper.ProjectApplyMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* HCS 审批通过即时推送(前端接入设计 §6,2026-10-10 增补)
|
||||
* <p>
|
||||
* 项目立项(C102)/合同(C103)/付款申请(C106 付款特批)审批通过(含变更通过)事务提交后立即触发单条推送,不再等待扫描周期;
|
||||
* 执行体复用 {@link HcsProjectSyncSupport#repushProject(Long)} / {@link HcsContractSyncSupport#repushContract(Long)} /
|
||||
* {@link HcsPaymentSyncSupport#repushPayment(Long)}
|
||||
* (已含审批状态校验、失败 markFailed、高风险特批挂起判断),失败仅留痕,由 HcsPushTask 扫描兜底补偿
|
||||
* (ADR-0001 修订:审批代码埋点为一行触发调用,推送逻辑仍收敛在 hcs support 包)。
|
||||
* <p>
|
||||
* 触发模型:审批事务内调用则注册 afterCommit 回调(事务回滚不推送),无活动事务(MK 回调单条更新自动提交)直接提交;
|
||||
* 自建 2 线程 daemon 池(工程未启用 @EnableAsync),HCS 推送可达 20s+ 不阻塞审批响应;
|
||||
* Redis 单条锁防与扫描任务并发双推(TTL 120s,持有值比对释放,照 HcsPushTask 惯例),
|
||||
* 极端双推由 HCS 侧判重/更新幂等吸收。与扫描任务同线程模型(无租户上下文),行为一致。
|
||||
* <p>
|
||||
* 例外:TMS 合同/项目立项的"通过"按钮路径由 Controller 在审批事务返回后分别调用
|
||||
* {@link #pushContractAndWait(Long)}、{@link #pushProjectAndWait(Long)} 同步推送
|
||||
* (审批响应会等待附件转传 + HCS 调用完成,与正式结算单审批一致),
|
||||
* 失败原因经 {@link #pollSyncFailure()} 交给 Controller 拼进审批响应(照 KingdeeApprovalSyncListener);
|
||||
* MK 回调路径仍异步,避免阻塞第三方回调。
|
||||
* <p>
|
||||
* 付款申请按"审批即发起特批"要求:即时推送不受 thirdParty.hcs.push.immediate-enabled 开关控制
|
||||
* ({@link #pushPaymentAndWait(Long)} / {@link #pushPaymentAfterCommit(Long)}),
|
||||
* 审批通过即以 C106-3 发起特批,履约计划与履约记录等特批通过后补推。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsImmediatePushSupport {
|
||||
|
||||
/**
|
||||
* 审批通过状态集合(与 HcsPushTask.APPROVED_STATUSES 一致,含变更通过)
|
||||
*/
|
||||
private static final List<String> APPROVED_STATUSES = List.of("approved", "change_approved");
|
||||
|
||||
/**
|
||||
* 推送线程编号(线程名 hcs-immediate-push-N 便于日志定位)
|
||||
*/
|
||||
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(1);
|
||||
|
||||
/**
|
||||
* 当前审批请求线程的合规推送失败原因,供审批 Controller 取走提示前端
|
||||
*/
|
||||
private static final ThreadLocal<String> SYNC_FAILURE = new ThreadLocal<>();
|
||||
|
||||
private final HcsProperties hcsProperties;
|
||||
private final HcsProjectSyncSupport hcsProjectSyncSupport;
|
||||
private final HcsContractSyncSupport hcsContractSyncSupport;
|
||||
private final HcsPaymentSyncSupport hcsPaymentSyncSupport;
|
||||
private final ProjectApplyMapper projectApplyMapper;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
private final BladeRedis bladeRedis;
|
||||
|
||||
/**
|
||||
* 推送线程池:2 线程 daemon,即时推送与定时扫描互不影响
|
||||
*/
|
||||
private final ExecutorService pushExecutor = Executors.newFixedThreadPool(2, task -> {
|
||||
Thread thread = new Thread(task, "hcs-immediate-push-" + THREAD_COUNTER.getAndIncrement());
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
|
||||
/**
|
||||
* 项目立项(C102)审批通过后即时推送:须在审批事务内调用(afterCommit 触发)
|
||||
*
|
||||
* @param id 项目立项单ID
|
||||
*/
|
||||
public void pushProjectAfterCommit(Long id) {
|
||||
this.submitAfterCommit(HcsSyncConstant.BIZ_TYPE_PROJECT, id, () -> this.pushProjectNow(id), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同(C103)审批通过后即时推送:须在审批事务内调用(afterCommit 触发)
|
||||
*
|
||||
* @param id 合同单ID
|
||||
*/
|
||||
public void pushContractAfterCommit(Long id) {
|
||||
this.submitAfterCommit(HcsSyncConstant.BIZ_TYPE_CONTRACT, id, () -> this.pushContractNow(id), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款申请审批通过后即时发起付款特批(C106-3,无需特批则直接推履约数据):
|
||||
* 须在审批事务内调用(afterCommit 触发);按"审批即发起特批"要求不受 immediate-enabled 开关控制
|
||||
*
|
||||
* @param id 付款申请单ID
|
||||
*/
|
||||
public void pushPaymentAfterCommit(Long id) {
|
||||
this.submitAfterCommit(HcsSyncConstant.BIZ_TYPE_PAYMENT, id, () -> this.pushPaymentNow(id), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同"通过"按钮路径:在审批事务提交返回后由 Controller 同步调用,失败原因留在当前线程供其取走
|
||||
*
|
||||
* @param id 合同单ID
|
||||
*/
|
||||
public void pushContractAndWait(Long id) {
|
||||
if (!hcsProperties.getPush().isImmediateEnabled()) {
|
||||
return;
|
||||
}
|
||||
this.pushWithLock(HcsSyncConstant.BIZ_TYPE_CONTRACT, id, () -> this.pushContractNow(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目立项"通过"按钮路径:在审批事务提交返回后由 Controller 同步调用,失败原因留在当前线程供其取走
|
||||
*
|
||||
* @param id 项目立项单ID
|
||||
*/
|
||||
public void pushProjectAndWait(Long id) {
|
||||
if (!hcsProperties.getPush().isImmediateEnabled()) {
|
||||
return;
|
||||
}
|
||||
this.pushWithLock(HcsSyncConstant.BIZ_TYPE_PROJECT, id, () -> this.pushProjectNow(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款申请"通过"按钮路径:在审批事务提交返回后由 Controller 同步调用,失败原因留在当前线程供其取走;
|
||||
* 按"审批即发起特批"要求不受 immediate-enabled 开关控制
|
||||
*
|
||||
* @param id 付款申请单ID
|
||||
*/
|
||||
public void pushPaymentAndWait(Long id) {
|
||||
this.pushWithLock(HcsSyncConstant.BIZ_TYPE_PAYMENT, id, () -> this.pushPaymentNow(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取走并清除当前审批请求的合规推送失败原因;null 表示推送成功、未触发或仍异步进行
|
||||
*/
|
||||
public static String pollSyncFailure() {
|
||||
String failure = SYNC_FAILURE.get();
|
||||
SYNC_FAILURE.remove();
|
||||
return failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交时机注册:有活动事务注册 afterCommit 回调(回滚不推送),无事务(MK 回调单条更新自动提交)直接异步
|
||||
*
|
||||
* @param requireSwitch 是否受 immediate-enabled 开关控制(项目/合同是,付款特批按"审批即发起"否)
|
||||
*/
|
||||
private void submitAfterCommit(String bizType, Long id, Runnable pushAction, boolean requireSwitch) {
|
||||
if (requireSwitch && !hcsProperties.getPush().isImmediateEnabled()) {
|
||||
return;
|
||||
}
|
||||
Runnable pushTask = () -> this.pushWithLock(bizType, id, pushAction);
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
pushExecutor.execute(pushTask);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pushExecutor.execute(pushTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条锁防并发双推:抢不到即放弃(扫描兜底),持有值比对后释放
|
||||
*/
|
||||
private void pushWithLock(String bizType, Long id, Runnable pushAction) {
|
||||
String lockKey = HcsSyncConstant.PUSH_SINGLE_LOCK_KEY_PREFIX + bizType + ":" + id;
|
||||
String lockHolder = UUID.randomUUID().toString();
|
||||
// 清理同线程残留的失败原因,避免线程复用串单(同步推送路径会写入)
|
||||
SYNC_FAILURE.remove();
|
||||
Boolean locked = bladeRedis.getValueOps()
|
||||
.setIfAbsent(lockKey, lockHolder, Duration.ofSeconds(HcsSyncConstant.PUSH_SINGLE_LOCK_TTL_SECONDS));
|
||||
if (!Boolean.TRUE.equals(locked)) {
|
||||
log.info("HCS即时推送跳过(其他线程推送中):bizType={},id={}", bizType, id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
pushAction.run();
|
||||
} finally {
|
||||
if (lockHolder.equals(bladeRedis.get(lockKey))) {
|
||||
bladeRedis.del(lockKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目立项推送执行体:非审批通过/已同步短路跳过,失败已由 Support markFailed,仅留痕交扫描补偿
|
||||
*/
|
||||
private void pushProjectNow(Long id) {
|
||||
try {
|
||||
ProjectApply projectApply = projectApplyMapper.selectById(id);
|
||||
if (projectApply == null || !APPROVED_STATUSES.contains(projectApply.getApprovalStatus())
|
||||
|| HcsSyncConstant.HCS_STATUS_SYNCED.equals(projectApply.getHcsSyncStatus())) {
|
||||
return;
|
||||
}
|
||||
hcsProjectSyncSupport.repushProject(id);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS项目立项即时推送失败(扫描任务将兜底补偿):id={},{}", id, exception.getMessage(), exception);
|
||||
// 失败原因留在当前线程供审批 Controller 取走;异步路径无人取走,仅影响线程池自身
|
||||
String reason = exception.getMessage() == null ? "合规接口调用异常" : exception.getMessage();
|
||||
SYNC_FAILURE.set(reason.startsWith("HCS项目立项推送失败:")
|
||||
? reason.substring("HCS项目立项推送失败:".length()) : reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同推送执行体:非审批通过/已同步/特批挂起中短路跳过,失败已由 Support markFailed,仅留痕交扫描补偿
|
||||
*/
|
||||
private void pushContractNow(Long id) {
|
||||
try {
|
||||
ContractManage contractManage = contractManageMapper.selectById(id);
|
||||
if (contractManage == null || !APPROVED_STATUSES.contains(contractManage.getApprovalStatus())
|
||||
|| HcsSyncConstant.HCS_STATUS_SYNCED.equals(contractManage.getHcsSyncStatus())) {
|
||||
return;
|
||||
}
|
||||
if (Arrays.asList(HcsSyncConstant.SPEC_STATUS_NEED, HcsSyncConstant.SPEC_STATUS_PROCESSING).contains(contractManage.getHcsSpecStatus())) {
|
||||
log.info("HCS合同即时推送跳过(特批挂起中):id={},hcsSpecStatus={}", id, contractManage.getHcsSpecStatus());
|
||||
return;
|
||||
}
|
||||
hcsContractSyncSupport.repushContract(id);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS合同即时推送失败(扫描任务将兜底补偿):id={},{}", id, exception.getMessage(), exception);
|
||||
// 失败原因留在当前线程供审批 Controller 取走;异步路径无人取走,仅影响线程池自身
|
||||
String reason = exception.getMessage() == null ? "合规接口调用异常" : exception.getMessage();
|
||||
SYNC_FAILURE.set(reason.startsWith("HCS合同推送失败:")
|
||||
? reason.substring("HCS合同推送失败:".length()) : reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款推送执行体:非审批通过/已同步短路跳过;失败已由 Support markFailed,仅留痕交扫描/手动重推补偿
|
||||
*/
|
||||
private void pushPaymentNow(Long id) {
|
||||
try {
|
||||
PaymentApplication payment = paymentApplicationMapper.selectById(id);
|
||||
if (payment == null || !APPROVED_STATUSES.contains(payment.getApprovalStatus())
|
||||
|| HcsSyncConstant.HCS_STATUS_SYNCED.equals(payment.getHcsSyncStatus())) {
|
||||
return;
|
||||
}
|
||||
hcsPaymentSyncSupport.repushPayment(id);
|
||||
} catch (Exception exception) {
|
||||
log.error("HCS付款即时推送失败(可手动重推或扫描任务补偿):id={},{}", id, exception.getMessage(), exception);
|
||||
// 失败原因留在当前线程供审批 Controller 取走;异步路径无人取走,仅影响线程池自身
|
||||
String reason = exception.getMessage() == null ? "合规接口调用异常" : exception.getMessage();
|
||||
SYNC_FAILURE.set(reason.startsWith("HCS付款申请推送失败:")
|
||||
? reason.substring("HCS付款申请推送失败:".length()) : reason);
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
pushExecutor.shutdown();
|
||||
}
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsAttachmentRefDTO;
|
||||
import org.springblade.hcs.dto.HcsIdVO;
|
||||
import org.springblade.hcs.dto.HcsInvoiceRegAddDTO;
|
||||
import org.springblade.hcs.dto.HcsInvoiceRegUpdateDTO;
|
||||
import org.springblade.hcs.dto.HcsPageQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsPageVO;
|
||||
import org.springblade.hcs.dto.HcsInvoiceRegVO;
|
||||
import org.springblade.hcs.dto.HcsRefVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.transport.constant.HcsFieldConstant;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.InvoiceApplicationInvoiceMapper;
|
||||
import org.springblade.transport.mapper.InvoiceApplicationMapper;
|
||||
import org.springblade.transport.mapper.InvoiceApplicationSettlementMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplication;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplicationSettlement;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 开票登记同步支撑组件(C111-1 查询判重 / C111-2 新增 / C111-3 更新,设计文档 §5.7,D12)
|
||||
* <p>
|
||||
* 审批通过即推 C111-2(票据日期空);金蝶开票完成(kingdeeStatus=invoiced,由金蝶结果回写置待同步)
|
||||
* → C111-3 回填 fdBillDate/fdStatus。判重 C111-1 conditions={"fdNumber": applicationNo}(§9-U4 联调确认)。
|
||||
* fdContractNumber 缺口:InvoiceApplication 无合同字段,经应收结算关联表回溯合同编号(§9-U10,回溯不到失败提示)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsInvoiceRegSyncSupport {
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final InvoiceApplicationMapper invoiceApplicationMapper;
|
||||
private final InvoiceApplicationSettlementMapper invoiceApplicationSettlementMapper;
|
||||
private final InvoiceApplicationInvoiceMapper invoiceApplicationInvoiceMapper;
|
||||
private final FormalSettlementMapper formalSettlementMapper;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final HcsAttachmentSupport hcsAttachmentSupport;
|
||||
|
||||
/**
|
||||
* 推送开票登记:无合规ID 走判重+C111-2 新增(金蝶已开票完成时紧接着 C111-3),有合规ID 走 C111-3
|
||||
*
|
||||
* @param application 开票申请单(approvalStatus=approved)
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String pushInvoiceRegistration(InvoiceApplication application) {
|
||||
if (StringUtil.isBlank(application.getHcsFdId())) {
|
||||
String existFdId = findExistFdId(application);
|
||||
if (StringUtil.isNotBlank(existFdId)) {
|
||||
markSynced(application, existFdId, "判重命中,回填已有合规开票登记ID");
|
||||
return "判重命中,回填已有合规开票登记ID";
|
||||
}
|
||||
String fdId = addInvoiceRegistration(application);
|
||||
// 开票登记ID 立即持久化(调用方必须保存)
|
||||
markSynced(application, fdId, "C111-2 开票登记新增成功");
|
||||
String result = "C111-2 开票登记新增成功";
|
||||
// 金蝶已开票完成(新增前回写竞态):紧接着回填票据日期
|
||||
if (KingdeeBfiConstant.KINGDEE_STATUS_INVOICED.equals(application.getKingdeeStatus())) {
|
||||
result += ";" + updateInvoiceRegistration(application);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
String result = updateInvoiceRegistration(application);
|
||||
markSynced(application, application.getHcsFdId(), "C111-3 开票登记更新成功");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条手动重推:重置为待同步并清零重试计数后立即推送
|
||||
*
|
||||
* @param id 开票申请单ID
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String repushInvoiceRegistration(Long id) {
|
||||
InvoiceApplication application = invoiceApplicationMapper.selectById(id);
|
||||
if (application == null) {
|
||||
throw new ServiceException("开票申请单不存在:" + id);
|
||||
}
|
||||
if (!"approved".equals(application.getApprovalStatus())) {
|
||||
throw new ServiceException("仅审批通过的开票申请可推送合规(当前状态:" + application.getApprovalStatus() + ")");
|
||||
}
|
||||
application.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
application.setHcsRetryCount(0);
|
||||
invoiceApplicationMapper.updateById(application);
|
||||
try {
|
||||
return pushInvoiceRegistration(application);
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
markFailed(application, message);
|
||||
throw new ServiceException("HCS开票登记推送失败:" + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C111-2 新增开票登记,返回合规 fdId(字段标注照 §3.4 规范,映射见设计文档 §5.7)
|
||||
*/
|
||||
private String addInvoiceRegistration(InvoiceApplication application) {
|
||||
HcsInvoiceRegAddDTO param = new HcsInvoiceRegAddDTO();
|
||||
// 单据编号:开票申请单号(同租户唯一,判重候选键)
|
||||
param.setFdNumber(application.getApplicationNo());
|
||||
// 登记日期:applicationDate(毫秒时间戳)
|
||||
param.setFdRegisterDate(toMillis(application.getApplicationDate()));
|
||||
// 【必填】合同编号:经应收结算链路回溯(§9-U10 关联字段确认后细化)
|
||||
ContractRef contractRef = requireContractRef(application);
|
||||
param.setFdContractNumber(contractRef.contractNo());
|
||||
// 合同 {fdId, fdName}:fdId 传合规合同ID(未推送不传),名称取结算单合同名称(v4 新增)
|
||||
param.setFdContract(contractRef.contract());
|
||||
// 发票类型:invoice-type-mapping 字典映射(§9-U3,未配置不传)
|
||||
param.setFdInvoiceType(hcsProperties.getInvoice().getInvoiceTypeMapping().get(application.getInvoiceType()));
|
||||
if (param.getFdInvoiceType() == null) {
|
||||
log.warn("HCS开票登记发票类型映射缺失:invoiceType=[{}] 不在 invoice.invoice-type-mapping,fdInvoiceType 不传(§9-U3)",
|
||||
application.getInvoiceType());
|
||||
}
|
||||
// 【必填】结算币种:currency-id-mapping(TMS 币种码 → HCS 币种fdId,经 C103-12 缓存取值)
|
||||
param.setFdCurrencyId(requireCurrencyId(application));
|
||||
// 开票方(销方):issuerName
|
||||
param.setFdInvoiceParty(application.getIssuerName());
|
||||
// 款项性质:配置 invoice.fund-nature-default(默认 progress)
|
||||
param.setFdFundNature(hcsProperties.getInvoice().getFundNatureDefault());
|
||||
// 开票金额(含税):invoiceAmount
|
||||
param.setFdInvoiceAmount(application.getInvoiceAmount());
|
||||
// 备注:remark
|
||||
param.setFdRemark(application.getRemark());
|
||||
// 状态:新增固定 0(已开票)
|
||||
param.setFdStatus(0);
|
||||
// 第三方创建人编码:申请人工号(User.code)
|
||||
param.setFdThirdCreatorCode(resolveCreatorCode(application));
|
||||
// 第三方创建部门编码:申请人部门编码
|
||||
param.setFdThirdCreatorDeptCode(resolveDeptCode(application));
|
||||
// 附件ID列表:attachmentsJson 经 C103-2 转传
|
||||
if (hcsProperties.getAttachment().isTransferEnabled() && StringUtil.isNotBlank(application.getAttachmentsJson())) {
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = hcsAttachmentSupport.transferAttachments(
|
||||
HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION, application.getId(),
|
||||
application.getAttachmentsJson(), resolveCreatorCode(application));
|
||||
param.setFdAttIds(attachmentRefs.stream().map(HcsAttachmentRefDTO::getFdId).toList());
|
||||
}
|
||||
log.info("HCS开票登记新增开始:applicationNo={}", application.getApplicationNo());
|
||||
HcsResult<HcsIdVO> result = hcsClient.invoiceRegistrationAdd(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdId())) {
|
||||
log.error("HCS开票登记新增失败响应:{},applicationNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), application.getApplicationNo());
|
||||
throw new HcsException("C111-2 开票登记新增失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* C111-3 更新开票登记:回填票据日期与状态(金蝶开票完成后)
|
||||
*/
|
||||
private String updateInvoiceRegistration(InvoiceApplication application) {
|
||||
HcsInvoiceRegUpdateDTO param = new HcsInvoiceRegUpdateDTO();
|
||||
// 【必填】开票登记ID(C111-2 新增返回)
|
||||
param.setFdId(application.getHcsFdId());
|
||||
// 票据日期:金蝶回写发票的最新开票日期(无发票记录取回写时间,待联调确认口径)
|
||||
param.setFdBillDate(latestInvoiceDate(application));
|
||||
// 状态:0 已开票
|
||||
param.setFdStatus(0);
|
||||
log.info("HCS开票登记更新开始:applicationNo={},fdId={}", application.getApplicationNo(), application.getHcsFdId());
|
||||
HcsResult<HcsIdVO> result = hcsClient.invoiceRegistrationUpdate(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS开票登记更新失败响应:{},applicationNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), application.getApplicationNo());
|
||||
throw new HcsException("C111-3 开票登记更新失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return "C111-3 开票登记更新成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* C111-1 开票登记列表判重(conditions={"fdNumber": applicationNo},§9-U4 查询能力联调确认)
|
||||
*/
|
||||
private String findExistFdId(InvoiceApplication application) {
|
||||
HcsPageQueryDTO query = new HcsPageQueryDTO();
|
||||
query.setColumns(List.of(HcsFieldConstant.INVOICE_FD_ID, HcsFieldConstant.INVOICE_FD_NUMBER));
|
||||
// 判重条件:单据编号精确匹配
|
||||
query.setConditions(Map.of(HcsFieldConstant.INVOICE_FD_NUMBER, Objects.toString(application.getApplicationNo(), "")));
|
||||
query.setOffset(0);
|
||||
query.setPageSize(10);
|
||||
HcsResult<HcsPageVO<HcsInvoiceRegVO>> result = hcsClient.invoiceRegistrationList(query);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS开票登记判重查询失败响应:{},applicationNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), application.getApplicationNo());
|
||||
throw new HcsException("C111-1 开票登记判重查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsPageVO<HcsInvoiceRegVO> page = result.getData();
|
||||
if (page == null || page.getContent() == null || page.getContent().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.getContent().get(0).getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 应收结算链路回溯合同引用:开票申请 → 应收结算关联 → 正式结算单合同(§9-U10);
|
||||
* fdContractNumber 必填,fdContract 随链路回填(fdId 为合规合同ID,未推送不传)
|
||||
*/
|
||||
private ContractRef requireContractRef(InvoiceApplication application) {
|
||||
InvoiceApplicationSettlement relation = invoiceApplicationSettlementMapper.selectOne(Wrappers.<InvoiceApplicationSettlement>lambdaQuery()
|
||||
.eq(InvoiceApplicationSettlement::getInvoiceApplicationId, application.getId())
|
||||
.last("limit 1"));
|
||||
if (relation != null && relation.getFormalSettlementId() != null) {
|
||||
FormalSettlement settlement = formalSettlementMapper.selectById(relation.getFormalSettlementId());
|
||||
if (settlement != null && StringUtil.isNotBlank(settlement.getContractNo())) {
|
||||
HcsRefVO contract = new HcsRefVO();
|
||||
contract.setFdName(settlement.getContractName());
|
||||
if (settlement.getContractId() != null) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(settlement.getContractId());
|
||||
if (contractManage != null) {
|
||||
contract.setFdId(contractManage.getHcsFdId());
|
||||
}
|
||||
}
|
||||
return new ContractRef(settlement.getContractNo(), contract);
|
||||
}
|
||||
}
|
||||
throw new HcsException("C111 开票登记推送失败,应收结算链路回溯不到合同编号(applicationNo=" + application.getApplicationNo()
|
||||
+ ",§9-U10 待确认关联字段)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同引用(fdContractNumber / fdContract)
|
||||
*/
|
||||
private record ContractRef(String contractNo, HcsRefVO contract) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 结算币种ID(currency-id-mapping,必填;TMS 无币种字段按 RMB 取映射)
|
||||
*/
|
||||
private String requireCurrencyId(InvoiceApplication application) {
|
||||
String currencyFdId = hcsProperties.getInvoice().getCurrencyIdMapping().get("RMB");
|
||||
if (StringUtil.isBlank(currencyFdId)) {
|
||||
throw new HcsException("C111 开票登记推送失败,币种ID 映射未配置(invoice.currency-id-mapping RMB,§9-U3)");
|
||||
}
|
||||
return currencyFdId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶回写发票的最新开票日期(毫秒),无发票记录取金蝶回写时间
|
||||
*/
|
||||
private Long latestInvoiceDate(InvoiceApplication application) {
|
||||
InvoiceApplicationInvoice invoice = invoiceApplicationInvoiceMapper.selectOne(Wrappers.<InvoiceApplicationInvoice>lambdaQuery()
|
||||
.eq(InvoiceApplicationInvoice::getInvoiceApplicationId, application.getId())
|
||||
.orderByDesc(InvoiceApplicationInvoice::getInvoiceDate)
|
||||
.last("limit 1"));
|
||||
LocalDateTime invoiceDate = invoice != null && invoice.getInvoiceDate() != null
|
||||
? invoice.getInvoiceDate() : application.getSyncedTime();
|
||||
return toMillis(invoiceDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填整体同步状态置 synced
|
||||
*/
|
||||
public void markSynced(InvoiceApplication application, String fdId, String message) {
|
||||
application.setHcsFdId(fdId);
|
||||
application.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
application.setHcsRetryCount(0);
|
||||
application.setHcsSyncTime(LocalDateTime.now());
|
||||
application.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
invoiceApplicationMapper.updateById(application);
|
||||
log.info("HCS开票登记推送成功:applicationNo={},fdId={},{}", application.getApplicationNo(), fdId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送失败回写:failed + 重试次数+1 + 失败原因
|
||||
*/
|
||||
public void markFailed(InvoiceApplication application, String message) {
|
||||
application.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED);
|
||||
application.setHcsRetryCount(application.getHcsRetryCount() == null ? 1 : application.getHcsRetryCount() + 1);
|
||||
application.setHcsSyncTime(LocalDateTime.now());
|
||||
application.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
invoiceApplicationMapper.updateById(application);
|
||||
log.error("HCS开票登记推送失败:applicationNo={},retryCount={},{}", application.getApplicationNo(), application.getHcsRetryCount(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请人工号(User.code,fdThirdCreatorCode)
|
||||
*/
|
||||
private String resolveCreatorCode(InvoiceApplication application) {
|
||||
User user = application.getCreateUser() == null ? null : UserCache.getUser(application.getCreateUser());
|
||||
return user == null ? null : user.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请人部门编码(fdThirdCreatorDeptCode)
|
||||
*/
|
||||
private String resolveDeptCode(InvoiceApplication application) {
|
||||
User user = application.getCreateUser() == null ? null : UserCache.getUser(application.getCreateUser());
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
// 用户冗余部门编码优先(可能逗号分隔,取第一个)
|
||||
if (StringUtil.isNotBlank(user.getDeptCodes())) {
|
||||
return user.getDeptCodes().split(",")[0].trim();
|
||||
}
|
||||
Long deptId = Func.toLong(user.getDeptId() == null ? null : user.getDeptId().split(",")[0].trim());
|
||||
Dept dept = deptId == null ? null : SysCache.getDept(deptId);
|
||||
return dept == null ? null : dept.getDeptCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime/LocalDate → 毫秒时间戳
|
||||
*/
|
||||
private Long toMillis(LocalDateTime localDateTime) {
|
||||
return localDateTime == null ? null : localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDate → 毫秒时间戳
|
||||
*/
|
||||
private Long toMillis(java.time.LocalDate localDate) {
|
||||
return localDate == null ? null : localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsCategoryVO;
|
||||
import org.springblade.hcs.dto.HcsCurrencyVO;
|
||||
import org.springblade.hcs.dto.HcsPageQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsPageVO;
|
||||
import org.springblade.hcs.dto.HcsTemplateVO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.hcs.util.HcsPageUtil;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* HCS 主数据缓存支撑组件(C103-6 模板 / C103-7 分类 / C103-12 币种,设计文档 §5.9)
|
||||
* <p>
|
||||
* 启动/首次使用时拉取并缓存(Redis,照 BFI token 缓存惯例,TTL 可配 master-data.ttl-minutes):
|
||||
* 模板列表供 template-mapping 校验,币种表供 currency-id-mapping 取值。
|
||||
* 提供管理端"刷新缓存"入口(HcsSyncController)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsMasterDataSupport {
|
||||
|
||||
/**
|
||||
* 主数据分页拉取页大小
|
||||
*/
|
||||
private static final int PAGE_SIZE = 200;
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final BladeRedis bladeRedis;
|
||||
|
||||
/**
|
||||
* 获取合同模板列表(缓存优先,未命中全量拉取)
|
||||
*/
|
||||
public List<HcsTemplateVO> getTemplates() {
|
||||
List<HcsTemplateVO> cached = readCache(HcsSyncConstant.MASTER_CACHE_TEMPLATES, HcsTemplateVO.class);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
return loadTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取合同分类列表(缓存优先,未命中全量拉取)
|
||||
*/
|
||||
public List<HcsCategoryVO> getCategories() {
|
||||
List<HcsCategoryVO> cached = readCache(HcsSyncConstant.MASTER_CACHE_CATEGORIES, HcsCategoryVO.class);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
return loadCategories();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取币种列表(缓存优先,未命中全量拉取)
|
||||
*/
|
||||
public List<HcsCurrencyVO> getCurrencies() {
|
||||
List<HcsCurrencyVO> cached = readCache(HcsSyncConstant.MASTER_CACHE_CURRENCIES, HcsCurrencyVO.class);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
return loadCurrencies();
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制刷新三类主数据缓存(管理端入口),并对 template-mapping 做存在性校验
|
||||
*
|
||||
* @return 刷新结果汇总
|
||||
*/
|
||||
public String refreshAll() {
|
||||
int templateCount = loadTemplates().size();
|
||||
int categoryCount = loadCategories().size();
|
||||
int currencyCount = loadCurrencies().size();
|
||||
validateTemplateMapping();
|
||||
String summary = String.format("HCS主数据缓存已刷新:模板 %d 条、分类 %d 条、币种 %d 条", templateCount, categoryCount, currencyCount);
|
||||
log.info(summary);
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量拉取合同模板并缓存
|
||||
*/
|
||||
private List<HcsTemplateVO> loadTemplates() {
|
||||
HcsPageQueryDTO query = baseQuery();
|
||||
List<HcsTemplateVO> rows = HcsPageUtil.queryAllPages(offset -> {
|
||||
query.setOffset(offset);
|
||||
return hcsClient.templateList(query).getData();
|
||||
});
|
||||
writeCache(HcsSyncConstant.MASTER_CACHE_TEMPLATES, rows);
|
||||
log.info("HCS合同模板列表已缓存,共 {} 条", rows.size());
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量拉取合同分类并缓存
|
||||
*/
|
||||
private List<HcsCategoryVO> loadCategories() {
|
||||
HcsPageQueryDTO query = baseQuery();
|
||||
List<HcsCategoryVO> rows = HcsPageUtil.queryAllPages(offset -> {
|
||||
query.setOffset(offset);
|
||||
return hcsClient.categoryList(query).getData();
|
||||
});
|
||||
writeCache(HcsSyncConstant.MASTER_CACHE_CATEGORIES, rows);
|
||||
log.info("HCS合同分类列表已缓存,共 {} 条", rows.size());
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全量拉取币种并缓存
|
||||
*/
|
||||
private List<HcsCurrencyVO> loadCurrencies() {
|
||||
HcsPageQueryDTO query = baseQuery();
|
||||
List<HcsCurrencyVO> rows = HcsPageUtil.queryAllPages(offset -> {
|
||||
query.setOffset(offset);
|
||||
return hcsClient.currencyList(query).getData();
|
||||
});
|
||||
writeCache(HcsSyncConstant.MASTER_CACHE_CURRENCIES, rows);
|
||||
log.info("HCS币种列表已缓存,共 {} 条", rows.size());
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 contract.template-mapping 中的模板ID是否存在于 HCS 模板列表(§9-U7,仅告警不阻断)
|
||||
*/
|
||||
private void validateTemplateMapping() {
|
||||
Map<String, String> mapping = hcsProperties.getContract().getTemplateMapping();
|
||||
if (mapping.isEmpty()) {
|
||||
log.warn("HCS合同模板映射(thirdParty.hcs.contract.template-mapping)未配置,合同推送将失败(§9-U7 待合规方提供模板ID清单)");
|
||||
return;
|
||||
}
|
||||
List<String> templateIds = getTemplates().stream().map(HcsTemplateVO::getFdId).toList();
|
||||
mapping.forEach((category, templateId) -> {
|
||||
if (StringUtil.isNotBlank(templateId) && !templateIds.contains(templateId)) {
|
||||
log.warn("HCS合同模板映射校验告警:合同类别 [{}] 映射的模板ID [{}] 不在合规模板列表中", category, templateId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询基础参数(offset/pageSize 必填,columns 各主数据接口选填)
|
||||
*/
|
||||
private HcsPageQueryDTO baseQuery() {
|
||||
HcsPageQueryDTO query = new HcsPageQueryDTO();
|
||||
// 【必填】每页条数
|
||||
query.setPageSize(PAGE_SIZE);
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读缓存(JSON 字符串反序列化)
|
||||
*/
|
||||
private <T> List<T> readCache(String cacheKey, Class<T> itemType) {
|
||||
String json = bladeRedis.get(cacheKey);
|
||||
if (StringUtil.isBlank(json)) {
|
||||
return null;
|
||||
}
|
||||
return JSONUtil.toList(json, itemType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写缓存(JSON 字符串 + 可配 TTL)
|
||||
*/
|
||||
private void writeCache(String cacheKey, List<?> rows) {
|
||||
bladeRedis.getValueOps().setIfAbsent(cacheKey, JSONUtil.toJsonStr(rows),
|
||||
Duration.ofMinutes(hcsProperties.getMasterData().getTtlMinutes()));
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsLegalResultDTO;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
import org.springblade.transport.event.KingdeeBillApprovedEvent;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HCS 付款链推送编排支撑组件(结算付款特批 C106-3/4 + 履约计划重推 C103-9 + 履约记录 C108-1/2 + 特批结果推送接收,设计文档 §5.0/§5.3/§5.5/§5.6)
|
||||
* <p>
|
||||
* 编排顺序:特批(提交时高风险/黑名单须特批附件或额度超额标记 need 时发起)→ 特批通过后再推履约计划(付款审批款项金额累加行)→ 履约记录(审批通过新建 / 实际付款推进更新)。
|
||||
* 特批审核期间(need/pushing/processing)仅发起/等待 C106,不推送履约计划与履约记录;驳回不产生履约数据。
|
||||
* 特批流程ID 与履约记录ID 在各自 Support 内即时持久化;整体同步状态(hcs_sync_status)由本组件统一回写。
|
||||
* 特批发起后 TMS 挂起 currentNode="特批审核",合规 paymentLegal/result 回调联动付款审批状态
|
||||
* (通过恢复"审批通过"并补推履约数据 / 驳回联动审批驳回走重新提交闭环),人工确认等效兜底。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsPaymentSyncSupport {
|
||||
|
||||
/**
|
||||
* 审批通过状态集合
|
||||
*/
|
||||
private static final List<String> APPROVED_STATUSES = List.of("approved");
|
||||
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final HcsSpecPaySyncSupport hcsSpecPaySyncSupport;
|
||||
private final HcsExeStageSyncSupport hcsExeStageSyncSupport;
|
||||
private final HcsPerfRecordSyncSupport hcsPerfRecordSyncSupport;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
/**
|
||||
* 推送付款申请到 HCS:特批(need 时发起 C106-3 后挂起)→ 特批通过后履约计划重推(C103-9 款项金额累加行)→ 履约记录(新建/更新)→ 回填整体同步状态
|
||||
*
|
||||
* @param payment 付款申请单(approvalStatus=approved)
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String pushPayment(PaymentApplication payment) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
// ① 结算付款特批:提交时高风险/黑名单(须特批附件)或额度超额(hcs_spec_status=need)且未发起时发起 C106-3;
|
||||
// 发起后挂起等待特批结果,履约计划与履约记录由特批通过后的 pushAfterSpecApproved 补推(驳回不产生履约数据)
|
||||
if (HcsSyncConstant.SPEC_STATUS_NEED.equals(payment.getHcsSpecStatus())) {
|
||||
String specResult = hcsSpecPaySyncSupport.pushSpecPay(payment);
|
||||
// C106-4 更新路径不改状态,统一归一为特批审核中,避免下轮扫描重复发起
|
||||
if (HcsSyncConstant.SPEC_STATUS_NEED.equals(payment.getHcsSpecStatus())) {
|
||||
payment.setHcsSpecStatus(HcsSyncConstant.SPEC_STATUS_PROCESSING);
|
||||
payment.setCurrentNode(HcsSyncConstant.SPEC_REVIEW_NODE);
|
||||
}
|
||||
markSynced(payment, specResult);
|
||||
return specResult;
|
||||
}
|
||||
// 特批审核中:仅等待合规回调/人工确认,不推送履约计划与履约记录
|
||||
if (HcsSyncConstant.SPEC_STATUS_PUSHING.equals(payment.getHcsSpecStatus())
|
||||
|| HcsSyncConstant.SPEC_STATUS_PROCESSING.equals(payment.getHcsSpecStatus())) {
|
||||
String message = "付款特批审核中,待特批通过后推送履约数据";
|
||||
markSynced(payment, message);
|
||||
return message;
|
||||
}
|
||||
// ② 履约计划重推(C103-9 行级更新):付款审批通过累加款项金额置 row_op=update 的计划行,
|
||||
// 凭已存合规阶段ID 重推;独立于履约记录开关,推送失败抛出由付款链置 failed 重试兜底
|
||||
String stageResult = this.pushExeStageUpdate(payment);
|
||||
if (StringUtil.isNotBlank(stageResult)) {
|
||||
if (result.length() > 0) {
|
||||
result.append(";");
|
||||
}
|
||||
result.append(stageResult);
|
||||
}
|
||||
// ③ 履约记录:审批通过新建 C108-1 / 实际付款推进更新 C108-2(一期暂不对接默认关)
|
||||
if (!hcsProperties.getPerfRecord().isSyncEnabled()) {
|
||||
markSynced(payment, result.toString());
|
||||
return result.toString();
|
||||
}
|
||||
if (result.length() > 0) {
|
||||
result.append(";");
|
||||
}
|
||||
result.append(hcsPerfRecordSyncSupport.pushPerfRecord(payment));
|
||||
markSynced(payment, result.toString());
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 履约计划重推(C103-9):聚合合同下 row_op 非 none 的计划行(含付款审批累加款项金额的 update 行)凭已存 fdId 重推;
|
||||
* 合同尚未推送合规时计划行随合同链路首推(首推即携带累加后金额),此处跳过
|
||||
*
|
||||
* @param payment 付款申请单
|
||||
* @return 推送结果描述,无待同步行返回空串
|
||||
*/
|
||||
private String pushExeStageUpdate(PaymentApplication payment) {
|
||||
if (payment.getContractId() == null) {
|
||||
return "";
|
||||
}
|
||||
ContractManage contractManage = contractManageMapper.selectById(payment.getContractId());
|
||||
if (contractManage == null || StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
return "";
|
||||
}
|
||||
String stageResult = hcsExeStageSyncSupport.syncExeStages(contractManage);
|
||||
return "履约规则无待同步行".equals(stageResult) ? "" : stageResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批通过后补推履约数据(C103-9 计划重推 + C108 履约记录):补推失败置 failed 留待人工重推兜底,
|
||||
* 不阻断特批结果落库与金蝶补推
|
||||
*
|
||||
* @param payment 付款申请单(hcs_spec_status=approved)
|
||||
*/
|
||||
private void pushAfterSpecApproved(PaymentApplication payment) {
|
||||
try {
|
||||
this.pushPayment(payment);
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "履约数据推送异常" : exception.getMessage();
|
||||
markFailed(payment, message);
|
||||
log.error("付款特批通过后履约数据推送失败:paymentNo={},{}", payment.getPaymentNo(), message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条手动重推:重置为待同步并清零重试计数后立即推送(页面"推送合规/重推"按钮入口)
|
||||
*
|
||||
* @param id 付款申请单ID
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String repushPayment(Long id) {
|
||||
PaymentApplication payment = paymentApplicationMapper.selectById(id);
|
||||
if (payment == null) {
|
||||
throw new ServiceException("付款申请单不存在:" + id);
|
||||
}
|
||||
if (!APPROVED_STATUSES.contains(payment.getApprovalStatus())) {
|
||||
throw new ServiceException("仅审批通过的付款申请可推送合规(当前状态:" + payment.getApprovalStatus() + ")");
|
||||
}
|
||||
payment.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
payment.setHcsRetryCount(0);
|
||||
paymentApplicationMapper.updateById(payment);
|
||||
try {
|
||||
return pushPayment(payment);
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
markFailed(payment, message);
|
||||
throw new ServiceException("HCS付款申请推送失败:" + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批结果人工确认(页面确认与合规 paymentLegal/result 回调等效,§9-U1 过渡兜底)
|
||||
*
|
||||
* @param id 付款申请单ID
|
||||
* @param result confirmed approved / rejected
|
||||
* @return 确认结果描述
|
||||
*/
|
||||
public String confirmSpecResult(Long id, String result) {
|
||||
PaymentApplication payment = paymentApplicationMapper.selectById(id);
|
||||
if (payment == null) {
|
||||
throw new ServiceException("付款申请单不存在:" + id);
|
||||
}
|
||||
if (!Arrays.asList(HcsSyncConstant.SPEC_STATUS_APPROVED, HcsSyncConstant.SPEC_STATUS_REJECTED).contains(result)) {
|
||||
throw new ServiceException("特批结果仅支持 approved/rejected");
|
||||
}
|
||||
if (!HcsSyncConstant.SPEC_STATUS_PROCESSING.equals(payment.getHcsSpecStatus())) {
|
||||
throw new ServiceException("仅特批中的付款申请允许确认结果(当前状态:" + payment.getHcsSpecStatus() + ")");
|
||||
}
|
||||
// 特批结果联动付款审批状态(通过恢复放行 / 驳回联动审批驳回),随 updateById 一并落库
|
||||
syncPaymentOnSpecResult(payment, HcsSyncConstant.SPEC_STATUS_APPROVED.equals(result));
|
||||
payment.setHcsSpecStatus(result);
|
||||
paymentApplicationMapper.updateById(payment);
|
||||
// 特批通过后补推履约计划与履约记录(驳回不产生履约数据),失败不阻断后续金蝶补推
|
||||
if (HcsSyncConstant.SPEC_STATUS_APPROVED.equals(result)) {
|
||||
this.pushAfterSpecApproved(payment);
|
||||
}
|
||||
publishKingdeeAutoSyncOnSpecApproved(payment, result);
|
||||
log.info("HCS特批结果人工确认:paymentNo={},specFdId={},结果={}", payment.getPaymentNo(), payment.getHcsSpecFdId(), result);
|
||||
return "特批结果已确认:" + result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款特批结果接收处理(合规 paymentLegal/result 回调通知特批审批结果):
|
||||
* sourceSystem 须与 third-party.hcs.src-system 配置一致(不符抛出"来源系统标识错误"异常),
|
||||
* requestId=TMS 付款申请ID、status=APPROVED/REJECTED;
|
||||
* 特批结果联动付款审批状态:approved 恢复"审批通过"放行(后续金蝶推送放行),
|
||||
* rejected 联动付款审批驳回(returned),走"重新编辑→提交→审批→重新特批"闭环
|
||||
*
|
||||
* @param param 合规推送报文
|
||||
* @return 处理结果描述
|
||||
*/
|
||||
public String handleLegalResult(HcsLegalResultDTO param) {
|
||||
// 来源系统校验:仅接受 third-party.hcs.src-system 配置的来源,防伪造回传
|
||||
if (!StrUtil.equalsIgnoreCase(param == null ? null : param.getSourceSystem(), hcsProperties.getSrcSystem())) {
|
||||
log.warn("付款特批结果推送来源系统标识错误:sourceSystem={}", param == null ? null : param.getSourceSystem());
|
||||
throw new ServiceException("来源系统标识错误");
|
||||
}
|
||||
String requestId = param == null ? null : param.getRequestId();
|
||||
PaymentApplication payment = locatePayment(requestId);
|
||||
if (payment == null) {
|
||||
log.warn("付款特批结果推送未定位到付款申请:requestId={},reason={}", requestId, param == null ? null : param.getReason());
|
||||
return "未定位到付款申请(requestId=" + requestId + ")";
|
||||
}
|
||||
String normalizedStatus = param.getStatus() == null ? "" : param.getStatus().trim();
|
||||
String status;
|
||||
if (HcsSyncConstant.LEGAL_RESULT_APPROVED.equalsIgnoreCase(normalizedStatus)) {
|
||||
status = HcsSyncConstant.SPEC_STATUS_APPROVED;
|
||||
} else if (HcsSyncConstant.LEGAL_RESULT_REJECTED.equalsIgnoreCase(normalizedStatus)) {
|
||||
status = HcsSyncConstant.SPEC_STATUS_REJECTED;
|
||||
} else {
|
||||
log.warn("付款特批结果状态未识别:paymentNo={},status={},reason={}", payment.getPaymentNo(), param.getStatus(), param.getReason());
|
||||
return "状态字段未识别(paymentNo=" + payment.getPaymentNo() + ")";
|
||||
}
|
||||
syncPaymentOnSpecResult(payment, HcsSyncConstant.SPEC_STATUS_APPROVED.equals(status));
|
||||
payment.setHcsSpecStatus(status);
|
||||
paymentApplicationMapper.updateById(payment);
|
||||
// 特批通过后补推履约计划与履约记录(驳回不产生履约数据),失败不阻断后续金蝶补推
|
||||
if (HcsSyncConstant.SPEC_STATUS_APPROVED.equals(status)) {
|
||||
this.pushAfterSpecApproved(payment);
|
||||
}
|
||||
publishKingdeeAutoSyncOnSpecApproved(payment, status);
|
||||
log.info("付款特批结果推送处理完成:paymentNo={},结果={},reason={}", payment.getPaymentNo(), status, param.getReason());
|
||||
return "特批结果已处理:" + status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批通过后自动补推金蝶:审批通过时因特批挂起已跳过同步(见 KingdeeApprovalSyncListener),
|
||||
* 此处重新发布审批通过事件复用同一条自动推送链(开关、失败落库与重推兜底口径不变)
|
||||
*/
|
||||
private void publishKingdeeAutoSyncOnSpecApproved(PaymentApplication payment, String specStatus) {
|
||||
if (!HcsSyncConstant.SPEC_STATUS_APPROVED.equals(specStatus)) {
|
||||
return;
|
||||
}
|
||||
eventPublisher.publishEvent(new KingdeeBillApprovedEvent(KingdeeBillApprovedEvent.BIZ_PAYMENT_APPLICATION, payment.getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批结果联动付款审批状态(handleLegalResult 与 confirmSpecResult 共用,仅改字段,随 updateById 落库):
|
||||
* approved 恢复"审批通过"展示并放行(审批状态保持 approved,金蝶推送门禁解除);
|
||||
* rejected 联动付款审批驳回(returned + "已驳回"),走重新编辑→提交→审批→重新特批闭环
|
||||
* (提交时重新校验收款方风险与特批附件)
|
||||
*/
|
||||
private void syncPaymentOnSpecResult(PaymentApplication payment, boolean approved) {
|
||||
if (approved) {
|
||||
payment.setCurrentNode("审批通过");
|
||||
} else {
|
||||
payment.setApprovalStatus(TransportBusiConstant.RETURNED);
|
||||
payment.setCurrentNode("已驳回");
|
||||
payment.setCurrentProcessor("待处理");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 TMS 付款申请ID 定位付款申请(合规推送 requestId)
|
||||
*/
|
||||
private PaymentApplication locatePayment(String requestId) {
|
||||
if (StringUtil.isBlank(requestId)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return paymentApplicationMapper.selectById(Long.valueOf(requestId.trim()));
|
||||
} catch (NumberFormatException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填整体同步状态置 synced
|
||||
*/
|
||||
public void markSynced(PaymentApplication payment, String message) {
|
||||
payment.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
payment.setHcsRetryCount(0);
|
||||
payment.setHcsSyncTime(LocalDateTime.now());
|
||||
payment.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
paymentApplicationMapper.updateById(payment);
|
||||
log.info("HCS付款申请推送成功:paymentNo={},{}", payment.getPaymentNo(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送失败回写:failed + 重试次数+1 + 失败原因
|
||||
*/
|
||||
public void markFailed(PaymentApplication payment, String message) {
|
||||
payment.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED);
|
||||
payment.setHcsRetryCount(payment.getHcsRetryCount() == null ? 1 : payment.getHcsRetryCount() + 1);
|
||||
payment.setHcsSyncTime(LocalDateTime.now());
|
||||
payment.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
paymentApplicationMapper.updateById(payment);
|
||||
log.error("HCS付款申请推送失败:paymentNo={},retryCount={},{}", payment.getPaymentNo(), payment.getHcsRetryCount(), message);
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsAttachmentRefDTO;
|
||||
import org.springblade.hcs.dto.HcsMechanismsDTO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordFormDataDTO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordPropsDTO;
|
||||
import org.springblade.hcs.dto.HcsPerfRecordSaveDTO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsFormSaveVO;
|
||||
import org.springblade.hcs.dto.HcsUserRefVO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.HcsContractExeStage;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.service.IContractExeStageService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HCS 履约记录同步支撑组件(C108-1 新建 / C108-2 更新,设计文档 §5.6,D11)
|
||||
* <p>
|
||||
* 付款申请为主线:审批通过 → C108-1 新增(返回 fdId 回填 hcs_perf_fd_id);
|
||||
* 实际付款推进(paidAmount 变化置待同步)→ C108-2 更新(formData.fdId=hcs_perf_fd_id 必填,响应无 data)。
|
||||
* 关联履约阶段:按合同+付款类型匹配已同步的履约规则行(付款/收款执行与计划阶段对应),
|
||||
* 无关联则不传 fd_id(§5.6)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsPerfRecordSyncSupport {
|
||||
|
||||
/**
|
||||
* 履约记录附件字段标识
|
||||
*/
|
||||
private static final String PERF_ATTACH_ENTITY_KEY = "fd_plan_att";
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final IContractExeStageService contractExeStageService;
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
private final HcsAttachmentSupport hcsAttachmentSupport;
|
||||
|
||||
/**
|
||||
* 推送履约记录:无记录ID 走 C108-1 新增,有记录ID 走 C108-2 更新;记录ID 立即持久化
|
||||
*
|
||||
* @param payment 付款申请单(approvalStatus=approved)
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String pushPerfRecord(PaymentApplication payment) {
|
||||
String operatorLoginName = resolveLoginName(payment);
|
||||
if (StringUtil.isBlank(payment.getHcsPerfFdId())) {
|
||||
HcsPerfRecordSaveDTO param = buildAddParam(payment, operatorLoginName);
|
||||
log.info("HCS履约记录新建开始:paymentNo={}", payment.getPaymentNo());
|
||||
HcsResult<HcsFormSaveVO> result = hcsClient.perfRecordAdd(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdId())) {
|
||||
log.error("HCS履约记录新建失败响应:{},paymentNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo());
|
||||
throw new HcsException("C108-1 履约记录新建失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
// 履约记录ID 立即持久化(调用方必须保存)
|
||||
payment.setHcsPerfFdId(result.getData().getFdId());
|
||||
paymentApplicationMapper.updateById(payment);
|
||||
log.info("HCS履约记录新建成功:paymentNo={},perfFdId={}", payment.getPaymentNo(), payment.getHcsPerfFdId());
|
||||
return "C108-1 履约记录新建成功";
|
||||
}
|
||||
// C108-2 更新:formData.fdId 必填,响应无 data,success=true 即成功
|
||||
HcsPerfRecordSaveDTO param = buildUpdateParam(payment, operatorLoginName);
|
||||
log.info("HCS履约记录更新开始:paymentNo={},perfFdId={}", payment.getPaymentNo(), payment.getHcsPerfFdId());
|
||||
HcsResult<Void> result = hcsClient.perfRecordUpdate(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS履约记录更新失败响应:{},paymentNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo());
|
||||
throw new HcsException("C108-2 履约记录更新失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return "C108-2 履约记录更新成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* C108-1 新建入参(字段标注照 §3.4 规范,映射见设计文档 §5.6)
|
||||
*/
|
||||
private HcsPerfRecordSaveDTO buildAddParam(PaymentApplication payment, String operatorLoginName) {
|
||||
HcsPerfRecordSaveDTO param = new HcsPerfRecordSaveDTO();
|
||||
// 【必填】当前操作人 {fdLoginName}(付款申请创建人,§9-U2)
|
||||
param.setCurUser(HcsUserRefVO.ofLoginName(operatorLoginName));
|
||||
param.setFormData(buildFormData(payment));
|
||||
// 【必填】机制数据(附件):C108-1 必填,无附件传空列表(附件转传开关关闭或无附件时)
|
||||
HcsMechanismsDTO mechanisms = buildMechanisms(payment, operatorLoginName);
|
||||
if (mechanisms == null) {
|
||||
mechanisms = new HcsMechanismsDTO();
|
||||
mechanisms.setAttachment(List.of());
|
||||
}
|
||||
param.setMechanisms(mechanisms);
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* C108-2 更新入参:formData.fdId 必填,传实际金额与合同ID
|
||||
*/
|
||||
private HcsPerfRecordSaveDTO buildUpdateParam(PaymentApplication payment, String operatorLoginName) {
|
||||
HcsPerfRecordSaveDTO param = new HcsPerfRecordSaveDTO();
|
||||
param.setCurUser(HcsUserRefVO.ofLoginName(operatorLoginName));
|
||||
HcsPerfRecordFormDataDTO formData = buildFormData(payment);
|
||||
// 【必填】履约记录ID(C108-1 新建返回)
|
||||
formData.setFdId(payment.getHcsPerfFdId());
|
||||
param.setFormData(formData);
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 履约记录 formData(新建/更新共用)
|
||||
*/
|
||||
private HcsPerfRecordFormDataDTO buildFormData(PaymentApplication payment) {
|
||||
ContractManage contractManage = payment.getContractId() == null ? null : contractManageMapper.selectById(payment.getContractId());
|
||||
HcsContractExeStage stageRow = findPaymentStageRow(payment);
|
||||
HcsPerfRecordFormDataDTO formData = new HcsPerfRecordFormDataDTO();
|
||||
HcsPerfRecordPropsDTO props = new HcsPerfRecordPropsDTO();
|
||||
// 申请金额:appliedAmount(接口按字符串传值)
|
||||
props.setFdApplyAmount(payment.getAppliedAmount() == null ? null : payment.getAppliedAmount().toPlainString());
|
||||
// 实际金额:paidAmount(实际付款推进后更新)
|
||||
props.setFdFactAmount(payment.getPaidAmount() == null ? null : payment.getPaidAmount().toPlainString());
|
||||
// 【必填】申请编号:付款申请单号
|
||||
props.setFdApplyNo(payment.getPaymentNo());
|
||||
// 【必填】计划日期:applyDate(毫秒时间戳)
|
||||
props.setFdPlanDate(toMillis(payment.getApplyDate()));
|
||||
// 计划类型:paymentType(原文拼写 fd_paln_type 必须沿用)
|
||||
props.setFdPalnType(payment.getPaymentType());
|
||||
// 性质:款项性质取关联履约规则行的款项性质(无关联不传)
|
||||
props.setFdProperty(stageRow == null ? null : stageRow.getFundNature());
|
||||
// 来源履约计划项ID:关联履约阶段 hcs_stage_fd_id(无关联则不传,§5.6)
|
||||
props.setFdId(stageRow == null ? null : stageRow.getHcsStageFdId());
|
||||
// 【必填】合同ID:HCS 合同ID(合同未推送时为空,联调确认兜底口径)
|
||||
props.setFdContractId(contractManage == null ? null : contractManage.getHcsFdId());
|
||||
// 业务ID:TMS 付款申请单主键(方便溯源,v4 新增)
|
||||
props.setFdBusinessId(payment.getId() == null ? null : String.valueOf(payment.getId()));
|
||||
// 创建时间:createTime(毫秒时间戳,v4 新增)
|
||||
props.setFdCreateTime(payment.getCreateTime() == null ? null : payment.getCreateTime().toInstant().toEpochMilli());
|
||||
// 表单模板ID:配置 perf-record.xform-id(环境值,§9-U9,未配置不传)
|
||||
String xformId = StringUtil.isBlank(hcsProperties.getPerfRecord().getXformId()) ? null : hcsProperties.getPerfRecord().getXformId();
|
||||
props.setFdXformId(xformId);
|
||||
// 【必填】表单模板ID(formData 顶层 fd_xform_id,v4 必填)
|
||||
formData.setFdXformId(xformId);
|
||||
formData.setDynamicProps(props);
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 履约记录附件挂载(无附件或开关关闭返回 null)
|
||||
*/
|
||||
private HcsMechanismsDTO buildMechanisms(PaymentApplication payment, String operatorLoginName) {
|
||||
if (!hcsProperties.getAttachment().isTransferEnabled() || StringUtil.isBlank(payment.getAttachmentsJson())) {
|
||||
return null;
|
||||
}
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = hcsAttachmentSupport.transferAttachments(
|
||||
HcsSyncConstant.BIZ_TYPE_PAYMENT, payment.getId(), payment.getAttachmentsJson(), operatorLoginName);
|
||||
if (attachmentRefs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
HcsMechanismsDTO mechanisms = new HcsMechanismsDTO();
|
||||
mechanisms.setAttachment(attachmentRefs.stream().map(ref -> {
|
||||
// 附件ID(C103-2 转传返回)
|
||||
ref.setFdEntityKey(PERF_ATTACH_ENTITY_KEY);
|
||||
return ref;
|
||||
}).toList());
|
||||
return mechanisms;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配合同的付款履约计划行(优先按付款类型映射的款项性质精确匹配,无则取首条已同步付款行兜底)
|
||||
*/
|
||||
private HcsContractExeStage findPaymentStageRow(PaymentApplication payment) {
|
||||
return contractExeStageService.findPaymentStageRow(payment.getContractId(),
|
||||
HcsSyncConstant.fundNatureOfPayment(payment.getPaymentType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作人登录名(thirdParty.hcs.operator-login-name,默认 srvadmin,§9-U2)
|
||||
*/
|
||||
private String resolveLoginName(PaymentApplication payment) {
|
||||
return hcsProperties.getOperatorLoginName();
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDate → 毫秒时间戳
|
||||
*/
|
||||
private Long toMillis(LocalDate localDate) {
|
||||
return localDate == null ? null : localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
}
|
||||
+467
@@ -0,0 +1,467 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsBpmOperationDTO;
|
||||
import org.springblade.hcs.dto.HcsFormSaveVO;
|
||||
import org.springblade.hcs.dto.HcsPageQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsPageVO;
|
||||
import org.springblade.hcs.dto.HcsProjectAddDTO;
|
||||
import org.springblade.hcs.dto.HcsProjectFormDataDTO;
|
||||
import org.springblade.hcs.dto.HcsProjectLegalResultDTO;
|
||||
import org.springblade.hcs.dto.HcsProjectPropsDTO;
|
||||
import org.springblade.hcs.dto.HcsProjectUpdateDTO;
|
||||
import org.springblade.hcs.dto.HcsProjectVO;
|
||||
import org.springblade.hcs.dto.HcsRefVO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsSaveOptionDTO;
|
||||
import org.springblade.hcs.dto.HcsUserRefVO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsFieldConstant;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.HcsContractCheckLogMapper;
|
||||
import org.springblade.transport.mapper.ProjectApplyMapper;
|
||||
import org.springblade.transport.pojo.entity.HcsContractCheckLog;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 项目立项(C102)同步支撑组件(设计文档 §5.1)
|
||||
* <p>
|
||||
* 触发与链路:审批通过(approved)→ C102-1 新建;变更审批通过(change_approved,hcs_fd_id 已存在)→ C102-2 更新。
|
||||
* 推送前判重:C102-3 conditions={"fd_num": projectCode},命中则直接回填 fdId 置 synced(跳过新建,§9-U4 查询能力联调确认)。
|
||||
* 失败不在此回写,由调用方(HcsPushTask/手动端点)统一 {@link #markFailed} 落 failed 状态进补偿重试。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsProjectSyncSupport {
|
||||
|
||||
/**
|
||||
* TMS 万元 → HCS 元换算倍率
|
||||
*/
|
||||
private static final BigDecimal WAN_TO_YUAN = BigDecimal.valueOf(10000);
|
||||
|
||||
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ProjectApplyMapper projectApplyMapper;
|
||||
private final HcsContractCheckLogMapper hcsContractCheckLogMapper;
|
||||
|
||||
/**
|
||||
* 推送项目立项到 HCS:判重 → 新建(C102-1)/ 更新(C102-2)→ 回填合规 fdId 置 synced
|
||||
*
|
||||
* @param projectApply 项目立项单(approvalStatus 须为 approved/change_approved)
|
||||
* @return 推送结果描述(供任务汇总与 hcs_sync_msg 落库)
|
||||
*/
|
||||
public String pushProject(ProjectApply projectApply) {
|
||||
// 首推先判重(C102-3),命中直接回填跳过新建;已有合规ID(变更单)走更新
|
||||
if (StringUtil.isBlank(projectApply.getHcsFdId())) {
|
||||
String existFdId = findExistFdId(projectApply);
|
||||
if (StringUtil.isNotBlank(existFdId)) {
|
||||
markSynced(projectApply, existFdId, "判重命中,回填已有合规项目ID");
|
||||
return "判重命中,回填已有合规项目ID";
|
||||
}
|
||||
String fdId = addProject(projectApply);
|
||||
String addMessage = "项目立项新建成功:" + projectApply.getProjectCode();
|
||||
markSynced(projectApply, fdId, addMessage);
|
||||
return addMessage;
|
||||
}
|
||||
updateProject(projectApply);
|
||||
String updateMessage = "项目立项更新成功:" + projectApply.getProjectCode();
|
||||
markSynced(projectApply, projectApply.getHcsFdId(), updateMessage);
|
||||
return updateMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* C102-1 项目立项新建,返回合规项目 fdId
|
||||
*/
|
||||
private String addProject(ProjectApply projectApply) {
|
||||
String operatorLoginName = requireLoginName(projectApply, "C102-1");
|
||||
HcsProjectAddDTO param = new HcsProjectAddDTO();
|
||||
|
||||
HcsBpmOperationDTO bpmOperation = new HcsBpmOperationDTO();
|
||||
// 【必填】操作类型:固定 drafter_submit(以起草人身份直接提交流程)
|
||||
bpmOperation.setOperationType("drafter_submit");
|
||||
// 【必填】立项表单模板编码:配置 thirdParty.hcs.project.template-code(环境值,§9-U9)
|
||||
bpmOperation.setTemplateCode(requireTemplateCode());
|
||||
param.setBpmOperation(bpmOperation);
|
||||
// 是否校验权限:接口调用固定 false
|
||||
param.setCheckAuth(false);
|
||||
// 【必填】当前操作人:项目经办人登录名(§9-U2 登录名同源性联调确认)
|
||||
param.setCurUser(HcsUserRefVO.ofLoginName(operatorLoginName));
|
||||
param.setFormData(buildFormData(projectApply, null));
|
||||
HcsSaveOptionDTO saveOption = new HcsSaveOptionDTO();
|
||||
// 【必填】是否保存草稿:saveDraft=false 直接提交
|
||||
saveOption.setSaveDraft(false);
|
||||
param.setSaveOption(saveOption);
|
||||
|
||||
log.info("HCS项目立项推送开始:{}(C102-1 新建)", projectApply.getProjectCode());
|
||||
HcsResult<HcsFormSaveVO> result = hcsClient.projectAdd(param);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdId())) {
|
||||
log.error("HCS项目立项新建失败响应:{},projectCode={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), projectApply.getProjectCode());
|
||||
throw new HcsException("C102-1 项目立项新建失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* C102-2 项目立项更新(入参同新建,去掉 bpmOperation/saveOption,formData.fdId 必填)
|
||||
*/
|
||||
private void updateProject(ProjectApply projectApply) {
|
||||
String operatorLoginName = requireLoginName(projectApply, "C102-2");
|
||||
HcsProjectUpdateDTO param = new HcsProjectUpdateDTO();
|
||||
// 是否校验权限:接口调用固定 false
|
||||
param.setCheckAuth(false);
|
||||
// 【必填】当前操作人:项目经办人登录名
|
||||
param.setCurUser(HcsUserRefVO.ofLoginName(operatorLoginName));
|
||||
// 更新必须携带合规项目 fdId
|
||||
param.setFormData(buildFormData(projectApply, projectApply.getHcsFdId()));
|
||||
|
||||
log.info("HCS项目立项推送开始:{}(C102-2 更新)", projectApply.getProjectCode());
|
||||
HcsResult<Void> result = hcsClient.projectUpdate(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS项目立项更新失败响应:{},projectCode={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), projectApply.getProjectCode());
|
||||
throw new HcsException("C102-2 项目立项更新失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装项目立项 formData(新建/更新共用,字段标注照 §3.4 规范,映射表见设计文档 §5.1)
|
||||
*/
|
||||
private HcsProjectFormDataDTO buildFormData(ProjectApply projectApply, String fdId) {
|
||||
HcsProjectFormDataDTO formData = new HcsProjectFormDataDTO();
|
||||
// 合规项目 fdId(C102-2 更新必填,C102-1 不传)
|
||||
formData.setFdId(fdId);
|
||||
HcsProjectPropsDTO props = new HcsProjectPropsDTO();
|
||||
// 【必填】项目名称:ProjectApply.projectName
|
||||
props.setFdProName(projectApply.getProjectName());
|
||||
|
||||
// erp请求id
|
||||
Long id = projectApply.getId();
|
||||
props.setFdErpRequestId(String.valueOf(id));
|
||||
|
||||
// 项目简称:ProjectApply.projectShortName
|
||||
props.setFdProSimpleName(projectApply.getProjectShortName());
|
||||
// 【必填】项目编号:ProjectApply.projectCode(判重候选键)
|
||||
props.setFdNum(projectApply.getProjectCode());
|
||||
// 创建单位:固定取配置 thirdParty.hcs.creator-unit-id/name(HCS 组织主数据,§9-U2;任一未配置回退开题部门名称仅传 fdName,其他合同接口同用此配置)
|
||||
HcsRefVO creatorUnit = hcsProperties.resolveCreatorUnit(projectApply.getBusinessDeptName());
|
||||
props.setFdCreatorUnit(creatorUnit);
|
||||
// 项目负责人:负责人登录名 {fdLoginName}(§9-U2)
|
||||
props.setFdHead(resolveUserRef(projectApply.getPrincipalUserId()));
|
||||
// todo 承接部门:与创建单位一致,取配置 thirdParty.hcs.creator-unit-id/name(测试线联调固定广西物产城矿发展有限公司)
|
||||
props.setFdTakerDept(creatorUnit);
|
||||
// 承接人:项目经办人登录名 {fdLoginName}
|
||||
props.setFdTaker(resolveUserRef(projectApply.getHandlerUserId()));
|
||||
// 付款额度:TMS fundLimit(万元)× 10000 转 HCS 元
|
||||
props.setFdPayLimit(toYuan(projectApply.getFundLimit()));
|
||||
// 开票额度:TMS receivableLimit(万元)× 10000 转 HCS 元
|
||||
props.setFdOutInvoiceLimit(toYuan(projectApply.getReceivableLimit()));
|
||||
// 项目建设内容:ProjectApply.situationRemark
|
||||
props.setFdProContent(projectApply.getSituationRemark());
|
||||
// 【必填】是否可用:固定 true
|
||||
props.setFdIsAvailable(true);
|
||||
// 可用单位列表:与创建单位一致(同走 creator-unit 配置)(废弃)
|
||||
// props.setFdAvailUnits(List.of(creatorUnit));
|
||||
|
||||
// 结算类型:TMS settle_method 字典文本映射 HCS 编码(1=先款后票,2=先票后款,3=先款后票+先票后款(同时存在))
|
||||
props.setFdSettlementType(mapSettlementType(projectApply.getSettlementMode()));
|
||||
// 计划保证金:TMS fundDemand 与 HCS fd_pro_plan_deposit 单位同为万元,直传不换算(接口说明 §5.1)
|
||||
props.setFdProPlanDeposit(projectApply.getFundDemand());
|
||||
// 是否自营项目:一期固定 "2"。是|1, 否|2
|
||||
props.setFdIsSelfPro("2");
|
||||
// TODO 待确认:项目收入目标 fd_pro_income_goal,TMS 无直接对应字段,一期不传(§5.1)
|
||||
// 金蝶同步状态:固定 "1" 推送成功|1
|
||||
// props.setFdKingdeeSyncStatus("1");
|
||||
// 【必填】来源标识:fd_sources,配置 thirdParty.hcs.src-system(约定值 WLTMS,D14)
|
||||
props.setFdSources(hcsProperties.getSrcSystem());
|
||||
|
||||
// 金蝶项目立项ID 来源系统主键 ID:TMS 项目立项单主键(D14,对账/幂等候选键)
|
||||
// props.setFdKingdeeId(String.valueOf(projectApply.getId()));
|
||||
|
||||
// 创建人:项目经办人登录名(扫描推送无登录上下文,取单据承办人,§9-U2)
|
||||
props.setFdCreator(resolveUserRef(projectApply.getHandlerUserId()));
|
||||
formData.setDynamicProps(props);
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结算方式字典文本映射 HCS 编码:先款后票=1、先票后款=2、先款后票+先票后款(同时存在)=3;未匹配时原值直传并告警
|
||||
*/
|
||||
private String mapSettlementType(String settlementMode) {
|
||||
if (StringUtil.isBlank(settlementMode)) {
|
||||
return null;
|
||||
}
|
||||
String code = switch (settlementMode.trim()) {
|
||||
case "先款后票" -> "1";
|
||||
case "先票后款" -> "2";
|
||||
case "先款后票+先票后款(同时存在)" -> "3";
|
||||
default -> null;
|
||||
};
|
||||
if (code == null) {
|
||||
log.warn("HCS项目立项结算类型未匹配编码,原值直传:settlementMode=[{}]", settlementMode);
|
||||
return settlementMode;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* C102-3 项目立项查询判重(conditions={"fd_num": projectCode},§9-U4 查询能力联调确认)
|
||||
*/
|
||||
private String findExistFdId(ProjectApply projectApply) {
|
||||
HcsPageQueryDTO query = new HcsPageQueryDTO();
|
||||
// C102-3 入参 columns 用下划线字段名
|
||||
query.setColumns(List.of(HcsFieldConstant.PROJ_FD_ID, HcsFieldConstant.PROJ_FD_NUM));
|
||||
// 判重条件:项目编号精确匹配
|
||||
query.setConditions(Map.of(HcsFieldConstant.PROJ_FD_NUM, Objects.toString(projectApply.getProjectCode(), "")));
|
||||
query.setOffset(0);
|
||||
query.setPageSize(10);
|
||||
query.setSorts(Map.of(HcsFieldConstant.PROJ_FD_CREATE_TIME, "desc"));
|
||||
// 查询需携带当前操作人(复用单据承办人登录名)
|
||||
query.setCurUser(HcsUserRefVO.ofLoginName(requireLoginName(projectApply, "C102-3")));
|
||||
HcsResult<HcsPageVO<HcsProjectVO>> result = hcsClient.projectList(query);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS项目立项判重查询失败响应:{},projectCode={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), projectApply.getProjectCode());
|
||||
throw new HcsException("C102-3 项目立项判重查询失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
HcsPageVO<HcsProjectVO> page = result.getData();
|
||||
if (page == null || page.getContent() == null || page.getContent().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return page.getContent().get(0).getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目金蝶同步结果接收处理(合规 projectLegal/result 推送,D902):
|
||||
* sourceSystem 须为 (不符抛"来源系统标识错误"异常);projectCode=TMS 项目编号定位(requestId 仅 X-ERP-Request-Id 回显不可定位);
|
||||
* success=true 回填金蝶项目ID/编号(kingdeeId/kingdeeNumber);success=false 记录失败原因;原文留痕 blade_hcs_check_log
|
||||
*
|
||||
* @param param 合规推送报文
|
||||
* @return 处理结果描述
|
||||
*/
|
||||
public String handleLegalResult(HcsProjectLegalResultDTO param) {
|
||||
// 来源系统校验:仅接受合规方约定的 ,防伪造回传
|
||||
if (!StrUtil.equalsIgnoreCase(param == null ? null : param.getSourceSystem(), hcsProperties.getSrcSystem())) {
|
||||
log.warn("项目金蝶同步结果推送来源系统标识错误:sourceSystem={}", param == null ? null : param.getSourceSystem());
|
||||
throw new ServiceException("来源系统标识错误");
|
||||
}
|
||||
String raw = JSONUtil.toJsonStr(param);
|
||||
ProjectApply projectApply = locateProjectByCode(param.getProjectCode());
|
||||
if (projectApply == null) {
|
||||
log.warn("项目金蝶同步结果推送未定位到项目:projectCode={},报文已留痕", param.getProjectCode());
|
||||
writeCallbackLog(null, HcsSyncConstant.CHECK_RESULT_SKIP, "项目金蝶同步结果推送未定位到项目:" + raw);
|
||||
return "已留痕:未定位到项目(projectCode=" + param.getProjectCode() + ")";
|
||||
}
|
||||
if (param.getSuccess() == null) {
|
||||
log.warn("项目金蝶同步结果推送 success 字段缺失:projectCode={},报文已留痕", projectApply.getProjectCode());
|
||||
writeCallbackLog(projectApply, HcsSyncConstant.CHECK_RESULT_SKIP, "项目金蝶同步结果推送 success 字段缺失:" + raw);
|
||||
return "已留痕:success 字段缺失(projectCode=" + projectApply.getProjectCode() + ")";
|
||||
}
|
||||
if (param.getSuccess()) {
|
||||
// 成功:回填金蝶项目ID/编号,同步结果信息随 hcs_sync_msg 留痕
|
||||
projectApply.setKingdeeId(param.getKingdeeId());
|
||||
projectApply.setKingdeeNumber(param.getProjectNumber());
|
||||
projectApply.setHcsSyncMsg(HcsSyncConstant.truncateMsg("项目金蝶同步结果回调:金蝶同步成功,kingdeeId=" + param.getKingdeeId()
|
||||
+ ",projectNumber=" + param.getProjectNumber()));
|
||||
appendKingdeeResultRecord(projectApply, "金蝶同步成功:金蝶项目ID=" + param.getKingdeeId()
|
||||
+ ",金蝶项目编号=" + param.getProjectNumber());
|
||||
projectApplyMapper.updateById(projectApply);
|
||||
writeCallbackLog(projectApply, HcsSyncConstant.CHECK_RESULT_PASS, "项目金蝶同步结果推送:" + raw);
|
||||
log.info("项目金蝶同步结果推送处理完成:projectCode={},结果=success,kingdeeId={}", projectApply.getProjectCode(), param.getKingdeeId());
|
||||
return "金蝶同步结果已处理:success";
|
||||
}
|
||||
// 失败:记录失败原因(金蝶同步在合规侧完成,TMS 不重推,失败重试由合规侧负责)
|
||||
projectApply.setHcsSyncMsg(HcsSyncConstant.truncateMsg("项目金蝶同步结果回调:金蝶同步失败:" + param.getReason()));
|
||||
appendKingdeeResultRecord(projectApply, "金蝶同步失败:" + param.getReason());
|
||||
projectApplyMapper.updateById(projectApply);
|
||||
writeCallbackLog(projectApply, HcsSyncConstant.CHECK_RESULT_FAIL, "项目金蝶同步结果推送:" + raw);
|
||||
log.info("项目金蝶同步结果推送处理完成:projectCode={},结果=fail,reason={}", projectApply.getProjectCode(), param.getReason());
|
||||
return "金蝶同步结果已处理:fail";
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶同步结果回填变更记录(changeRecordJson 追加一条;系统回调无登录用户,操作人记来源标识)
|
||||
*/
|
||||
private void appendKingdeeResultRecord(ProjectApply projectApply, String changeContent) {
|
||||
List<Object> records = StringUtil.isBlank(projectApply.getChangeRecordJson())
|
||||
? new ArrayList<>() : new ArrayList<>(JSONUtil.toList(projectApply.getChangeRecordJson(), Object.class));
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("changeDate", LocalDate.now().toString());
|
||||
record.put("handlerUserName", "合规系统回调");
|
||||
record.put("changeType", "金蝶同步结果");
|
||||
record.put("changeContent", changeContent);
|
||||
record.put("status", projectApply.getApprovalStatus());
|
||||
record.put("statusName", HcsSyncConstant.approvalStatusName(projectApply.getApprovalStatus()));
|
||||
records.add(record);
|
||||
projectApply.setChangeRecordJson(JSONUtil.toJsonStr(records));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ERP 项目编号定位项目(合规推送 projectCode,对应 C102 fd_num)
|
||||
*/
|
||||
private ProjectApply locateProjectByCode(String projectCode) {
|
||||
if (StringUtil.isBlank(projectCode)) {
|
||||
return null;
|
||||
}
|
||||
return projectApplyMapper.selectOne(Wrappers.<ProjectApply>lambdaQuery()
|
||||
.eq(ProjectApply::getProjectCode, projectCode.trim())
|
||||
.last("limit 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目金蝶同步结果推送留痕(blade_hcs_check_log,biz_type=project,项目未定位到时 bizId 为空,异常不影响主流程)
|
||||
*/
|
||||
private void writeCallbackLog(ProjectApply projectApply, String result, String msg) {
|
||||
try {
|
||||
HcsContractCheckLog checkLog = new HcsContractCheckLog();
|
||||
if (projectApply != null) {
|
||||
checkLog.setTenantId(projectApply.getTenantId());
|
||||
checkLog.setBizId(projectApply.getId());
|
||||
}
|
||||
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_PROJECT);
|
||||
checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_KINGDEE);
|
||||
checkLog.setResult(result);
|
||||
checkLog.setMsg(StringUtil.left(msg, HcsSyncConstant.SYNC_MSG_MAX_LENGTH));
|
||||
checkLog.setCreateTime(LocalDateTime.now());
|
||||
hcsContractCheckLogMapper.insert(checkLog);
|
||||
} catch (Exception exception) {
|
||||
log.error("项目金蝶同步结果推送留痕写入失败:projectCode={}", projectApply == null ? null : projectApply.getProjectCode(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条手动重推:重置为待同步并清零重试计数后立即推送(页面"推送合规/重推"按钮入口)
|
||||
*
|
||||
* @param id 项目立项单ID
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String repushProject(Long id) {
|
||||
ProjectApply projectApply = projectApplyMapper.selectById(id);
|
||||
if (projectApply == null) {
|
||||
throw new ServiceException("项目立项单不存在:" + id);
|
||||
}
|
||||
if (!List.of("approved", "change_approved").contains(projectApply.getApprovalStatus())) {
|
||||
throw new ServiceException("仅审批通过的项目立项可推送合规(当前状态:" + projectApply.getApprovalStatus() + ")");
|
||||
}
|
||||
projectApply.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
projectApply.setHcsRetryCount(0);
|
||||
projectApplyMapper.updateById(projectApply);
|
||||
try {
|
||||
return pushProject(projectApply);
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
markFailed(projectApply, message);
|
||||
throw new ServiceException("HCS项目立项推送失败:" + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填合规 fdId 并置 synced
|
||||
*/
|
||||
public void markSynced(ProjectApply projectApply, String fdId, String message) {
|
||||
projectApply.setHcsFdId(fdId);
|
||||
projectApply.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
projectApply.setHcsRetryCount(0);
|
||||
projectApply.setHcsSyncTime(LocalDateTime.now());
|
||||
projectApply.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
projectApplyMapper.updateById(projectApply);
|
||||
log.info("HCS项目立项推送成功:projectCode={},fdId={},{}", projectApply.getProjectCode(), fdId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送失败回写:failed + 重试次数+1 + 失败原因(供扫描任务按 hcs_retry_count < max-retry 自动补偿)
|
||||
*/
|
||||
public void markFailed(ProjectApply projectApply, String message) {
|
||||
projectApply.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED);
|
||||
projectApply.setHcsRetryCount(projectApply.getHcsRetryCount() == null ? 1 : projectApply.getHcsRetryCount() + 1);
|
||||
projectApply.setHcsSyncTime(LocalDateTime.now());
|
||||
projectApply.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
projectApplyMapper.updateById(projectApply);
|
||||
log.error("HCS项目立项推送失败:projectCode={},retryCount={},{}", projectApply.getProjectCode(), projectApply.getHcsRetryCount(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立项表单模板编码(未配置直接失败,提示配置项,§9-U9)
|
||||
*/
|
||||
private String requireTemplateCode() {
|
||||
String templateCode = hcsProperties.getProject().getTemplateCode();
|
||||
if (StringUtil.isBlank(templateCode)) {
|
||||
throw new HcsException("HCS项目立项推送失败,立项表单模板编码未配置(thirdParty.hcs.project.template-code,§9-U9)");
|
||||
}
|
||||
return templateCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作人登录名(curUser/fd_head/fd_taker/fd_creator 统一取配置,§9-U2)
|
||||
*/
|
||||
private String requireLoginName(ProjectApply projectApply, String scene) {
|
||||
return resolveLoginName(projectApply.getHandlerUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按用户ID 解析登录名引用(查不到返回 null,调用方按字段必填性兜底)
|
||||
*/
|
||||
private HcsUserRefVO resolveUserRef(Long userId) {
|
||||
String loginName = resolveLoginName(userId);
|
||||
return StringUtil.isBlank(loginName) ? null : HcsUserRefVO.ofLoginName(loginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作人登录名(thirdParty.hcs.operator-login-name,默认 srvadmin)
|
||||
*/
|
||||
private String resolveLoginName(Long userId) {
|
||||
return hcsProperties.getOperatorLoginName();
|
||||
}
|
||||
|
||||
/**
|
||||
* TMS 万元 → HCS 元
|
||||
*/
|
||||
private BigDecimal toYuan(BigDecimal wanAmount) {
|
||||
return wanAmount == null ? null : wanAmount.multiply(WAN_TO_YUAN);
|
||||
}
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsAttachmentRefDTO;
|
||||
import org.springblade.hcs.dto.HcsFormSaveVO;
|
||||
import org.springblade.hcs.dto.HcsMechanismsDTO;
|
||||
import org.springblade.hcs.dto.HcsReceiptBillFormDataDTO;
|
||||
import org.springblade.hcs.dto.HcsReceiptBillPropsDTO;
|
||||
import org.springblade.hcs.dto.HcsReceiptBillSaveDTO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementMapper;
|
||||
import org.springblade.transport.mapper.InvoiceReceiptMapper;
|
||||
import org.springblade.transport.mapper.InvoiceReceiptSettlementMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceipt;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceiptSettlement;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HCS 收票单同步支撑组件(C109-1 新建 / C109-2 更新,设计文档 §5.8,D13)
|
||||
* <p>
|
||||
* 审批通过 → C109-1;票据变更 → C109-2(顶层 fdId=hcs_fd_id 必填,只传变更字段、不带文档状态;
|
||||
* v4 无作废字段,作废推送待联调确认)。合同关联:应付结算链路回溯(收票 → 应收/应付结算关联 → 结算单合同),
|
||||
* 回溯不到时取页面人工补录的 hcs_contract_id/hcs_contract_no(D13)。
|
||||
* fd_ap_bill_src_subject(认领应付单号键)待联调(§9-U11),一期不传。
|
||||
* 返回双层嵌套 data.data.fdId → hcs_fd_id。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsReceiptBillSyncSupport {
|
||||
|
||||
/**
|
||||
* 收票单附件字段标识
|
||||
*/
|
||||
private static final String RECEIPT_ATTACH_ENTITY_KEY = "fd_attach";
|
||||
|
||||
/**
|
||||
* 收票日期格式(fd_bill_date 为 YYYY-MM-DD 字符串,非毫秒时间戳)
|
||||
*/
|
||||
private static final DateTimeFormatter BILL_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final InvoiceReceiptMapper invoiceReceiptMapper;
|
||||
private final InvoiceReceiptSettlementMapper invoiceReceiptSettlementMapper;
|
||||
private final FormalSettlementMapper formalSettlementMapper;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final HcsAttachmentSupport hcsAttachmentSupport;
|
||||
|
||||
/**
|
||||
* 推送收票单:无合规ID 走 C109-1 新建,有合规ID 走 C109-2 更新;整体状态由调用方回写
|
||||
*
|
||||
* @param invoiceReceipt 收票单(approvalStatus=approved)
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String pushReceiptBill(InvoiceReceipt invoiceReceipt) {
|
||||
String operatorLoginName = resolveLoginName(invoiceReceipt);
|
||||
if (StringUtil.isBlank(invoiceReceipt.getHcsFdId())) {
|
||||
HcsReceiptBillSaveDTO param = buildSaveParam(invoiceReceipt, operatorLoginName, true);
|
||||
log.info("HCS收票单新建开始:invoiceNo={}", invoiceReceipt.getInvoiceNo());
|
||||
HcsResult<HcsResult<HcsFormSaveVO>> result = hcsClient.addReceiveBill(param);
|
||||
String fdId = extractNestedFdId(invoiceReceipt, result);
|
||||
// 收票单ID 立即持久化(调用方必须保存)
|
||||
invoiceReceipt.setHcsFdId(fdId);
|
||||
invoiceReceiptMapper.updateById(invoiceReceipt);
|
||||
log.info("HCS收票单新建成功:invoiceNo={},fdId={}", invoiceReceipt.getInvoiceNo(), fdId);
|
||||
return "C109-1 收票单新建成功";
|
||||
}
|
||||
// C109-2 更新:顶层 fdId 必填,只传变更字段、不带文档状态
|
||||
HcsReceiptBillSaveDTO param = buildSaveParam(invoiceReceipt, operatorLoginName, false);
|
||||
log.info("HCS收票单更新开始:invoiceNo={},fdId={}", invoiceReceipt.getInvoiceNo(), invoiceReceipt.getHcsFdId());
|
||||
HcsResult<HcsResult<Void>> result = hcsClient.updateReceiveBill(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS收票单更新失败响应:{},invoiceNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), invoiceReceipt.getInvoiceNo());
|
||||
throw new HcsException("C109-2 收票单更新失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return "C109-2 收票单更新成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* 单条手动重推:重置为待同步并清零重试计数后立即推送
|
||||
*
|
||||
* @param id 收票单ID
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String repushReceiptBill(Long id) {
|
||||
InvoiceReceipt invoiceReceipt = invoiceReceiptMapper.selectById(id);
|
||||
if (invoiceReceipt == null) {
|
||||
throw new ServiceException("收票单不存在:" + id);
|
||||
}
|
||||
if (!"approved".equals(invoiceReceipt.getApprovalStatus())) {
|
||||
throw new ServiceException("仅审批通过的收票单可推送合规(当前状态:" + invoiceReceipt.getApprovalStatus() + ")");
|
||||
}
|
||||
invoiceReceipt.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_UNSYNCED);
|
||||
invoiceReceipt.setHcsRetryCount(0);
|
||||
invoiceReceiptMapper.updateById(invoiceReceipt);
|
||||
try {
|
||||
return pushReceiptBill(invoiceReceipt);
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
markFailed(invoiceReceipt, message);
|
||||
throw new ServiceException("HCS收票单推送失败:" + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 C109-1/C109-2 入参(字段标注照 §3.4 规范,映射表见设计文档 §5.8)
|
||||
*/
|
||||
private HcsReceiptBillSaveDTO buildSaveParam(InvoiceReceipt invoiceReceipt, String operatorLoginName, boolean add) {
|
||||
HcsReceiptBillSaveDTO param = new HcsReceiptBillSaveDTO();
|
||||
// 收票单主键:新建不传,更新必填(顶层 fdId)
|
||||
param.setFdId(invoiceReceipt.getHcsFdId());
|
||||
// 【必填】操作人登录名:收票单创建人登录名(§9-U2)
|
||||
param.setFdLoginName(operatorLoginName);
|
||||
HcsReceiptBillFormDataDTO formData = new HcsReceiptBillFormDataDTO();
|
||||
HcsReceiptBillPropsDTO props = new HcsReceiptBillPropsDTO();
|
||||
// 【必填】发票号码:invoiceNo
|
||||
props.setFdInvoiceCode(invoiceReceipt.getInvoiceNo());
|
||||
// 【必填】开票/收票日期:invoiceDate(YYYY-MM-DD 字符串)
|
||||
props.setFdBillDate(formatBillDate(invoiceReceipt.getInvoiceDate()));
|
||||
// 开票方(销方):issuerName
|
||||
props.setFdInvoicer(invoiceReceipt.getIssuerName());
|
||||
// 收票方(购方):receiverName
|
||||
props.setFdBuyer(invoiceReceipt.getReceiverName());
|
||||
// 【必填】价税合计金额:invoiceAmount
|
||||
props.setFdInvoiceAmtTaxIncl(invoiceReceipt.getInvoiceAmount());
|
||||
// 【必填】税率(小数):TMS taxRate(如 13)÷ 100 → 0.13
|
||||
props.setFdTaxRate(toTaxRate(invoiceReceipt.getTaxRate()));
|
||||
// 【必填】发票类型:receipt-bill.invoice-type-mapping 字典映射(§9-U3,未配置不传)
|
||||
props.setFdInvoiceType(hcsProperties.getReceiptBill().getInvoiceTypeMapping().get(invoiceReceipt.getInvoiceType()));
|
||||
// 【必填】合同主题/编号:应付结算链路回溯,回溯不到取人工补录(D13,均为空不传待联调兜底口径)
|
||||
ContractReference contractReference = resolveContract(invoiceReceipt);
|
||||
props.setFdContractSubject(contractReference.subject());
|
||||
props.setFdContractNo(contractReference.contractNo());
|
||||
// TODO 待联调(§9-U11):fd_ap_bill_src_subject 关联结算付款单的编号体系(应付单号键)v4 未明确,一期不传
|
||||
// 备注:remark
|
||||
props.setFdRemark(invoiceReceipt.getRemark());
|
||||
formData.setDynamicProps(props);
|
||||
param.setFormData(formData);
|
||||
// 附件挂载(发票扫描件):fdEntityKey=fd_attach,发起带 fdBindType=ADD,更新不带
|
||||
if (hcsProperties.getAttachment().isTransferEnabled() && StringUtil.isNotBlank(invoiceReceipt.getAttachmentsJson())) {
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = hcsAttachmentSupport.transferAttachments(
|
||||
HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT, invoiceReceipt.getId(),
|
||||
invoiceReceipt.getAttachmentsJson(), operatorLoginName);
|
||||
if (!attachmentRefs.isEmpty()) {
|
||||
HcsMechanismsDTO mechanisms = new HcsMechanismsDTO();
|
||||
mechanisms.setAttachment(attachmentRefs.stream().map(ref -> {
|
||||
// 附件ID(C103-2 转传返回)
|
||||
ref.setFdEntityKey(RECEIPT_ATTACH_ENTITY_KEY);
|
||||
// 绑定类型:仅发起传 ADD
|
||||
ref.setFdBindType(add ? "ADD" : null);
|
||||
return ref;
|
||||
}).toList());
|
||||
param.setMechanisms(mechanisms);
|
||||
}
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 C109-1 双层嵌套返回 data.data.fdId
|
||||
*/
|
||||
private String extractNestedFdId(InvoiceReceipt invoiceReceipt, HcsResult<HcsResult<HcsFormSaveVO>> result) {
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || !result.getData().isSuccess()
|
||||
|| result.getData().getData() == null || StringUtil.isBlank(result.getData().getData().getFdId())) {
|
||||
log.error("HCS收票单新建失败响应:{},invoiceNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), invoiceReceipt.getInvoiceNo());
|
||||
throw new HcsException("C109-1 收票单新建失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getData().getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同引用解析:收票 → 应收结算关联 → 结算单合同 → 合同已推送传合规ID,否则传人工补录值(D13)
|
||||
*/
|
||||
private ContractReference resolveContract(InvoiceReceipt invoiceReceipt) {
|
||||
// ① 人工补录兜底优先可直接用;先走链路回溯
|
||||
InvoiceReceiptSettlement relation = invoiceReceiptSettlementMapper.selectOne(Wrappers.<InvoiceReceiptSettlement>lambdaQuery()
|
||||
.eq(InvoiceReceiptSettlement::getInvoiceReceiptId, invoiceReceipt.getId())
|
||||
.last("limit 1"));
|
||||
if (relation != null && relation.getFormalSettlementId() != null) {
|
||||
FormalSettlement settlement = formalSettlementMapper.selectById(relation.getFormalSettlementId());
|
||||
if (settlement != null && settlement.getContractId() != null) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(settlement.getContractId());
|
||||
if (contractManage != null) {
|
||||
// 合同已推送传合规合同ID(fd_contract_subject 支持 ID 引用),编号传 TMS 合同编号
|
||||
String subject = StringUtil.isNotBlank(contractManage.getHcsFdId()) ? contractManage.getHcsFdId() : contractManage.getContractName();
|
||||
return new ContractReference(subject, contractManage.getContractNo());
|
||||
}
|
||||
}
|
||||
}
|
||||
// ② 回溯不到:页面人工补录(hcs_contract_id/hcs_contract_no,D13)
|
||||
if (invoiceReceipt.getHcsContractId() != null) {
|
||||
ContractManage contractManage = contractManageMapper.selectById(invoiceReceipt.getHcsContractId());
|
||||
if (contractManage != null) {
|
||||
String subject = StringUtil.isNotBlank(contractManage.getHcsFdId()) ? contractManage.getHcsFdId() : contractManage.getContractName();
|
||||
return new ContractReference(subject, StringUtil.isNotBlank(invoiceReceipt.getHcsContractNo()) ? invoiceReceipt.getHcsContractNo() : contractManage.getContractNo());
|
||||
}
|
||||
}
|
||||
if (StringUtil.isNotBlank(invoiceReceipt.getHcsContractNo())) {
|
||||
return new ContractReference(null, invoiceReceipt.getHcsContractNo());
|
||||
}
|
||||
log.warn("HCS收票单合同关联缺失:invoiceNo={},应付结算回溯与人工补录均为空,fd_contract_* 不传", invoiceReceipt.getInvoiceNo());
|
||||
return new ContractReference(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回填整体同步状态置 synced
|
||||
*/
|
||||
public void markSynced(InvoiceReceipt invoiceReceipt, String message) {
|
||||
invoiceReceipt.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED);
|
||||
invoiceReceipt.setHcsRetryCount(0);
|
||||
invoiceReceipt.setHcsSyncTime(LocalDateTime.now());
|
||||
invoiceReceipt.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
invoiceReceiptMapper.updateById(invoiceReceipt);
|
||||
log.info("HCS收票单推送成功:invoiceNo={},{}", invoiceReceipt.getInvoiceNo(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送失败回写:failed + 重试次数+1 + 失败原因
|
||||
*/
|
||||
public void markFailed(InvoiceReceipt invoiceReceipt, String message) {
|
||||
invoiceReceipt.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED);
|
||||
invoiceReceipt.setHcsRetryCount(invoiceReceipt.getHcsRetryCount() == null ? 1 : invoiceReceipt.getHcsRetryCount() + 1);
|
||||
invoiceReceipt.setHcsSyncTime(LocalDateTime.now());
|
||||
invoiceReceipt.setHcsSyncMsg(HcsSyncConstant.truncateMsg(message));
|
||||
invoiceReceiptMapper.updateById(invoiceReceipt);
|
||||
log.error("HCS收票单推送失败:invoiceNo={},retryCount={},{}", invoiceReceipt.getInvoiceNo(), invoiceReceipt.getHcsRetryCount(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作人登录名(thirdParty.hcs.operator-login-name,默认 srvadmin,§9-U2)
|
||||
*/
|
||||
private String resolveLoginName(InvoiceReceipt invoiceReceipt) {
|
||||
return hcsProperties.getOperatorLoginName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收票日期格式化(fd_bill_date 为 YYYY-MM-DD 字符串)
|
||||
*/
|
||||
private String formatBillDate(LocalDate invoiceDate) {
|
||||
return invoiceDate == null ? null : invoiceDate.format(BILL_DATE_FORMATTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 税率换算:TMS 整数值(如 13)÷ 100 → 小数(0.13)
|
||||
*/
|
||||
private BigDecimal toTaxRate(BigDecimal taxRate) {
|
||||
return taxRate == null ? null : taxRate.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同引用(fd_contract_subject / fd_contract_no)
|
||||
*/
|
||||
private record ContractReference(String subject, String contractNo) {
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.dto.HcsContractDetailQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsPageQueryDTO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsFieldConstant;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* HCS 对账查询支撑组件(设计文档 §5.9/§8-P5,运维收尾)
|
||||
* <p>
|
||||
* 按来源单据编号回查 HCS 侧数据(项目立项/合同/合同详情/开票登记),
|
||||
* 供对账查询页与人工排查使用;返回 HCS 原始数据结构。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsReconSupport {
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
|
||||
/**
|
||||
* 按项目编号回查 HCS 项目立项(C102-3)
|
||||
*
|
||||
* @param projectCode 项目编号
|
||||
* @return HCS 项目立项列表
|
||||
*/
|
||||
public HcsResult<?> reconProject(String projectCode) {
|
||||
HcsPageQueryDTO query = new HcsPageQueryDTO();
|
||||
// C102-3 入参 columns 用下划线字段名
|
||||
query.setColumns(List.of(HcsFieldConstant.PROJ_FD_ID, HcsFieldConstant.PROJ_FD_NUM, HcsFieldConstant.PROJ_FD_PRO_NAME));
|
||||
// 项目编号精确匹配
|
||||
query.setConditions(Map.of(HcsFieldConstant.PROJ_FD_NUM, Objects.toString(projectCode, "")));
|
||||
query.setOffset(0);
|
||||
query.setPageSize(50);
|
||||
HcsResult<?> result = hcsClient.projectList(query);
|
||||
return requireSuccess("项目立项对账", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按合同编号回查 HCS 合同(C103-3)
|
||||
*
|
||||
* @param contractNo 合同编号
|
||||
* @return HCS 合同列表
|
||||
*/
|
||||
public HcsResult<?> reconContract(String contractNo) {
|
||||
HcsPageQueryDTO query = new HcsPageQueryDTO();
|
||||
query.setColumns(List.of(HcsFieldConstant.CONTRACT_FD_ID, HcsFieldConstant.CONTRACT_FD_SUBJECT,
|
||||
HcsFieldConstant.CONTRACT_FD_NUMBER, HcsFieldConstant.CONTRACT_FD_CATE_HIERARCHY_ID));
|
||||
// 合同编号精确匹配(§9-U4 查询能力联调确认)
|
||||
query.setConditions(Map.of(HcsFieldConstant.CONTRACT_FD_NUMBER, Objects.toString(contractNo, "")));
|
||||
query.setOffset(0);
|
||||
query.setPageSize(50);
|
||||
HcsResult<?> result = hcsClient.contractList(query);
|
||||
return requireSuccess("合同对账", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同详情回查(C103-4,fdId 与 fdNumber 二选一)
|
||||
* <p>
|
||||
* v4 已定义详情响应字段表(HcsContractDetailVO),级联/集合字段内部结构以实际返回为准(设计文档 §5.9)
|
||||
*
|
||||
* @param fdId 合同ID(可空)
|
||||
* @param fdNumber 合同编号(可空)
|
||||
* @return HCS 合同详情
|
||||
*/
|
||||
public HcsResult<?> reconContractDetail(String fdId, String fdNumber) {
|
||||
if (StringUtil.isBlank(fdId) && StringUtil.isBlank(fdNumber)) {
|
||||
throw new HcsException("合同详情对账失败,fdId 与 fdNumber 至少传一项");
|
||||
}
|
||||
HcsContractDetailQueryDTO query = new HcsContractDetailQueryDTO();
|
||||
// 合同ID(与编号二选一)
|
||||
query.setFdId(fdId);
|
||||
// 合同编号(与ID二选一)
|
||||
query.setFdNumber(fdNumber);
|
||||
HcsResult<?> result = hcsClient.contractDetail(query);
|
||||
return requireSuccess("合同详情对账", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按申请单号回查 HCS 开票登记(C111-1)
|
||||
*
|
||||
* @param applicationNo 开票申请单号
|
||||
* @return HCS 开票登记列表
|
||||
*/
|
||||
public HcsResult<?> reconInvoiceRegistration(String applicationNo) {
|
||||
HcsPageQueryDTO query = new HcsPageQueryDTO();
|
||||
query.setColumns(List.of(HcsFieldConstant.INVOICE_FD_ID, HcsFieldConstant.INVOICE_FD_NUMBER,
|
||||
HcsFieldConstant.INVOICE_FD_BILL_DATE, HcsFieldConstant.INVOICE_FD_CONTRACT_NUMBER,
|
||||
HcsFieldConstant.INVOICE_FD_INVOICE_AMOUNT, HcsFieldConstant.INVOICE_FD_STATUS));
|
||||
// 单据编号精确匹配(§9-U4 查询能力联调确认)
|
||||
query.setConditions(Map.of(HcsFieldConstant.INVOICE_FD_NUMBER, Objects.toString(applicationNo, "")));
|
||||
query.setOffset(0);
|
||||
query.setPageSize(50);
|
||||
HcsResult<?> result = hcsClient.invoiceRegistrationList(query);
|
||||
return requireSuccess("开票登记对账", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验查询结果并返回
|
||||
*/
|
||||
private HcsResult<?> requireSuccess(String scene, HcsResult<?> result) {
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS{}查询失败响应:{}", scene, result == null ? "无响应"
|
||||
: String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()));
|
||||
throw new HcsException("HCS" + scene + "失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support.hcs;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.hcs.dto.HcsAttachmentRefDTO;
|
||||
import org.springblade.hcs.dto.HcsFormSaveVO;
|
||||
import org.springblade.hcs.dto.HcsMechanismsDTO;
|
||||
import org.springblade.hcs.dto.HcsResult;
|
||||
import org.springblade.hcs.dto.HcsSpecPayFormDataDTO;
|
||||
import org.springblade.hcs.dto.HcsSpecPayPropsDTO;
|
||||
import org.springblade.hcs.dto.HcsSpecPaySaveDTO;
|
||||
import org.springblade.hcs.exception.HcsException;
|
||||
import org.springblade.hcs.feign.IHcsClient;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HCS 结算付款特批同步支撑组件(C106-3 发起 / C106-4 更新,设计文档 §5.5)
|
||||
* <p>
|
||||
* 触发:付款申请提交时收款方外部客商高风险/黑名单(须特批附件)或额度超额标记 hcs_spec_status=need
|
||||
* → 审批通过后扫描任务发起 C106-3,返回 data.data.fdId(双层嵌套)→ hcs_spec_fd_id,状态置 processing
|
||||
* 并挂起 currentNode="特批审核";
|
||||
* 单据变更且特批流程存在时走 C106-4(顶层 fdId=hcs_spec_fd_id,附件不带 fdBindType)。
|
||||
* 特批结果:合规 paymentLegal/result 回调联动付款审批状态(通过放行/驳回联动审批驳回),
|
||||
* 页面人工确认等效兜底(§9-U1)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class HcsSpecPaySyncSupport {
|
||||
|
||||
/**
|
||||
* 特批附件字段标识
|
||||
*/
|
||||
private static final String SPEC_ATTACH_ENTITY_KEY = "fd_spc_approval_attach";
|
||||
|
||||
private final IHcsClient hcsClient;
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
private final HcsAttachmentSupport hcsAttachmentSupport;
|
||||
|
||||
/**
|
||||
* 推送结算付款特批:未发起走 C106-3,已发起走 C106-4;流程ID 与状态立即持久化
|
||||
*
|
||||
* @param payment 付款申请单(hcs_spec_status=need)
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
public String pushSpecPay(PaymentApplication payment) {
|
||||
String operatorLoginName = resolveLoginName(payment);
|
||||
HcsSpecPaySaveDTO param = buildSpecPayParam(payment, operatorLoginName);
|
||||
if (StringUtil.isBlank(payment.getHcsSpecFdId())) {
|
||||
// C106-3 发起:返回双层嵌套 data.data.fdId
|
||||
log.info("HCS结算付款特批发起开始:paymentNo={}", payment.getPaymentNo());
|
||||
HcsResult<HcsResult<HcsFormSaveVO>> result = hcsClient.addSpecApplyPay(param);
|
||||
String specFdId = extractNestedFdId(payment, result);
|
||||
payment.setHcsSpecFdId(specFdId);
|
||||
payment.setHcsSpecStatus(HcsSyncConstant.SPEC_STATUS_PROCESSING);
|
||||
// TMS 流程挂起展示:等待合规 paymentLegal/result 回调联动付款审批状态(与合同链路 hangSpecReview 口径一致)
|
||||
payment.setCurrentNode(HcsSyncConstant.SPEC_REVIEW_NODE);
|
||||
paymentApplicationMapper.updateById(payment);
|
||||
log.info("HCS结算付款特批发起成功:paymentNo={},specFdId={},挂起「{}」", payment.getPaymentNo(), specFdId, HcsSyncConstant.SPEC_REVIEW_NODE);
|
||||
return "C106-3 特批发起成功";
|
||||
}
|
||||
// C106-4 更新:顶层 fdId 必填,内层无业务数据,以内层 success 判断
|
||||
log.info("HCS结算付款特批更新开始:paymentNo={},specFdId={}", payment.getPaymentNo(), payment.getHcsSpecFdId());
|
||||
HcsResult<HcsResult<Void>> result = hcsClient.updateSpecApplyPay(param);
|
||||
if (result == null || !result.isSuccess()) {
|
||||
log.error("HCS结算付款特批更新失败响应:{},paymentNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo());
|
||||
throw new HcsException("C106-4 特批更新失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return "C106-4 特批更新成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 C106-3/C106-4 入参(字段标注照 §3.4 规范,映射表见设计文档 §5.5)
|
||||
*/
|
||||
private HcsSpecPaySaveDTO buildSpecPayParam(PaymentApplication payment, String operatorLoginName) {
|
||||
ContractManage contractManage = payment.getContractId() == null ? null : contractManageMapper.selectById(payment.getContractId());
|
||||
HcsSpecPaySaveDTO param = new HcsSpecPaySaveDTO();
|
||||
// 结算付款流程ID:发起不传,更新必填(C106-4 顶层 fdId)
|
||||
param.setFdId(payment.getHcsSpecFdId());
|
||||
// 【必填】操作人登录名:付款申请创建人登录名(§9-U2)
|
||||
param.setFdLoginName(operatorLoginName);
|
||||
// 【必填】流程模板ID:公司流程模板(spec-pay.template-mapping 按租户配置,环境/公司相关)
|
||||
param.setFdTemplateId(requireTemplateId(payment));
|
||||
HcsSpecPayFormDataDTO formData = new HcsSpecPayFormDataDTO();
|
||||
HcsSpecPayPropsDTO props = new HcsSpecPayPropsDTO();
|
||||
// 【必填】合同:传合规合同ID(blade_contract_manage.hcs_fd_id,非名称)
|
||||
props.setFdContractName(requireContractFdId(payment, contractManage));
|
||||
// 合同编号
|
||||
props.setFdContractNo(payment.getContractNo());
|
||||
// 【必填】单据编号:付款申请单号(来源付款单标识)
|
||||
props.setFdDocNo(payment.getPaymentNo());
|
||||
// 【必填】单据主题:"结算付款特批-" + 付款单号
|
||||
props.setFdDocSubject("结算付款特批-" + payment.getPaymentNo());
|
||||
// 来源付款单ID:付款申请单号(§5.5 映射表)
|
||||
props.setFdId(payment.getPaymentNo());
|
||||
// 【必填】付款金额:appliedAmount
|
||||
props.setFdPayAmount(payment.getAppliedAmount());
|
||||
// 【必填】合计金额:settlementAmount
|
||||
props.setFdTotalAmount(payment.getSettlementAmount() == null ? BigDecimal.ZERO : payment.getSettlementAmount());
|
||||
// 【必填】付款人 / 收款方
|
||||
props.setFdPayer(payment.getPayerName());
|
||||
props.setFdPayee(payment.getPayeeName());
|
||||
// 【必填】收款账户名 / 收款账号 / 开户行名称
|
||||
props.setFdReceiveAccountName(payment.getReceiptAccountName());
|
||||
props.setFdReceiveAccount(payment.getBankAccount());
|
||||
props.setFdBankName(payment.getBankName());
|
||||
// 【必填】结算币种:TMS 无币种字段,按 currency-name-mapping 默认值(人民币)
|
||||
props.setFdSettleCurrency(hcsProperties.getContract().getCurrencyNameMapping().getOrDefault("RMB", "人民币"));
|
||||
// 【必填】付款用途 / 款项性质 / 款项类型:字典值配置(§9-U3 待联调后配置)
|
||||
props.setFdPaymentPurpose(hcsProperties.getSpecPay().getPaymentPurpose());
|
||||
props.setFdPaymentNature(hcsProperties.getSpecPay().getPaymentNature());
|
||||
props.setFdPaymentType(hcsProperties.getSpecPay().getPaymentType());
|
||||
// 【必填】事由 / 补充说明:remark
|
||||
props.setFdReason(payment.getRemark());
|
||||
props.setFdSupplementNote(payment.getRemark());
|
||||
// 单据状态:配置 spec-pay.doc-status(示例 10)
|
||||
props.setFdDocStatus(hcsProperties.getSpecPay().getDocStatus());
|
||||
// 特批附件字段标识:固定 fd_spc_approval_attach
|
||||
props.setFdSpcApprovalAttach(SPEC_ATTACH_ENTITY_KEY);
|
||||
formData.setDynamicProps(props);
|
||||
// 【必填】表单层单据主题
|
||||
formData.setFdDocSubject("结算付款特批-" + payment.getPaymentNo());
|
||||
// 表单层单据状态:配置 spec-pay.doc-status
|
||||
formData.setFdDocStatus(hcsProperties.getSpecPay().getDocStatus());
|
||||
param.setFormData(formData);
|
||||
// 附件挂载:attachmentsJson 经 C103-2 转传,特批附件 fdEntityKey=fd_spc_approval_attach,
|
||||
// 发起带 fdBindType=ADD,更新不带
|
||||
param.setMechanisms(buildMechanisms(payment, operatorLoginName));
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 特批附件挂载(附件转传开关关闭或无附件时返回 null)
|
||||
*/
|
||||
private HcsMechanismsDTO buildMechanisms(PaymentApplication payment, String operatorLoginName) {
|
||||
if (!hcsProperties.getAttachment().isTransferEnabled() || StringUtil.isBlank(payment.getAttachmentsJson())) {
|
||||
return null;
|
||||
}
|
||||
List<HcsAttachmentRefDTO> attachmentRefs = hcsAttachmentSupport.transferAttachments(
|
||||
HcsSyncConstant.BIZ_TYPE_PAYMENT, payment.getId(), payment.getAttachmentsJson(), operatorLoginName);
|
||||
if (attachmentRefs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
HcsMechanismsDTO mechanisms = new HcsMechanismsDTO();
|
||||
boolean add = StringUtil.isBlank(payment.getHcsSpecFdId());
|
||||
mechanisms.setAttachment(attachmentRefs.stream().map(ref -> {
|
||||
// 附件ID(C103-2 转传返回)
|
||||
ref.setFdEntityKey(SPEC_ATTACH_ENTITY_KEY);
|
||||
// 绑定类型:仅发起传 ADD
|
||||
ref.setFdBindType(add ? "ADD" : null);
|
||||
return ref;
|
||||
}).toList());
|
||||
return mechanisms;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 C106-3 双层嵌套返回 data.data.fdId
|
||||
*/
|
||||
private String extractNestedFdId(PaymentApplication payment, HcsResult<HcsResult<HcsFormSaveVO>> result) {
|
||||
if (result == null || !result.isSuccess() || result.getData() == null || !result.getData().isSuccess()
|
||||
|| result.getData().getData() == null || StringUtil.isBlank(result.getData().getData().getFdId())) {
|
||||
log.error("HCS结算付款特批发起失败响应:{},paymentNo={}", result == null ? "无响应" : String.format("msg=%s, traceId=%s", result.getMsg(), result.getTraceId()), payment.getPaymentNo());
|
||||
throw new HcsException("C106-3 特批发起失败:" + (result == null ? "响应为空" : result.getMsg()));
|
||||
}
|
||||
return result.getData().getData().getFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款特批流程模板ID(fdTemplateId 必填,spec-pay.template-id,每个环境/公司不同)
|
||||
*/
|
||||
private String requireTemplateId(PaymentApplication payment) {
|
||||
String templateId = hcsProperties.getSpecPay().getTemplateId();
|
||||
if (StringUtil.isBlank(templateId)) {
|
||||
throw new HcsException("C106 特批推送失败,未配置付款特批流程模板ID(thirdParty.hcs.spec-pay.template-id)");
|
||||
}
|
||||
return templateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合规合同ID(fd_contract_name 必填,合同未推送时失败提示)
|
||||
*/
|
||||
private String requireContractFdId(PaymentApplication payment, ContractManage contractManage) {
|
||||
if (contractManage == null || StringUtil.isBlank(contractManage.getHcsFdId())) {
|
||||
throw new HcsException("C106 特批推送失败,关联合同尚未推送合规(paymentNo=" + payment.getPaymentNo() + ")");
|
||||
}
|
||||
return contractManage.getHcsFdId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作人登录名(thirdParty.hcs.operator-login-name,默认 srvadmin,§9-U2)
|
||||
*/
|
||||
private String resolveLoginName(PaymentApplication payment) {
|
||||
return hcsProperties.getOperatorLoginName();
|
||||
}
|
||||
}
|
||||
+3
@@ -29,6 +29,8 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.service.ICustomerArchiveService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -47,6 +49,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RefreshScope
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerKingdeeSyncTask {
|
||||
|
||||
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.task;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.hcs.config.HcsProperties;
|
||||
import org.springblade.transport.constant.HcsSyncConstant;
|
||||
import org.springblade.transport.mapper.ContractManageMapper;
|
||||
import org.springblade.transport.mapper.InvoiceApplicationMapper;
|
||||
import org.springblade.transport.mapper.InvoiceReceiptMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.mapper.ProjectApplyMapper;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplication;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceipt;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springblade.transport.support.hcs.HcsContractSpecSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsContractSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsInvoiceRegSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsPaymentSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsProjectSyncSupport;
|
||||
import org.springblade.transport.support.hcs.HcsReceiptBillSyncSupport;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* HCS 合规推送定时扫描任务(设计文档 §5.0,ADR-0001)
|
||||
* <p>
|
||||
* 审批代码零埋点:扫描只认 approvalStatus=approved/change_approved 且 hcs_sync_status=unsynced/failed(重试未超限),
|
||||
* 与审批入口(/approve、MK 回调)无关;失败落 failed 由下轮扫描自动补偿,超限仅人工重推(D7)。
|
||||
* <p>
|
||||
* 五类任务各自独立调度、独立开关(Nacos thirdParty.hcs.push.{bizType}.*):
|
||||
* <pre>
|
||||
* bizType 说明 配置节点
|
||||
* project 项目立项 C102 thirdParty.hcs.push.project.*
|
||||
* contract 合同 C103(内含风险校验/附件转传/履约规则) thirdParty.hcs.push.contract.*
|
||||
* payment 付款特批 C106-3 + 履约记录 C108 thirdParty.hcs.push.payment.*
|
||||
* invoice-application 开票登记 C111(含金蝶开票回填) thirdParty.hcs.push.invoice-application.*
|
||||
* invoice-receipt 收票单 C109 thirdParty.hcs.push.invoice-receipt.*
|
||||
* </pre>
|
||||
* 每个任务配置项:sync-enabled 开关(默认关,灰度打开)、cron 调度(默认每 5 分钟,各任务分钟位错峰
|
||||
* 0/1/2/3/4,避免同刻并发扫描与 HCS 接口流量尖峰);batch-size/max-retry 为全局配置(thirdParty.hcs.push.*),
|
||||
* 见 {@link HcsProperties.Push}。
|
||||
* 多实例部署时按任务隔离 Redis 分布式锁防重扫(setIfAbsent + 持有值比对释放,TTL 240s),任务间互不阻塞。
|
||||
* 扫描与推送逻辑收敛在 pushUnsyncedProjects/pushUnsyncedContracts/pushUnsyncedPayments 等方法,供手动端点(HcsSyncController)复用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RefreshScope
|
||||
@RequiredArgsConstructor
|
||||
public class HcsPushTask {
|
||||
|
||||
/**
|
||||
* 分布式锁 TTL(秒):须小于各任务 cron 间隔(默认 5 分钟)且大于单轮预期执行时长,实例崩溃后锁自动过期
|
||||
*/
|
||||
private static final long PUSH_TASK_LOCK_TTL_SECONDS = 240L;
|
||||
|
||||
/**
|
||||
* 审批通过状态集合(含变更通过)
|
||||
*/
|
||||
private static final List<String> APPROVED_STATUSES = List.of("approved", "change_approved");
|
||||
|
||||
private final HcsProperties hcsProperties;
|
||||
private final ProjectApplyMapper projectApplyMapper;
|
||||
private final ContractManageMapper contractManageMapper;
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
private final InvoiceApplicationMapper invoiceApplicationMapper;
|
||||
private final InvoiceReceiptMapper invoiceReceiptMapper;
|
||||
private final HcsProjectSyncSupport hcsProjectSyncSupport;
|
||||
private final HcsContractSyncSupport hcsContractSyncSupport;
|
||||
private final HcsContractSpecSyncSupport hcsContractSpecSyncSupport;
|
||||
private final HcsPaymentSyncSupport hcsPaymentSyncSupport;
|
||||
private final HcsInvoiceRegSyncSupport hcsInvoiceRegSyncSupport;
|
||||
private final HcsReceiptBillSyncSupport hcsReceiptBillSyncSupport;
|
||||
private final BladeRedis bladeRedis;
|
||||
|
||||
/**
|
||||
* 项目立项(C102)定时推送:扫描审批通过且待同步/待补偿的项目,独立开关与 cron 调度
|
||||
*/
|
||||
@Scheduled(cron = "${thirdParty.hcs.push.project.cron:0 */5 * * * ?}")
|
||||
public void pushHcsProject() {
|
||||
this.runOnce("项目立项", HcsSyncConstant.BIZ_TYPE_PROJECT, hcsProperties.getPush().getProject().isSyncEnabled(), this::pushUnsyncedProjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同(C103)定时推送:扫描审批通过且待同步/待补偿的合同(含风险校验→附件转传→C103-1→履约规则 C103-9 行级同步),
|
||||
* 并对特批中(processing)的合同做 C103-4 结果查询兜底,独立开关与 cron 调度
|
||||
*/
|
||||
@Scheduled(cron = "${thirdParty.hcs.push.contract.cron:0 1/5 * * * ?}")
|
||||
public void pushHcsContract() {
|
||||
this.runOnce("合同", HcsSyncConstant.BIZ_TYPE_CONTRACT, hcsProperties.getPush().getContract().isSyncEnabled(), this::syncHcsContracts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合同链路组合动作:待同步合同推送 + 特批结果查询兜底(合规推送 contractLegal/result 未达时 C103-4 轮询联动)
|
||||
*/
|
||||
private String syncHcsContracts() {
|
||||
String pushSummary = this.pushUnsyncedContracts();
|
||||
return pushSummary + ";" + hcsContractSpecSyncSupport.syncProcessingSpecResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款链(C106 特批 / C108 履约记录)定时推送:扫描审批通过且待同步/待补偿的付款申请,独立开关与 cron 调度
|
||||
*/
|
||||
@Scheduled(cron = "${thirdParty.hcs.push.payment.cron:0 2/5 * * * ?}")
|
||||
public void pushHcsPayment() {
|
||||
this.runOnce("付款链", HcsSyncConstant.BIZ_TYPE_PAYMENT, hcsProperties.getPush().getPayment().isSyncEnabled(), this::pushUnsyncedPayments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开票登记(C111)定时推送:扫描审批通过且待同步/待补偿的开票申请(含金蝶开票完成后回填),独立开关与 cron 调度
|
||||
*/
|
||||
@Scheduled(cron = "${thirdParty.hcs.push.invoice-application.cron:0 3/5 * * * ?}")
|
||||
public void pushHcsInvoiceApplication() {
|
||||
this.runOnce("开票登记", HcsSyncConstant.BIZ_TYPE_INVOICE_APPLICATION, hcsProperties.getPush().getInvoiceApplication().isSyncEnabled(), this::pushUnsyncedInvoiceApplications);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收票单(C109)定时推送:扫描审批通过且待同步/待补偿的收票单,独立开关与 cron 调度
|
||||
*/
|
||||
@Scheduled(cron = "${thirdParty.hcs.push.invoice-receipt.cron:0 4/5 * * * ?}")
|
||||
public void pushHcsInvoiceReceipt() {
|
||||
this.runOnce("收票单", HcsSyncConstant.BIZ_TYPE_INVOICE_RECEIPT, hcsProperties.getPush().getInvoiceReceipt().isSyncEnabled(), this::pushUnsyncedInvoiceReceipts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单任务执行模板:开关校验 → 分布式锁抢锁 → 执行推送 → 持有值比对后释放。
|
||||
* 锁按业务类型隔离,各任务可并行调度互不阻塞;TTL 见 {@link #PUSH_TASK_LOCK_TTL_SECONDS}。
|
||||
*
|
||||
* @param taskName 任务中文名(用于日志定位)
|
||||
* @param bizType 业务类型(同时作为锁 key 后缀)
|
||||
* @param syncEnabled 该任务的独立开关(thirdParty.hcs.push.{bizType}.sync-enabled)
|
||||
* @param pushAction 推送动作,返回结果汇总描述
|
||||
*/
|
||||
private void runOnce(String taskName, String bizType, boolean syncEnabled, Supplier<String> pushAction) {
|
||||
if (!syncEnabled) {
|
||||
// 开关未开启属常态(灰度),debug 级别避免轮询刷屏
|
||||
// log.debug("HCS{}推送任务未开启(thirdParty.hcs.push.{}.sync-enabled),本轮跳过", taskName, bizType);
|
||||
return;
|
||||
}
|
||||
// 多实例部署防重扫:setIfAbsent 抢锁,持有值比对后释放
|
||||
String lockKey = HcsSyncConstant.PUSH_TASK_LOCK_KEY_PREFIX + bizType;
|
||||
String lockHolder = UUID.randomUUID().toString();
|
||||
Boolean locked = bladeRedis.getValueOps()
|
||||
.setIfAbsent(lockKey, lockHolder, Duration.ofSeconds(PUSH_TASK_LOCK_TTL_SECONDS));
|
||||
if (!Boolean.TRUE.equals(locked)) {
|
||||
log.info("HCS{}推送任务其他实例执行中(lockKey={}),本轮跳过", taskName, lockKey);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
log.info("HCS{}推送任务开始执行", taskName);
|
||||
String summary = pushAction.get();
|
||||
log.info("HCS{}推送任务执行完成:{}", taskName, summary);
|
||||
} finally {
|
||||
if (lockHolder.equals(bladeRedis.get(lockKey))) {
|
||||
bladeRedis.del(lockKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描并推送待同步/待补偿的项目立项(C102),返回结果汇总
|
||||
*
|
||||
* @return 汇总描述
|
||||
*/
|
||||
public String pushUnsyncedProjects() {
|
||||
int maxRetry = hcsProperties.getPush().getMaxRetry();
|
||||
List<ProjectApply> projectList = projectApplyMapper.selectList(Wrappers.<ProjectApply>lambdaQuery()
|
||||
.in(ProjectApply::getApprovalStatus, APPROVED_STATUSES)
|
||||
.in(ProjectApply::getHcsSyncStatus, List.of(HcsSyncConstant.HCS_STATUS_UNSYNCED, HcsSyncConstant.HCS_STATUS_FAILED))
|
||||
.and(wrapper -> wrapper.isNull(ProjectApply::getHcsRetryCount).or().lt(ProjectApply::getHcsRetryCount, maxRetry))
|
||||
.last("limit " + hcsProperties.getPush().getBatchSize()));
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (ProjectApply projectApply : projectList) {
|
||||
try {
|
||||
hcsProjectSyncSupport.pushProject(projectApply);
|
||||
successCount++;
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
hcsProjectSyncSupport.markFailed(projectApply, message);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
String summary = String.format("扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)", projectList.size(), successCount, failCount, maxRetry);
|
||||
if (failCount > 0) {
|
||||
log.warn("HCS项目立项推送存在失败:{}", summary);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描并推送待同步/待补偿的合同(C103),返回结果汇总
|
||||
*
|
||||
* @return 汇总描述
|
||||
*/
|
||||
public String pushUnsyncedContracts() {
|
||||
int maxRetry = hcsProperties.getPush().getMaxRetry();
|
||||
List<ContractManage> contractList = contractManageMapper.selectList(Wrappers.<ContractManage>lambdaQuery()
|
||||
.in(ContractManage::getApprovalStatus, APPROVED_STATUSES)
|
||||
.in(ContractManage::getHcsSyncStatus, List.of(HcsSyncConstant.HCS_STATUS_UNSYNCED, HcsSyncConstant.HCS_STATUS_FAILED))
|
||||
.and(wrapper -> wrapper.isNull(ContractManage::getHcsRetryCount).or().lt(ContractManage::getHcsRetryCount, maxRetry))
|
||||
.last("limit " + hcsProperties.getPush().getBatchSize()));
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (ContractManage contractManage : contractList) {
|
||||
try {
|
||||
hcsContractSyncSupport.pushContract(contractManage);
|
||||
successCount++;
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
hcsContractSyncSupport.markFailed(contractManage, message);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
String summary = String.format("扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)", contractList.size(), successCount, failCount, maxRetry);
|
||||
if (failCount > 0) {
|
||||
log.warn("HCS合同推送存在失败:{}", summary);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描并推送待同步/待补偿的付款申请(C106 特批 + C108 履约记录),返回结果汇总
|
||||
*
|
||||
* @return 汇总描述
|
||||
*/
|
||||
public String pushUnsyncedPayments() {
|
||||
int maxRetry = hcsProperties.getPush().getMaxRetry();
|
||||
List<PaymentApplication> paymentList = paymentApplicationMapper.selectList(Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.eq(PaymentApplication::getApprovalStatus, "approved")
|
||||
.in(PaymentApplication::getHcsSyncStatus, List.of(HcsSyncConstant.HCS_STATUS_UNSYNCED, HcsSyncConstant.HCS_STATUS_FAILED))
|
||||
.and(wrapper -> wrapper.isNull(PaymentApplication::getHcsRetryCount).or().lt(PaymentApplication::getHcsRetryCount, maxRetry))
|
||||
.last("limit " + hcsProperties.getPush().getBatchSize()));
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (PaymentApplication payment : paymentList) {
|
||||
try {
|
||||
hcsPaymentSyncSupport.pushPayment(payment);
|
||||
successCount++;
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
hcsPaymentSyncSupport.markFailed(payment, message);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
String summary = String.format("扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)", paymentList.size(), successCount, failCount, maxRetry);
|
||||
if (failCount > 0) {
|
||||
log.warn("HCS付款链推送存在失败:{}", summary);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描并推送待同步/待补偿的开票申请(C111 开票登记,含金蝶开票完成后回填),返回结果汇总
|
||||
*
|
||||
* @return 汇总描述
|
||||
*/
|
||||
public String pushUnsyncedInvoiceApplications() {
|
||||
int maxRetry = hcsProperties.getPush().getMaxRetry();
|
||||
List<InvoiceApplication> applicationList = invoiceApplicationMapper.selectList(Wrappers.<InvoiceApplication>lambdaQuery()
|
||||
.eq(InvoiceApplication::getApprovalStatus, "approved")
|
||||
.in(InvoiceApplication::getHcsSyncStatus, List.of(HcsSyncConstant.HCS_STATUS_UNSYNCED, HcsSyncConstant.HCS_STATUS_FAILED))
|
||||
.and(wrapper -> wrapper.isNull(InvoiceApplication::getHcsRetryCount).or().lt(InvoiceApplication::getHcsRetryCount, maxRetry))
|
||||
.last("limit " + hcsProperties.getPush().getBatchSize()));
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (InvoiceApplication application : applicationList) {
|
||||
try {
|
||||
hcsInvoiceRegSyncSupport.pushInvoiceRegistration(application);
|
||||
successCount++;
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
hcsInvoiceRegSyncSupport.markFailed(application, message);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
String summary = String.format("扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)", applicationList.size(), successCount, failCount, maxRetry);
|
||||
if (failCount > 0) {
|
||||
log.warn("HCS开票登记推送存在失败:{}", summary);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描并推送待同步/待补偿的收票单(C109),返回结果汇总
|
||||
*
|
||||
* @return 汇总描述
|
||||
*/
|
||||
public String pushUnsyncedInvoiceReceipts() {
|
||||
int maxRetry = hcsProperties.getPush().getMaxRetry();
|
||||
List<InvoiceReceipt> receiptList = invoiceReceiptMapper.selectList(Wrappers.<InvoiceReceipt>lambdaQuery()
|
||||
.eq(InvoiceReceipt::getApprovalStatus, "approved")
|
||||
.in(InvoiceReceipt::getHcsSyncStatus, List.of(HcsSyncConstant.HCS_STATUS_UNSYNCED, HcsSyncConstant.HCS_STATUS_FAILED))
|
||||
.and(wrapper -> wrapper.isNull(InvoiceReceipt::getHcsRetryCount).or().lt(InvoiceReceipt::getHcsRetryCount, maxRetry))
|
||||
.last("limit " + hcsProperties.getPush().getBatchSize()));
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (InvoiceReceipt invoiceReceipt : receiptList) {
|
||||
try {
|
||||
hcsReceiptBillSyncSupport.pushReceiptBill(invoiceReceipt);
|
||||
hcsReceiptBillSyncSupport.markSynced(invoiceReceipt, "C109 收票单推送成功");
|
||||
successCount++;
|
||||
} catch (Exception exception) {
|
||||
String message = StringUtil.isBlank(exception.getMessage()) ? "推送异常" : exception.getMessage();
|
||||
hcsReceiptBillSyncSupport.markFailed(invoiceReceipt, message);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
String summary = String.format("扫描 %d 条,成功 %d 条,失败 %d 条(重试上限 %d)", receiptList.size(), successCount, failCount, maxRetry);
|
||||
if (failCount > 0) {
|
||||
log.warn("HCS收票单推送存在失败:{}", summary);
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
+3
@@ -26,6 +26,8 @@ import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IInvoiceApplicationService;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -47,6 +49,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RefreshScope
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentKingdeeResultTask {
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-third-party-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>blade-hcs-api</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>BladeX 合同合规系统(HCS)API 契约模块</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springblade</groupId>
|
||||
<artifactId>blade-core-tool</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.config;
|
||||
|
||||
import feign.Request;
|
||||
import feign.RequestInterceptor;
|
||||
import org.springblade.hcs.feign.HcsClientFallbackFactory;
|
||||
import org.springblade.hcs.interceptor.HcsAuthInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)Feign 客户端配置
|
||||
* <p>
|
||||
* 仅通过 {@link org.springframework.cloud.openfeign.FeignClient#configuration} 引用,避免拦截器全局生效。
|
||||
* 按 D7 约定不配置 Retryer:写接口重试会造成 HCS 重复建单,重试职责归扫描任务(hcs_retry_count 补偿)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class HcsFeignClientConfig {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor bladeFeignRequestInterceptor() {
|
||||
// 空实现覆盖全局 BladeFeignRequestInterceptor,防止其覆盖 HCS 的认证凭据
|
||||
return template -> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* HCS 降级工厂:非 2xx 时还原远程错误体 msg/traceId,避免全局兜底吞成 null(配合 IHcsClient 的 fallbackFactory 声明)
|
||||
*/
|
||||
@Bean
|
||||
public HcsClientFallbackFactory hcsClientFallbackFactory() {
|
||||
return new HcsClientFallbackFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* HCS 认证拦截器:所有请求注入 Authorization + Key-Auth 两个静态认证头(v4 接口说明)
|
||||
*/
|
||||
@Bean
|
||||
public RequestInterceptor hcsAuthInterceptor(HcsProperties properties) {
|
||||
return new HcsAuthInterceptor(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public feign.Logger.Level feignLoggerLevel() {
|
||||
return feign.Logger.Level.FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 超时:connect 10s / read 60s(照 BfiFeignClientConfig,D7)
|
||||
*/
|
||||
@Bean
|
||||
public Request.Options options() {
|
||||
return new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true);
|
||||
}
|
||||
}
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springblade.hcs.dto.HcsRefVO;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)配置类
|
||||
* <p>
|
||||
* Nacos 配置段见 third-party-api.yaml 的 thirdParty.hcs 节点:
|
||||
* 注解前缀按 Spring Boot 规范使用横线小写形式 third-party.hcs(照 BfiProperties 惯例),
|
||||
* YAML 中的驼峰键 thirdParty.hcs.* 经宽松绑定归一后等价生效。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "third-party.hcs")
|
||||
public class HcsProperties {
|
||||
/**
|
||||
* 合同合规系统(HCS)接口基础地址,直连可配(网关/iPaaS 部署时改此处,代码不感知)。
|
||||
* v4 接口说明:地址带网关前缀,如 http://172.16.203.134:8000/gwzh/HTHG
|
||||
*/
|
||||
private String baseUrl;
|
||||
/**
|
||||
* Authorization 请求头整值(含 Basic 前缀,如 Basic d2Njazp...,凭据走 Nacos + 环境变量注入)
|
||||
*/
|
||||
private String authorization;
|
||||
/**
|
||||
* Key-Auth 请求头值(v4 接口说明:所有接口固定携带 Authorization + Key-Auth 两个头)
|
||||
*/
|
||||
private String keyAuth;
|
||||
/**
|
||||
* 来源系统标识(fd_sources,D14 约定值 TMS)
|
||||
*/
|
||||
private String srcSystem = "WLTMS";
|
||||
/**
|
||||
* 操作人登录名(fdLoginName / curUser.fdLoginName,全部合同接口共用,§9-U2)
|
||||
*/
|
||||
private String operatorLoginName = "srvadmin";
|
||||
/**
|
||||
* Mock 开关(thirdParty.hcs.mockEnabled):true 时 IHcsClient 切换为本地 JSON 桩
|
||||
* ({mockDir}/{方法名}.json,见 {@link org.springblade.hcs.mock.HcsClientMock}),
|
||||
* 供联调前本地自测;生产环境必须为 false
|
||||
*/
|
||||
private boolean mockEnabled = false;
|
||||
/**
|
||||
* Mock 桩文件目录(与 mockEnabled 配套):存放各接口出参桩 {方法名}.json(源自 HCS 接口文档出参示例),
|
||||
* 每次调用现读,改文件即生效;默认本机开发目录,其他环境经 Nacos 按需覆盖
|
||||
*/
|
||||
private String mockDir = "F:/home/tms/mock/hcs";
|
||||
/**
|
||||
* 创建单位 fdId(HCS 组织主数据固定值,fd_creator_unit 等组织引用,各合同接口共用)
|
||||
*/
|
||||
private String creatorUnitId = "1j56etj23w358wne0ow2k5214nk4f24213w0";
|
||||
/**
|
||||
* 创建单位名称(与 creatorUnitId 配套,HCS 组织主数据固定值,各合同接口共用)
|
||||
*/
|
||||
private String creatorUnitName = "广西物产城矿发展有限公司";
|
||||
/**
|
||||
* 推送任务配置(扫描首推/补偿)
|
||||
*/
|
||||
private Push push = new Push();
|
||||
/**
|
||||
* 项目立项(C102)配置
|
||||
*/
|
||||
private Project project = new Project();
|
||||
/**
|
||||
* 合同(C103)配置
|
||||
*/
|
||||
private Contract contract = new Contract();
|
||||
/**
|
||||
* 履约规则(C103-9)配置
|
||||
*/
|
||||
private ExeStage exeStage = new ExeStage();
|
||||
/**
|
||||
* 客商风险校验(C101-1)配置
|
||||
*/
|
||||
private Risk risk = new Risk();
|
||||
/**
|
||||
* 额度校验(C106-1)配置
|
||||
*/
|
||||
private Credit credit = new Credit();
|
||||
/**
|
||||
* 结算付款特批(C106-3/4)配置
|
||||
*/
|
||||
private SpecPay specPay = new SpecPay();
|
||||
/**
|
||||
* 履约记录(C108-1/2)配置
|
||||
*/
|
||||
private PerfRecord perfRecord = new PerfRecord();
|
||||
/**
|
||||
* 开票登记(C111)配置
|
||||
*/
|
||||
private Invoice invoice = new Invoice();
|
||||
/**
|
||||
* 收票单(C109)配置
|
||||
*/
|
||||
private ReceiptBill receiptBill = new ReceiptBill();
|
||||
/**
|
||||
* 附件转传(C103-2)配置
|
||||
*/
|
||||
private Attachment attachment = new Attachment();
|
||||
/**
|
||||
* 主数据缓存(C103-6/7/12)配置
|
||||
*/
|
||||
private MasterData masterData = new MasterData();
|
||||
|
||||
/**
|
||||
* 创建单位引用解析:fdId+fdName 配置优先(third-party.hcs.creator-unit-id/name),
|
||||
* 任一未配置回退单据部门名称仅传 fdName(各合同接口共用)
|
||||
*
|
||||
* @param fallbackName 单据自身部门名称(如开题部门名称)
|
||||
* @return 创建单位引用
|
||||
*/
|
||||
public HcsRefVO resolveCreatorUnit(String fallbackName) {
|
||||
if (creatorUnitId == null || creatorUnitId.isEmpty() || creatorUnitName == null || creatorUnitName.isEmpty()) {
|
||||
return HcsRefVO.ofName(fallbackName);
|
||||
}
|
||||
HcsRefVO ref = new HcsRefVO();
|
||||
ref.setFdId(creatorUnitId);
|
||||
ref.setFdName(creatorUnitName);
|
||||
return ref;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Push {
|
||||
/**
|
||||
* 审批通过即时推送总开关(thirdParty.hcs.push.immediate-enabled,默认关闭灰度打开;关闭时完全回落扫描链路)
|
||||
*/
|
||||
private boolean immediateEnabled = false;
|
||||
/**
|
||||
* 每类单据单轮扫描批量(全局)
|
||||
*/
|
||||
private int batchSize = 50;
|
||||
/**
|
||||
* 推送失败自动重试上限,超限仅人工重推(全局)
|
||||
*/
|
||||
private int maxRetry = 5;
|
||||
/**
|
||||
* 项目立项(C102)推送开关(thirdParty.hcs.push.project.sync-enabled,默认关闭,灰度打开)
|
||||
*/
|
||||
private Task project = new Task();
|
||||
/**
|
||||
* 合同(C103)推送开关(thirdParty.hcs.push.contract.sync-enabled,默认关闭,灰度打开)
|
||||
*/
|
||||
private Task contract = new Task();
|
||||
/**
|
||||
* 付款链(C106 特批 / C108 履约记录)推送开关(thirdParty.hcs.push.payment.sync-enabled,默认关闭,灰度打开)
|
||||
*/
|
||||
private Task payment = new Task();
|
||||
/**
|
||||
* 开票登记(C111)推送开关(thirdParty.hcs.push.invoice-application.sync-enabled,默认关闭,灰度打开)
|
||||
*/
|
||||
private Task invoiceApplication = new Task();
|
||||
/**
|
||||
* 收票单(C109)推送开关(thirdParty.hcs.push.invoice-receipt.sync-enabled,默认关闭,灰度打开)
|
||||
*/
|
||||
private Task invoiceReceipt = new Task();
|
||||
}
|
||||
|
||||
/**
|
||||
* 单类单据推送任务配置(thirdParty.hcs.push.{bizType}.sync-enabled;
|
||||
* cron 有代码默认值错峰每 5 分钟,保持 @Scheduled 占位符形式,未纳入本类)
|
||||
*/
|
||||
@Data
|
||||
public static class Task {
|
||||
/**
|
||||
* 推送开关,默认关闭(灰度打开)
|
||||
*/
|
||||
private boolean syncEnabled = false;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Project {
|
||||
/**
|
||||
* 立项 bpmOperation.templateCode(环境相关值,§9-U9,待合规方提供)
|
||||
*/
|
||||
private String templateCode = "";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Contract {
|
||||
/**
|
||||
* TMS 合同类别 -> HCS 模板ID(fdTemplateId,§9-U7 待合规方提供模板清单)
|
||||
*/
|
||||
private Map<String, String> templateMapping = new HashMap<>();
|
||||
/**
|
||||
* TMS 审核状态 -> HCS 合同状态(xformFields CONTRACT_STATUS 必填,draft/approving/approved 等,§9-U3 待联调后配置)
|
||||
*/
|
||||
private Map<String, String> statusMapping = new HashMap<>();
|
||||
/**
|
||||
* TMS 币种码 -> HCS 币种名称(照金蝶 RMB->CNY 映射惯例)
|
||||
*/
|
||||
private Map<String, String> currencyNameMapping = new HashMap<>(Map.of("RMB", "人民币"));
|
||||
/**
|
||||
* 合同 fdSepcailContract 固定 false(D15,配置项预留)
|
||||
*/
|
||||
private boolean specialContractEnabled = false;
|
||||
/**
|
||||
* 合同推送前是否强制客商风险校验
|
||||
*/
|
||||
private boolean requireRiskCheck = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ExeStage {
|
||||
/**
|
||||
* 履约规则同步开关(C103-9 行级同步,一期暂不对接,默认关)
|
||||
*/
|
||||
private boolean syncEnabled = false;
|
||||
/**
|
||||
* 履约规则金额类型默认值(fdFundType)
|
||||
*/
|
||||
private String fundType = "byAmount";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Risk {
|
||||
/**
|
||||
* 合同推送前风险校验开关
|
||||
*/
|
||||
private boolean checkOnContract = true;
|
||||
/**
|
||||
* 付款提交前风险校验开关
|
||||
*/
|
||||
private boolean checkOnPayment = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Credit {
|
||||
/**
|
||||
* 额度校验开关(C106-1,一期暂不对接,默认关;关闭后特批无自动触发)
|
||||
*/
|
||||
private boolean checkEnabled = false;
|
||||
/**
|
||||
* 付款提交校验是否要求合同已推送合规(hcs_fd_id 存在)
|
||||
*/
|
||||
private boolean requireContractSynced = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SpecPay {
|
||||
/**
|
||||
* 特批单据状态(fd_doc_status,示例 10,§9-U3 待联调)
|
||||
*/
|
||||
private String docStatus = "10";
|
||||
/**
|
||||
* 付款用途字典值(fd_payment_purpose,§9-U3 待联调后配置)
|
||||
*/
|
||||
private String paymentPurpose = "";
|
||||
/**
|
||||
* 付款性质字典值(fd_payment_nature,§9-U3 待联调后配置)
|
||||
*/
|
||||
private String paymentNature = "";
|
||||
/**
|
||||
* 付款类型字典值(fd_payment_type,§9-U3 待联调后配置)
|
||||
*/
|
||||
private String paymentType = "";
|
||||
/**
|
||||
* 付款特批流程模板ID(fdTemplateId,每个环境/公司不同,按环境在 Nacos 维护)
|
||||
*/
|
||||
private String templateId = "";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Invoice {
|
||||
/**
|
||||
* TMS 发票类型 -> HCS 发票类型字典值(§9-U3 待联调后配置)
|
||||
*/
|
||||
private Map<String, String> invoiceTypeMapping = new HashMap<>();
|
||||
/**
|
||||
* TMS 币种码 -> HCS 币种 fdId(经 C103-12 币种表缓存取值,§9-U3 待联调后配置)
|
||||
*/
|
||||
private Map<String, String> currencyIdMapping = new HashMap<>();
|
||||
/**
|
||||
* 开票登记款项性质默认值(fdFundNature)
|
||||
*/
|
||||
private String fundNatureDefault = "progress";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ReceiptBill {
|
||||
/**
|
||||
* TMS 发票类型 -> HCS 收票发票类型字典值(§9-U3 待联调后配置)
|
||||
*/
|
||||
private Map<String, String> invoiceTypeMapping = new HashMap<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PerfRecord {
|
||||
/**
|
||||
* 履约记录同步开关(C108-1/2,一期暂不对接,默认关)
|
||||
*/
|
||||
private boolean syncEnabled = false;
|
||||
/**
|
||||
* 履约记录表单模板ID(fd_xform_id,环境相关值,§9-U9 待合规方提供)
|
||||
*/
|
||||
private String xformId = "";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Attachment {
|
||||
/**
|
||||
* 附件转传总开关
|
||||
*/
|
||||
private boolean transferEnabled = true;
|
||||
/**
|
||||
* TMS 文件服务下载基址(拉取附件流用)
|
||||
*/
|
||||
private String fileDownloadBase = "";
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MasterData {
|
||||
/**
|
||||
* 模板/分类/币种列表缓存时长(分钟),启动/首次使用时拉取并缓存(设计文档 §5.9)
|
||||
*/
|
||||
private int ttlMinutes = 120;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 第三方API 合同合规系统(HCS)配置类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@ComponentScan("org.springblade.hcs")
|
||||
@EnableConfigurationProperties(HcsProperties.class)
|
||||
@Configuration
|
||||
public class ThirdPartyHcsAutoConfiguration {
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.constant;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)常量类
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class HcsConstant {
|
||||
|
||||
/**
|
||||
* 接口调用成功 messageKey(统一出参 code,业务成败以 success 字段为准)
|
||||
*/
|
||||
public static final String SUCCESS_CODE = "return.optSuccess";
|
||||
|
||||
/**
|
||||
* 网关认证请求头名称(整值可配,如 Basic d2Njazp...)
|
||||
*/
|
||||
public static final String HEADER_AUTHORIZATION = "Authorization";
|
||||
|
||||
/**
|
||||
* 网关 Key-Auth 认证请求头名称(v4 接口说明:所有接口固定携带)
|
||||
*/
|
||||
public static final String HEADER_KEY_AUTH = "Key-Auth";
|
||||
|
||||
/**
|
||||
* signer 相对方单位类型:甲方=inner(内部主体)
|
||||
*/
|
||||
public static final String SIGNER_FD_TYPE_INNER = "inner";
|
||||
|
||||
/**
|
||||
* signer 相对方单位类型:乙方=supplier(外部相对方)
|
||||
*/
|
||||
public static final String SIGNER_FD_TYPE_SUPPLIER = "supplier";
|
||||
/**
|
||||
* signer 相对方单位类型:客户=customer
|
||||
*/
|
||||
public static final String SIGNER_FD_TYPE_CUSTOMER = "customer";
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-5 附件下载入参(对账/排查备用)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsAttachmentDownloadDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 附件ID
|
||||
*/
|
||||
private String fdId;
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)附件引用项(附件先经 C103-2 上传取得 fdId,再经 mechanisms.attachment[] 引用)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsAttachmentRefDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 附件ID(附件上传接口返回)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 附件名称(不含扩展名)
|
||||
*/
|
||||
private String fdFileName;
|
||||
/**
|
||||
* 附件扩展名
|
||||
*/
|
||||
private String fdFileExtName;
|
||||
/**
|
||||
* 附件完整文件名
|
||||
*/
|
||||
private String fullName;
|
||||
/**
|
||||
* 附件绑定的表单字段 key:特批 fd_spc_approval_attach、履约记录 fd_plan_att、收票单 fd_attach
|
||||
*/
|
||||
private String fdEntityKey;
|
||||
/**
|
||||
* 绑定类型:仅发起类接口传 ADD,更新类接口不传
|
||||
*/
|
||||
private String fdBindType;
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-2 附件上传出参
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsAttachmentVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 附件ID(调用方必须持久化/缓存映射,供各业务接口引用)
|
||||
*/
|
||||
private String fdId;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C102-1 项目立项新建 流程操作入参
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsBpmOperationDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 操作类型:固定 drafter_submit(以起草人身份直接提交流程)
|
||||
*/
|
||||
private String operationType;
|
||||
/**
|
||||
* 立项表单模板编码(环境相关值,配置 thirdParty.hcs.project.template-code,§9-U9)
|
||||
*/
|
||||
private String templateCode;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-7 合同分类列表出参行(fdParent 可组装分类树)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsCategoryVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 分类名称(查询条件 fdName.$contains)
|
||||
*/
|
||||
private String fdName;
|
||||
/**
|
||||
* 上级分类
|
||||
*/
|
||||
private HcsRefVO fdParent;
|
||||
/**
|
||||
* 状态:enable=启用 / disable=停用
|
||||
*/
|
||||
private String fdStatus;
|
||||
/**
|
||||
* 层级ID:由各级分类 fdId 以 x 包裹拼接(x+上级fdId+x+fdId+x,v4 新增)
|
||||
*/
|
||||
private String fdHierarchyId;
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private HcsRefVO fdCreator;
|
||||
/**
|
||||
* 创建人部门
|
||||
*/
|
||||
private HcsRefVO fdCreatorDept;
|
||||
/**
|
||||
* 创建时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdCreateTime;
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C107-1 新增认领记录入参(将收款流水认领到合同/履约项)
|
||||
* <p>
|
||||
* 校验:fdCollectionFlow.fdId、fdContractNumber(须在合规本地存在)、fdClaimAmount 必填,
|
||||
* 认领金额必须大于 0 且不能超过该流水收款金额;fdExePlanStageId 可选,若传须属于该合同。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsClaimRecordAddDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 收款流水(fdId 必填,为合规系统流水ID)
|
||||
*/
|
||||
private HcsRefVO fdCollectionFlow;
|
||||
/**
|
||||
* 认领合同 {fdId, fdName}(fdId 为合规合同ID,随编号一并回溯)
|
||||
*/
|
||||
private HcsRefVO fdContract;
|
||||
/**
|
||||
* 认领合同编号(必填,须在合规系统本地存在)
|
||||
*/
|
||||
private String fdContractNumber;
|
||||
/**
|
||||
* 对应履约项ID(可选,须属于该合同)
|
||||
*/
|
||||
private String fdExePlanStageId;
|
||||
/**
|
||||
* 认领款项类型(prepayment=预付款,progress=进度款,acceptance=验收款,retention=质保金)
|
||||
*/
|
||||
private String fdFundNature;
|
||||
/**
|
||||
* 认领人 {fdId, fdName}
|
||||
*/
|
||||
private HcsRefVO fdClaimer;
|
||||
/**
|
||||
* 认领人名称
|
||||
*/
|
||||
private String fdClaimerName;
|
||||
/**
|
||||
* 认领人部门 {fdId, fdName}
|
||||
*/
|
||||
private HcsRefVO fdClaimerDept;
|
||||
/**
|
||||
* 认领人部门名称
|
||||
*/
|
||||
private String fdClaimerDeptName;
|
||||
/**
|
||||
* 认领金额(必填,必须大于0,且不能超过收款金额)
|
||||
*/
|
||||
private BigDecimal fdClaimAmount;
|
||||
/**
|
||||
* 认领日期(毫秒时间戳)
|
||||
*/
|
||||
private Long fdClaimDate;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String fdRemark;
|
||||
/**
|
||||
* 状态:0=已认领(默认),1=已作废
|
||||
*/
|
||||
private Integer fdStatus;
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for their own commercial purposes.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this license agreement and terms of use,
|
||||
* and you expressly agree to be bound by the terms of use of this software.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C104-2 收款流水分页行(认领记录推送前按明细流水号定位合规侧收款流水ID)
|
||||
* <p>
|
||||
* 合规侧收款流水由合规自 ERP 侧同步(fdDataSource 默认 erp),TMS 以明细流水号(fdDetailSerialNumber)
|
||||
* 精确匹配取 fdId,供 C107-1 认领记录的 fdCollectionFlow.fdId 引用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsCollectionFlowVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合规收款流水ID(C107-1 fdCollectionFlow.fdId 引用)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 认领公告
|
||||
*/
|
||||
private String fdClaimNotice;
|
||||
/**
|
||||
* 明细流水号(与 TMS blade_kingdee_receipt_flow.detail_serial_no 同源)
|
||||
*/
|
||||
private String fdDetailSerialNumber;
|
||||
/**
|
||||
* 收款金额
|
||||
*/
|
||||
private BigDecimal fdCollectionAmount;
|
||||
/**
|
||||
* 认领状态:0=未认领,1=部分认领,2=认领完成
|
||||
*/
|
||||
private Integer fdClaimStatus;
|
||||
/**
|
||||
* 已认领金额
|
||||
*/
|
||||
private BigDecimal fdClaimedAmount;
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-4 合同详情入参(fdId 与 fdNumber 二选一)
|
||||
* <p>
|
||||
* TODO 待联调确认:出参 data 结构 v4 未定义,对账功能联调后以实际返回为准(设计文档 §5.9)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsContractDetailQueryDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 合同编号
|
||||
*/
|
||||
private String fdNumber;
|
||||
}
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-4 合同详情出参(v4 字段表)
|
||||
* <p>
|
||||
* 级联/集合类字段(对象/数组)内部结构 v4 标注以实际返回为准,暂以 Object/List<Object> 承接,
|
||||
* 联调确认后细化。fdSepcailContract 为接口文档原文拼写(Special 笔误,必须沿用)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsContractDetailVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String fdTenantId;
|
||||
/**
|
||||
* 可阅读标示
|
||||
*/
|
||||
private String fdReaderFlag;
|
||||
/**
|
||||
* 不可阅读标记(可阅读者在文档发布前生效)
|
||||
*/
|
||||
private String fdNotReaderFlag;
|
||||
/**
|
||||
* 可阅读者(在文档发布前生效)
|
||||
*/
|
||||
private String fdRBPFlag;
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
private String fdAlter;
|
||||
/**
|
||||
* 修改时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdAlterTime;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String fdDesc;
|
||||
/**
|
||||
* 是否隐藏
|
||||
*/
|
||||
private Boolean fdIsHidden;
|
||||
/**
|
||||
* 最后修改时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdLastModifiedTime;
|
||||
/**
|
||||
* 合同名称
|
||||
*/
|
||||
private String fdSubject;
|
||||
/**
|
||||
* 合同分类(层级ID)
|
||||
*/
|
||||
private String fdCateHierarchyId;
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private String fdNumber;
|
||||
/**
|
||||
* 状态:draft=草稿; approving=审批中; approved=已审批; tosign=待签订; signing=签订中; signerror=签订异常; signed=已签订等
|
||||
*/
|
||||
private String fdStatus;
|
||||
/**
|
||||
* 合同发起ID
|
||||
*/
|
||||
private String fdDraftMainId;
|
||||
/**
|
||||
* 合同模板ID
|
||||
*/
|
||||
private String fdTemplateId;
|
||||
/**
|
||||
* 审批状态:approving=审批中; approved=已审批; approvalinvalid=审批作废
|
||||
*/
|
||||
private String fdApprovalStatus;
|
||||
/**
|
||||
* 签订状态:tosign=待签订; signing=签订中; signerror=签订异常; signed=已签订; signinvalid=签订作废
|
||||
*/
|
||||
private String fdSignStatus;
|
||||
/**
|
||||
* 签订异常信息
|
||||
*/
|
||||
private String fdSignErrorMsg;
|
||||
/**
|
||||
* 进入待签订时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdToSignTime;
|
||||
/**
|
||||
* 签订发起时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdSignCreateTime;
|
||||
/**
|
||||
* 完成签订时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdSignedTime;
|
||||
/**
|
||||
* 归档状态:toarchive=待归档; archiving=归档中; archived=已归档; archiveerror=归档异常
|
||||
*/
|
||||
private String fdArchiveStatus;
|
||||
/**
|
||||
* 变更状态:changing=变更中; changed=已变更; terminated=已终止
|
||||
*/
|
||||
private String fdChangeStatus;
|
||||
/**
|
||||
* 履约状态:toplan=待履约; planing=履约中; planerror=履约异常; planed=已履约; planterminate=履约终止
|
||||
*/
|
||||
private String fdPlanStatus;
|
||||
/**
|
||||
* 进入待履约时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdToPlanTime;
|
||||
/**
|
||||
* 完成履约时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdPlanedTime;
|
||||
/**
|
||||
* 关闭状态:toclose=待关闭; closing=关闭中; closed=已关闭
|
||||
*/
|
||||
private String fdCloseStatus;
|
||||
/**
|
||||
* 进入待关闭时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdToCloseTime;
|
||||
/**
|
||||
* 关闭时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdClosedTime;
|
||||
/**
|
||||
* 签署提供者:psign=物理签署; esign=电子签署; qjsign=智能用印签署
|
||||
*/
|
||||
private String fdSignProvider;
|
||||
/**
|
||||
* 签署实体ID
|
||||
*/
|
||||
private String fdSignEntityId;
|
||||
/**
|
||||
* 归档方式:auto=一键申请; review=流程申请
|
||||
*/
|
||||
private String fdFileWay;
|
||||
/**
|
||||
* 归档实体ID
|
||||
*/
|
||||
private String fdArchiveEntityId;
|
||||
/**
|
||||
* 进入待归档时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdToArchiveTime;
|
||||
/**
|
||||
* 完成归档时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdArchivedTime;
|
||||
/**
|
||||
* 履约计划创建时机:draft=起草环节创建; toplan=履约环节创建
|
||||
*/
|
||||
private String fdPlanCreateMoment;
|
||||
/**
|
||||
* 关闭方式:auto=一键关闭; review=流程关闭
|
||||
*/
|
||||
private String fdCloseWay;
|
||||
/**
|
||||
* 关闭实体ID
|
||||
*/
|
||||
private String fdCloseEntityId;
|
||||
/**
|
||||
* 是否锁定
|
||||
*/
|
||||
private Boolean fdIsLocked;
|
||||
/**
|
||||
* 锁定原因:invalid=合同作废; change=合同变更; termination=合同终止
|
||||
*/
|
||||
private String fdLockReason;
|
||||
/**
|
||||
* 合同作废实体ID
|
||||
*/
|
||||
private String fdInvalidEntityId;
|
||||
/**
|
||||
* 上次合同变动发起实体ID
|
||||
*/
|
||||
private String fdLastChangeEntityId;
|
||||
/**
|
||||
* 合同终止发起实体ID
|
||||
*/
|
||||
private String fdTerminationEntityId;
|
||||
/**
|
||||
* 批量发起任务ID
|
||||
*/
|
||||
private String fdBatchTaskId;
|
||||
/**
|
||||
* 是否关联交易
|
||||
*/
|
||||
private Boolean fdIsRelaDeal;
|
||||
/**
|
||||
* 是否补录
|
||||
*/
|
||||
private Boolean fdIsRecord;
|
||||
/**
|
||||
* 履约计划(级联字段,结构以实际返回为准)
|
||||
*/
|
||||
private Object fdPlan;
|
||||
/**
|
||||
* 验收计划(级联字段,结构以实际返回为准)
|
||||
*/
|
||||
private Object fdAcptPlan;
|
||||
/**
|
||||
* 外部合同数据(级联字段,结构以实际返回为准)
|
||||
*/
|
||||
private Object fdOuterAttr;
|
||||
/**
|
||||
* 订单明细配置ID
|
||||
*/
|
||||
private String fdOrderDetailConfigId;
|
||||
/**
|
||||
* 收付款进度(结构以实际返回为准)
|
||||
*/
|
||||
private Object fdPayColProgress;
|
||||
/**
|
||||
* 开票进度(结构以实际返回为准)
|
||||
*/
|
||||
private Object fdInvoiceProgress;
|
||||
/**
|
||||
* 履约状态(新):0=正常; 1=低风险; 2=中风险; 3=高风险
|
||||
*/
|
||||
private Integer fdPlanStatusNew;
|
||||
/**
|
||||
* 是否冻结
|
||||
*/
|
||||
private Boolean fdIsFreeze;
|
||||
/**
|
||||
* 解冻表单关联ID
|
||||
*/
|
||||
private String fdUnfreezeFormId;
|
||||
/**
|
||||
* 合同履约流程状态:00/01/02/10/11/12/20/21/29/30/39
|
||||
*/
|
||||
private String fdPlanFlowStatus;
|
||||
/**
|
||||
* 合同履约流程ID
|
||||
*/
|
||||
private String fdPlanFlowId;
|
||||
/**
|
||||
* 是否评分
|
||||
*/
|
||||
private Boolean fdRated;
|
||||
/**
|
||||
* 完成审批时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdApprovedTime;
|
||||
/**
|
||||
* 是否同步金蝶
|
||||
*/
|
||||
private Boolean fdIsSyncJd;
|
||||
/**
|
||||
* 同步金蝶状态:0/1/2
|
||||
*/
|
||||
private Integer fdSyncJdStatus;
|
||||
/**
|
||||
* 特批合同(字段名原文拼写笔误,必须沿用)
|
||||
*/
|
||||
private Boolean fdSepcailContract;
|
||||
/**
|
||||
* 合同结算模式:open=开口合同; close=闭口合同。开口合同指合同签订时不明确约定总金额、总数量、具体收付款日期,按实际发生的供货量、业务量结算的合同
|
||||
*/
|
||||
private String fdContractGwNature;
|
||||
/**
|
||||
* 合同类型:standard=标准合同; halfstandard=半标准合同; notstandard=非标准合同
|
||||
*/
|
||||
private String fdContractCharacter;
|
||||
/**
|
||||
* 合同性质:general=普通合同; framework=框架协议
|
||||
*/
|
||||
private String fdContractNature;
|
||||
/**
|
||||
* 引用范本ID
|
||||
*/
|
||||
private String fdModelId;
|
||||
/**
|
||||
* 合同属性:main=主合同; child=子合同; common=普通
|
||||
*/
|
||||
private String fdContractAttribute;
|
||||
/**
|
||||
* 承办人
|
||||
*/
|
||||
private HcsRefVO fdUnderTaker;
|
||||
/**
|
||||
* 承办人部门
|
||||
*/
|
||||
private HcsRefVO fdUnderTakerDept;
|
||||
/**
|
||||
* 预计开始时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdEstimateStartTime;
|
||||
/**
|
||||
* 预计结束时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdEstimateEndTime;
|
||||
/**
|
||||
* 实际开始时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdStartTime;
|
||||
/**
|
||||
* 实际结束时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdEndTime;
|
||||
/**
|
||||
* 合同发起来源:inner=内部发起; outer=外部发起
|
||||
*/
|
||||
private String fdContractSource;
|
||||
/**
|
||||
* 外部来源系统
|
||||
*/
|
||||
private String fdOuterSystem;
|
||||
/**
|
||||
* 合同结算类型:payment=付款; payment_collection=收支双向; collection=收款; none=不结算
|
||||
*/
|
||||
private String fdSettlementType;
|
||||
/**
|
||||
* 合同金额
|
||||
*/
|
||||
private BigDecimal fdAmount;
|
||||
/**
|
||||
* 币种
|
||||
*/
|
||||
private String fdCurrencyId;
|
||||
/**
|
||||
* 是否原始合同
|
||||
*/
|
||||
private Boolean fdIsOrigin;
|
||||
/**
|
||||
* 原始合同ID
|
||||
*/
|
||||
private String fdOriginContractId;
|
||||
/**
|
||||
* 合同变更类型:change=合同变更; termination=合同终止
|
||||
*/
|
||||
private String fdChangeType;
|
||||
/**
|
||||
* 是否含税
|
||||
*/
|
||||
private Boolean fdIncludeTax;
|
||||
/**
|
||||
* 是否引用范本
|
||||
*/
|
||||
private Boolean fdUseModel;
|
||||
/**
|
||||
* 引用范本名称
|
||||
*/
|
||||
private String fdModelName;
|
||||
/**
|
||||
* 引用范本版本
|
||||
*/
|
||||
private String fdModelVersion;
|
||||
/**
|
||||
* 是否固定期限
|
||||
*/
|
||||
private Boolean fdIsFixedTerm;
|
||||
/**
|
||||
* 合同开始期限
|
||||
*/
|
||||
private String fdStartTerm;
|
||||
/**
|
||||
* 合同结束期限
|
||||
*/
|
||||
private String fdEndTerm;
|
||||
/**
|
||||
* 有效期限
|
||||
*/
|
||||
private String fdValidTerm;
|
||||
/**
|
||||
* 项目立项ID
|
||||
*/
|
||||
private String fdProjectId;
|
||||
/**
|
||||
* 合同份数
|
||||
*/
|
||||
private Integer fdServings;
|
||||
/**
|
||||
* 合同说明
|
||||
*/
|
||||
private String fdContractDescription;
|
||||
/**
|
||||
* 合同的变更流程状态:changing=变更中; changed=已变更; noChangeProcess=无变更流程
|
||||
*/
|
||||
private String fdChangeLbpmStatus;
|
||||
/**
|
||||
* 紧急程度:1=普通; 2=急件; 3=特急
|
||||
*/
|
||||
private Integer fdUrgencyLevel;
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private HcsRefVO fdCreator;
|
||||
/**
|
||||
* 创建时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdCreateTime;
|
||||
/**
|
||||
* 创建者部门
|
||||
*/
|
||||
private HcsRefVO fdCreatorDept;
|
||||
/**
|
||||
* 权限(级联字段,结构以实际返回为准)
|
||||
*/
|
||||
private Object fdPermissions;
|
||||
/**
|
||||
* 可阅读者(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdReaders;
|
||||
/**
|
||||
* 所有可阅读者(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdAllReaders;
|
||||
/**
|
||||
* 所有可阅读者ID(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdAllReaderIds;
|
||||
/**
|
||||
* 可维护者(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdEditors;
|
||||
/**
|
||||
* 所有可编辑者(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdAllEditors;
|
||||
/**
|
||||
* 协办人(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdCoTakers;
|
||||
/**
|
||||
* 合同时间线(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdTimeLines;
|
||||
/**
|
||||
* 合同关联(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdRelations;
|
||||
/**
|
||||
* 合同签约方及地位(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdStandingSigners;
|
||||
/**
|
||||
* 签印文件(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdSignFiles;
|
||||
/**
|
||||
* 业务明细(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdBusinessDetails;
|
||||
/**
|
||||
* 付款记录(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdPaymentRecords;
|
||||
/**
|
||||
* 收款记录(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdCollectionRecords;
|
||||
/**
|
||||
* 收票记录(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdInInvoices;
|
||||
/**
|
||||
* 开票记录(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdOutInvoices;
|
||||
/**
|
||||
* 预填写物理签印信息(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdPsignContents;
|
||||
/**
|
||||
* 审批人(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdApprovers;
|
||||
/**
|
||||
* 履约规则(结构以实际返回为准)
|
||||
*/
|
||||
private List<Object> fdExePlanRules;
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-1 合同发起入参(C103-11 合同更新入参结构相同,复用本 DTO)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsContractDraftDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同ID(C103-11 合同更新顶层必填,传 C103-1 返回的 fdContractId;C103-1 新建不传)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 合同模板ID(C103-6 模板列表获取,配置 template-mapping,§9-U7)
|
||||
*/
|
||||
private String fdTemplateId;
|
||||
/**
|
||||
* 外部系统标识(fd_sources,约定值 WLTMS,配置 thirdParty.hcs.src-system)
|
||||
*/
|
||||
private String fdOuterSystem;
|
||||
/**
|
||||
* 操作人登录名
|
||||
*/
|
||||
private String fdLoginName;
|
||||
/**
|
||||
* 保存类型:submit(提交)
|
||||
*/
|
||||
private String saveType;
|
||||
/**
|
||||
* 合同字段(模板字段 + 签署方)
|
||||
*/
|
||||
private HcsContractFieldJsonDTO fdFieldJson;
|
||||
/**
|
||||
* 附件数据(与 fdFieldJson 同级;C103-1 必填,无附件传空列表,C103-11 更新不传;fdEntityKey 普通附件枚举待合规方给出,暂不传)
|
||||
*/
|
||||
private List<HcsAttachmentRefDTO> attachment;
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-1 合同发起出参(fdContractId 必须持久化,用于合同更新、履约规则同步、额度校验等)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsContractDraftVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同ID(回填 TMS blade_contract_manage.hcs_fd_id)
|
||||
*/
|
||||
private String fdContractId;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-1 合同发起 fdFieldJson 节点(C103-1 新建 / C103-11 更新共用)
|
||||
* <p>
|
||||
* 附件挂载在顶层报文 attachment(与 fdFieldJson 同级),见 HcsContractDraftDTO。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsContractFieldJsonDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 模板字段值列表
|
||||
*/
|
||||
private List<HcsXformFieldDTO> xformFields;
|
||||
/**
|
||||
* 签署方(相对方)列表
|
||||
*/
|
||||
private List<HcsSignerDTO> signer;
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同特批完成后合规系统回传 TMS 的结果报文(POST /hcs/contractLegal/result,iPaaS 网关 /gwzh/HTHG/contractLegal/result)
|
||||
* <p>
|
||||
* 报文为"合同同步结果"结构:合规特批完成后同步金蝶,回调携带金蝶同步结果;
|
||||
* 定位合同用 contractCode(ERP 合同编号),requestId 仅是 X-ERP-Request-Id 请求头原样回传、不能用于定位。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsContractLegalResultDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同类型:deliverer=投送人;gc=钢厂;sett_center=结算中心
|
||||
*/
|
||||
private String contractType;
|
||||
/**
|
||||
* ERP 本次同步使用的合同编号(TMS blade_contract_manage.contract_no,定位合同用)
|
||||
*/
|
||||
private String contractCode;
|
||||
/**
|
||||
* 本次合同同步请求头 X-ERP-Request-Id,原样回传(不用于定位)
|
||||
*/
|
||||
private String requestId;
|
||||
/**
|
||||
* 金蝶同步成功传 true,失败传 false
|
||||
*/
|
||||
private Boolean success;
|
||||
/**
|
||||
* 金蝶同步失败原因;失败时建议填写
|
||||
*/
|
||||
private String reason;
|
||||
/**
|
||||
* 金蝶实际返回的合同 ID;成功时用于保存留痕
|
||||
*/
|
||||
private String kingdeeId;
|
||||
/**
|
||||
* 来源系统标识,约定传 WCCK(最长 64 字符)
|
||||
*/
|
||||
private String sourceSystem;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-3 合同信息列表出参行(判重与对账回查,按 columns 返回)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsContractVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 合同标题(判重条件 fdNumber 关联核对)
|
||||
*/
|
||||
private String fdSubject;
|
||||
/**
|
||||
* 合同编号(判重条件,§9-U4 查询能力联调确认)
|
||||
*/
|
||||
private String fdNumber;
|
||||
/**
|
||||
* 合同分类层级ID
|
||||
*/
|
||||
private String fdCateHierarchyId;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C106-1 项目/合同额度校验入参
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsCreditCheckDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同ID(必填,HCS 合同ID blade_contract_manage.hcs_fd_id)
|
||||
*/
|
||||
private String fdContractId;
|
||||
/**
|
||||
* 申请金额(必填,元)
|
||||
*/
|
||||
private BigDecimal fdApplyAmount;
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C106-1 项目/合同额度校验出参
|
||||
* <p>
|
||||
* 注意:超额时外层 success 仍为 true,必须以 fdIsCredit 为准;超额不拦截提交,转入特批链路(D15/R2-Q2)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsCreditCheckVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 额度是否通过:true 通过 / false 超额(必看字段)
|
||||
*/
|
||||
private Boolean fdIsCredit;
|
||||
/**
|
||||
* 合同ID
|
||||
*/
|
||||
private String fdContractId;
|
||||
/**
|
||||
* 合同金额
|
||||
*/
|
||||
private BigDecimal fdContractAmount;
|
||||
/**
|
||||
* 已付金额
|
||||
*/
|
||||
private BigDecimal fdPayedAmount;
|
||||
/**
|
||||
* 超额原因说明(回填 hcs_credit_msg)
|
||||
*/
|
||||
private String fdMessage;
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-12 合同币种列表出参行(开票登记 fdCurrencyId、履约计划币种取自本接口)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsCurrencyVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 币种ID(currency-id-mapping 目标值)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 币种中文名(查询条件 fdCName.$contains)
|
||||
*/
|
||||
private String fdCName;
|
||||
/**
|
||||
* 币种简称
|
||||
*/
|
||||
private String fdSName;
|
||||
/**
|
||||
* 币种英文名(如 CNY)
|
||||
*/
|
||||
private String fdEName;
|
||||
/**
|
||||
* 币种编码
|
||||
*/
|
||||
private String fdNumber;
|
||||
/**
|
||||
* 币种符号
|
||||
*/
|
||||
private String fdSymbol;
|
||||
/**
|
||||
* 状态:enable / disable
|
||||
*/
|
||||
private String fdStatus;
|
||||
/**
|
||||
* 是否本位币
|
||||
*/
|
||||
private Boolean fdIsStandardCurrency;
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private HcsRefVO fdCreator;
|
||||
/**
|
||||
* 创建时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdCreateTime;
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-9(新) 履约规则同步 行级操作项(数据源 blade_contract_exe_stage,ADR-0002)
|
||||
* <p>
|
||||
* 新增行可不带 fdId;更新/删除行必须带 fdId(合规阶段ID,回填自首次同步返回)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsExeplanStageDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 阶段项ID(行级 update/delete 必传,取 blade_contract_exe_stage.hcs_stage_fd_id)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 行级操作:add / update / delete
|
||||
*/
|
||||
private String fdEopExeplanOperateType;
|
||||
/**
|
||||
* 履约类型:payment=付款 / collection=收款
|
||||
*/
|
||||
private String fdPerformType;
|
||||
/**
|
||||
* 款项性质:prepayment=预付款 / progress=进度款 / acceptance=验收款 / retention=质保金
|
||||
*/
|
||||
private String fdFundNature;
|
||||
/**
|
||||
* 开票规则(如 payFirst)
|
||||
*/
|
||||
private String fdInvoiceRule;
|
||||
/**
|
||||
* 账期
|
||||
*/
|
||||
private Integer fdInvoiceCycle;
|
||||
/**
|
||||
* 款项金额(元,付款审批通过按申请付款金额累加更新)
|
||||
*/
|
||||
private BigDecimal fdFundAmount;
|
||||
/**
|
||||
* 资金日期(毫秒时间戳)
|
||||
*/
|
||||
private Long fdFundDate;
|
||||
/**
|
||||
* 金额类型(默认 byAmount 按金额,配置 thirdParty.hcs.exe-stage.fund-type)
|
||||
*/
|
||||
private String fdFundType;
|
||||
/**
|
||||
* 是否完成
|
||||
*/
|
||||
private Boolean fdIsFinish;
|
||||
/**
|
||||
* 扩展内容 JSON 字符串(固定 "{}")
|
||||
*/
|
||||
private String fdExtendContent;
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-9(新) 履约规则同步出参-变更阶段行
|
||||
* <p>
|
||||
* 合规返回的阶段 fdId 必须持久化(回填 blade_contract_exe_stage.hcs_stage_fd_id),后续行更新/删除全靠它。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsExeplanStageVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 阶段项ID(必须持久化)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 阶段唯一标识
|
||||
*/
|
||||
private String fdUniqueId;
|
||||
/**
|
||||
* 计划金额
|
||||
*/
|
||||
private BigDecimal fdPlanFundAmount;
|
||||
/**
|
||||
* 实际金额
|
||||
*/
|
||||
private BigDecimal fdActualFundAmount;
|
||||
/**
|
||||
* 履约类型:payment / collection
|
||||
*/
|
||||
private String fdPerformType;
|
||||
/**
|
||||
* 款项性质
|
||||
*/
|
||||
private String fdFundNature;
|
||||
/**
|
||||
* 开票规则
|
||||
*/
|
||||
private String fdInvoiceRule;
|
||||
/**
|
||||
* 账期
|
||||
*/
|
||||
private Integer fdInvoiceCycle;
|
||||
/**
|
||||
* 计划金额
|
||||
*/
|
||||
private BigDecimal fdFundAmount;
|
||||
/**
|
||||
* 资金日期(毫秒时间戳)
|
||||
*/
|
||||
private Long fdFundDate;
|
||||
/**
|
||||
* 金额类型
|
||||
*/
|
||||
private String fdFundType;
|
||||
/**
|
||||
* 扩展内容 JSON 字符串
|
||||
*/
|
||||
private String fdExtendContent;
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-9(新) 合同履约规则同步入参(支持整批增删改)
|
||||
* <p>
|
||||
* fdContractId 传 HCS 合同ID(blade_contract_manage.hcs_fd_id,非 TMS 主键);
|
||||
* fdExeplanId 已同步过则传(首次同步返回后持久化到 blade_contract_manage.hcs_exeplan_id)。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsExeplanSyncDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 合同ID(HCS 合同ID)
|
||||
*/
|
||||
private String fdContractId;
|
||||
/**
|
||||
* 外部系统标识(fd_sources,约定值 TMS)
|
||||
*/
|
||||
private String fdOuterSystem;
|
||||
/**
|
||||
* 操作人登录名
|
||||
*/
|
||||
private String fdLoginName;
|
||||
/**
|
||||
* 计划整体操作类型(如 add,随场景)
|
||||
*/
|
||||
private String fdEopExeplanOperateType;
|
||||
/**
|
||||
* 履约计划ID(已同步过则传)
|
||||
*/
|
||||
private String fdExeplanId;
|
||||
/**
|
||||
* 行级操作列表
|
||||
*/
|
||||
private List<HcsExeplanStageDTO> fdStages;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C103-9(新) 履约规则同步出参(fdExeplanId 与变更阶段 fdId 均须持久化)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsExeplanVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 履约计划ID(回填 blade_contract_manage.hcs_exeplan_id)
|
||||
*/
|
||||
private String fdExeplanId;
|
||||
/**
|
||||
* 本次变更的阶段列表(按行回填 hcs_stage_fd_id)
|
||||
*/
|
||||
private List<HcsExeplanStageVO> fdChangeStages;
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)流程单据新建返回结果(C102-1 项目立项、C106-3 特批发起、C108-1 履约记录新建、C109-1 收票单新建出参)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsFormSaveVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 单据主键ID(调用方必须持久化,供后续更新使用)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 表单模板ID
|
||||
*/
|
||||
private String fdXFormId;
|
||||
/**
|
||||
* 单据查看页面路径
|
||||
*/
|
||||
private String docViewUrl;
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)单据ID返回结果(新建类接口出参 data 仅含 fdId 的场景:C103-2/C111-2/C111-3 等)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsIdVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 单据ID(调用方必须持久化,供后续更新/删除/关联使用)
|
||||
*/
|
||||
private String fdId;
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C111-2 新增开票登记入参
|
||||
* <p>
|
||||
* 校验:fdContractNumber(须在合规本地存在)与 fdCurrencyId 必填;
|
||||
* 附件通过 fdAttIds(C103-2 附件上传返回的ID数组)引用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsInvoiceRegAddDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 单据编号(开票申请单号,同租户唯一,判重候选键)
|
||||
*/
|
||||
private String fdNumber;
|
||||
/**
|
||||
* 登记日期(毫秒时间戳)
|
||||
*/
|
||||
private Long fdRegisterDate;
|
||||
/**
|
||||
* 合同编号(必填,经应收结算链路回溯,§9-U10)
|
||||
*/
|
||||
private String fdContractNumber;
|
||||
/**
|
||||
* 合同 {fdId, fdName}(v4 新增,fdId 为合规合同ID,随编号一并回溯)
|
||||
*/
|
||||
private HcsRefVO fdContract;
|
||||
/**
|
||||
* 发票类型(字典映射 invoice-type-mapping,§9-U3)
|
||||
*/
|
||||
private String fdInvoiceType;
|
||||
/**
|
||||
* 结算币种ID(必填,currency-id-mapping,§9-U3)
|
||||
*/
|
||||
private String fdCurrencyId;
|
||||
/**
|
||||
* 开票方(销方 issuerName)
|
||||
*/
|
||||
private String fdInvoiceParty;
|
||||
/**
|
||||
* 款项性质(默认 progress,配置 invoice.fund-nature-default)
|
||||
*/
|
||||
private String fdFundNature;
|
||||
/**
|
||||
* 开票金额(含税)
|
||||
*/
|
||||
private BigDecimal fdInvoiceAmount;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String fdRemark;
|
||||
/**
|
||||
* 状态:0=已开票,1=已作废(新增固定 0)
|
||||
*/
|
||||
private Integer fdStatus;
|
||||
/**
|
||||
* 第三方创建人编码(申请人工号)
|
||||
*/
|
||||
private String fdThirdCreatorCode;
|
||||
/**
|
||||
* 第三方创建部门编码(申请部门编码)
|
||||
*/
|
||||
private String fdThirdCreatorDeptCode;
|
||||
/**
|
||||
* 附件ID列表(C103-2 附件上传返回)
|
||||
*/
|
||||
private List<String> fdAttIds;
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C111-3 更新开票登记入参(金蝶开票完成后回填票据日期/状态)
|
||||
* <p>
|
||||
* fdId 必填(C111-2 新增返回,持久化于 hcs_fd_id),其余可选;作废传 fdStatus=1。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsInvoiceRegUpdateDTO extends HcsInvoiceRegAddDTO {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 开票登记ID(必填,C111-2 新增返回,持久化于 hcs_fd_id)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 票据日期(毫秒时间戳,金蝶开票完成后回填)
|
||||
*/
|
||||
private Long fdBillDate;
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C111-1 查询开票登记列表出参行(判重条件 fdNumber,§9-U4 查询能力联调确认)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsInvoiceRegVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 单据编号(TMS 开票申请单号)
|
||||
*/
|
||||
private String fdNumber;
|
||||
/**
|
||||
* 登记日期(毫秒时间戳)
|
||||
*/
|
||||
private Long fdRegisterDate;
|
||||
/**
|
||||
* 票据日期(毫秒时间戳,金蝶开票完成后回填)
|
||||
*/
|
||||
private Long fdBillDate;
|
||||
/**
|
||||
* 合同
|
||||
*/
|
||||
private HcsRefVO fdContract;
|
||||
/**
|
||||
* 合同编号
|
||||
*/
|
||||
private String fdContractNumber;
|
||||
/**
|
||||
* 发票类型
|
||||
*/
|
||||
private String fdInvoiceType;
|
||||
/**
|
||||
* 结算币种(币种ID)
|
||||
*/
|
||||
private String fdCurrencyId;
|
||||
/**
|
||||
* 开票方
|
||||
*/
|
||||
private String fdInvoiceParty;
|
||||
/**
|
||||
* 款项性质(prepayment/progress/acceptance/retention)
|
||||
*/
|
||||
private String fdFundNature;
|
||||
/**
|
||||
* 开票金额(含税)
|
||||
*/
|
||||
private BigDecimal fdInvoiceAmount;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String fdRemark;
|
||||
/**
|
||||
* 状态:0=已开票,1=已作废
|
||||
*/
|
||||
private Integer fdStatus;
|
||||
/**
|
||||
* 数据来源(如 erp)
|
||||
*/
|
||||
private String fdDataSource;
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private HcsRefVO fdCreator;
|
||||
/**
|
||||
* 创建人部门
|
||||
*/
|
||||
private HcsRefVO fdCreatorDept;
|
||||
/**
|
||||
* 创建时间(毫秒时间戳)
|
||||
*/
|
||||
private Long fdCreateTime;
|
||||
/**
|
||||
* 第三方创建人编码
|
||||
*/
|
||||
private String fdThirdCreatorCode;
|
||||
/**
|
||||
* 第三方创建部门编码
|
||||
*/
|
||||
private String fdThirdCreatorDeptCode;
|
||||
/**
|
||||
* 发起主体(合同关联的发起主体名称)
|
||||
*/
|
||||
private String fdInitiator;
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)特批结果回传报文
|
||||
* <p>
|
||||
* 合规系统审批完成后经 iPaaS 网关推送 TMS:合同走 /hcs/contractLegal/result,
|
||||
* 付款走 /hcs/paymentLegal/result,通过 status=APPROVED、驳回 status=REJECTED,报文两接口共用。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsLegalResultDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 来源系统标识(约定 WLTMS,配置 thirdParty.hcs.src-system)
|
||||
*/
|
||||
private String sourceSystem;
|
||||
/**
|
||||
* 定位单据ID(付款特批=TMS 付款申请ID;合同特批=合规合同ID,与 id 同值,按 blade_contract_manage.hcs_fd_id 匹配)
|
||||
*/
|
||||
private String requestId;
|
||||
/**
|
||||
* 合规侧单据ID(合同特批回传携带,与 requestId 同值=合规合同ID;付款特批回传不传,忽略)
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 审批结果:APPROVED 通过 / REJECTED 驳回
|
||||
*/
|
||||
private String status;
|
||||
/**
|
||||
* 结果说明(驳回原因等,非必传)
|
||||
*/
|
||||
private String reason;
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)附件挂载容器(C106 特批 / C108 履约记录 / C109 收票单的 mechanisms 节点)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsMechanismsDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 附件列表(fdId 来自 C103-2 附件上传接口)
|
||||
*/
|
||||
private List<HcsAttachmentRefDTO> attachment;
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)通用分页查询入参(§1.5 分页与查询约定,各查询接口通用)
|
||||
* <p>
|
||||
* 注意:C102-3 项目立项查询的 columns/sorts 用下划线字段名(fd_id/fd_num/fd_pro_name/fd_create_time),
|
||||
* 其余查询接口出入参均为驼峰;出参 content 均为驼峰字段。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsPageQueryDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 查询返回的字段列表(视接口必填)
|
||||
*/
|
||||
private List<String> columns;
|
||||
/**
|
||||
* 查询条件,空对象表示无条件;支持操作符,如 {"fdName": {"$contains": "xx"}}(包含匹配)
|
||||
*/
|
||||
private Map<String, Object> conditions;
|
||||
/**
|
||||
* 分页偏移量(必填,从 0 开始)
|
||||
*/
|
||||
private Integer offset;
|
||||
/**
|
||||
* 每页条数(必填)
|
||||
*/
|
||||
private Integer pageSize;
|
||||
/**
|
||||
* 排序条件,如 {"fdCreateTime": "desc"}
|
||||
*/
|
||||
private Map<String, Object> sorts;
|
||||
/**
|
||||
* 当前操作人(C102-3 项目立项查询需携带,其余查询接口按需)
|
||||
*/
|
||||
private HcsUserRefVO curUser;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)分页查询返回结果(位于统一出参 data 内)
|
||||
*
|
||||
* @param <T> 数据行类型
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsPageVO<T> implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
private List<T> content;
|
||||
/**
|
||||
* 分页偏移量(从 0 开始)
|
||||
*/
|
||||
private Integer offset;
|
||||
/**
|
||||
* 每页条数
|
||||
*/
|
||||
private Integer pageSize;
|
||||
/**
|
||||
* 总条数
|
||||
*/
|
||||
private Integer totalSize;
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C108-1/C108-2 履约记录 formData 节点
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsPerfRecordFormDataDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 履约记录ID(C108-2 更新必填,取 C108-1 新建返回的 data.fdId)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 履约记录业务字段
|
||||
*/
|
||||
private HcsPerfRecordPropsDTO dynamicProps;
|
||||
/**
|
||||
* 单据主题
|
||||
*/
|
||||
private String fdDocSubject;
|
||||
/**
|
||||
* 单据状态(示例 10)
|
||||
*/
|
||||
private String fdDocStatus;
|
||||
/**
|
||||
* 表单模板ID(环境值,§9-U9)
|
||||
*/
|
||||
private String fdXformId;
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C108-1/C108-2 履约记录 dynamicProps 业务字段(字段名按接口文档下划线原样传值)
|
||||
* <p>
|
||||
* 注意:fd_paln_type 为接口文档原文拼写(plan 的笔误,必须沿用原拼写,否则字段无法识别);
|
||||
* 金额字段为字符串传值。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsPerfRecordPropsDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 申请金额(字符串传值)
|
||||
*/
|
||||
@JsonProperty("fd_apply_amount")
|
||||
private String fdApplyAmount;
|
||||
/**
|
||||
* 实际金额(字符串传值,实际付款推进时更新)
|
||||
*/
|
||||
@JsonProperty("fd_fact_amount")
|
||||
private String fdFactAmount;
|
||||
/**
|
||||
* 申请编号
|
||||
*/
|
||||
@JsonProperty("fd_apply_no")
|
||||
private String fdApplyNo;
|
||||
/**
|
||||
* 计划日期(毫秒时间戳)
|
||||
*/
|
||||
@JsonProperty("fd_plan_date")
|
||||
private Long fdPlanDate;
|
||||
/**
|
||||
* 计划类型(原文拼写 fd_paln_type,paymentType)
|
||||
*/
|
||||
@JsonProperty("fd_paln_type")
|
||||
private String fdPalnType;
|
||||
/**
|
||||
* 性质(款项性质,取关联履约规则行/结算性质)
|
||||
*/
|
||||
@JsonProperty("fd_property")
|
||||
private String fdProperty;
|
||||
/**
|
||||
* 版本ID
|
||||
*/
|
||||
@JsonProperty("fd_version")
|
||||
private String fdVersion;
|
||||
/**
|
||||
* 表单模板ID(环境值 fd_xform_id,§9-U9)
|
||||
*/
|
||||
@JsonProperty("fd_xform_id")
|
||||
private String fdXformId;
|
||||
/**
|
||||
* 来源履约计划项ID(关联履约阶段 hcs_stage_fd_id,无关联则不传)
|
||||
*/
|
||||
@JsonProperty("fd_id")
|
||||
private String fdId;
|
||||
/**
|
||||
* 履约计划id(关联履约阶段 hcs_stage_fd_id,接口必填,HCS 只认自己的阶段ID)
|
||||
*/
|
||||
@JsonProperty("fd_plan_id")
|
||||
private String fdPlanId;
|
||||
/**
|
||||
* 金蝶单据号(溯源用)
|
||||
*/
|
||||
@JsonProperty("fd_jd_doc_no")
|
||||
private String fdJdDocNo;
|
||||
/**
|
||||
* 合同ID(HCS 合同ID)
|
||||
*/
|
||||
@JsonProperty("fd_contract_id")
|
||||
private String fdContractId;
|
||||
/**
|
||||
* 创建时间(毫秒时间戳,v4 新增)
|
||||
*/
|
||||
@JsonProperty("fd_create_time")
|
||||
private Long fdCreateTime;
|
||||
/**
|
||||
* 业务ID(方便溯源用,v4 新增,TMS 传付款申请单ID)
|
||||
*/
|
||||
@JsonProperty("fd_business_id")
|
||||
private String fdBusinessId;
|
||||
/**
|
||||
* 附件不可下载
|
||||
*/
|
||||
@JsonProperty("fd_att_no_download")
|
||||
private Boolean fdAttNoDownload;
|
||||
/**
|
||||
* 草稿不可编辑打印
|
||||
*/
|
||||
@JsonProperty("fd_draft_no_edit_prints")
|
||||
private Boolean fdDraftNoEditPrints;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C108-1 履约记录新建入参(C108-2 更新入参结构相同,复用本 DTO)
|
||||
* <p>
|
||||
* 更新时 formData.fdId 必填(新增返回的ID,持久化于 hcs_perf_fd_id),另可传 dynamicProps.fd_contract_id 指定合同;
|
||||
* 更新响应无 data,以 success 判断结果。附件经 mechanisms.attachment[] 绑定,fdEntityKey=fd_plan_att。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsPerfRecordSaveDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 当前操作人 {fdLoginName}(必填)
|
||||
*/
|
||||
private HcsUserRefVO curUser;
|
||||
/**
|
||||
* 履约记录表单数据
|
||||
*/
|
||||
private HcsPerfRecordFormDataDTO formData;
|
||||
/**
|
||||
* 附件挂载
|
||||
*/
|
||||
private HcsMechanismsDTO mechanisms;
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)C102-1 项目立项新建入参
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsProjectAddDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 流程操作(operationType=drafter_submit、templateCode 环境值)
|
||||
*/
|
||||
private HcsBpmOperationDTO bpmOperation;
|
||||
/**
|
||||
* 是否校验权限(接口调用传 false)
|
||||
*/
|
||||
private Boolean checkAuth;
|
||||
/**
|
||||
* 当前操作人 {fdLoginName}
|
||||
*/
|
||||
private HcsUserRefVO curUser;
|
||||
/**
|
||||
* 项目表单数据
|
||||
*/
|
||||
private HcsProjectFormDataDTO formData;
|
||||
/**
|
||||
* 保存选项(saveDraft=false 直接提交)
|
||||
*/
|
||||
private HcsSaveOptionDTO saveOption;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.hcs.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 合同合规系统(HCS)项目立项 formData 节点(C102-1 新建 / C102-2 更新共用;更新时 fdId 必填)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HcsProjectFormDataDTO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 项目立项单ID(C102-1 新建返回,C102-2 更新必填)
|
||||
*/
|
||||
private String fdId;
|
||||
/**
|
||||
* 项目业务字段
|
||||
*/
|
||||
private HcsProjectPropsDTO dynamicProps;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user