menu

SiteGenesis / Server-side JS / Source: app_storefront_controllers/cartridge/controllers/COShipping.js

'use strict';

/**
 * Controller for the default single shipping scenario.
 * Single shipping allows only one shipment, shipping address, and shipping method per order.
 *
 * @module controllers/COShipping
 */

/* API Includes */
var CustomerMgr = require('dw/customer/CustomerMgr');
var HashMap = require('dw/util/HashMap');
var Resource = require('dw/web/Resource');
var ShippingMgr = require('dw/order/ShippingMgr');
var Site = require('dw/system/Site');
var Transaction = require('dw/system/Transaction');
var URLUtils = require('dw/web/URLUtils');

/* Script Modules */
var app = require('~/cartridge/scripts/app');
var guard = require('~/cartridge/scripts/guard');

/**
 * Prepares shipments. Theis function separates gift certificate line items from product
 * line items. It creates one shipment per gift certificate purchase
 * and removes empty shipments. If in-store pickup is enabled, it combines the
 * items for in-store pickup and removes them.
 * This function can be called by any checkout step to prepare shipments.
 *
 * @transactional
 * @return {Boolean} true if shipments are successfully prepared, false if they are not.
 */
function prepareShipments() {
    var cart, homeDeliveries;
    cart = app.getModel('Cart').get();

    homeDeliveries = Transaction.wrap(function () {
        var homeDeliveries = false;

        cart.updateGiftCertificateShipments();
        cart.removeEmptyShipments();

        if (Site.getCurrent().getCustomPreferenceValue('enableStorePickUp')) {
            homeDeliveries = cart.consolidateInStoreShipments();

            session.forms.singleshipping.inStoreShipments.shipments.clearFormElement();
            app.getForm('singleshipping.inStoreShipments.shipments').copyFrom(cart.getShipments());
        } else {
            homeDeliveries = true;
        }

        return homeDeliveries;
    });

    return homeDeliveries;
}

/**
 * Starting point for the single shipping scenario. Prepares a shipment by removing gift certificate and in-store pickup line items from the shipment.
 * Redirects to multishipping scenario if more than one physical shipment is required and redirects to billing if all line items do not require
 * shipping.
 *
 * @transactional
 */
function start() {
    var cart = app.getModel('Cart').get();
    var physicalShipments, pageMeta, homeDeliveries;

    if (!cart) {
        app.getController('Cart').Show();
        return;
    }
    // Redirects to multishipping scenario if more than one physical shipment is contained in the basket.
    physicalShipments = cart.getPhysicalShipments();
    if (Site.getCurrent().getCustomPreferenceValue('enableMultiShipping') && physicalShipments && physicalShipments.size() > 1) {
        app.getController('COShippingMultiple').Start();
        return;
    }

    // Initializes the singleshipping form and prepopulates it with the shipping address of the default
    // shipment if the address exists, otherwise it preselects the default shipping method in the form.
    if (cart.getDefaultShipment().getShippingAddress()) {
        app.getForm('singleshipping.shippingAddress.addressFields').copyFrom(cart.getDefaultShipment().getShippingAddress());
        app.getForm('singleshipping.shippingAddress.addressFields.states').copyFrom(cart.getDefaultShipment().getShippingAddress());
        app.getForm('singleshipping.shippingAddress').copyFrom(cart.getDefaultShipment());
    } else {
        if (customer.authenticated && customer.registered && customer.addressBook.preferredAddress) {
            app.getForm('singleshipping.shippingAddress.addressFields').copyFrom(customer.addressBook.preferredAddress);
            app.getForm('singleshipping.shippingAddress.addressFields.states').copyFrom(customer.addressBook.preferredAddress);
        }
    }
    session.forms.singleshipping.shippingAddress.shippingMethodID.value = cart.getDefaultShipment().getShippingMethodID();

    // Prepares shipments.
    homeDeliveries = prepareShipments();

    Transaction.wrap(function () {
        cart.calculate();
    });

    // Go to billing step, if we have no product line items, but only gift certificates in the basket, shipping is not required.
    if (cart.getProductLineItems().size() === 0) {
        app.getController('COBilling').Start();
    } else {
        pageMeta = require('~/cartridge/scripts/meta');
        pageMeta.update({
            pageTitle: Resource.msg('singleshipping.meta.pagetitle', 'checkout', 'SiteGenesis Checkout')
        });
        app.getView({
            ContinueURL: URLUtils.https('COShipping-SingleShipping'),
            Basket: cart.object,
            HomeDeliveries: homeDeliveries
        }).render('checkout/shipping/singleshipping');
    }
}

/**
 * Handles the selected shipping address and shipping method. Copies the
 * address details and gift options to the basket's default shipment. Sets the
 * selected shipping method to the default shipment.
 *
 * @transactional
 * @param {module:models/CartModel~CartModel} cart - A CartModel wrapping the current Basket.
 */
function handleShippingSettings(cart) {
    Transaction.wrap(function () {
        var defaultShipment, shippingAddress;
        defaultShipment = cart.getDefaultShipment();
        shippingAddress = cart.createShipmentShippingAddress(defaultShipment.getID());

        shippingAddress.setFirstName(session.forms.singleshipping.shippingAddress.addressFields.firstName.value);
        shippingAddress.setLastName(session.forms.singleshipping.shippingAddress.addressFields.lastName.value);
        shippingAddress.setAddress1(session.forms.singleshipping.shippingAddress.addressFields.address1.value);
        shippingAddress.setAddress2(session.forms.singleshipping.shippingAddress.addressFields.address2.value);
        shippingAddress.setCity(session.forms.singleshipping.shippingAddress.addressFields.city.value);
        shippingAddress.setPostalCode(session.forms.singleshipping.shippingAddress.addressFields.postal.value);
        shippingAddress.setStateCode(session.forms.singleshipping.shippingAddress.addressFields.states.state.value);
        shippingAddress.setCountryCode(session.forms.singleshipping.shippingAddress.addressFields.country.value);
        shippingAddress.setPhone(session.forms.singleshipping.shippingAddress.addressFields.phone.value);
        defaultShipment.setGift(session.forms.singleshipping.shippingAddress.isGift.value);
        defaultShipment.setGiftMessage(session.forms.singleshipping.shippingAddress.giftMessage.value);

        cart.updateShipmentShippingMethod(cart.getDefaultShipment().getID(), session.forms.singleshipping.shippingAddress.shippingMethodID.value, null, null);
        cart.calculate();

        cart.validateForCheckout();
    });
}

/**
 * Updates shipping address for the current customer with information from the singleshipping form. If a cart exists, redirects to the
 * {@link module:controllers/COShipping~start|start} function. If one does not exist, calls the {@link module:controllers/Cart~Show|Cart controller Show function}.
 *
 * @transactional
 */
function updateAddressDetails() {
    var addressID, segments, lookupCustomer, lookupID, address, profile;
    //Gets an empty cart object from the CartModel.
    var cart = app.getModel('Cart').get();

    if (!cart) {
        app.getController('Cart').Show();
        return;
    }
    addressID = request.httpParameterMap.addressID.value ? request.httpParameterMap.addressID.value : request.httpParameterMap.dwfrm_singleshipping_addressList.value;
    segments = addressID.split('??');

    lookupCustomer = customer;
    lookupID = addressID;

    if (segments.length > 1) {
        profile = CustomerMgr.queryProfile('email = {0}', segments[0]);
        lookupCustomer = profile.getCustomer();
        lookupID = segments[1];
    }

    address = lookupCustomer.getAddressBook().getAddress(lookupID);
    app.getForm('singleshipping.shippingAddress.addressFields').copyFrom(address);
    app.getForm('singleshipping.shippingAddress.addressFields.states').copyFrom(address);

    Transaction.wrap(function () {
        var defaultShipment = cart.getDefaultShipment();
        var shippingAddress = cart.createShipmentShippingAddress(defaultShipment.getID());

        shippingAddress.setFirstName(session.forms.singleshipping.shippingAddress.addressFields.firstName.value);
        shippingAddress.setLastName(session.forms.singleshipping.shippingAddress.addressFields.lastName.value);
        shippingAddress.setAddress1(session.forms.singleshipping.shippingAddress.addressFields.address1.value);
        shippingAddress.setAddress2(session.forms.singleshipping.shippingAddress.addressFields.address2.value);
        shippingAddress.setCity(session.forms.singleshipping.shippingAddress.addressFields.city.value);
        shippingAddress.setPostalCode(session.forms.singleshipping.shippingAddress.addressFields.postal.value);
        shippingAddress.setStateCode(session.forms.singleshipping.shippingAddress.addressFields.states.state.value);
        shippingAddress.setCountryCode(session.forms.singleshipping.shippingAddress.addressFields.country.value);
        shippingAddress.setPhone(session.forms.singleshipping.shippingAddress.addressFields.phone.value);
        defaultShipment.setGift(session.forms.singleshipping.shippingAddress.isGift.value);
        defaultShipment.setGiftMessage(session.forms.singleshipping.shippingAddress.giftMessage.value);
    });

    start();
}

/**
 * Form handler for the singleshipping form. Handles the following actions:
 * - __save__ - saves the shipping address from the form to the customer address book. If in-store
 * shipments are enabled, saves information from the form about in-store shipments to the order shipment.
 * Flags the save action as done and calls the {@link module:controllers/Cart~Show|Cart controller Show function}.
 * If it is not able to save the information, calls the {@link module:controllers/Cart~Show|Cart controller Show function}.
 * - __selectAddress__ - updates the address details and page metadata, sets the ContinueURL property to COShipping-SingleShipping,  and renders the singleshipping template.
 * - __shipToMultiple__ - calls the {@link module:controllers/COShippingMultiple~Start|COShippingMutliple controller Start function}.
 * - __error__ - calls the {@link module:controllers/COShipping~Start|COShipping controller Start function}.
 */
function singleShipping() {
    var singleShippingForm = app.getForm('singleshipping');
    singleShippingForm.handleAction({
        save: function () {
            var cart = app.getModel('Cart').get();
            if (!cart) {
                // @FIXME redirect
                app.getController('Cart').Show();
                return;
            }

            handleShippingSettings(cart);

            // Attempts to save the used shipping address in the customer address book.
            if (customer.authenticated && session.forms.singleshipping.shippingAddress.addToAddressBook.value) {
                app.getModel('Profile').get(customer.profile).addAddressToAddressBook(cart.getDefaultShipment().getShippingAddress());
            }
            // Binds the store message from the user to the shipment.
            if (Site.getCurrent().getCustomPreferenceValue('enableStorePickUp')) {
                if (!app.getForm(session.forms.singleshipping.inStoreShipments.shipments).copyTo(cart.getShipments())) {
                    require('./Cart').Show();
                    return;
                }
            }

            // Mark step as fulfilled.
            session.forms.singleshipping.fulfilled.value = true;
            // @FIXME redirect
            app.getController('COBilling').Start();
        },
        selectAddress: function () {
            updateAddressDetails(app.getModel('Cart').get());

            var pageMeta = require('~/cartridge/scripts/meta');
            pageMeta.update({
                pageTitle: Resource.msg('singleshipping.meta.pagetitle', 'checkout', 'SiteGenesis Checkout')
            });
            app.getView({
                ContinueURL: URLUtils.https('COShipping-SingleShipping')
            }).render('checkout/shipping/singleshipping');
        },
        shipToMultiple: app.getController('COShippingMultiple').Start,
        error: function () {
            response.redirect(URLUtils.https('COShipping-Start'));
        }
    });
}

/**
 * Selects a shipping method for the default shipment. Creates a transient address object, sets the shipping
 * method, and returns the result as JSON response.
 *
 * @transaction
 */
function selectShippingMethod() {
    var cart = app.getModel('Cart').get();
    var TransientAddress = app.getModel('TransientAddress');
    var address, applicableShippingMethods;

    if (!cart) {
        app.getView.render('checkout/shipping/selectshippingmethodjson');
        return;
    }
    address = new TransientAddress();
    address.countryCode = request.httpParameterMap.countryCode.stringValue;
    address.stateCode = request.httpParameterMap.stateCode.stringValue;
    address.postalCode = request.httpParameterMap.postalCode.stringValue;
    address.city = request.httpParameterMap.city.stringValue;
    address.address1 = request.httpParameterMap.address1.stringValue;
    address.address2 = request.httpParameterMap.address2.stringValue;

    applicableShippingMethods = cart.getApplicableShippingMethods(address);

    Transaction.wrap(function () {
        cart.updateShipmentShippingMethod(cart.getDefaultShipment().getID(), request.httpParameterMap.shippingMethodID.stringValue, null, applicableShippingMethods);
        cart.calculate();
    });

    app.getView({
        Basket: cart.object
    }).render('checkout/shipping/selectshippingmethodjson');
}

/**
 * Determines the list of applicable shipping methods for the default shipment of
 * the current basket. The applicable shipping methods are based on the
 * merchandise in the cart and any address parameters included in the request.
 * Changes the shipping method of this shipment if the current method
 * is no longer applicable. Precalculates the shipping cost for each applicable
 * shipping method by simulating the shipping selection i.e. explicitly adds each
 * shipping method and then calculates the cart.
 * The simulation is done so that shipping cost along
 * with discounts and promotions can be shown to the user before making a
 * selection.
 * @transaction
 */
function updateShippingMethodList() {
    var i, address, applicableShippingMethods, shippingCosts, currentShippingMethod, method;
    var cart = app.getModel('Cart').get();
    var TransientAddress = app.getModel('TransientAddress');

    if (!cart) {
        app.getController('Cart').Show();
        return;
    }
    address = new TransientAddress();
    address.countryCode = request.httpParameterMap.countryCode.stringValue;
    address.stateCode = request.httpParameterMap.stateCode.stringValue;
    address.postalCode = request.httpParameterMap.postalCode.stringValue;
    address.city = request.httpParameterMap.city.stringValue;
    address.address1 = request.httpParameterMap.address1.stringValue;
    address.address2 = request.httpParameterMap.address2.stringValue;

    applicableShippingMethods = cart.getApplicableShippingMethods(address);
    shippingCosts = new HashMap();
    currentShippingMethod = cart.getDefaultShipment().getShippingMethod() || ShippingMgr.getDefaultShippingMethod();

    // Transaction controls are for fine tuning the performance of the data base interactions when calculating shipping methods
    Transaction.begin();

    for (i = 0; i < applicableShippingMethods.length; i++) {
        method = applicableShippingMethods[i];

        cart.updateShipmentShippingMethod(cart.getDefaultShipment().getID(), method.getID(), method, applicableShippingMethods);
        cart.calculate();
        shippingCosts.put(method.getID(), cart.preCalculateShipping(method));
    }

    Transaction.rollback();

    Transaction.wrap(function () {
        cart.updateShipmentShippingMethod(cart.getDefaultShipment().getID(), currentShippingMethod.getID(), currentShippingMethod, applicableShippingMethods);
        cart.calculate();
    });

    session.forms.singleshipping.shippingAddress.shippingMethodID.value = cart.getDefaultShipment().getShippingMethodID();

    app.getView({
        Basket: cart.object,
        ApplicableShippingMethods: applicableShippingMethods,
        ShippingCosts: shippingCosts
    }).render('checkout/shipping/shippingmethods');
}

/**
 * Determines the list of applicable shipping methods for the default shipment of
 * the current customer's basket and returns the response as a JSON array. The
 * applicable shipping methods are based on the merchandise in the cart and any
 * address parameters are included in the request parameters.
 */
function getApplicableShippingMethodsJSON() {
    var cart = app.getModel('Cart').get();
    var TransientAddress = app.getModel('TransientAddress');
    var address, applicableShippingMethods;

    address = new TransientAddress();
    address.countryCode = request.httpParameterMap.countryCode.stringValue;
    address.stateCode = request.httpParameterMap.stateCode.stringValue;
    address.postalCode = request.httpParameterMap.postalCode.stringValue;
    address.city = request.httpParameterMap.city.stringValue;
    address.address1 = request.httpParameterMap.address1.stringValue;
    address.address2 = request.httpParameterMap.address2.stringValue;

    applicableShippingMethods = cart.getApplicableShippingMethods(address);

    app.getView({
        ApplicableShippingMethods: applicableShippingMethods
    }).render('checkout/shipping/shippingmethodsjson');
}

/**
 * Renders a form dialog to edit an address. The dialog is opened
 * by an Ajax request and ends in templates, which trigger a JavaScript
 * event. The calling page of this dialog is responsible for handling these
 * events.
 */
function editAddress() {
    session.forms.shippingaddress.clearFormElement();
    var shippingAddress = customer.getAddressBook().getAddress(request.httpParameterMap.addressID.stringValue);

    if (shippingAddress) {
        app.getForm(session.forms.shippingaddress).copyFrom(shippingAddress);
        app.getForm(session.forms.shippingaddress.states).copyFrom(shippingAddress);
    }

    app.getView({
        ContinueURL: URLUtils.https('COShipping-EditShippingAddress')
    }).render('checkout/shipping/shippingaddressdetails');
}

/**
 * Form handler for the shippingAddressForm. Handles the following actions:
 *  - __apply__ - if form information cannot be copied to the platform, it sets the ContinueURL property to COShipping-EditShippingAddress and
 *  renders the shippingAddressDetails template. Otherwise, it renders the dialogapply template.
 *  - __remove__ - removes the address from the current customer's address book and renders the dialogdelete template.
 */
function editShippingAddress() {
    var shippingAddressForm = app.getForm('shippingaddress');
    shippingAddressForm.handleAction({
        apply: function () {
            var object = {};
            // @FIXME what is this statement used for?
            if (!app.getForm(session.forms.shippingaddress).copyTo(object) || !app.getForm(session.forms.shippingaddress.states).copyTo(object)) {
                app.getView({
                    ContinueURL: URLUtils.https('COShipping-EditShippingAddress')
                }).render('checkout/shipping/shippingaddressdetails');
            } else {
                app.getView().render('components/dialog/dialogapply');
            }
        },
        remove: function () {
            customer.getAddressBook().removeAddress(session.forms.shippingaddress.object);
            app.getView().render('components/dialog/dialogdelete');
        }
    });
}

/*
* Module exports
*/

/*
* Web exposed methods
*/
/** Starting point for the single shipping scenario.
 * @see module:controllers/COShipping~start */
exports.Start = guard.ensure(['https'], start);
/** Selects a shipping method for the default shipment.
 * @see module:controllers/COShipping~selectShippingMethod */
exports.SelectShippingMethod = guard.ensure(['https', 'get'], selectShippingMethod);
/** Determines the list of applicable shipping methods for the default shipment of the current basket.
 * @see module:controllers/COShipping~updateShippingMethodList */
exports.UpdateShippingMethodList = guard.ensure(['https', 'get'], updateShippingMethodList);
/** Determines the list of applicable shipping methods for the default shipment of the current customer's basket and returns the response as a JSON array.
 * @see module:controllers/COShipping~getApplicableShippingMethodsJSON */
exports.GetApplicableShippingMethodsJSON = guard.ensure(['https', 'get'], getApplicableShippingMethodsJSON);
/** Renders a form dialog to edit an address.
 * @see module:controllers/COShipping~editAddress */
exports.EditAddress = guard.ensure(['https', 'get'], editAddress);
/** Updates shipping address for the current customer with information from the singleshipping form.
 * @see module:controllers/COShipping~updateAddressDetails */
exports.UpdateAddressDetails = guard.ensure(['https', 'get'], updateAddressDetails);
/** Form handler for the singleshipping form.
 * @see module:controllers/COShipping~singleShipping */
exports.SingleShipping = guard.ensure(['https', 'post', 'csrf'], singleShipping);
/** Form handler for the shippingAddressForm.
 * @see module:controllers/COShipping~editShippingAddress */
exports.EditShippingAddress = guard.ensure(['https', 'post'], editShippingAddress);

/*
 * Local methods
 */
exports.PrepareShipments = prepareShipments;