// /home/Autopartes/pos/static/js/invoicing.js
// Invoicing module: CFDI queue management, cancel, PDF
const Invoicing = (() => {
const API = '/pos/api/invoicing';
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 });
}
function badgeClass(status) {
return {
pending: 'badge-pending',
sending: 'badge-sending',
stamped: 'badge-stamped',
failed: 'badge-failed',
cancelled: 'badge-cancelled',
}[status] || '';
}
function badgeLabel(status) {
return {
pending: 'Pendiente',
sending: 'Enviando',
stamped: 'Timbrado',
failed: 'Fallido',
cancelled: 'Cancelado',
}[status] || status;
}
// ─── Queue List ────────────────────────────────
async function loadQueue() {
try {
const status = document.getElementById('filter-status').value;
const type = document.getElementById('filter-type').value;
let qs = '?per_page=50';
if (status) qs += `&status=${status}`;
if (type) qs += `&type=${type}`;
const res = await api(`/queue${qs}`);
renderQueue(res.data || []);
updateStats(res.data || []);
} catch (e) {
document.getElementById('queue-list').innerHTML =
`
Error: ${e.message}
`;
}
}
function updateStats(items) {
const counts = { pending: 0, sending: 0, stamped: 0, failed: 0, cancelled: 0 };
items.forEach(i => { if (counts[i.status] !== undefined) counts[i.status]++; });
document.getElementById('queue-stats').innerHTML = `
${counts.pending}
Pendientes
${counts.sending}
Enviando
${counts.stamped}
Timbrados
${counts.cancelled}
Cancelados
`;
}
function renderQueue(items) {
const container = document.getElementById('queue-list');
if (!items.length) { container.innerHTML = 'No hay CFDIs en la cola.
'; return; }
let html = `
| # | Venta | Tipo | Folio |
UUID | Estado | Reintentos | Fecha | Acciones |
`;
for (const item of items) {
const uuid = item.uuid_fiscal
? `${item.uuid_fiscal.substring(0, 8)}...`
: '-';
html += `
| ${item.id} |
#${item.sale_id} |
${item.type} |
${item.provisional_folio || '-'} |
${uuid} |
${badgeLabel(item.status)} |
${item.retry_count || 0} |
${item.created_at ? new Date(item.created_at).toLocaleDateString('es-MX') : ''} |
${item.status === 'stamped' ? `` : ''}
${item.sale_id ? `PDF` : ''}
|
`;
}
html += '
';
container.innerHTML = html;
}
// ─── Detail ────────────────────────────────────
async function showDetail(cfdiId) {
try {
const item = await api(`/queue/${cfdiId}`);
let html = `CFDI #${item.id}
#${item.sale_id}
${item.type}
${badgeLabel(item.status)}
${item.provisional_folio || '-'}
${item.uuid_fiscal || '-'}
${item.retry_count}
${item.created_at || '-'}
${item.stamped_at || '-'}
`;
if (item.error_message) {
html += `Error: ${item.error_message}
`;
}
if (item.cancel_motive) {
html += `Motivo cancelacion: ${item.cancel_motive}
`;
}
// XML preview
const xml = item.xml_signed || item.xml_unsigned;
if (xml) {
html += `XML
${escapeHtml(xml)}
`;
}
document.getElementById('detail-content').innerHTML = html;
document.getElementById('detail-modal').classList.add('active');
} catch (e) {
alert('Error: ' + e.message);
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ─── Process Queue ─────────────────────────────
async function processQueue() {
if (!confirm('Procesar todos los CFDIs pendientes?')) return;
try {
const result = await api('/queue/process', { method: 'POST' });
alert(`Procesados: ${result.processed}, Timbrados: ${result.stamped}, Fallidos: ${result.failed}`);
loadQueue();
} catch (e) {
alert('Error: ' + e.message);
}
}
// ─── Cancel ────────────────────────────────────
function showCancelModal(cfdiId) {
document.getElementById('cancel-cfdi-id').value = cfdiId;
document.getElementById('cancel-motive').value = '';
document.getElementById('cancel-replacement-uuid').value = '';
document.getElementById('replacement-uuid-group').style.display = 'none';
document.getElementById('cancel-modal').classList.add('active');
}
function onMotiveChange() {
const motive = document.getElementById('cancel-motive').value;
document.getElementById('replacement-uuid-group').style.display =
motive === '01' ? 'block' : 'none';
}
async function confirmCancel() {
const cfdiId = document.getElementById('cancel-cfdi-id').value;
const motive = document.getElementById('cancel-motive').value;
const replacementUuid = document.getElementById('cancel-replacement-uuid').value;
if (!motive) { alert('Selecciona un motivo de cancelacion.'); return; }
if (motive === '01' && !replacementUuid) { alert('UUID sustituto requerido para motivo 01.'); return; }
if (!confirm('Confirmar cancelacion ante el SAT?')) return;
try {
const body = { motive };
if (replacementUuid) body.replacement_uuid = replacementUuid;
await api(`/cancel/${cfdiId}`, { method: 'POST', body: JSON.stringify(body) });
closeModal('cancel-modal');
loadQueue();
alert('CFDI cancelado exitosamente.');
} catch (e) {
alert('Error: ' + e.message);
}
}
// ─── Modal helpers ─────────────────────────────
function closeModal(id) {
document.getElementById(id).classList.remove('active');
}
// ─── Init ──────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
loadQueue();
});
return {
loadQueue, processQueue, showDetail, showCancelModal,
onMotiveChange, confirmCancel, closeModal,
};
})();