diff --git a/pos/static/js/accounting.js b/pos/static/js/accounting.js new file mode 100644 index 0000000..b3bb513 --- /dev/null +++ b/pos/static/js/accounting.js @@ -0,0 +1,593 @@ +// /home/Autopartes/pos/static/js/accounting.js +// Accounting module: chart of accounts, journal entries, financial reports + +const Accounting = (() => { + const API = '/pos/api/accounting'; + let accounts = []; // cached for dropdowns + let entryLineCtr = 0; // counter for entry line IDs + + function token() { + return localStorage.getItem('pos_token') || ''; + } + + function headers() { + return { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' }; + } + + async function api(path, opts = {}) { + const res = await fetch(`${API}${path}`, { headers: headers(), ...opts }); + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.statusText })); + throw new Error(err.error || 'Request failed'); + } + return res.json(); + } + + function fmt(n) { + return parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + } + + // ─── Tabs ────────────────────────────────────── + + function initTabs() { + document.querySelectorAll('.tab').forEach(tab => { + tab.addEventListener('click', () => { + document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); + document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active')); + tab.classList.add('active'); + document.getElementById(`tab-${tab.dataset.tab}`).classList.add('active'); + + // Load data on tab switch + const t = tab.dataset.tab; + if (t === 'accounts') loadAccounts(); + if (t === 'entries') loadEntries(); + if (t === 'trial-balance') loadTrialBalance(); + if (t === 'income-statement') loadIncomeStatement(); + if (t === 'balance-sheet') loadBalanceSheet(); + if (t === 'aging') loadAging(); + if (t === 'periods') loadPeriods(); + }); + }); + } + + // ─── Chart of Accounts ───────────────────────── + + async function loadAccounts() { + try { + const res = await api('/accounts'); + accounts = res.data || []; + renderAccountsTree(); + } catch (e) { + document.getElementById('accounts-tree').innerHTML = + `
Error: ${e.message}
`; + } + } + + function renderAccountsTree() { + const container = document.getElementById('accounts-tree'); + if (!accounts.length) { container.innerHTML = 'No hay cuentas.
'; return; } + + // Build tree structure + const byParent = {}; + accounts.forEach(a => { + const pid = a.parent_id || 'root'; + if (!byParent[pid]) byParent[pid] = []; + byParent[pid].push(a); + }); + + function buildUl(parentId) { + const children = byParent[parentId] || []; + if (!children.length) return ''; + let html = 'Error: ${e.message}
`; + } + } + + function renderEntries(entries) { + const container = document.getElementById('entries-list'); + if (!entries.length) { container.innerHTML = 'No hay polizas en este periodo.
'; return; } + + let html = `| # | Fecha | Tipo | Descripcion | +Referencia | Monto | Auto | +
|---|---|---|---|---|---|---|
| ${e.entry_number} | +${e.date || ''} | +${e.type || ''} | +${e.description || ''} | +${e.reference_type ? `${e.reference_type} #${e.reference_id}` : ''} | +$${fmt(e.total_amount)} | +${e.is_auto ? 'Si' : 'No'} | +
${entry.description || ''}
+| Cuenta | Nombre | Cargo | +Abono | Nota | +
|---|---|---|---|---|
| ${l.account_code} | +${l.account_name} | +${l.debit ? '$' + fmt(l.debit) : ''} | +${l.credit ? '$' + fmt(l.credit) : ''} | +${l.description || ''} | +
| Totales | +$${fmt(entry.total_debit)} | +$${fmt(entry.total_credit)} | ++ | |
Error: ${e.message}
`; + } + } + + function renderTrialBalance(data) { + const rows = data.data || []; + let html = `| Codigo | Cuenta | +Saldo Inicial | Cargos | +Abonos | Saldo Final | +
|---|---|---|---|---|---|
| ${r.code} | ${r.name} | +$${fmt(r.saldo_inicial)} | +$${fmt(r.cargos)} | +$${fmt(r.abonos)} | +$${fmt(r.saldo_final)} | +
| Totales | +$${fmt(totals.si)} | +$${fmt(totals.c)} | +$${fmt(totals.a)} | +$${fmt(totals.sf)} | +|
Error: ${e.message}
`; + } + } + + function renderIncomeStatement(data) { + let html = '| INGRESOS | |
| ${item.code} - ${item.name} | $${fmt(item.amount)} |
| Total Ingresos | $${fmt(data.ingresos.total)} |
| COSTOS | |
| ${item.code} - ${item.name} | $${fmt(item.amount)} |
| Total Costos | $${fmt(data.costos.total)} |
| UTILIDAD BRUTA | $${fmt(data.utilidad_bruta)} |
| GASTOS | |
| ${item.code} - ${item.name} | $${fmt(item.amount)} |
| Total Gastos | $${fmt(data.gastos.total)} |
| UTILIDAD NETA | +$${fmt(data.utilidad_neta)} |
Error: ${e.message}
`; + } + } + + function renderBalanceSheet(data) { + const balancedBadge = data.balanced + ? 'Cuadrado' + : 'Descuadrado'; + + let html = `Al ${data.as_of} ${balancedBadge}
+| ACTIVO | |
| ${item.code} - ${item.name} | $${fmt(item.balance)} |
| Total Activo | $${fmt(data.activo.total)} |
| PASIVO | |
| ${item.code} - ${item.name} | $${fmt(item.balance)} |
| Total Pasivo | $${fmt(data.pasivo.total)} |
| CAPITAL | |
| ${item.code} - ${item.name} | $${fmt(item.balance)} |
| Total Capital | $${fmt(data.capital.total)} |
| Pasivo + Capital | +$${fmt(data.pasivo.total + data.capital.total)} |
Error: ${e.message}
`; + } + } + + function renderAging(data) { + const rows = data.data || []; + let html = `| Cliente | Corriente | +1-30d | 31-60d | +61-90d | 90+d | +Total | +
|---|---|---|---|---|---|---|
| ${r.name} | +$${fmt(r.corriente)} | +$${fmt(r.d1_30)} | +$${fmt(r.d31_60)} | +$${fmt(r.d61_90)} | +$${fmt(r.d90_plus)} | +$${fmt(r.total)} | +
| Totales | +$${fmt(t.corriente)} | +$${fmt(t.d1_30)} | +$${fmt(t.d31_60)} | +$${fmt(t.d61_90)} | +$${fmt(t.d90_plus)} | +$${fmt(t.total)} | +
Error: ${e.message}
`; + } + } + + function renderPeriods(periods) { + const months = ['', 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', + 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; + let html = `| Periodo | Estado | Cerrado por | Fecha cierre |
|---|---|---|---|
| ${months[p.month]} ${p.year} | +${badge} | +${p.closed_by_name || '-'} | +${p.closed_at ? new Date(p.closed_at).toLocaleDateString('es-MX') : '-'} | +