test_task_crm/app/api/v1/deals/views.py

121 lines
4.5 KiB
Python

"""Deal API endpoints backed by DealService."""
from __future__ import annotations
from decimal import Decimal
from fastapi import APIRouter, Depends, HTTPException, Query, status
from app.api.deps import get_deal_repository, get_deal_service, get_organization_context
from app.models.deal import DealRead, DealStage, DealStatus
from app.repositories.deal_repo import DealRepository, DealAccessError, DealQueryParams
from app.services.deal_service import (
DealService,
DealStageTransitionError,
DealStatusValidationError,
DealUpdateData,
)
from app.services.organization_service import OrganizationContext
from .models import DealCreatePayload, DealUpdatePayload
router = APIRouter(prefix="/deals", tags=["deals"])
@router.get("/", response_model=list[DealRead])
async def list_deals(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
status_filter: list[str] | None = Query(default=None, alias="status"),
min_amount: Decimal | None = None,
max_amount: Decimal | None = None,
stage: str | None = None,
owner_id: int | None = None,
order_by: str | None = None,
order: str | None = Query(default="desc", pattern="^(asc|desc)$"),
context: OrganizationContext = Depends(get_organization_context),
repo: DealRepository = Depends(get_deal_repository),
) -> list[DealRead]:
"""List deals for the current organization with optional filters."""
try:
statuses_value = [DealStatus(value) for value in status_filter] if status_filter else None
stage_value = DealStage(stage) if stage else None
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid deal filter") from exc
params = DealQueryParams(
organization_id=context.organization_id,
page=page,
page_size=page_size,
statuses=statuses_value,
stage=stage_value,
owner_id=owner_id,
min_amount=min_amount,
max_amount=max_amount,
order_by=order_by,
order_desc=(order != "asc"),
)
try:
deals = await repo.list(params=params, role=context.role, user_id=context.user_id)
except DealAccessError as exc:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc
return [DealRead.model_validate(deal) for deal in deals]
@router.post("/", response_model=DealRead, status_code=status.HTTP_201_CREATED)
async def create_deal(
payload: DealCreatePayload,
context: OrganizationContext = Depends(get_organization_context),
service: DealService = Depends(get_deal_service),
) -> DealRead:
"""Create a new deal within the current organization."""
data = payload.to_domain(organization_id=context.organization_id, fallback_owner=context.user_id)
try:
deal = await service.create_deal(data, context=context)
except DealAccessError as exc:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc
except DealStatusValidationError as exc: # pragma: no cover - creation shouldn't trigger
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
return DealRead.model_validate(deal)
@router.patch("/{deal_id}", response_model=DealRead)
async def update_deal(
deal_id: int,
payload: DealUpdatePayload,
context: OrganizationContext = Depends(get_organization_context),
repo: DealRepository = Depends(get_deal_repository),
service: DealService = Depends(get_deal_service),
) -> DealRead:
"""Update deal status, stage, or financial data."""
existing = await repo.get(
deal_id,
organization_id=context.organization_id,
role=context.role,
user_id=context.user_id,
)
if existing is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Deal not found")
updates = DealUpdateData(
status=payload.status,
stage=payload.stage,
amount=payload.amount,
currency=payload.currency,
)
try:
deal = await service.update_deal(existing, updates, context=context)
except DealAccessError as exc:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc
except DealStageTransitionError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
except DealStatusValidationError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
return DealRead.model_validate(deal)