from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app.api.deps import CurrentUserContext, get_current_user, get_db from app.schemas.common import ErrorResponse from app.schemas.reimbursement import ( ExpenseClaimActionResponse, ExpenseClaimItemUpdate, ExpenseClaimRead, ReimbursementCreate, ReimbursementRead, ) from app.services.expense_claims import ExpenseClaimService from app.services.reimbursement import ReimbursementService router = APIRouter() DbSession = Annotated[Session, Depends(get_db)] CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)] @router.get( "", response_model=list[ReimbursementRead], summary="查询报销申请列表", description="返回当前系统中的报销申请列表。", ) def list_reimbursements(db: DbSession) -> list[ReimbursementRead]: return ReimbursementService(db).list_reimbursements() @router.post( "", response_model=ReimbursementRead, status_code=status.HTTP_201_CREATED, summary="创建报销申请", description="创建一条新的报销申请记录,初始状态为 `draft`。", ) def create_reimbursement(payload: ReimbursementCreate, db: DbSession) -> ReimbursementRead: return ReimbursementService(db).create_reimbursement(payload) @router.get( "/claims", response_model=list[ExpenseClaimRead], summary="查询个人报销单列表", description="返回当前登录用户可见的真实个人报销单据列表。", ) def list_expense_claims(db: DbSession, current_user: CurrentUser) -> list[ExpenseClaimRead]: return ExpenseClaimService(db).list_claims(current_user) @router.get( "/claims/{claim_id}", response_model=ExpenseClaimRead, summary="读取个人报销单详情", description="根据报销单主键读取真实报销详情与费用明细。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "报销单不存在。", } }, ) def get_expense_claim(claim_id: str, db: DbSession, current_user: CurrentUser) -> ExpenseClaimRead: claim = ExpenseClaimService(db).get_claim(claim_id, current_user) if claim is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found") return claim @router.patch( "/claims/{claim_id}/items/{item_id}", response_model=ExpenseClaimRead, summary="更新草稿费用明细", description="更新草稿报销单中的单条费用明细。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "报销单或费用明细不存在。", }, status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse, "description": "草稿状态校验失败或字段校验失败。", }, }, ) def update_expense_claim_item( claim_id: str, item_id: str, payload: ExpenseClaimItemUpdate, db: DbSession, current_user: CurrentUser, ) -> ExpenseClaimRead: service = ExpenseClaimService(db) try: claim = service.update_claim_item( claim_id=claim_id, item_id=item_id, payload=payload, current_user=current_user, ) except LookupError as error: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error except ValueError as error: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error if claim is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found") return claim @router.post( "/claims/{claim_id}/submit", response_model=ExpenseClaimRead, summary="提交个人报销草稿", description="校验草稿信息完整性后,将报销单提交到审批流程。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "报销单不存在。", }, status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse, "description": "草稿信息不完整或状态不允许提交。", }, }, ) def submit_expense_claim(claim_id: str, db: DbSession, current_user: CurrentUser) -> ExpenseClaimRead: service = ExpenseClaimService(db) try: claim = service.submit_claim(claim_id, current_user) except ValueError as error: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error if claim is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found") return claim @router.delete( "/claims/{claim_id}", response_model=ExpenseClaimActionResponse, summary="删除个人报销草稿", description="删除当前登录用户可见的草稿报销单。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "报销单不存在。", }, status.HTTP_400_BAD_REQUEST: { "model": ErrorResponse, "description": "仅草稿状态允许删除。", }, }, ) def delete_expense_claim(claim_id: str, db: DbSession, current_user: CurrentUser) -> ExpenseClaimActionResponse: service = ExpenseClaimService(db) try: claim = service.delete_claim(claim_id, current_user) except ValueError as error: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error if claim is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found") return ExpenseClaimActionResponse( message=f"{claim.claim_no} 草稿已删除。", claim_id=claim.id, status="deleted", ) @router.get( "/{request_id}", response_model=ReimbursementRead, summary="读取报销申请详情", description="根据报销申请主键读取单据详情。", responses={ status.HTTP_404_NOT_FOUND: { "model": ErrorResponse, "description": "报销申请不存在。", } }, ) def get_reimbursement(request_id: str, db: DbSession) -> ReimbursementRead: request = ReimbursementService(db).get_reimbursement(request_id) if request is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Request not found") return request