UNPKG

@fabrix/spool-cart

Version:

Spool - eCommerce Spool for Fabrix

920 lines (919 loc) 31.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@fabrix/fabrix/dist/common"); const spool_sequelize_1 = require("@fabrix/spool-sequelize"); const errors_1 = require("@fabrix/spool-sequelize/dist/errors"); const lodash_1 = require("lodash"); const shortId = require("shortid"); const queryDefaults_1 = require("../utils/queryDefaults"); const enums_1 = require("../../enums"); const enums_2 = require("../../enums"); class CustomerResolver extends spool_sequelize_1.SequelizeResolver { findByIdDefault(id, options = {}) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Customer.default(this.app), options); return this.findById(id, options); } findByTokenDefault(token, options = {}) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Customer.default(this.app), options, { where: { token: token } }); return this.findOne(options); } findAndCountDefault(options = {}) { options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Customer.default(this.app), options || {}, { distinct: true }); return this.findAndCountAll(options); } resolveByInstance(cart, options = {}) { return Promise.resolve(cart); } resolveById(cart, options = {}) { return this.findById(cart.id, options) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Cart ${cart.id} not found`); } return resUser; }); } resolveByToken(cart, options = {}) { return this.findOne(lodash_1.defaultsDeep({ where: { token: cart.token } }, options)) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Cart token ${cart.token} not found`); } return resUser; }); } resolveByEmail(cart, options = {}) { return this.findOne(lodash_1.defaultsDeep({ where: { email: cart.email } }, options)) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Cart email ${cart.email} not found`); } return resUser; }); } resolveByNumber(cart, options = {}) { return this.findById(cart, options) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Cart ${cart.token} not found`); } return resUser; }); } resolveByString(cart, options = {}) { return this.findOne(lodash_1.defaultsDeep({ where: { token: cart } }, options)) .then(resUser => { if (!resUser && options.reject !== false) { throw new errors_1.ModelError('E_NOT_FOUND', `Cart ${cart} not found`); } return resUser; }); } resolve(customer, options = {}) { options.create = options.create || true; const CustomerModel = this; if (customer instanceof CustomerModel.instance) { return Promise.resolve(customer); } else if (customer && lodash_1.isObject(customer) && customer.id) { return CustomerModel.findById(customer.id, options) .then(resCustomer => { if (!resCustomer && options.create !== false) { return this.app.services.CustomerService.create(customer, options); } return resCustomer; }); } else if (customer && lodash_1.isObject(customer) && customer.email) { return CustomerModel.findOne(this.app.services.SequelizeService.mergeOptionDefaults(options, { where: { email: customer.email } })) .then(resCustomer => { if (!resCustomer && options.create !== false) { return this.app.services.CustomerService.create(customer, { transaction: options.transaction || null }); } return resCustomer; }); } else if (customer && lodash_1.isNumber(customer)) { return CustomerModel.findById(customer, options); } else if (customer && lodash_1.isString(customer)) { return CustomerModel.findOne(this.app.services.SequelizeService.mergeOptionDefaults(options, { where: { email: customer } })); } else if (options.create === false) { const err = new Error('Customer could not be resolved or created'); return Promise.reject(err); } else { return this.app.services.CustomerService.create(customer, options); } } } exports.CustomerResolver = CustomerResolver; class Customer extends common_1.FabrixModel { static get resolver() { return CustomerResolver; } static config(app, Sequelize) { return { options: { underscored: true, enums: { CUSTOMER_STATE: enums_1.CUSTOMER_STATE }, scopes: { live: { where: { live_mode: true } } }, hooks: { beforeCreate: [ (customer, options) => { if (customer.ip) { customer.create_ip = customer.ip; } if (!customer.token) { customer.token = `customer_${shortId.generate()}`; } } ], beforeUpdate: [ (customer, options) => { if (customer.ip) { customer.update_ip = customer.ip; } } ], afterCreate: [ (customer, options) => { return app.services.CustomerService.afterCreate(customer, options) .catch(err => { return Promise.reject(err); }); } ], afterUpdate: [ (customer, options) => { return app.services.CustomerService.afterUpdate(customer, options) .catch(err => { return Promise.reject(err); }); } ] }, getterMethods: { full_name: function () { if (this.first_name && this.last_name) { return `${this.first_name} ${this.last_name}`; } else if (this.company) { return `${this.company}`; } else { return null; } } } } }; } static schema(app, Sequelize) { return { token: { type: Sequelize.STRING, unique: true }, accepts_marketing: { type: Sequelize.BOOLEAN, defaultValue: true }, first_name: { type: Sequelize.STRING }, last_name: { type: Sequelize.STRING }, company: { type: Sequelize.STRING }, phone: { type: Sequelize.STRING }, email: { type: Sequelize.STRING, validate: { isEmail: true }, set: function (val) { return this.setDataValue('email', val ? val.toLowerCase() : null); } }, note: { type: Sequelize.STRING }, last_order_id: { type: Sequelize.INTEGER, }, last_order_name: { type: Sequelize.STRING }, state: { type: Sequelize.ENUM, values: lodash_1.values(enums_1.CUSTOMER_STATE), defaultValue: enums_1.CUSTOMER_STATE.ENABLED }, type: { type: Sequelize.STRING, defaultValue: 'default' }, tax_exempt: { type: Sequelize.BOOLEAN, defaultValue: false }, total_spent: { type: Sequelize.INTEGER, defaultValue: 0 }, avg_spent: { type: Sequelize.INTEGER, defaultValue: 0 }, currency: { type: Sequelize.STRING, defaultValue: app.config.get('cart.default_currency') || enums_2.CUSTOMER_DEFAULTS.CURRENCY }, total_orders: { type: Sequelize.INTEGER, defaultValue: 0 }, account_balance: { type: Sequelize.INTEGER, defaultValue: 0 }, verified_email: { type: Sequelize.BOOLEAN, defaultValue: false }, default_address_id: { type: Sequelize.INTEGER }, shipping_address_id: { type: Sequelize.INTEGER }, billing_address_id: { type: Sequelize.INTEGER }, ip: { type: Sequelize.STRING }, create_ip: { type: Sequelize.STRING }, update_ip: { type: Sequelize.STRING }, live_mode: { type: Sequelize.BOOLEAN, defaultValue: app.config.get('cart.live_mode') } }; } static associate(models) { models.Customer.belongsToMany(models.User, { as: 'owners', through: { model: models.UserItem, scope: { item: 'cart' } }, foreignKey: 'item_id', constraints: false }); models.Customer.belongsToMany(models.Address, { as: 'addresses', foreignKey: 'model_id', through: { model: models.ItemAddress, scope: { model: 'customer' }, constraints: false }, constraints: false }); models.Customer.belongsTo(models.Address, { as: 'shipping_address' }); models.Customer.belongsTo(models.Address, { as: 'billing_address' }); models.Customer.belongsTo(models.Address, { as: 'default_address' }); models.Customer.belongsToMany(models.Order, { as: 'orders', through: { model: models.CustomerOrder, unique: true }, foreignKey: 'customer_id' }); models.Customer.belongsTo(models.Order, { as: 'last_order', foreignKey: 'last_order_id', constraints: false }); models.Customer.belongsToMany(models.Tag, { as: 'tags', through: { model: models.ItemTag, unique: false, scope: { model: 'customer' } }, foreignKey: 'model_id', constraints: false }); models.Customer.belongsToMany(models.Collection, { as: 'collections', through: { model: models.ItemCollection, unique: false, scope: { model: 'customer' } }, foreignKey: 'model_id', constraints: false }); models.Customer.belongsToMany(models.Customer, { as: 'customers', through: { model: models.ItemCustomer, unique: false, scope: { model: 'customer' }, constraints: false }, foreignKey: 'model_id', otherKey: 'customer_id', constraints: false }); models.Customer.hasOne(models.Metadata, { as: 'metadata', foreignKey: 'customer_id' }); models.Customer.belongsToMany(models.Account, { as: 'accounts', through: { model: models.CustomerAccount, unique: false }, foreignKey: 'customer_id' }); models.Customer.belongsToMany(models.Source, { as: 'sources', through: { model: models.CustomerSource, unique: false }, foreignKey: 'customer_id' }); models.Customer.belongsToMany(models.User, { as: 'users', through: { model: models.CustomerUser, unique: true, }, foreignKey: 'customer_id' }); models.Customer.belongsToMany(models.Discount, { as: 'discounts', through: { model: models.ItemDiscount, unique: false, scope: { model: 'customer' } }, foreignKey: 'model_id', constraints: false }); models.Customer.hasOne(models.Cart, { as: 'default_cart', foreignKey: 'default_cart_id' }); models.Customer.belongsToMany(models.Cart, { as: 'carts', through: { model: models.CustomerCart, unique: false }, foreignKey: 'customer_id', constraints: false }); models.Customer.hasMany(models.Event, { as: 'events', foreignKey: 'object_id', scope: { object: 'customer' }, constraints: false }); models.Customer.belongsToMany(models.Event, { as: 'event_items', through: { model: models.EventItem, unique: false, scope: { object: 'customer' } }, foreignKey: 'object_id', constraints: false }); models.Customer.belongsToMany(models.Image, { as: 'images', through: { model: models.ItemImage, unique: false, scope: { model: 'customer' }, constraints: false }, foreignKey: 'model_id', constraints: false }); models.Customer.hasMany(models.DiscountEvent, { as: 'discount_events', foreignKey: 'customer_id' }); models.Customer.hasMany(models.AccountEvent, { as: 'account_events', foreignKey: 'customer_id' }); } } exports.Customer = Customer; Customer.prototype.getProductHistory = function (product, options = {}) { let hasPurchaseHistory = false, isSubscribed = false; return this.hasPurchaseHistory(product.id, options) .then(pHistory => { hasPurchaseHistory = pHistory; return this.isSubscribed(product.id, options); }) .then(pHistory => { isSubscribed = pHistory; return { has_purchase_history: hasPurchaseHistory, is_subscribed: isSubscribed }; }) .catch(err => { return { has_purchase_history: hasPurchaseHistory, is_subscribed: isSubscribed }; }); }; Customer.prototype.hasPurchaseHistory = function (productId, options = {}) { return this.app.models['OrderItem'].findOne({ where: { customer_id: this.id, product_id: productId, fulfillment_status: { $not: ['cancelled', 'pending', 'none'] } }, attributes: ['id'], transaction: options.transaction || null }) .then(pHistory => { if (pHistory) { return true; } else { return false; } }) .catch(err => { return false; }); }; Customer.prototype.isSubscribed = function (productId, options = {}) { return this.app.models['Subscription'].findOne({ where: { customer_id: this.id, active: true, line_items: { $contains: [{ product_id: productId }] } }, attributes: ['id'], transaction: options.transaction || null }) .then(pHistory => { if (pHistory) { return true; } else { return false; } }) .catch(err => { return false; }); }; Customer.prototype.getSalutation = function (options = {}) { let salutation = 'Customer'; if (this.full_name) { salutation = this.full_name; } else if (this.email) { salutation = this.email; } return salutation; }; Customer.prototype.getDefaultSource = function (options = {}) { const Source = this.app.models['Source']; return Source.findOne({ where: { customer_id: this.id, is_default: true }, transaction: options.transaction || null }) .then(source => { if (!source) { return Source.findOne({ where: { customer_id: this.id }, transaction: options.transaction || null }); } else { return source; } }) .then(source => { return source; }); }; Customer.prototype.setLastOrder = function (order) { this.last_order_name = order.name; this.last_order_id = order.id; return this; }; Customer.prototype.setTotalSpent = function (orderTotalDue) { this.total_spent = this.total_spent + orderTotalDue; return this; }; Customer.prototype.setTotalOrders = function () { this.total_orders = this.total_orders + 1; return this; }; Customer.prototype.setAvgSpent = function () { this.avg_spent = this.total_spent / this.total_orders; return this; }; Customer.prototype.setAccountBalance = function (newBalance) { this.account_balance = newBalance; return this; }; Customer.prototype.logAccountBalance = function (type = 'debit', price = 0, currency = 'USD', accountId, orderId, options = {}) { return this.createAccount_event({ type: type, price: price, account_id: accountId, order_id: orderId }, { transaction: options.transaction || null }) .then(_event => { const currencySymbol = this.app.services.ProxyCartService.formatCurrency(price, currency); const event = { object_id: this.id, object: 'customer', objects: [{ customer: this.id }], type: `customer.account_balance.${type}`, message: `Customer ${this.email || 'ID ' + this.id} account balance was ${type}ed by ${currencySymbol} ${currency}`, data: this }; return this.app.services.EventsService.publish(event.type, event, { save: true, transaction: options.transaction || null }); }) .then(_event => { const newBalance = type === 'debit' ? Math.max(0, this.account_balance - price) : this.account_balance + price; return this.setAccountBalance(newBalance); }); }; Customer.prototype.notifyUsers = function (preNotification, options = {}) { return this.resolveUsers({ attributes: ['id', 'email', 'username'], transaction: options.transaction || null, reload: options.reload || null, }) .then(() => { if (this.users && this.users.length > 0) { return this.app.services.NotificationService.create(preNotification, this.users, { transaction: options.transaction || null }) .then(notes => { this.app.log.debug('NOTIFY', this.id, this.email, this.users.map(u => u.id), preNotification.send_email, notes.users.map(u => u.id)); return notes; }); } else { return []; } }); }; Customer.prototype.resolveCollections = function (options = {}) { if (this.collections && this.collections.length > 0 && this.collections.every(d => d instanceof this.app.models['Collection'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getCollections({ transaction: options.transaction || null }) .then(_collections => { _collections = _collections || []; this.collections = _collections; this.setDataValue('collections', _collections); this.set('collections', _collections); return this; }); } }; Customer.prototype.resolveDiscounts = function (options = {}) { if (this.discounts && this.discounts.length > 0 && this.discounts.every(d => d instanceof this.app.models['Discount'].instance) && 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; }); } }; Customer.prototype.resolveMetadata = function (options = {}) { if (this.metadata && this.metadata instanceof this.app.models['Metadata'].instance && options.reload !== true) { return Promise.resolve(this); } else { return this.getMetadata({ transaction: options.transaction || null }) .then(_metadata => { _metadata = _metadata || { customer_id: this.id }; this.metadata = _metadata; this.setDataValue('metadata', _metadata); this.set('metadata', _metadata); return this; }); } }; Customer.prototype.resolveUsers = function (options = {}) { if (this.users && this.users.length > 0 && this.users.every(u => u instanceof this.app.models['User'].instance) && options.reload !== true) { return Promise.resolve(this); } else { return this.getUsers({ transaction: options.transaction || null }) .then(_users => { _users = _users || []; this.users = _users; this.setDataValue('users', _users); this.set('users', _users); return this; }); } }; Customer.prototype.resolveDefaultAddress = function (options = {}) { if (this.default_address && this.default_address instanceof this.app.models['Address'].instance && options.reload !== true) { return Promise.resolve(this); } else if (!this.default_address_id) { this.default_address = this.app.models['Address'].build({}); return Promise.resolve(this); } else { return this.getDefault_address({ transaction: options.transaction || null }) .then(address => { address = address || null; this.default_address = address; this.setDataValue('default_address', address); this.set('default_address', address); return this; }); } }; Customer.prototype.resolveShippingAddress = function (options = {}) { if (this.shipping_address && this.shipping_address instanceof this.app.models['Address'].instance && options.reload !== true) { return Promise.resolve(this); } else if (!this.shipping_address_id) { this.shipping_address = this.app.models['Address'].build({}); return Promise.resolve(this); } else { return this.getShipping_address({ transaction: options.transaction || null }) .then(address => { address = address || null; this.shipping_address = address; this.setDataValue('shipping_address', address); this.set('shipping_address', address); return this; }); } }; Customer.prototype.resolveBillingAddress = function (options = {}) { if (this.billing_address && this.billing_address instanceof this.app.models['Address'].instance && options.reload !== true) { return Promise.resolve(this); } else if (!this.billing_address_id) { this.billing_address = this.app.models['Address'].build({}); return Promise.resolve(this); } else { return this.getBilling_address({ transaction: options.transaction || null }) .then(address => { address = address || null; this.billing_address = address; this.setDataValue('billing_address', address); this.set('billing_address', address); return this; }); } }; Customer.prototype.resolvePaymentDetailsToSources = (options = {}) => { return this; }; Customer.prototype.sendRetargetEmail = function (options = {}) { return this.app.emails.Customer.retarget(this, { send_email: this.app.config.get('cart.emails.customerRetarget') }, { transaction: options.transaction || null }) .then(email => { return this.notifyUsers(email, { transaction: options.transaction || null }); }) .catch(err => { this.app.log.error(err); return; }); }; Customer.prototype.updateDefaultAddress = function (address, options = {}) { const Address = this.app.models['Address']; const defaultUpdate = Address.cleanAddress(address); return this.resolveDefaultAddress({ transaction: options.transaction || null }) .then(() => { if (address.id || address.token) { return Address.resolve(address, { transaction: options.transaction || null }) .then(_address => { return _address.update(defaultUpdate, { transaction: options.transaction || null }); }); } else { return this.default_address .merge(defaultUpdate) .save({ transaction: options.transaction || null }); } }) .then(defaultAddress => { this.default_address = defaultAddress; this.setDataValue('default_address', defaultAddress); this.set('default_address', defaultAddress); if (this.default_address_id !== defaultAddress.id) { return this.setDefault_address(defaultAddress.id, { transaction: options.transaction || null }); } return this; }); }; Customer.prototype.updateShippingAddress = function (address, options = {}) { const Address = this.app.models['Address']; const shippingUpdate = Address.cleanAddress(address); return this.resolveShippingAddress({ transaction: options.transaction || null }) .then(() => { if (address.id || address.token) { return Address.resolve(address, { transaction: options.transaction || null }) .then(_address => { return _address.update(shippingUpdate, { transaction: options.transaction || null }); }); } else { return this.shipping_address .merge(shippingUpdate) .save({ transaction: options.transaction || null }); } }) .then(shippingAddress => { this.shipping_address = shippingAddress; this.setDataValue('shipping_address', shippingAddress); this.set('shipping_address', shippingAddress); if (this.shipping_address_id !== shippingAddress.id) { return this.setShipping_address(shippingAddress.id, { transaction: options.transaction || null }); } return this; }); }; Customer.prototype.updateBillingAddress = function (address, options = {}) { const Address = this.app.models['Address']; const billingUpdate = Address.cleanAddress(address); return this.resolveBillingAddress({ transaction: options.transaction || null }) .then(() => { if (address.id || address.token) { return Address.resolve(address, { transaction: options.transaction || null }) .then(_address => { return _address.update(billingUpdate, { transaction: options.transaction || null }); }); } else { return this.billing_address .merge(billingUpdate) .save({ transaction: options.transaction || null }); } }) .then(billingAddress => { this.billing_address = billingAddress; this.setDataValue('billing_address', billingAddress); this.set('billing_address', billingAddress); if (this.billing_address_id !== billingAddress.id) { return this.setBilling_address(billingAddress.id, { transaction: options.transaction || null }); } return this; }); }; Customer.prototype.toJSON = function () { const resp = this instanceof this.app.models['Customer'].instance ? this.get({ plain: true }) : this; if (resp.tags) { resp.tags = resp.tags.map(tag => { if (lodash_1.isString(tag)) { return tag; } return tag.name; }); } else { resp.tags = []; } if (resp.metadata) { if (typeof resp.metadata.data !== 'undefined') { resp.metadata = resp.metadata.data; } } else { resp.metadata = {}; } return resp; };