✨ 收票单推送金蝶全链路(A303-2保存+A302-6指定应付+A801-3回写)与金蝶票池实时游标分页重构,下线镜像分页与定时绑定任务
This commit is contained in:
-1
@@ -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;
|
||||
|
||||
+7
-25
@@ -32,12 +32,12 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 金蝶票据池镜像统一查询条件(收票登记选票与正式结算单认领共用)
|
||||
* 金蝶票池实时统一查询条件(收票登记选票与正式结算单认领共用)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "金蝶票据池镜像统一查询条件")
|
||||
@Schema(description = "金蝶票池实时统一查询条件")
|
||||
public class KingdeeInvoicePoolQueryDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
@@ -74,21 +74,9 @@ public class KingdeeInvoicePoolQueryDTO implements Serializable {
|
||||
private String invoiceDateEnd;
|
||||
|
||||
/**
|
||||
* 开票单位(销方名称),模糊
|
||||
* 发票类型编码(票易通字典,如 26/27)
|
||||
*/
|
||||
@Schema(description = "开票单位(销方名称,模糊)")
|
||||
private String salerName;
|
||||
|
||||
/**
|
||||
* 受票单位(购方名称),模糊
|
||||
*/
|
||||
@Schema(description = "受票单位(购方名称,模糊)")
|
||||
private String buyerName;
|
||||
|
||||
/**
|
||||
* 发票类型名称,模糊(镜像存中文名)
|
||||
*/
|
||||
@Schema(description = "发票类型名称(模糊)")
|
||||
@Schema(description = "发票类型编码(票易通字典,如 26/27)")
|
||||
private String invoiceType;
|
||||
|
||||
/**
|
||||
@@ -104,14 +92,8 @@ public class KingdeeInvoicePoolQueryDTO implements Serializable {
|
||||
private String buyerTaxNo;
|
||||
|
||||
/**
|
||||
* 认领过滤:true 仅已认领,false 仅未认领,null 不过滤(已认领=存在正式结算单发票行)
|
||||
* 单据使用状态[1未用,30在用,60已用,65已入账],空不限(A801-1 原生条件)
|
||||
*/
|
||||
@Schema(description = "认领过滤:true已认领/false未认领/null不限")
|
||||
private Boolean claimed;
|
||||
|
||||
/**
|
||||
* 登记过滤:true 仅已登记,false 仅未登记,null 不过滤(已登记=存在非作废收票登记)
|
||||
*/
|
||||
@Schema(description = "登记过滤:true已登记/false未登记/null不限")
|
||||
private Boolean registered;
|
||||
@Schema(description = "单据使用状态[1未用,30在用,60已用,65已入账]")
|
||||
private String expenseStatus;
|
||||
}
|
||||
|
||||
+17
@@ -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;
|
||||
|
||||
/**
|
||||
* 收票登记实体类
|
||||
@@ -151,10 +152,26 @@ public class InvoiceReceipt extends TenantEntity {
|
||||
* 金蝶单据号(绑定票池后取发票流水号 serialNo)
|
||||
*/
|
||||
private String kingdeeBillNo;
|
||||
/**
|
||||
* 金蝶收票单单据编号(A303-2 批量保存生成/幂等预检命中回填)
|
||||
*/
|
||||
private String kingdeeReceivedInvoiceNo;
|
||||
/**
|
||||
* 金蝶同步状态(unsynced 未同步 / synced 已同步 / failed 同步失败)
|
||||
*/
|
||||
private String kingdeeStatus;
|
||||
/**
|
||||
* 收票单推送状态(unsynced 未推送 / pushed 已保存收票单 / assigned 已指定应付 / failed 推送失败)
|
||||
*/
|
||||
private String kingdeePushStatus;
|
||||
/**
|
||||
* 收票单推送失败原因(金蝶侧删除/红冲/查验不通过等异常提示人工)
|
||||
*/
|
||||
private String kingdeePushMsg;
|
||||
/**
|
||||
* 最近一次收票单推送/回写时间
|
||||
*/
|
||||
private LocalDateTime kingdeePushTime;
|
||||
/**
|
||||
* 附件信息JSON
|
||||
*/
|
||||
|
||||
+8
@@ -63,6 +63,10 @@ public class KingdeeInvoicePool extends TenantEntity {
|
||||
* 发票类型(票易通编码转名称后落库,如「数电发票(增值税专用发票)」)
|
||||
*/
|
||||
private String invoiceType;
|
||||
/**
|
||||
* 票易通原始发票类型编码(A801-1 invoiceType,如 26/27,A303-2 invoicetypef7_number 取数)
|
||||
*/
|
||||
private String invoiceTypeCode;
|
||||
/**
|
||||
* 税率(百分数,13 表示 13%)
|
||||
*/
|
||||
@@ -131,6 +135,10 @@ public class KingdeeInvoicePool extends TenantEntity {
|
||||
* 附件信息JSON(结构 {"source":"piaozone","path":"A801-2下载path","name":"文件名"})
|
||||
*/
|
||||
private String attachmentsJson;
|
||||
/**
|
||||
* A801-1 发票明细行JSON(BfiInvoicePoolItemVO 列表,A303-2 收票单分录取数来源)
|
||||
*/
|
||||
private String itemsJson;
|
||||
/**
|
||||
* 票池数据更新时间(A801-1 billCreateTime,镜像排序字段)
|
||||
*/
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ public class InvoiceReceiptVO extends InvoiceReceipt {
|
||||
@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 String createUserName;
|
||||
@TableField(exist = false) private String updateUserName;
|
||||
@TableField(exist = false) private List<InvoiceReceiptSettlement> settlements;
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 金蝶票池实时查询游标分页返回体(A801-1 无总数:hasMore 驱动上一页/下一页)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "金蝶票池游标分页返回体")
|
||||
public class KingdeeInvoicePoolPageVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 本页数据(金蝶实时行 + 本地认领/登记装饰标记) */
|
||||
@Schema(description = "本页数据")
|
||||
private List<KingdeeInvoicePoolVO> records;
|
||||
|
||||
/** 当前页码,从 1 开始 */
|
||||
@Schema(description = "当前页码")
|
||||
private Long current;
|
||||
|
||||
/** 每页条数 */
|
||||
@Schema(description = "每页条数")
|
||||
private Long size;
|
||||
|
||||
/** 是否还有下一页(服务端多取 1 条探测) */
|
||||
@Schema(description = "是否还有下一页")
|
||||
private Boolean hasMore;
|
||||
}
|
||||
+3
-3
@@ -46,7 +46,7 @@ public class KingdeeInvoicePoolVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 镜像主键,收票登记保存绑定用(kingdeeInvoicePoolId) */
|
||||
/** 镜像主键,仅已入镜像(已登记)的票非空 */
|
||||
@Schema(description = "镜像主键")
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
@@ -143,11 +143,11 @@ public class KingdeeInvoicePoolVO implements Serializable {
|
||||
@Schema(description = "默认认领金额")
|
||||
private BigDecimal matchedAmount;
|
||||
|
||||
/** 是否已被正式结算单认领 */
|
||||
/** 是否已被正式结算单认领(实时查询返回行的本地装饰标记,仅展示不作过滤) */
|
||||
@Schema(description = "是否已认领")
|
||||
private Boolean claimed;
|
||||
|
||||
/** 是否已存在非作废收票登记 */
|
||||
/** 是否已存在非作废收票登记(实时查询返回行的本地装饰标记,仅展示不作过滤) */
|
||||
@Schema(description = "是否已登记")
|
||||
private Boolean registered;
|
||||
}
|
||||
|
||||
+19
@@ -60,6 +60,11 @@ public class BfiProperties {
|
||||
*/
|
||||
private InvoiceApply invoiceApply = new InvoiceApply();
|
||||
|
||||
/**
|
||||
* 收票单推送配置(A303-2 收票单批量保存 / A302-6 收票指定应付 / A801-3 收票单查询回写)
|
||||
*/
|
||||
private ReceivedInvoice receivedInvoice = new ReceivedInvoice();
|
||||
|
||||
/**
|
||||
* 付款申请推送配置项
|
||||
*/
|
||||
@@ -95,4 +100,18 @@ public class BfiProperties {
|
||||
private String srcType = "0KzXs";
|
||||
}
|
||||
|
||||
/**
|
||||
* 收票单推送配置项(A303-2 / A302-6 / A801-3):
|
||||
* 发票类型编码、税收分类编码、购销方税号地址银行均取票池镜像与明细,不走配置
|
||||
*/
|
||||
@Data
|
||||
public static class ReceivedInvoice {
|
||||
|
||||
/**
|
||||
* A303-2 分录物料编码:联调期临时值(金蝶测试档案),发票明细无金蝶物料概念,
|
||||
* 真实物料口径与金蝶对齐后替换(与应收/应付联调临时值同源)
|
||||
*/
|
||||
private String materialNumber = "SP0324";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
@@ -152,6 +152,24 @@ public final class KingdeeBfiConstant {
|
||||
/** 金蝶同步状态:生成失败(推送异常/金蝶返回失败,可重推)。 */
|
||||
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";
|
||||
|
||||
|
||||
+3
-10
@@ -30,7 +30,7 @@ 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.KingdeeInvoicePoolPageVO;
|
||||
import org.springblade.transport.pojo.vo.FormalSettlementVO;
|
||||
import org.springblade.transport.excel.FormalSettlementExcel;
|
||||
import org.springblade.transport.pojo.vo.PreSettlementVO;
|
||||
@@ -262,15 +262,8 @@ public class FormalSettlementController extends BladeController {
|
||||
|
||||
@GetMapping("/invoice-pool")
|
||||
@ApiOperationSupport(order = 21)
|
||||
@Operation(summary = "金蝶票据池镜像分页查询(认领查询)")
|
||||
public R<IPage<KingdeeInvoicePoolVO>> invoicePool(KingdeeInvoicePoolQueryDTO query) {
|
||||
@Operation(summary = "金蝶票池实时分页查询(认领查询)")
|
||||
public R<KingdeeInvoicePoolPageVO> invoicePool(KingdeeInvoicePoolQueryDTO query) {
|
||||
return R.data(invoiceReceiptService.invoicePoolPage(query));
|
||||
}
|
||||
|
||||
@PostMapping("/invoice-pool/fetch")
|
||||
@ApiOperationSupport(order = 22)
|
||||
@Operation(summary = "按发票号码从金蝶票池补查单张发票")
|
||||
public R<KingdeeInvoicePoolVO> fetchInvoicePool(@RequestParam String invoiceNo) {
|
||||
return R.data(invoiceReceiptService.fetchKingdeeInvoiceByNo(invoiceNo));
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -39,7 +39,7 @@ import org.springblade.transport.pojo.dto.InvoiceReceiptSaveRequest;
|
||||
import org.springblade.transport.pojo.dto.InvoiceReceiptStatusRequest;
|
||||
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.pojo.vo.KingdeeInvoicePoolPageVO;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -81,18 +81,11 @@ public class InvoiceReceiptController extends BladeController {
|
||||
|
||||
@GetMapping("/invoice-pool")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@Operation(summary = "金蝶票据池镜像分页查询(含认领/登记标记)")
|
||||
public R<IPage<KingdeeInvoicePoolVO>> invoicePool(KingdeeInvoicePoolQueryDTO query) {
|
||||
@Operation(summary = "金蝶票池实时分页查询(含认领/登记标记)")
|
||||
public R<KingdeeInvoicePoolPageVO> invoicePool(KingdeeInvoicePoolQueryDTO query) {
|
||||
return R.data(invoiceReceiptService.invoicePoolPage(query));
|
||||
}
|
||||
|
||||
@PostMapping("/invoice-pool/fetch")
|
||||
@ApiOperationSupport(order = 14)
|
||||
@Operation(summary = "按发票号码从金蝶票池补查单张发票")
|
||||
public R<KingdeeInvoicePoolVO> fetchInvoicePool(@RequestParam String invoiceNo) {
|
||||
return R.data(invoiceReceiptService.fetchKingdeeInvoiceByNo(invoiceNo));
|
||||
}
|
||||
|
||||
@GetMapping("/settlement-candidates")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "可关联的应付正式结算单")
|
||||
@@ -163,10 +156,17 @@ public class InvoiceReceiptController extends BladeController {
|
||||
return R.data(invoiceReceiptService.syncKingdee(id));
|
||||
}
|
||||
|
||||
@PostMapping("/sync-invoice-pool")
|
||||
@ApiOperationSupport(order = 13)
|
||||
@Operation(summary = "手动触发金蝶全票池发票同步绑定")
|
||||
public R<String> syncInvoicePool() {
|
||||
return R.data(invoiceReceiptService.syncKingdeeInvoicePool());
|
||||
@PostMapping("/push-kingdee")
|
||||
@ApiOperationSupport(order = 15)
|
||||
@Operation(summary = "批量推送金蝶收票单(A303-2 收票单保存 + A302-6 收票指定应付)")
|
||||
public R<String> pushKingdee(@RequestBody List<Long> ids) {
|
||||
return R.data(invoiceReceiptService.pushKingdeeReceivedInvoice(ids));
|
||||
}
|
||||
|
||||
@PostMapping("/sync-kingdee-result")
|
||||
@ApiOperationSupport(order = 16)
|
||||
@Operation(summary = "手动触发金蝶收票单结果回写(A801-3 异常回收/指定补偿)")
|
||||
public R<String> syncKingdeeResult() {
|
||||
return R.data(invoiceReceiptService.syncKingdeeReceivedInvoiceResult());
|
||||
}
|
||||
}
|
||||
|
||||
-24
@@ -26,12 +26,8 @@
|
||||
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 接口
|
||||
@@ -41,24 +37,4 @@ import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO;
|
||||
@Mapper
|
||||
public interface KingdeeInvoicePoolMapper extends BaseMapper<KingdeeInvoicePool> {
|
||||
|
||||
/**
|
||||
* 金蝶票据池镜像统一分页查询:认领/登记标记由 EXISTS 子查询直出
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param query 统一查询条件(已在上层完成 trim 与日期格式校验)
|
||||
* @param tenantId 当前租户
|
||||
* @return 统一视图分页
|
||||
*/
|
||||
IPage<KingdeeInvoicePoolVO> selectInvoicePoolPage(IPage<KingdeeInvoicePoolVO> page,
|
||||
@Param("query") KingdeeInvoicePoolQueryDTO query, @Param("tenantId") String tenantId);
|
||||
|
||||
/**
|
||||
* 按发票号码查询单张镜像统一视图(补查落库后回读)
|
||||
*
|
||||
* @param invoiceNo 发票号码
|
||||
* @param tenantId 当前租户
|
||||
* @return 统一视图,无镜像时 null
|
||||
*/
|
||||
KingdeeInvoicePoolVO selectInvoicePoolVO(@Param("invoiceNo") String invoiceNo,
|
||||
@Param("tenantId") String tenantId);
|
||||
}
|
||||
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.springblade.transport.mapper.KingdeeInvoicePoolMapper">
|
||||
|
||||
<!-- 已认领:任一有效正式结算单发票行出现过(结算单作废不删发票行,须关联主单剔除作废与已删;自定义 SQL 无租户插件,手工带租户与逻辑删除) -->
|
||||
<sql id="claimedExists">
|
||||
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'))
|
||||
</sql>
|
||||
|
||||
<!-- 已登记:存在非作废收票登记 -->
|
||||
<sql id="registeredExists">
|
||||
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)
|
||||
</sql>
|
||||
|
||||
<!-- 统一视图列:价税合计与可认领/默认认领金额同源;镜像 invoice_type 落库即中文名 -->
|
||||
<sql id="invoicePoolVoColumns">
|
||||
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,
|
||||
<include refid="claimedExists"/> AS claimed,
|
||||
<include refid="registeredExists"/> AS registered
|
||||
</sql>
|
||||
|
||||
<select id="selectInvoicePoolPage" resultType="org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO">
|
||||
SELECT
|
||||
<include refid="invoicePoolVoColumns"/>
|
||||
FROM blade_kingdee_invoice_pool p
|
||||
WHERE p.is_deleted = 0 AND p.status = 1 AND p.tenant_id = #{tenantId}
|
||||
<if test="query.invoiceNo != null and query.invoiceNo != ''">
|
||||
AND p.invoice_no LIKE CONCAT('%', #{query.invoiceNo}, '%')
|
||||
</if>
|
||||
<if test="query.salerName != null and query.salerName != ''">
|
||||
AND p.issuer_name LIKE CONCAT('%', #{query.salerName}, '%')
|
||||
</if>
|
||||
<if test="query.buyerName != null and query.buyerName != ''">
|
||||
AND p.receiver_name LIKE CONCAT('%', #{query.buyerName}, '%')
|
||||
</if>
|
||||
<if test="query.invoiceType != null and query.invoiceType != ''">
|
||||
AND p.invoice_type LIKE CONCAT('%', #{query.invoiceType}, '%')
|
||||
</if>
|
||||
<if test="query.salerTaxNo != null and query.salerTaxNo != ''">
|
||||
AND p.saler_tax_no = #{query.salerTaxNo}
|
||||
</if>
|
||||
<if test="query.buyerTaxNo != null and query.buyerTaxNo != ''">
|
||||
AND p.buyer_tax_no = #{query.buyerTaxNo}
|
||||
</if>
|
||||
<if test="query.invoiceDateStart != null and query.invoiceDateStart != ''">
|
||||
AND p.invoice_date >= #{query.invoiceDateStart}
|
||||
</if>
|
||||
<if test="query.invoiceDateEnd != null and query.invoiceDateEnd != ''">
|
||||
AND p.invoice_date <= #{query.invoiceDateEnd}
|
||||
</if>
|
||||
<if test="query.claimed != null and query.claimed">
|
||||
AND <include refid="claimedExists"/>
|
||||
</if>
|
||||
<if test="query.claimed != null and !query.claimed">
|
||||
AND NOT <include refid="claimedExists"/>
|
||||
</if>
|
||||
<if test="query.registered != null and query.registered">
|
||||
AND <include refid="registeredExists"/>
|
||||
</if>
|
||||
<if test="query.registered != null and !query.registered">
|
||||
AND NOT <include refid="registeredExists"/>
|
||||
</if>
|
||||
ORDER BY p.source_updated_time DESC, p.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectInvoicePoolVO" resultType="org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO">
|
||||
SELECT
|
||||
<include refid="invoicePoolVoColumns"/>
|
||||
FROM blade_kingdee_invoice_pool p
|
||||
WHERE p.is_deleted = 0 AND p.status = 1 AND p.tenant_id = #{tenantId} AND p.invoice_no = #{invoiceNo}
|
||||
LIMIT 1
|
||||
</select>
|
||||
</mapper>
|
||||
+18
-25
@@ -31,10 +31,9 @@ import org.springblade.transport.pojo.dto.InvoiceReceiptSaveRequest;
|
||||
import org.springblade.transport.pojo.dto.InvoiceReceiptStatusRequest;
|
||||
import org.springblade.transport.pojo.dto.KingdeeInvoicePoolQueryDTO;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceipt;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceiptRecord;
|
||||
import org.springblade.transport.pojo.entity.KingdeeInvoicePool;
|
||||
import org.springblade.transport.pojo.vo.InvoiceReceiptVO;
|
||||
import org.springblade.transport.pojo.vo.KingdeeInvoicePoolVO;
|
||||
import org.springblade.transport.pojo.vo.KingdeeInvoicePoolPageVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -51,21 +50,14 @@ public interface IInvoiceReceiptService extends BaseService<InvoiceReceipt> {
|
||||
InvoiceReceiptVO detail(Long id);
|
||||
|
||||
/**
|
||||
* 金蝶票据池镜像分页查询(含本地认领/登记标记),收票登记选票与正式结算单认领共用
|
||||
* 金蝶票池实时统一查询(A801-1 游标分页 + 本地认领/登记装饰标记),收票登记选票与正式结算单认领共用
|
||||
* <p>A801-1 不返回总数,改用「多取 1 条探测」得出 hasMore 驱动上一页/下一页;
|
||||
* claimed/registered 仅本地展示装饰,不下推金蝶过滤。
|
||||
*
|
||||
* @param query 统一查询条件
|
||||
* @return 镜像分页
|
||||
* @return 实时游标分页
|
||||
*/
|
||||
IPage<KingdeeInvoicePoolVO> invoicePoolPage(KingdeeInvoicePoolQueryDTO query);
|
||||
|
||||
/**
|
||||
* 按发票号码从金蝶票池精确补查单张发票:与定时同步同一套白名单校验,
|
||||
* 通过后 upsert 入镜像并返回;不受增量时间窗口约束
|
||||
*
|
||||
* @param invoiceNo 发票号码
|
||||
* @return 镜像统一视图
|
||||
*/
|
||||
KingdeeInvoicePoolVO fetchKingdeeInvoiceByNo(String invoiceNo);
|
||||
KingdeeInvoicePoolPageVO invoicePoolPage(KingdeeInvoicePoolQueryDTO query);
|
||||
|
||||
List<Map<String, Object>> settlementCandidates(String keyword, Long receiptId);
|
||||
|
||||
@@ -86,22 +78,23 @@ public interface IInvoiceReceiptService extends BaseService<InvoiceReceipt> {
|
||||
String syncKingdee(Long id);
|
||||
|
||||
/**
|
||||
* 定时查询金蝶全票池发票(A801-1)并绑定收票登记
|
||||
* <p>
|
||||
* 按租户加分布式锁做 single-flight(需开启 {@code blade.lock.enabled}):多副本并发触发、
|
||||
* 或手动触发撞上进行中的轮次时,抢不到锁的一轮直接跳过并返回提示,不重复拉取与写库。
|
||||
* 批量推送金蝶收票单(图05 环节②③:A303-2 收票单批量保存 + A302-6 收票指定应付)。
|
||||
* <p>逐单独立成败:前置校验(审批通过、已绑定镜像、查验通过、恰好分摊一张应付结算单)→
|
||||
* A102-4 预检财务应付单已生成 → A801-3 幂等预检(命中跳过保存续推指定)→ A303-2 保存 →
|
||||
* 立即 A302-6 指定应付;成功置 assigned 并锁单,失败置 failed 提示人工处理后重推。
|
||||
*
|
||||
* @return 执行摘要,含拉取页数、镜像新增/更新、绑定与状态刷新统计
|
||||
* @param ids 收票登记id列表
|
||||
* @return 执行摘要(逐单失败原因)
|
||||
*/
|
||||
String syncKingdeeInvoicePool();
|
||||
String pushKingdeeReceivedInvoice(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 批量应用金蝶票池绑定结果(页级事务):登记绑定批量更新 + 同步流水批量写入,整页原子生效
|
||||
* 定时回写金蝶收票单结果(A801-3):金蝶侧删除/红冲/作废 → 回收为 failed 提示人工(不自动作废登记);
|
||||
* pushed(保存成功后中断)→ 指定补偿;assigned 仅补登缺失的收票单号
|
||||
*
|
||||
* @param receipts 变更比对后需更新、且已回填绑定字段的收票登记
|
||||
* @param records 同步流水,绑定关系变化或金蝶状态迁移时才写,可为 receipts 的子集
|
||||
* @return 执行摘要
|
||||
*/
|
||||
void applyInvoicePoolSyncBatch(List<InvoiceReceipt> receipts, List<InvoiceReceiptRecord> records);
|
||||
String syncKingdeeReceivedInvoiceResult();
|
||||
|
||||
/**
|
||||
* 应用金蝶票池同步结果:回填收票登记绑定关系与同步状态(独立事务,仅手动单条同步使用)
|
||||
@@ -109,7 +102,7 @@ public interface IInvoiceReceiptService extends BaseService<InvoiceReceipt> {
|
||||
* @param receipt 收票登记实体
|
||||
* @param invoice upsert 后的票据池镜像记录,null 表示金蝶票池未查询到
|
||||
* @param missReason 未命中原因,命中时可为空
|
||||
* @param rebind 是否强制重新绑定镜像(手动同步 true)
|
||||
* @param rebind 是否强制重新绑定镜像(定时绑定链路退役后唯一调用方手动同步恒传 true,形参保守保留)
|
||||
* @return 金蝶流水号(serialNo),未命中返回 null
|
||||
*/
|
||||
String applyInvoicePoolSyncResult(InvoiceReceipt receipt, KingdeeInvoicePool invoice, String missReason,
|
||||
|
||||
+551
-609
File diff suppressed because it is too large
Load Diff
+325
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.thirdparty.bfi.constant.BfiConstant;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignItemDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiInvoiceAssignRefDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiReceivedInvoiceSaveEntryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiInvoicePoolItemVO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiReceivedInvoiceVO;
|
||||
import org.springblade.thirdparty.bfi.service.IBfiService;
|
||||
import org.springblade.thirdparty.bfi.util.BfiUtil;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceipt;
|
||||
import org.springblade.transport.pojo.entity.KingdeeInvoicePool;
|
||||
import org.springblade.transport.service.KingdeeSyncContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 金蝶收票单同步支撑组件(图05 环节②③:A303-2 收票单批量保存 + A302-6 收票指定应付 + A801-3 收票单查询)。
|
||||
* <p>收票登记「推送金蝶」由 {@link org.springblade.transport.service.IInvoiceReceiptService} 编排前置校验
|
||||
* (审批通过、已绑定镜像、一票一应付、A102-4 应付单已生成预检、A801-3 幂等预检)后调用本组件;
|
||||
* 报文组装取数:主表组织/币种/往来户复用应付支撑组件的合同派生方法,票面字段与分录取自票池镜像
|
||||
* (invoice_type_code / items_json,A801-1 落库)。防重键:收票单 awrp_src_code=发票号码(一票一登记天然唯一),
|
||||
* awrp_entry_code=发票号码-明细行号;A302-6 按「收票单防重键 ↔ 财务应付单防重键」一票一应付指定。
|
||||
* 财务应付单侧(A102-2/A102-4)取数与校验复用 {@link KingdeeApBillSyncSupport}。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class KingdeeReceivedInvoiceSyncSupport {
|
||||
|
||||
/** A801-3 分页查询页大小 */
|
||||
private static final int RECEIVED_QUERY_PAGE_SIZE = 200;
|
||||
/** A801-3/A303-2 统一时间格式 */
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
/** A801-3 发票状态:作废(2)/红冲(3)/部分红冲(7)/全部红冲(8) 视为金蝶侧异常,回收推送状态 */
|
||||
private static final List<String> ABNORMAL_INVOICE_STATUS = List.of("2", "3", "7", "8");
|
||||
|
||||
private final IBfiService bfiService;
|
||||
private final KingdeeApBillSyncSupport kingdeeApBillSyncSupport;
|
||||
private final BfiProperties bfiProperties;
|
||||
|
||||
// ==================== A801-3 收票单查询(幂等预检/定时回写共用) ====================
|
||||
|
||||
/**
|
||||
* A801-3 按购方名称+修改时间窗口分页拉取收票单,本地按外部系统编号+来源单据号(防重键)过滤。
|
||||
* 契约无来源单据号查询参数,防重键匹配在返回行上进行;查询失败中断本轮,由任务层记录异常
|
||||
*
|
||||
* @param buyerName 购方名称(接口必填,取镜像受票单位)
|
||||
* @param srcCodes 待匹配的收票单防重键(发票号码)集合
|
||||
* @param windowStart 修改时间-开始(窗口不超一个月,调用方控制)
|
||||
* @param windowEnd 修改时间-结束
|
||||
* @return 发票号码 -> 收票单行(同键多行取修改时间最新)
|
||||
*/
|
||||
public Map<String, BfiReceivedInvoiceVO> queryReceivedInvoiceBySrcCodes(String buyerName, Collection<String> srcCodes,
|
||||
LocalDateTime windowStart, LocalDateTime windowEnd) {
|
||||
Map<String, BfiReceivedInvoiceVO> matched = new LinkedHashMap<>();
|
||||
if (Func.isBlank(buyerName) || Func.isEmpty(srcCodes)) {
|
||||
return matched;
|
||||
}
|
||||
BfiReceivedInvoiceQueryDTO param = new BfiReceivedInvoiceQueryDTO();
|
||||
param.setBuyername(buyerName.trim());
|
||||
param.setModifytimeS(windowStart.format(TIME_FORMATTER));
|
||||
param.setModifytimeE(windowEnd.format(TIME_FORMATTER));
|
||||
List<BfiReceivedInvoiceVO> rows = BfiUtil.queryAllPages(pageNo -> {
|
||||
param.setPageNo(pageNo);
|
||||
param.setPageSize(RECEIVED_QUERY_PAGE_SIZE);
|
||||
return bfiService.queryReceivedInvoice(param);
|
||||
});
|
||||
for (BfiReceivedInvoiceVO row : rows) {
|
||||
if (!bfiProperties.getSrcSys().equals(row.getAwrpSrcSys()) || Func.isBlank(row.getAwrpSrcCode())
|
||||
|| !srcCodes.contains(row.getAwrpSrcCode())) {
|
||||
continue;
|
||||
}
|
||||
BfiReceivedInvoiceVO exist = matched.get(row.getAwrpSrcCode());
|
||||
// 同防重键多行(删除重建等)取修改时间最新一行
|
||||
if (exist == null || compareModifyTime(row, exist) >= 0) {
|
||||
matched.put(row.getAwrpSrcCode(), row);
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金蝶侧发票状态是否异常(作废/红冲/部分红冲/全部红冲)
|
||||
*
|
||||
* @param invoiceStatus A801-3 invoicestatus
|
||||
* @return true 表示须回收推送状态、提示人工
|
||||
*/
|
||||
public boolean isAbnormalInvoiceStatus(String invoiceStatus) {
|
||||
return Func.isNotBlank(invoiceStatus) && ABNORMAL_INVOICE_STATUS.contains(invoiceStatus.trim());
|
||||
}
|
||||
|
||||
private int compareModifyTime(BfiReceivedInvoiceVO left, BfiReceivedInvoiceVO right) {
|
||||
String leftTime = left.getModifytime() == null ? "" : left.getModifytime();
|
||||
String rightTime = right.getModifytime() == null ? "" : right.getModifytime();
|
||||
return leftTime.compareTo(rightTime);
|
||||
}
|
||||
|
||||
// ==================== A303-2 收票单批量保存 ====================
|
||||
|
||||
/**
|
||||
* 推送金蝶收票单(A303-2 批量保存,调用方已完成 A801-3 幂等预检)。
|
||||
* 主表:组织/币种/往来户编码按分摊结算单关联合同派生(与应付单同源),销方=开票单位、购方=受票单位;
|
||||
* 分录逐票池明细行展开(items_json),物料编码为联调期临时配置值。
|
||||
* 联调口径沿用应付/应收侧:先只送 DTO 标注【必填】字段与票面核心字段(发票号码、税额金额),
|
||||
* 非必填的地址/电话/开户行等 setter 注释保留,联调确认后放开
|
||||
*
|
||||
* @param receipt 收票登记(审批通过、已绑定镜像)
|
||||
* @param mirror 票池镜像(invoice_type_code/items_json 取数来源)
|
||||
* @param contract 分摊应付结算单关联合同(组织/币种/往来户派生)
|
||||
* @param context 金蝶同步上下文(缓存载体)
|
||||
* @return 金蝶收票单单据编号
|
||||
*/
|
||||
public String pushReceivedInvoice(InvoiceReceipt receipt, KingdeeInvoicePool mirror, ContractManage contract,
|
||||
KingdeeSyncContext context) {
|
||||
if (Func.isBlank(mirror.getInvoiceTypeCode())) {
|
||||
throw new ServiceException("推送金蝶收票单失败:镜像缺少票易通发票类型编码,请先重新绑定票池发票");
|
||||
}
|
||||
List<BfiInvoicePoolItemVO> items = parseItems(mirror);
|
||||
BfiReceivedInvoiceSaveDTO param = new BfiReceivedInvoiceSaveDTO();
|
||||
// 【必填】外部系统编号(候选键)
|
||||
param.setAwrpSrcSys(bfiProperties.getSrcSys());
|
||||
// 【必填】外部系统来源单据号(候选键):发票号码(一票一登记天然唯一,重推同键幂等)
|
||||
param.setAwrpSrcCode(receipt.getInvoiceNo());
|
||||
// 发票号码(非必填但为收票单核心业务字段,必送)
|
||||
param.setInvoiceno(receipt.getInvoiceNo());
|
||||
// 【必填】收票日期:以推送当日为收票日期
|
||||
param.setReceivedate(LocalDate.now().atStartOfDay().format(TIME_FORMATTER));
|
||||
// 【必填】开票日期:镜像票面开票日期
|
||||
param.setIssuedate(kingdeeApBillSyncSupport.bfiDateTimeText(mirror.getInvoiceDate()));
|
||||
// 【必填】业务类型(PUR 采购,待金蝶确认)
|
||||
param.setBiztype(KingdeeBfiConstant.BFI_RECEIVED_BIZ_TYPE);
|
||||
// 【必填】发票类型.编码:票易通原始编码(26/27 等)
|
||||
param.setInvoicetypef7Number(mirror.getInvoiceTypeCode());
|
||||
// 【必填】付款方式:赊购(与应付单一致,不允许金蝶自动推付款)
|
||||
param.setPaymenttype(BfiConstant.PAY_MODE_CREDIT);
|
||||
// 【必填】往来类型:进项收票固定供应商
|
||||
param.setAsstacttype(BfiConstant.ASSTACT_TYPE_SUPPLIER);
|
||||
// 【必填】结算组织.编码:合同所属组织(与应付单同源)
|
||||
param.setOrgNumber(kingdeeApBillSyncSupport.bfiOrgNumber(contract));
|
||||
// 【必填】结算币别:合同结算币种
|
||||
param.setCurrencyNumber(kingdeeApBillSyncSupport.bfiCurrency(contract));
|
||||
// 【必填】往来户.编码:合同乙方(开票供应商)客商编码
|
||||
param.setReceivablessuppNumber(kingdeeApBillSyncSupport.bfiAsstactNumber(contract, context));
|
||||
// 【必填】汇率表.编码
|
||||
param.setExratetableNumber(BfiConstant.EXRATE_TABLE_DEFAULT);
|
||||
// 【必填】销方名称:镜像开票单位
|
||||
param.setAsstactname(mirror.getIssuerName());
|
||||
// 【必填】购方名称:镜像受票单位
|
||||
param.setBuyername(mirror.getReceiverName());
|
||||
// 录入价税合计(非必传,与应付/应收侧口径一致传 true)
|
||||
param.setIsincludetax(true);
|
||||
// 销方税号(非必传,联调确认后放开)
|
||||
// param.setSellertin(mirror.getSalerTaxNo());
|
||||
// 购方税号(非必传,联调确认后放开)
|
||||
// param.setBuyertin(mirror.getBuyerTaxNo());
|
||||
// 销方地址/电话/开户行/账号(非必传,被金蝶强校验时从镜像 bank_name/bank_account/issuing_bank/phone 补齐)
|
||||
// param.setSelleraddr(null);
|
||||
// param.setSellertel(mirror.getPhone());
|
||||
// param.setSellerbank(mirror.getBankName());
|
||||
// param.setSellracct(mirror.getBankAccount());
|
||||
// 备注(非必传)
|
||||
// param.setRemark(receipt.getRemark());
|
||||
// 【必填】外部单据类型
|
||||
param.setAwrpSrcType(KingdeeBfiConstant.BFI_RECEIVED_INVOICE_SRC_TYPE);
|
||||
// 【必填】明细分录:逐票池明细行展开
|
||||
param.setEntry(buildEntries(receipt.getInvoiceNo(), items));
|
||||
|
||||
BfiBillSaveResultVO saveResult = bfiService.saveReceivedInvoice(param);
|
||||
kingdeeApBillSyncSupport.requireBfiSaveSuccess(saveResult, "推送金蝶收票单");
|
||||
return saveResult.getResult().get(0).getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* 票池明细行 → A303-2 分录:金额/税额/数量/单价直取票面明细,税收分类编码取 goodsCode,
|
||||
* 税率换算为金蝶税率编码(0.13→V13),物料编码为联调期临时配置值(发票明细无金蝶物料概念)
|
||||
*
|
||||
* @param invoiceNo 发票号码(分录防重键前缀)
|
||||
* @param items 票池明细行
|
||||
* @return A303-2 明细分录
|
||||
*/
|
||||
private List<BfiReceivedInvoiceSaveEntryDTO> buildEntries(String invoiceNo, List<BfiInvoicePoolItemVO> items) {
|
||||
List<BfiReceivedInvoiceSaveEntryDTO> entries = new ArrayList<>();
|
||||
int index = 1;
|
||||
for (BfiInvoicePoolItemVO item : items) {
|
||||
BigDecimal amount = parseItemAmount(invoiceNo, item.getDetailAmount(), "金额");
|
||||
BigDecimal tax = parseItemAmount(invoiceNo, item.getTaxAmount(), "税额");
|
||||
BfiReceivedInvoiceSaveEntryDTO entry = new BfiReceivedInvoiceSaveEntryDTO();
|
||||
// 【必填】价税合计:明细行金额+税额
|
||||
entry.setEPricetaxtotal(kingdeeApBillSyncSupport.money(amount.add(tax)));
|
||||
// 【必填】数量:票面数量,无数量兜底 1
|
||||
BigDecimal quantity = parseItemAmount(invoiceNo, item.getNum(), "数量");
|
||||
entry.setQuantity(quantity.compareTo(BigDecimal.ZERO) > 0 ? quantity : BigDecimal.ONE);
|
||||
// 【必填】单价:票面单价(金额口径为价税合计录入,单价仅作参考)
|
||||
entry.setPrice(parseItemAmount(invoiceNo, item.getUnitPrice(), "单价"));
|
||||
// 【必填】物料编码.编码:联调期临时值,真实口径与金蝶对齐后替换
|
||||
entry.setMaterialNumber(bfiProperties.getReceivedInvoice().getMaterialNumber());
|
||||
// 【必填】税收分类.编码:票面明细税收分类编码
|
||||
if (Func.isBlank(item.getGoodsCode())) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + index + "行明细无税收分类编码");
|
||||
}
|
||||
entry.setETaxclassNumber(item.getGoodsCode().trim());
|
||||
// 【必填】税率.编码:票面税率(小数)换算金蝶税率编码(0.13→V13)
|
||||
entry.setTaxrateidNumber(taxRateCode(invoiceNo, index, item.getTaxRate()));
|
||||
// 金额(不含税)/税额(非必传,票面直取)
|
||||
entry.setEAmount(kingdeeApBillSyncSupport.money(amount));
|
||||
entry.setETax(kingdeeApBillSyncSupport.money(tax));
|
||||
// 【必填】外部系统来源分录标识(候选键):发票号码-行号
|
||||
entry.setAwrpEntryCode(invoiceNo + "-" + index);
|
||||
entries.add(entry);
|
||||
index++;
|
||||
}
|
||||
if (entries.isEmpty()) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "票池明细为空,请先补查票池发票明细");
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* 票池明细行 JSON 解析(items 为空时返回空列表,由调用方拦截提示补查)
|
||||
*/
|
||||
public List<BfiInvoicePoolItemVO> parseItems(KingdeeInvoicePool mirror) {
|
||||
if (Func.isBlank(mirror.getItemsJson())) {
|
||||
return List.of();
|
||||
}
|
||||
BfiInvoicePoolItemVO[] items = JsonUtil.parse(mirror.getItemsJson(), BfiInvoicePoolItemVO[].class);
|
||||
return items == null ? List.of() : Arrays.asList(items);
|
||||
}
|
||||
|
||||
/** 票面税率(小数文本)换算金蝶税率编码:0.13→V13、0.01→V1、0→V0 */
|
||||
private String taxRateCode(String invoiceNo, int lineNo, String taxRateText) {
|
||||
if (Func.isBlank(taxRateText)) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + lineNo + "行明细税率为空");
|
||||
}
|
||||
try {
|
||||
BigDecimal percent = new BigDecimal(taxRateText.trim()).multiply(BigDecimal.valueOf(100)).stripTrailingZeros();
|
||||
return "V" + percent.toPlainString();
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "第" + lineNo + "行明细税率[" + taxRateText + "]无法解析");
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal parseItemAmount(String invoiceNo, String value, String name) {
|
||||
if (Func.isBlank(value)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(value.trim());
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new ServiceException("推送金蝶收票单失败:发票" + invoiceNo + "明细" + name + "[" + value + "]无法解析");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== A302-6 收票指定应付 ====================
|
||||
|
||||
/**
|
||||
* 收票单指定财务应付单(A302-6,一票一应付):按「收票单防重键(发票号码)↔ 财务应付单防重键
|
||||
* (正式结算单号_结算明细单据号拼接)」建立绑定;金蝶侧解除指定(unassign)不自动执行,人工处理后重推
|
||||
*
|
||||
* @param invoiceSrcCode 收票单防重键(发票号码,即 A303-2 的 awrp_src_code)
|
||||
* @param apBillSrcCode 财务应付单防重键
|
||||
*/
|
||||
public void assignApBill(String invoiceSrcCode, String apBillSrcCode) {
|
||||
BfiInvoiceAssignItemDTO item = new BfiInvoiceAssignItemDTO();
|
||||
item.setApInvoice(List.of(buildAssignRef(invoiceSrcCode)));
|
||||
item.setApFinapbill(List.of(buildAssignRef(apBillSrcCode)));
|
||||
BfiBillSaveResultVO assignResult = bfiService.invoiceAssign("assign", List.of(item));
|
||||
kingdeeApBillSyncSupport.requireBfiSaveSuccess(assignResult, "金蝶收票单指定应付");
|
||||
}
|
||||
|
||||
private BfiInvoiceAssignRefDTO buildAssignRef(String awrpSrcCode) {
|
||||
BfiInvoiceAssignRefDTO ref = new BfiInvoiceAssignRefDTO();
|
||||
ref.setAwrpSrcCode(awrpSrcCode);
|
||||
ref.setAwrpSrcSys(bfiProperties.getSrcSys());
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.task;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 收票管理金蝶全票池发票定时绑定任务
|
||||
* <p>
|
||||
* 定时查询金蝶发票云全票池发票数据(A801-1,经 BFI 网关 rim 契约透传),
|
||||
* 按发票号码 upsert 票据池镜像并与收票登记绑定关联,回填金蝶流水号与同步状态。
|
||||
* 业务逻辑收敛在 {@link IInvoiceReceiptService#syncKingdeeInvoicePool()},便于手动触发联调。
|
||||
* <p>
|
||||
* 调度配置(Nacos thirdParty.bfi.invoiceReceipt.*):syncEnabled 开关、cron 表达式、incrementDays 增量窗口。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InvoiceReceiptKingdeeSyncTask {
|
||||
|
||||
private final IInvoiceReceiptService invoiceReceiptService;
|
||||
|
||||
/** 全票池绑定任务开关,默认关闭(联调稳定后开启) */
|
||||
@Value("${thirdParty.bfi.invoiceReceipt.syncEnabled:false}")
|
||||
private boolean syncEnabled;
|
||||
|
||||
/** 金蝶全票池发票绑定调度,默认每30分钟执行一次 */
|
||||
@Scheduled(cron = "${thirdParty.bfi.invoiceReceipt.cron:0 */30 * * * ?}")
|
||||
public void syncKingdeeInvoicePool() {
|
||||
if (!syncEnabled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String summary = invoiceReceiptService.syncKingdeeInvoicePool();
|
||||
log.info("定时绑定金蝶全票池发票:{}", summary);
|
||||
} catch (Exception exception) {
|
||||
log.error("定时绑定金蝶全票池发票异常", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -23,6 +23,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IInvoiceApplicationService;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
import org.springblade.transport.service.IPaymentApplicationService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
@@ -37,6 +38,9 @@ import org.springframework.stereotype.Component;
|
||||
* ③ A303-5 开票结果回写:开票申请发票结果落库并推进开票完成。
|
||||
* 各子流程业务逻辑收敛在对应服务方法,便于手动触发联调。
|
||||
* <p>
|
||||
* 另有金蝶收票单结果回写(A801-3,图05 环节②③异常回收/指定补偿)独立调度,
|
||||
* 配置 thirdParty.bfi.receivedInvoiceResult.*。
|
||||
* <p>
|
||||
* 调度配置(Nacos thirdParty.bfi.payResult.*):syncEnabled 开关、cron 表达式。
|
||||
*
|
||||
* @author Chill
|
||||
@@ -49,6 +53,7 @@ public class PaymentKingdeeResultTask {
|
||||
private final IPaymentApplicationService paymentApplicationService;
|
||||
private final IFormalSettlementService formalSettlementService;
|
||||
private final IInvoiceApplicationService invoiceApplicationService;
|
||||
private final IInvoiceReceiptService invoiceReceiptService;
|
||||
|
||||
/** 付款结果回写开关,默认开启 */
|
||||
@Value("${thirdParty.bfi.payResult.syncEnabled:false}")
|
||||
@@ -82,4 +87,22 @@ public class PaymentKingdeeResultTask {
|
||||
log.error("定时回写金蝶开票结果异常", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/** 金蝶收票单结果回写开关(A801-3),默认关闭(联调稳定后开启) */
|
||||
@Value("${thirdParty.bfi.receivedInvoiceResult.syncEnabled:false}")
|
||||
private boolean receivedInvoiceResultEnabled;
|
||||
|
||||
/** 金蝶收票单结果回写调度(异常回收/指定补偿/单号补登),默认每10分钟执行一次 */
|
||||
@Scheduled(cron = "${thirdParty.bfi.receivedInvoiceResult.cron:0 */10 * * * ?}")
|
||||
public void syncKingdeeReceivedInvoiceResult() {
|
||||
if (!receivedInvoiceResultEnabled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String summary = invoiceReceiptService.syncKingdeeReceivedInvoiceResult();
|
||||
log.info("定时回写金蝶收票单结果:{}", summary);
|
||||
} catch (Exception exception) {
|
||||
log.error("定时回写金蝶收票单结果异常", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -62,6 +62,12 @@ public class InvoiceReceiptWrapper extends BaseEntityWrapper<InvoiceReceipt, Inv
|
||||
case "failed" -> "同步失败";
|
||||
default -> "未同步";
|
||||
});
|
||||
vo.setKingdeePushStatusName(switch (entity.getKingdeePushStatus() == null ? "" : entity.getKingdeePushStatus()) {
|
||||
case "pushed" -> "收票单已保存";
|
||||
case "assigned" -> "已指定应付";
|
||||
case "failed" -> "推送失败";
|
||||
default -> "未推送";
|
||||
});
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
/**
|
||||
* 金蝶数据定时同步测试:手动触发收票管理金蝶全票池发票绑定(A801-1),打印执行摘要便于联调抽查。
|
||||
* <p>
|
||||
* 测试用属性显式关闭 {@code thirdParty.bfi.invoiceReceipt.syncEnabled}:{@code @SpringBootTest} 会连同
|
||||
* {@code @EnableScheduling} 一起启动上下文,不关闭则 cron 会在同一 JVM 内并发再跑一轮,
|
||||
* 与手动这一轮互相抢数据(未开启 blade.lock.enabled 时没有 single-flight 保护),执行摘要会对不上。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@SpringBootTest(classes = TransportApplication.class, properties = {
|
||||
"spring.profiles.active=dev",
|
||||
"spring.main.allow-bean-definition-overriding=true",
|
||||
"thirdParty.bfi.invoiceReceipt.syncEnabled=false"
|
||||
})
|
||||
class SyncKingdeeTest {
|
||||
|
||||
@Autowired
|
||||
private IInvoiceReceiptService invoiceReceiptService;
|
||||
|
||||
@Test
|
||||
void syncKingdeeInvoicePool() {
|
||||
String summary = invoiceReceiptService.syncKingdeeInvoicePool();
|
||||
System.out.println("金蝶数据定时同步执行结果:" + summary);
|
||||
}
|
||||
}
|
||||
@@ -148,18 +148,6 @@ thirdParty:
|
||||
syncEnabled: ${BFI_PAY_RESULT_SYNC_ENABLED:true}
|
||||
# 调度 cron,默认每10分钟
|
||||
cron: ${BFI_PAY_RESULT_CRON:0 */10 * * * ?}
|
||||
# 收票管理金蝶全票池发票定时绑定(A801-1 查询全票池发票数据)
|
||||
invoiceReceipt:
|
||||
# 绑定任务开关,默认关闭(联调稳定后开启)
|
||||
syncEnabled: ${BFI_INVOICE_RECEIPT_SYNC_ENABLED:false}
|
||||
# 调度 cron,默认每30分钟
|
||||
cron: ${BFI_INVOICE_RECEIPT_CRON:0 */30 * * * ?}
|
||||
# 增量窗口天数(按票池更新时间),首轮/兜底全量比对窗口
|
||||
incrementDays: ${BFI_INVOICE_RECEIPT_INCREMENT_DAYS:30}
|
||||
# 同步发票种类白名单(逗号分隔票易通编码,如 26,27 数电普票/专票),为空不限制
|
||||
invoiceTypes: ${BFI_INVOICE_RECEIPT_INVOICE_TYPES:}
|
||||
# 补偿绑定单轮限量(未绑定登记按发票号码精确补查条数上限)
|
||||
compensateLimit: ${BFI_INVOICE_RECEIPT_COMPENSATE_LIMIT:200}
|
||||
|
||||
#百度OCR配置,API Key和Secret Key请通过环境变量注入
|
||||
baidu:
|
||||
|
||||
@@ -121,18 +121,6 @@ thirdParty:
|
||||
syncEnabled: ${BFI_PAY_RESULT_SYNC_ENABLED:true}
|
||||
# 调度 cron,默认每10分钟
|
||||
cron: ${BFI_PAY_RESULT_CRON:0 */10 * * * ?}
|
||||
# 收票管理金蝶全票池发票定时绑定(A801-1 查询全票池发票数据)
|
||||
invoiceReceipt:
|
||||
# 绑定任务开关,默认关闭(联调稳定后开启)
|
||||
syncEnabled: ${BFI_INVOICE_RECEIPT_SYNC_ENABLED:false}
|
||||
# 调度 cron,默认每30分钟
|
||||
cron: ${BFI_INVOICE_RECEIPT_CRON:0 */30 * * * ?}
|
||||
# 增量窗口天数(按票池更新时间),首轮/兜底全量比对窗口
|
||||
incrementDays: ${BFI_INVOICE_RECEIPT_INCREMENT_DAYS:30}
|
||||
# 同步发票种类白名单(逗号分隔票易通编码,如 26,27 数电普票/专票),为空不限制
|
||||
invoiceTypes: ${BFI_INVOICE_RECEIPT_INVOICE_TYPES:}
|
||||
# 补偿绑定单轮限量(未绑定登记按发票号码精确补查条数上限)
|
||||
compensateLimit: ${BFI_INVOICE_RECEIPT_COMPENSATE_LIMIT:200}
|
||||
|
||||
#百度OCR配置,API Key和Secret Key请通过环境变量注入
|
||||
baidu:
|
||||
|
||||
@@ -49,15 +49,3 @@ thirdParty:
|
||||
syncEnabled: ${BFI_PAY_RESULT_SYNC_ENABLED:true}
|
||||
# 调度 cron,默认每10分钟
|
||||
cron: ${BFI_PAY_RESULT_CRON:0 */10 * * * ?}
|
||||
# 收票管理金蝶全票池发票定时绑定(A801-1 查询全票池发票数据)
|
||||
invoiceReceipt:
|
||||
# 绑定任务开关,默认关闭(联调稳定后开启)
|
||||
syncEnabled: ${BFI_INVOICE_RECEIPT_SYNC_ENABLED:false}
|
||||
# 调度 cron,默认每30分钟
|
||||
cron: ${BFI_INVOICE_RECEIPT_CRON:0 */30 * * * ?}
|
||||
# 增量窗口天数(按票池更新时间),首轮/兜底全量比对窗口
|
||||
incrementDays: ${BFI_INVOICE_RECEIPT_INCREMENT_DAYS:30}
|
||||
# 同步发票种类白名单(逗号分隔票易通编码,如 26,27 数电普票/专票),为空不限制
|
||||
invoiceTypes: ${BFI_INVOICE_RECEIPT_INVOICE_TYPES:}
|
||||
# 补偿绑定单轮限量(未绑定登记按发票号码精确补查条数上限)
|
||||
compensateLimit: ${BFI_INVOICE_RECEIPT_COMPENSATE_LIMIT:200}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
-- 收票管理 图05 环节②③:推送金蝶收票单(A303-2 收票单批量保存 + A302-6 收票指定应付 + A801-3 定时回写)
|
||||
-- 票据池镜像表 blade_kingdee_invoice_pool 新增:
|
||||
-- invoice_type_code 票易通原始发票类型编码(A801-1 invoiceType,如 26/27,A303-2 invoicetypef7_number 取数)
|
||||
-- items_json A801-1 发票明细行JSON(品名/数量/单价/金额/税率/税额/税收分类编码,A303-2 分录取数来源)
|
||||
-- 收票登记表 blade_invoice_receipt 新增:
|
||||
-- kingdee_received_invoice_no 金蝶收票单单据编号(A303-2 返回/幂等预检命中回填)
|
||||
-- kingdee_push_status 收票单推送状态(unsynced 未推送 / pushed 已保存 / assigned 已指定应付 / failed 推送失败)
|
||||
-- kingdee_push_msg 推送失败原因(金蝶侧删除/红冲/查验不通过等异常回收提示人工)
|
||||
-- kingdee_push_time 最近一次推送/回写时间
|
||||
-- 以下结构升级支持重复执行
|
||||
|
||||
-- ① blade_kingdee_invoice_pool.invoice_type_code
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_kingdee_invoice_pool` ADD COLUMN `invoice_type_code` varchar(8) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''票易通原始发票类型编码(A801-1 invoiceType,如26/27)'' AFTER `invoice_type`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_kingdee_invoice_pool'
|
||||
AND COLUMN_NAME = 'invoice_type_code'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
-- ② blade_kingdee_invoice_pool.items_json
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_kingdee_invoice_pool` ADD COLUMN `items_json` longtext COLLATE utf8mb4_general_ci COMMENT ''A801-1发票明细行JSON(A303-2分录取数来源)'' AFTER `attachments_json`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_kingdee_invoice_pool'
|
||||
AND COLUMN_NAME = 'items_json'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
-- ③ blade_invoice_receipt.kingdee_received_invoice_no
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_invoice_receipt` ADD COLUMN `kingdee_received_invoice_no` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''金蝶收票单单据编号(A303-2生成)'' AFTER `kingdee_bill_no`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_invoice_receipt'
|
||||
AND COLUMN_NAME = 'kingdee_received_invoice_no'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
-- ④ blade_invoice_receipt.kingdee_push_status
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_invoice_receipt` ADD COLUMN `kingdee_push_status` varchar(16) COLLATE utf8mb4_general_ci NOT NULL DEFAULT ''unsynced'' COMMENT ''收票单推送状态(unsynced未推送/pushed已保存/assigned已指定应付/failed推送失败)'' AFTER `kingdee_status`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_invoice_receipt'
|
||||
AND COLUMN_NAME = 'kingdee_push_status'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
-- ⑤ blade_invoice_receipt.kingdee_push_msg
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_invoice_receipt` ADD COLUMN `kingdee_push_msg` varchar(500) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''收票单推送失败原因'' AFTER `kingdee_push_status`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_invoice_receipt'
|
||||
AND COLUMN_NAME = 'kingdee_push_msg'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
-- ⑥ blade_invoice_receipt.kingdee_push_time
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_invoice_receipt` ADD COLUMN `kingdee_push_time` datetime DEFAULT NULL COMMENT ''最近一次收票单推送/回写时间'' AFTER `kingdee_push_msg`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_invoice_receipt'
|
||||
AND COLUMN_NAME = 'kingdee_push_time'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 金蝶票池镜像降级为登记存根(2026-09-22 票池改回实时查询收尾)
|
||||
-- 背景:选票链路已改为实时查询 A801-1,blade_kingdee_invoice_pool 仅保留被收票登记引用的镜像行(登记存根),
|
||||
-- 历史全量同步落库的未被引用镜像行属于退役数据,予以清理。
|
||||
-- 幂等:可重复执行,第二次影响 0 行。
|
||||
-- 注意:执行前必须先向用户报盘点数字并获确认(dev 库清理口径:事务 + 回滚 SQL + mysqldump 文本兜底)。
|
||||
|
||||
-- 执行前先跑盘点(只读):
|
||||
-- SELECT COUNT(*) AS prune_rows FROM blade_kingdee_invoice_pool p
|
||||
-- LEFT JOIN blade_invoice_receipt r ON r.kingdee_invoice_pool_id = p.id AND r.is_deleted = 0
|
||||
-- WHERE r.id IS NULL;
|
||||
-- SELECT COUNT(*) AS keep_rows FROM blade_kingdee_invoice_pool p
|
||||
-- INNER JOIN blade_invoice_receipt r ON r.kingdee_invoice_pool_id = p.id AND r.is_deleted = 0;
|
||||
|
||||
-- 票池镜像降级为登记存根:删除未被任何未删收票登记引用的镜像行(实时查询模式上线前执行)
|
||||
DELETE p FROM blade_kingdee_invoice_pool p
|
||||
LEFT JOIN blade_invoice_receipt r ON r.kingdee_invoice_pool_id = p.id AND r.is_deleted = 0
|
||||
WHERE r.id IS NULL;
|
||||
Reference in New Issue
Block a user