# 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 关联 ## 执行方式 本技能已预置为可直接执行的脚本,无需每次创建临时脚本。 ### 快速执行 ```bash python3 ~/.openclaw/workspace/skills/vala-order-amortization-stat/run.py --start {账期起始日} --end {账期结束日} ``` ### 示例 ```bash # 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模板 ```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_at(status=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. 冲销金额统一显示为负数