> getInvoiceItems() {
+ return R.fail("获取数据失败");
+ }
+
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceApplicationSaveRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceApplicationSaveRequest.java
index 207879ca..bd0130f7 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceApplicationSaveRequest.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceApplicationSaveRequest.java
@@ -71,6 +71,7 @@ public class InvoiceApplicationSaveRequest implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private String goodsCategory;
private String goodsName;
+ private String taxClassificationCode;
private String unit;
private BigDecimal quantity;
private BigDecimal unitPriceNoTax;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java
index 5f73b6c9..f7b4c29b 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/InvoiceReceiptSaveRequest.java
@@ -42,7 +42,6 @@ import java.util.List;
public class InvoiceReceiptSaveRequest implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private Long id;
- private Long kingdeeInvoicePoolId;
private String invoiceNo;
private LocalDate invoiceDate;
private String invoiceType;
@@ -54,8 +53,6 @@ public class InvoiceReceiptSaveRequest implements Serializable {
private String bankName;
private String bankAccount;
private String issuingBank;
- private String kingdeeBillNo;
- private String kingdeeStatus;
private String phone;
private String customerEmails;
private String departmentEmails;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/KingdeeInvoicePoolQueryDTO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/KingdeeInvoicePoolQueryDTO.java
new file mode 100644
index 00000000..4e726481
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/KingdeeInvoicePoolQueryDTO.java
@@ -0,0 +1,116 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to the License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.springblade.transport.pojo.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 金蝶票据池镜像统一查询条件(收票登记选票与正式结算单认领共用,查本地镜像)
+ *
+ * @author Chill
+ */
+@Data
+@Schema(description = "金蝶票据池镜像统一查询条件")
+public class KingdeeInvoicePoolQueryDTO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 当前页
+ */
+ @Schema(description = "当前页")
+ private Long current = 1L;
+
+ /**
+ * 每页条数
+ */
+ @Schema(description = "每页条数")
+ private Long size = 10L;
+
+ /**
+ * 发票号码,模糊
+ */
+ @Schema(description = "发票号码(模糊)")
+ private String invoiceNo;
+
+ /**
+ * 开票日期起 yyyy-MM-dd
+ */
+ @Schema(description = "开票日期起 yyyy-MM-dd")
+ private String invoiceDateStart;
+
+ /**
+ * 开票日期止 yyyy-MM-dd
+ */
+ @Schema(description = "开票日期止 yyyy-MM-dd")
+ private String invoiceDateEnd;
+
+ /**
+ * 开票单位(销方名称),模糊
+ */
+ @Schema(description = "开票单位(销方名称,模糊)")
+ private String salerName;
+
+ /**
+ * 受票单位(购方名称),模糊
+ */
+ @Schema(description = "受票单位(购方名称,模糊)")
+ private String buyerName;
+
+ /**
+ * 发票类型编码(票易通字典,如 26/27,镜像按 invoice_type_code 精确匹配)
+ */
+ @Schema(description = "发票类型编码(票易通字典,如 26/27)")
+ private String invoiceType;
+
+ /**
+ * 销方税号,精确
+ */
+ @Schema(description = "销方税号")
+ private String salerTaxNo;
+
+ /**
+ * 购方税号,精确
+ */
+ @Schema(description = "购方税号")
+ private String buyerTaxNo;
+
+ /**
+ * 认领过滤:true 仅已认领,false 仅未认领,null 不过滤(已认领=存在非作废正式结算单发票行)
+ */
+ @Schema(description = "认领过滤:true已认领/false未认领/null不限")
+ private Boolean claimed;
+
+ /**
+ * 登记过滤:true 仅已登记,false 仅未登记,null 不过滤(已登记=存在非作废收票登记)
+ */
+ @Schema(description = "登记过滤:true已登记/false未登记/null不限")
+ private Boolean registered;
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ReceiptClaimPushResult.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ReceiptClaimPushResult.java
new file mode 100644
index 00000000..dd27a8d9
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ReceiptClaimPushResult.java
@@ -0,0 +1,70 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for their own commercial purposes.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.pojo.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 收款认领推送金蝶结果(A301-3)
+ *
+ * 推送失败不抛异常跨事务边界(保证失败状态落库不被回滚),以本结果对象向调用方报告成败与原因。
+ *
+ * @author Chill
+ */
+@Data
+@Schema(description = "收款认领推送金蝶结果")
+public class ReceiptClaimPushResult implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "是否推送成功")
+ private boolean success;
+
+ @Schema(description = "金蝶收款单号(成功时返回,可能为空:金蝶响应未返回单号)")
+ private String kingdeeBillNo;
+
+ @Schema(description = "失败原因(成功时为空)")
+ private String message;
+
+ public static ReceiptClaimPushResult success(String kingdeeBillNo) {
+ ReceiptClaimPushResult result = new ReceiptClaimPushResult();
+ result.setSuccess(true);
+ result.setKingdeeBillNo(kingdeeBillNo);
+ return result;
+ }
+
+ public static ReceiptClaimPushResult fail(String message) {
+ ReceiptClaimPushResult result = new ReceiptClaimPushResult();
+ result.setSuccess(false);
+ result.setMessage(message);
+ return result;
+ }
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java
index fa780bbf..5ea8bec1 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java
@@ -161,4 +161,13 @@ public class CustomerArchive extends TenantEntity {
@Schema(description = "审核通过时间")
private LocalDateTime approvedTime;
+ @Schema(description = "金蝶协同状态:none未推送/exist金蝶已有/synced已推送/failed推送失败")
+ private String kingdeeStatus;
+
+ @Schema(description = "金蝶推送失败原因")
+ private String kingdeeFailReason;
+
+ @Schema(description = "金蝶推送时间")
+ private LocalDateTime kingdeeSyncTime;
+
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java
index 107b7a82..0a03304b 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlement.java
@@ -21,6 +21,8 @@ import java.time.LocalDateTime;
/**
* 正式结算单实体类
+ *
+ * 结算单主表,明细快照见 {@link FormalSettlementDetail},合计费用见 {@link FormalSettlementSummaryFee}
*
* @author Chill
*/
@@ -30,38 +32,144 @@ import java.time.LocalDateTime;
@Schema(description = "正式结算单")
public class FormalSettlement extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 正式结算单号(同租户唯一)
+ */
private String formalSettlementNo;
+ /**
+ * 来源类型:预结算合并/应收应付/混合来源
+ */
private String sourceType;
+ /**
+ * 结算类型:receivable-应收 payable-应付
+ */
private String settlementType;
+ /**
+ * 所属项目ID
+ */
private Long projectId;
+ /**
+ * 所属项目名称
+ */
private String projectName;
+ /**
+ * 所属组织ID
+ */
private Long deptId;
+ /**
+ * 所属组织名称
+ */
private String deptName;
+ /**
+ * 结算合同ID
+ */
private Long contractId;
+ /**
+ * 结算合同编号
+ */
private String contractNo;
+ /**
+ * 结算合同名称
+ */
private String contractName;
+ /**
+ * 付款方名称
+ */
private String payerName;
+ /**
+ * 收款方名称
+ */
private String payeeName;
+ /**
+ * 结算币种,默认 RMB
+ */
private String currency;
+ /**
+ * 结算金额(结算币种,含全部合计费用)
+ */
private BigDecimal settlementAmount;
+ /**
+ * 本位币,默认 RMB
+ */
private String localCurrency;
+ /**
+ * 本位币结算金额 = 结算金额 × 结算汇率
+ */
private BigDecimal localSettlementAmount;
+ /**
+ * 已申请付款金额,含来源预结算的预付
+ */
private BigDecimal appliedPaymentAmount;
+ /**
+ * 已收/已付金额合计,含来源预结算的预付
+ */
private BigDecimal paidAmount;
+ /**
+ * 剩余可付金额 = 结算金额 - 已收已付合计,最小为 0
+ */
private BigDecimal remainingPayableAmount;
+ /**
+ * 已开票/已收票金额,取开票申请与收票登记的分摊合计
+ */
private BigDecimal invoiceAmount;
+ /**
+ * 汇率日期
+ */
private LocalDate exchangeRateDate;
+ /**
+ * 结算汇率
+ */
private BigDecimal exchangeRate;
+ /**
+ * 开票状态(应收为开票、应付为收票):unreceived-未开票 partial-部分开票 completed-已开票
+ */
private String invoiceStatus;
+ /**
+ * 收付款状态:unpaid-未收付款 partial-部分收付款 paid-已收付款
+ */
private String paymentStatus;
+ /**
+ * 审核状态:draft-草稿 reviewing-审批中 approved-审批通过 returned-已驳回 voided-已作废
+ */
private String approvalStatus;
+ /**
+ * 当前审批节点,如「草稿」「财务审核」「审批通过」
+ */
private String currentNode;
+ /**
+ * 当前处理人姓名
+ */
private String currentProcessor;
+ /**
+ * 金蝶单据编号
+ */
private String kingdeeBillNo;
+ /**
+ * 金蝶单据id(星瀚内部主键,A902 附件上传定位用)
+ */
+ private String kingdeeBillId;
+ /**
+ * 金蝶同步状态:unsynced-未同步 synced-已同步 failed-同步失败
+ */
private String kingdeeSyncStatus;
+ /**
+ * 附件JSON
+ */
private String attachmentsJson;
+ /**
+ * 备注
+ */
private String remark;
+ /**
+ * 审核通过时间
+ */
private LocalDateTime approvedTime;
+ /**
+ * 同步金蝶时间
+ */
private LocalDateTime syncedTime;
+ /**
+ * 作废原因,驳回时记录驳回原因
+ */
private String voidReason;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetail.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetail.java
index bf3cda88..899dffe1 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetail.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetail.java
@@ -27,37 +27,136 @@ import java.time.LocalDateTime;
@TableName("blade_formal_settlement_detail")
public class FormalSettlementDetail extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 正式结算单ID
+ */
private Long formalSettlementId;
+ /**
+ * 来源预结算单ID
+ */
private Long sourcePreSettlementId;
+ /**
+ * 来源预结算明细ID
+ */
private Long sourcePreSettlementDetailId;
+ /**
+ * 来源应收应付明细ID
+ */
private Long sourceDetailId;
+ /**
+ * 行号
+ */
private Integer lineNo;
+ /**
+ * 单据编号
+ */
private String documentNo;
+ /**
+ * 运单ID
+ */
private Long waybillId;
+ /**
+ * 运单号
+ */
private String waybillNo;
+ /**
+ * 车牌号
+ */
private String vehicleNo;
+ /**
+ * 发货地址
+ */
private String departureAddress;
+ /**
+ * 收货地址
+ */
private String arrivalAddress;
+ /**
+ * 发货联系人
+ */
private String departureContact;
+ /**
+ * 发货联系方式
+ */
private String departurePhone;
+ /**
+ * 收货联系人
+ */
private String arrivalContact;
+ /**
+ * 收货联系方式
+ */
private String arrivalPhone;
+ /**
+ * 实际发货时间
+ */
private LocalDateTime actualDepartureTime;
+ /**
+ * 实际完成时间
+ */
private LocalDateTime actualCompletionTime;
+ /**
+ * 运输类型
+ */
private String transportType;
+ /**
+ * 货物名称
+ */
private String cargoName;
+ /**
+ * 货物类型
+ */
private String cargoType;
+ /**
+ * 运输总量
+ */
private BigDecimal transportQuantity;
+ /**
+ * 数量单位
+ */
private String quantityUnit;
+ /**
+ * 里程
+ */
private BigDecimal mileage;
+ /**
+ * 批次号
+ */
private String batchNo;
+ /**
+ * 运输单价
+ */
private BigDecimal unitPrice;
+ /**
+ * 运费
+ */
private BigDecimal freightAmount;
+ /**
+ * 费用项JSON
+ */
private String feeItemsJson;
+ /**
+ * 原金额
+ */
private BigDecimal originalAmount;
+ /**
+ * 调整金额
+ */
private BigDecimal adjustAmount;
+ /**
+ * 结算金额(含税)
+ */
private BigDecimal settlementAmountTax;
+ /**
+ * 结算金额(不含税)
+ */
private BigDecimal settlementAmountNoTax;
+ /**
+ * 币种
+ */
private String currency;
+ /**
+ * 备注
+ */
private String remark;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetailFee.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetailFee.java
index b7e5892a..c4e62e26 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetailFee.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementDetailFee.java
@@ -26,20 +26,68 @@ import java.math.BigDecimal;
@TableName("blade_formal_settlement_detail_fee")
public class FormalSettlementDetailFee extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 正式结算明细ID
+ */
private Long formalSettlementDetailId;
+ /**
+ * 源费用行ID
+ */
private Long sourceFeeId;
+ /**
+ * 行号
+ */
private String lineNo;
+ /**
+ * 货物名称
+ */
private String cargoName;
+ /**
+ * 货物类型
+ */
private String cargoType;
+ /**
+ * 运输量
+ */
private BigDecimal transportQuantity;
+ /**
+ * 数量单位
+ */
private String quantityUnit;
+ /**
+ * 里程
+ */
private BigDecimal mileage;
+ /**
+ * 运输单价
+ */
private BigDecimal unitPrice;
+ /**
+ * 运费
+ */
private BigDecimal freightAmount;
+ /**
+ * 费用项JSON
+ */
private String feeItemsJson;
+ /**
+ * 原金额
+ */
private BigDecimal originalAmount;
+ /**
+ * 调整金额
+ */
private BigDecimal adjustAmount;
+ /**
+ * 结算金额(含税)
+ */
private BigDecimal settlementAmountTax;
+ /**
+ * 结算金额(不含税)
+ */
private BigDecimal settlementAmountNoTax;
+ /**
+ * 备注
+ */
private String remark;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementPayment.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementPayment.java
index 962f7ad4..fdfb94eb 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementPayment.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementPayment.java
@@ -26,12 +26,20 @@ import java.math.BigDecimal;
@TableName("blade_formal_settlement_payment")
public class FormalSettlementPayment extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /** 正式结算单id */
private Long formalSettlementId;
+ /** 付款单号(租户内唯一) */
private String paymentNo;
+ /** 付款类型(final:结算尾款) */
private String paymentType;
+ /** 申请付款金额 */
private BigDecimal appliedAmount;
+ /** 已付金额 */
private BigDecimal paidAmount;
+ /** 单据状态(reviewing:审核中,approved:审核通过,returned:已退回,voided:已作废,paid:已付款) */
private String billStatus;
+ /** 金蝶(星瀚)单据编号 */
private String kingdeeBillNo;
+ /** 备注 */
private String remark;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementSummaryFee.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementSummaryFee.java
index 9292f68e..acfd797f 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementSummaryFee.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/FormalSettlementSummaryFee.java
@@ -28,13 +28,40 @@ import java.math.BigDecimal;
@Schema(description = "正式结算合计费用")
public class FormalSettlementSummaryFee extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 正式结算单ID
+ */
private Long formalSettlementId;
+ /**
+ * 行号
+ */
private Integer lineNo;
+ /**
+ * 费用类型
+ */
private String feeType;
+ /**
+ * 费用项
+ */
private String feeItem;
+ /**
+ * 原金额
+ */
private BigDecimal originalAmount;
+ /**
+ * 调整金额
+ */
private BigDecimal adjustAmount;
+ /**
+ * 结算金额
+ */
private BigDecimal settlementAmount;
+ /**
+ * 备注
+ */
private String remark;
+ /**
+ * 是否手工添加
+ */
private Integer manualFlag;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplication.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplication.java
index bf03a3ce..2b89b282 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplication.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplication.java
@@ -47,37 +47,140 @@ import java.time.LocalDateTime;
@Schema(description = "开票申请")
public class InvoiceApplication extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 开票申请单号(同租户唯一,A303-1 billno 幂等候选键)
+ */
private String applicationNo;
+ /**
+ * 所属项目ID(结算单快照带出)
+ */
private Long projectId;
+ /**
+ * 所属项目名称(结算单快照带出)
+ */
private String projectName;
+ /**
+ * 申请部门(所属组织)ID
+ */
private Long deptId;
+ /**
+ * 申请部门(所属组织)名称
+ */
private String deptName;
+ /**
+ * 开票单位名称(销方=结算单收款方快照,金蝶 salername 取数来源)
+ */
private String issuerName;
+ /**
+ * 受票方客商档案ID
+ */
private Long receiverCustomerId;
+ /**
+ * 受票方单位名称(客商开票信息企业全称快照,金蝶 buyername)
+ */
private String receiverName;
+ /**
+ * 发票类型:electronic_normal-数电普票 / electronic_special-数电专票
+ */
private String invoiceType;
+ /**
+ * 可开票金额(关联结算单剩余可开票额度合计)
+ */
private BigDecimal availableInvoiceAmount;
+ /**
+ * 本次开票金额(商品行价税合计,金蝶 totalamount)
+ */
private BigDecimal invoiceAmount;
+ /**
+ * 申请日期(提交审批时置当日)
+ */
private LocalDate applicationDate;
+ /**
+ * 申请人姓名
+ */
private String applicantName;
+ /**
+ * 承办部门ID(结算单所属组织快照)
+ */
private Long undertakingDeptId;
+ /**
+ * 承办部门名称(结算单所属组织快照)
+ */
private String undertakingDeptName;
+ /**
+ * 部门邮箱(发票推送邮箱,多个以分号分隔,最多3个,金蝶 buyeremail)
+ */
private String departmentEmails;
+ /**
+ * 受票方客商开票信息ID(blade_customer_invoice_info)
+ */
private Long receiverInvoiceInfoId;
+ /**
+ * 受票方纳税人识别号(开票信息快照,金蝶 buyertaxno)
+ */
private String taxpayerNo;
+ /**
+ * 受票方开户行(开票信息快照)
+ */
private String bankName;
+ /**
+ * 受票方开户账号(开票信息快照)
+ */
private String bankAccount;
+ /**
+ * 受票方注册地址(开票信息快照)
+ */
private String registeredAddress;
+ /**
+ * 受票方联系人
+ */
private String contactName;
+ /**
+ * 受票方联系电话
+ */
private String contactPhone;
+ /**
+ * 受票方邮箱
+ */
private String email;
+ /**
+ * 审批状态:draft-草稿 reviewing-审批中 approved-审批通过 returned-已驳回 voided-已作废
+ */
private String approvalStatus;
+ /**
+ * 当前审批节点,如「草稿」「开票审核」「审批通过」
+ */
private String currentNode;
+ /**
+ * 当前处理人姓名
+ */
private String currentProcessor;
+ /**
+ * 金蝶开票申请单编号(A303-1 返回,A303-5 查询 serialNo)
+ */
private String kingdeeBillNo;
+ /**
+ * 金蝶开票申请单id(星瀚内部主键,A902 附件上传定位用)
+ */
+ private String kingdeeBillId;
+ /**
+ * 金蝶同步状态:unsynced-未同步 synced-已同步 failed-同步失败 invoiced-开票完成(终态)
+ */
private String kingdeeStatus;
+ /**
+ * 同步金蝶时间
+ */
private LocalDateTime syncedTime;
+ /**
+ * 附件JSON
+ */
private String attachmentsJson;
+ /**
+ * 备注
+ */
private String remark;
+ /**
+ * 作废原因,驳回时记录驳回原因
+ */
private String voidReason;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationDetail.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationDetail.java
index 44104935..2d43fc59 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationDetail.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationDetail.java
@@ -44,25 +44,88 @@ import java.time.LocalDateTime;
@TableName("blade_invoice_application_detail")
public class InvoiceApplicationDetail extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 开票申请ID
+ */
private Long invoiceApplicationId;
+ /**
+ * 正式结算单ID
+ */
private Long formalSettlementId;
+ /**
+ * 来源正式结算明细ID
+ */
private Long formalSettlementDetailId;
+ /**
+ * 行号
+ */
private Integer lineNo;
+ /**
+ * 结算明细单据号(快照)
+ */
private String documentNo;
+ /**
+ * 运单号(快照)
+ */
private String waybillNo;
+ /**
+ * 车牌号(快照)
+ */
private String vehicleNo;
+ /**
+ * 起运地(快照)
+ */
private String departureAddress;
+ /**
+ * 到达地(快照)
+ */
private String arrivalAddress;
+ /**
+ * 实际发运时间(快照)
+ */
private LocalDateTime actualDepartureTime;
+ /**
+ * 实际完成时间(快照)
+ */
private LocalDateTime actualCompletionTime;
+ /**
+ * 运输方式(快照,字典 transport_type)
+ */
private String transportType;
+ /**
+ * 货物名称(快照)
+ */
private String cargoName;
+ /**
+ * 货物类型(快照)
+ */
private String cargoType;
+ /**
+ * 运输数量(快照)
+ */
private BigDecimal transportQuantity;
+ /**
+ * 数量单位(快照)
+ */
private String quantityUnit;
+ /**
+ * 里程(快照)
+ */
private BigDecimal mileage;
+ /**
+ * 批次号(快照)
+ */
private String batchNo;
+ /**
+ * 运费金额(快照)
+ */
private BigDecimal freightAmount;
+ /**
+ * 费用项明细JSON(快照)
+ */
private String feeItemsJson;
+ /**
+ * 结算金额(含税,快照)
+ */
private BigDecimal settlementAmountTax;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationInvoice.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationInvoice.java
new file mode 100644
index 00000000..8e145070
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationInvoice.java
@@ -0,0 +1,80 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
+ * Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.tenant.mp.TenantEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 开票申请金蝶发票结果实体类
+ *
+ * 定时任务经 A303-5 开票申请发票查询回写的发票明细(发票号唯一键幂等落库),
+ * 发票文件地址(板式文件/数电PDF/XML/预览图)独立落库供详情页查看下载,
+ * 原文 JSON 整体留档便于联调与追溯
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("blade_invoice_application_invoice")
+@Schema(description = "开票申请金蝶发票结果")
+public class InvoiceApplicationInvoice extends TenantEntity {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "开票申请ID")
+ private Long invoiceApplicationId;
+
+ @Schema(description = "金蝶开票申请单编号(A303-5 serialNo)")
+ private String serialNo;
+
+ @Schema(description = "发票号码")
+ private String invoiceNo;
+
+ @Schema(description = "开票日期")
+ private LocalDateTime invoiceDate;
+
+ @Schema(description = "价税合计")
+ private BigDecimal invoiceAmount;
+
+ @Schema(description = "板式文件下载地址(数电票为OFD,航信百望税盘普通电票为PDF,电子专票/UKey电票为OFD)")
+ private String invoiceFileUrl;
+
+ @Schema(description = "PDF图片预览地址")
+ private String invoiceImageUrl;
+
+ @Schema(description = "数电PDF下载地址(发票云5.0.025版本返回)")
+ private String invoicePdfFileUrl;
+
+ @Schema(description = "数电XML下载地址(发票云5.0.025版本返回)")
+ private String invoiceXmlFileUrl;
+
+ @Schema(description = "A303-5 返回原文JSON留档")
+ private String rawJson;
+
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationLine.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationLine.java
index 30ded9f6..60f04c39 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationLine.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationLine.java
@@ -43,18 +43,64 @@ import java.math.BigDecimal;
@TableName("blade_invoice_application_line")
public class InvoiceApplicationLine extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 开票申请ID
+ */
private Long invoiceApplicationId;
+ /**
+ * 所属发票张次ID
+ */
private Long invoiceSheetId;
+ /**
+ * 行号(张内序号)
+ */
private Integer lineNo;
+ /**
+ * 商品和服务分类名称(开票项目主数据选择,金蝶 goodssimplename)
+ */
private String goodsCategory;
+ /**
+ * 货物或服务简称(开票项目主数据选择,金蝶 goodsname)
+ */
private String goodsName;
+ /**
+ * 税收分类编码(选择开票内容时快照,推送金蝶 A303-1 分录直接取该值)
+ */
+ private String taxClassificationCode;
+ /**
+ * 单位
+ */
private String unit;
+ /**
+ * 数量
+ */
private BigDecimal quantity;
+ /**
+ * 不含税单价
+ */
private BigDecimal unitPriceNoTax;
+ /**
+ * 不含税金额
+ */
private BigDecimal amountNoTax;
+ /**
+ * 含税金额(历史兼容字段,与含税合计同值)
+ */
private BigDecimal amountWithTax;
+ /**
+ * 税率(0-100 百分比值,推送金蝶时转小数文本)
+ */
private BigDecimal taxRate;
+ /**
+ * 税额
+ */
private BigDecimal taxAmount;
+ /**
+ * 价税合计(含税合计,金蝶 taxamount)
+ */
private BigDecimal totalAmount;
+ /**
+ * 行备注
+ */
private String remark;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationRecord.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationRecord.java
index 8a411bd8..01c9e9b0 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationRecord.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationRecord.java
@@ -42,12 +42,36 @@ import java.io.Serial;
@TableName("blade_invoice_application_record")
public class InvoiceApplicationRecord extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 开票申请ID
+ */
private Long invoiceApplicationId;
+ /**
+ * 操作类型:create/save/submit/approve/return/void/sync 等
+ */
private String actionType;
+ /**
+ * 操作名称(展示用,如「提交审批」「同步金蝶」)
+ */
private String actionName;
+ /**
+ * 操作前审批状态
+ */
private String fromStatus;
+ /**
+ * 操作后审批状态
+ */
private String toStatus;
+ /**
+ * 操作人姓名
+ */
private String operatorName;
+ /**
+ * 原因说明(驳回/作废原因等)
+ */
private String reason;
+ /**
+ * 金蝶单据编号(同步类操作记录)
+ */
private String kingdeeBillNo;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSettlement.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSettlement.java
index 57a03396..ac6ba4ca 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSettlement.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSettlement.java
@@ -43,10 +43,28 @@ import java.math.BigDecimal;
@TableName("blade_invoice_application_settlement")
public class InvoiceApplicationSettlement extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 开票申请ID
+ */
private Long invoiceApplicationId;
+ /**
+ * 正式结算单ID
+ */
private Long formalSettlementId;
+ /**
+ * 正式结算单号(关联时快照)
+ */
private String formalSettlementNo;
+ /**
+ * 结算金额(关联时快照)
+ */
private BigDecimal settlementAmount;
+ /**
+ * 可开票金额(关联时快照)
+ */
private BigDecimal availableInvoiceAmount;
+ /**
+ * 本申请分摊开票金额(结算单开票状态按此汇总)
+ */
private BigDecimal allocatedInvoiceAmount;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSheet.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSheet.java
index 8550946d..bee703e4 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSheet.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceApplicationSheet.java
@@ -43,7 +43,20 @@ import java.math.BigDecimal;
@TableName("blade_invoice_application_sheet")
public class InvoiceApplicationSheet extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 开票申请ID
+ */
private Long invoiceApplicationId;
+ /**
+ * 发票张序号(从 1 开始)
+ */
private Integer sheetNo;
+ /**
+ * 本张开票金额(张内商品行价税合计)
+ */
private BigDecimal invoiceAmount;
+ /**
+ * 金蝶开票申请单编号(本张发票对应,A303-1 返回,A303-5 查询 serialNo)
+ */
+ private String kingdeeBillNo;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceReceipt.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceReceipt.java
index 67859e1c..9023e07e 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceReceipt.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/InvoiceReceipt.java
@@ -36,6 +36,7 @@ import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
import java.math.BigDecimal;
import java.time.LocalDate;
+import java.time.LocalDateTime;
/**
* 收票登记实体类
@@ -48,36 +49,135 @@ import java.time.LocalDate;
@Schema(description = "收票登记")
public class InvoiceReceipt extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 关联金蝶票据池镜像主键(blade_kingdee_invoice_pool.id)
+ */
@JsonSerialize(using = ToStringSerializer.class)
private Long kingdeeInvoicePoolId;
+ /**
+ * 发票号码(同租户唯一,一张发票仅可登记一次)
+ */
private String invoiceNo;
+ /**
+ * 开票日期
+ */
private LocalDate invoiceDate;
+ /**
+ * 发票类型(如「数电发票(增值税专用发票)」)
+ */
private String invoiceType;
+ /**
+ * 税率(百分数,13 表示 13%)
+ */
private BigDecimal taxRate;
+ /**
+ * 开票金额
+ */
private BigDecimal invoiceAmount;
+ /**
+ * 税额
+ */
private BigDecimal taxAmount;
+ /**
+ * 受票单位(购方)
+ */
private String receiverName;
+ /**
+ * 开票单位(销方)
+ */
private String issuerName;
+ /**
+ * 所属项目id
+ */
@JsonSerialize(using = ToStringSerializer.class)
private Long projectId;
+ /**
+ * 所属项目名称
+ */
private String projectName;
+ /**
+ * 所属组织id
+ */
@JsonSerialize(using = ToStringSerializer.class)
private Long deptId;
+ /**
+ * 所属组织名称
+ */
private String deptName;
+ /**
+ * 付款方
+ */
private String payerName;
+ /**
+ * 收款方
+ */
private String payeeName;
+ /**
+ * 开票单位开户银行
+ */
private String bankName;
+ /**
+ * 开票单位银行账号
+ */
private String bankAccount;
+ /**
+ * 开票单位开户网点
+ */
private String issuingBank;
+ /**
+ * 联系电话
+ */
private String phone;
+ /**
+ * 客户邮箱(分号分隔,最多 3 个)
+ */
private String customerEmails;
+ /**
+ * 部门邮箱(分号分隔,最多 3 个)
+ */
private String departmentEmails;
+ /**
+ * 审批状态(draft 草稿 / reviewing 审批中 / approved 审批通过 / returned 已驳回 / voided 已作废)
+ */
private String approvalStatus;
+ /**
+ * 当前审批节点
+ */
private String currentNode;
+ /**
+ * 当前处理人
+ */
private String currentProcessor;
- private String kingdeeBillNo;
- private String kingdeeStatus;
+ /**
+ * 金蝶收票单单据编号(A303-2 批量保存生成/幂等预检命中回填)
+ */
+ private String kingdeeReceivedInvoiceNo;
+ /**
+ * 金蝶收票单id(星瀚内部主键,A902 附件上传定位用)
+ */
+ private String kingdeeReceivedInvoiceId;
+ /**
+ * 收票单推送状态(unsynced 未推送 / pushed 已保存收票单 / assigned 已指定应付 / failed 推送失败)
+ */
+ private String kingdeePushStatus;
+ /**
+ * 收票单推送失败原因(金蝶侧删除/红冲/查验不通过等异常提示人工)
+ */
+ private String kingdeePushMsg;
+ /**
+ * 最近一次收票单推送/回写时间
+ */
+ private LocalDateTime kingdeePushTime;
+ /**
+ * 附件信息JSON
+ */
private String attachmentsJson;
+ /**
+ * 备注(最多 200 字)
+ */
private String remark;
+ /**
+ * 作废原因
+ */
private String voidReason;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeAttachmentSync.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeAttachmentSync.java
new file mode 100644
index 00000000..c8c0377d
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeAttachmentSync.java
@@ -0,0 +1,84 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * 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;
+
+/**
+ * 金蝶附件同步实体类(A902 附件上传到指定单据,单附件行级状态)
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("blade_kingdee_attachment_sync")
+@Schema(description = "金蝶附件同步")
+public class KingdeeAttachmentSync extends TenantEntity {
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * 业务单据类型(pay_application/formal_settlement_ap/formal_settlement_ar/invoice_receipt/invoice_application)
+ */
+ private String bizType;
+ /**
+ * 业务单据id
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long bizId;
+ /**
+ * 附件幂等键(用户附件=附件url;发票文件=A801-2 的 path)
+ */
+ private String attachmentKey;
+ /**
+ * 附件名称(展示/排查用)
+ */
+ private String attachmentName;
+ /**
+ * 金蝶侧文件路径(A902 返回 data.path)
+ */
+ private String kingdeePath;
+ /**
+ * 同步状态:1 成功 / 0 失败
+ */
+ private Integer syncStatus;
+ /**
+ * 失败原因(超限/下载失败/金蝶报错)
+ */
+ private String failReason;
+ /**
+ * 最近上传时间
+ */
+ private LocalDateTime syncTime;
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeBillLock.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeBillLock.java
new file mode 100644
index 00000000..1817ce2e
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeBillLock.java
@@ -0,0 +1,87 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.core.tenant.mp.TenantEntity;
+
+import java.io.Serial;
+import java.time.LocalDateTime;
+
+/**
+ * 金蝶单据锁定记录实体类
+ *
+ * 一行 = 一张金蝶目标单:推送成功后 lockBusiData 锁定(FAILED→LOCKED),金蝶经 unLockNotifyBusiData
+ * 回调解锁(→UNLOCKED,DELETE 时废除源单并还原关联数据);同时承担当前锁状态(编辑守卫/回调定位)
+ * 与锁定补偿扫表(FAILED+try_count)、锁定/解锁留痕
+ *
+ * @author Chill
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("blade_kingdee_bill_lock")
+@Schema(description = "金蝶单据锁定记录")
+public class KingdeeBillLock extends TenantEntity {
+ @Serial private static final long serialVersionUID = 1L;
+ /**
+ * WLTMS 单据类型(SETTLEMENT/INVOICE_APPLY,预留 PAY_APPLY/RECEIVED_INVOICE)
+ */
+ private String sourceBillType;
+ /**
+ * WLTMS 业务主键
+ */
+ private Long sourceBillId;
+ /**
+ * WLTMS 单号(正式结算单号/开票申请单号)
+ */
+ private String sourceBillNo;
+ /**
+ * 金蝶单据类型(ApFin_other_BT_S/arfin_other_BT_S/cdm_paybill_bank_elect_BT_S 等固定值)
+ */
+ private String targetBillType;
+ /**
+ * 金蝶单据编号(即业务单 kingdeeBillNo,开票申请按发票张次逐张一行)
+ */
+ private String targetBillNo;
+ /**
+ * 锁状态:failed-锁定失败(补偿重试对象)/locked-金蝶锁定中/unlocked-已解锁(终态)
+ */
+ private String lockStatus;
+ /**
+ * 最近动作来源操作类型:SAVE-单据新增,DELETE-单据删除(DELETE 回调触发废除还原)
+ */
+ private String operationType;
+ /**
+ * 锁定重试次数(补偿任务用,超上限停试)
+ */
+ private Integer tryCount;
+ /**
+ * 锁定时间
+ */
+ private LocalDateTime lockTime;
+ /**
+ * 解锁时间
+ */
+ private LocalDateTime unlockTime;
+ /**
+ * 推单发起人(锁定操作人)
+ */
+ private String lockOperator;
+ /**
+ * 回调操作人(金蝶侧操作人)
+ */
+ private String notifyOperator;
+ /**
+ * 锁定失败原因
+ */
+ private String failReason;
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java
index 261110c3..79eb12e7 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeInvoicePool.java
@@ -47,22 +47,108 @@ import java.time.LocalDateTime;
@Schema(description = "金蝶进项发票票据池镜像")
public class KingdeeInvoicePool extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /**
+ * 发票号码(同租户唯一,A801-1 invoiceNo,镜像 upsert 匹配键)
+ */
private String invoiceNo;
+ /**
+ * 金蝶发票流水号(票池唯一标识,A801-1 serialNo)
+ */
+ private String serialNo;
+ /**
+ * 开票日期
+ */
private LocalDate invoiceDate;
+ /**
+ * 发票类型(票易通编码转名称后落库,如「数电发票(增值税专用发票)」)
+ */
private String invoiceType;
+ /**
+ * 票易通原始发票类型编码(A801-1 invoiceType,如 26/27,A303-2 invoicetypef7_number 取数)
+ */
+ private String invoiceTypeCode;
+ /**
+ * 税率(百分数,13 表示 13%)
+ */
private BigDecimal taxRate;
+ /**
+ * 开票金额(A801-1 价税合计 totalAmount)
+ */
private BigDecimal invoiceAmount;
+ /**
+ * 税额
+ */
private BigDecimal taxAmount;
+ /**
+ * 受票单位(购方名称,A801-1 buyerName)
+ */
private String receiverName;
+ /**
+ * 购方税号(A801-1 buyerTaxNo)
+ */
+ private String buyerTaxNo;
+ /**
+ * 开票单位(销方名称,A801-1 salerName)
+ */
private String issuerName;
+ /**
+ * 销方税号(A801-1 salerTaxNo)
+ */
+ private String salerTaxNo;
+ /**
+ * 开票单位开户银行
+ */
private String bankName;
+ /**
+ * 开票单位银行账号
+ */
private String bankAccount;
+ /**
+ * 开票单位开户网点
+ */
private String issuingBank;
+ /**
+ * 联系电话
+ */
private String phone;
+ /**
+ * 客户邮箱(分号分隔,最多 3 个)
+ */
private String customerEmails;
+ /**
+ * 部门邮箱(分号分隔,最多 3 个)
+ */
private String departmentEmails;
+ /**
+ * 金蝶单据号(票池发票取发票流水号 serialNo)
+ */
private String kingdeeBillNo;
+ /**
+ * 金蝶同步状态(unsynced 未同步 / synced 已同步 / failed 同步失败)
+ */
private String kingdeeStatus;
+ /**
+ * 查验状态(A801-1 checkStatus,0 未查验 / 1 通过 / 2 不通过)
+ */
+ private String checkStatus;
+ /**
+ * 附件信息JSON(结构 {"source":"piaozone","path":"A801-2下载path","name":"文件名"})
+ */
private String attachmentsJson;
+ /**
+ * A801-1 发票明细行JSON(BfiInvoicePoolItemVO 列表,票池存档;收票单分录已改与应付单同源结算费用行)
+ */
+ private String itemsJson;
+ /**
+ * 票池数据更新时间(A801-1 billCreateTime,镜像排序字段)
+ */
private LocalDateTime sourceUpdatedTime;
+ /**
+ * 剩余可用金额(A801-1 availableAmount,金蝶可用税额口径,选票本地过滤用:>0 才可选)
+ */
+ private BigDecimal availableAmount;
+ /**
+ * 金额使用状态(A801-1 amountUseStatus,0 未使用可选 / 2 全部使用;选票本地过滤用)
+ */
+ private String amountUseStatus;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeReceiptFlow.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeReceiptFlow.java
index 79b75c1b..533394ae 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeReceiptFlow.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/KingdeeReceiptFlow.java
@@ -26,6 +26,8 @@
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;
@@ -49,16 +51,105 @@ public class KingdeeReceiptFlow extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
+ /**
+ * 金蝶认领公告id(A301-2 返回 id,认领推送 A301-3 必填)
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long billId;
+ /**
+ * 金蝶付款人id(A301-2 公告对应往来单位 id,认领推送 A301-3 用)
+ */
+ private String payerId;
+ /**
+ * 公告认领状态(A301-2 claimstatus:0未认领/1已认领)
+ */
+ private String kingdeeClaimStatus;
+ /**
+ * 我已认领(A301-2 claimme)
+ */
+ private Boolean claimMe;
+ /**
+ * 未认领金额(A301-2 unclaimamount)
+ */
+ private BigDecimal unclaimedAmount;
+ /**
+ * 手续费(A301-2 fee)
+ */
+ private BigDecimal fee;
+ /**
+ * 收款人(A301-2 recpayee)
+ */
+ private String payeeName;
+ /**
+ * 收款通知单号(A301-2 billno)
+ */
private String receiptNoticeNo;
+ /**
+ * 付款人名称(A301-2 recviewpayer)
+ */
private String payerName;
+ /**
+ * 收款金额(A301-2 reamount)
+ */
private BigDecimal receiptAmount;
+ /**
+ * 对方单位名称(A301-2 oppunit)
+ */
private String counterpartyName;
+ /**
+ * 对方银行账号(A301-2 oppbanknumber)
+ */
private String counterpartyAccount;
+ /**
+ * 对方开户行(A301-2 oppbank)
+ */
private String counterpartyBank;
+ /**
+ * 摘要(A301-2 description)
+ */
private String summary;
+ /**
+ * 交易日期(A301-2 tradetime)
+ */
private LocalDateTime transactionTime;
+ /**
+ * 明细流水号(A301-2 detailid,回退 tradedetailno / bankdetailno,幂等键)
+ */
private String detailSerialNo;
+ /**
+ * 已认领金额(本地认领驱动,初始为0)
+ */
private BigDecimal claimedAmount;
+ /**
+ * 认领状态(unclaimed未认领/partial部分认领/claimed认领完成,累计认领与公告未认领金额对表)
+ */
private String claimStatus;
+ /**
+ * 推送状态(none未推送/pushed已推送/failed推送失败;已推送为终态闸门,不允许二次推送)
+ */
+ private String pushStatus;
+ /**
+ * 最近一次推送失败原因(成功后清空)
+ */
+ private String pushFailReason;
+ /**
+ * 首次推送金蝶成功时间
+ */
+ private LocalDateTime pushedTime;
+ /**
+ * 金蝶推送来源单据编码(分摊结算单号下划线拼接,推送时生成落库;分摊不变则重试编码不变,A301-1 回写匹配键)
+ */
+ private String awrpSrcCode;
+ /**
+ * 金蝶收款单号(推送响应回填 / A301-1 回写推进)
+ */
+ private String kingdeeBillNo;
+ /**
+ * 金蝶回写状态(none未推送/pushed已推送/approved审核通过/received已收款/failed失败)
+ */
+ private String kingdeeBillStatus;
+ /**
+ * 来源更新时间(取交易日期,同步时刷新)
+ */
private LocalDateTime sourceUpdatedTime;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/PaymentApplication.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/PaymentApplication.java
index 1d6d033c..45ab1f4c 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/PaymentApplication.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/PaymentApplication.java
@@ -2,18 +2,24 @@
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
*
- * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
*
- * 1. This software is for development use only under a valid license from BladeX.
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
*
- * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
*
- * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
- * Copyright of this software remains with BladeX.
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
*
- * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
*
- * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
*
* Author: Chill Zhuang (bladejava@qq.com)
*/
@@ -34,43 +40,84 @@ import java.time.LocalDate;
@TableName("blade_payment_application")
public class PaymentApplication extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
+ /** 付款申请单号 */
private String paymentNo;
+ /** 付款类型:project_advance 项目预付 / progress_advance 进度预付 / settlement_payment 结算付款 */
private String paymentType;
+ /** 正式结算单id */
private Long settlementId;
+ /** 正式结算单号 */
private String settlementNo;
+ /** 预结算单id */
private Long preSettlementId;
+ /** 预结算单号 */
private String preSettlementNo;
+ /** 项目id */
private Long projectId;
+ /** 项目名称 */
private String projectName;
+ /** 所属组织id */
private Long deptId;
+ /** 所属组织名称 */
private String deptName;
+ /** 合同id */
private Long contractId;
+ /** 合同编号 */
private String contractNo;
+ /** 合同名称 */
private String contractName;
+ /** 付款方 */
private String payerName;
+ /** 收款方 */
private String payeeName;
+ /** 结算金额 */
private BigDecimal settlementAmount;
+ /** 应付金额 */
private BigDecimal payableAmount;
+ /** 来源单据类型:项目预付 / 正式结算单 / 预结算单 */
private String billType;
+ /** 付款比例(%) */
private BigDecimal paymentRatio;
+ /** 申请付款金额 */
private BigDecimal appliedAmount;
+ /** 付款方式,业务字典 pay_method */
private String paymentMethod;
+ /** 汇票台账id(票据付款时关联) */
private Long billLedgerId;
+ /** 票据号码 */
private String billNo;
+ /** 收款账户id */
private Long receiptAccountId;
+ /** 收款账户名称 */
private String receiptAccountName;
+ /** 收款银行名称 */
private String bankName;
+ /** 收款账号 */
private String bankAccount;
+ /** 申请人 */
private String applicantName;
+ /** 申请日期 */
private LocalDate applyDate;
+ /** 发票匹配状态:unmatched 未匹配 / matched 已匹配 */
private String invoiceStatus;
+ /** 匹配发票金额 */
private BigDecimal matchedInvoiceAmount;
+ /** 已付款金额 */
private BigDecimal paidAmount;
+ /** 审批状态:draft 草稿 / reviewing 审批中 / approved 审批通过 / returned 已驳回 / voided 已作废 */
private String approvalStatus;
+ /** 当前审批节点 */
private String currentNode;
+ /** 当前处理人 */
private String currentProcessor;
+ /** 金蝶单据号(A103-2 查询回填的星瀚单据编号 billno,如 FKSQ-xxxx) */
private String kingdeeBillNo;
+ /** 金蝶单据id(A103-1/A103-3 推送返回的星瀚内部主键,附件上传等使用) */
+ private String kingdeeBillId;
+ /** 金蝶同步状态:unsynced 未生成 / synced 已生成 / paying 付款中 / paid 已付款 / closed 已关闭 / failed 生成失败 */
private String kingdeeStatus;
+ /** 附件信息JSON */
private String attachmentsJson;
+ /** 备注 */
private String remark;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceiptClaim.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceiptClaim.java
index fc7f7862..2294d0a9 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceiptClaim.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ReceiptClaim.java
@@ -65,7 +65,13 @@ public class ReceiptClaim extends TenantEntity {
private String attachmentsJson;
private String remark;
private String claimStatus;
+ /**
+ * 金蝶收款单号(冗余展示:推送成功/A301-1 回写时由流水级联刷新)
+ */
private String kingdeeBillNo;
+ /**
+ * 金蝶回写状态(冗余展示:与流水 kingdee_bill_status 同步刷新)
+ */
private String kingdeeBillStatus;
@JsonSerialize(using = ToStringSerializer.class)
private Long voidedBy;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/FormalSettlementVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/FormalSettlementVO.java
index 1e507376..5929669a 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/FormalSettlementVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/FormalSettlementVO.java
@@ -9,6 +9,7 @@
package org.springblade.transport.pojo.vo;
import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.transport.pojo.entity.FormalSettlement;
@@ -18,6 +19,7 @@ import org.springblade.transport.pojo.entity.FormalSettlementInvoice;
import org.springblade.transport.pojo.entity.FormalSettlementSource;
import org.springblade.transport.pojo.entity.FormalSettlementPayment;
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
+import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
@@ -39,6 +41,15 @@ public class FormalSettlementVO extends FormalSettlement {
@TableField(exist = false) private String ids;
@TableField(exist = false) private String approvalStatusName;
@TableField(exist = false) private String settlementTypeName;
+ @TableField(exist = false) private Integer attachmentCount;
+ @TableField(exist = false) private String attachmentSyncStatus;
+ @TableField(exist = false) private String attachmentSyncStatusName;
+ @TableField(exist = false) @Schema(description = "金蝶锁状态:locked-锁定中/failed-锁定失败;unlocked为终态不返回,null=无锁或已解锁")
+ private String kingdeeLockStatus;
+ @TableField(exist = false) @Schema(description = "金蝶锁失败原因(仅锁定失败时有值)")
+ private String kingdeeLockFailReason;
+ @TableField(exist = false) @Schema(description = "金蝶锁重试次数(达上限5后不再自动补偿,需重推同步金蝶)")
+ private Integer kingdeeTryCount;
@TableField(exist = false) private String preSettlementNos;
@TableField(exist = false) private String preSettlementNo;
@TableField(exist = false) private List sources;
@@ -46,6 +57,8 @@ public class FormalSettlementVO extends FormalSettlement {
@TableField(exist = false) private List summaryFees;
@TableField(exist = false) private List payments;
@TableField(exist = false) private List invoices;
+ @TableField(exist = false) @Schema(description = "开票申请经金蝶回写的发票结果(含文件地址,只读展示)")
+ private List issuedInvoices;
@TableField(exist = false) private List paymentApplications;
@TableField(exist = false) private List adjustments;
@TableField(exist = false) private List changeRecords;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceApplicationVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceApplicationVO.java
index 664d02b0..c54945aa 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceApplicationVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceApplicationVO.java
@@ -30,6 +30,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.transport.pojo.entity.InvoiceApplication;
import org.springblade.transport.pojo.entity.InvoiceApplicationDetail;
+import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
import org.springblade.transport.pojo.entity.InvoiceApplicationRecord;
import org.springblade.transport.pojo.entity.InvoiceApplicationSettlement;
@@ -48,9 +49,16 @@ public class InvoiceApplicationVO extends InvoiceApplication {
@TableField(exist = false) private String settlementNos;
@TableField(exist = false) private String approvalStatusName;
@TableField(exist = false) private String kingdeeStatusName;
+ @TableField(exist = false) private Integer attachmentCount;
+ @TableField(exist = false) private String attachmentSyncStatus;
+ @TableField(exist = false) private String attachmentSyncStatusName;
+ @TableField(exist = false) private String kingdeeLockStatus;
+ @TableField(exist = false) private String kingdeeLockFailReason;
+ @TableField(exist = false) private Integer kingdeeTryCount;
@TableField(exist = false) private String createUserName;
@TableField(exist = false) private List settlements;
@TableField(exist = false) private List sheets;
@TableField(exist = false) private List details;
@TableField(exist = false) private List records;
+ @TableField(exist = false) private List invoices;
}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceReceiptVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceReceiptVO.java
index 8be4dcf2..2f344ff1 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceReceiptVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/InvoiceReceiptVO.java
@@ -48,7 +48,10 @@ public class InvoiceReceiptVO extends InvoiceReceipt {
@Serial private static final long serialVersionUID = 1L;
@TableField(exist = false) private String settlementNos;
@TableField(exist = false) private String approvalStatusName;
- @TableField(exist = false) private String kingdeeStatusName;
+ @TableField(exist = false) private String kingdeePushStatusName;
+ @TableField(exist = false) private Integer attachmentCount;
+ @TableField(exist = false) private String attachmentSyncStatus;
+ @TableField(exist = false) private String attachmentSyncStatusName;
@TableField(exist = false) private String createUserName;
@TableField(exist = false) private String updateUserName;
@TableField(exist = false) private List settlements;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/KingdeeInvoicePoolVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/KingdeeInvoicePoolVO.java
new file mode 100644
index 00000000..001bae66
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/KingdeeInvoicePoolVO.java
@@ -0,0 +1,157 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.pojo.vo;
+
+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 java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 金蝶票据池镜像统一视图(含本地认领/登记标记;认领口径:可匹配与默认认领金额=价税合计)
+ *
+ * @author Chill
+ */
+@Data
+@Schema(description = "金蝶票据池镜像统一视图")
+public class KingdeeInvoicePoolVO implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /** 镜像主键,仅已入镜像(已登记)的票非空 */
+ @Schema(description = "镜像主键")
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long id;
+
+ /** 发票号码 */
+ @Schema(description = "发票号码")
+ private String invoiceNo;
+
+ /** 金蝶发票流水号(票池唯一标识) */
+ @Schema(description = "金蝶发票流水号")
+ private String serialNo;
+
+ /** 开票日期 yyyy-MM-dd */
+ @Schema(description = "开票日期")
+ private String invoiceDate;
+
+ /** 发票类型名称(镜像落库时已由编码转名称) */
+ @Schema(description = "发票类型名称")
+ private String invoiceType;
+
+ /** 税率百分数,如 13 */
+ @Schema(description = "税率百分数")
+ private BigDecimal taxRate;
+
+ /** 价税合计 */
+ @Schema(description = "发票金额(价税合计)")
+ private BigDecimal invoiceAmount;
+
+ /** 税额 */
+ @Schema(description = "税额")
+ private BigDecimal taxAmount;
+
+ /** 受票单位(购方名称) */
+ @Schema(description = "受票单位")
+ private String receiverName;
+
+ /** 购方税号 */
+ @Schema(description = "购方税号")
+ private String buyerTaxNo;
+
+ /** 开票单位(销方名称) */
+ @Schema(description = "开票单位")
+ private String issuerName;
+
+ /** 销方税号 */
+ @Schema(description = "销方税号")
+ private String salerTaxNo;
+
+ /** 销方开户行名称 */
+ @Schema(description = "销方开户行名称")
+ private String bankName;
+
+ /** 销方银行账号 */
+ @Schema(description = "销方银行账号")
+ private String bankAccount;
+
+ /** 销方开户行及账号 */
+ @Schema(description = "开户行及账号")
+ private String issuingBank;
+
+ /** 销方电话 */
+ @Schema(description = "电话")
+ private String phone;
+
+ /** 客户邮箱 JSON 串 */
+ @Schema(description = "客户邮箱JSON")
+ private String customerEmails;
+
+ /** 部门邮箱 JSON 串 */
+ @Schema(description = "部门邮箱JSON")
+ private String departmentEmails;
+
+ /** 金蝶流水号(登记侧展示为金蝶单据号) */
+ @Schema(description = "金蝶单据号")
+ private String kingdeeBillNo;
+
+ /** 同步状态 unsynced/synced/failed */
+ @Schema(description = "金蝶同步状态")
+ private String kingdeeStatus;
+
+ /** 查验状态 */
+ @Schema(description = "查验状态")
+ private String checkStatus;
+
+ /** 附件 JSON(票易通下载 path) */
+ @Schema(description = "附件信息JSON")
+ private String attachmentsJson;
+
+ /** 可认领金额,口径同发票金额 */
+ @Schema(description = "可认领金额")
+ private BigDecimal availableInvoiceAmount;
+
+ /** 剩余可用金额(金蝶可用税额口径,与可认领金额的价税合计口径不同) */
+ @Schema(description = "剩余可用金额")
+ private BigDecimal availableTaxAmount;
+
+ /** 默认认领金额,口径同发票金额 */
+ @Schema(description = "默认认领金额")
+ private BigDecimal matchedAmount;
+
+ /** 是否已被正式结算单认领(实时查询返回行的本地装饰标记,仅展示不作过滤) */
+ @Schema(description = "是否已认领")
+ private Boolean claimed;
+
+ /** 是否已存在非作废收票登记(实时查询返回行的本地装饰标记,仅展示不作过滤) */
+ @Schema(description = "是否已登记")
+ private Boolean registered;
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/PaymentApplicationVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/PaymentApplicationVO.java
index 6e6c183a..d8bc998f 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/PaymentApplicationVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/PaymentApplicationVO.java
@@ -43,6 +43,12 @@ public class PaymentApplicationVO extends PaymentApplication {
@TableField(exist = false) private String paymentTypeName;
@TableField(exist = false) private String approvalStatusName;
@TableField(exist = false) private String kingdeeStatusName;
+ @TableField(exist = false) private Integer attachmentCount;
+ @TableField(exist = false) private String attachmentSyncStatus;
+ @TableField(exist = false) private String attachmentSyncStatusName;
+ @TableField(exist = false) private String kingdeeLockStatus;
+ @TableField(exist = false) private String kingdeeLockFailReason;
+ @TableField(exist = false) private Integer kingdeeTryCount;
@TableField(exist = false) private List invoices;
@TableField(exist = false) private List paymentRecords;
@TableField(exist = false) private List settlements;
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptClaimRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptClaimRecordVO.java
index 9802b661..400af8ae 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptClaimRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptClaimRecordVO.java
@@ -76,6 +76,11 @@ public class ReceiptClaimRecordVO extends ReceiptClaim {
private String claimStatusName;
@TableField(exist = false)
private String kingdeeBillStatusName;
+ /**
+ * 关联流水推送状态(none未推送/pushed已推送/failed推送失败,查询条件)
+ */
+ @TableField(exist = false)
+ private String pushStatus;
@TableField(exist = false)
private List settlements;
@TableField(exist = false)
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java
index b282c745..f32a9491 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ReceiptFlowVO.java
@@ -56,6 +56,12 @@ public class ReceiptFlowVO extends KingdeeReceiptFlow {
@TableField(exist = false)
private String claimStatusName;
@TableField(exist = false)
+ private String kingdeeClaimStatusName;
+ @TableField(exist = false)
+ private String pushStatusName;
+ @TableField(exist = false)
+ private String kingdeeBillStatusName;
+ @TableField(exist = false)
private BigDecimal remainingAmount;
@TableField(exist = false)
private String createUserName;
@@ -73,6 +79,16 @@ public class ReceiptFlowVO extends KingdeeReceiptFlow {
@TableField(exist = false)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime transactionEndTime;
+ /**
+ * 收款金额区间-最小值(查询条件)
+ */
+ @TableField(exist = false)
+ private BigDecimal receiptAmountStart;
+ /**
+ * 收款金额区间-最大值(查询条件)
+ */
+ @TableField(exist = false)
+ private BigDecimal receiptAmountEnd;
@TableField(exist = false)
private List claimRecords = new ArrayList<>();
}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java b/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java
index d08d36a6..edacf506 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java
@@ -66,6 +66,8 @@ public class SysClient implements ISysClient {
private final IFeeItemService feeItemService;
private final ICargoTypeService cargoTypeService;
+ private final IMeasurementUnitService measurementUnitService;
+ private final IInvoiceItemService invoiceItemService;
@Override
@GetMapping(MENU)
@@ -231,10 +233,27 @@ public class SysClient implements ISysClient {
.orderByAsc(CargoType::getCargoCode)));
}
+ @Override
+ @GetMapping(MEASUREMENT_UNITS)
+ public R> getMeasurementUnits() {
+ return R.data(measurementUnitService.list(Wrappers.lambdaQuery()
+ .eq(MeasurementUnit::getIsDeleted, 0)
+ .orderByAsc(MeasurementUnit::getUnitCode)));
+ }
+
@Override
@GetMapping(PERMISSIONS)
public R> getPermissions(String roleId) {
return R.data(menuService.permissionCodes(roleId));
}
+ @Override
+ @GetMapping(INVOICE_ITEMS)
+ public R> getInvoiceItems() {
+ return R.data(invoiceItemService.list(Wrappers.lambdaQuery()
+ .eq(InvoiceItem::getStatus, 1)
+ .eq(InvoiceItem::getIsDeleted, 0)
+ .orderByAsc(InvoiceItem::getCategoryName, InvoiceItem::getShortName)));
+ }
+
}
diff --git a/blade-service/blade-transport/pom.xml b/blade-service/blade-transport/pom.xml
index f55739f3..7f7e843b 100644
--- a/blade-service/blade-transport/pom.xml
+++ b/blade-service/blade-transport/pom.xml
@@ -47,10 +47,19 @@
org.springblade
blade-process-api
+
+ org.springblade
+ blade-bfi-api
+
org.springframework.boot
spring-boot-starter-amqp
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
io.minio
minio
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/TransportApplication.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/TransportApplication.java
index 27a73b5b..f5ece848 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/TransportApplication.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/TransportApplication.java
@@ -27,12 +27,14 @@ package org.springblade.transport;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 运输服务启动器
*
* @author Chill
*/
+@EnableScheduling
@BladeCloudApplication
public class TransportApplication {
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java
new file mode 100644
index 00000000..ecc40f74
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java
@@ -0,0 +1,413 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+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.stereotype.Component;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 业财融合(BFI)推送金蝶配置属性,统一收口 Nacos 配置 thirdParty.bfi.*:
+ *
+ * srcSys 为客商、付款申请等各业务推送金蝶的公共参数(外部系统编号);
+ * customer、payApply 为各业务专属参数;未配置时使用字段默认值。
+ *
+ * prefix 须为全小写(thirdparty.bfi),Spring 宽松绑定可匹配 Nacos 中的 camelCase 键(thirdParty.payApply.srcSys 等)。
+ *
+ * @author Chill
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "thirdparty.bfi")
+public class BfiProperties {
+
+ /**
+ * 数据来源系统编码(公共):客商、付款申请等推送金蝶时统一使用,默认 WLTMS(运输TMS)
+ */
+ private String srcSys = BfiConstant.DATA_SOURCE_WLTMS;
+
+ /**
+ * 付款申请推送配置(A103-3 付款申请保存)
+ */
+ private PayApply payApply = new PayApply();
+
+ /**
+ * 开票申请推送配置(A303-1 开票申请单新增 / A303-5 开票申请发票查询)
+ */
+ private InvoiceApply invoiceApply = new InvoiceApply();
+
+ /**
+ * 收票单推送配置(A303-2 收票单批量保存 / A302-6 收票指定应付 / A801-3 收票单查询回写)
+ */
+ private ReceivedInvoice receivedInvoice = new ReceivedInvoice();
+
+ /**
+ * 收款认领公告查询配置(A301-2)
+ */
+ private ClaimAnnounce claimAnnounce = new ClaimAnnounce();
+
+ /**
+ * 付款结果定时回写配置(A103-2/A103-4,总开关同时控制应收勾稽、开票结果回写)
+ */
+ private PayResult payResult = new PayResult();
+
+ /**
+ * 收票单结果定时回写开关(A801-3)
+ */
+ private ReceivedInvoiceResult receivedInvoiceResult = new ReceivedInvoiceResult();
+
+ /**
+ * 收款认领推送配置(A301-3)
+ */
+ private ReceiptClaimPush receiptClaimPush = new ReceiptClaimPush();
+
+ /**
+ * 全票池发票定时同步配置(A801-1)
+ */
+ private InvoiceReceipt invoiceReceipt = new InvoiceReceipt();
+
+ /**
+ * 客商金蝶数据定时同步配置(A902-1/A902-2)
+ */
+ private Customer customer = new Customer();
+
+ /**
+ * 金蝶黑名单查询配置
+ */
+ private Blacklist blacklist = new Blacklist();
+
+ /**
+ * 开票结果回写锁配置(A303-5)
+ */
+ private InvoiceResult invoiceResult = new InvoiceResult();
+
+ /**
+ * 应收单勾稽回写锁配置(A302-4)
+ */
+ private ArResult arResult = new ArResult();
+
+ /**
+ * 金蝶单据锁定补偿配置
+ */
+ private BillLock billLock = new BillLock();
+
+ /**
+ * 审批通过自动同步金蝶配置(结算单/付款申请/开票申请/收票登记)
+ */
+ private AutoSync autoSync = new AutoSync();
+
+ /**
+ * 应付/应收/收票单分录物料映射配置(A102-2/A302-2/A303-2 共用):
+ * 运输类按项目业务模式映射——国内运输按运单运输方式映射、国际运输映射国际货代服务;
+ * 装卸大类及手动添加的非运费杂费映射装卸物料
+ */
+ private Material material = new Material();
+
+ /**
+ * 付款申请推送配置项
+ */
+ @Data
+ public static class PayApply {
+
+ /**
+ * 单据类型编码:ap_payapply_BT_S 付款申请单、ap_payapply_oth_BT_S 其他付款单,默认其他付款单
+ */
+ private String billtype = "ap_payapply_oth_BT_S";
+
+ /**
+ * 汇率表编码,默认 01
+ */
+ private String exratetable = "01";
+
+ /**
+ * 外部单据类型,默认 0KzXs
+ */
+ private String srcType = "0KzXs";
+ }
+
+ /**
+ * 开票申请推送配置项(A303-1 / A303-5):
+ * 销方名称取申请单开票单位快照、销方税号取客商档案统一社会信用代码、税收分类编码取商品行开票内容快照,均不走配置
+ */
+ @Data
+ public static class InvoiceApply {
+
+ /**
+ * 分录外部单据类型,默认 0KzXs(与付款申请对齐)
+ */
+ private String srcType = "0KzXs";
+ }
+
+ /**
+ * 收票单推送配置项(A303-2 / A302-6 / A801-3):
+ * 发票类型编码、税收分类编码、购销方税号地址银行均取票池镜像与明细,不走配置
+ */
+ @Data
+ public static class ReceivedInvoice {
+
+ /**
+ * A303-2 分录物料编码:联调期临时值(金蝶测试档案),发票明细无金蝶物料概念,
+ * 真实物料口径与金蝶对齐后替换(与应收/应付联调临时值同源)
+ */
+ private String materialNumber = "SP0324";
+
+ /**
+ * A801-3 推送幂等预检回看窗口天数(金蝶修改时间窗口,契约约束不超过 1 个月)
+ */
+ private int queryWindowDays = 30;
+
+ /**
+ * A801-3 定时回写轮询窗口天数(同样受金蝶 1 个月窗口约束)
+ */
+ private int resultWindowDays = 30;
+
+ /**
+ * A801-3 回写 single-flight 锁租约秒数,须大于最坏一轮耗时(批量远程查询 + 逐单回写)
+ */
+ private long lockLeaseSeconds = 1800;
+ }
+
+ /**
+ * 收款认领公告查询配置项(A301-2)
+ */
+ @Data
+ public static class ClaimAnnounce {
+
+ /**
+ * 查询时间窗口(月数),默认 6(近半年)
+ */
+ private int months = 6;
+
+ /**
+ * 认领人(claimusers):配置后公告查询固定使用该值,不再按当前登录用户姓名查询
+ */
+ private String claimusers;
+ }
+
+ /**
+ * 分录物料映射配置项(thirdparty.bfi.material.*):默认值与金蝶测试档案对齐,
+ * 金蝶正式档案确认后直接在 Nacos 覆盖
+ */
+ @Data
+ public static class Material {
+
+ /**
+ * 国内运输:运单运输方式 -> 金蝶物料编码(公路运输/铁路运输/水路运输/航空运输四种),
+ * 运输方式未配置映射时直接拦截报错
+ */
+ private Map domestic = new LinkedHashMap<>() {{
+ put("公路运输", "XDWL11010001");
+ put("铁路运输", "XDWL11010002");
+ put("水路运输", "XDWL11010003");
+ put("航空运输", "Item-00001082");
+ }};
+
+ /**
+ * 国际运输业务模式物料编码(国际货运代理服务)
+ */
+ private String international = "Item-00001081";
+
+ /**
+ * 装卸大类、手动添加非运费杂费物料编码(装卸)
+ */
+ private String loading = "XDWL11002004";
+ }
+
+ /**
+ * 付款结果回写配置项(A103-2/A103-4)
+ */
+ @Data
+ public static class PayResult {
+
+ /**
+ * 回写任务开关,代码默认关闭(安全兜底),由 Nacos 按环境显式开启
+ */
+ private boolean syncEnabled = false;
+
+ /**
+ * 回写 single-flight 锁租约秒数,须大于最坏一轮耗时(分批远程查询 + 逐单回写)
+ */
+ private long lockLeaseSeconds = 1800;
+ }
+
+ /**
+ * 收票单结果回写配置项(A801-3)
+ */
+ @Data
+ public static class ReceivedInvoiceResult {
+
+ /**
+ * 回写任务开关,默认关闭(联调稳定后开启)
+ */
+ private boolean syncEnabled = false;
+ }
+
+ /**
+ * 收款认领推送配置项(A301-3)
+ */
+ @Data
+ public static class ReceiptClaimPush {
+
+ /**
+ * 推送任务开关,默认关闭(联调稳定后开启)
+ */
+ private boolean syncEnabled = false;
+
+ /**
+ * 推送 single-flight 锁租约秒数,须大于最坏一轮耗时(逐认领人远程查公告 + 逐条推送)
+ */
+ private long lockLeaseSeconds = 1800;
+ }
+
+ /**
+ * 全票池发票同步配置项(A801-1)
+ */
+ @Data
+ public static class InvoiceReceipt {
+
+ /**
+ * 同步任务开关,默认关闭(上线先手动跑一次全窗口铺底)
+ */
+ private boolean syncEnabled = false;
+
+ /**
+ * 增量窗口天数(按票池更新时间滚动)
+ */
+ private int incrementDays = 30;
+
+ /**
+ * 同步发票种类白名单(逗号分隔票易通编码,如 26,27 数电普票/专票),为空不限制
+ */
+ private String invoiceTypes = "";
+
+ /**
+ * 同步 single-flight 锁租约秒数,须大于最坏一轮耗时;进程异常退出时锁在租约到期后自动释放
+ */
+ private long lockLeaseSeconds = 1800;
+ }
+
+ /**
+ * 客商金蝶数据同步配置项(A902-1/A902-2)
+ */
+ @Data
+ public static class Customer {
+
+ /**
+ * 同步任务开关,默认关闭
+ */
+ private boolean syncEnabled = false;
+
+ /**
+ * 同步 single-flight 锁租约秒数,须大于最坏一轮耗时(复查 + 批量推送 + 停用联动)
+ */
+ private long lockLeaseSeconds = 1800;
+ }
+
+ /**
+ * 金蝶黑名单查询配置项
+ */
+ @Data
+ public static class Blacklist {
+
+ /**
+ * 客商提审命中风险警示类(B)时是否一并拦截;默认 false 仅放行告警,禁止交易类(A)始终拦截
+ */
+ private boolean blockRiskWarning = false;
+ }
+
+ /**
+ * 开票结果回写锁配置项(A303-5)
+ */
+ @Data
+ public static class InvoiceResult {
+
+ /**
+ * 回写 single-flight 锁租约秒数
+ */
+ private long lockLeaseSeconds = 1800;
+ }
+
+ /**
+ * 应收单勾稽回写锁配置项(A302-4)
+ */
+ @Data
+ public static class ArResult {
+
+ /**
+ * 勾稽 single-flight 锁租约秒数
+ */
+ private long lockLeaseSeconds = 1800;
+ }
+
+ /**
+ * 金蝶单据锁定补偿配置项
+ */
+ @Data
+ public static class BillLock {
+
+ /**
+ * 锁定补偿开关,默认关闭(安全兜底)
+ */
+ private boolean retryEnabled = false;
+ }
+
+ /**
+ * 审批通过自动同步金蝶配置项(结算单 A102-2/A302-2、付款申请 A103-1/A103-3、开票申请 A303-1、收票 A303-2+A302-6)
+ */
+ @Data
+ public static class AutoSync {
+
+ /**
+ * 结算单审批后自动推送开关
+ */
+ private boolean settlement = false;
+
+ /**
+ * 付款申请审批后自动推送开关
+ */
+ private boolean payment = false;
+
+ /**
+ * 开票申请审批后自动推送开关
+ */
+ private boolean invoiceApply = false;
+
+ /**
+ * 收票登记审批后自动推送开关
+ */
+ private boolean receipt = false;
+
+ /**
+ * failed 单据定时重推开关(仅重推生成失败,不扫 unsynced 存量)
+ */
+ private boolean retryEnabled = false;
+ }
+
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/CustomerBfiConstants.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/CustomerBfiConstants.java
new file mode 100644
index 00000000..712643da
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/CustomerBfiConstants.java
@@ -0,0 +1,63 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.constant;
+
+/**
+ * 客商档案金蝶(星瀚)协同常量
+ *
+ * @author Chill
+ */
+public class CustomerBfiConstants {
+
+ /**
+ * 推送金蝶固定值:业务职能全选(1销售,2结算,3付款,4收货)
+ */
+ public static final String BIZ_FUNCTION_ALL = "1,2,3,4";
+
+ /**
+ * 客商类型:内部组织(customer_kind 字段值)
+ */
+ public static final String CUSTOMER_KIND_INTERNAL = "internal";
+
+ /**
+ * 客商类型:外部客商(customer_kind 字段值)
+ */
+ public static final String CUSTOMER_KIND_EXTERNAL = "external";
+
+ /**
+ * 金蝶伙伴类型编码有效范围上界(BfiCustomerSaveDTO.type 全集 1~17)
+ */
+ public static final int PARTNER_TYPE_MAX = 17;
+
+ /**
+ * 营业期限类型:长期(TMS 表单文本)
+ */
+ public static final String BUSINESS_TERM_LONG = "长期";
+
+ private CustomerBfiConstants() {
+ }
+
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeAttachmentConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeAttachmentConstant.java
new file mode 100644
index 00000000..1355bcc6
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeAttachmentConstant.java
@@ -0,0 +1,176 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.constant;
+
+import java.util.Map;
+
+import org.springblade.core.log.exception.ServiceException;
+
+/**
+ * 金蝶附件同步(A902)常量
+ *
entityNumber/controlKey 映射以常量维护,二期客商(资质附件)等金蝶确认后补充条目即可,不改架构
+ *
+ * @author Chill
+ */
+public class KingdeeAttachmentConstant {
+
+ // ==================== 业务单据类型(biz_type) ====================
+
+ /**
+ * 付款申请单
+ */
+ public static final String BIZ_PAY_APPLICATION = "pay_application";
+ /**
+ * 正式结算-财务应付单面板
+ */
+ public static final String BIZ_FORMAL_SETTLEMENT_AP = "formal_settlement_ap";
+ /**
+ * 正式结算-财务应收单面板
+ */
+ public static final String BIZ_FORMAL_SETTLEMENT_AR = "formal_settlement_ar";
+ /**
+ * 收票单
+ */
+ public static final String BIZ_INVOICE_RECEIPT = "invoice_receipt";
+ /**
+ * 开票申请单
+ */
+ public static final String BIZ_INVOICE_APPLICATION = "invoice_application";
+
+ // ==================== 星瀚 entityNumber(A902 定位单据类型) ====================
+
+ /**
+ * 付款申请单
+ */
+ public static final String ENTITY_PAY_APPLICATION = "ap_payapply";
+ /**
+ * 财务应付单(正式结算应付面板)
+ */
+ public static final String ENTITY_AP_BILL = "ap_finapbill";
+ /**
+ * 财务应收单(正式结算应收面板)
+ */
+ public static final String ENTITY_AR_BILL = "ar_finarbill";
+ /**
+ * 收票单
+ */
+ public static final String ENTITY_INVOICE = "ap_invoice";
+ /**
+ * 开票申请单
+ */
+ public static final String ENTITY_INVOICE_APPLICATION = "sim_original_bill";
+
+ /**
+ * 业务单据类型 -> 星瀚 entityNumber 映射;
+ * 客商(资质附件)二期接入:待金蝶确认后补充专用 biz_type 与 entityNumber 条目
+ */
+ public static final Map ENTITY_NUMBERS = Map.of(
+ BIZ_PAY_APPLICATION, ENTITY_PAY_APPLICATION,
+ BIZ_FORMAL_SETTLEMENT_AP, ENTITY_AP_BILL,
+ BIZ_FORMAL_SETTLEMENT_AR, ENTITY_AR_BILL,
+ BIZ_INVOICE_RECEIPT, ENTITY_INVOICE,
+ BIZ_INVOICE_APPLICATION, ENTITY_INVOICE_APPLICATION
+ );
+
+ /**
+ * 星瀚附件面板/附件字段标识(各单据统一)
+ */
+ public static final String CONTROL_KEY = "attachmentpanel";
+
+ // ==================== 限制 ====================
+
+ /**
+ * 单文件软限制 20MB(待金蝶确认后调整);超限记失败原因跳过,不阻塞同单其余附件
+ */
+ public static final long MAX_FILE_SIZE_BYTES = 20L * 1024 * 1024;
+
+ // ==================== 同步行状态(sync_status) ====================
+
+ /**
+ * 同步失败
+ */
+ public static final int SYNC_STATUS_FAILED = 0;
+ /**
+ * 同步成功(终态,幂等跳过)
+ */
+ public static final int SYNC_STATUS_SUCCESS = 1;
+
+ // ==================== 附件同步聚合态(列表展示,运行时聚合非存储字段) ====================
+
+ /**
+ * 无附件:单据 attachmentsJson 无可上传附件
+ */
+ public static final String AGGREGATE_NONE = "none";
+ /**
+ * 未上传:有附件且成功数为 0
+ */
+ public static final String AGGREGATE_PENDING = "pending";
+ /**
+ * 部分未传:有附件且部分上传成功
+ */
+ public static final String AGGREGATE_PARTIAL = "partial";
+ /**
+ * 已上传:有附件且全部上传成功
+ */
+ public static final String AGGREGATE_DONE = "done";
+
+ /**
+ * 聚合态 -> 展示名称
+ */
+ public static final Map AGGREGATE_NAMES = Map.of(
+ AGGREGATE_NONE, "无附件",
+ AGGREGATE_PENDING, "未上传",
+ AGGREGATE_PARTIAL, "部分未传",
+ AGGREGATE_DONE, "已上传"
+ );
+
+ /**
+ * 业务单据类型对应星瀚 entityNumber
+ *
+ * @param bizType 业务单据类型
+ * @return entityNumber,未配置时抛业务异常(entityNumber 是硬约束,猜错即传不上)
+ */
+ public static String entityNumber(String bizType) {
+ String entityNumber = ENTITY_NUMBERS.get(bizType);
+ if (entityNumber == null) {
+ throw new ServiceException("金蝶附件上传失败:业务单据类型[" + bizType + "]未配置 entityNumber 映射");
+ }
+ return entityNumber;
+ }
+
+ /**
+ * 聚合态展示名称(未知态兜底返回原文)
+ *
+ * @param aggregateStatus 聚合态
+ * @return 展示名称
+ */
+ public static String aggregateName(String aggregateStatus) {
+ return AGGREGATE_NAMES.getOrDefault(aggregateStatus, aggregateStatus);
+ }
+
+ private KingdeeAttachmentConstant() {
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeBfiConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeBfiConstant.java
new file mode 100644
index 00000000..b1c45f44
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeBfiConstant.java
@@ -0,0 +1,253 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.constant;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 付款同步金蝶(星瀚)固定值。
+ * 金蝶侧档案编码暂以常量维护,待金蝶侧确认取值后直接修改常量重新发包。
+ *
+ * @author Chill
+ */
+public final class KingdeeBfiConstant {
+
+ /** 往来类型:供应商(付款申请与财务应付单共用)。 */
+ public static final String BFI_SUPPLIER_ASSTACT_TYPE = "bd_supplier";
+
+ /** 往来类型:客户(财务应付单按合同类别为客户合同时使用)。 */
+ public static final String BFI_CUSTOMER_ASSTACT_TYPE = "bd_customer";
+
+ /** 来源单据类型:上游合同(有合同时传 awrp_src_bt)。 */
+ public static final String BFI_CONTRACT_SRC_BT = "awrp_contract_info";
+
+ /** 无项目时传的项目编码占位值。 */
+ public static final String BFI_PROJECT_NUMBER_NONE = "999";
+
+ /** 联调默认物料编码:金蝶测试档案值,真实货物类型档案编码与金蝶对齐后替换。 */
+ public static final String BFI_AP_BILL_MATERIAL_NUMBER_TEST = "SP0324";
+
+ /** 联调默认计量单位编码:金蝶测试档案值,档案维护计量单位编码后替换。 */
+ public static final String BFI_AP_BILL_MEASUREUNIT_NUMBER_TEST = "TON";
+ /** 联调默认银行行号:金蝶测试档案值,档案维护银行行号后替换。 */
+ public static final String BFI_AP_BILL_BANKACCOUNT_NUMBER_TEST = "001110012009";
+
+ /** 联调默认项目编码:金蝶测试档案值,项目档案与金蝶对齐后替换。 */
+ public static final String BFI_AP_BILL_PROJECT_NUMBER_TEST = "999";
+
+ /** 付款类型编码:正式付款(结算付款)。 */
+ public static final String BFI_PAYMENT_TYPE_FORMAL = "201";
+
+ /** 付款类型编码:预付款。 */
+ public static final String BFI_PAYMENT_TYPE_PREPAY = "202";
+
+ /** A102-2 默认单据类型名称:服务采购应付(经 AP_BILL_BILLTYPE_MAP 转换为编码)。待确认:具体类型需金蝶侧最终确认,确认后修改此名称即可。 */
+ public static final String BFI_AP_BILL_BILLTYPE_NAME = "服务采购应付";
+
+ /** A102-2 款项性质编码(暂按 2001 材料采购)。待确认:编码需金蝶侧最终确认,确认后如有调整直接改此常量。 */
+ public static final String BFI_AP_BILL_PAY_PROPERTY = "2001";
+
+ /**
+ * A102-2 付款方式(赊购 CREDIT,允许金蝶侧后续调整付款金额;
+ */
+ public static final String BFI_AP_BILL_PUR_MODE_CREDIT = "CREDIT";
+ /**
+ * A102-2 付款方式 现购 CASH 会由金蝶自动 100% 推付款)。
+ */
+ public static final String BFI_AP_BILL_PUR_MODE_CASH = "CASH";
+
+ /** A102-2 必填:外部单据类型(应付单来源标识)。待确认:取值需金蝶侧确认,确认后如有调整直接改此常量。 */
+ public static final String BFI_AP_BILL_SRC_TYPE = "TMS财务应付单";
+
+ /** 财务应付单(ap_finapbill)单据类型映射:中文名称 -> 星瀚编码。 */
+ public static final Map AP_BILL_BILLTYPE_MAP = Map.ofEntries(
+ Map.entry("借贷项调整", "ApFin_borr_BT_S"),
+ Map.entry("费用采购应付", "ApFin_fee_BT_S"),
+ Map.entry("其他应付", "ApFin_other_BT_S"),
+ Map.entry("产品委外应付", "ApFin_product_BT_S"),
+ Map.entry("标准采购应付", "ApFin_pur_BT_S"),
+ Map.entry("采购费用应付", "ApFin_purfee_BT_S"),
+ Map.entry("销售费用应付", "ApFin_salefee_BT_S"),
+ Map.entry("服务采购应付", "ApFin_service_BT_S"),
+ Map.entry("资产采购应付", "ap_finapbill_asset_BT_S")
+ );
+
+ /** 财务应收单(ar_finarbill)单据类型映射:中文名称 -> 星瀚编码(当前付款链路未启用,应收结算单推送规则待业务确认)。 */
+ public static final Map AR_BILL_BILLTYPE_MAP = Map.ofEntries(
+ Map.entry("借贷项调整", "arfin_borrowar_BT_S"),
+ Map.entry("其他应收", "arfin_other_BT_S"),
+ Map.entry("销售费用应收", "arfin_salefee_BT_S"),
+ Map.entry("服务销售应收", "arfin_sersal_BT_S"),
+ Map.entry("标准销售应收", "arfin_standard_BT_S")
+ );
+
+ /** A302-2 默认单据类型名称:服务销售应收(经 AR_BILL_BILLTYPE_MAP 转换为编码)。待确认:具体类型需金蝶侧最终确认,确认后修改此名称即可。 */
+ public static final String BFI_AR_BILL_BILLTYPE_NAME = "服务销售应收";
+
+ /** A302-2 款项性质编码(暂按 1001 一般销售,与应付 2001 材料采购区分)。待确认:编码需金蝶侧最终确认,确认后如有调整直接改此常量。 */
+ public static final String BFI_AR_BILL_PAY_PROPERTY = "1001";
+
+ /** A302-2 付款方式:现销 CASH(与应付现购对称,由金蝶自动推收款)。待确认:取值需金蝶侧确认。 */
+ public static final String BFI_AR_BILL_PAY_MODE_CASH = "CASH";
+
+ /** A302-2 必填:外部单据类型(应收单来源标识)。待确认:取值需金蝶侧确认,确认后如有调整直接改此常量。 */
+ public static final String BFI_AR_BILL_SRC_TYPE = "TMS财务应收单";
+
+ /** A303-1 上游来源单据类型:财务应收单(awrp_src_bt)。 */
+ public static final String BFI_AR_BILL_SRC_BT = "ar_finarbill";
+
+ /** A301-3 认领推送核心单据类型:其他应付(e_corebilltype)。 */
+ public static final String BFI_CLAIM_CORE_BILL_TYPE = "ar_finarbill";
+
+ /** A303-1 发票种类:数电发票(普通发票)。 */
+ public static final String BFI_INVOICE_TYPE_DIGITAL_GENERAL = "10xdp";
+
+ /** A303-1 发票种类:数电发票(增值税专用发票)。 */
+ public static final String BFI_INVOICE_TYPE_DIGITAL_SPECIAL = "08xdp";
+
+ /** A303-1 特殊票种:货物运输(运费/货物运输类发票)。 */
+ public static final String BFI_INVOICE_SPECIAL_TYPE_FREIGHT = "E04";
+
+ /** A303-1 特殊票种:非特殊票种(其余费用类发票,如其他、非货物运输的运输类)。 */
+ public static final String BFI_INVOICE_SPECIAL_TYPE_NONE = "00";
+
+ /** A303-1 特殊票种货物运输判定关键字:命中商品名称或商品和服务分类简称即视为货物运输类。 */
+ public static final List BFI_INVOICE_FREIGHT_KEYWORDS = List.of("货物运输", "运费");
+
+ /** A303-1 货物运输单据体运输工具种类合法值:字典 transport_type 翻译名称命中则直送,否则兜底其它运输工具。 */
+ public static final List BFI_FREIGHT_TRANSPORT_TYPES = List.of("铁路运输", "公路运输", "水路运输", "航空运输", "管道运输", "其它运输工具");
+
+ /** A303-1 货物运输单据体运输工具种类兜底值。 */
+ public static final String BFI_FREIGHT_TRANSPORT_TYPE_OTHER = "其它运输工具";
+
+ /** 金蝶同步状态:开票完成(A303-5 查到发票,终态,开票申请专用)。 */
+ public static final String KINGDEE_STATUS_INVOICED = "invoiced";
+
+ /** 付款申请单(ap_payapply)单据类型映射:中文名称 -> 星瀚编码(使用规则待业务确认)。 */
+ public static final Map PAY_APPLY_BILLTYPE_MAP = Map.of(
+ "采购付款申请", "ap_payapply_BT_S",
+ "其他付款申请", "ap_payapply_oth_BT_S"
+ );
+
+ /** 金蝶同步状态:未生成(新建单据默认)。 */
+ public static final String KINGDEE_STATUS_UNSYNCED = "unsynced";
+
+ /** 金蝶同步状态:已生成(推送成功,等待付款)。 */
+ public static final String KINGDEE_STATUS_SYNCED = "synced";
+
+ /** 金蝶同步状态:付款中(A103-2 paystatus = Inpayment 部分付款)。 */
+ public static final String KINGDEE_STATUS_PAYING = "paying";
+
+ /** 金蝶同步状态:已付款(A103-2 paystatus = Alreadypay,终态)。 */
+ public static final String KINGDEE_STATUS_PAID = "paid";
+
+ /** 金蝶同步状态:已关闭(A103-2 billstatus = D,终态)。 */
+ public static final String KINGDEE_STATUS_CLOSED = "closed";
+
+ /** 金蝶同步状态:生成失败(推送异常/金蝶返回失败,可重推)。 */
+ public static final String KINGDEE_STATUS_FAILED = "failed";
+
+ /** 收票单推送状态:未推送(新建单据默认,票池绑定层的 kingdee_status 与此无关)。 */
+ public static final String RECEIVED_PUSH_UNSYNCED = "unsynced";
+
+ /** 收票单推送状态:金蝶收票单已保存(A303-2 成功,等待/已完成指定应付)。 */
+ public static final String RECEIVED_PUSH_PUSHED = "pushed";
+
+ /** 收票单推送状态:已指定应付(A302-6 成功,一票一应付,终态;金蝶侧异常回收获占)。 */
+ public static final String RECEIVED_PUSH_ASSIGNED = "assigned";
+
+ /** 收票单推送状态:推送失败(保存/指定异常或金蝶侧删除红冲,人工处理后重推)。 */
+ public static final String RECEIVED_PUSH_FAILED = "failed";
+
+ /** A303-2 业务类型(PUR:采购 / FEE:费用,TMS 服务费进项票暂按采购)。待确认:取值需金蝶侧最终确认。 */
+ public static final String BFI_RECEIVED_BIZ_TYPE = "PUR";
+
+ /** A303-2 必填:外部单据类型(收票单来源标识)。待确认:取值需金蝶侧确认,确认后如有调整直接改此常量。 */
+ public static final String BFI_RECEIVED_INVOICE_SRC_TYPE = "TMS收票登记";
+
+ /** A103-2 付款状态:已付款。 */
+ public static final String BFI_PAY_STATUS_ALREADY_PAY = "Alreadypay";
+
+ /** A103-2 付款状态:部分付款。 */
+ public static final String BFI_PAY_STATUS_IN_PAYMENT = "Inpayment";
+
+ /** A103-2 付款状态:未付款。 */
+ public static final String BFI_PAY_STATUS_UNPAID = "Unpaid";
+
+ /** A103-2 单据状态:已关闭。 */
+ public static final String BFI_BILL_STATUS_CLOSED = "D";
+
+ /** A801-1 查询限定发票状态:0 正常(票易通字典),作废/异常票不进镜像、不可补查 */
+ public static final String POOL_INVOICE_STATUS_NORMAL = "0";
+
+ /** A301-3 认领推送:收款类型.编码(示例 101,待金蝶确认后如有调整直接改此常量)。 */
+ public static final String BFI_CLAIM_REC_PAY_TYPE = "999";
+
+ /** A301-3 认领推送:付款人类型(默认客户 bd_customer,待金蝶确认后如有调整直接改此常量)。 */
+ public static final String BFI_CLAIM_PAYMENT_TYPE = "bd_customer";
+
+ /** 收款流水推送状态:未推送(未满足推送条件或尚未轮到推送)。 */
+ public static final String RECEIPT_CLAIM_PUSH_NONE = "none";
+
+ /** 收款流水推送状态:已推送(终态闸门,不允许二次推送)。 */
+ public static final String RECEIPT_CLAIM_PUSH_PUSHED = "pushed";
+
+ /** 收款流水推送状态:推送失败(本地认领保留,可重新推送,重试复用同一来源编码)。 */
+ public static final String RECEIPT_CLAIM_PUSH_FAILED = "failed";
+
+ /** 收款流水金蝶回写状态:未推送(认领完成推送前的初始状态)。 */
+ public static final String RECEIPT_CLAIM_BILL_NONE = "none";
+
+ /** 金蝶同步状态:认领结果已推送(A301-3 成功,等待金蝶收款单审核/收款,可继续回写)。 */
+ public static final String KINGDEE_STATUS_PUSHED = "pushed";
+
+ /** 金蝶同步状态:收款单已收款(A301-1 billstatus = D,终态)。 */
+ public static final String KINGDEE_STATUS_RECEIVED = "received";
+
+ /** 选票金额使用状态可选值:仅保留剩余可用金额>0 且金额使用状态为 0 的可用票,2-全部使用 */
+ public static final String POOL_AMOUNT_USE_STATUS_SELECTABLE = "0";
+
+ /** 单请求远程补页调用上限:金额可用过滤在拉取后进行,过滤率过低时以此封顶防请求无限拉长 */
+ public static final int POOL_MAX_REMOTE_CALLS = 3;
+
+ /** 选票单据使用状态固定为未用(1,票易通字典):仅未使用的票可被认领/登记,前端不提供该筛选条件 */
+ public static final String POOL_EXPENSE_STATUS_UNUSED = "1";
+
+ /**
+ * 按中文名称从映射字典转换单据类型编码。
+ *
+ * @param billtypeMap 单据类型映射字典
+ * @param name 单据类型中文名称
+ * @return 星瀚单据类型编码,未维护时返回 null
+ */
+ public static String billtypeBy(Map billtypeMap, String name) {
+ return billtypeMap.get(name);
+ }
+
+ private KingdeeBfiConstant() {
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeLockConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeLockConstant.java
new file mode 100644
index 00000000..6737bc3e
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/KingdeeLockConstant.java
@@ -0,0 +1,91 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.constant;
+
+import java.util.Map;
+
+/**
+ * 金蝶单据锁定/解锁(lockBusiData / unLockNotifyBusiData)固定值。
+ *
lockBusiData 由 WLTMS 调金蝶(源系统 WLTMS、目标系统 XH),
+ * unLockNotifyBusiData 由金蝶调 WLTMS(源系统 XH、目标系统 WLTMS);
+ * WLTMS 单据类型自定义,金蝶目标单据类型为固定值
+ *
+ * @author Chill
+ */
+public final class KingdeeLockConstant {
+
+ /** 锁动作:锁定 */
+ public static final String ACTION_LOCK = "LOCK";
+
+ /** 锁动作:解锁 */
+ public static final String ACTION_UNLOCK = "UNLOCK";
+
+ /** 来源操作类型:单据新增 */
+ public static final String OPERATION_SAVE = "SAVE";
+
+ /** 来源操作类型:单据删除(解锁回调携带 DELETE 时 WLTMS 废除源单并还原关联数据) */
+ public static final String OPERATION_DELETE = "DELETE";
+
+ /** 系统标识:WLTMS(运输管理系统,即本系统) */
+ public static final String SYSTEM_WLTMS = "WLTMS";
+
+ /** 系统标识:金蝶星瀚 XH */
+ public static final String SYSTEM_XH = "XH";
+
+ /** 锁状态:锁定失败(补偿定时任务的重试对象) */
+ public static final String STATUS_FAILED = "failed";
+
+ /** 锁状态:金蝶锁定中(源单冻结,不可编辑/作废) */
+ public static final String STATUS_LOCKED = "locked";
+
+ /** 锁状态:已解锁(终态;同单重推生成新锁记录) */
+ public static final String STATUS_UNLOCKED = "unlocked";
+
+ /** WLTMS 单据类型:正式结算单(应付/应收共用,按结算单 settlementType 区分金蝶目标类型) */
+ public static final String SOURCE_SETTLEMENT = "SETTLEMENT";
+
+ /** WLTMS 单据类型:开票申请单 */
+ public static final String SOURCE_INVOICE_APPLY = "INVOICE_APPLY";
+
+ /** WLTMS 单据类型:付款申请单(映射预留,本期不锁) */
+ public static final String SOURCE_PAY_APPLY = "PAY_APPLY";
+
+ /** WLTMS 单据类型:收票单(映射预留,本期不锁) */
+ public static final String SOURCE_RECEIVED_INVOICE = "RECEIVED_INVOICE";
+
+ /** 金蝶目标单据类型:财务应付单(A102-2,应付结算单推送后锁定) */
+ public static final String TARGET_AP_BILL = "ApFin_other_BT_S";
+
+ /** 金蝶目标单据类型:财务应收单(A302-2,应收结算单推送后锁定) */
+ public static final String TARGET_AR_BILL = "arfin_other_BT_S";
+
+ /** 金蝶目标单据类型:开票单(A303-1 开票申请单,按发票张次逐张一行锁记录) */
+ public static final String TARGET_INVOICE_BILL = "cdm_paybill_bank_elect_BT_S";
+
+ /** 金蝶目标单据类型:付款申请单(映射预留,本期不锁) */
+ public static final String TARGET_PAY_APPLY = "ap_payapply_oth_BT_S";
+
+ /** 金蝶目标单据类型:收票单(映射预留,本期不锁) */
+ public static final String TARGET_RECEIVED_INVOICE = "cdm_recbill_bank_BT_S";
+
+ /** 锁定重试上限:补偿任务超过该次数不再自动重试,人工排查(失败原因见 fail_reason) */
+ public static final int RETRY_MAX_COUNT = 5;
+
+ /** 金蝶目标单据类型 -> WLTMS 单据类型反查映射(解锁回调按 targetBillType 分发废除还原用) */
+ public static final Map TARGET_TO_SOURCE_MAP = Map.ofEntries(
+ Map.entry(TARGET_AP_BILL, SOURCE_SETTLEMENT),
+ Map.entry(TARGET_AR_BILL, SOURCE_SETTLEMENT),
+ Map.entry(TARGET_INVOICE_BILL, SOURCE_INVOICE_APPLY),
+ Map.entry(TARGET_PAY_APPLY, SOURCE_PAY_APPLY),
+ Map.entry(TARGET_RECEIVED_INVOICE, SOURCE_RECEIVED_INVOICE)
+ );
+
+ private KingdeeLockConstant() {
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/TransportBusiConstant.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/TransportBusiConstant.java
new file mode 100644
index 00000000..cf5e82b3
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/constant/TransportBusiConstant.java
@@ -0,0 +1,54 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.constant;
+
+/**
+ * 运输业务固定值。
+ *
+ * @author Chill
+ */
+public final class TransportBusiConstant {
+
+ /** 付款类型:结算付款(关联正式结算单的正式付款)。 */
+ public static final String PAYMENT_TYPE_SETTLEMENT = "settlement_payment";
+
+ /** 审批状态:草稿(允许编辑、删除)。 */
+ public static final String DRAFT = "draft";
+
+ /** 审批状态:审批中(审核节点,允许通过/驳回)。 */
+ public static final String REVIEWING = "reviewing";
+
+ /** 审批状态:审批通过。 */
+ public static final String APPROVED = "approved";
+
+ /** 审批状态:已驳回(允许再编辑后重新提交)。 */
+ public static final String RETURNED = "returned";
+
+ /** 审批状态:已作废(终态)。 */
+ public static final String VOIDED = "voided";
+
+ private TransportBusiConstant() {
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BfiCustomerController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BfiCustomerController.java
new file mode 100644
index 00000000..5faea7c8
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BfiCustomerController.java
@@ -0,0 +1,75 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.controller;
+
+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.boot.ctrl.BladeController;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerQueryDTO;
+import org.springblade.thirdparty.bfi.pojo.vo.BfiCustomerVO;
+import org.springblade.thirdparty.bfi.pojo.vo.BfiPageVO;
+import org.springblade.thirdparty.bfi.service.IBfiService;
+import org.springframework.web.bind.annotation.GetMapping;
+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;
+
+/**
+ * 业财融合(BFI)客商查询控制器
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/bfi")
+@Tag(name = "BFI客商", description = "业财融合客商查询接口")
+public class BfiCustomerController extends BladeController {
+
+ private final IBfiService bfiService;
+
+ @GetMapping("/customer/query-by-credit-code")
+ @Operation(summary = "按统一社会信用代码查询金蝶客商", description = "新增客商时查询金蝶是否已有该客商,已审核则回填工商信息")
+ public R queryByCreditCode(
+ @Parameter(description = "统一社会信用代码", required = true) @RequestParam String creditCode) {
+ if (StringUtil.isBlank(creditCode)) {
+ return R.fail("统一社会信用代码不能为空");
+ }
+ BfiCustomerQueryDTO param = new BfiCustomerQueryDTO();
+ param.setPageNo(1);
+ param.setPageSize(1);
+ param.setSocietycreditcode(List.of(creditCode.trim().toUpperCase()));
+ BfiPageVO page = bfiService.queryCustomer(param);
+ List rows = page == null ? null : page.getRows();
+ return R.data(rows == null || rows.isEmpty() ? null : rows.get(0));
+ }
+
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BfiExchangeRateController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BfiExchangeRateController.java
new file mode 100644
index 00000000..ebf87635
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/BfiExchangeRateController.java
@@ -0,0 +1,76 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.thirdparty.bfi.pojo.vo.BfiExchangeRateVO;
+import org.springblade.thirdparty.bfi.service.IBfiService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 业财融合(BFI)汇率查询控制器
+ *
+ * @author Chill
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/bfi")
+@Tag(name = "BFI汇率", description = "业财融合汇率查询接口")
+public class BfiExchangeRateController extends BladeController {
+
+ private final IBfiService bfiService;
+
+ @GetMapping("/exchange-rate")
+ @Operation(summary = "BFI汇率查询", description = "根据结算币种编码和汇率日期查询BFI汇率")
+ public R getBfiExchangeRate(
+ @RequestParam String currencyCode,
+ @RequestParam(required = false) String effectdate) {
+ LocalDate date = StringUtil.isBlank(effectdate) ? LocalDate.now() : LocalDate.parse(effectdate);
+ List rates = bfiService.queryExchangeRate(date, date);
+ if (rates == null || rates.isEmpty()) {
+ return R.fail("未查询到该日期的BFI汇率数据");
+ }
+ BfiExchangeRateVO matched = rates.stream()
+ .filter(r -> currencyCode.equalsIgnoreCase(r.getOrgcurNumber()))
+ .findFirst()
+ .orElse(null);
+ if (matched == null) {
+ return R.fail("未查询到币种 " + currencyCode + " 的汇率数据");
+ }
+ return R.data(matched);
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java
index c111098f..d0998aa3 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchiveController.java
@@ -31,6 +31,7 @@ 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.boot.ctrl.BladeController;
import org.springblade.core.excel.util.ExcelUtil;
import org.springblade.core.mp.support.Condition;
@@ -61,6 +62,7 @@ import java.util.List;
*
* @author Chill
*/
+@Slf4j
@RestController
@AllArgsConstructor
@PreAuth(menu = "customer_archive")
@@ -134,13 +136,21 @@ public class CustomerArchiveController extends BladeController {
}
/**
- * 审核通过
+ * 审核通过(通过后自动推送金蝶,推送失败不影响审批结果)
*/
@PostMapping("/approve")
@ApiOperationSupport(order = 6)
@Operation(summary = "审核通过", description = "传入id")
public R approve(@Parameter(description = "主键", required = true) @RequestParam Long id) {
- return R.status(customerArchiveService.approve(id));
+ boolean result = customerArchiveService.approve(id);
+ if (result) {
+ try {
+ customerArchiveService.syncKingdee(id);
+ } catch (Exception exception) {
+ log.error("客商审批通过后推送金蝶异常,id={}", id, exception);
+ }
+ }
+ return R.status(result);
}
/**
@@ -174,6 +184,16 @@ public class CustomerArchiveController extends BladeController {
return R.status(customerArchiveService.removeDraft(ids));
}
+ /**
+ * 推送金蝶(审批通过后失败重推入口)
+ */
+ @PostMapping("/sync-kingdee")
+ @ApiOperationSupport(order = 12)
+ @Operation(summary = "推送金蝶", description = "传入id,仅审批通过的客商可推送,已推送/金蝶已有自动幂等跳过")
+ public R syncKingdee(@Parameter(description = "主键", required = true) @RequestParam Long id) {
+ return R.data(customerArchiveService.syncKingdee(id));
+ }
+
/**
* 评分明细模板
*/
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java
index 49535ce5..01a53d18 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/FormalSettlementController.java
@@ -22,17 +22,22 @@ import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.transport.listener.KingdeeApprovalSyncListener;
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementStatusRequest;
import org.springblade.transport.pojo.dto.FormalSettlementInvoiceClaimRequest;
+import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO;
import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
+import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO;
import org.springblade.transport.pojo.vo.FormalSettlementVO;
import org.springblade.transport.excel.FormalSettlementExcel;
import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.service.IFormalSettlementService;
+import org.springblade.transport.service.IInvoiceReceiptService;
+import org.springblade.transport.service.IKingdeeBillLockService;
import org.springblade.transport.service.IPreSettlementService;
import org.springblade.transport.service.IReceiptFlowService;
import org.springframework.web.bind.annotation.GetMapping;
@@ -59,8 +64,10 @@ import java.math.RoundingMode;
@Tag(name = "正式结算单", description = "正式结算单管理")
public class FormalSettlementController extends BladeController {
private final IFormalSettlementService formalSettlementService;
+ private final IKingdeeBillLockService kingdeeBillLockService;
private final IPreSettlementService preSettlementService;
private final IReceiptFlowService receiptFlowService;
+ private final IInvoiceReceiptService invoiceReceiptService;
@GetMapping("/list")
@ApiOperationSupport(order = 1)
@@ -190,7 +197,19 @@ public class FormalSettlementController extends BladeController {
@PostMapping("/approve")
@ApiOperationSupport(order = 11)
@Operation(summary = "审批通过")
- public R approve(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.approve(request); return R.success("审批通过"); }
+ public R approve(@RequestBody FormalSettlementStatusRequest request) {
+ formalSettlementService.approve(request);
+ return approveResponse();
+ }
+
+ /** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
+ private R approveResponse() {
+ String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
+ if (syncFailure != null) {
+ return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
+ }
+ return R.success("审批通过");
+ }
@PostMapping("/return")
@ApiOperationSupport(order = 12)
@@ -204,11 +223,23 @@ public class FormalSettlementController extends BladeController {
@PostMapping("/sync-kingdee")
@ApiOperationSupport(order = 14)
- @Operation(summary = "推送金蝶应付单")
+ @Operation(summary = "推送金蝶(应付单/应收单)")
public R syncKingdee(@RequestParam Long id) { return R.data(formalSettlementService.syncKingdee(id)); }
- @GetMapping("/detail-fees")
+ @PostMapping("/sync-kingdee-ar-result")
@ApiOperationSupport(order = 15)
+ @Operation(summary = "手动勾稽金蝶应收单(A302-4 回写纠偏)")
+ public R syncKingdeeArResult() { return R.data(formalSettlementService.syncKingdeeArResult()); }
+
+ @PostMapping("/retry-kingdee-bill-lock")
+ @ApiOperationSupport(order = 17)
+ @Operation(summary = "手动补偿金蝶单据锁定(扫锁定失败锁记录重调lockBusiData,应付/应收/开票通用)")
+ public R retryKingdeeBillLock() {
+ return R.data(kingdeeBillLockService.retryFailedLocks());
+ }
+
+ @GetMapping("/detail-fees")
+ @ApiOperationSupport(order = 16)
@Operation(summary = "正式结算货物费用快照")
public R> detailFees(@RequestParam Long detailId) {
return R.data(formalSettlementService.detailFees(detailId));
@@ -250,4 +281,11 @@ public class FormalSettlementController extends BladeController {
public R>> receiptClaims(@RequestParam Long formalSettlementId) {
return R.data(receiptFlowService.settlementClaims(formalSettlementId));
}
+
+ @GetMapping("/invoice-pool")
+ @ApiOperationSupport(order = 21)
+ @Operation(summary = "金蝶票据池镜像分页查询(认领查询)")
+ public R> invoicePool(KingdeeInvoicePoolQueryDTO query) {
+ return R.data(invoiceReceiptService.invoicePoolPage(query));
+ }
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceApplicationController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceApplicationController.java
index c3ac9cc3..6d2ab648 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceApplicationController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceApplicationController.java
@@ -35,6 +35,7 @@ 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.listener.KingdeeApprovalSyncListener;
import org.springblade.transport.pojo.dto.InvoiceApplicationSaveRequest;
import org.springblade.transport.pojo.dto.InvoiceApplicationStatusRequest;
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
@@ -131,6 +132,15 @@ public class InvoiceApplicationController extends BladeController {
@Operation(summary = "审批通过开票申请")
public R approve(@RequestBody InvoiceApplicationStatusRequest request) {
invoiceApplicationService.approve(request);
+ return approveResponse();
+ }
+
+ /** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
+ private R approveResponse() {
+ String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
+ if (syncFailure != null) {
+ return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
+ }
return R.success("审批通过");
}
@@ -156,4 +166,11 @@ public class InvoiceApplicationController extends BladeController {
public R syncKingdee(@RequestParam Long id) {
return R.data(invoiceApplicationService.syncKingdee(id));
}
+
+ @PostMapping("/sync-kingdee-invoice-result")
+ @ApiOperationSupport(order = 13)
+ @Operation(summary = "手动回写金蝶开票结果(A303-5)")
+ public R syncKingdeeInvoiceResult() {
+ return R.data(invoiceApplicationService.syncKingdeeInvoiceResult());
+ }
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java
index 1a01d3c0..59eb30b6 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/InvoiceReceiptController.java
@@ -29,16 +29,19 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
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.listener.KingdeeApprovalSyncListener;
import org.springblade.transport.pojo.dto.InvoiceReceiptSaveRequest;
import org.springblade.transport.pojo.dto.InvoiceReceiptStatusRequest;
-import org.springblade.transport.pojo.entity.KingdeeInvoicePool;
+import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO;
import org.springblade.transport.pojo.vo.InvoiceReceiptVO;
+import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO;
import org.springblade.transport.service.IInvoiceReceiptService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@@ -80,9 +83,24 @@ public class InvoiceReceiptController extends BladeController {
@GetMapping("/invoice-pool")
@ApiOperationSupport(order = 3)
- @Operation(summary = "查询金蝶进项发票票据池")
- public R> invoicePool(@RequestParam(required = false) String keyword) {
- return R.data(invoiceReceiptService.invoicePool(keyword));
+ @Operation(summary = "金蝶票据池镜像分页查询(含认领/登记标记)")
+ public R> invoicePool(KingdeeInvoicePoolQueryDTO query) {
+ return R.data(invoiceReceiptService.invoicePoolPage(query));
+ }
+
+ @PostMapping("/sync-invoice-pool")
+ @ApiOperationSupport(order = 13)
+ @Operation(summary = "手动触发金蝶全票池发票同步(窗口拉取+镜像全列upsert)")
+ public R syncInvoicePool() {
+ return R.data(invoiceReceiptService.syncKingdeeInvoicePool());
+ }
+
+ @GetMapping("/invoice-pool/attachment/download")
+ @ApiOperationSupport(order = 17)
+ @Operation(summary = "金蝶票池发票文件下载(A801-2 流式回源)",
+ description = "按发票号定位金蝶侧文件:本地镜像优先,镜像未落库实时回源 A801-1;成功返回文件流,失败返回 JSON 错误")
+ public void downloadPoolAttachment(@RequestParam String invoiceNo, HttpServletResponse response) {
+ invoiceReceiptService.downloadPoolAttachment(invoiceNo, response);
}
@GetMapping("/settlement-candidates")
@@ -129,6 +147,15 @@ public class InvoiceReceiptController extends BladeController {
@Operation(summary = "审批通过收票登记")
public R approve(@RequestBody InvoiceReceiptStatusRequest request) {
invoiceReceiptService.approve(request);
+ return approveResponse();
+ }
+
+ /** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
+ private R approveResponse() {
+ String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
+ if (syncFailure != null) {
+ return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
+ }
return R.success("审批通过");
}
@@ -148,10 +175,17 @@ public class InvoiceReceiptController extends BladeController {
return R.success("作废成功");
}
- @PostMapping("/sync-kingdee")
- @ApiOperationSupport(order = 12)
- @Operation(summary = "同步金蝶发票状态")
- public R syncKingdee(@RequestParam Long id) {
- return R.data(invoiceReceiptService.syncKingdee(id));
+ @PostMapping("/push-kingdee")
+ @ApiOperationSupport(order = 15)
+ @Operation(summary = "批量推送金蝶收票单(A303-2 收票单保存 + A302-6 收票指定应付)")
+ public R pushKingdee(@RequestBody List ids) {
+ return R.data(invoiceReceiptService.pushKingdeeReceivedInvoice(ids));
+ }
+
+ @PostMapping("/sync-kingdee-result")
+ @ApiOperationSupport(order = 16)
+ @Operation(summary = "手动触发金蝶收票单结果回写(A801-3 异常回收/指定补偿)")
+ public R syncKingdeeResult() {
+ return R.data(invoiceReceiptService.syncKingdeeReceivedInvoiceResult());
}
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/KingdeeBfiPublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/KingdeeBfiPublicController.java
new file mode 100644
index 00000000..ade6c501
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/KingdeeBfiPublicController.java
@@ -0,0 +1,65 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.controller;
+
+import com.alibaba.fastjson2.JSON;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tenant.annotation.TenantIgnore;
+import org.springblade.thirdparty.bfi.pojo.dto.BfiBillLockDTO;
+import org.springblade.thirdparty.bfi.pojo.vo.BfiBillLockResultVO;
+import org.springblade.transport.service.IKingdeeBillLockService;
+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;
+
+/**
+ * 金蝶单据锁定回调 控制器
+ *
+ * unLockNotifyBusiData:金蝶(星瀚)侧解锁/删单后回调 WLTMS 送结果,
+ * 无登录态公开端点(模式同 CustomerArchivePublicController);
+ * operationType=DELETE 时 WLTMS 废除源单并还原关联数据。
+ * 应答与 lockBusiData 出参同构:{updateCount, updateResultList},逐单处理(1-成功,0-未匹配/失败)
+ *
+ * @author Chill
+ */
+@Slf4j
+@RestController
+@AllArgsConstructor
+@TenantIgnore
+// @PreAuth(AuthConstant.PERMIT_ALL)
+@RequestMapping("kingdee-bfi")
+@Tag(name = "金蝶单据锁定回调", description = "金蝶星瀚调用:单据解锁/删单通知")
+public class KingdeeBfiPublicController {
+
+ private final IKingdeeBillLockService kingdeeBillLockService;
+
+ /**
+ * 单据解锁通知(unLockNotifyBusiData):多张单据编号逗号拼接批量回调,逐张处理
+ *
+ * @param param 金蝶回调参数
+ * @return 处理结果,与 lockBusiData 出参同构
+ */
+ @PostMapping("unLockNotifyBusiData")
+ @Operation(summary = "单据解锁通知", description = "金蝶侧解锁/删单后回调;operationType=DELETE 时废除源单并还原关联数据")
+ public BfiBillLockResultVO unLockNotifyBusiData(@RequestBody BfiBillLockDTO param) {
+ log.info("金蝶单据解锁回调:{}", JSON.toJSONString(param));
+ try {
+ return BfiBillLockResultVO.of(kingdeeBillLockService.onUnlockNotify(param));
+ } catch (Exception exception) {
+ log.error("金蝶单据解锁回调处理失败:lockAction={},operationType={},targetBillType={},targetBillNo={}",
+ param == null ? null : param.getLockAction(), param == null ? null : param.getOperationType(),
+ param == null ? null : param.getTargetBillType(), param == null ? null : param.getTargetBillNo(), exception);
+ return BfiBillLockResultVO.of(0);
+ }
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java
index e2c6aafe..92a7f5a2 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/PaymentApplicationController.java
@@ -29,6 +29,7 @@ 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.listener.KingdeeApprovalSyncListener;
import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
@@ -81,7 +82,19 @@ public class PaymentApplicationController extends BladeController {
@PostMapping("/approve")
@ApiOperationSupport(order = 7)
@Operation(summary = "审批通过付款申请")
- public R approve(@RequestBody PaymentApplicationStatusRequest request) { paymentApplicationService.approve(request); return R.success("审批通过"); }
+ public R approve(@RequestBody PaymentApplicationStatusRequest request) {
+ paymentApplicationService.approve(request);
+ return approveResponse();
+ }
+
+ /** 审批接口统一响应:审批已生效,自动推送失败原因以警告文案返回(data=false 供前端区分提示样式) */
+ private R approveResponse() {
+ String syncFailure = KingdeeApprovalSyncListener.pollSyncFailure();
+ if (syncFailure != null) {
+ return R.data(Boolean.FALSE, "审批通过,但自动同步金蝶失败:" + syncFailure);
+ }
+ return R.success("审批通过");
+ }
@PostMapping("/return")
@ApiOperationSupport(order = 8)
@Operation(summary = "驳回付款申请")
@@ -100,4 +113,8 @@ public class PaymentApplicationController extends BladeController {
public R> syncKingdeeBatch(@RequestBody List ids) {
return R.data(paymentApplicationService.syncKingdeeBatch(ids));
}
+ @PostMapping("/sync-kingdee-result")
+ @ApiOperationSupport(order = 12)
+ @Operation(summary = "手动回写金蝶付款结果(A103-2/A103-4)")
+ public R syncKingdeeResult() { return R.data(paymentApplicationService.syncKingdeePaymentResult()); }
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java
index 7cfd3f85..3db05ad5 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptClaimRecordController.java
@@ -84,7 +84,15 @@ public class ReceiptClaimRecordController extends BladeController {
@PostMapping("/void")
@ApiOperationSupport(order = 4)
@Operation(summary = "作废认领记录并生成金蝶认领冲单")
- public R voidClaim(@RequestParam Long id) {
- return R.data(receiptClaimRecordService.voidClaim(id));
+ public R voidClaim(@RequestParam Long id) {
+ receiptClaimRecordService.voidClaim(id);
+ return R.success("作废成功");
+ }
+
+ @PostMapping("/query-kingdee-result")
+ @ApiOperationSupport(order = 5)
+ @Operation(summary = "查询金蝶认领结果(A301-1 收款单查询回写)")
+ public R queryKingdeeResult() {
+ return R.data(receiptClaimRecordService.refreshKingdeeResult());
}
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java
index 4aa3f02b..1bbee361 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceiptFlowController.java
@@ -35,8 +35,8 @@ 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.pojo.dto.ReceiptClaimPushResult;
import org.springblade.transport.pojo.dto.ReceiptClaimRequest;
-import org.springblade.transport.pojo.dto.ReceiptFlowSyncRequest;
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
import org.springblade.transport.service.IReceiptFlowService;
import org.springframework.web.bind.annotation.GetMapping;
@@ -95,8 +95,16 @@ public class ReceiptFlowController extends BladeController {
@PostMapping("/sync")
@ApiOperationSupport(order = 5)
- @Operation(summary = "手动同步金蝶收款流水")
- public R sync(@RequestBody(required = false) ReceiptFlowSyncRequest request) {
- return R.data(receiptFlowService.sync(request));
+ @Operation(summary = "手动同步金蝶收款流水(A301-2 收款认领公告查询)")
+ public R sync() {
+ return R.data(receiptFlowService.sync());
+ }
+
+ @PostMapping("/push-kingdee")
+ @ApiOperationSupport(order = 6)
+ @Operation(summary = "推送金蝶收款认领(A301-3 收款认领结果推送,仅认领完成流水可推送)")
+ public R pushKingdee(@RequestParam Long flowId) {
+ ReceiptClaimPushResult result = receiptFlowService.pushClaimToKingdee(flowId);
+ return result.isSuccess() ? R.data(result) : R.fail(result.getMessage());
}
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java
index 9790e3ee..c8cd804a 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ReceivablePayableDetailController.java
@@ -133,13 +133,15 @@ public class ReceivablePayableDetailController extends BladeController {
@ApiOperationSupport(order = 8)
@Operation(summary = "转结算候选明细")
public R>> transferCandidates(Query query,
+ @RequestParam(required = false) String documentNo,
+ @RequestParam(required = false) String contractNo,
@RequestParam(required = false) String contractName,
@RequestParam(required = false) String batchNo,
@RequestParam(required = false) String generateStartDate,
@RequestParam(required = false) String generateEndDate,
@RequestParam(required = false) String settlementBillType,
@RequestParam(required = false) String settlementType) {
- return R.data(detailService.transferCandidates(Condition.getPage(query), contractName, batchNo,
+ return R.data(detailService.transferCandidates(Condition.getPage(query), documentNo, contractNo, contractName, batchNo,
generateStartDate, generateEndDate, settlementBillType, settlementType));
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/event/KingdeeBillApprovedEvent.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/event/KingdeeBillApprovedEvent.java
new file mode 100644
index 00000000..9652742f
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/event/KingdeeBillApprovedEvent.java
@@ -0,0 +1,44 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
+ * Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.event;
+
+/**
+ * 单据审批通过事件:审批事务提交后由 {@code KingdeeApprovalSyncListener} 消费,
+ * 自动推送对应单据至金蝶(星瀚),省掉页面手动点「同步金蝶」。
+ *
+ * bizType 复用 MkProcessBizHandlers 既有取值;只在审批状态真正变为 approved 的那次发布,
+ * 重复回调被 approve 状态校验拒绝,天然不重复推送。
+ *
+ * @author Chill
+ */
+public record KingdeeBillApprovedEvent(String bizType, Long id) {
+
+ /** 正式结算单(应付 A102-2 / 应收 A302-2) */
+ public static final String BIZ_FORMAL_SETTLEMENT = "formal-settlement";
+
+ /** 付款申请(A103-1 正式 / A103-3 预付) */
+ public static final String BIZ_PAYMENT_APPLICATION = "payment-application";
+
+ /** 开票申请(A303-1) */
+ public static final String BIZ_INVOICE_APPLICATION = "invoice-application";
+
+ /** 收票登记(A303-2 + A302-6) */
+ public static final String BIZ_INVOICE_RECEIPT = "invoice-receipt";
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java
index 6632ef44..ebe4eec2 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java
@@ -111,4 +111,7 @@ public class CustomerArchiveExcel implements Serializable {
@ExcelProperty("审核通过时间")
private LocalDateTime approvedTime;
+ @ExcelProperty("金蝶同步状态")
+ private String kingdeeStatusName;
+
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/KingdeeApprovalSyncListener.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/KingdeeApprovalSyncListener.java
new file mode 100644
index 00000000..185a0c56
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/listener/KingdeeApprovalSyncListener.java
@@ -0,0 +1,140 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
+ * Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.listener;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.transport.config.BfiProperties;
+import org.springblade.transport.event.KingdeeBillApprovedEvent;
+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.stereotype.Component;
+import org.springframework.transaction.event.TransactionPhase;
+import org.springframework.transaction.event.TransactionalEventListener;
+
+import java.util.List;
+
+/**
+ * 单据审批通过自动同步金蝶监听器:审批事务提交(AFTER_COMMIT)后复用手动按钮同一 Service 方法推送,
+ * 与客商档案审批后自动推送同线程模式。
+ *
+ * 不变式:审批结果不受推送影响——监听器整体 try/catch 吞异常,推送异常只落 failed + 日志;
+ * 失败单据由 {@code KingdeeAutoSyncRetryTask} 定时重推、手动按钮兜底。
+ *
+ * 推送失败原因通过 {@link #pollSyncFailure()} 传递给审批接口响应:AFTER_COMMIT 监听与 approve
+ * 同线程同请求,Controller 在 approve 返回后取走原因拼进提示文案;OA 回调路径无人取走,
+ * 由监听入口清空兜底,不会串单。
+ *
+ * ponytail: 同步推送占用审批请求线程,若 BFI 网关大面积变慢导致审批接口可感知变慢,
+ * 再把路由调用提交到守护线程池(MkProcessMessageService 已有同款裸 ScheduledExecutorService 模式)。
+ *
+ * 域开关(Nacos thirdParty.bfi.autoSync.*)默认全部关闭,与既有定时任务开关风格一致;
+ * 关闭某域 = 该域回到现状(审批不自动推送、手动按钮照用)。
+ *
+ * @author Chill
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class KingdeeApprovalSyncListener {
+
+ private final IFormalSettlementService formalSettlementService;
+ private final IPaymentApplicationService paymentApplicationService;
+ private final IInvoiceApplicationService invoiceApplicationService;
+ private final IInvoiceReceiptService invoiceReceiptService;
+ private final BfiProperties bfiProperties;
+
+ /** 当前审批请求线程的自动推送失败原因,供 Controller 取走提示前端(见类注释线程说明) */
+ private static final ThreadLocal SYNC_FAILURE = new ThreadLocal<>();
+
+ /**
+ * 取走并清除当前审批请求的自动推送失败原因;null 表示推送成功或未触发推送。
+ * 须在 service.approve 返回后立即调用(监听器在审批事务提交时同步执行完毕)。
+ */
+ public static String pollSyncFailure() {
+ String failure = SYNC_FAILURE.get();
+ SYNC_FAILURE.remove();
+ return failure;
+ }
+
+ /**
+ * 事务提交后同步推送。fallbackExecution=true 兜底:approve 未开启事务时事件不再被默认丢弃。
+ *
+ * @param event 审批通过事件(bizType + 单据id)
+ */
+ @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
+ public void onBillApproved(KingdeeBillApprovedEvent event) {
+ SYNC_FAILURE.remove();
+ try {
+ switch (event.bizType()) {
+ case KingdeeBillApprovedEvent.BIZ_FORMAL_SETTLEMENT -> pushSettlement(event.id());
+ case KingdeeBillApprovedEvent.BIZ_PAYMENT_APPLICATION -> pushPayment(event.id());
+ case KingdeeBillApprovedEvent.BIZ_INVOICE_APPLICATION -> pushInvoiceApply(event.id());
+ case KingdeeBillApprovedEvent.BIZ_INVOICE_RECEIPT -> pushReceipt(event.id());
+ default -> log.warn("审批通过自动同步金蝶跳过:未知业务类型 bizType={} id={}", event.bizType(), event.id());
+ }
+ } catch (Exception exception) {
+ // 推送失败只记日志(失败状态已在推送方法内独立事务落库),不影响审批结果,交给重推任务与手动按钮兜底;
+ // 原因暂存线程变量,由审批 Controller 取走提示前端
+ log.error("审批通过自动同步金蝶失败 bizType={} id={}", event.bizType(), event.id(), exception);
+ String reason = exception.getMessage() == null ? "金蝶接口调用异常" : exception.getMessage();
+ // 手动按钮同文案已带"推送金蝶失败:"前缀,拼提示时去掉避免重复
+ SYNC_FAILURE.set(reason.startsWith("推送金蝶失败:") ? reason.substring("推送金蝶失败:".length()) : reason);
+ }
+ }
+
+ private void pushSettlement(Long id) {
+ if (!bfiProperties.getAutoSync().isSettlement()) {
+ return;
+ }
+ log.info("正式结算单[{}]审批通过自动同步金蝶开始", id);
+ String billNo = formalSettlementService.syncKingdee(id);
+ log.info("正式结算单[{}]审批通过自动同步金蝶完成:billNo={}", id, billNo);
+ }
+
+ private void pushPayment(Long id) {
+ if (!bfiProperties.getAutoSync().isPayment()) {
+ return;
+ }
+ log.info("付款申请[{}]审批通过自动同步金蝶开始", id);
+ String kingdeeBillId = paymentApplicationService.syncKingdee(id);
+ log.info("付款申请[{}]审批通过自动同步金蝶完成:kingdeeBillId={}", id, kingdeeBillId);
+ }
+
+ private void pushInvoiceApply(Long id) {
+ if (!bfiProperties.getAutoSync().isInvoiceApply()) {
+ return;
+ }
+ log.info("开票申请[{}]审批通过自动同步金蝶开始", id);
+ String billNo = invoiceApplicationService.syncKingdee(id);
+ log.info("开票申请[{}]审批通过自动同步金蝶完成:billNo={}", id, billNo);
+ }
+
+ private void pushReceipt(Long id) {
+ if (!bfiProperties.getAutoSync().isReceipt()) {
+ return;
+ }
+ log.info("收票登记[{}]审批通过自动推送金蝶收票单开始", id);
+ String summary = invoiceReceiptService.pushKingdeeReceivedInvoice(List.of(id), true);
+ log.info("收票登记[{}]审批通过自动推送金蝶收票单完成:{}", id, summary);
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml
index 6da293f2..31f15b31 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml
@@ -48,6 +48,9 @@
+
+
+
@@ -96,6 +99,10 @@
current_node,
current_processor,
approved_time,
+ kingdee_status,
+ kingdee_fail_reason,
+ kingdee_sync_time
+ approved_time,
(
SELECT s.review_status
FROM blade_customer_credit_score s
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/InvoiceApplicationInvoiceMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/InvoiceApplicationInvoiceMapper.java
new file mode 100644
index 00000000..b418deef
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/InvoiceApplicationInvoiceMapper.java
@@ -0,0 +1,32 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
+ * Copyright of this software remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
+
+/**
+ * 开票申请金蝶发票结果 Mapper
+ *
+ * @author Chill
+ */
+public interface InvoiceApplicationInvoiceMapper extends BaseMapper {
+
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeAttachmentSyncMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeAttachmentSyncMapper.java
new file mode 100644
index 00000000..a34b3f36
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeAttachmentSyncMapper.java
@@ -0,0 +1,39 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * 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.KingdeeAttachmentSync;
+
+/**
+ * 金蝶附件同步 Mapper
+ *
+ * @author Chill
+ */
+@Mapper
+public interface KingdeeAttachmentSyncMapper extends BaseMapper {
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeBillLockMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeBillLockMapper.java
new file mode 100644
index 00000000..89174b12
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeBillLockMapper.java
@@ -0,0 +1,18 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 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.KingdeeBillLock;
+
+/** 金蝶单据锁定记录 Mapper @author Chill */
+@Mapper
+public interface KingdeeBillLockMapper extends BaseMapper {
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java
index d299c635..29ddf75d 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.java
@@ -26,8 +26,12 @@
package org.springblade.transport.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO;
import org.springblade.transport.pojo.entity.KingdeeInvoicePool;
+import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO;
/**
* 金蝶进项发票票据池 Mapper 接口
@@ -36,4 +40,15 @@ import org.springblade.transport.pojo.entity.KingdeeInvoicePool;
*/
@Mapper
public interface KingdeeInvoicePoolMapper extends BaseMapper {
+
+ /**
+ * 金蝶票据池镜像统一分页查询:仅出可用票(剩余可用金额>0 且未使用),认领/登记标记由 EXISTS 子查询直出
+ *
+ * @param page 分页参数
+ * @param query 统一查询条件(已在上层完成 trim 与日期格式校验)
+ * @param tenantId 当前租户
+ * @return 统一视图分页
+ */
+ IPage selectInvoicePoolPage(IPage page,
+ @Param("query") KingdeeInvoicePoolQueryDTO query, @Param("tenantId") String tenantId);
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.xml
new file mode 100644
index 00000000..b25b0ddc
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/KingdeeInvoicePoolMapper.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+ EXISTS (SELECT 1 FROM blade_formal_settlement_invoice c
+ WHERE c.is_deleted = 0 AND c.tenant_id = p.tenant_id AND c.invoice_no = p.invoice_no
+ AND EXISTS (SELECT 1 FROM blade_formal_settlement s
+ WHERE s.id = c.formal_settlement_id AND s.is_deleted = 0
+ AND s.approval_status != 'voided'))
+
+
+
+
+ EXISTS (SELECT 1 FROM blade_invoice_receipt r
+ WHERE r.is_deleted = 0 AND r.tenant_id = p.tenant_id
+ AND r.approval_status != 'voided' AND r.invoice_no = p.invoice_no)
+
+
+
+
+ p.id, p.invoice_no, p.serial_no,
+ DATE_FORMAT(p.invoice_date, '%Y-%m-%d') AS invoice_date,
+ p.invoice_type, p.tax_rate, p.invoice_amount, p.tax_amount,
+ p.receiver_name, p.buyer_tax_no, p.issuer_name, p.saler_tax_no,
+ p.bank_name, p.bank_account, p.issuing_bank, p.phone,
+ p.customer_emails, p.department_emails,
+ p.kingdee_bill_no, p.kingdee_status, p.check_status, p.attachments_json,
+ p.invoice_amount AS available_invoice_amount,
+ p.invoice_amount AS matched_amount,
+ p.available_amount AS available_tax_amount,
+ AS claimed,
+ AS registered
+
+
+
+
+ AND p.available_amount > 0 AND p.amount_use_status = '0'
+
+
+
+ SELECT
+
+ FROM blade_kingdee_invoice_pool p
+ WHERE p.is_deleted = 0 AND p.status = 1 AND p.tenant_id = #{tenantId}
+
+
+ AND p.invoice_no LIKE CONCAT('%', #{query.invoiceNo}, '%')
+
+
+ AND p.issuer_name LIKE CONCAT('%', #{query.salerName}, '%')
+
+
+ AND p.receiver_name LIKE CONCAT('%', #{query.buyerName}, '%')
+
+
+ AND p.invoice_type_code = #{query.invoiceType}
+
+
+ AND p.saler_tax_no = #{query.salerTaxNo}
+
+
+ AND p.buyer_tax_no = #{query.buyerTaxNo}
+
+
+ AND p.invoice_date >= #{query.invoiceDateStart}
+
+
+ AND p.invoice_date <= #{query.invoiceDateEnd}
+
+
+ AND
+
+
+ AND NOT
+
+
+ AND
+
+
+ AND NOT
+
+ ORDER BY p.source_updated_time DESC, p.create_time DESC
+
+
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml
index 07a25531..b1337bbe 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/ReceiptClaimMapper.xml
@@ -36,6 +36,7 @@
f.summary,
f.transaction_time,
f.detail_serial_no,
+ f.push_status,
(SELECT GROUP_CONCAT(rcs.formal_settlement_no ORDER BY rcs.id SEPARATOR ',')
FROM blade_receipt_claim_settlement rcs
WHERE rcs.receipt_claim_id = c.id AND rcs.is_deleted = 0) AS associated_settlement_nos
@@ -91,6 +92,9 @@
AND c.kingdee_bill_status = #{query.kingdeeBillStatus}
+
+ AND f.push_status = #{query.pushStatus}
+
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java
index 8e25176e..918749f6 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mk/MkProcessBizHandlers.java
@@ -4,8 +4,10 @@ import lombok.RequiredArgsConstructor;
import org.springblade.core.tenant.annotation.TenantIgnore;
import org.springblade.core.tool.utils.Func;
import org.springblade.transport.pojo.dto.BillPaymentStatusRequest;
+import org.springblade.transport.pojo.dto.FormalSettlementStatusRequest;
import org.springblade.transport.pojo.dto.InvoiceApplicationStatusRequest;
import org.springblade.transport.pojo.dto.InvoiceReceiptStatusRequest;
+import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
import org.springblade.transport.pojo.entity.BillPayment;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.FormalSettlement;
@@ -311,11 +313,18 @@ public final class MkProcessBizHandlers {
if (entity == null || "approved".equals(entity.getApprovalStatus())) {
return entity != null;
}
- entity.setApprovalStatus("approved");
- entity.setCurrentNode("审核通过");
- entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
- entity.setApprovedTime(LocalDateTime.now());
- return service.updateById(entity);
+ // 统一走 service.approve:与手动审批同一入口(补变更记录),审批事务提交后自动触发金蝶推送事件;
+ // 仅审批中单据允许通过,重复回调被状态校验拒绝,不重复推送
+ FormalSettlementStatusRequest request = new FormalSettlementStatusRequest();
+ request.setId(id);
+ service.approve(request);
+ FormalSettlement updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
}
@Override
@@ -374,10 +383,18 @@ public final class MkProcessBizHandlers {
if (entity == null || "approved".equals(entity.getApprovalStatus())) {
return entity != null;
}
- entity.setApprovalStatus("approved");
- entity.setCurrentNode("审核通过");
- entity.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
- return service.updateById(entity);
+ // 统一走 service.approve:与手动审批同一入口(补票据额度联动 useBillBalance),审批事务提交后自动触发金蝶推送事件;
+ // 仅审批中单据允许通过,重复回调被状态校验拒绝,不重复推送
+ PaymentApplicationStatusRequest request = new PaymentApplicationStatusRequest();
+ request.setId(id);
+ service.approve(request);
+ PaymentApplication updated = service.getById(id);
+ if (updated != null) {
+ updated.setCurrentNode("审核通过");
+ updated.setCurrentProcessor(MkProcessNodeHelper.text(processorName, "系统"));
+ service.updateById(updated);
+ }
+ return true;
}
@Override
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java
index b822a2f0..459e35cd 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java
@@ -188,6 +188,29 @@ public interface ICustomerArchiveService extends BaseService {
*/
boolean removeDraft(String ids);
+ /**
+ * 推送金蝶(A902-1 复查 + A902-2 保存),幂等:已推送/金蝶已有直接返回
+ *
+ * @param id 主键
+ * @return 更新后的客商档案
+ */
+ CustomerArchive syncKingdee(Long id);
+
+ /**
+ * 定时同步金蝶客商数据:待推送客商自动推送 + 已同步客商使用状态回写
+ *
+ * @return 同步汇总说明
+ */
+ String syncKingdeeCustomerData();
+
+ /**
+ * 金蝶侧停用联动:停用客商档案并记录变更(仅当前启用的客商生效)
+ *
+ * @param id 主键
+ * @return 是否执行停用
+ */
+ boolean disableCustomerFromKingdee(Long id);
+
/**
* 基于启用评分量化表生成评分记录
*
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java
index cd8557b7..3393983e 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IFormalSettlementService.java
@@ -18,6 +18,7 @@ import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
+import java.util.Collection;
import java.util.List;
import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest;
@@ -40,9 +41,54 @@ public interface IFormalSettlementService extends BaseService
void returnBill(FormalSettlementStatusRequest request);
void voidBill(FormalSettlementStatusRequest request);
String syncKingdee(Long id);
+
+ /**
+ * 金蝶删单回调废除(unLockNotifyBusiData DELETE 分发):作废结算单、同步信息还原推送前状态
+ * (kingdeeSyncStatus→unsynced,清单据编号/星瀚内码/同步时间),操作人取金蝶回调报文
+ *
+ * @param id 正式结算单id
+ * @param operator 金蝶侧操作人
+ */
+ void voidByKingdee(Long id, String operator);
+
+ /**
+ * 单据级事务执行金蝶推送(由 syncKingdee 经自代理调用):
+ * A102-2/A302-2 保存并回写结算单单据编号与星瀚内码(kingdeeBillId)
+ *
+ * @param id 正式结算单id
+ * @return 金蝶单据编号
+ */
+ String doSyncKingdee(Long id);
+
+ /**
+ * 独立事务落"生成失败"状态并记失败原因(在推送事务回滚、行锁释放后执行,与付款/收票口径统一);
+ * 仅更新审批通过且未同步的单据,未审批/已同步/已删除单据自动跳过,保持原状态
+ *
+ * @param id 正式结算单id
+ * @param reason 失败原因(写变更记录留档)
+ */
+ void markKingdeeSyncFailed(Long id, String reason);
+
+ /**
+ * 重推金蝶生成失败(kingdeeSyncStatus=failed 且审批通过)的正式结算单:逐单独立成败,失败不中断。
+ * 只扫 failed 不扫 unsynced——存量"审批通过但从未推送"的历史单不自动补推,仍走手动按钮
+ *
+ * @return 执行摘要
+ */
+ String retryKingdeeFailed();
+
+ String syncKingdeeArResult();
List detailFees(Long detailId);
void adjustDetail(PreSettlementDetailAdjustRequest request);
void refreshPaymentSummary(Long settlementId);
+
+ /**
+ * 批量刷新收款汇总:固定数次查询完成 N 张结算单的重算,避免逐单刷新的 N+1
+ *
+ * @param settlementIds 结算单id集合
+ */
+ void refreshPaymentSummaries(Collection settlementIds);
+
void refreshPaymentSummariesForPreSettlement(Long preSettlementId);
String applyPayment(FormalSettlementPaymentRequest request);
List applyPayments(FormalSettlementBatchPaymentRequest request);
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java
index 93fe9fa5..aa9faf18 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IInvoiceApplicationService.java
@@ -55,4 +55,41 @@ public interface IInvoiceApplicationService extends BaseService {
InvoiceReceiptVO detail(Long id);
- List invoicePool(String keyword);
+ /**
+ * 金蝶票据池镜像统一查询(本地真分页,收票登记选票与正式结算单认领共用)。
+ * 仅出可用票(剩余可用金额>0 且金额使用状态未用),认领/登记三态过滤在 SQL 内直出;
+ * 票号精确查本地未命中时窄查一次 A801-1 回源入库后重查,兜底增量窗口缝隙内的当日新票。
+ *
+ * @param query 统一查询条件
+ * @return 镜像统一视图分页
+ */
+ IPage invoicePoolPage(KingdeeInvoicePoolQueryDTO query);
+
+ /**
+ * 金蝶全票池定时同步(瘦身版):滚动增量窗口分页拉取 A801-1 + 镜像批量全列 upsert。
+ * 无绑定回填/漏绑补偿/白名单(保存即写存根绑定,登记侧无需定时绑定);
+ * single-flight 锁防多副本并发,手动触发与定时任务共用。
+ *
+ * @return 执行摘要
+ */
+ String syncKingdeeInvoicePool();
+
+ /**
+ * 结算单认领确认闸门:按票号集合逐张重查 A801-1 并按选票同口径复核可用性,
+ * 选中到确认之间票被认领/使用/红冲在此拦截(与收票保存重查同闸门)
+ *
+ * @param invoiceNos 待认领发票号码集合
+ */
+ void assertInvoicesClaimable(Collection invoiceNos);
+
+ /**
+ * 金蝶票池发票文件下载(A801-2 流式回源,前端查看/下载代理端点)。
+ * 按发票号定位金蝶侧文件:本地镜像优先(票号同租户唯一,租户隔离由 MP 插件保证),
+ * 镜像未落库或无文件时实时回源 A801-1 取 downloadUrl;经 A801-2 下载后流式透传浏览器,
+ * 不落盘不进堆。写流前的业务失败以 JSON(R.fail)写入响应,前端按 Content-Type 识别提示。
+ *
+ * @param invoiceNo 发票号码
+ * @param response 响应对象(文件流写出口)
+ */
+ void downloadPoolAttachment(String invoiceNo, HttpServletResponse response);
List> settlementCandidates(String keyword, Long receiptId);
@@ -65,5 +104,40 @@ public interface IInvoiceReceiptService extends BaseService {
void voidBill(InvoiceReceiptStatusRequest request);
- String syncKingdee(Long id);
+ /**
+ * 批量推送金蝶收票单(图05 环节②③:A303-2 收票单批量保存 + A302-6 收票指定应付)。
+ * 逐单独立成败:前置校验(审批通过、已绑定镜像、查验通过、恰好分摊一张应付结算单)→
+ * A102-4 预检财务应付单已生成 → A801-3 幂等预检(命中跳过保存续推指定)→ A303-2 保存 →
+ * 立即 A302-6 指定应付;成功置 assigned 并锁单,失败置 failed 提示人工处理后重推。
+ *
+ * @param ids 收票登记id列表
+ * @return 执行摘要(逐单失败原因)
+ */
+ String pushKingdeeReceivedInvoice(List ids);
+
+ /**
+ * 自动推送路径批量推送金蝶收票单(审批通过事件 / 定时重推专用):
+ * 前置拦截(未绑定镜像/查验未通过/前置应付单未生成等)一并置 failed + 原因,纳入定时重推循环
+ *
+ * @param ids 收票登记id列表
+ * @param autoSync 自动推送标记(前置拦截置 failed)
+ * @return 执行摘要(逐单失败原因)
+ */
+ String pushKingdeeReceivedInvoice(List ids, boolean autoSync);
+
+ /**
+ * 重推金蝶推送失败(kingdeePushStatus=failed 且审批通过)的收票登记:逐单独立成败,失败不中断。
+ * 只扫 failed 不扫 unsynced——存量"审批通过但从未推送"的历史单不自动补推,仍走手动按钮
+ *
+ * @return 执行摘要
+ */
+ String retryKingdeeFailed();
+
+ /**
+ * 定时回写金蝶收票单结果(A801-3):金蝶侧删除/红冲/作废 → 回收为 failed 提示人工(不自动作废登记);
+ * pushed(保存成功后中断)→ 指定补偿;assigned 仅补登缺失的收票单号
+ *
+ * @return 执行摘要
+ */
+ String syncKingdeeReceivedInvoiceResult();
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IKingdeeAttachmentSyncService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IKingdeeAttachmentSyncService.java
new file mode 100644
index 00000000..758f42ba
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IKingdeeAttachmentSyncService.java
@@ -0,0 +1,37 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.transport.pojo.entity.KingdeeAttachmentSync;
+
+/**
+ * 金蝶附件同步服务
+ *
+ * @author Chill
+ */
+public interface IKingdeeAttachmentSyncService extends BaseService {
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IKingdeeBillLockService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IKingdeeBillLockService.java
new file mode 100644
index 00000000..7af9adfc
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IKingdeeBillLockService.java
@@ -0,0 +1,78 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.thirdparty.bfi.pojo.dto.BfiBillLockDTO;
+import org.springblade.transport.pojo.entity.KingdeeBillLock;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 金蝶单据锁定记录服务
+ *
+ * @author Chill
+ */
+public interface IKingdeeBillLockService extends IService {
+
+ /**
+ * 推送金蝶成功后锁单(由各推送链路在主单事务提交后调用):
+ * 每张金蝶目标单一行锁记录(先落 failed 再调 lockBusiData,成功翻 locked);
+ * 锁单接口 1对1:逐张调用(单次一个源单号对一个金蝶单号),单张成败互不影响;
+ * 已 locked 幂等跳过,failed 原地重锁并重置补偿计数;
+ * 异常独立捕获,不影响主单同步状态
+ *
+ * @param sourceBillType WLTMS 单据类型(SETTLEMENT/INVOICE_APPLY)
+ * @param sourceBillId WLTMS 业务主键
+ * @param sourceBillNo WLTMS 单号
+ * @param targetBillType 金蝶目标单据类型
+ * @param targetBillNos 金蝶单据编号集合(开票申请为多张)
+ * @return 锁定失败的金蝶单号摘要(逗号拼接),全部锁定成功返回 null;调用方可拼入页面提示
+ */
+ String lockAfterPush(String sourceBillType, Long sourceBillId, String sourceBillNo, String targetBillType, List targetBillNos);
+
+ /**
+ * 金蝶解锁回调(unLockNotifyBusiData):校验来源/目标系统与 lockAction,
+ * targetBillNo 支持逗号拼接批量,逐张按 targetBillType+targetBillNo 定位最新 locked 锁记录;
+ * operationType=DELETE 时按单据类型分发废除源单并还原关联数据
+ *
+ * @param param 金蝶回调参数
+ * @return 处理单据数(应答 updateCount)
+ */
+ int onUnlockNotify(BfiBillLockDTO param);
+
+ /**
+ * 编辑守卫:源单存在 locked 锁记录时抛 ServiceException(金蝶锁定中不可修改)
+ *
+ * @param sourceBillType WLTMS 单据类型
+ * @param sourceBillId WLTMS 业务主键
+ */
+ void checkEditable(String sourceBillType, Long sourceBillId);
+
+ /**
+ * 批量查源单当前锁状态(供列表/详情页填充展示标识):
+ * 每源单取最新一条非终态锁记录(locked/failed),unlocked 为终态不返回(前端视为无锁/已解锁)
+ *
+ * @param sourceBillType WLTMS 单据类型
+ * @param sourceBillIds 源单主键集合
+ * @return sourceBillId -> 最新锁记录,无活跃锁的源单不在 Map 中
+ */
+ Map mapActiveLocks(String sourceBillType, List sourceBillIds);
+
+ /**
+ * 锁定补偿:扫 failed 且 try_count 未达重试上限(RETRY_MAX_COUNT)的锁记录,逐张重调 lockBusiData
+ * (接口 1对1:单次一个源单号对一个金蝶单号,单张成败互不影响;定时任务定时触发,亦可人工触发);
+ * 成功翻 locked,失败 try_count+1 并记失败原因;
+ * 计数烧满上限后停止自动补偿(人工重推「同步金蝶」会重置计数重新接管)。锁记录落库均为批量更新
+ *
+ * @return 补偿摘要(待补偿/成功张数),供调用方记日志或页面提示
+ */
+ String retryFailedLocks();
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java
index 26779eda..6d4807af 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IPaymentApplicationService.java
@@ -21,6 +21,8 @@ package org.springblade.transport.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
+import org.springblade.thirdparty.bfi.pojo.vo.BfiPayApplyVO;
+import org.springblade.thirdparty.bfi.pojo.vo.BfiPayBillVO;
import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
import org.springblade.transport.pojo.entity.PaymentApplication;
@@ -41,5 +43,85 @@ public interface IPaymentApplicationService extends BaseService syncKingdeeBatch(List ids);
+
+ /**
+ * 单据级事务执行金蝶推送(由 syncKingdee 经自代理调用,保证整批同步不共用大事务)
+ *
+ * @param id 付款申请id
+ * @param context 批量共享上下文(档案缓存)
+ * @return 金蝶单据id
+ */
+ String doSyncKingdee(Long id, KingdeeSyncContext context);
+
+ /**
+ * 独立事务落"生成失败"状态(在推送事务回滚、行锁释放后执行);
+ * 仅更新审批通过的单据,未审批/已删除单据自动跳过,保持原状态
+ *
+ * @param id 付款申请id
+ */
+ void markKingdeeSyncFailed(Long id);
+
+ /**
+ * 重推金蝶生成失败(kingdeeStatus=failed 且审批通过)的付款申请:逐单独立成败,失败不中断。
+ * 只扫 failed 不扫 unsynced——存量"审批通过但从未推送"的历史单不自动补推,仍走手动按钮
+ *
+ * @return 执行摘要
+ */
+ String retryKingdeeFailed();
+
+ /**
+ * 金蝶单据解锁(ADR-0005):金蝶侧单据已被反审核删除时解除本地锁定——
+ * 金蝶状态置生成失败、清空金蝶单据内码与编号(原值日志留档),不自动重推,等待人工核实后重新推送。
+ * 触发来源:① B901 UNLOCK 回调(待建设);② A103-2 定时回写"已同步单据连续两轮查询消失"兜底。
+ * 仅对已生成/付款中单据生效,终态(已付款/已关闭)与生成失败单据自动跳过;
+ * 重推时 A103-2 勾稽预检兜底,误判场景(金蝶单据实际存在)自动回填不产生重复单据
+ *
+ * @param id 付款申请id
+ * @param source 解锁来源标识(B901 / A103-2,用于日志追溯)
+ */
+ void unlockBill(Long id, String source);
+
+ /**
+ * 定时回写金蝶付款结果(A103-2 付款申请单查询 / A103-4 付款处理查询)
+ *
+ * @return 执行摘要
+ */
+ String syncKingdeePaymentResult();
+
+ /**
+ * 回写单笔付款申请的金蝶付款申请单查询结果(A103-2),单据级事务
+ *
+ * @param entity 付款申请实体
+ * @param payApply A103-2 查询返回的付款申请单
+ * @return 是否付款完成(paystatus = Alreadypay)
+ */
+ boolean applyPayApplyResult(PaymentApplication entity, BfiPayApplyVO payApply);
+
+ /**
+ * 回填单笔付款申请的金蝶付款处理记录(A103-4),单据级事务
+ *
+ * @param entity 付款申请实体
+ * @param payBill A103-4 查询返回的付款处理单
+ */
+ void applyPayBillRecord(PaymentApplication entity, BfiPayBillVO payBill);
+
+ /**
+ * 付款方式是否为汇票付款(按业务字典 pay_method 条目文本匹配"汇票")
+ *
+ * @param paymentMethod 付款方式(字典键或值)
+ * @return true 表示汇票类付款
+ */
+ boolean isBillPayment(String paymentMethod);
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java
index 66a8886e..efd68227 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptClaimRecordService.java
@@ -45,5 +45,12 @@ public interface IReceiptClaimRecordService extends BaseService {
void updateAttachments(ReceiptClaimAttachmentsRequest request);
- String voidClaim(Long id);
+ void voidClaim(Long id);
+
+ /**
+ * A301-1 收款单查询回写认领结果:按来源单据编码匹配金蝶收款单,推进金蝶单据状态
+ *
+ * @return 回写条数
+ */
+ int refreshKingdeeResult();
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java
index 4fe29056..89586f79 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceiptFlowService.java
@@ -27,8 +27,8 @@ package org.springblade.transport.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
+import org.springblade.transport.pojo.dto.ReceiptClaimPushResult;
import org.springblade.transport.pojo.dto.ReceiptClaimRequest;
-import org.springblade.transport.pojo.dto.ReceiptFlowSyncRequest;
import org.springblade.transport.pojo.entity.KingdeeReceiptFlow;
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
@@ -52,5 +52,26 @@ public interface IReceiptFlowService extends BaseService {
Long claim(ReceiptClaimRequest request);
- int sync(ReceiptFlowSyncRequest request);
+ /**
+ * 手动同步金蝶收款流水:直连 A301-2 收款认领公告查询,按明细流水号幂等 upsert 收款流水表
+ *
+ * @return 本次同步条数
+ */
+ int sync();
+
+ /**
+ * 手动推送金蝶(A301-3 收款认领结果推送):认领完成且未推送过的流水才可推送,
+ * 推送前以当前用户姓名重查 A301-2 最新公告对账。与定时任务共用同一推送实现。
+ *
+ * @param flowId 收款流水id
+ * @return 推送结果(失败不抛异常,原因随结果返回)
+ */
+ ReceiptClaimPushResult pushClaimToKingdee(Long flowId);
+
+ /**
+ * 定时推送金蝶:扫描认领完成且未推送过的流水,按认领人姓名分组重查 A301-2 最新公告后逐条推送。
+ *
+ * @return 执行摘要
+ */
+ String pushClaimByTask();
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java
index ace2009e..196df17c 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IReceivablePayableDetailService.java
@@ -70,7 +70,8 @@ public interface IReceivablePayableDetailService extends BaseService> transferCandidates(IPage> page, String contractName, String batchNo,
+ IPage> transferCandidates(IPage> page, String documentNo, String contractNo,
+ String contractName, String batchNo,
String generateStartDate, String generateEndDate, String settlementBillType,
String settlementType);
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/KingdeeSyncContext.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/KingdeeSyncContext.java
new file mode 100644
index 00000000..aeac5967
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/KingdeeSyncContext.java
@@ -0,0 +1,56 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.service;
+
+import org.springblade.transport.pojo.entity.ContractManage;
+import org.springblade.transport.pojo.entity.CustomerReceiptAccount;
+import org.springblade.transport.pojo.entity.FormalSettlement;
+import org.springblade.transport.pojo.entity.FormalSettlementDetail;
+import org.springblade.transport.pojo.entity.ProjectApply;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 金蝶同步上下文:批量同步时全批共享的档案缓存,消除逐单重复的远程/数据库查询(N+1)。
+ *
单笔同步使用 {@link #empty()},各缓存为空时自动回退为逐单查询,行为与原逻辑一致。
+ *
+ * @author Chill
+ */
+public record KingdeeSyncContext(Map contractCache,
+ Map customerCodeCache, Map projectCache,
+ Map apBillNoCache,
+ Map> settlementDetailCache,
+ Map settlementCache,
+ Map receiptAccountCache,
+ Map feeCategoryByName) {
+
+ /** 空上下文:缓存可写但初始为空,首条记录命中后全批复用 */
+ public static KingdeeSyncContext empty() {
+ return new KingdeeSyncContext(new HashMap<>(), new HashMap<>(), new HashMap<>(),
+ new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
+ }
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java
index 180cb494..f0bd1f55 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/BillPaymentServiceImpl.java
@@ -35,6 +35,7 @@ import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.SysCache;
+import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.mapper.BillLedgerMapper;
import org.springblade.transport.mapper.BillLedgerUsageMapper;
import org.springblade.transport.mapper.BillPaymentMapper;
@@ -61,11 +62,6 @@ import java.util.Objects;
@RequiredArgsConstructor
public class BillPaymentServiceImpl extends BaseServiceImpl
implements IBillPaymentService {
- private static final String DRAFT = "draft";
- private static final String REVIEWING = "reviewing";
- private static final String APPROVED = "approved";
- private static final String RETURNED = "returned";
- private static final String VOIDED = "voided";
private static final String RELEASED = "released";
private final BillLedgerMapper billLedgerMapper;
@@ -103,7 +99,7 @@ public class BillPaymentServiceImpl extends BaseServiceImpllambdaQuery()
.eq(BillLedgerUsage::getBillPaymentId, entity.getId())
- .eq(BillLedgerUsage::getUsageStatus, APPROVED)
+ .eq(BillLedgerUsage::getUsageStatus, TransportBusiConstant.APPROVED)
.last("FOR UPDATE"));
if (exists != null) {
throw new ServiceException("该汇票付款已生成使用记录");
@@ -188,11 +184,11 @@ public class BillPaymentServiceImpl extends BaseServiceImpllambdaQuery()
.eq(BillLedgerUsage::getBillPaymentId, entity.getId())
- .eq(BillLedgerUsage::getUsageStatus, APPROVED)
+ .eq(BillLedgerUsage::getUsageStatus, TransportBusiConstant.APPROVED)
.last("FOR UPDATE"));
if (usage == null) {
throw new ServiceException("未找到汇票使用记录,无法作废");
@@ -233,7 +229,7 @@ public class BillPaymentServiceImpl extends BaseServiceImpl implements ICustomerArchiveService {
+ /**
+ * 状态:启用
+ */
private static final int STATUS_ENABLED = 1;
+
+ /**
+ * 状态:停用
+ */
private static final int STATUS_DISABLED = 2;
+
+ /**
+ * 准入类型:临时
+ */
private static final String ACCESS_TEMPORARY = "temporary";
+
+ /**
+ * 准入类型:正式
+ */
private static final String ACCESS_FORMAL = "formal";
+
+ /**
+ * 审批状态:草稿
+ */
private static final String APPROVAL_DRAFT = "draft";
+
+ /**
+ * 审批状态:审核中
+ */
private static final String APPROVAL_REVIEWING = "reviewing";
+
+ /**
+ * 审批状态:审批通过
+ */
private static final String APPROVAL_APPROVED = "approved";
+
+ /**
+ * 审批状态:审批驳回
+ */
private static final String APPROVAL_REJECTED = "rejected";
/** 客商所属组织固定为顶层组织 */
private static final String FIXED_OWNER_DEPT_NAME = "桂物物流集团";
+
+ /**
+ * 金蝶协同状态:未推送(新增客商默认值,审批通过后参与推送)
+ */
+ private static final String KINGDEE_STATUS_NONE = "none";
+
+ /**
+ * 金蝶协同状态:金蝶已有档案(A902-1 查到已审核,审批通过后跳过推送,终态)
+ */
+ private static final String KINGDEE_STATUS_EXIST = "exist";
+
+ /**
+ * 金蝶协同状态:已推送成功(终态,重推时幂等跳过)
+ */
+ private static final String KINGDEE_STATUS_SYNCED = "synced";
+
+ /**
+ * 金蝶协同状态:推送失败 / 金蝶审核中(可列表「同步金蝶」人工重推)
+ */
+ private static final String KINGDEE_STATUS_FAILED = "failed";
+
+ /**
+ * 客商编号前缀
+ */
private static final String CUSTOMER_CODE_PREFIX = "KS";
+ /**
+ * A902-1 批量查询分批大小
+ */
+ private static final int KINGDEE_CUSTOMER_QUERY_BATCH = 50;
/**
* 变更快照忽略字段:明细每次保存会重建主键/审计字段,不能作为“是否有修改”的依据
*/
@@ -147,6 +220,13 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl selectCustomerArchivePage(IPage page, CustomerArchiveVO customer) {
@@ -222,6 +302,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl page = bfiService.queryCustomer(param);
+ List rows = page == null ? null : page.getRows();
+ return rows == null || rows.isEmpty() ? null : rows.get(0);
+ }
+
+ private BfiBillSaveResultItemVO firstSaveItem(BfiBillSaveResultVO result) {
+ if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
+ return null;
+ }
+ return result.getResult().get(0);
+ }
+
+ private String buildSaveFailReason(BfiBillSaveResultVO result, BfiBillSaveResultItemVO item) {
+ if (item != null && Func.isNotEmpty(item.getErrors())) {
+ String messages = item.getErrors().stream()
+ .map(BfiBillSaveResultErrorVO::getRowMsg)
+ .filter(Func::isNotEmpty)
+ .flatMap(List::stream)
+ .collect(Collectors.joining(";"));
+ if (Func.isEmpty(messages)) {
+ messages = item.getErrors().stream()
+ .map(error -> String.valueOf(error.getEntityKey()))
+ .collect(Collectors.joining(";"));
+ }
+ return truncateText(messages);
+ }
+ if (result != null && Func.isNotEmpty(result.getFailCount())) {
+ return truncateText("金蝶返回失败数量:" + result.getFailCount());
+ }
+ return "金蝶返回结果为空";
+ }
+
+ private String truncateText(String text) {
+ if (text == null) {
+ return null;
+ }
+ return text.length() > 500 ? text.substring(0, 500) : text;
+ }
+
+ /**
+ * 定时同步推送报文组装:主表字段 + 批量查询的收款账户,不含联系人/发票/评分等推送无关明细,
+ * 替代逐条 detail 查询,避免 N+1
+ */
+ private CustomerArchiveVO buildSyncArchiveVO(CustomerArchive customer, List receiptAccounts) {
+ CustomerArchiveVO detail = Objects.requireNonNull(BeanUtil.copyProperties(customer, CustomerArchiveVO.class));
+ detail.setReceiptAccounts(receiptAccounts);
+ return detail;
+ }
+
+ /**
+ * 定时同步变更集中落库:档案批量更新 + 变更记录批量插入,失败仅记录日志,不影响汇总返回
+ */
+ private void flushSyncChanges(List updateList, List changeRecords) {
+ if (!updateList.isEmpty()) {
+ try {
+ updateBatchById(updateList);
+ } catch (Exception exception) {
+ log.error("定时同步金蝶客商批量更新档案异常,size={}", updateList.size(), exception);
+ }
+ }
+ if (!changeRecords.isEmpty()) {
+ try {
+ Db.saveBatch(changeRecords);
+ } catch (Exception exception) {
+ log.error("定时同步金蝶客商批量写入变更记录异常,size={}", changeRecords.size(), exception);
+ }
+ }
+ }
+
+ @Override
+ public String syncKingdeeCustomerData() {
+ return kingdeeSyncLockSupport.runSingleFlight(CUSTOMER_SYNC_LOCK_PREFIX, bfiProperties.getCustomer().getLockLeaseSeconds(),
+ "金蝶客商数据同步", this::executeCustomerDataSync);
+ }
+
+ /**
+ * 金蝶客商数据同步主体:A902-1 复查 + A902-2 推送 + 金蝶禁用联动停用客商档案,
+ * 由 {@link #syncKingdeeCustomerData()} 在分布式锁保护下调用
+ *
+ * @return 执行摘要
+ */
+ private String executeCustomerDataSync() {
+ log.info("金蝶客商数据定时同步开始");
+ // 客商数据量不大,一次查询审批通过客商,内存按金蝶协同状态分组
+ List approvedList = list(Wrappers.lambdaQuery()
+ .eq(CustomerArchive::getApprovalStatus, APPROVAL_APPROVED)
+ .orderByAsc(CustomerArchive::getId));
+ // ① 待推送客商:未推送/推送失败,单条失败不中断批次
+ List pendingList = approvedList.stream()
+ .filter(customer -> KINGDEE_STATUS_NONE.equals(customer.getKingdeeStatus())
+ || KINGDEE_STATUS_FAILED.equals(customer.getKingdeeStatus()))
+ .toList();
+ // ② 停用联动候选:已同步/金蝶已有且启用的客商
+ List enabledList = approvedList.stream()
+ .filter(customer -> (KINGDEE_STATUS_SYNCED.equals(customer.getKingdeeStatus())
+ || KINGDEE_STATUS_EXIST.equals(customer.getKingdeeStatus()))
+ && Objects.equals(customer.getStatus(), STATUS_ENABLED))
+ .toList();
+ // ③ 已推送客商:A902-2 成功仅代表金蝶受理(status=B),复查 status=C 已审核才是最终成功
+ List syncedList = approvedList.stream()
+ .filter(customer -> KINGDEE_STATUS_SYNCED.equals(customer.getKingdeeStatus()))
+ .toList();
+ log.info("金蝶客商数据定时同步查询到审批通过客商 {} 条(待推送 {},已推送 {},可停用联动 {})",
+ approvedList.size(), pendingList.size(), syncedList.size(), enabledList.size());
+ int pushed = 0;
+ int existed = 0;
+ int pushFailed = 0;
+ // 变更集中落库:档案批量更新、变更记录批量插入,避免逐条 update/insert 的 N+1 写入
+ List updateList = new ArrayList<>();
+ List changeRecords = new ArrayList<>();
+ // 批量 A902-1 复查待推送客商:金蝶已审核直接建档(exist)、审核中置 failed,仅不存在金蝶档案的才逐条推送,避免逐条复查的 N+1 调用
+ Map pendingKingdeeMap = queryKingdeeCustomerMap(pendingList.stream()
+ .map(CustomerArchive::getUnifiedCreditCode)
+ .filter(Func::isNotEmpty)
+ .map(code -> code.trim().toUpperCase())
+ .distinct()
+ .toList());
+ List pushList = new ArrayList<>();
+ for (CustomerArchive customer : pendingList) {
+ BfiCustomerVO kingdeeCustomer = pendingKingdeeMap.get(customer.getUnifiedCreditCode().trim().toUpperCase());
+ if (kingdeeCustomer == null) {
+ pushList.add(customer);
+ continue;
+ }
+ CustomerArchive after = copyCustomer(customer);
+ after.setKingdeeSyncTime(LocalDateTime.now());
+ if (BfiConstant.BILL_STATUS_AUDITED.equals(kingdeeCustomer.getStatus())) {
+ // 金蝶已存在已审核档案:跳过推送,直接建档
+ after.setKingdeeStatus(KINGDEE_STATUS_EXIST);
+ after.setKingdeeFailReason(null);
+ existed++;
+ collectChangeRecord(changeRecords, customer.getId(), "金蝶已存在该客商档案,跳过推送", customer, after);
+ } else {
+ // 金蝶侧暂存/已提交:审核中,置失败待重推
+ after.setKingdeeStatus(KINGDEE_STATUS_FAILED);
+ after.setKingdeeFailReason("金蝶侧该客商审核中,请稍后重推");
+ pushFailed++;
+ collectChangeRecord(changeRecords, customer.getId(), "推送金蝶失败:金蝶侧客商审核中", customer, after);
+ }
+ log.info("金蝶客商数据定时同步复查处理完成,id={},unifiedCreditCode={},kingdeeStatus={}",
+ customer.getId(), customer.getUnifiedCreditCode(), after.getKingdeeStatus());
+ updateList.add(after);
+ }
+ // 金蝶不存在档案的逐条推送(A902-2 保存):
+ // 报文仅需主表字段 + 收款账户,按客商ID批量一次查询收款账户组装报文,避免逐条 detail 的 N+1 查询
+ Map> receiptAccountsMap = loadReceiptAccountsMap(pushList.stream()
+ .map(CustomerArchive::getId)
+ .toList());
+ for (CustomerArchive customer : pushList) {
+ try {
+ CustomerArchiveVO detail = buildSyncArchiveVO(customer,
+ receiptAccountsMap.getOrDefault(customer.getId(), new ArrayList<>()));
+ CustomerArchive after = copyCustomer(customer);
+ after.setKingdeeSyncTime(LocalDateTime.now());
+ pushCustomerToKingdee(customer.getId(), customer, after, detail);
+ updateList.add(after);
+ if (Objects.equals(after.getKingdeeStatus(), KINGDEE_STATUS_SYNCED)) {
+ pushed++;
+ log.info("金蝶客商数据定时同步推送成功,id={},unifiedCreditCode={}",
+ customer.getId(), customer.getUnifiedCreditCode());
+ } else {
+ pushFailed++;
+ log.warn("金蝶客商数据定时同步推送失败,id={},unifiedCreditCode={},kingdeeStatus={},failReason={}",
+ customer.getId(), customer.getUnifiedCreditCode(), after.getKingdeeStatus(), after.getKingdeeFailReason());
+ }
+ } catch (Exception exception) {
+ pushFailed++;
+ log.error("定时同步金蝶客商推送异常,id={},unifiedCreditCode={}",
+ customer.getId(), customer.getUnifiedCreditCode(), exception);
+ }
+ }
+ // ②③ 共用:对已推送(synced)/金蝶已有(exist)客商按信用代码批量复查金蝶档案(审核状态 + 使用状态),仅调用一次 A902-1
+ int disabled = 0;
+ int disableFailed = 0;
+ int confirmed = 0;
+ int auditing = 0;
+ int unknown = 0;
+ if (!enabledList.isEmpty() || !syncedList.isEmpty()) {
+ List reviewCreditCodes = Stream.concat(syncedList.stream(), enabledList.stream())
+ .map(CustomerArchive::getUnifiedCreditCode)
+ .filter(Func::isNotEmpty)
+ .map(code -> code.trim().toUpperCase())
+ .distinct()
+ .toList();
+ Map kingdeeMap = reviewCreditCodes.isEmpty()
+ ? Map.of()
+ : queryKingdeeCustomerMap(reviewCreditCodes);
+ // ③ 审核确认:A902-1 复查 status=C 已审核才是最终成功,synced 升级为 exist(终态);
+ // A/B 审核中保持 synced 等待下轮复查(不重推,避免金蝶侧重复建档)
+ for (CustomerArchive customer : syncedList) {
+ BfiCustomerVO kingdeeCustomer = kingdeeMap.get(customer.getUnifiedCreditCode().trim().toUpperCase());
+ if (kingdeeCustomer == null) {
+ unknown++;
+ log.warn("金蝶客商审核状态复查未查询到档案,id={},unifiedCreditCode={}",
+ customer.getId(), customer.getUnifiedCreditCode());
+ continue;
+ }
+ if (!BfiConstant.BILL_STATUS_AUDITED.equals(kingdeeCustomer.getStatus())) {
+ auditing++;
+ log.info("金蝶客商审核中,等待下轮复查,id={},unifiedCreditCode={},kingdeeStatus={}",
+ customer.getId(), customer.getUnifiedCreditCode(), kingdeeCustomer.getStatus());
+ continue;
+ }
+ confirmed++;
+ CustomerArchive after = copyCustomer(customer);
+ after.setKingdeeStatus(KINGDEE_STATUS_EXIST);
+ after.setKingdeeFailReason(null);
+ updateList.add(after);
+ collectChangeRecord(changeRecords, customer.getId(), "金蝶客商已审核,推送最终成功", customer, after);
+ log.info("金蝶客商已审核,推送最终成功并升级为金蝶已有档案,id={},unifiedCreditCode={}",
+ customer.getId(), customer.getUnifiedCreditCode());
+ }
+ // ② 停用联动:金蝶禁用(enable=0)→ TMS 自动停用;金蝶恢复可用不自动启用(TMS 人工控制);
+ // 直接基于已查询档案内存处理,避免逐条自代理调用重复查询档案与权限
+ for (CustomerArchive customer : enabledList) {
+ BfiCustomerVO kingdeeCustomer = kingdeeMap.get(customer.getUnifiedCreditCode().trim().toUpperCase());
+ if (kingdeeCustomer == null || !"0".equals(kingdeeCustomer.getEnable())) {
+ continue;
+ }
+ try {
+ CustomerArchive after = copyCustomer(customer);
+ after.setStatus(STATUS_DISABLED);
+ collectChangeRecord(changeRecords, customer.getId(), "金蝶侧客商已停用,同步停用客商档案", customer, after);
+ updateList.add(after);
+ disabled++;
+ log.info("金蝶侧客商已停用,联动停用客商档案成功,id={},unifiedCreditCode={}",
+ customer.getId(), customer.getUnifiedCreditCode());
+ } catch (Exception exception) {
+ disableFailed++;
+ log.error("定时同步金蝶客商停用异常,id={},unifiedCreditCode={}",
+ customer.getId(), customer.getUnifiedCreditCode(), exception);
+ }
+ }
+ }
+ flushSyncChanges(updateList, changeRecords);
+ String summary = "待推送 " + pendingList.size() + " 条(推送成功 " + pushed + ",金蝶已有 " + existed
+ + ",失败 " + pushFailed + "),停用联动 " + disabled + " 条(失败 " + disableFailed + ")"
+ + ",已推送复查 " + syncedList.size() + " 条(金蝶已审核 " + confirmed + ",审核中 " + auditing
+ + ",未查询到 " + unknown + ")";
+ log.info("金蝶客商数据定时同步结束:{}", summary);
+ return summary;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean disableCustomerFromKingdee(Long id) {
+ CustomerArchive customer = ensureCustomerAccessible(id);
+ if (!Objects.equals(customer.getStatus(), STATUS_ENABLED)) {
+ return false;
+ }
+ CustomerArchive after = copyCustomer(customer);
+ after.setStatus(STATUS_DISABLED);
+ addChangeRecord(id, "金蝶侧客商已停用,同步停用客商档案", customer, after);
+ return updateById(after);
+ }
+
+ /**
+ * 按统一社会信用代码分批批量查询金蝶客商,键为大写信用代码;
+ * societycreditcode 支持数组一次查询多个,单批使用 BfiUtil.queryAllPages 自动翻页取全,
+ * 避免同一信用代码多条记录时漏查,单批查询异常不中断其余批次
+ *
+ * @param creditCodes 统一社会信用代码集合
+ * @return 大写信用代码 -> 金蝶客商
+ */
+ private Map queryKingdeeCustomerMap(List creditCodes) {
+ Map result = new HashMap<>();
+ for (int start = 0; start < creditCodes.size(); start += KINGDEE_CUSTOMER_QUERY_BATCH) {
+ List batch = creditCodes.subList(start, Math.min(start + KINGDEE_CUSTOMER_QUERY_BATCH, creditCodes.size()));
+ if (batch.isEmpty()) {
+ continue;
+ }
+ try {
+ BfiCustomerQueryDTO param = new BfiCustomerQueryDTO();
+ param.setSocietycreditcode(batch);
+ List rows = BfiUtil.queryAllPages(pageNo -> {
+ param.setPageNo(pageNo);
+ param.setPageSize(KINGDEE_CUSTOMER_QUERY_BATCH);
+ return bfiService.queryCustomer(param);
+ });
+ for (BfiCustomerVO vo : rows) {
+ if (Func.isNotEmpty(vo.getSocietycreditcode())) {
+ result.put(vo.getSocietycreditcode().trim().toUpperCase(), vo);
+ }
+ }
+ } catch (Exception exception) {
+ log.error("定时同步金蝶客商批量查询异常,batchSize={}", batch.size(), exception);
+ }
+ }
+ return result;
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public boolean changeStatus(Long id, Integer status) {
@@ -485,6 +925,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl invoices = customer.getInvoices();
+ if (Func.isEmpty(invoices)) {
+ return;
+ }
+ List taxNos = invoices.stream()
+ .map(CustomerInvoiceInfoVO::getTaxNo)
+ .filter(Func::isNotEmpty)
+ .map(taxNo -> taxNo.trim().toUpperCase())
+ .distinct()
+ .toList();
+ if (Func.isEmpty(taxNos)) {
+ return;
+ }
+ List blacklist = bfiService.queryBlacklist("1", taxNos);
+ BfiBlacklistVO hit = blacklist.stream()
+ .filter(item -> Func.isNotEmpty(item.getNumber()) && taxNos.contains(item.getNumber().trim().toUpperCase()))
+ .findFirst()
+ .orElse(null);
+ if (hit == null) {
+ return;
+ }
+ String category = Func.isEmpty(hit.getAwrpCombofieldTitle()) ? hit.getAwrpCombofield() : hit.getAwrpCombofieldTitle();
+ boolean forbidden = "A".equals(hit.getAwrpCombofield());
+ boolean riskWarning = "B".equals(hit.getAwrpCombofield());
+ if (forbidden || (riskWarning && bfiProperties.getBlacklist().isBlockRiskWarning())) {
+ throw new ServiceException(String.format("客商【%s】在国资委黑名单中(失信类别:%s,列入事由:%s),不允许提交准入审批",
+ customer.getFullName(), category, hit.getAwrpTextareafield()));
+ }
+ log.warn("客商命中金蝶黑名单风险警示类,放行,fullName={},taxNos={},失信类别={},列入事由={}",
+ customer.getFullName(), taxNos, category, hit.getAwrpTextareafield());
+ }
+
private void validateUnique(CustomerArchiveVO customer) {
LambdaQueryWrapper unifiedCreditCodeQuery = Wrappers.lambdaQuery()
.eq(CustomerArchive::getUnifiedCreditCode, customer.getUnifiedCreditCode())
@@ -854,12 +1336,26 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl loadReceiptAccounts(Long customerId) {
+ return loadReceiptAccountsMap(List.of(customerId)).getOrDefault(customerId, new ArrayList<>());
+ }
+
+ /**
+ * 按客商ID集合批量查询收款账户(键为客商ID),供定时同步批量组装金蝶报文等场景使用,避免逐条查询
+ *
+ * @param customerIds 客商ID集合
+ * @return 客商ID -> 收款账户列表(默认一条扣款排序)
+ */
+ private Map> loadReceiptAccountsMap(List customerIds) {
+ if (Func.isEmpty(customerIds)) {
+ return Map.of();
+ }
return receiptAccountMapper.selectList(Wrappers.lambdaQuery()
- .eq(CustomerReceiptAccount::getCustomerId, customerId)
+ .in(CustomerReceiptAccount::getCustomerId, customerIds)
.eq(CustomerReceiptAccount::getIsDeleted, 0)
.orderByDesc(CustomerReceiptAccount::getIsDefault)
.orderByAsc(CustomerReceiptAccount::getCreateTime))
- .stream().map(account -> Objects.requireNonNull(BeanUtil.copyProperties(account, CustomerReceiptAccountVO.class))).toList();
+ .stream().map(account -> Objects.requireNonNull(BeanUtil.copyProperties(account, CustomerReceiptAccountVO.class)))
+ .collect(Collectors.groupingBy(CustomerReceiptAccountVO::getCustomerId));
}
private List loadInvoices(Long customerId) {
@@ -1306,11 +1802,10 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl beforeSnapshot = customerSnapshot(before);
- Map afterSnapshot = customerSnapshot(after);
- addDetailSnapshot(beforeSnapshot, null);
- addDetailSnapshot(afterSnapshot, null);
- writeChangeRecord(customerId, content, beforeSnapshot, afterSnapshot);
+ CustomerChangeRecord record = buildChangeRecord(customerId, content, before, after);
+ if (record != null) {
+ changeRecordMapper.insert(record);
+ }
}
private void addChangeRecord(Long customerId, String content, CustomerArchive before, CustomerArchive after,
@@ -1319,10 +1814,35 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl afterSnapshot = customerSnapshot(after);
addDetailSnapshot(beforeSnapshot, beforeDetail);
addDetailSnapshot(afterSnapshot, afterDetail);
- writeChangeRecord(customerId, content, beforeSnapshot, afterSnapshot);
+ CustomerChangeRecord record = buildChangeRecord(customerId, content, beforeSnapshot, afterSnapshot);
+ if (record != null) {
+ changeRecordMapper.insert(record);
+ }
}
- private void writeChangeRecord(Long customerId, String content, Map beforeSnapshot,
+ /**
+ * 收集变更记录供定时同步集中批量插入(无实质变更不收集),避免逐条 insert
+ */
+ private void collectChangeRecord(List records, Long customerId, String content,
+ CustomerArchive before, CustomerArchive after) {
+ CustomerChangeRecord record = buildChangeRecord(customerId, content, before, after);
+ if (record != null) {
+ records.add(record);
+ }
+ }
+
+ /**
+ * 构建变更记录(无实质变更返回 null),供单条写入与定时同步批量收集共用
+ */
+ private CustomerChangeRecord buildChangeRecord(Long customerId, String content, CustomerArchive before, CustomerArchive after) {
+ Map beforeSnapshot = customerSnapshot(before);
+ Map afterSnapshot = customerSnapshot(after);
+ addDetailSnapshot(beforeSnapshot, null);
+ addDetailSnapshot(afterSnapshot, null);
+ return buildChangeRecord(customerId, content, beforeSnapshot, afterSnapshot);
+ }
+
+ private CustomerChangeRecord buildChangeRecord(Long customerId, String content, Map beforeSnapshot,
Map afterSnapshot) {
for (String field : afterSnapshot.keySet()) {
beforeSnapshot.putIfAbsent(field, null);
@@ -1343,7 +1863,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl maxLength) {
throw new ServiceException(message);
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerBfiConverter.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerBfiConverter.java
new file mode 100644
index 00000000..d4ec9544
--- /dev/null
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerBfiConverter.java
@@ -0,0 +1,282 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.system.cache.DictBizCache;
+import org.springblade.system.cache.SysCache;
+import org.springblade.system.pojo.entity.Dept;
+import org.springblade.system.pojo.entity.DictBiz;
+import org.springblade.thirdparty.bfi.constant.BfiConstant;
+import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerBankEntryDTO;
+import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerGroupEntryDTO;
+import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerLinkmanEntryDTO;
+import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerSaveDTO;
+import org.springblade.transport.config.BfiProperties;
+import org.springblade.transport.constant.CustomerBfiConstants;
+import org.springblade.transport.pojo.vo.CustomerArchiveVO;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 客商档案金蝶(星瀚)报文转换器
+ *
+ * @author Chill
+ */
+@Component
+@RequiredArgsConstructor
+public class CustomerBfiConverter {
+
+ private final BfiProperties bfiProperties;
+
+ /**
+ * 组装 A902-2 客户保存参数
+ *
+ * @param customer 客商档案
+ * @return 客户保存参数
+ */
+ public BfiCustomerSaveDTO buildSaveDTO(CustomerArchiveVO customer) {
+ BfiCustomerSaveDTO param = new BfiCustomerSaveDTO();
+ // 编码:不传由星瀚生成;金蝶客户编码与 TMS 客商编号保持一致
+ param.setNumber(customer.getCustomerCode());
+ // 数据状态:固定传 B 已提交,金蝶校验工商信息通过后由机器人自动审核
+ // param.setStatus(BfiConstant.BILL_STATUS_SUBMITTED);
+ // 【必填】名称:客商名称
+ param.setName(customer.getFullName());
+ // 【必填】统一社会信用代码(候选键):个人为身份证号或手机号
+ param.setSocietycreditcode(customer.getUnifiedCreditCode());
+
+ // 【必填】创建组织:客商所属组织编码对应的金蝶组织编码
+ // param.setCreateorgNumber(this.resolveOrgNumber(customer));
+
+ // 纳税人识别号:与统一社会信用代码一致时推送
+ param.setTxRegisterNo(customer.getUnifiedCreditCode());
+ // 法人代表:企业法人
+ param.setArtificialperson(customer.getLegalPerson());
+ // 【必填】伙伴类型:客商性质字典 dictKey 即伙伴类型编码,直接推送,无效编码抛异常
+ param.setType(this.resolvePartnerType(customer.getCustomerNature()));
+ // 详细地址:注册地址(省市区 + 详细地址拼接文本)
+ param.setBizpartnerAddress(customer.getRegisteredAddress());
+ // 经营范围:多选编码 → 字典文本「、」拼接
+ param.setBusinessscope(this.buildBusinessScopeText(customer.getBusinessScope()));
+ // 营业期限:长期传「长期」,固定期限传「至 yyyy-MM-dd」(TMS 未采集起始日)
+ param.setBusinessterm(this.buildBusinessTerm(customer.getBusinessTermType(), customer.getBusinessEndDate()));
+ // 联系电话:客商联系电话
+ param.setBizpartnerPhone(customer.getContactPhone());
+ // 备注:客商备注,空时不传
+ param.setFcusbeizhu(customer.getRemark());
+ // 业务职能:固定全选(1销售,2结算,3付款,4收货)
+ param.setBizfunction(CustomerBfiConstants.BIZ_FUNCTION_ALL);
+ // 数据来源系统:thirdParty.bfi.srcSys(公共参数),默认 WLTMS(运输TMS)
+ param.setAwrpDatasourcesys(bfiProperties.getSrcSys());
+ // 【必填】是否为贸易客商:固定传 A 是
+ param.setAwrpCusfortrade(BfiConstant.CUS_FOR_TRADE_YES);
+ // 【必填】分类标准分录:外部客商 1102、内部组织 1101
+ BfiCustomerGroupEntryDTO groupEntry = new BfiCustomerGroupEntryDTO();
+ groupEntry.setStandardidNumber(BfiConstant.GROUP_STANDARD_BASIC);
+ groupEntry.setGroupidNumber(this.resolveGroupId(customer.getCustomerKind()));
+ param.setEntryGroupstandard(List.of(groupEntry));
+ // 银行信息分录:收款账户明细(账户名称传开户行,币种默认 RMB)
+ param.setEntryBank(this.buildBankEntries(customer));
+ // 联系人信息分录:页面填写的客商负责人与联系电话
+ param.setEntryLinkman(this.buildLinkmanEntries(customer));
+ return param;
+ }
+
+ /**
+ * 金蝶创建组织编码:客商所属组织部门编码,与金蝶组织编码一致;
+ * 所属组织不存在或未维护部门编码时抛出异常,阻断推送
+ *
+ * @param customer 客商档案
+ * @return 金蝶创建组织编码
+ */
+ private String resolveOrgNumber(CustomerArchiveVO customer) {
+ Dept dept = customer.getDeptId() == null ? null : SysCache.getDept(customer.getDeptId());
+ if (dept == null) {
+ throw new ServiceException("推送金蝶失败,客商所属组织不存在,请检查客商所属组织配置");
+ }
+ if (Func.isBlank(dept.getDeptCode())) {
+ throw new ServiceException("推送金蝶失败,客商所属组织编码不能为空,请检查组织[" + dept.getDeptName() + "]是否维护部门编码");
+ }
+ return dept.getDeptCode();
+ }
+
+ /**
+ * 解析分类标准分录客户分组编码:内部组织 1101,其余(外部客商)1102
+ *
+ * @param customerKind 客商类型(internal 内部组织 / external 外部客商)
+ * @return 客户分组编码
+ */
+ private String resolveGroupId(String customerKind) {
+ if (CustomerBfiConstants.CUSTOMER_KIND_INTERNAL.equals(customerKind)) {
+ return BfiConstant.CUSTOMER_GROUP_INTERNAL;
+ }
+ return BfiConstant.CUSTOMER_GROUP_EXTERNAL;
+ }
+
+ /**
+ * 组装 A902-2 银行信息分录:每条收款账户一行分录,账户名称传开户行、币种默认 RMB,收款账户未填写时返回空分录
+ *
+ * @param customer 客商档案
+ * @return 银行信息分录
+ */
+ private List buildBankEntries(CustomerArchiveVO customer) {
+ if (Func.isEmpty(customer.getReceiptAccounts())) {
+ return List.of();
+ }
+ return customer.getReceiptAccounts().stream()
+ .filter(account -> Func.isNotBlank(account.getBankAccount()) || Func.isNotBlank(account.getBankName()))
+ .map(account -> {
+ BfiCustomerBankEntryDTO entry = new BfiCustomerBankEntryDTO();
+ // 银行账号
+ entry.setBankaccount(account.getBankAccount());
+ // 账户名称:传开户行
+ entry.setAccountname(account.getBankName());
+ // 开户银行.编码(行号) todo
+ String cnapsCode = account.getCnapsCode();
+ // cnapsCode = "001110012009";
+ entry.setBankNumber(cnapsCode);
+ // 币种:人民币(CNY)
+ entry.setCurrencyNumber(BfiConstant.CURRENCY_CNY);
+ // 是否默认银行
+ entry.setIsdefaultBank(true);
+ return entry;
+ })
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 组装 A902-2 联系人信息分录:页面填写的客商负责人与联系电话,未填负责人时返回空分录
+ *
+ * @param customer 客商档案
+ * @return 联系人信息分录
+ */
+ private List buildLinkmanEntries(CustomerArchiveVO customer) {
+ if (Func.isBlank(customer.getPrincipal())) {
+ return List.of();
+ }
+ BfiCustomerLinkmanEntryDTO entry = new BfiCustomerLinkmanEntryDTO();
+ // 联系人:客商负责人
+ entry.setContactperson(customer.getPrincipal());
+ // 电话:联系电话
+ entry.setPhone(customer.getContactPhone());
+ // 默认联系人
+ entry.setIsdefaultLinkman(Boolean.TRUE);
+ return List.of(entry);
+ }
+
+ /**
+ * 金蝶伙伴类型编码:客商性质(nature_of_client 字典)——
+ * 值为 dictKey(即伙伴类型编码,1~17)时直接使用;
+ * 值为 dictValue 文本(前端字典下拉存文本,含存量数据,如「我区国企/国企控股」)时按字典反查对应 dictKey;
+ * 均无法解析时抛出异常,阻断推送
+ *
+ * @param customerNature 客商性质(dictKey 编码或 dictValue 文本)
+ * @return 金蝶伙伴类型编码
+ */
+ public String resolvePartnerType(String customerNature) {
+ String nature = Func.isEmpty(customerNature) ? "" : customerNature.trim();
+ if (nature.matches("\\d{1,2}")) {
+ int partnerType = Integer.parseInt(nature);
+ if (partnerType >= 1 && partnerType <= CustomerBfiConstants.PARTNER_TYPE_MAX) {
+ return nature;
+ }
+ }
+ String partnerType = this.resolvePartnerTypeFromDictValue(nature);
+ if (Func.isBlank(partnerType)) {
+ throw new ServiceException("客商性质未配置有效的金蝶伙伴类型编码(nature_of_client 字典 dictKey):" + customerNature);
+ }
+ return partnerType;
+ }
+
+ /**
+ * 按客商性质文本(dictValue)反查 nature_of_client 字典,返回对应 dictKey(即伙伴类型编码)
+ *
+ * @param natureValue 客商性质文本
+ * @return 金蝶伙伴类型编码,未命中返回 null
+ */
+ private String resolvePartnerTypeFromDictValue(String natureValue) {
+ if (Func.isEmpty(natureValue)) {
+ return null;
+ }
+ List dictList = DictBizCache.getList("nature_of_client");
+ if (dictList == null) {
+ return null;
+ }
+ return dictList.stream()
+ .filter(dict -> natureValue.equals(dict.getDictValue()))
+ .map(DictBiz::getDictKey)
+ .map(key -> Func.isEmpty(key) ? "" : key.trim())
+ .filter(key -> {
+ if (!key.matches("\\d{1,2}")) {
+ return false;
+ }
+ int partnerType = Integer.parseInt(key);
+ return partnerType >= 1 && partnerType <= CustomerBfiConstants.PARTNER_TYPE_MAX;
+ })
+ .findFirst()
+ .orElse(null);
+ }
+
+ /**
+ * 经营范围多选编码 → 字典文本「、」拼接,字典缺失时回退原编码
+ *
+ * @param businessScope 逗号拼接的经营范围编码
+ * @return 金蝶经营范围文本
+ */
+ public String buildBusinessScopeText(String businessScope) {
+ if (Func.isEmpty(businessScope)) {
+ return "";
+ }
+ List dictList = DictBizCache.getList("scope_of_business");
+ Map scopeNames = dictList == null ? Map.of() : dictList.stream()
+ .collect(Collectors.toMap(DictBiz::getDictKey, DictBiz::getDictValue, (first, second) -> first));
+ return Func.toStrList(businessScope.replace(",", ",")).stream()
+ .map(code -> scopeNames.getOrDefault(code, code))
+ .collect(Collectors.joining("、"));
+ }
+
+ /**
+ * 营业期限:长期传「长期」;固定期限传「至 yyyy-MM-dd」(TMS 未采集起始日)
+ *
+ * @param businessTermType 营业期限类型
+ * @param businessEndDate 营业期限截止日
+ * @return 金蝶营业期限文本
+ */
+ public String buildBusinessTerm(String businessTermType, LocalDate businessEndDate) {
+ if (CustomerBfiConstants.BUSINESS_TERM_LONG.equals(businessTermType)) {
+ return CustomerBfiConstants.BUSINESS_TERM_LONG;
+ }
+ return businessEndDate == null ? "" : "至 " + businessEndDate;
+ }
+
+}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java
index 493511f2..d3f7a922 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/FormalSettlementServiceImpl.java
@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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;
@@ -27,6 +28,9 @@ import org.springblade.transport.mapper.FormalSettlementSummaryFeeMapper;
import org.springblade.transport.mapper.FormalSettlementPaymentMapper;
import org.springblade.transport.mapper.InvoiceReceiptMapper;
import org.springblade.transport.mapper.InvoiceReceiptSettlementMapper;
+import org.springblade.transport.mapper.InvoiceApplicationInvoiceMapper;
+import org.springblade.transport.mapper.InvoiceApplicationMapper;
+import org.springblade.transport.mapper.InvoiceApplicationSettlementMapper;
import org.springblade.transport.mapper.PreSettlementDetailMapper;
import org.springblade.transport.mapper.PreSettlementDetailFeeMapper;
import org.springblade.transport.mapper.PreSettlementMapper;
@@ -35,6 +39,12 @@ import org.springblade.transport.mapper.PaymentApplicationMapper;
import org.springblade.transport.mapper.PaymentApplicationInvoiceMapper;
import org.springblade.transport.mapper.PaymentApplicationSettlementMapper;
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
+import org.springblade.transport.constant.KingdeeAttachmentConstant;
+import org.springblade.transport.config.BfiProperties;
+import org.springblade.transport.constant.KingdeeBfiConstant;
+import org.springblade.transport.constant.KingdeeLockConstant;
+import org.springblade.transport.pojo.entity.KingdeeBillLock;
+import org.springblade.transport.constant.TransportBusiConstant;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.mapper.SettlementAdjustmentMapper;
@@ -54,6 +64,9 @@ import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
import org.springblade.transport.pojo.entity.FormalSettlementPayment;
import org.springblade.transport.pojo.entity.InvoiceReceipt;
import org.springblade.transport.pojo.entity.InvoiceReceiptSettlement;
+import org.springblade.transport.pojo.entity.InvoiceApplication;
+import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
+import org.springblade.transport.pojo.entity.InvoiceApplicationSettlement;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.entity.PreSettlementDetail;
@@ -71,14 +84,24 @@ import org.springblade.transport.pojo.vo.FormalSettlementVO;
import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.pojo.vo.SettlementAdjustmentVO;
import org.springblade.transport.service.IFormalSettlementService;
+import org.springblade.transport.service.IInvoiceReceiptService;
+import org.springblade.transport.service.IKingdeeBillLockService;
import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.service.IPreSettlementService;
import org.springblade.transport.service.IWaybillService;
+import org.springblade.transport.service.KingdeeSyncContext;
+import org.springblade.transport.support.KingdeeApBillSyncSupport;
+import org.springblade.transport.support.KingdeeArBillSyncSupport;
+import org.springblade.transport.support.KingdeeSyncLockSupport;
+import org.springblade.transport.support.KingdeeAttachmentSyncSupport;
import org.springblade.transport.wrapper.PreSettlementWrapper;
import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.transport.event.KingdeeBillApprovedEvent;
import org.springblade.transport.wrapper.FormalSettlementWrapper;
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
import org.springblade.system.cache.UserCache;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -88,6 +111,8 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -104,20 +129,31 @@ import java.util.stream.Collectors;
*/
@Service
@RequiredArgsConstructor
+@Slf4j
public class FormalSettlementServiceImpl extends BaseServiceImpl
implements IFormalSettlementService {
- private static final String DRAFT = "draft";
- private static final String REVIEWING = "reviewing";
- private static final String APPROVED = "approved";
- private static final String RETURNED = "returned";
- private static final String VOIDED = "voided";
+ /** 金蝶票池认领闸门(收票服务承载 A801-1 重查与可用性口径) */
+ private final IInvoiceReceiptService invoiceReceiptService;
+
+ /** 金蝶单据锁定记录服务(lockBusiData 锁定/补偿/解锁守卫) */
+ private final IKingdeeBillLockService kingdeeBillLockService;
+
+ /** 金蝶应收单勾稽 single-flight 锁键前缀(多副本定时任务与手动触发共用) */
+ private static final String AR_RESULT_SYNC_LOCK_PREFIX = "transport:formal-settlement:kingdee-ar-result-sync:";
+
+ /** 金蝶应收单勾稽结算明细分批查询单次条数上限(in 条件分批,避免单次过滤条件过长) */
+ private static final int AR_RESULT_DETAIL_QUERY_BATCH = 500;
+
private final FormalSettlementSourceMapper sourceMapper;
private final FormalSettlementSummaryFeeMapper summaryFeeMapper;
private final FormalSettlementPaymentMapper paymentMapper;
private final FormalSettlementInvoiceMapper invoiceMapper;
private final InvoiceReceiptMapper invoiceReceiptMapper;
private final InvoiceReceiptSettlementMapper invoiceReceiptSettlementMapper;
+ private final InvoiceApplicationMapper invoiceApplicationMapper;
+ private final InvoiceApplicationInvoiceMapper invoiceApplicationInvoiceMapper;
+ private final InvoiceApplicationSettlementMapper invoiceApplicationSettlementMapper;
private final FormalSettlementChangeRecordMapper changeRecordMapper;
private final FormalSettlementDetailMapper detailMapper;
private final FormalSettlementDetailFeeMapper detailFeeMapper;
@@ -135,6 +171,12 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl selectPage(IPage page, FormalSettlementVO query) {
@@ -163,13 +205,66 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl result = page(page, wrapper.orderByDesc(FormalSettlement::getCreateTime));
- return result.convert(this::toVO);
+ IPage voPage = result.convert(this::toVO);
+ fillAttachmentSyncStatus(voPage.getRecords());
+ fillKingdeeLockStatus(voPage.getRecords());
+ return voPage;
+ }
+
+ /**
+ * 填充金蝶锁状态标识(locked-锁定中/failed-锁定失败;unlocked 终态不返回):
+ * 锁表按本页源单一次批量查询,避免 N+1;locked 时前端可禁用编辑/作废,
+ * failed 时展示失败原因(tryCount 达上限 5 提示重推同步金蝶)
+ *
+ * @param rows 本页 VO 列表
+ */
+ private void fillKingdeeLockStatus(List rows) {
+ if (Func.isEmpty(rows)) {
+ return;
+ }
+ Map lockMap = kingdeeBillLockService.mapActiveLocks(KingdeeLockConstant.SOURCE_SETTLEMENT,
+ rows.stream().map(FormalSettlementVO::getId).toList());
+ for (FormalSettlementVO vo : rows) {
+ KingdeeBillLock lockRecord = lockMap.get(vo.getId());
+ if (lockRecord == null) {
+ continue;
+ }
+ vo.setKingdeeLockStatus(lockRecord.getLockStatus());
+ vo.setKingdeeLockFailReason(lockRecord.getFailReason());
+ vo.setKingdeeTryCount(lockRecord.getTryCount());
+ }
+ }
+
+ /**
+ * 填充附件同步聚合态(无附件/未上传/部分未传/已上传):按结算类型分业务类型一次 GROUP BY 查询,避免 N+1
+ *
+ * @param rows 本页 VO 列表
+ */
+ private void fillAttachmentSyncStatus(List rows) {
+ if (Func.isEmpty(rows)) {
+ return;
+ }
+ Map apSuccessCounts = kingdeeAttachmentSyncSupport.successCountByBizId(
+ KingdeeAttachmentConstant.BIZ_FORMAL_SETTLEMENT_AP, rows.stream()
+ .filter(row -> !"receivable".equals(row.getSettlementType())).map(FormalSettlementVO::getId).toList());
+ Map arSuccessCounts = kingdeeAttachmentSyncSupport.successCountByBizId(
+ KingdeeAttachmentConstant.BIZ_FORMAL_SETTLEMENT_AR, rows.stream()
+ .filter(row -> "receivable".equals(row.getSettlementType())).map(FormalSettlementVO::getId).toList());
+ for (FormalSettlementVO vo : rows) {
+ int attachmentCount = kingdeeAttachmentSyncSupport.countAttachments(vo.getAttachmentsJson());
+ Map successCounts = "receivable".equals(vo.getSettlementType()) ? arSuccessCounts : apSuccessCounts;
+ String aggregateStatus = kingdeeAttachmentSyncSupport.aggregateStatus(attachmentCount,
+ successCounts.getOrDefault(vo.getId(), 0));
+ vo.setAttachmentCount(attachmentCount);
+ vo.setAttachmentSyncStatus(aggregateStatus);
+ vo.setAttachmentSyncStatusName(KingdeeAttachmentConstant.aggregateName(aggregateStatus));
+ }
}
@Override
public IPage candidatePreSettlements(IPage page, PreSettlementVO query) {
IPage result = preSettlementMapper.selectPage(page, Wrappers.lambdaQuery()
- .eq(PreSettlement::getApprovalStatus, APPROVED)
+ .eq(PreSettlement::getApprovalStatus, TransportBusiConstant.APPROVED)
.and(w -> w.isNull(PreSettlement::getFormalSettlementNo).or().eq(PreSettlement::getFormalSettlementNo, ""))
.eq(query.getContractId() != null, PreSettlement::getContractId, query.getContractId())
.like(Func.isNotEmpty(query.getPreSettlementNo()), PreSettlement::getPreSettlementNo, query.getPreSettlementNo())
@@ -208,6 +303,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl sources = sourceMapper.selectList(Wrappers.lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, id).orderByAsc(FormalSettlementSource::getCreateTime));
vo.setSources(sources);
@@ -219,6 +315,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpllambdaQuery()
.eq(FormalSettlementPayment::getFormalSettlementId, id).orderByDesc(FormalSettlementPayment::getCreateTime)));
vo.setInvoices(listInvoices(settlement));
+ vo.setIssuedInvoices(listIssuedInvoices(id));
Set relationApplicationIds = paymentApplicationSettlementMapper.selectList(
Wrappers.lambdaQuery()
.select(PaymentApplicationSettlement::getPaymentApplicationId)
@@ -233,7 +330,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl wrapper.in(PaymentApplication::getId, relationApplicationIds));
}
})
- .eq(PaymentApplication::getPaymentType, "settlement_payment")
+ .eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
.eq(PaymentApplication::getIsDeleted, 0);
vo.setPaymentApplications(paymentApplicationMapper.selectList(paymentApplicationQuery
.orderByDesc(PaymentApplication::getCreateTime)).stream()
@@ -304,7 +401,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl receiptMap = receiptIds.isEmpty() ? Map.of()
: invoiceReceiptMapper.selectByIds(receiptIds).stream()
- .filter(receipt -> !VOIDED.equals(receipt.getApprovalStatus())
+ .filter(receipt -> !TransportBusiConstant.VOIDED.equals(receipt.getApprovalStatus())
&& !Objects.equals(receipt.getIsDeleted(), 1))
.collect(Collectors.toMap(InvoiceReceipt::getId, Function.identity()));
for (InvoiceReceiptSettlement relation : receiptRelations) {
@@ -336,6 +433,31 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl listIssuedInvoices(Long settlementId) {
+ List applicationIds = invoiceApplicationSettlementMapper.selectList(
+ Wrappers.lambdaQuery()
+ .select(InvoiceApplicationSettlement::getInvoiceApplicationId)
+ .eq(InvoiceApplicationSettlement::getFormalSettlementId, settlementId)
+ ).stream().map(InvoiceApplicationSettlement::getInvoiceApplicationId)
+ .filter(Objects::nonNull).distinct().toList();
+ if (applicationIds.isEmpty()) return List.of();
+ Set activeApplicationIds = invoiceApplicationMapper.selectBatchIds(applicationIds).stream()
+ .filter(application -> !TransportBusiConstant.VOIDED.equals(application.getApprovalStatus())
+ && !Objects.equals(application.getIsDeleted(), 1))
+ .map(InvoiceApplication::getId).collect(Collectors.toSet());
+ if (activeApplicationIds.isEmpty()) return List.of();
+ return invoiceApplicationInvoiceMapper.selectList(
+ Wrappers.lambdaQuery()
+ .in(InvoiceApplicationInvoice::getInvoiceApplicationId, activeApplicationIds)
+ .orderByAsc(InvoiceApplicationInvoice::getInvoiceApplicationId)
+ .orderByAsc(InvoiceApplicationInvoice::getCreateTime));
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public Long saveDraft(FormalSettlementSaveRequest request) {
@@ -387,7 +509,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpllambdaUpdate()
+ .eq(FormalSettlement::getId, id)
+ .set(FormalSettlement::getApprovalStatus, TransportBusiConstant.VOIDED)
+ .set(FormalSettlement::getCurrentNode, "已作废")
+ .set(FormalSettlement::getCurrentProcessor, operator)
+ .set(FormalSettlement::getVoidReason, "金蝶侧删单回调自动废除")
+ .set(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_UNSYNCED)
+ .set(FormalSettlement::getKingdeeBillNo, null)
+ .set(FormalSettlement::getKingdeeBillId, null)
+ .set(FormalSettlement::getSyncedTime, null));
+ // 变更流水:回调无登录态,操作人取金蝶报文(兜底 system,与 saveChange 同款)
+ FormalSettlementChangeRecord record = new FormalSettlementChangeRecord();
+ record.setFormalSettlementId(id);
+ record.setChangeType("结算单基本信息");
+ record.setOperationType("调整");
+ record.setChangeContent("金蝶删单回调自动废除并还原同步信息(金蝶单据已删除,可还原重推)");
+ record.setChangeReason("金蝶操作人:" + Func.toStr(operator));
+ record.setOperatorName(Func.isEmpty(operator) ? "system" : operator);
+ record.setChangeTime(LocalDateTime.now());
+ changeRecordMapper.insert(record);
+ }
+
+ @Override
+ public String syncKingdee(Long id) {
+ String billNo;
+ try {
+ // 单据级事务经自代理调用:单条失败只回滚自身
+ billNo = SpringUtil.getBean(IFormalSettlementService.class).doSyncKingdee(id);
+ } catch (Exception exception) {
+ log.error("正式结算单[{}]推送金蝶异常", id, exception);
+ // 推送事务已回滚、行锁已释放,此处独立事务落"生成失败"状态(与付款/收票口径统一,失败可被重推任务续推)
+ self().markKingdeeSyncFailed(id, exception.getMessage());
+ throw exception;
+ }
+ // 主单事务提交后锁定金蝶单据(lockBusiData):失败只留 failed 锁记录由补偿任务重锁,不污染主单同步状态;
+ // 锁定失败摘要拼入返回值,页面直接可见
+ String lockFailSummary = lockKingdeeBillAfterPush(id, billNo);
+ // 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
+ syncAttachmentsAfterPush(id);
+ return Func.isBlank(lockFailSummary) ? billNo
+ : billNo + ";注意:金蝶单据锁定未成功(" + lockFailSummary + "),系统将自动补偿重锁,重推同步金蝶可立即补锁";
+ }
+
+ /**
+ * 金蝶单据锁定(lockBusiData):A102-2/A302-2 推送成功事务提交后调用,目标单据类型按结算类型路由;
+ * 锁定异常独立捕获,不影响主单同步结果
+ *
+ * @param id 正式结算单id
+ * @param billNo 金蝶单据编号
+ * @return 锁定失败的金蝶单号摘要,全部锁定成功返回 null
+ */
+ private String lockKingdeeBillAfterPush(Long id, String billNo) {
+ try {
+ if (Func.isBlank(billNo)) {
+ return null;
+ }
+ FormalSettlement settlement = getById(id);
+ if (settlement == null) {
+ return null;
+ }
+ return kingdeeBillLockService.lockAfterPush(KingdeeLockConstant.SOURCE_SETTLEMENT, id, settlement.getFormalSettlementNo(),
+ "receivable".equals(settlement.getSettlementType())
+ ? KingdeeLockConstant.TARGET_AR_BILL : KingdeeLockConstant.TARGET_AP_BILL,
+ Func.toStrList(billNo));
+ } catch (Exception exception) {
+ log.error("正式结算单[{}]金蝶单据锁定联带动作异常(锁记录已落库待补偿)", id, exception);
+ return "锁定动作异常,已留档待补偿";
+ }
+ }
+
+ /** 自代理:跨方法调用确保事务注解生效(失败状态独立事务) */
+ private IFormalSettlementService self() {
+ return SpringUtil.getBean(IFormalSettlementService.class);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void markKingdeeSyncFailed(Long id, String reason) {
+ if (id == null) return;
+ // 条件更新:仅审批通过且未同步的单据落"生成失败"状态,未审批/已同步/已删除单据自动跳过保持原状态
+ int updated = baseMapper.update(null, Wrappers.lambdaUpdate()
+ .eq(FormalSettlement::getId, id)
+ .eq(FormalSettlement::getApprovalStatus, TransportBusiConstant.APPROVED)
+ .ne(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED)
+ .set(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED));
+ if (updated > 0) {
+ // 失败原因写变更记录留档(推送事务已回滚,此处独立事务);原因过长截断留档,不抛错
+ String failReason = Func.toStrWithEmpty(reason, "未知异常");
+ saveChange(id, "结算单基本信息", null, "调整",
+ "同步金蝶失败:" + (failReason.length() > 200 ? failReason.substring(0, 200) : failReason), null);
+ }
+ }
+
+ @Override
+ public String retryKingdeeFailed() {
+ List failedIds = list(Wrappers.lambdaQuery()
+ .eq(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED)
+ .eq(FormalSettlement::getApprovalStatus, TransportBusiConstant.APPROVED)
+ .orderByAsc(FormalSettlement::getCreateTime))
+ .stream().map(FormalSettlement::getId).toList();
+ return executeKingdeeRetry(failedIds);
+ }
+
+ /**
+ * 逐单重推失败单据:单条失败独立 try/catch 不中断整批,返回执行摘要。
+ * 推送前预检(A102-4/A302-4 幂等回填)保证重推不重复生成金蝶单据。
+ *
+ * @param failedIds 失败单据id集合
+ * @return 执行摘要
+ */
+ private String executeKingdeeRetry(List failedIds) {
+ if (Func.isEmpty(failedIds)) {
+ return "无生成失败单据";
+ }
+ int success = 0;
+ List failures = new ArrayList<>();
+ for (Long id : failedIds) {
+ try {
+ syncKingdee(id);
+ success++;
+ } catch (Exception exception) {
+ failures.add(id + ":" + exception.getMessage());
+ }
+ }
+ String summary = "重推生成失败单据:本批 " + failedIds.size() + " 条,成功 " + success + " 条";
+ if (!failures.isEmpty()) {
+ summary = summary + ",失败 " + failures.size() + " 条:" + String.join(";", failures);
+ }
+ return summary;
+ }
+
+ /**
+ * 附件联带上传(A902):主单推送成功(含幂等预检命中)后调用,以本地 kingdeeBillId 定位金蝶单据;
+ * 本地缺失(存量数据/幂等跳过路径未带回内码)时经 A102-4/A302-4 按防重键反查回填;
+ * 附件上传异常独立捕获,绝不影响主单同步结果
+ *
+ * @param id 正式结算单id
+ */
+ private void syncAttachmentsAfterPush(Long id) {
+ try {
+ FormalSettlement settlement = getById(id);
+ if (settlement == null || !"synced".equals(settlement.getKingdeeSyncStatus())) {
+ return;
+ }
+ String billPkId = settlement.getKingdeeBillId();
+ if (Func.isBlank(billPkId)) {
+ billPkId = resolveKingdeeBillId(settlement);
+ if (Func.isNotBlank(billPkId)) {
+ log.info("正式结算单[{}]经 A102-4/A302-4 反查回填星瀚单据内码:{}", settlement.getFormalSettlementNo(), billPkId);
+ FormalSettlement patch = new FormalSettlement();
+ patch.setId(settlement.getId());
+ patch.setKingdeeBillId(billPkId);
+ updateById(patch);
+ }
+ }
+ if (Func.isBlank(billPkId)) {
+ log.warn("正式结算单[{}]金蝶附件联带上传跳过:星瀚单据内码未落库且反查未命中,待重推补传",
+ settlement.getFormalSettlementNo());
+ return;
+ }
+ String bizType = "receivable".equals(settlement.getSettlementType())
+ ? KingdeeAttachmentConstant.BIZ_FORMAL_SETTLEMENT_AR : KingdeeAttachmentConstant.BIZ_FORMAL_SETTLEMENT_AP;
+ kingdeeAttachmentSyncSupport.syncUserAttachments(bizType, id, billPkId, settlement.getAttachmentsJson());
+ } catch (Exception exception) {
+ log.error("正式结算单[{}]金蝶附件联带上传异常(不影响主单同步状态,重推自动补传)", id, exception);
+ }
+ }
+
+ /**
+ * 反查星瀚单据内码:按结算类型组装防重键(与 A102-2/A302-2 传参一致)后调用对应查询接口
+ *
+ * @param settlement 正式结算单
+ * @return 星瀚单据内码,未查得返回 null
+ */
+ private String resolveKingdeeBillId(FormalSettlement settlement) {
+ List details = detailMapper.selectList(Wrappers.lambdaQuery()
+ .eq(FormalSettlementDetail::getFormalSettlementId, settlement.getId())
+ .eq(FormalSettlementDetail::getIsDeleted, 0)
+ .orderByAsc(FormalSettlementDetail::getLineNo));
+ if (Func.isEmpty(details)) {
+ return null;
+ }
+ if ("receivable".equals(settlement.getSettlementType())) {
+ return kingdeeArBillSyncSupport.queryArBillExistId(kingdeeArBillSyncSupport.arBillSrcCode(settlement, details));
+ }
+ return kingdeeApBillSyncSupport.queryApBillExistId(kingdeeApBillSyncSupport.apBillSrcCode(settlement, details));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String doSyncKingdee(Long id) {
+ FormalSettlement settlement = existing(id);
+ if (!TransportBusiConstant.APPROVED.equals(settlement.getApprovalStatus())) throw new ServiceException("仅审批通过的正式结算单允许同步金蝶");
+ // 已同步幂等返回(含历史由付款申请同步联动回写的存量已同步数据);星瀚内码由附件补传分支反查回填
if ("synced".equals(settlement.getKingdeeSyncStatus())) return settlement.getKingdeeBillNo();
- String kingdeeNo = "K3AP" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
- settlement.setKingdeeBillNo(kingdeeNo);
- settlement.setKingdeeSyncStatus("synced");
+ // 流程拆分:正式结算单「同步金蝶」负责推送财务应付单(A102-2)并回写结算单单据编号;
+ // 付款申请「同步金蝶」仅负责付款申请单(A103-1 应付下推),推送前经 A102-4 预检本单应付单已生成
+ List details = detailMapper.selectList(Wrappers.lambdaQuery()
+ .eq(FormalSettlementDetail::getFormalSettlementId, id)
+ .eq(FormalSettlementDetail::getIsDeleted, 0)
+ .orderByAsc(FormalSettlementDetail::getLineNo));
+ if (Func.isEmpty(details)) {
+ throw new ServiceException("推送金蝶失败:正式结算单无结算明细,无法解析物料与计量单位");
+ }
+ KingdeeSyncContext context = KingdeeSyncContext.empty();
+ // 单据类型路由:应付对应财务应付单(A102-2),应收对应财务应收单(A302-2),两链路同一套幂等预检与推送逻辑
+ String billNo;
+ String billPkId;
+ String changeDesc;
+ if ("receivable".equals(settlement.getSettlementType())) {
+ // 应收结算:财务应收单防重键(awrp_src_code):正式结算单号_结算明细单据号下划线拼接
+ String arBillSrcCode = kingdeeArBillSyncSupport.arBillSrcCode(settlement, details);
+ List entryGroups = kingdeeArBillSyncSupport.resolveArEntryGroups(settlement, details, context);
+ KingdeeApBillSyncSupport.BfiApBillPushOutcome outcome = kingdeeArBillSyncSupport.pushArBill(
+ arBillSrcCode, settlement, entryGroups, settlement.getFormalSettlementNo(), context);
+ billNo = outcome.billNo();
+ billPkId = outcome.billId();
+ changeDesc = "同步金蝶财务应收单" + billNo;
+ } else {
+ // 应付结算:财务应付单防重键,付款申请「同步金蝶」(A103-1 应付下推)推送前按该键经 A102-4 预检本单应付单已生成
+ // 付款申请 A103-1 下推前的前置
+ String apBillSrcCode = kingdeeApBillSyncSupport.apBillSrcCode(settlement, details);
+ List entryGroups = kingdeeApBillSyncSupport.resolveEntryGroups(settlement, details, context);
+ KingdeeApBillSyncSupport.BfiApBillPushOutcome outcome = kingdeeApBillSyncSupport.pushApBill(
+ apBillSrcCode, settlement, entryGroups, settlement.getFormalSettlementNo(), context);
+ billNo = outcome.billNo();
+ billPkId = outcome.billId();
+ changeDesc = "同步金蝶财务应付单" + billNo;
+ }
+ // 回写金蝶单据编号(A102-2/A302-2 单据编号)、星瀚单据内码(A902 附件上传定位用)与同步状态;
+ // 付款申请侧回填的付款申请单编号(A103-2)不是同一单据
+ settlement.setKingdeeBillNo(billNo);
+ settlement.setKingdeeBillId(billPkId);
+ settlement.setKingdeeSyncStatus(KingdeeBfiConstant.KINGDEE_STATUS_SYNCED);
settlement.setSyncedTime(LocalDateTime.now());
updateById(settlement);
- saveChange(settlement.getId(), "结算单基本信息", null, "调整", "同步金蝶单据" + kingdeeNo, "");
- return kingdeeNo;
+ saveChange(settlement.getId(), "结算单基本信息", null, "调整", changeDesc, "");
+ log.info("正式结算单[{}]同步金蝶完成:settlementType={},billNo={},billPkId={}",
+ settlement.getFormalSettlementNo(), settlement.getSettlementType(), billNo, billPkId);
+ return billNo;
+ }
+
+ @Override
+ public String syncKingdeeArResult() {
+ return kingdeeSyncLockSupport.runSingleFlight(AR_RESULT_SYNC_LOCK_PREFIX, bfiProperties.getArResult().getLockLeaseSeconds(),
+ "金蝶应收单勾稽", this::executeArResultSync);
+ }
+
+ /**
+ * 金蝶应收单勾稽主体:A302-4 批量查询财务应收单回填单据编号、生成失败纠偏为已同步,
+ * 由 {@link #syncKingdeeArResult()} 在分布式锁保护下调用。
+ * 分批与事务说明:
+ * ① 结算明细按 {@link #AR_RESULT_DETAIL_QUERY_BATCH} 分批 in 查询后内存分组,消除逐单查询 N+1;
+ * ② 回写聚合后经 {@code updateBatchById} 批量落库——本方法为 this 自调用,MP 自带的
+ * {@code @Transactional} 不生效,但其 executeBatch 会独立开启 BATCH SqlSession 并在结束时统一提交、
+ * 异常整体回滚,等效单次原子写;③ 整轮任务不包外层事务(远程星瀚查询不占库连接,勾稽按防重键
+ * 幂等可重跑),private + lambda 自调用上补加 {@code @Transactional} 会静默失效,禁止添加。
+ *
+ * @return 执行摘要
+ */
+ private String executeArResultSync() {
+ long startMillis = System.currentTimeMillis();
+ // 待勾稽应收结算单:审批通过 + 结算类型应收 + 金蝶状态为 已同步/生成失败(已同步一并轮询为补齐单号,生成失败纳入勾稽纠偏:
+ // 金蝶侧已生成说明上次推送实际成功,查到即回填纠正为已同步,避免人工重新推送产生重复单据)
+ List settlements = list(Wrappers.lambdaQuery()
+ .eq(FormalSettlement::getApprovalStatus, TransportBusiConstant.APPROVED)
+ .eq(FormalSettlement::getSettlementType, "receivable")
+ .in(FormalSettlement::getKingdeeSyncStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED,
+ KingdeeBfiConstant.KINGDEE_STATUS_FAILED)
+ .orderByAsc(FormalSettlement::getId));
+ if (Func.isEmpty(settlements)) {
+ log.info("金蝶应收单勾稽:无待勾稽应收结算单");
+ return "金蝶应收单勾稽:无待勾稽应收结算单";
+ }
+ long syncedCount = settlements.stream()
+ .filter(item -> KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(item.getKingdeeSyncStatus())).count();
+ log.info("金蝶应收单勾稽开始:待勾稽应收结算单{}笔(已同步{}笔,生成失败{}笔)",
+ settlements.size(), syncedCount, settlements.size() - syncedCount);
+ // 结算明细分批一次查全(消除逐单查询 N+1),按结算单 id 分组
+ List settlementIds = settlements.stream().map(FormalSettlement::getId).toList();
+ Map> detailsBySettlement = new HashMap<>();
+ kingdeeApBillSyncSupport.batchForeach(settlementIds, AR_RESULT_DETAIL_QUERY_BATCH, batch ->
+ detailsBySettlement.putAll(detailMapper.selectList(Wrappers.lambdaQuery()
+ .in(FormalSettlementDetail::getFormalSettlementId, batch)
+ .eq(FormalSettlementDetail::getIsDeleted, 0)
+ .orderByAsc(FormalSettlementDetail::getLineNo)).stream()
+ .collect(Collectors.groupingBy(FormalSettlementDetail::getFormalSettlementId))));
+ // 组装防重键(与 A302-2 保存传参一致),同一防重键对应一张财务应收单
+ Map settlementBySrcCode = new LinkedHashMap<>();
+ for (FormalSettlement settlement : settlements) {
+ String srcCode = kingdeeArBillSyncSupport.arBillSrcCode(settlement,
+ detailsBySettlement.getOrDefault(settlement.getId(), List.of()));
+ if (Func.isNotBlank(srcCode)) {
+ settlementBySrcCode.putIfAbsent(srcCode, settlement);
+ }
+ }
+ // A302-4 批量查询:查到 → 回填金蝶单据编号(缺失时)、生成失败纠偏为已同步;查不到 → 状态保持不变
+ Map existBillNos = kingdeeArBillSyncSupport.queryArBillExistNos(new ArrayList<>(settlementBySrcCode.keySet()));
+ // 勾稽回写聚合后经 updateBatchById 批量更新,避免逐单 update
+ // (this 自调用下 MP 事务注解不生效,executeBatch 自开 BATCH SqlSession 统一提交、异常整体回滚,等效原子写)
+ List toUpdateSettlements = new ArrayList<>();
+ int corrected = 0;
+ for (Map.Entry entry : settlementBySrcCode.entrySet()) {
+ String billNo = existBillNos.get(entry.getKey());
+ if (Func.isBlank(billNo)) {
+ continue;
+ }
+ FormalSettlement settlement = entry.getValue();
+ boolean failed = KingdeeBfiConstant.KINGDEE_STATUS_FAILED.equals(settlement.getKingdeeSyncStatus());
+ if (!failed && Func.isNotBlank(settlement.getKingdeeBillNo())) {
+ continue;
+ }
+ if (failed) {
+ corrected++;
+ }
+ if (Func.isBlank(settlement.getKingdeeBillNo())) {
+ settlement.setKingdeeBillNo(billNo);
+ }
+ settlement.setKingdeeSyncStatus(KingdeeBfiConstant.KINGDEE_STATUS_SYNCED);
+ settlement.setSyncedTime(LocalDateTime.now());
+ toUpdateSettlements.add(settlement);
+ // 生成失败纠偏与缺失单号补齐逐笔留痕,便于对账核查
+ log.info("正式结算单[{}]金蝶应收单勾稽回写:{},billNo={}",
+ settlement.getFormalSettlementNo(), failed ? "生成失败纠偏为已同步" : "补齐单据编号",
+ settlement.getKingdeeBillNo());
+ }
+ if (Func.isNotEmpty(toUpdateSettlements)) {
+ updateBatchById(toUpdateSettlements);
+ log.info("金蝶应收单勾稽批量回写完成:本批更新{}笔", toUpdateSettlements.size());
+ }
+ String summary = "金蝶应收单勾稽完成:待查应收结算单" + settlementBySrcCode.size() + "笔,回写单据信息" + toUpdateSettlements.size()
+ + "笔(生成失败纠偏" + corrected + "笔)";
+ log.info("{},耗时{}ms", summary, System.currentTimeMillis() - startMillis);
+ return summary;
}
@Override
@@ -533,6 +986,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl 0) {
throw new ServiceException("发票匹配结算单金额合计不能超过结算金额");
}
@@ -594,12 +1049,12 @@ public class FormalSettlementServiceImpl extends BaseServiceImpllambdaQuery()
.eq(FormalSettlementPayment::getFormalSettlementId, settlement.getId())
.eq(FormalSettlementPayment::getIsDeleted, 0)
- .notIn(FormalSettlementPayment::getBillStatus, RETURNED, VOIDED));
+ .notIn(FormalSettlementPayment::getBillStatus, TransportBusiConstant.RETURNED, TransportBusiConstant.VOIDED));
if (activePaymentCount > 0) throw new ServiceException("该结算单正在申请结算尾款,不允许再次提交申请付款");
BigDecimal amount = positive(appliedAmount, "申请付款金额");
BigDecimal available = money(settlement.getSettlementAmount()).subtract(money(settlement.getAppliedPaymentAmount()));
@@ -610,7 +1065,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl settlementIds) {
+ List ids = settlementIds == null ? List.of() : settlementIds.stream()
+ .filter(Objects::nonNull).distinct().toList();
+ if (ids.isEmpty()) {
+ return;
+ }
+ // 结算单一次锁行查询替代逐单 selectOne(id 升序加锁,与逐单版加锁顺序一致防死锁)
+ List settlements = baseMapper.selectList(Wrappers.lambdaQuery()
+ .in(FormalSettlement::getId, ids)
+ .orderByAsc(FormalSettlement::getId)
+ .last("FOR UPDATE"));
+ if (settlements.isEmpty()) {
+ return;
+ }
+ List settledIds = settlements.stream().map(FormalSettlement::getId).toList();
+ boolean hasPayable = settlements.stream().anyMatch(item -> "payable".equals(item.getSettlementType()));
+ boolean hasReceivable = settlements.stream().anyMatch(item -> "receivable".equals(item.getSettlementType()));
+
+ // 结算-付款申请关系(全量)
+ Map> relationsBySettlement = paymentApplicationSettlementMapper.selectList(
+ Wrappers.lambdaQuery()
+ .in(PaymentApplicationSettlement::getFormalSettlementId, settledIds)
+ .eq(PaymentApplicationSettlement::getIsDeleted, 0)).stream()
+ .collect(Collectors.groupingBy(PaymentApplicationSettlement::getFormalSettlementId));
+ // 付款申请(按结算单直挂,payment_type=结算)
+ Map> applicationsBySettlement = paymentApplicationMapper.selectList(
+ Wrappers.lambdaQuery()
+ .in(PaymentApplication::getSettlementId, settledIds)
+ .eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT)
+ .eq(PaymentApplication::getIsDeleted, 0)).stream()
+ .collect(Collectors.groupingBy(PaymentApplication::getSettlementId));
+ // 关系表引用的付款申请状态(全量一次)
+ Set relationApplicationIds = relationsBySettlement.values().stream()
+ .flatMap(List::stream).map(PaymentApplicationSettlement::getPaymentApplicationId)
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ Map relationStatuses = relationApplicationIds.isEmpty() ? Map.of()
+ : paymentApplicationMapper.selectList(Wrappers.lambdaQuery()
+ .select(PaymentApplication::getId, PaymentApplication::getApprovalStatus)
+ .in(PaymentApplication::getId, relationApplicationIds)
+ .eq(PaymentApplication::getIsDeleted, 0)).stream()
+ .collect(Collectors.toMap(PaymentApplication::getId, PaymentApplication::getApprovalStatus,
+ (first, second) -> first));
+ // 预收来源(仅应付结算单需要)
+ Map