Add role-based filtering and imporve code

This commit is contained in:
Urtzi Alfaro
2025-10-15 16:12:49 +02:00
parent 96ad5c6692
commit 8f9e9a7edc
158 changed files with 11033 additions and 1544 deletions

View File

@@ -4,7 +4,7 @@ Supplier Business Operations API endpoints (BUSINESS)
Handles approvals, status updates, active/top suppliers, and delivery/PO operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query, Path
from fastapi import APIRouter, Depends, HTTPException, Query, Path, Header
from typing import List, Optional, Dict, Any
from uuid import UUID
from datetime import datetime
@@ -25,6 +25,7 @@ from app.models.suppliers import SupplierType
from shared.auth.decorators import get_current_user_dep
from shared.routing import RouteBuilder
from shared.auth.access_control import require_user_role
from shared.security import create_audit_logger, AuditSeverity, AuditAction
# Create route builder for consistent URL structure
route_builder = RouteBuilder('suppliers')
@@ -32,6 +33,7 @@ route_builder = RouteBuilder('suppliers')
router = APIRouter(tags=["supplier-operations"])
logger = structlog.get_logger()
audit_logger = create_audit_logger("suppliers-service")
# ===== Supplier Operations =====
@@ -441,7 +443,7 @@ async def update_purchase_order_status(
@router.post(route_builder.build_nested_resource_route("purchase-orders", "po_id", "approve"), response_model=PurchaseOrderResponse)
@require_user_role(['admin', 'owner', 'member'])
@require_user_role(['admin', 'owner'])
async def approve_purchase_order(
approval_data: PurchaseOrderApproval,
po_id: UUID = Path(..., description="Purchase order ID"),
@@ -449,7 +451,7 @@ async def approve_purchase_order(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: Session = Depends(get_db)
):
"""Approve or reject a purchase order"""
"""Approve or reject a purchase order (Admin+ only)"""
try:
service = PurchaseOrderService(db)
@@ -460,12 +462,22 @@ async def approve_purchase_order(
if existing_order.tenant_id != current_user.tenant_id:
raise HTTPException(status_code=403, detail="Access denied")
# Capture PO details for audit
po_details = {
"po_number": existing_order.order_number,
"supplier_id": str(existing_order.supplier_id),
"total_amount": float(existing_order.total_amount) if existing_order.total_amount else 0.0,
"expected_delivery_date": existing_order.expected_delivery_date.isoformat() if existing_order.expected_delivery_date else None
}
if approval_data.action == "approve":
purchase_order = await service.approve_purchase_order(
po_id=po_id,
approved_by=current_user.user_id,
approval_notes=approval_data.notes
)
action = "approve"
description = f"Admin {current_user.get('email', 'unknown')} approved purchase order {po_details['po_number']}"
elif approval_data.action == "reject":
if not approval_data.notes:
raise HTTPException(status_code=400, detail="Rejection reason is required")
@@ -474,6 +486,8 @@ async def approve_purchase_order(
rejection_reason=approval_data.notes,
rejected_by=current_user.user_id
)
action = "reject"
description = f"Admin {current_user.get('email', 'unknown')} rejected purchase order {po_details['po_number']}"
else:
raise HTTPException(status_code=400, detail="Invalid action")
@@ -483,6 +497,34 @@ async def approve_purchase_order(
detail="Purchase order is not in pending approval status"
)
# Log HIGH severity audit event for purchase order approval/rejection
try:
await audit_logger.log_event(
db_session=db,
tenant_id=tenant_id,
user_id=current_user["user_id"],
action=action,
resource_type="purchase_order",
resource_id=str(po_id),
severity=AuditSeverity.HIGH.value,
description=description,
changes={
"action": approval_data.action,
"notes": approval_data.notes,
"po_details": po_details
},
endpoint=f"/purchase-orders/{po_id}/approve",
method="POST"
)
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("Purchase order approval processed",
po_id=str(po_id),
action=approval_data.action,
tenant_id=tenant_id,
user_id=current_user["user_id"])
return PurchaseOrderResponse.from_orm(purchase_order)
except HTTPException:
raise
@@ -672,3 +714,29 @@ async def get_top_purchased_inventory_products(
except Exception as e:
logger.error("Error getting top purchased inventory products", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve top purchased inventory products")
@router.get(route_builder.build_operations_route("count"))
async def get_supplier_count(
tenant_id: str = Path(..., description="Tenant ID"),
x_internal_request: str = Header(None),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: Session = Depends(get_db)
):
"""
Get total count of suppliers for a tenant
Internal endpoint for subscription usage tracking
"""
if x_internal_request != "true":
raise HTTPException(status_code=403, detail="Internal endpoint only")
try:
service = SupplierService(db)
suppliers = await service.get_suppliers(tenant_id=current_user.tenant_id)
count = len(suppliers)
return {"count": count}
except Exception as e:
logger.error("Error getting supplier count", error=str(e))
raise HTTPException(status_code=500, detail="Failed to retrieve supplier count")