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:
2026-06-11 08:59:56 +00:00
parent ea29cc31c0
commit 2b73c2c6db
23 changed files with 1665 additions and 230 deletions

View File

@@ -161,7 +161,7 @@ const Config = (() => {
_branches.forEach(function(b, idx) {
var statusBadge = b.is_active
? '<span class="badge badge--ok" style="padding:0 4px;font-size:0.625rem;">' + (idx === 0 ? 'Principal' : 'Activa') + '</span>'
? '<span class="badge badge--ok" style="padding:0 4px;font-size:0.625rem;">' + (b.is_main ? 'Principal' : 'Activa') + '</span>'
: '<span class="badge badge--inactive" style="padding:0 4px;font-size:0.625rem;">Inactiva</span>';
html += '<div class="device-card">'
@@ -170,14 +170,20 @@ const Config = (() => {
+ '</div>'
+ '<div class="device-card__body">'
+ '<div class="device-card__name">' + escHtml(b.name) + '</div>'
+ '<div class="device-card__detail">' + statusBadge + '</div>'
+ '<div class="device-card__detail">' + statusBadge
+ (b.rfc ? ' · RFC: ' + escHtml(b.rfc) : '')
+ (b.codigo_postal ? ' · CP: ' + escHtml(b.codigo_postal) : '')
+ '</div>'
+ (b.address ? '<div class="device-card__detail">' + escHtml(b.address) + '</div>' : '')
+ (b.phone ? '<div class="device-card__detail">' + escHtml(b.phone) + '</div>' : '')
+ '</div>'
+ '<div class="device-card__actions">'
+ '<button class="btn btn--ghost btn--sm" onclick="Config.editBranch(' + b.id + ')">Editar</button>'
+ '</div></div>';
});
// "Agregar Sucursal" card
html += '<div class="device-card" style="border-style:dashed;cursor:pointer;" onclick="Config.openModal(\'modal-branch\')">'
html += '<div class="device-card" style="border-style:dashed;cursor:pointer;" onclick="Config.openBranchModal()">'
+ '<div class="device-card__icon" style="background:transparent;border:2px dashed var(--color-border);">'
+ '<svg viewBox="0 0 24 24" style="stroke:var(--color-text-muted);"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>'
+ '</div>'
@@ -203,9 +209,36 @@ const Config = (() => {
});
}
function openBranchModal(branch) {
document.getElementById('branch-modal-title').textContent = branch ? 'Editar Sucursal' : 'Nueva Sucursal';
document.getElementById('branch-id').value = branch ? branch.id : '';
document.getElementById('branch-name').value = branch ? branch.name : '';
document.getElementById('branch-rfc').value = branch ? (branch.rfc || '') : '';
document.getElementById('branch-razon').value = branch ? (branch.razon_social || '') : '';
document.getElementById('branch-regimen').value = branch ? (branch.regimen_fiscal || '') : '';
document.getElementById('branch-cp').value = branch ? (branch.codigo_postal || '') : '';
document.getElementById('branch-serie').value = branch ? (branch.serie_cfdi || '') : '';
document.getElementById('branch-folio').value = branch ? (branch.folio_inicial || '') : '';
document.getElementById('branch-licencia').value = branch ? (branch.licencia_fiscal || '') : '';
document.getElementById('branch-address').value = branch ? (branch.address || '') : '';
document.getElementById('branch-phone').value = branch ? (branch.phone || '') : '';
document.getElementById('branch-main').checked = branch ? !!branch.is_main : false;
document.getElementById('branch-cert').value = branch ? (branch.certificado_pem || '') : '';
document.getElementById('branch-key').value = branch ? (branch.llave_pem || '') : '';
openModal('modal-branch');
}
function editBranch(branchId) {
var b = _branches.find(function(x) { return x.id === branchId; });
if (!b) { toast('Sucursal no encontrada', 'error'); return; }
openBranchModal(b);
}
async function saveBranch(data) {
var res = await fetch(API + '/branches', {
method: 'POST',
var branchId = document.getElementById('branch-id').value;
var url = API + '/branches' + (branchId ? '/' + branchId : '');
var res = await fetch(url, {
method: branchId ? 'PUT' : 'POST',
headers: headers(),
body: JSON.stringify(data)
});
@@ -429,14 +462,36 @@ const Config = (() => {
try {
await saveBranch({
name: name,
address: document.getElementById('branch-address').value.trim(),
phone: document.getElementById('branch-phone').value.trim()
rfc: document.getElementById('branch-rfc').value.trim() || null,
razon_social: document.getElementById('branch-razon').value.trim() || null,
regimen_fiscal: document.getElementById('branch-regimen').value.trim() || null,
codigo_postal: document.getElementById('branch-cp').value.trim() || null,
serie_cfdi: document.getElementById('branch-serie').value.trim() || null,
folio_inicial: document.getElementById('branch-folio').value ? parseInt(document.getElementById('branch-folio').value, 10) : null,
licencia_fiscal: document.getElementById('branch-licencia').value.trim() || null,
address: document.getElementById('branch-address').value.trim() || null,
phone: document.getElementById('branch-phone').value.trim() || null,
is_main: document.getElementById('branch-main').checked,
certificado_pem: document.getElementById('branch-cert').value.trim() || null,
llave_pem: document.getElementById('branch-key').value.trim() || null,
});
toast('Sucursal creada');
toast('Sucursal guardada');
closeModal('modal-branch');
// Reset form
document.getElementById('branch-id').value = '';
document.getElementById('branch-name').value = '';
document.getElementById('branch-rfc').value = '';
document.getElementById('branch-razon').value = '';
document.getElementById('branch-regimen').value = '';
document.getElementById('branch-cp').value = '';
document.getElementById('branch-serie').value = '';
document.getElementById('branch-folio').value = '';
document.getElementById('branch-licencia').value = '';
document.getElementById('branch-address').value = '';
document.getElementById('branch-phone').value = '';
document.getElementById('branch-main').checked = false;
document.getElementById('branch-cert').value = '';
document.getElementById('branch-key').value = '';
await loadBranches();
} catch (e) {
toast(e.message, 'error');
@@ -805,7 +860,7 @@ const Config = (() => {
loadBusiness, saveBusiness, saveTaxParams,
loadCurrency, saveCurrency,
loadModules, saveModules,
openModal, closeModal
openModal, closeModal, openBranchModal, editBranch
};
// Register Cmd+K items
if (typeof registerCmdKItem === "function") {