UNPKG

@fabrix/spool-cart

Version:

Spool - eCommerce Spool for Fabrix

608 lines (607 loc) 25.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@fabrix/fabrix/dist/common"); const lodash_1 = require("lodash"); const moment = require("moment"); const enums_1 = require("../../enums"); const enums_2 = require("../../enums"); const enums_3 = require("../../enums"); class DiscountService 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(); } create(discount, options) { options = options || {}; const Discount = this.app.models['Discount']; let appliesTo = discount.applies_to || []; if (discount.applies_to_id || discount.applies_to_model) { appliesTo.push({ id: discount.applies_to_id, model: discount.applies_to_model }); delete discount.applies_to_id; delete discount.applies_to_model; } delete discount.applies_to; appliesTo = appliesTo.filter(a => { if (a.model && a.id) { return a; } }); appliesTo.map(a => { a.model = a.model.charAt(0).toUpperCase() + a.model.slice(1); return a; }); let resDiscount; return Discount.create(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount was not created'); } resDiscount = _discount; return Discount.sequelize.Promise.mapSeries(appliesTo, applicant => { if (this.app.models[applicant.model]) { return this.app.models[applicant.model].findById(applicant.id, { transaction: options.transaction || null }) .then(_applicant => { if (!_applicant) { throw new Error(`${applicant.model} ${applicant.id} could not be found`); } return _applicant.addDiscount(resDiscount.id, { transaction: options.transaction || null }); }); } else { return; } }); }) .then(() => { return resDiscount; }); } update(identifier, discount, options) { options = options || {}; const Discount = this.app.models['Discount']; let resDiscount; return Discount.resolve(identifier, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return resDiscount.update(discount, { transaction: options.transaction || null }); }); } destroy(identifier, options) { options = options || {}; const Discount = this.app.models['Discount']; let resDiscount; return Discount.resolve(identifier, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return resDiscount.destroy({ transaction: options.transaction || null }); }) .then(() => { return resDiscount; }); } start(identifier, options) { options = options || {}; const Discount = this.app.models['Discount']; let resDiscount; return Discount.resolve(identifier, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return resDiscount .start({ transaction: options.transaction || null }) .save({ transaction: options.transaction || null }); }) .then(() => { return resDiscount; }); } expire(identifier, options) { options = options || {}; const Discount = this.app.models['Discount']; let resDiscount; return Discount.resolve(identifier, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return resDiscount .stop({ transaction: options.transaction || null }) .save({ transaction: options.transaction || null }); }) .then(() => { return resDiscount; }); } calculateCollections(obj, collections, resolver, options = {}) { const discountedLines = []; let type; let resObj; return resolver.resolve(obj, { transaction: options.transaction || null }) .then(_obj => { if (!_obj) { throw new Error('Could not resolve instance and calculate collection discounts'); } if (_obj instanceof this.app.models['Cart'].instance) { type = 'cart'; } else if (_obj instanceof this.app.models['Subscription'].instance) { type = 'subscription'; } else if (_obj instanceof this.app.models['Product'].instance) { type = 'product'; } else { throw new Error('Instance must be either Cart, Subscription, or Product'); } resObj = _obj; collections.forEach(collection => { if (!(collection.discount_rate > 0) && !(collection.percentage > 0)) { return; } if (['cart', 'subscription'].indexOf(type) > -1 && resObj.line_items.length === 0) { return; } const discountedLine = { id: collection.id, model: 'collection', type: null, name: collection.title, scope: collection.discount_scope, price: 0 }; if (['cart', 'subscription'].indexOf(type) > -1) { discountedLine.lines = []; } if (collection.discount_type === enums_1.COLLECTION_DISCOUNT_TYPE.FIXED) { discountedLine.rate = collection.discount_rate; discountedLine.type = enums_1.COLLECTION_DISCOUNT_TYPE.FIXED; } else if (collection.discount_type === enums_1.COLLECTION_DISCOUNT_TYPE.PERCENTAGE) { discountedLine.percentage = collection.discount_percentage; discountedLine.type = enums_1.COLLECTION_DISCOUNT_TYPE.PERCENTAGE; } if (['cart', 'subscription'].indexOf(type) > -1) { let publish = false; const lineItems = resObj.line_items.map((item, index) => { if (collection.discount_product_exclude.indexOf(item.type) > -1) { return item; } const inProducts = collection.products.some(product => product.id === item.product_id); if (collection.discount_scope === enums_2.COLLECTION_DISCOUNT_SCOPE.INDIVIDUAL && inProducts === false) { return item; } const lineDiscountedLine = lodash_1.omit(lodash_1.clone(discountedLine), 'lines'); if (discountedLine.type === enums_1.COLLECTION_DISCOUNT_TYPE.FIXED) { lineDiscountedLine.price = discountedLine.rate; } else if (discountedLine.type === enums_1.COLLECTION_DISCOUNT_TYPE.PERCENTAGE) { lineDiscountedLine.price = (item.price * discountedLine.percentage); } const calculatedPrice = Math.max(0, item.calculated_price - lineDiscountedLine.price); const totalDeducted = Math.min(item.price, (item.price - (item.price - lineDiscountedLine.price))); publish = true; item.discounted_lines.push(lineDiscountedLine); item.calculated_price = calculatedPrice; item.total_discounts = item.total_discounts + totalDeducted; discountedLine.price = discountedLine.price + totalDeducted; discountedLine.lines.push(index); return item; }); resObj.setLineItems(lineItems); if (publish) { discountedLines.push(discountedLine); } } else if (type === 'product') { if (collection.discount_product_exclude && collection.discount_product_exclude.indexOf(resObj.type) > -1) { return resObj; } const inProducts = collection.products && collection.products.some(colProduct => colProduct.id === resObj.id); if (collection.discount_scope === enums_2.COLLECTION_DISCOUNT_SCOPE.INDIVIDUAL && inProducts === false) { return resObj; } const lineDiscountedLine = lodash_1.omit(lodash_1.clone(discountedLine), 'lines'); if (discountedLine.type === enums_1.COLLECTION_DISCOUNT_TYPE.FIXED) { lineDiscountedLine.price = discountedLine.rate; } else if (discountedLine.type === enums_1.COLLECTION_DISCOUNT_TYPE.PERCENTAGE) { lineDiscountedLine.price = (resObj.price * discountedLine.percentage); } const calculatedPrice = Math.max(0, resObj.calculated_price - lineDiscountedLine.price); const totalDeducted = Math.min(resObj.price, (resObj.price - (resObj.price - lineDiscountedLine.price))); resObj.setCalculatedPrice(calculatedPrice); discountedLine.price = discountedLine.price + totalDeducted; discountedLines.push(discountedLine); } }); resObj.setDiscountedLines(discountedLines); return resObj; }); } expireThisHour(options = {}) { const start = moment().startOf('hour'); const end = start.clone().endOf('hour'); const Discount = this.app.models['Discount']; const errors = []; let discountsTotal = 0; this.app.log.debug('DiscountService.expireThisHour', start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')); return Discount.batch({ where: { ends_at: { $gte: start.format('YYYY-MM-DD HH:mm:ss'), $lte: end.format('YYYY-MM-DD HH:mm:ss') }, status: [enums_3.DISCOUNT_STATUS.ENABLED, enums_3.DISCOUNT_STATUS.DEPLETED] }, regressive: true, transaction: options.transaction || null }, discounts => { return Discount.sequelize.Promise.mapSeries(discounts, discount => { return this.expire(discount, { transaction: options.transaction || null }); }) .then(results => { discountsTotal = discountsTotal + results.length; return; }) .catch(err => { this.app.log.error(err); errors.push(err); return; }); }) .then(discounts => { const results = { discounts: discountsTotal, errors: errors }; this.app.log.info(results); this.app.services.EventsService.publish('discounts.end.complete', results); return results; }); } startThisHour(options = {}) { const start = moment().startOf('hour'); const end = start.clone().endOf('hour'); const Discount = this.app.models['Discount']; const errors = []; let discountsTotal = 0; this.app.log.debug('DiscountService.startThisHour', start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')); return Discount.batch({ where: { starts_at: { $gte: start.format('YYYY-MM-DD HH:mm:ss'), $lte: end.format('YYYY-MM-DD HH:mm:ss') }, status: enums_3.DISCOUNT_STATUS.DISABLED }, regressive: true, transaction: options.transaction || null }, discounts => { const Sequelize = Discount.sequelize; return Sequelize.Promise.mapSeries(discounts, discount => { return this.start(discount, { transaction: options.transaction || null }); }) .then(results => { discountsTotal = discountsTotal + results.length; return; }) .catch(err => { this.app.log.error(err); errors.push(err); return; }); }) .then(discounts => { const results = { discounts: discountsTotal, errors: errors }; this.app.log.info(results); this.app.services.EventsService.publish('discounts.start.complete', results); return results; }); } addProducts(discount, products, options = {}) { if (!Array.isArray(products)) { products = [products]; } const Sequelize = this.app.models['Discount'].sequelize; return Sequelize.transaction(t => { return Sequelize.Promise.mapSeries(products, product => { return this.addProduct(discount, product, { transaction: t }); }); }); } addProduct(discount, product, options = {}) { const Discount = this.app.models['Discount']; const Product = this.app.models['Product']; let resDiscount, resProduct; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Product.resolve(product, { transaction: options.transaction || null }); }) .then(_product => { if (!_product) { throw new Error('Product did not resolve'); } resProduct = _product; return resDiscount.hasProduct(resProduct.id, { transaction: options.transaction || null }); }) .then(hasProduct => { if (!hasProduct) { return resDiscount.addProduct(resProduct.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resProduct; }); } removeProduct(discount, product, options = {}) { const Discount = this.app.models['Discount']; const Product = this.app.models['Product']; let resDiscount, resProduct; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Product.resolve(product, { transaction: options.transaction || null }); }) .then(_product => { if (!_product) { throw new Error('Product did not resolve'); } resProduct = _product; return resDiscount.hasProduct(resProduct.id, { transaction: options.transaction || null }); }) .then(hasProduct => { if (hasProduct) { return resDiscount.removeProduct(resProduct.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resProduct; }); } addCustomers(discount, customers, options = {}) { if (!Array.isArray(customers)) { customers = [customers]; } const Sequelize = this.app.models['Discount'].sequelize; return Sequelize.transaction(t => { return Sequelize.Promise.mapSeries(customers, customer => { return this.addCustomer(discount, customer, { transaction: t }); }); }); } addCustomer(discount, customer, options = {}) { const Discount = this.app.models['Discount']; const Customer = this.app.models['Customer']; let resDiscount, resCustomer; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Customer.resolve(customer, { transaction: options.transaction || null, create: false }); }) .then(_customer => { if (!_customer) { throw new Error('Customer did not resolve'); } resCustomer = _customer; return resDiscount.hasCustomer(resCustomer.id, { transaction: options.transaction || null }); }) .then(hasCustomer => { if (!hasCustomer) { return resDiscount.addCustomer(resCustomer.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resCustomer; }); } removeCustomer(discount, customer, options) { options = options || {}; const Discount = this.app.models['Discount']; const Customer = this.app.models['Customer']; let resDiscount, resCustomer; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Customer.resolve(customer, { transaction: options.transaction || null, create: false }); }) .then(_customer => { if (!_customer) { throw new Error('Customer did not resolve'); } resCustomer = _customer; return resDiscount.hasCustomer(resCustomer.id, { transaction: options.transaction || null }); }) .then(hasCustomer => { if (hasCustomer) { return resDiscount.removeCustomer(resCustomer.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resCustomer; }); } addCart(discount, cart, options) { options = options || {}; const Discount = this.app.models['Discount']; const Cart = this.app.models['Cart']; let resDiscount, resCart; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Cart.resolve(cart, { transaction: options.transaction || null }); }) .then(_cart => { if (!_cart) { throw new Error('Cart did not resolve'); } resCart = _cart; return resDiscount.hasCart(resCart.id, { transaction: options.transaction || null }); }) .then(hasCart => { if (!hasCart) { return resDiscount.addCart(resCart.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resCart; }); } removeCart(discount, cart, options) { options = options || {}; const Discount = this.app.models['Discount']; const Cart = this.app.models['Cart']; let resDiscount, resCart; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Cart.resolve(cart, { transaction: options.transaction || null }); }) .then(_cart => { if (!_cart) { throw new Error('Cart did not resolve'); } resCart = _cart; return resDiscount.hasCart(resCart.id, { transaction: options.transaction || null }); }) .then(hasCart => { if (hasCart) { return resDiscount.removeCart(resCart.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resCart; }); } addCollections(discount, collections, options) { options = options || {}; if (!Array.isArray(collections)) { collections = [collections]; } const Sequelize = this.app.models['Discount'].sequelize; return Sequelize.transaction(t => { return Sequelize.Promise.mapSeries(collections, collection => { return this.addCollection(discount, collection, { transaction: t }); }); }); } addCollection(discount, collection, options) { options = options || {}; const Discount = this.app.models['Discount']; const Collection = this.app.models['Collection']; let resDiscount, resCollection; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Collection.resolve(collection, { transaction: options.transaction || null, reject: true }); }) .then(_collection => { if (!_collection) { throw new Error('Collection did not resolve'); } resCollection = _collection; return resDiscount.hasCollection(resCollection.id, { transaction: options.transaction || null }); }) .then(hasCollection => { if (!hasCollection) { return resDiscount.addCollection(resCollection.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resCollection; }); } removeCollection(discount, collection, options) { options = options || {}; const Discount = this.app.models['Discount']; const Collection = this.app.models['Collection']; let resDiscount, resCollection; return Discount.resolve(discount, { transaction: options.transaction || null }) .then(_discount => { if (!_discount) { throw new Error('Discount did not resolve'); } resDiscount = _discount; return Collection.resolve(collection, { transaction: options.transaction || null, reject: true }); }) .then(_collection => { if (!_collection) { throw new Error('Collection did not resolve'); } resCollection = _collection; return resDiscount.hasCollection(resCollection.id, { transaction: options.transaction || null }); }) .then(hasCollection => { if (hasCollection) { return resDiscount.removeCollection(resCollection.id, { transaction: options.transaction || null }); } return; }) .then(() => { return resCollection; }); } } exports.DiscountService = DiscountService;