@fabrix/spool-cart
Version:
Spool - eCommerce Spool for Fabrix
1,368 lines • 51.7 kB
JavaScript
"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 enums_1 = require("../../enums");
const enums_2 = require("../../enums");
const enums_3 = require("../../enums");
const queryDefaults_1 = require("../utils/queryDefaults");
class CartResolver extends spool_sequelize_1.SequelizeResolver {
findByIdDefault(id, options = {}) {
if (typeof id !== 'number' && typeof id !== 'string') {
throw new Error('Id was not a string or a number');
}
options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Cart.default(this.app), options);
return this.findById(id, options);
}
findOneDefault(options = {}) {
options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Cart.default(this.app), options);
return this.findOne(options);
}
findByTokenDefault(token, options = {}) {
if (typeof token !== 'string') {
throw new Error('Token is not a string');
}
options = this.app.services.SequelizeService.mergeOptionDefaults(queryDefaults_1.Cart.default(this.app), options, {
where: {
token: token
}
});
return this.findOne(options);
}
resolveByInstance(cart, options = {}) {
return Promise.resolve(cart);
}
resolveById(cart, options = {}) {
if (!(cart instanceof Object)) {
return Promise.reject(new Error('resolveById requires an object with an id property'));
}
return this.findByIdDefault(cart.id, options)
.then(resCart => {
if (!resCart && options.reject !== false) {
throw new errors_1.ModelError('E_NOT_FOUND', `Cart ${cart.id} not found`);
}
return resCart;
});
}
resolveByToken(cart, options = {}) {
if (!(cart instanceof Object)) {
return Promise.reject(new Error('resolveByToken requires an object with a token property'));
}
if (!(cart.token)) {
return Promise.reject(new Error('resolveByToken requires an object with a token property'));
}
return this.findByTokenDefault(cart.token, options)
.then(resCart => {
if (!resCart && options.reject !== false) {
throw new errors_1.ModelError('E_NOT_FOUND', `Cart token ${cart.token} not found`);
}
return resCart;
});
}
resolveByNumber(cart, options = {}) {
return this.findByIdDefault(cart, options)
.then(resCart => {
if (!resCart && options.reject !== false) {
throw new errors_1.ModelError('E_NOT_FOUND', `Cart ${cart.token} not found`);
}
return resCart;
});
}
resolveByString(cart, options = {}) {
return this.findByTokenDefault(cart, options)
.then(resCart => {
if (!resCart && options.reject !== false) {
throw new errors_1.ModelError('E_NOT_FOUND', `Cart ${cart} not found`);
}
return resCart;
});
}
resolve(cart, options = {}) {
const resolvers = {
'instance': cart instanceof this.instance,
'id': !!(cart && lodash_1.isObject(cart) && cart.id),
'token': !!(cart && lodash_1.isObject(cart) && cart.token),
'create': !!(cart && lodash_1.isObject(cart) && options.create !== false),
'number': !!(cart && lodash_1.isNumber(cart)),
'string': !!(cart && lodash_1.isString(cart))
};
const type = Object.keys(resolvers).find((key) => resolvers[key]);
switch (type) {
case 'instance': {
return this.resolveByInstance(cart, options);
}
case 'id': {
return this.resolveById(cart, options);
}
case 'token': {
return this.resolveByToken(cart, options);
}
case 'create': {
return this.create(cart, options);
}
case 'number': {
return this.resolveByNumber(cart, options);
}
case 'string': {
return this.resolveByString(cart, options);
}
default: {
const err = new Error(`Unable to resolve Cart ${cart}`);
return Promise.reject(err);
}
}
}
}
exports.CartResolver = CartResolver;
class Cart extends common_1.FabrixModel {
static get resolver() {
return CartResolver;
}
static config(app, Sequelize) {
return {
options: {
enums: {
CART_STATUS: enums_1.CART_STATUS
},
underscored: true,
scopes: {
live: {
where: {
live_mode: true
}
}
},
indexes: [
{
fields: ['line_items'],
using: 'gin',
operator: 'jsonb_path_ops'
},
{
fields: ['client_details'],
using: 'gin',
operator: 'jsonb_path_ops'
}
],
hooks: {
beforeCreate: [
(cart, options) => {
return app.services.CartService.beforeCreate(cart, options)
.catch(err => {
return Promise.reject(err);
});
}
],
beforeUpdate: [
(cart, options) => {
return app.services.CartService.beforeUpdate(cart, options)
.catch(err => {
return Promise.reject(err);
});
}
],
beforeSave: [
(cart, options) => {
return app.services.CartService.beforeSave(cart, options)
.catch(err => {
return Promise.reject(err);
});
}
]
}
}
};
}
static schema(app, Sequelize) {
return {
customer_id: {
type: Sequelize.INTEGER,
},
shop_id: {
type: Sequelize.INTEGER,
},
order_id: {
type: Sequelize.INTEGER,
},
billing_address_id: {
type: Sequelize.INTEGER,
},
shipping_address_id: {
type: Sequelize.INTEGER,
},
token: {
type: Sequelize.STRING,
unique: true
},
status: {
type: Sequelize.ENUM,
values: lodash_1.values(enums_1.CART_STATUS),
defaultValue: enums_1.CART_STATUS.OPEN
},
currency: {
type: Sequelize.STRING,
defaultValue: 'USD'
},
line_items: {
type: Sequelize.JSONB,
defaultValue: []
},
subtotal_price: {
type: Sequelize.INTEGER,
defaultValue: 0
},
discounted_lines: {
type: Sequelize.JSONB,
defaultValue: []
},
coupon_lines: {
type: Sequelize.JSONB,
defaultValue: []
},
shipping_lines: {
type: Sequelize.JSONB,
defaultValue: []
},
shipping_included: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
shipping_rate: {
type: Sequelize.JSONB,
defaultValue: []
},
shipping_rates: {
type: Sequelize.JSONB,
defaultValue: []
},
has_subscription: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
has_shipping: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
has_taxes: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
total_items: {
type: Sequelize.INTEGER,
defaultValue: 0
},
tax_shipping: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
tax_lines: {
type: Sequelize.JSONB,
defaultValue: []
},
tax_rate: {
type: Sequelize.FLOAT,
defaultValue: 0.0
},
tax_percentage: {
type: Sequelize.FLOAT,
defaultValue: 0.0
},
taxes_included: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
total_discounts: {
type: Sequelize.INTEGER,
defaultValue: 0
},
total_coupons: {
type: Sequelize.INTEGER,
defaultValue: 0
},
pricing_overrides: {
type: Sequelize.JSONB,
defaultValue: []
},
pricing_override_id: {
type: Sequelize.INTEGER
},
total_overrides: {
type: Sequelize.INTEGER,
defaultValue: 0
},
total_line_items_price: {
type: Sequelize.INTEGER,
defaultValue: 0
},
total_price: {
type: Sequelize.INTEGER,
defaultValue: 0
},
total_due: {
type: Sequelize.INTEGER,
defaultValue: 0
},
total_shipping: {
type: Sequelize.INTEGER,
defaultValue: 0
},
total_tax: {
type: Sequelize.INTEGER,
defaultValue: 0
},
total_weight: {
type: Sequelize.INTEGER,
defaultValue: 0
},
ip: {
type: Sequelize.STRING
},
create_ip: {
type: Sequelize.STRING
},
update_ip: {
type: Sequelize.STRING
},
client_details: {
type: Sequelize.JSONB,
defaultValue: {
'accept_language': null,
'browser_height': null,
'browser_ip': '0.0.0.0',
'browser_width': null,
'session_hash': null,
'user_agent': null
}
},
reservation_time: {
type: Sequelize.INTEGER,
defaultValue: 0
},
reservation_time_left: {
type: Sequelize.INTEGER,
defaultValue: 0
},
notes: {
type: Sequelize.TEXT
},
live_mode: {
type: Sequelize.BOOLEAN,
defaultValue: app.config.get('cart.live_mode')
}
};
}
static associate(models) {
models.Cart.belongsTo(models.Customer, {
foreignKey: 'customer_id'
});
models.Cart.belongsTo(models.Shop, {
foreignKey: 'shop_id'
});
models.Cart.belongsTo(models.Order, {
foreignKey: 'order_id'
});
models.Cart.belongsToMany(models.User, {
as: 'owners',
through: {
model: models.UserItem,
scope: {
item: 'cart'
}
},
foreign_id: 'item_id',
constraints: false
});
models.Cart.belongsTo(models.Address, {
as: 'shipping_address',
foreignKey: 'shipping_address_id'
});
models.Cart.belongsTo(models.Address, {
as: 'billing_address',
foreignKey: 'billing_address_id'
});
models.Cart.belongsToMany(models.Address, {
as: 'addresses',
foreignKey: 'model_id',
through: {
model: models.ItemAddress,
scope: {
model: 'cart'
},
constraints: false
},
constraints: false
});
models.Cart.belongsToMany(models.Discount, {
as: 'discounts',
through: {
model: models.ItemDiscount,
unique: false,
scope: {
model: 'cart'
}
},
foreignKey: 'model_id',
constraints: false
});
}
}
exports.Cart = Cart;
Cart.prototype.resetDefaults = function () {
this.total_items = 0;
this.total_shipping = 0;
this.subtotal_price = 0;
this.total_discounts = 0;
this.total_coupons = 0;
this.total_tax = 0;
this.total_weight = 0;
this.total_line_items_price = 0;
this.total_overrides = 0;
this.total_price = 0;
this.total_due = 0;
this.has_subscription = false;
this.has_shipping = false;
this.has_taxes = false;
this.discounted_lines = [];
this.coupon_lines = [];
this.tax_lines = this.tax_lines.filter(line => Object.keys(line).indexOf('line') === -1);
this.shipping_lines = this.shipping_lines.filter(line => Object.keys(line).indexOf('line') === -1);
this.line_items.map(item => {
item.shipping_lines = [];
item.discounted_lines = [];
item.coupon_lines = [];
item.tax_lines = [];
item.total_discounts = 0;
item.calculated_price = item.price;
return item;
});
return this;
};
Cart.prototype.setLineItems = function (lines = []) {
this.line_items = lines;
this.total_items = 0;
this.subtotal_price = 0;
this.total_line_items_price = 0;
this.has_shipping = this.line_items.some(item => item.requires_shipping);
this.has_taxes = this.line_items.some(item => item.requires_taxes);
this.line_items.forEach(item => {
if (item.requires_shipping) {
this.total_weight = this.total_weight + item.grams;
}
if (item.requires_subscription) {
this.has_subscription = true;
}
this.total_items = this.total_items + item.quantity;
this.subtotal_price = this.subtotal_price + item.price;
this.total_line_items_price = this.total_line_items_price + item.price;
});
return this.setTotals();
};
Cart.prototype.setItemDiscountedLines = function (item, discount, criteria) {
if (!(discount instanceof this.app.models['Discount'].instance)) {
throw new Error('setItemDiscountedLines expects discount parameter to be a Discount Instance');
}
item = discount.discountItem(item, criteria);
return item;
};
Cart.prototype.setItemsDiscountedLines = function (discounts, criteria) {
discounts = discounts || [];
criteria = criteria || [];
this.line_items = this.line_items || [];
this.discounted_lines = [];
const factoredDiscountedLines = [];
let discountsArr = [];
let discountedLines = [];
this.line_items = this.line_items.map((item, index) => {
discounts.forEach(discount => {
item = this.setItemDiscountedLines(item, discount, criteria);
});
if (item.discounted_lines.length > 0) {
const i = discountedLines.findIndex(line => line.line === index);
if (i > -1) {
discountedLines[i].discounts = [...discountedLines[i].discounts, ...item.discounted_lines];
}
else {
discountedLines.push({
line: index,
discounts: item.discounted_lines
});
}
}
return item;
});
discountedLines.forEach(line => {
discountsArr = [...discountsArr, ...line.discounts.map(d => d.id)];
});
discountedLines = discountedLines.map(line => {
line.discounts = line.discounts.map(discount => {
if (discount.rules.applies_once && discountsArr.filter(d => d === discount.id).length > 1) {
const arrRemove = discountsArr.findIndex(d => d === discount.id);
discountsArr = discountsArr.splice(arrRemove, 1);
discount.applies = false;
}
else if (discount.rules.minimum_order_amount > 0
&& this.total_line_items_price < discount.minimum_order_amount) {
discount.applies = false;
}
else if (discount.rules.applies_compound === false && discountsArr.length > 1) {
discount.applies = false;
}
else {
discount.applies = true;
}
return discount;
});
return line;
});
discountedLines.forEach(line => {
line.discounts.forEach(discount => {
const index = this.line_items[line.line].discounted_lines.findIndex(d => d.id === discount.id);
this.line_items[line.line].discounted_lines[index].applies = discount.applies;
});
});
this.line_items = this.line_items.map((item, index) => {
item.discounted_lines.forEach(discountedLine => {
if (discountedLine.applies === true) {
const calculatedPrice = Math.max(0, item.calculated_price - discountedLine.price);
const totalDeducted = Math.min(item.calculated_price, (item.calculated_price - (item.calculated_price - discountedLine.price)));
item.calculated_price = calculatedPrice;
item.total_discounts = Math.min(item.price, item.total_discounts + totalDeducted);
const fI = factoredDiscountedLines.findIndex(d => d.id === discountedLine.id);
if (fI > -1) {
factoredDiscountedLines[fI].lines = [...factoredDiscountedLines[fI].lines, index];
factoredDiscountedLines[fI].price = factoredDiscountedLines[fI].price + totalDeducted;
}
else {
discountedLine.lines = [index];
discountedLine.price = totalDeducted;
factoredDiscountedLines.push(discountedLine);
}
}
});
return item;
});
return this.setDiscountedLines(factoredDiscountedLines);
};
Cart.prototype.setDiscountedLines = function (lines = []) {
this.total_discounts = 0;
this.discounted_lines = lines;
this.discounted_lines.forEach(line => {
this.total_discounts = this.total_discounts + line.price;
});
return this.setTotals();
};
Cart.prototype.setPricingOverrides = function (lines = []) {
this.total_overrides = 0;
this.pricing_overrides = lines;
this.pricing_overrides.forEach(line => {
this.total_overrides = this.total_overrides + line.price;
});
return this.setTotals();
};
Cart.prototype.setCouponLines = function (lines = []) {
this.total_coupons = 0;
this.coupon_lines = lines;
this.coupon_lines.forEach(line => {
this.total_coupons = this.total_coupons + line.price;
});
return this.setTotals();
};
Cart.prototype.setItemsShippingLines = function (items) {
let shippingLines = [];
let totalShipping = 0;
this.line_items = this.line_items || [];
this.line_items = this.line_items.map((item, i) => {
const shippedLine = items.find(ii => ii.sku === item.sku);
if (shippedLine) {
shippedLine.shipping_lines = shippedLine.shipping_lines || [];
shippedLine.shipping_lines.map(line => {
line.line = i;
return line;
});
totalShipping = shippedLine.shipping_lines.forEach(line => {
totalShipping = totalShipping + line.price;
});
shippingLines = [...shippingLines, ...shippedLine.shipping_lines];
item.shipping_lines = shippedLine.shipping_lines;
item.total_shipping = totalShipping;
}
return item;
});
return this.setShippingLines(shippingLines);
};
Cart.prototype.setShippingLines = function (lines = []) {
this.total_shipping = 0;
this.shipping_lines = [...this.shipping_lines, ...lines],
this.shipping_lines.forEach(line => {
this.total_shipping = this.total_shipping + line.price;
});
return this.setTotals();
};
Cart.prototype.setItemsTaxLines = function (items) {
let taxesLines = [];
let totalTaxes = 0;
this.line_items = this.line_items || [];
this.line_items = this.line_items.map((item, i) => {
const taxedLine = items.find(ii => ii.sku === item.sku);
if (taxedLine) {
taxedLine.tax_lines = taxedLine.tax_lines || [];
taxedLine.tax_lines.map(line => {
line.line = i;
return line;
});
totalTaxes = taxedLine.tax_lines.forEach(line => {
totalTaxes = totalTaxes + line.price;
});
taxesLines = [...taxesLines, ...taxedLine.tax_lines];
item.tax_lines = taxedLine.tax_lines;
item.total_taxes = totalTaxes;
}
return item;
});
return this.setTaxLines(taxesLines);
};
Cart.prototype.setTaxLines = function (lines = []) {
this.total_tax = 0;
this.tax_lines = [...this.tax_lines, ...lines];
this.tax_lines.forEach(line => {
this.total_tax = this.total_tax + line.price;
});
return this.setTotals();
};
Cart.prototype.setTotals = function () {
this.total_price = Math.max(0, this.total_tax
+ this.total_shipping
+ this.subtotal_price);
this.total_due = Math.max(0, this.total_price
- this.total_discounts
- this.total_coupons
- this.total_overrides);
return this;
};
Cart.prototype.setLineProperties = function (line) {
if (line.properties) {
for (const l in line.properties) {
if (line.properties.hasOwnProperty(l)) {
line.price = line.price + line.properties[l].price;
line.price_per_unit = line.price_per_unit + line.properties[l].price;
}
}
}
return line;
};
Cart.prototype.line = function (data) {
data.Product = data.Product || {};
data.property_pricing = data.property_pricing || data.Product.property_pricing;
data.properties = data.properties || [];
const properties = {};
if (data.properties.length > 0
&& data.property_pricing) {
data.properties.forEach(prop => {
if (!prop.name) {
return;
}
if (data.property_pricing[prop.name]) {
properties[prop.name] = data.property_pricing[prop.name];
if (prop.value) {
properties[prop.name]['value'] = prop.value;
}
}
});
}
const line = {
shop_id: data.shop_id,
product_id: data.product_id,
product_handle: data.Product.handle,
variant_id: data.id || data.variant_id,
type: data.type,
sku: data.sku,
title: data.Product.title,
variant_title: data.title,
name: data.title === data.Product.title ? data.title : `${data.Product.title} - ${data.title}`,
properties: properties,
property_pricing: data.property_pricing,
option: data.option,
barcode: data.barcode,
price: data.price * data.quantity,
calculated_price: data.price * data.quantity,
compare_at_price: data.compare_at_price,
price_per_unit: data.price,
currency: data.currency,
fulfillment_service: data.fulfillment_service,
gift_card: data.gift_card,
requires_shipping: data.requires_shipping,
requires_taxes: data.requires_taxes,
tax_code: data.tax_code,
tax_lines: [],
total_taxes: 0,
shipping_lines: [],
total_shipping: 0,
discounted_lines: [],
total_discounts: 0,
requires_subscription: data.requires_subscription,
subscription_interval: data.subscription_interval,
subscription_unit: data.subscription_unit,
weight: data.weight * data.quantity,
weight_unit: data.weight_unit,
images: data.images.length > 0 ? data.images : data.Product.images,
quantity: data.quantity,
fulfillable_quantity: data.fulfillable_quantity,
max_quantity: data.max_quantity,
grams: this.app.services.ProxyCartService.resolveConversion(data.weight, data.weight_unit) * data.quantity,
vendors: data.Product.vendors,
vendor_id: data.vendor_id || null,
average_shipping: data.Product.average_shipping,
exclude_payment_types: data.Product.exclude_payment_types,
fulfillment_extras: data.fufillment_extras,
live_mode: data.live_mode
};
return line;
};
Cart.prototype.addLine = function (item, qty, properties, shop, options = {}) {
let lineQtyAvailable = -1;
let line;
return item.checkAvailability(qty, {
shop: options.shop || null,
transaction: options.transaction || null
})
.then(availability => {
if (!availability.allowed) {
throw new Error(`${availability.title} is not available in this quantity, please try a lower quantity`);
}
if (availability.shop) {
item.shop_id = availability.shop.id;
}
lineQtyAvailable = availability.quantity;
return item.checkRestrictions(this.Customer || this.customer_id, { transaction: options.transaction || null });
})
.then(restricted => {
if (restricted) {
throw new Error(`${restricted.title} can not be delivered to ${restricted.city} ${restricted.province} ${restricted.country}`);
}
const lineItems = this.line_items;
if (!qty || !lodash_1.isNumber(qty)) {
qty = 1;
}
const itemIndex = lodash_1.findIndex(lineItems, { variant_id: item.id });
if (itemIndex > -1) {
this.app.log.silly('Cart.addLine NEW QTY', lineItems[itemIndex]);
const maxQuantity = lineItems[itemIndex].max_quantity || -1;
let calculatedQty = lineItems[itemIndex].quantity + qty;
if (maxQuantity > -1 && calculatedQty > maxQuantity) {
calculatedQty = maxQuantity;
}
if (lineQtyAvailable > -1 && calculatedQty > lineQtyAvailable) {
calculatedQty = Math.max(0, lineQtyAvailable - calculatedQty);
}
lineItems[itemIndex].quantity = calculatedQty;
lineItems[itemIndex].fulfillable_quantity = calculatedQty;
lineItems[itemIndex] = this.setLineProperties(lineItems[itemIndex]);
this.line_items = lineItems;
}
else {
const maxQuantity = item.max_quantity || -1;
let calculatedQty = qty;
if (maxQuantity > -1 && calculatedQty > maxQuantity) {
calculatedQty = maxQuantity;
}
if (lineQtyAvailable > -1 && calculatedQty > lineQtyAvailable) {
calculatedQty = Math.max(0, lineQtyAvailable - calculatedQty);
}
item.quantity = calculatedQty;
item.fulfillable_quantity = calculatedQty;
item.max_quantity = maxQuantity;
item.properties = properties;
line = this.line(item);
line = this.setLineProperties(line);
lineItems.push(line);
this.line_items = lineItems;
}
return this;
});
};
Cart.prototype.removeLine = function (item, qty, options = {}) {
const lineItems = this.line_items;
if (!qty || !lodash_1.isNumber(qty)) {
qty = 1;
}
const itemIndex = lodash_1.findIndex(lineItems, { variant_id: item.id });
if (itemIndex > -1) {
lineItems[itemIndex].quantity = lineItems[itemIndex].quantity - qty;
lineItems[itemIndex].fulfillable_quantity = Math.max(0, lineItems[itemIndex].fulfillable_quantity - qty);
if (lineItems[itemIndex].quantity < 1) {
this.app.log.silly(`Cart.removeLine removing '${lineItems[itemIndex].variant_id}' line completely`);
lineItems.splice(itemIndex, 1);
}
this.line_items = lineItems;
return Promise.resolve(this);
}
};
Cart.prototype.addShipping = function (shipping = [], options = {}) {
const shippingLines = this.shipping_lines;
if (lodash_1.isArray(shipping)) {
shipping.forEach(ship => {
const i = lodash_1.findIndex(shippingLines, (s) => {
return s.name === ship.name;
});
ship.price = this.app.services.ProxyCartService.normalizeCurrency(parseInt(ship.price, 10));
if (i > -1) {
shippingLines[i] = ship;
}
else {
shippingLines.push(ship);
}
});
}
else if (lodash_1.isObject(shipping)) {
const i = lodash_1.findIndex(shippingLines, (s) => {
return s.name === shipping.name;
});
shipping.price = this.app.services.ProxyCartService.normalizeCurrency(parseInt(shipping.price, 10));
if (i > -1) {
shippingLines[i] = shipping;
}
else {
shippingLines.push(shipping);
}
}
this.shipping_lines = shippingLines;
return this.save({ transaction: options.transaction || null });
};
Cart.prototype.removeShipping = function (shipping = [], options = {}) {
const shippingLines = this.shipping_lines;
if (lodash_1.isArray(shipping)) {
shipping.forEach(ship => {
const i = lodash_1.findIndex(shippingLines, (s) => {
return s.name === ship.name;
});
if (i > -1) {
shippingLines.splice(i, 1);
}
});
}
else if (lodash_1.isObject(shipping)) {
const i = lodash_1.findIndex(shippingLines, (s) => {
return s.name === shipping.name;
});
if (i > -1) {
shippingLines.splice(i, 1);
}
}
this.shipping_lines = shippingLines;
return this.save({ transaction: options.transaction || null });
};
Cart.prototype.addTaxes = function (taxes = [], options = {}) {
const taxLines = this.tax_lines;
if (lodash_1.isArray(taxes)) {
taxes.forEach(tax => {
const i = lodash_1.findIndex(taxLines, (s) => {
return s.name === tax.name;
});
tax.price = this.app.services.ProxyCartService.normalizeCurrency(parseInt(tax.price, 10));
if (i > -1) {
taxLines[i] = tax;
}
else {
taxLines.push(tax);
}
});
}
else if (lodash_1.isObject(taxes)) {
const i = lodash_1.findIndex(taxLines, (s) => {
return s.name === taxes.name;
});
taxes.price = this.app.services.ProxyCartService.normalizeCurrency(parseInt(taxes.price, 10));
if (i > -1) {
taxLines[i] = taxes;
}
else {
taxLines.push(taxes);
}
}
this.tax_lines = taxLines;
return this.save({ transaction: options.transaction || null });
};
Cart.prototype.removeTaxes = function (taxes = [], options = {}) {
const taxLines = this.tax_lines;
if (lodash_1.isArray(taxes)) {
taxes.forEach(tax => {
const i = lodash_1.findIndex(taxLines, (s) => {
return s.name === tax.name;
});
if (i > -1) {
taxLines.splice(i, 1);
}
});
}
else if (lodash_1.isObject(taxes)) {
const i = lodash_1.findIndex(taxLines, (s) => {
return s.name === taxes.name;
});
if (i > -1) {
taxLines.splice(i, 1);
}
}
this.tax_lines = taxLines;
return this.save({ transaction: options.transaction || null });
};
Cart.prototype.close = function (status, save) {
this.status = status || enums_1.CART_STATUS.CLOSED;
if (save) {
return this.save(save);
}
return this;
};
Cart.prototype.draft = function (status, save) {
this.status = status || enums_1.CART_STATUS.DRAFT;
if (save) {
return this.save(save);
}
return this;
};
Cart.prototype.clear = function () {
this.line_items = [];
return this;
};
Cart.prototype.ordered = function (order, save) {
this.order_id = order.id;
this.status = enums_1.CART_STATUS.ORDERED;
if (save) {
return this.save(save);
}
return this;
};
Cart.prototype.buildOrder = function (options = {}) {
return {
client_details: options.client_details || this.client_details || {},
ip: options.ip || null,
payment_details: options.payment_details,
payment_kind: options.payment_kind || this.app.config.get('cart.orders.payment_kind'),
transaction_kind: options.transaction_kind || this.app.config.get('cart.orders.transaction_kind'),
fulfillment_kind: options.fulfillment_kind || this.app.config.get('cart.orders.fulfillment_kind'),
processing_method: options.processing_method || enums_3.PAYMENT_PROCESSING_METHOD.CHECKOUT,
shipping_address: options.shipping_address || this.shipping_address,
billing_address: options.billing_address || this.billing_address,
email: options.email || null,
customer_id: options.customer_id || this.customer_id || null,
user_id: options.user_id || this.user_id || null,
cart_token: this.token,
currency: this.currency,
line_items: this.line_items || [],
tax_lines: this.tax_lines || [],
shipping_lines: this.shipping_lines || [],
discounted_lines: this.discounted_lines || [],
coupon_lines: this.coupon_lines || [],
subtotal_price: this.subtotal_price,
taxes_included: this.taxes_included,
total_discounts: this.total_discounts,
total_coupons: this.total_coupons,
total_line_items_price: this.total_line_items_price,
total_price: this.total_due,
total_due: this.total_due,
total_tax: this.total_tax,
total_weight: this.total_weight,
total_items: this.total_items,
shop_id: this.shop_id,
has_shipping: this.has_shipping,
has_taxes: this.has_taxes,
has_subscription: this.has_subscription,
notes: this.notes,
pricing_override_id: this.pricing_override_id,
pricing_overrides: this.pricing_overrides,
total_overrides: this.total_overrides
};
};
Cart.prototype.calculatePricingOverrides = function (options = {}) {
this.line_items = this.line_items || [];
this.pricing_overrides = this.pricing_overrides || [];
let pricingOverrides = [];
let deduction = 0;
if (!this.customer_id) {
return Promise.resolve(this);
}
return Promise.resolve()
.then(() => {
if (this.Customer) {
return this.Customer;
}
else {
return this.app.models['Customer'].findById(this.customer_id, {
attributes: ['id', 'account_balance'],
transaction: options.transaction || null
});
}
})
.then(_customer => {
if (!_customer) {
return;
}
const exclusions = this.line_items.filter(item => {
item.exclude_payment_types = item.exclude_payment_types || [];
return item.exclude_payment_types.indexOf('Account Balance') !== -1;
});
pricingOverrides = this.pricing_overrides.filter(override => override);
const accountBalanceIndex = pricingOverrides.findIndex(p => p.name === 'Account Balance');
if (_customer.account_balance > 0) {
const removeTotal = lodash_1.sumBy(exclusions, (e) => e.calculated_price);
const deductibleTotal = Math.max(0, this.total_due - removeTotal);
deduction = Math.min(deductibleTotal, (deductibleTotal - (deductibleTotal - _customer.account_balance)));
if (deduction > 0) {
if (accountBalanceIndex === -1) {
pricingOverrides.push({
name: 'Account Balance',
price: deduction
});
}
else {
pricingOverrides[accountBalanceIndex].price = deduction;
}
}
}
else {
if (accountBalanceIndex > -1) {
pricingOverrides.splice(accountBalanceIndex, 1);
}
}
return this.setPricingOverrides(pricingOverrides);
})
.catch(err => {
this.app.log.error(err);
return this;
});
},
Cart.prototype.calculateDiscounts = function (options = {}) {
const criteria = [];
const productIds = this.line_items.map(item => item.product_id);
let collectionPairs = [], discountCriteria = [], checkHistory = [];
let resDiscounts;
return Promise.resolve()
.then(() => {
return this.getCollectionPairs({ transaction: options.transaction || null });
})
.then(_collections => {
collectionPairs = _collections || [];
if (this.id) {
criteria.push({
model: 'cart',
model_id: this.id
});
}
if (this.customer_id) {
criteria.push({
model: 'customer',
model_id: this.customer_id
});
}
if (productIds.length > 0) {
criteria.push({
model: 'product',
model_id: productIds
});
}
if (collectionPairs.length > 0) {
criteria.push({
model: 'collection',
model_id: collectionPairs.map(c => c.collection)
});
}
if (criteria.length > 0) {
return this.app.models['ItemDiscount'].findAll({
where: {
$or: criteria
},
attributes: ['discount_id', 'model', 'model_id'],
transaction: options.transaction || null
});
}
else {
return [];
}
})
.then(discounts => {
discounts.forEach(discount => {
const i = discountCriteria.findIndex(d => d.discount === discount.discount_id);
if (i > -1) {
if (!discountCriteria[i][discount.model]) {
discountCriteria[i][discount.model] = [];
}
discountCriteria[i][discount.model].push(discount.model_id);
}
else {
discountCriteria.push({
discount: discount.discount_id,
[discount.model]: [discount.model_id]
});
}
});
discountCriteria = discountCriteria.map(d => {
if (d.collection) {
d.collection.forEach(colId => {
const i = collectionPairs.findIndex(c => c.collection = colId);
if (i > -1) {
d = lodash_1.merge(d, collectionPairs[i]);
}
});
}
return d;
});
if (discounts.length > 0) {
return this.app.models['Discount'].findAll({
where: {
id: discounts.map(item => item.discount_id),
status: enums_2.DISCOUNT_STATUS.ENABLED
},
transaction: options.transaction || null
});
}
else {
return [];
}
})
.then(_discounts => {
_discounts = _discounts || [];
resDiscounts = _discounts;
resDiscounts.forEach(discount => {
if (discount.applies_once_per_customer && this.customer_id) {
checkHistory.push(discount);
}
});
if (checkHistory.length > 0) {
return Promise.all(checkHistory.map(discount => {
return discount.eligibleCustomer(this.customer_id, { transaction: options.transaction || null });
}));
}
else {
return [];
}
})
.then(_eligible => {
_eligible = _eligible || [];
_eligible.forEach(discount => {
const i = resDiscounts.findIndex(ii => ii.id === discount.id);
if (i > -1) {
resDiscounts.splice(i, 1);
}
});
return this.setItemsDiscountedLines(resDiscounts, discountCriteria);
})
.catch(err => {
this.app.log.error(err);
return this;
});
};
Cart.prototype.calculateShipping = function (options = {}) {
if (!this.has_shipping) {
return Promise.resolve(this);
}
return this.app.services.ShippingService.calculate(this, this.line_items, this.shipping_address, this.app.models['Cart'], options)
.then(shippingResult => {
this.setItemsShippingLines(shippingResult.line_items);
return this;
})
.catch(err => {
this.app.log.error(err);
return this;
});
};
Cart.prototype.calculateTaxes = function (options = {}) {
if (!this.has_taxes) {
return Promise.resolve(this);
}
return this.app.services.TaxService.calculate(this, this.line_items, this.shipping_address, this.app.models['Cart'], options)
.then(taxesResult => {
this.setItemsTaxLines(taxesResult.line_items);
return this;
})
.catch(err => {
this.app.log.error(err);
return this;
});
};
Cart.prototype.recalculate = function (options = {}) {
this.resetDefaults();
this.setLineItems(this.line_items);
return Promise.resolve()
.then(() => {
return this.calculateDiscounts({ transaction: options.transaction || null });
})
.then(() => {
return this.calculateShipping({ transaction: options.transaction || null });
})
.then(() => {
return this.calculateTaxes({ transaction: options.transaction || null });
})
.then(() => {
return this.calculatePricingOverrides({ transaction: options.transaction || null });
})
.then(() => {
return this.setTotals();
})
.catch(err => {
this.app.log.error(err);
return this;
});
};
Cart.prototype.resolveCustomer = function (options = {}) {
if (this.Customer
&& this.Customer instanceof this.app.models['Customer'].instance
&& options.reload !== true) {
return Promise.resolve(this);
}
else if (!this.customer_id) {
return Promise.resolve(this);
}
else {
return this.getCustomer({ transaction: options.transaction || null })
.then(_customer => {
if (_customer) {
_customer = _customer || null;
this.Customer = _customer;
this.setDataValue('Customer', _customer);
this.set('Customer', _customer);
}
return this;
});
}
};
Cart.prototype.resolveCustomerAndItemCollections = function (options = {}) {
this.line_items.forEach(item => {
});
};
Cart.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;
});
}
};
Cart.prototype.getCollectionPairs = function (options = {}) {
const collectionPairs = [];
const criteria = [];
let productIds = this.line_items.map(item => item.product_id);
productIds = productIds.filter(i => i);
let variantIds = this.line_items.map(item => item.variant_id);
variantIds = variantIds.filter(i => i);
return Promise.resolve()
.then(() => {
if (this.customer_id) {
criteria.push({
model: 'customer',
model_id: this.customer_id
});
}
if (productIds.length > 0) {
criteria.push({
model: 'product',
model_id: productIds
});
}
if (variantIds.length > 0) {
criteria.push({
model: 'productvariant',
model_id: variantIds
});
}
if (criteria.length > 0) {
return this.app.models['ItemCollection'].findAll({
where: {
$or: criteria
},
attributes: ['id', 'collection_id', 'model', 'model_id'],
transaction: options.transaction || null
});
}
return [];
})
.then(_collections => {
_collections = _collections || [];
_collections.forEach(collection => {
const i = collectionPairs.findIndex(c => c.id === collection.collection_id);
if (i > -1) {
if (!collectionPairs[i][collection.model]) {
collectionPairs[i][collection.model] = [];
}
collectionPairs[i][collection.model].push(collection.model_id);
}
else {
collectionPairs.push({
collection: collection.collection_id,
[collection.model]: [collection.model_id]
});
}
});
return collectionPairs;
})
.catch(err => {
this.app.log.error(err);
return [];
});
};
Cart.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;
});
}
};
Cart.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;
});
}
};
Cart.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(shipping