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.
- 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.
- 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:
- 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.
- Frontend Integration :
- Modify the
Cart.Detailed.View.Site.jsfile 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
getContextmethod 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
});
}
})
})
*/
});
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