const mongoose = require("mongoose"); const { v4: uuidv4 } = require("uuid"); const { actionHistorySchema } = require("../../models/Client/Client-model"); /* * Generic name/value specification. * * Examples: * Screen Size = 55 inch * Package Size = 500 g * Container Size = 10 inch */ const specificationSchema = new mongoose.Schema( { name: { type: String, required: true, trim: true }, value: { type: mongoose.Schema.Types.Mixed, required: true }, unit: { type: String, trim: true, default: undefined }, filterable: { type: Boolean, default: false }, displayOrder: { type: Number, default: 0 } }, { _id: false } ); /* * Additional product images. * Existing imageUrl remains the primary image. */ const ecommerceImageSchema = new mongoose.Schema( { url: { type: String, required: true, trim: true }, altText: { type: String, trim: true, default: undefined }, displayOrder: { type: Number, default: 0 } }, { _id: false } ); /* * E-commerce SEO fields. */ const seoSchema = new mongoose.Schema( { title: { type: String, trim: true, maxlength: 70, default: undefined }, description: { type: String, trim: true, maxlength: 170, default: undefined }, keywords: { type: [String], default: undefined } }, { _id: false, minimize: true } ); /* * Additional e-commerce configuration. * * Existing fields such as showInEcomm, isDelivery and featureProduct * remain authoritative during the compatibility period. */ const ecommerceMetadataSchema = new mongoose.Schema( { /* * Additional customer search words. * Do not duplicate product name/category/brand unnecessarily. */ searchKeywords: { type: [String], default: undefined }, /* * Examples: NEW, POPULAR, BEST_SELLER, SEASONAL. */ badges: { type: [String], default: undefined }, /* * Dynamic filter values not already represented by: * category, subcategory, brand, colors, price or tags. * * Examples: * { * size: ["M", "L"], * material: ["cotton"], * storage: ["128GB"], * energyRating: ["5-star"] * } */ filterAttributes: { type: Map, of: mongoose.Schema.Types.Mixed, default: undefined }, additionalImages: { type: [ecommerceImageSchema], default: undefined }, seo: { type: seoSchema, default: undefined }, displayOrder: { type: Number, default: undefined }, newArrivalUntil: { type: Date, default: undefined }, allowPickup: { type: Boolean, default: false }, minimumOrderQuantity: { type: Number, min: 1, default: 1 }, maximumOrderQuantity: { type: Number, min: 1, default: undefined }, orderIncrement: { type: Number, min: 0.001, default: 1 }, allowBackorder: { type: Boolean, default: false }, availabilityMessage: { type: String, trim: true, maxlength: 250, default: undefined } }, { _id: false, minimize: true } ); /* * Optional Commerce Intelligence configuration. */ const commerceIntelligenceSchema = new mongoose.Schema( { enabled: { type: Boolean, default: true }, occasionTags: { type: [String], default: undefined }, seasonTags: { type: [String], default: undefined }, useCaseTags: { type: [String], default: undefined }, targetRegions: { type: [String], default: undefined }, recommendationPriority: { type: Number, min: 0, max: 100, default: undefined }, excludeFromRecommendations: { type: Boolean, default: false }, manuallyVerifiedTags: { type: Boolean, default: false }, lastClassificationAt: { type: Date, default: undefined } }, { _id: false, minimize: true } ); /* * Existing packaging options. */ const packagingOptionSchema = new mongoose.Schema( { boxType: { type: String, required: true, trim: true }, quantity: { type: String, default: undefined }, price: { type: Number, min: 0, default: undefined }, offer_price: { type: Number, min: 0, default: undefined } }, { _id: false } ); const productSchema = new mongoose.Schema( { /* * ========================================================= * 1. PRODUCT IDENTITY * ========================================================= */ _id: { type: String, default: uuidv4 }, UId: { type: String, unique: true, sparse: true, index: true, trim: true }, name: { type: String, required: true, trim: true }, /* * Optional e-commerce URL identifier. * Can be deferred if current routes use _id or UId. */ slug: { type: String, lowercase: true, trim: true, sparse: true, default: undefined }, barcode: { type: String, trim: true, sparse: true, default: undefined }, /* * ========================================================= * 2. CATEGORY CONNECTIONS * ========================================================= */ catId: { type: String, ref: "NurseryCategory", required: true, index: true }, /* * Retain for existing pages. */ catName: { type: String, required: true, trim: true }, subCatId: { type: String, ref: "NurserySubCategory", default: null, index: true }, /* * Retain for existing pages. */ subCatName: { type: String, default: null, trim: true }, /* * Snapshot from Category.categoryType. * * Examples: * Nursery, Landscaping, Fashion, Electronics. * * Backend derives this from catId. */ categoryType: { type: String, trim: true, default: undefined, index: true }, /* * Snapshot from Category.type. * * Examples: * plant, Apparel, Mobile_Device, Home_Appliance. * * Backend derives this from catId. */ attributeType: { type: String, trim: true, default: undefined, index: true }, /* * 1 or missing = existing legacy product * 2 = attributes/e-commerce metadata product */ schemaVersion: { type: Number, enum: [1, 2], default: 2, index: true }, /* * ========================================================= * 3. OWNERSHIP AND VENDOR * ========================================================= */ userId: { type: String, ref: "NurseryUser", required: true }, vendor: [ { type: String, ref: "Vendor" } ], /* * ========================================================= * 4. GLOBAL PRESENTATION AND SEARCH FIELDS * ========================================================= */ brand: { type: String, trim: true, default: undefined, index: true }, imageUrl: { type: String, trim: true, default: undefined }, shortDescription: { type: String, trim: true, maxlength: 500, default: undefined }, description: { type: String, trim: true, default: undefined }, /* * Retain existing single specification field. */ specification: { type: String, default: undefined }, /* * New structured specifications. */ specifications: { type: [specificationSchema], default: undefined }, features: { type: [String], default: undefined }, tags: { type: [String], default: undefined }, /* * Existing schema contains both color and colors. * Retain both temporarily for compatibility. */ color: { type: String, trim: true, default: undefined }, colors: { type: [String], default: undefined }, /* * ========================================================= * 5. PRICE AND TAX * ========================================================= */ price: { type: Number, required: true, min: 0 }, offer_price: { type: Number, min: 0, default: undefined, validate: { validator(value) { return ( value === undefined || value === null || this.price === undefined || value <= this.price ); }, message: "Offer price cannot exceed the regular price" } }, purchase_cost: { type: Number, min: 0, default: undefined }, purchase_date: { type: Date, default: undefined }, taxCategoryId: { type: mongoose.Schema.Types.ObjectId, ref: "TaxCategory", default: undefined }, nonTaxable: { type: Boolean, default: false }, /* * ========================================================= * 6. AUTHORITATIVE INVENTORY * ========================================================= */ quantity: { type: Number, required: true, min: 0 }, /* * Subscription inventory reservation. */ reservedQuantity: { type: Number, default: 0, min: 0 }, /* * Regular order inventory reservation. */ reservedOrderQuantity: { type: Number, default: 0, min: 0 }, restock_level: { type: Number, default: 0, min: 0 }, reorder_level: { type: Number, default: 0, min: 0 }, rack_id: { type: String, ref: "Rack", default: undefined }, /* * ========================================================= * 7. SUBSCRIPTION-CONTROLLED INVENTORY * ========================================================= */ isSubscriptionControlled: { type: Boolean, default: false }, weeklySubscriptionCap: { type: Number, min: 0, default: undefined }, perSubscriberWeeklyQty: { type: Number, min: 0, default: undefined }, /* * ========================================================= * 8. PREORDER CONFIGURATION * ========================================================= */ isPreorder: { type: Boolean, default: false }, preOrderQuantity: { type: Number, min: 0, default: 0 }, preorderLimit: { type: Number, min: 0, default: undefined }, preorderPrice: { type: Number, min: 0, default: undefined }, expectedAvailabilityDate: { type: String, default: undefined }, expectedAvailabilityFromDate: { type: String, default: undefined }, expectedAvailabilityToDate: { type: String, default: undefined }, availabilityFromtime: { type: String, default: undefined }, availabilityTotime: { type: String, default: undefined }, availabilityType: { type: String, enum: ["date", "days"], default: undefined }, productAvailableFrom: { type: String, default: undefined }, productAvailableTo: { type: String, default: undefined }, pickedUpLocation: { type: String, default: undefined }, /* * ========================================================= * 9. UNIT AND PACKAGING * ========================================================= */ weightType: { type: String, trim: true, default: undefined }, weight: { type: String, trim: true, default: undefined }, unitType: { type: String, trim: true, default: undefined }, packagingOptions: { type: [packagingOptionSchema], default: undefined }, /* * ========================================================= * 10. POS AND E-COMMERCE FLAGS * ========================================================= */ showInEcomm: { type: Boolean, default: false, index: true }, isDelivery: { type: Boolean, default: false }, featureProduct: { type: Boolean, default: false, index: true }, isFav: { type: Boolean, default: false }, posOrder: { type: Number, default: 0 }, ecommerceMetadata: { type: ecommerceMetadataSchema, default: undefined }, /* * ========================================================= * 11. CATEGORY-SPECIFIC ATTRIBUTES * ========================================================= * * Save only fields allowed for Category.type. * * Plant example: * { * botanicalName: "Aloe vera", * sunlightRequirement: "Full Sun", * wateringNeed: "Low" * } * * Apparel example: * { * gender: "Men", * material: "Cotton", * sizes: ["M", "L"] * } */ attributes: { type: Map, of: mongoose.Schema.Types.Mixed, default: undefined }, /* * ========================================================= * 12. COMMERCE INTELLIGENCE * ========================================================= */ commerceIntelligence: { type: commerceIntelligenceSchema, default: undefined }, /* * ========================================================= * 13. STATUS AND AUDIT * ========================================================= */ active: { type: Boolean, default: true, index: true }, isDeleted: { type: Boolean, default: false, index: true }, deletedBy: { type: String, ref: "NurseryUser", default: undefined }, deletedAt: { type: Date, default: undefined }, updatedUser: { type: String, ref: "NurseryUser", default: undefined }, actionHistory: { type: [actionHistorySchema], default: undefined }, /* * Optional migration information. */ migrationMetadata: { migratedAt: { type: Date, default: undefined }, migratedBy: { type: String, default: undefined }, sourceVersion: { type: Number, default: undefined }, warnings: { type: [String], default: undefined } } }, { timestamps: true, /* * Prevent empty nested objects from being stored. */ minimize: true, /* * Retain virtuals if added later. */ toJSON: { virtuals: true }, toObject: { virtuals: true } } ); /**********************************************************************////////////////////////////////////////////////// /* * PLANT/TREE LEGACY FIELDS */ productSchema.add({ medical_name: { type: String, default: undefined }, plantType: { type: String, default: undefined }, varietyCultivar: { type: String, default: undefined }, commonLocalName: { type: String, default: undefined }, treeAge: { type: Number, default: undefined }, height: { type: String, default: undefined }, lifeSpan: { type: String, default: undefined }, soilType: { type: String, default: undefined }, wateringNeeds: { type: String, default: undefined }, sunlightRequirements: { type: String, default: undefined }, droughtTolerance: { type: String, default: undefined }, growingSeason: { type: String, default: undefined }, maturePlantHeight: { type: String, default: undefined }, RecommendedFertilizer: { type: [String], default: undefined } }); ////////////////////*****************************************************////////////////////////////////////////// /* * FOOD/GROCERY LEGACY FIELDS */ productSchema.add({ foodType: { type: String, default: undefined }, organic: { type: Boolean, default: undefined }, nonGMO: { type: Boolean, default: undefined }, glutenFree: { type: Boolean, default: undefined }, vegan: { type: Boolean, default: undefined }, kosher: { type: Boolean, default: undefined }, halal: { type: Boolean, default: undefined }, packageSize: { type: String, default: undefined }, harvestDate: { type: Date, default: undefined }, expirationDate: { type: Date, default: undefined }, manufacturedDate: { type: Date, default: undefined }, storageConditions: { type: String, default: undefined }, storageInstructions: { type: String, default: undefined }, countryOfOrigin: { type: String, default: undefined }, tasteProfile: { type: String, default: undefined }, ripeness: { type: String, default: undefined }, nutritionalInfo: { type: mongoose.Schema.Types.Mixed, default: undefined } });