406 lines
15 KiB
Markdown
406 lines
15 KiB
Markdown
:::tip 部门/职级/岗位
|
||
部门、职级、岗位是组织架构管理的三个核心模块。部门采用树形结构,职级和岗位为简单 CRUD 模块,均继承 `BaseService`。用户表通过 `dept_id`、`position_id`、`level_id` 三个外键关联这三个模块。
|
||
:::
|
||
|
||
## 部门管理
|
||
|
||
### 部门模型
|
||
|
||
```python
|
||
# src/modules/system/dept/models.py
|
||
# ============================================================
|
||
# 部门模型
|
||
# ============================================================
|
||
class Dept(BaseModel, BaseDB):
|
||
"""部门模型类"""
|
||
# ============================================================
|
||
# 表名配置
|
||
# ============================================================
|
||
__tablename__ = DB_PREFIX + "dept"
|
||
__table_comment__ = "部门表"
|
||
|
||
# ============================================================
|
||
# 字段定义
|
||
# ============================================================
|
||
# 部门名称
|
||
name = Column(String(150), nullable=False, index=True, comment="部门名称")
|
||
# 部门类型:1-公司 2-子公司 3-部门 4-小组
|
||
type = Column(Integer, default=0, server_default=text('0'), comment="部门类型:1-公司 2-子公司 3-部门 4-小组")
|
||
# 上级部门ID
|
||
parent_id = Column(Integer, default=0, server_default=text('0'), index=True, comment="上级部门ID")
|
||
# 部门排序
|
||
sort = Column(Integer, default=0, server_default=text('0'), comment="部门排序")
|
||
# 备注
|
||
note = Column(String(255), nullable=True, comment="备注")
|
||
|
||
# ============================================================
|
||
# 内置方法
|
||
# ============================================================
|
||
def __str__(self):
|
||
"""返回部门名称作为字符串表示"""
|
||
return '部门{}'.format(self.name)
|
||
```
|
||
|
||
### 树形结构
|
||
|
||
部门通过 `parent_id` 字段形成树形结构,与菜单类似:
|
||
|
||
```
|
||
公司总部 (parent_id=0)
|
||
├── 技术部 (parent_id=1)
|
||
│ ├── 前端组 (parent_id=2)
|
||
│ └── 后端组 (parent_id=2)
|
||
└── 市场部 (parent_id=1)
|
||
```
|
||
|
||
### 删除前校验
|
||
|
||
部门删除时需检查是否存在下级部门或用户引用,避免组织架构悬空:
|
||
|
||
```python
|
||
# src/modules/system/dept/service.py
|
||
from src.common.base_service import BaseService
|
||
from src.core.exceptions import BizError
|
||
from src.modules.system.dept.repository import DeptRepository
|
||
from src.modules.system.user.repository import UserRepository
|
||
|
||
|
||
class DeptService(BaseService):
|
||
"""部门服务类"""
|
||
repository = DeptRepository
|
||
form = DeptForm
|
||
model_name = "部门"
|
||
|
||
@classmethod
|
||
def before_delete(cls, ids):
|
||
"""
|
||
删除前校验:检查下级部门和用户引用
|
||
|
||
Args:
|
||
ids: 待删除的部门ID列表
|
||
|
||
Raises:
|
||
BizError: 存在下级部门或用户引用时抛出
|
||
"""
|
||
for did in ids:
|
||
# 检查是否存在下级部门
|
||
if DeptRepository.count_children(did) > 0:
|
||
raise BizError(f"部门ID {did} 存在下级部门,请先删除下级部门")
|
||
# 检查是否存在用户引用
|
||
if UserRepository.count_by({'dept_id': did}) > 0:
|
||
raise BizError(f"部门ID {did} 已被用户引用,请先调整用户部门")
|
||
```
|
||
|
||
## 职级管理
|
||
|
||
### 职级模型
|
||
|
||
```python
|
||
# src/modules/system/level/models.py
|
||
# ============================================================
|
||
# 职级模型
|
||
# ============================================================
|
||
class Level(BaseModel, BaseDB):
|
||
"""职级模型类"""
|
||
# ============================================================
|
||
# 表名配置
|
||
# ============================================================
|
||
__tablename__ = DB_PREFIX + "level"
|
||
__table_comment__ = "职级表"
|
||
|
||
# ============================================================
|
||
# 字段定义
|
||
# ============================================================
|
||
# 职级名称
|
||
name = Column(String(255), nullable=False, index=True, comment="职级名称")
|
||
# 职级状态:1-在用 2-停用
|
||
status = Column(Integer, default=0, server_default=text('0'), index=True, comment="职级状态:1-在用 2-停用")
|
||
# 职级排序
|
||
sort = Column(Integer, default=0, server_default=text('0'), comment="职级排序")
|
||
|
||
# ============================================================
|
||
# 内置方法
|
||
# ============================================================
|
||
def __repr__(self):
|
||
"""返回职级名称作为字符串表示"""
|
||
return '职级:{}'.format(self.name)
|
||
```
|
||
|
||
### 表单验证
|
||
|
||
```python
|
||
# src/modules/system/level/schema.py
|
||
from wtforms import StringField, IntegerField
|
||
from wtforms.validators import DataRequired, Length, NumberRange, Optional
|
||
from src.common.forms.base_form import BaseForm
|
||
from src.modules.system.level.repository import LevelRepository
|
||
|
||
|
||
class LevelForm(BaseForm):
|
||
"""职级创建/编辑表单"""
|
||
name = StringField('职级名称', validators=[
|
||
DataRequired(message='职级名称不能为空'),
|
||
Length(max=150, message='职级名称长度不得超过150个字符')
|
||
])
|
||
status = IntegerField('职级状态', validators=[
|
||
DataRequired(message='职级状态不能为空'),
|
||
NumberRange(min=1, max=2, message='职级状态值在1~2之间')
|
||
])
|
||
sort = IntegerField('职级排序', validators=[
|
||
Optional(),
|
||
NumberRange(min=0, max=99999, message='职级排序值在0~99999之间')
|
||
])
|
||
|
||
def validate_name(self, field):
|
||
"""职级名称唯一性校验"""
|
||
if not field.data:
|
||
return
|
||
current_id = self.id.data if self.id.data else None
|
||
if LevelRepository.exists_by({'name': field.data}, exclude_id=current_id):
|
||
raise ValidationError("职级名称已存在")
|
||
|
||
|
||
class LevelStatusForm(BaseForm):
|
||
"""职级状态更新表单"""
|
||
id = IntegerField('职级ID', validators=[
|
||
DataRequired(message='职级ID不能为空'),
|
||
NumberRange(min=1, message='职级ID必须大于0')
|
||
])
|
||
status = IntegerField('职级状态', validators=[
|
||
DataRequired(message='职级状态不能为空'),
|
||
NumberRange(min=1, max=2, message='职级状态值在1~2之间')
|
||
])
|
||
```
|
||
|
||
### 业务服务
|
||
|
||
职级模块继承 `BaseService`,声明差异点即可获得完整 CRUD 能力。删除前校验用户引用,支持 Excel 导入导出:
|
||
|
||
```python
|
||
# src/modules/system/level/service.py
|
||
from src.common.base_service import BaseService
|
||
from src.core.exceptions import BizError
|
||
from src.modules.system.level.schema import LevelForm, LevelStatusForm
|
||
from src.modules.system.level.repository import LevelRepository
|
||
from src.modules.system.user.repository import UserRepository
|
||
|
||
|
||
class LevelService(BaseService):
|
||
"""职级服务类,继承 BaseService 获得标准 CRUD 能力"""
|
||
repository = LevelRepository
|
||
form = LevelForm
|
||
status_form = LevelStatusForm
|
||
model_name = "职级"
|
||
|
||
@classmethod
|
||
def before_delete(cls, ids):
|
||
"""删除前校验:存在用户引用该职级时禁止删除"""
|
||
for lid in ids:
|
||
if UserRepository.count_by({'level_id': lid}) > 0:
|
||
raise BizError(f"职级ID {lid} 已被用户引用,无法删除")
|
||
|
||
|
||
# 模块级代理函数
|
||
get_level_page = LevelService.get_page
|
||
get_level_detail = LevelService.get_detail
|
||
add_level = LevelService.add
|
||
update_level = LevelService.update
|
||
delete_levels = LevelService.remove
|
||
update_level_status = LevelService.update_status
|
||
get_level_list = LevelService.get_list
|
||
```
|
||
|
||
## 岗位管理
|
||
|
||
### 岗位模型
|
||
|
||
```python
|
||
# src/modules/system/position/models.py
|
||
# ============================================================
|
||
# 岗位模型
|
||
# ============================================================
|
||
class Position(BaseModel, BaseDB):
|
||
"""岗位模型类"""
|
||
# ============================================================
|
||
# 表名配置
|
||
# ============================================================
|
||
__tablename__ = DB_PREFIX + "position"
|
||
__table_comment__ = "岗位表"
|
||
|
||
# ============================================================
|
||
# 字段定义
|
||
# ============================================================
|
||
# 岗位名称
|
||
name = Column(String(255), nullable=False, index=True, comment="岗位名称")
|
||
# 岗位状态:1-在用 2-停用
|
||
status = Column(Integer, default=0, server_default=text('0'), index=True, comment="岗位状态:1-在用 2-停用")
|
||
# 岗位排序
|
||
sort = Column(Integer, default=0, server_default=text('0'), comment="岗位排序")
|
||
|
||
# ============================================================
|
||
# 内置方法
|
||
# ============================================================
|
||
def __repr__(self):
|
||
"""返回岗位名称作为字符串表示"""
|
||
return '岗位:{}'.format(self.name)
|
||
```
|
||
|
||
### 表单验证
|
||
|
||
```python
|
||
# src/modules/system/position/schema.py
|
||
from wtforms import StringField, IntegerField
|
||
from wtforms.validators import DataRequired, Length, NumberRange, Optional
|
||
from src.common.forms.base_form import BaseForm
|
||
from src.modules.system.position.repository import PositionRepository
|
||
|
||
|
||
class PositionForm(BaseForm):
|
||
"""岗位创建/编辑表单"""
|
||
name = StringField('岗位名称', validators=[
|
||
DataRequired(message='岗位名称不能为空'),
|
||
Length(max=150, message='岗位名称长度不得超过150个字符')
|
||
])
|
||
status = IntegerField('岗位状态', validators=[
|
||
DataRequired(message='岗位状态不能为空'),
|
||
NumberRange(min=1, max=2, message='岗位状态值在1~2之间')
|
||
])
|
||
sort = IntegerField('岗位排序', validators=[
|
||
Optional(),
|
||
NumberRange(min=0, max=99999, message='岗位排序值在0~99999之间')
|
||
])
|
||
|
||
def validate_name(self, field):
|
||
"""岗位名称唯一性校验"""
|
||
if not field.data:
|
||
return
|
||
current_id = self.id.data if self.id.data else None
|
||
if PositionRepository.exists_by({'name': field.data}, exclude_id=current_id):
|
||
raise ValidationError("岗位名称已存在")
|
||
|
||
|
||
class PositionStatusForm(BaseForm):
|
||
"""岗位状态更新表单"""
|
||
id = IntegerField('岗位ID', validators=[
|
||
DataRequired(message='岗位ID不能为空'),
|
||
NumberRange(min=1, message='岗位ID必须大于0')
|
||
])
|
||
status = IntegerField('岗位状态', validators=[
|
||
DataRequired(message='岗位状态不能为空'),
|
||
NumberRange(min=1, max=2, message='岗位状态值在1~2之间')
|
||
])
|
||
```
|
||
|
||
### 数据访问层
|
||
|
||
```python
|
||
# src/modules/system/position/repository.py
|
||
from src.common.base_repository import BaseRepository
|
||
from src.common.constants.common import PAGE_SIZE
|
||
from src.modules.system.position.models import Position
|
||
|
||
|
||
class PositionRepository(BaseRepository):
|
||
"""岗位数据仓储,继承 BaseRepository 获得通用 CRUD 能力"""
|
||
model = Position
|
||
|
||
@classmethod
|
||
def page(cls, params: dict):
|
||
"""分页查询"""
|
||
return cls.find_page(
|
||
page=int(params.get('pageNo', 1)),
|
||
page_size=int(params.get('pageSize', PAGE_SIZE)),
|
||
filters={'status': params.get('status')},
|
||
like_filters={'name': params.get('name')},
|
||
order_by='sort',
|
||
)
|
||
|
||
@classmethod
|
||
def list_enabled(cls, params=None):
|
||
"""查询启用的岗位列表"""
|
||
params = params or {}
|
||
return cls.find_list(
|
||
filters={'status': 1},
|
||
like_filters={'name': params.get('name')},
|
||
order_by='sort',
|
||
)
|
||
```
|
||
|
||
### 业务服务
|
||
|
||
```python
|
||
# src/modules/system/position/service.py
|
||
from src.common.base_service import BaseService
|
||
from src.core.exceptions import BizError
|
||
from src.modules.system.position.schema import PositionForm, PositionStatusForm
|
||
from src.modules.system.position.repository import PositionRepository
|
||
from src.modules.system.user.repository import UserRepository
|
||
|
||
|
||
class PositionService(BaseService):
|
||
"""岗位服务类,继承 BaseService 获得标准 CRUD 能力"""
|
||
repository = PositionRepository
|
||
form = PositionForm
|
||
status_form = PositionStatusForm
|
||
model_name = "岗位"
|
||
|
||
@classmethod
|
||
def before_delete(cls, ids):
|
||
"""删除前校验:存在用户引用该岗位时禁止删除"""
|
||
for pid in ids:
|
||
if UserRepository.count_by({'position_id': pid}) > 0:
|
||
raise BizError(f"岗位ID {pid} 已被用户引用,无法删除")
|
||
|
||
|
||
# 模块级代理函数
|
||
get_position_page = PositionService.get_page
|
||
get_position_detail = PositionService.get_detail
|
||
add_position = PositionService.add
|
||
update_position = PositionService.update
|
||
delete_positions = PositionService.remove
|
||
update_position_status = PositionService.update_status
|
||
get_position_list = PositionService.get_list
|
||
```
|
||
|
||
## 通用 CRUD 接口
|
||
|
||
职级和岗位模块继承 BaseService,自动生成标准接口:
|
||
|
||
| 接口 | 方法 | 权限节点 | 说明 |
|
||
|------|------|---------|------|
|
||
| `/api/v1/level/page` | GET | `sys:level:page` | 职级分页列表 |
|
||
| `/api/v1/level/list` | GET | `sys:level:list` | 职级列表(无分页) |
|
||
| `/api/v1/level/detail/{id}` | GET | `sys:level:detail` | 职级详情 |
|
||
| `/api/v1/level/add` | POST | `sys:level:add` | 新增职级 |
|
||
| `/api/v1/level/update` | PUT | `sys:level:update` | 编辑职级 |
|
||
| `/api/v1/level/delete/{id}` | DELETE | `sys:level:delete` | 删除职级 |
|
||
| `/api/v1/level/batchDelete` | DELETE | `sys:level:batchDelete` | 批量删除 |
|
||
| `/api/v1/level/status` | PUT | `sys:level:status` | 设置状态 |
|
||
| `/api/v1/level/import` | POST | `sys:level:import` | 导入 Excel |
|
||
| `/api/v1/level/export` | GET | `sys:level:export` | 导出 Excel |
|
||
| `/api/v1/position/page` | GET | `sys:position:page` | 岗位分页列表 |
|
||
| `/api/v1/position/list` | GET | `sys:position:list` | 岗位列表(无分页) |
|
||
| `/api/v1/position/detail/{id}` | GET | `sys:position:detail` | 岗位详情 |
|
||
| `/api/v1/position/add` | POST | `sys:position:add` | 新增岗位 |
|
||
| `/api/v1/position/update` | PUT | `sys:position:update` | 编辑岗位 |
|
||
| `/api/v1/position/delete/{id}` | DELETE | `sys:position:delete` | 删除岗位 |
|
||
| `/api/v1/position/batchDelete` | DELETE | `sys:position:batchDelete` | 批量删除 |
|
||
| `/api/v1/position/status` | PUT | `sys:position:status` | 设置状态 |
|
||
|
||
:::tip Excel 导入导出
|
||
职级模块支持 Excel 导入导出(`import_level` / `export_level`),导入时逐行校验并返回成功/失败条数。岗位模块结构相同,可按需添加导入导出功能。
|
||
:::
|
||
|
||
## 总结
|
||
|
||
组织架构模块具备以下特点:
|
||
|
||
```
|
||
1. 部门树形:parent_id 形成多级部门结构,删除前校验下级部门和用户引用
|
||
2. 职级/岗位:简单 CRUD,继承 BaseService,声明差异点即可
|
||
3. 表单验证:WTForms Field 约束(DataRequired/Length/NumberRange)
|
||
4. 数据仓库:BaseRepository 提供通用 CRUD 能力,模块级代理函数
|
||
5. 用户关联:用户表通过 dept_id、position_id、level_id 关联
|
||
6. 删除保护:before_delete 钩子防止引用悬空
|
||
```
|