const { Op } = require('sequelize'); const ZohoLeadsBulk = require('../../data/models/zohoLeadsBulk'); const ZohoTasksBulk = require('../../data/models/zohoTasksBulk'); const ZohoContactsBulk = require('../../data/models/zohoContactsBulk'); const ZohoAccountsBulk = require('../../data/models/zohoAccountsBulk'); const ZohoDealsBulk = require('../../data/models/zohoDealsBulk'); const ZohoVendorsBulk = require('../../data/models/zohoVendorsBulk'); const ZohoInvoicesBulk = require('../../data/models/zohoInvoicesBulk'); const ZohoSalesOrdersBulk = require('../../data/models/zohoSalesOrdersBulk'); const ZohoPurchaseOrdersBulk = require('../../data/models/zohoPurchaseOrdersBulk'); const { success, failure } = require('../../utils/response'); class ReportsController { /** * Get comprehensive CRM KPIs combining all CRM modules data */ async getCrmKPIs(req, res) { try { const { start_date, end_date, period, owner } = req.query; const userId = req.user.uuid; // Build date filter const dateFilter = this.buildDateFilter(start_date, end_date, period); // Build owner filter const ownerFilter = owner ? { owner: { [Op.like]: `%${owner}%` } } : {}; // Base filters const baseFilters = { user_uuid: userId, provider: 'zoho', ...dateFilter, ...ownerFilter }; // Fetch data from all CRM modules in parallel const [ leadsData, tasksData, contactsData, accountsData, dealsData, vendorsData, invoicesData, salesOrdersData, purchaseOrdersData ] = await Promise.all([ ZohoLeadsBulk.findAll({ where: baseFilters, attributes: ['lead_source', 'lead_status', 'owner', 'created_time', 'company', 'email', 'phone'] }), ZohoTasksBulk.findAll({ where: baseFilters, attributes: ['status', 'priority', 'owner', 'created_time', 'due_date', 'subject', 'what_id'] }), ZohoContactsBulk.findAll({ where: baseFilters, attributes: ['first_name', 'last_name', 'email', 'phone', 'lead_source', 'account_name', 'owner', 'created_time'] }), ZohoAccountsBulk.findAll({ where: baseFilters, attributes: ['account_name', 'phone', 'website', 'industry', 'ownership', 'annual_revenue', 'owner', 'created_time'] }), ZohoDealsBulk.findAll({ where: baseFilters, attributes: ['deal_name', 'stage', 'amount', 'closing_date', 'account_name', 'contact_name', 'probability', 'lead_source', 'owner', 'created_time'] }), ZohoVendorsBulk.findAll({ where: baseFilters, attributes: ['vendor_name', 'email', 'phone', 'website', 'owner', 'created_time'] }), ZohoInvoicesBulk.findAll({ where: baseFilters, attributes: ['invoice_number', 'invoice_date', 'due_date', 'status', 'total', 'account_name', 'owner', 'created_time'] }), ZohoSalesOrdersBulk.findAll({ where: baseFilters, attributes: ['subject', 'status', 'due_date', 'total', 'account_name', 'owner', 'created_time'] }), ZohoPurchaseOrdersBulk.findAll({ where: baseFilters, attributes: ['subject', 'vendor_name', 'status', 'due_date', 'total', 'owner', 'created_time'] }) ]); // Calculate comprehensive KPIs const kpis = this.calculateComprehensiveCrmKPIs({ leads: leadsData, tasks: tasksData, contacts: contactsData, accounts: accountsData, deals: dealsData, vendors: vendorsData, invoices: invoicesData, salesOrders: salesOrdersData, purchaseOrders: purchaseOrdersData }); return res.json(success('Comprehensive CRM KPIs retrieved successfully', kpis)); } catch (error) { console.error('Error fetching CRM KPIs:', error); return res.status(500).json(failure('Failed to fetch CRM KPIs', 'INTERNAL_ERROR')); } } /** * Calculate comprehensive CRM KPIs from all modules */ calculateComprehensiveCrmKPIs(data) { const now = new Date(); const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000); // Module counts const moduleCounts = { leads: data.leads.length, contacts: data.contacts.length, accounts: data.accounts.length, deals: data.deals.length, tasks: data.tasks.length, vendors: data.vendors.length, invoices: data.invoices.length, salesOrders: data.salesOrders.length, purchaseOrders: data.purchaseOrders.length }; // Revenue and financial metrics const revenueMetrics = this.calculateRevenueMetrics(data); // Lead and sales pipeline metrics const pipelineMetrics = this.calculatePipelineMetrics(data, thirtyDaysAgo, sevenDaysAgo); // Operational efficiency metrics const operationalMetrics = this.calculateOperationalMetrics(data, now); // Customer and vendor metrics const customerMetrics = this.calculateCustomerMetrics(data, thirtyDaysAgo); // Business growth trends const growthMetrics = this.calculateGrowthMetrics(data, thirtyDaysAgo, ninetyDaysAgo); // Top performers and insights const insights = this.calculateBusinessInsights(data, revenueMetrics, pipelineMetrics); return { businessOverview: { totalRecords: Object.values(moduleCounts).reduce((sum, count) => sum + count, 0), moduleCounts, revenueMetrics, growthMetrics }, salesPipeline: { ...pipelineMetrics, dealStages: this.getDistribution(data.deals, 'stage'), leadSources: this.getDistribution(data.leads, 'lead_source'), conversionFunnel: this.calculateConversionFunnel(data) }, operationalEfficiency: { ...operationalMetrics, taskStatus: this.getDistribution(data.tasks, 'status'), invoiceStatus: this.getDistribution(data.invoices, 'status'), orderStatus: { salesOrders: this.getDistribution(data.salesOrders, 'status'), purchaseOrders: this.getDistribution(data.purchaseOrders, 'status') } }, customerRelationships: { ...customerMetrics, industryDistribution: this.getDistribution(data.accounts, 'industry'), contactSources: this.getDistribution(data.contacts, 'lead_source'), accountOwnership: this.getDistribution(data.accounts, 'owner') }, financialHealth: { totalRevenue: revenueMetrics.totalRevenue, totalInvoices: revenueMetrics.totalInvoices, averageInvoiceValue: revenueMetrics.averageInvoiceValue, overdueAmount: revenueMetrics.overdueAmount, revenueByMonth: revenueMetrics.revenueByMonth, topRevenueAccounts: revenueMetrics.topRevenueAccounts }, businessInsights: { ...insights, recommendations: this.generateRecommendations(data, revenueMetrics, pipelineMetrics, operationalMetrics) }, generatedAt: new Date().toISOString() }; } /** * Calculate CEO-level CRM KPIs focused on business statistics (Legacy method) */ calculateCrmKPIs(leadsData, tasksData) { const now = new Date(); const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000); // Business Growth Metrics const totalLeads = leadsData.length; const totalTasks = tasksData.length; // Lead Growth Trends const leadsLast7Days = leadsData.filter(lead => lead.created_time && new Date(lead.created_time) >= sevenDaysAgo ).length; const leadsLast30Days = leadsData.filter(lead => lead.created_time && new Date(lead.created_time) >= thirtyDaysAgo ).length; const leadsLast90Days = leadsData.filter(lead => lead.created_time && new Date(lead.created_time) >= ninetyDaysAgo ).length; // Lead Quality Metrics const qualifiedLeads = leadsData.filter(lead => lead.lead_status && ( lead.lead_status.toLowerCase().includes('qualified') || lead.lead_status.toLowerCase().includes('hot') || lead.lead_status.toLowerCase().includes('warm') ) ).length; const convertedLeads = leadsData.filter(lead => lead.lead_status && lead.lead_status.toLowerCase().includes('converted') ).length; // Lead Source Performance const leadSourcePerformance = this.getLeadSourcePerformance(leadsData); // Business Process Efficiency const completedTasks = tasksData.filter(task => task.status && task.status.toLowerCase().includes('completed') ).length; const highPriorityTasks = tasksData.filter(task => task.priority && task.priority.toLowerCase().includes('high') ).length; const overdueTasks = tasksData.filter(task => task.due_date && new Date(task.due_date) < now && task.status && !task.status.toLowerCase().includes('completed') ).length; // Strategic Insights const conversionRate = totalLeads > 0 ? (convertedLeads / totalLeads * 100).toFixed(2) : 0; const qualificationRate = totalLeads > 0 ? (qualifiedLeads / totalLeads * 100).toFixed(2) : 0; const taskCompletionRate = totalTasks > 0 ? (completedTasks / totalTasks * 100).toFixed(2) : 0; const overdueRate = totalTasks > 0 ? (overdueTasks / totalTasks * 100).toFixed(2) : 0; // Monthly Growth Rate const monthlyGrowthRate = this.calculateGrowthRate(leadsData, thirtyDaysAgo, ninetyDaysAgo); // Top Performing Lead Sources const topLeadSources = Object.entries(leadSourcePerformance) .map(([source, data]) => ({ source: source || 'Unknown', totalLeads: data.total, qualifiedLeads: data.qualified, conversionRate: data.total > 0 ? (data.converted / data.total * 100).toFixed(2) : 0, avgQuality: data.total > 0 ? (data.qualified / data.total * 100).toFixed(2) : 0 })) .sort((a, b) => b.totalLeads - a.totalLeads) .slice(0, 5); // Business Health Indicators const businessHealth = this.calculateBusinessHealth(leadsData, tasksData, now); return { businessOverview: { totalLeads, totalTasks, leadsLast7Days, leadsLast30Days, leadsLast90Days, monthlyGrowthRate: `${monthlyGrowthRate}%`, conversionRate: `${conversionRate}%`, qualificationRate: `${qualificationRate}%` }, leadQuality: { qualifiedLeads, convertedLeads, leadSourcePerformance: topLeadSources, statusDistribution: this.getDistribution(leadsData, 'lead_status') }, operationalEfficiency: { completedTasks, highPriorityTasks, overdueTasks, taskCompletionRate: `${taskCompletionRate}%`, overdueRate: `${overdueRate}%`, priorityDistribution: this.getDistribution(tasksData, 'priority') }, businessHealth: { overallScore: businessHealth.overallScore, leadGenerationHealth: businessHealth.leadGeneration, processEfficiency: businessHealth.processEfficiency, qualityMetrics: businessHealth.qualityMetrics }, strategicInsights: { topPerformingSources: topLeadSources.slice(0, 3), growthTrend: monthlyGrowthRate > 0 ? 'positive' : 'negative', efficiencyTrend: taskCompletionRate > 80 ? 'excellent' : taskCompletionRate > 60 ? 'good' : 'needs_improvement', qualityTrend: qualificationRate > 70 ? 'high' : qualificationRate > 50 ? 'medium' : 'low' }, generatedAt: new Date().toISOString() }; } /** * Get distribution of values for a specific field */ getDistribution(data, field) { const distribution = {}; data.forEach(item => { const value = item[field] || 'Unknown'; distribution[value] = (distribution[value] || 0) + 1; }); return distribution; } /** * Get lead source performance metrics */ getLeadSourcePerformance(leadsData) { const performance = {}; leadsData.forEach(lead => { const source = lead.lead_source || 'Unknown'; if (!performance[source]) { performance[source] = { total: 0, qualified: 0, converted: 0 }; } performance[source].total++; if (lead.lead_status && ( lead.lead_status.toLowerCase().includes('qualified') || lead.lead_status.toLowerCase().includes('hot') || lead.lead_status.toLowerCase().includes('warm') )) { performance[source].qualified++; } if (lead.lead_status && lead.lead_status.toLowerCase().includes('converted')) { performance[source].converted++; } }); return performance; } /** * Calculate monthly growth rate */ calculateGrowthRate(leadsData, thirtyDaysAgo, ninetyDaysAgo) { const leadsLast30Days = leadsData.filter(lead => lead.created_time && new Date(lead.created_time) >= thirtyDaysAgo ).length; const leadsPrevious60Days = leadsData.filter(lead => lead.created_time && new Date(lead.created_time) >= ninetyDaysAgo && new Date(lead.created_time) < thirtyDaysAgo ).length; if (leadsPrevious60Days === 0) return 0; const growthRate = ((leadsLast30Days - leadsPrevious60Days) / leadsPrevious60Days) * 100; return growthRate.toFixed(2); } /** * Calculate business health indicators */ calculateBusinessHealth(leadsData, tasksData, now) { const totalLeads = leadsData.length; const totalTasks = tasksData.length; // Lead Generation Health (0-100) const recentLeads = leadsData.filter(lead => lead.created_time && new Date(lead.created_time) >= new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) ).length; const leadGenerationHealth = Math.min(100, (recentLeads / 10) * 100); // Assuming 10 leads/week is healthy // Process Efficiency (0-100) const completedTasks = tasksData.filter(task => task.status && task.status.toLowerCase().includes('completed') ).length; const processEfficiency = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0; // Quality Metrics (0-100) const qualifiedLeads = leadsData.filter(lead => lead.lead_status && ( lead.lead_status.toLowerCase().includes('qualified') || lead.lead_status.toLowerCase().includes('hot') || lead.lead_status.toLowerCase().includes('warm') ) ).length; const qualityMetrics = totalLeads > 0 ? (qualifiedLeads / totalLeads) * 100 : 0; // Overall Score (weighted average) const overallScore = Math.round( (leadGenerationHealth * 0.4) + (processEfficiency * 0.3) + (qualityMetrics * 0.3) ); return { overallScore, leadGeneration: Math.round(leadGenerationHealth), processEfficiency: Math.round(processEfficiency), qualityMetrics: Math.round(qualityMetrics) }; } /** * Calculate revenue metrics from invoices and deals */ calculateRevenueMetrics(data) { const totalRevenue = data.invoices.reduce((sum, invoice) => sum + (parseFloat(invoice.total) || 0), 0); const totalDealValue = data.deals.reduce((sum, deal) => sum + (parseFloat(deal.amount) || 0), 0); const overdueInvoices = data.invoices.filter(invoice => { if (!invoice.due_date) return false; return new Date(invoice.due_date) < new Date() && invoice.status && !invoice.status.toLowerCase().includes('paid'); }); const overdueAmount = overdueInvoices.reduce((sum, invoice) => sum + (parseFloat(invoice.total) || 0), 0); const averageInvoiceValue = data.invoices.length > 0 ? totalRevenue / data.invoices.length : 0; // Revenue by month (last 12 months) const revenueByMonth = this.calculateRevenueByMonth(data.invoices); // Top revenue accounts const accountRevenue = {}; data.invoices.forEach(invoice => { const account = invoice.account_name || 'Unknown'; const amount = parseFloat(invoice.total) || 0; accountRevenue[account] = (accountRevenue[account] || 0) + amount; }); const topRevenueAccounts = Object.entries(accountRevenue) .map(([account, revenue]) => ({ account, revenue })) .sort((a, b) => b.revenue - a.revenue) .slice(0, 10); return { totalRevenue, totalDealValue, totalInvoices: data.invoices.length, averageInvoiceValue, overdueAmount, overdueCount: overdueInvoices.length, revenueByMonth, topRevenueAccounts }; } /** * Calculate pipeline metrics from leads and deals */ calculatePipelineMetrics(data, thirtyDaysAgo, sevenDaysAgo) { const totalLeads = data.leads.length; const totalDeals = data.deals.length; const qualifiedLeads = data.leads.filter(lead => lead.lead_status && ( lead.lead_status.toLowerCase().includes('qualified') || lead.lead_status.toLowerCase().includes('hot') || lead.lead_status.toLowerCase().includes('warm') ) ).length; const convertedLeads = data.leads.filter(lead => lead.lead_status && lead.lead_status.toLowerCase().includes('converted') ).length; const openDeals = data.deals.filter(deal => deal.stage && !deal.stage.toLowerCase().includes('closed') ); const closedWonDeals = data.deals.filter(deal => deal.stage && deal.stage.toLowerCase().includes('closed won') ); const totalPipelineValue = openDeals.reduce((sum, deal) => sum + (parseFloat(deal.amount) || 0), 0); const conversionRate = totalLeads > 0 ? (convertedLeads / totalLeads * 100).toFixed(2) : 0; const qualificationRate = totalLeads > 0 ? (qualifiedLeads / totalLeads * 100).toFixed(2) : 0; const winRate = totalDeals > 0 ? (closedWonDeals.length / totalDeals * 100).toFixed(2) : 0; return { totalLeads, totalDeals, qualifiedLeads, convertedLeads, openDeals: openDeals.length, closedWonDeals: closedWonDeals.length, totalPipelineValue, conversionRate: `${conversionRate}%`, qualificationRate: `${qualificationRate}%`, winRate: `${winRate}%` }; } /** * Calculate operational efficiency metrics */ calculateOperationalMetrics(data, now) { const totalTasks = data.tasks.length; const completedTasks = data.tasks.filter(task => task.status && task.status.toLowerCase().includes('completed') ).length; const overdueTasks = data.tasks.filter(task => task.due_date && new Date(task.due_date) < now && task.status && !task.status.toLowerCase().includes('completed') ).length; const highPriorityTasks = data.tasks.filter(task => task.priority && task.priority.toLowerCase().includes('high') ).length; const taskCompletionRate = totalTasks > 0 ? (completedTasks / totalTasks * 100).toFixed(2) : 0; const overdueRate = totalTasks > 0 ? (overdueTasks / totalTasks * 100).toFixed(2) : 0; return { totalTasks, completedTasks, overdueTasks, highPriorityTasks, taskCompletionRate: `${taskCompletionRate}%`, overdueRate: `${overdueRate}%` }; } /** * Calculate customer relationship metrics */ calculateCustomerMetrics(data, thirtyDaysAgo) { const totalContacts = data.contacts.length; const totalAccounts = data.accounts.length; const totalVendors = data.vendors.length; const recentContacts = data.contacts.filter(contact => contact.created_time && new Date(contact.created_time) >= thirtyDaysAgo ).length; const accountsWithRevenue = new Set(data.invoices.map(invoice => invoice.account_name)).size; const contactToAccountRatio = totalAccounts > 0 ? (totalContacts / totalAccounts).toFixed(2) : 0; return { totalContacts, totalAccounts, totalVendors, recentContacts, accountsWithRevenue, contactToAccountRatio }; } /** * Calculate growth metrics */ calculateGrowthMetrics(data, thirtyDaysAgo, ninetyDaysAgo) { const recentLeads = data.leads.filter(lead => lead.created_time && new Date(lead.created_time) >= thirtyDaysAgo ).length; const previousLeads = data.leads.filter(lead => lead.created_time && new Date(lead.created_time) >= ninetyDaysAgo && new Date(lead.created_time) < thirtyDaysAgo ).length; const recentDeals = data.deals.filter(deal => deal.created_time && new Date(deal.created_time) >= thirtyDaysAgo ).length; const previousDeals = data.deals.filter(deal => deal.created_time && new Date(deal.created_time) >= ninetyDaysAgo && new Date(deal.created_time) < thirtyDaysAgo ).length; const leadGrowthRate = previousLeads > 0 ? ((recentLeads - previousLeads) / previousLeads * 100).toFixed(2) : 0; const dealGrowthRate = previousDeals > 0 ? ((recentDeals - previousDeals) / previousDeals * 100).toFixed(2) : 0; return { recentLeads, previousLeads, recentDeals, previousDeals, leadGrowthRate: `${leadGrowthRate}%`, dealGrowthRate: `${dealGrowthRate}%` }; } /** * Calculate business insights */ calculateBusinessInsights(data, revenueMetrics, pipelineMetrics) { const now = new Date(); const leadSourcePerformance = this.getLeadSourcePerformance(data.leads); const topPerformingSources = Object.entries(leadSourcePerformance) .map(([source, data]) => ({ source: source || 'Unknown', totalLeads: data.total, qualifiedLeads: data.qualified, conversionRate: data.total > 0 ? (data.converted / data.total * 100).toFixed(2) : 0, avgQuality: data.total > 0 ? (data.qualified / data.total * 100).toFixed(2) : 0 })) .sort((a, b) => b.totalLeads - a.totalLeads) .slice(0, 5); const topRevenueOwners = this.getOwnerPerformance(data.invoices, 'total') .slice(0, 5); const mostActiveOwners = this.getOwnerPerformance(data.tasks, null, 'count') .slice(0, 5); return { topPerformingSources, topRevenueOwners, mostActiveOwners, businessHealth: this.calculateBusinessHealth(data.leads, data.tasks, now) }; } /** * Calculate conversion funnel */ calculateConversionFunnel(data) { const totalLeads = data.leads.length; const qualifiedLeads = data.leads.filter(lead => lead.lead_status && ( lead.lead_status.toLowerCase().includes('qualified') || lead.lead_status.toLowerCase().includes('hot') || lead.lead_status.toLowerCase().includes('warm') ) ).length; const convertedLeads = data.leads.filter(lead => lead.lead_status && lead.lead_status.toLowerCase().includes('converted') ).length; const totalDeals = data.deals.length; const closedWonDeals = data.deals.filter(deal => deal.stage && deal.stage.toLowerCase().includes('closed won') ).length; return { leads: { count: totalLeads, percentage: 100 }, qualified: { count: qualifiedLeads, percentage: totalLeads > 0 ? (qualifiedLeads / totalLeads * 100).toFixed(2) : 0 }, converted: { count: convertedLeads, percentage: totalLeads > 0 ? (convertedLeads / totalLeads * 100).toFixed(2) : 0 }, deals: { count: totalDeals, percentage: convertedLeads > 0 ? (totalDeals / convertedLeads * 100).toFixed(2) : 0 }, closedWon: { count: closedWonDeals, percentage: totalDeals > 0 ? (closedWonDeals / totalDeals * 100).toFixed(2) : 0 } }; } /** * Calculate revenue by month */ calculateRevenueByMonth(invoices) { const monthlyRevenue = {}; const now = new Date(); // Initialize last 12 months for (let i = 11; i >= 0; i--) { const date = new Date(now.getFullYear(), now.getMonth() - i, 1); const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; monthlyRevenue[monthKey] = 0; } invoices.forEach(invoice => { if (invoice.created_time) { const date = new Date(invoice.created_time); const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; if (monthlyRevenue.hasOwnProperty(monthKey)) { monthlyRevenue[monthKey] += parseFloat(invoice.total) || 0; } } }); return Object.entries(monthlyRevenue).map(([month, revenue]) => ({ month, revenue })); } /** * Get owner performance metrics */ getOwnerPerformance(data, valueField = null, metric = 'revenue') { const ownerStats = {}; data.forEach(item => { const owner = item.owner || 'Unassigned'; if (!ownerStats[owner]) { ownerStats[owner] = { count: 0, total: 0 }; } ownerStats[owner].count++; if (valueField && item[valueField]) { ownerStats[owner].total += parseFloat(item[valueField]) || 0; } }); return Object.entries(ownerStats) .map(([owner, stats]) => ({ owner, count: stats.count, [metric === 'revenue' ? 'total' : 'value']: stats.total })) .sort((a, b) => (metric === 'revenue' ? b.total - a.total : b.count - a.count)); } /** * Generate business recommendations */ generateRecommendations(data, revenueMetrics, pipelineMetrics, operationalMetrics) { const recommendations = []; // Revenue recommendations if (revenueMetrics.overdueAmount > revenueMetrics.totalRevenue * 0.2) { recommendations.push({ category: 'Financial', priority: 'High', title: 'Improve Collections', description: `${revenueMetrics.overdueCount} overdue invoices worth $${revenueMetrics.overdueAmount.toFixed(2)} need attention.`, action: 'Review and follow up on overdue invoices immediately.' }); } // Pipeline recommendations if (parseFloat(pipelineMetrics.conversionRate) < 10) { recommendations.push({ category: 'Sales', priority: 'High', title: 'Improve Lead Conversion', description: `Current conversion rate is ${pipelineMetrics.conversionRate}. Industry average is 15-20%.`, action: 'Review lead qualification process and follow-up procedures.' }); } // Operational recommendations if (parseFloat(operationalMetrics.overdueRate) > 20) { recommendations.push({ category: 'Operations', priority: 'Medium', title: 'Reduce Task Overdue Rate', description: `${operationalMetrics.overdueRate} of tasks are overdue.`, action: 'Implement better task prioritization and deadline management.' }); } // Growth recommendations if (data.leads.length < 50) { recommendations.push({ category: 'Growth', priority: 'Medium', title: 'Increase Lead Generation', description: `Only ${data.leads.length} leads in the system. Consider expanding marketing efforts.`, action: 'Invest in lead generation campaigns and improve website conversion.' }); } return recommendations; } /** * Build date filter based on parameters */ buildDateFilter(start_date, end_date, period) { if (start_date && end_date) { return { created_time: { [Op.between]: [new Date(start_date), new Date(end_date)] } }; } if (period) { const now = new Date(); let startDate; switch (period) { case '7d': startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); break; case '30d': startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); break; case '90d': startDate = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000); break; case '1y': startDate = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000); break; default: return {}; } return { created_time: { [Op.gte]: startDate } }; } return {}; } } const reportsController = new ReportsController(); module.exports = { getCrmKPIs: reportsController.getCrmKPIs.bind(reportsController) };