From 59a4893e84726f5db5b4112dc5ca8143103145f0 Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Fri, 26 Jun 2026 00:36:50 +0000 Subject: [PATCH] fix: reporte de Clientes usa /aging-summary agrupado por cliente --- pos/blueprints/accounting_bp.py | 67 +++++++++++++++++++++++++++++++++ pos/static/js/reports.js | 2 +- pos/static/pwa/sw.js | 2 +- pos/templates/reports.html | 2 +- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/pos/blueprints/accounting_bp.py b/pos/blueprints/accounting_bp.py index 7533925..99e08cb 100644 --- a/pos/blueprints/accounting_bp.py +++ b/pos/blueprints/accounting_bp.py @@ -687,6 +687,73 @@ def aging_report(): return jsonify({'data': rows, 'totals': totals}) +@accounting_bp.route('/aging-summary', methods=['GET']) +@require_auth('accounting.view') +def aging_summary(): + """Customer-level accounts receivable summary (used by reports).""" + conn = get_tenant_conn(g.tenant_id) + cur = conn.cursor() + + cur.execute(""" + SELECT c.id, c.name, c.rfc, c.credit_limit, c.credit_balance, + s.id as sale_id, s.total, s.created_at, + EXTRACT(DAY FROM NOW() - s.created_at)::int as days_outstanding + FROM customers c + JOIN sales s ON s.customer_id = c.id + WHERE s.sale_type = 'credit' + AND s.status = 'completed' + AND c.credit_balance > 0 + ORDER BY c.name, s.created_at + """) + + customers = {} + for r in cur.fetchall(): + cust_id = r[0] + if cust_id not in customers: + customers[cust_id] = { + 'id': r[0], 'name': r[1], 'rfc': r[2], + 'credit_limit': float(r[3]) if r[3] else 0, + 'credit_balance': float(r[4]) if r[4] else 0, + 'corriente': 0, 'd1_30': 0, 'd31_60': 0, 'd61_90': 0, 'd90_plus': 0, + 'total': 0, + } + + amount = float(r[6]) if r[6] else 0 + days = r[8] or 0 + + if days <= 0: + customers[cust_id]['corriente'] += amount + elif days <= 30: + customers[cust_id]['d1_30'] += amount + elif days <= 60: + customers[cust_id]['d31_60'] += amount + elif days <= 90: + customers[cust_id]['d61_90'] += amount + else: + customers[cust_id]['d90_plus'] += amount + + customers[cust_id]['total'] += amount + + result = list(customers.values()) + for c in result: + for key in ('corriente', 'd1_30', 'd31_60', 'd61_90', 'd90_plus', 'total'): + c[key] = round(c[key], 2) + + totals = { + 'corriente': round(sum(c['corriente'] for c in result), 2), + 'd1_30': round(sum(c['d1_30'] for c in result), 2), + 'd31_60': round(sum(c['d31_60'] for c in result), 2), + 'd61_90': round(sum(c['d61_90'] for c in result), 2), + 'd90_plus': round(sum(c['d90_plus'] for c in result), 2), + 'total': round(sum(c['total'] for c in result), 2), + 'count': len(result), + } + + cur.close() + conn.close() + return jsonify({'data': result, 'totals': totals}) + + # ─── Fiscal Periods ──────────────────────────────── @accounting_bp.route('/periods', methods=['GET']) diff --git a/pos/static/js/reports.js b/pos/static/js/reports.js index 4b9cf3e..117cc70 100644 --- a/pos/static/js/reports.js +++ b/pos/static/js/reports.js @@ -504,7 +504,7 @@ const Reports = (() => { agingEl.innerHTML = spinner(); try { - var data = await apiFetch('/pos/api/accounting/aging'); + var data = await apiFetch('/pos/api/accounting/aging-summary'); var clients = data.data || []; var totals = data.totals || {}; diff --git a/pos/static/pwa/sw.js b/pos/static/pwa/sw.js index 853caae..834d624 100644 --- a/pos/static/pwa/sw.js +++ b/pos/static/pwa/sw.js @@ -6,7 +6,7 @@ // The fetch handler normalizes static asset URLs (strips ?v= query strings) // so templates can use cache-busting query params freely. -const CACHE_NAME = 'nexus-pos-v25'; +const CACHE_NAME = 'nexus-pos-v26'; const APP_SHELL = [ '/pos/static/css/tokens.css', diff --git a/pos/templates/reports.html b/pos/templates/reports.html index b38a510..beefd2a 100644 --- a/pos/templates/reports.html +++ b/pos/templates/reports.html @@ -371,7 +371,7 @@ - +