feat: 推送失败统一落failed口径(修总纲11.2-#3)
- 结算单/开票申请推送失败由整体回滚保持unsynced改为:事务回滚后独立事务落failed+原因(结算写变更记录、开票写操作流水,无新列),与付款/收票口径统一 - 收票新增pushKingdeeReceivedInvoice(ids,autoSync)重载:自动推送路径前置拦截(未绑定镜像/查验未通过/前置应付单未生成等)置failed+原因纳入重推循环,已指定应付的良性跳过不改状态;手动批量路径保持拦截不动状态逐单报因 - 汇率未维护照旧不传不阻断
This commit is contained in:
+9
@@ -51,6 +51,15 @@ public interface IFormalSettlementService extends BaseService<FormalSettlement>
|
|||||||
*/
|
*/
|
||||||
String doSyncKingdee(Long id);
|
String doSyncKingdee(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 独立事务落"生成失败"状态并记失败原因(在推送事务回滚、行锁释放后执行,与付款/收票口径统一);
|
||||||
|
* 仅更新审批通过且未同步的单据,未审批/已同步/已删除单据自动跳过,保持原状态
|
||||||
|
*
|
||||||
|
* @param id 正式结算单id
|
||||||
|
* @param reason 失败原因(写变更记录留档)
|
||||||
|
*/
|
||||||
|
void markKingdeeSyncFailed(Long id, String reason);
|
||||||
|
|
||||||
String syncKingdeeArResult();
|
String syncKingdeeArResult();
|
||||||
List<FormalSettlementDetailFee> detailFees(Long detailId);
|
List<FormalSettlementDetailFee> detailFees(Long detailId);
|
||||||
void adjustDetail(PreSettlementDetailAdjustRequest request);
|
void adjustDetail(PreSettlementDetailAdjustRequest request);
|
||||||
|
|||||||
+9
@@ -65,5 +65,14 @@ public interface IInvoiceApplicationService extends BaseService<InvoiceApplicati
|
|||||||
*/
|
*/
|
||||||
String doSyncKingdee(Long id);
|
String doSyncKingdee(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 独立事务落"生成失败"状态并记失败原因(在推送事务回滚、行锁释放后执行,与付款/收票口径统一);
|
||||||
|
* 仅更新审批通过且未同步/未开票的单据,其余自动跳过,保持原状态
|
||||||
|
*
|
||||||
|
* @param id 开票申请id
|
||||||
|
* @param reason 失败原因(写操作流水留档)
|
||||||
|
*/
|
||||||
|
void markKingdeeSyncFailed(Long id, String reason);
|
||||||
|
|
||||||
String syncKingdeeInvoiceResult();
|
String syncKingdeeInvoiceResult();
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -115,6 +115,16 @@ public interface IInvoiceReceiptService extends BaseService<InvoiceReceipt> {
|
|||||||
*/
|
*/
|
||||||
String pushKingdeeReceivedInvoice(List<Long> ids);
|
String pushKingdeeReceivedInvoice(List<Long> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动推送路径批量推送金蝶收票单(审批通过事件 / 定时重推专用):
|
||||||
|
* 前置拦截(未绑定镜像/查验未通过/前置应付单未生成等)一并置 failed + 原因,纳入定时重推循环
|
||||||
|
*
|
||||||
|
* @param ids 收票登记id列表
|
||||||
|
* @param autoSync 自动推送标记(前置拦截置 failed)
|
||||||
|
* @return 执行摘要(逐单失败原因)
|
||||||
|
*/
|
||||||
|
String pushKingdeeReceivedInvoice(List<Long> ids, boolean autoSync);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定时回写金蝶收票单结果(A801-3):金蝶侧删除/红冲/作废 → 回收为 failed 提示人工(不自动作废登记);
|
* 定时回写金蝶收票单结果(A801-3):金蝶侧删除/红冲/作废 → 回收为 failed 提示人工(不自动作废登记);
|
||||||
* pushed(保存成功后中断)→ 指定补偿;assigned 仅补登缺失的收票单号
|
* pushed(保存成功后中断)→ 指定补偿;assigned 仅补登缺失的收票单号
|
||||||
|
|||||||
+33
-2
@@ -565,13 +565,44 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String syncKingdee(Long id) {
|
public String syncKingdee(Long id) {
|
||||||
// 单据级事务经自代理调用:单条失败只回滚自身
|
String billNo;
|
||||||
String billNo = SpringUtil.getBean(IFormalSettlementService.class).doSyncKingdee(id);
|
try {
|
||||||
|
// 单据级事务经自代理调用:单条失败只回滚自身
|
||||||
|
billNo = SpringUtil.getBean(IFormalSettlementService.class).doSyncKingdee(id);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
log.error("正式结算单[{}]推送金蝶异常", id, exception);
|
||||||
|
// 推送事务已回滚、行锁已释放,此处独立事务落"生成失败"状态(与付款/收票口径统一,失败可被重推任务续推)
|
||||||
|
self().markKingdeeSyncFailed(id, exception.getMessage());
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
|
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
|
||||||
syncAttachmentsAfterPush(id);
|
syncAttachmentsAfterPush(id);
|
||||||
return billNo;
|
return billNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 自代理:跨方法调用确保事务注解生效(失败状态独立事务) */
|
||||||
|
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.<FormalSettlement>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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 附件联带上传(A902):主单推送成功(含幂等预检命中)后调用,以本地 kingdeeBillId 定位金蝶单据;
|
* 附件联带上传(A902):主单推送成功(含幂等预检命中)后调用,以本地 kingdeeBillId 定位金蝶单据;
|
||||||
* 本地缺失(存量数据/幂等跳过路径未带回内码)时经 A102-4/A302-4 按防重键反查回填;
|
* 本地缺失(存量数据/幂等跳过路径未带回内码)时经 A102-4/A302-4 按防重键反查回填;
|
||||||
|
|||||||
+39
-2
@@ -509,13 +509,50 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String syncKingdee(Long id) {
|
public String syncKingdee(Long id) {
|
||||||
// 单据级事务经自代理调用:单条失败只回滚自身
|
String billNo;
|
||||||
String billNo = SpringUtil.getBean(IInvoiceApplicationService.class).doSyncKingdee(id);
|
try {
|
||||||
|
// 单据级事务经自代理调用:单条失败只回滚自身
|
||||||
|
billNo = SpringUtil.getBean(IInvoiceApplicationService.class).doSyncKingdee(id);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
log.error("开票申请[{}]推送金蝶异常", id, exception);
|
||||||
|
// 推送事务已回滚、行锁已释放,此处独立事务落"生成失败"状态(与付款/收票口径统一,失败可被重推任务续推)
|
||||||
|
self().markKingdeeSyncFailed(id, exception.getMessage());
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
|
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
|
||||||
syncAttachmentsAfterPush(id);
|
syncAttachmentsAfterPush(id);
|
||||||
return billNo;
|
return billNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 自代理:跨方法调用确保事务注解生效(失败状态独立事务) */
|
||||||
|
private IInvoiceApplicationService self() {
|
||||||
|
return SpringUtil.getBean(IInvoiceApplicationService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void markKingdeeSyncFailed(Long id, String reason) {
|
||||||
|
if (id == null) return;
|
||||||
|
// 条件更新:仅审批通过且未同步/未开票的单据落"生成失败"状态,其余自动跳过保持原状态
|
||||||
|
int updated = baseMapper.update(null, Wrappers.<InvoiceApplication>lambdaUpdate()
|
||||||
|
.eq(InvoiceApplication::getId, id)
|
||||||
|
.eq(InvoiceApplication::getApprovalStatus, TransportBusiConstant.APPROVED)
|
||||||
|
.notIn(InvoiceApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_SYNCED,
|
||||||
|
KingdeeBfiConstant.KINGDEE_STATUS_INVOICED)
|
||||||
|
.set(InvoiceApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_FAILED));
|
||||||
|
if (updated > 0) {
|
||||||
|
// 失败原因写操作流水留档(推送事务已回滚,此处独立事务)
|
||||||
|
record(id, "sync", "同步金蝶失败", TransportBusiConstant.APPROVED, TransportBusiConstant.APPROVED,
|
||||||
|
truncateReason(reason), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 失败原因截断留档(流水 reason 字段兜底 500 长度) */
|
||||||
|
private String truncateReason(String reason) {
|
||||||
|
String value = Func.toStrWithEmpty(reason, "未知异常");
|
||||||
|
return value.length() > 500 ? value.substring(0, 500) : value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 附件联带上传(A902):主单推送成功(含 A303-4 勾稽回填)后调用,以本地 kingdeeBillId 定位金蝶单据;
|
* 附件联带上传(A902):主单推送成功(含 A303-4 勾稽回填)后调用,以本地 kingdeeBillId 定位金蝶单据;
|
||||||
* 本地缺失(存量数据)跳过待重推补传;附件上传异常独立捕获,绝不影响主单同步结果
|
* 本地缺失(存量数据)跳过待重推补传;附件上传异常独立捕获,绝不影响主单同步结果
|
||||||
|
|||||||
+36
-3
@@ -536,6 +536,24 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String pushKingdeeReceivedInvoice(List<Long> ids) {
|
public String pushKingdeeReceivedInvoice(List<Long> ids) {
|
||||||
|
// 手动批量推送:前置拦截保持现状(blockedReason 不动推送状态,逐单报因)
|
||||||
|
return pushKingdeeReceivedInvoice(ids, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量推送收票登记至金蝶(图05 环节②③:A303-2 收票单批量保存 + A302-6 指定应付)。
|
||||||
|
* <p>
|
||||||
|
* 编排流程:参数去重校验 → 批量组装推送计划(分摊/镜像/结算单/结算明细一次取回防 N+1)→
|
||||||
|
* A102-4 应付单已生成预检 → A801-3 幂等预检(金蝶已有收票单则跳过保存续推指定)→
|
||||||
|
* 逐单推送(单笔失败不中断整批)→ 汇总成功/失败笔数返回
|
||||||
|
*
|
||||||
|
* @param ids 收票登记主键集合
|
||||||
|
* @param autoSync 自动推送路径(审批通过事件/定时重推):前置拦截置 failed + 原因纳入重推循环;
|
||||||
|
* 手动批量路径保持现状(拦截不改状态,逐单报因)
|
||||||
|
* @return 推送结果汇总(本批笔数、成功/失败笔数及失败原因明细)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String pushKingdeeReceivedInvoice(List<Long> ids, boolean autoSync) {
|
||||||
if (Func.isEmpty(ids)) {
|
if (Func.isEmpty(ids)) {
|
||||||
throw new ServiceException("请选择需要推送金蝶的收票登记");
|
throw new ServiceException("请选择需要推送金蝶的收票登记");
|
||||||
}
|
}
|
||||||
@@ -603,7 +621,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
|
|||||||
List<String> failures = new ArrayList<>();
|
List<String> failures = new ArrayList<>();
|
||||||
for (InvoiceReceipt receipt : receipts) {
|
for (InvoiceReceipt receipt : receipts) {
|
||||||
try {
|
try {
|
||||||
pushOneReceivedInvoice(planByReceipt.get(receipt.getId()), kingdeeRowByInvoiceNo, apBillNoBySrcCode, context);
|
pushOneReceivedInvoice(planByReceipt.get(receipt.getId()), kingdeeRowByInvoiceNo, apBillNoBySrcCode, context, autoSync);
|
||||||
success++;
|
success++;
|
||||||
} catch (RuntimeException pushException) {
|
} catch (RuntimeException pushException) {
|
||||||
failures.add("发票" + Func.toStrWithEmpty(receipt.getInvoiceNo(), String.valueOf(receipt.getId()))
|
failures.add("发票" + Func.toStrWithEmpty(receipt.getInvoiceNo(), String.valueOf(receipt.getId()))
|
||||||
@@ -621,27 +639,42 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 单张收票登记推送编排:前置校验 → A102-4 预检 → A801-3 幂等预检 → A303-2 保存 → pushed 落库 →
|
* 单张收票登记推送编排:前置校验 → A102-4 预检 → A801-3 幂等预检 → A303-2 保存 → pushed 落库 →
|
||||||
* A302-6 指定应付 → assigned;保存/指定阶段失败置 failed(预检拦截不改状态),断点可凭防重键续推
|
* A302-6 指定应付 → assigned;保存/指定阶段失败置 failed,断点可凭防重键续推。
|
||||||
|
* 自动推送路径(autoSync=true)前置拦截一并置 failed + 原因,纳入定时重推循环;
|
||||||
|
* 手动批量路径前置拦截保持不动状态(逐单报因),避免人工误触把可自助修复的单据打成失败
|
||||||
*
|
*
|
||||||
* @param plan 预检组装的推送计划
|
* @param plan 预检组装的推送计划
|
||||||
* @param kingdeeRows A801-3 幂等预检命中的金蝶收票单(发票号码 -> 行)
|
* @param kingdeeRows A801-3 幂等预检命中的金蝶收票单(发票号码 -> 行)
|
||||||
* @param apBillNoBySrcCode A102-4 已生成的财务应付单(防重键 -> 单据编号)
|
* @param apBillNoBySrcCode A102-4 已生成的财务应付单(防重键 -> 单据编号)
|
||||||
* @param context 金蝶同步上下文(合同/客商编码缓存)
|
* @param context 金蝶同步上下文(合同/客商编码缓存)
|
||||||
|
* @param autoSync 自动推送路径标记(前置拦截置 failed)
|
||||||
* @return 推送结果描述
|
* @return 推送结果描述
|
||||||
*/
|
*/
|
||||||
private String pushOneReceivedInvoice(ReceivedInvoicePushPlan plan, Map<String, BfiReceivedInvoiceVO> kingdeeRows,
|
private String pushOneReceivedInvoice(ReceivedInvoicePushPlan plan, Map<String, BfiReceivedInvoiceVO> kingdeeRows,
|
||||||
Map<String, String> apBillNoBySrcCode, KingdeeSyncContext context) {
|
Map<String, String> apBillNoBySrcCode, KingdeeSyncContext context, boolean autoSync) {
|
||||||
InvoiceReceipt receipt = plan.receipt();
|
InvoiceReceipt receipt = plan.receipt();
|
||||||
log.info("收票登记[{}]推送金蝶收票单开始:id={},推送状态={},应付单防重键={}", receipt.getInvoiceNo(), receipt.getId(),
|
log.info("收票登记[{}]推送金蝶收票单开始:id={},推送状态={},应付单防重键={}", receipt.getInvoiceNo(), receipt.getId(),
|
||||||
receipt.getKingdeePushStatus(), plan.apBillSrcCode());
|
receipt.getKingdeePushStatus(), plan.apBillSrcCode());
|
||||||
if (Func.isNotBlank(plan.blockedReason())) {
|
if (Func.isNotBlank(plan.blockedReason())) {
|
||||||
log.warn("收票登记[{}]推送金蝶收票单拦截:{}", receipt.getInvoiceNo(), plan.blockedReason());
|
log.warn("收票登记[{}]推送金蝶收票单拦截:{}", receipt.getInvoiceNo(), plan.blockedReason());
|
||||||
|
// 自动推送路径:前置不满足置 failed + 原因纳入重推循环("已指定应付"的良性跳过不改状态)
|
||||||
|
if (autoSync && !KingdeeBfiConstant.RECEIVED_PUSH_ASSIGNED.equals(
|
||||||
|
Func.toStrWithEmpty(receipt.getKingdeePushStatus(), KingdeeBfiConstant.RECEIVED_PUSH_UNSYNCED))) {
|
||||||
|
applyReceivedInvoicePushResult(receipt, receipt.getKingdeeReceivedInvoiceNo(), receipt.getKingdeeReceivedInvoiceId(),
|
||||||
|
KingdeeBfiConstant.RECEIVED_PUSH_FAILED, truncatePushMsg(plan.blockedReason()), true);
|
||||||
|
}
|
||||||
throw new ServiceException(plan.blockedReason());
|
throw new ServiceException(plan.blockedReason());
|
||||||
}
|
}
|
||||||
String apBillSrcCode = plan.apBillSrcCode();
|
String apBillSrcCode = plan.apBillSrcCode();
|
||||||
if (!apBillNoBySrcCode.containsKey(apBillSrcCode)) {
|
if (!apBillNoBySrcCode.containsKey(apBillSrcCode)) {
|
||||||
log.warn("收票登记[{}]推送金蝶收票单拦截:结算单[{}]未生成财务应付单(防重键={})", receipt.getInvoiceNo(),
|
log.warn("收票登记[{}]推送金蝶收票单拦截:结算单[{}]未生成财务应付单(防重键={})", receipt.getInvoiceNo(),
|
||||||
plan.settlement().getFormalSettlementNo(), apBillSrcCode);
|
plan.settlement().getFormalSettlementNo(), apBillSrcCode);
|
||||||
|
// 自动推送路径:前置应付单未生成置 failed + 原因,上游结算单就绪后由重推任务自动续推
|
||||||
|
if (autoSync) {
|
||||||
|
applyReceivedInvoicePushResult(receipt, receipt.getKingdeeReceivedInvoiceNo(), receipt.getKingdeeReceivedInvoiceId(),
|
||||||
|
KingdeeBfiConstant.RECEIVED_PUSH_FAILED,
|
||||||
|
truncatePushMsg("请先同步金蝶应付单:结算单" + plan.settlement().getFormalSettlementNo() + "未生成财务应付单"), true);
|
||||||
|
}
|
||||||
throw new ServiceException("请先同步金蝶应付单:结算单" + plan.settlement().getFormalSettlementNo()
|
throw new ServiceException("请先同步金蝶应付单:结算单" + plan.settlement().getFormalSettlementNo()
|
||||||
+ "未生成财务应付单");
|
+ "未生成财务应付单");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user