462 lines
14 KiB
Python
462 lines
14 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Post Service - 岗位服务层
|
|
"""
|
|
from io import BytesIO
|
|
from typing import Tuple, Dict, Any, Optional, List
|
|
|
|
from sqlalchemy import select, func, or_
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.base_service import BaseService
|
|
from core.post.model import Post
|
|
from core.post.schema import PostCreate, PostUpdate
|
|
|
|
|
|
class PostService(BaseService[Post, PostCreate, PostUpdate]):
|
|
"""
|
|
岗位服务层
|
|
继承BaseService,自动获得增删改查功能
|
|
"""
|
|
|
|
model = Post
|
|
|
|
# Excel导入导出配置
|
|
excel_columns = {
|
|
"name": "岗位名称",
|
|
"code": "岗位编码",
|
|
"post_type": "岗位类型",
|
|
"post_level": "岗位级别",
|
|
"status": "状态",
|
|
"description": "描述",
|
|
}
|
|
excel_sheet_name = "岗位列表"
|
|
|
|
@classmethod
|
|
def _export_converter(cls, item: Any) -> Dict[str, Any]:
|
|
"""导出数据转换器"""
|
|
return {
|
|
"name": item.name,
|
|
"code": item.code,
|
|
"post_type": item.get_post_type_display(),
|
|
"post_level": item.get_post_level_display(),
|
|
"status": "启用" if item.status else "禁用",
|
|
"description": item.description or "",
|
|
}
|
|
|
|
@classmethod
|
|
def _import_processor(cls, row: Dict[str, Any]) -> Optional[Post]:
|
|
"""导入数据处理器"""
|
|
name = row.get("name")
|
|
code = row.get("code")
|
|
if not name or not code:
|
|
return None
|
|
|
|
# 岗位类型映射
|
|
type_map = {"管理岗": 0, "技术岗": 1, "业务岗": 2, "职能岗": 3, "其他": 4}
|
|
post_type_str = row.get("post_type", "其他")
|
|
post_type = type_map.get(post_type_str, 4)
|
|
|
|
# 岗位级别映射
|
|
level_map = {"高层": 0, "中层": 1, "基层": 2, "一般员工": 3}
|
|
post_level_str = row.get("post_level", "一般员工")
|
|
post_level = level_map.get(post_level_str, 3)
|
|
|
|
status_str = row.get("status", "启用")
|
|
status = status_str in ("启用", "true", "True", "1", True)
|
|
|
|
return Post(
|
|
name=str(name),
|
|
code=str(code),
|
|
post_type=post_type,
|
|
post_level=post_level,
|
|
status=status,
|
|
description=str(row.get("description") or "") or None,
|
|
)
|
|
|
|
@classmethod
|
|
async def export_to_excel(
|
|
cls,
|
|
db: AsyncSession,
|
|
data_converter: Any = None
|
|
) -> BytesIO:
|
|
"""导出到Excel"""
|
|
return await super().export_to_excel(db, cls._export_converter)
|
|
|
|
@classmethod
|
|
async def import_from_excel(
|
|
cls,
|
|
db: AsyncSession,
|
|
file_content: bytes,
|
|
row_processor: Any = None
|
|
) -> Tuple[int, int]:
|
|
"""从Excel导入"""
|
|
return await super().import_from_excel(db, file_content, cls._import_processor)
|
|
|
|
@classmethod
|
|
async def get_user_count(cls, db: AsyncSession, post_id: str) -> int:
|
|
"""获取岗位下的用户数量"""
|
|
from core.user.model import User
|
|
result = await db.execute(
|
|
select(func.count(User.id)).where(
|
|
User.post_id == post_id,
|
|
User.is_deleted == False # noqa: E712
|
|
)
|
|
)
|
|
return result.scalar() or 0
|
|
|
|
@classmethod
|
|
async def can_delete(cls, db: AsyncSession, post_id: str) -> Tuple[bool, str]:
|
|
"""
|
|
检查岗位是否可以删除
|
|
|
|
:return: (是否可删除, 原因)
|
|
"""
|
|
user_count = await cls.get_user_count(db, post_id)
|
|
if user_count > 0:
|
|
return False, f"该岗位下还有 {user_count} 个用户,无法删除"
|
|
return True, ""
|
|
|
|
@classmethod
|
|
async def batch_update_status(
|
|
cls,
|
|
db: AsyncSession,
|
|
ids: List[str],
|
|
status: bool
|
|
) -> int:
|
|
"""
|
|
批量更新岗位状态
|
|
|
|
:return: 更新的记录数
|
|
"""
|
|
count = 0
|
|
for post_id in ids:
|
|
post = await cls.get_by_id(db, post_id)
|
|
if post:
|
|
post.status = status
|
|
count += 1
|
|
|
|
if count > 0:
|
|
await db.commit()
|
|
|
|
return count
|
|
|
|
@classmethod
|
|
async def batch_delete(
|
|
cls,
|
|
db: AsyncSession,
|
|
ids: List[str],
|
|
hard: bool = False
|
|
) -> Tuple[int, List[str]]:
|
|
"""
|
|
批量删除岗位
|
|
|
|
:return: (删除成功数, 删除失败的ID列表)
|
|
"""
|
|
success_count = 0
|
|
failed_ids = []
|
|
|
|
for post_id in ids:
|
|
can_del, reason = await cls.can_delete(db, post_id)
|
|
if can_del:
|
|
if await cls.delete(db, post_id, hard=hard):
|
|
success_count += 1
|
|
else:
|
|
failed_ids.append(post_id)
|
|
else:
|
|
failed_ids.append(post_id)
|
|
|
|
return success_count, failed_ids
|
|
|
|
@classmethod
|
|
async def search(
|
|
cls,
|
|
db: AsyncSession,
|
|
keyword: str,
|
|
page: int = 1,
|
|
page_size: int = 20
|
|
) -> Tuple[List[Post], int]:
|
|
"""
|
|
搜索岗位(模糊匹配名称、编码、描述)
|
|
"""
|
|
if not keyword:
|
|
return [], 0
|
|
|
|
# 构建搜索条件
|
|
search_filter = or_(
|
|
Post.name.ilike(f"%{keyword}%"),
|
|
Post.code.ilike(f"%{keyword}%"),
|
|
Post.description.ilike(f"%{keyword}%")
|
|
)
|
|
|
|
# 查询总数
|
|
count_result = await db.execute(
|
|
select(func.count(Post.id)).where(
|
|
search_filter,
|
|
Post.is_deleted == False # noqa: E712
|
|
)
|
|
)
|
|
total = count_result.scalar() or 0
|
|
|
|
# 查询数据
|
|
result = await db.execute(
|
|
select(Post).where(
|
|
search_filter,
|
|
Post.is_deleted == False # noqa: E712
|
|
)
|
|
.order_by(Post.sys_create_datetime.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
)
|
|
items = list(result.scalars().all())
|
|
|
|
return items, total
|
|
|
|
@classmethod
|
|
async def search_with_data_scope(
|
|
cls,
|
|
db: AsyncSession,
|
|
keyword: str,
|
|
page: int,
|
|
page_size: int
|
|
) -> Tuple[List[Post], int]:
|
|
"""
|
|
搜索岗位(模糊匹配名称、编码、描述,自动应用数据权限过滤)
|
|
|
|
自动从上下文获取当前用户信息和请求信息,无需手动传递任何参数
|
|
"""
|
|
if not keyword:
|
|
return [], 0
|
|
|
|
from utils.permission import apply_data_scope_filter
|
|
from utils.context import get_current_user_info_from_context
|
|
|
|
# 从上下文获取用户信息和请求信息
|
|
user_info = get_current_user_info_from_context()
|
|
if not user_info:
|
|
return [], 0
|
|
|
|
# 获取数据权限过滤条件
|
|
data_scope_filter = await apply_data_scope_filter(
|
|
db=db,
|
|
role_id=user_info.get("role_id"),
|
|
is_superuser=user_info.get("is_superuser", False),
|
|
user_id=user_info.get("user_id"),
|
|
user_dept_id=user_info.get("dept_id"),
|
|
request_path=user_info.get("request_path"),
|
|
http_method=user_info.get("http_method")
|
|
)
|
|
|
|
# 构建搜索条件
|
|
search_filter = or_(
|
|
Post.name.ilike(f"%{keyword}%"),
|
|
Post.code.ilike(f"%{keyword}%"),
|
|
Post.description.ilike(f"%{keyword}%")
|
|
)
|
|
|
|
# 构建基础查询
|
|
base_query = select(Post).where(
|
|
search_filter,
|
|
Post.is_deleted == False # noqa: E712
|
|
)
|
|
|
|
# 应用数据权限过滤
|
|
base_query = cls._apply_data_scope_to_query(
|
|
query=base_query,
|
|
data_scope_filter=data_scope_filter
|
|
)
|
|
|
|
# 查询总数
|
|
count_result = await db.execute(
|
|
select(func.count()).select_from(base_query.subquery())
|
|
)
|
|
total = count_result.scalar() or 0
|
|
|
|
# 查询数据
|
|
result = await db.execute(
|
|
base_query.order_by(Post.sys_create_datetime.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
)
|
|
items = list(result.scalars().all())
|
|
|
|
return items, total
|
|
|
|
@classmethod
|
|
async def get_stats(cls, db: AsyncSession) -> Dict[str, Any]:
|
|
"""获取岗位统计信息"""
|
|
# 总数
|
|
total_result = await db.execute(
|
|
select(func.count(Post.id)).where(Post.is_deleted == False) # noqa: E712
|
|
)
|
|
total_count = total_result.scalar() or 0
|
|
|
|
# 启用数
|
|
active_result = await db.execute(
|
|
select(func.count(Post.id)).where(
|
|
Post.status == True, # noqa: E712
|
|
Post.is_deleted == False # noqa: E712
|
|
)
|
|
)
|
|
active_count = active_result.scalar() or 0
|
|
|
|
# 按类型统计
|
|
type_stats = {}
|
|
for type_code, type_name in Post.POST_TYPE_CHOICES.items():
|
|
count_result = await db.execute(
|
|
select(func.count(Post.id)).where(
|
|
Post.post_type == type_code,
|
|
Post.is_deleted == False # noqa: E712
|
|
)
|
|
)
|
|
type_stats[type_name] = count_result.scalar() or 0
|
|
|
|
# 按级别统计
|
|
level_stats = {}
|
|
for level_code, level_name in Post.POST_LEVEL_CHOICES.items():
|
|
count_result = await db.execute(
|
|
select(func.count(Post.id)).where(
|
|
Post.post_level == level_code,
|
|
Post.is_deleted == False # noqa: E712
|
|
)
|
|
)
|
|
level_stats[level_name] = count_result.scalar() or 0
|
|
|
|
return {
|
|
'total_count': total_count,
|
|
'active_count': active_count,
|
|
'inactive_count': total_count - active_count,
|
|
'type_stats': type_stats,
|
|
'level_stats': level_stats,
|
|
}
|
|
|
|
@classmethod
|
|
async def get_by_dept(cls, db: AsyncSession, dept_id: str) -> List[Post]:
|
|
"""根据部门ID获取岗位列表"""
|
|
result = await db.execute(
|
|
select(Post).where(
|
|
Post.dept_id == dept_id,
|
|
Post.status == True, # noqa: E712
|
|
Post.is_deleted == False # noqa: E712
|
|
).order_by(Post.post_level, Post.name)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
@classmethod
|
|
async def get_by_type(cls, db: AsyncSession, post_type: int) -> List[Post]:
|
|
"""根据岗位类型获取岗位列表"""
|
|
result = await db.execute(
|
|
select(Post).where(
|
|
Post.post_type == post_type,
|
|
Post.status == True, # noqa: E712
|
|
Post.is_deleted == False # noqa: E712
|
|
).order_by(Post.post_level, Post.name)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
@classmethod
|
|
async def get_by_level(cls, db: AsyncSession, post_level: int) -> List[Post]:
|
|
"""根据岗位级别获取岗位列表"""
|
|
result = await db.execute(
|
|
select(Post).where(
|
|
Post.post_level == post_level,
|
|
Post.status == True, # noqa: E712
|
|
Post.is_deleted == False # noqa: E712
|
|
).order_by(Post.name)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
@classmethod
|
|
async def get_by_ids(cls, db: AsyncSession, ids: List[str]) -> List[Post]:
|
|
"""根据ID列表批量获取岗位"""
|
|
if not ids:
|
|
return []
|
|
|
|
result = await db.execute(
|
|
select(Post).where(
|
|
Post.id.in_(ids),
|
|
Post.is_deleted == False # noqa: E712
|
|
)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
@classmethod
|
|
async def get_post_users(cls, db: AsyncSession, post_id: str) -> List[Any]:
|
|
"""获取岗位下的用户列表"""
|
|
from core.user.model import User
|
|
|
|
result = await db.execute(
|
|
select(User).where(
|
|
User.post_id == post_id,
|
|
User.user_status == 1,
|
|
User.is_deleted == False # noqa: E712
|
|
)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
@classmethod
|
|
async def add_users_to_post(
|
|
cls,
|
|
db: AsyncSession,
|
|
post_id: str,
|
|
user_ids: List[str]
|
|
) -> int:
|
|
"""将用户添加到岗位"""
|
|
from core.user.model import User
|
|
|
|
post = await cls.get_by_id(db, post_id)
|
|
if not post:
|
|
return 0
|
|
|
|
added_count = 0
|
|
for user_id in user_ids:
|
|
result = await db.execute(
|
|
select(User).where(User.id == user_id)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
if user and user.post_id != post_id:
|
|
user.post_id = post_id
|
|
added_count += 1
|
|
|
|
if added_count > 0:
|
|
await db.commit()
|
|
|
|
return added_count
|
|
|
|
@classmethod
|
|
async def remove_users_from_post(
|
|
cls,
|
|
db: AsyncSession,
|
|
post_id: str,
|
|
user_ids: List[str]
|
|
) -> int:
|
|
"""从岗位中移除用户"""
|
|
from core.user.model import User
|
|
|
|
removed_count = 0
|
|
for user_id in user_ids:
|
|
result = await db.execute(
|
|
select(User).where(User.id == user_id)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
if user and user.post_id == post_id:
|
|
user.post_id = None
|
|
removed_count += 1
|
|
|
|
if removed_count > 0:
|
|
await db.commit()
|
|
|
|
return removed_count
|
|
|
|
@classmethod
|
|
async def get_all_simple(cls, db: AsyncSession) -> List[Post]:
|
|
"""获取所有启用的岗位(简化版,用于选择器)"""
|
|
result = await db.execute(
|
|
select(Post).where(
|
|
Post.status == True, # noqa: E712
|
|
Post.is_deleted == False # noqa: E712
|
|
).order_by(Post.post_level, Post.name)
|
|
)
|
|
return list(result.scalars().all())
|