소스 검색

docs(需求+spec): 财富中心贡献榜管理员口径以role为准(需求变更+实现任务书)

Agnes Agent 5 일 전
부모
커밋
b9cf2d20b7

+ 80 - 0
docs/superpowers/specs/2026-11-07-family-earnings-admin-role.md

@@ -0,0 +1,80 @@
+# 财富中心成员贡献榜:管理员身份以 role 为准 + 池权限可切换
+
+> 任务编号:待派发(cfc-backend + cfc-frontend)
+> 日期:2026-11-07
+> 触发:用户 "1、财富中心成员贡献榜中显示的管理员与实际身份不一致 2、earnings/admin/toggle-member接口报无权限操作,不知道有什么用 3、成员贡献榜每个人都写的'池关闭',不知道有什么用"
+> 主理人裁定:**管理员身份以 role 为准**(role ∈ {parent, teacher})
+
+## 一、需求
+
+1. 贡献榜显示的管理员与实际身份一致(以 role 为准)
+2. `toggle-member` 接口不再报「无权限操作」(家长/教师角色可操作)
+3. 「池关闭」语义可读 + 管理员可正常切换(不改默认值)
+
+## 二、根因(已定位)
+
+管理员判定**双口径割裂**:
+- 前端 `isAdmin`(`promotion/index.vue` L228-232)= `role === 'parent' || 'teacher'`
+- 后端 `FamilyEarningsController.isFamilyAdmin`(L131-134)+ `FamilyEarningsService.getMemberContributions`(L75)= `user.getIsFamilyAdmin()`(`is_family_admin` 字段)
+
+→ 家长角色但 `is_family_admin=0`:前端显示管理员(有按钮/switch),后端鉴权拒绝(「无权限操作」)、贡献榜标签非管理员。`is_family_admin` 只在建家庭/接邀请/转让时设置,普通家长默认 0。
+
+`family_earnings_member` 记录仅在 distribute/toggle 时创建 → 从未分配过的家庭全员默认 `canUseFamilyBalance=false`(「池关闭」)。
+
+## 三、方案(以 role 为准,后端 2 文件 + 前端 1 文件)
+
+### 3.1 后端 `cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyEarningsController.java`
+- `isFamilyAdmin(Long userId)`(L131-134):由 `user.getIsFamilyAdmin()` 改为**角色判定**:
+  ```java
+  private boolean isFamilyAdmin(Long userId) {
+      if (userId == null) return false;
+      User user = userMapper.selectById(userId);
+      String role = user != null ? user.getRole() : null;
+      return "parent".equals(role) || "teacher".equals(role);
+  }
+  ```
+- 其余不动(distribute/toggle-member/withdraw 均复用此方法)
+
+### 3.2 后端 `cfc-backend/src/main/java/com/etotem/cfc/service/FamilyEarningsService.java`
+- `getMemberContributions`(L75):`dto.setIsAdmin(user.getIsFamilyAdmin() != null && user.getIsFamilyAdmin())` 改为:
+  ```java
+  String role = user.getRole();
+  dto.setIsAdmin("parent".equals(role) || "teacher".equals(role));
+  ```
+- **注意**:`getFamilySummary` 内部调用 `getMemberContributions`,同步受益,无需另改
+
+### 3.3 前端 `cfc-frontend/pages/promotion/index.vue`
+- 成员贡献榜区块(members-card,L39-69)补一句说明文案,放 members-header(L40-45)之后 / member-list(L46)之前:
+  ```html
+  <text class="members-hint">池可用 = 该成员可使用家庭公共收益余额;池关闭 = 不可用,由管理员开启</text>
+  ```
+- 样式 `.members-hint`:小字、`$app-text-subtle`、`display:block`、`margin-bottom` 合适
+- 其余逻辑不动(isAdmin computed 已是 role 判定;switch `v-if="isAdmin && !m.isAdmin"` 现由后端对齐后不再误判)
+
+## 四、验收标准
+
+1. 家长/教师角色管理员:贡献榜成员显示「管理员」标签 = 该成员 role 为 parent/teacher;操作按钮/switch 显示与之一致
+2. 管理员点击贡献榜成员 switch(toggle-member)→ 不再报「无权限操作」,成功切换「池可用/池关闭」
+3. 切换后状态正确刷新(重新加载或本地更新)
+4. 贡献榜有「池可用/池关闭」语义说明文案
+5. 构建 `npm run build:mp-weixin` exit 0(前端);后端 `mvn test-compile` 通过(如有条件)
+
+## 五、硬约束
+
+- 后端只改上述 2 文件;前端只改 `promotion/index.vue`;**不改 `ReportMemberPermissionService`**(三口径正确实现,保留)
+- **不改默认池值**(`canUseFamilyBalance=false` 保留为安全默认)
+- **不动 `is_family_admin` 字段**(其他模块仍用)
+- 基于 `origin/cfclub` 建 `feature/family-earnings-admin-role` 分支,commit + push,**不自行合入 cfclub**
+- 完成后 `git status --porcelain` 干净
+
+## 六、风险
+
+- `teacher` 也视为管理员(与前端 isAdmin 一致);若产品仅要家长,需主理人确认,勿擅改
+- `getMemberContributions` 被 `getFamilySummary` 内部调用,改一处两接口受益(`/family/members` + `/family/summary`),核验时注意两者 isAdmin 一致
+- 后端无单测(FamilyEarningsService 无既有测试类),以接口实测为准:admin 角色调 toggle-member 应 200,child 角色应「无权限操作」
+
+## 七、验证数据(供开发/核验参考)
+
+- 构造:家长账号(role=parent,is_family_admin 可能 0)调 `POST /api/earnings/admin/toggle-member` body `{userId:<成员id>, enabled:true}` → 应 200「操作成功」
+- child 账号调同一接口 → 应「无权限操作」
+- 贡献榜 `/family/members` 返回的 isAdmin 应与该用户 role 一致

+ 87 - 0
docs/需求分析/2026-11-07-财富中心贡献榜管理员口径需求变更.md

@@ -0,0 +1,87 @@
+# 财富中心成员贡献榜:管理员身份口径统一 + 池权限可切换 — 需求变更说明(2026-11-07)
+
+> **版本:** v1.0(主理人裁定,以 role 为准)
+> **来源:** 用户 2026-11-07 需求(3 条)
+> **状态:** 现状核查完成 → 需求变更锁定(待入池/派发)
+> **关联台账:** `docs/superpowers/specs/2026-11-07-family-earnings-admin-role.md`(实现任务书)
+
+---
+
+## 一、需求清单(原话)
+
+1. 财富中心成员贡献榜中显示的**管理员与实际身份不一致**。
+2. `earnings/admin/toggle-member` 接口报**无权限操作**,不知道有什么用。
+3. 成员贡献榜每个人都写的**「池关闭」**,不知道有什么用。
+
+---
+
+## 二、现状与根因(cfclub @ b4cc6b791)
+
+### 页面与接口
+- **前端**:`cfc-frontend/pages/promotion/index.vue`(990 行)——家庭公共收益总览 + 成员贡献榜;调 `/api/earnings/*`
+- **后端**:`FamilyEarningsController`(`/api/earnings`)+ `FamilyEarningsService`
+
+### 根因 1 · 管理员身份双口径割裂
+| 处 | 口径 |
+|----|------|
+| 前端 `isAdmin`(promotion/index.vue L228-232) | `role === 'parent' \|\| role === 'teacher'`(**登录角色**) |
+| 前端贡献榜 `m.isAdmin` 标签(L56) | 来自后端 DTO |
+| 后端 `FamilyEarningsService.getMemberContributions` L75 | `user.getIsFamilyAdmin()`(**`is_family_admin` 字段**) |
+| 后端 `FamilyEarningsController.isFamilyAdmin` L131-134 | 仅 `user.getIsFamilyAdmin()` |
+
+→ 一个 `role='parent'` 但 `is_family_admin=0` 的用户:前端显示管理员(有操作按钮),贡献榜标签却不是管理员(或反之)。**`is_family_admin` 只在建家庭/接邀请/转让时设置**(`FamilyInvitationService` L180/260/299/308),普通家长加入已有家庭后默认 0。
+
+### 根因 2 · toggle-member 无权限
+- 前端显示 switch(L66):`isAdmin && !m.isAdmin`(`isAdmin`=role 判定)
+- 后端鉴权(Controller L88):`isFamilyAdmin(adminId)` 只查 `is_family_admin` 字段
+- → 家长角色(前端认为有权限)但 `is_family_admin=0` → 后端拒绝「无权限操作」。**同一人前端看有权限、后端看无权限**。
+
+### 根因 3 · 全员「池关闭」
+- `FamilyEarningsService.getMemberContributions` L94:查不到 `family_earnings_member` 记录时默认 `canUseFamilyBalance=false`(显示「池关闭」)
+- 该记录仅在 `distribute`(L206)/`toggleMemberAccess`(L298)时创建 → 正常家庭从未分配过就全员「池关闭」
+- **「池」语义**:家庭公共收益池(family_earnings 总账),`canUseFamilyBalance` 控制成员能否动用家庭公共余额。默认关闭是安全设计,管理员开启后成员才可用。用户"不知道有什么用"——功能存在但被权限问题挡住无法切换(根因 2),需在权限修复后**可正常切换**并**补一句说明文案**。
+
+### 参考实现(role 口径的正确范例)
+- `ReportMemberPermissionService.isFamilyAdmin`(L61-79):三口径(family.creator_id / users.is_family_admin / family_members.is_admin)——**这是其他模块的正确做法**,财富中心只判了其中一个,故割裂。
+
+---
+
+## 三、需求变更定义(目标态,以 role 为准)
+
+### 变更 F1 · 管理员身份统一为 role 口径(后端与前端对齐)
+- **后端** `FamilyEarningsService.getMemberContributions`(L75):`dto.setIsAdmin(...)` 改为**角色判定** `role ∈ {'parent','teacher'}`(`user.getRole()`),与前端 `isAdmin` computed 完全一致。
+- **后端** `FamilyEarningsController.isFamilyAdmin`(L131-134):同样改为角色判定 `role ∈ {'parent','teacher'}`(读 `User.getRole()`)。
+- 贡献榜「管理员」标签、管理员按钮、switch 显示条件、后端鉴权——**四者统一为 role 口径**。
+- 参考:`User.getRole()` 返回 `String`(ENUM parent/child/teacher,SchemaSyncHandler L71)。**判定函数**:`role != null && ("parent".equals(role) || "teacher".equals(role))`。
+
+### 变更 F2 · 池权限可正常切换 + 语义说明
+- 权限修复后(F1),管理员点击 switch → `toggleMemberAccess` 正常执行(创建/更新 `family_earnings_member.can_use_family_balance`),不再报「无权限操作」。
+- **前端**贡献榜区块(members-card)补一句说明文案:`池可用 = 该成员可使用家庭公共收益余额;池关闭 = 不可用,由管理员开启`(放 members-header 下或 member-list 上方,小字灰色,`$app-text-subtle`)。
+- **不改默认值**:`canUseFamilyBalance=false`(池关闭)是安全默认,保留;修复权限后管理员可正常开启,无"永远关着"问题。
+- **验收口径**:家长角色管理员对贡献榜成员点击 switch 不再报「无权限操作」;切换后「池可用/池关闭」状态正确刷新;贡献榜管理员标签与前端角色一致。
+
+---
+
+## 四、TDD 入池
+
+| 能力键 | 变更 | requirement 溯源 | design 溯源 |
+|--------|------|-----------------|------------|
+| `family.earnings.admin.role` | F1 | 本文档需求 1+2 | `FamilyEarningsController.isFamilyAdmin`(role 判定)+ `FamilyEarningsService.getMemberContributions`(isAdmin 由 role 判定) |
+| `family.earnings.pool.toggle` | F2 | 本文档需求 2+3 | `FamilyEarningsService.toggleMemberAccess`(已合入)+ `promotion/index.vue` switch |
+
+> RED 契约以「已合入 origin/cfclub 的 `FamilyEarningsController` 契约」为准(`/api/earnings/admin/toggle-member` 入参 `{userId, enabled}`)。
+
+---
+
+## 五、风险与注意事项(主理人核实)
+
+1. **`is_family_admin` 字段保留**:仅改为「不再作为财富中心鉴权依据」,字段本身不动(其他模块 `ReportMemberPermissionService` 仍用,勿删)。
+2. **role 判定边界**:`teacher` 也视为管理员(与前端 `isAdmin` 一致)——若产品只想要家长,需另行确认,勿擅自改。
+3. **不改 `ReportMemberPermissionService`**:那是正确的三口径实现,本任务只对齐财富中心的两个判定点。
+4. **默认池关闭保留**:是安全设计,本任务不改为默认开启;只保证「可切换」+「语义可读」。
+
+---
+
+## 六、关联记录
+
+- 现状核查完成;任务书待入 cfclub 后派发 lan-204。