PHP 8.2.30
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

Name Size Perms Modified Actions
- drwxr-xr-x 2026-03-20 23:25:48
Edit Download
127 B lr--r--r-- 2026-03-14 01:49:22
Edit Download
15.34 KB lrw-r--r-- 2026-02-19 22:06:47
Edit Download
30.77 KB lrw-r--r-- 2026-03-04 06:33:18
Edit Download
62.98 KB lrw-r--r-- 2026-03-01 23:46:52
Edit Download
10.79 KB lrw-r--r-- 2026-02-07 16:28:16
Edit Download
14.17 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
6.32 KB lrw-r--r-- 2026-02-06 17:10:19
Edit Download
15.13 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.50 KB lrw-r--r-- 2026-03-01 05:05:52
Edit Download
12.15 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
25.39 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
17.61 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
6.45 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
2.33 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
12.78 KB lrw-r--r-- 2026-02-21 08:13:30
Edit Download
9.96 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.51 KB lrw-r--r-- 2026-02-20 19:31:28
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).