From 6d07eab5c0c04db2d2dc05ab88485f1bc86edcd2 Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Thu, 16 Jul 2026 08:13:56 +0000 Subject: [PATCH] Add workshop module toggle in Modules & Integrations settings --- pos/blueprints/config_bp.py | 4 ++++ pos/static/js/app-init.js | 16 ++++++++++++++-- pos/static/js/config.js | 6 +++++- pos/static/js/pos.js | 12 +++++++++++- pos/static/js/sidebar.js | 2 +- pos/static/pwa/sw.js | 2 +- pos/templates/config.html | 10 ++++++++++ 7 files changed, 46 insertions(+), 6 deletions(-) diff --git a/pos/blueprints/config_bp.py b/pos/blueprints/config_bp.py index 51c087b..8d276ce 100644 --- a/pos/blueprints/config_bp.py +++ b/pos/blueprints/config_bp.py @@ -1066,6 +1066,7 @@ def get_modules(): 'marketplace': _bool('module_marketplace'), 'meli': _bool('module_meli'), 'catalog': _bool('module_catalog'), + 'workshop': _bool('module_workshop'), }) @@ -1082,6 +1083,7 @@ def update_modules(): 'module_marketplace': 'true' if data.get('marketplace') else 'false', 'module_meli': 'true' if data.get('meli') else 'false', 'module_catalog': 'true' if data.get('catalog') else 'false', + 'module_workshop': 'true' if data.get('workshop') else 'false', } for key, value in settings.items(): @@ -1098,6 +1100,8 @@ def update_modules(): 'whatsapp': data.get('whatsapp'), 'marketplace': data.get('marketplace'), 'meli': data.get('meli'), + 'catalog': data.get('catalog'), + 'workshop': data.get('workshop'), }}) diff --git a/pos/static/js/app-init.js b/pos/static/js/app-init.js index 6971348..7d71349 100644 --- a/pos/static/js/app-init.js +++ b/pos/static/js/app-init.js @@ -183,13 +183,20 @@ }; // ─── Page guard based on role + permissions ─── + function moduleEnabled(key) { + try { + var modules = JSON.parse(localStorage.getItem('pos_modules') || '{}'); + return modules[key] !== false; + } catch(e) { return true; } + } + function isPageAllowed(pagePath, userRole, userPerms) { if (userRole === 'owner' || userRole === 'admin') return true; // Restricted roles (workshop/mechanic/counter/cashier) see modules based on permissions. if (['workshop', 'mechanic', 'counter', 'cashier'].indexOf(userRole) !== -1) { var allowed = []; - if (userRole === 'workshop' || userRole === 'mechanic') { + if ((userRole === 'workshop' || userRole === 'mechanic') && moduleEnabled('workshop')) { allowed = ['/pos/workshop']; } var permMap = { @@ -208,6 +215,8 @@ }; for (var p in permMap) { if (userPerms.indexOf(p) !== -1 && allowed.indexOf(permMap[p]) === -1) { + // Hide workshop if its module is disabled + if (permMap[p] === '/pos/workshop' && !moduleEnabled('workshop')) continue; allowed.push(permMap[p]); } } @@ -229,7 +238,9 @@ fallback = '/pos/dashboard'; } else if (['workshop', 'mechanic', 'counter', 'cashier'].indexOf(userRole) !== -1) { var allowed = []; - if (userRole === 'workshop' || userRole === 'mechanic') allowed = ['/pos/workshop']; + if ((userRole === 'workshop' || userRole === 'mechanic') && moduleEnabled('workshop')) { + allowed = ['/pos/workshop']; + } var permMap = { 'pos.sell': '/pos/sale', 'pos.view': '/pos/sale', @@ -246,6 +257,7 @@ }; for (var p in permMap) { if (userPerms.indexOf(p) !== -1 && allowed.indexOf(permMap[p]) === -1) { + if (permMap[p] === '/pos/workshop' && !moduleEnabled('workshop')) continue; allowed.push(permMap[p]); } } diff --git a/pos/static/js/config.js b/pos/static/js/config.js index ddbc70f..da035bd 100644 --- a/pos/static/js/config.js +++ b/pos/static/js/config.js @@ -956,10 +956,12 @@ const Config = (() => { var cbMp = document.getElementById('cfg-module-marketplace'); var cbMeli = document.getElementById('cfg-module-meli'); var cbCat = document.getElementById('cfg-module-catalog'); + var cbWork = document.getElementById('cfg-module-workshop'); if (cbWa) cbWa.checked = data.whatsapp !== false; if (cbMp) cbMp.checked = data.marketplace !== false; if (cbMeli) cbMeli.checked = data.meli !== false; if (cbCat) cbCat.checked = data.catalog !== false; + if (cbWork) cbWork.checked = data.workshop !== false; localStorage.setItem('pos_modules', JSON.stringify(data)); } catch (e) { console.error('Config.loadModules:', e); @@ -980,14 +982,16 @@ const Config = (() => { var cbMp = document.getElementById('cfg-module-marketplace'); var cbMeli = document.getElementById('cfg-module-meli'); var cbCat = document.getElementById('cfg-module-catalog'); + var cbWork = document.getElementById('cfg-module-workshop'); var cbCr = document.getElementById('cfg-module-counter-remission'); - if (!cbWa && !cbMp && !cbMeli && !cbCat && !cbCr) return; + if (!cbWa && !cbMp && !cbMeli && !cbCat && !cbWork && !cbCr) return; try { var data = { whatsapp: cbWa ? cbWa.checked : true, marketplace: cbMp ? cbMp.checked : true, meli: cbMeli ? cbMeli.checked : true, catalog: cbCat ? cbCat.checked : true, + workshop: cbWork ? cbWork.checked : true, }; var res = await fetch(API + '/modules', { method: 'PUT', diff --git a/pos/static/js/pos.js b/pos/static/js/pos.js index 6b496af..7890ff2 100644 --- a/pos/static/js/pos.js +++ b/pos/static/js/pos.js @@ -34,6 +34,7 @@ const POS = (() => { let counterRemissionEnabled = false; let allowZeroPriceSales = true; let allowNegativeStock = false; + let workshopModuleEnabled = true; let currentPerms = []; let receiptConfig = {}; let couriers = []; @@ -93,7 +94,7 @@ const POS = (() => { } if (!canDiscount) hide('[onclick="POS.applyDiscount()"]'); if (!canEditPrice) hide('[onclick="POS.modifyPrice()"]'); - if (!canCreateWorkshopOrder) { + if (!canCreateWorkshopOrder || !workshopModuleEnabled) { hide('[onclick="POS.createServiceOrder()"]'); hide('[title="Orden de servicio"]'); } @@ -153,6 +154,14 @@ const POS = (() => { allowZeroPriceSales = true; allowNegativeStock = false; } + // Module toggles (from app-init preloaded modules) + try { + var modules = window.POS_USER && window.POS_USER.modules; + if (!modules) modules = JSON.parse(localStorage.getItem('pos_modules') || '{}'); + workshopModuleEnabled = modules.workshop !== false; + } catch (e) { + workshopModuleEnabled = true; + } // Counter remission workflow applies only to the counter role. canCreateRemission = counterRemissionEnabled && employeeRole === 'counter' && perms.includes('pos.remission'); @@ -1390,6 +1399,7 @@ const POS = (() => { // ─── Service Order from POS ────────── function createServiceOrder() { + if (!workshopModuleEnabled) { showToast('El módulo de taller no está habilitado'); return; } if (!canCreateWorkshopOrder) { showToast('No tienes permiso para ordenes de taller'); return; } if (cart.length === 0) { showToast('Carrito vacio'); return; } const zeroItem = findZeroPriceItem(); diff --git a/pos/static/js/sidebar.js b/pos/static/js/sidebar.js index 393eff0..4527484 100644 --- a/pos/static/js/sidebar.js +++ b/pos/static/js/sidebar.js @@ -80,7 +80,7 @@ window.renderSidebar = function(modulesOverride) { { label: _t('nav_management'), items: [ { id: 'customers', name: _t('customers'), href: '/pos/customers', icon: '' }, { id: 'remission_notes', name: _t('remission_notes'), href: '/pos/remission-notes', icon: '' }, - { id: 'workshop', name: 'Taller', href: '/pos/workshop', icon: '' }, + moduleEnabled('workshop') ? { id: 'workshop', name: 'Taller', href: '/pos/workshop', icon: '' } : null, { id: 'quotations', name: 'Cotizaciones', href: '/pos/quotations', icon: '' }, moduleEnabled('marketplace') ? { id: 'marketplace', name: 'Marketplace', href: '/pos/marketplace', icon: '' } : null, moduleEnabled('meli') ? { id: 'meli', name: 'MercadoLibre', href: '/pos/marketplace-external', icon: '' } : null, diff --git a/pos/static/pwa/sw.js b/pos/static/pwa/sw.js index 593e5fd..64ac67e 100644 --- a/pos/static/pwa/sw.js +++ b/pos/static/pwa/sw.js @@ -6,7 +6,7 @@ // The fetch handler normalizes static asset URLs (strips ?v= query strings) // so templates can use cache-busting query params freely. -const VERSION = 45; +const VERSION = 46; const CACHE_NAME = 'nexus-pos-v' + VERSION; const APP_SHELL = [ diff --git a/pos/templates/config.html b/pos/templates/config.html index 22b5e72..4fccedf 100644 --- a/pos/templates/config.html +++ b/pos/templates/config.html @@ -421,6 +421,16 @@ +
+
+ Taller / Servicio + Mostrar el módulo de órdenes de servicio y taller +
+ +
Notas de remisión en mostrador