add backend source code
This commit is contained in:
17
backend/app/api/v1/__init__.py
Normal file
17
backend/app/api/v1/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from fastapi import APIRouter
|
||||
from app.api.v1 import departments, staff, indicators, assessments, salary, stats, auth, finance, performance_plans, menus, templates, surveys
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
api_router.include_router(auth.router)
|
||||
api_router.include_router(departments.router)
|
||||
api_router.include_router(staff.router)
|
||||
api_router.include_router(indicators.router)
|
||||
api_router.include_router(assessments.router)
|
||||
api_router.include_router(salary.router)
|
||||
api_router.include_router(stats.router)
|
||||
api_router.include_router(finance.router)
|
||||
api_router.include_router(performance_plans.router)
|
||||
api_router.include_router(menus.router)
|
||||
api_router.include_router(templates.router)
|
||||
api_router.include_router(surveys.router)
|
||||
165
backend/app/api/v1/assessments.py
Normal file
165
backend/app/api/v1/assessments.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
API路由 - 绩效考核管理
|
||||
"""
|
||||
from typing import Annotated, Optional, List
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
AssessmentCreate, AssessmentUpdate, AssessmentResponse,
|
||||
AssessmentListResponse, ResponseBase
|
||||
)
|
||||
from app.services.assessment_service import AssessmentService
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/assessments", tags=["绩效考核"])
|
||||
|
||||
|
||||
@router.get("", summary="获取考核列表")
|
||||
async def get_assessments(
|
||||
staff_id: Optional[int] = Query(None, description="员工ID"),
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
status: Optional[str] = Query(None, description="状态"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取考核列表"""
|
||||
assessments, total = await AssessmentService.get_list(
|
||||
db, staff_id, department_id, period_year, period_month, status, page, page_size
|
||||
)
|
||||
|
||||
# 转换响应
|
||||
result = []
|
||||
for assessment in assessments:
|
||||
item = AssessmentListResponse.model_validate(assessment).model_dump()
|
||||
item["staff_name"] = assessment.staff.name if assessment.staff else None
|
||||
item["department_name"] = assessment.staff.department.name if assessment.staff and assessment.staff.department else None
|
||||
result.append(item)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{assessment_id}", summary="获取考核详情")
|
||||
async def get_assessment(
|
||||
assessment_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取考核详情"""
|
||||
assessment = await AssessmentService.get_by_id(db, assessment_id)
|
||||
if not assessment:
|
||||
raise HTTPException(status_code=404, detail="考核记录不存在")
|
||||
|
||||
result = AssessmentResponse.model_validate(assessment).model_dump()
|
||||
result["staff_name"] = assessment.staff.name if assessment.staff else None
|
||||
result["department_name"] = assessment.staff.department.name if assessment.staff and assessment.staff.department else None
|
||||
|
||||
# 添加明细指标名称
|
||||
for detail in result.get("details", []):
|
||||
for d in assessment.details:
|
||||
if d.id == detail["id"] and d.indicator:
|
||||
detail["indicator_name"] = d.indicator.name
|
||||
break
|
||||
|
||||
return {"code": 200, "message": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("", summary="创建考核记录")
|
||||
async def create_assessment(
|
||||
assessment_data: AssessmentCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""创建考核记录"""
|
||||
assessment = await AssessmentService.create(db, assessment_data)
|
||||
return {"code": 200, "message": "创建成功", "data": {"id": assessment.id}}
|
||||
|
||||
|
||||
@router.put("/{assessment_id}", summary="更新考核记录")
|
||||
async def update_assessment(
|
||||
assessment_id: int,
|
||||
assessment_data: AssessmentUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""更新考核记录"""
|
||||
assessment = await AssessmentService.update(db, assessment_id, assessment_data)
|
||||
if not assessment:
|
||||
raise HTTPException(status_code=400, detail="无法更新,考核记录不存在或状态不允许")
|
||||
return {"code": 200, "message": "更新成功", "data": {"id": assessment.id}}
|
||||
|
||||
|
||||
@router.post("/{assessment_id}/submit", summary="提交考核")
|
||||
async def submit_assessment(
|
||||
assessment_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""提交考核"""
|
||||
assessment = await AssessmentService.submit(db, assessment_id)
|
||||
if not assessment:
|
||||
raise HTTPException(status_code=400, detail="无法提交,考核记录不存在或状态不允许")
|
||||
return {"code": 200, "message": "提交成功"}
|
||||
|
||||
|
||||
@router.post("/{assessment_id}/review", summary="审核考核")
|
||||
async def review_assessment(
|
||||
assessment_id: int,
|
||||
approved: bool = Query(..., description="是否通过"),
|
||||
remark: Optional[str] = Query(None, description="审核意见"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""审核考核(需要管理员或经理权限)"""
|
||||
# 从当前用户获取审核人ID
|
||||
reviewer_id = current_user.id
|
||||
assessment = await AssessmentService.review(db, assessment_id, reviewer_id, approved, remark)
|
||||
if not assessment:
|
||||
raise HTTPException(status_code=400, detail="无法审核,考核记录不存在或状态不允许")
|
||||
return {"code": 200, "message": "审核通过" if approved else "已驳回"}
|
||||
|
||||
|
||||
@router.post("/{assessment_id}/finalize", summary="确认考核")
|
||||
async def finalize_assessment(
|
||||
assessment_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""确认考核(需要管理员或经理权限)"""
|
||||
assessment = await AssessmentService.finalize(db, assessment_id)
|
||||
if not assessment:
|
||||
raise HTTPException(status_code=400, detail="无法确认,考核记录不存在或状态不允许")
|
||||
return {"code": 200, "message": "确认成功"}
|
||||
|
||||
|
||||
@router.post("/batch-create", summary="批量创建考核")
|
||||
async def batch_create_assessments(
|
||||
department_id: int = Query(..., description="科室ID"),
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
indicators: List[int] = Query(..., description="指标ID列表"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""为科室批量创建考核(需要管理员或经理权限)"""
|
||||
assessments = await AssessmentService.batch_create_for_department(
|
||||
db, department_id, period_year, period_month, indicators
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": f"成功创建 {len(assessments)} 条考核记录",
|
||||
"data": {"count": len(assessments)}
|
||||
}
|
||||
135
backend/app/api/v1/auth.py
Normal file
135
backend/app/api/v1/auth.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
API路由 - 用户认证
|
||||
"""
|
||||
from typing import Annotated, List
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import verify_password, create_access_token, get_current_active_user
|
||||
from app.schemas.schemas import UserLogin, UserCreate, UserResponse, Token
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["用户认证"])
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token, summary="用户登录")
|
||||
async def login(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""用户登录"""
|
||||
from sqlalchemy import select
|
||||
|
||||
result = await db.execute(
|
||||
select(User).where(User.username == form_data.username)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if not user or not verify_password(form_data.password, user.password_hash):
|
||||
raise HTTPException(status_code=401, detail="用户名或密码错误")
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=403, detail="账户已禁用")
|
||||
|
||||
access_token = create_access_token(user.id)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@router.post("/login-json", summary="用户登录(JSON格式)")
|
||||
async def login_json(
|
||||
login_data: UserLogin,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""用户登录(JSON格式)"""
|
||||
result = await db.execute(
|
||||
select(User).where(User.username == login_data.username)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if not user or not verify_password(login_data.password, user.password_hash):
|
||||
raise HTTPException(status_code=401, detail="用户名或密码错误")
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=403, detail="账户已禁用")
|
||||
|
||||
access_token = create_access_token(user.id)
|
||||
return {"code": 200, "message": "登录成功", "data": {"access_token": access_token, "token_type": "bearer"}}
|
||||
|
||||
|
||||
@router.post("/register", summary="用户注册")
|
||||
async def register(
|
||||
user_data: UserCreate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""用户注册"""
|
||||
from sqlalchemy import select
|
||||
from app.core.security import get_password_hash
|
||||
|
||||
# 检查用户名是否已存在
|
||||
result = await db.execute(
|
||||
select(User).where(User.username == user_data.username)
|
||||
)
|
||||
if result.scalar_one_or_none():
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
|
||||
user = User(
|
||||
username=user_data.username,
|
||||
password_hash=get_password_hash(user_data.password),
|
||||
staff_id=user_data.staff_id,
|
||||
role=user_data.role
|
||||
)
|
||||
db.add(user)
|
||||
await db.flush()
|
||||
|
||||
return {"code": 200, "message": "注册成功", "data": {"id": user.id}}
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse, summary="获取当前用户")
|
||||
async def get_current_user_info(
|
||||
current_user: Annotated[User, Depends(get_current_active_user)]
|
||||
):
|
||||
"""获取当前用户信息"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/users", summary="获取用户列表")
|
||||
async def get_users(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取用户列表(需要登录)"""
|
||||
# 查询用户总数
|
||||
count_result = await db.execute(select(User))
|
||||
all_users = count_result.scalars().all()
|
||||
total = len(all_users)
|
||||
|
||||
# 分页查询
|
||||
offset = (page - 1) * page_size
|
||||
result = await db.execute(
|
||||
select(User).order_by(User.id.desc()).offset(offset).limit(page_size)
|
||||
)
|
||||
users = result.scalars().all()
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": u.id,
|
||||
"username": u.username,
|
||||
"role": u.role,
|
||||
"is_active": u.is_active,
|
||||
"last_login": u.last_login,
|
||||
"created_at": u.created_at
|
||||
}
|
||||
for u in users
|
||||
],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
107
backend/app/api/v1/departments.py
Normal file
107
backend/app/api/v1/departments.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
API路由 - 科室管理
|
||||
"""
|
||||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
DepartmentCreate, DepartmentUpdate, DepartmentResponse,
|
||||
ResponseBase, PaginatedResponse
|
||||
)
|
||||
from app.services.department_service import DepartmentService
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/departments", tags=["科室管理"])
|
||||
|
||||
|
||||
@router.get("", summary="获取科室列表")
|
||||
async def get_departments(
|
||||
dept_type: Optional[str] = Query(None, description="科室类型"),
|
||||
is_active: Optional[bool] = Query(None, description="是否启用"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室列表"""
|
||||
departments, total = await DepartmentService.get_list(
|
||||
db, dept_type, is_active, page, page_size
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [DepartmentResponse.model_validate(d) for d in departments],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/tree", summary="获取科室树形结构")
|
||||
async def get_department_tree(
|
||||
dept_type: Optional[str] = Query(None, description="科室类型"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室树形结构"""
|
||||
tree = await DepartmentService.get_tree(db, dept_type)
|
||||
return {"code": 200, "message": "success", "data": tree}
|
||||
|
||||
|
||||
@router.get("/{dept_id}", summary="获取科室详情")
|
||||
async def get_department(
|
||||
dept_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室详情"""
|
||||
department = await DepartmentService.get_by_id(db, dept_id)
|
||||
if not department:
|
||||
raise HTTPException(status_code=404, detail="科室不存在")
|
||||
return {"code": 200, "message": "success", "data": DepartmentResponse.model_validate(department)}
|
||||
|
||||
|
||||
@router.post("", summary="创建科室")
|
||||
async def create_department(
|
||||
dept_data: DepartmentCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""创建科室(需要管理员或经理权限)"""
|
||||
# 检查编码是否已存在
|
||||
existing = await DepartmentService.get_by_code(db, dept_data.code)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="科室编码已存在")
|
||||
|
||||
department = await DepartmentService.create(db, dept_data)
|
||||
return {"code": 200, "message": "创建成功", "data": DepartmentResponse.model_validate(department)}
|
||||
|
||||
|
||||
@router.put("/{dept_id}", summary="更新科室")
|
||||
async def update_department(
|
||||
dept_id: int,
|
||||
dept_data: DepartmentUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新科室(需要管理员或经理权限)"""
|
||||
department = await DepartmentService.update(db, dept_id, dept_data)
|
||||
if not department:
|
||||
raise HTTPException(status_code=404, detail="科室不存在")
|
||||
return {"code": 200, "message": "更新成功", "data": DepartmentResponse.model_validate(department)}
|
||||
|
||||
|
||||
@router.delete("/{dept_id}", summary="删除科室")
|
||||
async def delete_department(
|
||||
dept_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除科室(需要管理员或经理权限)"""
|
||||
success = await DepartmentService.delete(db, dept_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="无法删除,科室下存在子科室")
|
||||
return {"code": 200, "message": "删除成功"}
|
||||
216
backend/app/api/v1/finance.py
Normal file
216
backend/app/api/v1/finance.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
API路由 - 财务核算
|
||||
"""
|
||||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
FinanceRecordCreate, FinanceRecordUpdate, FinanceRecordResponse,
|
||||
DepartmentBalance, CategorySummary, ResponseBase
|
||||
)
|
||||
from app.services.finance_service import FinanceService
|
||||
from app.models.finance import RevenueCategory, ExpenseCategory, FinanceType
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/finance", tags=["财务核算"])
|
||||
|
||||
|
||||
@router.get("/revenue", summary="获取科室收入")
|
||||
async def get_revenue(
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室收入列表"""
|
||||
data = await FinanceService.get_department_revenue(
|
||||
db, department_id, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": data
|
||||
}
|
||||
|
||||
|
||||
@router.get("/expense", summary="获取科室支出")
|
||||
async def get_expense(
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室支出列表"""
|
||||
data = await FinanceService.get_department_expense(
|
||||
db, department_id, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": data
|
||||
}
|
||||
|
||||
|
||||
@router.get("/balance", summary="获取收支结余")
|
||||
async def get_balance(
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室收支结余"""
|
||||
data = await FinanceService.get_department_balance(
|
||||
db, department_id, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": data
|
||||
}
|
||||
|
||||
|
||||
@router.get("/revenue/by-category", summary="按类别统计收入")
|
||||
async def get_revenue_by_category(
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""按类别统计收入"""
|
||||
data = await FinanceService.get_revenue_by_category(
|
||||
db, department_id, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": data
|
||||
}
|
||||
|
||||
|
||||
@router.get("/expense/by-category", summary="按类别统计支出")
|
||||
async def get_expense_by_category(
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""按类别统计支出"""
|
||||
data = await FinanceService.get_expense_by_category(
|
||||
db, department_id, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": data
|
||||
}
|
||||
|
||||
|
||||
@router.get("/summary", summary="获取科室财务汇总")
|
||||
async def get_department_summary(
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., ge=1, le=12, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取所有科室的财务汇总"""
|
||||
data = await FinanceService.get_department_summary(
|
||||
db, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": data
|
||||
}
|
||||
|
||||
|
||||
@router.get("/categories", summary="获取财务类别")
|
||||
async def get_categories(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取收入和支出类别"""
|
||||
revenue_categories = [
|
||||
{"value": cat.value, "label": label}
|
||||
for cat, label in FinanceService.REVENUE_LABELS.items()
|
||||
]
|
||||
expense_categories = [
|
||||
{"value": cat.value, "label": label}
|
||||
for cat, label in FinanceService.EXPENSE_LABELS.items()
|
||||
]
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"revenue": revenue_categories,
|
||||
"expense": expense_categories
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("", summary="创建财务记录")
|
||||
async def create_finance_record(
|
||||
record_data: FinanceRecordCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""创建财务记录(需要管理员或经理权限)"""
|
||||
# 验证类别
|
||||
if record_data.finance_type == FinanceType.REVENUE:
|
||||
valid_categories = [cat.value for cat in RevenueCategory]
|
||||
else:
|
||||
valid_categories = [cat.value for cat in ExpenseCategory]
|
||||
|
||||
if record_data.category not in valid_categories:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"无效的类别: {record_data.category}"
|
||||
)
|
||||
|
||||
record = await FinanceService.create_finance_record(
|
||||
db,
|
||||
department_id=record_data.department_id,
|
||||
finance_type=record_data.finance_type,
|
||||
category=record_data.category,
|
||||
amount=record_data.amount,
|
||||
period_year=record_data.period_year,
|
||||
period_month=record_data.period_month,
|
||||
source=record_data.source,
|
||||
remark=record_data.remark
|
||||
)
|
||||
return {"code": 200, "message": "创建成功", "data": {"id": record.id}}
|
||||
|
||||
|
||||
@router.put("/{record_id}", summary="更新财务记录")
|
||||
async def update_finance_record(
|
||||
record_id: int,
|
||||
record_data: FinanceRecordUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新财务记录(需要管理员或经理权限)"""
|
||||
record = await FinanceService.update_finance_record(
|
||||
db, record_id, **record_data.model_dump(exclude_unset=True)
|
||||
)
|
||||
if not record:
|
||||
raise HTTPException(status_code=404, detail="财务记录不存在")
|
||||
return {"code": 200, "message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/{record_id}", summary="删除财务记录")
|
||||
async def delete_finance_record(
|
||||
record_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除财务记录(需要管理员或经理权限)"""
|
||||
success = await FinanceService.delete_finance_record(db, record_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="财务记录不存在")
|
||||
return {"code": 200, "message": "删除成功"}
|
||||
141
backend/app/api/v1/indicators.py
Normal file
141
backend/app/api/v1/indicators.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
API路由 - 考核指标管理
|
||||
"""
|
||||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
IndicatorCreate, IndicatorUpdate, IndicatorResponse,
|
||||
ResponseBase
|
||||
)
|
||||
from app.services.indicator_service import IndicatorService
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/indicators", tags=["考核指标"])
|
||||
|
||||
|
||||
@router.get("", summary="获取指标列表")
|
||||
async def get_indicators(
|
||||
indicator_type: Optional[str] = Query(None, description="指标类型"),
|
||||
bs_dimension: Optional[str] = Query(None, description="BSC 维度"),
|
||||
is_active: Optional[bool] = Query(None, description="是否启用"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取指标列表"""
|
||||
indicators, total = await IndicatorService.get_list(
|
||||
db, indicator_type, bs_dimension, is_active, page, page_size
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [IndicatorResponse.model_validate(i, from_attributes=True) for i in indicators],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/active", summary="获取所有启用的指标")
|
||||
async def get_active_indicators(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取所有启用的指标"""
|
||||
indicators = await IndicatorService.get_active_indicators(db)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [IndicatorResponse.model_validate(i, from_attributes=True) for i in indicators]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{indicator_id}", summary="获取指标详情")
|
||||
async def get_indicator(
|
||||
indicator_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取指标详情"""
|
||||
indicator = await IndicatorService.get_by_id(db, indicator_id)
|
||||
if not indicator:
|
||||
raise HTTPException(status_code=404, detail="指标不存在")
|
||||
return {"code": 200, "message": "success", "data": IndicatorResponse.model_validate(indicator, from_attributes=True)}
|
||||
|
||||
|
||||
@router.post("", summary="创建指标")
|
||||
async def create_indicator(
|
||||
indicator_data: IndicatorCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""创建指标(需要管理员或经理权限)"""
|
||||
# 检查编码是否已存在
|
||||
existing = await IndicatorService.get_by_code(db, indicator_data.code)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="指标编码已存在")
|
||||
|
||||
indicator = await IndicatorService.create(db, indicator_data)
|
||||
return {"code": 200, "message": "创建成功", "data": IndicatorResponse.model_validate(indicator, from_attributes=True)}
|
||||
|
||||
|
||||
@router.put("/{indicator_id}", summary="更新指标")
|
||||
async def update_indicator(
|
||||
indicator_id: int,
|
||||
indicator_data: IndicatorUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新指标(需要管理员或经理权限)"""
|
||||
indicator = await IndicatorService.update(db, indicator_id, indicator_data)
|
||||
if not indicator:
|
||||
raise HTTPException(status_code=404, detail="指标不存在")
|
||||
return {"code": 200, "message": "更新成功", "data": IndicatorResponse.model_validate(indicator, from_attributes=True)}
|
||||
|
||||
|
||||
@router.delete("/{indicator_id}", summary="删除指标")
|
||||
async def delete_indicator(
|
||||
indicator_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除指标(需要管理员或经理权限)"""
|
||||
success = await IndicatorService.delete(db, indicator_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="指标不存在")
|
||||
return {"code": 200, "message": "删除成功"}
|
||||
|
||||
|
||||
@router.get("/templates/list", summary="获取指标模板列表")
|
||||
async def get_indicator_templates(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取指标模板列表"""
|
||||
templates = await IndicatorService.get_templates()
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": templates
|
||||
}
|
||||
|
||||
|
||||
@router.post("/templates/import", summary="导入指标模板")
|
||||
async def import_indicator_template(
|
||||
template_data: dict,
|
||||
overwrite: bool = Query(False, description="是否覆盖已存在的指标"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""导入指标模板"""
|
||||
count = await IndicatorService.import_template(db, template_data, overwrite)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": f"成功导入 {count} 个指标",
|
||||
"data": {"created_count": count}
|
||||
}
|
||||
163
backend/app/api/v1/menus.py
Normal file
163
backend/app/api/v1/menus.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
菜单管理 API
|
||||
"""
|
||||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import MenuCreate, MenuUpdate, MenuResponse, ResponseBase
|
||||
from app.services.menu_service import MenuService
|
||||
from app.models.models import User, Menu
|
||||
|
||||
router = APIRouter(prefix="/menus", tags=["菜单管理"])
|
||||
|
||||
|
||||
@router.get("/tree", summary="获取菜单树")
|
||||
async def get_menu_tree(
|
||||
visible_only: bool = Query(True, description="是否只返回可见菜单"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取菜单树形结构"""
|
||||
tree = await MenuService.get_tree(db, visible_only)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": tree
|
||||
}
|
||||
|
||||
|
||||
@router.get("", summary="获取菜单列表")
|
||||
async def get_menus(
|
||||
menu_type: Optional[str] = Query(None, description="菜单类型"),
|
||||
is_visible: Optional[bool] = Query(None, description="是否可见"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取菜单列表"""
|
||||
menus = await MenuService.get_list(db, menu_type, is_visible)
|
||||
|
||||
menu_list = []
|
||||
for menu in menus:
|
||||
menu_dict = {
|
||||
"id": menu.id,
|
||||
"parent_id": menu.parent_id,
|
||||
"menu_type": menu.menu_type,
|
||||
"menu_name": menu.menu_name,
|
||||
"menu_icon": menu.menu_icon,
|
||||
"path": menu.path,
|
||||
"component": menu.component,
|
||||
"permission": menu.permission,
|
||||
"sort_order": menu.sort_order,
|
||||
"is_visible": menu.is_visible,
|
||||
"is_active": menu.is_active,
|
||||
"created_at": menu.created_at,
|
||||
"updated_at": menu.updated_at
|
||||
}
|
||||
menu_list.append(menu_dict)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": menu_list
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{menu_id}", summary="获取菜单详情")
|
||||
async def get_menu(
|
||||
menu_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取菜单详情"""
|
||||
menu = await MenuService.get_by_id(db, menu_id)
|
||||
if not menu:
|
||||
raise HTTPException(status_code=404, detail="菜单不存在")
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": menu.id,
|
||||
"parent_id": menu.parent_id,
|
||||
"menu_type": menu.menu_type,
|
||||
"menu_name": menu.menu_name,
|
||||
"menu_icon": menu.menu_icon,
|
||||
"path": menu.path,
|
||||
"component": menu.component,
|
||||
"permission": menu.permission,
|
||||
"sort_order": menu.sort_order,
|
||||
"is_visible": menu.is_visible,
|
||||
"is_active": menu.is_active
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("", summary="创建菜单")
|
||||
async def create_menu(
|
||||
menu_data: MenuCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""创建菜单(需要管理员或经理权限)"""
|
||||
# 如果指定了父菜单,检查父菜单是否存在
|
||||
if menu_data.parent_id:
|
||||
parent = await MenuService.get_by_id(db, menu_data.parent_id)
|
||||
if not parent:
|
||||
raise HTTPException(status_code=400, detail="父菜单不存在")
|
||||
|
||||
menu = await MenuService.create(db, menu_data.model_dump())
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "创建成功",
|
||||
"data": {"id": menu.id}
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{menu_id}", summary="更新菜单")
|
||||
async def update_menu(
|
||||
menu_id: int,
|
||||
menu_data: MenuUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新菜单(需要管理员或经理权限)"""
|
||||
menu = await MenuService.update(db, menu_id, menu_data.model_dump(exclude_unset=True))
|
||||
if not menu:
|
||||
raise HTTPException(status_code=404, detail="菜单不存在")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": {"id": menu.id}
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/{menu_id}", summary="删除菜单")
|
||||
async def delete_menu(
|
||||
menu_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除菜单(需要管理员或经理权限)"""
|
||||
success = await MenuService.delete(db, menu_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="无法删除,菜单不存在或存在子菜单")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "删除成功"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/init", summary="初始化默认菜单")
|
||||
async def init_default_menus(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""初始化默认菜单(需要管理员权限)"""
|
||||
await MenuService.init_default_menus(db)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "初始化成功"
|
||||
}
|
||||
309
backend/app/api/v1/performance_plans.py
Normal file
309
backend/app/api/v1/performance_plans.py
Normal file
@@ -0,0 +1,309 @@
|
||||
"""
|
||||
绩效计划管理 API
|
||||
"""
|
||||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
PerformancePlanCreate, PerformancePlanUpdate, PerformancePlanResponse,
|
||||
PerformancePlanStats, PlanKpiRelationCreate, PlanKpiRelationUpdate,
|
||||
ResponseBase
|
||||
)
|
||||
from app.services.performance_plan_service import PerformancePlanService
|
||||
from app.models.models import User, PlanStatus
|
||||
|
||||
router = APIRouter(prefix="/plans", tags=["绩效计划管理"])
|
||||
|
||||
|
||||
@router.get("", summary="获取绩效计划列表")
|
||||
async def get_performance_plans(
|
||||
plan_level: Optional[str] = Query(None, description="计划层级"),
|
||||
plan_year: Optional[int] = Query(None, description="计划年度"),
|
||||
department_id: Optional[int] = Query(None, description="科室 ID"),
|
||||
status: Optional[str] = Query(None, description="状态"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取绩效计划列表"""
|
||||
plans, total = await PerformancePlanService.get_list(
|
||||
db, plan_level, plan_year, department_id, status, page, page_size
|
||||
)
|
||||
|
||||
# 构建响应数据
|
||||
plan_list = []
|
||||
for plan in plans:
|
||||
plan_dict = {
|
||||
"id": plan.id,
|
||||
"plan_name": plan.plan_name,
|
||||
"plan_code": plan.plan_code,
|
||||
"plan_level": plan.plan_level,
|
||||
"plan_year": plan.plan_year,
|
||||
"plan_month": plan.plan_month,
|
||||
"status": plan.status,
|
||||
"department_id": plan.department_id,
|
||||
"department_name": plan.department.name if plan.department else None,
|
||||
"staff_id": plan.staff_id,
|
||||
"staff_name": plan.staff.name if plan.staff else None,
|
||||
"description": plan.description,
|
||||
"created_at": plan.created_at,
|
||||
"updated_at": plan.updated_at
|
||||
}
|
||||
plan_list.append(plan_dict)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": plan_list,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/tree", summary="获取绩效计划树")
|
||||
async def get_performance_plan_tree(
|
||||
plan_year: Optional[int] = Query(None, description="计划年度"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取绩效计划树形结构"""
|
||||
tree = await PerformancePlanService.get_tree(db, plan_year)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": tree
|
||||
}
|
||||
|
||||
|
||||
@router.get("/stats", summary="获取绩效计划统计")
|
||||
async def get_performance_plan_stats(
|
||||
plan_year: Optional[int] = Query(None, description="计划年度"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取绩效计划统计信息"""
|
||||
stats = await PerformancePlanService.get_stats(db, plan_year)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": stats
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{plan_id}", summary="获取绩效计划详情")
|
||||
async def get_performance_plan(
|
||||
plan_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取绩效计划详情"""
|
||||
plan = await PerformancePlanService.get_by_id(db, plan_id)
|
||||
if not plan:
|
||||
raise HTTPException(status_code=404, detail="绩效计划不存在")
|
||||
|
||||
# 构建响应数据
|
||||
kpi_relations = []
|
||||
for relation in plan.kpi_relations:
|
||||
kpi_relations.append({
|
||||
"id": relation.id,
|
||||
"indicator_id": relation.indicator_id,
|
||||
"indicator_name": relation.indicator.name if relation.indicator else None,
|
||||
"indicator_code": relation.indicator.code if relation.indicator else None,
|
||||
"target_value": relation.target_value,
|
||||
"target_unit": relation.target_unit,
|
||||
"weight": relation.weight,
|
||||
"scoring_method": relation.scoring_method,
|
||||
"scoring_params": relation.scoring_params,
|
||||
"remark": relation.remark
|
||||
})
|
||||
|
||||
plan_data = {
|
||||
"id": plan.id,
|
||||
"plan_name": plan.plan_name,
|
||||
"plan_code": plan.plan_code,
|
||||
"plan_level": plan.plan_level,
|
||||
"plan_year": plan.plan_year,
|
||||
"plan_month": plan.plan_month,
|
||||
"plan_type": plan.plan_type,
|
||||
"department_id": plan.department_id,
|
||||
"department_name": plan.department.name if plan.department else None,
|
||||
"staff_id": plan.staff_id,
|
||||
"staff_name": plan.staff.name if plan.staff else None,
|
||||
"parent_plan_id": plan.parent_plan_id,
|
||||
"description": plan.description,
|
||||
"strategic_goals": plan.strategic_goals,
|
||||
"key_initiatives": plan.key_initiatives,
|
||||
"status": plan.status,
|
||||
"submitter_id": plan.submitter_id,
|
||||
"submit_time": plan.submit_time,
|
||||
"approver_id": plan.approver_id,
|
||||
"approve_time": plan.approve_time,
|
||||
"approve_remark": plan.approve_remark,
|
||||
"version": plan.version,
|
||||
"is_active": plan.is_active,
|
||||
"created_at": plan.created_at,
|
||||
"updated_at": plan.updated_at,
|
||||
"kpi_relations": kpi_relations
|
||||
}
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": plan_data
|
||||
}
|
||||
|
||||
|
||||
@router.post("", summary="创建绩效计划")
|
||||
async def create_performance_plan(
|
||||
plan_data: PerformancePlanCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""创建绩效计划"""
|
||||
plan = await PerformancePlanService.create(db, plan_data, current_user.id)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "创建成功",
|
||||
"data": {"id": plan.id}
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{plan_id}", summary="更新绩效计划")
|
||||
async def update_performance_plan(
|
||||
plan_id: int,
|
||||
plan_data: PerformancePlanUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""更新绩效计划"""
|
||||
plan = await PerformancePlanService.update(db, plan_id, plan_data)
|
||||
if not plan:
|
||||
raise HTTPException(status_code=404, detail="绩效计划不存在")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": {"id": plan.id}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{plan_id}/submit", summary="提交绩效计划")
|
||||
async def submit_performance_plan(
|
||||
plan_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""提交绩效计划"""
|
||||
plan = await PerformancePlanService.submit(db, plan_id)
|
||||
if not plan:
|
||||
raise HTTPException(status_code=400, detail="无法提交,计划不存在或状态不允许")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "提交成功"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{plan_id}/approve", summary="审批绩效计划")
|
||||
async def approve_performance_plan(
|
||||
plan_id: int,
|
||||
approved: bool = Query(..., description="是否通过"),
|
||||
remark: Optional[str] = Query(None, description="审批意见"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""审批绩效计划(需要管理员或经理权限)"""
|
||||
plan = await PerformancePlanService.approve(db, plan_id, current_user.id, approved, remark)
|
||||
if not plan:
|
||||
raise HTTPException(status_code=400, detail="无法审批,计划不存在或状态不允许")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "审核通过" if approved else "已驳回"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{plan_id}/activate", summary="激活绩效计划")
|
||||
async def activate_performance_plan(
|
||||
plan_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""激活绩效计划(需要管理员或经理权限)"""
|
||||
plan = await PerformancePlanService.activate(db, plan_id)
|
||||
if not plan:
|
||||
raise HTTPException(status_code=400, detail="无法激活,计划不存在或状态不允许")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "激活成功"
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/{plan_id}", summary="删除绩效计划")
|
||||
async def delete_performance_plan(
|
||||
plan_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除绩效计划(需要管理员或经理权限)"""
|
||||
success = await PerformancePlanService.delete(db, plan_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="绩效计划不存在")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "删除成功"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{plan_id}/kpi-relations", summary="添加计划指标关联")
|
||||
async def add_kpi_relation(
|
||||
plan_id: int,
|
||||
kpi_data: PlanKpiRelationCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""添加计划指标关联(需要管理员或经理权限)"""
|
||||
relation = await PerformancePlanService.add_kpi_relation(db, plan_id, kpi_data)
|
||||
if not relation:
|
||||
raise HTTPException(status_code=404, detail="绩效计划不存在")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "添加成功",
|
||||
"data": {"id": relation.id}
|
||||
}
|
||||
|
||||
|
||||
@router.put("/kpi-relations/{relation_id}", summary="更新计划指标关联")
|
||||
async def update_kpi_relation(
|
||||
relation_id: int,
|
||||
kpi_data: PlanKpiRelationUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新计划指标关联(需要管理员或经理权限)"""
|
||||
relation = await PerformancePlanService.update_kpi_relation(db, relation_id, kpi_data.model_dump())
|
||||
if not relation:
|
||||
raise HTTPException(status_code=404, detail="指标关联不存在")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": {"id": relation.id}
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/kpi-relations/{relation_id}", summary="删除计划指标关联")
|
||||
async def delete_kpi_relation(
|
||||
relation_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除计划指标关联(需要管理员或经理权限)"""
|
||||
success = await PerformancePlanService.delete_kpi_relation(db, relation_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="指标关联不存在")
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "删除成功"
|
||||
}
|
||||
155
backend/app/api/v1/salary.py
Normal file
155
backend/app/api/v1/salary.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
API路由 - 工资核算管理
|
||||
"""
|
||||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
SalaryRecordCreate, SalaryRecordUpdate, SalaryRecordResponse,
|
||||
ResponseBase
|
||||
)
|
||||
from app.services.salary_service import SalaryService
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/salary", tags=["工资核算"])
|
||||
|
||||
|
||||
@router.get("", summary="获取工资记录列表")
|
||||
async def get_salary_records(
|
||||
staff_id: Optional[int] = Query(None, description="员工ID"),
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
status: Optional[str] = Query(None, description="状态"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取工资记录列表"""
|
||||
records, total = await SalaryService.get_list(
|
||||
db, staff_id, department_id, period_year, period_month, status, page, page_size
|
||||
)
|
||||
|
||||
result = []
|
||||
for record in records:
|
||||
item = SalaryRecordResponse.model_validate(record).model_dump()
|
||||
item["staff_name"] = record.staff.name if record.staff else None
|
||||
item["department_name"] = record.staff.department.name if record.staff and record.staff.department else None
|
||||
result.append(item)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{record_id}", summary="获取工资记录详情")
|
||||
async def get_salary_record(
|
||||
record_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取工资记录详情"""
|
||||
record = await SalaryService.get_by_id(db, record_id)
|
||||
if not record:
|
||||
raise HTTPException(status_code=404, detail="工资记录不存在")
|
||||
|
||||
result = SalaryRecordResponse.model_validate(record).model_dump()
|
||||
result["staff_name"] = record.staff.name if record.staff else None
|
||||
result["department_name"] = record.staff.department.name if record.staff and record.staff.department else None
|
||||
return {"code": 200, "message": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("", summary="创建工资记录")
|
||||
async def create_salary_record(
|
||||
record_data: SalaryRecordCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""创建工资记录(需要管理员或经理权限)"""
|
||||
record = await SalaryService.create(db, record_data)
|
||||
return {"code": 200, "message": "创建成功", "data": {"id": record.id}}
|
||||
|
||||
|
||||
@router.put("/{record_id}", summary="更新工资记录")
|
||||
async def update_salary_record(
|
||||
record_id: int,
|
||||
record_data: SalaryRecordUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新工资记录(需要管理员或经理权限)"""
|
||||
record = await SalaryService.update(db, record_id, record_data)
|
||||
if not record:
|
||||
raise HTTPException(status_code=400, detail="无法更新,记录不存在或状态不允许")
|
||||
return {"code": 200, "message": "更新成功", "data": {"id": record.id}}
|
||||
|
||||
|
||||
@router.post("/generate", summary="根据考核生成工资")
|
||||
async def generate_salary(
|
||||
staff_id: int = Query(..., description="员工ID"),
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""根据考核记录生成工资记录(需要管理员或经理权限)"""
|
||||
record = await SalaryService.generate_from_assessment(
|
||||
db, staff_id, period_year, period_month
|
||||
)
|
||||
if not record:
|
||||
raise HTTPException(status_code=400, detail="无法生成,未找到已确认的考核记录或已存在工资记录")
|
||||
return {"code": 200, "message": "生成成功", "data": {"id": record.id}}
|
||||
|
||||
|
||||
@router.post("/batch-generate", summary="批量生成工资")
|
||||
async def batch_generate_salary(
|
||||
department_id: int = Query(..., description="科室ID"),
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""为科室批量生成工资记录(需要管理员或经理权限)"""
|
||||
records = await SalaryService.batch_generate_for_department(
|
||||
db, department_id, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": f"成功生成 {len(records)} 条工资记录",
|
||||
"data": {"count": len(records)}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{record_id}/confirm", summary="确认工资")
|
||||
async def confirm_salary(
|
||||
record_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""确认工资(需要管理员或经理权限)"""
|
||||
record = await SalaryService.confirm(db, record_id)
|
||||
if not record:
|
||||
raise HTTPException(status_code=400, detail="无法确认,记录不存在或状态不允许")
|
||||
return {"code": 200, "message": "确认成功"}
|
||||
|
||||
|
||||
@router.post("/batch-confirm", summary="批量确认工资")
|
||||
async def batch_confirm_salary(
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""批量确认工资(需要管理员或经理权限)"""
|
||||
count = await SalaryService.batch_confirm(db, period_year, period_month, department_id)
|
||||
return {"code": 200, "message": f"成功确认 {count} 条工资记录", "data": {"count": count}}
|
||||
123
backend/app/api/v1/staff.py
Normal file
123
backend/app/api/v1/staff.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
API路由 - 员工管理
|
||||
"""
|
||||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
StaffCreate, StaffUpdate, StaffResponse,
|
||||
ResponseBase
|
||||
)
|
||||
from app.services.staff_service import StaffService
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/staff", tags=["员工管理"])
|
||||
|
||||
|
||||
@router.get("", summary="获取员工列表")
|
||||
async def get_staff_list(
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
status: Optional[str] = Query(None, description="状态"),
|
||||
keyword: Optional[str] = Query(None, description="搜索关键词"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取员工列表"""
|
||||
staff_list, total = await StaffService.get_list(
|
||||
db, department_id, status, keyword, page, page_size
|
||||
)
|
||||
|
||||
# 添加科室名称
|
||||
result = []
|
||||
for staff in staff_list:
|
||||
staff_dict = StaffResponse.model_validate(staff).model_dump()
|
||||
staff_dict["department_name"] = staff.department.name if staff.department else None
|
||||
result.append(staff_dict)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{staff_id}", summary="获取员工详情")
|
||||
async def get_staff(
|
||||
staff_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取员工详情"""
|
||||
staff = await StaffService.get_by_id(db, staff_id)
|
||||
if not staff:
|
||||
raise HTTPException(status_code=404, detail="员工不存在")
|
||||
|
||||
staff_dict = StaffResponse.model_validate(staff).model_dump()
|
||||
staff_dict["department_name"] = staff.department.name if staff.department else None
|
||||
return {"code": 200, "message": "success", "data": staff_dict}
|
||||
|
||||
|
||||
@router.post("", summary="创建员工")
|
||||
async def create_staff(
|
||||
staff_data: StaffCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""创建员工(需要管理员或经理权限)"""
|
||||
# 检查工号是否已存在
|
||||
existing = await StaffService.get_by_employee_id(db, staff_data.employee_id)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="工号已存在")
|
||||
|
||||
staff = await StaffService.create(db, staff_data)
|
||||
return {"code": 200, "message": "创建成功", "data": StaffResponse.model_validate(staff)}
|
||||
|
||||
|
||||
@router.put("/{staff_id}", summary="更新员工")
|
||||
async def update_staff(
|
||||
staff_id: int,
|
||||
staff_data: StaffUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新员工(需要管理员或经理权限)"""
|
||||
staff = await StaffService.update(db, staff_id, staff_data)
|
||||
if not staff:
|
||||
raise HTTPException(status_code=404, detail="员工不存在")
|
||||
return {"code": 200, "message": "更新成功", "data": StaffResponse.model_validate(staff)}
|
||||
|
||||
|
||||
@router.delete("/{staff_id}", summary="删除员工")
|
||||
async def delete_staff(
|
||||
staff_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除员工(需要管理员或经理权限)"""
|
||||
success = await StaffService.delete(db, staff_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="员工不存在")
|
||||
return {"code": 200, "message": "删除成功"}
|
||||
|
||||
|
||||
@router.get("/department/{department_id}", summary="获取科室员工")
|
||||
async def get_department_staff(
|
||||
department_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室下所有员工"""
|
||||
staff_list = await StaffService.get_by_department(db, department_id)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [StaffResponse.model_validate(s) for s in staff_list]
|
||||
}
|
||||
241
backend/app/api/v1/stats.py
Normal file
241
backend/app/api/v1/stats.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
统计报表 API
|
||||
"""
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user
|
||||
from app.models.models import User
|
||||
from app.services.stats_service import StatsService
|
||||
|
||||
router = APIRouter(prefix="/stats", tags=["统计报表"])
|
||||
|
||||
|
||||
@router.get("/bsc-dimension", summary="BSC 维度分析")
|
||||
async def get_bsc_dimension_stats(
|
||||
department_id: Optional[int] = Query(None, description="科室 ID"),
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取 BSC 四个维度的统计分析"""
|
||||
result = await StatsService.get_bsc_dimension_stats(
|
||||
db, department_id, period_year, period_month
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
|
||||
@router.get("/department", summary="科室绩效统计")
|
||||
async def get_department_stats(
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取各科室绩效统计"""
|
||||
result = await StatsService.get_department_stats(db, period_year, period_month)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
|
||||
@router.get("/trend", summary="趋势分析")
|
||||
async def get_trend_stats(
|
||||
department_id: Optional[int] = Query(None, description="科室 ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
months: Optional[int] = Query(6, ge=1, le=24, description="最近几个月"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取绩效趋势分析(月度)"""
|
||||
# 如果没有指定年份,使用当前年份
|
||||
if not period_year:
|
||||
period_year = datetime.now().year
|
||||
|
||||
result = await StatsService.get_trend_stats(db, department_id, period_year, months)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
|
||||
@router.get("/alerts", summary="预警数据")
|
||||
async def get_alerts(
|
||||
limit: Optional[int] = Query(10, ge=1, le=100, description="返回数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取预警数据(考核到期、工资未发等)"""
|
||||
# TODO: 从数据库实际查询预警数据
|
||||
# 目前返回模拟数据用于演示
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"lowScoreStaff": [], # 低分员工
|
||||
"incompleteDepartments": [], # 未完成考核科室
|
||||
"anomalyData": [] # 异常数据
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/period", summary="周期统计")
|
||||
async def get_period_stats(
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取周期统计数据"""
|
||||
# 如果没有指定年月,使用当前年月
|
||||
if not period_year:
|
||||
period_year = datetime.now().year
|
||||
if not period_month:
|
||||
period_month = datetime.now().month
|
||||
|
||||
# 获取该周期的考核统计
|
||||
result = await StatsService.get_department_stats(db, period_year, period_month)
|
||||
|
||||
# 计算汇总数据
|
||||
total_departments = len(result)
|
||||
total_staff = sum(dept.get('staff_count', 0) for dept in result)
|
||||
avg_score = sum(dept.get('avg_score', 0) for dept in result) / total_departments if total_departments > 0 else 0
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"period": f"{period_year}年{period_month}月",
|
||||
"total_departments": total_departments,
|
||||
"total_staff": total_staff,
|
||||
"avg_score": round(avg_score, 2),
|
||||
"departments": result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/kpi-gauges", summary="关键指标仪表盘")
|
||||
async def get_kpi_gauges(
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取关键指标仪表盘数据"""
|
||||
# 如果没有指定年月,使用当前年月
|
||||
if not period_year:
|
||||
period_year = datetime.now().year
|
||||
if not period_month:
|
||||
period_month = datetime.now().month
|
||||
|
||||
# TODO: 从数据库实际计算这些指标
|
||||
# 目前返回模拟数据用于演示
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"bed_usage_rate": 85.5, # 床位使用率 (%)
|
||||
"drug_ratio": 32.8, # 药占比 (%)
|
||||
"material_ratio": 18.5, # 材料占比 (%)
|
||||
"satisfaction_rate": 92.3 # 患者满意度 (%)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/finance-trend", summary="收支趋势")
|
||||
async def get_finance_trend(
|
||||
months: Optional[int] = Query(6, ge=1, le=24, description="最近几个月"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取收支趋势数据"""
|
||||
# TODO: 从数据库实际查询收支数据
|
||||
# 目前返回模拟数据用于演示
|
||||
from datetime import datetime
|
||||
current_month = datetime.now().month
|
||||
data = []
|
||||
for i in range(months, 0, -1):
|
||||
month = current_month - i + 1
|
||||
if month < 1:
|
||||
month += 12
|
||||
data.append({
|
||||
"period": f"{month}月",
|
||||
"income": 1000000 + (months - i) * 50000,
|
||||
"expense": 800000 + (months - i) * 30000,
|
||||
"profit": 200000 + (months - i) * 20000
|
||||
})
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": data
|
||||
}
|
||||
|
||||
|
||||
@router.get("/department-ranking", summary="科室绩效排名")
|
||||
async def get_department_ranking(
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
limit: Optional[int] = Query(10, ge=1, le=100, description="返回数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取科室绩效排名"""
|
||||
# 如果没有指定年月,使用当前年月
|
||||
if not period_year:
|
||||
period_year = datetime.now().year
|
||||
if not period_month:
|
||||
period_month = datetime.now().month
|
||||
|
||||
result = await StatsService.get_department_stats(db, period_year, period_month)
|
||||
# 返回前 limit 个
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result[:limit] if limit else result
|
||||
}
|
||||
|
||||
|
||||
@router.get("/ranking", summary="绩效排名")
|
||||
async def get_ranking_stats(
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
limit: int = Query(10, ge=1, le=100, description="返回数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取绩效排名前 N 名"""
|
||||
result = await StatsService.get_ranking_stats(db, period_year, period_month, limit)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result
|
||||
}
|
||||
|
||||
|
||||
@router.get("/completion", summary="指标完成度")
|
||||
async def get_completion_stats(
|
||||
indicator_id: Optional[int] = Query(None, description="指标 ID"),
|
||||
period_year: int = Query(..., description="年度"),
|
||||
period_month: int = Query(..., description="月份"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取指标完成度统计"""
|
||||
result = await StatsService.get_completion_stats(db, indicator_id, period_year, period_month)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": result
|
||||
}
|
||||
401
backend/app/api/v1/surveys.py
Normal file
401
backend/app/api/v1/surveys.py
Normal file
@@ -0,0 +1,401 @@
|
||||
"""
|
||||
满意度调查 API 接口
|
||||
"""
|
||||
from typing import Optional, List
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.models.models import SurveyStatus, SurveyType, QuestionType
|
||||
from app.services.survey_service import SurveyService
|
||||
|
||||
|
||||
router = APIRouter(prefix="/surveys", tags=["满意度调查"])
|
||||
|
||||
|
||||
# ==================== Pydantic Schemas ====================
|
||||
|
||||
class QuestionCreate(BaseModel):
|
||||
"""题目创建"""
|
||||
question_text: str = Field(..., description="题目内容")
|
||||
question_type: str = Field(..., description="题目类型")
|
||||
options: Optional[List[dict]] = Field(None, description="选项列表")
|
||||
score_max: int = Field(5, description="最高分值")
|
||||
is_required: bool = Field(True, description="是否必答")
|
||||
|
||||
|
||||
class QuestionResponse(BaseModel):
|
||||
"""题目响应"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
question_text: str
|
||||
question_type: str
|
||||
options: Optional[str] = None
|
||||
score_max: int
|
||||
is_required: bool
|
||||
sort_order: int
|
||||
|
||||
|
||||
class SurveyCreate(BaseModel):
|
||||
"""问卷创建"""
|
||||
survey_name: str = Field(..., description="问卷名称")
|
||||
survey_code: str = Field(..., description="问卷编码")
|
||||
survey_type: str = Field(..., description="问卷类型")
|
||||
description: Optional[str] = Field(None, description="描述")
|
||||
target_departments: Optional[List[int]] = Field(None, description="目标科室")
|
||||
is_anonymous: bool = Field(True, description="是否匿名")
|
||||
questions: Optional[List[QuestionCreate]] = Field(None, description="题目列表")
|
||||
|
||||
|
||||
class SurveyUpdate(BaseModel):
|
||||
"""问卷更新"""
|
||||
survey_name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
target_departments: Optional[List[int]] = None
|
||||
is_anonymous: Optional[bool] = None
|
||||
|
||||
|
||||
class SurveyResponse(BaseModel):
|
||||
"""问卷响应"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
survey_name: str
|
||||
survey_code: str
|
||||
survey_type: str
|
||||
description: Optional[str] = None
|
||||
target_departments: Optional[str] = None
|
||||
total_questions: int
|
||||
status: str
|
||||
start_date: Optional[datetime] = None
|
||||
end_date: Optional[datetime] = None
|
||||
is_anonymous: bool
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class SurveyDetailResponse(SurveyResponse):
|
||||
"""问卷详情响应"""
|
||||
questions: Optional[List[QuestionResponse]] = None
|
||||
|
||||
|
||||
class AnswerSubmit(BaseModel):
|
||||
"""回答提交"""
|
||||
question_id: int
|
||||
answer_value: str
|
||||
|
||||
|
||||
class ResponseSubmit(BaseModel):
|
||||
"""问卷回答提交"""
|
||||
department_id: Optional[int] = None
|
||||
respondent_type: str = "patient"
|
||||
respondent_id: Optional[int] = None
|
||||
respondent_phone: Optional[str] = None
|
||||
answers: List[AnswerSubmit]
|
||||
|
||||
|
||||
class ResponseResult(BaseModel):
|
||||
"""回答结果"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
survey_id: int
|
||||
department_id: Optional[int] = None
|
||||
total_score: float
|
||||
max_score: float
|
||||
satisfaction_rate: float
|
||||
submitted_at: datetime
|
||||
|
||||
|
||||
# ==================== API Endpoints ====================
|
||||
|
||||
@router.get("", summary="获取问卷列表")
|
||||
async def get_survey_list(
|
||||
survey_type: Optional[str] = Query(None, description="问卷类型"),
|
||||
status: Optional[str] = Query(None, description="状态"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""获取问卷列表"""
|
||||
type_filter = SurveyType(survey_type) if survey_type else None
|
||||
status_filter = SurveyStatus(status) if status else None
|
||||
|
||||
surveys, total = await SurveyService.get_survey_list(
|
||||
db, type_filter, status_filter, page, page_size
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": [
|
||||
SurveyResponse.model_validate(s) for s in surveys
|
||||
],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{survey_id}", summary="获取问卷详情")
|
||||
async def get_survey_detail(
|
||||
survey_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""获取问卷详情"""
|
||||
survey = await SurveyService.get_survey_by_id(db, survey_id)
|
||||
if not survey:
|
||||
raise HTTPException(status_code=404, detail="问卷不存在")
|
||||
|
||||
detail = SurveyDetailResponse.model_validate(survey)
|
||||
if survey.questions:
|
||||
detail.questions = [QuestionResponse.model_validate(q) for q in survey.questions]
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": detail
|
||||
}
|
||||
|
||||
|
||||
@router.post("", summary="创建问卷")
|
||||
async def create_survey(
|
||||
survey_data: SurveyCreate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""创建问卷"""
|
||||
try:
|
||||
survey_type = SurveyType(survey_data.survey_type)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="无效的问卷类型")
|
||||
|
||||
survey = await SurveyService.create_survey(
|
||||
db,
|
||||
survey_name=survey_data.survey_name,
|
||||
survey_code=survey_data.survey_code,
|
||||
survey_type=survey_type,
|
||||
description=survey_data.description,
|
||||
target_departments=survey_data.target_departments,
|
||||
is_anonymous=survey_data.is_anonymous
|
||||
)
|
||||
|
||||
# 添加题目
|
||||
if survey_data.questions:
|
||||
for question in survey_data.questions:
|
||||
try:
|
||||
question_type = QuestionType(question.question_type)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
await SurveyService.add_question(
|
||||
db,
|
||||
survey_id=survey.id,
|
||||
question_text=question.question_text,
|
||||
question_type=question_type,
|
||||
options=question.options,
|
||||
score_max=question.score_max,
|
||||
is_required=question.is_required
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "创建成功",
|
||||
"data": SurveyResponse.model_validate(survey)
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{survey_id}", summary="更新问卷")
|
||||
async def update_survey(
|
||||
survey_id: int,
|
||||
survey_data: SurveyUpdate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""更新问卷"""
|
||||
try:
|
||||
update_dict = survey_data.model_dump(exclude_unset=True)
|
||||
survey = await SurveyService.update_survey(db, survey_id, **update_dict)
|
||||
if not survey:
|
||||
raise HTTPException(status_code=404, detail="问卷不存在")
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "更新成功",
|
||||
"data": SurveyResponse.model_validate(survey)
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{survey_id}/publish", summary="发布问卷")
|
||||
async def publish_survey(
|
||||
survey_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""发布问卷"""
|
||||
try:
|
||||
survey = await SurveyService.publish_survey(db, survey_id)
|
||||
if not survey:
|
||||
raise HTTPException(status_code=404, detail="问卷不存在")
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "发布成功",
|
||||
"data": SurveyResponse.model_validate(survey)
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{survey_id}/close", summary="结束问卷")
|
||||
async def close_survey(
|
||||
survey_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""结束问卷"""
|
||||
try:
|
||||
survey = await SurveyService.close_survey(db, survey_id)
|
||||
if not survey:
|
||||
raise HTTPException(status_code=404, detail="问卷不存在")
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "问卷已结束",
|
||||
"data": SurveyResponse.model_validate(survey)
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.delete("/{survey_id}", summary="删除问卷")
|
||||
async def delete_survey(
|
||||
survey_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""删除问卷"""
|
||||
try:
|
||||
success = await SurveyService.delete_survey(db, survey_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="问卷不存在")
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "删除成功"
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/{survey_id}/submit", summary="提交问卷回答")
|
||||
async def submit_response(
|
||||
survey_id: int,
|
||||
response_data: ResponseSubmit,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""提交问卷回答"""
|
||||
try:
|
||||
answers = [a.model_dump() for a in response_data.answers]
|
||||
|
||||
response = await SurveyService.submit_response(
|
||||
db,
|
||||
survey_id=survey_id,
|
||||
department_id=response_data.department_id,
|
||||
answers=answers,
|
||||
respondent_type=response_data.respondent_type,
|
||||
respondent_id=response_data.respondent_id,
|
||||
respondent_phone=response_data.respondent_phone
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "提交成功",
|
||||
"data": ResponseResult.model_validate(response)
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/{survey_id}/stats", summary="获取问卷统计")
|
||||
async def get_survey_stats(
|
||||
survey_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""获取问卷各题目统计"""
|
||||
survey = await SurveyService.get_survey_by_id(db, survey_id)
|
||||
if not survey:
|
||||
raise HTTPException(status_code=404, detail="问卷不存在")
|
||||
|
||||
stats = await SurveyService.get_question_stats(db, survey_id)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"survey_id": survey_id,
|
||||
"survey_name": survey.survey_name,
|
||||
"total_responses": await _get_response_count(db, survey_id),
|
||||
"question_stats": stats
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/stats/department", summary="获取科室满意度统计")
|
||||
async def get_department_satisfaction(
|
||||
survey_id: Optional[int] = Query(None, description="问卷ID"),
|
||||
department_id: Optional[int] = Query(None, description="科室ID"),
|
||||
period_year: Optional[int] = Query(None, description="年度"),
|
||||
period_month: Optional[int] = Query(None, description="月份"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""获取科室满意度统计"""
|
||||
stats = await SurveyService.get_department_satisfaction(
|
||||
db, survey_id, department_id, period_year, period_month
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": stats
|
||||
}
|
||||
|
||||
|
||||
@router.get("/stats/trend/{department_id}", summary="获取满意度趋势")
|
||||
async def get_satisfaction_trend(
|
||||
department_id: int,
|
||||
months: int = Query(6, ge=1, le=12, description="查询月数"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""获取满意度趋势"""
|
||||
trend = await SurveyService.get_satisfaction_trend(db, department_id, months)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": trend
|
||||
}
|
||||
|
||||
|
||||
async def _get_response_count(db: AsyncSession, survey_id: int) -> int:
|
||||
"""获取问卷回答数"""
|
||||
from sqlalchemy import select, func
|
||||
from app.models.models import SurveyResponse
|
||||
|
||||
result = await db.execute(
|
||||
select(func.count(SurveyResponse.id))
|
||||
.where(SurveyResponse.survey_id == survey_id)
|
||||
)
|
||||
return result.scalar() or 0
|
||||
271
backend/app/api/v1/templates.py
Normal file
271
backend/app/api/v1/templates.py
Normal file
@@ -0,0 +1,271 @@
|
||||
"""
|
||||
API路由 - 指标模板管理
|
||||
"""
|
||||
from typing import Annotated, Optional, List
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_active_user, get_current_manager_user
|
||||
from app.schemas.schemas import (
|
||||
IndicatorTemplateCreate, IndicatorTemplateUpdate,
|
||||
IndicatorTemplateResponse, IndicatorTemplateListResponse,
|
||||
TemplateIndicatorCreate, TemplateIndicatorUpdate, TemplateIndicatorResponse,
|
||||
ResponseBase
|
||||
)
|
||||
from app.services.template_service import TemplateService
|
||||
from app.models.models import User
|
||||
|
||||
router = APIRouter(prefix="/templates", tags=["指标模板"])
|
||||
|
||||
|
||||
@router.get("", summary="获取模板列表")
|
||||
async def get_templates(
|
||||
template_type: Optional[str] = Query(None, description="模板类型"),
|
||||
is_active: Optional[bool] = Query(None, description="是否启用"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取模板列表"""
|
||||
templates, total = await TemplateService.get_list(
|
||||
db, template_type, is_active, page, page_size
|
||||
)
|
||||
return {
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": templates,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
|
||||
@router.get("/types", summary="获取模板类型列表")
|
||||
async def get_template_types(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取模板类型列表"""
|
||||
types = [
|
||||
{"value": "general", "label": "通用模板"},
|
||||
{"value": "surgical", "label": "手术临床科室"},
|
||||
{"value": "nonsurgical_ward", "label": "非手术有病房科室"},
|
||||
{"value": "nonsurgical_noward", "label": "非手术无病房科室"},
|
||||
{"value": "medical_tech", "label": "医技科室"},
|
||||
{"value": "nursing", "label": "护理单元"},
|
||||
{"value": "admin", "label": "行政科室"},
|
||||
{"value": "logistics", "label": "后勤科室"}
|
||||
]
|
||||
return {"code": 200, "message": "success", "data": types}
|
||||
|
||||
|
||||
@router.get("/dimensions", summary="获取BSC维度列表")
|
||||
async def get_dimensions(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取BSC维度列表"""
|
||||
dimensions = [
|
||||
{"value": "financial", "label": "财务管理", "weight_range": "30%-40%"},
|
||||
{"value": "customer", "label": "顾客服务", "weight_range": "25%-35%"},
|
||||
{"value": "internal_process", "label": "内部流程", "weight_range": "20%-30%"},
|
||||
{"value": "learning_growth", "label": "学习与成长", "weight_range": "5%-15%"}
|
||||
]
|
||||
return {"code": 200, "message": "success", "data": dimensions}
|
||||
|
||||
|
||||
@router.get("/{template_id}", summary="获取模板详情")
|
||||
async def get_template(
|
||||
template_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取模板详情"""
|
||||
template = await TemplateService.get_by_id(db, template_id)
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
# 构建响应数据
|
||||
indicators = []
|
||||
for ti in template.indicators:
|
||||
ind_dict = {
|
||||
"id": ti.id,
|
||||
"template_id": ti.template_id,
|
||||
"indicator_id": ti.indicator_id,
|
||||
"indicator_name": ti.indicator.name if ti.indicator else None,
|
||||
"indicator_code": ti.indicator.code if ti.indicator else None,
|
||||
"indicator_type": ti.indicator.indicator_type.value if ti.indicator else None,
|
||||
"bs_dimension": ti.indicator.bs_dimension.value if ti.indicator else None,
|
||||
"category": ti.category,
|
||||
"target_value": float(ti.target_value) if ti.target_value else None,
|
||||
"target_unit": ti.target_unit,
|
||||
"weight": float(ti.weight),
|
||||
"scoring_method": ti.scoring_method,
|
||||
"scoring_params": ti.scoring_params,
|
||||
"sort_order": ti.sort_order,
|
||||
"remark": ti.remark,
|
||||
"created_at": ti.created_at,
|
||||
"updated_at": ti.updated_at
|
||||
}
|
||||
indicators.append(ind_dict)
|
||||
|
||||
response_data = {
|
||||
"id": template.id,
|
||||
"template_name": template.template_name,
|
||||
"template_code": template.template_code,
|
||||
"template_type": template.template_type.value,
|
||||
"description": template.description,
|
||||
"dimension_weights": template.dimension_weights,
|
||||
"assessment_cycle": template.assessment_cycle,
|
||||
"is_active": template.is_active,
|
||||
"created_at": template.created_at,
|
||||
"updated_at": template.updated_at,
|
||||
"indicators": indicators
|
||||
}
|
||||
|
||||
return {"code": 200, "message": "success", "data": response_data}
|
||||
|
||||
|
||||
@router.post("", summary="创建模板")
|
||||
async def create_template(
|
||||
template_data: IndicatorTemplateCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""创建模板(需要管理员或经理权限)"""
|
||||
# 检查编码是否已存在
|
||||
existing = await TemplateService.get_by_code(db, template_data.template_code)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="模板编码已存在")
|
||||
|
||||
template = await TemplateService.create(db, template_data)
|
||||
return {"code": 200, "message": "创建成功", "data": {"id": template.id}}
|
||||
|
||||
|
||||
@router.put("/{template_id}", summary="更新模板")
|
||||
async def update_template(
|
||||
template_id: int,
|
||||
template_data: IndicatorTemplateUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新模板(需要管理员或经理权限)"""
|
||||
template = await TemplateService.update(db, template_id, template_data)
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
return {"code": 200, "message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/{template_id}", summary="删除模板")
|
||||
async def delete_template(
|
||||
template_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""删除模板(需要管理员或经理权限)"""
|
||||
success = await TemplateService.delete(db, template_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
return {"code": 200, "message": "删除成功"}
|
||||
|
||||
|
||||
# ==================== 模板指标管理 ====================
|
||||
|
||||
@router.get("/{template_id}/indicators", summary="获取模板指标列表")
|
||||
async def get_template_indicators(
|
||||
template_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取模板指标列表"""
|
||||
indicators = await TemplateService.get_template_indicators(db, template_id)
|
||||
|
||||
result = []
|
||||
for ti in indicators:
|
||||
ind_dict = {
|
||||
"id": ti.id,
|
||||
"template_id": ti.template_id,
|
||||
"indicator_id": ti.indicator_id,
|
||||
"indicator_name": ti.indicator.name if ti.indicator else None,
|
||||
"indicator_code": ti.indicator.code if ti.indicator else None,
|
||||
"indicator_type": ti.indicator.indicator_type.value if ti.indicator else None,
|
||||
"bs_dimension": ti.indicator.bs_dimension.value if ti.indicator else None,
|
||||
"category": ti.category,
|
||||
"target_value": float(ti.target_value) if ti.target_value else None,
|
||||
"target_unit": ti.target_unit,
|
||||
"weight": float(ti.weight),
|
||||
"scoring_method": ti.scoring_method,
|
||||
"scoring_params": ti.scoring_params,
|
||||
"sort_order": ti.sort_order,
|
||||
"remark": ti.remark,
|
||||
"created_at": ti.created_at,
|
||||
"updated_at": ti.updated_at
|
||||
}
|
||||
result.append(ind_dict)
|
||||
|
||||
return {"code": 200, "message": "success", "data": result}
|
||||
|
||||
|
||||
@router.post("/{template_id}/indicators", summary="添加模板指标")
|
||||
async def add_template_indicator(
|
||||
template_id: int,
|
||||
indicator_data: TemplateIndicatorCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""添加模板指标"""
|
||||
ti = await TemplateService.add_indicator(db, template_id, indicator_data)
|
||||
if not ti:
|
||||
raise HTTPException(status_code=400, detail="添加失败,模板不存在或指标已存在")
|
||||
return {"code": 200, "message": "添加成功", "data": {"id": ti.id}}
|
||||
|
||||
|
||||
@router.put("/{template_id}/indicators/{indicator_id}", summary="更新模板指标")
|
||||
async def update_template_indicator(
|
||||
template_id: int,
|
||||
indicator_id: int,
|
||||
indicator_data: TemplateIndicatorUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""更新模板指标"""
|
||||
ti = await TemplateService.update_indicator(db, template_id, indicator_id, indicator_data)
|
||||
if not ti:
|
||||
raise HTTPException(status_code=404, detail="模板指标不存在")
|
||||
return {"code": 200, "message": "更新成功"}
|
||||
|
||||
|
||||
@router.delete("/{template_id}/indicators/{indicator_id}", summary="移除模板指标")
|
||||
async def remove_template_indicator(
|
||||
template_id: int,
|
||||
indicator_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""移除模板指标"""
|
||||
success = await TemplateService.remove_indicator(db, template_id, indicator_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="模板指标不存在")
|
||||
return {"code": 200, "message": "移除成功"}
|
||||
|
||||
|
||||
@router.post("/{template_id}/indicators/batch", summary="批量添加模板指标")
|
||||
async def batch_add_template_indicators(
|
||||
template_id: int,
|
||||
indicators_data: List[TemplateIndicatorCreate],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: Annotated[User, Depends(get_current_manager_user)] = None
|
||||
):
|
||||
"""批量添加模板指标"""
|
||||
added_count = 0
|
||||
for idx, ind_data in enumerate(indicators_data):
|
||||
ind_data.sort_order = ind_data.sort_order or idx
|
||||
ti = await TemplateService.add_indicator(db, template_id, ind_data)
|
||||
if ti:
|
||||
added_count += 1
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"message": f"成功添加 {added_count} 个指标",
|
||||
"data": {"added_count": added_count}
|
||||
}
|
||||
Reference in New Issue
Block a user