feat: implement deal summary and funnel endpoints with response models
This commit is contained in:
parent
22442bfd2e
commit
92bd3b6c00
|
|
@ -1,32 +1,95 @@
|
|||
"""Analytics API stubs (deal summary and funnel)."""
|
||||
"""Analytics API endpoints for summaries and funnels."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, status
|
||||
from decimal import Decimal
|
||||
|
||||
from app.api.deps import get_organization_context
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from pydantic import BaseModel, ConfigDict, field_serializer
|
||||
|
||||
from app.api.deps import get_analytics_service, get_organization_context
|
||||
from app.models.deal import DealStage, DealStatus
|
||||
from app.services.analytics_service import AnalyticsService, DealSummary, StageBreakdown
|
||||
from app.services.organization_service import OrganizationContext
|
||||
|
||||
|
||||
def _decimal_to_str(value: Decimal) -> str:
|
||||
normalized = value.normalize()
|
||||
return format(normalized, "f")
|
||||
|
||||
router = APIRouter(prefix="/analytics", tags=["analytics"])
|
||||
|
||||
|
||||
def _stub(endpoint: str) -> dict[str, str]:
|
||||
return {"detail": f"{endpoint} is not implemented yet"}
|
||||
class StatusSummaryModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
status: DealStatus
|
||||
count: int
|
||||
amount_sum: Decimal
|
||||
|
||||
@field_serializer("amount_sum")
|
||||
def serialize_amount_sum(self, value: Decimal) -> str:
|
||||
return _decimal_to_str(value)
|
||||
|
||||
|
||||
@router.get("/deals/summary", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
class WonStatisticsModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
count: int
|
||||
amount_sum: Decimal
|
||||
average_amount: Decimal
|
||||
|
||||
@field_serializer("amount_sum", "average_amount")
|
||||
def serialize_decimal_fields(self, value: Decimal) -> str:
|
||||
return _decimal_to_str(value)
|
||||
|
||||
|
||||
class NewDealsWindowModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
days: int
|
||||
count: int
|
||||
|
||||
|
||||
class DealSummaryResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
by_status: list[StatusSummaryModel]
|
||||
won: WonStatisticsModel
|
||||
new_deals: NewDealsWindowModel
|
||||
total_deals: int
|
||||
|
||||
|
||||
class StageBreakdownModel(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
stage: DealStage
|
||||
total: int
|
||||
by_status: dict[DealStatus, int]
|
||||
conversion_to_next: float | None
|
||||
|
||||
|
||||
class DealFunnelResponse(BaseModel):
|
||||
stages: list[StageBreakdownModel]
|
||||
|
||||
|
||||
@router.get("/deals/summary", response_model=DealSummaryResponse)
|
||||
async def deals_summary(
|
||||
days: int = Query(30, ge=1, le=180),
|
||||
context: OrganizationContext = Depends(get_organization_context),
|
||||
) -> dict[str, str]:
|
||||
"""Placeholder for aggregated deal statistics."""
|
||||
_ = (days, context)
|
||||
return _stub("GET /analytics/deals/summary")
|
||||
service: AnalyticsService = Depends(get_analytics_service),
|
||||
) -> DealSummaryResponse:
|
||||
"""Return aggregated deal statistics for the current organization."""
|
||||
|
||||
summary: DealSummary = await service.get_deal_summary(context.organization_id, days=days)
|
||||
return DealSummaryResponse.model_validate(summary)
|
||||
|
||||
|
||||
@router.get("/deals/funnel", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
@router.get("/deals/funnel", response_model=DealFunnelResponse)
|
||||
async def deals_funnel(
|
||||
context: OrganizationContext = Depends(get_organization_context),
|
||||
) -> dict[str, str]:
|
||||
"""Placeholder for funnel analytics."""
|
||||
_ = context
|
||||
return _stub("GET /analytics/deals/funnel")
|
||||
service: AnalyticsService = Depends(get_analytics_service),
|
||||
) -> DealFunnelResponse:
|
||||
"""Return funnel breakdown by stages and statuses."""
|
||||
|
||||
breakdowns: list[StageBreakdown] = await service.get_deal_funnel(context.organization_id)
|
||||
return DealFunnelResponse(stages=[StageBreakdownModel.model_validate(item) for item in breakdowns])
|
||||
|
|
|
|||
Loading…
Reference in New Issue