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);
|
||||
|
||||
/**
|
||||
* 独立事务落"生成失败"状态并记失败原因(在推送事务回滚、行锁释放后执行,与付款/收票口径统一);
|
||||
* 仅更新审批通过且未同步的单据,未审批/已同步/已删除单据自动跳过,保持原状态
|
||||
*
|
||||
* @param id 正式结算单id
|
||||
* @param reason 失败原因(写变更记录留档)
|
||||
*/
|
||||
void markKingdeeSyncFailed(Long id, String reason);
|
||||
|
||||
String syncKingdeeArResult();
|
||||
List<FormalSettlementDetailFee> detailFees(Long detailId);
|
||||
void adjustDetail(PreSettlementDetailAdjustRequest request);
|
||||
|
||||
+9
@@ -65,5 +65,14 @@ public interface IInvoiceApplicationService extends BaseService<InvoiceApplicati
|
||||
*/
|
||||
String doSyncKingdee(Long id);
|
||||
|
||||
/**
|
||||
* 独立事务落"生成失败"状态并记失败原因(在推送事务回滚、行锁释放后执行,与付款/收票口径统一);
|
||||
* 仅更新审批通过且未同步/未开票的单据,其余自动跳过,保持原状态
|
||||
*
|
||||
* @param id 开票申请id
|
||||
* @param reason 失败原因(写操作流水留档)
|
||||
*/
|
||||
void markKingdeeSyncFailed(Long id, String reason);
|
||||
|
||||
String syncKingdeeInvoiceResult();
|
||||
}
|
||||
|
||||
+10
@@ -115,6 +115,16 @@ public interface IInvoiceReceiptService extends BaseService<InvoiceReceipt> {
|
||||
*/
|
||||
String pushKingdeeReceivedInvoice(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 自动推送路径批量推送金蝶收票单(审批通过事件 / 定时重推专用):
|
||||
* 前置拦截(未绑定镜像/查验未通过/前置应付单未生成等)一并置 failed + 原因,纳入定时重推循环
|
||||
*
|
||||
* @param ids 收票登记id列表
|
||||
* @param autoSync 自动推送标记(前置拦截置 failed)
|
||||
* @return 执行摘要(逐单失败原因)
|
||||
*/
|
||||
String pushKingdeeReceivedInvoice(List<Long> ids, boolean autoSync);
|
||||
|
||||
/**
|
||||
* 定时回写金蝶收票单结果(A801-3):金蝶侧删除/红冲/作废 → 回收为 failed 提示人工(不自动作废登记);
|
||||
* pushed(保存成功后中断)→ 指定补偿;assigned 仅补登缺失的收票单号
|
||||
|
||||
+33
-2
@@ -565,13 +565,44 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
|
||||
@Override
|
||||
public String syncKingdee(Long id) {
|
||||
// 单据级事务经自代理调用:单条失败只回滚自身
|
||||
String billNo = SpringUtil.getBean(IFormalSettlementService.class).doSyncKingdee(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;
|
||||
}
|
||||
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
|
||||
syncAttachmentsAfterPush(id);
|
||||
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 定位金蝶单据;
|
||||
* 本地缺失(存量数据/幂等跳过路径未带回内码)时经 A102-4/A302-4 按防重键反查回填;
|
||||
|
||||
+39
-2
@@ -509,13 +509,50 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
||||
|
||||
@Override
|
||||
public String syncKingdee(Long id) {
|
||||
// 单据级事务经自代理调用:单条失败只回滚自身
|
||||
String billNo = SpringUtil.getBean(IInvoiceApplicationService.class).doSyncKingdee(id);
|
||||
String billNo;
|
||||
try {
|
||||
// 单据级事务经自代理调用:单条失败只回滚自身
|
||||
billNo = SpringUtil.getBean(IInvoiceApplicationService.class).doSyncKingdee(id);
|
||||
} catch (Exception exception) {
|
||||
log.error("开票申请[{}]推送金蝶异常", id, exception);
|
||||
// 推送事务已回滚、行锁已释放,此处独立事务落"生成失败"状态(与付款/收票口径统一,失败可被重推任务续推)
|
||||
self().markKingdeeSyncFailed(id, exception.getMessage());
|
||||
throw exception;
|
||||
}
|
||||
// 主单事务提交后联带上传附件(A902):失败只记附件同步状态,不回滚不污染主单同步状态,重推自动补传
|
||||
syncAttachmentsAfterPush(id);
|
||||
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 定位金蝶单据;
|
||||
* 本地缺失(存量数据)跳过待重推补传;附件上传异常独立捕获,绝不影响主单同步结果
|
||||
|
||||
+36
-3
@@ -536,6 +536,24 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
|
||||
*/
|
||||
@Override
|
||||
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)) {
|
||||
throw new ServiceException("请选择需要推送金蝶的收票登记");
|
||||
}
|
||||
@@ -603,7 +621,7 @@ public class InvoiceReceiptServiceImpl extends BaseServiceImpl<InvoiceReceiptMap
|
||||
List<String> failures = new ArrayList<>();
|
||||
for (InvoiceReceipt receipt : receipts) {
|
||||
try {
|
||||
pushOneReceivedInvoice(planByReceipt.get(receipt.getId()), kingdeeRowByInvoiceNo, apBillNoBySrcCode, context);
|
||||
pushOneReceivedInvoice(planByReceipt.get(receipt.getId()), kingdeeRowByInvoiceNo, apBillNoBySrcCode, context, autoSync);
|
||||
success++;
|
||||
} catch (RuntimeException pushException) {
|
||||
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 落库 →
|
||||
* A302-6 指定应付 → assigned;保存/指定阶段失败置 failed(预检拦截不改状态),断点可凭防重键续推
|
||||
* A302-6 指定应付 → assigned;保存/指定阶段失败置 failed,断点可凭防重键续推。
|
||||
* 自动推送路径(autoSync=true)前置拦截一并置 failed + 原因,纳入定时重推循环;
|
||||
* 手动批量路径前置拦截保持不动状态(逐单报因),避免人工误触把可自助修复的单据打成失败
|
||||
*
|
||||
* @param plan 预检组装的推送计划
|
||||
* @param kingdeeRows A801-3 幂等预检命中的金蝶收票单(发票号码 -> 行)
|
||||
* @param apBillNoBySrcCode A102-4 已生成的财务应付单(防重键 -> 单据编号)
|
||||
* @param context 金蝶同步上下文(合同/客商编码缓存)
|
||||
* @param autoSync 自动推送路径标记(前置拦截置 failed)
|
||||
* @return 推送结果描述
|
||||
*/
|
||||
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();
|
||||
log.info("收票登记[{}]推送金蝶收票单开始:id={},推送状态={},应付单防重键={}", receipt.getInvoiceNo(), receipt.getId(),
|
||||
receipt.getKingdeePushStatus(), plan.apBillSrcCode());
|
||||
if (Func.isNotBlank(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());
|
||||
}
|
||||
String apBillSrcCode = plan.apBillSrcCode();
|
||||
if (!apBillNoBySrcCode.containsKey(apBillSrcCode)) {
|
||||
log.warn("收票登记[{}]推送金蝶收票单拦截:结算单[{}]未生成财务应付单(防重键={})", receipt.getInvoiceNo(),
|
||||
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()
|
||||
+ "未生成财务应付单");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user