Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Comprehensive Invoice Management System with Routes, Controllers, and Bulk Operations #955

Merged
merged 1 commit into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions backend/controllers/shop/sub-controllers/invoiceController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
const Invoice = require("../models/invoice");
const Joi = require("joi");

// Validation Schemas
const invoiceSchema = Joi.object({
orderId: Joi.string().required(),
userId: Joi.string().required(),
amount: Joi.number().min(0).required(),
dueDate: Joi.date().required(),
paymentId: Joi.string().optional(),
});

const statusUpdateSchema = Joi.object({
status: Joi.string()
.valid("Paid", "Unpaid", "Overdue", "Cancelled")
.required(),
});

// Generate invoice after payment confirmation
const generateInvoice = async (req, res) => {
const { error, value } = invoiceSchema.validate(req.body);
if (error) return res.status(400).json({ message: error.message });

const { orderId, userId, amount, dueDate, paymentId } = value;

const newInvoice = new Invoice({
invoiceId: `INV${Date.now()}`,
orderId,
userId,
amount,
dueDate,
paymentId,
});

try {
const invoice = await newInvoice.save();
res.status(201).json(invoice);
} catch (error) {
console.error(`Error generating invoice: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

// Retrieve invoices by user, order, status, or date range with pagination
const getInvoices = async (req, res) => {
const {
userId,
orderId,
status,
startDate,
endDate,
page = 1,
limit = 10,
sortBy = "invoiceDate",
order = "desc",
} = req.query;

try {
const filters = {};
if (userId) filters.userId = userId;
if (orderId) filters.orderId = orderId;
if (status) filters.status = status;

if (startDate || endDate) {
filters.invoiceDate = {};
if (startDate) filters.invoiceDate.$gte = new Date(startDate);
if (endDate) filters.invoiceDate.$lte = new Date(endDate);
}

const invoices = await Invoice.find(filters)
.sort({ [sortBy]: order === "asc" ? 1 : -1 })
.skip((page - 1) * limit)
.limit(Number(limit));

res.status(200).json(invoices);
} catch (error) {
console.error(`Error fetching invoices: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

// Retrieve a single invoice by ID
const getInvoiceById = async (req, res) => {
try {
const invoice = await Invoice.findById(req.params.id);
if (!invoice) return res.status(404).json({ message: "Invoice not found" });
res.status(200).json(invoice);
} catch (error) {
console.error(`Error fetching invoice by ID: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

// Update invoice status with validation
const updateInvoiceStatus = async (req, res) => {
const { error, value } = statusUpdateSchema.validate(req.body);
if (error) return res.status(400).json({ message: error.message });

try {
const invoice = await Invoice.findByIdAndUpdate(
req.params.id,
{ status: value.status },
{ new: true }
);
if (!invoice) return res.status(404).json({ message: "Invoice not found" });
res.status(200).json(invoice);
} catch (error) {
console.error(`Error updating invoice status: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

// Delete an invoice by ID
const deleteInvoice = async (req, res) => {
try {
const invoice = await Invoice.findByIdAndDelete(req.params.id);
if (!invoice) return res.status(404).json({ message: "Invoice not found" });
res.status(200).json({ message: "Invoice deleted successfully" });
} catch (error) {
console.error(`Error deleting invoice: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

// Mark overdue invoices
const markOverdueInvoices = async (req, res) => {
try {
const overdueInvoices = await Invoice.updateMany(
{ dueDate: { $lt: new Date() }, status: "Unpaid" },
{ status: "Overdue" }
);
res
.status(200)
.json({
message: `${overdueInvoices.nModified} invoices marked as overdue`,
});
} catch (error) {
console.error(`Error marking overdue invoices: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

// Summary of invoices by status
const invoiceSummary = async (req, res) => {
const { userId } = req.query;

try {
const match = userId ? { userId } : {};

const summary = await Invoice.aggregate([
{ $match: match },
{
$group: {
_id: "$status",
totalAmount: { $sum: "$amount" },
count: { $sum: 1 },
},
},
]);

const summaryData = summary.reduce((acc, item) => {
acc[item._id] = { totalAmount: item.totalAmount, count: item.count };
return acc;
}, {});

res.status(200).json(summaryData);
} catch (error) {
console.error(`Error generating invoice summary: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

// Bulk invoice generation
const generateBulkInvoices = async (req, res) => {
const { invoices } = req.body;

if (!Array.isArray(invoices) || invoices.length === 0) {
return res.status(400).json({ message: "Invalid invoices data" });
}

const validationErrors = invoices
.map((invoiceData, index) => {
const { error } = invoiceSchema.validate(invoiceData);
return error ? { index, message: error.message } : null;
})
.filter(Boolean);

if (validationErrors.length > 0) {
return res
.status(400)
.json({ message: "Validation errors", errors: validationErrors });
}

try {
const invoiceDocuments = invoices.map((invoiceData) => ({
...invoiceData,
invoiceId: `INV${Date.now() + Math.floor(Math.random() * 1000)}`,
}));

const createdInvoices = await Invoice.insertMany(invoiceDocuments);
res.status(201).json(createdInvoices);
} catch (error) {
console.error(`Error generating bulk invoices: ${error.message}`);
res.status(500).json({ message: "Internal server error" });
}
};

module.exports = {
generateInvoice,
getInvoices,
getInvoiceById,
updateInvoiceStatus,
deleteInvoice,
markOverdueInvoices,
invoiceSummary,
generateBulkInvoices,
};
47 changes: 47 additions & 0 deletions backend/model/shop/sub-model/invoice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const mongoose = require("mongoose");


const invoiceSchema = new mongoose.Schema(
{
invoiceId: { type: String, required: true, unique: true },
orderId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Order",
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
index: true, // Adds index for faster query on user invoices
},
amount: { type: Number, required: true, min: 0 },
invoiceDate: { type: Date, default: Date.now },
dueDate: {
type: Date,
required: true,
default: function () {
return new Date(+new Date() + 7 * 24 * 60 * 60 * 1000); // Default due date is 7 days from invoice date
},
},
status: {
type: String,
enum: ["Paid", "Unpaid", "Overdue", "Cancelled"],
default: "Unpaid",
},
paymentId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Payment",
sparse: true, // Allows null value indexing for unpaid invoices
},
},
{ timestamps: true }
);

// Virtual field to check if invoice is overdue
invoiceSchema.virtual("isOverdue").get(function () {
return this.status === "Unpaid" && this.dueDate < new Date();
});

const Invoice = mongoose.model("Invoice", invoiceSchema);
module.exports = Invoice;
39 changes: 39 additions & 0 deletions backend/routes/sub-routes/invoiceRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const express = require("express");
const router = express.Router();

const {
generateInvoice,
getInvoices,
getInvoiceById,
updateInvoiceStatus,
deleteInvoice,
markOverdueInvoices,
invoiceSummary,
generateBulkInvoices,
} = require("../controllers/invoiceController");

// Route to generate a new invoice after payment confirmation
router.post("/generate", generateInvoice);

// Route to retrieve invoices by user, order, status, or date range with pagination and sorting
router.get("/", getInvoices);

// Route to retrieve a specific invoice by its ID
router.get("/:id", getInvoiceById);

// Route to update an invoice's status
router.patch("/:id/status", updateInvoiceStatus);

// Route to delete an invoice by its ID
router.delete("/:id", deleteInvoice);

// Route to mark overdue invoices (based on the current date)
router.patch("/mark-overdue", markOverdueInvoices);

// Route to get an invoice summary (by status, optionally by user)
router.get("/summary", invoiceSummary);

// Route to generate bulk invoices at once
router.post("/generate-bulk", generateBulkInvoices);

module.exports = router;
Loading