fix(alerts): limit alerts to 500 per type in SQL + frontend pagination with 'Ver más' + summary bar

This commit is contained in:
2026-05-26 09:12:09 +00:00
parent 3009ffa1b0
commit 61bf84b2dc
4 changed files with 109 additions and 54 deletions

View File

@@ -272,38 +272,72 @@ def record_initial(conn, inventory_id, branch_id, quantity, cost=None):
return result
def get_alerts(conn, branch_id=None):
"""Get stock alerts: zero stock, below minimum, above maximum."""
stock_map = get_stock_bulk(conn, branch_id)
def get_alerts(conn, branch_id=None, limit_per_type=500):
"""Get stock alerts: zero stock, below minimum, above maximum.
Returns at most limit_per_type alerts per severity to avoid browser freeze.
"""
cur = conn.cursor()
where = "WHERE i.is_active = true"
branch_filter = ""
params = []
if branch_id:
where += " AND i.branch_id = %s"
branch_filter = " AND i.branch_id = %s"
params.append(branch_id)
# Use a single SQL query with window functions to rank and limit per type
cur.execute(f"""
SELECT i.id, i.part_number, i.name, i.min_stock, i.max_stock, i.branch_id
FROM inventory i {where}
""", params)
WITH stock AS (
SELECT inventory_id, COALESCE(SUM(quantity), 0) AS qty
FROM inventory_operations
GROUP BY inventory_id
),
alerts_raw AS (
SELECT
i.id AS inventory_id,
i.part_number,
i.name,
COALESCE(s.qty, 0) AS stock,
i.min_stock,
i.max_stock,
i.branch_id,
CASE
WHEN COALESCE(s.qty, 0) <= 0 THEN 'zero'
WHEN i.min_stock IS NOT NULL AND COALESCE(s.qty, 0) < i.min_stock THEN 'low'
WHEN i.max_stock IS NOT NULL AND COALESCE(s.qty, 0) > i.max_stock THEN 'over'
END AS alert_type,
CASE
WHEN COALESCE(s.qty, 0) <= 0 THEN 'critical'
WHEN i.min_stock IS NOT NULL AND COALESCE(s.qty, 0) < i.min_stock THEN 'warning'
WHEN i.max_stock IS NOT NULL AND COALESCE(s.qty, 0) > i.max_stock THEN 'info'
END AS severity
FROM inventory i
LEFT JOIN stock s ON s.inventory_id = i.id
WHERE i.is_active = true {branch_filter}
),
ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY alert_type ORDER BY inventory_id) AS rn
FROM alerts_raw
WHERE alert_type IS NOT NULL
)
SELECT inventory_id, part_number, name, stock, min_stock, max_stock, branch_id, alert_type, severity
FROM ranked
WHERE rn <= %s
ORDER BY severity DESC, inventory_id
""", params + [limit_per_type])
alerts = []
for row in cur.fetchall():
inv_id, part_num, name, min_s, max_s, br_id = row
stock = stock_map.get(inv_id, 0)
if stock <= 0:
alerts.append({'type': 'zero', 'severity': 'critical', 'inventory_id': inv_id,
'part_number': part_num, 'name': name, 'stock': stock, 'branch_id': br_id})
elif min_s and stock < min_s:
alerts.append({'type': 'low', 'severity': 'warning', 'inventory_id': inv_id,
'part_number': part_num, 'name': name, 'stock': stock,
'min_stock': min_s, 'branch_id': br_id})
elif max_s and stock > max_s:
alerts.append({'type': 'over', 'severity': 'info', 'inventory_id': inv_id,
'part_number': part_num, 'name': name, 'stock': stock,
'max_stock': max_s, 'branch_id': br_id})
alerts.append({
'inventory_id': row[0],
'part_number': row[1],
'name': row[2],
'stock': row[3],
'min_stock': row[4],
'max_stock': row[5],
'branch_id': row[6],
'type': row[7],
'severity': row[8],
})
cur.close()
return alerts