"""顾问管理接口(需求第 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}")