demo seed change 4

This commit is contained in:
Urtzi Alfaro
2025-12-14 19:05:37 +01:00
parent 4ae5356ad1
commit 82f9622411
16 changed files with 532 additions and 55 deletions

View File

@@ -9,7 +9,7 @@ from sqlalchemy import select, delete, func
import structlog
import uuid
from datetime import datetime, timezone, timedelta, date
from typing import Optional
from typing import Optional, Dict, Any
import os
import json
from pathlib import Path
@@ -26,6 +26,7 @@ from shared.schemas.reasoning_types import (
create_po_reasoning_supplier_contract
)
from app.core.config import settings
from shared.clients.suppliers_client import SuppliersServiceClient
logger = structlog.get_logger()
router = APIRouter(prefix="/internal/demo", tags=["internal"])
@@ -42,6 +43,155 @@ def verify_internal_api_key(x_internal_api_key: Optional[str] = Header(None)):
return True
async def _emit_po_approval_alerts_for_demo(
virtual_tenant_id: uuid.UUID,
pending_pos: list[PurchaseOrder]
) -> int:
"""
Emit alerts for pending approval POs during demo cloning.
Creates clients internally to avoid dependency injection issues.
Returns the number of alerts successfully emitted.
"""
if not pending_pos:
return 0
alerts_emitted = 0
try:
# Initialize clients locally for this operation
from shared.clients.suppliers_client import SuppliersServiceClient
from shared.messaging import RabbitMQClient
# Use the existing settings instead of creating a new config
# This avoids issues with property-based configuration
suppliers_client = SuppliersServiceClient(settings, "procurement-service")
rabbitmq_client = RabbitMQClient(settings.RABBITMQ_URL, "procurement-service")
# Connect to RabbitMQ
await rabbitmq_client.connect()
logger.info(
"Emitting PO approval alerts for demo",
pending_po_count=len(pending_pos),
virtual_tenant_id=str(virtual_tenant_id)
)
# Emit alerts for each pending PO
for po in pending_pos:
try:
# Get supplier details
supplier_details = await suppliers_client.get_supplier_by_id(
tenant_id=str(virtual_tenant_id),
supplier_id=str(po.supplier_id)
)
# Skip if supplier not found
if not supplier_details:
logger.warning(
"Supplier not found for PO, skipping alert",
po_id=str(po.id),
supplier_id=str(po.supplier_id)
)
continue
# Calculate urgency fields
now = datetime.utcnow()
hours_until_consequence = None
deadline = None
if po.required_delivery_date:
supplier_lead_time_days = supplier_details.get('standard_lead_time', 7)
approval_deadline = po.required_delivery_date - timedelta(days=supplier_lead_time_days)
deadline = approval_deadline
hours_until_consequence = (approval_deadline - now).total_seconds() / 3600
# Prepare alert payload
alert_data = {
'id': str(uuid.uuid4()),
'tenant_id': str(virtual_tenant_id),
'service': 'procurement',
'type': 'po_approval_needed',
'alert_type': 'po_approval_needed',
'type_class': 'action_needed',
'severity': 'high' if po.priority == 'critical' else 'medium',
'title': '',
'message': '',
'timestamp': datetime.utcnow().isoformat(),
'metadata': {
'po_id': str(po.id),
'po_number': po.po_number,
'supplier_id': str(po.supplier_id),
'supplier_name': supplier_details.get('name', ''),
'total_amount': float(po.total_amount),
'currency': po.currency,
'priority': po.priority,
'required_delivery_date': po.required_delivery_date.isoformat() if po.required_delivery_date else None,
'created_at': po.created_at.isoformat(),
'financial_impact': float(po.total_amount),
'urgency_score': 85,
'deadline': deadline.isoformat() if deadline else None,
'hours_until_consequence': round(hours_until_consequence, 1) if hours_until_consequence else None,
'reasoning_data': po.reasoning_data or {}
},
'message_params': {
'po_number': po.po_number,
'supplier_name': supplier_details.get('name', ''),
'total_amount': float(po.total_amount),
'currency': po.currency,
'priority': po.priority,
'required_delivery_date': po.required_delivery_date.isoformat() if po.required_delivery_date else None,
'items_count': 0,
'created_at': po.created_at.isoformat()
},
'actions': ['approve_po', 'reject_po', 'modify_po'],
'item_type': 'alert'
}
# Publish to RabbitMQ
await rabbitmq_client.publish_event(
exchange_name='alerts.exchange',
routing_key=f'alert.{alert_data["severity"]}.procurement',
event_data=alert_data
)
alerts_emitted += 1
logger.debug(
"PO approval alert emitted",
po_id=str(po.id),
po_number=po.po_number
)
except Exception as po_error:
logger.warning(
"Failed to emit alert for PO",
po_id=str(po.id),
po_number=po.po_number,
error=str(po_error)
)
# Continue with other POs
# Close RabbitMQ connection
await rabbitmq_client.close()
logger.info(
"PO approval alerts emission completed",
alerts_emitted=alerts_emitted,
total_pending=len(pending_pos)
)
return alerts_emitted
except Exception as e:
logger.error(
"Failed to emit PO approval alerts",
error=str(e),
virtual_tenant_id=str(virtual_tenant_id),
exc_info=True
)
# Don't fail the cloning process
return alerts_emitted
@router.post("/clone")
async def clone_demo_data(
base_tenant_id: str,
@@ -420,6 +570,39 @@ async def clone_demo_data(
# Commit all loaded data
await db.commit()
# Emit alerts for pending approval POs (CRITICAL for demo dashboard)
alerts_emitted = 0
try:
# Get all pending approval POs that were just created
pending_approval_pos = await db.execute(
select(PurchaseOrder).where(
PurchaseOrder.tenant_id == virtual_uuid,
PurchaseOrder.status == 'pending_approval'
)
)
pending_pos = pending_approval_pos.scalars().all()
logger.info(
"Found pending approval POs for alert emission",
count=len(pending_pos),
virtual_tenant_id=virtual_tenant_id
)
# Emit alerts using refactored function
if pending_pos:
alerts_emitted = await _emit_po_approval_alerts_for_demo(
virtual_tenant_id=virtual_uuid,
pending_pos=pending_pos
)
except Exception as e:
logger.error(
"Failed to emit PO approval alerts during demo cloning",
error=str(e),
virtual_tenant_id=virtual_tenant_id
)
# Don't fail the entire cloning process if alert emission fails
# Calculate total records
total_records = (stats["procurement_plans"] + stats["procurement_requirements"] +
stats["purchase_orders"] + stats["purchase_order_items"] +
@@ -439,7 +622,8 @@ async def clone_demo_data(
"status": "completed",
"records_cloned": total_records,
"duration_ms": duration_ms,
"details": stats
"details": stats,
"alerts_emitted": alerts_emitted
}
except ValueError as e: