chore: sync local project changes
This commit is contained in:
@@ -465,7 +465,127 @@
|
||||
|
||||
---
|
||||
|
||||
## 6. 上线计划
|
||||
## 6. 技术考量
|
||||
|
||||
### 6.1 依赖关系
|
||||
|
||||
| 系统/模块 | 依赖原因 | 优先级风险 |
|
||||
|-----------|---------|-----------|
|
||||
| 登录管理模块 | 员工账号的创建、冻结、密码重置由登录模块提供接口 | 高 |
|
||||
| 权限管理模块 | 员工的角色/权限分配在人事模块发起,权限模块执行 | 高 |
|
||||
| 系统配置模块 | 职务类别、部门级别、奖惩类型等枚举值从系统配置读取 | 中 |
|
||||
|
||||
### 6.2 核心技术设计
|
||||
|
||||
#### 6.2.1 组织树存储方案
|
||||
|
||||
采用 **Closure Table(闭包表)** 存储多层级部门树,最大支持 8 层嵌套:
|
||||
|
||||
| 方案 | 优点 | 缺点 | 选用理由 |
|
||||
|------|------|------|---------|
|
||||
| Adjacency List | 写入简单 | 递归查询慢,需多次 SQL | ❌ 不适合 8 层嵌套 |
|
||||
| Nested Set | 读取极快 | 写入时需重排序,高并发写冲突大 | ❌ 人事模块写入频繁 |
|
||||
| **Closure Table** | 读写均衡,查询任意层级 O(1) | 存储量略多 | ✅ **选用** |
|
||||
|
||||
**数据结构**:
|
||||
```python
|
||||
class OrgUnit(TenantModel): # 部门节点
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=100)
|
||||
level = CharField(choices=['事业部','大区','区域','片区','门店','店组','职能'])
|
||||
parent = ForeignKey('self', null=True, on_delete=PROTECT) # 直接父节点
|
||||
is_direct = BooleanField(default=True) # True=直营, False=加盟
|
||||
deleted_at = DateTimeField(null=True)
|
||||
|
||||
class OrgUnitClosure(TenantModel): # 闭包表
|
||||
ancestor = ForeignKey(OrgUnit, related_name='descendants')
|
||||
descendant = ForeignKey(OrgUnit, related_name='ancestors')
|
||||
depth = IntegerField() # 0=自身, 1=子, 2=孙...
|
||||
```
|
||||
|
||||
**查询示例**:
|
||||
- 查某节点所有子孙:`WHERE ancestor_id = X AND depth > 0`
|
||||
- 查某节点完整路径:`WHERE descendant_id = X ORDER BY depth DESC`
|
||||
- 移动节点:删除旧闭包记录 + 插入新闭包记录(事务内执行)
|
||||
|
||||
#### 6.2.2 员工状态机
|
||||
|
||||
员工在系统内的状态须通过后端状态机严格控制:
|
||||
|
||||
```
|
||||
[录入] ──► 试用期 ──► 正式 ──► 离职
|
||||
│ │
|
||||
└──────────────┘
|
||||
复职
|
||||
│
|
||||
▼
|
||||
试用期 / 正式(恢复上次状态)
|
||||
```
|
||||
|
||||
| 状态 | 允许流转目标 | 触发方式 |
|
||||
|------|------------|---------|
|
||||
| 试用期 | 正式、离职 | 管理员手动操作 |
|
||||
| 正式 | 离职 | 管理员手动操作 |
|
||||
| 离职 | 复职 | 管理员手动操作 |
|
||||
| 冻结 | 启用(需先恢复员工状态) | 账号冻结操作 |
|
||||
|
||||
**实现要求**:
|
||||
- 每次状态变更写入 `StaffChangeLog`(异动记录表),字段包含:变更人、变更类型、变更前后值、变更时间、备注
|
||||
- 员工离职时,系统自动:① 冻结关联登录账号 ② 解除权限角色 ③ 归属客源触发掉公检查
|
||||
- 员工状态变更失败返回明确错误码,前端展示友好提示
|
||||
|
||||
#### 6.2.3 异动记录审计策略
|
||||
|
||||
异动记录为**只追加(append-only)**设计,不支持修改或删除:
|
||||
|
||||
| 异动类型枚举 | 触发场景 |
|
||||
|------------|---------|
|
||||
| 入职 | 员工首次录入系统 |
|
||||
| 转正 | 试用期转为正式员工 |
|
||||
| 调岗 | 员工所属部门变更 |
|
||||
| 上级变动 | 直接上级变更 |
|
||||
| 离职 | 办理离职手续 |
|
||||
| 复职 | 离职员工重新入职 |
|
||||
| 账号冻结 | 账号被冻结 |
|
||||
| 账号启用 | 账号从冻结恢复 |
|
||||
|
||||
```python
|
||||
class StaffChangeLog(TenantModel):
|
||||
staff = ForeignKey(Staff)
|
||||
change_type = CharField(choices=[...]) # 见枚举表
|
||||
before_value = JSONField(null=True) # 变更前状态快照
|
||||
after_value = JSONField(null=True) # 变更后状态快照
|
||||
operator = ForeignKey(User)
|
||||
remark = TextField(blank=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
# 无 updated_at / deleted_at — append-only
|
||||
```
|
||||
|
||||
#### 6.2.4 敏感数据脱敏
|
||||
|
||||
| 数据 | 存储方式 | 展示方式 | 查看权限 |
|
||||
|------|---------|---------|---------|
|
||||
| 手机号 | AES-256-GCM 加密 + SHA-256 哈希索引 | `159****9696` | 需「查看员工手机号」权限,留操作日志 |
|
||||
| 证件号码 | AES-256-GCM 加密 | `410***********3037` | 需「查看员工证件」权限,留操作日志 |
|
||||
| 通讯录号码 | AES-256-GCM 加密 | 脱敏显示 | 点击「查看号码」后临时展示,留痕 |
|
||||
|
||||
### 6.3 已知风险
|
||||
|
||||
| 风险 | 可能性 | 影响 | 缓解措施 |
|
||||
|------|--------|------|---------|
|
||||
| 大型机构部门树深度超过 8 层 | 低 | 中 | 前端录入时限制最大深度,超出提示联系运营调整 |
|
||||
| 员工大批量导入时账号创建并发冲突 | 中 | 中 | 批量导入走 Celery 异步任务,序列化账号创建 |
|
||||
| 组织调整时历史客源/房源归属关系错乱 | 中 | 高 | 员工调岗不改变现有资源归属,仅更新组织关系;业务重新分配由管理员手动操作 |
|
||||
|
||||
### 6.4 待确认问题
|
||||
|
||||
- [ ] 员工工号规则:系统自动生成还是管理员手动填写?格式约定是什么? — 待业务确认 — 开发前必须明确
|
||||
- [ ] 「复职」时是否复用原有账号?还是重新创建新账号? — 待产品/法务确认
|
||||
- [ ] 部门移动操作(将某节点及其子树挂到新父节点)是否在 MVP 范围内? — 待产品确认
|
||||
|
||||
---
|
||||
|
||||
## 7. 上线计划
|
||||
|
||||
| 阶段 | 时间 | 受众 | 验收门槛 |
|
||||
|------|------|------|----------|
|
||||
|
||||
Reference in New Issue
Block a user