Improve the frontend
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user