
<!DOCTYPE html>

<!--[if IE]><![endif]-->
<!--[if IE 9]> <html class="no-js ie9 lt-ie10" lang="en"> <![endif]-->
<!--[if gt IE 9]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
<!-- include files here -->


<head>
    
<link rel="preconnect" href="https://fonts.gstatic.com/" />

  <link href="https://fonts.googleapis.com/css?family=Heebo:400,500,700&display=swap" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'"/>  	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=2">
	
	

	<title>Front Line Safety</title>
	<meta name="description" content="">
	<meta name="keywords" content="FLS">
		<meta name="robots" content="noindex, nofollow, noimageindex"><link rel="stylesheet" href="templates/fa/css/custom-font-awesome.min.css">
	<link rel="shortcut icon" type="image/x-icon" href="https://d23i9o1ddbbnse.cloudfront.net/images/fls-favicon.png?v=3983598294">
	<link rel="icon" type="image/png" href="" sizes="16x16" />
	<link rel="icon" type="image/png" href="" sizes="32x32" />
	<link rel="icon" type="image/png" href="" sizes="48x48" />

	<link rel="preload" href="//cdn.datatables.net/v/dt/dt-1.10.16/cr-1.4.1/fh-3.1.3/r-2.2.0/rg-1.0.2/datatables.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'"/>

	
		<link rel="preload" href="css/bootstrap-dashboard.css?1851c59" as="style" onload="this.onload=null;this.rel='stylesheet'">
		<link rel="preload" href="css/dashboard-user.css?1851c59" as="style" onload="this.onload=null;this.rel='stylesheet'">
		<link rel="preload" href="../css/focus.css?1851c59" as="style" onload="this.onload=null;this.rel='stylesheet'">

	
    <link rel="preload" href="css/notemplate.css?1851c59" as="style" onload="this.onload=null;this.rel='stylesheet'">


    <style>

        /* 
        ### Skins CSS Overrides
        */
        
                /* Dashboard Template Styles 
                --------------------------------------------------------------- */

                h1, h2, h3, h4, h5, h6,
                input, button, select, textarea,
                body, body.dashboard {
                    font-family: Heebo
                }

                a { 
                    color: #00A3E0; 
                }

                .text-warning,
                a.text-warning {
                    color: #8a6d3b;
                }

                .text-warning:hover {
                    color: #8a6d3b;
                }

                a.text-warning:hover {
                    color: #66512c;
                }

                .text-danger,
                a.text-danger {
                    color: #a94442;
                }

                .text-danger:hover {
                    color: #a94442;
                }

                a.text-danger:hover {
                    color: #843534;
                }

                .text-success,
                a.text-success {
                    color: #3c763d;
                }

                .text-success:hover {
                    color: #3c763d;
                }

                a.text-success:hover {
                    color: #2b542c;
                }

                .text-info,
                a.text-info {
                    color: #31708f;
                }

                .text-info:hover {
                    color: #31708f;
                }

                a.text-info:hover {
                    color: #245269;
                }

                li.active>a,
                a.active,
                a:hover,
                a:focus { 
                    color: rgb(26,188,250); 
                }

                li>a.activity-nav__link {
                    color: #333333e6;
                }

                li>a.activity-nav__link.active,
                li>a.activity-nav__link:hover,
                li>a.activity-nav__link:focus {
                    color: #333333;
                }

                .portal-home .thumbnail:hover {
                    color: rgb(26,188,250); 
                    border-color: rgb(26,188,250);
                }

                .portal-home .thumbnail:active {
                    border-color: #ddd;
                }

                h1, h2, h3, h4, h5, h6, 
                .formblock-heading, 
                legend, 
                .checkout-section-heading, 
                .checkout-section.active .checkout-section-heading { 
                    color: #444444; 
                }

                .pace .pace-progress {
                    background: #00A3E0;
                }


                /* Default buttons 
                --------------------------------------------------------------- */

                .btn-link {
                    color: #00A3E0;
                }

                .btn-link:not([disabled]):hover, 
                .btn-link:not([disabled]):focus, 
                .btn-link:not([disabled]):active, 
                .btn-link:not([disabled]).active {
                    color: rgb(26,188,250);
                }

                /* Primary buttons */
                .btn-primary, .btn-primary.button {
                    background-color: #00A3E0;
                    border-color: #00A3E0;
                    color: #FFFFFF;
                }

                .btn-primary:not([disabled]):hover, 
                .btn-primary:not([disabled]):focus, 
                .btn-primary:not([disabled]):active, 
                .btn-primary:not([disabled]).active, 
                .btn-primary:not([disabled]).button:hover, 
                .btn-primary:not([disabled]).button:focus, 
                .btn-primary:not([disabled]).button:active, 
                .btn-primary:not([disabled]).button.active {
                    background-color: rgb(26,188,250);
                    border-color: rgb(26,188,250);
                    color: #FFFFFF;
                }

                /* Secondary buttons */
                .btn:not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info), 
                .btn:not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info).button {
                    background-color: #ffffff;
                    border-color: rgb(204,204,204);
                    color: #444444;
                }

                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info):hover, 
                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info):focus, 
                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info):active, 
                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info).active, 
                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info).button:hover, 
                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info).button:focus, 
                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info).button:active, 
                .btn:not([disabled]):not(.btn-primary):not(.btn-error):not(.btn-danger):not(.btn-warning):not(.btn-success):not(.btn-info).button.active {
                    background-color: rgb(245,245,245);
                    border-color: rgb(204,204,204);
                    color: #222222;
                }

                .list-group-item.active, 
                .list-group-item.active:hover, 
                .list-group-item.active:focus {
                    color: #FFFFFF !important;
                    border-color: #00A3E0 !important;
                    background-color: #00A3E0 !important;
                }

                /* Dashboard Header
                --------------------------------------------------------------- */

                .focus-header,
                .app-settings__tabs,
                .dashboard-header-1 {
                    background-color: #FFFFFF;
                    color: #333333;
                    border-bottom: 1px solid rgb(230,230,230);
                }

                .focus-header__exit-wrapper {
                    border-color: rgb(230,230,230);
                }

                .focus-header__exit {
                    color: #333333;
                }
                
                .focus-header__exit:focus,
                .focus-header__exit:active,
                .focus-header__exit:hover {
                    color: #333333 !important;
                    background-color:  rgb(230,230,230);
                }

                body.dashboard:not(.menu-open) .dashboard-header-1 {
                    border-left: none;
                }
                
                body.dashboard .off-canvas-hide,
                body.dashboard.-loading-dom .menu-shelf__btn-wrapper,
                .dashboard-header-1 .app-navicon {
                    color: #333333;
                    border-left: 1px solid rgb(230,230,230);
                }

                body.dashboard .off-canvas-show {
                    color: #333333;
                    border-right: 1px solid rgb(230,230,230);
                }

                @media (max-width: 480px) {
                    .dashboard-header-1 .app-logo {
                        border-right: 1px solid rgb(230,230,230);
                    }
                }

                .dashboard-header-1 .app-logo__validip-name:after {
                    background-color: rgb(230,230,230);
                }

                body.dashboard .off-canvas-hide:hover,
                .dashboard-header-1 .app-navicon:not(.app-logo):hover {
                    color: #333333;
                    border-left: 1px solid rgb(230,230,230);
                    background-color: rgb(245,245,245);
                }

                .dashboard-header-1 .app-logo:hover {
                    background-color: transparent;
                }

                body.dashboard .off-canvas-show:hover {
                    color: #333333;
                    border-right: 1px solid rgb(230,230,230);
                    background-color: rgb(245,245,245);
                }

                body.dashboard .off-canvas-hide:active,
                .dashboard-header-1 .app-navicon:not(.app-logo):active {
                    color: #333333;
                    background-color: rgb(250,250,250);
                }

                .dashboard-header-1 .app-logo:active {
                    background-color: transparent;
                }

                body.dashboard .off-canvas-show:active {
                    color: #333333;
                    background-color: rgb(250,250,250);
                }

                .dashboard-header-1 .app-navicon:not(.app-logo).active {
                    color: #333333;
                    background-color: rgb(250,250,250);
                }

                .dashboard-header-1 .app-navicon:not(.app-logo).active:hover {
                    color: #333333;
                    background-color: rgb(245,245,245);
                }

                .dashboard-header-1 .app-navicon:not(.app-logo).active:active {
                    color: #333333;
                    background-color: rgb(250,250,250);
                }

                .dashboard-header-1 .app-navicon .badge {
                    background-color: #00A3E0;
                    color: #FFFFFF;
                }

                #dashboard-header-1 .header__search__button {
                    background-color: #00A3E0;
                    color: #FFFFFF;
                }

                #dashboard-header-1 .header__search__button:hover,
                #dashboard-header-1 .header__search__button:focus {
                    color: #FFFFFF;
                    background-color: rgb(26,188,250);
                }

                .dashboard-header-1 .menu-drawer__lock {
                    color: #333333;
                    border: none;
                    background: none;
                }

                .dashboard-header-1 .menu-drawer__lock:hover,
                .dashboard-header-1 .menu-drawer__lock:focus,
                .dashboard-header-1 .menu-drawer__lock:active,
                .dashboard-header-1 .menu-drawer__lock.active {
                    color: #333333;
                    border: none;
                    background: none;
                }

                .app-cartqty {
                    color: #333333;
                }

                .menu-drawer__filter-search.input-search input:focus,
                code:focus, pre:focus, select:focus, textarea:focus, input:not(.btn):focus {
                    border-color: #00A3E0;
                    box-shadow: 0 0 0 2px rgba(0, 163, 224, 0.15);
                }

                .ac_results li.ac_over {
                    background-color: #00A3E0;
                }

                @media (max-width: 979px) {
                    .app-search {
                        background-color: #FFFFFF;
                        color: #333333;
                    }
                }


                /* Products
                --------------------------------------------------------------- */

                .prod-pricing .retail {
                    color: #00A3E0;
                }


                /* Dashboard Sidebars
                --------------------------------------------------------------- */
                
                .toolbar {
                    background-color: #000000;
                }

                @media (max-width: 480px) {
                    body:not(.menu-open) .toolbar {
                        background: none !important;
                    }
                }

                .toolbar__btn--mobile {
                    color: #333333;
                    background: #FFFFFF;
                }

                body.menu-open .toolbar__btn--mobile {
                    color: #FFFFFF;
                    background: #00A3E0;
                }

                .toolbar:before {
                    background-color: #000000;
                }

                .toolbar__btn {
                    color: #FFFFFF;
                }

                .toolbar__btn:hover {
                    background-color: rgb(56,56,56);
                }
                
                .toolbar__btn:active {
                    background-color: rgb(76,76,76);
                }
                
                .toolbar__btn.active {
                    background-color: rgb(46,46,46);
                }
                
                .toolbar__btn.active:hover {
                    background-color: rgb(56,56,56);
                }
                
                .toolbar__btn.active:active {
                    background-color: rgb(51,51,51);
                }
                
                .menu-drawer {
                    background-color: #FFFFFF;
                    color: #333333;
                    border-right: 1px solid rgb(230,230,230);
                }

                .menu-drawer .linkset>li>a {
                    color: rgba(51,51,51,0.9);
                }

                .menu-drawer .linkset>li>a:hover {
                    color: #333333;
                    background-color: rgb(247,247,247);
                }

                .menu-drawer .linkset>li>a:focus {
                    color: #00A3E0;
                }

                .menu-drawer .linkset>li>a:active {
                    color: #333333;
                    background-color: rgb(252,252,252);
                }

                .menu-drawer .linkset >li > a.active {
                    color: #333333;
                    background-color: rgb(247,247,247);
                    color: #00A3E0;
                }

                .menu-drawer .linkset ul > li,
                .childlink__actions .add-to-quick-access,
                .childlink__actions .add {
                    border-color: rgb(245,245,245);
                }

                .menu-drawer .linkset > li.open > a,
                .menu-drawer .linkset ul > li.open > a {
                    background-color: rgb(247,247,247);
                }

                .menu-drawer .linkset > li.open > a:hover,
                .menu-drawer .linkset ul > li.open > a:hover {
                    background-color: rgb(242,242,242);
                }

                .menu-drawer .linkset > li.open > a:active,
                .menu-drawer .linkset ul > li.open > a:active {
                    background-color: rgb(250,250,250);
                }

                .menu-drawer .linkset ul li > a.active {
                    color: #00A3E0;
                }

                .menu-drawer .linkset ul li > a.active:hover {
                    background-color: rgb(247,247,247);
                }

                .menu-drawer .linkset ul li > a.active:active {
                    background-color: rgb(250,250,250);
                }

                .menu-drawer .linkset ul li > a {
                    color: rgba(51,51,51,0.8);
                }

                .menu-drawer .linkset ul li > a:hover {
                    color: #333333;
                    background-color: rgb(245,245,245);
                }

                .menu-drawer .linkset ul li > a:active {
                    color: #333333;
                    background-color: rgb(250,250,250);
                }

                .menu-drawer .linkset ul li > a:focus {
                    color: #333333;
                    color: #00A3E0;
                }

                .menu-drawer .linkset li .highlight {
                    color: #00A3E0;
                }

                .menu-drawer__filter-wrapper {
                    background: #FFFFFF;
                    border-bottom: 1px solid rgb(230,230,230);
                }

                .menu-drawer__collapse {
                    color: rgba(51,51,51,0.2);
                    border-left: 1px solid rgb(230,230,230);
                    background: none;
                }

                .menu-drawer__collapse:hover {
                    color: #00A3E0;
                    background-color: rgb(245,245,245);
                }

                .menu-drawer__collapse:active {
                    color: #00A3E0;
                    background-color: rgb(250,250,250);
                }

                .menu-drawer__menu-title {
                    color: #333333;
                    border-bottom: 1px solid rgb(230,230,230);
                }

                .childlink__actions .add,
                .childlink__actions .add-to-quick-access {
                    color: rgba(51,51,51,0.2);
                }

                .childlink__actions .add:hover,
                .childlink__actions .add-to-quick-access:hover {
                    background-color: rgb(245,245,245);
                }

                .childlink__actions .add:focus {
                    color: #00A3E0;
                }

                .childlink__actions .add:active,
                .childlink__actions .add-to-quick-access:active {
                    background-color: rgb(250,250,250);
                }

                .childlink__actions .add.active {
                    background-color: none;
                    color: #00A3E0;
                }

                .menu-drawer__add-card {
                    color: #333333;
                }

                .menu-drawer__add-card:hover,
                .menu-drawer__add-card:focus {
                    color: #00A3E0;
                    background-color: rgb(245,245,245);
                }

                .menu-drawer__add-card:active {
                    background-color: rgb(250,250,250);
                }

                .menu-drawer__add-card:hover .menu-drawer__add-icon {
                    color: #00A3E0;
                }

                .app-settings__tab-item:hover,
                .app-settings__tab-item--no-scroll:hover {
                    color: #00A3E0;
                }

                .app-settings__tab-item:active,
                .app-settings__tab-item--no-scroll:active {
                    background-color: rgb(252,252,252);
                }

                .app-settings__tab-item.active,
                .app-settings__tab-item--no-scroll.active {
                    background-color: rgb(250,250,250);
                    color: #00A3E0;
                }

                .app-settings__tab-item.active:active,
                .app-settings__tab-item--no-scroll.active:active {
                    background-color: rgb(245,245,245);
                }

                .number-callout {
                    background: #00A3E0;
                }

                /* Breadcrumbs
                --------------------------------------------------------------- */

                body.dashboard .breadcrumb.breadcrumb-cart li.active {
                    background: #00A3E0;
                    color: #FFFFFF;
                }

                body.dashboard .breadcrumb.breadcrumb-cart li.active:after {
                    border-color: transparent;
                    border-left-color: #00A3E0;
                    border-width: 20px;
                }

                /* Tooltip Popups
                --------------------------------------------------------------- */

        

                
        
    </style>

<script>
    // This function is needed for JS hooks.
    function getOriginalPageName() {
        return 'signin.asp';
    }

    function getUrlPath() {
        return '/signin.asp';
    }

    function getUrl() {
        return '/signin.asp?autopage=%2Fsitemap.asp';
    }

    function handleImageError(img, noImagePath){
        if(!noImagePath) {
            noImagePath = 'images/no-image.png';
        }
        if ($(img).attr('src') !== noImagePath) {
            $(img).attr('src', noImagePath);
        }    
    }
    var sitename = "frontline4";
    var isWorkerDomain = false;
    var processPageTitle = isWorkerDomain && !false;
    var bValidIp = true;
    var sOfUrl   = 'https://frontline4.cimproduction.com';
</script>


<script>
    var utils = {};

    utils.isCrossOriginFrame = function (parentContext) {
        try {
            if(!parentContext) parentContext = window;
            if (parentContext === window) return false;

            return (document.location.hostname !== parentContext.location.hostname);
        } catch (e) {
            return true;
        }
    }

    utils.getParameter = function (param, context) {
        var value;
        if(!context) context = window;
        
        // return empty string if cross origin (can't access params)
        if(utils.isCrossOriginFrame(context)) return '';

        var parameters = context.location.search.replace('?', '');

        if (parameters) {
            var pattern = new RegExp('\\b' + param + '=([^;&]+)', 'gi');
            value = parameters.split(pattern)[1];
        }

        return value || '';
    };

    utils.urlEncodePath = function(url) {
        if(!url) return '';
        var encodedUrl = url;
        var pathPortionRegex = /^(https:\/\/[^\/]+\/|\/)?([^?\n]+)(\?[^\n]*)?$/gi;
        var pathPortion = url?.matchAll(pathPortionRegex)?.toArray()[0][2];

        if(pathPortion) {
            var urlEncodedPathPortion = encodeURIComponent(utils.htmlDecode(pathPortion))?.replace(/%2f/gi, '/');
            encodedUrl = encodedUrl.replace(pathPortion, urlEncodedPathPortion);
        }

        return encodedUrl;
    };

    utils.buildImagePath = function(image){
        if(image && image.indexOf("http") > -1){
            imageUrl = image.replace(/http:/i, 'https:');
        }else{
            image = image.replace(oConfig.storefrontUrl, oConfig.sessionData.cdnUrl).toLowerCase();
            if(!image){
                imageUrl = oConfig.noImagePath || oConfig.sessionData.cdnUrl.replace(/\/+$/, '') + '/images/' + oConfig.defaultImage.replace(/^\/+/, '');
            }
            else if(image.indexOf("/") > -1){
                imageUrl = oConfig.sessionData.cdnUrl.replace(/\/+$/, '') + '/' + image.replace(/^\/+/, '');
            }
            else{
                imageUrl = oConfig.sessionData.cdnUrl.replace(/\/+$/, '') + '/images/' + image.replace(/^\/+/, '');
            }
        }

        imageUrl = utils.urlEncodePath(imageUrl);

        cacheBustingPrefix = '?';
        if(imageUrl.includes('?')) cacheBustingPrefix = '&';
        return imageUrl + cacheBustingPrefix + 'v=3983598294';
    };

    utils.pageUrl = location.href.replace(/.+\//, '');
    utils.loginUrl = 'security_logon.asp?autopage=' + encodeURIComponent(utils.pageUrl);

    utils.pageName = function(){
        return "signin.asp";
    }

    utils.pageType = function(){
        var pageType = "";
        if(utils.pageUrl == '' && pageType == ''){
            pageType = 'home';
        }else if(pageType != ''){
            switch(pageType){
                case 'prodcat':
                case 'product':
                    pageType = 'catalog';
                    break;
                case 'page-section':
                case 'webpage':
                    pageType = 'content';
                    break;
                default:
                    pageType = 'other';
                    break;
            }
        }else{
            switch(getOriginalPageName()){
                case 'pc_product_detail.asp':
                case 'pc_combined_results.asp':
                case 'largest_spend_products.asp':
                case 'largest_qty_ordered_products.asp': 
                case 'recently_ordered_products.asp': 
                case 'frequently_ordered_products.asp':
                    pageType = 'catalog';
                    break;
                case 'showcart.asp':
                case 'account.asp':
                    pageType = 'checkout';
                    break;
                default:
                    pageType = 'other';
                    break;

            }
        }
        return pageType;
    }

    utils.htmlEncode = function(value){
        return $('<textarea/>').text(value).html();
    }

    utils.htmlDecode = function(value){
        return $("<textarea/>").html(value).text();
    }

    utils.pageSubType = function(){
        pageSubType = "signin";
        if("" != ''){
            pageSubType = "";
        }
        return pageSubType;
    }

    utils.scrollTo = function (elementArg) {
        var element = $(elementArg).first();
        if (!element.length) return;

        // If the element is in an inactive tab, make the tab active.
        utils.activateTab(element);

        $('html, body').animate({
                scrollTop: $(element[0]).offset().top - 120 //offset to account for header bar
        }, 500);

    };
    
    utils.activateTab = function (element) {
        var tab = element.hasClass('.tab-pane') ? element : element.closest('.tab-pane');
        if (!tab.length || tab.hasClass('active')) return;
        var id = tab.attr('id');
        tab
            .closest('.tabbable')
            .find('.nav-tabs > li > a')
            .filter('[href="#' + id + '"], [data-target="#' + id + '"]')
            .tab('show');
    }

    utils.popToastr = function(title, subtext, config) {

        var confirmToastrConfig = config ? config : {
            'closeButton': true,
            'newestOnTop': true,
            'positionClass': 'toast-top-right',
            'preventDuplicates': false,
            'showDuration': 500,
            'hideDuration': 1000,
            'tapToDismiss': false,
            'timeOut': 5000,
            'extendedTimeOut': 1000
        };

        toastr.success(
            subtext,
            title,
            confirmToastrConfig
        );
    };

    utils.popToastrError = function(title, subtext, config) {
        var errorToastrConfig = {
            'closeButton': true,
            'newestOnTop': true,
            'positionClass': 'toast-top-right',
            'preventDuplicates': false,
            'showDuration': 500,
            'hideDuration': 1000,
            'tapToDismiss': true,
            'timeOut': 5000,
            'extendedTimeOut': 1000
        }

        $.extend(errorToastrConfig, config);

        toggleLoadingWidget(false);

        toastr.error(
            subtext,
            title,
            errorToastrConfig
        );
    }

    utils.decimalPlacesOnUnitPrices = parseInt(2) || 2;
    utils.decimalPlacesOnTotals = parseInt('2') || 2;
    utils.decimalPlacesAllowedOnProductQty = 0;
    utils.defaultQtyIncrement = 1;

    /*
     * 2024-06-07 - Client-side version (Server-side in Global Functions)
     * Standard implementation of quantity validation
     * calculation logic.
     * This assumes all values passed in already account
     * for the uom conversion factor if applicable.
     */
     utils.getValidProductQty = function(inputQty, minQty, maxQty, step, allowZeroValue) {

        // init as success result
        var qtyValidationResult = {
            inputQty: inputQty,
            validQty: inputQty,
            error: ''
        }

        if(typeof allowZeroValue === 'undefined') allowZeroValue = false;

        //- set defaults
        if(typeof step === 'undefined') step = utils.defaultQtyIncrement; //- Defined in site_config_overrides.asp
        if(typeof minQty === 'undefined') minQty = step;
        if(typeof maxQty === 'undefined') maxQty = 0;

        //- Ensure all values are numeric
        inputQty = parseFloat(inputQty);
        minQty = parseFloat(minQty);
        maxQty = parseFloat(maxQty);
        step = parseFloat(step);

        //- zero is valid when validating qty inputs elements
        //- when zero indicates the product is not selected
        //- for add to cart, like on input-qty view with a single
        //- add to cart button
        if(inputQty == 0 && allowZeroValue) {
            return qtyValidationResult;
        }

        //- retun min if inputQty is not passed in, is 0 or less than minQty.
        if( !inputQty || (minQty > 0 && inputQty < minQty)){
            qtyValidationResult.validQty = minQty;
            qtyValidationResult.error = 'min';
            return qtyValidationResult;
        }

        //- DecimalPlacesAllowedOnProductQty defined in site_config_overrides.asp
        //- and assigned to utils.decimalPlacesAllowedOnProductQty in global_scripts_top.asp
        //- It can be 0 so we add 1 to it so we have a multiplier of at least 10.
        //- floatFixMultiplier is used to account for precision issues with floating point
        //- arithmatic on base-10 decimal numbers.
        var floatFixMultiplier = Math.pow(10, (parseFloat(utils.decimalPlacesAllowedOnProductQty) + 1));

        var PadDifferenceFromMin = ( (inputQty * floatFixMultiplier) - (minQty * floatFixMultiplier) );
        var padStep = step * floatFixMultiplier;
        var padMinQty = minQty * floatFixMultiplier;
        var padMaxQty = maxQty * floatFixMultiplier;

        //- Force max to valid value
        if(maxQty < minQty) maxQty = 0;
        if(maxQty > 0) {
            var maxStepMod = (padMaxQty - padMinQty) % padStep;
            if(maxStepMod != 0) {
                var maxAdjust = Math.floor( (padMaxQty - padMinQty) / padStep ) * padStep;

                maxQty = (padMinQty + maxAdjust) / floatFixMultiplier;
            }
        }

        //- return max if inputQty is greater than max
        if(maxQty > 0 && inputQty > maxQty){
            qtyValidationResult.validQty = maxQty;
            qtyValidationResult.error = 'max';
            return qtyValidationResult;
        }

        // 1 isn't a valid default for qty when using
        // fractional quantities without a defined increment
        // or if it's off when it's simple sales uom, so
        // we set increment/step to 0
        var stepMod = step ? ( parseFloat( PadDifferenceFromMin % padStep ) / floatFixMultiplier ) : 0;

        if(stepMod != 0){

            padValidQty = (Math.ceil( PadDifferenceFromMin / padStep ) * padStep) + padMinQty

            var validQty = Math.round(padValidQty) / floatFixMultiplier;

            qtyValidationResult.validQty = validQty;
            if(maxQty > 0 && validQty > maxQty) qtyValidationResult.validQty = maxQty;
            if(validQty < minQty) qtyValidationResult.validQty = minQty;
            qtyValidationResult.error = 'inc';
            return qtyValidationResult;
        }

        return qtyValidationResult;
    }

</script>
<script>
    var cimcloud = {
        helpers: {
            url: getUrl,
            urlPath: getUrlPath,
            loginUrl: utils.loginUrl,
            pageType: utils.pageType,
            pageSubType: utils.pageSubType,
            pageName: utils.pageName,
            pageKey: '',
            urlParameter: utils.getParameter,
            buildImagePath: utils.buildImagePath,
            environment: "production"
        },
        session: {
            accountNumber: "",
            accountName: "",
            username: "",
            email: "",
            firstName: "",
            lastName: "",
            sitename: "frontline4",
            isLoggedIn: false,
            isImpersonation: false,
            parentSession: {
                username: "",
                email: "",
                firstName: "",
                lastName: ""
            },
            appliedRights: "FRONTLINE4-CUSTOM-SETTINGS,BASE-PUNCHOUTS-CXML,COUPON-TO-ERP-USE-FIXED-ITEM,APPLICATION-DEFAULTS-MODIFICATIONS-SETTINGS,SHADD-D,PRODUCT-IMAGES,SHIPPING-SETTINGS,SET-ACCOUNT-NUMBER-PREFIX,S100-CIMCLOUD-B2B-A,APPLICATION-DEFAULTS-SET-ACCOUNT-NUMBER-PREFIX-SETTINGS,COUPON2,APPLICATION-DEFAULTS-BASE_ORDERS-AND-SHIPMENTS-SETTINGS,CLASSIC-MIGRATION-SETTINGS,APPLICATION-DEFAULTS-PRODUCT-ALIAS-CUSTOM-SETTINGS-SETTINGS,APPLICATION-DEFAULTS-POWER-CUSTOMERS-SETTINGS,ADV-PRICE-DISPLAY-SETTINGS,BASE_INVOICES,SKIN-CUSTOMER,DS-SHIPVIA,COUPON3,SHOW-PRICE-BREAKS,APPLICATION-DEFAULTS-PUNCHOUT-ADDITIONS-SETTINGS,DS-PROD,MODIFICATIONS,PRODUCT-CATEGORIES,APPLICATION-DEFAULTS-BASE_INVOICES-SETTINGS,PRODUCT-ALIAS-CUSTOM-SETTINGS,FRONTLINE4-DEFAULTS-CUSTOM-LANDING-SIGNIN-PAGE-SETTINGS,APPLICATION-DEFAULTS-BASE_INVOICES-WORKERS-SETTINGS,APPLICATION-DEFAULTS-PRODUCT-GALLERY-SETTINGS,LOCATOR-AUTO-POP,CUSTOM-LANDING-SIGNIN-PAGE,APPLICATION-DEFAULTS-S100-CIMCLOUD-B2B-A-SETTINGS,APPLICATION-DEFAULTS-CUSTOM-SETTING-PASSWORD-RESET-SETTINGS,APPLICATION-DEFAULTS-SHIPPING-SETTINGS-SETTINGS,ABAN-CART-EMAIL,PRODUCT-DESC,CUSTOM-PRODUCT-CATALOG-SETTINGS-TOGGLE,APPLICATION-CUSTOM-SETTINGS,APPLICATION-DEFAULTS-UOM-SIMPLE-100-SETTINGS,CUSTOM-SETTING-PASSWORD-RESET,UPLOAD-ATC,APPLICATION-DEFAULTS-CUSTOM-RETAIL-TEMPLATE-SETTINGS-SETTINGS,ACUMATICA-SETTINGS,RECURRING-ORDER,APPLICATION-DEFAULTS-CATALOG-ALLOW-ATC-SETTINGS,APPLICATION-DEFAULTS-PC-MINQTY-SETTINGS,LOCK-CUSTOMER-ACCOUNT-DATA-EDITS,APPLICATION-DEFAULTS-CUSTOM-PRODUCT-CATALOG-SETTINGS-SETTINGS,CUSTOM-RETAIL-TEMPLATE-SETTINGS-TOGGLE,ORDER-PT-CXML,COUPON,PRODUCT-PRICE-LIST,PRODUCT-DOCS,APPLICATION-DEFAULTS-PRODUCT-CATEGORIES-SETTINGS,LOCATOR-GEO-DATA,SHIPPING-ADDR-CUSTOM-SETTINGS,BASE_PAYMENTS-AND-CREDITS,PC-CASEQTY,BASE_ORDERS-AND-SHIPMENTS,PUNCHOUT-ADDITIONS,APPLICATION-DEFAULTS-BASE-PUNCHOUTS-CXML-SETTINGS,UOM-SIMPLE-100,PC-CHILD-SEARCH-ROLLUP,APPLICATION-DEFAULTS-BASE-ADVANCED_CUSTOMER_PRICE_SETTINGS-SETTINGS,SITEMAPGEN,BULK-ADD-TO-CART,FUTURE-ORDER,ALIAS-NM-DS,POWER-CUSTOMERS-IMPERSONATE,POWER-CUSTOMERS,TAXEXEMPT-C1,ALIAS-B,LOCATOR,PRODUCT-GALLERY,CATALOG-ALLOW-ATC,PC-MINQTY,APPLICATION-DEFAULTS-CUSTOMER-SITE-CATALOG-SETTINGS-SETTINGS,DS-PCAT".split(',')
        },
        catalog: {}
    }

    var viewModels = viewModels || {};
</script>



<script src="/js/bundles/coreTop.js?1851c597c0055dcf59cd7417a427cdad91d70a87" ></script>


<script src="/js/bundles/corePlugins.js?1851c597c0055dcf59cd7417a427cdad91d70a87" ></script>


<script src="/js/bundles/coreVendors.js?1851c597c0055dcf59cd7417a427cdad91d70a87" ></script><style>
  .results h3.category-group.list-view {
    background: #444444;
    padding: 5px;
    color: #fff;
  }
  .results h4.group-by-heading.list-view {
    background: #5c616a;
    padding: 5px;
    color: #fff;
  }
</style>
</head>

<body 
    class="signin interior dashboard t-ui-phase-3 site-type-2 no-template loggedout    t-ui-phase-3 no-template no-template interior loggedout site-type-2 production frontline4 -loading-dom -loading-window" 
    
>

        
<script>
var timerStart = Date.now();  
var lastElapsed = 0; 
var pageHitDate = new Date();
var pageLoad = {
    pageHitKey: '1A246A0514344BF993F8AA1461F28DE2',
	sessionId: '842E429527CF45C0B5BC2E4372648B2E',
	orderId: '',
	createDate: (pageHitDate.getMonth()+1) + "/"
                + pageHitDate.getDate() + "/" 
                + pageHitDate.getFullYear() + " "  
                + pageHitDate.getHours() + ":"  
                + pageHitDate.getMinutes() + ":" 
                + pageHitDate.getSeconds(),
    priorTimeFrom: '',
    priorTime: '',
    urlRoot: 'frontline4.cimproduction.com',
    pageName: 'signin.asp',
    uri: window.location.href,
	querystring: 'autopage=%2Fsitemap.asp',
	logs: [],
    clientTime: 0,
    serverTime: 0,
	totalTime: 0,
    dbReadCount: 0,
    dbWriteCount: 0
}; 

function addTimer(s) {
	var elapsed = (Date.now()-timerStart); //KEEP milliseconds
	var log = {
        source: 3,
        stepWithinSource: pageLoad.logs.filter( function(obj) { obj.source === 3 }).length,
        nickname: s,
        stepTime: elapsed - lastElapsed,
        cumulativeTime: elapsed
	};
	pageLoad.logs.push(log);
	lastElapsed = elapsed;
}

function logPageLoad() {
    if(pageLoad.logs.length > 0) {
        //sum the server-side, object, & client-side times onto the header.
        var serverTime=0;
        var maxClientSide=0;
        var len = pageLoad.logs.length-1;
        $.each(pageLoad.logs,function(i,o){
            if(o.source==0) { //server-side`
                serverTime=serverTime+o.cumulativeTime;
                pageLoad.dbReadCount += o.dbReadCount;
                pageLoad.dbWriteCount += o.dbWriteCount;
            /*} else if(o.source==2) { //object.. but these times are actually included in the client-side time since it is an ajax call. Don't add it twice. */
            } else if(o.source==3 && o.cumulativeTime > maxClientSide) { // on the last client-side record
                maxClientSide=o.cumulativeTime;
            }
        });
        var pt = pageLoad.priorTime;
        if(pt != "" && !isNaN(pt)){
            pageLoad.priorTime = parseFloat(pt);           
        } else {
            pageLoad.priorTime = 0;
        }
        pageLoad.serverTime = serverTime;
        pageLoad.clientTime = maxClientSide
        pageLoad.totalTime = parseInt(serverTime + maxClientSide + pageLoad.priorTime); //trim decimals
        $.ajax({
            url: '/api/timers/paymentpage/1A246A0514344BF993F8AA1461F28DE2',
            data: JSON.stringify(pageLoad),
            type: "POST",
            contentType: "application/json"
        });
        drawPageHitData();
        pageLoad.logs = []; //<-- this is cleared so that the page hit isn't logged more than once.. See containing IF block...
    }
}

function drawPageHitData() {
    
}

function toSecString(ms) {
    var s = (parseInt(ms*10)/10); //only keep 1 decimal
    s = parseInt(s) / 1000;

    return s + "sec(s)";
}

function addPageLoadData(data) {
    /*
    OO data is an object with 3 props: reads, writes, details[]
    */
    if(data.reads) {
        pageLoad.dbReadCount += data.reads;
    }
    if(data.writes) {
        pageLoad.dbWriteCount += data.writes;
    }
    if(data.details) {
        pageLoad.logs = pageLoad.logs.concat(data.details);
    } 
}

addTimer('master top');
</script>

        
        <div class="app-signin dash-landing u-card">
            
    <header class="private-mode__header">
        <a href="https://frontline4.cimproduction.com" name="Logo link" class="private-mode__main-logo">
            <img src="https://d23i9o1ddbbnse.cloudfront.net/images/fls_logo.jpg?v=3983598294" alt="Logo" width="240" height="100">
        </a>
    </header>
    <div id="signedOutContent" class="private-mode">
                <div class="app-signin-content pull-left">
                    <h1 class="page-title">Welcome to your Front Line Safety Ordering Portal</h1>
<p><span>If you have any issues using your existing credentials please contact <a href="mailto:support@flsafety.com">support@flsafety.com</a> or 800-538-4555&nbsp; </span><span>&nbsp;</span></p>
<p>&nbsp;</p>
<p><span><img src="https://d23i9o1ddbbnse.cloudfront.net/images/fls_logo.png?v=3970295471" alt="" width="169" height="68" /></span></p>
                </div>
                <div class="app-signin-form pull-right">
                    

<script language="JavaScript">


	$(function() {
		addGlobalModalCompletionHandler($('.global-modal'), handleCreateLogin);
	});

	function handleCreateLogin(){
		console.log('modal done');
		toggleLoadingWidget(true);
		location.reload();
	}

	function CheckForm(form)
	{
		if (form.username.value == "")
		{
			alert("Please enter a username.");
			return false;
		}

	

		if (form.password.value == "")
		{
			alert("Password can not be blank")
			return false;
		}
	 
		showLoadingpopup();
		
		return true;
	}

	
	if (jQuery) {
		jQuery(function() { try { document.getElementById('logonUsername').focus(); } catch (err){ } });
	} else {
		window.onload = function() {
			try { document.getElementById('logonUsername').focus(); } catch (err) { }
		}
	}
	

	function showLoadingpopup() {
			
				toggleLoadingWidget(true);
			
		}
		
		function HideLoading() {
			
				toggleLoadingWidget(false);
			
		}

</script>


	<div class="login_overlay" id="login_overlay_div" style="display:none;">
		<div class="login_overlay_bkg">&nbsp;</div>
		<div class="login_overlay_win">
			<p><img src="js/jquery/loadinganimation.gif" /></p>
			<p><strong>Please wait while we load your profile&hellip;</strong></p>
		</div>
	</div>
			<header class="page-header">
			<h1>Sign in to Your Account</h1>
			</header>
	<div id="logon_container" class="row-fluid">
			
					
					<div class="local">
						
	<div class="contact-login well well-large" style="">
		<form
			name="custacctlogin"
			class="custom-account-login"
			id="custacctlogin"
			class="contact-login__form"
			method="POST"
			action="/security_logonscript_sitefront.asp?action=logon&parent_c_id=&returnpage=signin%2Easp%3F&pageredir=%2Fsitemap%2Easp%3F"
			onsubmit="return CheckForm(this);"
			autocapitalize="off"
			autocorrect="off" 
			
		>
			<div></div>

			<label for="logonUsername" class="login_title custom-account-login__label">Username</label>
			<div class="login_field custom-account-login__input-wrapper">
				<input type="text" name="username" autocorrect="off" autocapitalize="off" id="logonUsername" class="un custom-account-login__input" data-test="login_username" value="" placeholder="" >
			</div>

			

				<label for="logonPassword" class="login_title custom-account-login__label">Password</label>
				<div class="login_field custom-account-login__input-wrapper">
					<input type="password" name="password" autocorrect="off" autocapitalize="off" id="logonPassword" class="pw custom-account-login__input" data-test="login_password" value="" placeholder="">
				</div>

				<div class="login_btn_wrap custom-account-login__submit-wrapper">
								<button type="submit" class="btn btn-primary custom-account-login__submit" data-test="login_button">Sign In</button>
				</div>

				
				<div></div>
				<input type="hidden" name="logontype" value="customer">

		</form>
	</div>

	
		<div class="login-links" style="">
			<ul class="unstyled">
					<li class="login-links__request-username"> 
							<a id="request_username_link" class="login-links__request-username-link global-modal" href="request_username.asp" data-title="Forgot your username?" data-size="small">
								Forgot your username?
							</a> 
					</li>
					<li class="login-links__reset-password"> 
						<a id="pass_reset_link" class="login-links__reset-password-link global-modal" href="request_password_reset.asp" data-title="Reset your password" data-size="small">
							Reset your password
						</a> 
					</li>

			</ul>
		</div>
					</div>
			
					
					<div class="cxml">
						
					</div>
			
	</div>
	

	<div class="pull-right" style="">
		
	</div>
		<div id="wsplogin">
			<div>
				<form id="security_page" 
							method="POST" 
							action="https://frontline4.cimproduction.com/security_logonscript_sitefront.asp?source=validipproduction&login=1&pageredir=%2Fsitemap%2Easp%3F"
							onsubmit="toggleLoadingWidget(true)"
							>
					<input type="hidden" name="username" id="validip_username" value="wsptest">
					<input type="submit" value="ValidIP: Login wsptest" class="btn">
				</form>
			</div>
		</div>

<style>
	/* Inline OR Divider Styling */
	.or-divider {
		display: inline-flex;
		align-items: center;
		font-size: 16px;
		color: #777;
		width: 100%;
	}

	.or-divider::before, .or-divider::after {
		content: '';
		flex-grow: 1;
		height: 1px;
		background-color: #ddd;
	}

	.or-divider span {
		margin: 0 10px;
	}
</style>
                </div>
            
        </div>

        
<div class="global-body-append">
    
	<span class="app-logo__validip-info btn btn-mini">
		<img src="https://d23i9o1ddbbnse.cloudfront.net/images/cimcloud-logo-icon.png?v=3983598294" style="height: 12px;">
		<a href="#siteInfo" role="button" data-toggle="modal" class="site-info">	
			<span title="Currently on version frontline4.punch.1@4.13.7" style="color: #00A3E0;">frontline4 (frontline4.punch.1@4.13.7)</span>
		</a>&nbsp;&nbsp;
	</span>
	<div id="siteInfo" class="modal hide fade" tabindex="-1"  aria-hidden="true">
		<header class="modal-header">
			<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
			<h3 id="leaveCheckoutLabel"><i class="icon-info-sign"></i> Site Information</h3>
		</header>
		<div class="modal-body app-logo__validip-body">
			
		
		<table border="0" cellpadding="0" cellspacing="0">
            <tr>
				<td class="pageInfoLabel">Core Version:</td>
				<td>
					
						<a style="text-decoration: none;" 
						   href="https://bitbucket.org/websitepipeline/coresite/src/frontline4.punch.1@4.13.7/">
						   	frontline4.punch.1@4.13.7
						</a>
					
				</td>
			</tr>
				<tr>
					<td class="pageInfoLabel">K8Info Deployments:</td>
					<td>
						<a style="text-decoration: none;" 
						   href="https://frontline4.cimproduction.com/k8info_deployments.asp">
						   K8Info Deployments Page
						</a>
					</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">ServerName:</td>
					<td>WEBSERVER</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">WS_ID:</td>
					<td>7FE2EC2A6D604A4A85430091ADCD1347</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">L_WS_ID:</td>
					<td>7FE2EC2A6D604A4A85430091ADCD1347</td>
				</tr>
			<tr>
				<td class="pageInfoLabel">SiteName:</td>
				<td>frontline4</td>
			</tr>
			<tr>
				<td class="pageInfoLabel">Application URL:</td>
				<td>https://frontline4.cimproduction.com</td>
			</tr>
			<tr>
				<td class="pageInfoLabel">Workspace URL:</td>
				<td>https://frontline4.mycimcloud.com</td>
			</tr>
				<tr>
					<td class="pageInfoLabel">OrderFrontURL:</td>
					<td>https://frontline4.cimproduction.com</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">ALTERNATE_DB_NAME:</td>
					<td>frontline4</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">ALTERNATE_DB_SERVER:</td>
					<td>sql04-db07.i2network.local.\db07</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">FileName:</td>
					<td>c:\inetpub\wwwroot\WebSite\signin.asp</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">Local Commit Hash:</td>
					<td>1851c597c0055dcf59cd7417a427cdad91d70a87</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">SCRIPT_NAME:</td>
					<td>/signin.asp</td>
				</tr>
				<tr>
					<td class="pageInfoLabel">HTTP_X_ORIGINAL_URL:</td>
					<td></td>
				</tr>
				<tr>
					<td class="pageInfoLabel">getUrl():</td>
					<td>/signin.asp?autopage=%2Fsitemap.asp</td>
				</tr>
			<tr>
				<td class="pageInfoLabel">cimcloud.helpers.urlPath()</td>
				<td>/signin.asp</td>
			</tr>
			<tr>
				<td class="pageInfoLabel">cimcloud.helpers.pageName</td>
				<td>signin.asp</td>
			</tr>
		</table>
		</div>
		<div class="modal-footer">
			<a class="app-logo__validip-link btn btn-danger" href="https://frontline4.cimproduction.com/?action=logout"><i class="fas fa-power-off"></i> Sign Out</a>
			<a class="app-logo__validip-link btn" href="https://frontline4.cimproduction.com/session_info.asp?key=842E429527CF45C0B5BC2E4372648B2E"><i class="fas fa-code"></i> Session Info</a>
			<a class="app-logo__validip-link btn" href="https://frontline4.cimproduction.com/k8info_deployments.asp" target="_blank"><i class="fab fa-docker"></i> K8 Info</a>
			<a id="toggleTimerBtn" class="app-logo__validip-link btn" onclick="toggleTimers()"><i class="fas fa-clock"></i> Toggle Timers</a>
			
				<a class="app-logo__validip-link btn" target="_blank" href="https://bitbucket.org/websitepipeline/frontline4" target="_blank"><i class="fab fa-bitbucket"></i> Bitbucket Repo</a>
			
		</div>
	</div>
	<script>
		$('#demoToolsBtn').click(function() {
			$('.modal').modal('hide');
		});

		function toggleTimers() {
			$.ajax({url: "?toggle=timers&modal=1", 
					success: function(data){
						if(data == "ON") {
							$("#toggleTimerBtn").addClass("btn-danger");
						} else {
							$("#toggleTimerBtn").removeClass("btn-danger");
						}
						utils.popToastr('Success', 'Timers are now ' + data, { 'positionClass' : 'toast-bottom-right' });
					}
				});
		}
	</script><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="hide">
  <symbol id="icon-user-circle-o" viewBox="0 0 28 28">
    <path d="M14 0c7.734 0 14 6.266 14 14 0 7.688-6.234 14-14 14-7.75 0-14-6.297-14-14 0-7.734 6.266-14 14-14zM23.672 21.109c1.453-2 2.328-4.453 2.328-7.109 0-6.609-5.391-12-12-12s-12 5.391-12 12c0 2.656 0.875 5.109 2.328 7.109 0.562-2.797 1.922-5.109 4.781-5.109 1.266 1.234 2.984 2 4.891 2s3.625-0.766 4.891-2c2.859 0 4.219 2.312 4.781 5.109zM20 11c0-3.313-2.688-6-6-6s-6 2.688-6 6 2.688 6 6 6 6-2.688 6-6z"></path>
  </symbol>
  <symbol id="icon-address-card" viewBox="0 0 32 28">
    <path d="M16 17.672c0-2.422-0.594-5.109-3.063-5.109-0.766 0.438-1.797 1.188-2.938 1.188s-2.172-0.75-2.938-1.188c-2.469 0-3.063 2.688-3.063 5.109 0 1.359 0.891 2.328 2 2.328h8c1.109 0 2-0.969 2-2.328zM13.547 9.547c0-1.953-1.594-3.547-3.547-3.547s-3.547 1.594-3.547 3.547c0 1.969 1.594 3.547 3.547 3.547s3.547-1.578 3.547-3.547zM28 17.5v-1c0-0.281-0.219-0.5-0.5-0.5h-9c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h9c0.281 0 0.5-0.219 0.5-0.5zM28 13.438v-0.875c0-0.313-0.25-0.562-0.562-0.562h-8.875c-0.313 0-0.562 0.25-0.562 0.562v0.875c0 0.313 0.25 0.562 0.562 0.562h8.875c0.313 0 0.562-0.25 0.562-0.562zM28 9.5v-1c0-0.281-0.219-0.5-0.5-0.5h-9c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h9c0.281 0 0.5-0.219 0.5-0.5zM32 4.5v19c0 1.375-1.125 2.5-2.5 2.5h-5.5v-1.5c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1.5h-12v-1.5c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1.5h-5.5c-1.375 0-2.5-1.125-2.5-2.5v-19c0-1.375 1.125-2.5 2.5-2.5h27c1.375 0 2.5 1.125 2.5 2.5z"></path>
  </symbol>
  <symbol id="icon-building" viewBox="0 0 22 28">
    <path d="M21 0c0.547 0 1 0.453 1 1v26c0 0.547-0.453 1-1 1h-20c-0.547 0-1-0.453-1-1v-26c0-0.547 0.453-1 1-1h20zM8 4.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5zM8 8.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5zM8 12.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5zM8 16.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5zM6 21.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM6 17.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM6 13.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM6 9.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM6 5.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM14 25.5v-3c0-0.281-0.219-0.5-0.5-0.5h-5c-0.281 0-0.5 0.219-0.5 0.5v3c0 0.281 0.219 0.5 0.5 0.5h5c0.281 0 0.5-0.219 0.5-0.5zM14 17.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM14 13.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM14 9.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM14 5.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM18 21.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM18 17.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM18 13.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM18 9.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5zM18 5.5v-1c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5v1c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5z"></path>
  </symbol>
  <symbol id="icon-currency-dollar" viewBox="0 0 20 20">
    <path d="M10 20c-5.523 0-10-4.477-10-10s4.477-10 10-10v0c5.523 0 10 4.477 10 10s-4.477 10-10 10v0zM11 15h1c1.657 0 3-1.343 3-3s-1.343-3-3-3v0h-4.010c-0.552 0-1-0.448-1-1s0.448-1 1-1v0h6.010v-2h-3v-2h-2v2h-1c-1.657 0-3 1.343-3 3s1.343 3 3 3v0h4c0.552 0 1 0.448 1 1s-0.448 1-1 1v0h-6v2h3v2h2v-2z"></path>
  </symbol>
  <symbol id="icon-check-square" viewBox="0 0 24 28">
    <path d="M10.703 20.297l9.594-9.594c0.391-0.391 0.391-1.016 0-1.406l-1.594-1.594c-0.391-0.391-1.016-0.391-1.406 0l-7.297 7.297-3.297-3.297c-0.391-0.391-1.016-0.391-1.406 0l-1.594 1.594c-0.391 0.391-0.391 1.016 0 1.406l5.594 5.594c0.391 0.391 1.016 0.391 1.406 0zM24 6.5v15c0 2.484-2.016 4.5-4.5 4.5h-15c-2.484 0-4.5-2.016-4.5-4.5v-15c0-2.484 2.016-4.5 4.5-4.5h15c2.484 0 4.5 2.016 4.5 4.5z"></path>
  </symbol>
  <symbol id="icon-life-bouy" viewBox="0 0 28 28">
    <path d="M14 0c7.734 0 14 6.266 14 14s-6.266 14-14 14-14-6.266-14-14 6.266-14 14-14zM14 2c-2.031 0-3.953 0.516-5.641 1.406l3.031 3.031c0.828-0.281 1.703-0.438 2.609-0.438 0.922 0 1.781 0.156 2.609 0.438l3.031-3.031c-1.687-0.891-3.609-1.406-5.641-1.406zM3.406 19.641l3.031-3.031c-0.281-0.828-0.438-1.703-0.438-2.609 0-0.922 0.156-1.781 0.438-2.609l-3.031-3.031c-0.891 1.687-1.406 3.609-1.406 5.641s0.516 3.953 1.406 5.641zM14 26c2.031 0 3.953-0.516 5.641-1.406l-3.031-3.031c-0.828 0.281-1.687 0.438-2.609 0.438-0.906 0-1.781-0.156-2.609-0.438l-3.031 3.031c1.687 0.891 3.609 1.406 5.641 1.406zM14 20c3.313 0 6-2.688 6-6s-2.688-6-6-6-6 2.688-6 6 2.688 6 6 6zM21.562 16.609l3.031 3.031c0.891-1.687 1.406-3.609 1.406-5.641s-0.516-3.953-1.406-5.641l-3.031 3.031c0.281 0.828 0.438 1.703 0.438 2.609s-0.156 1.781-0.438 2.609z"></path>
  </symbol>
  <symbol id="icon-calendar-plus-o" viewBox="0 0 26 28">
    <path d="M24 4c1.094 0 2 0.906 2 2v20c0 1.094-0.906 2-2 2h-22c-1.094 0-2-0.906-2-2v-20c0-1.094 0.906-2 2-2h2v-1.5c0-1.375 1.125-2.5 2.5-2.5h1c1.375 0 2.5 1.125 2.5 2.5v1.5h6v-1.5c0-1.375 1.125-2.5 2.5-2.5h1c1.375 0 2.5 1.125 2.5 2.5v1.5h2zM18 2.5v4.5c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5v-4.5c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5zM6 2.5v4.5c0 0.281 0.219 0.5 0.5 0.5h1c0.281 0 0.5-0.219 0.5-0.5v-4.5c0-0.281-0.219-0.5-0.5-0.5h-1c-0.281 0-0.5 0.219-0.5 0.5zM24 26v-16h-22v16h22zM14 17h3.5c0.281 0 0.5 0.219 0.5 0.5v1c0 0.281-0.219 0.5-0.5 0.5h-3.5v3.5c0 0.281-0.219 0.5-0.5 0.5h-1c-0.281 0-0.5-0.219-0.5-0.5v-3.5h-3.5c-0.281 0-0.5-0.219-0.5-0.5v-1c0-0.281 0.219-0.5 0.5-0.5h3.5v-3.5c0-0.281 0.219-0.5 0.5-0.5h1c0.281 0 0.5 0.219 0.5 0.5v3.5z"></path>
  </symbol>
  <symbol id="icon-sticky-note" viewBox="0 0 24 28">
    <path d="M16 19.5v6.5h-14.5c-0.828 0-1.5-0.672-1.5-1.5v-21c0-0.828 0.672-1.5 1.5-1.5h21c0.828 0 1.5 0.672 1.5 1.5v14.5h-6.5c-0.828 0-1.5 0.672-1.5 1.5zM18 20h5.953c-0.141 0.75-0.547 1.594-1.016 2.063l-2.875 2.875c-0.469 0.469-1.313 0.875-2.063 1.016v-5.953z"></path>
  </symbol>
  <symbol id="icon-document-add" viewBox="0 0 32 32">
    <path d="M21 25v-3h1v3h3v1h-3v3h-1v-3h-3v-1h3zM17.257 29v0 0c1.009 1.221 2.535 2 4.243 2 3.038 0 5.5-2.462 5.5-5.5 0-2.137-1.219-3.99-3-4.9v-11.6l-6-7h-10.997c-1.106 0-2.003 0.898-2.003 2.007v22.985c0 1.109 0.891 2.007 1.997 2.007h10.26zM16.6 28h-9.6c-0.545 0-1-0.446-1-0.995v-23.009c0-0.54 0.446-0.995 0.996-0.995h10.004v4.994c0 1.119 0.895 2.006 1.998 2.006h4.002v10.207c-0.477-0.135-0.98-0.207-1.5-0.207-3.038 0-5.5 2.462-5.5 5.5 0 0.9 0.216 1.75 0.6 2.5v0 0zM18 3.5l4.7 5.5h-3.703c-0.546 0-0.997-0.452-0.997-1.009v-4.491zM21.5 30v0c-2.485 0-4.5-2.015-4.5-4.5s2.015-4.5 4.5-4.5c2.485 0 4.5 2.015 4.5 4.5s-2.015 4.5-4.5 4.5z"></path>
  </symbol>
  <symbol id="icon-document-add1" viewBox="0 0 32 32">
    <path d="M21 25v-3h1v3h3v1h-3v3h-1v-3h-3v-1h3zM16.022 29h-9.024c-1.107 0-1.997-0.899-1.997-2.007v-22.985c0-1.109 0.899-2.007 2.009-2.007h9.991v6.002c0 1.111 0.898 1.998 2.006 1.998h4.994v9.498c-0.77-0.321-1.614-0.498-2.5-0.498-3.59 0-6.5 2.91-6.5 6.5 0 1.289 0.375 2.49 1.022 3.5v0 0zM18 2v5.997c0 0.554 0.451 1.003 0.991 1.003h5.009l-6-7zM21.5 31c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5c-3.038 0-5.5 2.462-5.5 5.5s2.462 5.5 5.5 5.5v0z"></path>
  </symbol>
</svg>
</div>

    <script>
        function showLoadingpopup() {
            toggleLoadingWidget(true);
        }

        function HideLoading() {
            toggleLoadingWidget(false);
        }
    </script>



<div id="appLoadingWidget" class="sk-three-bounce--full-screen app-loading hide">
    <div class="sk-three-bounce">
        <div class="sk-child sk-bounce1"></div>
        <div class="sk-child sk-bounce2"></div>
        <div class="sk-child sk-bounce3"></div>
    </div>
</div>
    <div class="dashboard-bot-scripts">
        <div class="global-scripts">
    <script>

        var jsConfig = {
            static_pages: {
                content_key: '',
                content_type: ''
            }
        }
        if(processPageTitle){
            //$('.page-header h1').html('&nbsp;').show();
            var originalPageTitle = $('.page-header h1').html();
            var originalSubmitText = $('.form-actions button').html();
            var pageTitleText = "";
            if(pageTitleText.trim() != ''){
                $('.page-header h1').html(pageTitleText);
                $('.form-actions button').html( 'Save ' + pageTitleText);
                if(isWorkerDomain) pageTitleText = sitename.toUpperCase() + ' - ' + pageTitleText;
                document.title = pageTitleText;
            }else if (originalPageTitle){
                pageTitleText = originalPageTitle;
                $('.form-actions button').html( 'Save ' + pageTitleText);
                if(isWorkerDomain) pageTitleText = sitename.toUpperCase() + ' - ' + pageTitleText;
                document.title = pageTitleText;                
            }
            $('.page-header h1').show();
            $('.form-actions button').show();
        }
    </script>

    <script src="../hooks/js_custom_functions.js?1851c59"></script>
    <script src="js/vendor/stretchy.min.js?1851c59" data-filter=".stretchy, .stretchy *"></script>

    <noscript class="noscript">
        WARNING: You will not be able to place an order or use most features of this site with JavaScript disabled
    </noscript>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/js/all.min.js" integrity="sha512-Sr1M6mlMOXTKahO1wcUzFu/kAb3iZVaQWGvxOEePRm7c2NjGRZ7ckRT6218PaSXlz8eEoFpKkDVvl2rTqKrQLA==" crossorigin="anonymous" referrerpolicy="no-referrer" async></script>

<script src="/js/bundles/coreBot.js?1851c597c0055dcf59cd7417a427cdad91d70a87" ></script>


<script src="/js/bundles/coreBotPlugins.js?1851c597c0055dcf59cd7417a427cdad91d70a87" ></script>
        <script src="js/dashboard.js?1851c59"></script>
    </div>
    </div> <!-- END private-mode -->


    <!-- KO Templates -->
    
<script>
	var oConfig = {

		sessionData : {
			sc_id              : "1F17F6B91460487BA12DACDBC573AAE8",
			sessionKey         : "842E429527CF45C0B5BC2E4372648B2E",
			storefrontUrl      : "https://frontline4.cimproduction.com",
			orderfrontUrl      : "https://frontline4.cimproduction.com",
			cdnUrl             : "https://d23i9o1ddbbnse.cloudfront.net",
			isSuperUserSession : false,
			cartPage           : "showcart.asp"
		}
	}; 
	
	const additionalFields = [
		'content1',
		'content2',
		'content3',
		'content4',
		'opt1',
		'opt2',
		'opt3',
		'opt4',
		'opt5',
		'opt6',
		'opt7',
		'opt8',
		'opt9'
	];
</script>
<div class="catalog-templates-scripts">




	<script>
		utils.isMainProduct = function (product) {
			return product && ko.unwrap(viewModel.mainProduct) === product;
		};

		utils.buildQuickAddLink = function(product){
			var link = product.link();
			var orderKey = utils.getParameter('o_key');
			if(orderKey){
				orderKey = '&o_key=' + orderKey;
			}
			if(link.includes('?')){
				link += '&quickadd=true' + orderKey;
			}else{
				link += '?quickadd=true' + orderKey;
			}
			return link;
		};

		utils.setCookie = function (name, value) {
			//need to also set expiry date?
			document.cookie = name + '=' + value + ';path=/;';
		};

		// www.the-art-of-web.com/javascript/getcookie/
		utils.getCookie = function (name) {
			var re = new RegExp('\\b' + name + '=([^;]+)');
			var value = re.exec(document.cookie);
			return value ? unescape(value[1]) : undefined;
		};

		utils.isActiveQuote = ((utils.getParameter('modalaction') == 'addProduct' || utils.getParameter('modalaction', parent) == 'addProduct') && "".length == 32) && "" != "1F17F6B91460487BA12DACDBC573AAE8";

		//http://stackoverflow.com/questions/5999118/add-or-update-query-string-parameter
		utils.setParameter = function (key, value, uriArg) {
			var uri = uriArg || window.location.toString();
			var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
			var separator;
			if (uri.indexOf('?') === -1) {
				separator = '?';
			} else {
				if (uri[uri.length - 1] === '?') {
					separator = '';
				} else {
					separator = '&';
				}
			}
			if (uri.match(re)) {
				return uri.replace(re, '$1' + key + '=' + value + '$2');
			} else {
				return uri + separator + key + '=' + value;
			}
		};

		utils.handleImageError = function (img) {
			handleImageError(img, oConfig.noImagePath);
		};

		utils.drawHidePriceMessage = function(){
			return "";
		}

		utils.removeHTML = function(text){
			return $(text).text();
		}

		utils.decodeHTML = function(text) {
			return $('<textarea>').html(text).text()
		};

		utils.getStoredBreadcrumbs = function() {
			try {
				return JSON.parse(localStorage.getItem('breadcrumbs'));
			} catch(e) {
				return null;
			}
		};

		utils.setStoredBreadcrumbs = function(obj) {
			try {
				localStorage.setItem('breadcrumbs', JSON.stringify(obj));
			} catch(e) {
				// nothing to do if it didn't work
				console.error('failed to save breadcrumbs');
			}
		};

		utils.breadCrumbProperty = function(link) {
			if(link == '' || link === null){
				return '';
			} else {
				return 'itemListElement';
			}
		};

		utils.breadCrumbItemType = function(link) {
			if(link == '' || link === null){
				return '';
			} else {
				return 'ListItem';
			}
		};

		utils.pageUrl = location.pathname + location.search;
		if(utils.pageUrl.charAt(0) == '/') {
			utils.pageUrl = utils.pageUrl.substring(1);
		}
		utils.loginUrl = 'security_logon.asp?autopage=' + encodeURIComponent(utils.pageUrl);

		utils.formatMoney = (function (positiveExample, negativeExample) {
			/*
			This takes an example of a formatted currency and returns a function
			to format numbers in that same format. The first argument is the
			number 1111.2222 formatted with the desired default number of
			decimals. The second argument is the additive inverse of the first.
			For example:
			$1,111.22
			¥ 1,111.22
			€1.111,22
			1 111,22F
			*/
			'use strict';
			function buildFormatter(example) {
				var thousandsSeperator = getGroup(/1([^1]?)1/);
				var decimalSymbol = getGroup(/1([^12]?)2/);
				var decimalPlaces = (example.match(/2/g) || []).length;
				var thousandsRegex = /(\d)(?=(?:\d{3})+$)/g;

				var escapedDecimalSymbol = _.escapeRegExp(decimalSymbol);
				var symbolRegex = new RegExp('^([^1' + escapedDecimalSymbol + ']*).+?([^12' + escapedDecimalSymbol + ']*)$');
				var symbolMatch = symbolRegex.exec(example);
				var prefix = symbolMatch ? symbolMatch[1] || '' : '';
				var suffix = symbolMatch ? symbolMatch[2] || '' : '';
                var infoLost = 0;

				function getGroup(pattern) {
					var match = pattern.exec(example)
					return match ? match[1] : '';
				}

				function decimalToString(decimal, places) {
					if (!places) {
						return '';
					} else {
						var result = getZeroString(places);

						if (decimal) {
							// Converting to an integer and rounding helps with
							// floating point errors.
							var int = Math.round(decimal * Math.pow(10, places));
							// Because leading zeroes will be dropped after
							// converting to integer, the string needs to be
							// padded with zeroes after being converted to a
							// string.
							result = (result + int.toString()).slice(-places);
						}
                        if(int==Math.pow(10,places)) {
                            // in this case, we have to track a +1 to add to the result because "100" sliced down to 2 places loses info
							/*
							* 2020-07-22 - EJ - More info: This is due to things like utils.formatPrice(1.995, 2) returning 1.00 instead
							* of 2.00 or utils.formatPrice(1.9995, 3) returning 1.000 instead of 2.000
							* This is because the whole number is truncated, then the decimal is rounded and only the decimal portion
							* is added back to the whole number portion.
							* This means the rounded decimal portion is always 1 or less.  If it's less than 1 (ex .89) this works.
							* However, if it's 1.000, it would "lose" 1 since only the decimal portion is added back.
							* PS - Don't ask me about the original design, I didn't design it that way.  I only corrected the "if"
							*      statement above where it was hard-coded to 100 to be Math.pow(10,places) so it would work when
							*      rounded to something other than 2 decimal places.
							*/
                            infoLost = 1;
                        } else {
                            infoLost = 0;
                        }

						return decimalSymbol + result;
					}
				}

				function getZeroString(length) {
					var pow = Math.pow(10, length);
					return pow.toString().slice(1);
				}

				function addThousandsSeperator(integer) {
					return (integer + infoLost).toString().replace(thousandsRegex, '$1' + thousandsSeperator);
				}

				return function (value, places) {
					places = typeof places === 'undefined' ? decimalPlaces : parseInt(places);

					// Truncate the value to get the integer portion.
					var integer = value | 0;
					var decimal = decimalToString( ((value * 1000) % (1 * 1000)) / 1000, places); //do first because we need to know if info was lost.
					var number = addThousandsSeperator(integer) + decimal;
					return prefix + number + suffix;
				}
			}

			var formatPositive = buildFormatter(positiveExample);
			var formatNegative = buildFormatter(negativeExample);

			return function (value, places) {
				value = parseFloat(ko.unwrap(value));
				if (isNaN(value)) return value.toString();
				return (value < 0 ? formatNegative : formatPositive)(Math.abs(value), places);
			}
		}('$1,111.22', '($1,111.22)'));

		utils.formatPrice = _.partialRight(utils.formatMoney, parseInt(2) || 2);

		utils.plural = function (singularForm, quantifier, includeQuantity) {
			var quantifierValue = ko.unwrap(quantifier);
			var quantity = _.isArray(quantifierValue) ? quantifierValue.length : quantifier;
			return (includeQuantity ? quantity + ' ' : '') + attache.plural(singularForm, quantity);
		};

		utils.atcListButtonText = function() {
			if(utils.isActiveQuote) {
				return 'Add to ' + oConfig.labels.savedCarts;
			} else {
				return oConfig.labels.addToCartList || 'Add to Cart';
			}
		};

		utils.resetATCForm = function() {
			if(!runHook('catalogTemplatesOverrideResetATCForm', { utils: this })) {
				document.getElementById("atc_form").reset();
				_.forEach(viewModel.results(), function(result) {
					if (setSelectedQty()) {
						result.selectedQty(result.minQty() || 1);
					}else{
						result.selectedQty(undefined);
					}
					result.selectedQty.notifySubscribers();
				});
			}
		}

		utils.addToCart = function (details, cart, parentKey, useInstances) {

			function getQueryStringParameters() {
				var params = {
					type : 'v200add',
					sc_id : oConfig.overrideCartKey || oConfig.sessionData.sc_id,
					s_key : oConfig.sessionData.sessionKey,
					s_url : oConfig.sessionData.storefrontUrl,
					o_url : oConfig.sessionData.orderfrontUrl,
					createsessioncookie : '1',
					noredirect : '1',
					l_ws_key : '',
					mobile : 'no',
					action : 'postlogic',
					modal  : utils.getParameter("modal"),
					modalaction  : utils.getParameter("modalaction"),
					useinstances: useInstances,
					postProcess: true
				};

				// Only one order detail can be edited at a time, so if there
				// are more than one, choose an arbitrary one to edit.
				var editedOrderDetail = _.find(details, 'configuratorEditData');
				if (editedOrderDetail) params.odeditkey = editedOrderDetail.configuratorEditData.od_id;

				if (cart) {
					_.assign(params, {
						type: 'add-to-saved-cart',
						o_id: cart.key,
						nickname: cart.nickname
					});
				}

				return params;
			}

			function getPostData() {
				var data = {};
				var productKeys = [];
				var instanceKeys = [];
				var instancesWithKeys = {};

				//' Force use instances on if it's off / undefined and we detect duplicate p_keys
				if(!useInstances && details.length) {
					var distinctProductKeys = details.reduce(function(distinctValues, detail, index) {
						if(distinctValues.indexOf(detail.key()) < 0)
						{
							distinctValues.push(detail.key());
						}
						return distinctValues;
					}, []);

					if(distinctProductKeys && distinctProductKeys.length !== details.length) {
						//' Always use instances if we're adding the same product key to cart in one post
						useInstances = true;
					}
				}

				buildConfiguratorData = function(product) {

					/* config_json field example:
						{
							"question":"F4E82B1A5F5A4A3F915B6618A766754F",
							"questionText":"STRIKE (required)",
							"name":"6800_000_10",
							"answer":"790BA1B41D6E48ECB2A825CB02EDAB93",
							"answerText":"A - 4-7/8 ASA Strike",
							"question_erp_code":"10_001",
							"answer_erp_code":"01",
							"question_status":"True"
						}

						Full config_json structure:
						{ choices: [
							{ field1 },
							{ field2 }
						],
						  configType: 'cartoptions' // or 'configurator'
						}
					*/

					/*
					'	We're setting configType based on the customForm value,
					'	but we should never hit this code if it's true until we
					'	put configurator in KO.  This is here just in case or
					'	for future use.
					'   customForm not be ndefined if not on the product detail page.
					*/
					if(oConfig.showProductConfigurator && typeof customForm !== 'undefined' && !!customForm) {
						configType = 'configurator';
					} else {
						configType = 'cartoptions';
					}
					var configJson = {
										choices: [],
										configType: configType
									};
					var cartOptions = '';
					if(product.questions && product.questions().length > 0)  {
						product.questions().forEach(function(question) {
							configJsonQuestion = {
								question: question.q_key(),
								name: question.questionText(),
								questionText: question.questionText(),
								answerText: question.selectedAnswer(),
								question_status: true
							};
							if(question.erpCode()) {
								configJsonQuestion["question_erp_code"] = question.erpCode();
							}
							configJson.choices.push(configJsonQuestion);
							cartOptions += question.questionText() + "~" + question.selectedAnswer() + "|"
						});
					}
					return {
							configJson: configJson,
							cartOptions: cartOptions.slice(0, -1) // trim trailing pipe
					};
				}

				handleAddOnProducts = function(item){
					addOnProducts = [];
					if(item.selectedProduct().selectedAddOn && item.selectedProduct().selectedAddOn()){
						addOnProducts.push(item.selectedProduct().selectedAddOn());
					}else if(item.selectedProduct().selectedAddOns && item.selectedProduct().selectedAddOns().length > 0){
						_.forEach(item.selectedProduct().selectedAddOns(), function(item){
							addOnProducts.push(item);
						})
					}
					
					_.forEach(addOnProducts, function(item){
						//handle the add to cart for  addons
						prepareAddToCart(item);
					})
				}

				prepareAddToCart = function(product){
					var productKey = product.key();
					var minQty = product.minQty() || 1;
					var uom = ko.toJS(product.selectedUom());
					var lineKey = productKey;

					if(useInstances) {
						lineKey = utils.createGuid();
						var tempInstanceKey = product.instance || utils.createGuid();

						product.instanceKey = tempInstanceKey;
						product.lineKey = lineKey;

						instanceKeys.push(tempInstanceKey);

						if(!instancesWithKeys[tempInstanceKey]) {
							instancesWithKeys[tempInstanceKey] = [];
						}

						instancesWithKeys[tempInstanceKey].push(lineKey);

						data['instance_' + lineKey] = tempInstanceKey;
						data['p_id_' + lineKey] = productKey;
						data['instance_key_set'] = '1';
						
						if(product.isCarrier){	
							data["row_display_case_" + lineKey] = "group-parent";
							data['remove_type_' + lineKey] = 'instance';
						}else if(product.carrier && product.carrier.trim() != ''){
							data["remove_type_" + lineKey] = "hide";
							data["qty_display_type_" + lineKey] = "text"; 
							data["row_display_case_" + lineKey] = "group-child";
							if(product.qtyType == 'ratio') {
								data['qty_control_from_' + lineKey] = 'parent';
								data['multiplier_on_parent_qty_' + lineKey] = product.qtyMultiplier;
							}
						}else{
							data['remove_type_' + lineKey] = 'instance';
						}
					}
					
					data['qty_' + lineKey] = product.selectedQty() || minQty;
					if(!!uom){
						data['uom_' + lineKey] = uom.description;
						if(uom.hasOwnProperty("UomId")) data['uom_id_' + lineKey] = uom.UomId;
						data['uom_conversion_' + lineKey] = uom.uom_conversion ?? 1.0;
					}
					data['pw_id_' + lineKey] = ko.unwrap(product.selectedWarehouse().key) || oConfig.defaultWarehouse;

					if(product.type == 'child') {
						if(parentKey && product.key() !== parentKey) {
							data['parent_p_id_' + lineKey] = parentKey;
						} else if(product.parents && product.parents().length > 0){
							data['parent_p_id_' + lineKey] = product.parents()[0].key();
						}
					}

					
					data['url_' + lineKey]           = product.link();
					data['minqty_' + lineKey]        = product.minOrderQty();
					data['maxqty_' + lineKey]        = product.maxOrderQty();
					data['qty_increment_' + lineKey] = product.stepQty();

					_.assign(data, buildConfiguratorPostData(productKey));
					if (minQty && minQty != oConfig.defaultMinimumQuantity) data['min_order_qty_' + lineKey] = product.minOrderQty();

					if(product.configuratorEditData && product.configuratorEditData.lineNumber) {
						data['line_number_' + lineKey] = product.configuratorEditData.lineNumber;
					}

					if(product.configuratorEditData && product.configuratorEditData.minQty) {
						data['minqty_' + lineKey] = product.configuratorEditData.minQty;
					}

					if(product.configuratorEditData && product.configuratorEditData.maxQty) {
						data['maxqty_' + lineKey] = product.configuratorEditData.maxQty;
					}

					if(product.configuratorEditData && product.configuratorEditData.qtyIncrement) {
						data['qty_increment_' + lineKey] = product.configuratorEditData.qtyIncrement;
					}

					if(product.configuratorEditData && product.configuratorEditData.removeType) {
						data['remove_type_' + lineKey] = product.configuratorEditData.removeType;
					}

					if(product.configuratorEditData && product.configuratorEditData.priceCalcType) {
						data['price_calc_type_' + lineKey] = product.configuratorEditData.priceCalcType;
						if(product.configuratorEditData.priceCalcType == 'fixed') {
							data['price_' + lineKey] = product.configuratorEditData.price;
						}
					}

					if(product.questions && product.questions().length > 0 && product.hasCartOptions()) {
						var configData = buildConfiguratorData(product);
						data['config_json_' + lineKey] = JSON.stringify(configData.configJson);
						data['cart_option_' + lineKey] = configData.cartOptions;
					}

					if (product.pic) {
						data['pic_t_' + lineKey] = product.pic; 
					}

					additionalFields.forEach(function(field){
						if(product[field] != ''){
							data[field+'_'+lineKey] = product[field];
						}
					});

					productKeys.push(lineKey); //This needs to be above the hook so that the order in which the items are added to cart will persist. - cainb 2019-01-23 12:35:49
				}

				_.forEach(details, function (item) {

					if(oConfig.detailConfig.useAddOnProducts && item.showAddOns){
						handleAddOnProducts(item);
					}
					
					prepareAddToCart(item.selectedProduct());
					runHook('getPostDataBeforeReturn', { self: self, data: data, product: item.selectedProduct(), productKeys: productKeys });
				});

				data.keys = _.uniq(productKeys).join(',');

				if(instanceKeys.length) {
					data.instances = _.uniq(instanceKeys).join(',');

					for(key in instancesWithKeys) {
						data[key + '_keys'] = instancesWithKeys[key].join(',');
					}
				}

				return data;
			}

			function getAjaxConfig() {
				return {
					url: 'i_i_add_to_cart.asp?' + $.param(getQueryStringParameters()),
					type: 'POST',
					data: $.param(getPostData(), true),  // true forces jquery to not add brackets to array keys
					headers: {
						'X-Requested-With': 'XMLHttpRequest',
						'Accept': 'application/json, text/plain, * / *',
						'Content-Type': 'application/x-www-form-urlencoded'
					}
				};
			}

			function logSummary() {
				var cartName = cart ? ' "' + cart.nickname + '"' : '';
				console.log('Products added to cart' + cartName + ':\n' + _.map(details, function (product) {
					return '\tQty: ' + (product.selectedQty() || 1) + ' - ' + product.name();
				}).join('\n'));
			}

			function successHandler(responseData, status, request) {
				var cartLines = JSON.parse(responseData)[1].detailLines;
				var newCart = [];
				_.each(cartLines, function(line){
					newCart.push({
						prod_key: line.productID,
						parent_p_id: line.parentProductID,
						qty: line.qty,
						od_key: line.orderDetailKey
					})
				});

				if((oConfig.t_ga4 && oConfig.t_ga4_analytics != '') || (!oConfig.t_ga4_in_gtm && oConfig.t_gtm)) {
					try{
						var gtmProducts = [];
						var line_total = 0;
						_.each(details, function(item){

							var gtmProduct = {
								item_id: item.sku(),
			                    item_name: item.name(),
			                    currency: "USD",
			                    price: Number(item.unitPrice()),
			                    quantity: Number(item.selectedQty())
							}
							gtmProducts.push(gtmProduct);

							line_total = Number(item.selectedQty()) * item.unitPrice();
						});

						if (gtmProducts.length >= 1) {

							window.dataLayer = window.dataLayer || [];
	        				function gtag(){dataLayer.push(arguments);}

							gtag("event", "add_to_cart", {
				                currency: "USD",
				                value: line_total,
				                items: gtmProducts
				            });
						}
					} catch (err){
						console.log(err);
					}
				}else if(oConfig.t_gtm) {
					var gtmProducts = [];
					_.forEach(details, function (item) {

						var gtmProduct = {
							'name': item.name(),
							'id': item.sku(),
							'price': item.unitPrice(),
							'quantity': Number(item.selectedQty())
						}
						gtmProducts.push(gtmProduct);
					});

					if (gtmProducts.length >= 1) {
						window.dataLayer.push({
							'event': oConfig.t_gtm_add_to_cart_event_name,
							'ecommerce': {
								'currencyCode': 'USD',
								'add': {
									'products': gtmProducts
								}
							},
							'customer': {
								'email': cimcloud.session.email,
								'firstname': cimcloud.session.firstName,
								'lastname': cimcloud.session.lastName
							}
						});
					}
				}

				if(oConfig.isModal) {
					parent.modal.done([newCart]);
					try{
						parent.fncReloadCartWindow();
					}catch(err){
					}

					var page = window.parent ? window.parent.location.pathname.split("/").pop() : window.location.pathname.split("/").pop();
					if(!page.includes("payment.asp")) {
						parent.top.utils.toastrATC(details, newCart);
					}
				}else{
					viewModel.activeCart(newCart);

					if(viewModel.mainProduct && viewModel.mainProduct.configuratorEditData) {
						window.location = oConfig.sessionData.cartPage;
						return;
					}
					if(!cart) {
						try{
							fncReloadCartWindow();
						}catch(err){

						}
						if(typeof ofConfig === "undefined" || !ofConfig.stopToastrATC) {
							utils.toastrATC(details, cart);
						}
					} else {
						utils.modalATC(cart, data, details);
					}
					if(oConfig.pageName == 'pc_combined_results.asp' && isActiveLayout('list')) {
						utils.resetATCForm();
					}

					logSummary();

					toggleLoadingWidget(false);

					//render promo messaging template and display if applicable.
					element = utils.renderTemplateToContainer('catalog.promo_bar', data);

					runHook('addToCartSuccessHandler', { self: self, responseData: responseData, details: details });
				}
			}

			function errorHandler(responseData, status, request) {
				logSummary();

				var errorToastrConfig = {
					'closeButton': true,
					'newestOnTop': true,
					'positionClass': 'toast-top-right',
					'preventDuplicates': false,
					'showDuration': 200,
					'hideDuration': 1000,
					'tapToDismiss': true,
					'timeOut': 3000,
					'extendedTimeOut': 1000
				}

				toggleLoadingWidget(false);

				console.log(responseData);

				toastr.error(
					'Unable to add product to cart',
					'There was an error',
					errorToastrConfig
				);

			}
			var bPass = true;

			// TODO: add UoM support (qty_display, etc). Fields aren't available on the UoM Price objects yet.
			if (!_.isArray(details)) {
				details = [ details ];
			}
			var data = {
				details: details,
				cart: cart
			};

			var questionsValid = true;

			details.some(function(detail) {
				if(detail.hasOwnProperty("questionsHaveValidAnswers")) {
					questionsValid = detail.questionsHaveValidAnswers();

					if(!questionsValid) {
						detail.atcErrorText("Required option fields missing.");
					} else {
						detail.atcErrorText("");
					}

					if(!questionsValid && detail.hasOwnProperty("currentParent")) {
						detail.currentParent().rowsAtcAttempted(true);
					}
				}
				return !questionsValid;
			});

			if(!questionsValid) {
				return false;
			}

			if(details.length > 0 && details[0].useConfigurator && details[0].useConfigurator()){
				/*
					doing an [eval] on an undefined throws an error.
					so, we have to try-catch.
						The template may be overriden to allow a product (with configurator)
						to be added to cart w/o any config options selected.
				*/
				try {
					if(!eval('formValidation' + oConfig.formValidationKey).check()){
						bPass = false;
					}
				} catch(e) {
					//not using FormBuilder
				}
			}else if(details.length <= 0) {
				bPass = false;
			}

			if(bPass){
				toggleLoadingWidget(true);
				cart = ko.toJS(cart);

				var promise = $.ajax(getAjaxConfig())
					.error(errorHandler)
					.done(successHandler);

				return promise;

			}else{
				return false;
			}
		};
		
		utils.toastrATC = function(details, cart) {
			if(utils.isActiveQuote){
				var sPage = oConfig.sessionData.orderfrontUrl + '/' + "payment.asp?o_key=" + oConfig.overrideCartKey;
			}else{
				var sPage = oConfig.sessionData.orderfrontUrl + '/' + oConfig.sessionData.cartPage + "?o_key=" + oConfig.sessionData.sc_id;
			}

			if(utils.isActiveQuote && oConfig.isModal){
				sPage = "javascript:window.history.back();"
			}
			var confirmToastrConfig = {
				'closeButton': true,
				'newestOnTop': true,
				'onclick': function() {
					window.location = sPage;
				},
				'positionClass': 'toast-top-right',
				'preventDuplicates': false,
				'showDuration': 200,
				'hideDuration': 1000,
				'tapToDismiss': false,
				'timeOut': 3000,
				'extendedTimeOut': 1000
			}

			if(cart && utils.isActiveQuote) {
				var confirmTextConfig =  {title: utils.plural(oConfig.labels.savedCartsLabels.itemText, details, true) + ' ' + oConfig.labels.savedCartsLabels.modalConfirmationHeader }
			}else{
				if(utils.isActiveQuote){
					var confirmTextConfig = { title: utils.plural(' product', details, true) + ' added to ' + oConfig.labels.savedCarts }
				}else{
					var confirmTextConfig = { title: utils.plural(' product', details, true) + ' added to Cart'}
				}
			};

			runHook('toastrATCConfiguration', { confirmToastrConfig: confirmToastrConfig, confirmTextConfig: confirmTextConfig });

			utils.popToastr(
				confirmTextConfig.title,
				'Click or tap to view',
				confirmToastrConfig
			);
		}

		utils.modalATC = function(cart, data, details) {
			var modalConfig = cart && utils.isActiveQuote ?
			{
				body: 'catalog.saved_cart_confirmation_body',
				size: '',
				title: utils.plural(oConfig.labels.savedCartsLabels.itemText, details, true) + ' ' + oConfig.labels.savedCartsLabels.modalConfirmationHeader
			} :
			{
				body: 'catalog.atc_body',
				footer: 'catalog.atc_popup_buttons',
				size: getATCModalSize(),
				title: utils.plural(' Product', details, true) + ' Added to Cart'
			};

			runHook('modalATCAfterConfiguration', { self: self, cart: cart, modalConfig: modalConfig, details: details });

			utils.openModal(modalConfig, data);
		}

		utils.renderTemplateToContainer = function (template, data) {
			var surrogate = document.createElement('div');
			var $container = $('<div>').append(surrogate);

			ko.renderTemplate(
				template,
				data,
				null,
				surrogate,
				'replaceNode'
			);

			return $container;
		};

		utils.getSavedCart = function (carts, callback) {
			var selectedCart = ko.observable();
			selectedCart.subscribe(callback);

			var data = { savedCarts: carts, selectedCart: selectedCart };
			utils.openModal({
				body: oConfig.savedCartPopupTemplate,
				size: 'small',
				title: oConfig.labels.savedCartsLabels.modalHeader,
				backdrop: 'static'
			}, data);
		};

		utils.createGuid = function () {
			// https://stackoverflow.com/a/8809472
			var d = new Date().getTime();
			if(window.performance && typeof window.performance.now === "function"){
				d += performance.now(); //use high-precision timer if available
			}
			var guid = 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
				var r = (d + Math.random()*16)%16 | 0;
				d = Math.floor(d/16);
				return (c=='x' ? r : (r&0x3|0x8)).toString(16);
			});
			return guid.toUpperCase();
		};

		// Generates a unique ID and returns it twice in a row. Intended for use with the `for`
		// attribute on `label` elements.
		(function () {
			var previousId;

			utils.labelId = function () {
				var id = previousId || utils.createGuid();
				previousId = previousId ? null : id;
				return id;
			};
		}());

		utils.openModal = function (options, data) {
			_.forEach(['header', 'body', 'footer'], function (prop) {
				if (options[prop]) {
					options[prop] = utils.renderTemplateToContainer(options[prop], data);
				}
			});
			modal.open(options);
		};

		var productModel = function (product) {
			var self = this;

			var productCollectionMapping = {
				create: function (options) {
					return new productModel(options.data);
				}
			}

			ko.mapping.fromJS(product, {
				copy: [
					'tab2_html',
					'opt1',
					'opt2',
					'opt3',
					'opt4',
					'opt5',
					'opt6',
					'opt7',
					'opt8',
					'opt9',
					'accountHistory',
					'reviews',
					'tabs',
					'type',
					'promoDescriptions',
					'configuratorEditData',
					'calc_inv_message',
					'use_cart_options',
					'showAddOns',
					'addOnLabel',
					'addOnDisplay',
					'showPrice'
				],
				related: productCollectionMapping,
				secondRelated: productCollectionMapping,
				children: productCollectionMapping,
				alsoBought: productCollectionMapping,
				addOns: productCollectionMapping
			}, self);

			self.children.extend({ deferred: true });

			self.atcButtonText = ko.observable('');
			self.atcErrorText = ko.observable('');

			if(oConfig.isModal && !ko.unwrap(self.link).includes("modal=1")) {
				self.link((function() {
					var processedLink = ko.unwrap(self.link);
					if(processedLink.indexOf('?') > -1) {
						processedLink += "&";
					} else {
						processedLink += "?";
					}
					processedLink += 'modal=1&modalaction=' + utils.getParameter("modalaction");

					return processedLink;
				})());
			}

			if(utils.getParameter('quickadd') == 'true' && self.type === 'parent'){
				self.childDisplayType('droplist');
			}

			self.updateAtcButtonText = function() {
				if(self.configuratorEditData && self.configuratorEditData.od_id) {
					self.atcButtonText(oConfig.labels.updateCartItem || 'Update');
				} else if (utils.isActiveQuote) {
					self.atcButtonText('Add To ' + oConfig.labels.savedCarts);
				} else {
					self.atcButtonText(oConfig.labels.addToCart || 'Add');
				}
			};

			self.updateAtcButtonText();

			var accordionDisplaySetting = self.tabGroupAccordionDisplay();

			// set defaultTabAccordionState based for first tab
			var defaultTabAccordionState = accordionDisplaySetting === 'open-first' || accordionDisplaySetting === 'open-all';
			self.tabs.forEach(function(tab) {
				tab.open = ko.observable(defaultTabAccordionState);
				// set defaultTabAccordionState for remaining tabs (after first)
				defaultTabAccordionState = accordionDisplaySetting === 'open-all';
			});	

			self.hasCartOptions = ko.computed(function() {
				if(!oConfig.useCartOptions){
					return false;
				}else{
					if(self.questions && self.questions() && self.questions().length && (self.type === 'parent' || self.useCartOptions())) {
						return true;
					} else {
						if(self.children && self.children().length) {
							return self.children().reduce(function(hasQuestions, child) {
								return hasQuestions || (!!child.questions && !!child.questions() && !!child.questions().length && child.useCartOptions());
							}, false);
						} else {
							return false;
						}
					}
				}
			});

			self.requireInfoForAtc = ko.computed(function () {
				return !!(
						self.childDisplayType() || 
						self.useConfigurator() || 
						( oConfig.useCartOptions && self.hasCartOptions() ) ||
						(self.showAddOns && oConfig.detailConfig.useAddOnProducts && oConfig.searchConfig.requireAddOnSelection)
					) && oConfig.pageName == 'pc_combined_results.asp';
			});

			self.priceDisplay = ko.observable(function () {
				var template = 'catalog.details_link';
				if(oConfig.pageName == 'pc_product_detail.asp'){
					template = 'catalog.selected_product_price_display';
				}else if (self.useConfigurator()){
					template = 'catalog.configurator_price_display';
				}else if (self.requireInfoForAtc() && self.childStartingPrice()) {
					template = 'catalog.price_start_at'
				} else if (!self.requireInfoForAtc()) {
					template = 'catalog.selected_product_price_display';
				}

				//Manually check for template overrides since this is dynamically returned.
				//Need to convert this to a function and use any time template names are returned.
				if(document.getElementById(template + '_custom')){
					return template + '_custom';
				}else{
					return template;
				}

			}());

			self.selectedQty = ko.observable(function() {
				if(self.configuratorEditData) {
					return self.configuratorEditData.qty;
				}else if(self.qty){
					return self.qty;
				} else {
					return undefined;
				}
			}());

			self.isQtyValid = ko.observable(true);

			self.selectedWarehouse = ko.observable(_.find(self.inventory.warehouses(), function(wh){ return wh.isDefault() } ) || [] );

			if(self.configuratorEditData && self.configuratorEditData.pw_id) {
				self.selectedWarehouse(_.find(self.inventory.warehouses(), function(wh){ return wh.key() == self.configuratorEditData.pw_id } ) || [] );
			}
				
			_.each(self.uomPrice(),function(uom){
				uom.isDefault( (oConfig.useSalesUomAsDefault && uom.uom() == "uom_sales") || (!oConfig.useSalesUomAsDefault && uom.uom() == "uom_std"))

				var bHasBreaks = false;
				bHasBreaks = uom.breaks && uom.breaks() && uom.breaks().length > 0;
				uom.hasBreaks(bHasBreaks);
			});

			if(oConfig.useSalesUom)
			{
				var salesUomIndex = self.uomPrice().findIndex(function (uom) { return uom.uom() == "uom_sales"});
				if(salesUomIndex > -1) {
					for(i = self.uomPrice().length -1; i > 0; i--) {
						if(i != salesUomIndex) {
							self.uomPrice.splice(i, 1);
						}
					}
				}
			}

			self.selectedUom = ko.observable(
				(function () {
					var uom = undefined;

					// It's possible that this is a parent product and the configurator
					// edit data will not match a uom on the parent, however the selectedProduct
					// has not been set at this point, so we can't just use selectedProduct
					// With the previous version of this code, this would make the selectedUom
					// undefined and break other parts of the code because it skipped all other
					// checks if configuratorEditData had a uom value. Now it will check the
					// other conditions if the configuratorEditData doesn't match an available uom.
					// There's certainly a better way to handle this, but this will do for now.
					if(self.configuratorEditData && self.configuratorEditData.uom) {
						uom = _.find(self.uomPrice(), function (price) {
							return price.description() == self.configuratorEditData.uom;
						});
					}

					if(!uom) {
						uom = _.find(self.uomPrice(), function (price) {
							if (price.hasOwnProperty("isDefault")){
								return price.isDefault(); //set from SSJS
							} else if(typeof price.IsDefault() === "undefined"){
								return price.ConversionRate() === 1; //fallback
							} else{
								return price.IsDefault() || price.ConversionRate() === 1 || price.ConversionRate() === 12; //returned from COM
							}
						});
					}

					if(!uom && self.uomPrice().length > 0) {
						//if still no match then take the first one
						uom = self.uomPrice()[0];
					}

					return uom;
				})());

			// Set the selected quantity back to the default if the UOM changes
			self.selectedUom.subscribe(function(newValue) {
				var defaultQty = newValue.minQty() || newValue.step() || 1;
				self.selectedQty(defaultQty);
			});

			self.processedBreaks = ko.computed(function() {
				if (!( self.selectedUom() && self.selectedUom().hasBreaks() )) {
					return [];
				}

				var processed = [];
				var data = self.selectedUom().breaks();

				//converted from price break popup window to properly display single breaks that actually have multiple break records
				if(data[data.length -1].qty() == '1000000' || data[data.length -1].qty() == '100000000'){
					data.pop();
				}

				//If there's only one break we will skip showing the price break information
				if(data.length == 1) {
					return [];
				}

				processed = data.map(function(lineBreak) {
					var price, startQ, endQ, range, qty, proceed;
					price = lineBreak.price();
					startQ = lineBreak.qty();
					endQ = lineBreak.qtyEnd();
					proceed = true;

					if (endQ != '0') {
						range = startQ + ' - ' + endQ;
					} else {
						range = startQ + '+';
					}

					if(proceed){
						return {
							price: price,
							range: range
						}
					}else{
						return {}
					}
				});
				return processed.filter( function(value){ return JSON.stringify(value) !== '{}'; });
			});

			self.configPrice = buildConfiguratorPriceObservable(self.key());

			self.unitPrice = ko.computed(function () {
				if(oConfig.useListPriceOverride){
					return self.selectedUom().suggestedPrice()
				}

				if(self.configuratorEditData && self.configuratorEditData.priceCalcType == 'fixed') {
					return self.configuratorEditData.price;
				}
				return self.selectedUom().price() + self.configPrice();
			});

			self.priceTrace = ko.computed(function() {
				var bPriceErrorMessage = 'Price trace is not available';
				return self.selectedUom().priceTrace
					? self.selectedUom().priceTrace()
					: bPriceErrorMessage;
			});

			self.youSavePercent = ko.computed(function () {
				return (((self.selectedUom().suggestedPrice() - self.selectedUom().price()) / self.selectedUom().suggestedPrice()) * 100).toFixed(oConfig.youSaveDecimalPlaces) + '%';
			});

			self.suggestedPrice = ko.computed(function () {
				return self.selectedUom().suggestedPrice();
			});

			self.showYouSave = ko.computed(function () {
				return parseFloat((self.selectedUom().suggestedPrice() || 0).toFixed(oConfig.detailConfig.globalUnitPriceDecimalPlaces)) > parseFloat((self.selectedUom().price() || 0).toFixed(oConfig.detailConfig.globalUnitPriceDecimalPlaces)) && self.selectedUom().suggestedPrice() > 0 && oConfig.showProdYouSave && !oConfig.useListPriceOverride;
			});

			self.childView = function () {
				// return (self.childDisplayType() || 'stand-alone') + '-layout';
				return 'stand-alone-layout';
			}();

			if(!self.mapPriceType() && oConfig.defaultPriceDisplayType) {
				self.mapPriceType(oConfig.defaultPriceDisplayType);
			}

			self.showAtc = function () {
				var primary1    = self.inventory.showAtc() && oConfig.searchConfig.showAtc && self.mapPriceType() !== 'show_message';
				var secondary1  = oConfig.isLoggedIn || self.mapPriceType() !== 'require_login_for_atc';
				var secondary2  = oConfig.isLoggedIn || self.mapPriceType() !== 'require_login_for_price_and_atc';
				var tertiary1   = self.mapPriceType() === 'require_atc' || (self.mapPriceType() === 'require_login_or_atc' && !oConfig.isLoggedIn);
				var tertiary2   = self.mapPriceType() !== 'hide' && (self.showProdAtc() != 0);
				var quaternary1 = !(self.type === 'parent' && self.childDisplayType() == 'droplist' && oConfig.pageName != 'pc_combined_results.asp')

				return primary1 &&
						(secondary1) &&
						(secondary2) && (
							(tertiary1) ||
							(tertiary2)
						)
						&& quaternary1;
			}();

			self.showQuickAtc = ko.computed(function () {
				var showQuickAtc = self.showAtc && !self.requireInfoForAtc() && self.type !== "parent"; //initial setting
				if(oConfig.showChildrenSelection && self.type === "child" ){
					if(self.unitPrice() > 0 && self.showAtc){
						showQuickAtc = true;
					}
					else{
						showQuickAtc = false;
					}
				}

				return showQuickAtc; //(self.showAtc && !self.requireInfoForAtc())
			});

			self.allowQuickAddModal = ko.computed(function() {
				if (oConfig.pageName == 'pc_product_detail.asp') return false;

				var exceptions = ['exploded-view', 'input-qty'];
				if (exceptions.includes(self.childDisplayType())) return false;

				return true;
			});

			self.isInCart = ko.computed(function(){
				var cartCount = 0;
				if(viewModel.activeCart && viewModel.activeCart().length){
					var product = _.filter(viewModel.activeCart(), function(cart){
						return cart.prod_key == self.key() || cart.parent_p_id == self.key();
					});
					cartCount = product.length;
				}
				return cartCount;
			});

			self.showPrice = function () {
				if (typeof self.showPrice !== 'undefined') return self.showPrice;
				return self.type !== 'parent' && (
						(self.mapPriceType() !== 'show_message') &&
						!((self.mapPriceType() === 'require_login_for_price_and_atc') && !oConfig.isLoggedIn) &&
						!(self.mapPriceType() === 'require_atc' || (self.mapPriceType() === 'require_login_or_atc' && !oConfig.isLoggedIn))
					) &&
					(self.mapPriceType() !== 'hide') && (self.showProdPrice() != 0) &&
					oConfig.showPricingOrderEntry;
			}();

			self.showBreaks = ko.observable(function(){
				return oConfig.useBreaks && oConfig.useBreaksTable && oConfig.pageName == 'pc_product_detail.asp';
			}());

			self.mapPriceMessage = function () {
				if (self.mapPriceType() === 'show_message') {
					return oConfig.mapBehaviorMessages.showMessage;
				} else if (self.mapPriceType() === 'require_login_for_price_and_atc' && !oConfig.isLoggedIn) {
					return oConfig.mapBehaviorMessages.requireLoginMessage;
				} else if (self.mapPriceType() === 'require_login_for_atc' && !oConfig.isLoggedIn) {
					return oConfig.mapBehaviorMessages.requireLoginAtcMessage;
				} else if (self.mapPriceType() === 'require_atc') {
					return oConfig.mapBehaviorMessages.requireAtcMessage;
				} else if (self.mapPriceType() === 'require_login_or_atc' && !oConfig.isLoggedIn) {
					return oConfig.mapBehaviorMessages.requireLoginOrAtcMessage;
				} else if (self.mapPriceType() !== 'hide') {
					return '';
				}
			}();

			self.warehouseTemplate = function () {
				if (oConfig.showWarehouses && self.inventory.warehouses().length > 0 && self.inventory.inventoryStatus() === 'in') {
					if ((oConfig.allowWarehouseSelection && (oConfig.pageName == 'pc_combined_results.asp' && oConfig.searchConfig.hideAtcShowInv)) || (oConfig.allowWarehouseSelection && self.showQuickAtc())) {
						return 'warehouse_droplist';
					} else if (oConfig.useWarehousesTable) {
						return 'warehouse_table';
					} else {
						if(self.showQuickAtc()){
							return 'warehouse_info';
						}else{
							return '';
						}
					}
				} else {
					return 'warehouse_off';
				}
			}();

			self.priceBreaksTemplate = function(){
				if(oConfig.useBreaks){
					if(oConfig.useBreaksTable && oConfig.pageName == 'pc_product_detail.asp'){
						return 'catalog.qty_breaks_table';
					}else{
						return 'catalog.qty_breaks';
					}
				}else{
					return 'catalog.breaks_off';
				}
			}();

			self.uomTemplate = ko.computed(function() {
				var template;
				if (self.selectedProduct().uomPrice().length > 1) {
					template = 'catalog.uom_select';
				} else {
					template = 'catalog.uom_input';
				}

				if(document.getElementById(template + '_custom')){
					return template + '_custom';
				}else{
					return template;
				}
			});

			self.documentsTemplate = function () {
				return 'catalog.documents';
			}();

			self.qtyInputClick = function (key) {
				jQuery('#qty_'+key).focus();
				jQuery('#qty_'+key).select();
			};

			self.validateQtyInput = function (data) {
				if (utils.getCookie('productLayout') === 'list') {
					return '';
				} else {
					return '1';
				}
			};

			self.hideOnListView = function () {
				if ((isActiveLayout('list') && !oConfig.detailConfig.productKey ) || (self.type === 'child' && oConfig.detailConfig.productKey)) {
					return 'hide';
				}
			};

			self.isChild = function () {
				if (self.type === 'child'){
					return true;
				}else{
					return false;
				}
			}

			self.isParent = function() {
				if (self.type.toLowerCase() === 'parent'){
					return true;
				}else{
					return false;
				}
			}

			self.minQty = ko.computed(function () {
				if (self.configuratorEditData && self.configuratorEditData.minQty) {
					self.selectedProduct().selectedUom().minQty(self.configuratorEditData.minQty);
				}
				if (oConfig.useMinimumQuantity) {
					return self.selectedProduct().selectedUom().minQty();
				} else {
					return 0;
				}
			});

			self.stepQty = ko.computed(function () {
				if (self.configuratorEditData && self.configuratorEditData.qtyIncrement) {
					self.selectedProduct().selectedUom().step(self.configuratorEditData.qtyIncrement);
				}
				if (oConfig.useQuantityIncrement) {
					return self.selectedProduct().selectedUom().step();
				} else {
					return oConfig.defaultQuantityIncrement;
				}
			});

			self.maxQty = ko.computed(function () {
				if (self.configuratorEditData && self.configuratorEditData.maxQty) {
					self.selectedProduct().selectedUom().maxQty(self.configuratorEditData.maxQty);
				}
				if (oConfig.useMaximumQuantity && self.selectedProduct().selectedUom().maxQty() > 0) {
					return self.selectedProduct().selectedUom().maxQty();
				} else {
					return '';
				}
			});
			
			self.recentlyViewedTabExists = ko.computed(function() { 
				if (!Array.isArray(self.tabs) || self.tabs.length === 0) {
					return false;
				}
				return self.tabs.some(function(tab) {
					return tab.dynamicSource == "recently_viewed"; 
				});
			});

			self.customersAlsoBoughtExists = ko.computed(function() { 
				if (!Array.isArray(self.tabs) || self.tabs.length === 0) {
					return false;
				}
				return self.tabs.some(function(tab) { 
					return tab.dynamicSource == "customers_also_bought"; 
				}); 
			});

			self.hasQuantityRestrictions = ko.computed(function () {
				return (oConfig.useMinimumQuantity || oConfig.useMaximumQuantity || oConfig.useQuantityIncrement) && 
						!!(
							(self.minQty() && self.minQty() !== oConfig.defaultMinimumQuantity) || 
							( (!self.selectedUom().uom_conversion() || self.selectedUom().uom_conversion() == 1) && self.stepQty() && self.stepQty() != oConfig.defaultQuantityIncrement) || 
							self.maxQty()
						);
			});

			self.quantityRestrictionsHtml = ko.computed(function () {
				var lines = [];

				if (self.minQty() && self.minQty() !== oConfig.defaultMinimumQuantity)
					lines.push(oConfig.labels.minQtyPopover.replace(/<min_qty>/,self.minQty()));

				if ( (!self.selectedUom().uom_conversion() || self.selectedUom().uom_conversion() == 1) && self.stepQty() && self.stepQty() != oConfig.defaultQuantityIncrement)
					lines.push('Qty Increment: ' + self.stepQty());

				if (self.maxQty())
					lines.push('Maximum Qty: ' + self.maxQty());

				return lines.join('<br>');
			});

			self.addToSavedCart = function () {
				utils.getSavedCart(viewModel.savedCarts(), function (cart) {
					if (cart) {
						if (!_.includes(viewModel.savedCarts(), cart)) {
							// Insert the new cart in sorted order, notifying exactly once.
							var index = _.sortedIndex(viewModel.savedCarts(), cart, 'nickname');
							viewModel.savedCarts.splice(index, 0, cart);
						}

						var parentKey;

						if(self.type == 'child' && viewModel.mainProduct) {
							parentKey = ko.unwrap(viewModel.mainProduct).key();
						}

						utils.addToCart(self, cart, parentKey);
					}
				});
			};

			self.addToCart = function () {
				var parentKey;

				if(self.type == 'child' && viewModel.mainProduct) {
					parentKey = ko.unwrap(viewModel.mainProduct).key();
				}

				return utils.addToCart(self, undefined, parentKey);
			};

			self.atcPopupSuccess = function () {

			};

			self.atcPopupFail = function () {

			};

			self.searchfields = function() {
				var includedSearchfields = Object.keys(self).filter(function(key) { return key.startsWith('searchfield'); }).sort();
				var searchfieldsObject = {};
				includedSearchfields.forEach(function(searchfieldName) {
					if(searchfieldName == "searchfields") // The existing searchfields object
					{
						var originalKeys = Object.keys(self[searchfieldName]).filter(function(key) { return key.startsWith('searchfield'); });
						//console.log(JSON.stringify(originalKeys));
						originalKeys.forEach(function(key) {
							if(!searchfieldsObject[key]) {
								searchfieldsObject[key] = ko.unwrap(self[searchfieldName][key])
							}
						});
					} else {
						if(!oConfig.searchFieldsShell[searchfieldName]) {
							// because we can't use "?." until we stop trying to support IE
							// like: oConfig.searchFieldsShell[searchfieldName]?.label || ''
							oConfig.searchFieldsShell[searchfieldName] = {
								label: '',
								show: false
							};
						}
						searchfieldsObject[searchfieldName] = {
							field: ko.observable(searchfieldName),
							value: ko.observable(ko.unwrap(self[searchfieldName])),
							label: ko.observable(oConfig.searchFieldsShell[searchfieldName].label || ''),
							show: ko.observable(!!ko.unwrap(self[searchfieldName]) && (oConfig.searchFieldsShell[searchfieldName].show || false))
						}
					}
				});
				return searchfieldsObject;
			}();

			self.selectFirstTab = function () {
				$('#tab1 > a').click();
			};

			self.getWebPage = function (webPageId) {
				return 'page.asp?p_key=' + webPageId + ' .wpc_page_content';
			};

			self.useTabs = function () {
				return oConfig.detailConfig.useAdvancedTabs && self.tabs && self.tabs.length && self.showTabs();
			}

			self.usingDownloadsTab = function() {
				var index = _.findIndex(self.tabs, function(t) {
								return t.dynamicSource == "downloads"
							});
				return self.useTabs() && index > -1;
			}

			self.getSmartListLinkOrderStats = function(parent){
				if(!parent){
					return 'product_history_detail.asp'
				}
				if(oConfig.detailConfig.useSkuDisplay && oConfig.detailConfig.useAliases) {
					return 'product_history_detail.asp?search2=(searchexact~p.sku_display~' + parent.sku() + '|OR|searchexact~pa.sku_alias~' + parent.sku() + '&s=' + parent.sku()
				}
				else if(oConfig.detailConfig.useAliases){
					return 'product_history_detail.asp?search2=searchexact~pa.sku_alias~' + parent.sku() + '&s=' + parent.sku()
				}
				else if(oConfig.detailConfig.useSkuDisplay) {
					return 'product_history_detail.asp?search2=searchexact~p.sku_display~' + parent.sku() + '&s=' + parent.sku()
				}else{
					return 'product_history_detail.asp?search2=searchexact~p.sku~' + parent.sku() + '&s=' + parent.sku()
				}
			};

			self.getSmartListLinkInvoiceStats = function(parent){
				if(!parent){
					return 'products_invoiced_detail.asp'
				}
				if(oConfig.detailConfig.useSkuDisplay && oConfig.detailConfig.useAliases) {
					return 'products_invoiced_detail.asp?search2=(searchexact~p.sku_display~' + parent.sku()+ '|OR|searchexact~pa.sku_alias~' + parent.sku() + '&s=' + parent.sku()
				}
				else if(oConfig.detailConfig.useAliases){
					return 'products_invoiced_detail.asp?search2=searchexact~pa.sku_alias~' + parent.sku() + '&s=' + parent.sku()
				}
				else if(oConfig.detailConfig.useSkuDisplay) {
					return 'products_invoiced_detail.asp?search2=searchexact~p.sku_display~' + parent.sku() + '&s=' + parent.sku()
				}else{
					return 'products_invoiced_detail.asp?search2=searchexact~p.sku~' + parent.sku() + '&s=' + parent.sku()
				}
			};
			
			self.explodedViewHasQty = function(product){
				hasQty = false;
				_.each(product.children(), function(child){
					if(!hasQty && child.diagramQty() != ''){
						hasQty = true;
					}
				});
				return hasQty;
			};

			self.explodedViewHasNotes = function(product){
				hasNotes = false;
				_.each(product.children(), function(child){
					if(!hasNotes && child.diagramNotes() != ''){
						hasNotes = true;
					}
				});
				return hasNotes;
			};

			self.tableColumns = ko.computed( function () {
				var columns = Array.prototype.concat(
					[
						{
							if: oConfig.showImgCol,
							label: oConfig.labels.imgCol,
							field: 'thumb',
							template: 'catalog.input_qty_thumb_display',
							cellClass: 'qty-input-table-thumb'
						},
						{
							if: oConfig.showNameCol && self.childDisplayType() !== 'add-row',
							label: oConfig.labels.nameCol,
							field: 'nm',
							template: 'catalog.input_qty_nm_display'
						},
						{
							label: 'SKU',
							field: 'sku',
							template: 'catalog.input_qty_sku_display'
						},
						{
							if: oConfig.useCartOptions && self.hasCartOptions(),
							label: 'Options',
							field: 'questions',
							template: 'catalog.cart_options'
						},
						{
							if: self.childDisplayType() == 'exploded-view' && self.explodedViewHasQty(self),
							label: oConfig.labels.qtyCol,
							field: 'diagramQty'
						},
						{
							if: self.childDisplayType() == 'exploded-view' && self.explodedViewHasNotes(self),
							label: oConfig.labels.notesCol,
							field: 'diagramNotes'
						}

					],
					self.childDisplayType() !== 'add-row' ? self.childSelectors() || [] : [],
					[
						{
							if: oConfig.usePromos,
							label: 'Promo',
							template: 'catalog.input_qty_promo_display'
						},
						{
							if: oConfig.useIdp,
							label: 'Status',
							template: _.property('warehouseTemplate')
						},
						{
							label: 'Price',
							headerClass: 'cell-right',
							cellClass: 'cell-right',
							field: 'unitPrice',
							format: utils.formatPrice,
							template: 'catalog.selected_product_price_display'
						},
						{
							label: 'Actions',
							headerClass: 'cell-right',
							cellClass: 'cell-right',
							template: 'catalog.atc_qty_input',
							width: '1%'
						}
					]
				);

				// diagram_number needs to be added to the child products using the pos field on the mapping table
				if (self.childDisplayType() == 'exploded-view') {
					columns.unshift({
						label: oConfig.labels.idCol,
						field: 'diagramNumber'
					});
				}

				return _.filter(columns, function (column) {
					return !('if' in column) || ko.unwrap(column.if);
				});
			});

			runHook('productModelReviews', { self: self, product: product, reviews: self.reviews });

			self.reviews = new PagedArray(self.reviews, 3);

			self.setupQuestions = function() {
				self.questions().forEach(function(question) {
					question.answers = ko.observableArray();

					if(question.type() === 'select') {
						if(question.useSelectOne()) {
							question.answers.push({
								label: question.selectOneText(),
								value: ''
							});
						}

						if(question.answerList().length > 0) {
							var answersSplit = question.answerList().split('|');
							answersSplit.forEach(function(answer) {
								var answerSet = answer.split('~');
								question.answers.push(
									{
										label: answerSet[0],
										value: answerSet[0]
									});
							});
						}
					}
				});
			}

			self.rowCollection = ko.observableArray();

			self.addRows = function(numberOfRows) {
				numberOfRows = numberOfRows || 1;
				for(var i=0;i<numberOfRows;i++) {
					var row = {
						selectedChild: ko.observable(undefined)
					}
					//' Clone the children for the selector so that the child
					//' references across rows are not the same.
					row.children = self.children ?  self.children().map(
														function(item) {
															var jsChild = ko.mapping.toJS(item);
															var koChild = ko.mapping.fromJS(jsChild, productMapping);

															//' Add the currentParent back as the re-mapping rests it
															koChild.currentParent = ko.observable(self);

															//' Add a new property for the current row index
															//' to make it easier to remove the items
															//' We use a guid to ensure it's always consistent.
															koChild.rowKey = utils.createGuid();
															return koChild;
														})
												: [];
					self.rowCollection.push(row);
				}
			}

			self.setupAddRowView = function() {
				if(self.childDisplayType() === 'add-row') {
					self.addRows();
					if(!!oConfig.childSkuMatch) {
						var matchedChild = self.rowCollection()[0].children.filter(function(child) {
												return oConfig.childSkuMatch === child.sku();
											});

						if(matchedChild && matchedChild.length > 0){
							self.rowCollection()[0].selectedChild(
								matchedChild[0]
							);
						}
					}
				}
			}

			self.childSelectors = ko.observableArray([]);

			self.processChildren = function() {
				_.each(self.children(), function(child){
					child.promoDescriptions = [];
					_.each(self.promoDescriptions, function(promo){
						if(promo.targetKeys.indexOf(child.key()) > -1){
							child.promoDescriptions.push(promo);
						}
					});
					child.configuratorEditData = self.configuratorEditData;
					child.currentParent(self);
					child.updateAtcButtonText();
					child.setupQuestions();
				});

				_.forEach(self, function (value, key) {
					var keyString = String(key);
					var matched = keyString.match(/^opt(\d)$/);

					if (matched && value) {
						var selector = {};
						selector.field = matched[0];
						selector.label = value;
						// _.sortByAll has been absorbed into _.sortBy in lodash 4.0.0+
						var lodashSortBy = typeof(_.sortByAll) == 'function' ? _.sortByAll : _.sortBy;

						selector.options = lodashSortBy(
							_.uniq(
								_.map(self.children(), function (child) {
									var optXsort = (typeof(child[matched[0] + '_sort']) == 'function' ? child[matched[0] + '_sort']() : child[matched[0] + '_sort']) || 0;
									var optSort = (typeof(child[matched[0]]) == 'function' ? child[matched[0]]() : child[matched[0]]) || 0;

									var thisOpt = {
										option: child[matched[0]],
										sort: optXsort || optSort
									};

									runHook("selectorOptionsItemOverride", { key: key, option: thisOpt, product: child });

									return thisOpt;

								}),
								'option'
							),
							[ 'sort', 'option' ]
						);
						selector.selectedOption = ko.observable();
						if (oConfig.detailConfig.selectChildProductOnLoad) {
							selector.selectedOption(selector.options[0]);
						}

						var childSku = oConfig.childSkuMatch || (!!self.configuratorEditData ? self.configuratorEditData.sku : '') || '';

						if (childSku){
							var oChild = {};
							_.each(self.children(),function(child){
								if(childSku == child.sku()) {
									oChild = child;
									return false;
								}
							});
							_.each(selector.options,function(option){
								if(option.option == oChild[selector.field]){
									selector.selectedOption(option);
								}
							})
						}
						selector.showSelector = ko.observable(true);
						self.childSelectors.push(selector);
					}
				});

				self.selectedOptions = ko.computed(function () {
					return _.map(ko.unwrap(self.childSelectors), function (item) {
						var selection = { option: '', sort: '' };
						if (item.selectedOption()) {
							selection = item.selectedOption().option;
						}
						return { field: item.field, option: selection };
					});
				});

				self.selectedProduct = ko.computed(function () {
					var filter = {};
					_.forEach(self.selectedOptions(), function (option) {
						filter[option.field] = option.option;
					});
					var filteredChildren = _.filter(self.children(), filter);
					if (filteredChildren
						&& filteredChildren.length === 1
						&& self.childDisplayType() !== 'input-qty'
						&& self.childDisplayType() !== 'exploded-view'
						&& self.childDisplayType() !== 'matrix-all'
						&& self.childDisplayType() !== 'add-row'
						) {
							return filteredChildren[0];
					} else {
						return self;
					}
				});

				self.setupQuestions();
				self.setupAddRowView();
			}

			if (self.children && self.children().length) {
				self.processChildren();
			} else {
				self.selectedProduct = ko.observable(self);
			}

			self.loadNextSelector = function(optNumber, callback, asyncLoadOptions) {
				var selectedOpts = _.map(self.childSelectors().filter(function(selector) {return !!selector.selectedOption && !!selector.selectedOption();}), function(s) {
					return s.selectedOption().option;
				}).join('~');
				$.ajax({
					url: oConfig.pageName, //current page.
					data: {
						ajax: 'get-product-opts',
						key: self.key(),
						optFields: selectedOpts,
						modal: 1,
						modalaction: utils.getParameter("modalaction")
					},
					dataType: 'json',
					success: function(data) {
						var selector = self.childSelectors().find(function(selector) {
							return selector.field == 'opt' + optNumber;
						});

						if(!selector) {
							selector = {
								field: 'opt' + optNumber,
								label: self['opt' + optNumber],
								showSelector: ko.observable(oConfig.displayDroplistPlaceholdersForLazyLoad)
							}
							self.childSelectors.push(selector);
						}

						selector.index = optNumber;
						selector.options = data.map(function(o) {
							return {
								option: o.opt,
								sort: 0,
								count: o.count,
								pKey: o.p_key
							}
						});
						selector.loading = ko.observable(false);
						selector.selectedOption = ko.observable();
						selector.showSelector(oConfig.displayDroplistPlaceholdersForLazyLoad || !!selector.options.length);

						selector.selectedOption.subscribe(function(newValue) {
							//clear any selectors after this one
							self.childSelectors().forEach(function(s) {
								if(s.index > selector.index) {
									s.selectedOption(undefined);
									s.options.splice(0, s.options.length);
									s.selectedOption.notifySubscribers();
									s.showSelector(oConfig.displayDroplistPlaceholdersForLazyLoad);
								}
							});

							self.childSelectors.notifySubscribers();

							if(self.children) self.children.removeAll();
							// did we actually select something
							if (newValue) {
								selector.loading(true);
								var callback = function() {
									selector.loading(false);
								}
								if (selector.index < self.childSelectors().length) {
									self.loadNextSelector(selector.index + 1, callback, asyncLoadOptions);
								} else {
									//load the child
									if (asyncLoadOptions) asyncLoadOptions.done = true;
									self.loadChildProduct(newValue.pKey, callback);
								}
							}else{
								self.selectedProduct(self);
							}
						});
						self.childSelectors.notifySubscribers();
					},
					error: function() {
						utils.popToastrError('Error Loading List', 'There was an error loading the product drop list');
					},
					complete: function() {
						if (callback) callback();
						if (asyncLoadOptions) {
							var asyncLoadOptionsArray = asyncLoadOptions.getArray();
							var index = optNumber - 1;
							var option = self.childSelectors()[index];
							var availableOptions = option.options;
							var selectedOption = null;
							if (index < asyncLoadOptionsArray.length) {
								_.forEach(availableOptions, function (value, key) {
									if (value.option == asyncLoadOptionsArray[index]) selectedOption = value;
								});
							} else if (oConfig.detailConfig.selectChildProductOnLoad) {
								selectedOption = availableOptions[0];
							}
							option.selectedOption(selectedOption);
						}
					}
				})
			}

			self.loadChildProduct = function(pKey, callback) {
				$.ajax({
					url: oConfig.pageName, //current page.
					data: {
						ajax: 'get-child-product',
						key: pKey,
						parentKey: oConfig.mainProductKey,
						modal: 1,
						modalaction: utils.getParameter("modalaction"),
						action: utils.getParameter("action") == 'showpricetrace' ? 'showpricetrace' : ''
					},
					dataType: 'json',
					success: function(data) {
						var product = ko.mapping.fromJS(data.products[0], productMapping);
						product.type = "child";
						// self.children.push(product);
						if(self.questions && self.questions().length && (oConfig.useCartOptions && product.useCartOptions() && !product.f_id())) {
							if(product.questions) {
								product.questions(ko.mapping.fromJS(ko.mapping.toJS(self.questions))());
							} else {
								product.questions = ko.observableArray(ko.mapping.fromJS(ko.mapping.toJS(self.questions))());
							}
						}
						product.setupQuestions();
						product.configuratorEditData = self.configuratorEditData;
						product.updateAtcButtonText();
						self.selectedProduct(product);
						runHook('productDetailLoadChildProductSuccess', { self: self, product: product });
					},
					error: function() {
						utils.popToastrError('Error Loading Product', 'There was an error loading the product');
					},
					complete: callback
				});
			}

			self.quantityRestrictionsHtml.subscribe(function(newValue) {
				var popoverElement = $('#pop-' + $.escapeSelector(self.sku().replace(/([^A-Za-z0-9[\]{}_.:-])\s?/g, '_')));
				if(popoverElement){
					var popoverData = popoverElement.data('popover');
					if (popoverData) {
						popoverData.options.content = newValue;
					}
				}
			});

			self.lazyLoadingInProgress = ko.observable(false);

			if (self.key() == oConfig.mainProductKey && self.type === 'parent' && self.children().length === 0 && getOriginalPageName() === 'pc_product_detail.asp' && self.childDisplayType() != 'droplist') {
				if (!runHook('productDetailGetChildProductsByParentKey', { self: self }, {}, this)) {
				self.lazyLoadingInProgress(true);
				$.ajax({
					url: oConfig.pageName, //current page.
					data: {
						ajax: 'get-child-products-by-parent-key',
						key: oConfig.mainProductKey,
						modal: 1,
						modalaction: utils.getParameter("modalaction")
					},
					dataType: 'json',
					success: function(data) {
						data.forEach(function(childProduct) {
							var product = ko.mapping.fromJS(childProduct, productMapping);
							product.type = "child";
							if(self.questions && self.questions().length && product.useCartOptions() && !product.f_id()) {
								if(product.questions) {
									product.questions(ko.mapping.fromJS(ko.mapping.toJS(self.questions))());
								} else {
									product.questions = ko.observableArray(ko.mapping.fromJS(ko.mapping.toJS(self.questions))());
								}
								//product.hasCartOptions(true);
							}
							product.configuratorEditData = self.configuratorEditData;
							product.updateAtcButtonText();
							//self.selectedProduct(product);
							self.children.push(product);
						});

						self.processChildren();
						/*
						if(self.childDisplayType() === 'matrix-all') {
							self.matrix(ko.unwrap(ko.mapping.fromJS(generateMatrixData(ko.mapping.toJS(self)))));
						}
                        */
						self.lazyLoadingInProgress(false);
					},
					error: function() {
						utils.popToastrError('Error Loading Product', 'There was an error loading the product');
					},
					complete: function() { self.lazyLoadingInProgress(false); }
				});
			}
			}

			/*
				This function generates the display data for parent products whose
				childDisplayType is 'matrix-all'. It returns a data structure that
				looks like this:

				[
					{
						label: <<Label of row>>,
						swatch: <<Either an HTML color without the # or blank. E.g. 00ff44>>,
						thumb: <<A thumbnail image link to display if the swatch is blank>>,
						cols: [
							{
								label: <<Label of column>>,
								product: <<This links to the product.children[x] data. When KO maps this, it will be its own self-contained ProductModel.>>
							},
							{
								... more columns
							}
						]
					},
					{
						... more rows
					}
				]
			*/
			
			self.matrix = ko.computed(function () {
				if (self.childDisplayType() === 'matrix-all') {
					/***** Main method *****/

					// Initial vars
					var rowOpt = '';
					var colOpt = '';
					var swatchOpt = "color_code";

					// Build the matrix
					var rawChildren = self.children();
					var bucketed = bucketChildren(rawChildren, rowOpt, colOpt, swatchOpt);
					bucketed.rowKeys = sortKeys(bucketed.rowKeys);
					bucketed.colKeys = sortKeys(bucketed.colKeys);

					runHook('productDetailMatrixBeforeConstruction', {
						parent: self,
						configs: {
							rowOpt: rowOpt,
							colOpt: colOpt,
							swatchOpt: swatchOpt
						},
						bucketed: bucketed
					});

					var matrix = constructMatrix(bucketed);

					runHook('productDetailMatrixData', {
						parent: self,
						matrix: matrix
					});

					return matrix;
				} else {
					return [];
				}

				/***** Helper functions *****/

				// Find the unique row and column values and their sorts, and index the children.
				function bucketChildren(rawChildren, rowOpt, colOpt, swatchOpt) {
					var initial = {
						products: {},
						rowKeys: {},
						colKeys: {}
					};

					var result = _.reduce(rawChildren, function(acc, cur) {
						var rowLabel = ko.toJS(cur[rowOpt]);
						var rowSort = ko.toJS(cur[rowOpt + '_sort']);
						var rowSwatch = ko.toJS(cur[swatchOpt]);
						var rowThumb = ko.toJS(cur['thumb']);
						var colLabel = ko.toJS(cur[colOpt]);
						var colSort = ko.toJS(cur[colOpt + '_sort']);

						// Index the child
						if (!acc.products[rowLabel]) {
							acc.products[rowLabel] = {};
						}
						acc.products[rowLabel][colLabel] = cur;

						// Keep track of the unique row values
						if (!acc.rowKeys[rowLabel]) {
							acc.rowKeys[rowLabel] = {
								label: rowLabel,
								sort: rowSort,
								swatch: rowSwatch,
								thumb: rowThumb
							};
						}

						// Make sure we have a swatch and thumb
						if (!acc.rowKeys[rowLabel].swatch) {
							acc.rowKeys[rowLabel].swatch = rowSwatch;
						}
						if (!acc.rowKeys[rowLabel].thumb) {
							acc.rowKeys[rowLabel].thumb = rowThumb;
						}

						// Keep track of the unique column values
						acc.colKeys[colLabel] = {
							label: colLabel,
							sort: colSort
						};

						runHook('productDetailMatrixReduce', {
							parent: self,
							configs: {
								rowOpt: rowOpt,
								colOpt: colOpt,
								swatchOpt: swatchOpt
							},
							acc: acc,
							cur: cur
						});

						return acc;
					}, initial);

					runHook('productDetailMatrixBucketed', {
						parent: self,
						configs: {
							rowOpt: rowOpt,
							colOpt: colOpt,
							swatchOpt: swatchOpt
						},
						bucketed: bucketed
					});

					return result;
				}

				// Sort the rowKeys and colKeys
				function sortKeys(obj) {
					var list = _.values(obj);
					var sorted = _.sortBy(list, 'sort');
					return sorted;
				}

				function constructMatrix(bucketed) {
					// Construct the matrix, one row at a time
					var matrix = _.map(bucketed.rowKeys, function(row) {
						// Get values
						var label = row.label || '';
						var swatch = row.swatch || '';
						var thumb = row.thumb || 'images/no-image.png';

						// Generate the columns
						var cols = constructColumns(bucketed, row);

						// Return the results
						var row = {
							label: label,
							swatch: swatch,
							thumb: ko.observable(thumb), // Don't know why this is an observable, so leaving it
							cols: cols
						};

						runHook('productDetailMatrixRow', {
							parent: self,
							bucketed: bucketed,
							row: row
						});

						return row;
					});

					return matrix;
				}

				function constructColumns(bucketed, row) {
					var columns = _.map(bucketed.colKeys, function(col) {
						// Find the product that sits at the intersection of row and col
						var product = bucketed.products[row.label][col.label];

						var col = {
							label: col.label,
							product: product
						};

						runHook('productDetailMatrixCol', {
							parent: self,
							bucketed: bucketed,
							row: row,
							col: col
						});

						return col;
					});

					return columns;
				}
			}).extend({ deferred: true });

			if ((self.key() == oConfig.mainProductKey || oConfig.mainProductKey == '') && self.type !== 'parent') {
				self.setupQuestions();
			}

			// 2019-07-05 EJ - This only needs to run on the mainProduct on
			//                 the product detail page so I added a mainProductKey
			//                 var and check it against the current product's key
			if (self.key() == oConfig.mainProductKey && self.type === 'parent' && self.childSelectors().length === 0 && getOriginalPageName() === 'pc_product_detail.asp' && self.childDisplayType() == 'droplist') {

				if(!self.opt1 || self.opt1 == ''){
					//force this to stand-alone because it has bad data setup.
					self.type = 'stand-alone';
					console.log('This Parent product is set to "droplist" type but doesn\'t have a value in the opt1 field.  This is required for drop list type products.');
				}else{
					var selectorCount = 0;
					_.forEach(self, function (value, key) {
						var keyString = String(key);
						var matched = keyString.match(/^opt(\d)$/);

						if (matched && value) {
							selectorCount++;
							var selector = {};
							selector.field = matched[0];
							selector.label = value;
							selector.options = [];
							selector.selectedOption = ko.observable();
							selector.loading = ko.observable(matched[0] === 'opt1');
							selector.index = selectorCount;
							selector.showSelector = ko.observable(oConfig.displayDroplistPlaceholdersForLazyLoad);

							self.childSelectors.push(selector);
						}
					});

					// as third option:
					//   pass an array of options to do loading
					//   pass a shorter array to load up to that point then load first option from then on
					//   pass null/false to not do loading
					self.loadNextSelector(1, null, new (function() { // this is an inplace constructor
						this.array = oConfig.childOptionList,
						this.done = false;
						this.getArray = function() {
							if (this.done) return [];
							else return this.array;
						}
					})());
				}
			}

			self.removeRow = function(rowKey) {
				var index = _.findIndex(self.rowCollection(), function(row) {
					return row.selectedChild().rowKey === rowKey;
				});

				self.rowCollection.splice(index, 1);
			}

			self.currentParent = ko.observable(function() {
				if(self.type !== 'child') {
					return self;
				}

				if (self.currentparent && self.currentParent()) {
					return self.currentParent
				}

				if (self.type === 'child' && viewModel && ko.unwrap(viewModel.mainProduct)) {
					var matchingParent = self.parents().find(
						function(parent) {
							return parent.key() === ko.unwrap(viewModel.mainProduct).key();
						}
					)
					return matchingParent ? ko.unwrap(viewModel.mainProduct) : self;
				}

				return self;
			}());

			self.addRowsToCart = function() {
				var items = self.rowCollection().map(
					function(item) {
						return item.selectedChild();
					}
				).filter(
				function(item) {
					return item !== undefined
				});

				utils.addToCart(items, undefined, self.key(), true);
			}

			self.rowsAtcAttempted = ko.observable(false);

			self.questionsHaveValidAnswers = ko.computed(function() {
				var validOptions = true;

				if(self.useCartOptions()) {
					self.questions().some(function(question) {
						var answer = question.selectedAnswer();
						var answerPopulated = (answer !== '' && answer !== undefined);
						if(question.required() && !answerPopulated) {
							validOptions = false;
						}
						return !validOptions;
					});
				}

				return validOptions;
			});

			self.allRowsValid = ko.computed(function() {
				var rowsValid = true;

				self.rowCollection().some(function(row) {
					rowsValid = row.selectedChild() === undefined || row.selectedChild().questionsHaveValidAnswers();
					return !rowsValid;
				});

				return rowsValid;
			});

			// List of fields that will have legitimate HTML in them. Note - we
			// are intentionally not putting these in a site option in order to
			// force any changes to go through source control.
			var fieldsToDecode = [
				'inventory.stockMessage',
				'description',
				'name',
				'tabs>staticContent',
				'thumbAltText',
				'picAltText',
				'addOnLabel',
				'opt1',
				'opt2',
				'opt3',
				'opt4',
				'opt5',
				'opt6'
			];

			runHook('fieldsToDecodeFilter', { self: self, product: product, fieldsToDecode: fieldsToDecode });

   			// Set each field in the above list to the decoded version of itself
            fieldsToDecode.forEach(function(propName) {
                //using a > to denote a property on an array.
                if(propName.indexOf('>') > -1){
                    //get the array and the property on that array to be decoded
                    var collection = self[propName.split('>')[0]];
                    var propName = propName.split('>')[1];

                    //now check to see if the array is observable before looping
                    if(typeof collection == 'function'){
                        _.each(collection(), function(obj){
                            if (typeof obj()[propName] == 'function') { // If it is an observable
                                obj()[propName](utils.decodeHTML(obj()[propName]()) );
                            } else if (typeof obj()[propName] == 'string') { // If it is a string
                                obj()[propName] = utils.decodeHTML(obj()[propName]);
                            } else {
                                // Do nothing
                            }
                        })
                    }else{
                        _.each(collection, function(obj){
                            if (typeof obj[propName] == 'function') { // If it is an observable
                                obj[propName](utils.decodeHTML(obj[propName]()) );
                            } else if (typeof obj[propName] == 'string') { // If it is a string
                                obj[propName] = utils.decodeHTML(obj[propName]);
                            } else {
                                // Do nothing
                            }
                        })
                    }

                }else{
                    var field = _.get(self, propName);

                    if (typeof field == 'function') { // If it is an observable
                        field( utils.decodeHTML(field()) );
                    } else if (typeof field == 'string') { // If it is a string
                        _.set(self, propName, utils.decodeHTML(field));
                    } else {
                        // Do nothing
                    }
                }
            });

			if(self.currentParent()) {

				if(self.documents().length == 0 && self.currentParent().documents().length > 0 ){
					self.documents(self.currentParent().documents());
				}

				if(!self.description() && self.currentParent().description()) {
					self.description(self.currentParent().description())
				}

				//Handle Tab Inheritance at the individual tab instead of the array of tabs
				var newTabs = [];
				for(i=1; i <= oConfig.detailConfig.numberOfProductTabs; i++){
					var parentTab = _.filter(self.currentParent().tabs, function(tab){ return tab.index == i})[0];
					var childTab  = _.filter(self.tabs,                 function(tab){ return tab.index == i})[0];
					if(childTab){
						newTabs.push(childTab);
					}else if(!childTab && parentTab){
						//Inherit from the parent
						newTabs.push(parentTab);
					}
				}
				if(newTabs){
					self.tabs = newTabs;
				}

			}

			if (self.showAddOns)  {
				if (self.addOnDisplay == 'checkboxes')
					self.selectedAddOns = ko.observableArray([]);
				else //             ^ note difference in plural
					self.selectedAddOn  = ko.observable();
			}

			self.addOnOptions = function(){
				var options = [];
				
				if(self.addOns){
					options = _.map(self.addOns(), function(product){
						product.selectedQty(product.minQty() || 1);
						return ko.observable(product)
					})
				}
				return options;
			}();

			runHook('productModelBottom', { self: self, product: product });
		};

		var productMapping = {
			create: function (options) {
				return new productModel(options.data);
			}
		};

		var searchResultsPropertiesModel = function (properties) {
			var self = this;

			self.selectedSort = ko.observable(utils.getParameter('sortby') || oConfig.searchConfig.defaultSort);
			self.selectedRpp = ko.observable(utils.getParameter('rpp') || oConfig.searchConfig.rpp);
			self.startCount = ko.observable((oConfig.searchConfig.page - 1) * oConfig.searchConfig.rpp + 1);

			self.endCount = ko.observable(function () {
				var rpp = (oConfig.searchConfig.rpp === 0) ? oConfig.searchConfig.total : oConfig.searchConfig.rpp;
				return Math.min(((oConfig.searchConfig.page - 1) * rpp) + rpp, oConfig.searchConfig.total);
			}());

			self.maxPage = ko.observable(
				Math.ceil(oConfig.searchConfig.total / oConfig.searchConfig.rpp)
			);

			self.selectedLayout = ko.observable(
				utils.getCookie('productLayout')
			);

			self.changeRpp = function (value) {
				var sUrl = utils.setParameter('rpp', value);
				sUrl = sUrl + '&page=1';
				window.location =  sUrl;
			};

			self.layoutTemplate = ko.computed(function () {
				utils.setCookie('productLayout', self.selectedLayout());
				window.location.reload();
				return 'catalog.' + self.selectedLayout() + '_view'
			});

			runHook('searchResultsPropertiesModelBottom', { self: self, properties: properties });
		};

		function PagedArray(array, pageSize) {
			//' if array has already been processed, just return it
			if(array && array.all) {
				return array;
			}
			var self = this;
			self.all = ko.observableArray(array || []);
			self.pageSize = ko.observable(pageSize || 10);
			self.page = ko.observable(1).extend({ counter: 1 });
			self.numPages = ko.computed(function () {
				return Math.ceil(self.size() / self.pageSize());
			});
			self.size = ko.computed(function () {
				return self.all().length;
			});
			self.items = ko.computed(function () {
				var pageSize = self.pageSize();
				var start = (self.page() - 1) * pageSize;
				return self.all.slice(start, start + pageSize);
			});
		}
	</script>


<script>
	$(function () {
		if(typeof(ko) != 'undefined'){
			ko.applyBindings();
		}
	});
	
</script>
</div>

<div class="cart-templates-scripts">
<script type="text/javascript">

    addTimer("start of ko model");
    $( document ).ready(function() {

        if('signin.asp' == 'payment.asp' ){
            $(document).on('hidden', '#global_modal', function () {
                if( $('#handle-modal-exit').val() == 'true'){
                    $('#handle-modal-exit').val(false);
                    viewModel.genericModalComplete();
                }
            });
        }
        addTimer("cart_templates: document.ready");

    });

    function emptyHandler(){}

    function scrollToSection(id) {
         $('html, body').animate({
            // subtracting body padding-top accounts for toolbars with absolute
            // position, such as the SU bar
            scrollTop: $(id).offset().top - parseInt($("body").css('padding-top'))
         }, 300,
        function(){
            $('html, body').clearQueue();
        });
    }

    function reloadPage() {
        viewModel.processing(true);
        location.reload();
	}

	function sendError(message) {
		if (window['insightRUM']) {
			insightRUM.rawErrors.push(["Error", sPageName, 1, 1, new Error(message.substring(0,149))]);
		}
	}

    function buildContinueShoppingUrl() {
        var url = ofConfig.continueShopPage;
        if (ofConfig.guestLoggedIn) {
            url = utils.setParameter('action', 'logout', url);
        } else if (ofConfig.superUserOrderFormMode) {
            url = ofConfig.createNewQuoteLink;
        }
        return url;
    }

    function drawContinueShoppingLabel() {
        var label = '<i class="icon-shopping-cart"></i> Continue Shopping';
        var su_workflow = '';
        if (su_workflow === "quote") {
            label = '<i class="icon-plus"></i> Create New ' + su_workflow;
        }
        return label;
    }

    function isValidRequestedDate(date) {
		try {
			isBusinessDay = businessDaySettings[1][""].work_days_of_week == undefined || businessDaySettings[1][""].work_days_of_week.indexOf(date.getDay()+1) > -1;
			isHoliday = businessDaySettings[1][""].holiday_dates != undefined && businessDaySettings[1][""].holiday_dates.indexOf(moment(date).format("MM/DD/YYYY")) > -1;
			if(isBusinessDay && !isHoliday) {
				return true;
			} else {
				return false;
			}
		} catch (error) {
			sendError("isValidRequestedDate: " + error.message);
			return true;
		}
    }

    function calculateAdjustedLeadTimeDays(effectiveOrderDate, leadTimeDays) {
        /*
            EJ - 2016-11-10 - adding 1 initially due to JS interpreting the date from the db as UTC date
                              when in fact it is a local date at midnight.  This causes the date
                              in JS to be the day before at 7pm (-5) or 8pm (-4) depending on time
                              of year.
                              Since we are only really concerned with the day, we can add one to
                              compensate for this.
        */
        var adjustedLeadTimeDays = leadTimeDays;

        /* EJ - 2016-11-10 - This if block helps mitigate when the cuttoff time has passed, but the
                             effective_order_date on the SelectedShipVia has not be recalced by the
                             ordering object yet.  It essentially checks the cutoff if the effective_order_date
                             is today.  Otherwise, there is no need to calculate it.
        */
        if(moment(effectiveOrderDate).date() <= moment().date()) {
            var currentDateTime = new Date();
            var currentUTCSeconds = currentDateTime.getUTCSeconds() + (60 * currentDateTime.getUTCMinutes()) + (60 * 60 * currentDateTime.getUTCHours());

            if(currentUTCSeconds > businessDaySettings[1][""].utc_cutoff_time_seconds) {
                adjustedLeadTimeDays += 1;
            }
        }

        var startingDate = moment();

        for(var i = 1; i < adjustedLeadTimeDays + 1; i++) {
            if(!isValidRequestedDate(startingDate.add(1, "days").toDate())) {
                adjustedLeadTimeDays += 1;
            }
        }

        return adjustedLeadTimeDays;
    }

    function autoAllocateItems() {
        if((!viewModel.useMultiShipEditUI()) || (viewModel.shipments().length === 1
            && viewModel.shipments()[0].shipTo().key() != ""
            && viewModel.shipments()[0].shipTo().key() != null)) {
                if(viewModel.shipments()[0].details().length === 0) {
                    viewModel.moveAllItemsToShipment(viewModel.shipments()[0]);
                }
        }
    }

    //This div is contained in the SU dashboard bar.
    utils.setActiveQuote = function (orderKey) {
        if(orderKey != ofConfig.SessionOrderKey) {
             jQuery.ajax({
                url: 'payment.asp' + '?o_key=' + viewModel.orderKey() + '&ajax=true&pageaction=setActiveQuote&quote_id=' + orderKey + '&randomnum=' + new Date().getTime()
                , cache: false
                , success: function(data,status,request){

                }
                , error: function(data) {
                    alert('Failed to set order as active quote.');
                }
                , complete: function(data) {
                }
            });
        }
    }

    utils.removeActiveQuote = function (bStartNewQuote) {
        utils.setActiveQuote('');
        if(bStartNewQuote){
            window.location = 'payment.asp' + (ofConfig.isModal ? '?modal=1' : '');
        }else{
            $('.active-quote-message').hide();
        }
    };

    var orderInfoPostUrl = 'payment.asp'; //'https://' + window.location.hostname + window.location.pathname;
    var orderInfoPostApiUrl = "/ordering/";
    var countries = [];
    var newCustomer = false;
    var viewModel;
    var order;

    function addAddressHandler() {
        viewModel.genericModalComplete();
        //viewModel.allocateShipments(false);
        apiGetShippingAddresses();
    }

    function changeAddressFromFinder(response, object){
        console.log(response);
        var address = new addressInfo({ key: response.sha_key.value, state: '', city: '', zipCode: '', country: '', name: response.sha_key_disp.value, address1: '', global: 0 });
        object.shipTo(address);
    }

    function getAddressOptFields(){
        var addressOptFields = [];
        for(i=1; i <= 5; i++){
            if(ofConfig['showAddressOpt'+i]){
                addressOptFields.push(i);
            }
        }
        return addressOptFields;
    }

    function apiGetShippingAddresses(editedKey) {
        $.ajax({
            url: orderInfoPostUrl + '?o_key=' + viewModel.orderKey() + '&ajax=true&pageaction=apiGetShippingAddresses',
            success: function(data) {
                // viewModel.shippingAddresses = processShippingAddresses(JSON.parse(data))();
                // viewModel.shippingAddresses.notifySubscribers();
                var newVarForTesting = processShippingAddresses(JSON.parse(data))();
				var editedShaKey = editedKey || '';
				var bFound;
                _.each(newVarForTesting, function(newItem){
                    _.each(viewModel.shippingAddresses(),function(oldItem){
                        if(editedShaKey != ''){
                            if(oldItem.key() == editedShaKey && newItem.key() == editedShaKey){
                                oldItem.name(newItem.name());
                                oldItem.phone(newItem.phone());
                                oldItem.address1(newItem.address1());
                                oldItem.address2(newItem.address2());
                                oldItem.address3(newItem.address3());
                                oldItem.address4(newItem.address4());
                                oldItem.address5(newItem.address5());
                                oldItem.city(newItem.city());
                                oldItem.state(newItem.state());
                                oldItem.zipCode(newItem.zipCode());
                                oldItem.opt1(newItem.opt1());
                                oldItem.opt2(newItem.opt2());
                                oldItem.opt3(newItem.opt3());
                                oldItem.opt4(newItem.opt4());
                                oldItem.opt5(newItem.opt5());
                                return false;
                            }
                            bFound = true;
                        }else{
                            bFound = false;
                            if(oldItem.key() == newItem.key()){
                                bFound = true;
                                return false;
                            }
                        }
                    });
                    if(!bFound && editedShaKey == ''){
                        viewModel.shippingAddresses.push(ko.mapping.fromJS(newItem, shippingAddressMappingOptions));
                        return false;
                    }
                });
                viewModel.availableAddresses = viewModel.shippingAddresses;
            },
            error: function(){
                alert('error getting shipping addresses');
            }
        });
    }

    var shippingAddressMappingOptions = {
        create: function(options) {
            var address = new addressInfo(options.data);
            address.summaryLine = ko.computed(function() {
                var lineText = '';

                function separator(separatorText) {
                    return lineText === '' ? '' : separatorText;
                }

                function addField(field, separatorText) {
                    if(!separatorText) { separatorText = ', '}
                    lineText += field ? separator(separatorText) + field : '';
                }

                addField(address.name());
                addField(address.company());
                addField(address.attention());
                addField(address.firstName());
                addField(address.lastName(), address.firstName() ? ' ' : '');
                addField(address.address1());
                addField(address.address2());
                addField(address.address3());
                addField(address.address4());
                addField(address.address5());
                addField(address.global());
                addField(address.city());
                addField(address.state());
                addField(address.zipCode(), ' ');
                addField(address.country());

                return lineText;
            });
            return address;
        }
    }

    function getNewAddress() {
        return {
            name: "",
            firstName: "",
            lastName: "",
            company: "",
            attention: "",
            address1: "",
            address2: "",
            address3: "",
            address4: "",
            address5: "",
            country: "USA",
            city: "",
            state: "",
            county: "",
            zipCode: "",
            phone: "",
            email: "",
            global: "0",
            opt1: "",
            opt2: "",
            opt3: "",
            opt4: "",
            opt5: "",
            key: utils.createGuid()
        }
    }

    function getShippingFromBilling() {
        var newAddress = JSON.parse(ko.toJSON(viewModel.Account));

        newAddress.firstName = viewModel.Customer().firstName();
        newAddress.LastName = viewModel.Customer().LastName();
        newAddress.Email = viewModel.Customer().Email();
        newAddress.Phone = viewModel.Customer().Phone();
        newAddress.Name = "";
        newAddress.Attention = viewModel.Customer().firstName() + ' ' + viewModel.Customer().LastName();
        newAddress.key = utils.createGuid();

        return newAddress;
    }

    function getNewShipment() {
        return new Shipment({
            Key: utils.createGuid(),
            SelectedShipVia: {},
            ShipViaChoices: [],
            Details: [],
            LeadTimeDays: null,
            ShipTo: getNewAddress(),
            Comments: "",
            availableAddresses: shippingAddresses
        });
    }

    function generateRefId(){
        var today = new Date();
        var dd = today.getDate();
        var mm = today.getMonth()+1; //January is 0!
        var yyyy = today.getFullYear();
        var rand = Math.floor(Math.random() * 1000000)

        if(dd<10) {
            dd='0'+dd
        }

        if(mm<10) {
            mm='0'+mm
        }

        today = yyyy+mm+dd+'-'+rand;
        return today;
    }

    var StateChoice = function(code, name) {
        var self = this;

        var choice = {};
        choice["code"] = code;
        choice["name"] = name;

        ko.mapping.fromJS(choice, {}, self);
    }

    var detailLineInstanceSort = function (a, b) {
            var aInstance = a.instance();
            var bInstance = b.instance();
            var aLineNum = a.orderDetailNum();
            var bLineNum = b.orderDetailNum();
            var aParentId = a.parentProductID().trim();
            var bParentId = b.parentProductID().trim();

            if(aInstance == bInstance) {
                return (aParentId < bParentId) ? -1 : (aParentId > bParentId) ? 1 : 0;
            } else {
                return (aLineNum < bLineNum) ? -1 : 1;
            }
    }

    var shipmentDetailsInstanceSort = function(a, b) {
        return detailLineInstanceSort(a.orderDetail, b.orderDetail);
    }

    var addressInfo = function(addressData, addressType) {
        var self = this;

        if(!addressData) {
            addressData = getNewAddress();
        }

        // Order JSON returns isGlobalAddress while the rest of the template loads in data relying on global
        if('isGlobalAddress' in addressData && !('global' in addressData)){
            addressData.global = addressData.isGlobalAddress;
        }
        
        addressData.addressType = ko.observable(addressType || 'shipping');

        ko.mapping.fromJS(addressData, {}, self);

        if(!self.state()) {
            self.state(undefined);
        }

        for(var prop in self) {
            if(self.hasOwnProperty(prop) && typeof self[prop] === 'function') {
                if(self[prop]() === null || self[prop]() === 'null') {
                    self[prop]('');
                }
            }
        }

        self.address1.extend({ required: true });
        self.country.extend({ required: true });
        self.city.extend({ required: false });
        self.state.extend({ required: ofConfig.bRequireStateForShipping });
        self.zipCode.extend({ required: ofConfig.bRequireZIPCodeForShipping });

        if(!self.country()) {
            self.country("USA");
        }

        self.stateChoices = ko.observableArray();

        self.editing = ko.observable(false);

        self.loadStates = function() {
            countries.forEach(function(country) {
                if(country.iso3 === self.country()) {
                    self.stateChoices(country.states);
                }
            });
            if(!self.country()){
                self.stateChoices.removeAll();
            }
        }

        self.country.subscribe(function() {
            self.loadStates();
        });

        self.loadStates();
    }

    var detailMap = function(detailLine, shipment, order) {
        var self = this;
        self.detailLine = detailLine;
        self.shipment = shipment;
        self.totalOrderQty = ko.computed(function() { return detailLine.qty(); });
        self.qtyToShip = ko.observable();
        self.qtyInShipment = ko.observable(function() {
                self.qtyToShip(self.shipment.details().reduce(function(prev, curr, index, arr) {
                    if(curr.orderDetailId() == detailLine.orderDetailKey()) {
                        return prev + Number(curr.qtyToShip());
                    } else {
                        return prev;
                    }
                }, 0));
                return self.qtyToShip();
            }()
        );

        self.qtyInAllShipments = ko.computed(function() {
            var theKey = self.shipment.key();
            var allShipmentsTotal = order.shipments().reduce(function(runningTotal, individualShipment) {
                var individualShipmentTotal = individualShipment.details().reduce(function(runningSubTotal, detail) {
                    if(detail.orderDetailId() == detailLine.orderDetailKey()) {
                        return runningSubTotal + Number(detail.qtyToShip() || 0);
                    } else {
                        return runningSubTotal;
                    }
                }, 0);
                return runningTotal += individualShipmentTotal;
            }, 0);

            var currentShipmentTotal = self.shipment.details().reduce(function(runningSubTotal, detail) {
                if(detail.orderDetailId() == detailLine.orderDetailKey()) {
                    return runningSubTotal + Number(detail.qtyToShip() || 0);
                } else {
                    return runningSubTotal;
                }
            }, 0)

            var otherShipmentsTotal = allShipmentsTotal - currentShipmentTotal;

            return otherShipmentsTotal + Number(self.qtyToShip() || 0);
        });

        self.unallocatedQty = ko.computed(function() { return self.detailLine.qty() - self.qtyInAllShipments(); });
        self.toggleItemSelect = ko.computed({
            read: function() {
                return this.qtyToShip() > 0;
            },
            write: function(isChecked) {
                if(isChecked){
                    this.qtyToShip(1);
                } else {
                    this.qtyToShip(0);
                }
            }
        }, self);

        runHook('detailLineModelBottom', { self: self, detailLine: self });
    };

    var Shipment = function(shipmentData) {
        var self = this;
        ko.mapping.fromJS(shipmentData, {
            shipTo : shippingAddressMappingOptions,
            shipViaChoices : {
                create: function(options) {
                    if(options.data == null) {
                        return ko.mapping.fromJS([]);
                    } else {
                        return ko.mapping.fromJS(options.data);
                    }
                }
            },
            selectedShipVia : {
                create: function(options) {
                    if(options.data == null) {
                        return ko.mapping.fromJS({requestDate: "0001-01-01T00:00:00"});
                    } else {
                        return ko.mapping.fromJS(options.data);
                    }
                }
            },
            details: {
                create: function(options) {
                    options.data.orderDetail.configuratorJson = options.data.orderDetail.configuratorJson ? JSON.parse(options.data.orderDetail.configuratorJson) : {};
                    if(options.data.orderDetail.configuratorJson && options.data.orderDetail.configuratorJson.choices) {
                        options.data.orderDetail.configuratorJson.choices = options.data.orderDetail.configuratorJson.choices.sort(function(a, b) {
                            if(a.pos == b.pos) {
                                return 0;
                            }
                            return a.pos < b.pos ? -1 : 1;
                        });
                    }
                    if(options.data.orderDetail.configuratorJson) {
                        options.data.orderDetail.configuratorJson.configType = options.data.orderDetail.configuratorJson.configType || 'configurator';
                    }
                    options.data.orderDetail.parentProductID  = options.data.orderDetail.parentProductID.trim() || '';
                    options.data.orderDetail.editing = false;

                    return ko.mapping.fromJS(options.data);
                }
            }
        }, self);

        self.details().forEach(function(detail) {
            var detailLine = detail.orderDetail;
            detailLine.instanceChildren = self.details().filter(function(item) {
                                            return item.orderDetail.instance() == detailLine.instance() && item.orderDetail.parentProductID().trim() != '' && item.orderDetail != detailLine;
                                        }).map(function(item) { return item.orderDetail; }) || [];

            var mainProductArray = self.details().filter(function(item) {
                                            return item.orderDetail.instance() == detailLine.instance() && item.orderDetail.removeType() == 'instance';
                                        });
            var mainProduct = mainProductArray.length > 0 ? mainProductArray[0].orderDetail : detailLine;

            var mainProductQty = mainProduct.qty();

            detailLine.instanceUnitPrice = ko.observable(self.details().reduce(function(current, item) {
                if(!ofConfig.showProductDiscount){
                    item.orderDetail.priceBeforeAdjustment(item.orderDetail.price());
                }
                if(item.orderDetail.instance() == detailLine.instance()) {
                    //This IF block is attempting to account for calculating the unit price on "nested" configurator lines.  Any other product type should fall into
                    //  The else block so it just pulls the quantity from the od lines.
                    //TODO:  Account for configurator products AND split by address as this calculation will likely not work in that scenario.
                    //       In this scenario, it should use the extended quantity on the shipment not the one from the OD line.
                    if(item.orderDetail.parentProductID().trim() != '' && item.orderDetail.removeType() != 'instance' && item.orderDetail.removeType() != 'OD_Key') {
                        return current += ((item.orderDetail.priceBeforeAdjustment() || item.orderDetail.price()) * item.qtyToShip()) / mainProductQty;
                    } else {
                        return current += (item.orderDetail.priceBeforeAdjustment() || item.orderDetail.price());
                    }
                } else {
                    return current;
                }
            },0));

            detailLine.instanceUnitPriceDisplay = ko.computed(function(){
                return utils.formatMoney(detailLine.instanceUnitPrice(), utils.decimalPlacesOnUnitPrices);
            });

            detailLine.instanceExtPrice = ko.observable(self.details().reduce(function(current, item) {
                if(item.orderDetail.instance() == detailLine.instance()) {
                    return current += (item.orderDetail.priceBeforeAdjustment() || item.orderDetail.price()) * item.qtyToShip();
                    // return current += item.orderDetail.extTotal();
                } else {
                    return current;
                }
            },0));
        });

        self.details.sort(shipmentDetailsInstanceSort);

        self.availableAddresses = shippingAddresses;

        self.shipTo = ko.observable(self.shipTo);

        self.availableAddresses().forEach(function(address,index){
            if(!self.shipTo()){
                self.shipTo(address);
                return false;
            }
            if(address.key() == self.shipTo().key()){
                self.shipTo(address);
            }
        });

        self.earliestShipDate = ko.observable(
            function() {
                if(self.selectedShipVia && self.selectedShipVia.effectiveOrderDate) {
                    var adjustedLeadTimeDays = calculateAdjustedLeadTimeDays(self.selectedShipVia.effectiveOrderDate(), self.leadTimeDays());
                    return moment(self.selectedShipVia.effectiveOrderDate()).add(adjustedLeadTimeDays, "days").format("MM/DD/YYYY");
                } else {
                    return moment(new Date(self.ExpectedShipDate)).format("MM/DD/YYYY");
                }
            }()
        );

        self.requestedShipDate = ko.observable(
            function() {
                if(self.selectedShipVia && self.selectedShipVia.requestDate()) {
                    if(self.selectedShipVia.requestDate() >= self.earliestShipDate()) {
                        return moment(new Date(self.selectedShipVia.requestDate())).format("MM/DD/YYYY");
                    } else {
                        return self.earliestShipDate();
                    }
                } else {
                    if(self.selectedShipVia && self.selectedShipVia.effectiveOrderDate) {
                        var adjustedLeadTimeDays = calculateAdjustedLeadTimeDays(self.selectedShipVia.effectiveOrderDate(), self.selectedShipVia.leadTimeDays());
                        return moment(self.selectedShipVia.effectiveOrderDate()).add(adjustedLeadTimeDays - 1, "days").format("MM/DD/YYYY");
                    } else {
                        return moment(new Date(self.ExpectedShipDate)).format("MM/DD/YYYY");
                    }
                }
            }()
        );

        if(self.selectedShipVia && self.selectedShipVia.shippingAccountId){
            self.selectedShipVia.shippingAccountId = ko.validatedObservable(self.selectedShipVia.shippingAccountId() || '').extend({ required: true, message: 'Please select a shipping account.'});

            self.selectedShipVia.shippingAccountId.subscribe(function(shippingAccountKey){
                var postData = {};
                var orderShipViaDetails = [];

                orderShipViaDetails = [
                    {
                        "osvd_key" : self.selectedShipVia.orderShipViaDetailKey(),
                        "sa_id" : shippingAccountKey
                    }
                ]

                postData =
                {
                    "Tables": [
                        {
                            "TableName" : "orders_ship_via_details",
                            "TableKeyField" : "osvd_key",
                            "UserKeyField" : "osvd_key",
                            "UserKeyIsPrimaryKey" : "True",
                            "Data" : orderShipViaDetails
                        }
                    ]
                };

                postLogicJsonAjax(postData, true);
            });
        }else{
            self.selectedShipVia = {};
            self.selectedShipVia.shippingAccountId = ko.observable('');
            self.selectedShipVia.collectShippingAccount = ko.observable(false);
            self.selectedShipVia.name = ko.observable('');
            self.selectedShipVia.description = ko.observable('');
            self.selectedShipVia.total = ko.observable(0);
            self.selectedShipVia.shipViaChoiceID = ko.observable('');
            self.selectedShipVia.sv_ref_id = ko.observable('');
        };

        if(self.shipViaChoices && self.shipViaChoices().length > 0){
            self.selectedShipViaChoice = ko.observable(self.shipViaChoices().find(function(choice) {
                return (choice.shipViaChoiceKey() == self.selectedShipVia.shipViaChoiceID() || choice.shipViaRefID() == self.selectedShipVia.sv_ref_id());
            }) || self.shipViaChoices()[0]);
        }else{
            self.selectedShipViaChoice = ko.observable();
        }

        self.shipToValid = ko.computed(function() {
            return ko.validatedObservable(self.shipTo()).isValid();
        });

        self.saveShipToAddress = function(shipment) {

            var shipmentArray = [];

            var details = shipment.details().map(function(map, index, array) {
                return {
                    orderDetailId : map.orderDetailId(),
                    qtyToShip: map.qtyToShip()
                }
            });

            var shipmentInfo = {
                "Key": shipment.key(),
                "ShipmentId": shipment.key(),
                "ShipTo": {
                    key         : shipment.shipTo().key(),
                    name        : shipment.shipTo().name(),
                    company     : shipment.shipTo().company(),
                    attention   : shipment.shipTo().attention(),
                    address1    : shipment.shipTo().address1(),
                    address2    : shipment.shipTo().address2(),
                    address3    : shipment.shipTo().address3(),
                    address4    : shipment.shipTo().address4(),
                    address5    : shipment.shipTo().address5(),
                    city        : shipment.shipTo().city(),
                    state       : shipment.shipTo().state(),
                    zipCode     : shipment.shipTo().zipCode(),
                    county      : shipment.shipTo().county(),
                    country     : shipment.shipTo().country(),
                    email       : shipment.shipTo().email(),
                    firstName   : shipment.shipTo().firstName(),
                    lastName    : shipment.shipTo().lastName(),
                    phone       : shipment.shipTo().phone(),
                    global      : shipment.shipTo().global(),
                    opt1        : shipment.shipTo().opt1(),
                    opt2        : shipment.shipTo().opt2(),
                    opt3        : shipment.shipTo().opt3(),
                    opt4        : shipment.shipTo().opt4(),
                    opt5        : shipment.shipTo().opt5()
                },
                "Details": details
            };

            shipmentArray.push(shipmentInfo);

            var postOptions = {
                data: "shipmentJson=" + encodeURIComponent(JSON.stringify(shipmentArray)),
                success: function(data) {
                    autoAllocateItems();
                    self.showShippingAddressEdit(false);
                },
                error: function() {
                    alert('error saving shipping address');
                }
            }

            postInfo(postOptions, 'updateShipToAddress', shipment.key());
        };

        self.setShippingToBilling = function(shipment) {
            var newAddress = getShippingFromBilling();
            shipment.shipTo(ko.mapping.fromJS(newAddress,shippingAddressMappingOptions));
            shipment.saveShipToAddress(shipment);
        }

        self.showShippingAddressEdit = ko.computed({
            read: function() {
                if(!self.shipToValid()) {
                    self.shipTo().editing(true);
                }
                return self.shipTo().editing();
            },
            write: function(newValue) {
                if(!newValue && self.shipToValid()) {
                    self.shipTo().editing(false);
                } else {
                    self.shipTo().editing(true);
                }
            }
        });

        // self.EarliestShipDate.extend();
        // self.RequestedShipDate.extend({ min: self.EarliestShipDate });
        // self.RequestedShipDate.extend({ min: { params: self.EarliestShipDate, message: "The earliest ship date available is {0}" } });

        self.detailsMap = ko.observableArray([]);

        self.valid = ko.computed(function() {
            return self.shipToValid() && self.details().length > 0; // && self.RequestedShipDate.isValid();
        });

        self.itemsSelected = ko.computed(function() {
            return self.details().length > 0;
        });

        self.selectItemsComplete = function(shipment) {

            var shipmentArray = [];

            var lineMappings = shipment.detailsMap().map(function(map, index, array) {
                return {
                    OrderDetailId : map.detailLine.orderDetailKey(),
                    QtyToShip: map.qtyToShip()
                }
            });

            var shipmentInfo = {
                Key: shipment.key(),
                ShipmentId: shipment.key(),
                Details: []
            };
            shipmentInfo.Details = lineMappings;
            shipmentArray.push(shipmentInfo);

            var postOptions = {
                data: "shipmentJson=" + encodeURIComponent(JSON.stringify(shipmentArray)),
                error: function() {
                    alert('error saving item list on shipment');
                }
            }

            postInfo(postOptions, 'updateShipToAddress');

            $('#modal_add_prods[tabindex="-1"]:hidden').remove()
            $('#modal_add_prods').modal('hide');
        };

        self.selectedShipViaChoice.subscribe(function(newShipViaChoiceValue) {
            var shipViaChoiceKey = newShipViaChoiceValue.shipViaChoiceKey();
            var shipmentKey = self.key();

            var postOptions = {
                url: orderInfoPostUrl + '?ajax=true&pageaction=setShipmentShipVia&o_key=' + viewModel.orderKey()  + '&shipViaChoiceKey=' + shipViaChoiceKey + '&shipmentKey=' + shipmentKey,
                error: function() {
                    alert('error setting Shipping Method');
                }
            }

            postInfo(postOptions, 'setShipmentShipVia');

        });

        self.comments.subscribe(function(newComment) {

            var shipmentCommentPostData = {};

            var shipmentInfo = [{
                "os_key": self.key(),
                "comments": newComment
            }];

            shipmentCommentPostData =
            {
                "Tables": [
                    {
                        "TableName"           : "order_shipments",
                        "TableKeyField"       : "os_key",
                        "UserKeyField"        : "os_key",
                        "UserKeyIsPrimaryKey" : "True",
                        "Data"                : shipmentInfo
                    }
                ]
            };

            postLogicJsonAjax(shipmentCommentPostData);
        });

        self.requestedShipDate.subscribe(function(newRequestedShipDate) {

            if (!(newRequestedShipDate instanceof Date) || isNaN(newRequestedShipDate.valueOf())) {
                console.log('requestedShipDate is not a date');
                return;
            }

            var postOptions = {
                data: "shipmentId=" + encodeURIComponent(self.key()) + "&requestedShipDate=" + encodeURIComponent(moment(newRequestedShipDate).format('MM/DD/YYYY')),
                error: function() {
                    alert('error setting shipment requested ship date');
                }
            }

            postInfo(postOptions, 'setRequestedShipDate');
        });

        self.details().forEach(function(thisLine) {
            thisLine.hasChildProducts = ko.observable(function() {
                return self.details().filter(function(outerLine) {
                    return outerLine.orderDetail.instance() == thisLine.orderDetail.instance()
                            && thisLine.orderDetail.removeType() != 'instance'; // main product has removeType of instance
                }).length > 1;
            }());

            thisLine.orderDetail.hasParentInCart = ko.observable(function() {
                return self.details().filter(function(outerLine) {
                    return outerLine.orderDetail.orderDetailKey() != thisLine.orderDetail.orderDetailKey()
                            && outerLine.orderDetail.instance() == thisLine.orderDetail.instance()
                            && outerLine.orderDetail.removeType() == 'instance';
                }).length > 0;
            }());
        });

        if(ofConfig.useAccountDefaultShipVia && self.shipViaChoices && self.shipViaChoices().length > 0) {
            var filteredChoice = self.shipViaChoices().find(function(choice) {
                return choice.shipViaChoiceKey() == ofConfig.accountDefaultShipViaKey ||
                choice.shipViaRefID() == ofConfig.accountDefaultShipViaCode
            });

            if(filteredChoice) {
                self.shipViaChoices([ filteredChoice ]);
            }
        }

        self.shipTo.subscribe(function(shipTo) {
            if(viewModel && viewModel.setShipTo) {
                viewModel.setShipTo(self, shipTo);
            }
        })

        runHook('shipmentModelBottom', { self: self, shipmentData: shipmentData });
    }

    var Customer = function(customerData) {
        var self = this;

        ko.mapping.fromJS(customerData, {}, self);

        self.firstName.extend({ required: true });
        self.lastName.extend({ required: true });
        self.email.extend({ email: { message: 'A valid email address is required.', params: true }, required: { message: 'A valid email address is required.', params: true} });
        self.phone.extend({ required: true });
    }

    /*
    var Account = function(accountData) {
        var self = this;

        ko.mapping.fromJS(accountData, {
            create: function(options) {
                return new addressInfo(options.data);
            }
        }, self);

        if(!self.Country()) {
            self.Country('USA');
        }

        self.Company.extend({ required: true });
        self.Address1.extend({ required: true });
        self.Country.extend({ required: true });
        self.City.extend({ required: true });
        if(!self.State()) {
            self.State("");
        }
        self.State.extend({ required: true });
        self.ZipCode.extend({ required: true });
    }
    */

    var Order = function(orderData) {
        addTimer("start order mapping");
        var self = this;

        self.activeAjaxRequestCount = ko.observable(0);

        ko.mapping.fromJS(orderData, {
            detailLines : {
                create: function(options) {
                    options.data.configuratorJson = options.data.configuratorJson ? JSON.parse(options.data.configuratorJson) : {};
                    options.data.parentProductID  = options.data.parentProductID.trim() || '';
                    options.data.editing = false;
                    options.data.uomConversion = !options.data.uomConversion ? 1 : options.data.uomConversion;
                    options.data.minQty = oConfig.convertMinQtyFromStdToSalesUom(options.data.minQty, options.data.uomConversion);
                    options.data.maxQty = oConfig.convertMaxQtyFromStdToSalesUom(options.data.maxQty, options.data.uomConversion);
                    options.data.qtyIncrement = (options.data.uomConversion == 0 || options.data.uomConversion === 1) ? (options.data.qtyIncrement || oConfig.defaultQuantityIncrement) : 0;
                    return ko.mapping.fromJS(options.data);
                }
            },
            orderActions : {
                create: function(options) {
                    if(options.data) {
                        if(!options.data.hasOwnProperty('showPaymentMethods')) {
                            options.data.showPaymentMethods = true;
                        }
                    }
                    return ko.mapping.fromJS(options.data);
                }
            },
            shipments : {
                create: function(options) {
                    return new Shipment(options.data);
                }
            },
            customer : {
                create: function(options) {
                    return options.data ? new Customer(options.data) : {};
                }
            },
            account : {
                create: function(options) {
                    return new addressInfo(options.data);
                }
            },
            paymentMethod : {
                create: function(options) {
                    if(options.data == null || options.data == '') {
                        return ko.mapping.fromJS({
                            paymentMethodKey: "",
                            paymentType: "",
                        });
                    } else {
                        options.data.paymentType = (options.data.paymentType || "").toLowerCase();
                        return ko.mapping.fromJS(options.data);
                    }
                }
            },
            vaultedPayment : {
                create: function(options) {
                    if(options.data == null) {
                        return ko.mapping.fromJS({
                            key: "",
                            vaultKey: "",
                            nickName: "",
                            nameOnCard: "",
                            cardType: "",
                            expMonth: 0,
                            expYear: 0,
                            first2: "0",
                            last4: "0",
                            address1: "",
                            address2: "",
                            city: "",
                            state: "",
                            zip: "",
                            country: "",
                            status: true,
                            merchantId: "",
                            customerId: "",
                            accountId: "",
                            gatewayInfoDirty: false,
                            accountLast4: "",
                            achAccountType: "",
                            bankName: "",
                            routingLast4: "",
                            type: "",
                            customerProfileId: ""
                        });
                    } else {
                        return ko.mapping.fromJS(options.data);
                    }
                }
            },
            roRunDate : {
                update: function(options) {
                    //Quick fix to hide invalid expiration.
                    //ToDo: update to check for other "invalid" dates.
                    date = new Date(options.data);

                    var isValid = date instanceof Date && !isNaN(date);
                    if(moment(date).format("MM/DD/YYYY") == '01/01/0001' || moment(date).format("MM/DD/YYYY") == '01/01/2001'){
                        isValid = false;
                    }

                    if(!isValid) {
                        return moment().format("MM/DD/YYYY");
                    } else {
                        return moment(date).format("MM/DD/YYYY");
                    }
                }
            },
            roRecCase : {
                update: function(options) {
                    if(!options.data) {
                        return 'month';
                    } else {
                        return options.data;
                    }
                }
            },
            roRecQty : {
                update: function(options) {
                    if(!options.data || options.data == 0) {
                        return 1;
                    } else {
                        return options.data;
                    }
                }
            },
            'ignore': ["errorMessages"]
        }, self);

        self.showCredits = ko.observable(false);
        self.allowQtyControls = ko.observable(false);
        self.disablePlaceOrder = ko.observable(false);

        self.detailLines().forEach(function(detailLine) {
            // We can't debounce/trottle an observable
            // so we add a computed to track qty changes.
            // We use this in a subscription to call
            // update cart to update qty change in bulk atc
            // to avoid loading the qty change without posting
            // and updating the entire order model (for perf)
            detailLine.qtyTracker = ko.computed(function() {
                return detailLine.qty();
            }).extend({throttle: 500});
            detailLine.qtyTracker.subscribe(function (newQty) {
                self.postQtyUpdate();
            }, detailLine);
            
            detailLine.workerPriceTracker = ko.computed(function() {
                return detailLine.price();
            }).extend({throttle: 500});
            detailLine.workerPriceTracker.subscribe(function (newQty) {
                self.setWorkerPriceOverride(detailLine);
            }, detailLine);

            detailLine.workerQtyLimitsTracker = ko.computed(function() {
                return {
                    removeType: detailLine.removeType(),
                    minQty: detailLine.minQty(),
                    maxQty: detailLine.maxQty(),
                    qtyIncrement: detailLine.qtyIncrement()
                };
            }).extend({throttle: 500});
            detailLine.workerQtyLimitsTracker.subscribe(function (newValue) {
                self.setWorkerQtyLimits(detailLine);
            });

            detailLine.instanceChildren = self.detailLines().filter(function(item) {
                                            return item.instance() == detailLine.instance() && item != detailLine;
                                        }) || [];

            var mainProduct = self.detailLines().filter(function(item) {
                                            return item.instance() == detailLine.instance() && item.removeType() == 'instance';
                                        })[0] || detailLine;

            var mainProductQty = mainProduct.qty();

            detailLine.instanceUnitPrice = ko.observable(self.detailLines().reduce(function(current, item) {
                if(!ofConfig.showProductDiscount){
                    item.priceBeforeAdjustment(item.price());
                }
                if(item.instance() == detailLine.instance()) {
                    if(item.parentProductID().trim() != '' && item.removeType() != 'instance') {
                        return current += ((item.priceBeforeAdjustment() || item.price()) * item.qty()) / mainProductQty;
                    } else {
                        return current += (item.priceBeforeAdjustment() || item.price());
                    }
                } else {
                    return current;
                }
            },0));

            detailLine.instanceUnitPriceDisplay = ko.computed(function(){
                return utils.formatMoney(detailLine.instanceUnitPrice(), utils.decimalPlacesOnUnitPrices);
            });

            detailLine.instanceExtPrice = ko.observable(self.detailLines().reduce(function(current, item) {
                if(item.instance() == detailLine.instance()) {
                    return current += item.extTotal();
                } else {
                    return current;
                }
            },0));

            detailLine.hasQuantityRestrictions = ko.computed(function () {
                return !!(
                            (detailLine.minQty() && detailLine.minQty() !== oConfig.defaultMinimumQuantity) || 
                            ((!detailLine.uomConversion() || detailLine.uomConversion() == 1) && detailLine.qtyIncrement() && detailLine.qtyIncrement() !== oConfig.defaultMinimumQuantity) || 
                            detailLine.maxQty()
                        );
            });

			detailLine.quantityRestrictionsHtml = ko.computed(function () {
				var lines = [];

				if (detailLine.minQty() && detailLine.minQty() != oConfig.defaultMinimumQuantity)
					lines.push(oConfig.labels.minQtyPopover.replace(/<min_qty>/,detailLine.minQty()));

                if ( (!detailLine.uomConversion() || detailLine.uomConversion() == 1) && detailLine.qtyIncrement() && detailLine.qtyIncrement() != oConfig.defaultQuantityIncrement)
                    lines.push('Qty Increment: ' + detailLine.qtyIncrement());

				if (detailLine.maxQty())
					lines.push('Maximum Qty: ' + detailLine.maxQty());

				return lines.join('<br>');
			});
        });

        self.needToChoosePromos = ko.observable();

        self.paymentMethodSelected = ko.observable(function() {
                return typeof self.paymentMethod.paymentType == 'function';
            }()
        );

        self.termsAgreement = ko.observable(function() {
                return self.legalResponse() == 'agreed';
            }()
        );

        self.detailLines.sort(detailLineInstanceSort);

        self.shipments.sort(function(left, right) {
            return left.position() == right.position() ? 0 : (left.position() < right.position() ? -1 : 1);
        });

        self.processing = ko.observable(false);
        self.populatingShippingAddresses = ko.observable(false);
        self.availableShippingAddresses = ko.observableArray([]);
        self.newCouponCode = ko.observable("");
        self.newGiftCertificate = ko.observable("");
        if(!self.errorMessages) {
            self.errorMessages = ko.observableArray();
        };
        // self.orderPlaced = ko.observable(false);

        self.orderPlaced = ko.computed(function() {
            return self.completed()
        });

        self.Customer = ko.validatedObservable(self.Customer);
        self.Account = ko.validatedObservable(self.Account);

        self.shippingAddresses = shippingAddresses;

        self.addressTypes = function(){
            var addressTypes = [];
            addressTypes.push({
                label: ofConfig.addressBookLabel,
                global: '0'
            });
            if(ofConfig.useLocalPickup){
                addressTypes.push({
                    label: ofConfig.localPickupLabel,
                    global: '1'
                })
            }
            return addressTypes;
        };

        self.defaultShaKey = function() {
            sShaKey = sDefaultShaKey;
            if (sShaKey == ''){
                sShaKey = self.shippingAddresses()[0].sha_key;
            }
            return sShaKey;
        }
        self.billingSectionValid = ko.computed(function () {
            return self.Customer.isValid() && self.Account.isValid();
        });

        self.toggleShowBillingEdit = function() {
            if(self.showBillingEdit()) { // && newCustomer) {
                var postData = new CreateCustomerPost(self);
                self.postBillingUpdate(postData);
            } else {
                self.showBillingEdit(!self.showBillingEdit());
            }
        };

        self.postBillingUpdate = function(postData) {

            var postUrl = orderInfoPostUrl + '?o_key=' + viewModel.orderKey() + '&origin=bill-ship';
            var dataString;
            var keyCount = 0;

            $.each(postData, function(key, value) {
                keyCount += 1;

                if(keyCount > 1) {
                    dataString += "&"
                }
                dataString += key + "=" + encodeURIComponent(value);
            });

            var postOptions = {
                url: postUrl,
                type: "POST",
                data: dataString,
                success: function(data) {
                    self.showBillingEdit(!self.showBillingEdit());
                },
                error: function() {
                    alert('error posting billing update');
                }
            }

            postInfo(postOptions, '', 'checkout_shipping');
        }

        self.editingBilling = ko.observable(false);
        self.showBillingEdit = ko.computed({
            read: function() {
                if(!self.billingSectionValid()) {
                    self.editingBilling(true);
                }
                return self.editingBilling(); // && !self.billingSectionValid();
            },
            write: function(newValue) {
                if(!newValue && self.billingSectionValid()) {
                    self.editingBilling(false);
                } else {
                    self.editingBilling(true);
                }
            }
        });

        self.moveAllItemsToShipment = function(shipment) {
            self.processing(true);

            var shipmentArray = [];

            var lineMappings = self.detailLines().map(function(map, index, array) {
                return {
                    OrderDetailId : map.orderDetailKey(),
                    QtyToShip: map.qty()
                }
            });

            var shipmentInfo = {
                Key: shipment.key(),
                ShipmentId: shipment.key(),
                Details: []
            };

            self.shipments().forEach(function(otherShipment) {
                if(otherShipment.key() != shipment.key()) {
                    shipmentArray.push({
                        key: otherShipment.key(),
                        ShipmentId: otherShipment.key(),
                        Delete: true
                    });
                }
            });

            shipmentInfo.Details = lineMappings;
            shipmentArray.push(shipmentInfo);

            var postOptions = {
                data: "shipmentJson=" + encodeURIComponent(JSON.stringify(shipmentArray)),
                error: function() {
                    alert('error moving all products to shipment');
                },
                complete: function() {
                    self.processing(false);
                }
            }

            postInfo(postOptions, 'updateShipToAddress');

            $('#modal_add_prods[tabindex="-1"]:hidden').remove()
            $('#modal_add_prods').modal('hide');
        };

        self.isOverLineLimit = ko.observable(checkLineLimit(self));

        function checkLineLimit(vm){
            //use the alternate ui (showcart.asp) when over the line limit threshold
            // Don't allow this to happen if using user create shipments since the showcart page doesn't support
            // Split shipping.
            if(oConfig.allowUserCreatedShipments){
                return false;
            }else if (ofConfig.useTooManyLinesRestriction && vm.detailLines().length >= parseInt(ofConfig.tooManyLinesQuantity)){
                return true;
            }else{
                return false;
            }
        }

        self.isMinimumOrderTotalMet = ko.observable(checkOrderTotal(self));
        //moved definition to global scope

        self.shippingComplete = ko.observable(isShippingComplete());

        function isShippingComplete(){
            if ( self.completed() || self.lifecycleStage() == 'cancelled' || self.lifecycleStage() == 'punchout_punched_in') {
                return true;
            }

            // force step 2 if there are no lines on the order.
            if (self.detailLines().length == 0){
                return false;
            }

            // force step 3 if the lines exceed the limit.
            if (self.isOverLineLimit()){
                return true;
            }

            // force step 2 if any shipments are missing an address.
            _.each(self.shipments(),function(shipment){
                if(!shipment.shipTo().key()){
                    return false;
                }
            });

            if(!self.isMinimumOrderTotalMet()){
                utils.setCookie(self.orderKey() + '-step', '2');
            }

            if(utils.getCookie(self.orderKey()+'-step') == undefined){
                if(ofConfig.defaultShippingSectionOpen){
                    utils.setCookie(self.orderKey() + '-step', '2');
                }else{
                    utils.setCookie(self.orderKey() + '-step', '3');
                }
            }

            if (utils.getCookie(self.orderKey() + '-step') == '2'){
                return false;
            }else{
                return true;
            }
        };

        self.shipmentsComplete = ko.observable(false || self.completed());

        self.shipmentsSectionsValid = ko.computed(function() {
            var result = true;
            self.shipments().forEach(function(shipment) {
                if(!shipment.valid() || (shipment.shipTo && shipment.shipTo().editing())) {
                    result = false;
                }
            });
            return result;
        });

        self.toggleShowShippingEdit = function() {
            self.showShippingEdit(!self.showShippingEdit());
            if(!self.showShippingEdit()) {
                scrollToSection('#checkout_summary');
            }
        };

        self.moveItemToNewShipTo = function(item){
            self.itemShipToMap().push({
                orderDetailKey: item.orderDetailKey,
                instance: ko.observable(item.instance()),
                key: ko.observable(item.orderDetailKey),
                qty: ko.observable(item.qtyIncrement() || 1),
                qtyControlledFrom: ko.observable(item.qtyControlledFrom()),
                minQty: ko.observable(item.minQty()),
                qtyIncrement: ko.observable(item.qtyIncrement()),
                getPrice: item.getPrice,
                suPrice: item.suPrice,
                shaKey: ko.observable(item.shaKey()),
                shipTo: ko.observable({}),
                moveItemToNewShipTo: self.moveItemToNewShipTo,
                removeItemToShipToMap: self.removeItemToShipToMap,
                availableAddresses: shippingAddresses,
                parentProductID: item.parentProductID
            });
            item.qty(item.qty() - (item.qtyIncrement() || 1));
            self.itemShipToMap.notifySubscribers();
        };

        self.currentPackage = ko.observable(function(){
            var item = new Object();
            item.shippingAddressId = ko.observable(sDefaultShaKey);
            item.shipTo = ko.observable()
            item.details = ko.observableArray([]);
            return item;
        }());


        self.toggleItemInCurrentPackage = function(item){
            if(!_.contains(self.currentPackage().details(),item)){
                self.currentPackage().details().push(item)
            }else{
                _.pull(self.currentPackage().details(), item)
            }
            self.currentPackage.notifySubscribers();
        };

        self.isInCurrentPackage = function(item){
            return _.contains(self.currentPackage().details(),item);
        };

        self.removeItemToShipToMap = function(item){
            _.pull(self.itemShipToMap(),item);
            self.itemShipToMap.notifySubscribers();
        };

        self.getTotalQty = function(instance){
            var totalQty = 0;
            var mappings = self.itemsOnOrder();
            if(self.useMultiShipEditUI && self.useMultiShipEditUI()){
                mappings = self.itemShipToMap();
            }
            _.each(mappings, function(map){
                if(map.instance() == instance  && map.qtyControlledFrom() != 1){
                    totalQty += parseFloat(map.qty());
                    //exit after the first item is found so the carrier product qty will display.
                    //or not - added && map.qtyControlledFrom() != 1 to account for config products
                    // qtyControlledFrom is "1" when controlled by the parent
                    //return false;
                }
            });
            return totalQty;
        };

        self.getOrderDetailQty = function(instance){
            var totalQty = 0;
            self.itemsOnOrder().forEach(function(map){
                if(map.instance() == instance && map.qtyControlledFrom() != 1){
                    totalQty += parseFloat(map.qty());
                }
            });
            return totalQty;
        };

        self.setItemShipToMapPrice = function(data){
            self.itemShipToMap().forEach(function(map){
                if(map.instance() == data.instance()){
                    if(self.superUserRestrictMinPrice() & data.superUserMinPrice() != null && data.price() < data.superUserMinPrice()) {
                        data.price(undefined);
                        data.price(data.superUserMinPrice());
                    }
                    map.suPrice = data.price();
                }
            });
        };

        self.setWorkerPriceOverride = function(data){
            var orderDetail = [];

            orderDetail.push({
                orderDetailKey: data.orderDetailKey(),
                PriceCalculationType: 'fixed',
                Price: data.price(),
                PriceBeforeAdjustment: data.price()
            });

            var postOptions = {
                data: "orderDetail=" + encodeURIComponent(JSON.stringify(orderDetail)),
                success: function(data) {
                    $('#cartAdvSettings.collapse').toggleClass('in'); 
                    $('#onOrderAdvBtn').toggleClass('active');
                },
                error: function() {
                    alert('Error saving Unit Price');
                }
            }

            postInfo(postOptions, 'setOrderDetailFields');
        }

        self.setWorkerQtyLimits = function(data) {
            var orderDetail = [];

            orderDetail.push({
                orderDetailKey: data.orderDetailKey(),
                MinQty: data.minQty() * data.uomConversion(),
                MaxQty: data.maxQty() * data.uomConversion(),
                QtyIncrement: data.qtyIncrement(),
                RemoveType: data.removeType()
            });

            var postOptions = {
                data: "orderDetail=" + encodeURIComponent(JSON.stringify(orderDetail)),
                success: function(data) {
                    $('#cartAdvSettings.collapse').toggleClass('in');
                    $('#onOrderAdvBtn').toggleClass('active');
                    toggleLoadingWidget(false);
                },
                error: function() {
                    alert('Error saving qty limits');
                }
            }
            toggleLoadingWidget(true);
            postInfo(postOptions, 'setOrderDetailFields');
        }

        self.resetSuperUserPriceOverride = function(data){
            var orderDetail = [];

           orderDetail.push({
                orderDetailKey: data.orderDetailKey(),
                PriceCalculationType: 'std'
            });

            var postOptions = {
                data: "orderDetail=" + encodeURIComponent(JSON.stringify(orderDetail)),
                success: function(data) {
                },
                error: function() {
                    alert('Error resetting Price Override');
                }
            }

            postInfo(postOptions, 'setOrderDetailFields');
        };

        self.resetSuperUserShippingPrice = function(data){
            var postOptions = {
                url: "payment.asp?o_key=" + viewModel.orderKey() + "&svc_key=" + data.shipViaChoiceKey() + '&ajax=true&pageaction=resetShippingPrice&randomnum=' + new Date().getTime() ,
                success: function(data) {
                },
                error: function() {
                    alert('error resetting shipping price');
                }
            }

            postInfo(postOptions, 'resetSuperUserShippingPrice');
        }

        self.returnToPendingOrders = function() {
            window.location = 'quotes.asp?revert=1';
        }

        self.superUserOrderFormMode = ko.observable(ofConfig.superUserOrderFormMode && ofConfig.isSuperUserSession);

        self.getOrderLabel = function(){
            switch(self.recordType()){
                case 'future-order':
                    orderLabel = 'Future Order';
                    break;
                case 'recurring-order':
                    orderLabel = 'Recurring Order';
                    break;
                case 'saved-cart':
                    orderLabel = ofConfig.SavedCartLabel;
                    break;
                default:
                    if(self.superUserOrderFormMode()){
                        orderLabel = ofConfig.SavedCartLabel;
                    }else{
                        orderLabel = 'Order';
                    }
                    break;
            }
            //TODO: once we confirm this logic, return the calculated label instead of the Option.
            //return orderLabel;
            return ofConfig.SavedCartLabel;
        };

        self.superUserSetShipViaPrice = function(data, event){
            var postOptions = {
                url: 'payment.asp' + '?o_key=' + viewModel.orderKey() + '&svc_key=' + data.shipViaChoiceKey() + '&price=' + event.target.value + '&ajax=true&pageaction=setShippingPrice&randomnum=' + new Date().getTime(),
                success: function(data) {
                },
                error: function() {
                    alert('error resetting shipping price');
                }
            };
            postInfo(postOptions, 'setShippingPrice');
        };

        self.postQtyUpdate = function() {

            var postUrl = 'i_i_add_to_cart.asp' + '?ajax=qtyUpdate&modal=1&o_key=' + viewModel.orderKey();
            var dataString = '';
            var unqString = '';
            var keyCount = 0;

            self.detailLines().forEach(function(detailLine) {
                if(dataString.length) dataString += '&';
                dataString += 'qty_' + detailLine.orderDetailKey() + '=' + detailLine.qty();
                if(unqString.length) unqString +=',';
                unqString += detailLine.orderDetailKey();
            });

            dataString += '&unqs=' + unqString;

            var postOptions = {
                url: postUrl,
                type: "POST",
                data: dataString,
                success: function(data) {
                    //
                },
                error: function() {
                    alert('error updating quantity');
                }
            }

            postInfo(postOptions, '','');
        }

        self.hasMultipleShipToAddresses = function() {
            var shaKeys = [];
            var hasMultipleAddresses = false;
            self.shipments().forEach(function(shipment) {
                var shaKey = shipment.shipTo().key();
                if(shaKeys.indexOf(shaKey)) {
                    shaKeys.push(shaKey);
                }
                if(shaKeys.length > 1) {
                    hasMultipleAddresses = true;
                    return;
                }
            });
            return hasMultipleAddresses;
        };

        self.useMultiShipEditUI = ko.observable(self.hasMultipleShipToAddresses());

        self.allocateShipments = function(moveToNextStep){
            var shipmentArray = [];
            var listOfShaKeys = [];
            var newShipmentKey;
            var orderDetail = [];

            toggleLoadingWidget(true);

            if(viewModel.useMultiShipEditUI()){

                //Get a unique list of shipping address keys
                self.itemShipToMap().forEach(function(item){
                    if ( listOfShaKeys.indexOf( item.shipTo().key() ) == -1 ){
                        listOfShaKeys.push(item.shipTo().key());
                    }
                })

                //Loop the sha_keys to create shipments for each
                var items = self.itemShipToMap();
                var newShipmentCount = 0;
                listOfShaKeys.forEach(function(shaKey){
                    //reuse existing shipment keys if they exist.
                    if( newShipmentCount+1 <= self.shipments().length ){
                        newShipmentKey = self.shipments()[newShipmentCount].key();
                    }else{
                        newShipmentKey = utils.createGuid();
                    }

                    var shipmentInfo = {
                        Key: newShipmentKey,
                        ShipmentId: newShipmentKey,
                        ShippingAddressId: shaKey,
                        Details: []
                    };

                    //Loop the product - ship to mapping records to create shipment details
                    var shipments = items;
                    shipments.forEach(function(shipment){
                        if(shipment.shipTo().key() == shaKey){
                            shipmentInfo.Details.push( {
                                OrderDetailId: shipment.orderDetailKey,
                                QtyToShip: shipment.qty()
                            })
                        }
                    });
                    newShipmentCount++;
                    shipmentArray.push(shipmentInfo);
                });
            }else{
                listOfShaKeys.push(self.shipments()[0].shipTo().key());

                //Single Shipping - reuse the first shipment key
                newShipmentKey = self.shipments()[0].key();
                var shipmentInfo = {
                    Key: newShipmentKey,
                    ShipmentId: newShipmentKey,
                    ShippingAddressId: self.shipments()[0].shipTo().key(),
                    Details: []
                };

                //JB - loop the items on order since we're in single shipment mode.
                viewModel.itemsOnOrder().forEach(function(item){
                    shipmentInfo.Details.push( {
                        OrderDetailId:  item.orderDetailKey(),
                        QtyToShip: item.qty()
                    });
                });
                shipmentArray.push(shipmentInfo);
            }

            //Remove any orphaned shipments from the original data if the user is created shipments
            if(listOfShaKeys.length < self.shipments().length && oConfig.allowUserCreatedShipments){
                var count = 0;
                _.eachRight(self.shipments(), function(oldShipment){
                    if( count + 1 <= (self.shipments().length - listOfShaKeys.length ) ){
                        shipmentArray.push({
                            Key: oldShipment.key(),
                            Delete: true
                        });
                        count++;
                    }
                });
            }

            //Order Detail Field Updates
            var details = self.itemShipToMap()
            _.each(details, function(item){
                var superUserPrice = parseFloat(item.suPrice);
                var getprice = parseFloat(item.getPrice());

                if( isNaN(superUserPrice)){
                    superUserPrice = getprice;
                }

                if(viewModel.superUserOrderFormMode()) {
                    if (getprice != superUserPrice) {
                        //build OD object
                        if(!_.find(orderDetail, {orderDetailKey: item.orderDetailKey}) ){
                            var obj = {
                                orderDetailKey: item.orderDetailKey,
                                Price: superUserPrice,
                                PriceDisplay: getprice,
                                PriceCalculationType: 'fixed'
                            };

                            if(ofConfig.useTradeAdjustmentOnSession){
                                obj.PriceBeforeAdjustment = superUserPrice;
                            }
                            orderDetail.push(obj);
                        }
                    }

                    var detail = _.find(orderDetail, {orderDetailKey: item.orderDetailKey});

                    if(!detail) {
                        detail = {
                            orderDetailKey: item.orderDetailKey
                        }
                        orderDetail.push(detail);
                    }

                    // var newValues = _.find(viewModel.itemsOnOrder(), {orderDetailKey: item.orderDetailKey});

                    var itemOnOrder = viewModel.itemsOnOrder().reduce(function(returnItem, currentItem) {
                        if(currentItem.orderDetailKey() == item.orderDetailKey) {
                            returnItem = currentItem;
                        }
                        return returnItem;
                    })

                    detail.minQty        = parseFloat(itemOnOrder.minQty ? itemOnOrder.minQty() : 0);
                    detail.maxQty        = parseFloat(itemOnOrder.maxQty ? itemOnOrder.maxQty() : 0);
                    detail.qtyIncrement  = parseFloat(itemOnOrder.qtyIncrement ? itemOnOrder.qtyIncrement() : oConfig.defaultQuantityIncrement);

                    // TODO: Need to track original removeType value - checking has children for now
                    var defaultRemoveType = itemOnOrder.hasChildProducts() ? 'instance' : 'OD_Key';

                    detail.removeType   = itemOnOrder.removeType ? itemOnOrder.removeType() : defaultRemoveType;

                    runHook('allocateShipmentsOrderDetailUpdate', { itemOnOrder: itemOnOrder, detailLine: detail });
                }
                toggleLoadingWidget(false);
            });

            var postOptions = {
                data: "shipmentJson=" + encodeURIComponent(JSON.stringify(shipmentArray)) + "&orderDetail=" + encodeURIComponent(JSON.stringify(orderDetail)),
                success: function(){
                    if(ofConfig.bUsePromotions){
                        jQuery.ajax({
                            url: 'payment.asp' + '?o_key=' + viewModel.orderKey() + '&ajax=true&pageaction=checkPromos&randomnum=' + new Date().getTime()
                            , cache: false
                            , type: 'GET'
                            , success: function(data,status,request){
                                if(JSON.parse(data)){
                                    viewModel.processing(true);

                                    //redirect to reward selection page.
                                    //This is not opening in a modal for responsive reasons.
                                    var nextPage = encodeURIComponent('payment.asp?o_key=' + viewModel.orderKey() + (ofConfig.isModal ? '&modal=1' : ''));
                                    document.location = 'promo_reward_selection.asp?quickadd=1&next_page=' + nextPage + '&o_key=' + viewModel.orderKey() + (ofConfig.isModal ? '&modal=1' : '');
                                }
                            }
                            , error: function(data) {
                                console.log('Error checking Promos');
                            }
                            , complete: function(data) {
                            }
                        });
                    }
                },
                error: function() {
                    self.addShipment();
                    autoAllocateItems();
                    alert('error saving product list on shipment');
                }
            }

            postInfo(postOptions, 'updateShipToAddress');
            if(moveToNextStep === undefined) {
                moveToNextStep=true;
            }
            if(self.isOverLineLimit()){
                moveToNextStep = true;
            }
            console.log(self);
            self.shippingComplete(moveToNextStep);
            self.shippingComplete.notifySubscribers();
            if(moveToNextStep) {
                utils.setCookie(self.orderKey() + '-step' , '3');
            }
        };


        self.showShippingEdit = ko.observable(!self.completed());
        self.currentShipment = ko.observable();

        self.toggleMultiShip = function (data, event) {
            event.preventDefault();
            self.useMultiShipEditUI(!self.useMultiShipEditUI());

            self.shippingComplete.notifySubscribers();
        };

        self.toggleOrderPlaced = function (data, event) {
            event.preventDefault();

            var postData = {};

            if(viewModel.paymentMethod.paymentType() === 'cc') {

                var defaultCcnChecked = $('#ccpm_default_ccn_id_selection').is(':checked');

                if(defaultCcnChecked) {
                    postData.setDefaultCcn = 1
                }

                postData.ccn_key =  $('#ccpm_ccn_id').val();
                postData.address =  $('#ccpm_address_input').val();
                postData.city =     $('#ccpm_city_input').val();
                postData.country =  $('#ccpm_country_input').val();
                postData.state =    $('#ccpm_state_dropdown').prop('disabled') ? $('#ccpm_state_text').val() : $('#ccpm_state_dropdown').val();
                postData.zip =      $('#ccpm_zip_input').val();

                //oSavedPaymentMethodsCreditCard.saveBillingAddress();
            }

            var postOptions = {
                data: postData,
                success: function(data){
                    viewModel.updateOrderAccess();
                    var response = JSON.parse(data);
                    if(Object.keys(response[0].Errors).length == 0) {
                        //document.location = 'payment.asp?o_key=' + viewModel.orderKey() + '#checkout_confirmation';
                        scrollToSection('#checkout');
                    }else{
                        scrollToSection('#checkout');
                    }
                },
                error: function(data) {
                    alert('error placing order');
                }
            }


            if ($.active > 0) {
                viewModel.processing(true);
                $( document ).one("ajaxStop", function(){
                    postInfo(postOptions, 'placeOrder');
                });
            }
            else {
                postInfo(postOptions, 'placeOrder');
            }

        };

        self.showItemSelector = function(shipment) {
            shipment.detailsMap([]);
            self.detailLines().forEach(function(detailLine, detailIndex, detailArray) {
                shipment.detailsMap().push(new detailMap(detailLine, shipment, self));
            });

            self.currentShipment(shipment);
            $('#modal_add_prods').modal('show');
        };

        self.addShipment = function() {

            var newShipment = getNewShipment();

            var successFunction;

            if(self.shipments().length === 0) {
                /* EJ - 2016-11-15
                   I feel dirty about this, but it's a quick workaround for an odd issue
                   happening on first load of the payment page for a new order, before the first
                   shipment is added.  The issue presents as a "corrupt" countries array (missing
                   states for first country) and validation issues on Account form.

                   The only difference in code on client (verified with Arraxis Merge) is the orderPayload.
                   The first page load doesn't have shipments or paymethods (empty array and null respectively),
                   whereas the second and all subsequent page loads do.

                   The issue goes away once the first shipment is added, so I'm forcing a reload
                   the first time a shipment is added, which was already happening already
                   on first page load if there were no shipments on the order.

                   The first shipment being added needs to be moved to the ordering object eventually,
                   which will negate the need for this code anyway, so I didn't spend more hours trying
                   to find the root cause.
                */

                // so spinner stays active until location.reload happens
                self.activeAjaxRequestCount(self.activeAjaxRequestCount() + 1);

                successFunction = function() {
                    location.reload();
                }
            } else {
                successFunction = autoAllocateItems;
            }


            var shipmentArray = [];

            var shipmentInfo = {
                Key: newShipment.key(),
                ShipmentId: newShipment.key(),
                Details: []
            };

            shipmentArray.push(shipmentInfo);


            var postOptions = {
                data: "shipmentJson=" + encodeURIComponent(JSON.stringify(shipmentArray)),
                error: function() {
                    alert('error setting Shipping Method');
                },
                success: successFunction
            }

            postInfo(postOptions, 'updateShipToAddress', newShipment.key(), self);

        };

        self.deleteShipment = function(shipmentToDelete, e, index) {

            var shipmentArray = [];

            var shipmentInfo = {
                Key: shipmentToDelete.key(),
                Delete: true
            };

            shipmentArray.push(shipmentInfo);

            var postOptions = {
                data: "shipmentJson=" + encodeURIComponent(JSON.stringify(shipmentArray)),
                error: function() {
                    alert('error moving all items to shipment');
                },
                success: autoAllocateItems
            }

            postInfo(postOptions, 'updateShipToAddress', 'checkout_shipping');
        };
        self.setShipTo = function(shipment, shipToAddress) {

            var sha_id = shipToAddress.key();
            var shipmentId = shipment.key();

            var postOptions = {
                data: "shipmentId=" + shipmentId + "&sha_id=" + sha_id,
                success: function(data) {
                    autoAllocateItems();
                    self.shipmentsSectionsValid.notifySubscribers();
                },
                error: function() {
                    alert('error saving shipping address');
                }
            }

            postInfo(postOptions, 'setSha');

            $('#modal_addressbook[tabindex="-1"]:hidden').remove()
            $('#modal_addressbook').modal('hide');
        };

        self.showNewAddressForm = function(shipment) {
            shipment.shipTo(new addressInfo(getNewAddress()));

            $('#modal_addressbook[tabindex="-1"]:hidden').remove()
            $('#modal_addressbook').modal('hide');
        };

        self.showAddAddress = function(shipment){

            $('#modal_add_shipto').modal('show');
        };

        self.shipments().forEach(function(shipment) {

            self.detailLines().forEach(function(detailLine, detailIndex, detailArray) {
                shipment.detailsMap().push(new detailMap(detailLine, shipment, self));
            });
        });

        self.updatePoRequired = function() {
            if(self.paymentMethod && self.paymentMethod.paymentMethodKey() != ''){
            var isReallyRequired = self.paymentMethod.poRequired() && self.paymentMethod.collectPO();
            self.paymentMethod.poRequired(isReallyRequired);
        }
        }

        self.updatePoRequired();

        self.poNumber.extend({
            required: {
                onlyIf: function() {
                    return viewModel && viewModel.paymentMethod.poRequired()
                }
            }
        });

        self.poNumber.subscribe(function(poNumber) {
            postOrderHeaderField("ponumber", poNumber);
        });

        self.comments.subscribe(function(newComments) {
            postOrderHeaderField("comment", newComments);
        });

        self.nickname.subscribe(function(nickname) {
            postOrderHeaderField("nickname", nickname);
        });

        self.orderCcEmail.subscribe(function(orderCcEmail) {
            postOrderHeaderField("order_cc_email", orderCcEmail);
        });

        self.isValidDate = function(date){
            //Quick fix to hide invalid expiration.
            //ToDo: update to check for other "invalid" dates.

            // Parse the date parts to integers
            var parts = date.split("-");
            var day = parseInt(parts[2], 10);
            var month = parseInt(parts[1], 10);
            var year = parseInt(parts[0], 10);

            // Check the ranges of month and year
            if(year < 1000 || year > 3000 || month == 0 || month > 12)
                return false;

            var monthLength = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

            // Adjust for leap years
            if(year % 400 == 0 || (year % 100 != 0 && year % 4 == 0))
                monthLength[1] = 29;

            // Check the range of the day
            return day > 0 && day <= monthLength[month - 1];
        };

        self.getMinimumRecurringOrderDate = function(){
            var today = moment();
            var minimumDate = moment(today).add(1, 'days');
            return moment(minimumDate).format("MM/DD/YYYY");
        }

        self.roRunDate.subscribe(function(roRunDate) {
            postOrderHeaderField("ro_rundate", roRunDate);
        });

        self.roRecCase.subscribe(function(roRecCase) {
            postOrderHeaderField("ro_rec_case", roRecCase);
        });

        self.roRecQty.subscribe(function(roRecQty) {
            postOrderHeaderField("ro_rec_qty", roRecQty);
        });

        self.updateRecurringType = function(data, event){
            newValue = event.target.value;
            sOrderDetailKey = data.orderDetailKey();
            switch(newValue){
                case 'fixed':
                    postOrderDetailFields(sOrderDetailKey, ['ro_recurring_type','ro_skip_next_runs'], ['fixed',0]);
                    break;
                case 'recurring':
                    postOrderDetailFields(sOrderDetailKey, ['ro_recurring_type','ro_skip_next_runs'], ['recurring',0]);
                    break;
                case 'skipnext':
                    postOrderDetailFields(sOrderDetailKey, ['ro_recurring_type','ro_skip_next_runs'], ['recurring',1]);
                    break;
                case 'remove':
                    viewModel.removeProduct(data);
                    break;
            }

        };

        self.getRecurringLabel = function(data){
            var sMessage = '';
            var sAction = data.orderDetail.roRecurringType();
            var sSkipRuns = data.orderDetail.roSkipNextRuns();
            switch(sAction){
                case 'recurring':
                case '':
                    if(sSkipRuns == 0){
                        //recurring
                        sMessage = ofConfig.roRecurringItemLabel;
                    }else{
                        //skip.
                        sMessage = ofConfig.roSkippedItemLabel;
                    }
                    break;
                case 'fixed':
                        sMessage = ofConfig.roOnetimeItemLabel;
                    break;
                case 'disabled':
                        sMessage = ofConfig.roDisabledItemLabel;
                    break;
            }
            return sMessage;
        };
        self.customerComment = ko.observable('');

        self.noteBook = ko.observable();

        self.populateNoteBook = function(){
            var noteBook = {};
            var count = 0;

            noteBook.notes =  ko.observableArray([]);

            if(noteBookPayload.length > 0 ){

                noteBook.nbKey = noteBookPayload[0].nb_key;
                noteBook.id    = noteBookPayload[0].id;
                noteBook.refId = noteBookPayload[0].ref_id;

                _.each(noteBookPayload,function(note){
                    if(note.internal_notes || note.external_notes) {
                        count++;
                        item = {};

                        item.internalNotes    = note.internal_notes;
                        item.externalNotes    = note.external_notes;
                        item.postedByName     = note.c_f_nm + ' ' + note.c_l_nm;
                        item.postedByUsername = note.username;
                        item.createDate       = note.create_date;
                        noteBook.notes.push(ko.observable(item));
                    }
                });
            }

            self.noteBook(noteBook);
        }();

        self.buildOrderHeaderFields = function(aFields) {
            //Available order header fields that can be POSTed using this function
            var orderHeaderFields = {
                "UseMultipleShipments": viewModel.useMultipleShipments(),
                "Nickname" : viewModel.nickname(),
                "QuoteExpirationDate": viewModel.expirationDate()
            };

            var orderHeaderFieldsJson = {};

            //Add only the field(s) that are passed in aFields.
            _.forEach(aFields, function(sField) {
                orderHeaderFieldsJson[sField] = orderHeaderFields[sField];
            });

            self.setOrderHeaderFields(orderHeaderFieldsJson);

        };

        self.setOrderHeaderFields = function(orderHeaderFieldsJson) {

            var postOptions = {
                data: "orderHeaderFields=" + encodeURIComponent(JSON.stringify(orderHeaderFieldsJson)),
                error: function(data) {
                    alert('error setting Order Header fields');
                }
            };

            postInfo(postOptions, 'setOrderHeaderFields');
        };

        if(self.shipments().length === 0) {
            //self.addShipment();
        }

        self.setPaymentMethod = function(paymentMethod) {
            if(paymentMethod.refID() != viewModel.paymentMethod.refID()){
                var postOptions = {
                    data: "pm_id=" + paymentMethod.paymentMethodKey(),
                    error: function(data) {
                        alert('error setting pm');
                    },
                    success: function() {
                        $('#ccpm_container').removeClass('hide');
                        viewModel.updatePoRequired();
                    }
                }

                postInfo(postOptions, 'setPmId');
            }
        }

        self.ccn_id.subscribe(function(newCCN) {
            if(newCCN !== '') {
                var payload = 'ccn_id=' + newCCN;
                var postOptions = {
                    data: payload,
                    error: function(data) {
                        alert('error setting CCN');
                    }
                }

                postInfo(postOptions, 'setCCN');
            }
        });

        self.SetCouponCode = function(couponCode) {
            var payload = 'couponcode=' + encodeURIComponent(couponCode);
            var postOptions = {
                data: payload,
                error: function(data) {
                    alert('error applying coupon');
                }
            }

            postInfo(postOptions, 'setCoupon');
        }

        self.SetGiftCertificate = function(giftCertCode) {
            var payload = 'giftCertCode=' + encodeURIComponent(giftCertCode);
            var postOptions = {
                data: payload,
                error: function(data) {
                    alert('error applying gift certificate');
                }
            }

            postInfo(postOptions, 'setGiftCertCode');
        };

        self.allItemsAllocated = ko.computed(function() {
            var totalItemCount = self.detailLines().reduce(function(previous, current) {
                return previous + current.qty();
            }, 0);

            var allocatedItemCount = 0;
            self.shipments().forEach(function(shipment) {
                allocatedItemCount += shipment.details().reduce(function(previous, current) {
                    return previous + current.qtyToShip();
                }, 0);
            });

            return totalItemCount === allocatedItemCount;
        });

        self.itemsOnOrder = ko.computed(function() {
            var items = [];
            self.detailLines().forEach(function(detailLine) {
                detailLine.hasChildProducts = ko.observable(function() {
                    return self.detailLines().filter(function(outerLine) {
                        return outerLine.instance() == detailLine.instance()
                                && detailLine.removeType() != 'instance'
                    }).length > 1
                }());

                detailLine.hasParentInCart = ko.observable(function() {
                    return self.detailLines().filter(function(outerLine) {
                        return outerLine.orderDetailKey() != detailLine.orderDetailKey()
                                && outerLine.instance() == detailLine.instance()
                                && detailLine.removeType() == 'instance'
                    }).length > 0
                }());

                detailLine.instanceChildren = self.detailLines().filter(function(item) {
                    return item.instance() == detailLine.instance() && item.removeType() != 'instance' && item.parentProductID().trim() != '' && item != detailLine;
                }) || [];

                var mainProduct = self.detailLines().filter(function(item) {
                                                return item.instance() == detailLine.instance() && item.removeType() == 'instance';
                                            })[0] || detailLine;

                var mainProductQty = mainProduct.qty();

                detailLine.superUserMinPrice = ko.observable(detailLine.commodity[ofConfig.superUserMinPriceProperty]() || 0);

                detailLine.instanceUnitPrice = ko.observable(self.detailLines().reduce(function(current, item) {
                    if(!ofConfig.showProductDiscount){
                        item.priceBeforeAdjustment(item.price());
                    }
                    if(item.instance() == detailLine.instance()) {
                        if(item.parentProductID().trim() != '' && item.removeType() != 'instance') {
                            return current += ((item.priceBeforeAdjustment() || item.price()) * item.qty()) / mainProductQty;
                        } else {
                            return current += (item.priceBeforeAdjustment() || item.price());
                        }
                    } else {
                        return current;
                    }
                },0));

                detailLine.instanceUnitPriceDisplay = ko.computed(function(){
                    return utils.formatMoney(detailLine.instanceUnitPrice(), utils.decimalPlacesOnUnitPrices);
                });

                detailLine.instanceExtPrice = ko.observable(self.detailLines().reduce(function(current, item) {
                    if(item.instance() == detailLine.instance()) {
                        return current += item.extTotal();
                    } else {
                        return current;
                    }
                },0));

                detailLine.allocatedQty = ko.observable(function() {
                    var runningTotal = 0;
                    self.shipments().forEach(function(shipment) {
                        shipment.details().forEach(function(shipDetail) {
                            if(shipDetail.orderDetailId() === detailLine.orderDetailKey()) {
                                runningTotal += shipDetail.qtyToShip();
                            }
                        });
                    });
                    return runningTotal;
                }());

                detailLine.minGrossMarginPercent = ko.computed(function() {
                    if(self.superUserRestrictMinPrice() & detailLine.superUserMinPrice() != null)
                    {
                        return ( (detailLine.superUserMinPrice() - detailLine.commodity.cost()) / detailLine.superUserMinPrice() * 100 ).toFixed();
                    } else {
                        return undefined;
                    }
                });

                detailLine.grossMarginPercent = ko.computed({
                    write: function(newVal) {
                        var localGrossMarginPercent = parseInt(detailLine.minGrossMarginPercent() || 0);
                        var val = parseInt(newVal);
                        if(val < localGrossMarginPercent) {
                            val = localGrossMarginPercent;
                        }
                        var marginPercent = val / 100;
                        var markup = marginPercent / (1 - marginPercent);
                        var newPrice = (detailLine.commodity.cost() * (1 + markup)).toFixed(2);
                        detailLine.price(undefined);
                        detailLine.price(newPrice);
                        detailLine.priceCalculationType('fixed');
                    },
                    read: function() {
                        var marginPercent = (detailLine.price() - detailLine.commodity.cost()) / detailLine.price();
                        return (marginPercent * 100).toFixed(0);
                    },
                    deferEvaluation: true
                });

                detailLine.minMarkupPercent = ko.computed(function() {
                    if(self.superUserRestrictMinPrice() & detailLine.superUserMinPrice() != null)
                    {
                        return ( detailLine.superUserMinPrice() / detailLine.commodity.cost() - 1 ).toFixed();
                    } else {
                        return undefined;
                    }
                });

                detailLine.markupPercent = ko.computed({
                    write: function(newVal) {
                        var minMarkupPercent = parseInt(detailLine.minMarkupPercent || 0);
                        var val = parseInt(newVal);
                        if(val < minMarkupPercent) {
                            val = minMarkupPercent;
                        }
                        var markup = val / 100;
                        var newPrice = (detailLine.commodity.cost() * (1 + markup)).toFixed(2);
                        detailLine.price(undefined);
                        detailLine.price(newPrice);
                        detailLine.priceCalculationType('fixed');
                    },
                    read: function() {
                        var markup = (detailLine.price() - detailLine.commodity.cost()) / detailLine.commodity.cost();
                        return (markup * 100).toFixed(0);
                    },
                    deferEvaluation: true
                });

                detailLine.maxDiscountPercent = ko.computed(function() {
                    if(self.superUserRestrictMinPrice() & detailLine.superUserMinPrice() != null)
                    {
                        return (100 * (1 - (detailLine.superUserMinPrice() / detailLine.commodity.retailPrice()))).toFixed();
                    } else {
                        return undefined;
                    }
                });

                detailLine.discountPercent = ko.computed({
                    write: function(newVal) {
                        var maxDiscountPercent = parseInt(detailLine.maxDiscountPercent() || 100);
                        var val = parseInt(newVal);
                        if(val > maxDiscountPercent) {
                            val = maxDiscountPercent;
                        }
                        var discount = val / 100;
                        var newPrice = (detailLine.commodity.retailPrice() * (1 - discount)).toFixed(2);
                        detailLine.price(undefined);
                        detailLine.price(newPrice);
                        detailLine.priceCalculationType('fixed');
                    },
                    read: function() {
                        var discount = 1 - detailLine.price() / detailLine.commodity.retailPrice();
                        return (discount * 100).toFixed(0);
                    },
                    deferEvaluation: true
                });

                detailLine.minGrossMarginAmount = ko.computed(function() {
                    if(self.superUserRestrictMinPrice() & detailLine.superUserMinPrice() != null)
                    {
                        return detailLine.superUserMinPrice() - detailLine.commodity.cost();
                    } else {
                        return undefined;
                    }
                });

                detailLine.grossMarginAmount = ko.computed({
                    write: function(newVal) {
                        var localMminGrossMarginAmount = parseInt(detailLine.minGrossMarginAmount() || 0);
                        var val = parseInt(newVal);
                        if(val < localMminGrossMarginAmount) {
                            val = localMminGrossMarginAmount;
                        }
                        var grossMargin = parseFloat(val);
                        var newPrice = (detailLine.commodity.cost() + grossMargin).toFixed(2);
                        detailLine.price(undefined);
                        detailLine.price(newPrice);
                        detailLine.priceCalculationType('fixed');
                    },
                    read: function() {
                        return (detailLine.price() - detailLine.commodity.cost()).toFixed(2);
                    },
                    deferEvaluation: true
                });

                detailLine.allowDeletes = ko.computed({
                    write: function(val) {
                        // TODO: need to store previous value for removeType (od_key, hide, instance) - checking child prods for now
                        if(val) {
                            detailLine.hasChildProducts() ? detailLine.removeType('instance') : detailLine.removeType('OD_Key');
                        } else {
                            detailLine.removeType('hide');
                        }
                    },
                    read: function() {
                        return !(detailLine.removeType() == 'hide')
                    }
                });

                detailLine.suMinQty = ko.computed({
                    write: function(val) {
                        val = parseFloat(val);
                        if(isNaN(val)) {
                            val = detailLine.minQty();
                        }
                        if(!detailLine.maxQty() == 0 && val > detailLine.maxQty()) {
                            detailLine.minQty(detailLine.maxQty());
                        } else {
                            detailLine.minQty(val);
                        }
                        detailLine.suMaxQty(detailLine.maxQty());
                        detailLine.suMinQty.notifySubscribers();
                    },
                    read: function() {
                        return detailLine.minQty();
                    }
                });

                detailLine.suMaxQty = ko.computed({
                    write: function(val) {
                        var originalValue = detailLine.maxQty();
                        var newValue = parseFloat(val);;

                        if(isNaN(newValue)) {
                            newValue = originalValue;
                        }
                        if(newValue > 0) {
                            if(detailLine.minQty() > 0 && newValue < detailLine.minQty()) {
                                newValue = detailLine.minQty();
                            } else {
                                var diff = ( (newValue-detailLine.minQty()) * 1000 ) % ( (detailLine.qtyIncrement() || 1) * 1000 ) / 1000;
                                newValue = newValue - diff;
                                newValue = detailLine.minQty() > 0 && newValue < detailLine.minQty() ? detailLine.minQty() : newValue;
                            }
                        } else {
                            newValue = 0;
                        }

                        // reset maxQty to workaround KO bug that causes
                        // underlying value to be changed, but the UI to
                        // not update if the end value is the same as the
                        // original value
                        /* - not a bug, need to notifySubscribers UI was broken
                        detailLine.maxQty(99);
                        detailLine.maxQty(0);
                        */

                        // set valid newValue
                        detailLine.maxQty(newValue);
                    },
                    read: function() {
                        return detailLine.maxQty();
                    }
                });

                detailLine.suQtyIncrement = ko.computed({
                    write: function(val) {
                        val = parseFloat(val);
                        if(isNaN(val)) {
                            val = detailLine.qtyIncrement();
                        }
                        detailLine.qtyIncrement(val);
                        detailLine.suMaxQty(detailLine.maxQty());
                        detailLine.suQtyIncrement.notifySubscribers();
                    },
                    read: function() {
                        return detailLine.qtyIncrement();
                    }
                });

                detailLine.selectedRoAction = ko.computed(function(){
                    var sAction = detailLine.roRecurringType();
                    var skipNextRuns = detailLine.roSkipNextRuns();
                    if( sAction == '' || sAction == 'recurring'){
                        if( skipNextRuns == 0){
                            sAction = 'recurring';
                        }else{
                            sAction = 'skipnext';
                        }
                    }
                    return sAction;
                });

                detailLine.hasQuantityRestrictions = ko.computed(function () {
                    return !!(
                                (detailLine.minQty() && detailLine.minQty() !== oConfig.defaultMinimumQuantity) || 
                                ((!detailLine.uomConversion() || detailLine.uomConversion() == 1) && detailLine.qtyIncrement() && detailLine.qtyIncrement() !== oConfig.defaultMinimumQuantity) || 
                                detailLine.maxQty()
                            );
                });

                detailLine.quantityRestrictionsHtml = ko.computed(function () {
                    var lines = [];

                    if (detailLine.minQty() && detailLine.minQty() != oConfig.defaultMinimumQuantity)
                        lines.push(oConfig.labels.minQtyPopover.replace(/<min_qty>/,detailLine.minQty()));

                    if ( (!detailLine.uomConversion() || detailLine.uomConversion() == 1) && detailLine.qtyIncrement() && detailLine.qtyIncrement() != oConfig.defaultQuantityIncrement)
                        lines.push('Qty Increment: ' + detailLine.qtyIncrement());

                    if (detailLine.maxQty())
                        lines.push('Maximum Qty: ' + detailLine.maxQty());

                    return lines.join('<br>');
                });

                items.push(detailLine);
            });

            runHook('ItemsOnOrderArrayBeforeReturn', { detailLines: items });

            return items;
        });
        
        self.detailLines.subscribe(function(){
            self.isMinimumOrderTotalMet(checkOrderTotal(self));
        });
        self.itemsOnOrder.subscribe(function(){
            self.isMinimumOrderTotalMet(checkOrderTotal(self));
        });

        self.itemShipToMap = ko.computed(function(){
            var items = [];
            self.shipments().forEach(function(shipment) {
                oShipTo = shipment.shipTo();

                shipment.details().forEach(function(shipDetail) {
                    var item                   = new Object();
                    item.shaKey                = ko.observable(oShipTo.key());
                    item.key                   = ko.observable(shipDetail.orderDetailId());
                    item.orderDetailKey        = shipDetail.orderDetailId();
                    item.instance              = ko.observable(shipDetail.orderDetail.instance());
                    item.qty                   = ko.observable(shipDetail.qtyToShip());
                    item.qtyControlledFrom     = ko.observable(shipDetail.orderDetail.qtyControlledFrom());
                    item.qtyIncrement          = ko.observable(shipDetail.orderDetail.qtyIncrement());
                    item.getPrice              = shipDetail.orderDetail.price,
                    item.suPrice               = shipDetail.orderDetail.price,
                    item.minQty                = ko.observable(shipDetail.orderDetail.minQty());
                    item.moveItemToNewShipTo   = self.moveItemToNewShipTo;
                    item.removeItemToShipToMap = self.removeItemToShipToMap;
                    item.availableAddresses    = shippingAddresses;
                    item.shipTo                = ko.observable({});
                    item.parentProductID       = shipDetail.orderDetail.parentProductID();
                    item.availableAddresses().forEach(function(address,index){
                        if( address.key() == oShipTo.key()){
                            item.shipTo(address);
                        }
                    });

                    if(shipDetail.orderDetail.instanceChildren.length > 0){
                        item.shipTo.subscribe(function(newValue) {
                            items.forEach(function(map) {
                                if(map.instance() == item.instance() && map.shipTo().key() != item.shipTo().key()) {
                                    map.shipTo(item.shipTo());
                                }
                            })
                        });
                    }

                    items.push(item);
                })
            });

            return items;
        });
        
        _.each(self.itemShipToMap(), function(item){
            item.qty.subscribe(function () { 
                self.isMinimumOrderTotalMet(checkOrderTotal(self));
            });   
        });

        self.selectedOrderActionRefId = ko.observable(
            function(){
                if(self.orderActions().length > 0 ){
                    return self.orderActions()[0].refID();
                }else{
                    return '';
                }
            }()
        );

        self.selectedOrderAction = ko.computed(
            function(){
                var action = _.find(self.orderActions(), function(action){
                    if( action.refID() == self.selectedOrderActionRefId() ){
                        return action;
                    }
                });
                if(action && action.refID()){
                    return action;
                }else if(self.orderActions().length > 0 ){
                    return self.orderActions()[0];
                }else{
                    return {
                        showPaymentMethods: ko.observable(true),
                        showRecurringOrderSettings: ko.observable(false)
                    };
                }
            }
        );

        self.showOrderActionButton = function(data, parent) {
            //override this function for custom logic to show custom buttons
            var sSelectedRefId = self.selectedOrderAction() && self.selectedOrderActionRefId();
            var bShow = data.showButton() && (data.refID() == sSelectedRefId || parent.orderActions().length == 1);

            return bShow;
        };

        self.getAddressFinderId = function(id, data, parent){
            selector = id + '_' + parent.orderDetailKey + '_' + ($.isEmptyObject(data) ? 0 : data.key());
            return selector;
        };

       self.selectedLifecycleStage = ko.computed(function(){
            var stage = _.find(self.lifecycleStages(), function(stage){
                if( stage.refID() == self.lifecycleStage() ){
                    return stage;
                }
            });
            if(stage){
                return stage;
            }else{
                return {
                    alwaysShowMessage: ko.observable(false),
                    confirmationMessage: ko.observable('')
                };
            }
       });

        if(self.lifecycleStage() == 'cancelled' || self.lifecycleStage() == 'punchout_punched_in'){
            self.completed(true);
        }

        self.showConfirmationMessage = ko.observable(function(){
            return (self.completed() || self.lifecycleStage() == 'pending' || self.lifecycleStage() == 'cancelled' || self.selectedLifecycleStage().alwaysShowMessage());
        }())

        self.allowAccountEdits         = ko.observable(self.allowAccountEdits() && ofConfig.AllowAccountEdits);
        self.allowShaEdits             = ko.observable(self.allowShaEdits() && ofConfig.AllowShippingEdits);
        self.allowWarehouseSelection   = ko.observable(ofConfig.allowWarehouseSelection );
        self.superUserRestrictMinPrice = ko.observable(ofConfig.superUserRestrictMinPrice);

        self.postOrderToGoogleAnalytics  = function() {

            if (self.completed() && self.tracked() != '1' ) {

                if(oConfig.t_ga4 && oConfig.t_ga4_analytics != '') {
                    try {

                        var items = [];

                        for(var i = 0; i < self.detailLines().length; i++) {
                            var line = self.detailLines()[i];

                            var item = {
                                item_id: line.sku(),
                                item_name: line.name(),
                                currency: "USD",
                                price: line.price(),
                                quantity: Number(line.qty()),
                                index: i
                            }
                            items.push(item);
                        }

                        if (items.length >= 1) {

                            window.dataLayer = window.dataLayer || [];
                            function gtag(){dataLayer.push(arguments);}

                            gtag("event", "purchase", {
                                transaction_id: self.orderNumber(),
                                affiliation: ofConfig.t_tracking_company,
                                value: self.orderTotal(),
                                tax: self.taxTotal(),
                                shipping: self.shippingTotal(),
                                currency: "USD",
                                coupon: self.couponCode(),
                                items: items
                            });
                        }
                    } catch(err) {
                        console.log(err);
                    } 
                }else if(ofConfig.t_gtm) {
                    try {
                        var productsOrdered = [];
                        var transactionProducts = [];
                        var transactionItems = [];

                        for(var i = 0; i < self.detailLines().length; i++) {
                            var line = self.detailLines()[i];

                            productsOrdered.push({
                                'orderNumber': self.orderNumber(),
                                'name': self.customer.firstName() + ' ' + self.customer.lastName(),
                                'sku': line.sku(),
                                'category': line.searchfield1(),
                                'brand': line.searchfield5(),
                                'unitPrice': line.price(),
                                'quantityOrdered': line.qty()
                            });

                            // id is used for Enhanced Ecommerce
                            // sku is used for Google Analytics (non-EE)
                            transactionProducts.push({
                                'id': line.sku(),
                                'sku': line.sku(),
                                'name': line.name(),
                                'category': line.searchfield1(),
                                'price': line.price(),
                                'quantity': line.qty()
                            });

                            transactionItems.push({
                                'item_id': line.sku(),
                                'item_name': line.name(),
                                'price': line.price(),
                                'quantity': line.qty()
                            });
                        }

                        window.dataLayer.push({ 'pageType' : 'confirmation' });
                        window.dataLayer.push({ 'orderNumber' : self.orderNumber() });
                        window.dataLayer.push({ 'storeName' : ofConfig.t_tracking_company });
                        window.dataLayer.push({ 'productTotal' : self.productTotal() });
                        window.dataLayer.push({ 'shippingTotal' : self.shippingTotal() });
                        window.dataLayer.push({ 'taxTotal' : self.taxTotal() });
                        window.dataLayer.push({ 'orderTotal' : self.orderTotal() });
                        window.dataLayer.push({ 'customerName' : self.customer.firstName() + ' ' + self.customer.lastName() });
                        window.dataLayer.push({ 'customerFirstname' : self.customer.firstName() });
                        window.dataLayer.push({ 'customerLastName' : self.customer.lastName() });
                        window.dataLayer.push({ 'customerCity' : self.account.city() });
                        window.dataLayer.push({ 'customerState' : self.account.state() });
                        window.dataLayer.push({ 'customerCountry' : self.account.country() });
                        window.dataLayer.push({ 'customerEmail' : self.customer.email() });
                        window.dataLayer.push({ 'couponCode' : self.couponCode() });
                        window.dataLayer.push({ 'couponAmount' : self.couponDiscountTotals.totalDiscount() });
                        window.dataLayer.push({ 'paymentMethod' : self.paymentMethod.name() });
                        window.dataLayer.push({ 'sessionIP' : ofConfig.SessionIP });

                        // For Google Analytics (non-EE)
                        window.dataLayer.push({
                            'transactionId': self.orderNumber(),
                            'transactionAffiliation': ofConfig.t_tracking_company,
                            'transactionTotal': self.orderTotal(),
                            'transactionTax': self.taxTotal(),
                            'transactionShipping': self.shippingTotal(),
                            'transactionProducts': transactionProducts,
                            'transactionItems': transactionItems
                        });

                        // For Enhanced Ecommerce
                        window.dataLayer.push({
                            'event': ofConfig.t_gtm_transaction_event_name,
                            'ecommerce': {
                                'purchase': {
                                    'actionField': {
                                        'id': self.orderNumber(),
                                        'affiliation': ofConfig.t_tracking_company,
                                        'revenue': self.orderTotal(),
                                        'tax': self.taxTotal(),
                                        'shipping': self.shippingTotal(),
                                        'coupon': self.couponCode()
                                    },
                                    'products': transactionProducts,
                                    'items': transactionItems
                                }
                            }
                        });

                        window.dataLayer.push({ 'productsOrdered' : productsOrdered });

                    } catch(err) {}

                } 

                if(ofConfig.t_adwords && ofConfig.t_adwords_analytics && ofConfig.t_adwords_conversion){

                    window.dataLayer = window.dataLayer || [];
                    function gtag(){dataLayer.push(arguments);}

                    gtag('event', 'conversion', {
                        'send_to': ofConfig.t_adwords_analytics + '/' + ofConfig.t_adwords_conversion,
                        'value': self.orderTotal(), // Total
                        'currency': 'USD',
                        'transaction_id': self.orderNumber(), // Order ID
                        'shipping' : self.shippingTotal(), // Shipping
                        'tax' : self.taxTotal() // Tax
                    });

                }

                if(ofConfig.t_facebook && ofConfig.t_facebook_analytics){

                    fbq('track', 'Purchase', {
                        value: self.orderTotal(),
                        currency: 'USD'
                    });

                }

                if(ofConfig.t_bing && ofConfig.t_bing_analytics){

                    var items = [];
                    var item_ids = [];

                    for(var i = 0; i < self.detailLines().length; i++) {
                        var line = self.detailLines()[i];
                        var item_id = line.sku();

                        var item = {
                            id: line.sku(),
                            item_name: line.name(),
                            price: line.price(),
                            quantity: Number(line.qty())
                        }
                        items.push(item);
                        item_ids.push(item_id);
                    }

                    if (items.length >= 1) {
                        window.uetq = window.uetq || [];
                        window.uetq.push('event', 'purchase', { 
                            'transaction_id': self.orderNumber(),
                            'ecomm_prodid': item_ids,
                            'ecomm_pagetype': 'purchase',
                            'revenue_value': self.orderTotal(),  // Value of the conversion
                            'currency': 'USD',
                            'items': items
                        });
                    }

                }
            }
        }

        self.updateOrderAccess = function(){
            if( self.completed() || self.lifecycleStage() == 'cancelled' || self.lifecycleStage() == 'punchout_punched_in'){
                self.allowAccountEdits( false );
                self.allowContactEdits( false );
                self.allowShaEdits( false );
                self.allowWarehouseSelection( false );
                self.allowProductAdds( false );
                self.allowShipViaEdits( false );
                self.allowPayMethodEdits( false );
                self.allowCcnChanges( false );
                self.allowPlaceOrder( false );
                self.allowQtyControls( false );
                self.disablePlaceOrder( true );
            }else if(self.superUserOrderFormMode()){
                self.allowAccountEdits(self.allowAccountEdits() && ofConfig.superUserAllowAccountEdits);
                self.allowContactEdits(self.allowContactEdits() && ofConfig.superUserAllowContactEdits);
                self.allowShaEdits(self.allowShaEdits() && ofConfig.superUserAllowShaEdits);
                self.allowWarehouseSelection(ofConfig.allowWarehouseSelection || ofConfig.superUserAllowWarehouseSelection );
                self.allowProductAdds(true);
                self.allowShipViaEdits(self.allowShipViaEdits() || ofConfig.superUserAllowShipViaEdits);
                self.allowPayMethodEdits(self.allowPayMethodEdits() || ofConfig.superUserAllowPayMethodEdits);
                self.allowCcnChanges(self.allowCcnChanges() || ofConfig.superUserAllowCcnChanges);
                self.allowPlaceOrder(self.allowPlaceOrder() || ofConfig.superUserAllowPlaceOrder);
                self.allowQtyControls( ofConfig.superUserAllowQtyControls );
                self.disablePlaceOrder( false );
            }else{
                self.allowAccountEdits( self.allowAccountEdits() && ofConfig.AllowAccountEdits );
                self.allowContactEdits( self.allowContactEdits() && ofConfig.allowContactEdits );
                self.allowShaEdits( self.allowShaEdits() && ofConfig.AllowShippingEdits );
                self.allowWarehouseSelection( ofConfig.allowWarehouseSelection );
                self.allowProductAdds( self.allowProductAdds() );
                self.allowQtyControls( false );
                self.disablePlaceOrder( false );
            }

            var inFocusTemplate = true;
            if(self.completed() && inFocusTemplate) {
                $('#focusHeaderExit').removeAttr("data-toggle");
                $('#focusHeaderExit').attr("href" , "https://frontline4.cimproduction.com");
            }
        };

        self.updateOrderAccess();

        self.continueProcessingForm = function(data) {
            var bContinueProcessing = true;

            //TODO: Get this validation working using the validation plugin.
            //This is a quick 'fix' to prevent the page from processing the order action
            //when the shipping account is required but not set.
            _.each(viewModel.shipments(), function(shipment, index){
                if(ofConfig.CollectShipAccount && shipment.selectedShipVia.collectShippingAccount() && !shipment.selectedShipVia.shippingAccountId.isValid() ){
                    //form is not valid
                    scrollToSection('#shipping-account-info' + (index+1));
                    //jQuery('#shipping-account-select' + shipmentCount).addClass('control-group error');
                    bContinueProcessing = false;
                    return false;
                }
            });

            if(viewModel.selectedOrderAction != undefined && viewModel.selectedOrderAction().showPaymentMethods()) {
                //TODO: Do CC validation in a less janky way
                //This is a quick 'fix' to prevent the page from processing the order action
                //when the CC billing address info is invalid
                //Currently doesn't provide any messaging
                if(viewModel.allowPayMethodEdits() && viewModel.paymentMethod.paymentType() === 'cc' && (data.placeOrderActionType() == 'place' || data.placeOrderActionType() == 'validate-only')) {

                    if(   ($('#ccpm_cvv2_code').is(':visible') && !$('#ccpm_cvv2_code').val())
                        || !$('#ccpm_address_input').val()
                        || !$('#ccpm_city_input').val()
                        || !$('#ccpm_country_input').val()
                        || !($('#ccpm_state_dropdown').prop('disabled') ? $('#ccpm_state_text').val() : $('#ccpm_state_dropdown').val())
                        || !$('#ccpm_zip_input').val()
                    ) {
                        // CC billing address form is not valid
                        $("#ccpm_card_billing_address_section").addClass("in").css({ "height" : "auto" });
                        $("#ccpm_card_billing_address_section :input:visible:not('button')")
                            .filter(function() { return $(this).val() == ""; })
                            .trigger("change");
                        $("#ccpm_cvv2_code").trigger("change");
                        scrollToSection('#payment_methods');
                        bContinueProcessing = false;
                        return false;
                    }
                }
                //Pretty much just following the same pattern as cc validation.
                //Need to stop the page from processing if the poNum is required and not entered.
                if(viewModel.paymentMethod.collectPO() && viewModel.paymentMethod.poRequired() && !$('#ponumber').val()) {
                    jQuery('#controlgroup_invoice_ponumber').addClass('control-group error');
                    scrollToSection('#payment_methods');
                    bContinueProcessing = false;
                    return false;
                }
            }

            //hook for adding custom validation checks
            var config = {
                continueProcessing: bContinueProcessing
            };
            runHook('cartTemplatesContinueProcessing', config);
            return config.continueProcessing;
        };

        self.isCCValid = function() {
            var valid = true;
            var currentDate = new Date();
            var currentMonth = currentDate.getMonth() + 1;
            var currentYear = currentDate.getFullYear();

            if(viewModel.paymentMethod.paymentType() != 'cc') {
                return true;
            }

            if(!viewModel.vaultedPayment.status()) {
                valid = false;
            }

            if (viewModel.vaultedPayment.expYear() < currentYear) {
                valid = false;
            }

            if (viewModel.vaultedPayment.expYear() === currentYear && viewModel.vaultedPayment.expMonth() < currentMonth) {
                valid = false;
            }
            
            return valid;
        };

        self.processOrderAction = function(data){
            var bContinueProcessing = self.continueProcessingForm(data);

            if(bContinueProcessing){

                if( data.showComments() && viewModel.customerComment() != '' ){
                    self.saveCustomerComment();
                }

                if( !data.showOrderCcEmail() && viewModel.orderCcEmail() != ''){
                    //if the user entered an email then chose an OA that isn't showing the email input, blank it out.
                    viewModel.orderCcEmail('');
                }

                if(data.showRecurringOrderSettings()){
                    self.saveRecurringOrderSettings();
                }

                var postData = {};
                var bPost = true;
                postData.orderActionKey = data.orderActionKey();

                if(viewModel.paymentMethod.paymentType() === 'cc' && (viewModel.selectedOrderAction != undefined && viewModel.selectedOrderAction().showPaymentMethods())) {

                    var defaultCcnChecked = $('#ccpm_default_ccn_id_selection').is(':checked');

                    if(defaultCcnChecked) {
                        postData.setDefaultCcn = 1
                    }

                    postData.ccn_key =  $('#ccpm_ccn_id').val();
                    postData.cvv     =  $('#ccpm_cvv2_code').val();
                    postData.address =  $('#ccpm_address_input').val();
                    postData.city    =  $('#ccpm_city_input').val();
                    postData.country =  $('#ccpm_country_input').val();
                    postData.state   =  $('#ccpm_state_dropdown').prop('disabled') ? $('#ccpm_state_text').val() : $('#ccpm_state_dropdown').val();
                    postData.zip     =  $('#ccpm_zip_input').val();
                    postData.tsm_id  =  viewModel.paymentMethod.merchantId();

                }else if(viewModel.paymentMethod.paymentType() === 'paypal' && data.placeOrderActionType() == 'place'){
                    bPost = false;
                    postPayPal(data.orderActionKey());
                }

                var postOptions = {
                    data: postData,
                    error: function() {
                        alert('Error Processing Order Action');
                    }
                }

                var scrollToId = 'checkout';

                runHook('cartTemplateOverridePostOptions', { postOptions: postOptions, data: data, self: self });

                if (bPost){
                    var postOrderAction = function() {
                        if ($.active > 0) {
                            viewModel.processing(true);
                            $( document ).one("ajaxStop", function(){
                                postInfo(postOptions, 'processOrderAction', scrollToId);
                            });
                        }
                        else {
                            postInfo(postOptions, 'processOrderAction', scrollToId);
                        }
                    }

                    if(data.placeOrderActionType() == 'punch-in'){
                        self.postPunchin(postOrderAction, postOptions);
                    } else {
                        postOrderAction();
                    }
                }
            }
        };

        self.postPunchin = function(fncPostOrderAction, orderActionPostOptions){
            toggleLoadingWidget(true);
            var model = viewModel;
            model.activeAjaxRequestCount(model.activeAjaxRequestCount() + 1);
            model.processing(true);

            var bPostPunchIn = false;
            var punchinXmlUrl = orderInfoPostUrl + '?o_key=' + model.orderKey() + '&ajax=true&pageaction=getPunchinXml';
            var getPunchinXmlPostOptions = {
                url: punchinXmlUrl,
                data: { orderKey: viewModel.orderKey() },
                success: function(data) {
                    if (data.trim().startsWith('<?xml')) {
                        $('input#cxml-urlencoded').val(data);
                        orderActionPostOptions.success = function() {
                            bPostPunchIn = true;
                        }
                        orderActionPostOptions.complete = function() {
                            if(bPostPunchIn) {
                                $('form#punch-in').submit();
                            }
                        }
                        fncPostOrderAction();
                    } else {
                        alert('Error Processing Order Action: Unable to retrieve valid punchin data.');
                    }
                },
                error: function() {
                    alert('Error Processing Order Action: Unable to retrieve punchin data.');
                },
                complete: function() {
                    model.activeAjaxRequestCount(model.activeAjaxRequestCount() - 1);
                    if(model.activeAjaxRequestCount() < 1) {
                        model.processing(false);
                    }
                    toggleLoadingWidget(false);
                }
            };
            
            if ($.active > 0) {
                viewModel.processing(true);
                $( document ).one("ajaxStop", function(){
                    $.ajax(getPunchinXmlPostOptions);
                });
            }
            else {
                $.ajax(getPunchinXmlPostOptions);
            }
        };

        self.saveRecurringOrderSettings = function(){
            var postData = [];
            var order = [];

            var orderKey = viewModel.orderKey();

            var roRunDate = viewModel.roRunDate();
            var roRecCase = viewModel.roRecCase();
            var roRecQty  = viewModel.roRecQty();
            //This has to be set before processOrderAction or the OO will "unset" the values above when the record_type isn't set to recurring.
            var recordType = "recurring";


            order.push({
                "o_key" : orderKey,
                "ro_rundate": roRunDate,
                "ro_rec_case": roRecCase,
                "ro_rec_qty": roRecQty,
                "record_type": recordType
            });

            postData =
            {
                "Tables": [
                    {
                        "TableName"           : "orders",
                        "TableKeyField"       : "o_key",
                        "UserKeyField"        : "o_key",
                        "UserKeyIsPrimaryKey" : "True",
                        "Data"                : order
                    }
                ]
            }

            jQuery.ajax({
                url: 'payment.asp' + '?o_key=' + viewModel.orderKey() + '&ajax=true&pageaction=postLogicJSON&randomnum=' + new Date().getTime()
                , data: "postLogicJSON=" + encodeURIComponent(JSON.stringify(postData))
                , cache: false
                , type: 'POST'
                , dataType: 'json'
                , success: function(data,status,request){
                    viewModel.roRunDate(roRunDate);
                    viewModel.roRecCase(roRecCase);
                    viewModel.roRecQty(roRecQty);
                }
                , error: function(data) {
                    alert('Error posting Recurring Order Settings');
                }
                , complete: function(data) {
                }
            });
        };

        self.saveCustomerComment = function(){

            var postData = [];
            var noteBook = [];
            var note = [];
            var order = [];

            var notebookTableName = ofConfig.useNewNotebooks ? 'Notebooks' : 'note_books';
            var notebookKeyField = ofConfig.useNewNotebooks ? 'Id' : 'nb_key';
            var notebookRefField = ofConfig.useNewNotebooks ? 'ReferenceId' : 'ref_id';
            var ref_id = self.noteBook().refId || generateRefId();
            var n_key = utils.createGuid();

            if(ofConfig.useNewNotebooks) {
                var notebookId = parseInt(self.noteBook().id || 0) || ref_id;

                noteBook.push({
                    "Id": notebookId,
                    "ReferenceId": notebookId
                });

                order.push({
                    "o_key" : self.orderKey(),
                    "NotebookId": notebookId
                });

                note.push({
                    "n_key"          : n_key,
                    "ref_id"         : generateRefId(),
                    "NotebookId"     : notebookId,
                    "a_id"           : ofConfig.superUserAccountKey || ofConfig.AccountKey,
                    "c_id"           : ofConfig.superUserCustomerKey || ofConfig.CustomerKey,
                    "create_date"    : new Date().toDateString(),
                    "external_notes" : self.customerComment()
                });

            } else {
                var nb_key = self.noteBook().nbKey || utils.createGuid();

                order.push({
                    "o_key" : self.orderKey(),
                    "nb_id" : nb_key
                });

                noteBook.push({
                    "nb_key"        : nb_key,
                    "ref_id"        : ref_id,
                    "create_date"   : new Date().toDateString(),
                    "book_type"     : 'order',
                    "resource_c_id" : ofConfig.superUserCustomerKey || ofConfig.CustomerKey
                });

                note.push({
                    "n_key"          : n_key,
                    "ref_id"         : generateRefId(),
                    "nb_id"          : nb_key,
                    "a_id"           : ofConfig.superUserAccountKey || ofConfig.AccountKey,
                    "c_id"           : ofConfig.superUserCustomerKey || ofConfig.CustomerKey,
                    "create_date"    : new Date().toDateString(),
                    "external_notes" : self.customerComment()
                });
            }

            postData =
            {
                "Tables": [
                    {
                        "TableName"           : notebookTableName,
                        "TableKeyField"       : notebookKeyField,
                        "UserKeyField"        : notebookRefField,
                        "UserKeyIsPrimaryKey" : "False",
                        "Data"                : noteBook
                    },
                    {
                        "TableName"           : "orders",
                        "TableKeyField"       : "o_key",
                        "UserKeyField"        : "o_key",
                        "UserKeyIsPrimaryKey" : "True",
                        "Data"                : order
                    },
                    {
                        "TableName"           : "notes",
                        "TableKeyField"       : "n_key",
                        "UserKeyField"        : "ref_id",
                        "UserKeyIsPrimaryKey" : "False",
                        "Data"                : note
                    }
                ]
            }

            jQuery.ajax({
                url: 'payment.asp' + '?o_key=' + viewModel.orderKey() + '&ajax=true&pageaction=postLogicJSON&randomnum=' + new Date().getTime()
                , data: "postLogicJSON=" + encodeURIComponent(JSON.stringify(postData))
                , cache: false
                , type: 'POST'
                , dataType: 'json'
                , success: function(data,status,request){
                    viewModel.updateNoteBook(note[0]);
                    viewModel.customerComment('');
                    return data.PostedKeys[notebookTableName][0];
                }
                , error: function(data) {
                    alert('Error posting Comment');
                }
                , complete: function(data) {
                }
            });
        };

        self.updateNoteBook = function(note) {
            var item = {};

            item.internalNotes    = note.internal_notes;
            item.externalNotes    = note.external_notes;
            item.postedByName     = ofConfig.superUserName || ofConfig.customerName;
            item.postedByUsername = ofConfig.superUserUsername || ofConfig.customerUsername;
            item.createDate       = note.create_date;

            viewModel.noteBook().notes.unshift(item);
        };

        self.genericModalComplete = function(data){
            var postOptions = {
                success: function(data) {
                },
                error: function() {
                    alert('error reloading shipping accounts');
                }
            }

            postInfo(postOptions, 'getOrderJSON');
            return true;
        };

        self.removeProduct = function(data){
            if(ofConfig.bUseRemoveMsg){
                var bRemove = confirm(ofConfig.sRemoveProductMessage);
            }
            else{
                bRemove = true;
            }

            if (bRemove) {
                viewModel.processing(true);
                var itemToDelete = data;
                var removeType = itemToDelete.removeType();
                var orderDetailKey = itemToDelete.orderDetailKey();
                var instance = itemToDelete.instance();
                var sRemoveUrl = 'i_i_add_to_cart.asp?type=remove&unq=' + orderDetailKey + '&o_id=' + data.orderID() + '&modal=1';

                jQuery.ajax({
                    url: sRemoveUrl
                    , cache: false
                    , type: 'GET'
                    , success: function(data,status,request){
                        //Calling getOrderJSON here to handle removing reward products 
                        // From the UI when the target is removed.
                        viewModel.genericModalComplete();
                    }
                    , error: function(data) {
                        alert('Error removing item.');
                        viewModel.processing(false);
                    }
                    , complete: function(data) {
                        viewModel.processing(false);
                    }
                });
            }
        };

        self.resetProductEdits = function(editingDetailLine) {
            viewModel.detailLines()
                .forEach(function(detailLine) {
                    detailLine.editing(false);
                });
            viewModel.shipments()
                .forEach(function(shipment) {
                    shipment.details()
                        .forEach(function(detail) {
                            detail.orderDetail.editing(false);
                        });
                });
            viewModel.mainProduct(undefined);
            utils.removeActiveQuote();

            if(editingDetailLine) {
                editingDetailLine.editing(true);
            }
        }

        self.editProduct = function(data) {
            self.resetProductEdits();
            var detailLine = data;
            if(data.orderDetail) {
                detailLine = data.orderDetail;
            }
            var detailKey  = detailLine.orderDetailKey();
            var productKey = detailLine.parentProductID().trim() || detailLine.productID();

            var productInfo = {
                productKey: detailLine.productID(),
                productSku: detailLine.sku(),
                parentChildType: detailLine.commodity.parentChildType() || '',
                parentProductId: detailLine.parentProductID().trim()
            }

            self.loadProductForEdit(productInfo, detailKey, detailLine)
        }

        self.cancelEditProduct = function(data) {
            self.resetProductEdits();
        }

        self.loadProductForEdit = function(productInfo, detailLineId, detailLine) {
            viewModel.processing(true);
            self.resetProductEdits(detailLine);

            var postOptions = {
                success: function(data) {
                    var response = JSON.parse(data);
                    var product = response.product;
                    switch(product.childDisplayType) {
                        case 'input-qty':
                        case 'exploded-view':
                        case 'matrix-all':
                        case 'add-row':
                            product.childDisplayType = 'droplist'
                            break;
                        default:
                    };

                    if(!!response.childSkuMatch) {
                        oConfig.childSkuMatch = response.childSkuMatch;
                    }

                    utils.setActiveQuote(viewModel.orderKey());
                    viewModel.mainProduct(ko.mapping.fromJS(product, productMapping));
                    viewModel.addToSavedCart();
                },
                url: orderInfoPostUrl + '?o_key=' + self.orderKey() + '&p_key=' + (productInfo.parentProductId || productInfo.productKey) + '&od_key=' + detailLineId + '&sku=' + productInfo.productSku + '&ajax=true&pageaction=getProductData&modal=1',
                type: 'GET',
                error: function() {
                    alert('Error loading product data!');
                }
            }

            $.ajax({
                url      : postOptions.url,
                success  : postOptions.success,
                type     : postOptions.type,
                error    : postOptions.error,
                complete : function() { viewModel.processing(false); }
            });
        }

        self.productFinderSearchTerm = ko.observable('');

        self.productFinderUrl = ko.computed(function() {
            var searchTerm = self.productFinderSearchTerm();
            var search2 = ofConfig.productFinderSearchstring.replace(/__query__/g, searchTerm);
            return 'bulk_atc.asp?find_product=1&s=' + searchTerm + '&search2=' + search2;
        });

        self.showRemoveLink = function(data){
            if(!ofConfig.allowProductRemoves){
                return false;
            }

            var instanceKeys = viewModel.detailLines().reduce(function(instanceKeys, line) {
                if(instanceKeys.indexOf(line.instance()) == -1) {
                    instanceKeys.push(line.instance());
                }
                return instanceKeys;
            }, []);

            if(!ofConfig.allowRemoveLastItem && instanceKeys.length < 2) {
                return false;
            }

            if(data.removeType() == 'hide'){
                return false;
            }
            return true;
        };

        self.breadcrumbMarkup = ko.computed(function(){
            var thisIsCartPage = ofConfig.cartPage === getOriginalPageName();
            var isComplete = self.completed() || (self.lifecycleStage() == 'punchout_punched_in')

            var paymentCrumbLabel = "Payment";
            if(!ofConfig.showPaymentMethodsSection) {
                paymentCrumbLabel = thisIsCartPage ? "Cart" : "Finalize"
            }
            var completeCrumbLabel = ofConfig.isPunchoutSession ? "Sent to Procurement" : "Order Placed";

            var cartCrumb = "<li><a href=\"" + ofConfig.cartPage + "\"><i class=\"icon-shopping-cart\"></i> Cart</a> <span class=\"divider\"><i class=\"icon-chevron-right\"></i></span></li>";
            if(thisIsCartPage) {
                cartCrumb = "";
            }
            var accountCrumb = "<li><a href=\"account.asp\"><i class=\"icon-info-sign\"></i> Billing / Shipping</a> <span class=\"divider\"><i class=\"icon-chevron-right\"></i></span></li>";
            var paymentCrumb = "<li class=\"" + (isComplete ? "" : "active") + "\"><i class=\"icon-lock\"></i> " + paymentCrumbLabel + " <span class=\"divider\"><i class=\"icon-chevron-right\"></i></span></li>";
            var completeCrumb = "<li class=\"" + (isComplete ? "active" : "") + "\"><i class=\"icon-ok\"></i> " + completeCrumbLabel + "</li>";

            var dynamicBreadcrumbMarkup = "<ul class=\"breadcrumb breadcrumb-cart\">" + cartCrumb + accountCrumb + paymentCrumb + completeCrumb + "</ul>";

            if(self.cartType != 'recurring' && (thisIsCartPage || ofConfig.isPunchoutSession || !ofConfig.showPaymentMethodsSection)) {
                return dynamicBreadcrumbMarkup;
            }            

            if(self.recordType() == 'cart') {
                return ofConfig.sPaymentHeaderLabel;
            } else if (self.recordType() == 'recurring') {
                return '<ul class="breadcrumb breadcrumb-cart"><li class="active">Order</li><li><i class="icon-ok"></i> Order Placed</li></ul>';
            } else {
                return '<ul class="breadcrumb breadcrumb-cart"><li class="active">Order</li><li><i class="icon-ok"></i> Order Placed</li></ul>';
            }
        });

        self.expirationDate = ko.observable(function() {
            return new moment(self.quoteExpirationDate()).format("MM/DD/YYYY");
        }());

        self.expirationDate.subscribe(function(newExpirationDate) {
            self.buildOrderHeaderFields(["QuoteExpirationDate"]);
        });

        // Used for Google Analytics and Tag Manager, etc
         /*
        Runs when order is completed but payment page doesn't re-fresh
        - Example user pays with a credit card or bill me payment option
        */
        self.orderPlaced.subscribe(function() {
            self.postOrderToGoogleAnalytics();
            postOrderHeaderField('tracked', '1');
            self.tracked('1');
        });
        /*
        Runs when order is completed and the payment page re-freshes
        - Happens when user checks out with paypal and is re-directed back to payment page
        - with a completed order
        */
        if (self.completed() && self.tracked() != '1' ) {
            self.postOrderToGoogleAnalytics();
            postOrderHeaderField('tracked', '1');
            self.tracked('1');
         }

        addTimer("orderModelBottom");
        runHook('orderModelBottom', { self: self, order: self });
        addTimer("end order mapping");
    } // end of var Order = blah blah blah 

    function processShippingAddresses(data){
        var addressArray = data;
        var newshippingAddresses;
        if(Array.isArray(addressArray)) {
            newshippingAddresses = addressArray.map(function(item, index, arr) {
                address = new addressInfo();
                address.name(item.sha_nm);
                address.company(item.s_company || '');
                address.attention(item.attention || '');
                address.address1(item.s_add1 || '');
                address.address2(item.s_add2 || '');
                address.address3(item.s_add3 || '');
                address.address4(item.s_add4 || '');
                address.address5(item.s_add5 || '');
                address.country(item.s_country || 'USA');
                address.city(item.s_city || '');
                address.state(item.s_state || undefined);
                address.zipCode(item.s_zip || '');
                address.phone(item.s_phone || '');
                address.email(item.em || '');
                address.global(item.globaladdress || '0');
                address.opt1(item.s_opt1 || '');
                address.opt2(item.s_opt2 || '');
                address.opt3(item.s_opt3 || '');
                address.opt4(item.s_opt4 || '');
                address.opt5(item.s_opt5 || '');
                address.key(item.sha_key);
                address.editing(false);
                return address;
            });
        }

        /*
        Changing to an observable array (was an obeservable object that contained an array)
        */
        var observableAryOfSHA = ko.observableArray([]);
        newshippingAddresses.forEach(function(obj){
            observableAryOfSHA.push(ko.mapping.fromJS(obj, shippingAddressMappingOptions));
        });

        return observableAryOfSHA; //ko.mapping.fromJS(newshippingAddresses, shippingAddressMappingOptions);
    }

    var orderMapping = {
        create: function(options) {
            var newOrder = new Order(options.data);

            return newOrder; // new Order(options.data);
        }
    }

    //Build Json for order header field and optional additional tables.
    //Pass the Json to the Post Logic Ajax function
    var postOrderHeaderField = function(sOrderField, viewModelProperty, extraTablesJson, callbacks) {
        console.log(sOrderField);
        var postData = {};
        var order = [];

        order = [{
            "o_key" : ofConfig.OKey
        }];

        order[0][sOrderField] = viewModelProperty;

        postData =
        {
            "Tables": [
                {
                    "TableName"           : "orders",
                    "TableKeyField"       : "o_key",
                    "UserKeyField"        : "o_key",
                    "UserKeyIsPrimaryKey" : "True",
                    "Data"                : order
                }
            ]
        };

        //Optional additional tables on the order
        if (!!extraTablesJson) {
            postData["Tables"].push(extraTablesJson);
        }

        self.postLogicJsonAjax(postData, false, callbacks);
    };

    var postOrderDetailFields = function(sOrderDetailKey, sDetailFields, viewModelProperties, callbacks) {

        var postData = {};
        var orderLine = [];

        orderLines = [{
            "od_key" : sOrderDetailKey
        }];

        for(var i = 0; i < sDetailFields.length; i++){
            orderLines[0][sDetailFields[i]] = viewModelProperties[i];
        }

        postData =
        {
            "Tables": [
                {
                    "TableName"           : "order_detail",
                    "TableKeyField"       : "od_key",
                    "UserKeyField"        : "od_key",
                    "UserKeyIsPrimaryKey" : "True",
                    "Data"                : orderLines
                }
            ]
        };
        self.postLogicJsonAjax(postData, false, callbacks);
    };

    //Posts Json directly to table(s) using Post Logic
    //Should be used for fields that do not affect other properties in the ViewModel (i.e. text areas)
    //No actions on success.
    var postLogicJsonAjax = function(postData, getOrderJSON, callbacks) {
        toggleLoadingWidget(true);
        callbacks = callbacks || {};

        jQuery.ajax({
            url: 'payment.asp' + '?o_key=' + ofConfig.OKey + '&getorderJSON=' + getOrderJSON + '&ajax=true&pageaction=postLogicJSON&randomnum=' + new Date().getTime(),
            data: "postLogicJSON=" + encodeURIComponent(JSON.stringify(postData)),
            cache: false,
            type: 'POST',
            dataType: 'json',
            success: callbacks.success,
            error: function(jqXHR, textStatus, errorThrown) {
                console.log(jqXHR.status);
                if (typeof callbacks.error == 'function') {
                    callbacks.error(jqXHR, textStatus, errorThrown);
                } else {
                    if(jqXHR.status == 401){
                        location.href = 'showcart.asp?section=ValidateOrderOwnership&err=not-logged-in'
                    }else{
                        alert('Error posting to ' + (postData["Tables"][0]["TableName"] || "postLogicJsonAjax") + '.');
                    }
                }
            },
            complete: function(){
                toggleLoadingWidget(false);
            }
        });
    };


    var apiRoutedPageActions = [
        /*
        // can't be converted yet
        'placeOrder',
        'processOrderAction',
        */
        'setSha',
        'setCCN',
        'setPmId',
        'setCoupon',
        'setGiftCertCode',
        'setOrderHeaderFields',
        'setRequestedShipDate',
        'setShippingPrice',
        'getOrderJSON',
        'resetShippingPrice',
        'setShipmentShipVia'
    ];
    
    apiRoutedPageActions = apiRoutedPageActions.concat([
        'updateShipToAddress',
        'setOrderDetailFields',
        'assignOwnership'
    ]);
    

    var postInfo = function(ajaxOptions, pageAction, scrollToId, model) {
        toggleLoadingWidget(true);
        if(!model) {
            model = viewModel;
        }

        model.activeAjaxRequestCount(model.activeAjaxRequestCount() + 1);

        model.processing(true);

        model.errorMessages.removeAll();

        var existingSuccess = ajaxOptions.success;
        var existingError = ajaxOptions.error;
        var existingComplete = ajaxOptions.complete;
        
        var querystring;
        var comUrl;
        if(ajaxOptions.url){
            querystring = ajaxOptions.url.substring(ajaxOptions.url.indexOf('?'))
            comUrl = orderInfoPostUrl + querystring;
        }else{
            querystring = '?o_key=' + model.orderKey();
            comUrl = orderInfoPostUrl + '?o_key=' + model.orderKey() + '&ajax=true&pageaction=' + pageAction;
        } 
        var apiUrl = orderInfoPostApiUrl + pageAction + querystring;
                
        if (apiRoutedPageActions.includes(pageAction)) {
            ajaxOptions.url = apiUrl;
        } else if (!ajaxOptions.url) {
            ajaxOptions.url = comUrl;
        }

        if(!ajaxOptions.type) {
            ajaxOptions.type = 'POST';
        }

        ajaxOptions.success = function(data) {
            try{
                var response = JSON.parse(data);
            } catch (error){
                /*
                * if we get invalid json here, it's most likely a redirect due to
                * an expired session or other issue causing an html page to be
                * returned instead of processing the ajax request
                */
                location.href = 'payment.asp?o_key=' + model.orderKey();
            }
            runHook('postInfoAjaxSuccessFunction', {response: response});
            if(Array.isArray(response) && Object.keys(response[0].Errors).length > 0) {
                for (var msg in response[0].Errors) {
                    if(response[0].Errors.hasOwnProperty(msg)) {
                        scrollToId = 'errorList';

                        model.errorMessages.push(
                            {
                                "errorType": msg,
                                "message": response[0].Errors[msg]
                            }
                        );
                    }
                }
            }

            ko.mapping.fromJS(response[1], orderMapping, model);

            model.updateOrderAccess();
            if(model.isOverLineLimit()){
                model.shippingComplete(true);
            }
            if(typeof existingSuccess === 'function') {
                existingSuccess(data);
            }
            
            _.each(model.itemShipToMap(), function(item){
                item.qty.subscribe(function () { 
                    model.isMinimumOrderTotalMet(checkOrderTotal(model));
                });   
            });

            if(scrollToId) {
                model.showConfirmationMessage(true);
                scrollToSection('#' + scrollToId);
            }
            
            model.detailLines().forEach(function(detailLine) {
                // We can't debounce/trottle an observable
                // so we add a computed to track qty changes.
                // We use this in a subscription to call
                // update cart to update qty change in bulk atc
                // to avoid loading the qty change without posting
                // and updating the entire order model (for perf)
                detailLine.qtyTracker = ko.computed(function() {
                    return detailLine.qty();
                }).extend({throttle: 500});
                detailLine.qtyTracker.subscribe(function (newQty) {
                    model.postQtyUpdate();
                }, detailLine);
                detailLine.workerPriceTracker = ko.computed(function() {
                    return detailLine.price();
                }).extend({throttle: 500});
                detailLine.workerPriceTracker.subscribe(function (newQty) {
                    model.setWorkerPriceOverride(detailLine);
                }, detailLine);
                detailLine.workerQtyLimitsTracker = ko.computed(function() {
                    return {
                        removeType: detailLine.removeType(),
                        minQty: detailLine.minQty(),
                        maxQty: detailLine.maxQty(),
                        qtyIncrement: detailLine.qtyIncrement()
                    };
                }).extend({throttle: 500});
                detailLine.workerQtyLimitsTracker.subscribe(function (newValue) {
                    model.setWorkerQtyLimits(detailLine);
                });
            });
        };

        ajaxOptions.error = function(jqXHR, textStatus, errorThrown) {
            if(jqXHR.status == 401){
                location.href = 'showcart.asp?section=ValidateOrderOwnership&err=not-logged-in'
            }
            if (ajaxOptions.url != comUrl) {
                // if API call fails, retry with COM AJAX
                ajaxOptions.url = comUrl;
                $.ajax({
                    url: orderInfoPostUrl + '?o_key=' + model.orderKey() + '&ajax=true&pageaction=logError&url=' + encodeURIComponent(apiUrl) + '&method=' + pageAction + '&message=API error (' + jqXHR.status + ')',
                    success: function() {
                    },
                    error: function(jqXHR, textStatus, errorThrown) {
                        if(jqXHR.status == 401){
                            location.href = 'showcart.asp?section=ValidateOrderOwnership&err=not-logged-in'
                        }
                    }
                });
                $.ajax(ajaxOptions);
                return;
            }
            if(typeof existingError === 'function') {
                existingError(); //data is not defined
            }
        };

        ajaxOptions.complete = function() {
            runHook('postInfoAjaxCompleteFunction', { model: model, ajaxOptions: ajaxOptions, pageAction: pageAction, scrollToId: scrollToId });
            if(typeof existingComplete === 'function') {
                existingComplete();
            }
            model.activeAjaxRequestCount(model.activeAjaxRequestCount() - 1);
            if(model.activeAjaxRequestCount() < 1) {
                model.processing(false);

                /* EJ - 2016-11-14
                   Ugly hack to get around ko.mapping not initializing the Account
                   property correctly when re-mapping on existing object.  I think
                   it may have to do with manually converting the Account property
                   to a validatedObservable the first time through.
                */
                fixUpNulls(model.Account());
            }
            toggleLoadingWidget(false);
        };

        $.ajax(ajaxOptions);
    }

    var fixUpNulls = function(account) {
        for(var prop in account) {
            if(account.hasOwnProperty(prop) && typeof account[prop] === 'function') {
                if(account[prop]() === null || account[prop]() === 'null') {
                    account[prop]('');
                }
            }
        }
    }

    var postPayPal = function(orderActionKey) {
        viewModel.processing(true);

        jQuery.ajax({
            url: 'payment.asp' + '?paypal_order_submit=1&oa_id=' + orderActionKey + '&o_key=' + viewModel.orderKey() + '&randomnum=' + new Date().getTime()
            , data: "o_key=" + viewModel.orderKey()
            , cache: false
            , type: 'POST'
            , dataType: 'json'
            , success: function(data){
				if(!data.status && data.message != ''){
					viewModel.errorMessages.push(
                            {
                                "errorType": "payment_methods",
                                "message": data.message
                            }
                        );
					viewModel.processing(false);
				}
                else if(data.url != ''){
                    document.location = data.url;
                }

            }
            , error: function(data) {
                alert('Error attempting PayPal checkout');
                viewModel.processing(false);
            }
            , complete: function(data) {
            }
        });
    }

    var addFromProductFinder = function(productInfo) {
        viewModel.productFinderSearchTerm('');

        var productInfo = {
            productKey: productInfo.key,
            productSku: productInfo.sku,
            parentChildType: productInfo.parent_child_type,
            parentProductId: productInfo.parent_p_id
        }

        viewModel.loadProductForEdit(productInfo, '');
    }

    var checkOrderTotal = function(model){
        var orderTotalValid = true;
        var subTotal = 0;
        if(ofConfig.useMinimumOrderTotal && !model.completed()){
            if(model.useMultiShipEditUI && model.useMultiShipEditUI()){
                _.each(model.itemShipToMap(), function(item){
                    subTotal += (item.qty() * item.getPrice());
                });
                console.log('itemShipToMap');
            }else if(model.itemsOnOrder){
                _.each(model.itemsOnOrder(), function(item){
                    subTotal += (item.qty() * item.price());
                });
            }else{
                var subTotal = model.productTotal();
            }
            var minimumTotal = ofConfig.minimumOrderTotal;

            if(ofConfig.useAccountMinimumOrderTotal && ofConfig.accountMinimumOrderTotal > 0){
                minimumTotal = ofConfig.accountMinimumOrderTotal;
            }
            if(subTotal < minimumTotal){
                orderTotalValid = false;
            }
        }
        return orderTotalValid;
    }

    $(function() {
        if(getOriginalPageName() !== 'bulk_atc.asp') {
            addGlobalModalCompletionHandler($('#find_product'), addFromProductFinder);
        }

        runHook('cartTemplatesDocumentReady', {self: viewModel});
    });

    addTimer("end of ko model");
</script>
</div>

<script type="text/html" id="catalog.category_list_custom">
    <div class="category-listings" data-layout="gallery" data-bind="foreach: $data">
        <div class="prod-card">
            <div class="prod-thumb" data-bind="visible: oConfig.searchConfig.showThumb && pic2">
                <a data-bind="attr: { href: link, title: utils.decodeHTML(name), 'data-key': key }">
                    <i data-bind="attr: { class: utils.decodeHTML(pic2), alt: utils.decodeHTML(altPicThumb) ||  utils.decodeHTML(name)}"/></i>
                </a>
            </div>
            <div class="prod-info">
                <div class="prod-desc">
                    <div class="prod-nm">
                        <a data-bind="attr: { href: link, title: utils.decodeHTML(name), 'data-key': key }, html: name"></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</script>

<script type="text/html" id="catalog.searchfields_custom">
    <!-- Exclude sf1 - It is used for grouping -->
    <!-- ko if: oConfig.showSearchFields && $data && show() && field()!="searchfield1" -->
        <dt class="prod-searchfields__label" data-bind="html: label"></dt>
        <dd class="prod-searchfields__value" data-bind="html: value"></dd>
    <!-- /ko -->
</script>
<script type="text/html" id="catalog.list_view_custom">
    <!-- ko if: !isShopAll() -->
        <form
            name="atc_form"
            id="atc_form"
            class="frmAddToCart"
            method="POST"
            data-bind="attr: { action: oConfig.formAction, submit: submitForm($data) }"
        >
            <div
                id="prod_listings"
                class="prod-listings"
                data-bind="fastForEach: results"
            >
                <!-- ko template: { name: 'catalog.product_card' } -->
                <!-- /ko -->
            </div>
            <!-- ko if: oConfig.searchConfig.showAtc && showAddAllToCart() && !oConfig.showListViewATC -->
                <div class="multi-add" data-bind="template: 'catalog.list_view_atc'"></div>
            <!-- /ko -->
        </form>
    <!-- /ko -->
    <!-- ko if: isShopAll() -->
        <form
            name="atc_form"
            id="atc_form"
            class="frmAddToCart"
            method="POST"
            data-bind="attr: { action: oConfig.formAction, submit: submitForm($data) }"
        >
            <!-- ko foreach: categories -->
                <h3 data-bind="html: name, visible: name && name.trim() !== ''"
                    class="category-group list-view"></h3>
                <!-- ko foreach: subcategories -->
                    <h4 data-bind="html: name, visible: name && name.trim() !== ''"
                        class="subcategory-group list-view"></h4>
                    <div class="prod-listings"
                            data-bind="fastForEach: products">
                        <!-- ko template: 'catalog.product_card' -->
                        <!-- /ko -->
                    </div>
                <!-- /ko -->
            <!-- /ko -->
            <!-- ko if: oConfig.searchConfig.showAtc && showAddAllToCart() && !oConfig.showListViewATC -->
                <div class="multi-add" data-bind="template: 'catalog.list_view_atc'"></div>
            <!-- /ko -->
        </form>
    <!-- /ko -->
</script>

<script type="text/html" id="catalog.gallery_view_custom">
    <!-- ko if: !isShopAll() -->
        <div
			id="prod_listings"
			class="prod-listings"
			data-bind="fastForEach: results"
		>
			<!-- ko template: 'catalog.product_card' -->
			<!-- /ko -->
		</div>
    <!-- /ko -->
    <!-- ko if: isShopAll() -->
        <!-- ko foreach: categories -->
            <h3 data-bind="html: name, visible: name && name.trim() !== ''"
                class="category-group gallery-view"></h3>
            <!-- ko foreach: subcategories -->
                <h4 data-bind="html: name, visible: name && name.trim() !== ''"
                    class="subcategory-group gallery-view"></h4>
                <div class="prod-listings"
                        data-bind="fastForEach: products">
                    <!-- ko template: 'catalog.product_card' -->
                    <!-- /ko -->
                </div>
            <!-- /ko -->
        <!-- /ko -->
    <!-- /ko -->
</script>
<script type="text/html" id="catalog.input_qty_table_custom">
	<div class="tablesaw-overflow qty-input-table">
		<table class="tablesaw tablesaw-stack" data-tablesaw-mode="stack" role="presentation">
			<thead>
				<tr data-bind="fastForEach: tableColumns">
					<th data-bind="
							html: utils.htmlDecode(ko.unwrap($data).label),
							css: ko.unwrap($data).headerClass,
							attr: {
								width: ko.unwrap($data).width
							}
						">
					</th>
				</tr>
			</thead>
			<!-- ko template: { afterRender: function() { $(document).trigger('enhance.tablesaw'); } } -->
				<tbody data-bind="fastForEach: children">
					<tr data-bind="fastForEach: $parent.tableColumns, css: ((showAtc ? '' : 'disabled ') + (oConfig.childSkuMatch == sku() ? 'selected' : ''))">
					<!-- ko if: $data.template -->
						<td data-bind="
								text: ko.unwrap($data).label != 'Image' ? ko.unwrap($data).label : '',
								css: ko.unwrap($data).label != 'Image' ? 'tablesaw__label-mobile' : '',
								visible: ko.unwrap($data).label != 'Image'
							">
						</td>
						<td data-bind="
								template: {name: template, data: ko.unwrap($parent) },
								css: ko.unwrap($data).cellClass
							">
						</td>
					<!-- /ko -->

					<!-- ko if: !$data.template -->
						<td data-bind="
								text: ko.unwrap($data).label != 'Image' ? ko.unwrap($data).label : '',
								css: ko.unwrap($data).label != 'Image' ? 'tablesaw__label-mobile' : '',
								visible: ko.unwrap($data).label != 'Image'
							">
						</td>
						<td data-bind="
								text: ($data.format || _.identity)(ko.unwrap($parent)[field]),
								css: ko.unwrap($data).cellClass
							">
						</td>
					<!-- /ko -->
					</tr>
				</tbody>
			<!-- /ko -->
		</table>
	</div>
</script>

<!-- Overridden to show disabled inputs on input-qty-view -->
<script type="text/html" id="catalog.product_actions_custom">
    <!-- ko if: !selectedProduct().showQuickAtc() -->
        <div class="input-prepend input-append">
            <input type="number" pattern="\d*" class="qty-input" placeholder="Qty" aria-label="Qty" min="1" step="1" max="" disabled />
        </div>
    <!-- /ko -->
    <!-- ko if: selectedProduct().showQuickAtc -->
        <!-- ko if: selectedProduct().isInCart -->
            <div class="already-in-cart muted text-small"><i class="fas fa-shopping-cart"></i> Already in Cart</div>
        <!-- /ko -->
        <div class="input-prepend input-append">
            <!-- ko if: hasQuantityRestrictions -->
                <a href="#0"
                    class="add-on"
                    data-bind="popover, attr: { 'data-content': quantityRestrictionsHtml, 'data-key' : key, 'id': 'pop-' + $.escapeSelector(sku().replace(/([^A-Za-z0-9[\]{}_.:-])\s?/g, '_')) }"
                    data-trigger="hover"
                    data-html="true"
                    data-placement="top"
                >
                    <i class="icon-question-sign"></i>
                </a>
            <!-- /ko -->
            <input
                data-bind="
                    value: selectedProduct().selectedQty,
                    init: function () {
                        if (setSelectedQty()) {
                            if(typeof qty != 'undefined' && qty() >= minQty()){
                                selectedQty(qty());
                            }else{
                                selectedQty(minQty() || 1)
                            }
                        };
                    },
                    attr: {
                        'min' : oConfig.useMinimumQuantity && minQty() || oConfig.defaultMinimumQuantity,
                        'step': oConfig.useQuantityIncrement && stepQty() || oConfig.defaultQuantityIncrement,
                        'max' : oConfig.useMaximumQuantity && maxQty()
                    },
                    visible: !isChild() || oConfig.allowChildATC,
                    validateQty
                "
                type="number"
                pattern="\d*"
                class="qty-input"
                placeholder="Qty"
                aria-label="Qty"
            >
            <!-- ko template: {name: 'catalog.product_actions_add', if: selectedProduct().childDisplayType() !== 'matrix-all'} --><!-- /ko -->
        </div>
        <div class="text-error" data-bind="visible: !questionsHaveValidAnswers() && currentParent().rowsAtcAttempted()">Please ensure all required fields are populated.</div>
    <!-- /ko -->

    <!-- ko if: !selectedProduct().showQuickAtc() && !allowQuickAddModal() -->
        <a class="btn btn-primary btn-view-details" data-bind="
            attr: { href: link },
            visible: (isParent()) || ((!isChild() || oConfig.allowChildATC) && (showAtc) && !(oConfig.showChildrenSelection && selectedProduct().isChild())),
            html: oConfig.labels.selectOptionsLabel "></a>
    <!-- /ko -->
    <!-- ko if: !selectedProduct().showQuickAtc() && oConfig.allowQuickAdd && allowQuickAddModal() -->
        <a
            class="btn btn-primary btn-view-details global-modal quick-add"
            data-bind="
                html: oConfig.labels.selectOptionsLabel,
                attr: { href: utils.buildQuickAddLink($data), id: 'quick-add_' + key() },
                visible: (isParent()) || ((!isChild() || oConfig.allowChildATC) && (showAtc) && !(oConfig.showChildrenSelection && selectedProduct().isChild())),
                addModalHandler: { 'element' : $('.quick-add'), 'handler' : function(data){  fncReloadCartWindow();  viewModel.activeCart(data);} }
            "
        ></a>
    <!-- /ko -->
</script>

<script>
    
    var showSearchAll = true;
    $(function(){
        if(showSearchAll){
            var targetKey = '807E241228D542C899F660EF64747BA8';
            var searcAllLink = $('<li class="retail-main-nav__li-3"><a href="' + 'pc_combined_results.asp?shopall=1' + '" class="retail-main-nav__a-3">' + 'Shop  All' + '</a></li>');
            var secondSearcAllLink = $('<li class="retail-main-nav__li-2"><a href="' + 'pc_combined_results.asp?shopall=1' + '" class="retail-main-nav__a-2">' + 'Shop  All' + '</a></li>');

            // Find the menu's LI by data-key and inject into its child UL
            $('li[data-key="' + targetKey + '"] > ul').prepend(searcAllLink);
            $('li[data-key="' + targetKey + '"]').before(secondSearcAllLink);
        }
    });

    registerHook({
        name: "afterGetData",
        func: function(args){
            // must define the observable here as the templates load in before the "afterApplyBindings" hook.
            viewModel.categories = ko.observableArray();
            // Set the shopall flag as an observable for use in templates
            viewModel.isShopAll = ko.observable(getUrlParameter("shopall") === "1");
        }
    });
    
    registerHook({
        name: "afterApplyBindings",
        func: function(emptyObj){
            if(viewModel.results && viewModel.isShopAll()){
                var hideThese = Array("");
                //group items by category (Some values are to be hidden; Force them to blank)
                viewModel.results().forEach(obj => {
                    //Check for values to be hidden: Feature Setting [CUSTOM_CATS_TO_HIDE: ]
                    if(obj.parentCategoryName && obj.parentCategoryName()!="") {
                        if(hideThese.includes(obj.parentCategoryName()) || hideThese.includes(utils.decodeHTML(obj.parentCategoryName()))){
                            obj.parentCategoryName("");
                        }
                    }
                    // Get the parentCategoryName value for this product
                    var category = obj.parentCategoryName ? obj.parentCategoryName() : "";

                    // Lookup the category group by name
                    var categoryItem = viewModel.categories().find(function(cat){
                        return cat.name == category;
                    });

                    // Create the category if it doesn't exist
                    if (!categoryItem) {
                        categoryItem = {name: category, results: [], subcategories: ko.observableArray()};
                        // Add the new category to the view model
                        viewModel.categories().push(categoryItem);
                    }
                    
                    categoryItem.results.push(obj);

                    // Get the childCategoryName value for this product
                    var subcategory = (obj.childCategoryName ? obj.childCategoryName() : "");

                    // Lookup the subcategory group within this category by name
                    var subcategoryItem = categoryItem.subcategories().find(function(group){
                        return group.name == subcategory;
                    });
                    
                    // Create the subcategory if it doesn't exist
                    if (!subcategoryItem) {
                        subcategoryItem = {name: subcategory, products: []};
                        // Add the new subcategory to the category's subcategories observable array
                        categoryItem.subcategories().push(subcategoryItem);
                    }
                    
                    subcategoryItem.products.push(obj);
                });

                viewModel.categories.notifySubscribers();
            }
        }
    });

</script>
    </body>

</html>
