Improve the frontend modals

This commit is contained in:
Urtzi Alfaro
2025-10-27 16:33:26 +01:00
parent 61376b7a9f
commit 858d985c92
143 changed files with 9289 additions and 2306 deletions

View File

@@ -67,7 +67,7 @@ async def get_delivery_performance_stats(
try:
service = DeliveryService(db)
stats = await service.get_delivery_performance_stats(
tenant_id=current_user.tenant_id,
tenant_id=current_user["tenant_id"],
days_back=days_back,
supplier_id=supplier_id
)
@@ -89,7 +89,7 @@ async def get_delivery_summary_stats(
"""Get delivery summary statistics for dashboard"""
try:
service = DeliveryService(db)
stats = await service.get_upcoming_deliveries_summary(current_user.tenant_id)
stats = await service.get_upcoming_deliveries_summary(current_user["tenant_id"])
return DeliverySummaryStats(**stats)
except Exception as e:
logger.error("Error getting delivery summary stats", error=str(e))

View File

@@ -41,9 +41,9 @@ async def create_delivery(
try:
service = DeliveryService(db)
delivery = await service.create_delivery(
tenant_id=current_user.tenant_id,
tenant_id=current_user["tenant_id"],
delivery_data=delivery_data,
created_by=current_user.user_id
created_by=current_user["user_id"]
)
return DeliveryResponse.from_orm(delivery)
except ValueError as e:
@@ -106,7 +106,7 @@ async def list_deliveries(
)
deliveries = await service.search_deliveries(
tenant_id=current_user.tenant_id,
tenant_id=current_user["tenant_id"],
search_params=search_params
)
@@ -135,7 +135,7 @@ async def get_delivery(
raise HTTPException(status_code=404, detail="Delivery not found")
# Check tenant access
if delivery.tenant_id != current_user.tenant_id:
if delivery.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
return DeliveryResponse.from_orm(delivery)
@@ -164,13 +164,13 @@ async def update_delivery(
existing_delivery = await service.get_delivery(delivery_id)
if not existing_delivery:
raise HTTPException(status_code=404, detail="Delivery not found")
if existing_delivery.tenant_id != current_user.tenant_id:
if existing_delivery.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
delivery = await service.update_delivery(
delivery_id=delivery_id,
delivery_data=delivery_data,
updated_by=current_user.user_id
updated_by=current_user["user_id"]
)
if not delivery:

View File

@@ -102,7 +102,7 @@ async def get_suppliers_needing_review(
raise HTTPException(status_code=500, detail="Failed to retrieve suppliers needing review")
@router.post(route_builder.build_nested_resource_route("suppliers", "supplier_id", "approve"), response_model=SupplierResponse)
@router.post(route_builder.build_resource_action_route("", "supplier_id", "approve"), response_model=SupplierResponse)
@require_user_role(['admin', 'owner', 'member'])
async def approve_supplier(
approval_data: SupplierApproval,
@@ -123,7 +123,7 @@ async def approve_supplier(
if approval_data.action == "approve":
supplier = await service.approve_supplier(
supplier_id=supplier_id,
approved_by=current_user.user_id,
approved_by=current_user["user_id"],
notes=approval_data.notes
)
elif approval_data.action == "reject":
@@ -132,7 +132,7 @@ async def approve_supplier(
supplier = await service.reject_supplier(
supplier_id=supplier_id,
rejection_reason=approval_data.notes,
rejected_by=current_user.user_id
rejected_by=current_user["user_id"]
)
else:
raise HTTPException(status_code=400, detail="Invalid action")
@@ -148,7 +148,7 @@ async def approve_supplier(
raise HTTPException(status_code=500, detail="Failed to process supplier approval")
@router.get(route_builder.build_resource_detail_route("suppliers/types", "supplier_type"), response_model=List[SupplierSummary])
@router.get(route_builder.build_resource_detail_route("types", "supplier_type"), response_model=List[SupplierSummary])
async def get_suppliers_by_type(
supplier_type: str = Path(..., description="Supplier type"),
tenant_id: str = Path(..., description="Tenant ID"),
@@ -183,7 +183,7 @@ async def get_todays_deliveries(
"""Get deliveries scheduled for today"""
try:
service = DeliveryService(db)
deliveries = await service.get_todays_deliveries(current_user.tenant_id)
deliveries = await service.get_todays_deliveries(current_user["tenant_id"])
return [DeliverySummary.from_orm(delivery) for delivery in deliveries]
except Exception as e:
logger.error("Error getting today's deliveries", error=str(e))
@@ -199,7 +199,7 @@ async def get_overdue_deliveries(
"""Get overdue deliveries"""
try:
service = DeliveryService(db)
deliveries = await service.get_overdue_deliveries(current_user.tenant_id)
deliveries = await service.get_overdue_deliveries(current_user["tenant_id"])
return [DeliverySummary.from_orm(delivery) for delivery in deliveries]
except Exception as e:
logger.error("Error getting overdue deliveries", error=str(e))
@@ -233,7 +233,7 @@ async def get_scheduled_deliveries(
service = DeliveryService(db)
deliveries = await service.get_scheduled_deliveries(
tenant_id=current_user.tenant_id,
tenant_id=current_user["tenant_id"],
date_from=date_from_parsed,
date_to=date_to_parsed
)
@@ -262,13 +262,13 @@ async def update_delivery_status(
existing_delivery = await service.get_delivery(delivery_id)
if not existing_delivery:
raise HTTPException(status_code=404, detail="Delivery not found")
if existing_delivery.tenant_id != current_user.tenant_id:
if existing_delivery.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
delivery = await service.update_delivery_status(
delivery_id=delivery_id,
status=status_data.status,
updated_by=current_user.user_id,
updated_by=current_user["user_id"],
notes=status_data.notes,
update_timestamps=status_data.update_timestamps
)
@@ -303,12 +303,12 @@ async def receive_delivery(
existing_delivery = await service.get_delivery(delivery_id)
if not existing_delivery:
raise HTTPException(status_code=404, detail="Delivery not found")
if existing_delivery.tenant_id != current_user.tenant_id:
if existing_delivery.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
delivery = await service.mark_as_received(
delivery_id=delivery_id,
received_by=current_user.user_id,
received_by=current_user["user_id"],
inspection_passed=receipt_data.inspection_passed,
inspection_notes=receipt_data.inspection_notes,
quality_issues=receipt_data.quality_issues,
@@ -341,7 +341,7 @@ async def get_deliveries_by_purchase_order(
deliveries = await service.get_deliveries_by_purchase_order(po_id)
# Check tenant access for first delivery (all should belong to same tenant)
if deliveries and deliveries[0].tenant_id != current_user.tenant_id:
if deliveries and deliveries[0].tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
return [DeliverySummary.from_orm(delivery) for delivery in deliveries]
@@ -363,7 +363,7 @@ async def get_purchase_order_statistics(
"""Get purchase order statistics for dashboard"""
try:
service = PurchaseOrderService(db)
stats = await service.get_purchase_order_statistics(current_user.tenant_id)
stats = await service.get_purchase_order_statistics(current_user["tenant_id"])
return stats
except Exception as e:
logger.error("Error getting purchase order statistics", error=str(e))
@@ -379,7 +379,7 @@ async def get_orders_requiring_approval(
"""Get purchase orders requiring approval"""
try:
service = PurchaseOrderService(db)
orders = await service.get_orders_requiring_approval(current_user.tenant_id)
orders = await service.get_orders_requiring_approval(current_user["tenant_id"])
return [PurchaseOrderSummary.from_orm(order) for order in orders]
except Exception as e:
logger.error("Error getting orders requiring approval", error=str(e))
@@ -395,7 +395,7 @@ async def get_overdue_orders(
"""Get overdue purchase orders"""
try:
service = PurchaseOrderService(db)
orders = await service.get_overdue_orders(current_user.tenant_id)
orders = await service.get_overdue_orders(current_user["tenant_id"])
return [PurchaseOrderSummary.from_orm(order) for order in orders]
except Exception as e:
logger.error("Error getting overdue orders", error=str(e))
@@ -419,13 +419,13 @@ async def update_purchase_order_status(
existing_order = await service.get_purchase_order(po_id)
if not existing_order:
raise HTTPException(status_code=404, detail="Purchase order not found")
if existing_order.tenant_id != current_user.tenant_id:
if existing_order.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
purchase_order = await service.update_order_status(
po_id=po_id,
status=status_data.status,
updated_by=current_user.user_id,
updated_by=current_user["user_id"],
notes=status_data.notes
)
@@ -459,7 +459,7 @@ async def approve_purchase_order(
existing_order = await service.get_purchase_order(po_id)
if not existing_order:
raise HTTPException(status_code=404, detail="Purchase order not found")
if existing_order.tenant_id != current_user.tenant_id:
if existing_order.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
# Capture PO details for audit
@@ -473,7 +473,7 @@ async def approve_purchase_order(
if approval_data.action == "approve":
purchase_order = await service.approve_purchase_order(
po_id=po_id,
approved_by=current_user.user_id,
approved_by=current_user["user_id"],
approval_notes=approval_data.notes
)
action = "approve"
@@ -484,7 +484,7 @@ async def approve_purchase_order(
purchase_order = await service.reject_purchase_order(
po_id=po_id,
rejection_reason=approval_data.notes,
rejected_by=current_user.user_id
rejected_by=current_user["user_id"]
)
action = "reject"
description = f"Admin {current_user.get('email', 'unknown')} rejected purchase order {po_details['po_number']}"
@@ -550,12 +550,12 @@ async def send_to_supplier(
existing_order = await service.get_purchase_order(po_id)
if not existing_order:
raise HTTPException(status_code=404, detail="Purchase order not found")
if existing_order.tenant_id != current_user.tenant_id:
if existing_order.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
purchase_order = await service.send_to_supplier(
po_id=po_id,
sent_by=current_user.user_id,
sent_by=current_user["user_id"],
send_email=send_email
)
@@ -589,13 +589,13 @@ async def confirm_supplier_receipt(
existing_order = await service.get_purchase_order(po_id)
if not existing_order:
raise HTTPException(status_code=404, detail="Purchase order not found")
if existing_order.tenant_id != current_user.tenant_id:
if existing_order.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
purchase_order = await service.confirm_supplier_receipt(
po_id=po_id,
supplier_reference=supplier_reference,
confirmed_by=current_user.user_id
confirmed_by=current_user["user_id"]
)
if not purchase_order:
@@ -628,13 +628,13 @@ async def cancel_purchase_order(
existing_order = await service.get_purchase_order(po_id)
if not existing_order:
raise HTTPException(status_code=404, detail="Purchase order not found")
if existing_order.tenant_id != current_user.tenant_id:
if existing_order.tenant_id != current_user["tenant_id"]:
raise HTTPException(status_code=403, detail="Access denied")
purchase_order = await service.cancel_purchase_order(
po_id=po_id,
cancellation_reason=cancellation_reason,
cancelled_by=current_user.user_id
cancelled_by=current_user["user_id"]
)
if not purchase_order:
@@ -662,7 +662,7 @@ async def get_orders_by_supplier(
try:
service = PurchaseOrderService(db)
orders = await service.get_orders_by_supplier(
tenant_id=current_user.tenant_id,
tenant_id=current_user["tenant_id"],
supplier_id=supplier_id,
limit=limit
)
@@ -684,7 +684,7 @@ async def get_inventory_product_purchase_history(
try:
service = PurchaseOrderService(db)
history = await service.get_inventory_product_purchase_history(
tenant_id=current_user.tenant_id,
tenant_id=current_user["tenant_id"],
inventory_product_id=inventory_product_id,
days_back=days_back
)
@@ -706,7 +706,7 @@ async def get_top_purchased_inventory_products(
try:
service = PurchaseOrderService(db)
products = await service.get_top_purchased_inventory_products(
tenant_id=current_user.tenant_id,
tenant_id=current_user["tenant_id"],
days_back=days_back,
limit=limit
)
@@ -732,7 +732,7 @@ async def get_supplier_count(
try:
service = SupplierService(db)
suppliers = await service.get_suppliers(tenant_id=current_user.tenant_id)
suppliers = await service.get_suppliers(tenant_id=current_user["tenant_id"])
count = len(suppliers)
return {"count": count}

View File

@@ -15,7 +15,7 @@ from app.services.supplier_service import SupplierService
from app.models.suppliers import SupplierPriceList
from app.schemas.suppliers import (
SupplierCreate, SupplierUpdate, SupplierResponse, SupplierSummary,
SupplierSearchParams
SupplierSearchParams, SupplierDeletionSummary
)
from shared.auth.decorators import get_current_user_dep
from shared.routing import RouteBuilder
@@ -30,7 +30,7 @@ router = APIRouter(tags=["suppliers"])
logger = structlog.get_logger()
audit_logger = create_audit_logger("suppliers-service")
@router.post(route_builder.build_base_route("suppliers"), response_model=SupplierResponse)
@router.post(route_builder.build_base_route(""), response_model=SupplierResponse)
@require_user_role(['admin', 'owner', 'member'])
async def create_supplier(
supplier_data: SupplierCreate,
@@ -41,10 +41,15 @@ async def create_supplier(
"""Create a new supplier"""
try:
service = SupplierService(db)
# Get user role from current_user dict
user_role = current_user.get("role", "member").lower()
supplier = await service.create_supplier(
tenant_id=UUID(tenant_id),
supplier_data=supplier_data,
created_by=current_user.user_id
created_by=current_user["user_id"],
created_by_role=user_role
)
return SupplierResponse.from_orm(supplier)
except ValueError as e:
@@ -54,7 +59,7 @@ async def create_supplier(
raise HTTPException(status_code=500, detail="Failed to create supplier")
@router.get(route_builder.build_base_route("suppliers"), response_model=List[SupplierSummary])
@router.get(route_builder.build_base_route(""), response_model=List[SupplierSummary])
async def list_suppliers(
tenant_id: str = Path(..., description="Tenant ID"),
search_term: Optional[str] = Query(None, description="Search term"),
@@ -84,7 +89,7 @@ async def list_suppliers(
raise HTTPException(status_code=500, detail="Failed to retrieve suppliers")
@router.get(route_builder.build_resource_detail_route("suppliers", "supplier_id"), response_model=SupplierResponse)
@router.get(route_builder.build_resource_detail_route("", "supplier_id"), response_model=SupplierResponse)
async def get_supplier(
supplier_id: UUID = Path(..., description="Supplier ID"),
tenant_id: str = Path(..., description="Tenant ID"),
@@ -106,7 +111,7 @@ async def get_supplier(
raise HTTPException(status_code=500, detail="Failed to retrieve supplier")
@router.put(route_builder.build_resource_detail_route("suppliers", "supplier_id"), response_model=SupplierResponse)
@router.put(route_builder.build_resource_detail_route("", "supplier_id"), response_model=SupplierResponse)
@require_user_role(['admin', 'owner', 'member'])
async def update_supplier(
supplier_data: SupplierUpdate,
@@ -126,7 +131,7 @@ async def update_supplier(
supplier = await service.update_supplier(
supplier_id=supplier_id,
supplier_data=supplier_data,
updated_by=current_user.user_id
updated_by=current_user["user_id"]
)
if not supplier:
@@ -142,7 +147,7 @@ async def update_supplier(
raise HTTPException(status_code=500, detail="Failed to update supplier")
@router.delete(route_builder.build_resource_detail_route("suppliers", "supplier_id"))
@router.delete(route_builder.build_resource_detail_route("", "supplier_id"))
@require_user_role(['admin', 'owner'])
async def delete_supplier(
supplier_id: UUID = Path(..., description="Supplier ID"),
@@ -207,6 +212,77 @@ async def delete_supplier(
raise HTTPException(status_code=500, detail="Failed to delete supplier")
@router.delete(
route_builder.build_resource_action_route("", "supplier_id", "hard"),
response_model=SupplierDeletionSummary
)
@require_user_role(['admin', 'owner'])
async def hard_delete_supplier(
supplier_id: UUID = Path(..., description="Supplier ID"),
tenant_id: str = Path(..., description="Tenant ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Hard delete supplier and all associated data (Admin/Owner only, permanent)"""
try:
service = SupplierService(db)
# Check supplier exists
existing_supplier = await service.get_supplier(supplier_id)
if not existing_supplier:
raise HTTPException(status_code=404, detail="Supplier not found")
# Capture supplier data before deletion
supplier_data = {
"id": str(existing_supplier.id),
"name": existing_supplier.name,
"status": existing_supplier.status.value,
"supplier_code": existing_supplier.supplier_code
}
# Perform hard deletion
deletion_summary = await service.hard_delete_supplier(supplier_id, UUID(tenant_id))
# Log audit event for hard deletion
try:
# Get sync db session for audit logging
from app.core.database import SessionLocal
sync_db = SessionLocal()
try:
await audit_logger.log_deletion(
db_session=sync_db,
tenant_id=tenant_id,
user_id=current_user["user_id"],
resource_type="supplier",
resource_id=str(supplier_id),
resource_data=supplier_data,
description=f"Hard deleted supplier '{supplier_data['name']}' and all associated data",
endpoint=f"/suppliers/{supplier_id}/hard",
method="DELETE",
metadata=deletion_summary
)
sync_db.commit()
finally:
sync_db.close()
except Exception as audit_error:
logger.warning("Failed to log audit event", error=str(audit_error))
logger.info("Hard deleted supplier",
supplier_id=str(supplier_id),
tenant_id=tenant_id,
user_id=current_user["user_id"],
deletion_summary=deletion_summary)
return deletion_summary
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except HTTPException:
raise
except Exception as e:
logger.error("Error hard deleting supplier", supplier_id=str(supplier_id), error=str(e))
raise HTTPException(status_code=500, detail="Failed to hard delete supplier")
@router.get(
route_builder.build_base_route("count"),
response_model=dict
@@ -237,7 +313,7 @@ async def count_suppliers(
@router.get(
route_builder.build_resource_action_route("suppliers", "supplier_id", "products"),
route_builder.build_resource_action_route("", "supplier_id", "products"),
response_model=List[Dict[str, Any]]
)
async def get_supplier_products(