PHP 8.2.30
Preview: expenseController.js Size: 8.50 KB
/home/byroehnu/easepay.easetack.com/controllers/expenseController.js

const { prisma } = require('../config/prisma');


const getExpenses = async (req, res) => {
  try {
    const { page = 1, limit = 10, startDate, endDate, category } = req.query;
    const offset = (parseInt(page) - 1) * parseInt(limit);

    // Build where clause
    const whereClause = { userId: req.user.id };
    
    if (startDate) {
      whereClause.date = {
        ...whereClause.date,
        gte: new Date(startDate)
      };
    }

    if (endDate) {
      whereClause.date = {
        ...whereClause.date,
        lte: new Date(endDate)
      };
    }

    if (category) {
      whereClause.category = category;
    }

    // Get expenses with pagination
    const expenses = await prisma.expense.findMany({
      where: whereClause,
      orderBy: { date: 'desc' },
      skip: offset,
      take: parseInt(limit),
      include: {
        business: {
          select: {
            name: true
          }
        }
      }
    });

    // Get total count for pagination
    const total = await prisma.expense.count({
      where: whereClause
    });

    const totalPages = Math.ceil(total / parseInt(limit));

    res.status(200).json({
      success: true,
      expenses: expenses,
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total,
        totalPages
      }
    });
  } catch (error) {
    console.error('Get expenses error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to get expenses'
    });
  }
};


const getExpenseById = async (req, res) => {
  try {
    const { id } = req.params;

    const expense = await prisma.expense.findFirst({
      where: {
        id: parseInt(id),
        userId: req.user.id
      },
      include: {
        business: {
          select: {
            businessName: true
          }
        }
      }
    });

    if (!expense) {
      return res.status(404).json({
        success: false,
        message: 'Expense not found'
      });
    }

    res.status(200).json({
      success: true,
      expense: expense
    });
  } catch (error) {
    console.error('Get expense by ID error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to get expense'
    });
  }
};


const createExpense = async (req, res) => {
  try {
    const {
      description,
      amount,
      category,
      paymentMethod,
      expenseDate,
      businessId
    } = req.body;

    // Validation
    if (!description || !amount || !category) {
      return res.status(400).json({
        success: false,
        message: 'Please provide description, amount, and category'
      });
    }

    // If businessId provided, verify it belongs to the user
    if (businessId) {
      const business = await prisma.business.findFirst({
        where: {
          id: businessId,
          userId: req.user.id
        }
      });

      if (!business) {
        return res.status(400).json({
          success: false,
          message: 'Invalid business ID'
        });
      }
    }

    const newExpense = await prisma.expense.create({
      data: {
        userId: req.user.id,
        businessId: businessId || null,
        description,
        amount: parseFloat(amount),
        category,
        paymentMethod: paymentMethod || 'CASH',
        date: expenseDate ? new Date(expenseDate) : new Date()
      }
    });

    res.status(201).json({
      success: true,
      message: 'Expense created successfully',
      expense: newExpense
    });
  } catch (error) {
    console.error('Create expense error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to create expense'
    });
  }
};


const updateExpense = async (req, res) => {
  try {
    const { id } = req.params;
    const {
      description,
      amount,
      category,
      paymentMethod,
      expenseDate,
      businessId
    } = req.body;

    // Check if expense exists and belongs to user
    const existingExpense = await prisma.expense.findFirst({
      where: {
        id: parseInt(id),
        userId: req.user.id
      }
    });

    if (!existingExpense) {
      return res.status(404).json({
        success: false,
        message: 'Expense not found'
      });
    }

    // If businessId provided, verify it belongs to the user
    if (businessId) {
      const business = await prisma.business.findFirst({
        where: {
          id: businessId,
          userId: req.user.id
        }
      });

      if (!business) {
        return res.status(400).json({
          success: false,
          message: 'Invalid business ID'
        });
      }
    }

    // Build update data with only provided fields
    const updateData = {};
    if (description !== undefined) updateData.description = description;
    if (amount !== undefined) updateData.amount = parseFloat(amount);
    if (category !== undefined) updateData.category = category;
    if (paymentMethod !== undefined) updateData.paymentMethod = paymentMethod;
    if (expenseDate !== undefined) updateData.date = new Date(expenseDate);
    if (businessId !== undefined) updateData.businessId = businessId;

    const updatedExpense = await prisma.expense.update({
      where: { id: parseInt(id) },
      data: updateData
    });

    res.status(200).json({
      success: true,
      message: 'Expense updated successfully',
      expense: updatedExpense
    });
  } catch (error) {
    console.error('Update expense error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to update expense'
    });
  }
};


const deleteExpense = async (req, res) => {
  try {
    const { id } = req.params;

    const existingExpense = await prisma.expense.findFirst({
      where: {
        id: parseInt(id),
        userId: req.user.id
      }
    });

    if (!existingExpense) {
      return res.status(404).json({
        success: false,
        message: 'Expense not found'
      });
    }

    await prisma.expense.delete({
      where: { id: parseInt(id) }
    });

    res.status(200).json({
      success: true,
      message: 'Expense deleted successfully'
    });
  } catch (error) {
    console.error('Delete expense error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to delete expense'
    });
  }
};


const getExpenseAnalytics = async (req, res) => {
  try {
    // Get total expenses
    const totalExpenses = await prisma.expense.aggregate({
      where: { userId: req.user.id },
      _sum: { amount: true },
      _count: { id: true }
    });

    // Get expenses by category
    const expensesByCategory = await prisma.expense.groupBy({
      by: ['category'],
      where: { userId: req.user.id },
      _sum: { amount: true },
      _count: { id: true }
    });

    // Get monthly expenses for current year
    const currentYear = new Date().getFullYear();
    const monthlyExpenses = await prisma.expense.groupBy({
      by: ['date'],
      where: {
        userId: req.user.id,
        date: {
          gte: new Date(`${currentYear}-01-01`),
          lte: new Date(`${currentYear}-12-31`)
        }
      },
      _sum: { amount: true },
      _count: { id: true }
    });

    // Process monthly data
    const monthlyData = Array.from({ length: 12 }, (_, i) => ({
      month: i + 1,
      expenses: 0,
      count: 0
    }));

    monthlyExpenses.forEach(expense => {
      const month = expense.date.getMonth();
      if (monthlyData[month]) {
        monthlyData[month].expenses += Number(expense._sum.amount) || 0;
        monthlyData[month].count += expense._count.id;
      }
    });

    const analytics = {
      totalExpenses: Number(totalExpenses._sum.amount) || 0,
      totalCount: totalExpenses._count.id || 0,
      expensesByCategory: expensesByCategory.map(cat => ({
        category: cat.category,
        amount: Number(cat._sum.amount) || 0,
        count: cat._count.id
      })),
      monthlyExpenses: monthlyData
    };

    res.status(200).json({
      success: true,
      data: analytics
    });
  } catch (error) {
    console.error('Error fetching expense analytics:', error);
    res.status(500).json({
      success: false,
      message: 'Server error'
    });
  }
};

module.exports = {
  getExpenses,
  getExpenseById,
  createExpense,
  updateExpense,
  deleteExpense,
  getExpenseAnalytics
};

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).