223 lines
9.5 KiB
JavaScript
223 lines
9.5 KiB
JavaScript
/**
|
|
* cuentas.js — Accounts receivable logic for Nexus Autoparts
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var API = '';
|
|
var currentCustomerId = null;
|
|
var customerPage = 1;
|
|
|
|
// ================================================================
|
|
// Utility
|
|
// ================================================================
|
|
|
|
function toast(msg, type) {
|
|
var el = document.createElement('div');
|
|
el.className = 'toast ' + (type || 'success');
|
|
el.textContent = msg;
|
|
document.body.appendChild(el);
|
|
setTimeout(function () { el.remove(); }, 3000);
|
|
}
|
|
|
|
function api(path, opts) {
|
|
opts = opts || {};
|
|
return fetch(API + path, opts).then(function (r) {
|
|
if (!r.ok) return r.json().then(function (d) { throw new Error(d.error || 'Error'); });
|
|
return r.json();
|
|
});
|
|
}
|
|
|
|
function esc(s) {
|
|
if (!s) return '';
|
|
var d = document.createElement('div');
|
|
d.textContent = s;
|
|
return d.innerHTML;
|
|
}
|
|
|
|
function fmt(n) {
|
|
return '$' + (parseFloat(n) || 0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
}
|
|
|
|
function fmtDate(d) {
|
|
if (!d) return '';
|
|
var dt = new Date(d);
|
|
return dt.toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' });
|
|
}
|
|
|
|
// ================================================================
|
|
// Customer List
|
|
// ================================================================
|
|
|
|
var searchTimer = null;
|
|
document.getElementById('customer-search').addEventListener('input', function () {
|
|
clearTimeout(searchTimer);
|
|
searchTimer = setTimeout(function () {
|
|
customerPage = 1;
|
|
loadCustomers();
|
|
}, 400);
|
|
});
|
|
|
|
function loadCustomers() {
|
|
var search = document.getElementById('customer-search').value;
|
|
var grid = document.getElementById('customer-grid');
|
|
grid.innerHTML = '<div class="empty-state">Cargando...</div>';
|
|
|
|
var params = '?page=' + customerPage + '&per_page=30';
|
|
if (search) params += '&search=' + encodeURIComponent(search);
|
|
|
|
api('/api/pos/customers' + params).then(function (res) {
|
|
var data = res.data || [];
|
|
if (data.length === 0) {
|
|
grid.innerHTML = '<div class="empty-state">No se encontraron clientes</div>';
|
|
document.getElementById('customer-pagination').innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = data.map(function (c) {
|
|
return '<div class="customer-card-item" data-id="' + c.id_customer + '">' +
|
|
'<div class="cci-name">' + esc(c.name) + '</div>' +
|
|
'<div class="cci-rfc">' + esc(c.rfc || 'Sin RFC') + '</div>' +
|
|
'<div class="cci-balance-row">' +
|
|
'<span class="cci-balance ' + (c.balance > 0 ? 'positive' : 'zero') + '">' + fmt(c.balance) + '</span>' +
|
|
'<span class="cci-limit">Limite: ' + fmt(c.credit_limit) + '</span></div></div>';
|
|
}).join('');
|
|
|
|
grid.querySelectorAll('.customer-card-item').forEach(function (card) {
|
|
card.addEventListener('click', function () {
|
|
showCustomerDetail(parseInt(card.getAttribute('data-id')));
|
|
});
|
|
});
|
|
|
|
// Pagination
|
|
var pag = res.pagination;
|
|
var pagEl = document.getElementById('customer-pagination');
|
|
if (pag.total_pages <= 1) { pagEl.innerHTML = ''; return; }
|
|
pagEl.innerHTML = '<button ' + (pag.page <= 1 ? 'disabled' : '') + ' data-p="' + (pag.page - 1) + '">«</button>' +
|
|
'<span class="page-info">Pag ' + pag.page + '/' + pag.total_pages + '</span>' +
|
|
'<button ' + (pag.page >= pag.total_pages ? 'disabled' : '') + ' data-p="' + (pag.page + 1) + '">»</button>';
|
|
pagEl.querySelectorAll('button').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
customerPage = parseInt(btn.getAttribute('data-p'));
|
|
loadCustomers();
|
|
});
|
|
});
|
|
}).catch(function (err) {
|
|
console.error('Error loading customers:', err);
|
|
grid.innerHTML = '<div class="empty-state">Error al cargar clientes</div>';
|
|
});
|
|
}
|
|
|
|
// ================================================================
|
|
// Customer Detail
|
|
// ================================================================
|
|
|
|
function showCustomerDetail(customerId) {
|
|
currentCustomerId = customerId;
|
|
document.getElementById('list-view').style.display = 'none';
|
|
document.getElementById('detail-view').style.display = 'block';
|
|
|
|
api('/api/pos/customers/' + customerId + '/statement').then(function (res) {
|
|
var c = res.customer;
|
|
document.getElementById('dh-name').textContent = c.name;
|
|
document.getElementById('dh-rfc').textContent = c.rfc || 'Sin RFC';
|
|
|
|
var balEl = document.getElementById('dh-balance');
|
|
balEl.textContent = fmt(c.balance);
|
|
balEl.className = 'dh-value ' + (c.balance > 0 ? 'danger' : 'success');
|
|
|
|
document.getElementById('dh-limit').textContent = fmt(c.credit_limit);
|
|
document.getElementById('dh-terms').textContent = c.payment_terms + ' dias';
|
|
|
|
// Invoices
|
|
var invBody = document.getElementById('invoice-list');
|
|
if (res.invoices.length === 0) {
|
|
invBody.innerHTML = '<tr><td colspan="5" class="empty-state">Sin facturas</td></tr>';
|
|
} else {
|
|
invBody.innerHTML = res.invoices.map(function (i) {
|
|
return '<tr>' +
|
|
'<td style="font-family:monospace;font-weight:600">' + esc(i.folio) + '</td>' +
|
|
'<td>' + fmtDate(i.date_issued) + '</td>' +
|
|
'<td>' + fmt(i.total) + '</td>' +
|
|
'<td>' + fmt(i.amount_paid) + '</td>' +
|
|
'<td><span class="status-badge ' + i.status + '">' + i.status + '</span></td></tr>';
|
|
}).join('');
|
|
}
|
|
|
|
// Payments
|
|
var payBody = document.getElementById('payment-list');
|
|
if (res.payments.length === 0) {
|
|
payBody.innerHTML = '<tr><td colspan="5" class="empty-state">Sin pagos</td></tr>';
|
|
} else {
|
|
payBody.innerHTML = res.payments.map(function (p) {
|
|
return '<tr>' +
|
|
'<td>' + fmtDate(p.date_payment) + '</td>' +
|
|
'<td style="font-weight:600;color:var(--success)">' + fmt(p.amount) + '</td>' +
|
|
'<td>' + esc(p.payment_method) + '</td>' +
|
|
'<td>' + esc(p.reference || '') + '</td>' +
|
|
'<td>' + esc(p.invoice_folio || 'General') + '</td></tr>';
|
|
}).join('');
|
|
}
|
|
|
|
// Populate invoice dropdown for payment form
|
|
var invSelect = document.getElementById('pay-invoice');
|
|
invSelect.innerHTML = '<option value="">Abono general</option>';
|
|
res.invoices.filter(function (i) { return i.status !== 'paid' && i.status !== 'cancelled'; })
|
|
.forEach(function (i) {
|
|
invSelect.innerHTML += '<option value="' + i.id_invoice + '">' +
|
|
i.folio + ' — ' + fmt(i.total - i.amount_paid) + ' pendiente</option>';
|
|
});
|
|
});
|
|
}
|
|
|
|
document.getElementById('btn-back-list').addEventListener('click', function () {
|
|
document.getElementById('detail-view').style.display = 'none';
|
|
document.getElementById('list-view').style.display = 'block';
|
|
currentCustomerId = null;
|
|
loadCustomers();
|
|
});
|
|
|
|
// ================================================================
|
|
// Register Payment
|
|
// ================================================================
|
|
|
|
document.getElementById('btn-pay').addEventListener('click', function () {
|
|
var amount = parseFloat(document.getElementById('pay-amount').value);
|
|
if (!amount || amount <= 0) {
|
|
toast('Ingresa un monto valido', 'error');
|
|
return;
|
|
}
|
|
|
|
var invoiceId = document.getElementById('pay-invoice').value;
|
|
|
|
api('/api/pos/payments', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
customer_id: currentCustomerId,
|
|
amount: amount,
|
|
payment_method: document.getElementById('pay-method').value,
|
|
reference: document.getElementById('pay-reference').value.trim() || null,
|
|
invoice_id: invoiceId ? parseInt(invoiceId) : null,
|
|
notes: document.getElementById('pay-notes').value.trim() || null
|
|
})
|
|
}).then(function () {
|
|
toast('Pago de ' + fmt(amount) + ' registrado');
|
|
// Clear form
|
|
document.getElementById('pay-amount').value = '';
|
|
document.getElementById('pay-reference').value = '';
|
|
document.getElementById('pay-notes').value = '';
|
|
// Refresh detail
|
|
showCustomerDetail(currentCustomerId);
|
|
}).catch(function (err) {
|
|
toast(err.message, 'error');
|
|
});
|
|
});
|
|
|
|
// ================================================================
|
|
// Init
|
|
// ================================================================
|
|
|
|
loadCustomers();
|
|
})();
|