For Zoho Services only:


I'm actually part of something bigger at Ascent Business Solutions recognized as the top Zoho Premium Solutions Partner in the United Kingdom.

Ascent Business Solutions offer support for smaller technical fixes and projects for larger developments, such as migrating to a ZohoCRM.  A team rather than a one-man-band is always available to ensure seamless progress and address any concerns. You'll find our competitive support rates with flexible, no-expiration bundles at https://ascentbusiness.co.uk/zoho-services/uk-zoho-support.  For larger projects, talk to our experts and receive dedicated support from our hands-on project consultants at https://ascentbusiness.co.uk/zoho-services/zoho-crm-implementation.

The team I manage specializes in coding API integrations between Zoho and third-party finance/commerce suites such as Xero, Shopify, WooCommerce, and eBay; to name but a few.  Our passion lies in creating innovative solutions where others have fallen short as well as working with new businesses, new sectors, and new ideas.  Our success is measured by the growth and ROI we deliver for clients, such as transforming a garden shed hobby into a 250k monthly turnover operation or generating a +60% return in just three days after launch through online payments and a streamlined e-commerce solution, replacing a paper-based system.

If you're looking for a partner who can help you drive growth and success, we'd love to work with you.  You can reach out to us on 0121 392 8140 (UK) or info@ascentbusiness.co.uk.  You can also visit our website at https://ascentbusiness.co.uk.
ZohoCRM Client Script: On Change of Dropdown: Subform Rewrite: REST Function

ZohoCRM Client Script: On Change of Dropdown: Subform Rewrite: REST Function

What?
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");

}
  1.  /* ******************************************************************************* 
  2.      Function:       csfn_Invoice_OnEdit_Type 
  3.      Label:          csfn_Invoice_OnEdit_Type 
  4.      Trigger:        Triggered on an invoice type change 
  5.      Purpose:        Prepares the invoice as a deposit invoice or final balance invoice 
  6.      Inputs:         string value 
  7.      Outputs:        - 
  8.   
  9.      Date Created:   2025-03-31 (Joel Lipman) 
  10.                      - Initial release 
  11.   
  12.      Date Modified:    ??? 
  13.                      - ??? 
  14.   
  15.      More Information: 
  16.                      Any information that may help 
  17.   
  18.  ******************************************************************************* */ 
  19.  // attempt 
  20.  try { 
  21.   
  22.      // get the type of invoice selected 
  23.      var v_InvoiceType = ZDK.Page.getField('Invoice_Type').getValue()
  24.   
  25.      // check source quote is specified 
  26.      var v_QuoteID = ZDK.Page.getField('Converted_From_ID').getValue()
  27.   
  28.      // only make changes to the invoice if 1 of the 2 invoice types selected 
  29.      if ((v_InvoiceType == "Deposit" || v_InvoiceType == "Final Balance") && v_QuoteID != null) { 
  30.   
  31.          // prompt with a confirmation box 
  32.          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')
  33.   
  34.          // if "Proceed" was clicked 
  35.          if (b_Proceed) { 
  36.   
  37.              // start Loader 
  38.              ZDK.Client.showLoader({type:'page', template:'spinner', message:'Re-writing Invoice Line Items...' })
  39.   
  40.              // reset the subform of Invoiced Items 
  41.              ZDK.Page.getField('Invoiced_Items').setValue([])
  42.   
  43.              // execute REST API function with parameters 
  44.              var o_Params = {}
  45.              var o_Headers = {}
  46.              o_Params.p_QuoteID = v_QuoteID; 
  47.              o_Params.auth_type = "apikey"
  48.              o_Params.zapikey = "1003.<the_zapikey_to_your_function>"
  49.              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)
  50.   
  51.              // now parse the output from the function 
  52.              var o_ParsedQuote = JSON.parse(JSON.stringify(r_QuotedRecord))
  53.   
  54.              // valid?  note the underscore before the details variable 
  55.              a_QuotedItems = []
  56.              if (o_ParsedQuote._code == "SUCCESS") { 
  57.                  if (o_ParsedQuote._details !== undefined) { 
  58.                      if (o_ParsedQuote._details.statusMessage !== undefined) { 
  59.                          if (o_ParsedQuote._details.statusMessage.details !== undefined) { 
  60.                              a_QuotedItems = JSON.parse("[" + o_ParsedQuote._details.statusMessage.details.output + "]")
  61.                          } 
  62.                      } 
  63.                  } 
  64.              } 
  65.   
  66.              // re-initialize 
  67.              var a_NewSuformRows = []
  68.   
  69.              // if final balance, then include the line items from the quote / if deposit, then don't include the original line items 
  70.              if (v_InvoiceType == "Final Balance") { 
  71.   
  72.                  // now loop through the quoted items / line items 
  73.                  for (i = 0; i < a_QuotedItems.length; i++){ 
  74.   
  75.                      // initialize new line item 
  76.                      var o_NewLineItem = {}
  77.   
  78.                      // store this line item as an object 
  79.                      o_ThisLineItem = a_QuotedItems[i]
  80.                      console.log(o_ThisLineItem)
  81.   
  82.                      // if not specified as optional then this will be transferred to the invoice 
  83.                      if (o_ThisLineItem.Optional != "Optional Not Included") { 
  84.   
  85.                          // 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) 
  86.                          o_NewLineItem.CUSTOM = o_ThisLineItem.CUSTOM; 
  87.                          o_NewLineItem.Config = o_ThisLineItem.Config; 
  88.                          o_NewLineItem.Cost_Line_Item_Total = o_ThisLineItem.Cost_Line_Item_Total; 
  89.                          o_NewLineItem.Description = o_ThisLineItem.Description; 
  90.                          o_NewLineItem.Discount1 = o_ThisLineItem.Discount1; 
  91.                          o_NewLineItem.Discount_Line_Item_Total = o_ThisLineItem.Discount_Line_Item_Total; 
  92.                          o_NewLineItem.Exchange_Rates_1 = o_ThisLineItem.Exchange_Rates_1; 
  93.                          o_NewLineItem.List_Price = o_ThisLineItem.List_Price; 
  94.                          o_NewLineItem.Manufacturer = o_ThisLineItem.Manufacturer; 
  95.                          o_NewLineItem.Margin = o_ThisLineItem.Margin; 
  96.                          o_NewLineItem.Net_Total = o_ThisLineItem.Net_Total; 
  97.                          o_NewLineItem.Product_Name = o_ThisLineItem.Product_Name; 
  98.                          o_NewLineItem.Purchase_Price = o_ThisLineItem.Purchase_Price; 
  99.                          o_NewLineItem.Purchase_Price_Euro = o_ThisLineItem.Purchase_Price_Euro; 
  100.                          o_NewLineItem.Quantity = o_ThisLineItem.Quantity; 
  101.                          o_NewLineItem.Sub_Total = o_ThisLineItem.Sub_Total; 
  102.                          o_NewLineItem.VAT1 = o_ThisLineItem.VAT1; 
  103.                          o_NewLineItem.VAT_Line_Item_Total = o_ThisLineItem.VAT_Line_Item_Total; 
  104.                          o_NewLineItem.Vendor_Currency = o_ThisLineItem.Vendor_Currency; 
  105.   
  106.                          // going to create a new set of line items 
  107.                          a_NewSuformRows.push(o_NewLineItem)
  108.   
  109.                      } 
  110.   
  111.                  } 
  112.   
  113.              } 
  114.   
  115.              // determine deposit amount 
  116.              // get grand total (note that formula for grand total excludes line items which were optional) 
  117.              v_QuoteGrandTotal = 0.00
  118.              r_QuoteDetails = ZDK.Apps.crm.Quotes.fetchById(v_QuoteID)
  119.              if (r_QuoteDetails.Grand_Total != undefined) { 
  120.                  v_QuoteGrandTotal = r_QuoteDetails.Grand_Total; 
  121.              } 
  122.   
  123.              // determine deposit amount (half of grand total) but on the final balance invoice, we are deducting the deposit 
  124.              v_DepositAmount = Math.round((v_QuoteGrandTotal / 2) * 100) / 100
  125.   
  126.              // conditions based on the invoice type 
  127.              if (v_InvoiceType == "Final Balance") { 
  128.                  v_DepositAmount = -Math.abs(v_DepositAmount)
  129.              } 
  130.   
  131.              // insert a product line item called "Deposit" 
  132.              var o_NewLineItem = {}
  133.   
  134.              // the record ID of the product "Deposit" is inserted into Product_Name 
  135.              o_NewLineItem.List_Price = v_DepositAmount; 
  136.              o_NewLineItem.Product_Name = {}
  137.              o_NewLineItem.Product_Name.name = "Deposit"
  138.              o_NewLineItem.Product_Name.id = "123456000009876543"
  139.              o_NewLineItem.Quantity = 1
  140.              o_NewLineItem.VAT1 = o_ThisLineItem.VAT1; 
  141.              a_NewSuformRows.push(o_NewLineItem)
  142.   
  143.              // write back the new line items to the invoice record "Invoiced_Items" 
  144.              var o_NewSubformRows = JSON.parse(JSON.stringify(a_NewSuformRows))
  145.              ZDK.Page.getField('Invoiced_Items').setValue(o_NewSubformRows)
  146.   
  147.              // end loader 
  148.              ZDK.Client.hideLoader()
  149.              ZDK.Client.showMessage('Invoice Line Items re-written successfully', { type: 'success' })
  150.   
  151.          } 
  152.   
  153.      } 
  154.   
  155.  } catch (e) { 
  156.   
  157.       // return error (don't display it, just show it in the logs) 
  158.      console.log(e)
  159.       //ZDK.Client.showMessage("Client Script error")
  160.   
  161.  } 

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;
}
  1.  string standalone.fn_Quotes_GetQuotedItems(Int p_QuoteID) 
  2.  { 
  3.  /* ******************************************************************************* 
  4.          Function:       string standalone.fn_Quotes_GetQuotedItems(int p_QuoteID) 
  5.          Label:          Fn - Quotes - Get Quoted Items 
  6.          Trigger:        Triggered by a client script to retrieve quoted items 
  7.          Purpose:        Standard Product_Details can be returned in client script. 
  8.                          The purpose of this function is to retrieve the crm v2.1 Quoted_Items (custom fields of a line item) 
  9.          Inputs:         int p_QuoteID 
  10.          Outputs:        - 
  11.   
  12.          Date Created:   2025-03-31 (Joel Lipman) 
  13.                          - Initial release 
  14.   
  15.          Date Modified:    2025-03-31 (Joel Lipman) 
  16.                          - Removed crmAPIResponse.  Returns as list (JSON string) 
  17.   
  18.          More Information: 
  19.                          Any information that may help 
  20.   
  21.      ******************************************************************************* */ 
  22.      // 
  23.      // initialize 
  24.      l_QuotedItems = List()
  25.      // 
  26.      // fetch this record 
  27.      r_QuoteDetails = invokeUrl 
  28.      [ 
  29.          url :"https://www.zohoapis.com/crm/v7/Quotes/" + p_QuoteID + "?fields=Quoted_Items" 
  30.          type :GET 
  31.          connection:"j_crm" 
  32.      ]
  33.      // 
  34.      // should only be 1 record but respecting JSON data structure and parse 
  35.      l_Datas = ifnull(r_QuoteDetails.get("data"),Map())
  36.      for each  m_Data in l_Datas 
  37.      { 
  38.          l_QuotedItems = ifnull(m_Data.get("Quoted_Items"),List())
  39.          break
  40.      } 
  41.      // 
  42.      return l_QuotedItems; 
  43.  } 

Category: Zoho :: Article: 902

Add comment

Your rating:

Submit

Credit where Credit is Due:


Feel free to copy, redistribute and share this information. All that we ask is that you attribute credit and possibly even a link back to this website as it really helps in our search engine rankings.

Disclaimer: Please note that the information provided on this website is intended for informational purposes only and does not represent a warranty. The opinions expressed are those of the author only. We recommend testing any solutions in a development environment before implementing them in production. The articles are based on our good faith efforts and were current at the time of writing, reflecting our practical experience in a commercial setting.

Thank you for visiting and, as always, we hope this website was of some use to you!

Kind Regards,

Joel Lipman
www.joellipman.com

Please publish modules in offcanvas position.