Skip to content

Commit

Permalink
Merge pull request #955 from IkkiOcean/backend-schema-11
Browse files Browse the repository at this point in the history
Added Comprehensive Invoice Management System with Routes, Controllers, and Bulk Operations
  • Loading branch information
manikumarreddyu authored Nov 10, 2024
2 parents f518995 + 7b82e00 commit 890b11a
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 0 deletions.
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;

0 comments on commit 890b11a

Please sign in to comment.