An article on a client script used in CRM which is a working example of rewriting a subform (line items of an invoice) and calls a REST API fuction to return the custom fields of the line items. Pretty much a function which took me a day to write.
Why?
As mentioned, it took me a while to write and I would like to use it as a reference for when I forget how to do this.
The use-case here is that we have a dropdown/picklist on a CRM invoice record that has the options of "Deposit" and "Final Balance". This rewrites the invoice line items to represent a deposit or a final balance invoice with the deposit deducted. Because these line items have custom fields, we need to copy over all the custom values to the new line items.
How?
This is about an hour's work doing it on a CRM workflow (save of record) but here we're going to do it in client script:
Additionally, the line items will be sourced from the quote that was converted to the invoice. For this to work, we've mapped a field on the invoice (not a lookup but a text field) which when a quote gets converted to an invoice, will map over the quote ID to the field "Converted From ID".
Furthermore, fn_quotes_getquoteditems is a custom function (referred to in the code snippet below) in CRM that uses invokeURL to get the line items from the quote as well as the custom fields.
copyraw
/* ******************************************************************************* Function: csfn_Invoice_OnEdit_Type Label: csfn_Invoice_OnEdit_Type Trigger: Triggered on an invoice type change Purpose: Prepares the invoice as a deposit invoice or final balance invoice Inputs: string value Outputs: - Date Created: 2025-03-31 (Joel Lipman) - Initial release Date Modified: ??? - ??? More Information: Any information that may help ******************************************************************************* */ // attempt try { // get the type of invoice selected var v_InvoiceType = ZDK.Page.getField('Invoice_Type').getValue(); // check source quote is specified var v_QuoteID = ZDK.Page.getField('Converted_From_ID').getValue(); // only make changes to the invoice if 1 of the 2 invoice types selected if ((v_InvoiceType == "Deposit" || v_InvoiceType == "Final Balance") && v_QuoteID != null) { // prompt with a confirmation box var b_Proceed = ZDK.Client.showConfirmation('Changing Invoice Type to '+v_InvoiceType+' will re-write the line item(s).\n\nAre you sure you want to proceed?', 'Proceed', 'Cancel'); // if "Proceed" was clicked if (b_Proceed) { // start Loader ZDK.Client.showLoader({type:'page', template:'spinner', message:'Re-writing Invoice Line Items...' }); // reset the subform of Invoiced Items ZDK.Page.getField('Invoiced_Items').setValue([]); // execute REST API function with parameters var o_Params = {}; var o_Headers = {}; o_Params.p_QuoteID = v_QuoteID; o_Params.auth_type = "apikey"; o_Params.zapikey = "1003.<the_zapikey_to_your_function>"; var r_QuotedRecord = ZDK.Apps.CRM.Connections.invoke("j_crm", "https://www.zohoapis.com/crm/v7/functions/fn_quotes_getquoteditems/actions/execute", "GET", 1, o_Params, o_Headers); // now parse the output from the function var o_ParsedQuote = JSON.parse(JSON.stringify(r_QuotedRecord)); // valid? note the underscore before the details variable a_QuotedItems = []; if (o_ParsedQuote._code == "SUCCESS") { if (o_ParsedQuote._details !== undefined) { if (o_ParsedQuote._details.statusMessage !== undefined) { if (o_ParsedQuote._details.statusMessage.details !== undefined) { a_QuotedItems = JSON.parse("[" + o_ParsedQuote._details.statusMessage.details.output + "]"); } } } } // re-initialize var a_NewSuformRows = []; // if final balance, then include the line items from the quote / if deposit, then don't include the original line items if (v_InvoiceType == "Final Balance") { // now loop through the quoted items / line items for (i = 0; i < a_QuotedItems.length; i++){ // initialize new line item var o_NewLineItem = {}; // store this line item as an object o_ThisLineItem = a_QuotedItems[i]; console.log(o_ThisLineItem); // if not specified as optional then this will be transferred to the invoice if (o_ThisLineItem.Optional != "Optional Not Included") { // field mapping of quoted items to invoiced items (i console logged out this line item and copied each of the fields hoping they had the same API name in both modules) o_NewLineItem.CUSTOM = o_ThisLineItem.CUSTOM; o_NewLineItem.Config = o_ThisLineItem.Config; o_NewLineItem.Cost_Line_Item_Total = o_ThisLineItem.Cost_Line_Item_Total; o_NewLineItem.Description = o_ThisLineItem.Description; o_NewLineItem.Discount1 = o_ThisLineItem.Discount1; o_NewLineItem.Discount_Line_Item_Total = o_ThisLineItem.Discount_Line_Item_Total; o_NewLineItem.Exchange_Rates_1 = o_ThisLineItem.Exchange_Rates_1; o_NewLineItem.List_Price = o_ThisLineItem.List_Price; o_NewLineItem.Manufacturer = o_ThisLineItem.Manufacturer; o_NewLineItem.Margin = o_ThisLineItem.Margin; o_NewLineItem.Net_Total = o_ThisLineItem.Net_Total; o_NewLineItem.Product_Name = o_ThisLineItem.Product_Name; o_NewLineItem.Purchase_Price = o_ThisLineItem.Purchase_Price; o_NewLineItem.Purchase_Price_Euro = o_ThisLineItem.Purchase_Price_Euro; o_NewLineItem.Quantity = o_ThisLineItem.Quantity; o_NewLineItem.Sub_Total = o_ThisLineItem.Sub_Total; o_NewLineItem.VAT1 = o_ThisLineItem.VAT1; o_NewLineItem.VAT_Line_Item_Total = o_ThisLineItem.VAT_Line_Item_Total; o_NewLineItem.Vendor_Currency = o_ThisLineItem.Vendor_Currency; // going to create a new set of line items a_NewSuformRows.push(o_NewLineItem); } } } // determine deposit amount // get grand total (note that formula for grand total excludes line items which were optional) v_QuoteGrandTotal = 0.00; r_QuoteDetails = ZDK.Apps.CRM.Quotes.fetchById(v_QuoteID); if (r_QuoteDetails.Grand_Total != undefined) { v_QuoteGrandTotal = r_QuoteDetails.Grand_Total; } // determine deposit amount (half of grand total) but on the final balance invoice, we are deducting the deposit v_DepositAmount = Math.round((v_QuoteGrandTotal / 2) * 100) / 100; // conditions based on the invoice type if (v_InvoiceType == "Final Balance") { v_DepositAmount = -Math.abs(v_DepositAmount); } // insert a product line item called "Deposit" var o_NewLineItem = {}; // the record ID of the product "Deposit" is inserted into Product_Name o_NewLineItem.List_Price = v_DepositAmount; o_NewLineItem.Product_Name = {}; o_NewLineItem.Product_Name.name = "Deposit"; o_NewLineItem.Product_Name.id = "123456000009876543"; o_NewLineItem.Quantity = 1; o_NewLineItem.VAT1 = o_ThisLineItem.VAT1; a_NewSuformRows.push(o_NewLineItem); // write back the new line items to the invoice record "Invoiced_Items" var o_NewSubformRows = JSON.parse(JSON.stringify(a_NewSuformRows)); ZDK.Page.getField('Invoiced_Items').setValue(o_NewSubformRows); // end loader ZDK.Client.hideLoader(); ZDK.Client.showMessage('Invoice Line Items re-written successfully', { type: 'success' }); } } } catch (e) { // return error (don't display it, just show it in the logs) console.log(e); //ZDK.Client.showMessage("Client Script error"); }
- /* *******************************************************************************
- Function: csfn_Invoice_OnEdit_Type
- Label: csfn_Invoice_OnEdit_Type
- Trigger: Triggered on an invoice type change
- Purpose: Prepares the invoice as a deposit invoice or final balance invoice
- Inputs: string value
- Outputs: -
- Date Created: 2025-03-31 (Joel Lipman)
- - Initial release
- Date Modified: ???
- - ???
- More Information:
- Any information that may help
- ******************************************************************************* */
- // attempt
- try {
- // get the type of invoice selected
- var v_InvoiceType = ZDK.Page.getField('Invoice_Type').getValue();
- // check source quote is specified
- var v_QuoteID = ZDK.Page.getField('Converted_From_ID').getValue();
- // only make changes to the invoice if 1 of the 2 invoice types selected
- if ((v_InvoiceType == "Deposit" || v_InvoiceType == "Final Balance") && v_QuoteID != null) {
- // prompt with a confirmation box
- var b_Proceed = ZDK.Client.showConfirmation('Changing Invoice Type to '+v_InvoiceType+' will re-write the line item(s).\n\nAre you sure you want to proceed?', 'Proceed', 'Cancel');
- // if "Proceed" was clicked
- if (b_Proceed) {
- // start Loader
- ZDK.Client.showLoader({type:'page', template:'spinner', message:'Re-writing Invoice Line Items...' });
- // reset the subform of Invoiced Items
- ZDK.Page.getField('Invoiced_Items').setValue([]);
- // execute REST API function with parameters
- var o_Params = {};
- var o_Headers = {};
- o_Params.p_QuoteID = v_QuoteID;
- o_Params.auth_type = "apikey";
- o_Params.zapikey = "1003.<the_zapikey_to_your_function>";
- var r_QuotedRecord = ZDK.Apps.crm.Connections.invoke("j_crm", "https://www.zohoapis.com/crm/v7/functions/fn_quotes_getquoteditems/actions/execute", "GET", 1, o_Params, o_Headers);
- // now parse the output from the function
- var o_ParsedQuote = JSON.parse(JSON.stringify(r_QuotedRecord));
- // valid? note the underscore before the details variable
- a_QuotedItems = [];
- if (o_ParsedQuote._code == "SUCCESS") {
- if (o_ParsedQuote._details !== undefined) {
- if (o_ParsedQuote._details.statusMessage !== undefined) {
- if (o_ParsedQuote._details.statusMessage.details !== undefined) {
- a_QuotedItems = JSON.parse("[" + o_ParsedQuote._details.statusMessage.details.output + "]");
- }
- }
- }
- }
- // re-initialize
- var a_NewSuformRows = [];
- // if final balance, then include the line items from the quote / if deposit, then don't include the original line items
- if (v_InvoiceType == "Final Balance") {
- // now loop through the quoted items / line items
- for (i = 0; i < a_QuotedItems.length; i++){
- // initialize new line item
- var o_NewLineItem = {};
- // store this line item as an object
- o_ThisLineItem = a_QuotedItems[i];
- console.log(o_ThisLineItem);
- // if not specified as optional then this will be transferred to the invoice
- if (o_ThisLineItem.Optional != "Optional Not Included") {
- // field mapping of quoted items to invoiced items (i console logged out this line item and copied each of the fields hoping they had the same API name in both modules)
- o_NewLineItem.CUSTOM = o_ThisLineItem.CUSTOM;
- o_NewLineItem.Config = o_ThisLineItem.Config;
- o_NewLineItem.Cost_Line_Item_Total = o_ThisLineItem.Cost_Line_Item_Total;
- o_NewLineItem.Description = o_ThisLineItem.Description;
- o_NewLineItem.Discount1 = o_ThisLineItem.Discount1;
- o_NewLineItem.Discount_Line_Item_Total = o_ThisLineItem.Discount_Line_Item_Total;
- o_NewLineItem.Exchange_Rates_1 = o_ThisLineItem.Exchange_Rates_1;
- o_NewLineItem.List_Price = o_ThisLineItem.List_Price;
- o_NewLineItem.Manufacturer = o_ThisLineItem.Manufacturer;
- o_NewLineItem.Margin = o_ThisLineItem.Margin;
- o_NewLineItem.Net_Total = o_ThisLineItem.Net_Total;
- o_NewLineItem.Product_Name = o_ThisLineItem.Product_Name;
- o_NewLineItem.Purchase_Price = o_ThisLineItem.Purchase_Price;
- o_NewLineItem.Purchase_Price_Euro = o_ThisLineItem.Purchase_Price_Euro;
- o_NewLineItem.Quantity = o_ThisLineItem.Quantity;
- o_NewLineItem.Sub_Total = o_ThisLineItem.Sub_Total;
- o_NewLineItem.VAT1 = o_ThisLineItem.VAT1;
- o_NewLineItem.VAT_Line_Item_Total = o_ThisLineItem.VAT_Line_Item_Total;
- o_NewLineItem.Vendor_Currency = o_ThisLineItem.Vendor_Currency;
- // going to create a new set of line items
- a_NewSuformRows.push(o_NewLineItem);
- }
- }
- }
- // determine deposit amount
- // get grand total (note that formula for grand total excludes line items which were optional)
- v_QuoteGrandTotal = 0.00;
- r_QuoteDetails = ZDK.Apps.crm.Quotes.fetchById(v_QuoteID);
- if (r_QuoteDetails.Grand_Total != undefined) {
- v_QuoteGrandTotal = r_QuoteDetails.Grand_Total;
- }
- // determine deposit amount (half of grand total) but on the final balance invoice, we are deducting the deposit
- v_DepositAmount = Math.round((v_QuoteGrandTotal / 2) * 100) / 100;
- // conditions based on the invoice type
- if (v_InvoiceType == "Final Balance") {
- v_DepositAmount = -Math.abs(v_DepositAmount);
- }
- // insert a product line item called "Deposit"
- var o_NewLineItem = {};
- // the record ID of the product "Deposit" is inserted into Product_Name
- o_NewLineItem.List_Price = v_DepositAmount;
- o_NewLineItem.Product_Name = {};
- o_NewLineItem.Product_Name.name = "Deposit";
- o_NewLineItem.Product_Name.id = "123456000009876543";
- o_NewLineItem.Quantity = 1;
- o_NewLineItem.VAT1 = o_ThisLineItem.VAT1;
- a_NewSuformRows.push(o_NewLineItem);
- // write back the new line items to the invoice record "Invoiced_Items"
- var o_NewSubformRows = JSON.parse(JSON.stringify(a_NewSuformRows));
- ZDK.Page.getField('Invoiced_Items').setValue(o_NewSubformRows);
- // end loader
- ZDK.Client.hideLoader();
- ZDK.Client.showMessage('Invoice Line Items re-written successfully', { type: 'success' });
- }
- }
- } catch (e) {
- // return error (don't display it, just show it in the logs)
- console.log(e);
- //ZDK.Client.showMessage("Client Script error");
- }
standalone.fn_Quotes_GetQuotedItems
If you really need it although this is the basic CRM function to return the line items from the quote using InvokeURL so as to use CRM API v7 and get the custom fields as well:
copyraw
string standalone.fn_Quotes_GetQuotedItems(Int p_QuoteID) { /* ******************************************************************************* Function: string standalone.fn_Quotes_GetQuotedItems(int p_QuoteID) Label: Fn - Quotes - Get Quoted Items Trigger: Triggered by a client script to retrieve quoted items Purpose: Standard Product_Details can be returned in client script. The purpose of this function is to retrieve the crm v2.1 Quoted_Items (custom fields of a line item) Inputs: int p_QuoteID Outputs: - Date Created: 2025-03-31 (Joel Lipman) - Initial release Date Modified: 2025-03-31 (Joel Lipman) - Removed crmAPIResponse. Returns as list (JSON string) More Information: Any information that may help ******************************************************************************* */ // // initialize l_QuotedItems = List(); // // fetch this record r_QuoteDetails = invokeurl [ url :"https://www.zohoapis.com/crm/v7/Quotes/" + p_QuoteID + "?fields=Quoted_Items" type :GET connection:"j_crm" ]; // // should only be 1 record but respecting JSON data structure and parse l_Datas = ifnull(r_QuoteDetails.get("data"),Map()); for each m_Data in l_Datas { l_QuotedItems = ifnull(m_Data.get("Quoted_Items"),List()); break; } // return l_QuotedItems; }
- string standalone.fn_Quotes_GetQuotedItems(Int p_QuoteID)
- {
- /* *******************************************************************************
- Function: string standalone.fn_Quotes_GetQuotedItems(int p_QuoteID)
- Label: Fn - Quotes - Get Quoted Items
- Trigger: Triggered by a client script to retrieve quoted items
- Purpose: Standard Product_Details can be returned in client script.
- The purpose of this function is to retrieve the crm v2.1 Quoted_Items (custom fields of a line item)
- Inputs: int p_QuoteID
- Outputs: -
- Date Created: 2025-03-31 (Joel Lipman)
- - Initial release
- Date Modified: 2025-03-31 (Joel Lipman)
- - Removed crmAPIResponse. Returns as list (JSON string)
- More Information:
- Any information that may help
- ******************************************************************************* */
- //
- // initialize
- l_QuotedItems = List();
- //
- // fetch this record
- r_QuoteDetails = invokeUrl
- [
- url :"https://www.zohoapis.com/crm/v7/Quotes/" + p_QuoteID + "?fields=Quoted_Items"
- type :GET
- connection:"j_crm"
- ];
- //
- // should only be 1 record but respecting JSON data structure and parse
- l_Datas = ifnull(r_QuoteDetails.get("data"),Map());
- for each m_Data in l_Datas
- {
- l_QuotedItems = ifnull(m_Data.get("Quoted_Items"),List());
- break;
- }
- //
- return l_QuotedItems;
- }
Category: Zoho :: Article: 902
Add comment