UNPKG

@fabrix/spool-cart

Version:

Spool - eCommerce Spool for Fabrix

1,154 lines (1,153 loc) 39.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@fabrix/fabrix/dist/common"); const spool_sequelize_1 = require("@fabrix/spool-sequelize"); const errors_1 = require("@fabrix/spool-sequelize/dist/errors"); const lodash_1 = require("lodash"); const queryDefaults_1 = require("../utils/queryDefaults"); const enums_1 = require("../../enums"); const enums_2 = require("../../enums"); const enums_3 = require("../../enums"); class ProductResolver extends spool_sequelize_1.SequelizeResolver { findByIdDefault(criteria, options = {}) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Product.default(this.app), options); let resProduct; return this.findById(criteria, options) .then(product => { resProduct = product; if (resProduct && options.req && options.req.customer) { return resProduct.getCustomerHistory(options.req.customer, { transaction: options.transaction || null }); } else { return; } }) .then(() => { if (resProduct) { return resProduct.calculate({ req: options.req || null, transaction: options.transaction || null }); } else { return resProduct; } }); } findByHandleDefault(handle, options = {}) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Product.default(this.app), options, { where: { handle: handle } }); let resProduct; return this.findOne(options) .then(product => { resProduct = product; if (resProduct && options.req && options.req.customer) { return resProduct.getCustomerHistory(options.req.customer, { transaction: options.transaction || null }); } else { return; } }) .then(() => { if (resProduct) { return resProduct.calculate({ req: options.req || null, transaction: options.transaction || null }); } else { return resProduct; } }); } findOneDefault(options = {}) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Product.default(this.app), options); let resProduct; return this.findOne(options) .then(product => { if (!product) { } resProduct = product; if (resProduct && options.req && options.req.customer) { return resProduct.getCustomerHistory(options.req.customer, { transaction: options.transaction || null }); } else { return; } }) .then(() => { if (resProduct) { return resProduct.calculate({ req: options.req || null, transaction: options.transaction || null }); } else { return resProduct; } }); } findAllDefault(options = {}) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Product.findAllDefault(this.app), options); return this.findAll(options); } findAndCountDefault(options) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Product.findAllDefault(this.app), options); return this.findAndCountAll(options); } resolveByInstance(product, options = {}) { return Promise.resolve(product); } resolveById(product, options = {}) { return this.findById(product.id, options) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `product ${product.id} not found`); } return resUser; }); } resolveByToken(product, options = {}) { return this.findOne(this.app.services.SequelizeService.mergeOptionDefaults(options, { where: { token: product.token } })) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `product token ${product.token} not found`); } return resUser; }); } resolveByhandle(product, options = {}) { return this.findOne(this.app.services.SequelizeService.mergeOptionDefaults(options, { where: { handle: product.handle } })) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `product handle ${product.handle} not found`); } return resUser; }); } resolveByNumber(product, options = {}) { return this.findById(product, options) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `product ${product.token} not found`); } return resUser; }); } resolveByString(product, options = {}) { return this.findOne(this.app.services.SequelizeService.mergeOptionDefaults(options, { where: { handle: product } })) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `product ${product} not found`); } return resUser; }); } resolve(product, options = {}) { const resolvers = { 'instance': product instanceof this.instance, 'id': !!(product && lodash_1.isObject(product) && product.id), 'handle': !!(product && lodash_1.isObject(product) && product.token), 'number': !!(product && lodash_1.isNumber(product)), 'string': !!(product && lodash_1.isString(product)) }; const type = Object.keys(resolvers).find((key) => resolvers[key]); switch (type) { case 'instance': { return this.resolveByInstance(product, options); } case 'id': { return this.resolveById(product, options); } case 'handle': { return this.resolveByToken(product, options); } case 'number': { return this.resolveByNumber(product, options); } case 'string': { return this.resolveByString(product, options); } default: { const err = new Error(`Unable to resolve product ${product}`); return Promise.reject(err); } } } } exports.ProductResolver = ProductResolver; class Product extends common_1.FabrixModel { static get resolver() { return ProductResolver; } static config(app, Sequelize) { return { options: { enums: { UNITS: enums_1.UNITS, PRODUCT_DEFAULTS: enums_2.PRODUCT_DEFAULTS, }, underscored: true, scopes: { live: { where: { live_mode: true } }, published: { where: { published: true } } }, hooks: { beforeValidate: [ (product, options) => { if (!product.handle && product.title) { product.handle = product.title; } if (!product.calculated_price && product.price) { product.calculated_price = product.price; } if (!product.compare_at_price && product.price) { product.compare_at_price = product.price; } } ], beforeCreate: [ (product, options) => { return app.services.ProductService.beforeCreate(product, options) .catch(err => { return Promise.reject(err); }); } ], beforeUpdate: [ (product, options) => { return app.services.ProductService.beforeUpdate(product, options) .catch(err => { return Promise.reject(err); }); } ] } } }; } static schema(app, Sequelize) { return { host: { type: Sequelize.STRING, defaultValue: enums_2.PRODUCT_DEFAULTS.HOST }, handle: { type: Sequelize.STRING, allowNull: false, unique: true, set: function (val) { this.setDataValue('handle', app.services.ProxyCartService.handle(val)); } }, title: { type: Sequelize.STRING, set: function (val) { this.setDataValue('title', app.services.ProxyCartService.title(val)); } }, body: { type: Sequelize.TEXT }, html: { type: Sequelize.TEXT }, seo_title: { type: Sequelize.STRING, set: function (val) { this.setDataValue('seo_title', app.services.ProxyCartService.title(val)); } }, seo_description: { type: Sequelize.TEXT, set: function (val) { this.setDataValue('seo_description', app.services.ProxyCartService.description(val)); } }, type: { type: Sequelize.STRING, allowNull: false, set: function (val) { this.setDataValue('type', app.services.ProxyCartService.title(val)); } }, tax_code: { type: Sequelize.STRING, defaultValue: enums_2.PRODUCT_DEFAULTS.TAX_CODE }, compare_at_price: { type: Sequelize.INTEGER, defaultValue: enums_2.PRODUCT_DEFAULTS.PRICE }, price: { type: Sequelize.INTEGER, defaultValue: enums_2.PRODUCT_DEFAULTS.PRICE }, calculated_price: { type: Sequelize.VIRTUAL(Sequelize.INTEGER), defaultValue: enums_2.PRODUCT_DEFAULTS.CALCULATED_PRICE }, currency: { type: Sequelize.STRING, defaultValue: enums_2.PRODUCT_DEFAULTS.CURRENCY }, total_orders: { type: Sequelize.INTEGER, defaultValue: 0 }, discounted_lines: { type: Sequelize.JSONB, defaultValue: enums_2.PRODUCT_DEFAULTS.DISCOUNTED_LINES }, total_discounts: { type: Sequelize.INTEGER, defaultValue: enums_2.PRODUCT_DEFAULTS.TOTAL_DISCOUNTS }, published_scope: { type: Sequelize.STRING, defaultValue: enums_2.PRODUCT_DEFAULTS.PUBLISHED_SCOPE }, published: { type: Sequelize.BOOLEAN, defaultValue: enums_2.PRODUCT_DEFAULTS.PUBLISHED }, published_at: { type: Sequelize.DATE }, unpublished_at: { type: Sequelize.DATE }, available: { type: Sequelize.BOOLEAN, defaultValue: enums_2.PRODUCT_DEFAULTS.AVAILABLE }, options: { type: Sequelize.JSONB, defaultValue: enums_2.PRODUCT_DEFAULTS.OPTIONS }, property_pricing: { type: Sequelize.JSONB, defaultValue: {} }, weight: { type: Sequelize.INTEGER, defaultValue: enums_2.PRODUCT_DEFAULTS.WEIGHT }, weight_unit: { type: Sequelize.ENUM, values: lodash_1.values(enums_1.UNITS), defaultValue: enums_2.PRODUCT_DEFAULTS.WEIGHT_UNIT }, review_score: { type: Sequelize.INTEGER, defaultValue: enums_2.PRODUCT_DEFAULTS.REVIEWS_SCORE }, total_reviews: { type: Sequelize.INTEGER, defaultValue: enums_2.PRODUCT_DEFAULTS.TOTAL_REVIEWS }, total_variants: { type: Sequelize.INTEGER, defaultValue: enums_2.PRODUCT_DEFAULTS.TOTAL_VARIANTS }, average_shipping: { type: Sequelize.INTEGER, defaultValue: 0 }, exclude_payment_types: { type: Sequelize.JSONB, defaultValue: [] }, google: { type: Sequelize.JSONB, defaultValue: {} }, amazon: { type: Sequelize.JSONB, defaultValue: {} }, live_mode: { type: Sequelize.BOOLEAN, defaultValue: app.config.get('cart.live_mode') } }; } static associate(models) { models.Product.hasMany(models.ProductImage, { as: 'images', foreignKey: 'product_id', through: null, onDelete: 'CASCADE' }); models.Product.hasMany(models.ProductVariant, { as: 'variants', foreignKey: 'product_id', onDelete: 'CASCADE' }); models.Product.hasMany(models.ProductReview, { as: 'reviews', foreignKey: 'product_id', onDelete: 'CASCADE' }); models.Product.belongsToMany(models.Collection, { as: 'collections', through: { model: models.ItemCollection, unique: false, scope: { model: 'product' } }, foreignKey: 'model_id', constraints: false }); models.Product.hasOne(models.Metadata, { as: 'metadata', foreignKey: 'product_id' }); models.Product.belongsToMany(models.Vendor, { as: 'vendors', through: { model: models.VendorProduct, unique: false, }, foreignKey: 'product_id', }); models.Product.belongsToMany(models.Shop, { as: 'shops', through: { model: models.ShopProduct, unique: false, }, foreignKey: 'product_id', }); models.Product.belongsToMany(models.Tag, { as: 'tags', through: { model: models.ItemTag, unique: false, scope: { model: 'product' } }, foreignKey: 'model_id', otherKey: 'tag_id', constraints: false }); models.Product.belongsToMany(models.Product, { as: 'associations', through: { model: models.ProductAssociation, unique: false }, foreignKey: 'product_id', otherKey: 'associated_product_id', }); models.Product.belongsToMany(models.Product, { as: 'relations', through: { model: models.ProductAssociation, unique: false }, foreignKey: 'associated_product_id', otherKey: 'product_id', }); models.Product.belongsToMany(models.Discount, { as: 'discounts', through: { model: models.ItemDiscount, unique: false, scope: { model: 'product' } }, foreignKey: 'model_id', constraints: false }); models.Product.belongsToMany(models.Event, { as: 'event_items', through: { model: models.EventItem, unique: false, scope: { object: 'product' } }, foreignKey: 'object_id', constraints: false }); } } exports.Product = Product; Product.prototype.setItemDiscountedLines = function (discounts = [], criteria = [], options = {}) { this.discounted_lines = []; const factoredDiscountedLines = []; let discountsArr = []; if (options.req && options.req.cart) { options.req.cart.line_items = options.req.cart.line_items || []; options.req.cart.line_items.map(item => { discounts.forEach(discount => { item = discount.discountItem(item, criteria); }); return item; }); options.req.cart.line_items.forEach(item => { item.discounted_lines = item.discounted_lines || []; discountsArr = [...discountsArr, item.discounted_lines.map(line => line.id)]; }); } discounts.forEach(discount => { discount.discountItem(this, criteria); }); this.discounted_lines.forEach(line => { discountsArr = [...discountsArr, line.id]; }); this.discounted_lines = this.discounted_lines.map(discount => { discount.rules = discount.rules || {}; if (discount.rules.applies_once && discountsArr.filter(d => d === discount.id).length > 1) { const arrRemove = discountsArr.findIndex(d => d === discount.id); discountsArr = discountsArr.splice(arrRemove, 1); discount.applies = false; } else if (discount.rules.minimum_order_amount > 0 && options.req && options.req.cart && (options.req.cart.total_price + this.price) < discount.minimum_order_amount) { discount.applies = false; } else if (discount.rules.applies_compound === false && discountsArr.length > 1) { discount.applies = false; } else { discount.applies = true; } return discount; }); this.discounted_lines.forEach(discountedLine => { if (discountedLine.applies === true) { const calculatedPrice = Math.max(0, this.calculated_price - discountedLine.price); const totalDeducted = Math.min(this.calculated_price, (this.calculated_price - (this.calculated_price - discountedLine.price))); this.calculated_price = calculatedPrice; this.total_discounts = Math.min(this.price, this.total_discounts + totalDeducted); const fI = factoredDiscountedLines.findIndex(d => d.id === discountedLine.id); if (fI > -1) { factoredDiscountedLines[fI].price = factoredDiscountedLines[fI].price + totalDeducted; } else { discountedLine.price = totalDeducted; factoredDiscountedLines.push(discountedLine); } } }); return this.setDiscountedLines(factoredDiscountedLines); }; Product.prototype.setDiscountedLines = function (lines) { this.total_discounts = 0; this.discounted_lines = lines || []; this.discounted_lines.forEach(line => { this.total_discounts = this.total_discounts + line.price; }); return this.setTotals(); }; Product.prototype.setCalculatedPrice = function (calculatedPrice) { this.calculated_price = calculatedPrice; return this; }; Product.prototype.setTotals = function () { this.total_price = Math.max(0, this.total_tax + this.total_shipping + this.subtotal_price); this.total_due = Math.max(0, this.total_price - this.total_discounts - this.total_coupons - this.total_overrides); return this; }; Product.prototype.getCustomerHistory = function (customer, options = {}) { let hasPurchaseHistory = false, isSubscribed = false; return this.hasPurchaseHistory(customer.id, options) .then(pHistory => { hasPurchaseHistory = pHistory; return this.isSubscribed(customer.id, options); }) .then(pHistory => { isSubscribed = pHistory; this.setDataValue('has_purchase_history', hasPurchaseHistory); this.setDataValue('is_subscribed', isSubscribed); return this; }) .catch(err => { this.setDataValue('has_purchase_history', hasPurchaseHistory); this.setDataValue('is_subscribed', isSubscribed); return this; }); }; Product.prototype.hasPurchaseHistory = function (customerId, options = {}) { return this.app.models['OrderItem'].findOne({ where: { customer_id: customerId, product_id: this.id, fulfillment_status: { $not: ['cancelled', 'pending', 'none'] } }, attributes: ['id'], transaction: options.transaction || null }) .then(pHistory => { if (pHistory) { return true; } else { return false; } }) .catch(err => { return false; }); }; Product.prototype.isSubscribed = function (customerId, options = {}) { return this.app.models['Subscription'].findOne({ where: { customer_id: customerId, active: true, line_items: { $contains: [{ product_id: this.id }] } }, attributes: ['id'], transaction: options.transaction || null }) .then(pHistory => { if (pHistory) { return true; } else { return false; } }) .catch(err => { return false; }); }; Product.prototype.getCollectionPairs = function (options = {}) { const collectionPairs = []; const criteria = []; return Promise.resolve() .then(() => { if (options.req && options.req.customer && options.req.customer.id) { criteria.push({ model: 'customer', model_id: options.req.customer.id }); } if (this.id) { criteria.push({ model: 'product', model_id: this.id }); } if (criteria.length > 0) { return this.app.models['ItemCollection'].findAll({ where: { $or: criteria }, attributes: ['id', 'collection_id', 'model', 'model_id'], transaction: options.transaction || null }); } return []; }) .then(_collections => { _collections.forEach(collection => { const i = collectionPairs.findIndex(c => c.id === collection.collection_id); if (i > -1) { if (!collectionPairs[i][collection.model]) { collectionPairs[i][collection.model] = []; } collectionPairs[i][collection.model].push(collection.model_id); } else { collectionPairs.push({ collection: collection.collection_id, [collection.model]: [collection.model_id] }); } }); return collectionPairs; }) .catch(err => { this.app.log.error(err); return []; }); }; Product.prototype.calculateDiscounts = function (options = {}) { const criteria = []; let collectionPairs = [], discountCriteria = [], checkHistory = []; let resDiscounts; return Promise.resolve() .then(() => { return this.getCollectionPairs({ req: options.req || null, transaction: options.transaction || null }); }) .then(_collections => { collectionPairs = _collections; if (options.req && options.req.cart && options.req.cart.id) { criteria.push({ model: 'cart', model_id: options.req.cart.id }); } if (options.req && options.req.customer && options.req.customer.id) { criteria.push({ model: 'customer', model_id: options.req.customer.id }); } if (this.id) { criteria.push({ model: 'product', model_id: this.id }); } if (collectionPairs.length > 0) { criteria.push({ model: 'collection', model_id: collectionPairs.map(c => c.collection) }); } if (criteria.length > 0) { return this.app.models['ItemDiscount'].findAll({ where: { $or: criteria }, attributes: ['discount_id', 'model', 'model_id'], transaction: options.transaction || null }); } else { return []; } }) .then(discounts => { discounts.forEach(discount => { const i = discountCriteria.findIndex(d => d.discount === discount.discount_id); if (i > -1) { if (!discountCriteria[i][discount.model]) { discountCriteria[i][discount.model] = []; } discountCriteria[i][discount.model].push(discount.model_id); } else { discountCriteria.push({ discount: discount.discount_id, [discount.model]: [discount.model_id] }); } }); discountCriteria = discountCriteria.map(d => { if (d.collection) { d.collection.forEach(colId => { const i = collectionPairs.findIndex(c => c.collection = colId); if (i > -1) { d = lodash_1.merge(d, collectionPairs[i]); } }); } return d; }); if (discounts.length > 0) { return this.app.models['Discount'].findAll({ where: { id: discounts.map(item => item.discount_id), status: enums_3.DISCOUNT_STATUS.ENABLED }, transaction: options.transaction || null }); } else { return []; } }) .then(_discounts => { _discounts = _discounts || []; resDiscounts = _discounts; resDiscounts.forEach(discount => { if (discount.applies_once_per_customer && options.req && options.req.customer && options.req.customer.id) { checkHistory.push(discount); } }); if (checkHistory.length > 0) { return Promise.all(checkHistory.map(discount => { return discount.eligibleCustomer(options.req.customer.id, { transaction: options.transaction || null }); })); } else { return []; } }) .then(_eligible => { _eligible = _eligible || []; _eligible.forEach(discount => { const i = resDiscounts.findIndex(ii => ii.id === discount.id); if (i > -1) { resDiscounts.splice(i, 1); } }); return this.setItemDiscountedLines(resDiscounts, discountCriteria, options); }) .catch(err => { this.app.log.error(err); return this; }); }; Product.prototype.calculate = function (options = {}) { if (!this) { return; } this.calculated_price = this.price; return this.calculateDiscounts(options) .then(() => { return this; }); }; Product.prototype.mergeIntoCollections = function (colsB = []) { const collections = this.collections; colsB.forEach(collection => { if (!this.collections.some(colA => colA.id === collection.id)) { collections.push(collection); } }); this.collections = collections; this.setDataValue('collections', collections); this.set('collections', collections); return this; }; Product.prototype.toJSON = function () { const position = this.position; const resp = this instanceof this.app.models['Product'].instance ? this.get({ plain: true }) : this; resp.calculated_price = resp.price; if (resp.tags) { resp.tags = resp.tags.map(tag => { if (tag && lodash_1.isString(tag)) { return tag; } else if (tag && tag.name && tag.name !== '') { return tag.name; } }); } if (resp.variants) { resp.variants.map((variant, idx) => { if (variant.tags) { resp.variants[idx].tags = variant.tags.map(tag => { if (tag && lodash_1.isString(tag)) { return tag; } else if (tag && tag.name) { return tag.name; } }); } if (variant.metadata) { if (typeof variant.metadata.data !== 'undefined') { resp.variants[idx].metadata = variant.metadata.data; } } resp.variants[idx].calculated_price = variant.price; }); } if (resp.metadata) { if (typeof resp.metadata.data !== 'undefined') { resp.metadata = resp.metadata.data; } } if (resp.vendors) { resp.vendors = resp.vendors.map(vendor => { if (vendor && lodash_1.isString(vendor)) { return vendor; } else { return vendor.name; } }); } if (position) { resp.position = position; } return resp; }; Product.prototype.resolveVariants = function (options = {}) { if (this.variants && this.variants.every(v => v instanceof this.app.models['ProductVariant'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getVariants({ limit: 500, transaction: options.transaction || null, order: [['position', 'ASC']] }) .then(variants => { variants = variants || []; this.variants = variants; this.setDataValue('variants', variants); this.set('variants', variants); return this; }); } }; Product.prototype.getDefaultVariant = function (options = {}) { if (this.variants && this.variants.every(v => v instanceof this.app.models['ProductVariant'].instance) && this.variants.some(v => v.position === 1) && options.reload !== true) { return Promise.resolve(this.variants.find(v => v.position === 1)); } else { return this.getVariants({ where: { position: 1 }, limit: 1, transaction: options.transaction || null }) .then(variants => { const variant = variants[0] || null; return variant; }); } }; Product.prototype.resolveAssociations = function (options = {}) { if (this.associations && this.associations.every(p => p instanceof this.app.models['Product'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getAssociations({ limit: 100, transaction: options.transaction || null }) .then(associations => { associations = associations || []; this.associations = associations; this.setDataValue('associations', associations); this.set('associations', associations); return this; }); } }; Product.prototype.resolveImages = function (options = {}) { if (this.images && this.images.every(i => i instanceof this.app.models['ProductImage'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getImages({ limit: 50, transaction: options.transaction || null, order: [['position', 'ASC']] }) .then(images => { images = images || []; this.images = images; this.setDataValue('images', images); this.set('images', images); return this; }); } }; Product.prototype.resolveVendors = function (options = {}) { if (this.vendors && this.vendors.every(v => v instanceof this.app.models['Vendor'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getVendors({ limit: 50, transaction: options.transaction || null }) .then(vendors => { vendors = vendors || []; this.vendors = vendors; this.setDataValue('vendors', vendors); this.set('vendors', vendors); return this; }); } }; Product.prototype.resolveVendors = function (options = {}) { if (this.vendors && this.vendors.every(v => v instanceof this.app.models['Vendor'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getVendors({ limit: 50, transaction: options.transaction || null }) .then(vendors => { vendors = vendors || []; this.vendors = vendors; this.setDataValue('vendors', vendors); this.set('vendors', vendors); return this; }); } }; Product.prototype.resolveMetadata = function (options = {}) { if (this.metadata && this.metadata instanceof this.app.models['Metadata'].instance && options.reload !== true) { return Promise.resolve(this); } else { return this.getMetadata({ transaction: options.transaction || null }) .then(_metadata => { _metadata = _metadata || { product_id: this.id }; this.metadata = _metadata; this.setDataValue('metadata', _metadata); this.set('metadata', _metadata); return this; }); } }; Product.prototype.resolveShops = function (options = {}) { if (this.shops && this.shops.length > 0 && this.shops.every(d => d instanceof this.app.models['Shop'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getShops({ limit: 50, transaction: options.transaction || null }) .then(shops => { shops = shops || []; this.shops = shops; this.setDataValue('shops', shops); this.set('shops', shops); return this; }); } }; Product.prototype.resolveTags = function (options = {}) { if (this.tags && this.tags.length > 0 && this.tags.every(t => t instanceof this.app.models['Tag'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getTags({ limit: 50, transaction: options.transaction || null }) .then(tags => { tags = tags || []; this.tags = tags; this.setDataValue('tags', tags); this.set('tags', tags); return this; }); } }; Product.prototype.resolveCollections = function (options = {}) { if (this.collections && this.collections.length > 0 && this.collections.every(c => c instanceof this.app.models['Collection'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getCollections({ limit: 50, transaction: options.transaction || null }) .then(collections => { collections = collections || []; this.collections = collections; this.setDataValue('collections', collections); this.set('collections', collections); return this; }); } }; Product.prototype.resolveCoupons = function (options = {}) { if (this.coupons && this.coupons.length > 0 && this.coupons.every(c => c instanceof this.app.models['Coupon'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getCoupons({ transaction: options.transaction || null }) .then(coupons => { coupons = coupons || []; this.coupons = coupons; this.setDataValue('coupons', coupons); this.set('coupons', coupons); return this; }); } }; Product.prototype.resolveDiscounts = function (options = {}) { if (this.discounts && this.discounts.length > 0 && this.discounts.every(d => d instanceof this.app.models['Discount'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getDiscounts({ limit: 10, transaction: options.transaction || null }) .then(_discounts => { _discounts = _discounts || []; this.discounts = _discounts; this.setDataValue('discounts', _discounts); this.set('discounts', _discounts); return this; }); } };