> **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 API(P0) | 端点 | 方法 | 权限 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` 对应条目