const { getStoreLocation } = require('../services/siw'); const { findMerakiNetwork, getMerakiDeviceAvailabilities, getMerakiClients } = require('../services/meraki'); const { getMDMDevices } = require('../services/mdm'); async function getStoreHealth(storeNumber) { console.log(`🔍 Running health analysis for store ${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), require('../services/siw').getStorePaymentTerminals(storeNumber), require('../services/siw').getStorePrinters(storeNumber) ]); } } catch (err) { console.error('❌ Health analysis error:', err.message); } let summary = `**📊 Store ${storeNumber} Health Summary - ${locationData?.name || 'Unknown Store'}**\n\n`; let overallScore = 100; const issues = []; // === Network Infrastructure === summary += `**🌐 Network Infrastructure**\n`; if (merakiNetwork) { summary += `- 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; summary += `- Switching: ${swOnline}/${switches.length} online\n`; summary += `- Access Points: ${apOnline}/${aps.length} online`; if (apAlerting > 0) { summary += ` (${apAlerting} alerting)`; } summary += `\n\n`; // Scoring & Issues if (swOnline < switches.length) { const offlineSw = switches.length - swOnline; overallScore -= offlineSw * 15; issues.push(`${offlineSw} switch(es) offline`); } if (apOnline < aps.length || apAlerting > 0) { const totalApIssues = aps.length - apOnline; if (apAlerting > 0) { overallScore -= totalApIssues * 3; issues.push(`${apAlerting} access point(s) alerting (check cabling/power)`); } else { overallScore -= totalApIssues * 5; issues.push(`${totalApIssues} access point(s) offline`); } } } catch (e) { console.error('Meraki availability error:', e.message); } } else { summary += `- Meraki Network: ❌ Not Found\n\n`; overallScore -= 50; issues.push("No Meraki network found"); } // === Core Systems === summary += `**🖥️ POS Systems**\n`; if (mdmDevices && mdmDevices.length > 0) { const servers = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('SRV')); const mobileRegs = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('MR')); const custDisplays = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('CD')); const iphones = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('IPH')); // Server (heavy penalty) let serverStatus = '❓ Unknown'; if (servers.length > 0) { const match = merakiClients.find(c => c?.description && servers.some(s => (s.UserName || s.DeviceFriendlyName || '').toLowerCase().includes((c.description || '').toLowerCase()) ) ); serverStatus = match && match.status === 'Online' ? '✅ Online' : '❌ Offline'; if (serverStatus === '❌ Offline') { overallScore -= 35; issues.push(`Store Server ${servers[0]?.UserName} is offline`); } } summary += `- Store Server: ${serverStatus}\n`; // Mobile Registers let mobileOnline = 0; mobileRegs.forEach(reg => { const name = (reg.UserName || reg.DeviceFriendlyName || '').trim(); const match = merakiClients.find(c => c?.description && c.description.toLowerCase().includes(name.toLowerCase()) || (c.user && c.user.toLowerCase().includes(name.toLowerCase())) ); const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline'; if (status === '✅ Online') mobileOnline++; if (status === '❌ Offline') issues.push(`Mobile Register ${name} is offline`); }); summary += `- 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 = merakiClients.find(c => c?.description && c.description.toLowerCase().includes(name.toLowerCase()) || (c.user && c.user.toLowerCase().includes(name.toLowerCase())) ); if (match && match.status === 'Online') cdOnline++; if (match && match.status !== 'Online') issues.push(`Customer Display ${name} is offline`); }); summary += `- Customer Displays: ${cdOnline}/${custDisplays.length} online\n`; let iphoneOnline = 0; iphones.forEach(phone => { const name = (phone.UserName || phone.DeviceFriendlyName || '').trim(); const match = merakiClients.find(c => c?.description && c.description.toLowerCase().includes(name.toLowerCase()) || (c.user && c.user.toLowerCase().includes(name.toLowerCase())) ); if (match && match.status === 'Online') iphoneOnline++; if (match && match.status !== 'Online') issues.push(`iPhone ${name} is offline`); }); summary += `- Store iPhones: ${iphoneOnline}/${iphones.length} online\n\n`; } else { summary += `- No MDM data available\n\n`; overallScore -= 20; } // === POS & Peripherals (with per-device penalty) === summary += `**💳 POS Peripherals**\n`; // Payment Terminals if (paymentTerminals && paymentTerminals.length > 0) { let onlinePayments = 0; paymentTerminals.forEach(term => { const name = (term.device_name || term.adyen_device_name || '').trim().toLowerCase(); const cleanIp = (term.ip_address || '').split('.')[0].toLowerCase(); const match = merakiClients.find(c => c?.description && ( c.description.toLowerCase().includes(name) || c.description.toLowerCase().includes(cleanIp) ) ); const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline'; if (status === '✅ Online') onlinePayments++; if (status === '❌ Offline') { overallScore -= 5; // penalty per offline terminal issues.push(`Payment Terminal ${name} is offline`); } }); summary += `- 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 = merakiClients.find(c => c?.description && c.description.toLowerCase().includes(name) ); const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline'; if (status === '✅ Online') onlinePrinters++; if (status === '❌ Offline') { overallScore -= 4; // penalty per offline printer issues.push(`Printer ${name} is offline`); } }); summary += `- Printers: ${onlinePrinters}/${filteredPrinters.length} online\n`; } // === Overall Status === overallScore = Math.max(0, Math.round(overallScore)); let statusEmoji = overallScore >= 90 ? '🟢' : overallScore >= 70 ? '🟡' : '🔴'; let statusText = overallScore >= 90 ? 'Good' : overallScore >= 70 ? 'Fair' : 'Needs Attention'; summary += `\n**Overall Status**: ${statusEmoji} ${statusText} (${overallScore}% healthy)\n\n`; if (issues.length > 0) { summary += `**⚠️ Issues Detected**\n`; issues.forEach(issue => summary += `- ${issue}\n`); } else { summary += `**✅ No major issues detected**\n`; } return { summary }; } module.exports = { getStoreHealth };