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.

335 lines (308 loc) 9.91 kB
import mongoose from 'mongoose'; /** * Simple Cart Model - Organized structure matching our payload * Ready for both authenticated and anonymous users */ const simpleCartSchema = new mongoose.Schema({ // User identification (either userId or sessionId for anonymous) userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: false // Allow anonymous carts }, // Cart items with organized structure items: [{ // Product details (matches our payload structure) productDetails: { productId: { type: String, required: true }, productType: { type: String, required: true, enum: ['equipment_rental', 'minimart', 'makeover_session', 'studio_session'] }, name: { type: String, required: true }, description: { type: String, default: '' }, shortDescription: { type: String, default: '' }, price: { type: Number, required: true, min: 0 }, originalPrice: { type: Number, min: 0 }, quantity: { type: Number, required: true, min: 1, default: 1 }, // Product images images: [{ url: String, alt: String, isPrimary: { type: Boolean, default: false } }], // Product category and classification category: String, subcategory: String, tags: [String], // Product specifications and features specifications: { type: Map, of: mongoose.Schema.Types.Mixed, default: {} }, features: [String], // Inventory and availability stock: { type: Number, default: 0 }, isActive: { type: Boolean, default: true }, // Pricing details pricing: { baseRate: Number, rateType: { type: String, enum: ['hourly', 'daily', 'weekly', 'monthly', 'fixed'], default: 'daily' }, currency: { type: String, default: 'NGN' } }, // Additional metadata sku: String, barcode: String, isFeatured: { type: Boolean, default: false }, // Store the original product data snapshot productSnapshot: { type: Map, of: mongoose.Schema.Types.Mixed, default: {} } }, // Booking details (for equipment rentals) bookingDetails: { startDate: { type: String, // ISO date string required: false }, endDate: { type: String, // ISO date string required: false }, pickupTime: { type: String, // HH:MM format required: false }, returnTime: { type: String, // HH:MM format required: false }, duration: { type: Number, // in days or hours required: false }, specialRequests: [{ type: String }] }, // Session details (for makeover and studio sessions) sessionDetails: { sessionDate: { type: String, // ISO date string required: false }, sessionTime: { type: String, // HH:MM format required: false }, duration: { type: Number, // in minutes required: false }, specialRequests: [{ type: String }] }, // Referee details (for equipment rentals) refereeDetails: { name: { type: String, required: false }, email: { type: String, required: false }, phone: { type: String, required: false } }, // Calculated fields calculatedTotal: { type: Number, default: 0 }, // Metadata addedAt: { type: Date, default: Date.now }, lastUpdated: { type: Date, default: Date.now } }], // Cart totals totals: { subtotal: { type: Number, default: 0 }, discount: { type: Number, default: 0 }, total: { type: Number, default: 0 } }, // Cart status status: { type: String, enum: ['active', 'abandoned', 'converted'], default: 'active' }, // Expiration for abandoned carts expiresAt: { type: Date, default: function () { return new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days } } }, { timestamps: true }); // Indexes for performance simpleCartSchema.index({ userId: 1 }); simpleCartSchema.index({ status: 1 }); simpleCartSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); // Virtual for item count simpleCartSchema.virtual('itemCount').get(function () { return this.items.reduce((total, item) => total + item.productDetails.quantity, 0); }); // Virtual for unique item count simpleCartSchema.virtual('uniqueItemCount').get(function () { return this.items.length; }); // Method to calculate totals simpleCartSchema.methods.calculateTotals = function () { this.totals.subtotal = this.items.reduce((total, item) => { return total + item.calculatedTotal; }, 0); this.totals.total = this.totals.subtotal - this.totals.discount; return { subtotal: this.totals.subtotal, discount: this.totals.discount, total: this.totals.total }; }; // Method to add item to cart simpleCartSchema.methods.addItem = function (itemData) { const { productDetails, bookingDetails, sessionDetails, refereeDetails } = itemData; // Calculate total for this item const calculatedTotal = productDetails.price * productDetails.quantity; // Check if item already exists based on product type let existingItemIndex = -1; if (productDetails.productType === 'equipment_rental' && bookingDetails) { // For equipment rentals, check by productId + bookingDetails existingItemIndex = this.items.findIndex(item => item.productDetails.productId === productDetails.productId && item.bookingDetails?.startDate === bookingDetails.startDate && item.bookingDetails?.endDate === bookingDetails.endDate ); } else if ((productDetails.productType === 'makeover' || productDetails.productType === 'studio') && sessionDetails) { // For makeover and studio sessions, check by productId + sessionDetails existingItemIndex = this.items.findIndex(item => item.productDetails.productId === productDetails.productId && item.sessionDetails?.sessionDate === sessionDetails.sessionDate && item.sessionDetails?.sessionTime === sessionDetails.sessionTime ); } else { // For other product types (minimart), check by productId only existingItemIndex = this.items.findIndex(item => item.productDetails.productId === productDetails.productId ); } if (existingItemIndex !== -1) { // Update existing item this.items[existingItemIndex].productDetails.quantity += productDetails.quantity; this.items[existingItemIndex].calculatedTotal = this.items[existingItemIndex].productDetails.price * this.items[existingItemIndex].productDetails.quantity; this.items[existingItemIndex].lastUpdated = new Date(); } else { // Add new item this.items.push({ productDetails, bookingDetails: bookingDetails || {}, sessionDetails: sessionDetails || {}, refereeDetails: refereeDetails || {}, calculatedTotal, addedAt: new Date(), lastUpdated: new Date() }); } // Recalculate totals this.calculateTotals(); return this; }; // Method to remove item from cart simpleCartSchema.methods.removeItem = function (itemId) { this.items = this.items.filter(item => item._id.toString() !== itemId); this.calculateTotals(); return this; }; // Method to update item quantity simpleCartSchema.methods.updateItemQuantity = function (itemId, quantity) { const item = this.items.id(itemId); if (item) { item.productDetails.quantity = quantity; item.calculatedTotal = item.productDetails.price * quantity; item.lastUpdated = new Date(); this.calculateTotals(); } return this; }; // Method to clear cart simpleCartSchema.methods.clearCart = function () { this.items = []; this.totals.subtotal = 0; this.totals.discount = 0; this.totals.total = 0; return this; }; export default mongoose.model('Cart', simpleCartSchema);