✨ 新增金蝶付款结果定时回写任务(A103-2/A103-4)并抽取BFI分页通用翻页工具
This commit is contained in:
+2
@@ -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 {
|
||||
|
||||
|
||||
+32
-2
@@ -66,13 +66,13 @@ public final class KingdeeBfiConstant {
|
||||
* A102-2 明细必填:物料编码。TMS 无物料档案,当前为占位空值,
|
||||
* 推送前会校验拦截。待确认:需金蝶侧提供运输服务类物料编码后补充。
|
||||
*/
|
||||
public static final String BFI_AP_BILL_MATERIAL = "";
|
||||
public static final String BFI_AP_BILL_MATERIAL = "GWZB01.003";
|
||||
|
||||
/**
|
||||
* A102-2 明细必填:计量单位编码。TMS 无对应档案,当前为占位空值,
|
||||
* 推送前会校验拦截。待确认:需金蝶侧确认编码(如"吨")后补充。
|
||||
*/
|
||||
public static final String BFI_AP_BILL_MEASURE_UNIT = "";
|
||||
public static final String BFI_AP_BILL_MEASURE_UNIT = "PCS";
|
||||
|
||||
/** A102-2 必填:外部单据类型(应付单来源标识)。待确认:取值需金蝶侧确认,确认后如有调整直接改此常量。 */
|
||||
public static final String BFI_AP_BILL_SRC_TYPE = "TMS财务应付单";
|
||||
@@ -105,6 +105,36 @@ public final class KingdeeBfiConstant {
|
||||
"其他付款申请", "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";
|
||||
|
||||
/** 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";
|
||||
|
||||
/**
|
||||
* 按中文名称从映射字典转换单据类型编码。
|
||||
*
|
||||
|
||||
+4
@@ -100,4 +100,8 @@ public class PaymentApplicationController extends BladeController {
|
||||
public R<List<String>> syncKingdeeBatch(@RequestBody List<Long> ids) {
|
||||
return R.data(paymentApplicationService.syncKingdeeBatch(ids));
|
||||
}
|
||||
@PostMapping("/sync-kingdee-result")
|
||||
@ApiOperationSupport(order = 12)
|
||||
@Operation(summary = "手动回写金蝶付款结果(A103-2/A103-4)")
|
||||
public R<String> syncKingdeeResult() { return R.data(paymentApplicationService.syncKingdeePaymentResult()); }
|
||||
}
|
||||
|
||||
+26
@@ -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;
|
||||
@@ -42,4 +44,28 @@ public interface IPaymentApplicationService extends BaseService<PaymentApplicati
|
||||
void voidBill(PaymentApplicationStatusRequest request);
|
||||
String syncKingdee(Long id);
|
||||
List<String> syncKingdeeBatch(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 定时回写金蝶付款结果(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);
|
||||
}
|
||||
|
||||
+350
-15
@@ -30,6 +30,7 @@ import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.SpringUtil;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.system.cache.DictBizCache;
|
||||
import org.springblade.system.cache.SysCache;
|
||||
@@ -39,11 +40,17 @@ import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillSaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillSaveEntryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyPushEntryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplyQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplySaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayApplySaveEntryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiPayBillQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultItemVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiPayApplyEntryVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiPayApplyVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiPayBillVO;
|
||||
import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.thirdparty.bfi.util.BfiUtil;
|
||||
import org.springblade.transport.mapper.FormalSettlementMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementPaymentMapper;
|
||||
import org.springblade.transport.mapper.BillLedgerMapper;
|
||||
@@ -93,7 +100,10 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -108,6 +118,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
private static final String APPROVED = "approved";
|
||||
private static final String RETURNED = "returned";
|
||||
private static final String VOIDED = "voided";
|
||||
/** A103-2 按 awrp_src_code 批量查询单次条数上限 */
|
||||
private static final int PAY_RESULT_QUERY_BATCH = 50;
|
||||
/** A103-2 / A103-4 分页查询页大小 */
|
||||
private static final int PAY_RESULT_PAGE_SIZE = 200;
|
||||
/** 历史模拟付款记录单号前缀 */
|
||||
private static final String MOCK_PAYMENT_RECORD_PREFIX = "MOCK-PAY-";
|
||||
private final PaymentApplicationInvoiceMapper invoiceMapper;
|
||||
private final PaymentApplicationRecordMapper recordMapper;
|
||||
private final PaymentApplicationSettlementMapper settlementRelationMapper;
|
||||
@@ -212,7 +228,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entity.setPaymentNo(nextNo());
|
||||
entity.setApprovalStatus(DRAFT);
|
||||
entity.setCurrentNode("草稿");
|
||||
entity.setKingdeeStatus("unsynced");
|
||||
entity.setKingdeeStatus(KingdeeBfiConstant.KINGDEE_STATUS_UNSYNCED);
|
||||
entity.setPaidAmount(BigDecimal.ZERO);
|
||||
entity.setInvoiceStatus("unmatched");
|
||||
}
|
||||
@@ -330,7 +346,13 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
public String syncKingdee(Long id) {
|
||||
PaymentApplication entity = lockedPayment(id);
|
||||
if (!APPROVED.equals(entity.getApprovalStatus())) throw new ServiceException("仅审批通过的付款申请允许生成金蝶单据");
|
||||
if ("synced".equals(entity.getKingdeeStatus())) return entity.getKingdeeBillNo();
|
||||
if (KingdeeBfiConstant.KINGDEE_STATUS_SYNCED.equals(entity.getKingdeeStatus())
|
||||
|| KingdeeBfiConstant.KINGDEE_STATUS_PAYING.equals(entity.getKingdeeStatus())
|
||||
|| KingdeeBfiConstant.KINGDEE_STATUS_PAID.equals(entity.getKingdeeStatus())
|
||||
|| KingdeeBfiConstant.KINGDEE_STATUS_CLOSED.equals(entity.getKingdeeStatus())) {
|
||||
// 存量数据金蝶单据id可能仍存于 kingdeeBillNo,兜底返回
|
||||
return Func.isNotEmpty(entity.getKingdeeBillId()) ? entity.getKingdeeBillId() : entity.getKingdeeBillNo();
|
||||
}
|
||||
String kingdeeBillId;
|
||||
try {
|
||||
if ("settlement_payment".equals(entity.getPaymentType())) {
|
||||
@@ -352,13 +374,17 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
markSyncFailed(entity);
|
||||
throw new ServiceException("推送金蝶付款申请失败:" + exception.getMessage());
|
||||
}
|
||||
entity.setKingdeeBillNo(kingdeeBillId);
|
||||
entity.setKingdeeStatus("synced");
|
||||
entity.setPaidAmount(money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP));
|
||||
mockPaymentRecords(entity);
|
||||
// 星瀚单据内部主键落 kingdeeBillId(附件上传等使用);
|
||||
// 金蝶单据编号(billno)与付款状态由定时回写任务经 A103-2/A103-4 查询回填
|
||||
entity.setKingdeeBillId(kingdeeBillId);
|
||||
entity.setKingdeeStatus(KingdeeBfiConstant.KINGDEE_STATUS_SYNCED);
|
||||
// 付款完成模拟逻辑已停用(mockPaymentRecords / 履约付款数据直接置 paid),
|
||||
// 真实付款结果改由定时回写任务驱动;联调环境需要模拟时恢复以下注释即可
|
||||
// entity.setPaidAmount(money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP));
|
||||
// mockPaymentRecords(entity);
|
||||
// syncPreSettlementAdvance(entity);
|
||||
// syncFormalSettlementPayments(entity);
|
||||
updateById(entity);
|
||||
syncPreSettlementAdvance(entity);
|
||||
syncFormalSettlementPayments(entity);
|
||||
return kingdeeBillId;
|
||||
}
|
||||
|
||||
@@ -379,6 +405,282 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String syncKingdeePaymentResult() {
|
||||
// ① 待回写付款申请:审批通过且金蝶状态为 已生成/付款中(终态已付款/已关闭不再轮询)
|
||||
List<PaymentApplication> applications = list(Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.eq(PaymentApplication::getApprovalStatus, APPROVED)
|
||||
.in(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED, KingdeeBfiConstant.KINGDEE_STATUS_PAYING)
|
||||
.orderByAsc(PaymentApplication::getId));
|
||||
IPaymentApplicationService self = SpringUtil.getBean(IPaymentApplicationService.class);
|
||||
int updated = 0;
|
||||
int failed = 0;
|
||||
int paidCount = 0;
|
||||
Map<String, BfiPayApplyVO> payApplyMap = new HashMap<>();
|
||||
if (!applications.isEmpty()) {
|
||||
// ② A103-2 付款申请单查询:按 paymentNo(awrp_src_code)分批查询,同一来源单号取最近修改的一条
|
||||
List<String> paymentNos = applications.stream()
|
||||
.map(PaymentApplication::getPaymentNo).filter(Func::isNotEmpty).distinct().toList();
|
||||
for (int from = 0; from < paymentNos.size(); from += PAY_RESULT_QUERY_BATCH) {
|
||||
List<String> batch = paymentNos.subList(from, Math.min(from + PAY_RESULT_QUERY_BATCH, paymentNos.size()));
|
||||
for (BfiPayApplyVO payApply : queryPayApplyPages(batch)) {
|
||||
if (Func.isEmpty(payApply.getAwrpSrcCode())) {
|
||||
continue;
|
||||
}
|
||||
BfiPayApplyVO existing = payApplyMap.get(payApply.getAwrpSrcCode());
|
||||
if (existing == null || strCompare(payApply.getModifytime(), existing.getModifytime()) > 0) {
|
||||
payApplyMap.put(payApply.getAwrpSrcCode(), payApply);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (PaymentApplication application : applications) {
|
||||
BfiPayApplyVO payApply = payApplyMap.get(application.getPaymentNo());
|
||||
if (payApply == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (self.applyPayApplyResult(application, payApply)) paidCount++;
|
||||
updated++;
|
||||
} catch (Exception exception) {
|
||||
failed++;
|
||||
log.error("付款申请[{}]回写金蝶付款申请单结果异常", application.getPaymentNo(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
// ③ A103-4 付款处理查询:付款中/已付款单据 + 已付款但尚无付款记录的单据(补偿),回填付款记录
|
||||
Map<Long, PaymentApplication> recordCandidates = new LinkedHashMap<>();
|
||||
for (PaymentApplication application : applications) {
|
||||
BfiPayApplyVO payApply = payApplyMap.get(application.getPaymentNo());
|
||||
if (payApply != null && !KingdeeBfiConstant.BFI_PAY_STATUS_UNPAID.equals(payApply.getPaystatus())) {
|
||||
recordCandidates.put(application.getId(), application);
|
||||
}
|
||||
}
|
||||
list(Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.eq(PaymentApplication::getApprovalStatus, APPROVED)
|
||||
.eq(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_PAID))
|
||||
.stream().filter(application -> realRecordCount(application.getId()) == 0)
|
||||
.forEach(application -> recordCandidates.putIfAbsent(application.getId(), application));
|
||||
int recordWritten = 0;
|
||||
int recordFailed = 0;
|
||||
if (!recordCandidates.isEmpty()) {
|
||||
Map<String, List<PaymentApplication>> orgGroups = new LinkedHashMap<>();
|
||||
for (PaymentApplication application : recordCandidates.values()) {
|
||||
BfiPayApplyVO payApply = payApplyMap.get(application.getPaymentNo());
|
||||
String orgNumber = payApply == null ? null : payApply.getPayorgNumber();
|
||||
if (Func.isBlank(orgNumber)) {
|
||||
try {
|
||||
// A103-4 付款人(公司)编码必填:优先取 A103-2 返回付款组织编码,兜底按合同所属组织解析
|
||||
orgNumber = bfiOrgNumber(requireContract(application));
|
||||
} catch (Exception exception) {
|
||||
log.warn("付款申请[{}]付款处理查询跳过,付款组织编码获取失败:{}", application.getPaymentNo(), exception.getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
orgGroups.computeIfAbsent(orgNumber, key -> new ArrayList<>()).add(application);
|
||||
}
|
||||
// 以付款处理单源单id/源单编号建立索引,与本地金蝶单据id/单据编号勾稽
|
||||
Map<String, BfiPayBillVO> payBillBySourceId = new HashMap<>();
|
||||
Map<String, BfiPayBillVO> payBillBySourceNumber = new HashMap<>();
|
||||
for (String orgNumber : orgGroups.keySet()) {
|
||||
for (BfiPayBillVO payBill : queryPayBillPages(orgNumber)) {
|
||||
if (Func.isEmpty(payBill.getBillno())) continue;
|
||||
if (payBill.getSourcebillid() != null) {
|
||||
payBillBySourceId.put(String.valueOf(payBill.getSourcebillid()), payBill);
|
||||
}
|
||||
if (Func.isNotEmpty(payBill.getSourcebillnumber())) {
|
||||
payBillBySourceNumber.put(payBill.getSourcebillnumber(), payBill);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (PaymentApplication application : recordCandidates.values()) {
|
||||
BfiPayBillVO payBill = matchPayBill(application, payBillBySourceId, payBillBySourceNumber);
|
||||
if (payBill == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
self.applyPayBillRecord(application, payBill);
|
||||
recordWritten++;
|
||||
} catch (Exception exception) {
|
||||
recordFailed++;
|
||||
log.error("付款申请[{}]回填金蝶付款处理记录异常", application.getPaymentNo(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (applications.isEmpty() && recordCandidates.isEmpty()) {
|
||||
return "金蝶付款结果回写:无待回写付款申请";
|
||||
}
|
||||
return "金蝶付款结果回写完成:待查付款申请" + applications.size() + "笔,回写单据信息" + updated
|
||||
+ "笔(付款完成" + paidCount + "笔,失败" + failed + "笔),回填付款记录" + recordWritten + "条(失败" + recordFailed + "条)";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean applyPayApplyResult(PaymentApplication entity, BfiPayApplyVO payApply) {
|
||||
if (Func.isNotEmpty(payApply.getBillno())) {
|
||||
entity.setKingdeeBillNo(payApply.getBillno());
|
||||
}
|
||||
if (payApply.getId() != null) {
|
||||
entity.setKingdeeBillId(String.valueOf(payApply.getId()));
|
||||
}
|
||||
String kingdeeStatus = kingdeeResultStatus(payApply);
|
||||
entity.setKingdeeStatus(kingdeeStatus);
|
||||
// 已付金额:A103-2 分录 e_paidamt 汇总;分录缺失且已付款时兜底取申请金额
|
||||
if (Func.isNotEmpty(payApply.getEntry())) {
|
||||
BigDecimal paidAmount = payApply.getEntry().stream()
|
||||
.map(BfiPayApplyEntryVO::getEPaidamt).filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
entity.setPaidAmount(paidAmount.setScale(2, RoundingMode.HALF_UP));
|
||||
} else if (KingdeeBfiConstant.KINGDEE_STATUS_PAID.equals(kingdeeStatus)) {
|
||||
entity.setPaidAmount(money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
updateById(entity);
|
||||
if (KingdeeBfiConstant.KINGDEE_STATUS_PAID.equals(kingdeeStatus)) {
|
||||
// 付款完成:联动履约付款数据(预结算预付记录、正式结算付款记录)
|
||||
syncPreSettlementAdvance(entity);
|
||||
syncFormalSettlementPayments(entity);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void applyPayBillRecord(PaymentApplication entity, BfiPayBillVO payBill) {
|
||||
// 清理历史模拟付款记录,真实付款记录以金蝶付款处理单为准
|
||||
recordMapper.delete(Wrappers.<PaymentApplicationRecord>lambdaQuery()
|
||||
.eq(PaymentApplicationRecord::getPaymentApplicationId, entity.getId())
|
||||
.likeRight(PaymentApplicationRecord::getPaymentNo, MOCK_PAYMENT_RECORD_PREFIX));
|
||||
String billNo = payBill.getBillno();
|
||||
PaymentApplicationRecord record = recordMapper.selectList(Wrappers.<PaymentApplicationRecord>lambdaQuery()
|
||||
.eq(PaymentApplicationRecord::getPaymentApplicationId, entity.getId())
|
||||
.eq(Func.isNotEmpty(billNo), PaymentApplicationRecord::getPaymentNo, billNo)
|
||||
.last("limit 1")).stream().findFirst().orElse(null);
|
||||
boolean isNew = record == null;
|
||||
if (isNew) {
|
||||
record = new PaymentApplicationRecord();
|
||||
record.setPaymentApplicationId(entity.getId());
|
||||
record.setPaymentNo(billNo);
|
||||
}
|
||||
if (payBill.getActpayamt() != null) {
|
||||
record.setPaidAmount(payBill.getActpayamt().setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
record.setPaidDate(parseKingdeeDate(payBill.getPaydate()));
|
||||
record.setKingdeeBillNo(billNo);
|
||||
// 付款处理单查询结果整体留档(凭证号、银行流水、电子回单等信息),便于追溯
|
||||
record.setVoucherJson(JsonUtil.toJson(payBill));
|
||||
if (isNew) {
|
||||
recordMapper.insert(record);
|
||||
} else {
|
||||
recordMapper.updateById(record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A103-2 返回状态映射本地金蝶同步状态:
|
||||
* paystatus=Alreadypay 已付款(终态)优先;billstatus=D 已关闭(终态)次之;
|
||||
* paystatus=Inpayment 付款中;其余(A暂存/B已提交/C已审核 + Unpaid)保持已生成继续轮询
|
||||
*
|
||||
* @param payApply A103-2 查询返回的付款申请单
|
||||
* @return 本地金蝶同步状态
|
||||
*/
|
||||
private String kingdeeResultStatus(BfiPayApplyVO payApply) {
|
||||
if (KingdeeBfiConstant.BFI_PAY_STATUS_ALREADY_PAY.equals(payApply.getPaystatus())) {
|
||||
return KingdeeBfiConstant.KINGDEE_STATUS_PAID;
|
||||
}
|
||||
if (KingdeeBfiConstant.BFI_BILL_STATUS_CLOSED.equals(payApply.getBillstatus())) {
|
||||
return KingdeeBfiConstant.KINGDEE_STATUS_CLOSED;
|
||||
}
|
||||
if (KingdeeBfiConstant.BFI_PAY_STATUS_IN_PAYMENT.equals(payApply.getPaystatus())) {
|
||||
return KingdeeBfiConstant.KINGDEE_STATUS_PAYING;
|
||||
}
|
||||
return KingdeeBfiConstant.KINGDEE_STATUS_SYNCED;
|
||||
}
|
||||
|
||||
/**
|
||||
* A103-2 分页查询:来源单据号批量 + 外部系统编号(防重键),自动翻页
|
||||
*
|
||||
* @param awrpSrcCodes 来源单据号(付款申请单号)批次
|
||||
* @return 付款申请单列表
|
||||
*/
|
||||
private List<BfiPayApplyVO> queryPayApplyPages(List<String> awrpSrcCodes) {
|
||||
BfiPayApplyQueryDTO param = new BfiPayApplyQueryDTO();
|
||||
param.setAwrpSrcCode(awrpSrcCodes);
|
||||
param.setAwrpSrcSys(bfiPayApplySrcSys);
|
||||
return BfiUtil.queryAllPages(pageNo -> {
|
||||
param.setPageNo(pageNo);
|
||||
param.setPageSize(PAY_RESULT_PAGE_SIZE);
|
||||
return bfiService.queryPayApply(param);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A103-4 分页查询:按付款人(公司)编码 + 最近1个月业务日期窗口(接口限制不超过1个月),自动翻页
|
||||
*
|
||||
* @param orgNumber 付款人(公司)编码
|
||||
* @return 付款处理单列表
|
||||
*/
|
||||
private List<BfiPayBillVO> queryPayBillPages(String orgNumber) {
|
||||
LocalDate endDate = LocalDate.now();
|
||||
// 业务日期窗口控制在不超过1个月
|
||||
LocalDate beginDate = endDate.minusMonths(1).plusDays(1);
|
||||
BfiPayBillQueryDTO param = new BfiPayBillQueryDTO();
|
||||
param.setOrgNumber(List.of(orgNumber));
|
||||
param.setBeginBizdate(beginDate.format(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
param.setEndBizdate(endDate.format(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
return BfiUtil.queryAllPages(pageNo -> {
|
||||
param.setPageNo(pageNo);
|
||||
param.setPageSize(PAY_RESULT_PAGE_SIZE);
|
||||
return bfiService.queryPayBill(param);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 以金蝶付款处理单源单id(=付款申请单星瀚id)/源单编号(=付款申请单单据编号)勾稽匹配
|
||||
*
|
||||
* @param application 付款申请实体
|
||||
* @param payBillBySourceId 源单id索引
|
||||
* @param payBillBySourceNumber 源单编号索引
|
||||
* @return 匹配的付款处理单,未匹配返回 null
|
||||
*/
|
||||
private BfiPayBillVO matchPayBill(PaymentApplication application, Map<String, BfiPayBillVO> payBillBySourceId,
|
||||
Map<String, BfiPayBillVO> payBillBySourceNumber) {
|
||||
if (Func.isNotEmpty(application.getKingdeeBillId())
|
||||
&& payBillBySourceId.containsKey(application.getKingdeeBillId())) {
|
||||
return payBillBySourceId.get(application.getKingdeeBillId());
|
||||
}
|
||||
if (Func.isNotEmpty(application.getKingdeeBillNo())) {
|
||||
return payBillBySourceNumber.get(application.getKingdeeBillNo());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 统计付款申请的真实付款记录数(剔除历史模拟记录,兼容手工记录付款单号为空的场景) */
|
||||
private long realRecordCount(Long applicationId) {
|
||||
return recordMapper.selectCount(Wrappers.<PaymentApplicationRecord>lambdaQuery()
|
||||
.eq(PaymentApplicationRecord::getPaymentApplicationId, applicationId)
|
||||
.and(wrapper -> wrapper.notLikeRight(PaymentApplicationRecord::getPaymentNo, MOCK_PAYMENT_RECORD_PREFIX)
|
||||
.or().isNull(PaymentApplicationRecord::getPaymentNo)));
|
||||
}
|
||||
|
||||
/** 解析金蝶返回日期(yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss),解析失败返回 null */
|
||||
private LocalDate parseKingdeeDate(String dateText) {
|
||||
if (Func.isBlank(dateText)) return null;
|
||||
try {
|
||||
return LocalDate.parse(dateText.trim().substring(0, 10));
|
||||
} catch (Exception exception) {
|
||||
log.warn("金蝶日期解析失败:{}", dateText);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 时间字符串比较(yyyy-MM-dd HH:mm:ss),空值视为最小 */
|
||||
private int strCompare(String left, String right) {
|
||||
if (Objects.equals(left, right)) return 0;
|
||||
if (left == null) return -1;
|
||||
if (right == null) return 1;
|
||||
return left.compareTo(right);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送金蝶付款申请单(A103-3 付款申请保存,预付款路径),返回金蝶单据id
|
||||
*
|
||||
@@ -478,40 +780,66 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
String billDateText = (entity.getApplyDate() == null ? LocalDate.now() : entity.getApplyDate()).format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
BigDecimal applyAmount = money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP);
|
||||
BfiApBillSaveDTO param = new BfiApBillSaveDTO();
|
||||
// 【必填】单据类型.编码
|
||||
param.setBilltypeidNumber(billtypeNumber);
|
||||
// 【必填】结算组织.编码
|
||||
param.setOrgNumber(orgNumber);
|
||||
// 【必填】单据日期
|
||||
param.setBizdate(billDateText);
|
||||
// 【必填】往来类型(bd_supplier:供应商)
|
||||
param.setAsstacttype(KingdeeBfiConstant.BFI_SUPPLIER_ASSTACT_TYPE);
|
||||
// 【必填】往来户.编码
|
||||
param.setAsstactNumber(bfiSupplierCode(entity));
|
||||
// 【必填】付款方式(CASH:现购, CREDIT:赊购)
|
||||
param.setPurmode(KingdeeBfiConstant.BFI_AP_BILL_PUR_MODE);
|
||||
// 【必填】结算币别.货币代码
|
||||
param.setCurrencyNumber(currency);
|
||||
// 【必填】付款组织.编码
|
||||
param.setPayorgNumber(orgNumber);
|
||||
param.setPurorgNumber(orgNumber);
|
||||
// 采购组织.编码(非必传)
|
||||
// param.setPurorgNumber(orgNumber);
|
||||
// 【必填】款项性质.编码
|
||||
param.setPaypropertyNumber(KingdeeBfiConstant.BFI_AP_BILL_PAY_PROPERTY);
|
||||
// TODO 待确认:结算方式编码(settlementtype_number)与税率编码(taxrateid_number)需金蝶侧确认后补充,当前不传
|
||||
param.setIsincludetax(true);
|
||||
param.setIspricetotal(true);
|
||||
// 录入含税价(非必传)
|
||||
// param.setIsincludetax(true);
|
||||
// 录入总价,为 true 后可不录入单价(非必传)
|
||||
// param.setIspricetotal(true);
|
||||
// 汇率表.编码(非必传)
|
||||
param.setExratetableNumber(bfiPayApplyExratetable);
|
||||
// 汇率日期(非必传)
|
||||
param.setExratedate(billDateText);
|
||||
param.setAsstactname(entity.getPayeeName());
|
||||
// 往来单位名称(非必传)
|
||||
// param.setAsstactname(entity.getPayeeName());
|
||||
// 备注(非必传)
|
||||
param.setRemark(entity.getPaymentNo() + (Func.isEmpty(entity.getSettlementNo()) ? "" : " " + entity.getSettlementNo()));
|
||||
// 【必填】外部系统编号(候选键)
|
||||
param.setAwrpSrcSys(bfiPayApplySrcSys);
|
||||
// 【必填】外部系统来源单据号(候选键)
|
||||
param.setAwrpSrcCode(entity.getPaymentNo());
|
||||
// 来源单据(非必传,有上游合同时传 awrp_contract_info)
|
||||
param.setAwrpSrcBt(KingdeeBfiConstant.BFI_CONTRACT_SRC_BT);
|
||||
// 上游外部系统单据号/上游合同号(非必传)
|
||||
if (Func.isNotEmpty(entity.getContractNo())) {
|
||||
param.setAwrpSrcConNo(entity.getContractNo());
|
||||
}
|
||||
// 【必填】外部单据类型
|
||||
param.setAwrpSrcType(KingdeeBfiConstant.BFI_AP_BILL_SRC_TYPE);
|
||||
|
||||
BfiApBillSaveEntryDTO entry = new BfiApBillSaveEntryDTO();
|
||||
// 【必填】物料编码.编码
|
||||
entry.setMaterialNumber(KingdeeBfiConstant.BFI_AP_BILL_MATERIAL);
|
||||
// 【必填】计量单位.编码
|
||||
entry.setMeasureunitNumber(KingdeeBfiConstant.BFI_AP_BILL_MEASURE_UNIT);
|
||||
// 无吨位数据:数量固定 1,配合 ispricetotal=true 录入总价(含税应付金额)
|
||||
// 【必填】数量(无吨位数据:数量固定 1,配合 ispricetotal=true 录入总价)
|
||||
entry.setQuantity(BigDecimal.ONE);
|
||||
// 应付金额(含税)(非必传)
|
||||
entry.setEPricetaxtotal(applyAmount);
|
||||
// 【必填】项目.项目编码
|
||||
entry.setProjectNumber(bfiProjectNumber(entity));
|
||||
// 应付单分录唯一标识,A103-1 下推时以 awrp_finapbill_entryid 关联此行
|
||||
// 【必填】外部系统来源分录标识(候选键),A103-1 下推时以 awrp_finapbill_entryid 关联此行
|
||||
entry.setAwrpEntryCode(entity.getPaymentNo() + "-1");
|
||||
// 分录备注(非必传)
|
||||
entry.setERemark(entity.getRemark());
|
||||
param.setDetailentry(List.of(entry));
|
||||
|
||||
@@ -599,7 +927,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
private void markSyncFailed(PaymentApplication entity) {
|
||||
entity.setKingdeeStatus("failed");
|
||||
entity.setKingdeeStatus(KingdeeBfiConstant.KINGDEE_STATUS_FAILED);
|
||||
updateById(entity);
|
||||
}
|
||||
|
||||
@@ -652,6 +980,13 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return project == null || Func.isEmpty(project.getProjectCode()) ? KingdeeBfiConstant.BFI_PROJECT_NUMBER_NONE : project.getProjectCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款完成模拟(已停用):原在同步成功后模拟付款完成数据(paidAmount = 申请金额、MOCK-PAY- 前缀付款记录)。
|
||||
* 真实付款结果现由定时回写任务 syncKingdeePaymentResult 经 A103-2/A103-4 查询回填驱动,
|
||||
* 暂保留本方法以备联调环境无金蝶付款数据时模拟使用。
|
||||
*
|
||||
* @param entity 付款申请实体
|
||||
*/
|
||||
private void mockPaymentRecords(PaymentApplication entity) {
|
||||
recordMapper.delete(Wrappers.<PaymentApplicationRecord>lambdaQuery()
|
||||
.eq(PaymentApplicationRecord::getPaymentApplicationId, entity.getId()));
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments from this software for such purposes.
|
||||
* Copyright of this software remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.task;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 金蝶付款结果定时回写任务
|
||||
* <p>
|
||||
* 定时查询金蝶(星瀚)付款结果并更新付款信息与履约付款数据:
|
||||
* A103-2 付款申请单查询回填金蝶单据编号与单据状态,A103-4 付款处理查询回填付款记录。
|
||||
* 业务逻辑收敛在 {@link IPaymentApplicationService#syncKingdeePaymentResult()},便于手动触发联调。
|
||||
* <p>
|
||||
* 调度配置(Nacos thirdParty.bfi.payResult.*):syncEnabled 开关、cron 表达式。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentKingdeeResultTask {
|
||||
|
||||
private final IPaymentApplicationService paymentApplicationService;
|
||||
|
||||
/** 付款结果回写开关,默认开启 */
|
||||
@Value("${thirdParty.bfi.payResult.syncEnabled:false}")
|
||||
private boolean syncEnabled;
|
||||
|
||||
/** 金蝶付款结果回写调度,默认每10分钟执行一次 */
|
||||
@Scheduled(cron = "${thirdParty.bfi.payResult.cron:0 */10 * * * ?}")
|
||||
public void syncKingdeePaymentResult() {
|
||||
if (!syncEnabled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String summary = paymentApplicationService.syncKingdeePaymentResult();
|
||||
log.info("定时回写金蝶付款结果:{}", summary);
|
||||
} catch (Exception exception) {
|
||||
log.error("定时回写金蝶付款结果异常", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -22,6 +22,7 @@ package org.springblade.transport.wrapper;
|
||||
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.pojo.entity.PaymentApplication;
|
||||
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
|
||||
|
||||
@@ -50,8 +51,11 @@ public class PaymentApplicationWrapper extends BaseEntityWrapper<PaymentApplicat
|
||||
default -> entity.getApprovalStatus();
|
||||
});
|
||||
vo.setKingdeeStatusName(switch (entity.getKingdeeStatus() == null ? "" : entity.getKingdeeStatus()) {
|
||||
case "synced" -> "已生成";
|
||||
case "failed" -> "生成失败";
|
||||
case KingdeeBfiConstant.KINGDEE_STATUS_SYNCED -> "已生成";
|
||||
case KingdeeBfiConstant.KINGDEE_STATUS_PAYING -> "付款中";
|
||||
case KingdeeBfiConstant.KINGDEE_STATUS_PAID -> "已付款";
|
||||
case KingdeeBfiConstant.KINGDEE_STATUS_CLOSED -> "已关闭";
|
||||
case KingdeeBfiConstant.KINGDEE_STATUS_FAILED -> "生成失败";
|
||||
default -> "未生成";
|
||||
});
|
||||
return vo;
|
||||
|
||||
Reference in New Issue
Block a user