ai_member_xiaoxi/skills/vala-order-amortization-stat/SKILL.md
2026-04-25 08:00:01 +08:00

21 KiB
Raw Blame History

SKILL.md - vala-order-amortization-stat 订单均摊结算统计技能

技能描述

用于统计指定账期内的订单均摊收入、退费冲销,按天计算均摊金额,支持部分退费场景的退后订单金额和周期自动计算。

触发场景

当用户提到以下关键词组合时激活本技能:

  1. 订单均摊、按天均摊、收入均摊
  2. 账期结算、月度结算、季度结算
  3. 退费冲销、部分退款均摊

前置依赖

  1. 已连接线上PostgreSQL数据库 vala_bi 库
  2. 依赖表:
    • bi_vala_order订单主表
    • bi_refund_order退费订单表
    • bi_vala_seasonal_ticket季卡周期表
    • bi_vala_app_account用户账户表用于剔除测试账号
  3. 表关联规则:
    • bi_vala_order.trade_no ↔ bi_refund_order.trade_no 关联
    • bi_vala_order.out_trade_no ↔ bi_refund_order.out_trade_no 关联

执行方式

本技能已预置为可直接执行的脚本,无需每次创建临时脚本。

快速执行

python3 ~/.openclaw/workspace/skills/vala-order-amortization-stat/run.py --start {账期起始日} --end {账期结束日}

示例

# 2026年3月账期
python3 ~/.openclaw/workspace/skills/vala-order-amortization-stat/run.py --start 2026-03-01 --end 2026-03-31

# 也支持位置参数
python3 ~/.openclaw/workspace/skills/vala-order-amortization-stat/run.py 2026-03-01 2026-03-31

输出

  1. Excel报表保存到output/订单均摊结算报表_{start}_{end}.xlsx两个Sheet汇总表、订单明细
  2. 控制台输出JSON格式的汇总结果可直接解析

目录结构

vala-order-amortization-stat/
├── SKILL.md          # 技能说明(本文件)
├── run.py            # 执行脚本(传入账期参数即可)
└── sql/
    ├── summary.sql   # 汇总查询SQL模板
    └── detail.sql    # 订单明细查询SQL模板

操作流程

步骤1确认核心参数

执行前必须向用户确认以下参数:

  • 账期起始日期格式YYYY-MM-DD
  • 账期结束日期格式YYYY-MM-DD
  • 默认输出维度:账期整体汇总,无需按天/周/月拆分,无需分渠道统计

步骤2数据过滤规则

  1. 订单范围:
    • 2025-09-01 至 账期结束日 内创建的所有订单
    • bi_vala_order.status IN (3,4)(已完成、已退款订单)
    • 订单实际支付金额≥10元bi_vala_order.pay_amount_int ≥ 1000单位
    • 关联bi_vala_app_account表仅保留bi_vala_app_account.status = 1的非测试账号订单
  2. 退费范围:
    • 退费完成时间落在账期内的所有退款成功记录判定条件bi_refund_order.updated_at退款处理完成时间在账期起止时间范围内
    • bi_refund_order.status = 3退款处理完成
  3. 账期前退费订单处理规则:
    • 退费完成时间在账期起始日之前的订单,不会纳入本账期的冲销逻辑:
      • 全额退费订单:冲销动作已在退费发生的对应账期执行完毕,本账期完全不统计该订单的任何数据
      • 部分退费订单:冲销动作、退后订单金额/剩余周期调整已在退费发生的对应账期完成,本账期仅按调整后的退后订单金额计算均摊,不产生额外冲销金额

步骤3核心计算逻辑

3.1 周期计算

  • 每个订单的总均摊周期 = bi_vala_seasonal_ticket中同一order_no下status != -1的不同id数量 × 90天
  • 转正日期规则试用期7天包含下单日
    • 均摊起始日(转正日期)= 订单下单日 + 7天下单日取bi_vala_order.created_at的日期部分
    • 示例4月22日下单4月29日00:00:00为转正日即均摊起始日
  • 税率规则:
    • 订单下单时间在2026年5月1日之前税率1%
    • 订单下单时间在2026年5月1日及之后税率6%
    • 订单税后金额 = (bi_vala_order.pay_amount_int / 100) / (1 + 税率)(单位:元)
    • 订单税费金额 = (bi_vala_order.pay_amount_int / 100) / (1 + 税率) × 税率(单位:元)
    • 计税方式:价外税,即订单金额为含税价,需先剥离税额再计算税后金额
  • 均摊规则更新:所有均摊计算均基于税后金额进行,含税金额和税费单独统计
    • 日均摊金额(税后)= 订单税后金额 / 总均摊周期
    • 🔹 周期最后一天补差规则:订单均摊周期的最后一天,均摊金额不按日均摊计算,采用补差方式确保总额完全匹配:
      • 正常订单:最后一天均摊金额 = 订单税后总金额 - 前(总均摊周期-1天累计已均摊金额
      • 部分退费订单:最后一天均摊金额 = 退后订单税后金额 - 剩余均摊周期前(剩余天数-1天累计已均摊金额 避免浮点精度导致的金额尾差

3.2 退费场景计算

所有退费冲销金额均为税后金额,税费同步对应冲销

  1. 所有退费订单(全额/部分)通用计算
    • 历史退费订单冲销均摊金额从2026年9月1日起至账期起始日之前该订单已产生的全部税后均摊金额统一显示为负数用于冲销
    • 历史退费订单冲销税费金额:上述历史均摊金额对应的税费(统一显示为负数,用于冲销)
  2. 全额退费bi_refund_order.refund_type = 2:无需额外计算补充均摊,仅执行上述通用冲销逻辑
  3. 部分退费bi_refund_order.refund_type = 3:在通用冲销逻辑基础上,额外计算:
    • 历史部分退费订单补充均摊金额:部分退费后的退后订单待均摊税后金额,在本账期内产生的均摊收入
    • 历史部分退费订单补充税费金额:上述补充均摊金额对应的税费

3.3 账期内收入计算

账期内总收入 = 账期内所有正常订单的日均摊金额总和 + 账期内所有退费冲销金额总和

核心SQL模板

-- 步骤0获取所有历史退费记录无时间范围用于标记订单是否为退费订单不受当前账期限制
all_refund_records AS (
  SELECT
    out_trade_no AS order_no,
    SUM(refund_amount_int) / 100 AS total_refund_amount, -- 累计退费总金额(支持多笔部分退费叠加)
    MAX(CASE WHEN refund_type = 2 THEN 1 ELSE 0 END) AS is_full_refund, -- 是否全额退费只要有一笔全额退费即标记为1
    MAX(refund_type) AS refund_type,
    MAX(DATE(updated_at)) AS latest_refund_date -- 最后一次退费完成时间
  FROM bi_refund_order
  WHERE status = 3
  GROUP BY out_trade_no
),
-- 步骤1获取所有符合条件的订单基础信息
order_base AS (
  SELECT 
    o.id AS order_id,
    o.out_trade_no AS order_no, -- 修正bi_vala_order无order_no字段使用out_trade_no作为订单号
    o.trade_no, -- 新增关联退费订单需要trade_no字段
    o.pay_amount_int / 100 AS pay_amount,
    -- 计算税率、税费、税后金额
    CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_rate,
    (o.pay_amount_int / 100) * CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_amount,
    (o.pay_amount_int / 100) * (1 - CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END) AS after_tax_amount,
    DATE_ADD(DATE(o.created_at), INTERVAL 7 DAY) AS amortization_start_date,
    o.order_status, -- 修正字段名是order_status不是status
    a.id AS account_id,
    o.key_from,
    o.sale_channel,
    -- 新增:订单退费标记字段(所有历史退费均可识别,不受当前账期限制)
    CASE WHEN ar.order_no IS NOT NULL THEN 1 ELSE 0 END AS has_refund, -- 是否有退费记录
    COALESCE(ar.total_refund_amount, 0) AS total_refund_amount, -- 累计退费金额
    COALESCE(ar.is_full_refund, 0) AS is_full_refund, -- 是否全额退费
    ar.refund_type,
    ar.latest_refund_date -- 退费完成日期(解决历史退费订单退费日期为空的问题)
  FROM bi_vala_order o
  JOIN bi_vala_app_account a ON o.account_id = a.id
  LEFT JOIN all_refund_records ar ON o.out_trade_no = ar.order_no -- 关联全量退费记录,不限制时间
  WHERE 
    o.created_at >= '2025-09-01'
    AND o.order_status IN (3,4) -- 修正字段名是order_status不是status
    AND o.pay_amount_int >= 1000
    AND a.status = 1
),
-- 步骤2计算每个订单的总均摊周期
order_cycle AS (
  SELECT 
    order_no,
    COUNT(DISTINCT id) * 90 AS total_cycle_days
  FROM bi_vala_seasonal_ticket
  WHERE status != -1
  GROUP BY order_no
),
-- 步骤3获取账期内的退费记录仅用于计算当前账期的冲销金额不影响订单退费状态标记
refund_records AS (
  SELECT 
    r.out_trade_no AS order_no,
    r.refund_amount_int / 100 AS refund_amount, -- 修正使用分为单位的refund_amount_int避免字符串转换问题除以100得到元
    r.refund_type,
    DATE(r.updated_at) AS refund_date -- 退费日期使用updated_atstatus=3时即为退费完成时间
  FROM bi_refund_order r
  JOIN order_base o ON r.out_trade_no = o.order_no OR r.trade_no = o.trade_no -- 修正支持out_trade_no/trade_no双字段匹配退费订单
  WHERE 
    r.status = 3
    AND r.updated_at BETWEEN '${账期起始日}' AND '${账期结束日}'
)
,
-- 步骤4合并订单基础信息与均摊周期
order_with_cycle AS (
  SELECT
    ob.*,
    COALESCE(oc.total_cycle_days, 0) AS total_cycle_days,
    -- 均摊结束日 = 转正日 + 总周期天数 - 1
    ob.amortization_start_date + COALESCE(oc.total_cycle_days, 0) - 1 AS amortization_end_date,
    -- 日均摊金额(税后)
    CASE WHEN COALESCE(oc.total_cycle_days, 0) > 0
      THEN ob.after_tax_amount / oc.total_cycle_days
      ELSE 0
    END AS daily_amort_amount,
    -- 日税费均摊
    CASE WHEN COALESCE(oc.total_cycle_days, 0) > 0
      THEN ob.tax_amount / oc.total_cycle_days
      ELSE 0
    END AS daily_tax_amount
  FROM order_base ob
  LEFT JOIN order_cycle oc ON ob.order_no = oc.order_no
),

-- 步骤5判断订单在账期内的状态分类并计算各类均摊金额
order_amortization AS (
  SELECT
    owc.*,
    -- 是否为账期内新增订单
    CASE WHEN DATE(owc.amortization_start_date) <= '${账期结束日}'
              AND owc.amortization_start_date >= '${账期起始日}' - INTERVAL '6 days'
         THEN 1 ELSE 0 END AS is_new_in_period,
    -- 账期内新增且已转正
    CASE WHEN DATE(owc.amortization_start_date) >= '${账期起始日}'
              AND DATE(owc.amortization_start_date) <= '${账期结束日}'
         THEN 1
         WHEN owc.amortization_start_date < '${账期起始日}'
         THEN 1
         ELSE 0
    END AS is_formal,

    -- ===== 正常均摊金额计算(未退费 或 账期前部分退费的订单)=====
    -- 均摊有效起始日(取转正日和账期起始日的较大值)
    GREATEST(owc.amortization_start_date, '${账期起始日}'::date) AS eff_start,
    -- 均摊有效结束日(取均摊结束日和账期结束日的较小值)
    LEAST(owc.amortization_end_date, '${账期结束日}'::date) AS eff_end

  FROM order_with_cycle owc
  WHERE owc.total_cycle_days > 0  -- 排除无均摊周期的异常订单
),

-- 步骤6计算每个订单在账期内的均摊天数和金额
order_period_amort AS (
  SELECT
    oa.*,
    -- 账期内有效均摊天数
    CASE WHEN oa.eff_end >= oa.eff_start
      THEN (oa.eff_end - oa.eff_start + 1)
      ELSE 0
    END AS period_amort_days,

    -- 该订单从转正日到账期结束日的总已均摊天数(用于计算历史累计)
    CASE WHEN '${账期结束日}'::date >= oa.amortization_start_date
      THEN LEAST('${账期结束日}'::date, oa.amortization_end_date) - oa.amortization_start_date + 1
      ELSE 0
    END AS total_amorted_days_to_period_end,

    -- 该订单从转正日到账期起始日前一天的历史已均摊天数
    CASE WHEN '${账期起始日}'::date > oa.amortization_start_date
      THEN LEAST('${账期起始日}'::date - 1, oa.amortization_end_date) - oa.amortization_start_date + 1
      ELSE 0
    END AS historical_amorted_days

  FROM order_amortization oa
),

-- 步骤7计算最终均摊金额含补差逻辑
order_final_amort AS (
  SELECT
    opa.*,

    -- === 正常订单(未退费 或 账期前已处理的退费订单)在账期内的均摊 ===

    -- 判断是否为账期前全额退费(本账期完全不统计)
    CASE WHEN opa.has_refund = 1 AND opa.is_full_refund = 1
              AND opa.latest_refund_date < '${账期起始日}'::date
         THEN 1 ELSE 0
    END AS is_pre_period_full_refund,

    -- 判断是否为账期前部分退费(本账期按退后订单金额均摊)
    CASE WHEN opa.has_refund = 1 AND opa.is_full_refund = 0
              AND opa.latest_refund_date < '${账期起始日}'::date
         THEN 1 ELSE 0
    END AS is_pre_period_partial_refund,

    -- 判断是否为账期内退费
    CASE WHEN opa.has_refund = 1
              AND opa.latest_refund_date >= '${账期起始日}'::date
              AND opa.latest_refund_date <= '${账期结束日}'::date
         THEN 1 ELSE 0
    END AS is_current_period_refund,

    -- 部分退费后的退后订单税后金额
    CASE WHEN opa.has_refund = 1 AND opa.is_full_refund = 0
         THEN (opa.pay_amount - opa.total_refund_amount) * (1 - opa.tax_rate)
         ELSE opa.after_tax_amount
    END AS refunded_after_tax_amount,

    -- 部分退费后的退后订单税费
    CASE WHEN opa.has_refund = 1 AND opa.is_full_refund = 0
         THEN (opa.pay_amount - opa.total_refund_amount) * opa.tax_rate
         ELSE opa.tax_amount
    END AS refunded_tax_amount

  FROM order_period_amort opa
),

-- 步骤8最终汇总计算
summary_calc AS (
  SELECT
    -- ===== 账期内新增订单统计 =====
    COUNT(CASE WHEN DATE(amortization_start_date - INTERVAL '7 days') >= '${账期起始日}'::date
                    AND DATE(amortization_start_date - INTERVAL '7 days') <= '${账期结束日}'::date
              THEN 1 END) AS total_new_orders,

    COUNT(CASE WHEN DATE(amortization_start_date - INTERVAL '7 days') >= '${账期起始日}'::date
                    AND DATE(amortization_start_date - INTERVAL '7 days') <= '${账期结束日}'::date
                    AND amortization_start_date <= '${账期结束日}'::date
              THEN 1 END) AS formal_orders,

    COUNT(CASE WHEN DATE(amortization_start_date - INTERVAL '7 days') >= '${账期起始日}'::date
                    AND DATE(amortization_start_date - INTERVAL '7 days') <= '${账期结束日}'::date
                    AND amortization_start_date > '${账期结束日}'::date
              THEN 1 END) AS trial_orders,

    -- ===== 冲销前均摊金额(税后):所有非"账期前全额退费"订单在账期内的均摊 =====
    ROUND(SUM(
      CASE
        WHEN is_pre_period_full_refund = 1 THEN 0  -- 账期前全额退费,不统计
        WHEN is_pre_period_partial_refund = 1 THEN  -- 账期前部分退费,按退后订单金额均摊
          CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
            THEN refunded_after_tax_amount / total_cycle_days * period_amort_days
            ELSE 0
          END
        WHEN is_current_period_refund = 0 AND period_amort_days > 0 THEN  -- 正常未退费订单
          CASE
            -- 补差逻辑:如果账期结束日恰好是均摊最后一天
            WHEN eff_end = amortization_end_date
            THEN after_tax_amount - daily_amort_amount * (total_cycle_days - 1)
                 + daily_amort_amount * (period_amort_days - 1)
            ELSE daily_amort_amount * period_amort_days
          END
        ELSE 0
      END
    )::numeric, 2) AS pre_writeoff_amort,

    -- ===== 冲销前税费 =====
    ROUND(SUM(
      CASE
        WHEN is_pre_period_full_refund = 1 THEN 0
        WHEN is_pre_period_partial_refund = 1 THEN
          CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
            THEN refunded_tax_amount / total_cycle_days * period_amort_days
            ELSE 0
          END
        WHEN is_current_period_refund = 0 AND period_amort_days > 0 THEN
          daily_tax_amount * period_amort_days
        ELSE 0
      END
    )::numeric, 2) AS pre_writeoff_tax,

    -- ===== 冲销均摊金额(税后):账期内退费订单的历史已均摊金额 =====
    ROUND(SUM(
      CASE WHEN is_current_period_refund = 1 AND historical_amorted_days > 0
        THEN daily_amort_amount * historical_amorted_days
        ELSE 0
      END
    )::numeric, 2) AS writeoff_amort,

    -- ===== 冲销税费 =====
    ROUND(SUM(
      CASE WHEN is_current_period_refund = 1 AND historical_amorted_days > 0
        THEN daily_tax_amount * historical_amorted_days
        ELSE 0
      END
    )::numeric, 2) AS writeoff_tax,

    -- ===== 补充均摊金额(税后):账期内部分退费订单,退后订单金额的均摊 =====
    ROUND(SUM(
      CASE WHEN is_current_period_refund = 1
                AND is_full_refund = 0
                AND total_cycle_days > 0
                AND period_amort_days > 0
        THEN (pay_amount - total_refund_amount) * (1 - tax_rate) / total_cycle_days * period_amort_days
        ELSE 0
      END
    )::numeric, 2) AS supplement_amort,

    -- ===== 补充税费 =====
    ROUND(SUM(
      CASE WHEN is_current_period_refund = 1
                AND is_full_refund = 0
                AND total_cycle_days > 0
                AND period_amort_days > 0
        THEN (pay_amount - total_refund_amount) * tax_rate / total_cycle_days * period_amort_days
        ELSE 0
      END
    )::numeric, 2) AS supplement_tax

  FROM order_final_amort
)

-- 最终输出汇总结果
SELECT
  total_new_orders AS "订单数",
  formal_orders AS "正式订单数",
  trial_orders AS "试用订单数",
  pre_writeoff_tax AS "冲销前税费",
  writeoff_tax AS "冲销税费",
  supplement_tax AS "补充税费",
  ROUND((pre_writeoff_tax - writeoff_tax + supplement_tax)::numeric, 2) AS "冲销后税费",
  pre_writeoff_amort AS "冲销前均摊金额",
  writeoff_amort AS "冲销均摊金额",
  supplement_amort AS "补充均摊金额",
  ROUND((pre_writeoff_amort - writeoff_amort + supplement_amort)::numeric, 2) AS "冲销后均摊金额"
FROM summary_calc;

输出格式

  1. 优先输出Excel报表存放于output/目录下,命名格式:订单均摊结算报表_${账期起始日}_${账期结束日}.xlsx
  2. 报表包含以下Sheet
    • 汇总表:订单数、正式订单数、试用订单数、冲销前税费、冲销税费、补充税费、冲销后税费、冲销前均摊金额、冲销均摊金额、补充均摊金额、冲销后均摊金额
    • 订单明细:订单号、订单金额、税率、税额、税后金额、总均摊周期、已均摊天数、历史均摊金额、账期均摊金额、未确认收入、剩余周期
  3. 文字回复核心指标:

    📊 账期${账期起始日}至${账期结束日}均摊结算结果: 总订单数XXX单 正式订单数XXX单 试用订单数XXX单 冲销后税费XXX元 冲销后均摊金额税后净收入XXX元

汇总指标计算逻辑(所有均摊金额均为税后金额)

  1. 订单数:账期内(下单日落在账期起止时间范围内)新增的所有符合条件的订单总数量
  2. 正式订单数账期内新增的订单中截止账期结束日已过7天试用期下单日+7天 ≤ 账期结束日)的订单数量,仅正式订单开始参与均摊计算
  3. 试用订单数账期内新增的订单中截止账期结束日仍处于7天试用期内下单日+7天 > 账期结束日)的订单数量,试用订单未开始均摊,不参与金额计算
  4. 冲销前税费:账期内所有正式订单的含税订单总金额 × 各订单对应税率 的总和
  5. 冲销税费:本账期内发生退费的所有订单的含税订单总金额 × 各订单对应税率 的总和(正数展示,计算时扣除)
  6. 补充税费:本账期内发生部分退费的订单,退后订单含税金额 × 各订单对应税率 的总和,其中:部分退费订单退后含税金额 = 原订单含税金额 - 退费含税金额
  7. 冲销后税费:账期内最终确认的总税费,计算公式:冲销后税费 = 冲销前税费 - 冲销税费 + 补充税费
  8. 冲销前均摊金额(税后):历史未退费订单 + 账期内正式订单 在本账期内产生的税后均摊收入总和
  9. 冲销均摊金额(税后):本账期内发生退费的所有订单,需要冲销的历史均摊金额总和(正数展示,计算时扣除)
  10. 补充均摊金额(税后):本账期内发生部分退费的订单,从转正日起至账期最后一日产生的税后均摊金额总和
  11. 冲销后均摊金额(税后):账期内最终确认的税后净收入,计算公式:冲销后均摊金额 = 冲销前均摊金额 - 冲销均摊金额 + 补充均摊金额

注意事项

  1. 所有金额保留2位小数百分比保留1位小数
  2. 部分退费场景需确保bi_vala_seasonal_ticket中对应退费部分的status已更新为-1
  3. 自动剔除测试账号,无需额外过滤
  4. 冲销金额统一显示为负数