feat: Fase 1-3 completas - precios proveedor, multi-sucursal, factura global
Fase 1: Lista de precios de proveedor - Tabla supplier_catalog_prices en master DB - Endpoints GET/POST/PUT/DELETE /supplier-catalog/prices - Upload CSV/Excel de precios de proveedor - Visualizacion de supplier_price en catalogo y POS Fase 2: Multi-sucursal completo - Migracion v4.0: inventory.branch_id=NULL, tabla inventory_stock - Campos fiscales en branches (RFC, regimen, CP, serie CFDI, certificados) - Trigger trg_update_inventory_stock para sincronizar stock por sucursal - Backend config_bp.py con CRUD de sucursales fiscales - Backend inventory_bp.py y pos_bp.py refactorizados para inventario compartido - Backend invoicing_bp.py usa datos fiscales de la sucursal de la venta - Frontend config.html/js con modal de sucursales expandido Fase 3: Factura global mensual - Migracion v4.1: tablas global_invoice_sales, sales.global_invoiced_at - build_global_invoice_xml() con InformacionGlobal SAT-compliant - Servicio global_invoice.py para agrupar ventas PUE <=000 - Endpoints POST/GET /global-invoice y /global-invoice/eligible-sales - Frontend invoicing.html/js con boton y modal de factura global
This commit is contained in:
@@ -46,6 +46,11 @@
|
||||
var checkoutBtn = document.getElementById('checkoutBtn');
|
||||
var cartFab = document.getElementById('cartFab');
|
||||
var cartCloseBtn = document.getElementById('cartCloseBtn');
|
||||
// Supplier prices upload
|
||||
var uploadPricesBtn = document.getElementById('uploadPricesBtn');
|
||||
var uploadPricesModal= document.getElementById('uploadPricesModal');
|
||||
var uploadPricesFile = document.getElementById('uploadPricesFile');
|
||||
var uploadPricesStatus=document.getElementById('uploadPricesStatus');
|
||||
|
||||
// ─── Navigation State ───
|
||||
var nav = {
|
||||
@@ -1053,6 +1058,7 @@
|
||||
'</div>' +
|
||||
'<div class="part-card__footer">' +
|
||||
(p.local_price ? '<span class="part-card__price">$' + fmt(p.local_price) + '</span>' : '<span class="part-card__price" style="color:var(--color-text-muted);">Sin precio</span>') +
|
||||
(p.supplier_price ? '<span class="part-card__price" style="color:#2d7d46;font-size:0.85em;">Prov: $' + fmt(p.supplier_price) + '</span>' : '') +
|
||||
stockBadge +
|
||||
'</div>' +
|
||||
'</article>';
|
||||
@@ -2105,6 +2111,53 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Supplier prices upload ─────────────────────────────────────────────
|
||||
function openUploadPricesModal() {
|
||||
if (uploadPricesModal) uploadPricesModal.style.display = 'flex';
|
||||
if (uploadPricesStatus) uploadPricesStatus.innerHTML = '';
|
||||
if (uploadPricesFile) uploadPricesFile.value = '';
|
||||
}
|
||||
function closeUploadPricesModal() {
|
||||
if (uploadPricesModal) uploadPricesModal.style.display = 'none';
|
||||
}
|
||||
async function submitUploadPrices() {
|
||||
if (!uploadPricesFile || !uploadPricesFile.files || !uploadPricesFile.files[0]) {
|
||||
if (uploadPricesStatus) uploadPricesStatus.innerHTML = '<span style="color:var(--color-error);">Selecciona un archivo primero.</span>';
|
||||
return;
|
||||
}
|
||||
var form = new FormData();
|
||||
form.append('file', uploadPricesFile.files[0]);
|
||||
if (uploadPricesStatus) uploadPricesStatus.innerHTML = 'Subiendo...';
|
||||
try {
|
||||
var res = await fetch('/pos/api/supplier-catalog/prices/upload', {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': 'Bearer ' + token },
|
||||
body: form
|
||||
});
|
||||
var data = await res.json();
|
||||
if (res.ok && data.success) {
|
||||
if (uploadPricesStatus) uploadPricesStatus.innerHTML = '<span style="color:var(--color-success);">✓ Precios actualizados: ' + data.processed + ' (insertados: ' + data.inserted + ', actualizados: ' + data.updated + ')</span>';
|
||||
uploadPricesFile.value = '';
|
||||
} else {
|
||||
var msg = data.error || 'Error al subir precios';
|
||||
var details = (data.details || []).join('<br>');
|
||||
if (uploadPricesStatus) uploadPricesStatus.innerHTML = '<span style="color:var(--color-error);">' + esc(msg) + '</span>' + (details ? '<div style="margin-top:4px;font-size:0.9em;">' + details + '</div>' : '');
|
||||
}
|
||||
} catch (e) {
|
||||
if (uploadPricesStatus) uploadPricesStatus.innerHTML = '<span style="color:var(--color-error);">Error de red: ' + esc(e.message) + '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
function shouldShowUploadPricesButton() {
|
||||
try {
|
||||
var user = JSON.parse(localStorage.getItem('pos_employee') || '{}');
|
||||
return user.role === 'owner' || user.role === 'admin';
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
if (uploadPricesBtn && shouldShowUploadPricesButton()) {
|
||||
uploadPricesBtn.style.display = 'inline-flex';
|
||||
}
|
||||
|
||||
window.CatalogApp = {
|
||||
toggleCart: toggleCart,
|
||||
goToCheckout: goToCheckout,
|
||||
@@ -2124,6 +2177,9 @@
|
||||
togglePlate: togglePlate,
|
||||
lookupPlate: lookupPlate,
|
||||
setMode: setCatalogMode,
|
||||
openUploadPricesModal: openUploadPricesModal,
|
||||
closeUploadPricesModal: closeUploadPricesModal,
|
||||
submitUploadPrices: submitUploadPrices,
|
||||
};
|
||||
|
||||
// ─── INIT ───
|
||||
|
||||
Reference in New Issue
Block a user