UNPKG

@fabrix/spool-cart

Version:

Spool - eCommerce Spool for Fabrix

1,217 lines 72.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@fabrix/fabrix/dist/common"); const errors_1 = require("@fabrix/spool-sequelize/dist/errors"); const fs = require("fs"); const _ = require("lodash"); const enums_1 = require("../../enums"); class ProductService extends common_1.FabrixService { publish(type, event, options = {}) { if (this.app.services.EventsService) { options.include = options.include || [{ model: this.app.models.EventItem.instance, as: 'objects' }]; return this.app.services.EventsService.publish(type, event, options); } this.app.log.debug('spool-events is not installed, please install it to use publish'); return Promise.resolve(); } resolveItem(item, options = {}) { const Product = this.app.models.Product; const ProductVariant = this.app.models.ProductVariant; const Image = this.app.models.ProductImage; if (item.id || item.variant_id || item.product_variant_id) { const id = item.id || item.variant_id || item.product_variant_id; return ProductVariant.findById(id, { transaction: options.transaction || null, include: [ { model: Product.instance, include: [ { model: Image.instance, as: 'images', attributes: ['src', 'full', 'thumbnail', 'small', 'medium', 'large', 'alt', 'position'] } ] }, { model: Image.instance, as: 'images', attributes: ['src', 'full', 'thumbnail', 'small', 'medium', 'large', 'alt', 'position'] } ] }); } else if (item.product_id) { return ProductVariant.findOne({ where: { product_id: item.product_id, position: 1 }, transaction: options.transaction || null, include: [ { model: Product.instance, include: [ { model: Image.instance, as: 'images', attributes: ['src', 'full', 'thumbnail', 'small', 'medium', 'large', 'alt', 'position'] } ] }, { model: Image.instance, as: 'images', attributes: ['src', 'full', 'thumbnail', 'small', 'medium', 'large', 'alt', 'position'] } ] }); } else { const err = new errors_1.ModelError('E_NOT_FOUND', `${item} not found`); return Promise.reject(err); } } resolveItems(items, options = {}) { if (!Array.isArray(items)) { items = [items]; } const Sequelize = this.app.models['Product'].sequelize; return Sequelize.transaction(t => { return Sequelize.Promise.mapSeries(items, item => { return this.resolveItem(item, { transaction: t }); }); }); } addProducts(products, options = {}) { if (!Array.isArray(products)) { products = [products]; } const Sequelize = this.app.models['Product'].sequelize; return Sequelize.transaction(t => { return Sequelize.Promise.mapSeries(products, product => { return this.addProduct(product, { transaction: t }); }); }); } addProduct(product, options = {}) { const Product = this.app.models.Product; return Product.findOne({ where: { host: product.host ? product.host : 'localhost', handle: product.handle }, attributes: ['id'], transaction: options.transaction || null }) .then(resProduct => { if (resProduct instanceof Product.instance) { product.id = resProduct.id; return this.updateProduct(product, options); } else { return this.createProduct(product, options); } }); } createProduct(product, options = {}) { const Product = this.app.models.Product; const Tag = this.app.models.Tag; const Variant = this.app.models.ProductVariant; const Metadata = this.app.models.Metadata; const Collection = this.app.models.Collection; const Vendor = this.app.models.Vendor; const Shop = this.app.models.Shop; if (!product) { const err = new Error('A product is required'); return Promise.reject(err); } product = this.productDefaults(product); const create = { host: product.host, handle: product.handle, title: product.title, seo_title: product.seo_title, seo_description: product.seo_description, body: product.body, type: product.type, price: product.price, compare_at_price: product.compare_at_price, calculated_price: product.calculated_price, tax_code: product.tax_code, published: product.published, available: product.available, published_scope: product.published_scope, weight: product.weight, weight_unit: product.weight_unit, average_shipping: product.average_shipping, property_pricing: product.property_pricing, exclude_payment_types: product.exclude_payment_types, metadata: Metadata.transform(product.metadata || {}), google: product.google, amazon: product.amazon, options: product.options }; if (product.published === true) { create.published_at = new Date(); } if (product.published === false) { create.unpublished_at = new Date(); } if (product.published_scope) { create.published_scope = product.published_scope; } if (product.seo_title) { create.seo_title = product.seo_title; } if (!product.seo_title && product.title) { create.seo_title = product.title; } if (product.seo_description) { create.seo_description = this.app.services.ProxyCartService.description(product.seo_description); } if (!product.seo_description && product.body) { create.seo_description = this.app.services.ProxyCartService.description(product.body); } let images = []; if (product.images.length > 0) { product.images = product.images.map(image => { image.variant = 0; return image; }); images = images.concat(product.images); delete product.images; } let variants = [{ title: product.title, sku: product.sku, vendors: product.vendors, google: product.google, amazon: product.amazon }]; if (product.published === true) { variants[0].published_at = create.published_at; } if (product.published === false) { variants[0].unpublished_at = create.unpublished_at; } if (!variants[0].sku) { variants.splice(0, 1); } if (product.variants.length > 0) { variants = variants.concat(product.variants); } variants = variants.map((variant, index) => { variant = this.variantDefaults(variant, product); variant.position = index + 1; if (product.published && variant.published !== false) { variant.published = true; } if (variant.published) { variant.published_at = create.published_at; } if (variant.images.length > 0) { variant.images = variant.images.map(image => { image.variant = index; return image; }); images = images.concat(variant.images); delete variant.images; } if (variant.option) { const keys = Object.keys(variant.option); create.options = _.union(create.options, keys); } return variant; }); variants = variants.filter(variant => variant); create.total_variants = variants.length; create.variants = variants; images = images.map((image, index) => { image.position = index + 1; return image; }); let resProduct; return Product.create(create, { include: [ { model: Variant.instance, as: 'variants', include: [ { model: Metadata.instance, as: 'metadata' } ] }, { model: Metadata.instance, as: 'metadata', } ], transaction: options.transaction || null }) .then(createdProduct => { if (!createdProduct) { throw new Error('Product was not created'); } resProduct = createdProduct; if (product.tags && product.tags.length > 0) { product.tags = _.sortedUniq(product.tags.filter(n => n)); return Tag.transformTags(product.tags, { transaction: options.transaction || null }); } return; }) .then(tags => { if (tags && tags.length > 0) { return resProduct.setTags(tags.map(tag => tag.id), { transaction: options.transaction || null }); } return; }) .then(productTags => { if (product.shops && product.shops.length > 0) { product.shops = _.sortedUniq(product.shops.filter(n => n)); return Shop.transformShops(product.shops, { transaction: options.transaction || null }); } return; }) .then(shops => { if (shops && shops.length > 0) { return Product.sequelize.Promise.mapSeries(resProduct.variants, variant => { return variant.setShops(shops, { through: { product_id: resProduct.id }, transaction: options.transaction || null }); }); } return; }) .then(shops => { if (product.collections && product.collections.length > 0) { product.collections = _.sortedUniq(product.collections.filter(n => n)); return Collection.transformCollections(product.collections, { transaction: options.transaction || null }); } return; }) .then(collections => { if (collections && collections.length > 0) { return Product.sequelize.Promise.mapSeries(collections, collection => { const through = collection.product_position ? { position: collection.product_position } : {}; return resProduct.addCollection(collection.id, { through: through, transaction: options.transaction || null }); }); } return; }) .then(productCollections => { if (product.vendors && product.vendors.length > 0) { return Vendor.transformVendors(product.vendors, { transaction: options.transaction || null }); } return; }) .then(vendors => { if (vendors && vendors.length > 0) { return resProduct.setVendors(vendors.map(v => v.id), { through: { vendor_price: resProduct.price }, transaction: options.transaction || null }); } return; }) .then(vendors => { return Product.sequelize.Promise.mapSeries(images, image => { if (typeof image.variant !== 'undefined') { if (resProduct.variants && resProduct.variants[image.variant] && resProduct.variants[image.variant].id) { image.product_variant_id = resProduct.variants[image.variant].id; } delete image.variant; } return resProduct.createImage(image, { transaction: options.transaction || null }); }); }) .then(createdImages => { return Product.findByIdDefault(resProduct.id, { transaction: options.transaction || null }); }); } updateProducts(products) { if (!Array.isArray(products)) { products = [products]; } const Product = this.app.models.Product; return Product.datastore.transaction(t => { return Product.sequelize.Promise.mapSeries(products, product => { return this.updateProduct(product, { transaction: t }); }); }); } updateProduct(product, options) { options = options || {}; const Product = this.app.models['Product']; const Variant = this.app.models['ProductVariant']; const Image = this.app.models['ProductImage']; const Tag = this.app.models['Tag']; const Collection = this.app.models['Collection']; const Vendor = this.app.models['Vendor']; const productOptions = []; let resProduct, update = {}; return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new Error('Product not found'); } resProduct = _product; return resProduct.resolveVariants({ transaction: options.transaction || null }); }) .then(() => { if (product.collections) { return resProduct.resolveCollections({ transaction: options.transaction || null }); } return; }) .then(() => { return resProduct.resolveImages({ transaction: options.transaction || null }); }) .then(() => { if (product.metadata) { return resProduct.resolveMetadata({ transaction: options.transaction || null }); } return; }) .then(() => { if (product.associations) { return resProduct.resolveAssociations({ transaction: options.transaction || null }); } return; }) .then(() => { if (product.vendors) { return resProduct.resolveVendors({ transaction: options.transaction || null }); } return; }) .then(() => { update = { host: product.host || resProduct.host, handle: product.handle || resProduct.handle, seo_title: product.seo_title || resProduct.seo_title, seo_description: product.seo_description || resProduct.seo_description, body: product.body || resProduct.body, type: product.type || resProduct.type, published_scope: product.published_scope || resProduct.published_scope, available: product.available, average_shipping: product.average_shipping, property_pricing: product.property_pricing, exclude_payment_types: product.exclude_payment_types, weight: product.weight || resProduct.weight, weight_unit: product.weight_unit || resProduct.weight_unit, requires_shipping: product.requires_shipping || resProduct.requires_shipping, tax_code: product.tax_code || resProduct.tax_code, options: productOptions }; product.variants = product.variants || []; product.images = product.images || []; product.tags = product.tags || []; product.collections = product.collections || []; product.associations = product.associations || []; if (product.published === true && resProduct.published === false) { update.published = resProduct.variants[0].published = product.published; update.published_at = resProduct.variants[0].published_at = new Date(); } if (product.published === false && resProduct.published === true) { update.published = resProduct.variants[0].published = product.published; update.unpublished_at = resProduct.variants[0].unpublished_at = new Date(); } if (product.sku) { resProduct.variants[0].sku = product.sku; } if (product.title) { update.title = resProduct.variants[0].title = product.title; } if (product.price) { update.price = resProduct.variants[0].price = product.price; } if (product.compare_at_price) { update.compare_at_price = resProduct.variants[0].compare_at_price = product.compare_at_price; } if (product.seo_title) { update.seo_title = product.seo_title; } if (product.title && !product.seo_title) { update.seo_title = product.title; } if (product.seo_description) { update.seo_description = this.app.services.ProxyCartService.description(product.seo_description); } if (!product.seo_description && product.body) { update.seo_description = this.app.services.ProxyCartService.description(product.body); } resProduct.variants = resProduct.variants.map(variant => { const variantToUpdate = product.variants.find(v => variant.id === v.id || variant.sku === v.sku) || {}; if (variantToUpdate && variantToUpdate.images) { let newImages = variantToUpdate.images.filter(image => !image.id); newImages = newImages.map(image => { image.product_id = resProduct.id; image.product_variant_id = variant.id; return Image.build(image); }); resProduct.images = _.concat(resProduct.images, newImages); } for (const k in variantToUpdate) { if (variantToUpdate.hasOwnProperty(k) && variantToUpdate.hasOwnProperty(k)) { if (!_.isNil(variantToUpdate[k])) { variant[k] = variantToUpdate[k]; } } } return variant; }); product.variants = product.variants.filter(variant => !variant.id && !resProduct.variants.find(v => { return v.id === variant.id || v.sku === variant.sku; })); product.variants = product.variants.map((variant) => { variant.product_id = resProduct.id; variant = this.variantDefaults(variant, resProduct.get({ plain: true })); if (variant.images.length > 0) { resProduct.images = resProduct.images.map(image => { return _.extend(image, variant.images.find(i => i.id === image.id || i.src === image.src)); }); variant.images = variant.images.filter(image => !image.id); variant.images = variant.images.map(image => { image.product_id = resProduct.id; return Image.build(image); }); resProduct.images = _.concat(resProduct.images, variant.images); } return Variant.build(variant); }); resProduct.variants = _.sortBy(_.concat(resProduct.variants, product.variants), 'position'); resProduct.variants = resProduct.variants.map((variant, index) => { variant.position = index + 1; return variant; }); resProduct.total_variants = resProduct.variants.length; resProduct.variants.forEach(variant => { if (variant.option) { const keys = Object.keys(variant.option); update.options = _.union(update.options, keys); } }); resProduct.images = resProduct.images.map(image => { const imageToUpdate = product.images.find(i => i.id === image.id || i.src === image.src) || {}; for (const k in imageToUpdate) { if (imageToUpdate.hasOwnProperty(k) && imageToUpdate.hasOwnProperty(k)) { if (!_.isNil(imageToUpdate[k])) { image[k] = imageToUpdate[k]; } } } return image; }); product.images = product.images.filter(image => !image.id && !resProduct.images.find(i => { return i.id === image.id || i.src === image.src; })); product.images = product.images.map(image => { image.product_id = resProduct.id; return Image.build(image); }); resProduct.images = _.sortBy(_.concat(resProduct.images, product.images), 'position'); resProduct.images = resProduct.images.map((image, index) => { image.position = index + 1; return image; }); return resProduct.updateAttributes(update, { transaction: options.transaction || null }); }) .then(updateProduct => { if (product.tags && product.tags.length > 0) { product.tags = _.sortedUniq(product.tags.filter(n => n)); return Tag.transformTags(product.tags, { transaction: options.transaction || null }); } return; }) .then(tags => { if (tags && tags.length > 0) { return resProduct.setTags(tags.map(t => t.id), { transaction: options.transaction || null }); } return; }) .then(productTags => { if (product.collections && product.collections.length > 0) { product.collections = _.sortedUniq(product.collections.filter(n => n)); return Collection.transformCollections(product.collections, { transaction: options.transaction || null }); } return; }) .then(collections => { if (collections && collections.length > 0) { return Product.sequelize.Promise.mapSeries(collections, collection => { const through = collection.product_position ? { position: collection.product_position } : {}; return resProduct.addCollection(collection.id, { through: through, hooks: false, individualHooks: false, returning: false, transaction: options.transaction || null }); }); } return; }) .then(() => { if (product.metadata && _.isObject(product.metadata)) { resProduct.metadata.data = product.metadata || {}; return resProduct.metadata.save({ transaction: options.transaction || null }); } return; }) .then(metadata => { if (product.vendors && product.vendors.length > 0) { return Vendor.transformVendors(product.vendors, { transaction: options.transaction || null }); } return; }) .then(vendors => { if (vendors && vendors.length > 0) { return resProduct.setVendors(vendors.map(v => v.id), { transaction: options.transaction || null }); } return; }) .then(vendors => { return Product.sequelize.Promise.mapSeries(resProduct.variants, variant => { if (variant instanceof Variant.instance) { return variant.save({ transaction: options.transaction || null }) .catch(err => { this.app.log.error(err); return variant; }); } else { return resProduct.createVariant(variant, { transaction: options.transaction || null }); } }); }) .then(variants => { return Product.sequelize.Promise.mapSeries(resProduct.images, image => { if (typeof image.variant !== 'undefined') { image.product_variant_id = resProduct.variants[image.variant].id; delete image.variant; } if (image instanceof Image.instance) { return image.save({ transaction: options.transaction || null }); } else { return resProduct.createImage(image, { transaction: options.transaction || null }); } }); }) .then(images => { return Product.findByIdDefault(resProduct.id, { transaction: options.transaction || null }); }); } removeProducts(products) { if (!Array.isArray(products)) { products = [products]; } const Product = this.app.models['Product']; return Product.sequelize.Promise.mapSeries(products, product => { return this.removeProduct(product); }); } removeProduct(product, options = {}) { if (!product.id) { const err = new errors_1.ModelError('E_NOT_FOUND', 'Product is missing id'); return Promise.reject(err); } const Product = this.app.models.Product; return Product.destroy({ where: { id: product.id }, transaction: options.transaction || null }); } removeVariants(variants) { if (!Array.isArray(variants)) { variants = [variants]; } const Product = this.app.models['Product']; return Product.sequelize.Promise.mapSeries(variants, variant => { return this.removeVariant(variant); }); } createVariant(product, variant, options = {}) { const Product = this.app.models['Product']; const Variant = this.app.models['ProductVariant']; let resProduct, resVariant, productOptions = []; return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new errors_1.ModelError('E_NOT_FOUND', 'Could not find Product'); } resProduct = _product; variant.product_id = resProduct.id; variant = this.variantDefaults(variant, resProduct); return resProduct.createVariant(variant, { transaction: options.transaction || null }); }) .then(_variant => { resVariant = _variant; return Variant.findAll({ where: { product_id: resProduct.id }, transaction: options.transaction || null }); }) .then(variants => { const updates = _.sortBy(variants, 'position'); _.map(updates, (_variant, index) => { _variant.position = index + 1; }); _.map(updates, _variant => { const keys = Object.keys(_variant.option); productOptions = _.union(productOptions, keys); }); return Product.sequelize.Promise.mapSeries(updates, _variant => { return _variant.save({ transaction: options.transaction || null }); }); }) .then(updatedVariants => { resProduct.options = productOptions; resProduct.total_variants = updatedVariants.length; return resProduct.save({ transaction: options.transaction || null }); }) .then(updatedProduct => { return Variant.findByIdDefault(resVariant.id, { transaction: options.transaction || null }); }); } createVariants(product, variants, options) { const Product = this.app.models['Product']; return Product.sequelize.Promise.mapSeries(variants, variant => { return this.createVariant(product, variant, options); }); } updateVariant(product, variant, options) { options = options || {}; const Product = this.app.models['Product']; const Variant = this.app.models['ProductVariant']; const Image = this.app.models['ProductImage']; let resProduct, resVariant, productOptions = []; return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new Error('Product did not resolve'); } resProduct = _product; return Variant.resolve(variant, options); }) .then(foundVariant => { resVariant = foundVariant; resVariant = _.extend(resVariant, _.omit(variant, ['id', 'sku'])); resVariant = this.variantDefaults(resVariant, resProduct); return resVariant.resolveImages({ transaction: options.transaction || null }); }).then(() => { return resVariant.save({ transaction: options.transaction || null }); }) .then(_variant => { return Variant.findAll({ where: { product_id: resProduct.id }, transaction: options.transaction || null }); }) .then(variants => { const updates = _.sortBy(variants, 'position'); _.map(updates, (_variant, index) => { _variant.position = index + 1; }); _.map(updates, _variant => { const keys = Object.keys(_variant.option); productOptions = _.union(productOptions, keys); }); return Product.sequelize.Promise.mapSeries(updates, _variant => { return _variant.save({ transaction: options.transaction || null }); }); }) .then(updatedVariants => { resProduct.options = product.options; return resProduct.save({ transaction: options.transaction || null }); }) .then(updatedProduct => { return Variant.findByIdDefault(resVariant.id, { transaction: options.transaction || null }); }); } updateVariants(product, variants, options) { const Product = this.app.models['Product']; return Product.sequelize.Promise.mapSeries(variants, variant => { return this.updateVariant(product, variant, options); }); } removeVariant(id, options = {}) { const Product = this.app.models['Product']; const Variant = this.app.models.ProductVariant; let resVariant, resProduct; let updates; let productOptions = []; return Variant.resolve(id, { transaction: options.transaction || null }) .then(foundVariant => { resVariant = foundVariant; return Product.resolve(resVariant.product_id, { transaction: options.transaction || null }); }) .then(product => { resProduct = product; return Variant.findAll({ where: { product_id: resVariant.product_id }, transaction: options.transaction || null }); }) .then(foundVariants => { updates = _.sortBy(_.filter(foundVariants, variant => { if (variant.id !== resVariant.id) { return variant; } }), 'position'); _.map(updates, (variant, index) => { variant.position = index + 1; }); _.map(updates, variant => { const keys = Object.keys(variant.option); productOptions = _.union(productOptions, keys); }); return Variant.sequelize.Promise.mapSeries(updates, variant => { return variant.save({ transaction: options.transaction || null }); }); }) .then(updatedVariants => { resProduct.options = productOptions; resProduct.total_variants = updatedVariants.length; return resProduct.save({ transaction: options.transaction || null }); }) .then(updatedProduct => { return resVariant.destroy({ transaction: options.transaction || null }); }) .then(destroyed => { return resVariant; }); } removeImages(images) { if (!Array.isArray(images)) { images = [images]; } const Product = this.app.models['Product']; return Product.sequelize.Promise.mapSeries(images, image => { const id = typeof image.id !== 'undefined' ? image.id : image; return this.removeImage(id); }); } removeImage(id, options = {}) { const Image = this.app.models['ProductImage']; const Product = this.app.models['Product']; const Variant = this.app.models['ProductVariant']; let resDestroy; return Image.findById(id, { transaction: options.transaction || null }) .then(_image => { if (!_image) { throw new Error('Image not found'); } resDestroy = _image; return Image.findAll({ where: { product_id: resDestroy.product_id }, order: [['position', 'ASC']], transaction: options.transaction || null }); }) .then(foundImages => { foundImages = foundImages.filter(image => image.id !== id); foundImages = foundImages.map((image, index) => { image.position = index + 1; return image; }); return Image.sequelize.Promise.mapSeries(foundImages, image => { return image.save({ transaction: options.transaction || null }); }); }) .then(updatedImages => { return resDestroy.destroy({ transaction: options.transaction || null }); }) .then(() => { return resDestroy; }); } addImage(product, variant, image, options = {}) { const Image = this.app.models['ProductImage']; const Product = this.app.models['Product']; const Variant = this.app.models['ProductVariant']; let resProduct, resImage, resVariant; return Product.resolve(product, { transaction: options.transaction || null }) .then(foundProduct => { if (!foundProduct) { throw new Error('Product could not be resolved'); } resProduct = foundProduct; if (variant) { return Variant.resolve(variant, { transaction: options.transaction || null }); } else { return null; } }) .then(_variant => { resVariant = _variant ? _variant.id : null; return resProduct.createImage({ product_variant_id: resVariant, src: image, position: options.position || null, alt: options.alt || null }, { transaction: options.transaction }); }) .then(createdImage => { if (!createdImage) { throw new Error('Image Could not be created'); } resImage = createdImage; return Image.findAll({ where: { product_id: resProduct.id }, order: [['position', 'ASC']], transaction: options.transaction || null }); }) .then(_images => { _images = _images.map((_image, index) => { _image.position = index + 1; return _image; }); return Image.sequelize.Promise.mapSeries(_images, _image => { return _image.save({ transaction: options.transaction || null }); }); }) .then(updatedImages => { return resImage.reload(); }); } updateImages(images, options = {}) { if (!Array.isArray(images)) { images = [images]; } const Product = this.app.models['Product']; return Product.sequelize.Promise.mapSeries(images, image => { return this.updateImage(image, image, options); }); } updateImage(image, body, options = {}) { const Image = this.app.models['ProductImage']; const Product = this.app.models['Product']; const Variant = this.app.models['ProductVariant']; let resUpdate; return Image.resolve(image, { transaction: options.transaction || null }) .then(_image => { if (!_image) { throw new Error('Image not found'); } resUpdate = _image; return Image.findAll({ where: { product_id: resUpdate.product_id }, order: [['position', 'ASC']], transaction: options.transaction || null }); }) .then(updatedImages => { return resUpdate.update(body, { transaction: options.transaction || null }); }) .then(() => { return resUpdate; }); } createImage(product, variant, filePath, options = {}) { const image = fs.readFileSync(filePath); const Image = this.app.models['ProductImage']; const Product = this.app.models['Product']; const Variant = this.app.models['ProductVariant']; let resProduct, resImage, resVariant; return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new Error('Product could not be resolved'); } resProduct = _product; if (variant) { return Variant.resolve(variant, { transaction: options.transaction || null }); } else { return null; } }) .then(_variant => { resVariant = _variant ? _variant.id : null; return this.app.services.ProxyCartService.uploadImage(image, filePath); }) .then(uploadedImage => { return resProduct.createImage({ product_variant_id: resVariant, src: uploadedImage.url, position: options.position || null, alt: options.alt || null }, { transaction: options.transaction }); }) .then(createdImage => { if (!createdImage) { throw new Error('Image Could not be created'); } resImage = createdImage; return Image.findAll({ where: { product_id: resProduct.id }, order: [['position', 'ASC']], transaction: options.transaction || null }); }) .then(foundImages => { foundImages = foundImages.map((_image, index) => { _image.position = index + 1; return _image; }); return Image.sequelize.Promise.mapSeries(foundImages, _image => { return _image.save({ transaction: options.transaction || null }); }); }) .then(updatedImages => { return resImage.reload(); }); } addTag(product, tag, options) { options = options || {}; const Product = this.app.models['Product']; const Tag = this.app.models['Tag']; let resProduct, resTag; return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product not found'); } resProduct = _product; return Tag.resolve(tag, { transaction: options.transaction || null }); }) .then(_tag => { if (!_tag) { throw new errors_1.ModelError('E_NOT_FOUND', 'Tag not found'); } resTag = _tag; return resProduct.hasTag(resTag.id, { transaction: options.transaction || null }); }) .then(hasTag => { if (!hasTag) { return resProduct.addTag(resTag.id, { transaction: options.transaction || null }); } return resProduct; }) .then(_tag => { return Product.findByIdDefault(resProduct.id, { transaction: options.transaction || null }); }); } removeTag(product, tag, options) { options = options || {}; const Product = this.app.models['Product']; const Tag = this.app.models['Tag']; let resProduct, resTag; return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product not found'); } resProduct = _product; return Tag.resolve(tag, { transaction: options.transaction || null }); }) .then(_tag => { if (!_tag) { throw new errors_1.ModelError('E_NOT_FOUND', 'Tag not found'); } resTag = _tag; return resProduct.hasTag(resTag.id, { transaction: options.transaction || null }); }) .then(hasTag => { if (hasTag) { return resProduct.removeTag(resTag.id, { transaction: options.transaction || null }); } return false; }) .then(newTag => { return Product.findByIdDefault(resProduct.id, { transaction: options.transaction || null }); }); } addAssociations(product, associations = [], options = {}) { const Product = this.app.models['Product']; let resProduct; return Product.resolve(product, options) .then(_product => { resProduct = _product; return Product.sequelize.Promise.mapSeries(associations, a => { return this.addAssociation(resProduct, a, options); }); }); } addVariantAssociations(product, variant, associations = [], options = {}) { const ProductVariant = this.app.models['ProductVariant']; let resVariant; return ProductVariant.resolve(variant, options) .then(_variant => { resVariant = _variant; return ProductVariant.sequelize.Promise.mapSeries(associations, a => { return this.addVariantAssociation(resVariant, a, options); }); }); } addAssociation(product, association, options = {}) { const Product = this.app.models['Product']; const ProductVariant = this.app.models['ProductVariant']; let resProduct, resVariant, resAssociationProduct, resAssociationVariant, through; if (!product || !association) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product or Association was not provided'); } return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product not found'); } resProduct = _product; if (product.sku) { return ProductVariant.resolve(product, { transaction: options.transaction || null }); } else { return resProduct.getDefaultVariant({ transaction: options.transaction || null }); } }) .then(_variant => { if (_variant) { resVariant = _variant; } return Product.resolve(association, { transaction: options.transaction || null }); }) .then(_association => { if (!_association) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product not found'); } resAssociationProduct = _association; if (association.sku) { return ProductVariant.resolve(association); } else { return resAssociationProduct.getDefaultVariant({ transaction: options.transaction || null }); } }) .then(_variantAssociation => { if (_variantAssociation) { resAssociationVariant = _variantAssociation; } through = resVariant && resAssociationVariant ? { variant_id: resVariant.id, associated_variant_id: resAssociationVariant.id, } : {}; if (association.position !== 'undefined') { through.position = association.position; } return resProduct.hasAssociation(resAssociationProduct.id, { transaction: options.transaction || null, through: through }); }) .then(hasAssociation => { if (!hasAssociation) { return resProduct.addAssociation(resAssociationProduct.id, { transaction: options.transaction || null, through: through }) .then(() => { return resProduct.save({ transaction: options.transaction || null }); }); } return false; }) .then(_newAssociation => { return resAssociationProduct; }); } removeAssociation(product, association, options = {}) { const Product = this.app.models['Product']; const ProductVariant = this.app.models['ProductVariant']; let resProduct, resVariant, resAssociationProduct, resAssociationVariant, through; if (!product || !association) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product or Association was not provided'); } return Product.resolve(product, { transaction: options.transaction || null }) .then(_product => { if (!_product) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product not found'); } resProduct = _product; if (product.sku) { return ProductVariant.resolve(product); } return; }) .then(_variant => { if (_variant) { resVariant = _variant; } return Product.resolve(association, { transaction: options.transaction || null }); }) .then(_association => { if (!_association) { throw new errors_1.ModelError('E_NOT_FOUND', 'Product not found'); } resAssociationProduct = _association; if (association.sku) { return ProductVariant.resolve(association); } return; }) .then(_variantAssociation => { if (_variantAssociation) { resAssociationVariant = _variantAssociation; } through =