"""Deal API endpoints.""" from __future__ import annotations from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_db from app.core.deps import get_current_user from app.models.deal import DealStage from app.models.user import User from app.schemas.deal import ( DealCreate, DealOut, DealPipelineOut, DealStageUpdate, DealUpdate, ) from app.services.deal_service import ( InvalidAccount, create_deal as svc_create_deal, get_deal as svc_get_deal, get_pipeline, list_deals as svc_list_deals, soft_delete_deal as svc_soft_delete_deal, update_deal as svc_update_deal, update_stage as svc_update_stage, ) router = APIRouter(prefix="/deals", tags=["deals"]) @router.post( "/", response_model=DealOut, status_code=status.HTTP_201_CREATED, summary="Create a new deal", ) async def create_deal( payload: DealCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> DealOut: try: deal = await svc_create_deal( db, payload, org_id=current_user.org_id, owner_id=current_user.id ) except InvalidAccount as e: raise HTTPException(status_code=404, detail=str(e)) from e return DealOut.model_validate(deal) @router.get( "/", response_model=list[DealOut], summary="List deals (paginated, filterable)", ) async def list_deals( skip: int = Query(0, ge=0), limit: int = Query(20, ge=1, le=100), stage: Optional[DealStage] = None, owner_id: Optional[int] = None, account_id: Optional[int] = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> list[DealOut]: deals = await svc_list_deals( db, org_id=current_user.org_id, skip=skip, limit=limit, stage=stage.value if stage else None, owner_id=owner_id, account_id=account_id, ) return [DealOut.model_validate(d) for d in deals] @router.get( "/pipeline", response_model=list[DealPipelineOut], summary="Get pipeline view (deals grouped by stage)", ) async def get_pipeline_endpoint( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> list[DealPipelineOut]: pipeline = await get_pipeline(db, org_id=current_user.org_id) out: list[DealPipelineOut] = [] for item in pipeline: out.append( DealPipelineOut( stage=DealStage(item["stage"]), count=int(item["count"]), total_value=float(item["total_value"]), ) ) return out @router.get( "/{deal_id}", response_model=DealOut, summary="Get one deal", ) async def get_deal( deal_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> DealOut: deal = await svc_get_deal(db, deal_id, org_id=current_user.org_id) if deal is None: raise HTTPException(status_code=404, detail="Deal not found") return DealOut.model_validate(deal) @router.patch( "/{deal_id}", response_model=DealOut, summary="Update a deal (does NOT change stage)", ) async def update_deal( deal_id: int, payload: DealUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> DealOut: deal = await svc_get_deal(db, deal_id, org_id=current_user.org_id) if deal is None: raise HTTPException(status_code=404, detail="Deal not found") updated = await svc_update_deal(db, deal, payload) return DealOut.model_validate(updated) @router.patch( "/{deal_id}/stage", response_model=DealOut, summary="Change a deal's stage (creates DealStageHistory entry)", ) async def update_deal_stage( deal_id: int, payload: DealStageUpdate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> DealOut: deal = await svc_get_deal(db, deal_id, org_id=current_user.org_id) if deal is None: raise HTTPException(status_code=404, detail="Deal not found") updated = await svc_update_stage( db, deal, payload.stage, changed_by=current_user.id, reason=payload.reason ) return DealOut.model_validate(updated) @router.delete( "/{deal_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response, summary="Soft-delete a deal", ) async def delete_deal( deal_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ) -> Response: deal = await svc_get_deal(db, deal_id, org_id=current_user.org_id) if deal is None: raise HTTPException(status_code=404, detail="Deal not found") await svc_soft_delete_deal(db, deal) return Response(status_code=status.HTTP_204_NO_CONTENT) __all__ = ["router"]