关于 tres-invoice-bill-matching
tres-invoice-bill-matching 是用于将 TRES 账本中的区块链交易与 ERP 发票或账单匹配,并可选同步到 ERP 的对话式技能。它通过候选项和用户确认来完成匹配与更新。
主要功能
- 01检查已连接的 ERP
- 02按交易或单据查找匹配项
- 03根据金额和日期筛选候选项
- 04配置付款账户与法币对齐
- 05确认后应用匹配并可选同步
适用场景
- 01将链上付款匹配到 ERP 账单
- 02将入账交易与 ERP 发票关联
- 03查找特定发票或账单对应的交易
MCP 相关
使用 TRES 的 GraphQL 操作查询 ERP 集成、发票、账单和交易,并在用户明确批准后执行匹配或同步变更。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ anthropics/ claude-plugins-community --skill tres-invoice-bill-matching安装后在 Claude Code 中输入/tres-invoice-bill-matching确认已加载。
详细介绍
TRES — 发票/账单匹配与 ERP 同步
端到端工作流:让用户在 TRES 账本中将未结清的 ERP 发票或账单与区块链交易进行匹配,然后可选择将匹配的条目推送到已连接的 ERP。
无论用户从哪一端开始(交易哈希或发票/账单 ID),流程都相同。该技能会逐步引导用户完成七个对话步骤(验证 ERP → 确认输入 → 获取并提供建议 → 用户选择 → 配置付款账户和法币 → 确认并应用 → 循环)。保持简洁——显示编号选项,记录用户的选择,然后继续。未经用户明确回答“是”,绝不执行任何变更。
基本原则
- 先确认组织。 从
get_viewer开始,并告诉用户“您已连接到 {orgName}。”这样,如果有人使用了错误的令牌,也能及时发现并纠正。 - 先读取,再写入。 在显示变更摘要前,务必获取交易、发票/账单和付款账户的当前状态。出错比操作慢更糟。
- 变更需要明确批准。 显示摘要表(交易 · 发票/账单 · 付款账户 · 法币对齐 · 同步),并在任何变更前询问“应用这些变更吗?”。只有用户明确同意后才能继续。
- 不确定时,使用架构内省。 字段名、枚举值和参数结构可能会发生变化。如果查询/变更因“unknown field”或“invalid enum”报错,请调用
introspect(<TypeName>)或build_query(<operationName>)并进行调整——不要猜测。下文特别标注为“运行时验证”的操作最可能需要这样处理。 - 此技能采用循环流程。 匹配成功后,询问“还要匹配其他项目吗?”,并从步骤 2 重新开始。不要重新检查 ERP——每个会话只需检查一次。
步骤 1 — 验证 ERP 是否已连接
运行:
query { integration(first: 50) { results { id integratedApp isErp connectionStatus companyName } } }
筛选出 isErp == true 且 connectionStatus == "ACTIVE" 的结果。应能识别的受支持 ERP 包括 Xero、QuickBooks Online (QBO) 和 NetSuite——integratedApp 的取值分别为 XERO、QUICKBOOKS 和 NETSUITE。(如果遇到未知值,introspect("IntegrationsQueryNode") 可以确认枚举。)忽略 integratedApp 为空的行——API 偶尔会返回空行。
- 未连接 ERP: 告诉用户必须先连接 ERP 才能进行匹配,并引导他们访问
https://app.tres.finance/settings/integrations,然后停止。 - 已连接一个 ERP: 默认使用该 ERP,只需说明“正在与 {companyName}({integratedApp})进行匹配。”
- 已连接多个 ERP: 询问用户要使用哪一个——不同的 ERP 各自有独立的发票/账单存储。
缓存所选 ERP 的 id、integratedApp 和 companyName,供后续步骤及循环使用。
步骤 2 — 确认用户掌握的信息
询问用户是否有:
- 交易哈希(例如
0x…)——最佳情况,可立即锁定匹配项; - 发票/账单 ID 或编号(数字格式的内部 ID,或面向用户的
invoiceNumber/billNumber); - 或者两者都有。
如果两者都没有,要求用户至少提供一项。如果他们不确定该标识符对应的是发票还是账单,也可以接受,并在步骤 3 中尝试两种查询。
也接受较宽松的描述形式——“INV-123”、“账单 9988”、“上周给 Acme 的账单”。erpInvoices / erpBills 上的 freeText 筛选器可以处理这些描述。
继续之前,如果用户尚未提供交易哈希,务必询问交易日期(或大致的日期范围)。 账本交易量很大——日期是缩小候选范围最有用的单一筛选条件。同时也可以补充:“如果您刚好有交易哈希,请现在粘贴——这样就能精确锁定匹配项。” 无论从交易查账单,还是从账单查交易,日期都很重要。
步骤 3 — 获取已知对象并生成排序后的匹配建议
分为两个分支。根据用户提供的信息选择相应分支。如果用户同时提供了交易哈希和发票/账单 ID,请直接跳到步骤 5(匹配项已确定)。
分支 A — 用户有交易哈希
- 获取交易及其子交易:
query GetTx($hash: String!) { transaction(identifier: $hash, currency: "usd", limit: 1) { results { id identifier timestamp platform children { id amount balanceFactor isInternalTransfer fiatValue sender { identifier displayName isInternal } recipient { identifier displayName isInternal } asset { symbol identifier } } } } } - 选出相关子交易。跳过 Gas 和内部转账,优先选择一方是用户钱包、另一方是外部交易对手的交易。判断方向:
balanceFactor为负 → 资金流出 → 查找要结清的账单。balanceFactor为正 → 资金流入 → 查找要结清的发票。 如果有多个符合条件的子交易(例如包含多个交易环节的兑换),请列出并让用户选择一项。
- 对于发票(资金流入),先尝试后端匹配建议——这些建议已预先计算并排序:
重要提示:此端点仅适用于发票。对于账单(资金流出),请直接跳到下面的备用方案——架构中没有公开后端账单建议查询。query Suggest($subTxIds: [String]!) { subTransactionToInvoiceMatchSuggestions( subTransactionId_In: $subTxIds, minScore: 0.3, ordering: "-score", first: 10 ) { results { id score confidenceTier scoreBreakdown { txHashMatch primaryMatchFactors } invoice { id invoiceId invoiceNumber customerName origAmount balance dueDate billingStatus integration { integratedApp companyName } } } } } - **备用方案(没有发票建议时,或始终适用于账单):**查询
erpInvoices(资金流入)或erpBills(资金流出),并按以下条件筛选:- 金额范围:子交易法币金额的
±20%。 - 日期范围:
dateCreated_Range = [tx.timestamp − 60d, tx.timestamp]— 账单/发票的开具时间应在付款当天或之前,绝不会在付款之后。无需考虑日期晚于付款日期的发票/账单。 - 联系人:如果用户提供了供应商/客户的名称,请将其作为
freeText传入。切勿尝试将链上收款地址与 ERP 联系人匹配——ERP 联系人标识符(来自 QBO/Xero/NetSuite)是内部 ID,绝不是钱包地址。有些联系人标签的文本碰巧类似供应商名称,但架构没有“类似”筛选条件,因此请使用freeText搜索vendor/customer字段。 在客户端按以下顺序对备用结果集排序:金额接近程度 → 日期接近程度(优先选择交易前最近的账单/发票)→freeText输出中的名称匹配项。
- 金额范围:子交易法币金额的
- 按编号列出排名靠前的至多 5 项(绝不能更多——选项太多不如太少)。每项显示:置信度等级、金额($)、客户/供应商名称、
invoiceNumber/billNumber、到期日,以及primaryMatchFactors(例如amount_exact、contact_match)。最匹配的排在最前。
分支 B — 用户有发票/账单 ID 或编号
- 解析实体。先尝试
erpInvoices,如果不是发票,再尝试erpBills(如果用户说的是“账单”,则反过来)。对于发票,包含内嵌的建议:
对于账单,查询query GetInvoice($id: Float, $text: String) { erpInvoices(id: $id, freeText: $text, first: 1) { results { id invoiceNumber customerName origAmount balance dateCreated dueDate billingStatus integration { integratedApp companyName } suggestedSubTransactionMatches { id score confidenceTier subTransaction { id amount fiatValue asset { symbol identifier } tx { identifier timestamp platform } } } } } }erpBills——注意,账单不会公开suggestedSubTransactionMatches,因此只获取账单元数据(id billNumber vendorName vendorId origAmount balance dateCreated dueDate billingStatus integration { integratedApp companyName }),然后直接进入后备步骤。如果id查询无结果,则使用freeText: $userInput重试。 - 对于发票,将内嵌的
suggestedSubTransactionMatches用作排序后的列表。 - 后备方案(发票没有建议结果,或账单一律如此):
- 首先,询问用户大致的付款日期(并提醒他们:“如果你有 tx hash,那是最快的方式”)。付款发生在账单/发票日期当天或之后,通常在几天内——不会在之前。
如果用户无法提供具体日期,默认使用
[dateCreated, dateCreated + 60 days]作为时间范围。 - 使用服务端筛选条件查询
transaction,避免拉取整个账本。使用:platform: <configuredNetwork or user's network>(例如"ethereum"),timestamp_Gte/timestamp_Lte→ 用户提供的日期范围,children_Asset_Identifier_In: [<assetIdentifier>]→ 账单的configuredAsset.identifier(例如 USDC 合约),或发票的预期资产,children_FiatValue_Between: "<low>,<high>"→origAmount附近的法币金额范围(例如±20%)。这是由两个逗号分隔数字组成的字符串。例如,"0.4,0.6"表示一笔 $0.50 的账单。- 可选的
children_BalanceFactor: -1(资金流出,适用于账单)或1(资金流入,适用于发票)。 - 仅当无法信任法币价格时,才将
children_Amount_Between用作额外筛选条件(例如,经过预定价的历史资产等)。
- 如果筛选结果仍然很多,在展示结果前,请用户缩小日期范围或进一步 уточ金额范围。
- 首先,询问用户大致的付款日期(并提醒他们:“如果你有 tx hash,那是最快的方式”)。付款发生在账单/发票日期当天或之后,通常在几天内——不会在之前。
如果用户无法提供具体日期,默认使用
- 以编号列表形式展示最多 5 笔候选交易——每笔交易都显示:
**tx hash(截短为
0xabcd…1234)、发送方 → 接收方地址(相同的缩短格式)、代币金额 + 符号、法币价值和时间戳。**tx hash 是用户能识别的面向用户标识;原始地址和代币金额可在仅凭法币金额过于模糊时帮助区分(许多交易的金额相近)。**绝不要向用户显示subTxId之类的内部 ID——这些对用户毫无意义。**将subTxId保留在内部供变更操作使用;所有面向用户的输出都应通过 tx hash 引用该交易。 如果没有合适的匹配项,请如实告知并询问 tx hash——不要猜测并倾倒一大串结果。
第 4 步 — 用户选择匹配项(或要求查看更多)
提示:“选择一个编号,或输入 more 以扩大搜索范围(告诉我一个联系人姓名、日期或金额,方便我缩小范围)。”
- 输入编号时:捕获选定的
(subTxId, invoiceOrBillId, entityType),然后继续第 5 步。 - 输入
more时:使用更宽松的范围或用户补充的线索(联系人筛选条件、扩展后的日期范围、金额范围)重新查询,展示新的列表并重复此步骤。 - 用户说“这些都不是”或类似内容时:妥善结束——“好的,没有匹配项。等你有更多信息时再告诉我。”
第 5 步 — 配置付款账户和(可选)法币对齐
选定一对匹配项后:
-
存款账户(用于
matchApAr.depositAccountId)。 这是一个 ERP 集成账户 (账单将记入的 Xero/QBO/NetSuite 总账账户,例如“Crypto Wallet – ETH”、 “Cash and cash equivalents”)。它不是 tres 内部账户或钱包账户。Schema 外键指向schema_integrationaccount;该值位于ErpBill.depositAccount/ErpInvoice.depositAccount中,类型为IntegrationAccountQuery { id name type value }。解析顺序:
- 使用账单/发票上预设的账户。 在 ERP 中创建实体时,用户通常已选择存款账户。读取
erpBills.results[].depositAccount.id(或erpInvoices.results[].depositAccount.id)。如果该值 非 null,则将该 id 用作depositAccountId,并在摘要中提及即可——无需询问。 - 否则询问用户。 查询
integrationAccount(integration: <erpId>, first: 100),并以编号列表向用户 展示 ASSET 类型的账户(现金/钱包/加密货币总账账户);记录用户选择的integrationAccount.id。 paymentToInternalAccounts在 tres 端将 钱包 → 资产 映射起来,适用于出款 工作流——它不是matchApAr的depositAccountId来源。此处不要使用它。
- 使用账单/发票上预设的账户。 在 ERP 中创建实体时,用户通常已选择存款账户。读取
-
法币对齐。 比较子交易的
fiatValue与发票/账单的origAmount。分三种情况:fiatValue为 null 或 0:询问 “此交易尚未设置法币价值。是否将其对齐为 ${origAmount}, 以便该 {bill/invoice} 全额结清?(y/n)” — 不设默认选项,必须明确答复是。fiatValue与origAmount不同:询问 “是否将交易法币价值从 $X 对齐为 $Y,以便 AP/AR 全额结清?(y/n)” — 不设默认选项,必须明确答复是。- 数值匹配:跳过。
未经明确同意,绝不要运行
setManualFiatValue——即使用户最初的请求暗示要“结清发票”, 法币对齐也是单独的写入操作,需要单独征得同意。如果用户回答否,匹配仍会继续,但该实体可能无法全额付款。
步骤 6 — 确认并应用
显示以下汇总表——仅使用人类可读的值:tx 哈希(截断)、付款账户名称
(绝不使用其 ID)、发票/账单编号或客户/供应商名称。内部 ID(subTxId、depositAccountId、
DB entityId)仅用于变更载荷,绝不向用户展示。
| 交易 | 0xabcd…1234 · {asset.symbol} {amount} · ${fiatValue} |
| 发票/账单 | {invoiceNumber or billNumber} · {customer/vendor} · ${origAmount} {currency} |
| 付款账户 | {depositAccount.name} |
| 对齐法币金额 | 是 → ${origAmount} / 否 |
| 匹配后同步到 ERP | 是 / 否 |
询问:"将这些变更应用到 {orgName}?(是/否)"——只有用户回答是才继续。将此视为仅批准 匹配操作。法币金额对齐和 ERP 同步都需要各自单独的明确肯定答复(法币金额在步骤 5 确认, 同步在下方提问)——即使用户最初的请求中说了“同步到 QBO”,也仍要在此确认,因为用户是在看到 实际匹配详情之前提出的请求。
然后按以下顺序执行变更,并逐项报告结果:
-
匹配(始终执行):
mutation Match($entityType: String!, $matches: [SubTxMatchInput]!) { matchApAr(entityType: $entityType, matches: $matches) { results { subTxId entityId depositAccountId } } }变量:
entityType:小写——"invoice"或"bill"。大写值会被拒绝,并返回Invalid entity_type。matches[].subTxId:字符串(例如"4548595830")。matches[].entityId:整数——发票/账单的 DBid(来自erpInvoices.results[].id/erpBills.results[].id),而不是面向用户的invoiceNumber/billNumber。传入字符串 会报错 "Int cannot represent non-integer value";传入账单编号则查无结果,并报错 "One or more Bill IDs not found"。matches[].depositAccountId:整数——集成账户 ID(见步骤 5)。
-
法币金额对齐(仅当用户选择进行时):
mutation AlignFiat($id: String!, $newFiatValue: String!, $currency: String) { setManualFiatValue(id: $id, newFiatValue: $newFiatValue, currency: $currency) { success } }将子交易 ID 作为
id,将目标值作为字符串传入newFiatValue(例如发票/账单的origAmount,如"123.45"),并设置currency: "usd"。如果响应返回与“locked period”(锁定期间)相关的错误,说明包含该交易的会计期间已关闭——向用户说明情况;不要尝试强制执行。 -
ERP 同步(每次都先询问): "现在将此交易同步到 {erpName}?(是/否)"——此时未获得明确肯定答复, 绝不进行同步,即使用户最初的请求中包含“并同步到 QBO”也是如此。同步调用会在 ERP 中显示, 且比匹配操作更难撤销,因此需要单独确认。若回答是:
mutation Sync($txIds: [String]!, $entityType: String!) { syncSpecificTransactions(transactionIds: $txIds, entitySourceType: $entityType) { status } }entitySourceType与匹配调用中的entityType保持一致。
向用户报告结果时,请用tx 哈希(截断)来指代交易,并用付款账户的名称来指代账户——绝不使用
subTxId 或 depositAccountId。这些内部 ID 仅用于调试,不应出现在面向用户的输出中。
如果任何变更报错,请原样显示错误消息,并提出重试。真正将记录关联起来的是匹配操作——如果它失败, 后续操作都没有意义。
步骤 7 — 循环
询问:"再匹配一笔交易或一张发票/账单?(是/否)"
- 是 → 从步骤 2 重新开始(跳过步骤 1——保留本次会话中缓存的 ERP)。
- 否 → 结束:"完成。本次会话已处理 {n} 项。"
已验证使用的 TRES MCP 操作
| 用途 | 操作 | 类型 |
|---|---|---|
| 组织身份 | get_viewer | MCP 工具 |
| 架构发现 | introspect, build_query | MCP 工具 |
| 检查 ERP 连接 | integration(筛选条件 isErp=true) | 查询 |
| 按哈希获取交易 | transaction(identifier:) | 查询 |
| 获取发票(包含嵌入的建议) | erpInvoices | 查询 |
| 获取账单(包含嵌入的建议) | erpBills | 查询 |
| 预计算的子交易 → 发票匹配建议 | subTransactionToInvoiceMatchSuggestions | 查询 |
| 映射至 COA 的付款账户 | paymentToInternalAccounts | 查询 |
| 匹配子交易 ↔ 发票/账单 | matchApAr | 变更 |
| 调整法币价值 | setManualFiatValue(在运行时验证参数) | 变更 |
| 将交易同步至 ERP | syncSpecificTransactions | 变更 |
| 撤销匹配(应用户要求) | manualUnmatchSubtransactions | 变更 |
不在支持范围内(如有询问,请礼貌地引导)
- 批量匹配(多对多)——此技能尚不支持。
- 发送付款(
sendBillPayment、sendInvoicePayment)——属于单独的工作流。 - 连接或撤销连接 ERP 本身——请使用
tres-settings-management技能。 - 以叙述形式解释交易——请使用
tres-tx-story。 - 将区块浏览器链接导入账本——请使用
tres-explorer-tx-to-ledger。