UNPKG

trailpack-proxy-cart

Version:

eCommerce - Trailpack for Proxy Engine

538 lines (528 loc) 17.5 kB
/* eslint new-cap: [0] */ /* eslint no-console: [0] */ 'use strict' const Model = require('trails/model') const _ = require('lodash') const helpers = require('proxy-engine-helpers') const Errors = require('proxy-engine-errors') const queryDefaults = require('../utils/queryDefaults') const FULFILLMENT_STATUS = require('../../lib').Enums.FULFILLMENT_STATUS const FULFILLMENT_SERVICE = require('../../lib').Enums.FULFILLMENT_SERVICE /** * @module Fulfillment * @description Fulfillment Model */ module.exports = class Fulfillment extends Model { /** * * @param app * @param Sequelize * @returns {{}} */ static config (app, Sequelize) { return { options: { underscored: true, // defaultScope: { // where: { // live_mode: app.config.proxyEngine.live_mode // } // }, scopes: { live: { where: { live_mode: true } }, none: { where: { status: FULFILLMENT_STATUS.NONE } }, pending: { where: { status: FULFILLMENT_STATUS.PENDING } }, sent: { where: { status: FULFILLMENT_STATUS.SENT } }, partial: { where: { status: FULFILLMENT_STATUS.PARTIAL } }, fulfilled: { where: { status: FULFILLMENT_STATUS.FULFILLED } }, cancelled: { where: { status: FULFILLMENT_STATUS.CANCELLED } }, }, hooks: { beforeCreate: (values, options) => { return app.services.FulfillmentService.beforeCreate(values, options) .catch(err => { return Promise.reject(err) }) }, beforeUpdate: (values, options) => { return app.services.FulfillmentService.beforeUpdate(values, options) .catch(err => { return Promise.reject(err) }) }, afterCreate: (values, options) => { return app.services.FulfillmentService.afterCreate(values, options) .catch(err => { return Promise.reject(err) }) }, afterUpdate: (values, options) => { return app.services.FulfillmentService.afterUpdate(values, options) .catch(err => { return Promise.reject(err) }) } }, classMethods: { FULFILLMENT_STATUS: FULFILLMENT_STATUS, FULFILLMENT_SERVICE: FULFILLMENT_SERVICE, /** * Associate the Model * @param models */ associate: (models) => { models.Fulfillment.belongsTo(models.Order, { foreignKey: 'order_id', // allowNull: false }) models.Fulfillment.hasMany(models.OrderItem, { as: 'order_items', foreignKey: 'fulfillment_id', }) }, findByIdDefault: function(id, options) { options = app.services.ProxyEngineService.mergeOptionDefaults( queryDefaults.Fulfillment.default(app), options || {} ) return this.findById(id, options) }, findAndCountDefault: function(options) { options = app.services.ProxyEngineService.mergeOptionDefaults( queryDefaults.Fulfillment.default(app), options || {} ) return this.findAndCount(options) }, /** * * @param fulfillment * @param options * @returns {*} */ resolve: function(fulfillment, options){ options = options || {} const Fulfillment = this if (fulfillment instanceof Fulfillment){ return Promise.resolve(fulfillment) } else if (fulfillment && _.isObject(fulfillment) && fulfillment.id) { return Fulfillment.findByIdDefault(fulfillment.id, options) .then(resFulfillment => { if (!resFulfillment) { throw new Errors.FoundError(Error(`Fulfillment ${fulfillment.id} not found`)) } return resFulfillment }) } else if (fulfillment && (_.isString(fulfillment) || _.isNumber(fulfillment))) { return Fulfillment.findByIdDefault(fulfillment, options) .then(resFulfillment => { if (!resFulfillment) { throw new Errors.FoundError(Error(`Fulfillment ${fulfillment} not found`)) } return resFulfillment }) } else { const err = new Error('Unable to resolve Fulfillment') return Promise.reject(err) } } }, instanceMethods: { /** * * @returns {*} */ pending: function() { return this }, /** * * @returns {*} */ none: function() { return this }, /** * * @returns {*} */ partial: function() { return this }, /** * * @returns {*} */ sent: function() { this.sent_at = new Date(Date.now()) this.status = FULFILLMENT_STATUS.SENT return this }, /** * * @returns {*} */ fulfilled: function() { this.fulfilled_at = new Date(Date.now()) this.status = FULFILLMENT_STATUS.FULFILLED return this }, /** * * @returns {*} */ cancelled: function() { this.cancelled_at = new Date(Date.now()) this.status = FULFILLMENT_STATUS.CANCELLED return this }, /** * * @param data */ fulfillUpdate: function (data, options) { data = data || {} options = options || {} return this.resolveOrderItems({ transaction: options.transaction || null, reload: options.reload || null }) .then(() => { this.status = data.status || this.status this.status_url = data.status_url || this.status_url this.tracking_company = data.tracking_company || this.tracking_company this.tracking_number = data.tracking_number || this.tracking_number this.extras = data.extras || this.extras this.receipt = data.receipt || this.receipt return this.sequelize.Promise.mapSeries(this.order_items, item => { item.fulfillment_status = this.status return item.save({ fields: ['fulfillment_status'], transaction: options.transaction || null }) }) }) .then(() => { return this.saveFulfillmentStatus({transaction: options.transaction || null}) }) }, /** * * @param options * @returns {Promise.<T>} */ reconcileFulfillmentStatus: function (options) { options = options || {} return this.resolveFulfillmentStatus({ transaction: options.transaction || null, reload: options.reload || null }) .then(() => { if (this.changed('status')) { return this.getOrder({transaction: options.transaction || null}) } else { return null } }) .then(resOrder => { if (resOrder) { return resOrder.saveFulfillmentStatus({transaction: options.transaction || null}) } else { return null } }) .then(resOrder => { if (resOrder) { // Save the status changes return resOrder.saveStatus({transaction: options.transaction || null}) } else { return null } }) .then(() => { return this }) }, /** * * @param options * @returns {*} */ resolveFulfillmentStatus: function(options) { options = options || {} // let currentStatus, previousStatus if (!this.id){ return Promise.resolve(this) } return this.resolveOrderItems({ transaction: options.transaction || null, reload: options.reload || null }) .then(() => { this.setFulfillmentStatus() return this }) }, /** * * @param options * @returns {*} */ resolveOrderItems: function(options) { options = options || {} if ( this.order_items && this.order_items.every(i => i instanceof app.orm['OrderItem']) && options.reload !== true ) { return Promise.resolve(this) } else { return this.getOrder_items({transaction: options.transaction || null}) .then(orderItems => { orderItems = orderItems || [] this.order_items = orderItems this.setDataValue('order_items', orderItems) this.set('order_items', orderItems) return this }) } }, /** * * @param options * @returns {Promise.<T>} */ saveFulfillmentStatus: function (options) { options = options || {} return this.resolveFulfillmentStatus({ transaction: options.transaction || null, reload: options.reload || null }) .then(() => { return this.save({transaction: options.transaction || null}) }) }, /** * * @returns {*} */ setFulfillmentStatus: function(){ if (!this.order_items) { throw new Error('Fulfillment.setFulfillmentStatus requires order_items to be populated') } let fulfillmentStatus = FULFILLMENT_STATUS.PENDING let totalFulfillments = 0 let totalPartialFulfillments = 0 let totalSentFulfillments = 0 let totalNonFulfillments = 0 let totalPendingFulfillments = 0 let totalCancelledFulfillments = 0 let totalQty = 0 this.order_items.forEach(item => { totalQty = totalQty + item.quantity if (item.fulfillment_status === FULFILLMENT_STATUS.FULFILLED) { totalFulfillments = totalFulfillments + item.quantity } else if (item.fulfillment_status === FULFILLMENT_STATUS.PARTIAL) { totalPartialFulfillments = totalPartialFulfillments + item.quantity } else if (item.fulfillment_status === FULFILLMENT_STATUS.SENT) { totalSentFulfillments = totalSentFulfillments + item.quantity } else if (item.fulfillment_status === FULFILLMENT_STATUS.PENDING) { totalPendingFulfillments = totalPendingFulfillments + item.quantity } else if (item.fulfillment_status === FULFILLMENT_STATUS.NONE) { totalNonFulfillments = totalNonFulfillments + item.quantity } else if (item.fulfillment_status === FULFILLMENT_STATUS.CANCELLED) { totalCancelledFulfillments = totalCancelledFulfillments + item.quantity } }) if (totalFulfillments === totalQty && totalQty > 0) { fulfillmentStatus = FULFILLMENT_STATUS.FULFILLED } else if (totalSentFulfillments === totalQty && totalQty > 0) { fulfillmentStatus = FULFILLMENT_STATUS.SENT } else if (totalPartialFulfillments > 0 && totalQty > 0) { fulfillmentStatus = FULFILLMENT_STATUS.PARTIAL } else if (totalPendingFulfillments === totalQty && totalQty > 0) { fulfillmentStatus = FULFILLMENT_STATUS.PENDING // back to default } else if (totalNonFulfillments === totalQty && totalQty > 0) { fulfillmentStatus = FULFILLMENT_STATUS.NONE // back to default } else if (totalCancelledFulfillments === totalQty && totalQty > 0) { fulfillmentStatus = FULFILLMENT_STATUS.CANCELLED } this.has_shipping = this.order_items.some(i => i.requires_shipping === true) this.status = fulfillmentStatus this.total_items = totalQty this.total_fulfilled = totalFulfillments this.total_sent_to_fulfillment = totalSentFulfillments this.total_pending_fulfillments = totalPendingFulfillments this.total_cancelled = totalCancelledFulfillments return this } } } } } /** * * @param app * @param Sequelize * @returns {{}} */ static schema (app, Sequelize) { return { order_id: { type: Sequelize.INTEGER, // references: { // model: 'Order', // key: 'id' // }, allowNull: false }, // The receipt given by fulfillment service receipt: { type: Sequelize.TEXT }, //The status of the fulfillment: // pending: in process of doing something // none: nothing has happened // sent: the fulfillment has been sent to fulfillment service // fulfilled: has been fulfilled // partial: has been partially fulfilled (in case of split orders) // cancelled: has been cancelled status: { type: Sequelize.ENUM, values: _.values(FULFILLMENT_STATUS), defaultValue: FULFILLMENT_STATUS.PENDING }, // The total number of order items in this instance total_items: { type: Sequelize.INTEGER, defaultValue: 0 }, // Total Order Items Fulfilled total_fulfilled: { type: Sequelize.INTEGER, defaultValue: 0 }, // Total Order Items Sent to Fulfillment total_sent_to_fulfillment: { type: Sequelize.INTEGER, defaultValue: 0 }, // Total Order Items Cancelled by Fulfillment total_cancelled: { type: Sequelize.INTEGER, defaultValue: 0 }, // Total Order Items not Fulfilled total_pending_fulfillments: { type: Sequelize.INTEGER, defaultValue: 0 }, // The URL pointing to the order status web page. status_url: { type: Sequelize.STRING }, // If this fulfillment contains items that need to be shipped. has_shipping: { type: Sequelize.BOOLEAN, defaultValue: false }, // The name of the fulfillment service provider service: { type: Sequelize.STRING, defaultValue: FULFILLMENT_SERVICE.MANUAL }, //The name of the tracking company. tracking_company: { type: Sequelize.STRING }, // The shipping number, provided by the shipping company. tracking_number: { type: Sequelize.STRING }, // Extra attributes in JSON to send to fulfillment: // Create a return label to be included in the shipment // "include_return_label": true, // If a signature confirmation is required // "require_signature_confirmation": true, // "insurance": { // "amount": "200", // "currency": "USD", // "provider": "FEDEX" // "content": "t-shirts" // }, // "alcohol": { // "contains_alcohol": true, // boolean // "recipient_type": "licensee" // // }, // "dry_ice": { // "contains_dry_ice": true, // boolean // "weight": "0.1" // Weight in grams // } extras: helpers.JSONB('Fulfillment', app, Sequelize, 'extras', { defaultValue: {} }), // Live mode live_mode: { type: Sequelize.BOOLEAN, defaultValue: app.config.proxyEngine.live_mode }, // Date time sent to fulfillment at sent_at: { type: Sequelize.DATE }, // Date time fulfilled at fulfilled_at: { type: Sequelize.DATE }, // Date time cancelled at cancelled_at: { type: Sequelize.DATE } } } }