fix: reporte de Clientes usa /aging-summary agrupado por cliente
This commit is contained in:
@@ -687,6 +687,73 @@ def aging_report():
|
|||||||
return jsonify({'data': rows, 'totals': totals})
|
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 ────────────────────────────────
|
# ─── Fiscal Periods ────────────────────────────────
|
||||||
|
|
||||||
@accounting_bp.route('/periods', methods=['GET'])
|
@accounting_bp.route('/periods', methods=['GET'])
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ const Reports = (() => {
|
|||||||
agingEl.innerHTML = spinner();
|
agingEl.innerHTML = spinner();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var data = await apiFetch('/pos/api/accounting/aging');
|
var data = await apiFetch('/pos/api/accounting/aging-summary');
|
||||||
var clients = data.data || [];
|
var clients = data.data || [];
|
||||||
var totals = data.totals || {};
|
var totals = data.totals || {};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// The fetch handler normalizes static asset URLs (strips ?v= query strings)
|
// The fetch handler normalizes static asset URLs (strips ?v= query strings)
|
||||||
// so templates can use cache-busting query params freely.
|
// so templates can use cache-busting query params freely.
|
||||||
|
|
||||||
const CACHE_NAME = 'nexus-pos-v25';
|
const CACHE_NAME = 'nexus-pos-v26';
|
||||||
|
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
'/pos/static/css/tokens.css',
|
'/pos/static/css/tokens.css',
|
||||||
|
|||||||
@@ -371,7 +371,7 @@
|
|||||||
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
<script src="/pos/static/js/splash-loader.js?v=1" defer></script>
|
||||||
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
<script src="/pos/static/js/pos-utils.js?v=2" defer></script>
|
||||||
<script src="/pos/static/js/sidebar.js" defer></script>
|
<script src="/pos/static/js/sidebar.js" defer></script>
|
||||||
<script src="/pos/static/js/reports.js?v=3" defer></script>
|
<script src="/pos/static/js/reports.js?v=4" defer></script>
|
||||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||||
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user