report route added

This commit is contained in:
yashwin-foxy 2025-09-18 18:39:03 +05:30
parent 81dcfcd843
commit 9e785012fe
3 changed files with 370 additions and 0 deletions

View File

@ -0,0 +1,334 @@
const { Op } = require('sequelize');
const ZohoLeadsBulk = require('../../data/models/zohoLeadsBulk');
const ZohoTasksBulk = require('../../data/models/zohoTasksBulk');
const { success, failure } = require('../../utils/response');
class ReportsController {
/**
* Get comprehensive CRM KPIs combining leads and tasks 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_id: userId,
provider: 'zoho',
...dateFilter,
...ownerFilter
};
// Fetch data in parallel
const [leadsData, tasksData] = await Promise.all([
ZohoLeadsBulk.findAll({
where: baseFilters,
attributes: [
'lead_source',
'lead_status',
'owner',
'created_time',
'company'
]
}),
ZohoTasksBulk.findAll({
where: baseFilters,
attributes: [
'status',
'priority',
'owner',
'created_time',
'due_date',
'subject'
]
})
]);
// Calculate KPIs
const kpis = this.calculateCrmKPIs(leadsData, tasksData);
return res.json(success('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 CEO-level CRM KPIs focused on business statistics
*/
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)
};
}
/**
* 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)
};

View File

@ -0,0 +1,34 @@
const express = require('express');
const Joi = require('joi');
const auth = require('../middlewares/auth');
const { getCrmKPIs } = require('../controllers/reportsController');
const router = express.Router();
// Validation middleware
const validate = (schema, property = 'query') => {
return (req, res, next) => {
const { error } = schema.validate(req[property]);
if (error) {
return res.status(400).json({
status: 'error',
message: 'Validation error',
details: error.details[0].message
});
}
next();
};
};
// Validation schema for CRM KPIs
const crmKPISchema = Joi.object({
start_date: Joi.date().optional(),
end_date: Joi.date().optional(),
period: Joi.string().valid('7d', '30d', '90d', '1y', 'all').optional(),
owner: Joi.string().optional()
});
// Main CRM KPI route - combines leads and tasks data
router.get('/crm/kpis', auth, validate(crmKPISchema), getCrmKPIs);
module.exports = router;

View File

@ -12,6 +12,7 @@ const userRoutes = require('./api/routes/userRoutes');
const authRoutes = require('./api/routes/authRoutes');
const integrationRoutes = require('./api/routes/integrationRoutes');
const bulkReadRoutes = require('./api/routes/bulkReadRoutes');
const reportsRoutes = require('./api/routes/reportsRoutes');
const sequelize = require('./db/pool');
const app = express();
@ -43,6 +44,7 @@ app.use(`${config.app.apiPrefix}/auth`, authRoutes);
app.use(`${config.app.apiPrefix}/users`, userRoutes);
app.use(`${config.app.apiPrefix}/integrations`, integrationRoutes);
app.use(`${config.app.apiPrefix}/bulk-read`, bulkReadRoutes);
app.use(`${config.app.apiPrefix}/reports`, reportsRoutes);
module.exports = app;