- 11 个超 20KB 的 SKILL.md 拆分,大章节移入 references/details.md - 7 处非标准字段(arguments/user_invocable)移入 metadata - 修 req-dev 4 条相对链接;validate 跳过行内代码避免  误报 - validate-skills.py: 23 warns -> 0 warns,0 errors
504 lines
17 KiB
Markdown
504 lines
17 KiB
Markdown
---
|
||
name: finance
|
||
description: 财务对账技能。支持银行对账、应收对账、应付对账、内部往来对账等。通过自然语言实现账单导入、差异分析、对账报告生成。当用户提到对账、银行流水、应收应付、账单核对、财务核销相关任务时自动激活。
|
||
metadata:
|
||
invocation: user-invoked
|
||
disable-model-invocation: true
|
||
---
|
||
## 功能概述
|
||
|
||
- **银行对账**: 银行流水 vs 系统收付款记录匹配
|
||
- **应收对账**: 客户对账单生成与核对
|
||
- **应付对账**: 供应商对账单核对
|
||
- **内部对账**: 公司间/部门间往来核对
|
||
- **差异分析**: 自动识别未达账项、差异原因
|
||
- **对账报告**: 生成标准化对账报告
|
||
- **扫描单据OCR**: PDF/图片扫描件识别,提取订单号与对账单匹配(适用于诉讼证据核对)
|
||
|
||
---
|
||
|
||
## 对账类型
|
||
|
||
### 1. 银行对账
|
||
|
||
| 对账项 | 说明 |
|
||
|--------|------|
|
||
| 银行已收/企业未收 | 银行已入账,系统未记录 |
|
||
| 企业已收/银行未收 | 系统已记录,银行未入账 |
|
||
| 银行已付/企业未付 | 银行已扣款,系统未记录 |
|
||
| 企业已付/银行未付 | 系统已记录,银行未扣款 |
|
||
|
||
### 2. 应收对账
|
||
|
||
- 客户欠款余额核对
|
||
- 发票与回款匹配
|
||
- 账龄分析
|
||
|
||
### 3. 应付对账
|
||
|
||
- 供应商应付余额核对
|
||
- 采购入库与付款匹配
|
||
- 预付款冲销
|
||
|
||
---
|
||
|
||
## 数据格式要求
|
||
|
||
### 银行流水格式
|
||
|
||
```csv
|
||
交易日期,交易类型,摘要,金额,余额,对方账户,对方名称,交易流水号
|
||
2026-01-15,收入,货款,50000.00,150000.00,622848******1234,深圳XX公司,TXN20260115001
|
||
2026-01-16,支出,采购款,-30000.00,120000.00,622848******5678,广州YY公司,TXN20260116001
|
||
```
|
||
|
||
### 系统收付款格式
|
||
|
||
```csv
|
||
单据日期,单据类型,单据编号,金额,客户/供应商,备注,关联流水号
|
||
2026-01-15,收款单,SK20260115001,50000.00,深圳XX公司,货款,TXN20260115001
|
||
2026-01-16,付款单,FK20260116001,30000.00,广州YY公司,采购款,
|
||
```
|
||
|
||
---
|
||
|
||
## 自然语言操作示例
|
||
|
||
### 银行对账
|
||
|
||
| 用户说 | 执行操作 |
|
||
|--------|----------|
|
||
| "导入银行流水" | 解析银行导出的 Excel/CSV |
|
||
| "和系统记录对账" | 自动匹配流水号/金额/日期 |
|
||
| "找出未达账项" | 识别双方差异记录 |
|
||
| "生成银行余额调节表" | 输出标准调节表 |
|
||
|
||
### 应收对账
|
||
|
||
| 用户说 | 执行操作 |
|
||
|--------|----------|
|
||
| "生成客户对账单" | 按客户汇总应收明细 |
|
||
| "核对XX公司的应收款" | 单客户对账 |
|
||
| "应收账龄分析" | 按账龄分段统计 |
|
||
| "哪些发票还没收到款" | 查询未核销发票 |
|
||
|
||
### 应付对账
|
||
|
||
| 用户说 | 执行操作 |
|
||
|--------|----------|
|
||
| "核对供应商对账单" | 导入并匹配供应商账单 |
|
||
| "本月应付汇总" | 按供应商统计应付 |
|
||
| "哪些采购单还没付款" | 查询未付款采购单 |
|
||
|
||
### 扫描单据OCR对账
|
||
|
||
| 用户说 | 执行操作 |
|
||
|--------|----------|
|
||
| "识别扫描件与对账单匹配" | OCR 提取 PDF 订单号并与 Excel 对账 |
|
||
| "承运商起诉了,核对证据" | 诉讼证据对账分析 |
|
||
| "PDF发票识别" | OCR 提取发票信息 |
|
||
| "找出对账单没有的订单" | 差异订单识别 |
|
||
|
||
---
|
||
|
||
## Python 代码模板
|
||
|
||
### 银行对账核心逻辑
|
||
|
||
```python
|
||
import pandas as pd
|
||
from datetime import datetime
|
||
|
||
def load_bank_statement(file_path: str) -> pd.DataFrame:
|
||
"""加载银行流水"""
|
||
df = pd.read_excel(file_path) if file_path.endswith('.xlsx') else pd.read_csv(file_path)
|
||
|
||
# 标准化列名
|
||
column_mapping = {
|
||
'交易日期': 'date',
|
||
'金额': 'amount',
|
||
'交易流水号': 'ref_no',
|
||
'对方名称': 'counterparty',
|
||
'摘要': 'memo'
|
||
}
|
||
df = df.rename(columns=column_mapping)
|
||
df['date'] = pd.to_datetime(df['date'])
|
||
df['source'] = 'bank'
|
||
|
||
return df
|
||
|
||
def load_system_records(file_path: str) -> pd.DataFrame:
|
||
"""加载系统收付款记录"""
|
||
df = pd.read_excel(file_path) if file_path.endswith('.xlsx') else pd.read_csv(file_path)
|
||
|
||
column_mapping = {
|
||
'单据日期': 'date',
|
||
'金额': 'amount',
|
||
'关联流水号': 'ref_no',
|
||
'客户/供应商': 'counterparty',
|
||
'单据编号': 'doc_no'
|
||
}
|
||
df = df.rename(columns=column_mapping)
|
||
df['date'] = pd.to_datetime(df['date'])
|
||
df['source'] = 'system'
|
||
|
||
return df
|
||
|
||
def reconcile(bank_df: pd.DataFrame, system_df: pd.DataFrame) -> dict:
|
||
"""
|
||
执行对账
|
||
|
||
Returns:
|
||
{
|
||
'matched': 已匹配记录,
|
||
'bank_only': 银行有/系统无 (银行未达),
|
||
'system_only': 系统有/银行无 (企业未达),
|
||
'summary': 对账汇总
|
||
}
|
||
"""
|
||
# 方法1: 按流水号精确匹配
|
||
bank_with_ref = bank_df[bank_df['ref_no'].notna() & (bank_df['ref_no'] != '')]
|
||
system_with_ref = system_df[system_df['ref_no'].notna() & (system_df['ref_no'] != '')]
|
||
|
||
matched_by_ref = pd.merge(
|
||
bank_with_ref,
|
||
system_with_ref,
|
||
on='ref_no',
|
||
suffixes=('_bank', '_system'),
|
||
how='inner'
|
||
)
|
||
|
||
matched_refs = set(matched_by_ref['ref_no'])
|
||
|
||
# 方法2: 无流水号的按金额+日期模糊匹配
|
||
bank_unmatched = bank_df[~bank_df['ref_no'].isin(matched_refs)]
|
||
system_unmatched = system_df[~system_df['ref_no'].isin(matched_refs)]
|
||
|
||
# 按金额和日期(±3天)匹配
|
||
fuzzy_matched = []
|
||
bank_remaining = bank_unmatched.copy()
|
||
system_remaining = system_unmatched.copy()
|
||
|
||
for idx, bank_row in bank_unmatched.iterrows():
|
||
for sys_idx, sys_row in system_remaining.iterrows():
|
||
if (abs(bank_row['amount'] - sys_row['amount']) < 0.01 and
|
||
abs((bank_row['date'] - sys_row['date']).days) <= 3):
|
||
fuzzy_matched.append({
|
||
'bank_idx': idx,
|
||
'system_idx': sys_idx,
|
||
'amount': bank_row['amount'],
|
||
'bank_date': bank_row['date'],
|
||
'system_date': sys_row['date']
|
||
})
|
||
system_remaining = system_remaining.drop(sys_idx)
|
||
bank_remaining = bank_remaining.drop(idx)
|
||
break
|
||
|
||
# 未匹配项
|
||
bank_only = bank_remaining # 银行有/系统无
|
||
system_only = system_remaining # 系统有/银行无
|
||
|
||
# 汇总
|
||
summary = {
|
||
'bank_total': bank_df['amount'].sum(),
|
||
'system_total': system_df['amount'].sum(),
|
||
'matched_count': len(matched_by_ref) + len(fuzzy_matched),
|
||
'bank_only_count': len(bank_only),
|
||
'bank_only_amount': bank_only['amount'].sum() if len(bank_only) > 0 else 0,
|
||
'system_only_count': len(system_only),
|
||
'system_only_amount': system_only['amount'].sum() if len(system_only) > 0 else 0,
|
||
'difference': bank_df['amount'].sum() - system_df['amount'].sum()
|
||
}
|
||
|
||
return {
|
||
'matched': pd.concat([matched_by_ref, pd.DataFrame(fuzzy_matched)]),
|
||
'bank_only': bank_only,
|
||
'system_only': system_only,
|
||
'summary': summary
|
||
}
|
||
```
|
||
|
||
### 银行余额调节表
|
||
|
||
```python
|
||
def generate_bank_reconciliation_report(
|
||
bank_balance: float,
|
||
book_balance: float,
|
||
bank_only: pd.DataFrame,
|
||
system_only: pd.DataFrame,
|
||
as_of_date: str
|
||
) -> str:
|
||
"""生成银行余额调节表"""
|
||
|
||
# 分类未达账项
|
||
bank_receipts_not_in_book = bank_only[bank_only['amount'] > 0]['amount'].sum()
|
||
bank_payments_not_in_book = bank_only[bank_only['amount'] < 0]['amount'].sum()
|
||
book_receipts_not_in_bank = system_only[system_only['amount'] > 0]['amount'].sum()
|
||
book_payments_not_in_bank = system_only[system_only['amount'] < 0]['amount'].sum()
|
||
|
||
report = f"""
|
||
================================================================================
|
||
银行存款余额调节表
|
||
截止日期: {as_of_date}
|
||
================================================================================
|
||
|
||
一、银行对账单余额 {bank_balance:>15,.2f}
|
||
|
||
加: 企业已收/银行未收 {book_receipts_not_in_bank:>15,.2f}
|
||
减: 企业已付/银行未付 {abs(book_payments_not_in_bank):>15,.2f}
|
||
─────────────────
|
||
调节后余额 {bank_balance + book_receipts_not_in_bank + book_payments_not_in_bank:>15,.2f}
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
二、企业账面余额 {book_balance:>15,.2f}
|
||
|
||
加: 银行已收/企业未收 {bank_receipts_not_in_book:>15,.2f}
|
||
减: 银行已付/企业未付 {abs(bank_payments_not_in_book):>15,.2f}
|
||
─────────────────
|
||
调节后余额 {book_balance + bank_receipts_not_in_book + bank_payments_not_in_book:>15,.2f}
|
||
|
||
================================================================================
|
||
未达账项明细
|
||
================================================================================
|
||
|
||
【银行已收/企业未收】
|
||
"""
|
||
|
||
if len(bank_only[bank_only['amount'] > 0]) > 0:
|
||
for _, row in bank_only[bank_only['amount'] > 0].iterrows():
|
||
report += f" {row['date'].strftime('%Y-%m-%d')} {row.get('counterparty', ''):<20} {row['amount']:>12,.2f}\n"
|
||
else:
|
||
report += " (无)\n"
|
||
|
||
report += "\n【银行已付/企业未付】\n"
|
||
if len(bank_only[bank_only['amount'] < 0]) > 0:
|
||
for _, row in bank_only[bank_only['amount'] < 0].iterrows():
|
||
report += f" {row['date'].strftime('%Y-%m-%d')} {row.get('counterparty', ''):<20} {row['amount']:>12,.2f}\n"
|
||
else:
|
||
report += " (无)\n"
|
||
|
||
report += "\n【企业已收/银行未收】\n"
|
||
if len(system_only[system_only['amount'] > 0]) > 0:
|
||
for _, row in system_only[system_only['amount'] > 0].iterrows():
|
||
report += f" {row['date'].strftime('%Y-%m-%d')} {row.get('counterparty', ''):<20} {row['amount']:>12,.2f}\n"
|
||
else:
|
||
report += " (无)\n"
|
||
|
||
report += "\n【企业已付/银行未付】\n"
|
||
if len(system_only[system_only['amount'] < 0]) > 0:
|
||
for _, row in system_only[system_only['amount'] < 0].iterrows():
|
||
report += f" {row['date'].strftime('%Y-%m-%d')} {row.get('counterparty', ''):<20} {row['amount']:>12,.2f}\n"
|
||
else:
|
||
report += " (无)\n"
|
||
|
||
return report
|
||
```
|
||
|
||
### 应收对账
|
||
|
||
```python
|
||
def generate_ar_statement(
|
||
customer_name: str,
|
||
transactions: pd.DataFrame,
|
||
as_of_date: str
|
||
) -> str:
|
||
"""
|
||
生成客户对账单
|
||
|
||
transactions 格式:
|
||
- date: 日期
|
||
- doc_type: 单据类型 (发票/收款/退货)
|
||
- doc_no: 单据编号
|
||
- debit: 借方(应收增加)
|
||
- credit: 贷方(应收减少)
|
||
- memo: 摘要
|
||
"""
|
||
|
||
df = transactions.sort_values('date')
|
||
|
||
report = f"""
|
||
================================================================================
|
||
客户对账单
|
||
================================================================================
|
||
客户名称: {customer_name}
|
||
对账期间: {df['date'].min().strftime('%Y-%m-%d')} 至 {as_of_date}
|
||
================================================================================
|
||
|
||
日期 单据类型 单据编号 借方 贷方 余额
|
||
--------------------------------------------------------------------------------
|
||
"""
|
||
|
||
balance = 0
|
||
for _, row in df.iterrows():
|
||
debit = row.get('debit', 0) or 0
|
||
credit = row.get('credit', 0) or 0
|
||
balance += debit - credit
|
||
|
||
report += f"{row['date'].strftime('%Y-%m-%d')} {row['doc_type']:<8} {row['doc_no']:<16} {debit:>12,.2f} {credit:>12,.2f} {balance:>12,.2f}\n"
|
||
|
||
report += f"""
|
||
--------------------------------------------------------------------------------
|
||
合计 {df['debit'].sum():>12,.2f} {df['credit'].sum():>12,.2f} {balance:>12,.2f}
|
||
================================================================================
|
||
|
||
期末应收余额: {balance:,.2f}
|
||
|
||
请贵司核对以上账目,如有异议请于收到对账单后 7 日内书面告知。
|
||
如无异议,视为确认。
|
||
|
||
对账联系人: _______________
|
||
联系电话: _______________
|
||
"""
|
||
|
||
return report
|
||
```
|
||
|
||
### 账龄分析
|
||
|
||
```python
|
||
from datetime import datetime, timedelta
|
||
|
||
def aging_analysis(
|
||
receivables: pd.DataFrame,
|
||
as_of_date: str,
|
||
aging_buckets: list = [30, 60, 90, 180, 365]
|
||
) -> pd.DataFrame:
|
||
"""
|
||
应收账龄分析
|
||
|
||
receivables 格式:
|
||
- customer: 客户
|
||
- invoice_date: 发票日期
|
||
- amount: 未收金额
|
||
"""
|
||
|
||
as_of = datetime.strptime(as_of_date, '%Y-%m-%d')
|
||
|
||
def get_aging_bucket(invoice_date):
|
||
days = (as_of - invoice_date).days
|
||
for i, bucket in enumerate(aging_buckets):
|
||
if days <= bucket:
|
||
if i == 0:
|
||
return f'0-{bucket}天'
|
||
else:
|
||
return f'{aging_buckets[i-1]+1}-{bucket}天'
|
||
return f'{aging_buckets[-1]+1}天以上'
|
||
|
||
df = receivables.copy()
|
||
df['invoice_date'] = pd.to_datetime(df['invoice_date'])
|
||
df['aging_days'] = (as_of - df['invoice_date']).dt.days
|
||
df['aging_bucket'] = df['invoice_date'].apply(get_aging_bucket)
|
||
|
||
# 按客户和账龄分组汇总
|
||
summary = df.pivot_table(
|
||
index='customer',
|
||
columns='aging_bucket',
|
||
values='amount',
|
||
aggfunc='sum',
|
||
fill_value=0
|
||
)
|
||
|
||
# 添加合计列
|
||
summary['合计'] = summary.sum(axis=1)
|
||
|
||
# 添加合计行
|
||
summary.loc['合计'] = summary.sum()
|
||
|
||
return summary
|
||
```
|
||
|
||
---
|
||
|
||
## 对账报告模板
|
||
|
||
### Excel 对账报告
|
||
|
||
```python
|
||
from openpyxl import Workbook
|
||
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
|
||
|
||
def export_reconciliation_to_excel(
|
||
result: dict,
|
||
output_path: str,
|
||
report_date: str
|
||
):
|
||
"""导出对账结果到 Excel"""
|
||
|
||
wb = Workbook()
|
||
|
||
# Sheet1: 对账汇总
|
||
ws_summary = wb.active
|
||
ws_summary.title = "对账汇总"
|
||
|
||
summary = result['summary']
|
||
ws_summary['A1'] = '对账汇总报告'
|
||
ws_summary['A1'].font = Font(bold=True, size=14)
|
||
ws_summary['A2'] = f'对账日期: {report_date}'
|
||
|
||
headers = ['项目', '金额', '笔数']
|
||
for col, header in enumerate(headers, 1):
|
||
ws_summary.cell(row=4, column=col, value=header)
|
||
|
||
data = [
|
||
('银行流水合计', summary['bank_total'], '-'),
|
||
('系统记录合计', summary['system_total'], '-'),
|
||
('已匹配', '-', summary['matched_count']),
|
||
('银行有/系统无', summary['bank_only_amount'], summary['bank_only_count']),
|
||
('系统有/银行无', summary['system_only_amount'], summary['system_only_count']),
|
||
('差异金额', summary['difference'], '-'),
|
||
]
|
||
|
||
for row, (item, amount, count) in enumerate(data, 5):
|
||
ws_summary.cell(row=row, column=1, value=item)
|
||
ws_summary.cell(row=row, column=2, value=amount)
|
||
ws_summary.cell(row=row, column=3, value=count)
|
||
|
||
# Sheet2: 银行未达账项
|
||
ws_bank = wb.create_sheet("银行未达账项")
|
||
if len(result['bank_only']) > 0:
|
||
for col, header in enumerate(result['bank_only'].columns, 1):
|
||
ws_bank.cell(row=1, column=col, value=header)
|
||
for row, data_row in enumerate(result['bank_only'].values, 2):
|
||
for col, value in enumerate(data_row, 1):
|
||
ws_bank.cell(row=row, column=col, value=value)
|
||
|
||
# Sheet3: 企业未达账项
|
||
ws_system = wb.create_sheet("企业未达账项")
|
||
if len(result['system_only']) > 0:
|
||
for col, header in enumerate(result['system_only'].columns, 1):
|
||
ws_system.cell(row=1, column=col, value=header)
|
||
for row, data_row in enumerate(result['system_only'].values, 2):
|
||
for col, value in enumerate(data_row, 1):
|
||
ws_system.cell(row=row, column=col, value=value)
|
||
|
||
wb.save(output_path)
|
||
print(f"对账报告已保存: {output_path}")
|
||
```
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
1. **数据格式统一**: 确保日期、金额格式一致后再对账
|
||
2. **匹配规则灵活**: 先精确匹配流水号,再模糊匹配金额+日期
|
||
3. **差异原因分析**: 对未达账项要逐笔核实原因
|
||
4. **定期对账**: 建议至少每月进行一次银行对账
|
||
5. **凭证留存**: 对账报告需打印签字存档
|
||
6. **OCR 核对**: 扫描件 OCR 后应人工抽查,确保识别准确
|
||
7. **多承运商校验**: 诉讼场景下必须检查订单是否属于其他承运商,防止对方混入他人订单骗取运费
|
||
|
||
---
|
||
|
||
## 与其他技能协作
|
||
|
||
- **data-excel**: 处理复杂的 Excel 数据转换
|
||
- **feishu**: 将对账报告保存到飞书云文档
|
||
- **siyuan**: 记录对账工作日志到思源笔记
|
||
|
||
## 详细内容
|
||
|
||
以下章节已移到 [references/details.md](references/details.md):
|
||
|
||
- 常见对账场景
|