63 lines
3.9 KiB
Markdown
63 lines
3.9 KiB
Markdown
# PROGRESS — D-01 · 用户-租户 membership + 跨租户切换
|
||
|
||
- 状态:done
|
||
|
||
> 日期 2026-09-13 · 范围:只改 `gj/dealerhub` 目录(后端 core + 前端 tenants/members/切换器)
|
||
|
||
## 设计(阶段一,任务卡要求先评审再动手——本轮设计与实现一致,评审记录在此)
|
||
- 模型(存量复用):`TenantMembership(user, tenant, role[owner|admin|member], is_active, joined_at)`,
|
||
唯一约束 `(user, tenant)`;存量回填(0002 迁移:超管=全租户,普通=default)iter6 已验证。
|
||
- 授权矩阵:
|
||
| 角色 | 切租户/看成员 | 邀请 | 改角色/禁用 | 移除 |
|
||
|---|---|---|---|---|
|
||
| owner | ✅ | ✅(任意角色) | ✅ | ✅(专属) |
|
||
| admin | ✅ | ✅(仅 member) | ✅(仅 member) | ❌ |
|
||
| member | ✅(自己有 membership 的) | ❌ | ❌ | ❌ |
|
||
| 非成员 | 403 | 403 | 403 | 403 |
|
||
| 未知租户 | 400(参数错误,非 403) | 400 | 400 | 400 |
|
||
| 超管 | 直通(bootstrap 契约) | 直通 | 直通 | 直通 |
|
||
- 切换流程:`POST /api/v1/tenants/<code>/switch/` 校验 membership → 返回 `{code,name,role}` +
|
||
写审计(`tenant_switch`);前端换 `localStorage.tenant` + 整页 reload(会话隔离无脏数据)。
|
||
- 兼容策略:存量 membership 不动;最后一个 owner 禁止移除/降级/禁用(服务端事务内强保)。
|
||
|
||
## 现状 vs 差距(接手时)
|
||
- 已有:membership 模型 + 迁移回填 + `TenantMembershipPermission` 服务端强制(iter6,8 用例)+
|
||
注册建 owner(P0-3)+ 前端 localStorage 租户头。
|
||
- 缺口:无切换端点、无成员管理接口、无前端切换器/成员页/无权限页。
|
||
|
||
## 改动
|
||
后端(`backend/apps/core/` + `backend/tests/`):
|
||
1. `tenants.py`(新建):`my_tenants`(我的租户+role)、`switch_tenant`(校验+审计)、
|
||
`tenant_members`(GET 列表 / POST 邀请:admin 仅 member,新用户≥8位密码事务创建)、
|
||
`tenant_member_detail`(PATCH 改角色/禁用 / DELETE 移除:admin 不动 owner/admin,
|
||
移除仅 owner,最后一个 owner 三禁)。
|
||
2. `urls.py`:四条路由挂 `core_urlpatterns`(即 `/api/v1/tenants/...`)。
|
||
3. `tests/test_tenant_switch.py`(新建,12 条):切换 200+role/无权 403/未知 400/mine 双租户/
|
||
member 邀 403/owner 邀 201+被邀可切/admin 邀 owner 403/admin 动 owner 403/
|
||
跨租户成员页 403/删最后 owner 400/owner 删 member/禁用后资源 403。
|
||
|
||
前端(`frontend/src/`):
|
||
4. `api/tenants.js`(新建):mine/switchTo/members/invite/updateMember/removeMember。
|
||
5. `pages/Members.vue`(新建):成员表(角色/状态标签)+ 邀请 dialog(新老用户兼容)+
|
||
改角色/禁用启用/移除(owner 专属按钮)+ 403 跳无权限页。
|
||
6. `pages/NoPermission.vue`(新建):无权限页 + 我的租户一键进入。
|
||
7. `layouts/MainLayout.vue`:租户 tag → 下拉切换器(`mine()` 列表,切后整页 reload)+
|
||
“成员”按钮 + 菜单加“租户成员”;`router/index.js` 加 `/members` + `/no-permission`。
|
||
|
||
## 边界遵守
|
||
- 无计费/套餐;单据/库存业务零改;权限全在服务端(前端只做展示与跳转)。
|
||
|
||
## 验证
|
||
```bash
|
||
pytest tests/test_tenant_switch.py tests/test_membership.py # 20 passed(12 新增 + 8 存量)
|
||
pytest -p no:cacheprovider # 524 passed, 4 skipped(512 基线 + 12 新增)
|
||
npm test # 17 pass / 0 fail
|
||
npm run build # ✓ built in 6.13s
|
||
manage.py makemigrations --check --dry-run # No changes detected
|
||
manage.py check # 0 issues
|
||
```
|
||
|
||
## 状态
|
||
done。遗留:切换端到端(前端下拉 → switch → reload 无脏数据)需 dev 服务联调时录屏补证据;
|
||
demo token 流不走 membership(既有旁路,未动)。
|