UNPKG

@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
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();