Files
nexus/Project/fonrey/TECH_STACK/组织人事技术方案.md
2026-04-30 20:33:51 +08:00

360 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
# Fonrey 组织人事技术方案
**版本**: 1.0
**项目**: Fonrey 房产经纪管理系统
**技术栈**: Django 4.x + HTMX + Alpine.js + PostgreSQL 16 + Redis + Celery
**关联 PRD**: `PRD/组织人事管理/组织人事管理模块PRD.md`v1.2
**关联数据模型**: `DATA_MODEL/DATA_MODEL_ORG.md`(本方案不重复 DDL
**关联契约规范**: `TECH_STACK/API_CONTRACT.md`(全局 API 契约权威)
**关联枚举字典**: `DATA_MODEL/ENUMS.md`
**最后更新**: 2026-04-27
---
## 变更历史
| 日期 | 变更人 | 变更内容 |
|---|---|---|
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
## 一、文档定位与边界
本文件只定义组织人事模块的:
1. 服务边界与模块协作
2. API 端点设计(重点)
3. HTMX 局刷协议
4. 权限接入、异动审计、缓存与性能约束
5. 测试与验收映射
> **不在本文件展开**`org_units/staff/staff_transfer_logs/staff_reward_punish/staff_accounts` 结构细节。以 `DATA_MODEL_ORG.md` 为唯一权威。
---
## 二、范围定义(以 TASK.md 为准)
### 2.1 P0 必须覆盖
- US-ORG-001管理员维护公司组织结构部门/门店树)
- US-ORG-002管理员查看与维护员工列表
- US-ORG-003管理员办理员工入职并创建系统账号
### 2.2 P1/P2 预留
- US-ORG-010员工离职与调动
- US-ORG-011员工通讯录
- US-ORG-012员工职务管理
- US-ORG-020全局异动记录
- US-ORG-021员工奖惩记录
- US-ORG-022门店分布地图
---
## 三、模块架构边界
## 3.1 模块职责(`apps/org`
- 管理组织树(公司→事业部→大区→区域→片区→门店→店组→职能)
- 管理员工主档(任职、联系方式、账号、状态)
- 管理入职与账号开通主流程
- 维护人事异动审计链路(入职/调动/离职/复职等)
## 3.2 外部依赖
| 依赖模块 | 依赖内容 | 依赖方式 |
|---|---|---|
| `apps/account` | 新员工账号创建、初始密码策略、登录状态联动 | Service 调用 |
| `apps/permission` | 组织/员工管理权限校验 | PermissionChecker |
| `apps/property` | 员工离职/调动时业务归属迁移(预留) | 异步任务 |
| `apps/client` | 客源归属迁移(预留) | 异步任务 |
| `core/encryption.py` | 手机号/证件号加密存储与脱敏 | 统一工具 |
| `core/cache.py` | 组织树缓存、员工选择器缓存 | Redis |
| `Celery` | 批量导出、跨模块归属迁移、异动统计重算 | 异步任务 |
## 3.3 分层约束
- `views.py` 仅做参数校验、权限门禁、响应组装
- 组织层级规则(店组必须挂门店、经纪人归属限制)必须在 `services/` 强校验
- 员工状态变更必须联动写 `staff_transfer_logs`(不可删除)
- 与账号相关操作通过 `AccountService`,禁止跨模块直写登录表
---
## 四、API 设计原则
1. 页面路由与数据 API 分离:
- 页面:`/org/...`
- 数据:`/api/org/...`
2. 组织树、员工列表、异动记录采用 HTMX 局刷,减少全页刷新。
3. 手机号/证件号默认脱敏展示,明文查看须权限与审计。
4. 账号状态(启用/冻结)与员工状态变更必须一致性联动。
5. 所有异动事件统一错误协议与日志结构,便于审计与报表。
---
## 五、端点清单(核心)
## 5.1 页面路由SSR + HTMX 容器)
| 路径 | 方法 | 鉴权 | 说明 |
|---|---|---|---|
| `/org/structure/` | GET | 是 | 组织结构主页面(组织树 + 员工列表) |
| `/org/departments/create/` | GET | 是 | 新增部门页面 |
| `/org/departments/{org_id}/` | GET | 是 | 部门详情 |
| `/org/departments/{org_id}/edit/` | GET | 是 | 编辑部门 |
| `/org/staff/{staff_id}/` | GET | 是 | 员工详情页(多 Tab |
| `/org/transfer-logs/` | GET | 是 | 全局异动记录页(预留) |
## 5.2 HTMX 片段端点
| 路径 | 方法 | 用途 | 返回 |
|---|---|---|---|
| `/org/fragments/org-tree/` | GET | 左侧组织树局刷 | HTML |
| `/org/fragments/staff-table/` | POST | 员工列表筛选/分页局刷 | HTML |
| `/org/staff/{id}/fragments/tab/{tab}/` | GET | 员工详情 Tab 懒加载 | HTML |
| `/org/fragments/department-staff-selector/` | GET | 选择器弹层局刷 | HTML |
> fragment 端点必须校验 `HX-Request=true`,否则 400。
## 5.3 JSON APIP0
| 端点 | 方法 | 权限 code建议 | 说明 |
|---|---|---|---|
| `/api/org/departments/tree/` | GET | `hr.org.view.scope` | 组织树加载US-001 |
| `/api/org/departments/` | POST | `hr.org.edit.allow` | 新增部门US-001 |
| `/api/org/departments/{id}/` | PATCH | `hr.org.edit.allow` | 编辑部门US-001 |
| `/api/org/departments/{id}/close/` | POST | `hr.org.edit.allow` | 关闭部门 |
| `/api/org/staff/query/` | POST | `hr.staff.view.scope` | 员工列表查询US-002 |
| `/api/org/staff/{id}/detail/` | GET | `hr.staff.view.scope` | 员工详情US-002 |
| `/api/org/staff/onboard/` | POST | `hr.staff.create.allow` | 办理入职+建账号US-003 |
| `/api/org/staff/{id}/base-info/` | PATCH | `hr.staff.edit.allow` | 编辑员工基础信息US-002 |
| `/api/org/staff/{id}/account/freeze/` | POST | `hr.staff.account.manage.allow` | 冻结账号(预留) |
| `/api/org/staff/{id}/account/unfreeze/` | POST | `hr.staff.account.manage.allow` | 解冻账号(预留) |
| `/api/org/staff/{id}/phone/view/` | POST | `hr.staff.phone.view.allow` | 查看完整手机号(审计) |
---
## 六、关键 API 规范(请求/响应)
## 6.1 新增部门
`POST /api/org/departments/`
```json
{
"name": "都市港湾店二组",
"parent_id": "uuid",
"type": "group",
"address_city": "上海",
"address_district": "闵行区",
"address_detail": "XX路XX号",
"manager_id": "uuid"
}
```
规则:
- `type=group` 时父节点必须为 `store`
- 违反层级约束返回 400
## 6.2 员工列表查询
`POST /api/org/staff/query/`
```json
{
"keyword": "张三",
"filters": {
"org_unit_ids": ["uuid"],
"status": ["active", "probation"],
"job_category": ["置业顾问"]
},
"sort": {"field": "updated_at", "order": "desc"},
"pagination": {"mode": "keyset", "cursor": "opaque_cursor", "limit": 20}
}
```
## 6.3 办理入职并创建账号
`POST /api/org/staff/onboard/`
```json
{
"name": "李雷",
"phone": "13800000000",
"org_unit_id": "uuid",
"job_title": "高级业务员",
"job_category": "置业顾问",
"role_codes": ["agent"],
"supervisor_id": "uuid"
}
```
成功 `201`
```json
{
"staff_id": "uuid",
"user_id": 12345,
"initial_password_sent": true,
"message": "入职办理成功"
}
```
联动要求:
- 创建 `staff`
- 创建/绑定 `auth_user`
- 写入一条 `staff_transfer_logs(transfer_type='onboard')`
## 6.4 查看员工手机号(审计)
`POST /api/org/staff/{id}/phone/view/`
```json
{
"reason": "入职资料核对"
}
```
规则:
- 必须有 `hr.staff.phone.view.allow`
- 返回明文仅本次有效
- 必须写入审计日志(操作者、时间、原因)
---
## 七、HTMX 交互约定
## 7.1 Header 约定
- 请求:`HX-Request: true`
- 成功:`HX-Trigger: {"toast":{"level":"success","message":"操作成功"}}`
- 失败:`HX-Trigger: {"toast":{"level":"error","message":"操作失败"}}`
- 跳转:`HX-Redirect: /org/staff/{id}/`
## 7.2 模板分片命名
| 场景 | 模板 |
|---|---|
| 组织树 | `templates/org/fragments/org_tree.html` |
| 员工列表 | `templates/org/fragments/staff_table.html` |
| 员工详情-异动记录 | `templates/org/fragments/staff_transfer_logs.html` |
| 部门人员选择器 | `templates/org/fragments/department_staff_selector.html` |
---
## 八、权限与数据范围
## 8.1 最小权限矩阵P0 建议)
| 能力 | 权限 code |
|---|---|
| 查看组织结构范围 | `hr.org.view.scope` |
| 编辑组织结构 | `hr.org.edit.allow` |
| 查看员工列表范围 | `hr.staff.view.scope` |
| 新建员工 | `hr.staff.create.allow` |
| 编辑员工 | `hr.staff.edit.allow` |
| 账号冻结/恢复 | `hr.staff.account.manage.allow` |
| 查看员工手机号 | `hr.staff.phone.view.allow` |
## 8.2 数据范围叠加
最终可见数据 = `scope 过滤``员工状态过滤``组织层级过滤`
- 系统管理员可走 `is_system_admin` 短路
- 普通管理员按 scope 限制组织子树
---
## 九、异步任务与缓存策略
## 9.1 Celery 任务
| 任务 | 触发时机 | 说明 |
|---|---|---|
| `org_staff_export_task` | 导出员工列表 | 异步导出 Excel |
| `org_transfer_affiliation_task` | 离职/调动后(预留) | 迁移房源/客源归属 |
| `org_transfer_summary_task` | 每日定时 | 异动统计重算 |
## 9.2 Redis Key 规范
| Key | TTL | 说明 |
|---|---|---|
| `{schema}:org:tree:{staff_id}` | 300s | 组织树缓存 |
| `{schema}:org:staff:list:{hash}` | 60s | 员工筛选缓存 |
| `{schema}:org:staff:detail:{staff_id}` | 120s | 员工详情聚合缓存 |
| `{schema}:org:selector:staff:{org_unit_id}` | 300s | 选择器缓存 |
写操作成功后必须主动失效相关 key。
---
## 十、性能与可靠性约束
1. 组织树查询基于 `path` 前缀与索引,避免递归 N+1。
2. 员工列表采用 Keyset 分页,避免深分页性能退化。
3. 员工详情按 Tab 懒加载,避免一次加载全量档案。
4. 批量操作(导入/导出/调动)需限流与幂等控制。
---
## 十一、安全与合规
1. 手机号与证件号全程加密存储,前端默认脱敏。
2. 以下动作必须写 `staff_transfer_logs`:入职、调动、离职、复职、上级变更、冻结/解冻。
3. `staff.status='resigned'``staff.status='frozen'` 时,`auth_user.is_active=False`
4. 恢复在职后才可重新启用账号。
---
## 十二、错误码建议
| code | HTTP | 场景 |
|---|---|---|
| `ORG_UNIT_NOT_FOUND` | 404 | 部门不存在或无权限 |
| `ORG_INVALID_HIERARCHY` | 400 | 组织层级违反规则(如店组不挂门店) |
| `STAFF_NOT_FOUND` | 404 | 员工不存在或无权限 |
| `STAFF_PHONE_DUPLICATED` | 409 | 手机号冲突 |
| `STAFF_ACCOUNT_CREATE_FAILED` | 500 | 账号创建失败 |
| `ORG_PERMISSION_DENIED` | 403 | 权限不足 |
---
## 十三、测试映射
### 13.1 P0 User Story 映射
| User Story | 最低覆盖 |
|---|---|
| US-ORG-001 | 组织树查询、新增/编辑部门、层级约束校验 |
| US-ORG-002 | 员工列表筛选、详情加载、权限过滤 |
| US-ORG-003 | 入职建档+账号创建、必填校验、异动日志写入 |
测试文件:`tests/integration/org/test_us_org.py`
### 13.2 强制测试约束
- 集成测试使用 `TenantClient`
- HTMX 请求携带 `HTTP_HX_REQUEST=true`
- 权限三态200 / 403 / 302
- 外部服务(短信/邮件/Redis/Celery全部 mock
---
## 十四、落地顺序建议
1. 先实现组织树与员工列表查询US-ORG-001/002
2. 再实现入职流程与账号开通US-ORG-003
3. 补齐审计与缓存失效机制
4. 再推进离职/调动、通讯录、奖惩等 P1/P2 能力
---
## 十五、文档同步规则
- 枚举变更:同步 `DATA_MODEL/ENUMS.md`
- 权限 code 变更:同步 `DATA_MODEL/DATA_MODEL_PERMISSION.md`
- 数据结构变更:同步 `DATA_MODEL/DATA_MODEL_ORG.md`
- API 变更:同步本文件与 `PRD/TASK.md` 对应条目