Preview: dashboardController.js
Size: 15.13 KB
//home/byroehnu/easepay.easetack.com/controllers/dashboardController.js
const { prisma } = require('../config/prisma');
const getDashboardOverview = async (req, res) => {
try {
const { timeframe = 'month' } = req.query;
let startDate, endDate, compareStartDate, compareEndDate;
const now = new Date();
// Set date ranges based on timeframe
switch (timeframe) {
case 'week':
const weekStart = new Date(now.setDate(now.getDate() - now.getDay()));
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
startDate = new Date(weekStart.setHours(0, 0, 0, 0));
endDate = new Date(weekEnd.setHours(23, 59, 59, 999));
compareStartDate = new Date(startDate);
compareStartDate.setDate(compareStartDate.getDate() - 7);
compareEndDate = new Date(endDate);
compareEndDate.setDate(compareEndDate.getDate() - 7);
break;
case 'month':
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
compareStartDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
compareEndDate = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
break;
case 'year':
startDate = new Date(now.getFullYear(), 0, 1);
endDate = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999);
compareStartDate = new Date(now.getFullYear() - 1, 0, 1);
compareEndDate = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 999);
break;
default:
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
compareStartDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
compareEndDate = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
}
// Current period sales
const currentSales = await prisma.sale.aggregate({
where: {
userId: req.user.id,
date: {
gte: startDate,
lte: endDate
}
},
_sum: { amount: true },
_count: { id: true }
});
// Previous period sales for comparison
const previousSales = await prisma.sale.aggregate({
where: {
userId: req.user.id,
date: {
gte: compareStartDate,
lte: compareEndDate
}
},
_sum: { amount: true },
_count: { id: true }
});
// Current period expenses
const currentExpenses = await prisma.expense.aggregate({
where: {
userId: req.user.id,
date: {
gte: startDate,
lte: endDate
}
},
_sum: { amount: true },
_count: { id: true }
});
// Previous period expenses for comparison
const previousExpenses = await prisma.expense.aggregate({
where: {
userId: req.user.id,
date: {
gte: compareStartDate,
lte: compareEndDate
}
},
_sum: { amount: true },
_count: { id: true }
});
// Recent transactions (last 10)
const recentTransactions = await prisma.$transaction([
prisma.sale.findMany({
where: { userId: req.user.id },
orderBy: { date: 'desc' },
take: 5,
select: {
id: true,
amount: true,
customerName: true,
date: true,
description: true
}
}),
prisma.expense.findMany({
where: { userId: req.user.id },
orderBy: { date: 'desc' },
take: 5,
select: {
id: true,
amount: true,
vendor: true,
date: true,
description: true
}
})
]);
// Combine and sort transactions
const allTransactions = [
...recentTransactions[0].map(sale => ({
id: sale.id,
type: 'sale',
amount: sale.amount,
description: sale.description || `Sale to ${sale.customerName}`,
date: sale.date
})),
...recentTransactions[1].map(expense => ({
id: expense.id,
type: 'expense',
amount: -expense.amount,
description: expense.description || `Expense to ${expense.vendor}`,
date: expense.date
}))
].sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 10);
// Calculate metrics
const currentRevenue = Number(currentSales._sum.amount) || 0;
const previousRevenue = Number(previousSales._sum.amount) || 0;
const currentExpenseTotal = Number(currentExpenses._sum.amount) || 0;
const previousExpenseTotal = Number(previousExpenses._sum.amount) || 0;
const revenueChange = previousRevenue > 0
? ((currentRevenue - previousRevenue) / previousRevenue) * 100
: currentRevenue > 0 ? 100 : 0;
const expenseChange = previousExpenseTotal > 0
? ((currentExpenseTotal - previousExpenseTotal) / previousExpenseTotal) * 100
: currentExpenseTotal > 0 ? 100 : 0;
const currentProfit = currentRevenue - currentExpenseTotal;
const previousProfit = previousRevenue - previousExpenseTotal;
const profitChange = previousProfit !== 0
? ((currentProfit - previousProfit) / Math.abs(previousProfit)) * 100
: currentProfit > 0 ? 100 : currentProfit < 0 ? -100 : 0;
const overview = {
revenue: {
current: currentRevenue,
previous: previousRevenue,
change: revenueChange,
count: currentSales._count.id || 0
},
expenses: {
current: currentExpenseTotal,
previous: previousExpenseTotal,
change: expenseChange,
count: currentExpenses._count.id || 0
},
profit: {
current: currentProfit,
previous: previousProfit,
change: profitChange
},
recentTransactions: allTransactions,
timeframe
};
res.status(200).json({
success: true,
data: overview
});
} catch (error) {
console.error('Get dashboard overview error:', error);
res.status(500).json({
success: false,
message: 'Failed to get dashboard overview'
});
}
};
const getSalesAnalytics = async (req, res) => {
try {
const { period = 'month' } = req.query;
let groupBy, dateFormat;
let dateRange = {};
const now = new Date();
switch (period) {
case 'week':
// Last 7 days
dateRange = {
gte: new Date(now.setDate(now.getDate() - 7)),
lte: new Date()
};
groupBy = 'date';
break;
case 'month':
// Last 30 days
dateRange = {
gte: new Date(now.setDate(now.getDate() - 30)),
lte: new Date()
};
groupBy = 'date';
break;
case 'year':
// Last 12 months
dateRange = {
gte: new Date(now.setMonth(now.getMonth() - 12)),
lte: new Date()
};
groupBy = 'date';
break;
default:
dateRange = {
gte: new Date(now.setDate(now.getDate() - 30)),
lte: new Date()
};
groupBy = 'date';
}
// Get sales data grouped by date
const salesData = await prisma.sale.findMany({
where: {
userId: req.user.id,
date: dateRange
},
select: {
amount: true,
date: true,
category: true
},
orderBy: { date: 'asc' }
});
// Process data for charts
const dailyData = {};
const categoryData = {};
salesData.forEach(sale => {
const dateKey = sale.date.toISOString().split('T')[0];
const amount = Number(sale.amount);
// Daily aggregation
if (!dailyData[dateKey]) {
dailyData[dateKey] = { date: dateKey, revenue: 0, count: 0 };
}
dailyData[dateKey].revenue += amount;
dailyData[dateKey].count += 1;
// Category aggregation
const category = sale.category || 'Uncategorized';
if (!categoryData[category]) {
categoryData[category] = { category, revenue: 0, count: 0 };
}
categoryData[category].revenue += amount;
categoryData[category].count += 1;
});
const analytics = {
daily: Object.values(dailyData),
categories: Object.values(categoryData),
summary: {
totalRevenue: salesData.reduce((sum, sale) => sum + Number(sale.amount), 0),
totalSales: salesData.length,
averageOrderValue: salesData.length > 0
? salesData.reduce((sum, sale) => sum + Number(sale.amount), 0) / salesData.length
: 0
}
};
res.status(200).json({
success: true,
data: analytics
});
} catch (error) {
console.error('Get sales analytics error:', error);
res.status(500).json({
success: false,
message: 'Failed to get sales analytics'
});
}
};
const getExpenseAnalytics = async (req, res) => {
try {
const { period = 'month' } = req.query;
let dateRange = {};
const now = new Date();
switch (period) {
case 'week':
dateRange = {
gte: new Date(now.setDate(now.getDate() - 7)),
lte: new Date()
};
break;
case 'month':
dateRange = {
gte: new Date(now.setDate(now.getDate() - 30)),
lte: new Date()
};
break;
case 'year':
dateRange = {
gte: new Date(now.setMonth(now.getMonth() - 12)),
lte: new Date()
};
break;
default:
dateRange = {
gte: new Date(now.setDate(now.getDate() - 30)),
lte: new Date()
};
}
// Get expenses data
const expensesData = await prisma.expense.findMany({
where: {
userId: req.user.id,
date: dateRange
},
select: {
amount: true,
date: true,
category: true
},
orderBy: { date: 'asc' }
});
// Process data for charts
const dailyData = {};
const categoryData = {};
expensesData.forEach(expense => {
const dateKey = expense.date.toISOString().split('T')[0];
const amount = Number(expense.amount);
// Daily aggregation
if (!dailyData[dateKey]) {
dailyData[dateKey] = { date: dateKey, expenses: 0, count: 0 };
}
dailyData[dateKey].expenses += amount;
dailyData[dateKey].count += 1;
// Category aggregation
const category = expense.category || 'Uncategorized';
if (!categoryData[category]) {
categoryData[category] = { category, expenses: 0, count: 0 };
}
categoryData[category].expenses += amount;
categoryData[category].count += 1;
});
const analytics = {
daily: Object.values(dailyData),
categories: Object.values(categoryData),
summary: {
totalExpenses: expensesData.reduce((sum, expense) => sum + Number(expense.amount), 0),
totalCount: expensesData.length,
averageExpense: expensesData.length > 0
? expensesData.reduce((sum, expense) => sum + Number(expense.amount), 0) / expensesData.length
: 0
}
};
res.status(200).json({
success: true,
data: analytics
});
} catch (error) {
console.error('Get expense analytics error:', error);
res.status(500).json({
success: false,
message: 'Failed to get expense analytics'
});
}
};
const getHomeDashboard = async (req, res) => {
try {
const userId = req.user.id;
const businessId = req.user.businessId; // If applicable
const now = new Date();
const startOfDay = new Date(now.setHours(0, 0, 0, 0));
const endOfDay = new Date(now.setHours(23, 59, 59, 999));
// 1. Today's Profit (Today's Sales - Today's Expenses)
const todaysSales = await prisma.sale.aggregate({
where: { userId, date: { gte: startOfDay, lte: endOfDay } },
_sum: { amount: true }
});
const todaysExpenses = await prisma.expense.aggregate({
where: { userId, date: { gte: startOfDay, lte: endOfDay } },
_sum: { amount: true }
});
const todaysProfit = (Number(todaysSales._sum.amount) || 0) - (Number(todaysExpenses._sum.amount) || 0);
// 2. Cash in Hand (All time CASH sales - All time CASH expenses)
const cashSales = await prisma.sale.aggregate({
where: { userId, paymentMethod: 'CASH' },
_sum: { amount: true }
});
const cashExpenses = await prisma.expense.aggregate({
where: { userId, paymentMethod: 'CASH' },
_sum: { amount: true }
});
const cashInHand = (Number(cashSales._sum.amount) || 0) - (Number(cashExpenses._sum.amount) || 0);
// 3. Money in Bank (All time TRANSFER/POS sales - All time TRANSFER/POS expenses)
const bankSales = await prisma.sale.aggregate({
where: { userId, paymentMethod: { in: ['TRANSFER', 'POS'] } },
_sum: { amount: true }
});
const bankExpenses = await prisma.expense.aggregate({
where: { userId, paymentMethod: { in: ['TRANSFER', 'POS'] } },
_sum: { amount: true }
});
const moneyInBank = (Number(bankSales._sum.amount) || 0) - (Number(bankExpenses._sum.amount) || 0);
// 4. Recent Activities (Last 10 combined)
const recentSales = await prisma.sale.findMany({
where: { userId },
orderBy: { date: 'desc' },
take: 5,
select: { id: true, amount: true, description: true, date: true, paymentMethod: true }
});
const recentExpenses = await prisma.expense.findMany({
where: { userId },
orderBy: { date: 'desc' },
take: 5,
select: { id: true, amount: true, description: true, date: true, paymentMethod: true }
});
const recentInvoices = await prisma.invoice.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
take: 5,
select: { id: true, invoiceNumber: true, total: true, status: true, createdAt: true, customerName: true }
});
// Format and sort activities
let activities = [
...recentSales.map(s => ({ type: 'SALE', ...s, date: s.date })),
...recentExpenses.map(e => ({ type: 'EXPENSE', ...e, date: e.date })),
...recentInvoices.map(i => ({ type: 'INVOICE', ...i, date: i.createdAt }))
];
activities.sort((a, b) => new Date(b.date) - new Date(a.date));
activities = activities.slice(0, 10); // Keep only top 10
res.status(200).json({
success: true,
data: {
todaysProfit,
cashInHand,
moneyInBank,
recentActivities: activities
}
});
} catch (error) {
console.error('Get home dashboard error:', error);
res.status(500).json({
success: false,
message: 'Failed to get home dashboard data'
});
}
};
module.exports = {
getDashboardOverview,
getSalesAnalytics,
getExpenseAnalytics,
getHomeDashboard
};
Directory Contents
Dirs: 1 × Files: 17