# 财务单据标准模型 ## 1. 为什么需要标准模型 OCR、MCP、用户填写、业务数据库可能都描述同一张发票,但字段名和格式不同。 如果没有标准模型: - 规则无法复用。 - Agent 难以解释。 - Hermes 难以批量统计。 - MCP 返回结果难以合并。 这里要区分三层: - 标准模型:定义 Agent、规则、MCP、OCR、数据库之间统一交换的数据结构。 - 业务数据库表:定义 MVP 阶段真正落库存储、查询和统计所依赖的业务表。 - 文件存储对象:定义原始票据、预览件、OCR 中间产物、验真结果附件的存储位置与版本规则。 如果只有标准模型,没有业务表和文件资产表,User Agent 无法真正发起报销;如果只有数据库表,没有统一标准模型,语义解析、规则解释、OCR 回填和 Hermes 巡检会越来越混乱。 ## 2. 标准对象 第一版建议定义这些对象: ```text Invoice Receipt ExpenseClaim PaymentRequest AccountsReceivableRecord AccountsPayableRecord BankTransaction Contract Customer Vendor Employee CostCenter DocumentAsset RiskEvent ``` 说明: - 对外语义层建议统一使用 `ExpenseClaim` 概念,不再把“报销申请”和“报销单据”拆成两个平行主概念。 - 现有代码中仍有 `reimbursement_requests` 表,MVP 阶段不建议再继续扩张该表,而应以 `expense_claims` 作为报销主表。 - `reimbursement_requests` 可保留用于兼容旧页面或审批联动,但新能力默认挂到 `expense_claims`。 ## 3. Invoice 标准模型 ```json { "invoice_id": "", "invoice_code": "", "invoice_number": "", "invoice_type": "", "seller_name": "", "seller_tax_no": "", "buyer_name": "", "buyer_tax_no": "", "issue_date": "", "total_amount": 0, "tax_amount": 0, "currency": "CNY", "verify_status": "", "ocr_confidence": 0, "source_document_id": "" } ``` ## 4. ExpenseClaim 标准模型 ```json { "claim_id": "", "claim_no": "", "employee_id": "", "employee_name": "", "department_id": "", "department_name": "", "cost_center_code": "", "project_code": "", "expense_type": "", "reason": "", "location": "", "amount": 0, "currency": "CNY", "status": "", "occurred_at": "", "submitted_at": "", "approval_stage": "", "items": [], "attachments": [], "risk_flags": [] } ``` 说明: - `reason`、`location`、`occurred_at` 是报销语义判断、规则解释、风险识别的最小必要字段。 - 一张报销单通常包含多条费用明细,标准模型中允许聚合,数据库层必须拆到明细表。 - `attachments` 指向文件资产,不直接嵌入二进制文件。 ## 5. AccountsReceivableRecord 标准模型 ```json { "ar_id": "", "document_no": "", "customer_id": "", "customer_name": "", "contract_no": "", "invoice_no": "", "amount_receivable": 0, "amount_received": 0, "amount_outstanding": 0, "currency": "CNY", "due_date": "", "posting_date": "", "status": "", "aging_days": 0, "risk_flags": [] } ``` ## 6. AccountsPayableRecord 标准模型 ```json { "ap_id": "", "document_no": "", "vendor_id": "", "vendor_name": "", "invoice_no": "", "amount_payable": 0, "amount_paid": 0, "amount_outstanding": 0, "currency": "CNY", "due_date": "", "posting_date": "", "status": "", "aging_days": 0, "risk_flags": [] } ``` ## 7. BankTransaction 标准模型 ```json { "transaction_id": "", "bank_account": "", "transaction_date": "", "amount": 0, "currency": "CNY", "counterparty_name": "", "summary": "", "matched_object_type": "", "matched_object_id": "", "match_status": "" } ``` ## 8. MVP 真实业务表设计 标准模型不等于数据库表,但 MVP 至少要有以下真实表,才能支撑 Day 5 用户报销对话、Day 6 风险巡检和后续审批/验真闭环。 ### 8.1 设计原则 - 报销主数据统一落在 `expense_claims`,不再新建第三套“报销主表”。 - 原始票据文件二进制不进数据库,只存元数据和关联信息。 - OCR 结果、发票结构化结果、验真结果、风险事件要分表存,避免把所有字段塞进一个 JSON。 - 所有表都要能被 Agent 解释,也要能被 Hermes 批量扫表。 - `reimbursement_requests` 进入兼容态,不作为新能力主干表继续扩展。 ### 8.2 报销主表 `expense_claims` 用途: - 作为用户报销会话最终落单的主业务对象。 - 承接语义层补槽后的草稿、提交、审批、打回、归档状态。 建议字段: ```text id string(36) PK claim_no string(50) UK, 报销单号 source string(30) 来源: agent/web/import/api title string(200) 报销标题 employee_id string(64) 申请人 ID employee_name string(100) 申请人姓名 department_id string(64) 部门 ID department_name string(100) 部门名 company_code string(50) 公司编码 cost_center_code string(50) 成本中心 project_code string(50) 项目编码 expense_type string(50) 费用大类 reason text 事由 location string(100) 地点 amount numeric(12,2) 报销总金额 currency string(10) 币种 invoice_count int 附件票据数 attachment_count int 附件总数 occurred_start_at timestamptz 发生开始时间 occurred_end_at timestamptz 发生结束时间 submitted_at timestamptz 提交时间 status string(30) draft/submitted/approved/rejected/paid status_changed_at timestamptz 最近状态变更时间 status_changed_by string(64) 最近状态变更人 status_change_note text 状态变更备注 approval_stage string(50) 当前审批节点 risk_level string(20) none/low/medium/high risk_flags_json json 风险标记快照 conversation_id string(64) 对话会话 ID created_by string(64) 创建人 updated_by string(64) 更新人 created_at timestamptz updated_at timestamptz ``` 说明: - 现有模型已有一部分字段,后续只做增量扩展即可。 - `occurred_start_at`、`occurred_end_at` 比单一 `occurred_at` 更适合差旅、接待等跨时段报销。 ### 8.2.1 报销状态流转建议 建议状态: ```text draft submitted approved rejected paid ``` 建议流转: ```text 语义补槽完成 -> 创建 expense_claims 草稿 -> status = draft 用户继续补充字段 / 上传附件 -> 更新 expense_claims / expense_claim_items / expense_item_documents -> status 仍为 draft 用户明确确认提交 -> status = submitted -> 写入 submitted_at / status_changed_at / status_changed_by 审批流结果回写 -> status = approved 或 rejected 付款完成回写 -> status = paid ``` 边界: - User Agent 可以创建 `draft`,也可以在用户确认后提交到 `submitted`。 - User Agent 不应直接把状态改为 `approved`、`rejected`、`paid`。 - 所有状态变化都应写审计日志,必要时保留 `status_change_note`。 ### 8.3 报销明细表 `expense_claim_items` 用途: - 表达一单多明细。 - 作为 OCR 发票比对、重复报销识别、风险定位的最小粒度。 建议字段: ```text id string(36) PK claim_id string(36) FK -> expense_claims.id line_no int 明细序号 item_date date 费用发生日期 item_type string(50) 费用小类 item_reason text 明细事由 item_location string(100) 明细地点 merchant_name string(200) 商户/酒店/餐厅 customer_name string(200) 客户单位 participants_json json 参与人员 transport_type string(50) 交通方式 item_amount numeric(12,2) 明细金额 tax_amount numeric(12,2) 税额 currency string(10) invoice_match_status string(30) unmatched/partial/matched risk_level string(20) risk_flags_json json remark text created_at timestamptz updated_at timestamptz ``` 说明: - 现有 `invoice_id` 单字段不足以覆盖多张附件挂同一明细的情况,后续应改为关联表。 ### 8.4 票据资产主表 `document_assets` 用途: - 作为所有原始附件的主索引表。 - 支持报销单、报销明细、审批、验真、风控证据等多对象挂载。 建议字段: ```text id string(36) PK biz_domain string(30) expense/ap/ar/common biz_object_type string(50) expense_claim/expense_item/approval_record biz_object_id string(36) 业务对象 ID document_type string(50) invoice/receipt/itinerary/contract/other document_subtype string(50) vat_special/taxi/train/hotel/meal 等 source string(30) upload/agent/import/system original_filename string(255) mime_type string(100) file_ext string(20) page_count int file_size_bytes bigint sha256 string(64) 去重与追溯 storage_provider string(30) local/minio/s3/oss/cos storage_bucket string(100) 本地模式可为空 storage_key string(500) 指向当前有效版本原件 current_version_no int classification_status string(30) pending/success/failed ocr_status string(30) pending/running/success/failed virus_scan_status string(30) pending/clean/infected retention_policy string(30) finance_default/legal_hold/manual uploaded_by string(64) uploaded_at timestamptz created_at timestamptz updated_at timestamptz ``` ### 8.5 票据版本表 `document_asset_versions` 用途: - 保留原始文件和后续重新上传版本。 - 允许“修正”但不允许覆盖原始证据。 建议字段: ```text id string(36) PK document_id string(36) FK -> document_assets.id version_no int 1,2,3... is_current bool change_reason string(100) replace/rotate/desensitize/reupload original_filename string(255) mime_type string(100) file_size_bytes bigint sha256 string(64) storage_provider string(30) storage_bucket string(100) storage_key string(500) uploaded_by string(64) uploaded_at timestamptz created_at timestamptz ``` ### 8.6 衍生文件表 `document_derivatives` 用途: - 存储缩略图、预览 PDF、逐页图片、脱敏件等衍生产物。 建议字段: ```text id string(36) PK document_version_id string(36) FK -> document_asset_versions.id derivative_type string(50) thumb/preview/page_image/desensitized page_no int 可空 mime_type string(100) file_size_bytes bigint storage_provider string(30) storage_bucket string(100) storage_key string(500) created_by string(64) created_at timestamptz ``` ### 8.7 OCR 结果表 `document_ocr_results` 用途: - 保留每次 OCR 原始结果、模型版本、置信度和错误信息。 - 支持后续重跑 OCR 与人工纠错对比。 建议字段: ```text id string(36) PK document_id string(36) FK -> document_assets.id document_version_id string(36) FK -> document_asset_versions.id ocr_engine string(50) paddle/aliyun/tencent/openai 等 ocr_model string(100) run_no int 第几次 OCR status string(30) success/failed/partial language string(20) raw_text text raw_result_json json structured_result_json json confidence numeric(5,4) error_message text started_at timestamptz finished_at timestamptz created_at timestamptz ``` ### 8.8 发票结构化表 `invoice_structured_records` 用途: - 将发票核心字段标准化后独立存储,便于查重、验真、规则计算。 建议字段: ```text id string(36) PK document_id string(36) FK -> document_assets.id ocr_result_id string(36) FK -> document_ocr_results.id invoice_code string(50) invoice_number string(50) invoice_type string(50) seller_name string(200) seller_tax_no string(50) buyer_name string(200) buyer_tax_no string(50) issue_date date total_amount numeric(12,2) tax_amount numeric(12,2) currency string(10) check_code string(100) is_red_invoice bool is_electronic bool ocr_confidence numeric(5,4) normalized_status string(30) normalized/manual_corrected duplicate_fingerprint string(100) 发票号+代码+金额+日期 created_at timestamptz updated_at timestamptz ``` ### 8.9 发票验真记录表 `invoice_verification_records` 用途: - 保留每次调用税局/第三方验真服务的结果,支持追溯。 建议字段: ```text id string(36) PK invoice_record_id string(36) FK -> invoice_structured_records.id verification_channel string(50) tax_mcp/third_party/manual request_payload_json json response_payload_json json verify_status string(30) verified/unverified/voided/error voided bool red_reversed bool verified_amount numeric(12,2) verified_issue_date date error_code string(50) error_message text verified_by string(64) verified_at timestamptz created_at timestamptz ``` ### 8.10 明细与票据关联表 `expense_item_documents` 用途: - 解决一条明细可关联多张票据、一张票据也可能支撑多条拆分明细的场景。 建议字段: ```text id string(36) PK claim_id string(36) FK -> expense_claims.id claim_item_id string(36) FK -> expense_claim_items.id document_id string(36) FK -> document_assets.id relation_type string(30) evidence/invoice/boarding_pass/receipt allocated_amount numeric(12,2) 分摊到该明细的金额 match_status string(30) unmatched/partial/matched match_confidence numeric(5,4) created_at timestamptz updated_at timestamptz ``` ### 8.11 风险事件表 `risk_events` 用途: - 记录风险命中,而不是只在主表里塞一个 `risk_flags_json`。 - 作为 Agent 解释“为什么拦截”的核心依据。 建议字段: ```text id string(36) PK biz_domain string(30) expense/ap/ar biz_object_type string(50) expense_claim/expense_item/invoice biz_object_id string(36) risk_code string(50) duplicate_invoice/amount_mismatch 等 risk_name string(100) risk_level string(20) low/medium/high hit_source string(30) rule/agent/hermes/manual evidence_json json status string(30) open/confirmed/resolved/ignored detected_at timestamptz detected_by string(64) resolved_at timestamptz resolved_by string(64) resolution_note text created_at timestamptz updated_at timestamptz ``` ### 8.12 风险处置表 `risk_actions` 用途: - 记录每次人工确认、驳回、忽略、要求补件等处置动作。 建议字段: ```text id string(36) PK risk_event_id string(36) FK -> risk_events.id action_type string(30) confirm/reject/ignore/request_more action_note text operator_id string(64) operator_name string(100) created_at timestamptz ``` ### 8.13 文件访问审计表 `document_access_logs` 用途: - 记录谁看过、下载过、导出过原始票据。 - 支撑财务审计和数据安全追溯。 建议字段: ```text id string(36) PK document_id string(36) FK -> document_assets.id document_version_id string(36) FK -> document_asset_versions.id action string(30) preview/download/export/delete operator_id string(64) operator_name string(100) operator_role string(50) client_ip string(64) user_agent string(255) trace_id string(64) created_at timestamptz ``` ## 9. 表关系建议 ```text expense_claims └─ expense_claim_items └─ expense_item_documents └─ document_assets └─ document_asset_versions └─ document_derivatives └─ document_ocr_results └─ invoice_structured_records └─ invoice_verification_records risk_events -> 可指向 expense_claims / expense_claim_items / invoice_structured_records risk_actions -> risk_events document_access_logs -> document_assets / document_asset_versions ``` 原则: - 主业务对象和文件资产解耦。 - OCR、验真、风险都挂在文件资产或业务对象之上,不把责任塞到一个巨表。 - 文件版本和业务关系分离,避免替换附件时把历史证据冲掉。 ## 10. 与现有表的衔接策略 当前代码中已经存在: - `expense_claims` - `expense_claim_items` - `reimbursement_requests` 建议策略: - `expense_claims` 继续作为未来报销主表。 - `expense_claim_items` 增量扩字段并替换当前单一 `invoice_id` 直连方式。 - `reimbursement_requests` 暂不删除,但冻结扩表。 - 如旧流程仍引用 `reimbursement_requests`,可在过渡期建立: - `request_no -> claim_no` 对照字段 - 或由 `approval_records` 同时支持两类来源对象 不建议做法: - 再新建第四张“报销申请主表”。 - 把原始发票图片以 blob 方式存进 `expense_claims`。 - 把 OCR、验真、风控结果全塞进一个 JSON 大字段。 ## 11. 实施顺序建议 Phase 1: - 扩展 `expense_claims` - 扩展 `expense_claim_items` - 新增 `document_assets` - 新增 `document_asset_versions` - 新增 `expense_item_documents` Phase 2: - 新增 `document_ocr_results` - 新增 `invoice_structured_records` - 新增 `invoice_verification_records` - 新增 `document_derivatives` Phase 3: - 新增 `risk_events` - 新增 `risk_actions` - 新增 `document_access_logs` Phase 4: - 逐步弱化 `reimbursement_requests` - 将 Agent 草稿、审批、OCR、验真、风控全收敛到 `expense_claims` 体系 ## 12. 对 Agent 的直接收益 - 用户说“我要报销”时,Agent 能先创建 `expense_claims` 草稿,再持续补槽。 - 用户上传票据后,系统有明确的 `document_assets` 与 `expense_item_documents` 可挂载。 - OCR 和验真结果不是一次性临时输出,而是可追溯、可回放、可审计的长期资产。 - Agent 回答“为什么被拦截”时,可以直接引用 `risk_events` 和票据证据,不再靠拼字符串解释。