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