from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status from fastapi.responses import Response from sqlalchemy.orm import Session from app.api.deps import get_db from app.api.pagination import PageNumber, PageSize, page_payload, wants_page from app.schemas.common import ErrorResponse, PaginatedResponse from app.schemas.employee import ( EmployeeCreate, EmployeeImportResultRead, EmployeeMetaRead, EmployeeRead, EmployeeUpdate, ) from app.services.employee import EmployeeService from app.services.employee_pagination import EmployeePaginationService router = APIRouter() DbSession = Annotated[Session, Depends(get_db)] @router.get( "/meta", response_model=EmployeeMetaRead, summary="读取员工目录元数据", description="返回员工总数、状态汇总和可选角色列表,供员工管理页面初始化使用。", ) def get_employee_meta(db: DbSession) -> EmployeeMetaRead: return EmployeeService(db).get_employee_meta() @router.get( "", response_model=list[EmployeeRead] | PaginatedResponse[EmployeeRead], summary="查询员工列表", description="按状态和关键字筛选员工目录。", ) def list_employees( db: DbSession, status_filter: Annotated[ str | None, Query(alias="status", description="员工状态筛选值。"), ] = None, keyword: Annotated[ str | None, Query(description="姓名、工号、邮箱等关键字模糊查询。"), ] = None, page: PageNumber = None, page_size: PageSize = None, ) -> list[EmployeeRead] | PaginatedResponse[EmployeeRead]: if wants_page(page, page_size): return page_payload( EmployeePaginationService(db).list_employees_page( status=status_filter, keyword=keyword, page=page, page_size=page_size, ) ) return EmployeeService(db).list_employees(status=status_filter, keyword=keyword) @router.get( "/import-template", summary="下载员工导入模板", description="下载固定格式的员工 Excel 导入模板。", ) def download_employee_import_template(db: DbSession) -> Response: content = EmployeeService(db).build_import_template() return Response( content=content, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={ "Content-Disposition": 'attachment; filename="employee-import-template.xlsx"' }, ) @router.get( "/export", summary="导出员工 Excel", description="按筛选条件导出员工目录 Excel 文件。", ) def export_employees( db: DbSession, status_filter: Annotated[ str | None, Query(alias="status", description="员工状态筛选值。"), ] = None, keyword: Annotated[ str | None, Query(description="姓名、工号、邮箱等关键字模糊查询。"), ] = None, ) -> Response: content = EmployeeService(db).export_employees(status=status_filter, keyword=keyword) return Response( content=content, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": 'attachment; filename="employee-export.xlsx"'}, ) @router.post( "/import", response_model=EmployeeImportResultRead, summary="导入员工 Excel", description="按模板批量导入员工。全部校验通过后才写入数据库,任一行有错则整批不导入。", ) async def import_employees( db: DbSession, file: Annotated[UploadFile, File(description="待导入的员工 Excel 文件。")], ) -> EmployeeImportResultRead: filename = (file.filename or "").lower() if not filename.endswith(".xlsx"): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="当前仅支持上传 .xlsx 格式的员工表格。", ) content = await file.read() return EmployeeService(db).import_employees(content) @router.post( "", response_model=EmployeeRead, status_code=status.HTTP_201_CREATED, summary="创建员工", description="创建新的员工目录记录,并初始化基础角色与组织归属。", responses={ status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse, "description": "员工数据校验失败。", } }, ) def create_employee(payload: EmployeeCreate, db: DbSession) -> EmployeeRead: try: return EmployeeService(db).create_employee(payload) except ValueError as exc: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc @router.get( "/{employee_id}", response_model=EmployeeRead, summary="读取员工详情", description="根据员工主键读取员工完整档案信息。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "员工不存在。", } }, ) def get_employee(employee_id: str, db: DbSession) -> EmployeeRead: employee = EmployeeService(db).get_employee(employee_id) if employee is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Employee not found") return employee @router.patch( "/{employee_id}", response_model=EmployeeRead, summary="更新员工", description="更新员工基础信息、角色、密码等可维护字段。", responses={ status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse, "description": "请求字段不合法。", }, status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "员工不存在。", }, }, ) def update_employee(employee_id: str, payload: EmployeeUpdate, db: DbSession) -> EmployeeRead: try: return EmployeeService(db).update_employee(employee_id, payload) except LookupError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc @router.post( "/{employee_id}/disable", response_model=EmployeeRead, summary="停用员工", description="将员工状态切换为停用,阻止其继续登录系统。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "员工不存在。", } }, ) def disable_employee(employee_id: str, db: DbSession) -> EmployeeRead: try: return EmployeeService(db).disable_employee(employee_id) except LookupError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc @router.post( "/{employee_id}/enable", response_model=EmployeeRead, summary="启用员工", description="将停用员工恢复为在职状态,使其可以重新登录系统。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "员工不存在。", } }, ) def enable_employee(employee_id: str, db: DbSession) -> EmployeeRead: try: return EmployeeService(db).enable_employee(employee_id) except LookupError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc