@ideal-photography/shared
Version:
Shared MongoDB and utility logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, audit logs, minimart items/orders, and push notification subscriptions.
374 lines (337 loc) • 12.7 kB
JavaScript
import { models } from '../mongoDB/index.js';
class OrderService {
constructor() {
// Remove circular dependency - we'll import CartService methods when needed
}
/**
* Create order from cart
* @param {string} userId - User ID
* @param {Object} orderData - Order data
* @returns {Promise<Object>} Created order
*/
async createOrderFromCart(userId, orderData) {
try {
// Get user's cart directly from database to avoid circular dependency
const cart = await models.Cart.findOne({ userId }).populate('items');
if (!cart || cart.items.length === 0) {
throw new Error('Cart is empty');
}
// Calculate totals
await cart.calculateTotals();
// Basic validation - check if all items are valid
const invalidItems = cart.items.filter(item => !item.productSnapshot || !item.subtotal);
if (invalidItems.length > 0) {
throw new Error('Cart contains invalid items');
}
// Generate order number
const orderNumber = models.Order.generateOrderNumber();
// Prepare order items from cart
const orderItems = cart.items.map(item => ({
productId: item.productId,
productSnapshot: item.productSnapshot,
quantity: item.quantity,
subtotal: item.subtotal,
options: item.options
}));
// Create order
const order = await models.Order.create({
orderNumber,
userId,
items: orderItems,
subtotal: cart.subtotal,
tax: cart.tax,
total: cart.total,
paymentMethod: orderData.paymentMethod || 'paystack',
shippingAddress: orderData.shippingAddress || {},
notes: orderData.notes
});
// Clear cart after successful order creation - direct database operation
await models.CartItem.deleteMany({ _id: { $in: cart.items } });
cart.clear();
await cart.save();
return {
success: true,
order: {
id: order._id,
orderNumber: order.orderNumber,
total: order.total,
status: order.status,
paymentStatus: order.paymentStatus,
createdAt: order.createdAt
}
};
} catch (error) {
console.error('Error creating order from cart:', error);
throw error;
}
}
/**
* Get order by ID
* @param {string} orderId - Order ID
* @param {string} userId - User ID (for authorization)
* @returns {Promise<Object>} Order data
*/
async getOrderById(orderId, userId) {
try {
const order = await models.Order.findOne({ _id: orderId, userId });
if (!order) {
throw new Error('Order not found');
}
return {
success: true,
order: {
id: order._id,
orderNumber: order.orderNumber,
items: order.items,
subtotal: order.subtotal,
tax: order.tax,
total: order.total,
status: order.status,
paymentStatus: order.paymentStatus,
paymentMethod: order.paymentMethod,
shippingAddress: order.shippingAddress,
notes: order.notes,
trackingNumber: order.trackingNumber,
estimatedDelivery: order.estimatedDelivery,
actualDelivery: order.actualDelivery,
createdAt: order.createdAt,
updatedAt: order.updatedAt
}
};
} catch (error) {
console.error('Error getting order:', error);
throw error;
}
}
/**
* Get user's orders
* @param {string} userId - User ID
* @param {Object} filters - Filter options
* @returns {Promise<Object>} Orders and pagination info
*/
async getUserOrders(userId, filters = {}) {
try {
const query = { userId };
if (filters.status) {
query.status = filters.status;
}
if (filters.paymentStatus) {
query.paymentStatus = filters.paymentStatus;
}
const [orders, total] = await Promise.all([
models.Order.find(query)
.sort({ createdAt: -1 })
.skip(filters.skip || 0)
.limit(filters.limit || 20)
.lean(),
models.Order.countDocuments(query)
]);
return {
success: true,
orders,
pagination: {
total,
page: Math.floor((filters.skip || 0) / (filters.limit || 20)) + 1,
limit: filters.limit || 20,
pages: Math.ceil(total / (filters.limit || 20))
}
};
} catch (error) {
console.error('Error getting user orders:', error);
throw error;
}
}
/**
* Update order status
* @param {string} orderId - Order ID
* @param {string} status - New status
* @param {string} updatedBy - User ID who updated
* @param {string} notes - Optional notes
* @returns {Promise<Object>} Updated order
*/
async updateOrderStatus(orderId, status, updatedBy, notes = null) {
try {
const order = await models.Order.findById(orderId);
if (!order) {
throw new Error('Order not found');
}
order.updateStatus(status, notes);
await order.save();
return {
success: true,
order: {
id: order._id,
orderNumber: order.orderNumber,
status: order.status,
notes: order.notes,
updatedAt: order.updatedAt
}
};
} catch (error) {
console.error('Error updating order status:', error);
throw error;
}
}
/**
* Update payment status
* @param {string} orderId - Order ID
* @param {string} paymentStatus - Payment status
* @param {string} paymentReference - Payment reference
* @returns {Promise<Object>} Updated order
*/
async updatePaymentStatus(orderId, paymentStatus, paymentReference = null) {
try {
const order = await models.Order.findById(orderId);
if (!order) {
throw new Error('Order not found');
}
order.updatePaymentStatus(paymentStatus, paymentReference);
await order.save();
return {
success: true,
order: {
id: order._id,
orderNumber: order.orderNumber,
paymentStatus: order.paymentStatus,
paymentReference: order.paymentReference,
paymentDate: order.paymentDate,
updatedAt: order.updatedAt
}
};
} catch (error) {
console.error('Error updating payment status:', error);
throw error;
}
}
/**
* Add tracking information
* @param {string} orderId - Order ID
* @param {string} trackingNumber - Tracking number
* @param {Date} estimatedDelivery - Estimated delivery date
* @returns {Promise<Object>} Updated order
*/
async addTracking(orderId, trackingNumber, estimatedDelivery = null) {
try {
const order = await models.Order.findById(orderId);
if (!order) {
throw new Error('Order not found');
}
order.addTracking(trackingNumber, estimatedDelivery);
await order.save();
return {
success: true,
order: {
id: order._id,
orderNumber: order.orderNumber,
trackingNumber: order.trackingNumber,
estimatedDelivery: order.estimatedDelivery,
status: order.status,
updatedAt: order.updatedAt
}
};
} catch (error) {
console.error('Error adding tracking:', error);
throw error;
}
}
/**
* Mark order as delivered
* @param {string} orderId - Order ID
* @returns {Promise<Object>} Updated order
*/
async markDelivered(orderId) {
try {
const order = await models.Order.findById(orderId);
if (!order) {
throw new Error('Order not found');
}
order.markDelivered();
await order.save();
return {
success: true,
order: {
id: order._id,
orderNumber: order.orderNumber,
status: order.status,
actualDelivery: order.actualDelivery,
updatedAt: order.updatedAt
}
};
} catch (error) {
console.error('Error marking order as delivered:', error);
throw error;
}
}
/**
* Cancel order
* @param {string} orderId - Order ID
* @param {string} reason - Cancellation reason
* @returns {Promise<Object>} Updated order
*/
async cancelOrder(orderId, reason = null) {
try {
const order = await models.Order.findById(orderId);
if (!order) {
throw new Error('Order not found');
}
if (order.status === 'delivered' || order.status === 'cancelled') {
throw new Error('Cannot cancel order in current status');
}
order.updateStatus('cancelled', reason);
await order.save();
return {
success: true,
order: {
id: order._id,
orderNumber: order.orderNumber,
status: order.status,
notes: order.notes,
updatedAt: order.updatedAt
}
};
} catch (error) {
console.error('Error cancelling order:', error);
throw error;
}
}
/**
* Get order statistics
* @param {string} userId - User ID (optional, for user-specific stats)
* @returns {Promise<Object>} Order statistics
*/
async getOrderStatistics(userId = null) {
try {
const query = userId ? { userId } : {};
const [
totalOrders,
pendingOrders,
completedOrders,
cancelledOrders,
totalRevenue
] = await Promise.all([
models.Order.countDocuments(query),
models.Order.countDocuments({ ...query, status: 'pending' }),
models.Order.countDocuments({ ...query, status: 'delivered' }),
models.Order.countDocuments({ ...query, status: 'cancelled' }),
models.Order.aggregate([
{ $match: { ...query, paymentStatus: 'paid' } },
{ $group: { _id: null, total: { $sum: '$total' } } }
])
]);
return {
success: true,
statistics: {
totalOrders,
pendingOrders,
completedOrders,
cancelledOrders,
totalRevenue: totalRevenue[0]?.total || 0
}
};
} catch (error) {
console.error('Error getting order statistics:', error);
throw error;
}
}
}
export default new OrderService();