🤖 每日自动备份 - 2026-05-16 08:00:01

This commit is contained in:
小溪 2026-05-16 08:00:01 +08:00
parent 83160895d0
commit ac4e2eb3ce
9 changed files with 479 additions and 807 deletions

View File

@ -1,6 +1,6 @@
{ {
"version": 1, "version": 1,
"updatedAt": "2026-05-14T06:41:55.506Z", "updatedAt": "2026-05-15T09:14:59.142Z",
"entries": { "entries": {
"memory:memory/2026-05-06.md:1:20": { "memory:memory/2026-05-06.md:1:20": {
"key": "memory:memory/2026-05-06.md:1:20", "key": "memory:memory/2026-05-06.md:1:20",
@ -9,18 +9,20 @@
"endLine": 20, "endLine": 20,
"source": "memory", "source": "memory",
"snippet": "# 2026-05-06 工作日志 ## 任务1学而思/讯飞/希沃/京东方月度渠道订单报表 - [李承龙需求] 创建按月汇总四个渠道订单的 Python 脚本 - 脚本路径:`/root/.openclaw/workspace/scripts/monthly_channel_order_report.py` - **渠道映射:** 仅端内 `key_from='app-active-h5-0-0'`sale_channel: 21=学而思, 22=讯飞, 26=希沃, 27=京东方 - **输出格式:** 四个渠道各一个 Sheet每月按天展示学而思/希沃/京东方只显示有订单日期,讯飞显示全月每天) - **指标差异:** - 学而思/希沃/京东方订单数、GMV、CPS分成学而思×0.45希沃×0.4京东方×0.3)、订单号(分号拼接) - 讯飞订单数、GMV、新增注册download_channel LIKE '%讯飞%'、CPA分成新增注册×7、订单号 - **退费剔除:** NOT EXISTS (bi_refund_order.status=3) AND order_status != 4 - **测试运行:** 2026-04 月份,新增注册 537讯飞订单合计 15 单GMV 14585 元 - **订单号格式问题:** Excel 科学计数法 → 设置单元格为文本格式解决 - **psycopg2 转义:** LIKE 中 `%` 需写成 `%%`psycopg2 的 `%s` 占位符冲突) ## 任务2销售线", "snippet": "# 2026-05-06 工作日志 ## 任务1学而思/讯飞/希沃/京东方月度渠道订单报表 - [李承龙需求] 创建按月汇总四个渠道订单的 Python 脚本 - 脚本路径:`/root/.openclaw/workspace/scripts/monthly_channel_order_report.py` - **渠道映射:** 仅端内 `key_from='app-active-h5-0-0'`sale_channel: 21=学而思, 22=讯飞, 26=希沃, 27=京东方 - **输出格式:** 四个渠道各一个 Sheet每月按天展示学而思/希沃/京东方只显示有订单日期,讯飞显示全月每天) - **指标差异:** - 学而思/希沃/京东方订单数、GMV、CPS分成学而思×0.45希沃×0.4京东方×0.3)、订单号(分号拼接) - 讯飞订单数、GMV、新增注册download_channel LIKE '%讯飞%'、CPA分成新增注册×7、订单号 - **退费剔除:** NOT EXISTS (bi_refund_order.status=3) AND order_status != 4 - **测试运行:** 2026-04 月份,新增注册 537讯飞订单合计 15 单GMV 14585 元 - **订单号格式问题:** Excel 科学计数法 → 设置单元格为文本格式解决 - **psycopg2 转义:** LIKE 中 `%` 需写成 `%%`psycopg2 的 `%s` 占位符冲突) ## 任务2销售线",
"recallCount": 1, "recallCount": 2,
"dailyCount": 0, "dailyCount": 0,
"groundedCount": 0, "groundedCount": 0,
"totalScore": 1, "totalScore": 2,
"maxScore": 1, "maxScore": 1,
"firstRecalledAt": "2026-05-06T12:28:28.905Z", "firstRecalledAt": "2026-05-06T12:28:28.905Z",
"lastRecalledAt": "2026-05-06T12:28:28.905Z", "lastRecalledAt": "2026-05-15T04:06:01.561Z",
"queryHashes": [ "queryHashes": [
"c30fa9d13974" "c30fa9d13974",
"3d07645a9dc4"
], ],
"recallDays": [ "recallDays": [
"2026-05-06" "2026-05-06",
"2026-05-15"
], ],
"conceptTags": [ "conceptTags": [
"学而思/讯飞/希沃/京东方月度渠道订单报表", "学而思/讯飞/希沃/京东方月度渠道订单报表",
@ -73,18 +75,20 @@
"endLine": 12, "endLine": 12,
"source": "memory", "source": "memory",
"snippet": "# 2026-03-01.md - First Day Online - Came online for the first time. - Met Cris, my creator and mentor. - Received my name: 小溪 (Xiao Xi). - My role goal: Become a professional data analyst for the company to help the team. - Updated IDENTITY.md and USER.md with our conversation details. - Added core rule to MEMORY.md: Use Chinese as primary external communication language. - Installed find-skills skill successfully for searching skills. - Tried to install create-skills but it wasn't found; attempted skill-creator instead but hit rate limits. - Finally successfully installed skill-builder as an alternative for creating skills after multiple attempts and waiting for rate limits to reset. - Ex", "snippet": "# 2026-03-01.md - First Day Online - Came online for the first time. - Met Cris, my creator and mentor. - Received my name: 小溪 (Xiao Xi). - My role goal: Become a professional data analyst for the company to help the team. - Updated IDENTITY.md and USER.md with our conversation details. - Added core rule to MEMORY.md: Use Chinese as primary external communication language. - Installed find-skills skill successfully for searching skills. - Tried to install create-skills but it wasn't found; attempted skill-creator instead but hit rate limits. - Finally successfully installed skill-builder as an alternative for creating skills after multiple attempts and waiting for rate limits to reset. - Ex",
"recallCount": 1, "recallCount": 2,
"dailyCount": 0, "dailyCount": 0,
"groundedCount": 0, "groundedCount": 0,
"totalScore": 1, "totalScore": 2,
"maxScore": 1, "maxScore": 1,
"firstRecalledAt": "2026-05-11T02:51:50.465Z", "firstRecalledAt": "2026-05-11T02:51:50.465Z",
"lastRecalledAt": "2026-05-11T02:51:50.465Z", "lastRecalledAt": "2026-05-15T09:14:59.142Z",
"queryHashes": [ "queryHashes": [
"6e09f5970960" "6e09f5970960",
"a6b909e7228c"
], ],
"recallDays": [ "recallDays": [
"2026-05-11" "2026-05-11",
"2026-05-15"
], ],
"conceptTags": [ "conceptTags": [
"identity.md", "identity.md",

62
memory/2026-05-15-1014.md Normal file
View File

@ -0,0 +1,62 @@
# Session: 2026-05-15 10:14:29 GMT+8
- **Session Key**: agent:main:feishu:direct:ou_e63ce6b760ad39382852472f28fbe2a2
- **Session ID**: 050c77e8-cf05-4ace-9f6b-9f26eeacd89c
- **Source**: feishu
## Conversation Summary
user: L1付费用户是不是包含仅L1和L1+L2
assistant: 是的。「L1付费用户」= 仅L1192人+ L1+L21,207人= **1,399人**「L2付费用户」= 仅L21,370人+ L1+L21,207人= **2,577人**
之前图表是按「仅L1」「仅L2」拆的没把 L1+L2 合并进各组。按你这个定义重新生成?
assistant: L1 付费群 = 仅L1 + L1+L2 = **1,399人**截至最后一周L2 付费群 = 仅L2 + L1+L2 = **2,577人**
按这个定义重新生成的 4 张图表:
assistant: 已发送。两组图表的口径:
| 维度 | 付费人数 | 无课消率 |
|------|---------|---------|
| **L1付费群**仅L1 + L1+L2 | 1,399 | 47% |
| **L2付费群**仅L2 + L1+L2 | 2,577 | 56% |
L1+L2 的 1,207 人在两张图里都有出现重叠因为这是按「买过L1」和「买过L2」两个独立视角拆分。
user: 这个数据也按照不要仅L1、仅L2分类按照上面的L1和L2做分类统计
Reply target of current user message (untrusted, for context):
```json
{
"body": "[message_id=om_x100b6f6d5c39b13cb28ccaa53a07a6a] <file key=\"file_v3_0011m_3c86ed86-147b-446e-b2f9-e2218f217e0g\" name=\"course_consumption_by_level_v2.xlsx\"/>"
}
```
assistant: 把 Excel 也更新为 L1/L2 付费群口径不再按仅L1/仅L2拆分
assistant: 已发送。与之前的对比——合并 L1+L2 后:
| 指标 | 仅L1/仅L2 口径 | L1/L2 付费群口径 |
|------|---------------|-----------------|
| L1 无课消率 | 31%仅192人 | **43%**1,399人 |
| L2 无课消率 | 66%仅1,370人 | **56%**2,577人 |
L1 视角下无消率从 31% 升到 43%L1+L2 用户拉高了总体L2 视角下从 66% 降到 56%(同样因为 L1+L2 注入活跃用户)。
user: Continue the OpenClaw runtime event.
assistant: NO_REPLY
user: 还需要做一个调整就是L1付费用户的课消只看L1的课程不看L2的课程
assistant: 明白。L1/L2 分别只看对应级别的课程,不是一个人的所有课消都混在一起算。
assistant: L1 有效 60 章333-400 除去 U0L2 有效 240 章55-331 除去 U0。更新脚本按课程级别过滤课消。
assistant: 差异很大。按课程级别过滤后:
| 维度 | 之前(所有课程) | 现在(只看对应级别课程) |
|------|----------|----------|
| L1无消率 | 43% | **48%** |
| L2无消率 | 56% | **78%** 🔴 |
L2 付费群中只有 **564 人22%** 完成了 L2 课程——之前 1,126 人很多是在学 L1 的课。生成图表和 Excel。
assistant: 现在生成 v4 Excel 和发送文件。
assistant: 全部发送完毕。核心变化一览:
| 指标 | v3所有课程 | v4只看对应级别 | 变化 |
|------|:-----:|:-----:|------|
| L1 无消率 | 43% | 48% | +5pp |
| L2 无消率 | 56% | **78%** | **+22pp** 🔴 |
| L2 有消用户 | 1,126 | 564 | **减少562人** |
L2 付费群 2,577 人中,仅 564 人22%)在 L2 课程上有课消——L1+L2 用户1,207 人)买了 L2 产品但大多只在学 L1尚未推进到 L2。

41
memory/2026-05-15-1713.md Normal file
View File

@ -0,0 +1,41 @@
# Session: 2026-05-15 17:13:35 GMT+8
- **Session Key**: agent:main:feishu:direct:ou_e63ce6b760ad39382852472f28fbe2a2
- **Session ID**: 3cc5e51c-d172-48bb-8e28-8e415f50c8af
- **Source**: feishu
## Conversation Summary
assistant: 订单数暴增到 2929有问题。查下数据库实际体量
assistant: 28 笔符合条件,不是 1847。回退验证基线
assistant: 找到问题了:`amortization_end_date` 仍使用旧的 `oc.total_cycle_days`=0导致 `period_amort_days=0` → 当期摊销=0。需要同时修正 `amortization_end_date`
assistant: 需要改三处:`amortization_end_date`、`daily_amort_amount`、`daily_tax_amount` 都依赖 `oc.total_cycle_days`(旧值=0。在 `order_with_cycle``order_classified` 之间插入修正 CTE
assistant: 还要改 `order_classified` 的 FROM 表名:
assistant: 重新跑,验证目标订单的当期摊销是否正确:
assistant: 列名冲突,`owc.*` 和新增修正列重名。重新整理结构:把衍生字段计算移到 `order_with_cycle_fixed` 中,彻底消除冲突。
assistant: 彻底解决:把 `order_with_cycle` 中的 `amortization_end_date`/`daily_amort_amount`/`daily_tax_amount` 移到 `order_with_cycle_fixed` 中计算,避免列名冲突:
assistant: run.py 汇总说 1108 但 Excel 明细 2929 行,数量不对。查下具体情况:
assistant: 明细 2929 行是全部历史订单,汇总"订单数 1108"仅统计账期内下单的。这两个数字口径不同,都正确。
前面我把 2 笔 599 元订单误判为 pay<1000实际上 `pay_amount_int >= 1000` 是分单位10元门槛599元远超它们此前被排除的真实原因也是 `total_cycle_days=0 + has_refund=0`现已通过 fallback 正确纳入
最终确认所有 10 笔:
assistant: 全部 10 笔已回归。原因分析:
| 订单 | 下单 | 退费 | 4月报表状态 | 说明 |
|------|------|------|------------|------|
| dd...982 | 2/11 | 5/2 | 正常订单, 当期摊销 164.93 | 4月时仍在正常均摊 |
| wxxd..6428 | 3/23 | 5/8 | 正常订单, 当期摊销 197.69 | 同上 |
| 6× wxxd 4/26-28 | 4/26-28 | 5/5-7 | 试用期中, 当期摊销 0 | 转正日 > 4/30尚未开始均摊 |
| xhs..3416 | 4/30 | 5/7 | 试用期中, 当期摊销 0 | 同上 |
| zfb..1766 | 4/22 | 5/6 | 正常订单, 当期摊销 13.18 | 4月时仍在正常均摊 |
根因:退费在 5 月 → `all_refund_records` 过滤 → `has_refund=0`,但季卡已被删(`active=0`) → `total_cycle_days=0` → 不满足 `order_classified` WHERE 条件。修复:`total_cycle_days=0 ∧ has_refund=0 ∧ original>0` 时回退到原始周期。
发最终报表:
assistant: NO_REPLY
user: 刚才的逻辑更新到skill里面了么
assistant: 还没,补上:
assistant: 已更新。补充了两处:
1. CTE 链描述中加了 `order_with_cycle_fixed` 步骤
2. 新增 **3.1.1** 节记录 fallback 逻辑(退费在账期后 + 季卡已删 → 回退到原始周期)

View File

@ -1,24 +1,28 @@
# SKILL.md - vala-order-amortization-stat 订单均摊结算统计技能 # SKILL.md - vala-order-amortization-stat 订单均摊结算统计技能
## 技能描述 ## 技能描述
用于统计指定账期内的订单均摊收入、退费冲销,按天计算均摊金额,支持部分退费场景的退后订单金额和周期自动计算。 用于统计指定账期内的订单均摊收入、退费冲销,按天计算均摊金额,支持部分退费场景的退后订单金额和周期自动计算。
## 触发场景 ## 触发场景
当用户提到以下关键词组合时激活本技能: 当用户提到以下关键词组合时激活本技能:
1. 订单均摊、按天均摊、收入均摊 1. 订单均摊、按天均摊、收入均摊
2. 账期结算、月度结算、季度结算 2. 账期结算、月度结算、季度结算
3. 退费冲销、部分退款均摊 3. 退费冲销、部分退款均摊
## 前置依赖 ## 前置依赖
1. 已连接线上PostgreSQL数据库 vala_bi 库 1. 已连接线上PostgreSQL数据库 vala_bi 库
2. 依赖表: 2. 依赖表:
- bi_vala_order订单主表 - bi_vala_order订单主表
- bi_refund_order退费订单表 - bi_refund_order退费订单表
- bi_vala_seasonal_ticket季卡周期表 - bi_vala_seasonal_ticket季卡周期表
- bi_vala_app_account用户账户表用于剔除测试账号 - bi_vala_app_account用户账户表用于关联不限制status
3. 表关联规则: 3. 表关联规则:
- bi_vala_order.trade_no ↔ bi_refund_order.trade_no 关联
- bi_vala_order.out_trade_no ↔ bi_refund_order.out_trade_no 关联 - bi_vala_order.out_trade_no ↔ bi_refund_order.out_trade_no 关联
- bi_vala_order.trade_no ↔ bi_refund_order.trade_no 关联
## 执行方式 ## 执行方式
本技能已预置为可直接执行的脚本,无需每次创建临时脚本 本技能已预置为可直接执行的脚本。
### 快速执行 ### 快速执行
```bash ```bash
@ -35,8 +39,8 @@ python3 ~/.openclaw/workspace/skills/vala-order-amortization-stat/run.py 2026-03
``` ```
### 输出 ### 输出
1. Excel报表保存到`output/订单均摊结算报表_{start}_{end}.xlsx`两个Sheet汇总表、订单明细 1. Excel报表保存到`output/订单均摊结算报表_{start}_{end}.xlsx`三个Sheet汇总表、订单明细、本月预收账款
2. 控制台输出JSON格式的汇总结果,可直接解析 2. 控制台输出JSON格式的汇总结果
### 目录结构 ### 目录结构
``` ```
@ -44,376 +48,102 @@ vala-order-amortization-stat/
├── SKILL.md # 技能说明(本文件) ├── SKILL.md # 技能说明(本文件)
├── run.py # 执行脚本(传入账期参数即可) ├── run.py # 执行脚本(传入账期参数即可)
└── sql/ └── sql/
├── summary.sql # 汇总查询SQL模板 ├── _common.sql # 公共CTE链detail/summary 共用),含 order_base → order_with_cycle → order_with_cycle_fixed → order_classified → order_with_days
└── detail.sql # 订单明细查询SQL模板 ├── detail.sql # 订单明细输出SELECT from order_with_days
├── summary.sql # 汇总输出(聚合 SELECT from order_with_days
└── prepaid.sql # 月度预收账款(独立查询)
``` ```
## 操作流程 ## 操作流程
### 步骤1确认核心参数 ### 步骤1确认核心参数
执行前必须向用户确认以下参数 执行前向用户确认:
- 账期起始日期格式YYYY-MM-DD - 账期起始日期格式YYYY-MM-DD
- 账期结束日期格式YYYY-MM-DD - 账期结束日期格式YYYY-MM-DD
- 默认输出维度:账期整体汇总,无需按天/周/月拆分,无需分渠道统计
### 步骤2数据过滤规则 ### 步骤2数据过滤规则
1. 订单范围: 1. 订单范围:
- 2025-06-01 至 账期结束日 内创建的所有订单 - 2025-06-01 至 账期结束日 内创建的所有订单
- bi_vala_order.status IN (3,4)(已完成、已退款订单) - bi_vala_order.order_status IN (3,4)(已完成、已退款订单)
- 订单实际支付金额≥10元bi_vala_order.pay_amount_int ≥ 1000单位 - 订单实际支付金额≥10元bi_vala_order.pay_amount_int ≥ 1000单位
- 关联bi_vala_app_account表不限制status不剔除测试账号 - 关联 bi_vala_app_account 表(不限制 status不剔除测试账号
2. 退费范围: 2. 退费范围:
- 退费完成时间落在账期内的所有退款成功记录判定条件bi_refund_order.updated_at退款处理完成时间在账期起止时间范围内 - 退费记录范围:截至账期结束日前的所有退费成功记录,用于标记订单退费状态
- bi_refund_order.status = 3(退款处理完成) - 账期内退费判定:bi_refund_order.updated_at 在账期起止范围内,status = 3
3. 账期前退费订单处理规则: 3. 账期前退费订单处理规则:
- 退费完成时间在账期起始日之前的订单,不会纳入本账期的冲销逻辑: - 全额退费is_full_refund = 1累计退费金额 ≥ 原订单金额):本账期完全不统计该订单
- 全额退费订单:冲销动作已在退费发生的对应账期执行完毕,本账期完全不统计该订单的任何数据 - 部分退费is_full_refund = 0按退后订单金额/退后周期在本账期内正常均摊
- 部分退费订单:冲销动作、退后订单金额/剩余周期调整已在退费发生的对应账期完成,本账期仅按调整后的退后订单金额计算均摊,不产生额外冲销金额 4. 试用期内退费处理规则:
- 退费日期 < 转正日下单日+7天的退费订单从未产生均摊收入不触发冲销
### 步骤3核心计算逻辑 ### 步骤3核心计算逻辑
#### 3.1 周期计算 #### 3.1 周期计算
- 每个订单的总均摊周期 = bi_vala_seasonal_ticket中同一order_no下status != -1的不同id数量 × 90天 - 原始均摊周期 = bi_vala_seasonal_ticket 中同一 out_trade_no 下所有不同 id 数量 × 90 天
- 转正日期规则试用期7天包含下单日 - 退后均摊周期 = bi_vala_seasonal_ticket 中同一 out_trade_no 下 status != -1 的不同 id 数量 × 90 天
- 均摊起始日(转正日期)= 订单下单日 + 7天下单日取bi_vala_order.created_at的日期部分 - 转正日期 = 订单下单日 + 7 天(下单日取 bi_vala_order.pay_success_date 的日期部分)
- 示例4月22日下单4月29日00:00:00为转正日即均摊起始日 - 示例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_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判断订单在账期内的状态分类并计算各类均摊金额 #### 3.1.1 退费在账期后 + 季卡已删的fallback2026-05-15 修复)
order_amortization AS ( 当退费发生在账期结束后,季卡状态已经被置为 `status=-1`,导致 `active_cycle_days=0`,但账期内该订单仍处于正常均摊状态,不应被排除。
SELECT - 判定条件:`active_cycle_days=0 AND original_cycle_days>0 AND has_refund=0`(退费记录被 `DATE(updated_at) <= period_end` 过滤掉)
owc.*, - 处理:`total_cycle_days` 回退到 `original_cycle_days`,后续计算(均摊结束日、日均摊金额、日税费)均在 `order_with_cycle_fixed` CTE 中用 effective `total_cycle_days` 重算
-- 是否为账期内新增订单 - 避免此问题的正确做法:不应依赖季卡状态判断订单是否在本账期内正常,退费过滤的时效性需要与订单状态独立判断
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,
-- ===== 正常均摊金额计算(未退费 或 账期前部分退费的订单)===== #### 3.2 税率与税费计算(价外税)
-- 均摊有效起始日(取转正日和账期起始日的较大值) - 税率:下单时间 < 2026-05-01 1% 2026-05-01 6%
GREATEST(owc.amortization_start_date, '${账期起始日}'::date) AS eff_start, - 计税方式:价外税,订单金额为含税价
-- 均摊有效结束日(取均摊结束日和账期结束日的较小值) - 税后金额(元)= (pay_amount_int / 100) / (1 + tax_rate)
LEAST(owc.amortization_end_date, '${账期结束日}'::date) AS eff_end - 税费金额(元)= tax_amount = 税后金额 × tax_rate
FROM order_with_cycle owc #### 3.3 均摊规则
WHERE owc.total_cycle_days > 0 -- 排除无均摊周期的异常订单 - 所有均摊计算均基于税后金额进行
), - 日均摊金额(税后)= 税后金额 / 总均摊周期
- 🔹 周期最后一天补差规则:订单均摊周期的最后一天,均摊金额 = 税后总金额 - 前(N-1)天累计已均摊金额,避免浮点尾差
-- 步骤6计算每个订单在账期内的均摊天数和金额 #### 3.4 退费场景分类6类
order_period_amort AS ( | 场景 | 描述 | 处理方式 |
SELECT |------|------|---------|
oa.*, | A | 账期内下单 + 部分退费 | 按退后金额从转正日均摊period_amort_days |
-- 账期内有效均摊天数 | B | 历史下单 + 账期内部分退费 | 冲销历史均摊 + 按退后金额重新均摊refunded_amort_days_to_period_end |
CASE WHEN oa.eff_end >= oa.eff_start | C | 账期前部分退费 | 按退后金额正常均摊period_amort_days无冲销 |
THEN (oa.eff_end - oa.eff_start + 1) | D | 账期内下单 + 全额退费 | 不产生均摊,不触发冲销 |
ELSE 0 | E | 历史下单 + 账期内全额退费 | 冲销全部历史均摊,无退后均摊 |
END AS period_amort_days, | F | 账期前全额退费 | 本账期完全排除 |
-- 该订单从转正日到账期结束日的总已均摊天数(用于计算历史累计)
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` 1. Excel报表存放于 `output/` 目录,命名格式:`订单均摊结算报表_{start}_{end}.xlsx`
2. 报表包含以下Sheet 2. 报表包含以下Sheet
- 汇总表:订单数、正式订单数、试用订单数、冲销前税费、冲销税费、补充税费、冲销后税费、冲销前均摊金额、冲销均摊金额、补充均摊金额、冲销后均摊金额
- 订单明细:订单号、下单日期、退费日期、订单分类、原订单金额、退款金额、退后金额、税率、原订单税额、当前税后金额、当前税额、原始均摊周期、退后均摊周期、历史累计均摊金额、冲销历史累计摊销金额、退后订单历史累计摊销金额、冲销原税款、冲销未确认收入、当期摊销金额、账期税额、未确认收入、剩余周期 ### 汇总表Sheet1
| 列 | 说明 |
|----|------|
| 订单数 | 账期内下单的订单总数 |
| 正式订单数 | 账期内下单且已过7天试用期的订单数 |
| 试用订单数 | 账期内下单但仍处于试用期的订单数 |
| 冲销历史累计摊销金额 | 场景B/E冲销的历史均摊金额总和负数 |
| 退后订单历史累计摊销金额 | 站在账期最后一天看的历史有效摊销金额总和 |
| 冲销原税款 | 场景B/E冲销的税费总和负数 |
| 冲销未确认收入 | 场景B/E冲销的未确认收入总和负数 |
| 当期摊销金额 | 本账期内所有订单产生的均摊收入总和(核心指标) |
| 账期税额 | 账期内新增已转正订单的税费总和 |
| 未确认收入 | 截至账期最后一天尚未均摊的税后金额总和 |
### 订单明细Sheet2
每笔订单一行,包含:订单号、下单日期、退费日期、订单分类、原订单金额、退款金额、退后金额、税率、原订单税额、当前税后金额、当前税额、原始均摊周期、退后均摊周期、历史累计均摊金额、冲销历史累计摊销金额、退后订单历史累计摊销金额、冲销原税款、冲销未确认收入、当期摊销金额、账期税额、未确认收入、剩余周期
### 本月预收账款Sheet3
按"上月订单转正"/"本月订单转正"/"本月订单未转正"三类统计新增和退费含税金额。
3. 文字回复核心指标: 3. 文字回复核心指标:
> 📊 账期${账期起始日}至${账期结束日}均摊结算结果: > 📊 账期{start}至{end}均摊结算结果:
> 总订单数XXX单 > 总订单数XXX单 | 正式订单数XXX单 | 试用订单数XXX单
> 正式订单数XXX单 > 当期摊销金额税后净收入XXX元 | 账期税额XXX元
> 试用订单数XXX单 > 冲销历史累计摊销金额XXX元 | 未确认收入XXX元
> 冲销后税费XXX元
> 冲销后均摊金额税后净收入XXX元
## 汇总指标计算逻辑(所有均摊金额均为税后金额)
1. **订单数**:账期内(下单日落在账期起止时间范围内)新增的所有符合条件的订单总数量
2. **正式订单数**账期内新增的订单中截止账期结束日已过7天试用期下单日+7天 ≤ 账期结束日)的订单数量,仅正式订单开始参与均摊计算
3. **试用订单数**账期内新增的订单中截止账期结束日仍处于7天试用期内下单日+7天 > 账期结束日)的订单数量,试用订单未开始均摊,不参与金额计算
4. **冲销前税费**:账期内所有正式订单的含税订单总金额 × 各订单对应税率 的总和
5. **冲销税费**:本账期内发生退费的所有订单的含税订单总金额 × 各订单对应税率 的总和(正数展示,计算时扣除)
6. **补充税费**:本账期内发生部分退费的订单,退后订单含税金额 × 各订单对应税率 的总和,其中:部分退费订单退后含税金额 = 原订单含税金额 - 退费含税金额
7. **冲销后税费**:账期内最终确认的总税费,计算公式:`冲销后税费 = 冲销前税费 - 冲销税费 + 补充税费`
8. **冲销前均摊金额(税后)**:历史未退费订单 + 账期内正式订单 在本账期内产生的税后均摊收入总和
9. **冲销均摊金额(税后)**:本账期内发生退费的所有订单,需要冲销的历史均摊金额总和(正数展示,计算时扣除)
10. **补充均摊金额(税后)**:本账期内发生部分退费的订单,从转正日起至账期最后一日产生的税后均摊金额总和
11. **冲销后均摊金额(税后)**:账期内最终确认的税后净收入,计算公式:`冲销后均摊金额 = 冲销前均摊金额 - 冲销均摊金额 + 补充均摊金额`
## 注意事项 ## 注意事项
1. 所有金额保留2位小数百分比保留1位小数 1. 所有金额保留2位小数百分比保留1位小数
2. 部分退费场景需确保bi_vala_seasonal_ticket中对应退费部分的status已更新为-1 2. 部分退费场景:需确保 bi_vala_seasonal_ticket 中对应退费部分的 status 已更新为 -1
3. 自动剔除测试账号,无需额外过滤 3. 税后金额计算使用价外税公式:含税价 / (1 + 税率)
4. 冲销金额统一显示为负数 4. SQL 模板统一使用 `{period_start}``{period_end}` 占位符,由 run.py 替换
5. 公共 CTE 链位于 `sql/_common.sql`detail/summary 共享,修改业务逻辑只需改一处

View File

@ -1,5 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
订单均摊结算统计脚本
用法
python3 run.py --start 2026-03-01 --end 2026-03-31
python3 run.py 2026-03-01 2026-03-31
"""
import os import os
import sys import sys
import re import re
@ -9,7 +15,6 @@ import psycopg2
import pandas as pd import pandas as pd
def main(): def main():
# Parse command line arguments
parser = argparse.ArgumentParser(description='订单均摊结算统计脚本') parser = argparse.ArgumentParser(description='订单均摊结算统计脚本')
parser.add_argument('--start', type=str, help='账期起始日YYYY-MM-DD') parser.add_argument('--start', type=str, help='账期起始日YYYY-MM-DD')
parser.add_argument('--end', type=str, help='账期结束日YYYY-MM-DD') parser.add_argument('--end', type=str, help='账期结束日YYYY-MM-DD')
@ -17,7 +22,6 @@ def main():
parser.add_argument('pos_end', nargs='?', type=str, help='账期结束日(位置参数)') parser.add_argument('pos_end', nargs='?', type=str, help='账期结束日(位置参数)')
args = parser.parse_args() args = parser.parse_args()
# Get start and end dates
start_date = args.start or args.pos_start start_date = args.start or args.pos_start
end_date = args.end or args.pos_end end_date = args.end or args.pos_end
@ -27,46 +31,51 @@ def main():
print("用法2python3 run.py 2026-03-01 2026-03-31", file=sys.stderr) print("用法2python3 run.py 2026-03-01 2026-03-31", file=sys.stderr)
sys.exit(1) sys.exit(1)
# Read PostgreSQL password from secrets.env # ── 1. 读取数据库密码 ──
secrets_path = '/root/.openclaw/workspace/secrets.env' secrets_path = '/root/.openclaw/workspace/secrets.env'
if not os.path.exists(secrets_path): if not os.path.exists(secrets_path):
print(f"错误secrets.env文件不存在 {secrets_path}", file=sys.stderr) print(f"错误secrets.env文件不存在 {secrets_path}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(secrets_path, 'r', encoding='utf-8') as f: with open(secrets_path, 'r', encoding='utf-8') as f:
secrets_content = f.read() secrets_content = f.read()
pg_password_match = re.search(r"PG_ONLINE_PASSWORD='(.*)'", secrets_content) pg_password_match = re.search(r"PG_ONLINE_PASSWORD='(.*)'", secrets_content)
if not pg_password_match: if not pg_password_match:
print("错误未找到PG_ONLINE_PASSWORD配置", file=sys.stderr) print("错误未找到PG_ONLINE_PASSWORD配置", file=sys.stderr)
sys.exit(1) sys.exit(1)
pg_password = pg_password_match.group(1) pg_password = pg_password_match.group(1)
# Read SQL templates # ── 2. 读取 SQL 模板 ──
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
summary_sql_path = os.path.join(script_dir, 'sql', 'summary.sql') sql_dir = os.path.join(script_dir, 'sql')
detail_sql_path = os.path.join(script_dir, 'sql', 'detail.sql') common_sql_path = os.path.join(sql_dir, '_common.sql')
prepaid_sql_path = os.path.join(script_dir, 'sql', 'prepaid.sql') detail_sql_path = os.path.join(sql_dir, 'detail.sql')
summary_sql_path = os.path.join(sql_dir, 'summary.sql')
prepaid_sql_path = os.path.join(sql_dir, 'prepaid.sql')
if not os.path.exists(summary_sql_path) or not os.path.exists(detail_sql_path): for p in [common_sql_path, detail_sql_path, summary_sql_path]:
print("错误SQL模板文件不存在", file=sys.stderr) if not os.path.exists(p):
print(f"错误SQL模板文件不存在 {p}", file=sys.stderr)
sys.exit(1) sys.exit(1)
with open(summary_sql_path, 'r', encoding='utf-8') as f: with open(common_sql_path, 'r', encoding='utf-8') as f:
summary_sql_template = f.read() common_sql_raw = f.read()
with open(detail_sql_path, 'r', encoding='utf-8') as f: with open(detail_sql_path, 'r', encoding='utf-8') as f:
detail_sql_template = f.read() detail_output_raw = f.read()
prepaid_sql_template = None with open(summary_sql_path, 'r', encoding='utf-8') as f:
summary_output_raw = f.read()
# 拼接公共CTE + 各自SELECT → 统一替换参数(避免多次格式化)
detail_sql = (common_sql_raw + "\n" + detail_output_raw).format(
period_start=start_date, period_end=end_date)
summary_sql = (common_sql_raw + "\n" + summary_output_raw).format(
period_start=start_date, period_end=end_date)
prepaid_sql = None
if os.path.exists(prepaid_sql_path): if os.path.exists(prepaid_sql_path):
with open(prepaid_sql_path, 'r', encoding='utf-8') as f: with open(prepaid_sql_path, 'r', encoding='utf-8') as f:
prepaid_sql_template = f.read() prepaid_sql = f.read().format(period_start=start_date, period_end=end_date)
# Replace placeholders # ── 3. 连接数据库并执行查询 ──
summary_sql = summary_sql_template.format(period_start=start_date, period_end=end_date)
detail_sql = detail_sql_template.format(period_start=start_date, period_end=end_date)
prepaid_sql = prepaid_sql_template.format(period_start=start_date, period_end=end_date) if prepaid_sql_template else None
# Connect to PostgreSQL
try: try:
conn = psycopg2.connect( conn = psycopg2.connect(
host="bj-postgres-16pob4sg.sql.tencentcdb.com", host="bj-postgres-16pob4sg.sql.tencentcdb.com",
@ -79,7 +88,6 @@ def main():
print(f"数据库连接失败:{str(e)}", file=sys.stderr) print(f"数据库连接失败:{str(e)}", file=sys.stderr)
sys.exit(1) sys.exit(1)
# Execute queries
try: try:
summary_df = pd.read_sql(summary_sql, conn) summary_df = pd.read_sql(summary_sql, conn)
detail_df = pd.read_sql(detail_sql, conn) detail_df = pd.read_sql(detail_sql, conn)
@ -91,7 +99,7 @@ def main():
finally: finally:
conn.close() conn.close()
# Write to Excel # ── 4. 输出 Excel 报表 ──
output_dir = '/root/.openclaw/workspace/output' output_dir = '/root/.openclaw/workspace/output'
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
output_file = os.path.join(output_dir, f'订单均摊结算报表_{start_date}_{end_date}.xlsx') output_file = os.path.join(output_dir, f'订单均摊结算报表_{start_date}_{end_date}.xlsx')
@ -106,7 +114,7 @@ def main():
print(f"Excel生成失败{str(e)}", file=sys.stderr) print(f"Excel生成失败{str(e)}", file=sys.stderr)
sys.exit(1) sys.exit(1)
# Print summary result as JSON # ── 5. 控制台输出 JSON 汇总 ──
result = summary_df.to_dict(orient='records')[0] result = summary_df.to_dict(orient='records')[0]
print(json.dumps(result, ensure_ascii=False, indent=2)) print(json.dumps(result, ensure_ascii=False, indent=2))
print(f"\n报表已保存到:{output_file}", file=sys.stderr) print(f"\n报表已保存到:{output_file}", file=sys.stderr)

View File

@ -0,0 +1,167 @@
-- 订单均摊结算统计 — 公共CTE链detail.sql / summary.sql 共用)
-- 参数:{period_start} 账期起始日,{period_end} 账期结束日
-- 所有金额均为税后金额(价外税,含税价 / (1+税率)
WITH
-- 步骤0获取所有历史退费记录不受当前账期限制
all_refund_records AS (
SELECT
out_trade_no AS order_no,
SUM(refund_amount_int)::numeric / 100 AS total_refund_amount,
MAX(refund_type) AS refund_type,
MAX(DATE(updated_at)) AS latest_refund_date
FROM bi_refund_order
WHERE status = 3
AND DATE(updated_at) <= '{period_end}'::date
GROUP BY out_trade_no
),
-- 步骤1获取所有符合条件的订单基础信息
order_base AS (
SELECT
o.id AS order_id,
o.out_trade_no AS order_no,
o.trade_no,
DATE(o.pay_success_date) AS order_date,
o.pay_amount_int::numeric / 100 AS pay_amount,
CASE WHEN DATE(o.pay_success_date) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_rate,
(o.pay_amount_int::numeric / 100) / (1 + CASE WHEN DATE(o.pay_success_date) < '2026-05-01' THEN 0.01 ELSE 0.06 END)
* CASE WHEN DATE(o.pay_success_date) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_amount,
(o.pay_amount_int::numeric / 100) / (1 + CASE WHEN DATE(o.pay_success_date) < '2026-05-01' THEN 0.01 ELSE 0.06 END) AS after_tax_amount,
DATE(o.pay_success_date) + INTERVAL '7 days' AS amortization_start_date,
o.order_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,
CASE WHEN ar.order_no IS NOT NULL
AND COALESCE(ar.total_refund_amount, 0) >= o.pay_amount_int::numeric / 100
THEN 1 ELSE 0 END 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.pay_success_date >= '2025-06-01'
AND o.pay_success_date <= '{period_end}'::date + INTERVAL '1 day' - INTERVAL '1 second'
AND o.order_status IN (3,4)
AND o.pay_amount_int >= 1000
),
-- 步骤2合并计算原始均摊周期和退后均摊周期一次表扫描
order_cycle AS (
SELECT
out_trade_no AS order_no,
COUNT(DISTINCT id) * 90 AS original_cycle_days,
COUNT(DISTINCT CASE WHEN status != -1 THEN id END) * 90 AS active_cycle_days
FROM bi_vala_seasonal_ticket
GROUP BY out_trade_no
),
-- 步骤3合并订单基础信息与均摊周期仅计算基础字段衍生字段在步骤3.5统一计算)
order_with_cycle AS (
SELECT
ob.*,
COALESCE(oc.active_cycle_days, 0) AS active_cycle_days,
-- total_cycle_days: 退后均摊周期。若active=0且has_refund=0且original>0
-- 说明退费发生在账期后(季卡已删但账期内无退款记录),回退到原始周期
CASE WHEN COALESCE(oc.active_cycle_days, 0) = 0
AND COALESCE(oc.original_cycle_days, 0) > 0
AND ob.has_refund = 0
THEN COALESCE(oc.original_cycle_days, 0)
ELSE COALESCE(oc.active_cycle_days, 0)
END AS total_cycle_days,
COALESCE(oc.original_cycle_days, 0) AS original_cycle_days,
-- 原始均摊结束日
ob.amortization_start_date
+ COALESCE(oc.original_cycle_days, 0) * INTERVAL '1 day' - INTERVAL '1 day' AS original_amortization_end_date,
-- 原始日均摊金额(基于原订单税后金额和原始周期)
CASE WHEN COALESCE(oc.original_cycle_days, 0) > 0
THEN ob.after_tax_amount / oc.original_cycle_days ELSE 0 END AS original_daily_amort_amount,
-- 原始日税费均摊
CASE WHEN COALESCE(oc.original_cycle_days, 0) > 0
THEN ob.tax_amount / oc.original_cycle_days ELSE 0 END AS original_daily_tax_amount
FROM order_base ob
LEFT JOIN order_cycle oc ON ob.order_no = oc.order_no
),
-- 步骤3.5基于effective total_cycle_days计算衍生字段均摊结束日、日均摊金额、日税费金额
order_with_cycle_fixed AS (
SELECT
owc.*,
-- 均摊结束日基于effective total_cycle_days
CASE WHEN owc.total_cycle_days > 0
THEN owc.amortization_start_date + owc.total_cycle_days * INTERVAL '1 day' - INTERVAL '1 day'
ELSE NULL END AS amortization_end_date,
-- 退后日均摊金额
CASE WHEN owc.total_cycle_days > 0 AND owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate) / owc.total_cycle_days
WHEN owc.total_cycle_days > 0
THEN owc.after_tax_amount / owc.total_cycle_days
ELSE 0 END AS daily_amort_amount,
-- 退后日税费均摊
CASE WHEN owc.total_cycle_days > 0 AND owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate) * owc.tax_rate / owc.total_cycle_days
WHEN owc.total_cycle_days > 0
THEN owc.tax_amount / owc.total_cycle_days
ELSE 0 END AS daily_tax_amount
FROM order_with_cycle owc
),
-- 步骤4判断订单场景分类
order_classified AS (
SELECT
owc.*,
GREATEST(owc.amortization_start_date, '{period_start}'::date) AS eff_start,
LEAST(owc.amortization_end_date, '{period_end}'::date) AS eff_end,
-- 试用期内退费(退费日 < 转正日,从未产生均摊,不应触发冲销)
CASE WHEN owc.has_refund = 1
AND owc.latest_refund_date < DATE(owc.amortization_start_date)
THEN 1 ELSE 0 END AS is_trial_period_refund,
-- 账期前全额退费场景F本账期完全排除
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 1
AND owc.latest_refund_date < '{period_start}'::date
THEN 1 ELSE 0 END AS is_pre_period_full_refund,
-- 账期前部分退费场景C按退后金额正常均摊
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
AND owc.latest_refund_date < '{period_start}'::date
THEN 1 ELSE 0 END AS is_pre_period_partial_refund,
-- 账期内退费场景A/B/D/E
CASE WHEN owc.has_refund = 1
AND owc.latest_refund_date >= '{period_start}'::date
AND owc.latest_refund_date <= '{period_end}'::date
THEN 1 ELSE 0 END AS is_current_period_refund,
-- 账期内下单
CASE WHEN owc.order_date >= '{period_start}'::date
AND owc.order_date <= '{period_end}'::date
THEN 1 ELSE 0 END AS is_ordered_in_period,
-- 退后订单税后金额
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate)
ELSE owc.after_tax_amount END AS refunded_after_tax_amount,
-- 退后订单税费
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate) * owc.tax_rate
ELSE owc.tax_amount END AS refunded_tax_amount
FROM order_with_cycle_fixed owc
WHERE owc.total_cycle_days > 0
OR (owc.has_refund = 1 AND owc.is_full_refund = 1 AND owc.original_cycle_days > 0)
),
-- 步骤5计算均摊天数
order_with_days AS (
SELECT
oc2.*,
-- 账期内有效均摊天数(基于退后周期)
CASE WHEN oc2.eff_end >= oc2.eff_start AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (oc2.eff_end - oc2.eff_start))::int + 1 ELSE 0 END AS period_amort_days,
-- 从转正日到账期结束日的总已均摊天数(基于退后周期)
CASE WHEN '{period_end}'::date >= oc2.amortization_start_date AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_end}'::date, oc2.amortization_end_date) - oc2.amortization_start_date))::int + 1
ELSE 0 END AS total_amorted_days_to_period_end,
-- 从转正日到账期起始日前一天的历史已均摊天数(基于原始周期,用于冲销计算)
CASE WHEN '{period_start}'::date > oc2.amortization_start_date AND oc2.original_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_start}'::date - 1, oc2.original_amortization_end_date) - oc2.amortization_start_date))::int + 1
ELSE 0 END AS historical_amorted_days,
-- 从转正日到账期结束日的天数(基于退后周期,用于退后订单重新均摊)
CASE WHEN '{period_end}'::date >= oc2.amortization_start_date AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_end}'::date, oc2.amortization_end_date) - oc2.amortization_start_date))::int + 1
ELSE 0 END AS refunded_amort_days_to_period_end
FROM order_classified oc2
)

View File

@ -1,185 +1,6 @@
WITH -- 订单明细输出(依赖 _common.sql 的 CTE 链)
-- 步骤0获取所有历史退费记录 -- 最终查询从 order_with_days 中选取所有字段,按场景分类展示每笔订单的均摊明细
all_refund_records AS (
SELECT
out_trade_no AS order_no,
SUM(refund_amount_int) / 100 AS total_refund_amount,
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,
o.trade_no,
DATE(o.created_at) AS order_date,
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.0) / (1 + CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END) * CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_amount,
(o.pay_amount_int / 100.0) / (1 + CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END) AS after_tax_amount,
DATE(o.created_at) + INTERVAL '7 days' AS amortization_start_date,
o.order_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,
CASE WHEN ar.order_no IS NOT NULL AND COALESCE(ar.total_refund_amount, 0) >= o.pay_amount_int / 100 THEN 1 ELSE 0 END 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-06-01'
AND o.created_at <= '{period_end}'::date + INTERVAL '1 day' - INTERVAL '1 second'
AND o.order_status IN (3,4)
AND o.pay_amount_int >= 1000
),
-- 步骤2计算每个订单的当前均摊周期退费后和原始均摊周期退费前
order_cycle AS (
SELECT
out_trade_no AS order_no,
COUNT(DISTINCT id) * 90 AS total_cycle_days
FROM bi_vala_seasonal_ticket
WHERE status != -1
GROUP BY out_trade_no
),
order_cycle_original AS (
SELECT
out_trade_no AS order_no,
COUNT(DISTINCT id) * 90 AS original_cycle_days
FROM bi_vala_seasonal_ticket
GROUP BY out_trade_no
),
-- 步骤3合并订单基础信息与均摊周期
order_with_cycle AS (
SELECT
ob.*,
COALESCE(oc.total_cycle_days, 0) AS total_cycle_days,
COALESCE(oco.original_cycle_days, 0) AS original_cycle_days,
ob.amortization_start_date + COALESCE(oc.total_cycle_days, 0) * INTERVAL '1 day' - INTERVAL '1 day' AS amortization_end_date,
-- 原始均摊结束日(退费前)
ob.amortization_start_date + COALESCE(oco.original_cycle_days, 0) * INTERVAL '1 day' - INTERVAL '1 day' AS original_amortization_end_date,
-- 原始日均摊金额(基于原订单税后金额和原始周期)
CASE WHEN COALESCE(oco.original_cycle_days, 0) > 0
THEN ob.after_tax_amount / oco.original_cycle_days
ELSE 0
END AS original_daily_amort_amount,
-- 原始日税费均摊
CASE WHEN COALESCE(oco.original_cycle_days, 0) > 0
THEN ob.tax_amount / oco.original_cycle_days
ELSE 0
END AS original_daily_tax_amount,
-- 退后日均摊金额(基于退后订单税后金额和退后周期)
CASE WHEN COALESCE(oc.total_cycle_days, 0) > 0 AND ob.has_refund = 1 AND ob.is_full_refund = 0
THEN (ob.pay_amount - ob.total_refund_amount) / (1 + ob.tax_rate) / oc.total_cycle_days
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 AND ob.has_refund = 1 AND ob.is_full_refund = 0
THEN (ob.pay_amount - ob.total_refund_amount) / (1 + ob.tax_rate) * ob.tax_rate / oc.total_cycle_days
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
LEFT JOIN order_cycle_original oco ON ob.order_no = oco.order_no
),
-- 步骤4判断订单场景分类
order_classified AS (
SELECT
owc.*,
-- 均摊有效起始日
GREATEST(owc.amortization_start_date, '{period_start}'::date) AS eff_start,
-- 均摊有效结束日
LEAST(owc.amortization_end_date, '{period_end}'::date) AS eff_end,
-- 判断是否为试用期内退费(退费日 < 转正日,从未产生均摊,不应触发冲销)
CASE WHEN owc.has_refund = 1
AND owc.latest_refund_date < DATE(owc.amortization_start_date)
THEN 1 ELSE 0
END AS is_trial_period_refund,
-- 判断是否为账期前全额退费场景F排除
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 1
AND owc.latest_refund_date < '{period_start}'::date
THEN 1 ELSE 0
END AS is_pre_period_full_refund,
-- 判断是否为账期前部分退费场景C
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
AND owc.latest_refund_date < '{period_start}'::date
THEN 1 ELSE 0
END AS is_pre_period_partial_refund,
-- 判断是否为账期内退费
CASE WHEN owc.has_refund = 1
AND owc.latest_refund_date >= '{period_start}'::date
AND owc.latest_refund_date <= '{period_end}'::date
THEN 1 ELSE 0
END AS is_current_period_refund,
-- 判断是否为账期内下单(下单日在账期内)
CASE WHEN owc.order_date >= '{period_start}'::date
AND owc.order_date <= '{period_end}'::date
THEN 1 ELSE 0
END AS is_ordered_in_period,
-- 退后订单税后金额
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate)
ELSE owc.after_tax_amount
END AS refunded_after_tax_amount,
-- 退后订单税费
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate) * owc.tax_rate
ELSE owc.tax_amount
END AS refunded_tax_amount
FROM order_with_cycle owc
WHERE owc.total_cycle_days > 0 OR (owc.has_refund = 1 AND owc.is_full_refund = 1 AND owc.original_cycle_days > 0)
),
-- 步骤5计算均摊天数
order_with_days AS (
SELECT
oc2.*,
-- 账期内有效均摊天数(基于退后周期)
CASE WHEN oc2.eff_end >= oc2.eff_start AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (oc2.eff_end - oc2.eff_start))::int + 1
ELSE 0
END AS period_amort_days,
-- 从转正日到账期结束日的总已均摊天数(基于退后周期)
CASE WHEN '{period_end}'::date >= oc2.amortization_start_date AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_end}'::date, oc2.amortization_end_date) - oc2.amortization_start_date))::int + 1
ELSE 0
END AS total_amorted_days_to_period_end,
-- 从转正日到账期起始日前一天的历史已均摊天数(基于原始周期,用于冲销计算)
CASE WHEN '{period_start}'::date > oc2.amortization_start_date AND oc2.original_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_start}'::date - 1, oc2.original_amortization_end_date) - oc2.amortization_start_date))::int + 1
ELSE 0
END AS historical_amorted_days,
-- 从转正日到账期结束日的天数(基于退后周期,用于退后订单的重新均摊)
CASE WHEN '{period_end}'::date >= oc2.amortization_start_date AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_end}'::date, oc2.amortization_end_date) - oc2.amortization_start_date))::int + 1
ELSE 0
END AS refunded_amort_days_to_period_end
FROM order_classified oc2
)
-- 最终输出
SELECT SELECT
order_no AS "订单号", order_no AS "订单号",
order_date AS "下单日期", order_date AS "下单日期",
@ -204,8 +25,7 @@ SELECT
CASE CASE
WHEN has_refund = 1 AND is_full_refund = 0 WHEN has_refund = 1 AND is_full_refund = 0
THEN (pay_amount - total_refund_amount) / (1 + tax_rate) THEN (pay_amount - total_refund_amount) / (1 + tax_rate)
WHEN has_refund = 1 AND is_full_refund = 1 WHEN has_refund = 1 AND is_full_refund = 1 THEN 0
THEN 0
ELSE after_tax_amount ELSE after_tax_amount
END::numeric, 2 END::numeric, 2
) AS "当前税后金额", ) AS "当前税后金额",
@ -213,17 +33,14 @@ SELECT
CASE CASE
WHEN has_refund = 1 AND is_full_refund = 0 WHEN has_refund = 1 AND is_full_refund = 0
THEN (pay_amount - total_refund_amount) / (1 + tax_rate) * tax_rate THEN (pay_amount - total_refund_amount) / (1 + tax_rate) * tax_rate
WHEN has_refund = 1 AND is_full_refund = 1 WHEN has_refund = 1 AND is_full_refund = 1 THEN 0
THEN 0
ELSE tax_amount ELSE tax_amount
END::numeric, 2 END::numeric, 2
) AS "当前税额", ) AS "当前税额",
original_cycle_days AS "原始均摊周期", original_cycle_days AS "原始均摊周期",
total_cycle_days AS "退后均摊周期", total_cycle_days AS "退后均摊周期",
-- 历史累计均摊金额(仅历史订单有值,账期内订单=0账期内退费按退费前状态算 -- 历史累计均摊金额(仅历史订单有值,账期内下单=0
-- 账期前部分退费:按退后日均摊×退后历史天数
-- 正常订单/账期内退费:按原始日均摊×历史天数
ROUND( ROUND(
CASE CASE
WHEN is_ordered_in_period = 1 THEN 0 WHEN is_ordered_in_period = 1 THEN 0
@ -235,18 +52,14 @@ SELECT
END::numeric, 2 END::numeric, 2
) AS "历史累计均摊金额", ) AS "历史累计均摊金额",
-- 冲销历史累计摊销金额(场景B/E历史下单+账期内退费,排除试用期内退费) -- 冲销历史累计摊销金额(场景B/E历史下单+账期内退费,排除试用期内退费)
ROUND( ROUND(
CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0 CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0
AND is_trial_period_refund = 0 AND is_trial_period_refund = 0 AND historical_amorted_days > 0
AND historical_amorted_days > 0 THEN -(historical_amorted_days * original_daily_amort_amount) ELSE 0 END::numeric, 2
THEN -(historical_amorted_days * original_daily_amort_amount)
ELSE 0
END::numeric, 2
) AS "冲销历史累计摊销金额", ) AS "冲销历史累计摊销金额",
-- 退后订单历史累计摊销金额(站在账期最后一天,看所有订单在账期第一天之前的有效历史摊销金额) -- 退后订单历史累计摊销金额(站在账期最后一天,看所有订单在账期第一天之前的有效历史摊销)
-- 账期内退费的订单按退后状态算;账期前部分退费按退后状态算;正常订单按原始状态算
ROUND( ROUND(
CASE CASE
WHEN is_ordered_in_period = 1 THEN 0 WHEN is_ordered_in_period = 1 THEN 0
@ -265,82 +78,63 @@ SELECT
ROUND( ROUND(
CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0 CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0
AND is_trial_period_refund = 0 AND is_trial_period_refund = 0
THEN -tax_amount THEN -tax_amount ELSE 0 END::numeric, 2
ELSE 0
END::numeric, 2
) AS "冲销原税款", ) AS "冲销原税款",
-- 冲销未确认收入场景B/E:历史下单+账期内退费,排除试用期内退费 -- 冲销未确认收入场景B/E
ROUND( ROUND(
CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0 CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0
AND is_trial_period_refund = 0 AND is_trial_period_refund = 0
THEN -(after_tax_amount - historical_amorted_days * original_daily_amort_amount) THEN -(after_tax_amount - historical_amorted_days * original_daily_amort_amount) ELSE 0 END::numeric, 2
ELSE 0
END::numeric, 2
) AS "冲销未确认收入", ) AS "冲销未确认收入",
-- 当期摊销金额 -- 当期摊销金额核心按6种场景分支计算
ROUND( ROUND(
CASE CASE
-- 场景F账期前全额退费不统计
WHEN is_pre_period_full_refund = 1 THEN 0 WHEN is_pre_period_full_refund = 1 THEN 0
-- 试用期内全额退费,无均摊
WHEN is_trial_period_refund = 1 AND is_full_refund = 1 THEN 0 WHEN is_trial_period_refund = 1 AND is_full_refund = 1 THEN 0
-- 试用期内部分退费,按退后金额从转正日正常均摊
WHEN is_trial_period_refund = 1 AND is_full_refund = 0 THEN WHEN is_trial_period_refund = 1 AND is_full_refund = 0 THEN
CASE WHEN total_cycle_days > 0 AND period_amort_days > 0 CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
THEN daily_amort_amount * period_amort_days ELSE 0 END THEN daily_amort_amount * period_amort_days ELSE 0 END
-- 场景D账期内下单+全额退费,无均摊
WHEN is_current_period_refund = 1 AND is_full_refund = 1 AND is_ordered_in_period = 1 THEN 0 WHEN is_current_period_refund = 1 AND is_full_refund = 1 AND is_ordered_in_period = 1 THEN 0
-- 场景E历史下单+账期内全额退费,无退后均摊
WHEN is_current_period_refund = 1 AND is_full_refund = 1 AND is_ordered_in_period = 0 THEN 0 WHEN is_current_period_refund = 1 AND is_full_refund = 1 AND is_ordered_in_period = 0 THEN 0
-- 场景A账期内下单+部分退费,按退后订单金额/退后周期从转正日均摊 -- 场景A账期内下单+部分退费
WHEN is_current_period_refund = 1 AND is_full_refund = 0 AND is_ordered_in_period = 1 THEN WHEN is_current_period_refund = 1 AND is_full_refund = 0 AND is_ordered_in_period = 1 THEN
CASE WHEN total_cycle_days > 0 AND period_amort_days > 0 CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
THEN daily_amort_amount * period_amort_days THEN daily_amort_amount * period_amort_days ELSE 0 END
ELSE 0 -- 场景B历史下单+账期内部分退费(仅账期内均摊)
END
-- 场景B历史下单+账期内部分退费,按退后订单金额/退后周期从转正日重新均摊
WHEN is_current_period_refund = 1 AND is_full_refund = 0 AND is_ordered_in_period = 0 THEN WHEN is_current_period_refund = 1 AND is_full_refund = 0 AND is_ordered_in_period = 0 THEN
CASE WHEN total_cycle_days > 0 AND refunded_amort_days_to_period_end > 0 CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
THEN daily_amort_amount * refunded_amort_days_to_period_end THEN daily_amort_amount * period_amort_days ELSE 0 END
ELSE 0 -- 场景C账期前部分退费
END
-- 场景C账期前部分退费按退后订单金额/退后周期正常均摊
WHEN is_pre_period_partial_refund = 1 THEN WHEN is_pre_period_partial_refund = 1 THEN
CASE WHEN total_cycle_days > 0 AND period_amort_days > 0 CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
THEN daily_amort_amount * period_amort_days THEN daily_amort_amount * period_amort_days ELSE 0 END
ELSE 0 -- 正常订单(含补差逻辑)
END
-- 正常订单
WHEN period_amort_days > 0 THEN WHEN period_amort_days > 0 THEN
CASE CASE WHEN eff_end = amortization_end_date
WHEN eff_end = amortization_end_date THEN after_tax_amount - original_daily_amort_amount * (original_cycle_days - 1)
THEN after_tax_amount - original_daily_amort_amount * (original_cycle_days - 1) + original_daily_amort_amount * (period_amort_days - 1) + original_daily_amort_amount * (period_amort_days - 1)
ELSE original_daily_amort_amount * period_amort_days ELSE original_daily_amort_amount * period_amort_days END
END
ELSE 0 ELSE 0
END::numeric, 2 END::numeric, 2
) AS "当期摊销金额", ) AS "当期摊销金额",
-- 退后订单税额(仅账期内下单且已转正的非全额退费订单计税) -- 账期税额(仅账期内下单且已转正的非全额退费订单计税)
ROUND( ROUND(
CASE CASE
-- 账期内下单 + 已转正 + 部分退费 → 退后订单税额
WHEN is_ordered_in_period = 1 WHEN is_ordered_in_period = 1
AND DATE(amortization_start_date) <= '{period_end}'::date AND DATE(amortization_start_date) <= '{period_end}'::date
AND is_current_period_refund = 1 AND is_full_refund = 0 AND is_current_period_refund = 1 AND is_full_refund = 0
THEN refunded_tax_amount THEN refunded_tax_amount
-- 账期内下单 + 已转正 + 未全额退费(含未退费)→ 原订单税额
WHEN is_ordered_in_period = 1 WHEN is_ordered_in_period = 1
AND DATE(amortization_start_date) <= '{period_end}'::date AND DATE(amortization_start_date) <= '{period_end}'::date
AND (has_refund = 0 OR (is_current_period_refund = 0 AND is_full_refund = 0)) AND (has_refund = 0 OR (is_current_period_refund = 0 AND is_full_refund = 0))
THEN tax_amount THEN tax_amount ELSE 0
ELSE 0
END::numeric, 2 END::numeric, 2
) AS "账期税额", ) AS "账期税额",
-- 未确认收入 -- 未确认收入(截至账期最后一天尚未均摊的税后金额)
ROUND( ROUND(
CASE CASE
WHEN is_pre_period_full_refund = 1 THEN 0 WHEN is_pre_period_full_refund = 1 THEN 0
@ -349,12 +143,10 @@ SELECT
refunded_after_tax_amount - daily_amort_amount * refunded_amort_days_to_period_end refunded_after_tax_amount - daily_amort_amount * refunded_amort_days_to_period_end
WHEN is_pre_period_partial_refund = 1 THEN WHEN is_pre_period_partial_refund = 1 THEN
refunded_after_tax_amount - daily_amort_amount * total_amorted_days_to_period_end refunded_after_tax_amount - daily_amort_amount * total_amorted_days_to_period_end
ELSE ELSE after_tax_amount - original_daily_amort_amount * total_amorted_days_to_period_end
after_tax_amount - original_daily_amort_amount * total_amorted_days_to_period_end
END::numeric, 2 END::numeric, 2
) AS "未确认收入", ) AS "未确认收入",
-- 剩余周期
CASE CASE
WHEN is_current_period_refund = 1 AND is_full_refund = 1 THEN 0 WHEN is_current_period_refund = 1 AND is_full_refund = 1 THEN 0
ELSE (total_cycle_days - total_amorted_days_to_period_end) ELSE (total_cycle_days - total_amorted_days_to_period_end)

View File

@ -17,29 +17,30 @@ month_range AS (
all_refund_records AS ( all_refund_records AS (
SELECT SELECT
out_trade_no AS order_no, out_trade_no AS order_no,
SUM(refund_amount_int) / 100 AS total_refund_amount, SUM(refund_amount_int)::numeric / 100 AS total_refund_amount,
MAX(CASE WHEN refund_type = 2 THEN 1 ELSE 0 END) AS is_full_refund, MAX(CASE WHEN refund_type = 2 THEN 1 ELSE 0 END) AS is_full_refund,
MAX(DATE(updated_at)) AS latest_refund_date MAX(DATE(updated_at)) AS latest_refund_date
FROM bi_refund_order FROM bi_refund_order
WHERE status = 3 WHERE status = 3
AND DATE(updated_at) <= '{period_end}'::date
GROUP BY out_trade_no GROUP BY out_trade_no
), ),
-- 所有订单基础信息(含退费标记) -- 所有订单基础信息(含退费标记)
order_base AS ( order_base AS (
SELECT SELECT
o.out_trade_no AS order_no, o.out_trade_no AS order_no,
DATE(o.created_at) AS order_date, DATE(o.pay_success_date) AS order_date,
o.pay_amount_int / 100 AS pay_amount, o.pay_amount_int::numeric / 100 AS pay_amount,
CASE WHEN ar.order_no IS NOT NULL THEN 1 ELSE 0 END AS has_refund, 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.total_refund_amount, 0) AS total_refund_amount,
CASE WHEN ar.order_no IS NOT NULL AND COALESCE(ar.total_refund_amount, 0) >= o.pay_amount_int / 100 THEN 1 ELSE 0 END AS is_full_refund, CASE WHEN ar.order_no IS NOT NULL AND COALESCE(ar.total_refund_amount, 0) >= o.pay_amount_int::numeric / 100 THEN 1 ELSE 0 END AS is_full_refund,
ar.latest_refund_date, ar.latest_refund_date,
-- 转正日期 = 下单日 + 7天 -- 转正日期 = 下单日 + 7天
DATE(o.created_at) + INTERVAL '7 days' AS amortization_start_date, DATE(o.pay_success_date) + INTERVAL '7 days' AS amortization_start_date,
-- 有效金额(部分退费 = 原金额 - 退费;未退费 = 原金额;全额退费 = 0 -- 有效金额(部分退费 = 原金额 - 退费;未退费 = 原金额;全额退费 = 0
CASE CASE
WHEN ar.order_no IS NOT NULL AND COALESCE(ar.total_refund_amount, 0) >= o.pay_amount_int / 100 THEN 0 WHEN ar.order_no IS NOT NULL AND COALESCE(ar.total_refund_amount, 0) >= o.pay_amount_int::numeric / 100 THEN 0
ELSE o.pay_amount_int / 100 - COALESCE(ar.total_refund_amount, 0) ELSE o.pay_amount_int::numeric / 100 - COALESCE(ar.total_refund_amount, 0)
END AS effective_amount, END AS effective_amount,
o.order_status o.order_status
FROM bi_vala_order o FROM bi_vala_order o
@ -47,8 +48,8 @@ order_base AS (
LEFT JOIN all_refund_records ar ON o.out_trade_no = ar.order_no LEFT JOIN all_refund_records ar ON o.out_trade_no = ar.order_no
CROSS JOIN month_range mr CROSS JOIN month_range mr
WHERE WHERE
o.created_at >= mr.last_month_start o.pay_success_date >= mr.last_month_start
AND o.created_at <= mr.period_end AND o.pay_success_date <= mr.period_end
AND o.order_status IN (3,4) AND o.order_status IN (3,4)
AND o.pay_amount_int >= 1000 AND o.pay_amount_int >= 1000
AND a.status = 1 AND a.status = 1

View File

@ -1,147 +1,26 @@
WITH -- 汇总输出(依赖 _common.sql 的 CTE 链)
-- 步骤0获取所有历史退费记录 -- 对 order_with_days 进行聚合,输出账期整体汇总指标
all_refund_records AS ( -- 列名与 detail.sql 保持一致,方便交叉验证(明细 SUM = 汇总值)
SELECT
out_trade_no AS order_no,
SUM(refund_amount_int) / 100 AS total_refund_amount,
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,
o.trade_no,
DATE(o.created_at) AS order_date,
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.0) / (1 + CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END) * CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END AS tax_amount,
(o.pay_amount_int / 100.0) / (1 + CASE WHEN DATE(o.created_at) < '2026-05-01' THEN 0.01 ELSE 0.06 END) AS after_tax_amount,
DATE(o.created_at) + INTERVAL '7 days' AS amortization_start_date,
o.order_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,
CASE WHEN ar.order_no IS NOT NULL AND COALESCE(ar.total_refund_amount, 0) >= o.pay_amount_int / 100 THEN 1 ELSE 0 END 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-06-01'
AND o.created_at <= '{period_end}'::date + INTERVAL '1 day' - INTERVAL '1 second'
AND o.order_status IN (3,4)
AND o.pay_amount_int >= 1000
),
-- 步骤2计算均摊周期
order_cycle AS (
SELECT out_trade_no AS order_no, COUNT(DISTINCT id) * 90 AS total_cycle_days
FROM bi_vala_seasonal_ticket WHERE status != -1 GROUP BY out_trade_no
),
order_cycle_original AS (
SELECT out_trade_no AS order_no, COUNT(DISTINCT id) * 90 AS original_cycle_days
FROM bi_vala_seasonal_ticket GROUP BY out_trade_no
),
-- 步骤3合并
order_with_cycle AS (
SELECT
ob.*,
COALESCE(oc.total_cycle_days, 0) AS total_cycle_days,
COALESCE(oco.original_cycle_days, 0) AS original_cycle_days,
ob.amortization_start_date + COALESCE(oc.total_cycle_days, 0) * INTERVAL '1 day' - INTERVAL '1 day' AS amortization_end_date,
ob.amortization_start_date + COALESCE(oco.original_cycle_days, 0) * INTERVAL '1 day' - INTERVAL '1 day' AS original_amortization_end_date,
CASE WHEN COALESCE(oco.original_cycle_days, 0) > 0
THEN ob.after_tax_amount / oco.original_cycle_days ELSE 0
END AS original_daily_amort_amount,
CASE WHEN COALESCE(oco.original_cycle_days, 0) > 0
THEN ob.tax_amount / oco.original_cycle_days ELSE 0
END AS original_daily_tax_amount,
CASE WHEN COALESCE(oc.total_cycle_days, 0) > 0 AND ob.has_refund = 1 AND ob.is_full_refund = 0
THEN (ob.pay_amount - ob.total_refund_amount) / (1 + ob.tax_rate) / oc.total_cycle_days
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 AND ob.has_refund = 1 AND ob.is_full_refund = 0
THEN (ob.pay_amount - ob.total_refund_amount) / (1 + ob.tax_rate) * ob.tax_rate / oc.total_cycle_days
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
LEFT JOIN order_cycle_original oco ON ob.order_no = oco.order_no
),
-- 步骤4分类
order_classified AS (
SELECT
owc.*,
GREATEST(owc.amortization_start_date, '{period_start}'::date) AS eff_start,
LEAST(owc.amortization_end_date, '{period_end}'::date) AS eff_end,
-- 试用期内退费(退费日 < 转正日,从未产生均摊,不应触发冲销)
CASE WHEN owc.has_refund = 1
AND owc.latest_refund_date < DATE(owc.amortization_start_date)
THEN 1 ELSE 0 END AS is_trial_period_refund,
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 1 AND owc.latest_refund_date < '{period_start}'::date
THEN 1 ELSE 0 END AS is_pre_period_full_refund,
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0 AND owc.latest_refund_date < '{period_start}'::date
THEN 1 ELSE 0 END AS is_pre_period_partial_refund,
CASE WHEN owc.has_refund = 1
AND owc.latest_refund_date >= '{period_start}'::date AND owc.latest_refund_date <= '{period_end}'::date
THEN 1 ELSE 0 END AS is_current_period_refund,
CASE WHEN owc.order_date >= '{period_start}'::date AND owc.order_date <= '{period_end}'::date
THEN 1 ELSE 0 END AS is_ordered_in_period,
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate) ELSE owc.after_tax_amount
END AS refunded_after_tax_amount,
CASE WHEN owc.has_refund = 1 AND owc.is_full_refund = 0
THEN (owc.pay_amount - owc.total_refund_amount) / (1 + owc.tax_rate) * owc.tax_rate ELSE owc.tax_amount
END AS refunded_tax_amount
FROM order_with_cycle owc
WHERE owc.total_cycle_days > 0 OR (owc.has_refund = 1 AND owc.is_full_refund = 1 AND owc.original_cycle_days > 0)
),
-- 步骤5计算天数
order_with_days AS (
SELECT
oc2.*,
CASE WHEN oc2.eff_end >= oc2.eff_start AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (oc2.eff_end - oc2.eff_start))::int + 1 ELSE 0
END AS period_amort_days,
CASE WHEN '{period_end}'::date >= oc2.amortization_start_date AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_end}'::date, oc2.amortization_end_date) - oc2.amortization_start_date))::int + 1 ELSE 0
END AS total_amorted_days_to_period_end,
CASE WHEN '{period_start}'::date > oc2.amortization_start_date AND oc2.original_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_start}'::date - 1, oc2.original_amortization_end_date) - oc2.amortization_start_date))::int + 1 ELSE 0
END AS historical_amorted_days,
CASE WHEN '{period_end}'::date >= oc2.amortization_start_date AND oc2.total_cycle_days > 0
THEN EXTRACT(DAY FROM (LEAST('{period_end}'::date, oc2.amortization_end_date) - oc2.amortization_start_date))::int + 1 ELSE 0
END AS refunded_amort_days_to_period_end
FROM order_classified oc2
)
-- 步骤6最终汇总与订单明细Sheet2列对齐前四列替换为订单统计+税率其余SUM
SELECT SELECT
COUNT(CASE WHEN order_date >= '{period_start}'::date AND order_date <= '{period_end}'::date THEN 1 END) AS "订单数", -- 订单统计
COUNT(CASE WHEN order_date >= '{period_start}'::date AND order_date <= '{period_end}'::date COUNT(CASE WHEN order_date >= '{period_start}'::date
AND order_date <= '{period_end}'::date THEN 1 END) AS "订单数",
COUNT(CASE WHEN order_date >= '{period_start}'::date
AND order_date <= '{period_end}'::date
AND amortization_start_date <= '{period_end}'::date THEN 1 END) AS "正式订单数", AND amortization_start_date <= '{period_end}'::date THEN 1 END) AS "正式订单数",
COUNT(CASE WHEN order_date >= '{period_start}'::date AND order_date <= '{period_end}'::date COUNT(CASE WHEN order_date >= '{period_start}'::date
AND order_date <= '{period_end}'::date
AND amortization_start_date > '{period_end}'::date THEN 1 END) AS "试用订单数", AND amortization_start_date > '{period_end}'::date THEN 1 END) AS "试用订单数",
-- 冲销历史累计摊销金额SUM明细列逻辑 -- 冲销历史累计摊销金额
ROUND(SUM( ROUND(SUM(
CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0 CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0
AND is_trial_period_refund = 0 AND is_trial_period_refund = 0 AND historical_amorted_days > 0
AND historical_amorted_days > 0 THEN -(historical_amorted_days * original_daily_amort_amount) ELSE 0 END
THEN -(historical_amorted_days * original_daily_amort_amount)
ELSE 0
END
)::numeric, 2) AS "冲销历史累计摊销金额", )::numeric, 2) AS "冲销历史累计摊销金额",
-- 退后订单历史累计摊销金额SUM明细列逻辑 -- 退后订单历史累计摊销金额
ROUND(SUM( ROUND(SUM(
CASE CASE
WHEN is_ordered_in_period = 1 THEN 0 WHEN is_ordered_in_period = 1 THEN 0
@ -152,29 +31,24 @@ SELECT
THEN historical_amorted_days * daily_amort_amount THEN historical_amorted_days * daily_amort_amount
WHEN historical_amorted_days > 0 WHEN historical_amorted_days > 0
THEN historical_amorted_days * original_daily_amort_amount THEN historical_amorted_days * original_daily_amort_amount
ELSE 0 ELSE 0 END
END
)::numeric, 2) AS "退后订单历史累计摊销金额", )::numeric, 2) AS "退后订单历史累计摊销金额",
-- 冲销原税款SUM -- 冲销原税款
ROUND(SUM( ROUND(SUM(
CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0 CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0
AND is_trial_period_refund = 0 AND is_trial_period_refund = 0
THEN -tax_amount THEN -tax_amount ELSE 0 END
ELSE 0
END
)::numeric, 2) AS "冲销原税款", )::numeric, 2) AS "冲销原税款",
-- 冲销未确认收入SUM -- 冲销未确认收入
ROUND(SUM( ROUND(SUM(
CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0 CASE WHEN is_current_period_refund = 1 AND is_ordered_in_period = 0
AND is_trial_period_refund = 0 AND is_trial_period_refund = 0
THEN -(after_tax_amount - historical_amorted_days * original_daily_amort_amount) THEN -(after_tax_amount - historical_amorted_days * original_daily_amort_amount) ELSE 0 END
ELSE 0
END
)::numeric, 2) AS "冲销未确认收入", )::numeric, 2) AS "冲销未确认收入",
-- 当期摊销金额SUM -- 当期摊销金额
ROUND(SUM( ROUND(SUM(
CASE CASE
WHEN is_pre_period_full_refund = 1 THEN 0 WHEN is_pre_period_full_refund = 1 THEN 0
@ -188,37 +62,31 @@ SELECT
CASE WHEN total_cycle_days > 0 AND period_amort_days > 0 CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
THEN daily_amort_amount * period_amort_days ELSE 0 END THEN daily_amort_amount * period_amort_days ELSE 0 END
WHEN is_current_period_refund = 1 AND is_full_refund = 0 AND is_ordered_in_period = 0 THEN WHEN is_current_period_refund = 1 AND is_full_refund = 0 AND is_ordered_in_period = 0 THEN
CASE WHEN total_cycle_days > 0 AND refunded_amort_days_to_period_end > 0 CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
THEN daily_amort_amount * refunded_amort_days_to_period_end ELSE 0 END THEN daily_amort_amount * period_amort_days ELSE 0 END
WHEN is_pre_period_partial_refund = 1 THEN WHEN is_pre_period_partial_refund = 1 THEN
CASE WHEN total_cycle_days > 0 AND period_amort_days > 0 CASE WHEN total_cycle_days > 0 AND period_amort_days > 0
THEN daily_amort_amount * period_amort_days ELSE 0 END THEN daily_amort_amount * period_amort_days ELSE 0 END
WHEN period_amort_days > 0 THEN WHEN period_amort_days > 0 THEN
CASE CASE WHEN eff_end = amortization_end_date
WHEN eff_end = amortization_end_date THEN after_tax_amount - original_daily_amort_amount * (original_cycle_days - 1)
THEN after_tax_amount - original_daily_amort_amount * (original_cycle_days - 1) + original_daily_amort_amount * (period_amort_days - 1) + original_daily_amort_amount * (period_amort_days - 1)
ELSE original_daily_amort_amount * period_amort_days ELSE original_daily_amort_amount * period_amort_days END
END ELSE 0 END
ELSE 0
END
)::numeric, 2) AS "当期摊销金额", )::numeric, 2) AS "当期摊销金额",
-- 账期税额SUM -- 账期税额
ROUND(SUM( ROUND(SUM(
CASE CASE
WHEN is_ordered_in_period = 1 WHEN is_ordered_in_period = 1 AND DATE(amortization_start_date) <= '{period_end}'::date
AND DATE(amortization_start_date) <= '{period_end}'::date
AND is_current_period_refund = 1 AND is_full_refund = 0 AND is_current_period_refund = 1 AND is_full_refund = 0
THEN refunded_tax_amount THEN refunded_tax_amount
WHEN is_ordered_in_period = 1 WHEN is_ordered_in_period = 1 AND DATE(amortization_start_date) <= '{period_end}'::date
AND DATE(amortization_start_date) <= '{period_end}'::date
AND (has_refund = 0 OR (is_current_period_refund = 0 AND is_full_refund = 0)) AND (has_refund = 0 OR (is_current_period_refund = 0 AND is_full_refund = 0))
THEN tax_amount THEN tax_amount ELSE 0 END
ELSE 0
END
)::numeric, 2) AS "账期税额", )::numeric, 2) AS "账期税额",
-- 未确认收入SUM -- 未确认收入
ROUND(SUM( ROUND(SUM(
CASE CASE
WHEN is_pre_period_full_refund = 1 THEN 0 WHEN is_pre_period_full_refund = 1 THEN 0
@ -227,10 +95,9 @@ SELECT
refunded_after_tax_amount - daily_amort_amount * refunded_amort_days_to_period_end refunded_after_tax_amount - daily_amort_amount * refunded_amort_days_to_period_end
WHEN is_pre_period_partial_refund = 1 THEN WHEN is_pre_period_partial_refund = 1 THEN
refunded_after_tax_amount - daily_amort_amount * total_amorted_days_to_period_end refunded_after_tax_amount - daily_amort_amount * total_amorted_days_to_period_end
ELSE ELSE after_tax_amount - original_daily_amort_amount * total_amorted_days_to_period_end END
after_tax_amount - original_daily_amort_amount * total_amorted_days_to_period_end
END
)::numeric, 2) AS "未确认收入" )::numeric, 2) AS "未确认收入"
FROM order_with_days FROM order_with_days
WHERE is_pre_period_full_refund = 0; WHERE is_pre_period_full_refund = 0
AND NOT (is_trial_period_refund = 1 AND is_full_refund = 1);