trailpack-proxy-cart
Version:
eCommerce - Trailpack for Proxy Engine
828 lines (758 loc) • 25.2 kB
JavaScript
/* eslint no-console: [0] */
'use strict'
const Service = require('trails/service')
const _ = require('lodash')
const moment = require('moment')
const COLLECTION_DISCOUNT_TYPE = require('../../lib').Enums.COLLECTION_DISCOUNT_TYPE
const COLLECTION_DISCOUNT_SCOPE = require('../../lib').Enums.COLLECTION_DISCOUNT_SCOPE
const DISCOUNT_STATUS = require('../../lib').Enums.DISCOUNT_STATUS
/**
* @module DiscountService
* @description Discount Service
*/
module.exports = class DiscountService extends Service {
/**
*
* @param discount
* @param options
* @returns {Promise.<TResult>|*}
*/
create(discount, options){
options = options || {}
const Discount = this.app.orm['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
// Filter out bad requests
appliesTo = appliesTo.filter(a => {
if (a.model && a.id) {
return a
}
})
// make all the model match schema just in case it came through lowercase.
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.orm[applicant.model]) {
return this.app.orm[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
})
}
/**
* @param identifier
* @param discount
* @param options
* @returns {Promise.<TResult>|*}
*/
update(identifier, discount, options){
options = options || {}
const Discount = this.app.orm['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})
})
}
/**
* @param identifier
* @param options
* @returns {Promise.<TResult>}
*/
destroy(identifier, options){
options = options || {}
const Discount = this.app.orm['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
})
}
/**
*
* @param identifier
* @param options
* @returns {Promise.<TResult>|*}
*/
start(identifier, options) {
options = options || {}
const Discount = this.app.orm['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
})
}
/**
*
* @param identifier
* @param options
* @returns {Promise.<TResult>|*}
*/
expire(identifier, options) {
options = options || {}
const Discount = this.app.orm['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
})
}
/**
*
* @param obj: cart/subscription Instance
* @param collections
* @param resolver
* @param options
* @returns {Promise.<T>}
*/
calculateCollections(obj, collections, resolver, options){
options = options || {}
// Set the default
const discountedLines = []
let type
// Resolve the instance: product, subscription, cart
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.orm['Cart']) {
type = 'cart'
}
else if (_obj instanceof this.app.orm['Subscription']) {
type = 'subscription'
}
else if (_obj instanceof this.app.orm['Product']) {
type = 'product'
}
else {
throw new Error('Instance must be either Cart, Subscription, or Product')
}
resObj = _obj
// Loop through collection and apply discounts, stop if there are no line items
collections.forEach(collection => {
// If the collection doesn't have a discount ignore
if (!collection.discount_rate > 0 && !collection.percentage > 0) {
return
}
// If object is a cart/subscription with line items and they are empty then ignore
if (['cart','subscription'].indexOf(type) > -1 && resObj.line_items.length === 0) {
return
}
// Set the default discounted line
const discountedLine = {
id: collection.id,
model: 'collection',
type: null,
name: collection.title,
scope: collection.discount_scope,
price: 0
}
// if cart or subscription, add lines array for tracking
if (['cart','subscription'].indexOf(type) > -1) {
discountedLine.lines = []
}
// Set type variable and percentage/rate
if (collection.discount_type === COLLECTION_DISCOUNT_TYPE.FIXED) {
discountedLine.rate = collection.discount_rate
discountedLine.type = COLLECTION_DISCOUNT_TYPE.FIXED
}
else if (collection.discount_type === COLLECTION_DISCOUNT_TYPE.PERCENTAGE) {
discountedLine.percentage = collection.discount_percentage
discountedLine.type = COLLECTION_DISCOUNT_TYPE.PERCENTAGE
}
// Determine Scope
// if (collection.discount_scope == COLLECTION_DISCOUNT_SCOPE.GLOBAL) {
// If cart or subscription
if (['cart','subscription'].indexOf(type) > -1) {
let publish = false
const lineItems = resObj.line_items.map((item, index) => {
// Search Exclusion
if (collection.discount_product_exclude.indexOf(item.type) > -1) {
return item
}
// Check if Individual Scope
const inProducts = collection.products.some(product => product.id === item.product_id)
if (collection.discount_scope === COLLECTION_DISCOUNT_SCOPE.INDIVIDUAL && inProducts === false) {
return item
}
// const lineDiscountedLines = item.discounted_lines
// Set the default Discounted Line
const lineDiscountedLine = _.omit(_.clone(discountedLine), 'lines')
if (discountedLine.type === COLLECTION_DISCOUNT_TYPE.FIXED) {
lineDiscountedLine.price = discountedLine.rate
}
else if (discountedLine.type === 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 this to the parent discounted lines
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
})
// Set the mutated line items
resObj.setLineItems(lineItems)
if (publish) {
// Add the discounted Line
discountedLines.push(discountedLine)
}
}
// If product
else if (type === 'product') {
if (collection.discount_product_exclude && collection.discount_product_exclude.indexOf(resObj.type) > -1) {
return resObj
}
// Check if Individual Scope
const inProducts = collection.products && collection.products.some(colProduct => colProduct.id === resObj.id)
if (collection.discount_scope === COLLECTION_DISCOUNT_SCOPE.INDIVIDUAL && inProducts === false){
return resObj
}
// const lineDiscountedLines = item.discounted_lines
// Set the default Discounted Line
const lineDiscountedLine = _.omit(_.clone(discountedLine), 'lines')
if (discountedLine.type === COLLECTION_DISCOUNT_TYPE.FIXED) {
lineDiscountedLine.price = discountedLine.rate
}
else if (discountedLine.type === 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)))
// Publish this to the parent discounted lines
resObj.setCalculatedPrice(calculatedPrice)
discountedLine.price = discountedLine.price + totalDeducted
discountedLines.push(discountedLine)
}
})
resObj.setDiscountedLines(discountedLines)
return resObj
})
}
/**
*
* @returns {Promise.<T>|*}
*/
expireThisHour(options) {
options = options || {}
const start = moment().startOf('hour')
const end = start.clone().endOf('hour')
const Discount = this.app.orm['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: [DISCOUNT_STATUS.ENABLED, 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 => {
// Calculate Totals
discountsTotal = discountsTotal + results.length
return
})
.catch(err => {
// errorsTotal++
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.ProxyEngineService.publish('discounts.end.complete', results)
return results
})
}
/**
*
* @returns {Promise.<TResult>|*}
*/
startThisHour(options) {
options = options || {}
const start = moment().startOf('hour')
const end = start.clone().endOf('hour')
const Discount = this.app.orm['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: 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 => {
// Calculate Totals
discountsTotal = discountsTotal + results.length
return
})
.catch(err => {
// errorsTotal++
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.ProxyEngineService.publish('discounts.start.complete', results)
return results
})
}
/**
* Add Multiple products
* @param discount
* @param products
* @param options
* @returns {Promise.<*>}
*/
addProducts(discount, products, options) {
options = options || {}
if (!Array.isArray(products)) {
products = [products]
}
const Sequelize = this.app.orm['Discount'].sequelize
// const addedProducts = []
// Setup Transaction
return Sequelize.transaction(t => {
return Sequelize.Promise.mapSeries(products, product => {
return this.addProduct(discount, product, {
transaction: t
})
})
})
}
/**
*
* @param discount
* @param product
* @param options
* @returns {Promise.<TResult>}
*/
addProduct(discount, product, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Product = this.app.orm['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
})
}
/**
*
* @param discount
* @param product
* @param options
* @returns {Promise.<TResult>}
*/
removeProduct(discount, product, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Product = this.app.orm['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
})
}
/**
* Add Multiple customers
* @param discount
* @param customers
* @param options
* @returns {Promise.<*>}
*/
addCustomers(discount, customers, options) {
options = options || {}
if (!Array.isArray(customers)) {
customers = [customers]
}
const Sequelize = this.app.orm['Discount'].sequelize
// const addedCustomers = []
// Setup Transaction
return Sequelize.transaction(t => {
return Sequelize.Promise.mapSeries(customers, customer => {
return this.addCustomer(discount, customer, {
transaction: t
})
})
})
}
/**
*
* @param discount
* @param customer
* @param options
* @returns {Promise.<TResult>}
*/
addCustomer(discount, customer, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Customer = this.app.orm['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
})
}
/**
*
* @param discount
* @param customer
* @param options
* @returns {Promise.<TResult>}
*/
removeCustomer(discount, customer, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Customer = this.app.orm['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
})
}
/**
*
* @param discount
* @param cart
* @param options
* @returns {Promise.<TResult>}
*/
addCart(discount, cart, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Cart = this.app.orm['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
})
}
/**
*
* @param discount
* @param cart
* @param options
* @returns {Promise.<TResult>}
*/
removeCart(discount, cart, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Cart = this.app.orm['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
})
}
/**
* Add Multiple collections
* @param discount
* @param collections
* @param options
* @returns {Promise.<*>}
*/
addCollections(discount, collections, options) {
options = options || {}
if (!Array.isArray(collections)) {
collections = [collections]
}
const Sequelize = this.app.orm['Discount'].sequelize
// const addedCollections = []
// Setup Transaction
return Sequelize.transaction(t => {
return Sequelize.Promise.mapSeries(collections, collection => {
return this.addCollection(discount, collection, {
transaction: t
})
})
})
}
/**
*
* @param discount
* @param collection
* @param options
* @returns {Promise.<TResult>}
*/
addCollection(discount, collection, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Collection = this.app.orm['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})
})
.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
})
}
/**
*
* @param discount
* @param collection
* @param options
* @returns {Promise.<TResult>}
*/
removeCollection(discount, collection, options) {
options = options || {}
const Discount = this.app.orm['Discount']
const Collection = this.app.orm['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})
})
.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
})
}
}