Start integrating the onboarding flow with backend 3

This commit is contained in:
Urtzi Alfaro
2025-09-04 23:19:53 +02:00
parent 9eedc2e5f2
commit 0faaa25e58
26 changed files with 314 additions and 767 deletions

View File

@@ -12,7 +12,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Path, status
from sqlalchemy.ext.asyncio import AsyncSession
import structlog
from shared.auth.decorators import get_current_user_dep, get_current_tenant_id_dep
from shared.auth.decorators import get_current_user_dep
from app.core.database import get_db
from app.services.performance_service import PerformanceTrackingService, AlertService
from app.services.dashboard_service import DashboardService
@@ -54,19 +54,12 @@ async def calculate_supplier_performance(
period: PerformancePeriod = Query(...),
period_start: datetime = Query(...),
period_end: datetime = Query(...),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
performance_service: PerformanceTrackingService = Depends(get_performance_service),
db: AsyncSession = Depends(get_db)
):
"""Calculate performance metrics for a supplier"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
metric = await performance_service.calculate_supplier_performance(
db, supplier_id, tenant_id, period, period_start, period_end
)
@@ -104,18 +97,11 @@ async def get_supplier_performance_metrics(
date_from: Optional[datetime] = Query(None),
date_to: Optional[datetime] = Query(None),
limit: int = Query(50, ge=1, le=500),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Get performance metrics for a supplier"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
# TODO: Implement get_supplier_performance_metrics in service
# For now, return empty list
metrics = []
@@ -139,19 +125,12 @@ async def get_supplier_performance_metrics(
async def evaluate_performance_alerts(
tenant_id: UUID = Path(...),
supplier_id: Optional[UUID] = Query(None, description="Specific supplier to evaluate"),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
alert_service: AlertService = Depends(get_alert_service),
db: AsyncSession = Depends(get_db)
):
"""Evaluate and create performance-based alerts"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
alerts = await alert_service.evaluate_performance_alerts(db, tenant_id, supplier_id)
logger.info("Performance alerts evaluated",
@@ -179,18 +158,11 @@ async def get_supplier_alerts(
date_from: Optional[datetime] = Query(None),
date_to: Optional[datetime] = Query(None),
limit: int = Query(50, ge=1, le=500),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Get supplier alerts with filtering"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
# TODO: Implement get_supplier_alerts in service
# For now, return empty list
alerts = []
@@ -212,18 +184,11 @@ async def update_alert(
alert_update: AlertUpdate,
tenant_id: UUID = Path(...),
alert_id: UUID = Path(...),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Update an alert (acknowledge, resolve, etc.)"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
# TODO: Implement update_alert in service
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
@@ -248,19 +213,12 @@ async def get_performance_dashboard_summary(
tenant_id: UUID = Path(...),
date_from: Optional[datetime] = Query(None),
date_to: Optional[datetime] = Query(None),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
dashboard_service: DashboardService = Depends(get_dashboard_service),
db: AsyncSession = Depends(get_db)
):
"""Get comprehensive performance dashboard summary"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
summary = await dashboard_service.get_performance_dashboard_summary(
db, tenant_id, date_from, date_to
)
@@ -285,19 +243,12 @@ async def get_supplier_performance_insights(
tenant_id: UUID = Path(...),
supplier_id: UUID = Path(...),
days_back: int = Query(30, ge=1, le=365),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
dashboard_service: DashboardService = Depends(get_dashboard_service),
db: AsyncSession = Depends(get_db)
):
"""Get detailed performance insights for a specific supplier"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
insights = await dashboard_service.get_supplier_performance_insights(
db, tenant_id, supplier_id, days_back
)
@@ -323,19 +274,12 @@ async def get_supplier_performance_insights(
async def get_performance_analytics(
tenant_id: UUID = Path(...),
period_days: int = Query(90, ge=1, le=365),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
dashboard_service: DashboardService = Depends(get_dashboard_service),
db: AsyncSession = Depends(get_db)
):
"""Get advanced performance analytics"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
analytics = await dashboard_service.get_performance_analytics(
db, tenant_id, period_days
)
@@ -359,19 +303,12 @@ async def get_performance_analytics(
@router.get("/tenants/{tenant_id}/business-model", response_model=BusinessModelInsights)
async def get_business_model_insights(
tenant_id: UUID = Path(...),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
dashboard_service: DashboardService = Depends(get_dashboard_service),
db: AsyncSession = Depends(get_db)
):
"""Get business model detection and insights"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
insights = await dashboard_service.get_business_model_insights(db, tenant_id)
logger.info("Business model insights retrieved",
@@ -395,19 +332,12 @@ async def get_alert_summary(
tenant_id: UUID = Path(...),
date_from: Optional[datetime] = Query(None),
date_to: Optional[datetime] = Query(None),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
dashboard_service: DashboardService = Depends(get_dashboard_service),
db: AsyncSession = Depends(get_db)
):
"""Get alert summary by type and severity"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
summary = await dashboard_service.get_alert_summary(db, tenant_id, date_from, date_to)
return summary
@@ -428,18 +358,11 @@ async def get_alert_summary(
async def generate_performance_report(
report_request: PerformanceReportRequest,
tenant_id: UUID = Path(...),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Generate a performance report"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
# TODO: Implement report generation
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
@@ -463,18 +386,11 @@ async def export_performance_data(
date_from: Optional[datetime] = Query(None),
date_to: Optional[datetime] = Query(None),
supplier_ids: Optional[List[UUID]] = Query(None),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Export performance data"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
if format.lower() not in ["json", "csv", "excel"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -502,17 +418,10 @@ async def export_performance_data(
@router.get("/tenants/{tenant_id}/config")
async def get_performance_config(
tenant_id: UUID = Path(...),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep)
):
"""Get performance tracking configuration"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
from app.core.config import settings
config = {
@@ -565,17 +474,10 @@ async def get_performance_config(
@router.get("/tenants/{tenant_id}/health")
async def get_performance_health(
tenant_id: UUID = Path(...),
current_tenant: str = Depends(get_current_tenant_id_dep),
current_user: dict = Depends(get_current_user_dep)
):
"""Get performance service health status"""
try:
if str(tenant_id) != current_tenant:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant data"
)
return {
"service": "suppliers-performance",
"status": "healthy",

View File

@@ -3,7 +3,7 @@
Supplier API endpoints
"""
from fastapi import APIRouter, Depends, HTTPException, Query, Path
from fastapi import APIRouter, Depends, HTTPException, Query, Path, Request
from typing import List, Optional
from uuid import UUID
import structlog
@@ -18,23 +18,22 @@ from app.schemas.suppliers import (
from shared.auth.decorators import get_current_user_dep
from typing import Dict, Any
router = APIRouter(prefix="/suppliers", tags=["suppliers"])
router = APIRouter(prefix="/tenants/{tenant_id}/suppliers", tags=["suppliers"])
logger = structlog.get_logger()
@router.post("", response_model=SupplierResponse)
@router.post("/", response_model=SupplierResponse)
async def create_supplier(
supplier_data: SupplierCreate,
tenant_id: str = Path(..., description="Tenant ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: Session = Depends(get_db)
):
"""Create a new supplier"""
# require_permissions(current_user, ["suppliers:create"])
try:
service = SupplierService(db)
supplier = await service.create_supplier(
tenant_id=current_user.tenant_id,
tenant_id=UUID(tenant_id),
supplier_data=supplier_data,
created_by=current_user.user_id
)
@@ -46,14 +45,15 @@ async def create_supplier(
raise HTTPException(status_code=500, detail="Failed to create supplier")
@router.get("", response_model=List[SupplierSummary])
@router.get("/", response_model=List[SupplierSummary])
async def list_suppliers(
tenant_id: str = Path(..., description="Tenant ID"),
search_term: Optional[str] = Query(None, description="Search term"),
supplier_type: Optional[str] = Query(None, description="Supplier type filter"),
status: Optional[str] = Query(None, description="Status filter"),
limit: int = Query(50, ge=1, le=1000, description="Number of results to return"),
offset: int = Query(0, ge=0, description="Number of results to skip"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: Session = Depends(get_db)
):
"""List suppliers with optional filters"""
@@ -69,7 +69,7 @@ async def list_suppliers(
offset=offset
)
suppliers = await service.search_suppliers(
tenant_id=current_user.tenant_id,
tenant_id=UUID(tenant_id),
search_params=search_params
)
return [SupplierSummary.from_orm(supplier) for supplier in suppliers]
@@ -80,7 +80,7 @@ async def list_suppliers(
@router.get("/statistics", response_model=SupplierStatistics)
async def get_supplier_statistics(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_id: str = Path(..., description="Tenant ID"),
db: Session = Depends(get_db)
):
"""Get supplier statistics for dashboard"""
@@ -88,7 +88,7 @@ async def get_supplier_statistics(
try:
service = SupplierService(db)
stats = await service.get_supplier_statistics(current_user.tenant_id)
stats = await service.get_supplier_statistics(UUID(tenant_id))
return SupplierStatistics(**stats)
except Exception as e:
logger.error("Error getting supplier statistics", error=str(e))
@@ -97,7 +97,7 @@ async def get_supplier_statistics(
@router.get("/active", response_model=List[SupplierSummary])
async def get_active_suppliers(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_id: str = Path(..., description="Tenant ID"),
db: Session = Depends(get_db)
):
"""Get all active suppliers"""
@@ -105,7 +105,7 @@ async def get_active_suppliers(
try:
service = SupplierService(db)
suppliers = await service.get_active_suppliers(current_user.tenant_id)
suppliers = await service.get_active_suppliers(UUID(tenant_id))
return [SupplierSummary.from_orm(supplier) for supplier in suppliers]
except Exception as e:
logger.error("Error getting active suppliers", error=str(e))
@@ -114,8 +114,8 @@ async def get_active_suppliers(
@router.get("/top", response_model=List[SupplierSummary])
async def get_top_suppliers(
tenant_id: str = Path(..., description="Tenant ID"),
limit: int = Query(10, ge=1, le=50, description="Number of top suppliers to return"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: Session = Depends(get_db)
):
"""Get top performing suppliers"""
@@ -123,7 +123,7 @@ async def get_top_suppliers(
try:
service = SupplierService(db)
suppliers = await service.get_top_suppliers(current_user.tenant_id, limit)
suppliers = await service.get_top_suppliers(UUID(tenant_id), limit)
return [SupplierSummary.from_orm(supplier) for supplier in suppliers]
except Exception as e:
logger.error("Error getting top suppliers", error=str(e))
@@ -132,8 +132,8 @@ async def get_top_suppliers(
@router.get("/pending-review", response_model=List[SupplierSummary])
async def get_suppliers_needing_review(
tenant_id: str = Path(..., description="Tenant ID"),
days_since_last_order: int = Query(30, ge=1, le=365, description="Days since last order"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: Session = Depends(get_db)
):
"""Get suppliers that may need performance review"""
@@ -142,7 +142,7 @@ async def get_suppliers_needing_review(
try:
service = SupplierService(db)
suppliers = await service.get_suppliers_needing_review(
current_user.tenant_id, days_since_last_order
UUID(tenant_id), days_since_last_order
)
return [SupplierSummary.from_orm(supplier) for supplier in suppliers]
except Exception as e:
@@ -153,7 +153,7 @@ async def get_suppliers_needing_review(
@router.get("/{supplier_id}", response_model=SupplierResponse)
async def get_supplier(
supplier_id: UUID = Path(..., description="Supplier ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_id: str = Path(..., description="Tenant ID"),
db: Session = Depends(get_db)
):
"""Get supplier by ID"""
@@ -166,10 +166,6 @@ async def get_supplier(
if not supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
# Check tenant access
if supplier.tenant_id != current_user.tenant_id:
raise HTTPException(status_code=403, detail="Access denied")
return SupplierResponse.from_orm(supplier)
except HTTPException:
raise
@@ -191,12 +187,10 @@ async def update_supplier(
try:
service = SupplierService(db)
# Check supplier exists and belongs to tenant
# Check supplier exists
existing_supplier = await service.get_supplier(supplier_id)
if not existing_supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
if existing_supplier.tenant_id != current_user.tenant_id:
raise HTTPException(status_code=403, detail="Access denied")
supplier = await service.update_supplier(
supplier_id=supplier_id,
@@ -220,7 +214,6 @@ async def update_supplier(
@router.delete("/{supplier_id}")
async def delete_supplier(
supplier_id: UUID = Path(..., description="Supplier ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: Session = Depends(get_db)
):
"""Delete supplier (soft delete)"""
@@ -229,12 +222,10 @@ async def delete_supplier(
try:
service = SupplierService(db)
# Check supplier exists and belongs to tenant
# Check supplier exists
existing_supplier = await service.get_supplier(supplier_id)
if not existing_supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
if existing_supplier.tenant_id != current_user.tenant_id:
raise HTTPException(status_code=403, detail="Access denied")
success = await service.delete_supplier(supplier_id)
if not success:
@@ -261,12 +252,10 @@ async def approve_supplier(
try:
service = SupplierService(db)
# Check supplier exists and belongs to tenant
# Check supplier exists
existing_supplier = await service.get_supplier(supplier_id)
if not existing_supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
if existing_supplier.tenant_id != current_user.tenant_id:
raise HTTPException(status_code=403, detail="Access denied")
if approval_data.action == "approve":
supplier = await service.approve_supplier(
@@ -299,7 +288,7 @@ async def approve_supplier(
@router.get("/types/{supplier_type}", response_model=List[SupplierSummary])
async def get_suppliers_by_type(
supplier_type: str = Path(..., description="Supplier type"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_id: str = Path(..., description="Tenant ID"),
db: Session = Depends(get_db)
):
"""Get suppliers by type"""
@@ -315,7 +304,7 @@ async def get_suppliers_by_type(
raise HTTPException(status_code=400, detail="Invalid supplier type")
service = SupplierService(db)
suppliers = await service.get_suppliers_by_type(current_user.tenant_id, type_enum)
suppliers = await service.get_suppliers_by_type(UUID(tenant_id), type_enum)
return [SupplierSummary.from_orm(supplier) for supplier in suppliers]
except HTTPException:
raise