Dynamic Budget Calculations in the Cart Page

Overview

This document outlines the implementation of dynamic budget calculations for item groups within the shopping cart, along with the development and enhancement of real-time budget updates. It captures all code modifications related to the project.

Implement dynamic budget calculations for each item group and ensure the real-time update of the remaining balance in the cart’s header. This task focuses on functionality related to budget display and ensuring changes in the cart are reflected in real-time.

  1. Implement Budget Display for Item Groups
  • Implement the same logic to calculate and display the remaining budget for each Item Group in the header section.
  • Ensure the budget is calculated separately for each group and displayed alongside the items in the cart.
  1. Real-Time Budget Updates
  • Develop functionality to update the remaining budget dynamically when items are added, removed, or updated in the cart.
  • Ensure that changes are reflected in the header with minimal delay, even with slight processing time.
  • Optimize the cart page performance to minimize the delay in updating the remaining balance

Solution Overview

The solution consists of the following components:

  1. Backend Functionality:
  • Implement logic for real-time retrieval and updates of item group balances.
  • Use SuiteScript to fetch and pass budget details to the frontend, leveraging saved searches and logging error messages for any missing information.
  1. Frontend Integration :
  • Modify the Cart.Detailed.View.Site.js file to display the remaining budget for each item category and categories the items in each category as separate sections  
  • There are two custom records in the NetSuite account for handling the Category and Budget associated with each customers

GED Category: Custom Record

Purpose: This record stores a list of items assigned to each category, along with the customers who have access to them.

  • The parent customer selects the CLUB NAME (PARENT CUSTOMER) field to assign sub-customers to the category record automatically. This identifies the customer who requires the specific category.
  • The Items Tab in the GED Item Category record allows item selection for each category.
  • Customer Record Integration: The client can view or update the assigned categories from the customer record.

GED Budget:Budget Custom Record

  • Purpose: This record tracks the budget’s name, cycle, amount, remaining balance, and associated customer.
  • Customer Record Integration: Budget details are accessible within the customer record for straightforward management.

Steps to Implement SolutionCode Modifications

File: Cart.Detailed.View.Site.js

  • Extended the getContext method to integrate category details from the user’s profile.
  • Implemented logic to categorize items within the cart based on defined categories.
  • Incorporated budget calculations after accounting for item costs, including tax rates.
  • Added handling for empty categories and exceptional items that do not belong to any category.

javascriptdefine('Cart.Detailed.View.Site', [

  'Cart.Detailed.View',

  'jQuery',

  'underscore',

  'ProductDetails.AddToProductList.View',

  'Profile.Model',

  'Utils',

  'Cart.Summary.View',

  'Backbone.CollectionView',

  'Cart.Item.Summary.View'

  , 'Cart.Item.Actions.View',

  'Cart.Lines.View'

], function (

  CartDetailedView,

  jQuery,

  _,

  ProductDetailsAddToProductListView,

  ProfileModel,

  Utils,

  CartSummaryView,

  BackboneCollectionView,

  CartItemSummaryView

  , CartItemActionsView,

  CartLinesView

) {

  'use strict';

  var profile = ProfileModel.getInstance();

  var prototype = CartDetailedView.prototype;

  _.extend(prototype, {

    childViews: {

      'Cart.Summary': function () {

        return new CartSummaryView({

          model: this.model

          , showEstimated: this.showEstimated

          , application: this.application

        });

      }

      , 'Item.ListNavigable': function () {

        var delegateProfile;

        var categories, remainingBudget;

        delegateProfile = profile.get('delegateProfile') ? profile.get('delegateProfile') : null;

        categories = delegateProfile.categoryDetails ? delegateProfile.categoryDetails : null;

        var itemLines = this.model.get('lines').models;

        var categorizedItems = [];

        var exceptionalItems = []; // For items that don't belong to any category

        if (categories.length > 0) {

          categories.forEach(function (category) {

            remainingBudget = category.balance;

            var categoryItems = [];

            // Loop through item lines and find matching items for this category

            itemLines.forEach(function (itemLine) {

              var itemCategory = itemLine.get('item').get('custitem_jj_ged_itemgrp');

              itemLine.set('type', 'item');

              var itemCategoryArray = itemCategory ? itemCategory.split(',').map(function (cat) {

                return cat.trim();

              }) : [];

              var isCategoryMatched = itemCategoryArray.find(function (itemCat) {

                return itemCat === category.category;

              });

              // If item matches the category, add it to the category's items

              if (isCategoryMatched) {

                var itemCost = parseFloat(itemLine.get('total'));

                var taxRate1 = parseFloat(itemLine.get('tax_rate1')) || 0;

                var taxRate2 = parseFloat(itemLine.get('tax_rate2')) || 0;

                var taxAmount1 = (taxRate1 / 100) * itemCost;

                var taxAmount2 = (taxRate2 / 100) * itemCost;

                itemCost += taxAmount1 + taxAmount2;

                remainingBudget = parseFloat((remainingBudget - itemCost).toFixed(2));

                categoryItems.push(itemLine);

              }

            });

            category.newBalance = Utils.formatCurrency(remainingBudget);

            category.amount = Utils.formatCurrency(category.amount);

            var tempcategoryItems = [];

            tempcategoryItems.push({

              type: 'categoryHeading',

              categoryName: category.category,

              remainingBudget: category.newBalance,

              amount: category.amount

            });

            tempcategoryItems = new Backbone.Collection(tempcategoryItems.map(function (child) {

              return new Backbone.Model(child);

            }));

            // Check if there are no items under this category

            if (categoryItems.length != 0) {

              categorizedItems.push(tempcategoryItems.models[0]);

              // Add the categorized items under the category heading

              categoryItems.forEach(function (item) {

                categorizedItems.push(item);

              });

            }

          });

          // Handle items that do not belong to any category

          itemLines.forEach(function (itemLine) {

            var itemCategory = itemLine.get('item').get('custitem_jj_ged_itemgrp');

            itemLine.set('type', 'item');

            var itemCategoryArray = itemCategory ? itemCategory.split(',').map(function (cat) {

              return cat.trim();

            }) : [];

            var isItemCategorized = categories.some(function (category) {

              return itemCategoryArray.includes(category.category);

            });

            if (!isItemCategorized) {

              exceptionalItems.push(itemLine);

            }

          });

          if (exceptionalItems.length > 0) {

            var tempcategoryItems = [];

            tempcategoryItems.push({

              type: 'categoryHeading',

              categoryName: 'Exceptional Items',

              remainingBudget: '',

              amount: ''

            });

            tempcategoryItems = new Backbone.Collection(tempcategoryItems.map(function (child) {

              return new Backbone.Model(child);

            }));

            categorizedItems.push(tempcategoryItems.models[0])

            exceptionalItems.forEach(function (item) {

              categorizedItems.push(item);

            });

          }

        } else {

          categorizedItems = this.model.get('lines');

        }

        return new BackboneCollectionView({

          collection: categorizedItems

          , viewsPerRow: 1

          , childView: CartLinesView

          , childViewOptions: {

            navigable: true

            , application: this.application

            , SummaryView: CartItemSummaryView

            , ActionsView: CartItemActionsView

            , showAlert: false

          }

        });

      }

    },

    getContext: _.wrap(prototype.getContext, function (fn) {

      var context = fn.apply(this, _.toArray(arguments).slice(1));

      try {

        var customerAdmin = _.find(profile.get('customfields'), function (field) { return field.name === 'custentity_dp_customer_admin' });

        context.customerGroup = customerAdmin && customerAdmin.value === '6080' ? false : context.customerGroup;

      } catch (error) {

        console.error('Error @ Cart.Detailed.View', error);

      }

      return context;

    })

  });

  // remove AddAllToProductList child view

  /*

  _.extend(prototype, {

    childViews: _.extend(prototype.childViews, {

      'AddAllToProductList': function() {

        var cartItems = this.model.get('lines').models;

        _.each(cartItems, function(item) {

          item.areAttributesValid = function areAttributesValid(attributes, validators) {

            var current_validation = _.extend({}, this.validation);

            _.extend(this.validation, validators || {});

            var result = this.isValid(attributes);

            this.validation = current_validation;

            return result;

          }

        });

        return new ProductDetailsAddToProductListView({

          model: cartItems[0],

          allCartItems: cartItems,

          application: this.options.application

        });

      }

    })

  })

  */

});

image-20241021-084609.png

Deployment

The files updated for implementing the customization are added below:

  • Cart.Detailed.View.Site.js
  • Cart.Lines.View.Site.js
  • cart_lines.tpl
  • Cart.Lines.View.js

Leave a comment

Your email address will not be published. Required fields are marked *