Spaces:
Sleeping
Sleeping
| """ | |
| Expense API Endpoints - Ticket expense management | |
| Provides endpoints for: | |
| - Creating expenses with location verification | |
| - Listing and retrieving expenses | |
| - Approval/rejection workflow | |
| - Payment routing details (who, how, where to send money) | |
| - Marking expenses as paid | |
| - Statistics and reporting | |
| """ | |
| from fastapi import APIRouter, Depends, HTTPException, status, Query, BackgroundTasks | |
| from sqlalchemy.orm import Session | |
| from typing import Optional, List | |
| from uuid import UUID | |
| from app.api.deps import get_db, get_current_user | |
| from app.models.user import User | |
| from app.schemas.ticket_expense import ( | |
| TicketExpenseCreate, | |
| TicketExpenseUpdate, | |
| TicketExpenseApprove, | |
| TicketExpenseMarkPaid, | |
| TicketExpensePaymentDetails, | |
| TicketExpenseResponse, | |
| TicketExpenseListResponse, | |
| TicketExpenseStats, | |
| ) | |
| from app.services.expense_service import ExpenseService | |
| from app.core.exceptions import NotFoundException, ValidationException | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/expenses", tags=["Expenses"]) | |
| # ============================================ | |
| # CREATE EXPENSE | |
| # ============================================ | |
| def create_expense( | |
| data: TicketExpenseCreate, | |
| current_user: User = Depends(get_current_user), | |
| db: Session = Depends(get_db) | |
| ): | |
| """Create a new expense""" | |
| try: | |
| expense = ExpenseService.create_expense(db, data, current_user.id) | |
| # Add user names for response | |
| response = TicketExpenseResponse.model_validate(expense) | |
| response.incurred_by_user_name = expense.incurred_by_user.full_name if expense.incurred_by_user else None | |
| return response | |
| except NotFoundException as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| except ValidationException as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| # ============================================ | |
| # LIST EXPENSES | |
| # ============================================ | |
| def list_expenses( | |
| ticket_id: Optional[UUID] = Query(None, description="Filter by ticket"), | |
| assignment_id: Optional[UUID] = Query(None, description="Filter by assignment"), | |
| incurred_by_user_id: Optional[UUID] = Query(None, description="Filter by user"), | |
| category: Optional[str] = Query(None, description="Filter by category"), | |
| is_approved: Optional[bool] = Query(None, description="Filter by approval status"), | |
| is_paid: Optional[bool] = Query(None, description="Filter by payment status"), | |
| page: int = Query(1, ge=1, description="Page number"), | |
| page_size: int = Query(50, ge=1, le=100, description="Items per page"), | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_user) | |
| ): | |
| """List expenses with filters""" | |
| skip = (page - 1) * page_size | |
| expenses, total = ExpenseService.list_expenses( | |
| db, | |
| ticket_id=ticket_id, | |
| assignment_id=assignment_id, | |
| incurred_by_user_id=incurred_by_user_id, | |
| category=category, | |
| is_approved=is_approved, | |
| is_paid=is_paid, | |
| skip=skip, | |
| limit=page_size | |
| ) | |
| # Add user names | |
| expense_responses = [] | |
| for expense in expenses: | |
| response = TicketExpenseResponse.model_validate(expense) | |
| response.incurred_by_user_name = expense.incurred_by_user.full_name if expense.incurred_by_user else None | |
| response.approved_by_user_name = expense.approved_by_user.full_name if expense.approved_by_user else None | |
| response.paid_to_user_name = expense.paid_to_user.full_name if expense.paid_to_user else None | |
| expense_responses.append(response) | |
| pages = (total + page_size - 1) // page_size | |
| return TicketExpenseListResponse( | |
| expenses=expense_responses, | |
| total=total, | |
| page=page, | |
| page_size=page_size, | |
| pages=pages | |
| ) | |
| # ============================================ | |
| # GET EXPENSE | |
| # ============================================ | |
| def get_expense( | |
| expense_id: UUID, | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_user) | |
| ): | |
| """Get expense by ID""" | |
| try: | |
| expense = ExpenseService.get_expense(db, expense_id) | |
| response = TicketExpenseResponse.model_validate(expense) | |
| response.incurred_by_user_name = expense.incurred_by_user.full_name if expense.incurred_by_user else None | |
| response.approved_by_user_name = expense.approved_by_user.full_name if expense.approved_by_user else None | |
| response.paid_to_user_name = expense.paid_to_user.full_name if expense.paid_to_user else None | |
| return response | |
| except NotFoundException as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| # ============================================ | |
| # UPDATE EXPENSE | |
| # ============================================ | |
| def update_expense( | |
| expense_id: UUID, | |
| data: TicketExpenseUpdate, | |
| current_user: User = Depends(get_current_user), | |
| db: Session = Depends(get_db) | |
| ): | |
| """Update expense""" | |
| try: | |
| expense = ExpenseService.update_expense(db, expense_id, data, current_user.id) | |
| response = TicketExpenseResponse.model_validate(expense) | |
| response.incurred_by_user_name = expense.incurred_by_user.full_name if expense.incurred_by_user else None | |
| return response | |
| except NotFoundException as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| except ValidationException as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| # ============================================ | |
| # APPROVE/REJECT EXPENSE | |
| # ============================================ | |
| def approve_expense( | |
| expense_id: UUID, | |
| data: TicketExpenseApprove, | |
| background_tasks: BackgroundTasks, | |
| current_user: User = Depends(get_current_user), | |
| db: Session = Depends(get_db) | |
| ): | |
| """Approve or reject expense""" | |
| try: | |
| expense = ExpenseService.approve_expense(db, expense_id, data, current_user.id, background_tasks) | |
| response = TicketExpenseResponse.model_validate(expense) | |
| response.incurred_by_user_name = expense.incurred_by_user.full_name if expense.incurred_by_user else None | |
| response.approved_by_user_name = expense.approved_by_user.full_name if expense.approved_by_user else None | |
| return response | |
| except NotFoundException as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| except ValidationException as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| # ============================================ | |
| # UPDATE PAYMENT DETAILS | |
| # ============================================ | |
| def update_payment_details( | |
| expense_id: UUID, | |
| data: TicketExpensePaymentDetails, | |
| current_user: User = Depends(get_current_user), | |
| db: Session = Depends(get_db) | |
| ): | |
| """Update payment routing details""" | |
| try: | |
| expense = ExpenseService.update_payment_details(db, expense_id, data) | |
| response = TicketExpenseResponse.model_validate(expense) | |
| response.incurred_by_user_name = expense.incurred_by_user.full_name if expense.incurred_by_user else None | |
| response.approved_by_user_name = expense.approved_by_user.full_name if expense.approved_by_user else None | |
| return response | |
| except NotFoundException as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| except ValidationException as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| # ============================================ | |
| # MARK AS PAID | |
| # ============================================ | |
| def mark_expense_paid( | |
| expense_id: UUID, | |
| data: TicketExpenseMarkPaid, | |
| background_tasks: BackgroundTasks, | |
| current_user: User = Depends(get_current_user), | |
| db: Session = Depends(get_db) | |
| ): | |
| """Mark expense as paid""" | |
| try: | |
| expense = ExpenseService.mark_paid(db, expense_id, data, background_tasks) | |
| response = TicketExpenseResponse.model_validate(expense) | |
| response.incurred_by_user_name = expense.incurred_by_user.full_name if expense.incurred_by_user else None | |
| response.approved_by_user_name = expense.approved_by_user.full_name if expense.approved_by_user else None | |
| response.paid_to_user_name = expense.paid_to_user.full_name if expense.paid_to_user else None | |
| return response | |
| except NotFoundException as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| except ValidationException as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| # ============================================ | |
| # STATISTICS | |
| # ============================================ | |
| def get_expense_stats( | |
| ticket_id: Optional[UUID] = Query(None, description="Filter by ticket"), | |
| assignment_id: Optional[UUID] = Query(None, description="Filter by assignment"), | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_user) | |
| ): | |
| """Get expense statistics""" | |
| stats = ExpenseService.get_expense_stats(db, ticket_id, assignment_id) | |
| return TicketExpenseStats(**stats) | |
| # ============================================ | |
| # DELETE EXPENSE | |
| # ============================================ | |
| def delete_expense( | |
| expense_id: UUID, | |
| current_user: User = Depends(get_current_user), | |
| db: Session = Depends(get_db) | |
| ): | |
| """Delete expense""" | |
| try: | |
| ExpenseService.delete_expense(db, expense_id, current_user.id) | |
| return None | |
| except NotFoundException as e: | |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) | |
| except ValidationException as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |