Improve the frontend

This commit is contained in:
Urtzi Alfaro
2025-10-21 19:50:07 +02:00
parent 05da20357d
commit 8d30172483
105 changed files with 14699 additions and 4630 deletions

View File

@@ -14,6 +14,33 @@ import structlog
logger = structlog.get_logger()
def format_quantity(value: float, decimals: int = 2) -> str:
"""
Format quantity with proper rounding to avoid floating point errors
Args:
value: The numeric value to format
decimals: Number of decimal places (default 2)
Returns:
Formatted string with proper decimal representation
"""
return f"{round(value, decimals):.{decimals}f}"
def format_currency(value: float) -> str:
"""
Format currency value with proper rounding
Args:
value: The currency value to format
Returns:
Formatted currency string
"""
return f"{round(value, 2):.2f}"
class AlertSeverity:
"""Alert severity levels"""
LOW = "low"
@@ -208,6 +235,9 @@ async def generate_inventory_alerts(
if days_until_expiry < 0:
# Expired stock
qty_formatted = format_quantity(float(stock.current_quantity))
loss_formatted = format_currency(float(stock.total_cost)) if stock.total_cost else "0.00"
await create_demo_alert(
db=db,
tenant_id=tenant_id,
@@ -215,7 +245,7 @@ async def generate_inventory_alerts(
severity=AlertSeverity.URGENT,
title=f"Stock Caducado: {ingredient.name}",
message=f"El lote {stock.batch_number} caducó hace {abs(days_until_expiry)} días. "
f"Cantidad: {stock.current_quantity:.2f} {ingredient.unit_of_measure.value}. "
f"Cantidad: {qty_formatted} {ingredient.unit_of_measure.value}. "
f"Acción requerida: Retirar inmediatamente del inventario y registrar como pérdida.",
service="inventory",
rabbitmq_client=rabbitmq_client,
@@ -225,15 +255,17 @@ async def generate_inventory_alerts(
"batch_number": stock.batch_number,
"expiration_date": stock.expiration_date.isoformat(),
"days_expired": abs(days_until_expiry),
"quantity": float(stock.current_quantity),
"quantity": qty_formatted,
"unit": ingredient.unit_of_measure.value,
"estimated_loss": float(stock.total_cost) if stock.total_cost else 0.0
"estimated_loss": loss_formatted
}
)
alerts_created += 1
elif days_until_expiry <= 3:
# Expiring soon
qty_formatted = format_quantity(float(stock.current_quantity))
await create_demo_alert(
db=db,
tenant_id=tenant_id,
@@ -241,7 +273,7 @@ async def generate_inventory_alerts(
severity=AlertSeverity.HIGH,
title=f"Próximo a Caducar: {ingredient.name}",
message=f"El lote {stock.batch_number} caduca en {days_until_expiry} día{'s' if days_until_expiry > 1 else ''}. "
f"Cantidad: {stock.current_quantity:.2f} {ingredient.unit_of_measure.value}. "
f"Cantidad: {qty_formatted} {ingredient.unit_of_measure.value}. "
f"Recomendación: Planificar uso prioritario en producción inmediata.",
service="inventory",
rabbitmq_client=rabbitmq_client,
@@ -251,7 +283,7 @@ async def generate_inventory_alerts(
"batch_number": stock.batch_number,
"expiration_date": stock.expiration_date.isoformat(),
"days_until_expiry": days_until_expiry,
"quantity": float(stock.current_quantity),
"quantity": qty_formatted,
"unit": ingredient.unit_of_measure.value
}
)
@@ -260,26 +292,31 @@ async def generate_inventory_alerts(
# Low stock alert
if stock.current_quantity < ingredient.low_stock_threshold:
shortage = ingredient.low_stock_threshold - stock.current_quantity
current_qty = format_quantity(float(stock.current_quantity))
threshold_qty = format_quantity(float(ingredient.low_stock_threshold))
shortage_qty = format_quantity(float(shortage))
reorder_qty = format_quantity(float(ingredient.reorder_quantity))
await create_demo_alert(
db=db,
tenant_id=tenant_id,
alert_type="low_stock",
severity=AlertSeverity.MEDIUM,
title=f"Stock Bajo: {ingredient.name}",
message=f"Stock actual: {stock.current_quantity:.2f} {ingredient.unit_of_measure.value}. "
f"Umbral mínimo: {ingredient.low_stock_threshold:.2f}. "
f"Faltante: {shortage:.2f} {ingredient.unit_of_measure.value}. "
f"Se recomienda realizar pedido de {ingredient.reorder_quantity:.2f} {ingredient.unit_of_measure.value}.",
message=f"Stock actual: {current_qty} {ingredient.unit_of_measure.value}. "
f"Umbral mínimo: {threshold_qty}. "
f"Faltante: {shortage_qty} {ingredient.unit_of_measure.value}. "
f"Se recomienda realizar pedido de {reorder_qty} {ingredient.unit_of_measure.value}.",
service="inventory",
rabbitmq_client=rabbitmq_client,
metadata={
"stock_id": str(stock.id),
"ingredient_id": str(ingredient.id),
"current_quantity": float(stock.current_quantity),
"threshold": float(ingredient.low_stock_threshold),
"reorder_point": float(ingredient.reorder_point),
"reorder_quantity": float(ingredient.reorder_quantity),
"shortage": float(shortage)
"current_quantity": current_qty,
"threshold": threshold_qty,
"reorder_point": format_quantity(float(ingredient.reorder_point)),
"reorder_quantity": reorder_qty,
"shortage": shortage_qty
}
)
alerts_created += 1
@@ -287,24 +324,28 @@ async def generate_inventory_alerts(
# Overstock alert (if max_stock_level is defined)
if ingredient.max_stock_level and stock.current_quantity > ingredient.max_stock_level:
excess = stock.current_quantity - ingredient.max_stock_level
current_qty = format_quantity(float(stock.current_quantity))
max_level_qty = format_quantity(float(ingredient.max_stock_level))
excess_qty = format_quantity(float(excess))
await create_demo_alert(
db=db,
tenant_id=tenant_id,
alert_type="overstock",
severity=AlertSeverity.LOW,
title=f"Exceso de Stock: {ingredient.name}",
message=f"Stock actual: {stock.current_quantity:.2f} {ingredient.unit_of_measure.value}. "
f"Nivel máximo recomendado: {ingredient.max_stock_level:.2f}. "
f"Exceso: {excess:.2f} {ingredient.unit_of_measure.value}. "
message=f"Stock actual: {current_qty} {ingredient.unit_of_measure.value}. "
f"Nivel máximo recomendado: {max_level_qty}. "
f"Exceso: {excess_qty} {ingredient.unit_of_measure.value}. "
f"Considerar reducir cantidad en próximos pedidos o buscar uso alternativo.",
service="inventory",
rabbitmq_client=rabbitmq_client,
metadata={
"stock_id": str(stock.id),
"ingredient_id": str(ingredient.id),
"current_quantity": float(stock.current_quantity),
"max_level": float(ingredient.max_stock_level),
"excess": float(excess)
"current_quantity": current_qty,
"max_level": max_level_qty,
"excess": excess_qty
}
)
alerts_created += 1
@@ -437,21 +478,23 @@ async def generate_equipment_alerts(
# Low efficiency alert
if equipment.efficiency_percentage and equipment.efficiency_percentage < 80.0:
efficiency_formatted = format_quantity(float(equipment.efficiency_percentage), 1)
await create_demo_alert(
db=db,
tenant_id=tenant_id,
alert_type="equipment_low_efficiency",
severity=AlertSeverity.LOW,
title=f"Eficiencia Baja: {equipment.name}",
message=f"El equipo {equipment.name} está operando con eficiencia reducida ({equipment.efficiency_percentage:.1f}%). "
f"Eficiencia objetivo: e 85%. "
message=f"El equipo {equipment.name} está operando con eficiencia reducida ({efficiency_formatted}%). "
f"Eficiencia objetivo: 85%. "
f"Revisar causas: limpieza, calibración, desgaste de componentes.",
service="production",
rabbitmq_client=rabbitmq_client,
metadata={
"equipment_id": str(equipment.id),
"equipment_name": equipment.name,
"efficiency": float(equipment.efficiency_percentage)
"efficiency": efficiency_formatted
}
)
alerts_created += 1
@@ -554,6 +597,8 @@ async def generate_order_alerts(
# High priority pending orders
if order.priority == 'high' and order.status == 'pending':
amount_formatted = format_currency(float(order.total_amount))
await create_demo_alert(
db=db,
tenant_id=tenant_id,
@@ -561,7 +606,7 @@ async def generate_order_alerts(
severity=AlertSeverity.MEDIUM,
title=f"Pedido Prioritario Pendiente: {order.order_number}",
message=f"El pedido de alta prioridad {order.order_number} está pendiente de confirmación. "
f"Monto: ¬{float(order.total_amount):.2f}. "
f"Monto: {amount_formatted}. "
f"Revisar disponibilidad de ingredientes y confirmar producción.",
service="orders",
rabbitmq_client=rabbitmq_client,
@@ -569,7 +614,7 @@ async def generate_order_alerts(
"order_id": str(order.id),
"order_number": order.order_number,
"priority": order.priority,
"total_amount": float(order.total_amount)
"total_amount": amount_formatted
}
)
alerts_created += 1