# 变更审计记录：到手金额口径修复（全小程序统一）

| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-26 02:48:42 |
| Prompt-ID | P20260325-090826 |
| Session-ID | 0ef5e906 |
| Session 路径 | docs/audit/session_logs/2026-03/25/24_e10246a9_090825 |

## 操作摘要

修复小程序全场景"到手金额"计算口径。原实现使用 `ledger_amount`（毛收入，未扣抽成），标注"到手"但实际不是到手。修复后统一为：`hours × net_rate`，其中 net_rate 从 DWS 层 `v_dws_assistant_salary_calc` 获取抽成参数计算：
- 基础课：`base_course_price - base_deduction`
- 激励课/超休课：`bonus_course_price × (1 - bonus_deduction_ratio)`

验证：小燕×轩哥 3月10日 → 毛收入 ¥154.94 → 到手 ¥134.00（1.117h × 120）✓

## 影响范围

| 文件 | 函数/方法 | 变更类型 |
|------|-----------|----------|
| `apps/backend/app/services/fdw_queries.py` | `get_service_records()` | 修改 |
| `apps/backend/app/services/fdw_queries.py` | `get_service_records_for_task()` | 修改 |
| `apps/backend/app/services/fdw_queries.py` | `get_coach_service_records()` | 修改 |
| `apps/backend/app/services/fdw_queries.py` | `get_service_records_90days()` | 修改 |
| `apps/backend/app/services/coach_service.py` | `get_coach_detail()` | 修改 |

## 技术方案

### SQL 层（fdw_queries.py 四个函数统一改法）

每个查询新增 `LEFT JOIN app.v_dws_assistant_salary_calc sc`，按 `assistant_id` + `salary_month`（`date_trunc('month', create_time)::date`）关联。`income` 字段从原来的 `sl.ledger_amount` 改为 CASE 表达式：

```sql
CASE
    WHEN sl.skill_name ILIKE '%%激励%%' OR sl.skill_name ILIKE '%%超休%%'
    THEN (sl.income_seconds / 3600.0) * COALESCE(sc.bonus_course_price * (1 - sc.bonus_deduction_ratio), 0)
    ELSE (sl.income_seconds / 3600.0) * COALESCE(sc.base_course_price - sc.base_deduction, 0)
END AS income
```

### Service 层（coach_service.py）

`get_coach_detail()` 中 `monthly_salary` 从原来的 `gross_salary` 改为 DWS 层已扣抽成的四项之和：
```python
"monthly_salary": (
    salary_this.get("assistant_pd_money_total", 0.0)
    + salary_this.get("assistant_cx_money_total", 0.0)
    + salary_this.get("bonus_money", 0.0)
    + salary_this.get("room_income", 0.0)
)
```

## 风险评估

- `salary_calc` 无当月数据时 `COALESCE` 回退 0（到手=0），但这种情况说明 DWS 未跑完，属于数据质量问题而非代码 bug
- `LEFT JOIN` 不会导致行膨胀（salary_calc 按 assistant_id + salary_month 唯一）
- `get_service_records_90days()` 的 SUM 聚合也已同步改为到手口径

## 本次对话文件变更

### 删除的文件
- `docs/audit/session_logs/2026-03/25/19_e69c9fee_040048/main_01_c847b139.md`

## 改动注解

### `apps/backend/app/services/fdw_queries.py`
- 变更类型：修改
- 原始原因：服务记录列表和绩效页顶部卡片使用 `ledger_amount`（毛收入，未扣抽成），标注"到手"但实际不是到手金额。用户反馈 60 天内服务记录列表中到手金额计算不对。
- 思路分析：在 SQL 层通过 `LEFT JOIN v_dws_assistant_salary_calc` 获取每月的课程定价和抽成参数，用 CASE 表达式区分基础课和激励课/超休课两种费率计算方式。选择 `LEFT JOIN` 而非 `INNER JOIN` 确保 salary_calc 缺失时不丢失记录（COALESCE 回退 0）。四个查询函数（`get_service_records`、`get_service_records_for_task`、`get_coach_service_records`、`get_service_records_90days`）统一改法，保证全小程序口径一致。
- 修改结果：所有服务记录列表页面的 `income` 字段现在返回真正的到手金额。影响页面：绩效页服务记录、任务详情页服务记录、助教详情页服务记录、常客统计（90天聚合）。

### `apps/backend/app/services/coach_service.py`
- 变更类型：修改
- 原始原因：绩效页顶部卡片的 `monthly_salary` 使用 `gross_salary`（毛收入），与行级到手金额口径不一致。
- 思路分析：DWS 层 `salary_calc` 已经提供了扣抽成后的分项金额（`assistant_pd_money_total`、`assistant_cx_money_total`、`bonus_money`、`room_income`），直接求和即为到手总额，无需重复计算抽成逻辑。
- 修改结果：绩效页顶部卡片的月收入数字与行级服务记录的到手金额口径统一。

### `apps/admin-web/src/__tests__/tabUrlSync.property.test.tsx`
- 变更类型：修改（非高风险，简要注解）
- 与本次到手金额修复无关，属于同一 commit 范围内的其他变更。

## DDL/迁移检查

- 无新增迁移 SQL
- DDL 基线状态：未更新（⚠️ DDL 基线待合并）

## 合规检查

- `api_changed`: false — 接口签名未变，仅返回值语义修正
- `openapi_spec_stale`: false — 无需重新导出
