Skip to content

Instantly share code, notes, and snippets.

@sf-thomas-loesche
Forked from shethj/Readme.md
Last active March 17, 2025 08:31
Show Gist options
  • Save sf-thomas-loesche/3446c7d71a97e559bf1caee96ae56d9f to your computer and use it in GitHub Desktop.
Save sf-thomas-loesche/3446c7d71a97e559bf1caee96ae56d9f to your computer and use it in GitHub Desktop.
Merge basket utility functions using Script API for SFRA & SiteGenesis storefronts.

Merge Basket utility functions using Script API

You can implement merge baskets using existing Script API. A recommended approach is to append to existing SFRA/SG controllers where a shopper can log in and implement functionality to then merge baskets as shown in the scripts below.

How to use this script

In a custom SFRA/SG cartridge, extend the Login functionality using server.extend() and server.append() functions like so:

*/cartridges/controllers/Account.js

"use strict";
var account = module.superModule;
var server = require("server");
var BasketMgr = require("dw/order/BasketMgr");

var mergeBasketUtil = require("*/cartridge/scripts/helpers/mergeBasketUtil")

server.extend(account);

server.append("Login", function (req, res, next) {
   if (res.json.success) {
    var mergeBasketUtil = require("*/cartridge/scripts/mergeBasketUtil.js");
    // CurrentBasket will contain the basket from the previous guest shopper
    // but already transferred to the registered shopper
    var basket = BasketMgr.getCurrentBasket();
    // StoredBasket will contain a storefront basket from the registered shopper
    // that existed before the login (will be deleted after login!!!)
    var storedBasket = BasketMgr.getStoredBasket();
    if (storedBasket && basket) {
     mergeBasketUtil.mergeBasket(storedBasket, basket);
    }
  }
  return next();
});

module.exports = server.exports();

Note the call to mergeBaskets() as part of the MergeBasketUtil above. This is a sample utility script provided as an unsupported reference. Refer to the complete implementation of the function below in mergeBasketUtil.js You can add that implementation to your custom cartridge under */cartridges/scripts/helpers/mergeBasketUtil.js for example.

Summary of MergeBasketUtil

The storedBasket (first parameter) will be merged into basket (second parameter), after which the storedBasket will be deleted. The term merge, means the item from storeBasket will be added to or copied over the basket item.

  • Each basket item affected is described below.
  • CouponLineItems (merge Custom Attributes only if new)
  • Shipments (merges Custom Attributes, ShippingMethod and ShippingAddress with caveats, see gist)
  • ProductLineItems (regards Add Product to Basket Behavior: Allow Repeats, Disallow Repeats, Increment Quantity)
    • merge Custom Attributes, WishListItem, InventoryListId
    • merge WishListItem (ProductListItem)
    • merge InventoryListId (not completely supported)
    • handled types: Simple Products, Bundles, Options, Variants, BonusProductLineItems
  • GiftCertificateLineItems (Custom Attributes and GiftRegistryItem with caveats, see gist)
  • GiftCertificatePaymentInstruments (merges Custom Attributes only if new with caveats, see gist)

More SFRA controllers to extend

The example script above shows how to override login on the Login Page by appending the /Account-Login controller. However, a shopper can also choose to create a new account from the Login page which then triggers a shopper login or the shopper might also login into their existing account during the checkout process.

You can use the script above to implement merge baskets for all those different scenarios by appending their respective SFRA controllers:

List of controllers with Login flow implementations in SFRA base:

  • Login on Login Page: /Account-Login
  • Create account on Login Page: /Account-SubmitRegistration
  • Login during Checkout /CheckoutServices-LoginCustomer

Learn more about extending/appending to controllers here.

Items merged in this implementation

  • CouponLineItems
    • merge Custom Attributes only if the CouponLineItem was newly created
  • Shipments
    • merge Custom Attributes only if the shipment was newly created
    • merge ShippingMethod (if not yet existing)
    • merge ShippingAddress (if not yet existing) including custom attrubutes
      • Normally BasketMgr.getCurrentBasket() has removed the addresses from the transferred basket (target basket) and therefore the target shipment would only contain the source Shipping addresses.
  • ProductLineItems (regards Add Product to Basket Behavior: Allow Repeats, Disallow Repeats, Increment Quantity)
    • merge Custom Attributes only if the product line item was newly created
    • merge WishListItem (ProductListItem)
    • merge InventoryListId (not completely supported)
    • handled types
      • Simple Products
      • Bundles
      • Options
      • Variants
      • BonusProductLineItems (if BonusProductChoice)
  • GiftCertificateLineItems
    • merge Custom Attributes only if the GiftCertificateLineItem was newly created
    • merge GiftRegistryItem (ProductListItem)
  • GiftCertificatePaymentInstruments
    • Normally BasketMgr.getCurrentBasket() has removed the GiftCertificatePaymentInstruments from the transferred basket (target basket) and therefore the target basket would only contain the source GiftCertificatePaymentInstruments
    • merge Custom Attributes only if the GiftCertificatePaymentInstrument was newly created

Items NOT merged in this implementation

  • ShippingLineItems - should be added by the next calcuation
  • ProductShippingLineItems - should be added by the next calcuation
  • PriceAdjustments
    • manual - will be lost
    • other - will be newly added by the next calculation
  • BonusProductLineItems (if not BonusProductChoice) - will be added by the next calculation
  • BillingAddress
  • Basket Custom attributes
"use strict";
var transaction = require("dw/system/Transaction");
/**
* Merges current and stored basket
* @param {dw.order.Basket} sourceBasket - the source basket, usually retrieve via dw.order.BasketMgr.getStoredBasket();
* @param {dw.order.Basket} targetBasket - the target basket, usually retrieve via dw.order.BasketMgr.getCurrentBasket();
*/
function mergeBasket(sourceBasket, targetBasket) {
var basketMergeContext = new BasketMergeContext(targetBasket);
transaction.wrap(() => {
// merge shipments
mergeShipments(sourceBasket, basketMergeContext);
// merge coupon line items
sourceBasket.couponLineItems.toArray().forEach(couponItem => {
var couponCode = couponItem.couponCode;
if (!targetBasket.getCouponLineItem(couponCode)) {
var couponInfo = "merge CouponLineItem " + couponCode;
var isBasedOnCampaign = couponItem.basedOnCampaign;
dw.system.Logger.debug(couponInfo + " add " + (isBasedOnCampaign ? "campaign based" : "adhoc"));
var targetCouponItem = targetBasket.createCouponLineItem(couponCode, isBasedOnCampaign);
var copyInfo = couponInfo + " copy CouponLineItem";
copySystemAttributes(copyInfo, couponItem, targetCouponItem);
copyCustomAttributes(copyInfo, couponItem, targetCouponItem);
}
});
// merge gift certificate line items
mergeGiftCertifateLineItems(sourceBasket, basketMergeContext);
// calculate basket
if (dw.system.HookMgr.hasHook("dw.order.calculate")) {
dw.system.Logger.debug("calculate cart using dw.order.calculate");
dw.system.HookMgr.callHook("dw.order.calculate", targetBasket);
} else if (dw.system.HookMgr.hasHook("dw.ocapi.shop.basket.calculate")) {
dw.system.Logger.warn("calculate cart using deprecated dw.ocapi.shop.basket.calculate");
dw.system.HookMgr.callHook("dw.ocapi.shop.basket.calculate", targetBasket);
} else {
dw.system.Logger.error("calculate cart was no possible because hook is dw.order.calculate");
}
// merge bonus discount line items
mergeBonusDiscountLineItems(sourceBasket, basketMergeContext);
// merge gift certificate payment instrument
mergeGiftCertifatePaymentInstruments(sourceBasket, basketMergeContext);
});
}
/**
* Merge shipments.<br/>
* Shipments are merged by ID, otherwise a new shipment is created.<br/>
* Merged Data:
* - System and custom attributes were copied if a new stipment is created.
* - The shipping address is copied if a new shipment is created or the existing shipment does not have a shipping address.
* BasketMgr.getCurrentBasket() has cleaned the shipping address in the transferred (target) basket if called after login.
* Therefore the target shipment would only contain the source shipping address.
* - The shipping method is copied if a new shipment is created or the existing shipment does not have a shipping method.
* @param {dw.order.Basket} sourceBasket - the source basket
* @param {BasketMergeContext} basketMergeContext - the basket merge context
*/
function mergeShipments(sourceBasket, basketMergeContext) {
var targetBasket = basketMergeContext.targetBasket;
sourceBasket.shipments.toArray().forEach(sourceShipment => {
var targetShipment;
var shipmentId = sourceShipment.ID;
var info = "merge Shipment " + shipmentId;
var existingShipment = targetBasket.getShipment(shipmentId)
if (existingShipment) {
targetShipment = existingShipment;
} else {
dw.system.Logger.debug(info + " create shipment");
targetShipment = targetBasket.createShipment(shipmentId);
var copyInfo = info + " copy Shipment"
copySystemAttributes(copyInfo, sourceShipment, targetShipment, id => id == "shipmentNo");
copyCustomAttributes(copyInfo, sourceShipment, targetShipment);
}
var sourceAddress = sourceShipment.shippingAddress;
// merge shipping address (normally BasketMgr.getCurrent() has cleaned Addresses in the transferred basket)
if (sourceAddress && !targetShipment.shippingAddress) {
dw.system.Logger.debug(info + " create shipping address");
var targetAddress = targetShipment.createShippingAddress();
// address1,address2,city,companyName,countryCode,firstName,jobTitle,lastName,phone,postalCode,postBox,salutation,secondName,stateCode,suffix,suite,title
var copyInfo = info + " copy OrderAddress"
copySystemAttributes(copyInfo, sourceAddress, targetAddress);
copyCustomAttributes(copyInfo, sourceAddress, targetAddress);
}
// merge shipping method
copySystemAttributeIfExisting(info + " copy Shipment", sourceShipment, targetShipment, "shippingMethod");
// merge product line items
sourceShipment.getProductLineItems().toArray().forEach(sourceItem =>
mergeProductItem(sourceItem, basketMergeContext)
);
});
}
/**
* Merge product line items.<br/>
* The merge regards the Basket Preference <b>Add Product to Basket Behavior</b>:
* - <b>Increment Quantity</b> Quantity of an already existing matching ProductLineItem increased, otherwise a new ProductLineItem is created.
* - <b>Allow Repeats</b> Always a new ProductLineItem is created.
* - <b>Disallow Repeats</b> Ignore if a metching ProductLineItem already exists, otherwise a new ProductLineItem is created.
*
* Handled product line item types:
* - SimpleProducts are copied
* - OptionProducts are copied
* - Bundles are copied
* - Variants are copied
* - Simple BonusProductLineItems skiped - will be added when promotions will be applied (calculate cart)
* - ChoiceOfBonusProduct BonusProductLineItems skipped - will be copied with mergeBonusDiscountLineItems
* - AdHoc Producs are copied
*
* Merged Data:
* - System and custom attributes were copied if a new ProductLineItem is created.
* - The product list item reference (WishListItem) is copied if a new ProductLineItem.
* @param {dw.order.ProductLineItem} sourceItem - the source product line item
* @param {BasketMergeContext} mergeContext - the merge context
*/
function mergeProductItem(sourceItem, mergeContext) {
var targetBasket = mergeContext.targetBasket;
var shipmentId = sourceItem.shipment.ID;
var productId = sourceItem.productID;
var targetShipment = getShipment(targetBasket, shipmentId);
var info = "merge Shipment " + shipmentId + " Product " + productId;
if (sourceItem.isBonusProductLineItem()) {
if (sourceItem.getBonusDiscountLineItem()) {
// retain customer selected BONUS_CHOICE
dw.system.Logger.debug(info + " handle choice of bonus product later");
return;
} else {
// bonus products will be added when promotions will be applied
dw.system.Logger.debug(info + " skip bonus product");
return;
}
}
dw.system.Logger.debug(info + " quantity=" + sourceItem.quantity);
var product = dw.catalog.ProductMgr.getProduct(productId);
var targetItem
if (sourceItem.productListItem) {
targetItem = targetBasket.createProductLineItem(sourceItem.productListItem, targetShipment);
} else if (product) {
var optionModel = getOptionModel(info, sourceItem)
targetItem = targetBasket.createProductLineItem(product, optionModel, targetShipment);
} else {
targetItem = targetBasket.createProductLineItem(productId, targetShipment);
}
var oldQuantity = mergeContext.getQuantity(targetItem);
var newQuantity
if (oldQuantity == targetItem.quantityValue) {
// handling for Add Product to Basket Behavior : Disallow Repeats
dw.system.Logger.debug(info + " do not merge because already existing with quantity=" + oldQuantity);
return;
} else if (oldQuantity) {
// item merged
newQuantity = oldQuantity + sourceItem.quantityValue;
dw.system.Logger.debug(info + " increase quantity " + oldQuantity + " -> " + newQuantity);
} else {
// new item created
newQuantity = sourceItem.quantityValue;
dw.system.Logger.debug(info + " create with quantity " + newQuantity);
// copy Attributes only for new product line items
targetItem.setQuantityValue(newQuantity);
copyProductItemAttributes(info, sourceItem, targetItem);
}
// update the merge context
mergeContext.updateQuantity(targetItem, newQuantity);
}
/**
* Copy product line item
* @param {string} info
* @param {dw.order.ProductLineItem} sourceItem - the source product line item
* @param {dw.order.ProductLineItem} targetItem - the target product line item
*/
function copyProductItemAttributes(info, sourceItem, targetItem) {
var copyInfo = info + " copy ProductLineItem"
// copy system attributes
copySystemAttributes(copyInfo, sourceItem, targetItem)
// copying custom attributes
copyCustomAttributes(copyInfo, sourceItem, targetItem);
// copy productInventoryListID
copySystemAttributeIfExisting(copyInfo, sourceItem, targetItem, "productInventoryListID", true);
var sourceDependentItems = getDependentProductItems(sourceItem);
var targetDependentItems = getDependentProductItems(targetItem);
if (sourceDependentItems.length > 0) {
dw.system.Logger.debug(info + " dependent items " + sourceDependentItems.length);
}
sourceDependentItems.forEach((sourceDependentItem, key) => {
var dependentInfo = info + " dependent " + sourceDependentItem.productID;
var targetDependentItem = resolveTargetProductLineItem(dependentInfo, sourceDependentItem, targetDependentItems)
if (targetDependentItem) {
// select the correct variant if possible
if (targetDependentItem.product && targetDependentItem.product.isMaster() && sourceDependentItem.product && !sourceDependentItem.product.isMaster()) {
targetDependentItem.replaceProduct(sourceDependentItem.product)
}
copyProductItemAttributes(dependentInfo, sourceDependentItem, targetDependentItem)
} else {
dw.system.Logger.warn(dependentInfo + " target item missing " + key);
}
});
}
/**
* Resolve target product line item
* @param {string} info - the info for logging
* @param {dw.order.ProductLineItem} sourceItem - the source product line item
* @param {Array<dw.order.ProductLineItem>} targetItems - the target product line items
*/
function resolveTargetProductLineItem(info, sourceItem, targetItems) {
var quantity = sourceItem.quantityValue;
function findTargetItem(productId) {
var index = targetItems.findIndex(item => productId == item.productID && quantity == item.quantityValue);
if (index != -1) {
var item = targetItems[index];
targetItems.splice(index, 1);
return item;
}
};
var targetItem = findTargetItem(sourceItem.productID);
if (targetItem) {
return targetItem;
}
var product = sourceItem.product;
if (product && product.isVariant()) {
var master = product.variationModel.master;
dw.system.Logger.debug(info + " resolve target master " + master.ID);
return findTargetItem(master.ID);
}
}
/**
* Get the dependet product line items
* @param {dw.order.ProductLineItem} parent
* @returns {Array<dw.order.ProductLineItem}
*/
function getDependentProductItems(parent) {
return parent.bundledProductLineItems.toArray().concat(
parent.optionProductLineItems.toArray());
}
/**
* Merge gift certificate line items.<br/>
* GiftCertifateLineItems are always copied.
* @param {dw.order.Basket} sourceBasket - the source basket
* @param {BasketMergeContext} basketMergeContext - the basket merge context
*/
function mergeGiftCertifateLineItems(sourceBasket, basketMergeContext) {
var targetBasket = basketMergeContext.targetBasket;
sourceBasket.giftCertificateLineItems.toArray().forEach(sourceItem => {
var amount = sourceItem.priceValue;
var sourceShipment = sourceItem.shipment;
var info = "Merge GiftCertificateLineItem amount=" + amount + " shipment";
var targetItem = targetBasket.createGiftCertificateLineItem(amount, sourceItem.recipientEmail);
dw.system.Logger.debug(info + " shpipment=" + sourceShipment.ID);
targetItem.shipment = getShipment(targetBasket, sourceShipment.ID);
var copyInfo = info + " copy GiftCertificateLineItem";
copySystemAttributes(copyInfo, sourceItem, targetItem);
copyCustomAttributes(copyInfo, sourceItem, targetItem);
// source product list item might already be removed with BasketMgr.getCurrentBasket();
copySystemAttributeIfExisting(copyInfo, sourceItem, targetItem, "productListItem");
});
}
/**
* Merge gift certificate paymenent instruments.<br/>
* GiftCertifatePaymentInstruments are created if no GiftCertifatePaymentInstrument with the same giftCertificateCode exists.<br/>
* Please regard: BasketMgr.getCurrentBasket() cleans the GiftCertifatePaymentInstruments of the transferred Basket.
* Therefore the target basket will only contain the source basket GiftCertifatePaymentInstruments.
* @param {dw.order.Basket} sourceBasket - the source basket
* @param {BasketMergeContext} basketMergeContext - the basket merge context
*/
function mergeGiftCertifatePaymentInstruments(sourceBasket, basketMergeContext) {
var targetBasket = basketMergeContext.targetBasket;
var targetCodes = targetBasket.giftCertificatePaymentInstruments.toArray().map(instr => instr.giftCertificateCode);
sourceBasket.giftCertificatePaymentInstruments.toArray().forEach(sourceGiftCertificate => {
var giftCertificateCode = sourceGiftCertificate.giftCertificateCode;
var info = "merge gift certificate payment instrument " + sourceGiftCertificate.maskedGiftCertificateCode;
if (targetCodes.includes(giftCertificateCode)) {
dw.system.Logger.debug(info + " already existing");
return;
}
dw.system.Logger.debug(info + " create");
var sourceTransaction = sourceGiftCertificate.paymentTransaction;
var amount = sourceTransaction.amount;
var targetGiftCertificate = targetBasket.createGiftCertificatePaymentInstrument(giftCertificateCode, amount);
// ignore paymentMethod
var copyInfo = info + " copy OrderPaymentInstrument"
copySystemAttributes(copyInfo, sourceGiftCertificate, targetGiftCertificate, id => id == "paymentMethod");
copyCustomAttributes(copyInfo, sourceGiftCertificate, targetGiftCertificate);
var targetTransaction = targetGiftCertificate.paymentTransaction;
var copyInfo = info + " copy PaymentTransaction"
copySystemAttributes(copyInfo, sourceTransaction, targetTransaction, id => id == "type");
copyCustomAttributes(copyInfo, sourceTransaction, targetTransaction);
});
}
/**
* Merge bonus discount line items.<br/>
* The method does not create BonusDiscountLineItems in the target Basket.<br/>
* The target BonusDiscountLineItems were already created by the basket calculation.<br/>
* BonusDiscountLineItems are merged by promotionId, maxBonusItems and bonusProducts.<br/>
* Merging of BonusDiscountLineItems creates BonusDiscountLineItems (ChoiceOfBonusProduct) with the select Bonus Product.
* @param {dw.order.Basket} sourceBasket - the source basket
* @param {BasketMergeContext} basketMergeContext - the basket merge context
*/
function mergeBonusDiscountLineItems(sourceBasket, basketMergeContext) {
function countSelectedQuantities(discountLineItem) {
return discountLineItem.bonusProductLineItems.toArray().reduce((sum, pli) => sum + pli.quantityValue, 0);
}
var targetBasket = basketMergeContext.targetBasket;
var targetDiscountLineItems = targetBasket.bonusDiscountLineItems.toArray();
sourceBasket.bonusDiscountLineItems.toArray().forEach(sourceDiscountLineItem => {
var sourceBonusProductLineItems = sourceDiscountLineItem.bonusProductLineItems;
if (sourceBonusProductLineItems.size() == 0) {
dw.system.Logger.debug(info + " without selected Products");
return;
}
var promotionId = sourceDiscountLineItem.promotionID;
var info = "merge bonus discount " + promotionId;
var targetDiscountLineItem = targetDiscountLineItems.find(item => promotionId == item.promotionID //
&& sourceDiscountLineItem.maxBonusItems == item.maxBonusItems //
&& collectionEquals(sourceDiscountLineItem.bonusProducts, item.bonusProducts)
);
if (!targetDiscountLineItem) {
dw.system.Logger.debug(info + " no matching target BonusDiscountLineItem")
return;
}
var selectedQuantitySource = countSelectedQuantities(sourceDiscountLineItem);
var selectedQuantityTarget = countSelectedQuantities(targetDiscountLineItem);
if (selectedQuantitySource + selectedQuantityTarget > targetDiscountLineItem.maxBonusItems) {
var alreadySelectedBonusProducts = targetDiscountLineItem.bonusProductLineItems.toArray().map(item => item.productID + "=" + item.quantityValue);
dw.system.Logger.debug(info + " has already selected bonus products " + alreadySelectedBonusProducts);
return;
}
sourceBonusProductLineItems.toArray().forEach(sourceBonusProductLineItem => {
var product = sourceBonusProductLineItem.product;
var optionModel = sourceBonusProductLineItem.optionModel;
var qty = sourceBonusProductLineItem.quantityValue
var targetShipment = targetBasket.getShipment(sourceBonusProductLineItem.shipment.ID);
// createBonusProductLineItem( bonusDiscountLineItem,product, optionModel,shipment )
var targetBonusProductLineItem = targetBasket.createBonusProductLineItem(targetDiscountLineItem, product, optionModel, targetShipment);
var infoSelected = info + " select " + product.ID + "=" + qty
dw.system.Logger.debug(infoSelected);
targetBonusProductLineItem.quantityValue = qty;
var copyInfo = infoSelected + " copy ProductLineItem"
copySystemAttributes(copyInfo, sourceBonusProductLineItem, targetBonusProductLineItem);
copyCustomAttributes(copyInfo, sourceBonusProductLineItem, targetBonusProductLineItem);
})
});
}
/**
* Evaluates if two dw.util.Collection are equal
* @param {dw.util.Collection} collection1
* @param {dw.util.Collection} collection2
* @returns {boolean}
*/
function collectionEquals(collection1, collection2) {
var len = collection1.size();
if (len != collection2.size()) {
return false;
}
for (let i = 0; i < len; i++) {
if (collection1[i] != collection2[i]) {
return false;
}
}
return true;
}
/**
* Optional transform a value.
* @param {*} value - the value
* @param {Function<*,*>} optionalTransform - the optional transform
*/
function transform(value, optionalTransform) {
if (optionalTransform) {
return optionalTransform(value)
}
return value
}
/**
* Get system attributes
* @param object - the object to determine its system attributes
* @param {Function<string>} - ignore predicate to determine ignored system attributes
* @returns {Array<dw.object.ObjectAttributeDefinition>}
*/
function getSystemAttributes(object, ignore) {
return object.describe().attributeDefinitions.toArray()
.filter(attribute => attribute.isSystem() && !attribute.isKey() && !ignore(attribute.ID))
.sort((a, b) => a.ID > b.ID ? 1 : 0);
}
/**
* copy system attributes by attribute definition
* @param {string} info - the info for logging
* @param source - the source object to copy its system attributes
* @param target - the target object to copy the system attributes into
* @param {Array<dw.object.ObjectAttributeDefinition>} attributes
*/
function copySystemAttributesExplicitByAttributes(info, source, target, attributes) {
var ids = attributes.map(attribute => attribute.ID);
dw.system.Logger.debug(info + " system attributes [" + ids + "]", info);
attributes.forEach(attribute => {
var id = attribute.ID;
if (isEnumAttribute(attribute)) {
copySystemAttributeFromTo(info, source, target, id, id, e => e.value);
} else {
copySystemAttributeFromTo(info, source, target, id, id, null);
}
});
}
/**
* Evaluate if the attrubute is an enum.
* @param {dw.object.ObjectAttributeDefinition} attribute
* @returns {boolean}
*/
function isEnumAttribute(attribute) {
var attributeType = attribute.valueTypeCode;
return attributeType == dw.object.ObjectAttributeDefinition.VALUE_TYPE_ENUM_OF_INT;
}
/**
* copy system attribute if exists in source
* @param {string} info - the info for logging
* @param source - the source object to copy its system attributes
* @param target - the target object to copy the system attributes into
* @param {string} attributeName
* @param {boolean} logValue - define if the value is logged
*/
function copySystemAttributeIfExisting(info, source, target, attributeName, logValue) {
var value = source[attributeName];
if (value) {
if (logValue) {
dw.system.Logger.debug(info + " system attribute " + attributeName + "=" + value);
} else {
dw.system.Logger.debug(info + " system attribute " + attributeName);
}
target[attributeName] = value;
}
}
/**
* copy system attributes by name
* @param {string} info - the info for logging
* @param source - the source object to copy its system attributes
* @param target - the target object to copy the system attributes into
* @param {Array<string>} attributeNames
*/
function copySystemAttributesExplicitByNames(info, source, target, attributeNames) {
var desc = target.describe();
var type = desc.ID;
dw.system.Logger.debug(info + " copy " + type + " system attributes [" + attributeNames + "]");
attributeNames.forEach(id =>
copySystemAttributeFromTo(info, source, target, id, id, null)
);
}
/**
* copy a system attribute
* @param {string} info - the info for logging
* @param source - the source object to copy its system attrubute
* @param target - the target object to copy the system attribute into
* @param {string} sourceAttributeName
* @param {string} targetAttributeName
* @param {Function<?,?>} converter - converter to convert the value or null
*/
function copySystemAttributeFromTo(info, source, target, sourceAttributeName, targetAttributeName, converter) {
if (sourceAttributeName != targetAttributeName) {
dw.system.Logger.debug("{} system attributes [{} -> {}]", info, sourceAttributeName, targetAttributeName);
}
try {
var sourceValue = source[sourceAttributeName];
target[targetAttributeName] = transform(sourceValue, converter);
}
catch (e) {
dw.system.Logger.error("{} set system attribute {} -> {} {}", info, sourceAttributeName, targetAttributeName, e);
}
}
/**
* copy system attributes based on the {dw.object.ObjectTypeDefinition}
* @param {string} info - the info for logging
* @param source - the source object to copy its system attributes
* @param target - the target object to copy the system attributes into
* @param {Function<string>} ignore - predicate to determine ignored system attributes
*/
function copySystemAttributes(info, source, target, ignore) {
var attributes = getSystemAttributes(target, id => {
if (ignore && ignore(id)) {
return true;
}
// skip some unmodifiable system attributes
return id == "creationDate" || id == "lastModified" || id == "UUID";
});
copySystemAttributesExplicitByAttributes(info, source, target, attributes);
}
/**
* copy custom attributes
* @param {string} into - the info for logging
* @param source - the source object to copy its custom attributes
* @param target - the target object to copy the custom attributes into
*/
function copyCustomAttributes(info, source, target) {
var array = new Array();
for (var id in source.custom) {
var value = source.custom[id];
target.custom[id] = value;
array.push(id);
}
dw.system.Logger.debug(info + " custom attributes [" + array + "]");
}
/**
* Get a Map by item uuid
* @param {dw.order.Basket} basket - the basket
* @param {Function<dw.order.ProductLineItem>} transform - the transform to extract a value out of the ProductLineItem
* @returns {Map<string,?>}
*/
function getItemsByUUID(basket, transform) {
// collecting all items from basket
return basket.productLineItems.toArray().reduce((map, item) => {
map.set(item.getUUID(), transform(item));
return map;
}, new Map());
}
/**
* @param {dw.order.ProductLineItem} targetItem
* @param {double} initialQuatity
*/
function TargetProductLineItem(targetItem) {
var _quantity = targetItem.quantity;
this.targetItem = targetItem;
this.updateQuantity =
/**
* @param {double} quantity
*/
function(quantity) {
_quantity = quantity;
targetItem.setQuantityValue(quantity);
}
this.getQuantity =
/**
* @returns {dw.value.Quantity}
*/
function() {
return _quantity;
}
return this;
}
/**
* @param {dw.order.Basket} targetBasket
*/
function BasketMergeContext(targetBasket) {
this.targetBasket = targetBasket;
var _targetProductLineItems = getItemsByUUID(targetBasket, item => new TargetProductLineItem(item));
this.getTargetProductLineItems =
/**
* @returns {Map<string,TargetProductLineItem}
*/
function() {
return _targetProductLineItems;
};
/**
* @param {string} uuid
* @returns {TargetProductLineItem}
*/
function getTargetProductLineItem(uuid) {
return _targetProductLineItems.get(uuid);
};
this.updateQuantity =
/**
* @param {dw.order.ProductLineItem} targetItem
* @param {double} quantity
*/
function(targetItem, quantity) {
var target = getTargetProductLineItem(targetItem.UUID);
if (!target) {
target = new TargetProductLineItem(targetItem);
_targetProductLineItems.set(targetItem.UUID, target);
}
target.updateQuantity(quantity);
};
this.getQuantity =
/**
* @param {dw.order.ProductLineItem} targetItem
* @returns {sw.value.Quantity}
*/
function(targetItem) {
var target = getTargetProductLineItem(targetItem.UUID);
return target ? target.getQuantity() : null;
};
return this;
}
/**
* Get the ProductOptionModel or null.
* @param {string} info - the info for logging
* @param {dw.order.ProductLineItem} item - the product line item
* @returns {dw.catalog.ProductOptionModel}
*/
function getOptionModel(info, item) {
var optionModel = item.optionModel;
if (optionModel) {
optionModel.options.toArray().forEach(option => {
var optionValue = optionModel.getSelectedOptionValue(option);
dw.system.Logger.debug(info + " option " + option.ID + "=" + optionValue.ID);
});
}
return optionModel;
}
/**
* Get the Shipment with the provided shipment id or the default shipment.
* @param {dw.order.Basket} basket: the basket
* @param {string} shipmentId: the shipment Id
* @returns {dw.order.Shipment}
*
*/
function getShipment(basket, shipmentId) {
return basket.getShipment(shipmentId) || basket.defaultShipment;
}
module.exports = {
mergeBasket: mergeBasket
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment