271 lines
7.3 KiB
JavaScript
271 lines
7.3 KiB
JavaScript
import Promotion from "../models/Promotion.model.js";
|
|
import logger from "../utils/logger.js";
|
|
|
|
/**
|
|
* Get all promotions for a store
|
|
*/
|
|
export const getPromotions = async (req, res) => {
|
|
try {
|
|
const { query } = req;
|
|
const filter = { storeId: req.user.storeId };
|
|
|
|
// Apply filters if provided
|
|
if (query.isActive) {
|
|
filter.isActive = query.isActive === 'true';
|
|
}
|
|
|
|
if (query.search) {
|
|
filter.code = { $regex: query.search, $options: 'i' };
|
|
}
|
|
|
|
// Handle date filters
|
|
if (query.active === 'true') {
|
|
const now = new Date();
|
|
filter.startDate = { $lte: now };
|
|
filter.$or = [
|
|
{ endDate: null },
|
|
{ endDate: { $gte: now } }
|
|
];
|
|
filter.isActive = true;
|
|
}
|
|
|
|
const promotions = await Promotion.find(filter).sort({ createdAt: -1 });
|
|
|
|
return res.status(200).json(promotions);
|
|
} catch (error) {
|
|
logger.error("Error fetching promotions", { error });
|
|
return res.status(500).json({ message: "Failed to fetch promotions", error: error.message });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a single promotion by ID
|
|
*/
|
|
export const getPromotionById = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const promotion = await Promotion.findOne({
|
|
_id: id,
|
|
storeId: req.user.storeId
|
|
});
|
|
|
|
if (!promotion) {
|
|
return res.status(404).json({ message: "Promotion not found" });
|
|
}
|
|
|
|
return res.status(200).json(promotion);
|
|
} catch (error) {
|
|
logger.error("Error fetching promotion", { error });
|
|
return res.status(500).json({ message: "Failed to fetch promotion", error: error.message });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a new promotion
|
|
*/
|
|
export const createPromotion = async (req, res) => {
|
|
try {
|
|
const {
|
|
code,
|
|
discountType,
|
|
discountValue,
|
|
minOrderAmount = 0,
|
|
maxUsage = null,
|
|
isActive = true,
|
|
startDate = new Date(),
|
|
endDate = null,
|
|
description = ""
|
|
} = req.body;
|
|
|
|
// Validate required fields
|
|
if (!code || !discountType || discountValue === undefined) {
|
|
return res.status(400).json({ message: "Code, discount type, and discount value are required" });
|
|
}
|
|
|
|
// Check if code already exists for this store
|
|
const existingPromo = await Promotion.findOne({
|
|
storeId: req.user.storeId,
|
|
code: code.toUpperCase()
|
|
});
|
|
|
|
if (existingPromo) {
|
|
return res.status(400).json({ message: "A promotion with this code already exists" });
|
|
}
|
|
|
|
// Create new promotion
|
|
const newPromotion = new Promotion({
|
|
storeId: req.user.storeId,
|
|
code: code.toUpperCase(),
|
|
discountType,
|
|
discountValue,
|
|
minOrderAmount,
|
|
maxUsage,
|
|
isActive,
|
|
startDate,
|
|
endDate,
|
|
description
|
|
});
|
|
|
|
await newPromotion.save();
|
|
|
|
return res.status(201).json(newPromotion);
|
|
} catch (error) {
|
|
logger.error("Error creating promotion", { error });
|
|
|
|
if (error.name === 'ValidationError') {
|
|
return res.status(400).json({
|
|
message: "Invalid promotion data",
|
|
details: Object.values(error.errors).map(err => err.message)
|
|
});
|
|
}
|
|
|
|
return res.status(500).json({ message: "Failed to create promotion", error: error.message });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update an existing promotion
|
|
*/
|
|
export const updatePromotion = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const updates = req.body;
|
|
|
|
// Ensure the promotion exists and belongs to the vendor's store
|
|
const promotion = await Promotion.findOne({
|
|
_id: id,
|
|
storeId: req.user.storeId
|
|
});
|
|
|
|
if (!promotion) {
|
|
return res.status(404).json({ message: "Promotion not found" });
|
|
}
|
|
|
|
// Remove storeId from updates if present (can't change store)
|
|
if (updates.storeId) {
|
|
delete updates.storeId;
|
|
}
|
|
|
|
// Ensure code is uppercase if present
|
|
if (updates.code) {
|
|
updates.code = updates.code.toUpperCase();
|
|
|
|
// Check if updated code conflicts with existing promo
|
|
const codeExists = await Promotion.findOne({
|
|
storeId: req.user.storeId,
|
|
code: updates.code,
|
|
_id: { $ne: id } // Exclude current promotion
|
|
});
|
|
|
|
if (codeExists) {
|
|
return res.status(400).json({ message: "A promotion with this code already exists" });
|
|
}
|
|
}
|
|
|
|
// Update the promotion
|
|
const updatedPromotion = await Promotion.findByIdAndUpdate(
|
|
id,
|
|
{ ...updates, updatedAt: new Date() },
|
|
{ new: true, runValidators: true }
|
|
);
|
|
|
|
return res.status(200).json(updatedPromotion);
|
|
} catch (error) {
|
|
logger.error("Error updating promotion", { error });
|
|
|
|
if (error.name === 'ValidationError') {
|
|
return res.status(400).json({
|
|
message: "Invalid promotion data",
|
|
details: Object.values(error.errors).map(err => err.message)
|
|
});
|
|
}
|
|
|
|
return res.status(500).json({ message: "Failed to update promotion", error: error.message });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Delete a promotion
|
|
*/
|
|
export const deletePromotion = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// Ensure the promotion exists and belongs to the vendor's store
|
|
const promotion = await Promotion.findOne({
|
|
_id: id,
|
|
storeId: req.user.storeId
|
|
});
|
|
|
|
if (!promotion) {
|
|
return res.status(404).json({ message: "Promotion not found" });
|
|
}
|
|
|
|
await Promotion.findByIdAndDelete(id);
|
|
|
|
return res.status(200).json({ message: "Promotion deleted successfully" });
|
|
} catch (error) {
|
|
logger.error("Error deleting promotion", { error });
|
|
return res.status(500).json({ message: "Failed to delete promotion", error: error.message });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Validate a promotion code for a store
|
|
*/
|
|
export const validatePromotion = async (req, res) => {
|
|
try {
|
|
const { code, orderTotal } = req.body;
|
|
const { storeId } = req.params;
|
|
|
|
if (!code || !storeId) {
|
|
return res.status(400).json({ message: "Promotion code and store ID are required" });
|
|
}
|
|
|
|
// Find the promotion
|
|
const promotion = await Promotion.findOne({
|
|
storeId,
|
|
code: code.toUpperCase(),
|
|
isActive: true,
|
|
});
|
|
|
|
if (!promotion) {
|
|
return res.status(404).json({ message: "Promotion not found or inactive" });
|
|
}
|
|
|
|
// Check if the promotion is valid
|
|
if (!promotion.isValid()) {
|
|
return res.status(400).json({ message: "Promotion is no longer valid" });
|
|
}
|
|
|
|
// Check minimum order amount
|
|
if (orderTotal && orderTotal < promotion.minOrderAmount) {
|
|
return res.status(400).json({
|
|
message: `Order total must be at least £${promotion.minOrderAmount} to use this promotion`,
|
|
minOrderAmount: promotion.minOrderAmount
|
|
});
|
|
}
|
|
|
|
// Calculate discount if order total provided
|
|
let discountAmount = null;
|
|
if (orderTotal) {
|
|
discountAmount = promotion.calculateDiscount(orderTotal);
|
|
}
|
|
|
|
return res.status(200).json({
|
|
promotion: {
|
|
_id: promotion._id,
|
|
code: promotion.code,
|
|
discountType: promotion.discountType,
|
|
discountValue: promotion.discountValue,
|
|
minOrderAmount: promotion.minOrderAmount,
|
|
},
|
|
discountAmount,
|
|
message: "Promotion is valid"
|
|
});
|
|
} catch (error) {
|
|
logger.error("Error validating promotion", { error });
|
|
return res.status(500).json({ message: "Failed to validate promotion", error: error.message });
|
|
}
|
|
};
|