213 lines
6.0 KiB
JavaScript
213 lines
6.0 KiB
JavaScript
const Order = require('../models/Order');
|
|
const Product = require('../models/Product');
|
|
const InventoryHistory = require('../models/InventoryHistory');
|
|
const Cart = require('../models/Cart');
|
|
const { sendEmail } = require('../utils/emailSender');
|
|
const { successResponse, errorResponse } = require('../utils/apiResponse');
|
|
|
|
// @desc Create new order
|
|
// @route POST /api/orders
|
|
// @access Private
|
|
const createOrder = async (req, res, next) => {
|
|
try {
|
|
const {
|
|
orderItems,
|
|
shippingAddress,
|
|
paymentMethod,
|
|
itemsPrice,
|
|
taxPrice,
|
|
shippingPrice,
|
|
discountAmount,
|
|
totalAmount
|
|
} = req.body;
|
|
|
|
if (!orderItems || orderItems.length === 0) {
|
|
return errorResponse(res, 'No order items provided', [], 400);
|
|
}
|
|
|
|
// 1. Check stock and reduce inventory
|
|
for (const item of orderItems) {
|
|
const product = await Product.findById(item.product);
|
|
if (!product) {
|
|
return errorResponse(res, `Product ${item.name} not found`, [], 400);
|
|
}
|
|
if (product.stock < item.quantity) {
|
|
return errorResponse(res, `Insufficient stock for product ${product.name}`, [], 400);
|
|
}
|
|
|
|
// Record stock deduction
|
|
const oldStock = product.stock;
|
|
product.stock -= item.quantity;
|
|
product.salesCount += item.quantity;
|
|
await product.save();
|
|
|
|
await InventoryHistory.create({
|
|
product: product._id,
|
|
sku: product.sku,
|
|
changeType: 'Order Placed',
|
|
quantityChanged: -item.quantity,
|
|
previousStock: oldStock,
|
|
newStock: product.stock,
|
|
reason: `Order placed by user ${req.user.name}`,
|
|
performedBy: req.user._id
|
|
});
|
|
}
|
|
|
|
// 2. Generate unique order number
|
|
const orderNumber = `GTR-${Math.floor(100000 + Math.random() * 900000)}`;
|
|
|
|
// 3. Save Order
|
|
const order = new Order({
|
|
orderNumber,
|
|
user: req.user._id,
|
|
orderItems,
|
|
shippingAddress,
|
|
paymentMethod,
|
|
itemsPrice,
|
|
taxPrice,
|
|
shippingPrice,
|
|
discountAmount,
|
|
totalAmount,
|
|
isPaid: paymentMethod === 'Stripe', // Auto mark paid for simulated stripe
|
|
paidAt: paymentMethod === 'Stripe' ? Date.now() : null,
|
|
orderStatus: paymentMethod === 'Stripe' ? 'Payment Confirmed' : 'Pending',
|
|
statusHistory: [
|
|
{
|
|
status: paymentMethod === 'Stripe' ? 'Payment Confirmed' : 'Pending',
|
|
note: `Order created via ${paymentMethod}`
|
|
}
|
|
]
|
|
});
|
|
|
|
const createdOrder = await order.save();
|
|
|
|
// 4. Clear user cart
|
|
await Cart.findOneAndDelete({ user: req.user._id });
|
|
|
|
// 5. Send order confirmation email
|
|
sendEmail({
|
|
templateName: 'Order Confirmation',
|
|
to: req.user.email,
|
|
variables: {
|
|
customerName: req.user.name,
|
|
orderId: createdOrder.orderNumber,
|
|
orderTotal: `$${createdOrder.totalAmount.toFixed(2)}`
|
|
}
|
|
});
|
|
|
|
return successResponse(res, 'Order created successfully', createdOrder, 201);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
// @desc Get logged in user orders
|
|
// @route GET /api/orders/my-orders
|
|
// @access Private
|
|
const getMyOrders = async (req, res, next) => {
|
|
try {
|
|
const orders = await Order.find({ user: req.user._id }).sort({ createdAt: -1 });
|
|
return successResponse(res, 'User orders retrieved', orders);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
// @desc Get order by ID
|
|
// @route GET /api/orders/:id
|
|
// @access Private
|
|
const getOrderById = async (req, res, next) => {
|
|
try {
|
|
const order = await Order.findById(req.params.id).populate('user', 'name email phone');
|
|
if (!order) {
|
|
return errorResponse(res, 'Order not found', [], 404);
|
|
}
|
|
|
|
// Check authorization: customer can only view own order unless Admin
|
|
if (order.user._id.toString() !== req.user._id.toString() && req.user.role !== 'Admin') {
|
|
return errorResponse(res, 'Not authorized to view this order', [], 403);
|
|
}
|
|
|
|
return successResponse(res, 'Order retrieved', order);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
// @desc Get all orders (Admin)
|
|
// @route GET /api/orders
|
|
// @access Private/Admin
|
|
const getAllOrders = async (req, res, next) => {
|
|
try {
|
|
const { status } = req.query;
|
|
const filter = {};
|
|
if (status) filter.orderStatus = status;
|
|
|
|
const orders = await Order.find(filter)
|
|
.populate('user', 'name email')
|
|
.sort({ createdAt: -1 });
|
|
|
|
return successResponse(res, 'All orders retrieved', orders);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
// @desc Update order status & shipping info (Admin)
|
|
// @route PUT /api/orders/:id/status
|
|
// @access Private/Admin
|
|
const updateOrderStatus = async (req, res, next) => {
|
|
try {
|
|
const { orderStatus, trackingNumber, shippingProvider, note } = req.body;
|
|
const order = await Order.findById(req.params.id).populate('user', 'name email');
|
|
|
|
if (!order) {
|
|
return errorResponse(res, 'Order not found', [], 404);
|
|
}
|
|
|
|
if (orderStatus) {
|
|
order.orderStatus = orderStatus;
|
|
order.statusHistory.push({
|
|
status: orderStatus,
|
|
note: note || `Status updated to ${orderStatus}`
|
|
});
|
|
}
|
|
|
|
if (trackingNumber) order.trackingNumber = trackingNumber;
|
|
if (shippingProvider) order.shippingProvider = shippingProvider;
|
|
|
|
if (orderStatus === 'Delivered') {
|
|
order.isDelivered = true;
|
|
order.deliveredAt = Date.now();
|
|
}
|
|
|
|
const updatedOrder = await order.save();
|
|
|
|
// Trigger status change email
|
|
if (['Shipped', 'Delivered', 'Cancelled'].includes(orderStatus)) {
|
|
sendEmail({
|
|
templateName: orderStatus === 'Shipped' ? 'Order Shipped' : 'Order Confirmation',
|
|
to: order.user.email,
|
|
variables: {
|
|
customerName: order.user.name,
|
|
orderId: order.orderNumber,
|
|
trackingNumber: order.trackingNumber || 'N/A'
|
|
}
|
|
});
|
|
}
|
|
|
|
return successResponse(res, 'Order status updated', updatedOrder);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
createOrder,
|
|
getMyOrders,
|
|
getOrderById,
|
|
getAllOrders,
|
|
updateOrderStatus
|
|
};
|
|
|