diff --git a/doc/docs/业务流程/2026-10-31-先票后款改造方案V2(收票登记A303-2+付款结算单A102-2+A302-6指定应付·实现流程与逻辑).md b/doc/docs/业务流程/2026-10-31-先票后款改造方案V2(收票登记A303-2+付款结算单A102-2+A302-6指定应付·实现流程与逻辑).md new file mode 100644 index 00000000..d3f8c943 --- /dev/null +++ b/doc/docs/业务流程/2026-10-31-先票后款改造方案V2(收票登记A303-2+付款结算单A102-2+A302-6指定应付·实现流程与逻辑).md @@ -0,0 +1,184 @@ +# 先票后款改造方案 V2(收票登记 A303-2 + 付款结算单 A102-2 + A302-6 指定应付 · 实现流程与逻辑) + +> 日期:2026-10-31 +> 分支:feature/20261031-bfi-收票收款改造 +> 需求来源:禅道 Bug #15850《拆分收票登记,不要求关联结算单,改为在结算单认领收票单》+ 补充说明 +> 前版方案:2026-10-31-先票后款改造方案(收票登记A303-2+付款结算单A102-2+A302-6指定应付).md(本版经需求核对后重排,口径不变) +> 接口文档:doc/docs/业财融合系统接口文档.xlsx(A303-2 / A302-6 / A901-1 / A102-2) +> 关联代码:blade-service/blade-transport(收票登记 InvoiceReceipt、正式结算单 FormalSettlement)、blade-service-api/blade-transport-api + +--- + +## 1. 需求还原(原文逐条) + +**禅道 #15850**:拆分收票登记,不要求关联结算单,改为在结算单认领收票单,认领结果关联到收票单上展示。 + +**补充说明(2026-10-31)**: + +| # | 需求原文 | 理解 | +| --- | --- | --- | +| 1 | 收票登记:需要选择发票,推金蝶收票单批量保存 A303-2 | 收票登记改为纯"收票"动作:从票池选发票 → 推 A303-2 生成金蝶收票单 | +| 2 | 往来户:发票的销售方,在金蝶的供应商代码 | A303-2 主表 receivablessupp_number = 销方名称经客商档案→A901-1 查供应商编码 | +| 3 | 结算组织:合同的承办部门 | 收票登记不挂合同后由**页面手选承办部门**,沿部门树上溯平台公司取金蝶组织编码 | +| 4 | 明细就是发票上的 | A303-2 分录逐行取发票明细,不再从结算明细/费用项拆分 | +| 5 | 在付款结算单上做发票跟结算单的指定:选收票单 + 付款结算单 | 结算单侧认领收票单;财务应付单(A102-2)生成后调 A302-6 完成指定应付 | +| 6 | 整体就是先票后款 | 收票登记=先票(只推收票单),付款结算单=后款(生成应付单+指定应付) | + +**原始需求截图中的两处口径,与简单实现有偏差,已拍板**: +- 截图写"可选**属于该合同**…的收票单"——因收票登记不再挂合同,选票**不按合同过滤**(拍板口径:收票登记先不挂合同); +- 截图付款申请单部分——本期不动(拍板口径 8)。 + +## 2. 需求符合性核对结论 + +前版方案(V1)10 条拍板口径与需求逐条对得上,**无缺口**;3 项默认(明细取镜像不新建接口 / 结算组织页面手选 / 币别固定 CNY)即"简单改造"的取值口径,本版直接沿用并落地。已预改 3 处代码(supplierCode 字段、SaveRequest deptId/deptName、kingdeeOrgNumberByDept)与本方案一致,直接生效。 + +## 3. 总体链路(先票后款两段式) + +### 阶段一:先票——收票登记(不挂结算单/合同) + +``` +选票(A801-1 票池镜像)→ 查发票明细(镜像 items_json)→ 手选结算组织 → 保存草稿 + (销方供应商代码尽力回填:销方名称→客商档案→A901-1 supplierCode) +→ 提交审批 → 审批通过 → A303-2 收票单批量保存(分录=发票明细行,往来户=销方供应商代码) +→ 终态【pushed 已保存收票单】(不再立即 A302-6,也删除 A102-4 应付单预检) +→ 附件联带上传 A902 +→ A801-3 定时回写:红冲/作废回收 failed;金蝶缺单号补登 +``` + +### 阶段二:后款——付款结算单 + +``` +路径A 新增/编辑时关联:发票信息"选择"收票单(候选=审批通过+未被认领)→ 全额认领 +路径B 审批后补关联:列表"发票登记" → 选票保存 → 弹窗"是否将发票匹配数据推送金蝶?" → 确认调 A302-6 + → 两处落关系:FormalSettlementInvoice(结算单侧展示)+ InvoiceReceiptSettlement(收票登记"结算信息"区展示) +→ 结算单审核通过 → A102-2 财务应付单(现状保留)→ 对本单认领且收票单已推(pushed)的逐单 A302-6 指定应付 +→ 收票单【assigned 已指定应付】;单笔失败不阻断结算同步,由 A801-3"指定补偿"自动重试 +``` + +### 关键时序(后款指定应付) + +| 事件 | 动作 | +| --- | --- | +| 结算单审核通过 | A102-2 推送成功后,遍历本单认领关系:收票单 pushed → 调 A302-6 → 置 assigned;失败仅记日志汇总,不阻断 | +| 收票单尚未推送(A303-2 未完成) | 跳过;收票重推任务推成 pushed 后,A801-3"指定补偿"按认领关系自动补指定 | +| 发票登记确认推送 | 手动触发同上指定逻辑(A102-2 已同步 synced 是前置) | +| 结算单作废 / 认领移除 | **不同步金蝶**(不调 A302-6 unassign),金蝶侧人工解除指定后重推 | + +## 4. 数据模型变更与 SQL + +| 变更 | 内容 | 说明 | +| --- | --- | --- | +| `blade_invoice_receipt` 加列 | `supplier_code VARCHAR(50)` | 往来户金蝶供应商代码(发票销售方,保存时尽力回填、推送前强校验) | +| 新表 | 无 | 明细复用 `blade_kingdee_invoice_pool.items_json`(行号 seq 做稳定分录键),不建快照表 | +| 关系表 | 复用 `InvoiceReceiptSettlement` | 收票登记↔结算单认领关系,由结算单侧重建 | +| 菜单 | 付款结算单(code=payment_settlement)下加"发票登记"按钮 | code=payment_settlement_invoice_register | + +SQL:`doc/sql/transport/lqj/temp/06_blade_invoice_receipt_supplier_code_menu_20261031.sql`,**可重复执行**: +1. `supplier_code` 列:information_schema 判断 + PREPARE 动态 ALTER(utf8mb4_general_ci); +2. 菜单:parent 按 code=payment_settlement 定位,NOT EXISTS 防重。 + +## 5. A303-2 报文 + +### 5.1 主表(对比现状) + +| 字段 | 现状 | 目标 | +| --- | --- | --- | +| receivablessupp_number 往来户.编码 | 合同乙方客商编码(bfiAsstactNumber) | **receipt.supplierCode**(销售方金蝶供应商代码);缺失实时查一次 A901-1,仍缺失报错阻断 | +| asstact_number 销售方名称.编码(非必填) | 不送 | 同 supplierCode | +| org_number 结算组织.编码 | 合同项目业务部门反查平台公司 | **页面手选部门**沿部门树反查平台公司(`kingdeeOrgNumberByDept`,已预改) | +| currency_number 结算币别 | 合同结算币种 | 固定 **CNY** | +| asstacttype 往来类型 | 固定常量 | 固定常量不变(不挂合同直接取常量) | +| 其余(awrp_src_code=登记id、receivedate=当日、issuedate、biztype=PUR、invoicetypef7、paymenttype、税率表默认、销购双方名称/税号/银行) | — | 不变 | + +### 5.2 分录(镜像明细行逐行一条) + +| 金蝶字段 | 取值 | +| --- | --- | +| e_pricetaxtotal | 明细金额 detailAmount + 税额 taxAmount(合计为 0 拦截) | +| quantity | num(≤0/空兜底 1) | +| price | unitPrice(空按 价税合计/数量 反算) | +| material_number | goodsCode(行商品编码,缺失拦截) | +| e_taxclass_number | goodsCode(数电票行商品编码即税收分类编码) | +| taxrateid_number | "V"+整数税率(13/13%/0.13→V13;缺失、0/免税行拦截提示人工) | +| e_amount / e_tax | detailAmount / taxAmount(空不送) | +| awrp_entry_code | **发票号码_seq**(候选键,重推幂等) | + +> `BfiReceivedInvoiceSaveEntryDTO` 已有 eTaxclassNumber / taxrateidNumber 字段,只差赋值。明细数据源与进项发票明细接口(rim_inv_electric_qu)行结构一致,**不新建 BFI Feign 接口**;镜像缺票种/明细时按票号回源 fetchMirrorItemsFromPool 兜底。 + +## 6. 后端改造清单(按文件) + +### blade-transport-api(2 处已预改 ✓) + +| 文件 | 改动 | +| --- | --- | +| pojo/entity/InvoiceReceipt.java | +`supplierCode`(✓ 已改) | +| pojo/dto/InvoiceReceiptSaveRequest.java | +`deptId/deptName`;`settlements` 注释改为"兼容历史入参"(✓ 已改) | + +### blade-service/blade-transport + +| 文件 | 改动 | +| --- | --- | +| support/KingdeeApBillSyncSupport.java | +`kingdeeOrgNumberByDept(deptId, context)`,kingdeeOrgNumber 重构复用 `platformCompanyOrgNumber`(✓ 已改) | +| support/KingdeeReceivedInvoiceSyncSupport.java | pushReceivedInvoice 签名改 `(receipt, mirror, context)`(去 contract/entryGroups);主表按 §5.1 取值;buildEntries 改镜像明细行(§5.2)+ parseMirrorItems / taxRateCode / parseMirrorAmount 辅助 | +| service/impl/InvoiceReceiptServiceImpl.java | 见下方①~⑤ | +| service/IInvoiceReceiptService.java | +4 方法声明,推送方法 Javadoc 更新 | +| service/impl/FormalSettlementServiceImpl.java | 见下方⑥~⑧ | +| service/IFormalSettlementService.java | +pushInvoiceAssignKingdee | +| controller/InvoiceReceiptController.java | +GET `/invoice-receipt/invoice-detail-query?invoiceNo=`(读镜像 items_json,缺则回源解析);+GET `/invoice-receipt/claim-candidates?keyword=`(approved+未被认领分页,limit 200);/push-kingdee 文案更新 | +| controller/FormalSettlementController.java | +POST `/formal-settlement/invoice-assign-kingdee` | + +**InvoiceReceiptServiceImpl**: +1. saveDraft:settlements 可选,未传时**不动**既有分摊关系(关系只由结算单侧维护);结算组织取手选 deptId/deptName;付款方=受票单位、收款方=开票单位;supplierCode 经 queryCustomerCode(销方名称) 尽力回填; +2. validateRequest 去掉必选结算单校验;validateStoredAllocation 无关联时跳过; +3. 推送计划缩为 (receipt, mirror, blockedReason):删 A102-4 预检与 A302-6 立即指定(推送终点=pushed);推送前置补齐镜像明细(itemsJson/invoiceTypeCode 空则按票号回源); +4. 回写 reassignPushedReceivedInvoice:无认领关系 → 跳过(不再置 failed); +5. 新增 invoiceDetailQuery / claimCandidates / syncSettlementReceiptRelations / assignClaimedReceipts; +6. `poolMirrorChanged` 比对集补 `itemsJson`(filled 为 null 时跳过比对):明细行变化而票面金额未变(单价变总价不变、goodsCode 修正、行拆合)时不再被"无变化"跳过——先票后款后 items_json 是 A303-2 分录唯一取数来源,必须跟随票池刷新。 + +**FormalSettlementServiceImpl**: +6. rebuildInvoices(payable)与 claimInvoices 末尾挂钩 `invoiceReceiptService.syncSettlementReceiptRelations(settlementId, invoiceRows)`:按行发票号匹配审批通过收票登记 + **一票一结算单校验**(已挂其他非作废应付结算单则拦截)+ 重建 InvoiceReceiptSettlement + 刷新结算单收票状态; +7. doSyncKingdee payable 分支:A102-2 成功后调 `assignClaimedReceipts(settlementId, apBillSrcCode)`(失败不阻断); +8. 新增 pushInvoiceAssignKingdee(settlementId):前置 payable + approved + kingdeeSyncStatus=synced,调 A302-6。 + +**新方法语义**: +- `syncSettlementReceiptRelations(settlementId, invoiceRows)`:一票一结算单校验 → 删本结算单旧关系 → 按 invoiceNo 匹配 approved 收票登记重建关系(allocatedInvoiceAmount=matchedAmount 全额认领)→ 刷新本结算单及被移除关系结算单的收票状态; +- `assignClaimedReceipts(settlementId, apBillSrcCode)`:遍历认领关系,收票单 pushed → A302-6 指定 → assigned;未推送/已指定分别跳过;失败收集汇总返回; +- `claimCandidates(keyword)`:approved + 未被任何非作废应付结算单认领 + 关键字(发票号/销方/购方),limit 200。 + +## 7. 定时任务影响 + +| 任务 | 变化 | +| --- | --- | +| A801-3 收票结果回写(syncKingdeeReceivedInvoiceResult) | "指定补偿"升级为重试引擎:pushed + 已认领 → 自动补发 A302-6;pushed + 未认领 → 跳过不再置 failed;异常回收/单号补登不变 | +| A801-1 票池同步(syncKingdeeInvoicePool) | 明细本已随 fillInvoicePool 落 items_json(新增/更新均写,源侧无明细维持原值);改造点仅在 poolMirrorChanged 比对集补 itemsJson(见 §6-6) | +| 收票推送重推(retryKingdeeFailed / 自动重推) | 不再依赖 A102-4,重推仅需镜像+供应商代码+结算组织齐备 | +| 结算单自动重推(KingdeeAutoSyncRetryTask) | 不变(重推 A102-2 后仍会触发指定) | + +## 8. 前端改造点(../tms-erp-web,另仓库) + +- 收票登记:去税率、去关联结算单选择;加"结算组织"部门选择(必选,即合同承办部门);加发票明细区块(invoice-detail-query 按票号带出);详情"结算信息"区展示认领数据(reference-information/detail 已有出参); +- 付款结算单:新增页发票信息"选择"数据源切 `/invoice-receipt/claim-candidates`(审批通过+未认领),匹配金额固定=发票金额;编辑/查看去税率列;查看页去"认领查询"按钮;列表加"发票登记"入口;发票登记页=结算基本信息只读 + 选票 + 保存后"是否将发票匹配数据推送金蝶?"弹窗 → 确认调 `/invoice-assign-kingdee`。 + +## 9. 兼容性与风险 + +| 点 | 说明 | +| --- | --- | +| 历史收票登记 | 已 assigned 单据不受影响;历史编辑保存未传 settlements 时保留原分摊关系不动 | +| 历史分摊链路 | 传 settlements 的老入参仍走原校验/落关系逻辑(前端切换期间兼容) | +| pushed 状态语义变化 | 收票推送终态由 assigned 变 pushed,KingdeeBfiConstant.RECEIVED_PUSH_PUSHED 相关判断需同步梳理(锁后幂等判断、A801-3 回写分支等) | +| 明细时点 | 明细取镜像最新值:登记→推送间金蝶侧若修正明细行,推送用新明细;红冲/作废由回写回收状态兜底 | +| 免税/零税率行 | taxrateid_number 无法映射,推送时报错人工核实(如业务出现需与金蝶约定编码后放开) | +| currency_number=CNY | 不挂合同后的默认,出现外币票需补充取值来源 | +| 反向操作 | 结算单作废/认领移除不同步金蝶(已拍板),金蝶侧人工解除指定后重推 | + +## 10. 编码顺序与验证 + +1. blade-transport-api:实体/DTO(✓ 已预改); +2. 支撑组件:KingdeeReceivedInvoiceSyncSupport 报文改造; +3. 收票服务:InvoiceReceiptServiceImpl ①~⑤ + 接口声明; +4. 结算服务:FormalSettlementServiceImpl ⑥~⑧ + 接口声明; +5. 控制器:两个端点收票侧 + 一个端点结算侧; +6. SQL:06_blade_invoice_receipt_supplier_code_menu_20261031.sql(可重复执行); +7. 编译:`mvn clean compile -DskipTests -pl blade-service-api/blade-transport-api,blade-service/blade-transport -am`;测试交由用户执行。 + +> 付款申请单(A103-1)链路本期不动。