feat: 增强员工管理与报销单全流程功能

- 新增员工Excel导入服务(employee_spreadsheet)及导入/导出API端点
- 员工服务增加批量创建、邮箱唯一校验、组织架构关联等能力
- 报销单提交补充身份回填、部门信息透传及预审结果展示优化
- 认证流程增加部门信息(departmentName)并在schema中同步扩展
- 用户Agent服务增加部门关联与报销单回填逻辑
- 前端员工管理页面全面重构,新增导入导出、搜索过滤、分页等功能
- 前端审批中心、审计、差旅报销等视图交互与样式优化
- 新增TableLoadingState共享组件及员工导入测试用例
This commit is contained in:
caoxiaozhu
2026-05-20 14:21:56 +08:00
parent 57957d11a0
commit d7e98a58b9
46 changed files with 4022 additions and 305 deletions

View File

@@ -2,12 +2,19 @@ from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query, status
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.schemas.common import ErrorResponse
from app.schemas.employee import EmployeeCreate, EmployeeMetaRead, EmployeeRead, EmployeeUpdate
from app.schemas.employee import (
EmployeeCreate,
EmployeeImportResultRead,
EmployeeMetaRead,
EmployeeRead,
EmployeeUpdate,
)
from app.services.employee import EmployeeService
router = APIRouter()
@@ -44,6 +51,67 @@ def list_employees(
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,