会议差旅报销 (Conference Expense Reimbursement) 是做什么的?
使用相同的数据行生成两份电子表格,总额相同,并将它们引用的每张收据保存到 <conference folder>/החזרים/קבלות/ 中:
- SENT 文件
<conference folder>/החזרים/<full name> הוצאות.xlsx— 唯一需要提交给财务部门的文件。B 列סעיף= 预算表分类;包含收据工作表;银行信息块;汇率。不包含预算与实际支出的对比。 - INTERNAL 文件
<conference folder>/החזרים/<full name> הוצאות - פנימי.xlsx— 用户的本地副本,永不附在邮件中发送或提交。B 列השירות= 每项支出的具体内容(例如 “קפה”、“רכבת שדה–עיר”),在הערות列之后有一个סעיף分类列,并包含预算对比表。
两个文件均为希伯来语且从右至左显示;用英语与用户交流。电子表格的布局来源于内置的 assets/claim-template.xlsx(仅包含标签);用户无需自行提供电子表格。
个人资料(全名和银行信息)。 读取 ~/.config/conference-reimbursement/profile.json(格式为 {"full_name": "...", "bank": {"בנק": "...", "סניף": "...", "מספר חשבון": "..."}})。如果该文件缺失或不完整,使用 AskUserQuestion 工具询问缺失的字段(用户输入自己的值作为回答),然后以权限 600 保存该文件。文件仅保存在该路径下——不要放在任何仓库或项目文件夹内。切勿提交该文件,切勿在聊天或报告中打印银行详细信息(应说明“银行信息:来自个人资料”),且仅在暂存区域的 spec 文件的 bank 键中使用这些信息。
通过 AskUserQuestion 工具向用户提出所有问题——不要用散文形式提问——每次只做一个决策,并将推荐选项放在首位。
选择模式
- 模式 A — 完整报销。 已批准的预算表 PDF、银行信息(个人资料)、收据文件夹和邮箱访问权限全部可用。执行步骤 1–7。
- 模式 B — 部分输入。 上述一项或多项缺失。执行相同步骤,忽略缺失部分,切勿自行编造:
- 用户拒绝提供银行信息 → 省略
bank;脚本写入空白标记单元格并警告“银行信息缺失——请手动填写”;在报告中说明; - 无邮箱访问权限 → 仅使用用户放入
קבלות/中的文件;在报告中说明未搜索邮箱;在步骤 7 中打印邮件正文而非草拟邮件; - 无预算表 → 省略
budget;INTERNAL 文件在无预算表的情况下构建,且不与批准金额进行比较。
- 用户拒绝提供银行信息 → 省略
在报告中将每个模式 B 的缺口列在“需手动完成”部分。
1. 收集输入(只读)
- 已批准的预算。 打开会议文件夹中的旅行预算表 PDF(
*טופס נסיעות*)。自行阅读;不同机构的表格可能不同。记录每项批准的金额(美元为单位)(每日津贴 אש״ל、机票 טיסה、酒店、保险、注册、其他)以及旅行日期。 - 个人资料。 按上述方式加载全名和银行信息(仅在首次询问)。
- 实体收据。 列出
קבלות/中的文件,并读取每张图片(如有 HEIC 格式,先转换,见步骤 3)。记录日期、提供商、金额、货币、信用卡末四位数字和用餐人数。 - 邮箱收据。 在预订日期到返回日期再加约两周的时间范围内搜索邮箱,并查找用户的旅行标签或文件夹。在一个轮次中一次性运行每个类别的查询——先阅读
references/email-sources.md。不要仅局限于用户列出的内容:发现未被注意到的费用是任务的一部分;对找到的每一项都向用户提出。 - 排除其他行程。 打开时间窗口内的每笔财务转账和每个航班预订,核对会议、路线和日期。在报告中记录每项排除原因。
2. 询问政策决策
仅询问适用的决策,每次一个问题,并将推荐选项放在首位。以下默认值来自一次过去的报销申请;它们是建议,并非机构规则。
- 合住酒店预订 → 仅自己的房间(推荐)/ 整个预订。
- 共享餐食 → 全部账单 / 仅自己的份额。将所选选项标记出来,供财务人员参考。
- 汇率 → 返回日期的以色列银行代表性汇率,每种货币一个汇率(推荐)/ 每张收据日期对应的汇率。
- 共享物品(一张涵盖两人的海报发票)→ 报销用户自己的份额,并附上说明。
- 与机票一起购买的附加服务(座位、行李、保险)→ 归入机票一项(推荐)。
3. 将收据保存为 PDF
- 邮件附件。 如果邮件连接器返回了邮件正文但未返回附件,首先重新检查它现在是否能够获取附件;如果不行,通过浏览器自动化下载(打开邮件,点击附件的下载图标,然后将文件从
~/Downloads移到指定位置)。如果没有浏览器自动化,请用户自行下载附件或将其放入קבלות/文件夹。 - 无附件的邮件(订单确认信)→ 打印为 PDF。
- 将每个文件命名为
<内容> <提供商> <单据号>.pdf,使用希伯来语。 - HEIC 照片 → 在原文件旁添加一个 PDF 副本,切勿修改原文件。macOS 使用命令:
sips -s format pdf IN.heic --out IN.pdf。其他系统使用 pillow-heif 库(如已安装),否则请用户将照片导出为 PDF 或 JPEG 格式。 对于电子表格的收据工作表,还需将每张照片和截图转换为 JPEG 格式,以收据的基础文件名命名,保存在暂存区域(切勿覆盖原文件):sips -s format jpeg -s formatOptions 70 -Z 1400 IN --out "<收据基础文件名>.jpg"。 - 读取每个保存的 PDF。从文档本身获取金额,而非邮件文本。如果两者不一致,以文档为准并标记差异。
4. 构建电子表格
-
获取所选日期的以色列银行代表性汇率(boi.org.il),涵盖使用的所有外币。将日期填入
rates.label:שער יציג בנק ישראל, <dd.mm.yyyy>。切勿使用记忆中的汇率或此前报销申请中的汇率。 -
在暂存区域写入 spec JSON。Schema、布局和模式 B 行为请参考:
references/spec-format.md;合成的实例请参考:references/example-spec.json。 从个人资料中设置bank。省略template:默认使用内置模板。 -
行规则:
- 每一行对应一张收据,按日期排序;
- 金额仅出现在一个键中:原始货币(
usd、eur或其他也在rates中的货币代码)或ils; receipt=קבלות/中的文件基础名称,多个文件用+连接;note解释任何财务人员可能质疑的内容(共享账单、半张发票、从酒店发票中拆分出的项目);line= 预算行名称,拼写必须与budget中的完全一致;SENT 文件的 B 列显示此名称(仅分类,不要显示“咖啡”或“晚餐”之类的具体描述——财务人员看到的是批准分类),INTERNAL 文件的סעיף列和预算表也使用它;service= 用希伯来语简短描述项目(“קפה”、“ארוחת ערב”、“רכבת שדה–עיר”);仅在 INTERNAL 文件的 B 列中显示(如果省略,则显示分类);- 餐食(包括会议晚宴)归入每日津贴一项;
- 酒店账单中的餐厅费用应单独列为餐食行,酒店行金额相应减少。
receipt_images= 步骤 3 中生成的 JPEG 文件(仅本次行程的照片和截图)。它们将放入קבלות工作表,每行 5 张,每张图片下方有一个标题单元格,内容为其文件基础名称。通过邮件收到的 PDF 不嵌入电子表格;它们将放入步骤 7 的压缩包中。
-
运行:
python3 -I "${CLAUDE_PLUGIN_ROOT}/skills/conference-reimbursement/scripts/build_claim.py" spec.json一次运行即可生成两个文件。它输出 JSON,包含
output(SENT 文件)、output_internal(INTERNAL 文件)、total_ILS、total_USD和warnings。修复所有关于预算行的警告;将模式 B 的警告一并写入报告。output_internal默认路径是在 SENT 路径的文件名.xlsx前加上- פנימי;仅在需要将本地副本放置在其他位置时,才在 spec 中设置它。 -
脚本拒绝覆盖任一输出文件,除非在 spec 中设置了
"overwrite": true。切勿对用户已手动编辑过的文件设置此选项;应写入新文件名或询问用户。
5. 验证
“完成”意味着已运行并检查过。先阅读 references/pitfalls.md。
-
运行检查:
python3 -I "${CLAUDE_PLUGIN_ROOT}/skills/conference-reimbursement/scripts/check_claim.py" spec.jsontotal/total_internal:脚本的 Python 总金额等于 LibreOffice 重新计算后每个文件的סה״כ בש״ח单元格值(精确到 0.01 ₪),因此两个文件的总金额相同;sent_no_budget:SENT 文件中不包含预算对比表;receipts:每个引用的文件都存在于קבלות/文件夹中;budget:每一行的line都是预算行,且 INTERNAL 文件预算表中的实际总额等于total_ILS / USD 汇率。
对于标记为
not_run的检查项目,报告为“未运行”,切勿报告为“已通过”。 -
独立审计:启动一个子代理,仅向其提供 SENT 电子表格和收据文件夹——不要提供你的结论。要求它检查每一行的金额、货币、日期和文件是否与收据一致,并列出发票员可能质疑的任何问题。修复真实错误;对其他问题仅作报告。
6. 报告
按以下顺序报告:
- 两个文件的路径(SENT:待附加;INTERNAL:仅本地)、行数、总 ₪ 和约等 USD 金额,并与批准总额进行比较。
- 新增的收据(以及哪些是通过搜索用户列表之外的途径发现的)。
- 使用的汇率,包括来源和日期。
- 超出预算的行,以表格形式列出:行名称、批准金额、实际金额、差异。
- 不同来源(邮件与文档)之间的差异。
- 排除项(属于其他行程的项目)。
- 财务人员标记(来自
references/pitfalls.md)。 - 需手动完成的部分(模式 B 的缺口、脚本警告)。
- 未经询问而做出的决策。
7. 草拟发送给财务联系人的邮件
先阅读 references/email-draft.md;按顺序执行。仅使用和附加 SENT 文件(output,<full name> הוצאות.xlsx);切勿附加、上传或引用 INTERNAL - פנימי 文件——email_summary.py 会拒绝它。简而言之:
- 阅读用户与财务联系人之前的报销沟通线程(已发送邮件,大约两年内,包含回复),并模仿其风格和主题模式。
- 询问:银行信息是否包含在邮件中;是否包含预算或汇率行(默认:都不包含);是否密送给同行旅伴(默认:否;切勿编造地址)。
- 运行
scripts/zip_ereceipts.py spec.json→ 生成包含电子收据 PDF 的קבלות אלקטרוניות.zip压缩包(使用 UTF-8 文件名;切勿使用 macOS 的zip命令)。照片和截图已包含在קבלות工作表中。 - 运行
scripts/email_summary.py spec.json …→ 生成 RTL HTML 正文(“היי ,” / “מה שלומך? :)” … “תודה רבה!”),包含一个汇总表,其中有一个“קבלה”列,标明每张收据的位置,以及从电子表格中读取的银行信息。 - 检查其
total_ILS是否与构建步骤中的总额一致;如有rounding_diff则提及。 - 先调用 Gmail 的
create_draft,并传入htmlBody。然后通过 Claude 在 Chrome 中附加 SENT 电子表格和压缩包(在草稿的撰写文件输入框上使用file_upload方法),并确认两个附件都已添加——连接器仅支持在调用中以 base64 形式传递附件,而update_draft会丢弃它们。切勿发送邮件。向用户提供草稿链接。
公式
- 行金额:
ILS_i = amount_i × rate[cur_i],如果已经是 ₪ 金额则直接使用。 - 总金额:
T = Σ ILS_i;美元金额为T / rate[USD]。 - 每个预算行 ℓ(INTERNAL 文件):
actual_ℓ = Σ_{line_i = ℓ} ILS_i / rate[USD];diff_ℓ = approved_ℓ − actual_ℓ,负值表示超出预算。当每一行的line都在预算表中时,Σ_ℓ actual_ℓ = T / rate[USD]。
示例:汇率 USD 4.00,EUR 4.50;注册费 $500,酒店 €600,出租车 ₪200 → 2,000 + 2,700 + 200 = 4,900 ₪ = $1,225。
判断事项
- 酒店自身的发票优先于预订网站的确认单(因为酒店发票是税务凭证)。
- 银行信息来自本地个人资料,应放入 spec 的
bank键中;脚本会按标签写入这些信息。仅通过 AskUserQuestion 工具询问银行信息,不要用散文形式询问,也不要复述这些信息。 - 如果之前有部分报销属于本次行程,应将相关行标记为“已报销”。
- 用户所在机构的规则决定政策问题;如有疑问应向用户提问,而不是自行假设规则。
参考资料
references/email-sources.md— 每个类别的邮箱查询内容和检查事项(步骤 1.4)。references/spec-format.md— spec 键、两个文件的布局、脚本输出、模式 B 行为(步骤 4)。references/example-spec.json— 合成的完整 spec 示例(步骤 4)。references/pitfalls.md— 以前见过的失败案例和财务人员标记检查清单(步骤 5–6)。references/email-draft.md— 邮件草稿:之前的通信线程、措辞、附件(步骤 7)。