Project

General

Profile

Support #10287 ยป product_schema_changes.txt

Yalavarthi Thriveni, 08/11/2026 05:22 AM

 
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
}
});
    (1-1/1)