@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.
159 lines (142 loc) • 4.44 kB
JavaScript
import mongoose from 'mongoose';
const minimartItemSchema = new mongoose.Schema({
// Basic Information
name: {
type: String,
required: [true, 'Item name is required'],
trim: true,
maxlength: [100, 'Item name must be less than 100 characters']
},
description: {
type: String,
required: [true, 'Description is required'],
maxlength: [1000, 'Description must be less than 1000 characters']
},
shortDescription: {
type: String,
maxlength: [200, 'Short description must be less than 200 characters']
},
type: {
type: String,
default: 'mini_mart_sale'
},
// Category & Classification
category: {
type: String,
required: [true, 'Category is required']
},
subcategory: {
type: String,
maxlength: [100, 'Subcategory must be less than 100 characters']
},
// Pricing Information
pricing: {
salePrice: {
type: Number,
required: [true, 'Sale price is required'],
min: [0, 'Sale price must be positive']
},
costPrice: {
type: Number,
min: [0, 'Cost price must be positive']
},
compareAtPrice: {
type: Number,
min: [0, 'Compare at price must be positive']
},
currency: {
type: String,
default: 'NGN'
}
},
// Simple Inventory (no complex tracking)
inventory: {
stockQuantity: {
type: Number,
required: [true, 'Stock quantity is required'],
min: [0, 'Stock quantity cannot be negative'],
default: 0
}
},
// Product Identification
sku: {
type: String,
unique: true,
default: () => 'MINI-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5).toUpperCase()
},
barcode: {
type: String,
unique: true,
sparse: true // Allow multiple null values
},
// Media & Content
images: [String], // Array of Cloudinary image URLs
tags: [String],
// Status & Features
isActive: {
type: Boolean,
default: true
},
isFeatured: {
type: Boolean,
default: false
},
// Admin & Audit
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Admin'
},
lastModifiedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Admin'
},
// Additional Notes
notes: String
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// Virtuals
minimartItemSchema.virtual('profitMargin').get(function () {
if (this.pricing.costPrice && this.pricing.salePrice) {
return ((this.pricing.salePrice - this.pricing.costPrice) / this.pricing.salePrice) * 100;
}
return 0;
});
minimartItemSchema.virtual('isOutOfStock').get(function () {
return this.inventory.stockQuantity <= 0;
});
// Indexes for efficient queries
minimartItemSchema.index({ name: 'text', description: 'text', tags: 'text' });
minimartItemSchema.index({ category: 1 });
minimartItemSchema.index({ isActive: 1 });
minimartItemSchema.index({ isFeatured: 1 });
minimartItemSchema.index({ 'pricing.salePrice': 1 });
// Note: sku and barcode already have unique indexes from schema definition
minimartItemSchema.index({ createdAt: -1 });
// Pre-save middleware
minimartItemSchema.pre('save', function (next) {
// Auto-generate SKU if not provided
if (!this.sku) {
this.sku = 'MINI-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5).toUpperCase();
}
next();
});
// Simple methods
minimartItemSchema.methods.adjustStock = function (quantity, operation = 'subtract') {
if (operation === 'add') {
this.inventory.stockQuantity += quantity;
} else {
this.inventory.stockQuantity = Math.max(0, this.inventory.stockQuantity - quantity);
}
return this.save();
};
// Static methods
minimartItemSchema.statics.getOutOfStockItems = function () {
return this.find({
'inventory.stockQuantity': { $lte: 0 },
isActive: true
});
};
export default mongoose.model('MinimartItem', minimartItemSchema);