const { getStoreLocation, getStorePaymentTerminals, getStorePrinters } = require('../services/siw'); const { findMerakiNetwork, getMerakiDeviceAvailabilities, getMerakiClients, } = require('../services/meraki'); const { getMDMDevices } = require('../services/mdm'); const { findMatchingClient, getClientStatus } = require('../utils/merakiMatcher'); const { createHealthReport } = require('../models/HealthReport'); const { MDM_DEVICE_TYPES, filterMdmByType } = require('../constants'); const logger = require('../utils/logger'); // Configurable scoring penalties (higher = worse impact) const SCORING = { noMerakiNetwork: 50, switchOffline: 15, apOffline: 5, apAlerting: 3, serverOffline: 35, noMdmData: 20, paymentTerminalOffline: 5, printerOffline: 4, // Mobile registers, customer displays and iPhones currently contribute to issues list only }; async function getStoreHealth(storeNumber, _mode = 'default') { logger.info('Running health analysis', { storeNumber }); let locationData, merakiNetwork, merakiClients = [], mdmDevices = [], paymentTerminals = [], printers = []; try { [locationData, merakiNetwork] = await Promise.all([ getStoreLocation(storeNumber), findMerakiNetwork(storeNumber), ]); if (merakiNetwork) { [merakiClients, mdmDevices, paymentTerminals, printers] = await Promise.all([ getMerakiClients(merakiNetwork.id), getMDMDevices(storeNumber), getStorePaymentTerminals(storeNumber), getStorePrinters(storeNumber), ]); } } catch (err) { logger.error('Health analysis error', { error: err.message }); } const healthReport = createHealthReport(storeNumber, locationData?.name || 'Unknown Store'); // === Network Infrastructure === if (merakiNetwork) { healthReport.addSection( '**🌐 Network Infrastructure**', `- Meraki Network: ✅ [${merakiNetwork.name}](${merakiNetwork.url})\n` ); try { const avail = await getMerakiDeviceAvailabilities(merakiNetwork.id); const switches = avail.filter(d => d.productType === 'switch' && d.status !== 'dormant'); const aps = avail.filter(d => d.productType === 'wireless' && d.status !== 'dormant'); const swOnline = switches.filter(d => d.status === 'online').length; const apOnline = aps.filter(d => d.status === 'online').length; const apAlerting = aps.filter(d => d.status === 'alerting').length; let netContent = `- Switching: ${swOnline}/${switches.length} online\n`; netContent += `- Access Points: ${apOnline}/${aps.length} online`; if (apAlerting > 0) { netContent += ` (${apAlerting} alerting)`; } netContent += `\n\n`; healthReport.addSection('', netContent); // append // Scoring & Issues if (swOnline < switches.length) { const offlineSw = switches.length - swOnline; healthReport.deduct(offlineSw * SCORING.switchOffline, `${offlineSw} switch(es) offline`); } if (apOnline < aps.length || apAlerting > 0) { const totalApIssues = aps.length - apOnline; if (apAlerting > 0) { healthReport.deduct( totalApIssues * SCORING.apAlerting, `${apAlerting} access point(s) alerting (check cabling/power)` ); } else { healthReport.deduct( totalApIssues * SCORING.apOffline, `${totalApIssues} access point(s) offline` ); } } } catch (e) { logger.error('Meraki availability error', { error: e.message }); } } else { healthReport.addSection('**🌐 Network Infrastructure**', `- Meraki Network: ❌ Not Found\n\n`); healthReport.deduct(SCORING.noMerakiNetwork, 'No Meraki network found'); } // === Core Systems === healthReport.addSection('**🖥️ POS Systems**', ''); if (mdmDevices && mdmDevices.length > 0) { const servers = filterMdmByType(mdmDevices, MDM_DEVICE_TYPES.SERVER); const mobileRegs = filterMdmByType(mdmDevices, MDM_DEVICE_TYPES.MOBILE_REGISTER); const custDisplays = filterMdmByType(mdmDevices, MDM_DEVICE_TYPES.CUSTOMER_DISPLAY); const iphones = filterMdmByType(mdmDevices, MDM_DEVICE_TYPES.IPHONE); // Server (heavy penalty) let serverStatus = '❓ Unknown'; if (servers.length > 0) { const srv = servers[0]; const match = findMatchingClient(merakiClients, { UserName: srv.UserName, DeviceFriendlyName: srv.DeviceFriendlyName, }); serverStatus = getClientStatus(match); if (serverStatus === '❌ Offline') { healthReport.deduct( SCORING.serverOffline, `Store Server ${srv?.UserName || srv?.DeviceFriendlyName} is offline` ); } } healthReport.addSection('', `- Store Server: ${serverStatus}\n`); // Mobile Registers let mobileOnline = 0; mobileRegs.forEach(reg => { const name = (reg.UserName || reg.DeviceFriendlyName || '').trim(); const match = findMatchingClient(merakiClients, { UserName: reg.UserName, DeviceFriendlyName: reg.DeviceFriendlyName, }); const status = getClientStatus(match); if (status === '✅ Online') mobileOnline++; if (status === '❌ Offline') healthReport.addIssue(`Mobile Register ${name} is offline`); }); healthReport.addSection( '', `- Mobile Registers: ${mobileOnline}/${mobileRegs.length} online\n` ); // Customer Displays & iPhones (lighter penalty) let cdOnline = 0; custDisplays.forEach(cd => { const name = (cd.UserName || cd.DeviceFriendlyName || '').trim(); const match = findMatchingClient(merakiClients, { UserName: cd.UserName, DeviceFriendlyName: cd.DeviceFriendlyName, }); const status = getClientStatus(match); if (status === '✅ Online') cdOnline++; if (status === '❌ Offline') healthReport.addIssue(`Customer Display ${name} is offline`); }); healthReport.addSection('', `- Customer Displays: ${cdOnline}/${custDisplays.length} online\n`); let iphoneOnline = 0; iphones.forEach(phone => { const name = (phone.UserName || phone.DeviceFriendlyName || '').trim(); const match = findMatchingClient(merakiClients, { UserName: phone.UserName, DeviceFriendlyName: phone.DeviceFriendlyName, }); const status = getClientStatus(match); if (status === '✅ Online') iphoneOnline++; if (status === '❌ Offline') healthReport.addIssue(`iPhone ${name} is offline`); }); healthReport.addSection('', `- Store iPhones: ${iphoneOnline}/${iphones.length} online\n\n`); } else { healthReport.addSection('', `- No MDM data available\n\n`); healthReport.deduct(SCORING.noMdmData); } // === POS & Peripherals (with per-device penalty) === healthReport.addSection('**💳 POS Peripherals**', ''); // Payment Terminals if (paymentTerminals && paymentTerminals.length > 0) { let onlinePayments = 0; paymentTerminals.forEach(term => { const match = findMatchingClient(merakiClients, { name: term.device_name, adyenName: term.adyen_device_name, ip_address: term.ip_address, }); const terminalName = (term.device_name || term.adyen_device_name || 'unknown').trim(); const status = getClientStatus(match); if (status === '✅ Online') onlinePayments++; if (status === '❌ Offline') { healthReport.deduct( SCORING.paymentTerminalOffline, `Payment Terminal ${terminalName} is offline` ); } }); healthReport.addSection( '', `- Payment Terminals: ${onlinePayments}/${paymentTerminals.length} online\n` ); } // Printers const filteredPrinters = (printers || []).filter( p => (p.connection_type_name || '').toLowerCase() !== 'usb' ); if (filteredPrinters.length > 0) { let onlinePrinters = 0; filteredPrinters.forEach(printer => { const name = (printer.printer_name || '').trim().toLowerCase(); const match = findMatchingClient(merakiClients, { name: printer.printer_name }); const status = getClientStatus(match); if (status === '✅ Online') onlinePrinters++; if (status === '❌ Offline') { healthReport.deduct(SCORING.printerOffline, `Printer ${name} is offline`); } }); healthReport.addSection( '', `- Printers: ${onlinePrinters}/${filteredPrinters.length} online\n` ); } // Finalize and return const report = healthReport.finalize(); return { summary: report.summary }; } module.exports = { getStoreHealth };