# 审计记录：修复小程序前端档位进度条无刻度 + bonus_money 计算

- 日期：2026-03-24
- Prompt：修复小程序前端没有档位进度 / 达XXX可得X元没有显示金额
- 类型：Bug 修复

## 原始原因

用户反馈小程序任务列表页：1) 绩效进度条没有档位刻度；2) "达XXX即得X元"金额为 0。

## 直接原因

1. `tier_nodes` 断裂：`_parse_salary_row` 返回 `"tier_nodes": []` → 兜底变成 `[0]` → 前端 `maxHours=0`
2. `bonus_money` 断裂：之前取 `salary_calc.sprint_bonus`，但 SPRINT 奖金已于 2026-02-28 过期，当前月份值为 0

## 改动方案

1. `fdw_queries.py`：`batch_query_for_task_list` 增加第 8 步查询 `app.v_cfg_performance_tier`（含 `base_deduction`, `bonus_deduction_ratio`）；`get_performance_tiers` 同步增加两列
2. `task_manager.py`：`_build_performance_summary` 中：
   - `tier_nodes` 从配置表 `min_hours` 构建（如 `[0, 120, 150, 180, 210]`）
   - `next_tier_hours` 根据 `effective_hours` 找第一个 > total_hours 的档位
   - `tier_completed` 当超过最高档时为 True
   - `bonus_money` = 基础课节省 + 打赏课节省（见下方公式）

### bonus_money 公式（用户确认版）

```
基础课节省 = next_tier_min_hours × (当前档 base_deduction - 下一档 base_deduction)
打赏课节省 = 当前打赏课时(bonus_hours) × bonus_course_price × (当前档 bonus_deduction_ratio - 下一档 bonus_deduction_ratio)
bonus_money = 基础课节省 + 打赏课节省
```

示例：80h 基础 + 20h 打赏，T0→T1：基础 `120×(28-18)=1200`，打赏 `20×190×(0.50-0.40)=380`，合计 1580 元。

- `bonus_course_price` 从 `salary_calc.incentive_rate` 读取（当前统一 190 元/小时），禁止硬编码
- `base_deduction` / `bonus_deduction_ratio` 从 `cfg_performance_tier` 配置表读取

## 文件清单

| 文件 | 变更 |
|------|------|
| `apps/backend/app/services/fdw_queries.py` | 查询增加 `base_deduction` + `bonus_deduction_ratio` 列，返回 `performance_tiers` |
| `apps/backend/app/services/task_manager.py` | 用配置表构建 tier_nodes，用抽成差额（基础+打赏）计算 bonus_money |

## 风险评估

- 低风险：仅影响前端展示数据，不涉及金额写入或工资计算
- `bonus_money` 计算公式已与用户确认（含基础课 + 打赏课两部分）

## 回滚策略

还原两个文件的修改即可。

## 验证 SQL

```sql
-- 1. 确认 cfg_performance_tier 有 base_deduction + bonus_deduction_ratio 数据
SELECT tier_code, min_hours, base_deduction, bonus_deduction_ratio
FROM dws.cfg_performance_tier
WHERE effective_from <= CURRENT_DATE AND effective_to >= CURRENT_DATE
ORDER BY tier_level;

-- 2. 确认 RLS 视图含两列
SET LOCAL app.current_site_id = '1';
SELECT tier_code, min_hours, base_deduction, bonus_deduction_ratio
FROM app.v_cfg_performance_tier
WHERE effective_from <= CURRENT_DATE AND effective_to >= CURRENT_DATE
ORDER BY tier_level;

-- 3. 模拟计算：80h 基础 + 20h 打赏，T0→T1
-- 基础课节省 = 120 × (28 - 18) = 1200
-- 打赏课节省 = 20 × 190 × (0.50 - 0.40) = 380
-- bonus_money = 1580
SELECT 120 * (28 - 18) AS base_saving,
       20 * 190 * (0.50 - 0.40) AS bonus_saving,
       120 * (28 - 18) + 20 * 190 * (0.50 - 0.40) AS total_bonus_money;
```
