Files
ai-proj-helper/skills-biz/finance-plugin/skills/SKILL.md
T
johnQ 237631147a chore(skill): 清理全部 23 条历史校验警告
- 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
2026-10-31 22:21:07 +08:00

17 KiB
Raw Blame History

name, description, metadata, disable-model-invocation
name description metadata disable-model-invocation
finance 财务对账技能。支持银行对账、应收对账、应付对账、内部往来对账等。通过自然语言实现账单导入、差异分析、对账报告生成。当用户提到对账、银行流水、应收应付、账单核对、财务核销相关任务时自动激活。
invocation
user-invoked
true

功能概述

  • 银行对账: 银行流水 vs 系统收付款记录匹配
  • 应收对账: 客户对账单生成与核对
  • 应付对账: 供应商对账单核对
  • 内部对账: 公司间/部门间往来核对
  • 差异分析: 自动识别未达账项、差异原因
  • 对账报告: 生成标准化对账报告
  • 扫描单据OCR: PDF/图片扫描件识别,提取订单号与对账单匹配(适用于诉讼证据核对)

对账类型

1. 银行对账

对账项 说明
银行已收/企业未收 银行已入账,系统未记录
企业已收/银行未收 系统已记录,银行未入账
银行已付/企业未付 银行已扣款,系统未记录
企业已付/银行未付 系统已记录,银行未扣款

2. 应收对账

  • 客户欠款余额核对
  • 发票与回款匹配
  • 账龄分析

3. 应付对账

  • 供应商应付余额核对
  • 采购入库与付款匹配
  • 预付款冲销

数据格式要求

银行流水格式

交易日期,交易类型,摘要,金额,余额,对方账户,对方名称,交易流水号
2026-01-15,收入,货款,50000.00,150000.00,622848******1234,深圳XX公司,TXN20260115001
2026-01-16,支出,采购款,-30000.00,120000.00,622848******5678,广州YY公司,TXN20260116001

系统收付款格式

单据日期,单据类型,单据编号,金额,客户/供应商,备注,关联流水号
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 代码模板

银行对账核心逻辑

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
    }

银行余额调节表

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

应收对账

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

账龄分析

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 对账报告

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:

  • 常见对账场景