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 }); } };