fix(audit): corrige errores criticos y mayores, mejora UX/accesibilidad y optimiza rendimiento
- Arregla @require_auth, permisos, race conditions, locks de caja/stock - Elimina N+1 en layaway, flotilla, dashboard y global_invoice - Asegura folios atomicos para CFDI, ordenes de servicio y polizas - Protege client_secret de MercadoLibre en backend - Conecta botones/filtros de config, customers, accounting e invoicing - Mejora accesibilidad (labels/aria-label) y estados de carga/vacio - Limpia accounting.js obsoleto y consolida accounting.v9.js - Actualiza cache busting a v32 y Service Worker a v32 - Documenta todo en docs/AUDIT_Y_MEJORAS_2026-06-15.md Tests: 35 passed
This commit is contained in:
@@ -118,3 +118,70 @@ def get_employee_stats():
|
||||
finally:
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
@dashboard_stats_bp.route('/credit-alerts', methods=['GET'])
|
||||
@require_auth()
|
||||
def credit_alerts():
|
||||
"""Credit sales that are overdue or due within the next 7 days."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("""
|
||||
SELECT s.id,
|
||||
c.name as customer_name,
|
||||
s.total,
|
||||
s.created_at,
|
||||
s.created_at + INTERVAL '30 days' as due_date,
|
||||
COALESCE(SUM(sp.amount), 0) as paid
|
||||
FROM sales s
|
||||
JOIN customers c ON c.id = s.customer_id
|
||||
LEFT JOIN sale_payments sp ON sp.sale_id = s.id
|
||||
WHERE s.sale_type = 'credit'
|
||||
AND s.status = 'completed'
|
||||
GROUP BY s.id, c.name, s.total, s.created_at
|
||||
HAVING s.total - COALESCE(SUM(sp.amount), 0) > 0
|
||||
AND s.created_at + INTERVAL '30 days' <= NOW() + INTERVAL '30 days'
|
||||
ORDER BY due_date
|
||||
LIMIT 50
|
||||
""")
|
||||
|
||||
rows = []
|
||||
now = datetime.utcnow()
|
||||
for r in cur.fetchall():
|
||||
sale_id = r[0]
|
||||
customer_name = r[1]
|
||||
total = float(r[2]) if r[2] else 0
|
||||
paid = float(r[5]) if r[5] else 0
|
||||
balance = round(total - paid, 2)
|
||||
created_at = r[3]
|
||||
due_date = r[4]
|
||||
days_until_due = (due_date.replace(tzinfo=None) - now).days if due_date else None
|
||||
|
||||
if days_until_due is None:
|
||||
continue
|
||||
status = 'overdue' if days_until_due < 0 else ('due_soon' if days_until_due <= 7 else 'current')
|
||||
label = 'Vencida' if status == 'overdue' else ('Por vencer' if status == 'due_soon' else 'Al corriente')
|
||||
|
||||
rows.append({
|
||||
'sale_id': sale_id,
|
||||
'folio': f'VTA-{sale_id}',
|
||||
'customer_name': customer_name,
|
||||
'issue_date': created_at.isoformat() if created_at else None,
|
||||
'due_date': due_date.isoformat() if due_date else None,
|
||||
'days_until_due': days_until_due,
|
||||
'total': total,
|
||||
'paid': paid,
|
||||
'balance': balance,
|
||||
'status': status,
|
||||
'status_label': label,
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'data': rows,
|
||||
'overdue_count': sum(1 for r in rows if r['status'] == 'overdue'),
|
||||
'due_soon_count': sum(1 for r in rows if r['status'] == 'due_soon'),
|
||||
})
|
||||
finally:
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user