trailpack-proxy-cart
Version:
eCommerce - Trailpack for Proxy Engine
639 lines (628 loc) • 22.3 kB
JavaScript
/* eslint new-cap: [0] */
/* eslint no-console: [0] */
'use strict'
const Model = require('trails/model')
const Errors = require('proxy-engine-errors')
const helpers = require('proxy-engine-helpers')
const _ = require('lodash')
const queryDefaults = require('../utils/queryDefaults')
const COLLECTION_SORT_ORDER = require('../../lib').Enums.COLLECTION_SORT_ORDER
const COLLECTION_PURPOSE = require('../../lib').Enums.COLLECTION_PURPOSE
const COLLECTION_DISCOUNT_SCOPE = require('../../lib').Enums.COLLECTION_DISCOUNT_SCOPE
const COLLECTION_DISCOUNT_TYPE = require('../../lib').Enums.COLLECTION_DISCOUNT_TYPE
const COLLECTION_TAX_TYPE = require('../../lib').Enums.COLLECTION_TAX_TYPE
const COLLECTION_SHIPPING_TYPE = require('../../lib').Enums.COLLECTION_SHIPPING_TYPE
/**
* @module ProductCollection
* @description Product Collection Model
*/
module.exports = class Collection extends Model {
static config (app, Sequelize) {
return {
options: {
underscored: true,
enums: {
COLLECTION_SORT_ORDER: COLLECTION_SORT_ORDER,
COLLECTION_PURPOSE: COLLECTION_PURPOSE,
COLLECTION_DISCOUNT_SCOPE: COLLECTION_DISCOUNT_SCOPE,
COLLECTION_DISCOUNT_TYPE: COLLECTION_DISCOUNT_TYPE,
COLLECTION_TAX_TYPE: COLLECTION_TAX_TYPE,
},
scopes: {
live: {
where: {
live_mode: true
}
}
},
hooks: {
beforeValidate(values, options) {
if (!values.handle && values.title) {
values.handle = values.title
}
},
beforeCreate(values, options) {
if (values.body) {
const bodyDoc = app.services.RenderGenericService.renderSync(values.body)
values.body_html = bodyDoc.document
}
if (values.excerpt) {
const excerptDoc = app.services.RenderGenericService.renderSync(values.excerpt)
values.excerpt_html = excerptDoc.document
}
},
beforeUpdate(values, options) {
if (values.body) {
const bodyDoc = app.services.RenderGenericService.renderSync(values.body)
values.body_html = bodyDoc.document
}
if (values.excerpt) {
const excerptDoc = app.services.RenderGenericService.renderSync(values.excerpt)
values.excerpt_html = excerptDoc.document
}
}
},
classMethods: {
/**
*
* @param models
*/
associate: (models) => {
// Product Assoc
models.Collection.belongsToMany(models.Product, {
as: 'products',
through: {
model: models.ItemCollection,
unique: false,
scope: {
model: 'product'
},
constraints: false
},
foreignKey: 'collection_id',
otherKey: 'model_id',
constraints: false
})
// Collection Assoc
models.Collection.belongsToMany(models.Collection, {
as: 'collections',
through: {
model: models.ItemCollection,
unique: false,
scope: {
model: 'collection'
},
constraints: false
},
// foreignKey: 'model_id',
// otherKey: 'collection_id',
foreignKey: 'collection_id',
otherKey: 'model_id',
constraints: false
})
// Customer Assoc
models.Collection.belongsToMany(models.Customer, {
as: 'customers',
through: {
model: models.ItemCollection,
unique: false,
scope: {
model: 'customer'
},
constraints: false
},
foreignKey: 'collection_id',
otherKey: 'model_id',
constraints: false
})
// Discount Assoc
models.Collection.belongsToMany(models.Discount, {
as: 'discounts',
through: {
model: models.ItemDiscount,
unique: false,
scope: {
model: 'collection'
}
},
foreignKey: 'model_id',
constraints: false
})
// Images Assoc
models.Collection.belongsToMany(models.Image, {
as: 'images',
through: {
model: models.ItemImage,
unique: false,
scope: {
model: 'collection'
},
constraints: false
},
foreignKey: 'model_id',
constraints: false
})
// Metadata Assoc
models.Collection.hasOne(models.Metadata, {
as: 'metadata',
foreignKey: 'collection_id'
})
models.Collection.belongsToMany(models.Tag, {
as: 'tags',
through: {
model: models.ItemTag,
unique: false,
scope: {
model: 'collection'
}
},
foreignKey: 'model_id',
otherKey: 'tag_id',
constraints: false
})
},
findByIdDefault: function(id, options) {
options = app.services.ProxyEngineService.mergeOptionDefaults(
queryDefaults.Collection.default(app),
options || {}
)
return this.findById(id, options)
},
findByHandleDefault: function(handle, options) {
options = app.services.ProxyEngineService.mergeOptionDefaults(
queryDefaults.Collection.default(app),
{
where: {
handle: handle
}
},
options || {}
)
return this.findOne(options)
},
findOneDefault: function(options) {
options = app.services.ProxyEngineService.mergeOptionDefaults(
queryDefaults.Collection.default(app),
options || {}
)
return this.findOne(options)
},
findAllDefault: function(options) {
options = app.services.ProxyEngineService.mergeOptionDefaults(
queryDefaults.Collection.default(app),
options || {}
)
return this.findAll(options)
},
findAndCountDefault: function(options) {
options = app.services.ProxyEngineService.mergeOptionDefaults(
queryDefaults.Collection.findAndCountDefault(app),
options || {}
)
return this.findAndCount(options)
},
/**
*
* @param collection
* @param options
* @returns {*}
*/
resolve: function(collection, options){
options = options || {}
const Collection = this
if (collection instanceof Collection){
return Promise.resolve(collection)
}
else if (collection && _.isObject(collection) && collection.id) {
return Collection.findById(collection.id, options)
.then(foundCollection => {
if (!foundCollection) {
throw new Errors.FoundError(Error(`Collection ${collection.id} not found`))
}
return foundCollection
})
}
else if (collection && _.isObject(collection) && collection.handle) {
return Collection.findOne(app.services.ProxyEngineService.mergeOptionDefaults(
options,
{
where: {
handle: collection.handle
}
}
)
)
.then(resCollection => {
if (resCollection) {
return resCollection
}
collection.title = collection.title || collection.handle
return app.services.CollectionService.create(collection, {transaction: options.transaction})
})
}
else if (collection && _.isObject(collection) && collection.title) {
return Collection.findOne(options = app.services.ProxyEngineService.mergeOptionDefaults(
options,
{
where: {
handle: app.services.ProxyCartService.handle(collection.title)
}
}
)
)
.then(resCollection => {
if (resCollection) {
return resCollection
}
collection.handle = collection.handle || app.ProxyCartService.handle(collection.title)
return app.services.CollectionService.create(collection, {transaction: options.transaction})
})
}
else if (collection && _.isNumber(collection)) {
return Collection.findById(collection, options)
.then(foundCollection => {
if (!foundCollection) {
throw new Errors.FoundError(Error(`Collection ${collection.id} not found`))
}
return foundCollection
})
}
else if (collection && _.isString(collection)) {
return Collection.findOne(options = app.services.ProxyEngineService.mergeOptionDefaults(
options,
{
where: {
handle: app.services.ProxyCartService.handle(collection)
}
}
)
)
.then(resCollection => {
if (resCollection) {
return resCollection
}
return app.services.CollectionService.create({
handle: app.services.ProxyCartService.handle(collection),
title: collection
}, {
transaction: options.transaction || null
})
})
}
else {
// TODO make Proper Error
const err = new Error(`Not able to resolve collection ${collection}`)
return Promise.reject(err)
}
},
/**
*
* @param collections
* @param options
* @returns {Promise.<T>}
*/
transformCollections: (collections, options) => {
options = options || {}
collections = collections || []
const Collection = app.orm['Collection']
const Sequelize = Collection.sequelize
// Transform if necessary to objects
collections = collections.map(collection => {
if (collection && _.isNumber(collection)) {
return { id: collection }
}
else if (collection && _.isString(collection)) {
return {
handle: app.services.ProxyCartService.handle(collection),
title: collection
}
}
else if (collection && _.isObject(collection) && (collection.title || collection.handle)) {
collection.handle = app.services.ProxyCartService.handle(collection.handle) || app.services.ProxyCartService.handle(collection.title)
return collection
}
})
// Filter out undefined
collections = collections.filter(collection => collection)
return Sequelize.Promise.mapSeries(collections, collection => {
return Collection.findOne({
where: _.pick(collection, ['id','handle']),
attributes: ['id', 'handle', 'title'],
transaction: options.transaction || null
})
.then(foundCollection => {
if (foundCollection) {
return _.extend(foundCollection, collection)
}
else {
return app.services.CollectionService.create(collection, {
transaction: options.transaction || null
})
.then(createdCollection => {
return _.extend(createdCollection, collection)
})
}
})
})
},
/**
*
* @param collections
*/
reverseTransformCollections: (collections) => {
collections = collections || []
collections.map(collection => {
if (collection && _.isString(collection)) {
return collection
}
else if (collection && collection.title) {
return collection.title
}
})
return collections
}
},
instanceMethods: {
/**
* TODO, this should likely be done with a view
* Format return data
* Converts tags to array of strings
* Returns only metadata data
*/
toJSON: function() {
// Make JSON
const resp = this instanceof app.orm['Collection'] ? this.get({ plain: true }) : this
// Set Defaults
// resp.calculated_price = resp.price
// Transform Tags to array on toJSON
if (resp.tags) {
// console.log(resp.tags)
resp.tags = resp.tags.map(tag => {
if (tag && _.isString(tag)) {
return tag
}
else if (tag && tag.name && tag.name !== '') {
return tag.name
}
})
}
// Transform Metadata to plain on toJSON
if (resp.metadata) {
if (typeof resp.metadata.data !== 'undefined') {
resp.metadata = resp.metadata.data
}
}
return resp
},
/**
*
* @param options
* @returns {*}
*/
resolveMetadata: function(options) {
options = options || {}
if (
this.metadata
&& this.metadata instanceof app.orm['Metadata']
&& options.reload !== true
) {
return Promise.resolve(this)
}
else {
return this.getMetadata({transaction: options.transaction || null})
.then(_metadata => {
_metadata = _metadata || {collection_id: this.id}
this.metadata = _metadata
this.setDataValue('metadata', _metadata)
this.set('metadata', _metadata)
return this
})
}
},
resolveDiscounts: function(options) {
options = options || {}
if (
this.discounts
&& this.discount.every(d => d instanceof app.orm['Discount'])
&& options.reload !== true
) {
return Promise.resolve(this)
}
else {
return this.getDiscounts({transaction: options.transaction || null})
.then(_discounts => {
_discounts = _discounts || []
this.discounts = _discounts
this.setDataValue('discounts', _discounts)
this.set('discounts', _discounts)
return this
})
}
}
}
}
}
}
static schema (app, Sequelize) {
return {
// The handle of the Collection
handle: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
set: function(val) {
this.setDataValue('handle', app.services.ProxyCartService.splitHandle(val) || null)
}
},
// The title of the Collection
title: {
type: Sequelize.STRING,
allowNull: false,
set: function(val) {
this.setDataValue('title', app.services.ProxyCartService.title(val))
}
// unique: true
},
// SEO title
seo_title: {
type: Sequelize.STRING,
set: function(val) {
this.setDataValue('seo_title', app.services.ProxyCartService.title(val))
}
},
// SEO description
seo_description: {
type: Sequelize.TEXT,
set: function(val) {
this.setDataValue('seo_description', app.services.ProxyCartService.description(val))
}
},
// The purpose of the collection
primary_purpose: {
type: Sequelize.ENUM,
values: _.values(COLLECTION_PURPOSE),
defaultValue: COLLECTION_PURPOSE.GROUP
},
// The id of the Shop
shop_id: {
type: Sequelize.INTEGER
},
// A description in text
description: {
type: Sequelize.TEXT
},
// An excerpt of the body of a collection (in markdown or html)
excerpt: {
type: Sequelize.TEXT
},
// The excerpt html of a collection (DO NOT EDIT DIRECTLY)
excerpt_html: {
type: Sequelize.TEXT
},
// The body of a collection (in markdown or html)
body: {
type: Sequelize.TEXT
},
// The html of a collection (DO NOT EDIT DIRECTLY)
body_html: {
type: Sequelize.TEXT
},
// If the Collection is published
published: {
type: Sequelize.BOOLEAN,
defaultValue: true
},
// When the Collection was published
published_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW
},
// The scope Collection is published
published_scope: {
type: Sequelize.STRING,
defaultValue: 'global'
},
// When the collection was unpublished
unpublished_at: {
type: Sequelize.DATE
},
// The position this collection is in reference to the other collections when displayed.
position: {
type: Sequelize.INTEGER,
defaultValue: 0
},
// The way Items are displayed in this collection
sort_order: {
type: Sequelize.ENUM,
values: _.values(COLLECTION_SORT_ORDER),
defaultValue: COLLECTION_SORT_ORDER.ALPHA_DESC
},
// TODO Tax Override for products in this collection
// The type of a tax modifier
tax_type: {
type: Sequelize.ENUM,
values: _.values(COLLECTION_TAX_TYPE),
defaultValue: COLLECTION_TAX_TYPE.PERCENTAGE
},
// The rate of added tax if tax_type is a rate
tax_rate: {
type: Sequelize.FLOAT,
defaultValue: 0.0
},
// The percentage of added tax if tax_type is a percentage
tax_percentage: {
type: Sequelize.FLOAT,
defaultValue: 0.0
},
// The name of the tax modifier
tax_name: {
type: Sequelize.STRING
},
// TODO Shipping Override for products in this collection
// The type of the shipping modifier (rate, percentage)
shipping_type: {
type: Sequelize.ENUM,
values: _.values(COLLECTION_SHIPPING_TYPE),
defaultValue: COLLECTION_SHIPPING_TYPE.PERCENTAGE
},
// The shipping rate to be applied if shipping_type is rate
shipping_rate: {
type: Sequelize.FLOAT,
defaultValue: 0.0
},
// The shipping percentage to be applied if shipping_type is percentage
shipping_percentage: {
type: Sequelize.FLOAT,
defaultValue: 0.0
},
// The name of the shipping modifier
shipping_name: {
type: Sequelize.STRING
},
// // The scope of the discount price modifier for the collection (individual, global)
// discount_scope: {
// type: Sequelize.ENUM,
// values: _.values(COLLECTION_DISCOUNT_SCOPE),
// defaultValue: COLLECTION_DISCOUNT_SCOPE.INDIVIDUAL
// },
// // The type of the discount modifier (rate, percentage)
// discount_type: {
// type: Sequelize.ENUM,
// values: _.values(COLLECTION_DISCOUNT_TYPE),
// defaultValue: COLLECTION_DISCOUNT_TYPE.PERCENTAGE
// },
// // The discount rate if the discount_type is rate
// discount_rate: {
// type: Sequelize.FLOAT,
// defaultValue: 0.0
// },
// // A percentage to apply if the discount_type is percentage
// discount_percentage: {
// type: Sequelize.FLOAT,
// defaultValue: 0.0
// },
// // TODO allow product includes
// // List of product types allowed to discount
// discount_product_include: helpers.JSONB('Collection', app, Sequelize, 'discount_product_include', {
// defaultValue: []
// }),
// // List of product_type [<string>] to forcefully excluded from discount modifiers
// discount_product_exclude: helpers.JSONB('Collection', app, Sequelize, 'discount_product_exclude', {
// defaultValue: []
// }),
// List of product_type [<string>] to forcefully excluded from shipping modifiers
shipping_product_exclude: helpers.JSONB('Collection', app, Sequelize, 'shipping_product_exclude', {
defaultValue: []
}),
// List of product_type [<string>] to forcefully excluded from tax modifiers
tax_product_exclude: helpers.JSONB('Collection', app, Sequelize, 'tax_product_exclude', {
defaultValue: []
}),
// Live Mode
live_mode: {
type: Sequelize.BOOLEAN,
defaultValue: app.config.proxyEngine.live_mode
}
}
}
}