// /home/Autopartes/pos/static/js/invoicing.js // Invoicing module — wired to design-system HTML IDs // Tabs: panel-facturas, panel-notas, panel-complementos, panel-cancelaciones, panel-config // Modals: modalDetalleOverlay, modalCancelOverlay 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 }); } // ---- Auth check ---- function checkAuth() { if (!token()) { window.location.href = '/pos/login'; return false; } return true; } // ---- Tab switching (matches design system onclick="switchTab('xxx')") ---- function switchTab(name) { // Deactivate all tabs document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('is-active'); btn.setAttribute('aria-selected', 'false'); }); document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('is-active')); // Activate the clicked tab button const tabBtn = document.querySelector(`.tab-btn[onclick*="'${name}'"]`) || document.getElementById(`tab-${name}`); if (tabBtn) { tabBtn.classList.add('is-active'); tabBtn.setAttribute('aria-selected', 'true'); } // Activate the target panel const panel = document.getElementById(`panel-${name}`); if (panel) panel.classList.add('is-active'); // Load data for the activated tab if (name === 'facturas') loadFacturas(); if (name === 'notas') loadNotas(); if (name === 'complementos') loadComplementos(); if (name === 'cancelaciones') loadCancelaciones(); } // ---- Badge helpers ---- function statusBadge(status) { const map = { pending: { css: 'badge--pendiente', label: 'Pendiente' }, pendiente: { css: 'badge--pendiente', label: 'Pendiente' }, sending: { css: 'badge--proceso', label: 'Enviando' }, stamped: { css: 'badge--timbrada', label: 'Timbrada' }, timbrada: { css: 'badge--timbrada', label: 'Timbrada' }, failed: { css: 'badge--rechazada', label: 'Fallido' }, cancelled: { css: 'badge--cancelada', label: 'Cancelada' }, cancelada: { css: 'badge--cancelada', label: 'Cancelada' }, ppd: { css: 'badge--ppd', label: 'PPD' }, proceso: { css: 'badge--proceso', label: 'En proceso' }, aceptada: { css: 'badge--aceptada', label: 'Aceptada SAT' }, rechazada: { css: 'badge--rechazada', label: 'Rechazada SAT' }, }; const s = map[status] || { css: '', label: status || '' }; return `${s.label}`; } // ---- Facturas (Tab 1) — loads from CFDI queue with type=Ingreso ---- async function loadFacturas() { const panel = document.getElementById('panel-facturas'); if (!panel) return; const tbody = panel.querySelector('.data-table tbody'); if (!tbody) return; try { const res = await api('/queue?per_page=50&type=Ingreso'); const items = res.data || []; if (!items.length) { tbody.innerHTML = '
No hay solicitudes de cancelacion.
'; return; } grid.innerHTML = allItems.map(item => { const cardClass = item.status === 'cancelled' ? 'cancel-card--aceptada' : item.status === 'cancelling' ? 'cancel-card--proceso' : item.cancel_accepted === false ? 'cancel-card--rechazada' : 'cancel-card--proceso'; const badgeText = item.status === 'cancelled' ? statusBadge('aceptada') : item.cancel_accepted === false ? statusBadge('rechazada') : statusBadge('proceso'); return `Error: ${e.message}
`; } } // ---- Detail modal (uses modalDetalleOverlay) ---- async function showDetail(cfdiId) { const overlay = document.getElementById('modalDetalleOverlay'); if (!overlay) return; try { const item = await api(`/queue/${cfdiId}`); const modalCard = overlay.querySelector('.modal-card'); if (!modalCard) return; // Update header const headerTitle = modalCard.querySelector('div > div:first-child > div:first-child'); const headerSub = modalCard.querySelector('div > div:first-child > div:nth-child(2)'); if (headerTitle) headerTitle.textContent = 'Detalle de Factura'; if (headerSub) headerSub.textContent = `${item.provisional_folio || 'CFDI-' + item.id} \u2014 ${item.status === 'stamped' ? 'Timbrada' : item.status === 'cancelled' ? 'Cancelada' : item.status === 'pending' ? 'Pendiente' : item.status || ''}`; // Update detail grid const detailGrid = modalCard.querySelector('div:nth-child(2)'); if (detailGrid) { detailGrid.innerHTML = `Error: ${escapeHtml(item.error_message)}
` : ''} ${(item.xml_signed || item.xml_unsigned) ? `${escapeHtml(item.xml_signed || item.xml_unsigned)}
` : ''}`;
}
// Wire the cancel button inside modal footer
const cancelBtn = modalCard.querySelector('div:last-child button:last-child');
if (cancelBtn && item.status === 'stamped') {
cancelBtn.style.display = '';
cancelBtn.onclick = () => {
overlay.style.display = 'none';
showCancelModal(cfdiId);
};
} else if (cancelBtn) {
cancelBtn.style.display = 'none';
}
// Store current CFDI id for use by footer buttons
overlay.dataset.cfdiId = cfdiId;
overlay.dataset.saleId = item.sale_id || '';
overlay.style.display = 'flex';
} catch (e) {
alert('Error al cargar detalle: ' + e.message);
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ---- Cancel modal (uses modalCancelOverlay) ----
let cancelTargetId = null;
function showCancelModal(cfdiId) {
cancelTargetId = cfdiId;
const overlay = document.getElementById('modalCancelOverlay');
if (overlay) overlay.style.display = 'flex';
}
async function confirmCancel() {
if (!cancelTargetId) return;
const overlay = document.getElementById('modalCancelOverlay');
if (!overlay) return;
const selectedRadio = overlay.querySelector('input[name="motivo-sat"]:checked');
if (!selectedRadio) { alert('Selecciona un motivo de cancelacion.'); return; }
const motive = selectedRadio.value;
const uuidInput = overlay.querySelector('input[type="text"]');
const replacementUuid = uuidInput ? uuidInput.value.trim() : '';
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/${cancelTargetId}`, { method: 'POST', body: JSON.stringify(body) });
overlay.style.display = 'none';
cancelTargetId = null;
loadFacturas();
alert('CFDI cancelado exitosamente.');
} catch (e) {
alert('Error: ' + e.message);
}
}
// ---- Process entire 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}`);
loadFacturas();
} catch (e) {
alert('Error: ' + e.message);
}
}
// ---- Clock ----
function startClock() {
const el = document.getElementById('live-clock');
if (!el) return;
const tick = () => {
const now = new Date();
el.textContent = now.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
};
tick();
setInterval(tick, 1000);
}
// ---- Wire cancel modal "Solicitar Cancelacion SAT" button ----
function wireCancelModal() {
const overlay = document.getElementById('modalCancelOverlay');
if (!overlay) return;
const footerBtns = overlay.querySelectorAll('div:last-child button');
if (footerBtns.length >= 2) {
// Last button = confirm cancel
footerBtns[footerBtns.length - 1].onclick = () => confirmCancel();
// Second to last = close
footerBtns[footerBtns.length - 2].onclick = () => {
overlay.style.display = 'none';
cancelTargetId = null;
};
}
}
// ---- Wire detail modal close button ----
function wireDetailModal() {
const overlay = document.getElementById('modalDetalleOverlay');
if (!overlay) return;
const closeBtn = overlay.querySelector('button[onclick*="modalDetalleOverlay"]');
if (closeBtn) {
closeBtn.onclick = () => { overlay.style.display = 'none'; };
}
}
// ---- Init ----
function init() {
if (!checkAuth()) return;
startClock();
wireDetailModal();
wireCancelModal();
// Load initial tab data (facturas is active by default)
loadFacturas();
}
document.addEventListener('DOMContentLoaded', init);
// ---- Nueva Factura modal ----
function showNewInvoiceModal() {
const overlay = document.getElementById('newInvoiceModalOverlay');
if (!overlay) return;
const input = overlay.querySelector('#invoiceSaleId');
if (input) input.value = '';
document.getElementById('invoiceResult').innerHTML = '';
overlay.style.display = 'flex';
}
function closeNewInvoiceModal() {
const overlay = document.getElementById('newInvoiceModalOverlay');
if (overlay) overlay.style.display = 'none';
}
async function submitNewInvoice() {
const saleId = parseInt(document.getElementById('invoiceSaleId').value);
const resultEl = document.getElementById('invoiceResult');
if (!saleId) {
resultEl.innerHTML = 'Ingrese un ID de venta valido.';
return;
}
try {
const result = await api('/invoice', {
method: 'POST',
body: JSON.stringify({ sale_id: saleId }),
});
resultEl.innerHTML = 'Factura generada: ' + (result.provisional_folio || 'CFDI-' + (result.id || '')) + '';
loadFacturas();
setTimeout(() => closeNewInvoiceModal(), 1500);
} catch (e) {
resultEl.innerHTML = 'Error: ' + e.message + '';
}
}
// ---- Nota de Credito placeholder ----
function notaCreditoPlaceholder() {
alert('Nota de credito: proximamente');
}
// Expose switchTab globally for onclick handlers in HTML
window.switchTab = switchTab;
window.showNewInvoiceModal = showNewInvoiceModal;
window.closeNewInvoiceModal = closeNewInvoiceModal;
window.submitNewInvoice = submitNewInvoice;
window.notaCreditoPlaceholder = notaCreditoPlaceholder;
return {
switchTab, loadFacturas, loadNotas, loadComplementos, loadCancelaciones,
showDetail, showCancelModal, confirmCancel, processQueue,
showNewInvoiceModal, closeNewInvoiceModal, submitNewInvoice, notaCreditoPlaceholder,
};
// Register Cmd+K items
if (typeof registerCmdKItem === "function") {
registerCmdKItem({ group: "Principal", label: "POS Ventas", href: "/pos/sale", icon: "🛒" });
registerCmdKItem({ group: "Principal", label: "Catálogo", href: "/pos/catalog", icon: "📁" });
registerCmdKItem({ group: "Principal", label: "Clientes", href: "/pos/customers", icon: "👤" });
registerCmdKItem({ group: "Principal", label: "Dashboard", href: "/pos/dashboard", icon: "📊" });
}
})();