Improve the frontend and repository layer

This commit is contained in:
Urtzi Alfaro
2025-10-23 07:44:54 +02:00
parent 8d30172483
commit 07c33fa578
112 changed files with 14726 additions and 2733 deletions

View File

@@ -13,6 +13,8 @@ from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role, admin_role_required
from shared.routing import RouteBuilder
from shared.security import create_audit_logger, AuditSeverity, AuditAction
from app.services.pos_config_service import POSConfigurationService
from app.schemas.pos_config import POSConfigurationListResponse
router = APIRouter()
logger = structlog.get_logger()
@@ -22,23 +24,41 @@ route_builder = RouteBuilder('pos')
@router.get(
route_builder.build_base_route("configurations"),
response_model=dict
response_model=POSConfigurationListResponse
)
@require_user_role(['viewer', 'member', 'admin', 'owner'])
async def list_pos_configurations(
tenant_id: UUID = Path(...),
pos_system: Optional[str] = Query(None),
is_active: Optional[bool] = Query(None),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=100),
current_user: dict = Depends(get_current_user_dep),
db=Depends(get_db)
):
"""List all POS configurations for a tenant"""
try:
return {
"configurations": [],
"total": 0,
"supported_systems": ["square", "toast", "lightspeed"]
}
service = POSConfigurationService()
configurations = await service.get_configurations_by_tenant(
tenant_id=tenant_id,
pos_system=pos_system,
is_active=is_active,
skip=skip,
limit=limit
)
total = await service.count_configurations_by_tenant(
tenant_id=tenant_id,
pos_system=pos_system,
is_active=is_active
)
return POSConfigurationListResponse(
configurations=configurations,
total=total,
supported_systems=["square", "toast", "lightspeed"]
)
except Exception as e:
logger.error("Failed to list POS configurations", error=str(e), tenant_id=tenant_id)
raise HTTPException(status_code=500, detail=f"Failed to list configurations: {str(e)}")

View File

@@ -14,6 +14,8 @@ from app.core.database import get_db
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role, admin_role_required
from shared.routing import RouteBuilder
from app.services.pos_transaction_service import POSTransactionService
from app.services.pos_config_service import POSConfigurationService
router = APIRouter()
logger = structlog.get_logger()
@@ -74,15 +76,33 @@ async def get_sync_status(
):
"""Get synchronization status and recent sync history"""
try:
transaction_service = POSTransactionService()
# Get sync metrics from transaction service
sync_metrics = await transaction_service.get_sync_metrics(tenant_id)
# Get last successful sync time
sync_status = sync_metrics["sync_status"]
last_successful_sync = sync_status.get("last_sync_at")
# Calculate sync success rate
total = sync_metrics["total_transactions"]
synced = sync_status.get("synced", 0)
success_rate = (synced / total * 100) if total > 0 else 100.0
return {
"current_sync": None,
"last_successful_sync": None,
"recent_syncs": [],
"last_successful_sync": last_successful_sync.isoformat() if last_successful_sync else None,
"recent_syncs": [], # Could be enhanced with actual sync history
"sync_health": {
"status": "healthy",
"success_rate": 95.5,
"average_duration_minutes": 3.2,
"last_error": None
"status": "healthy" if success_rate > 90 else "degraded" if success_rate > 70 else "unhealthy",
"success_rate": round(success_rate, 2),
"average_duration_minutes": 3.2, # Placeholder - could calculate from actual data
"last_error": None,
"total_transactions": total,
"synced_count": synced,
"pending_count": sync_status.get("pending", 0),
"failed_count": sync_status.get("failed", 0)
}
}
except Exception as e:
@@ -159,12 +179,35 @@ async def test_pos_connection(
):
"""Test connection to POS system (Admin/Owner only)"""
try:
config_service = POSConfigurationService()
# Get the configuration to verify it exists
configurations = await config_service.get_configurations_by_tenant(
tenant_id=tenant_id,
skip=0,
limit=100
)
config = next((c for c in configurations if str(c.id) == str(config_id)), None)
if not config:
raise HTTPException(status_code=404, detail="Configuration not found")
# For demo purposes, we assume connection is successful if config exists
# In production, this would actually test the POS API connection
is_connected = config.is_connected and config.is_active
return {
"status": "success",
"message": "Connection test successful",
"success": is_connected,
"status": "success" if is_connected else "failed",
"message": f"Connection test {'successful' if is_connected else 'failed'} for {config.pos_system}",
"tested_at": datetime.utcnow().isoformat(),
"config_id": str(config_id)
"config_id": str(config_id),
"pos_system": config.pos_system,
"health_status": config.health_status
}
except HTTPException:
raise
except Exception as e:
logger.error("Failed to test POS connection", error=str(e),
tenant_id=tenant_id, config_id=config_id)

View File

@@ -4,15 +4,22 @@ ATOMIC layer - Basic CRUD operations for POS transactions
"""
from fastapi import APIRouter, Depends, HTTPException, Path, Query
from typing import Optional, Dict, Any
from typing import Optional
from uuid import UUID
from datetime import datetime
from decimal import Decimal
import structlog
from app.core.database import get_db
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role
from shared.routing import RouteBuilder
from app.services.pos_transaction_service import POSTransactionService
from app.schemas.pos_transaction import (
POSTransactionResponse,
POSTransactionListResponse,
POSTransactionDashboardSummary
)
router = APIRouter()
logger = structlog.get_logger()
@@ -21,7 +28,7 @@ route_builder = RouteBuilder('pos')
@router.get(
route_builder.build_base_route("transactions"),
response_model=dict
response_model=POSTransactionListResponse
)
@require_user_role(['viewer', 'member', 'admin', 'owner'])
async def list_pos_transactions(
@@ -38,20 +45,46 @@ async def list_pos_transactions(
):
"""List POS transactions for a tenant"""
try:
return {
"transactions": [],
"total": 0,
"has_more": False,
"summary": {
"total_amount": 0,
"transaction_count": 0,
"sync_status": {
"synced": 0,
"pending": 0,
"failed": 0
}
service = POSTransactionService()
transactions = await service.get_transactions_by_tenant(
tenant_id=tenant_id,
pos_system=pos_system,
start_date=start_date,
end_date=end_date,
status=status,
is_synced=is_synced,
skip=offset,
limit=limit
)
total = await service.count_transactions_by_tenant(
tenant_id=tenant_id,
pos_system=pos_system,
start_date=start_date,
end_date=end_date,
status=status,
is_synced=is_synced
)
# Get sync metrics for summary
sync_metrics = await service.get_sync_metrics(tenant_id)
# Calculate summary
total_amount = sum(float(t.total_amount) for t in transactions if t.status == "completed")
has_more = (offset + limit) < total
return POSTransactionListResponse(
transactions=transactions,
total=total,
has_more=has_more,
summary={
"total_amount": total_amount,
"transaction_count": len(transactions),
"sync_status": sync_metrics["sync_status"]
}
}
)
except Exception as e:
logger.error("Failed to list POS transactions", error=str(e), tenant_id=tenant_id)
raise HTTPException(status_code=500, detail=f"Failed to list transactions: {str(e)}")
@@ -59,7 +92,7 @@ async def list_pos_transactions(
@router.get(
route_builder.build_resource_detail_route("transactions", "transaction_id"),
response_model=dict
response_model=POSTransactionResponse
)
@require_user_role(['viewer', 'member', 'admin', 'owner'])
async def get_pos_transaction(
@@ -70,13 +103,46 @@ async def get_pos_transaction(
):
"""Get a specific POS transaction"""
try:
return {
"id": str(transaction_id),
"tenant_id": str(tenant_id),
"status": "completed",
"is_synced": True
}
service = POSTransactionService()
transaction = await service.get_transaction_with_items(
transaction_id=transaction_id,
tenant_id=tenant_id
)
if not transaction:
raise HTTPException(status_code=404, detail="Transaction not found")
return transaction
except HTTPException:
raise
except Exception as e:
logger.error("Failed to get POS transaction", error=str(e),
tenant_id=tenant_id, transaction_id=transaction_id)
raise HTTPException(status_code=500, detail=f"Failed to get transaction: {str(e)}")
@router.get(
route_builder.build_operations_route("transactions-dashboard"),
response_model=POSTransactionDashboardSummary
)
@require_user_role(['viewer', 'member', 'admin', 'owner'])
async def get_transactions_dashboard(
tenant_id: UUID = Path(...),
current_user: dict = Depends(get_current_user_dep),
db=Depends(get_db)
):
"""Get dashboard summary for POS transactions"""
try:
service = POSTransactionService()
summary = await service.get_dashboard_summary(tenant_id)
logger.info("Transactions dashboard retrieved",
tenant_id=str(tenant_id),
total_today=summary.total_transactions_today)
return summary
except Exception as e:
logger.error("Failed to get transactions dashboard", error=str(e), tenant_id=tenant_id)
raise HTTPException(status_code=500, detail=f"Failed to get dashboard: {str(e)}")