96 lines
3.8 KiB
Python
96 lines
3.8 KiB
Python
"""顾问管理接口(需求第 5 节扩展模块)。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Query
|
|
|
|
from app.core.deps import DbSession, ReadAccount, WriteAccount
|
|
from app.core.exceptions import BusinessError, ConflictError
|
|
from app.core.response import Resp, ok
|
|
from app.dao.advisor_dao import AdvisorDao
|
|
from app.model import Advisor
|
|
from app.schema.advisor_schema import AdvisorCreate, AdvisorOut, AdvisorUpdate
|
|
from app.schema.common import PageResult
|
|
|
|
router = APIRouter(prefix="/advisors", tags=["2.8 顾问管理(扩展)"])
|
|
|
|
|
|
def _to_out(advisor, count: int | None = None) -> AdvisorOut:
|
|
out = AdvisorOut.model_validate(advisor)
|
|
if count is not None:
|
|
out.student_count = count
|
|
return out
|
|
|
|
|
|
@router.get("", response_model=Resp[PageResult[AdvisorOut]], summary="顾问列表")
|
|
def list_advisors(
|
|
db: DbSession,
|
|
_: ReadAccount,
|
|
page: Annotated[int, Query(ge=1)] = 1,
|
|
page_size: Annotated[int, Query(ge=1, le=200)] = 10,
|
|
keyword: Annotated[str | None, Query(description="姓名/编号/电话")] = None,
|
|
dept: Annotated[str | None, Query()] = None,
|
|
gender: Annotated[int | None, Query(ge=1, le=2)] = None,
|
|
order_by: Annotated[str, Query()] = "id",
|
|
order: Annotated[str, Query(pattern="^(asc|desc)$")] = "desc",
|
|
):
|
|
stmt = AdvisorDao.build_stmt(keyword=keyword, dept=dept, gender=gender, order_by=order_by, order=order)
|
|
items, total, page, pages = AdvisorDao.paginate(db, stmt, page, page_size)
|
|
counts = AdvisorDao.student_count_map(db)
|
|
return ok({
|
|
"total": total, "page": page, "page_size": page_size, "pages": pages,
|
|
"items": [_to_out(a, counts.get(a.id, 0)) for a in items],
|
|
})
|
|
|
|
|
|
@router.post("", response_model=Resp[AdvisorOut], summary="新增顾问")
|
|
def create_advisor(db: DbSession, _: WriteAccount, payload: AdvisorCreate):
|
|
advisor_no = (payload.advisor_no or "").strip() or AdvisorDao.next_advisor_no(db)
|
|
if AdvisorDao.first_by(db, advisor_no=advisor_no):
|
|
raise ConflictError(f"顾问编号 {advisor_no} 已存在")
|
|
advisor = Advisor(
|
|
advisor_no=advisor_no,
|
|
name=payload.name,
|
|
gender=payload.gender,
|
|
phone=payload.phone,
|
|
email=payload.email,
|
|
dept=payload.dept,
|
|
remark=payload.remark,
|
|
)
|
|
db.add(advisor)
|
|
db.commit()
|
|
db.refresh(advisor)
|
|
return ok(_to_out(advisor, 0), msg=f"顾问 {advisor.name} 已创建,编号 {advisor.advisor_no}")
|
|
|
|
|
|
@router.get("/{advisor_id}", response_model=Resp[AdvisorOut], summary="顾问详情")
|
|
def get_advisor(db: DbSession, _: ReadAccount, advisor_id: int):
|
|
advisor = AdvisorDao.get_or_404(db, advisor_id, "顾问")
|
|
counts = AdvisorDao.student_count_map(db)
|
|
return ok(_to_out(advisor, counts.get(advisor.id, 0)))
|
|
|
|
|
|
@router.put("/{advisor_id}", response_model=Resp[AdvisorOut], summary="更新顾问")
|
|
def update_advisor(db: DbSession, _: WriteAccount, advisor_id: int, payload: AdvisorUpdate):
|
|
advisor = AdvisorDao.get_or_404(db, advisor_id, "顾问")
|
|
for key, value in payload.model_dump(exclude_unset=True).items():
|
|
if value is not None:
|
|
setattr(advisor, key, value)
|
|
db.commit()
|
|
db.refresh(advisor)
|
|
counts = AdvisorDao.student_count_map(db)
|
|
return ok(_to_out(advisor, counts.get(advisor.id, 0)), msg="更新成功")
|
|
|
|
|
|
@router.delete("/{advisor_id}", summary="逻辑删除顾问(名下还有学生时拒绝)")
|
|
def delete_advisor(db: DbSession, _: WriteAccount, advisor_id: int):
|
|
advisor = AdvisorDao.get_or_404(db, advisor_id, "顾问")
|
|
count = AdvisorDao.student_count_map(db).get(advisor_id, 0)
|
|
if count:
|
|
raise BusinessError(f"顾问「{advisor.name}」名下还有 {count} 名学生,请先转交后再删除")
|
|
advisor.soft_delete()
|
|
db.commit()
|
|
return ok(msg=f"已删除顾问 {advisor.name}")
|