From b6105295bb9e9e370955c706de2313497a45e2f0 Mon Sep 17 00:00:00 2001 From: v6ole Date: Sun, 12 Jul 2026 13:05:49 +0800 Subject: [PATCH] feat: add Leave API endpoints + frontend API module Co-Authored-By: Claude --- backend/app/api/leaves.py | 127 +++++++++++++++++++++++++++++++++++++ backend/app/main.py | 3 +- frontend/src/api/leaves.ts | 19 ++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 backend/app/api/leaves.py create mode 100644 frontend/src/api/leaves.ts diff --git a/backend/app/api/leaves.py b/backend/app/api/leaves.py new file mode 100644 index 0000000..fd7e9d4 --- /dev/null +++ b/backend/app/api/leaves.py @@ -0,0 +1,127 @@ +from uuid import UUID +from fastapi import APIRouter, Depends, Query, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from app.database import get_db +from app.middleware.auth import get_current_user, RoleChecker +from app.schemas.leave import LeaveCreate, LeaveUpdate +from app.services import leaves as leave_service + +router = APIRouter(prefix="/leaves", tags=["Leaves"]) + + +@router.get("") +async def list_leaves( + manager_id: str | None = Query(None), + status: str | None = Query(None), + page: int = Query(1, ge=1), + page_size: int = Query(25, ge=1, le=100), + db: AsyncSession = Depends(get_db), + current_user: dict = Depends(get_current_user), +): + """List leave records. Managers see only their own.""" + return await leave_service.get_leaves( + db=db, + user_id=current_user["user_id"], + role=current_user["role"], + manager_id=manager_id, + status=status, + page=page, + page_size=page_size, + ) + + +@router.post("") +async def create_leave( + data: LeaveCreate, + db: AsyncSession = Depends(get_db), + current_user: dict = Depends(get_current_user), +): + """Create a leave record. Director can create for anyone; manager only for self.""" + try: + leave = await leave_service.create_leave( + db=db, + data=data.model_dump(), + submitted_by=UUID(current_user["user_id"]), + role=current_user["role"], + ) + return { + "id": str(leave.id), + "manager_id": str(leave.manager_id), + "leave_type": leave.leave_type, + "start_date": str(leave.start_date), + "end_date": str(leave.end_date), + "reason": leave.reason, + "submitted_by": str(leave.submitted_by), + } + except PermissionError as e: + raise HTTPException(status_code=403, detail=str(e)) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@router.put("/{leave_id}") +async def update_leave( + leave_id: UUID, + data: LeaveUpdate, + db: AsyncSession = Depends(get_db), + current_user: dict = Depends(get_current_user), +): + """Update a leave record.""" + try: + leave = await leave_service.update_leave( + db=db, + leave_id=leave_id, + data=data.model_dump(exclude_none=True), + user_id=UUID(current_user["user_id"]), + role=current_user["role"], + ) + return { + "id": str(leave.id), + "manager_id": str(leave.manager_id), + "leave_type": leave.leave_type, + "start_date": str(leave.start_date), + "end_date": str(leave.end_date), + "reason": leave.reason, + } + except PermissionError as e: + raise HTTPException(status_code=403, detail=str(e)) + except ValueError as e: + raise HTTPException(status_code=404 if "不存在" in str(e) else 400, detail=str(e)) + + +@router.delete("/{leave_id}") +async def delete_leave( + leave_id: UUID, + db: AsyncSession = Depends(get_db), + current_user: dict = Depends(get_current_user), +): + """Delete a leave record.""" + try: + ok = await leave_service.delete_leave( + db=db, + leave_id=leave_id, + user_id=UUID(current_user["user_id"]), + role=current_user["role"], + ) + if not ok: + raise HTTPException(status_code=404, detail="请假记录不存在") + return {"status": "deleted"} + except PermissionError as e: + raise HTTPException(status_code=403, detail=str(e)) + + +@router.get("/overview") +async def leave_overview( + reference_date: str | None = Query(None), + db: AsyncSession = Depends(get_db), + current_user: dict = Depends(get_current_user), +): + """Get weekly leave overview for dashboard card.""" + from datetime import date + ref = date.fromisoformat(reference_date) if reference_date else None + return await leave_service.get_leave_overview( + db=db, + user_id=current_user["user_id"], + role=current_user["role"], + reference_date=ref, + ) diff --git a/backend/app/main.py b/backend/app/main.py index db2f63d..606c97e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -6,7 +6,7 @@ from app.config import settings from app.database import engine, Base, async_session from app.api import router as api_router from app.api import auth, users, customers, visits, work_plans, mini_business, key_visits -from app.api import dashboard, upload, export, import_data, wecom, daily_notes, ai_summary, system_config +from app.api import dashboard, upload, export, import_data, wecom, daily_notes, ai_summary, system_config, leaves from app.models.system_config import SystemConfig from app.services.scheduler_manager import start_scheduler, shutdown_scheduler @@ -119,6 +119,7 @@ app.include_router(work_plans.router, prefix="/api") app.include_router(mini_business.router, prefix="/api") app.include_router(key_visits.router, prefix="/api") app.include_router(dashboard.router, prefix="/api") +app.include_router(leaves.router, prefix="/api") app.include_router(upload.router, prefix="/api") app.include_router(export.router, prefix="/api") app.include_router(import_data.router, prefix="/api") diff --git a/frontend/src/api/leaves.ts b/frontend/src/api/leaves.ts new file mode 100644 index 0000000..30a6f6f --- /dev/null +++ b/frontend/src/api/leaves.ts @@ -0,0 +1,19 @@ +import api from './index' + +export const leavesApi = { + list(params?: Record) { + return api.get('/leaves/', { params }) + }, + create(data: Record) { + return api.post('/leaves/', data) + }, + update(id: string, data: Record) { + return api.put(`/leaves/${id}`, data) + }, + delete(id: string) { + return api.delete(`/leaves/${id}`) + }, + overview(params?: Record) { + return api.get('/leaves/overview', { params }) + }, +}