face / src /controllers /expenseController.js
aliroohan179's picture
feat: implement business date utility and financial reporting system including expense tracking, platform sales, and profit-loss analysis endpoints
ec62259
Raw History Blame Contribute Delete
4.15 kB
const Expense = require('../models/Expense');
const { log } = require('../utils/activityLogger');
// GET /api/expenses
exports.getExpenses = async (req, res) => {
const { from, to, category, page = 1, limit = 50 } = req.query;
const filter = {};
if (category) filter.category = category;
if (from || to) {
filter.date = {};
if (from) filter.date.$gte = new Date(from);
if (to) filter.date.$lte = new Date(new Date(to).setHours(23, 59, 59, 999));
}
const [expenses, total] = await Promise.all([
Expense.find(filter)
.populate('createdBy', 'name username')
.sort({ date: -1 })
.skip((page - 1) * limit)
.limit(Number(limit)),
Expense.countDocuments(filter),
]);
res.json({ expenses, total, page: Number(page), pages: Math.ceil(total / limit) });
};
// POST /api/expenses
exports.createExpense = async (req, res) => {
const expense = await Expense.create({ ...req.body, createdBy: req.user._id });
log(req.user, 'ADD_EXPENSE', { entity: 'Expense', entityId: expense._id, detail: `${expense.title} Rs.${expense.amount}` });
res.status(201).json(expense);
};
// POST /api/expenses/bulk — create multiple expenses in one go
exports.createBulkExpenses = async (req, res) => {
const { expenses, date } = req.body;
if (!Array.isArray(expenses) || expenses.length === 0) {
return res.status(400).json({ message: 'Array of expenses is required' });
}
const defaultDate = date ? new Date(date) : new Date();
const docsToInsert = expenses
.filter((e) => e.title && e.title.trim() && !isNaN(Number(e.amount)) && Number(e.amount) >= 0)
.map((e) => ({
title: e.title.trim(),
category: e.category || 'Misc',
amount: Number(e.amount),
date: e.date ? new Date(e.date) : defaultDate,
note: e.note || '',
recurring: Boolean(e.recurring),
frequency: e.recurring ? e.frequency || 'monthly' : null,
createdBy: req.user._id,
}));
if (docsToInsert.length === 0) {
return res.status(400).json({ message: 'No valid expense entries provided' });
}
const created = await Expense.insertMany(docsToInsert);
const totalAmount = created.reduce((s, e) => s + e.amount, 0);
log(req.user, 'BULK_EXPENSE', {
entity: 'Expense',
detail: `Added ${created.length} expenses totalling Rs.${totalAmount}`,
});
res.status(201).json({ count: created.length, expenses: created });
};
// PUT /api/expenses/:id
exports.updateExpense = async (req, res) => {
const expense = await Expense.findById(req.params.id);
if (!expense) return res.status(404).json({ message: 'Expense not found' });
// Build human-readable diff for audit trail
const changedFields = Object.keys(req.body)
.filter((k) => String(expense[k]) !== String(req.body[k]))
.map((k) => `${k}: "${expense[k]}" → "${req.body[k]}"`)
.join(', ');
Object.assign(expense, req.body);
expense.editLog.push({ user: req.user._id, changes: changedFields || 'no change' });
await expense.save();
log(req.user, 'EDIT_EXPENSE', { entity: 'Expense', entityId: expense._id, detail: changedFields });
res.json(expense);
};
// DELETE /api/expenses/:id
exports.deleteExpense = async (req, res) => {
const expense = await Expense.findByIdAndDelete(req.params.id);
if (!expense) return res.status(404).json({ message: 'Expense not found' });
log(req.user, 'DELETE_EXPENSE', { entity: 'Expense', entityId: expense._id, detail: expense.title });
res.json({ message: 'Expense deleted' });
};
// POST /api/expenses/:id/duplicate — copy to next month (recurring helper)
exports.duplicateExpense = async (req, res) => {
const source = await Expense.findById(req.params.id);
if (!source) return res.status(404).json({ message: 'Expense not found' });
const newDate = new Date(source.date);
newDate.setMonth(newDate.getMonth() + 1);
const copy = await Expense.create({
title: source.title,
category: source.category,
amount: source.amount,
date: newDate,
note: source.note,
recurring: source.recurring,
frequency: source.frequency,
createdBy: req.user._id,
});
res.status(201).json(copy);
};