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.

265 lines (251 loc) 8.33 kB
import mongoose from 'mongoose'; const orderSchema = new mongoose.Schema({ orderNumber: { type: String, required: true, unique: true }, uuid: { type: String, required: true, unique: true }, customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, customerInfo: { name: { type: String, required: true }, email: { type: String, required: true }, phone: { type: String, required: true }, address: String, city: String, country: String, verificationStatus: { nin: { type: String, default: 'not_submitted' }, driversLicense: { type: String, default: 'not_submitted' } } }, referrerInfo: { name: String, phone: String, email: String, relationship: { type: String, default: 'self' } }, orderType: { type: String, enum: ['rental', 'purchase', 'service', 'mixed'], required: true }, items: [{ productId: { type: mongoose.Schema.Types.ObjectId, required: true }, productType: { type: String, enum: ['studio_session', 'equipment_rental', 'minimart', 'makeover_session'], required: true }, productInfo: { type: String, required: true }, // JSON string containing product details quantity: { type: Number, required: true }, unitPrice: { type: Number, required: true }, subtotal: { type: Number, required: true }, status: { type: String, default: 'pending' }, serviceDetails: { date: Date, time: String, duration: Number, specialRequests: [String] }, rentalPeriod: { startDate: Date, endDate: Date, duration: Number, pickupTime: String, returnTime: String, referee: { type: Map, of: mongoose.Schema.Types.Mixed } } }], pricing: { subtotal: { type: Number, required: true }, discountTotal: { type: Number, default: 0 }, taxTotal: { type: Number, default: 0 }, total: { type: Number, required: true }, currency: { type: String, default: 'NGN' } }, status: { type: String, enum: ['cart', 'checkout', 'payment_pending', 'payment_failed', 'payment_confirmed', 'processing', 'ready_for_pickup', 'in_progress', 'completed', 'cancelled', 'refunded'], default: 'checkout' }, payment: { method: { type: String, enum: ['paystack', 'bank_transfer', 'cash'], default: 'paystack' }, status: { type: String, enum: ['pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded'], default: 'pending' }, paystack: { reference: String, paidAt: Date, amounts: { paid: Number } }, transactionHistory: [{ type: { type: String, required: true }, amount: Number, reference: String, date: { type: Date, default: Date.now } }] }, workflow: { placedAt: { type: Date, default: Date.now }, confirmedAt: Date, cancelledAt: Date }, cancellation: { reason: String, cancelledBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, cancelledAt: Date }, internalNotes: [{ note: String, addedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, addedAt: { type: Date, default: Date.now } }], // Stock management metadata metadata: { stockDecremented: { type: Boolean, default: false }, stockDecrementedAt: Date, stockRestored: { type: Boolean, default: false }, stockRestoredAt: Date, stockUpdates: [{ productId: mongoose.Schema.Types.ObjectId, productType: String, action: String, quantity: Number, timestamp: { type: Date, default: Date.now } }], stockRestorationUpdates: [{ productId: mongoose.Schema.Types.ObjectId, productType: String, action: String, quantity: Number, timestamp: { type: Date, default: Date.now } }] }, // Legacy fields for backward compatibility (will be removed in future) trackingNumber: String, estimatedDelivery: Date, actualDelivery: Date, notes: String }, { timestamps: true }); // Indexes (orderNumber already has unique index from schema definition) orderSchema.index({ customer: 1 }); orderSchema.index({ status: 1 }); orderSchema.index({ 'payment.status': 1 }); orderSchema.index({ createdAt: -1 }); orderSchema.index({ orderType: 1 }); orderSchema.index({ 'customerInfo.email': 1 }); // Virtual for item count orderSchema.virtual('itemCount').get(function () { return this.items.length; }); // Method to generate order number orderSchema.statics.generateOrderNumber = function () { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substr(2, 5); return `ORD-${timestamp}-${random}`.toUpperCase(); }; // Method to update payment status orderSchema.methods.updatePaymentStatus = function (status, paymentReference = null) { this.payment.status = status; if (paymentReference) { this.payment.paystack.reference = paymentReference; } if (status === 'completed') { this.payment.paystack.paidAt = new Date(); this.status = 'payment_confirmed'; this.workflow.confirmedAt = new Date(); } this.updatedAt = new Date(); return this; }; // Method to update order status orderSchema.methods.updateStatus = function (status, notes = null) { this.status = status; if (notes) { this.internalNotes.push({ note: notes, addedBy: this.customer, addedAt: new Date() }); } this.updatedAt = new Date(); return this; }; // Method to add tracking orderSchema.methods.addTracking = function (trackingNumber, estimatedDelivery = null) { this.trackingNumber = trackingNumber; if (estimatedDelivery) { this.estimatedDelivery = estimatedDelivery; } this.status = 'processing'; this.updatedAt = new Date(); return this; }; // Method to mark as delivered orderSchema.methods.markDelivered = function () { this.status = 'completed'; this.actualDelivery = new Date(); this.updatedAt = new Date(); return this; }; // Method to get display total orderSchema.methods.getDisplayTotal = function () { return `${this.pricing.currency} ${this.pricing.total.toLocaleString()}`; }; // Method to get display subtotal orderSchema.methods.getDisplaySubtotal = function () { return `${this.pricing.currency} ${this.pricing.subtotal.toLocaleString()}`; }; // Method to get display tax orderSchema.methods.getDisplayTax = function () { return `${this.pricing.currency} ${this.pricing.taxTotal.toLocaleString()}`; }; // Method to cancel order orderSchema.methods.cancelOrder = function (reason, cancelledBy) { this.status = 'cancelled'; this.cancellation = { reason: reason || 'Order cancelled by customer', cancelledBy: cancelledBy || this.customer, cancelledAt: new Date() }; this.workflow.cancelledAt = new Date(); this.updatedAt = new Date(); return this; }; // Method to add payment transaction orderSchema.methods.addPaymentTransaction = function (type, amount, reference) { this.payment.transactionHistory.push({ type: type, amount: amount, reference: reference, date: new Date() }); this.updatedAt = new Date(); return this; }; export default mongoose.model('Order', orderSchema);