/* eslint-disable */
'use strict';
/** @module calculate */
/**
* This javascript file implements methods (via Common.js exports) that are needed by
* the new (smaller) CalculateCart.ds script file. This allows OCAPI calls to reference
* these tools via the OCAPI 'hook' mechanism
*/
var HashMap = require('dw/util/HashMap');
var PromotionMgr = require('dw/campaign/PromotionMgr');
var ShippingMgr = require('dw/order/ShippingMgr');
var ShippingLocation = require('dw/order/ShippingLocation');
var TaxMgr = require('dw/order/TaxMgr');
var Logger = require('dw/system/Logger');
var Status = require('dw/system/Status');
var HookMgr = require('dw/system/HookMgr');
var collections = require('*/cartridge/scripts/util/collections');
/**
* @function calculate
*
* calculate is the arching logic for computing the value of a basket. It makes
* calls into cart/calculate.js and enables both SG and OCAPI applications to share
* the same cart calculation logic.
*
* @param {object} basket The basket to be calculated
*/
exports.calculate = function (basket) {
// ===================================================
// ===== CALCULATE PRODUCT LINE ITEM PRICES =====
// ===================================================
calculateProductPrices(basket);
// ===================================================
// ===== CALCULATE GIFT CERTIFICATE PRICES =====
// ===================================================
calculateGiftCertificatePrices(basket);
// ===================================================
// ===== Note: Promotions must be applied =====
// ===== after the tax calculation for =====
// ===== storefronts based on GROSS prices =====
// ===================================================
// ===================================================
// ===== APPLY PROMOTION DISCOUNTS =====
// ===== Apply product and order promotions. =====
// ===== Must be done before shipping =====
// ===== calculation. =====
// ===================================================
PromotionMgr.applyDiscounts(basket);
// ===================================================
// ===== CALCULATE SHIPPING COSTS =====
// ===================================================
// apply product specific shipping costs
// and calculate total shipping costs
HookMgr.callHook('dw.order.calculateShipping', 'calculateShipping', basket);
// ===================================================
// ===== APPLY PROMOTION DISCOUNTS =====
// ===== Apply product and order and =====
// ===== shipping promotions. =====
// ===================================================
PromotionMgr.applyDiscounts(basket);
// since we might have bonus product line items, we need to
// reset product prices
calculateProductPrices(basket);
// ===================================================
// ===== CALCULATE TAX =====
// ===================================================
HookMgr.callHook('dw.order.calculateTax', 'calculateTax', basket);
// ===================================================
// ===== CALCULATE BASKET TOTALS =====
// ===================================================
basket.updateTotals();
// ===================================================
// ===== DONE =====
// ===================================================
return new Status(Status.OK);
};
/**
* @function calculateProductPrices
*
* Calculates product prices based on line item quantities. Set calculates prices
* on the product line items. This updates the basket and returns nothing
*
* @param {object} basket The basket containing the elements to be computed
*/
function calculateProductPrices (basket) {
// get total quantities for all products contained in the basket
var productQuantities = basket.getProductQuantities();
var productQuantitiesIt = productQuantities.keySet().iterator();
// get product prices for the accumulated product quantities
var productPrices = new HashMap();
while (productQuantitiesIt.hasNext()) {
var prod = productQuantitiesIt.next();
var quantity = productQuantities.get(prod);
productPrices.put(prod, prod.priceModel.getPrice(quantity));
}
// iterate all product line items of the basket and set prices
var productLineItems = basket.getAllProductLineItems().iterator();
while (productLineItems.hasNext()) {
var productLineItem = productLineItems.next();
// handle non-catalog products
if (!productLineItem.catalogProduct) {
productLineItem.setPriceValue(productLineItem.basePrice.valueOrNull);
continue;
}
var product = productLineItem.product;
// handle option line items
if (productLineItem.optionProductLineItem) {
// for bonus option line items, we do not update the price
// the price is set to 0.0 by the promotion engine
if (!productLineItem.bonusProductLineItem) {
productLineItem.updateOptionPrice();
}
// handle bundle line items, but only if they're not a bonus
} else if (productLineItem.bundledProductLineItem) {
// no price is set for bundled product line items
// handle bonus line items
// the promotion engine set the price of a bonus product to 0.0
// we update this price here to the actual product price just to
// provide the total customer savings in the storefront
// we have to update the product price as well as the bonus adjustment
} else if (productLineItem.bonusProductLineItem && product !== null) {
var price = product.priceModel.price;
var adjustedPrice = productLineItem.adjustedPrice;
productLineItem.setPriceValue(price.valueOrNull);
// get the product quantity
var quantity2 = productLineItem.quantity;
// we assume that a bonus line item has only one price adjustment
var adjustments = productLineItem.priceAdjustments;
if (!adjustments.isEmpty()) {
var adjustment = adjustments.iterator().next();
var adjustmentPrice = price.multiply(quantity2.value).multiply(-1.0).add(adjustedPrice);
adjustment.setPriceValue(adjustmentPrice.valueOrNull);
}
// set the product price. Updates the 'basePrice' of the product line item,
// and either the 'netPrice' or the 'grossPrice' based on the current taxation
// policy
// handle product line items unrelated to product
} else if (product === null) {
productLineItem.setPriceValue(null);
// handle normal product line items
} else {
productLineItem.setPriceValue(productPrices.get(product).valueOrNull);
}
}
}
/**
* @function calculateGiftCertificates
*
* Function sets either the net or gross price attribute of all gift certificate
* line items of the basket by using the gift certificate base price. It updates the basket in place.
*
* @param {object} basket The basket containing the gift certificates
*/
function calculateGiftCertificatePrices (basket) {
var giftCertificates = basket.getGiftCertificateLineItems().iterator();
while (giftCertificates.hasNext()) {
var giftCertificate = giftCertificates.next();
giftCertificate.setPriceValue(giftCertificate.basePrice.valueOrNull);
}
}
exports.calculateShipping = function(basket) {
ShippingMgr.applyShippingCost(basket);
return new Status(Status.OK);
}
/**
* @function calculateTax <p>
*
* Determines tax rates for all line items of the basket. Uses the shipping addresses
* associated with the basket shipments to determine the appropriate tax jurisdiction.
* Uses the tax class assigned to products and shipping methods to lookup tax rates. <p>
*
* Sets the tax-related fields of the line items. <p>
*
* Handles gift certificates, which aren't taxable. <p>
*
* Note that the function implements a fallback to the default tax jurisdiction
* if no other jurisdiction matches the specified shipping location/shipping address.<p>
*
* Note that the function implements a fallback to the default tax class if a
* product or a shipping method does explicitly define a tax class.
*
* @param {dw.order.Basket} basket The basket containing the elements for which taxes need to be calculated
*/
exports.calculateTax = function(basket) {
var basketCalculationHelpers = require('*/cartridge/scripts/helpers/basketCalculationHelpers');
var taxes = basketCalculationHelpers.calculateTaxes(basket);
// convert taxes into hashmap for performance.
var taxesMap = {};
taxes.taxes.forEach(function (item) {
taxesMap[item.uuid] = { value: item.value, amount: item.amount };
});
var lineItems = basket.getAllLineItems();
var totalShippingGrossPrice = 0;
var totalShippingNetPrice = 0;
// update taxes for all line items
collections.forEach(lineItems, function (lineItem) {
var tax = taxesMap[lineItem.UUID];
if (tax) {
if (tax.amount) {
lineItem.updateTaxAmount(tax.value);
if (lineItem instanceof dw.order.ShippingLineItem) {
totalShippingGrossPrice += lineItem.getAdjustedGrossPrice();
totalShippingNetPrice += lineItem.getAdjustedNetPrice();
}
} else {
lineItem.updateTax(tax.value);
}
} else {
if (lineItem.taxClassID === TaxMgr.customRateTaxClassID) {
// do not touch tax rate for fix rate items
lineItem.updateTax(lineItem.taxRate);
} else {
// otherwise reset taxes to null
lineItem.updateTax(null);
}
}
});
// besides shipment line items, we need to calculate tax for possible order-level price adjustments
// this includes order-level shipping price adjustments
if (!basket.getPriceAdjustments().empty || !basket.getShippingPriceAdjustments().empty) {
if (collections.first(basket.getPriceAdjustments(), function (priceAdjustment) {
return taxesMap[priceAdjustment.UUID] === null;
}) || collections.first(basket.getShippingPriceAdjustments(), function (shippingPriceAdjustment) {
return taxesMap[shippingPriceAdjustment.UUID] === null;
})) {
// tax hook didn't provide taxes for global price adjustment, we need to calculate them ourselves.
// calculate a mix tax rate from
var basketPriceAdjustmentsTaxRate = ((basket.getMerchandizeTotalGrossPrice().value + basket.getShippingTotalGrossPrice().value)
/ (basket.getMerchandizeTotalNetPrice().value + basket.getShippingTotalNetPrice())) - 1;
var basketPriceAdjustments = basket.getPriceAdjustments();
collections.forEach(basketPriceAdjustments, function (basketPriceAdjustment) {
basketPriceAdjustment.updateTax(basketPriceAdjustmentsTaxRate);
});
var basketShippingPriceAdjustments = basket.getShippingPriceAdjustments();
collections.forEach(basketShippingPriceAdjustments, function(basketShippingPriceAdjustment) {
basketShippingPriceAdjustment.updateTax(totalShippingGrossPrice/totalShippingNetPrice - 1);
});
}
}
// if hook returned custom properties attach them to the order model
if (taxes.custom) {
Object.keys(taxes.custom).forEach(function (key) {
basket.custom[key] = taxes.custom[key];
});
}
return new Status(Status.OK);
}