Print

Zoho CRM: Update a custom field in line items / product details using REST API v2.1

What?
This is a quick article documenting how to update custom fields in a line items or product details section of a transactional module such as Quotes, Sales Orders or Invoices using code: Zoho Deluge.

Why?
At time of print, Zoho had recently introduced the ability to have custom fields in your line items, alongside the product name, list price, quantity, tax, etc. In the example below, we have added a column called "Group Name" in the CRM Quote module as per the following screenshot:
Populating Custom Field in a Quote Line Item

How?
Again at the time of this article, this is only modifiable when using REST API v2.1. We are going to update the field with label "Group Name" but API name "Grouping" in a list item column.

The standard code you used to use even for REST API v2.0 would have been something like the following:
copyraw
//
// init
l_CrmLineItems = List();
//
// some sample values
v_CrmProductID = "123456789012345678";
//
// build up product details JSON to send
m_LineItemProduct = Map();
m_LineItemProduct.put("id",v_CrmProductID);
m_LineItem = Map();
m_LineItem.put("product",m_LineItemProduct);
m_LineItem.put("quantity",5);
m_LineItem.put("list_price",0.99);
v_LineItemTax = 0.99 * 5 * 0.2;
m_LineItem.put("Tax",v_LineItemTax);
if(v_LineItemTax>0)
{
	l_TaxOptions = List();
	m_TaxOption1 = Map();
	m_TaxOption1.put("percentage",20);
	m_TaxOption1.put("name","Sales Tax");
	m_TaxOption1.put("value",v_LineItemTax);
	l_TaxOptions.add(m_TaxOption1);
	m_LineItem.put("line_tax",l_TaxOptions);
}
m_LineItem.put("total",5.94);
m_LineItem.put("product_description","My Test Description");
l_CrmLineItems.add(m_LineItem);
//
m_CreateQuote = Map();
m_CreateQuote.put("Subject","My Test Quote");
m_CreateQuote.put("Product_Details",l_CrmLineItems);
  1.  // 
  2.  // init 
  3.  l_CrmLineItems = List()
  4.  // 
  5.  // some sample values 
  6.  v_CrmProductID = "123456789012345678"
  7.  // 
  8.  // build up product details JSON to send 
  9.  m_LineItemProduct = Map()
  10.  m_LineItemProduct.put("id",v_CrmProductID)
  11.  m_LineItem = Map()
  12.  m_LineItem.put("product",m_LineItemProduct)
  13.  m_LineItem.put("quantity",5)
  14.  m_LineItem.put("list_price",0.99)
  15.  v_LineItemTax = 0.99 * 5 * 0.2
  16.  m_LineItem.put("Tax",v_LineItemTax)
  17.  if(v_LineItemTax>0) 
  18.  { 
  19.      l_TaxOptions = List()
  20.      m_TaxOption1 = Map()
  21.      m_TaxOption1.put("percentage",20)
  22.      m_TaxOption1.put("name","Sales Tax")
  23.      m_TaxOption1.put("value",v_LineItemTax)
  24.      l_TaxOptions.add(m_TaxOption1)
  25.      m_LineItem.put("line_tax",l_TaxOptions)
  26.  } 
  27.  m_LineItem.put("total",5.94)
  28.  m_LineItem.put("product_description","My Test Description")
  29.  l_CrmLineItems.add(m_LineItem)
  30.  // 
  31.  m_CreateQuote = Map()
  32.  m_CreateQuote.put("Subject","My Test Quote")
  33.  m_CreateQuote.put("Product_Details",l_CrmLineItems)
Which when converted and sent in JSON would look something like the following request:
copyraw
{
  "Subject": "My Test Quote",
  "Product_Details": [
    {
      "product": {
        "Product_Code": "TEST1",
        "Currency": "GBP",
        "name": "My Test Product",
        "id": "123456789012345678"
      },
      "quantity": 5,
      "Discount": 0,
      "Tax": 0.99,
      "list_price": 0.99,
      "total": 5.94,
      "product_description": "My Test Description",
      "line_tax": [
      {
        "percentage": 20,
        "name": "Sales Tax",
        "value": 0.99
      }
      ]
    }
  ]
}
  1.  { 
  2.    "Subject": "My Test Quote", 
  3.    "Product_Details": [ 
  4.      { 
  5.        "product": { 
  6.          "Product_Code": "TEST1", 
  7.          "Currency": "GBP", 
  8.          "name": "My Test Product", 
  9.          "id": "123456789012345678" 
  10.        }, 
  11.        "quantity": 5, 
  12.        "Discount": 0, 
  13.        "Tax": 0.99, 
  14.        "list_price": 0.99, 
  15.        "total": 5.94, 
  16.        "product_description": "My Test Description", 
  17.        "line_tax": [ 
  18.        { 
  19.          "percentage": 20, 
  20.          "name": "Sales Tax", 
  21.          "value": 0.99 
  22.        } 
  23.        ] 
  24.      } 
  25.    ] 
  26.  } 

Updating with REST API v2.1
The new process doesn't just require you to add in this custom field per line item, but also requires a change to the other fields in the line item. There is an additional note that when using v2.1, you no longer post to the "Product_Details" key but to the respective module line item API name as per the following screenshot:
Populating Custom Field in a Quote Line Item - Referring to API names

Following the API names in this example, the Deluge code to send would be something like the following instead (reminder: instead of Product_Details, post to the API name in this case "Quoted_Items"):
copyraw
//
// init
l_CrmLineItems = List();
//
// some sample values
v_CrmProductID = "123456789012345678";
//
// build up product details JSON to send
m_LineItem = Map();
m_LineItem.put("Product_Name",v_CrmProductID);
m_LineItem.put("Quantity",5);
m_LineItem.put("List_Price",0.99);
// setting a tax as an example
v_LineItemTax = 0.99 * 5 * 0.2;
m_LineItem.put("Tax",v_LineItemTax);
l_TaxOptions = List();
if(v_LineItemTax>0)
{
	m_TaxOption1 = Map();
	m_TaxOption1.put("percentage",20);
	m_TaxOption1.put("name","Sales Tax");
	m_TaxOption1.put("value",v_LineItemTax);
	l_TaxOptions.add(m_TaxOption1);
}
else
{
	m_TaxOption0 = Map();
	m_TaxOption0.put("percentage",0);
	m_TaxOption0.put("name","Zero Tax");
	m_TaxOption0.put("value",0);
	l_TaxOptions.add(m_TaxOption0);
}
m_LineItem.put("Line_Tax",l_TaxOptions);
m_LineItem.put("Discount",0);
m_LineItem.put("Description","My Test Description");
m_LineItem.put("Grouping","My Test Group");
l_CrmLineItems.add(m_LineItem);
//
m_CreateQuote = Map();
m_CreateQuote.put("Subject","My Test Quote");
m_CreateQuote.put("Quoted_Items",l_CrmLineItems);
//
// send to CRM
l_RecordsToSend = List();
l_RecordsToSend.add(m_CreateQuote);
m_Data = Map();
m_Data.put("data", l_RecordsToSend);
//
// this is REST API so by default all triggers run.  Use an empty list to stop or prevent these from triggering.
m_Data.put("trigger",[]);
//
// send via REST API v2.1 on EU datacenter
r_CreateCrmQuote = invokeurl
[
	url :"https://www.zohoapis.eu/crm/v2.1/Quotes"
	type :POST
	parameters:m_Data.toString()
	connection:"myconnection"
];
  1.  // 
  2.  // init 
  3.  l_CrmLineItems = List()
  4.  // 
  5.  // some sample values 
  6.  v_CrmProductID = "123456789012345678"
  7.  // 
  8.  // build up product details JSON to send 
  9.  m_LineItem = Map()
  10.  m_LineItem.put("Product_Name",v_CrmProductID)
  11.  m_LineItem.put("Quantity",5)
  12.  m_LineItem.put("List_Price",0.99)
  13.  // setting a tax as an example 
  14.  v_LineItemTax = 0.99 * 5 * 0.2
  15.  m_LineItem.put("Tax",v_LineItemTax)
  16.  l_TaxOptions = List()
  17.  if(v_LineItemTax>0) 
  18.  { 
  19.      m_TaxOption1 = Map()
  20.      m_TaxOption1.put("percentage",20)
  21.      m_TaxOption1.put("name","Sales Tax")
  22.      m_TaxOption1.put("value",v_LineItemTax)
  23.      l_TaxOptions.add(m_TaxOption1)
  24.  } 
  25.  else 
  26.  { 
  27.      m_TaxOption0 = Map()
  28.      m_TaxOption0.put("percentage",0)
  29.      m_TaxOption0.put("name","Zero Tax")
  30.      m_TaxOption0.put("value",0)
  31.      l_TaxOptions.add(m_TaxOption0)
  32.  } 
  33.  m_LineItem.put("Line_Tax",l_TaxOptions)
  34.  m_LineItem.put("Discount",0)
  35.  m_LineItem.put("Description","My Test Description")
  36.  m_LineItem.put("Grouping","My Test Group")
  37.  l_CrmLineItems.add(m_LineItem)
  38.  // 
  39.  m_CreateQuote = Map()
  40.  m_CreateQuote.put("Subject","My Test Quote")
  41.  m_CreateQuote.put("Quoted_Items",l_CrmLineItems)
  42.  // 
  43.  // send to CRM 
  44.  l_RecordsToSend = List()
  45.  l_RecordsToSend.add(m_CreateQuote)
  46.  m_Data = Map()
  47.  m_Data.put("data", l_RecordsToSend)
  48.  // 
  49.  // this is REST API so by default all triggers run.  Use an empty list to stop or prevent these from triggering. 
  50.  m_Data.put("trigger",[])
  51.  // 
  52.  // send via REST API v2.1 on EU datacenter 
  53.  r_CreateCrmQuote = invokeUrl 
  54.  [ 
  55.      url :"https://www.zohoapis.eu/crm/v2.1/Quotes" 
  56.      type :POST 
  57.      parameters:m_Data.toString() 
  58.      connection:"myconnection" 
  59.  ]
or if converted to JSON, the request would read as:
copyraw
{
  "data": {
    "Subject": "My Test Quote",
    "Quoted_Items": [
      {
        "Product_Name": "123456789012345678",
        "Quantity": 5,
        "Discount": 0,
        "Tax": 0.99,
        "List_Price": 0.99,
        "Total": 5.94,
        "Description": "My Test Description",
        "Grouping": "My Test Group",
        "Line_Tax": [
            {
              "percentage": 20,
              "name": "Sales Tax",
              "value": 0.99
            }
          ]
      }
    ]
  },
  "trigger": []
}
  1.  { 
  2.    "data": { 
  3.      "Subject": "My Test Quote", 
  4.      "Quoted_Items": [ 
  5.        { 
  6.          "Product_Name": "123456789012345678", 
  7.          "Quantity": 5, 
  8.          "Discount": 0, 
  9.          "Tax": 0.99, 
  10.          "List_Price": 0.99, 
  11.          "Total": 5.94, 
  12.          "Description": "My Test Description", 
  13.          "Grouping": "My Test Group", 
  14.          "Line_Tax": [ 
  15.              { 
  16.                "percentage": 20, 
  17.                "name": "Sales Tax", 
  18.                "value": 0.99 
  19.              } 
  20.            ] 
  21.        } 
  22.      ] 
  23.    }, 
  24.    "trigger": [] 
  25.  } 

Reading a Response
The response on success would be something like:
copyraw
{
  "data": [
    {
      "code": "SUCCESS",
      "details": {
        "Modified_Time": "2021-05-17T11:02:59+01:00",
        "Modified_By": {
          "name": "Joel Admin",
          "id": "123456"
        },
        "Created_Time": "2021-05-17T11:02:59+01:00",
        "id": "98498465735491156",
        "Created_By": {
          "name": "Joel Admin",
          "id": "123456"
        }
      },
      "message": "record added",
      "status": "success"
    }
  ]
}
  1.  { 
  2.    "data": [ 
  3.      { 
  4.        "code": "SUCCESS", 
  5.        "details": { 
  6.          "Modified_Time": "2021-05-17T11:02:59+01:00", 
  7.          "Modified_By": { 
  8.            "name": "Joel Admin", 
  9.            "id": "123456" 
  10.          }, 
  11.          "Created_Time": "2021-05-17T11:02:59+01:00", 
  12.          "id": "98498465735491156", 
  13.          "Created_By": { 
  14.            "name": "Joel Admin", 
  15.            "id": "123456" 
  16.          } 
  17.        }, 
  18.        "message": "record added", 
  19.        "status": "success" 
  20.      } 
  21.    ] 
  22.  } 
and if using code to capture the ID:
copyraw
v_ResultingQuoteID = 0;
if(r_CreateCrmQuote.toMap().get("data") != null)
{
	if(r_CreateCrmQuote.toMap().get("data").get(0) != null)
	{
		if(r_CreateCrmQuote.toMap().get("data").get(0).get("details") != null)
		{
			v_ResultingQuoteID = r_CreateCrmQuote.toMap().get("data").get(0).get("details").get("id");
		}
	}
}
  1.  v_ResultingQuoteID = 0
  2.  if(r_CreateCrmQuote.toMap().get("data") != null) 
  3.  { 
  4.      if(r_CreateCrmQuote.toMap().get("data").get(0) != null) 
  5.      { 
  6.          if(r_CreateCrmQuote.toMap().get("data").get(0).get("details") != null) 
  7.          { 
  8.              v_ResultingQuoteID = r_CreateCrmQuote.toMap().get("data").get(0).get("details").get("id")
  9.          } 
  10.      } 
  11.  } 

Retrieving a record with 2.1 JSON
The following is a snippet when querying a CRM module (invoices) from a CRM function. The important fields I wanted were some custom fields within the line items subform. Here I'm adding the parameters to ensure we get CRM records that are also pending approval or being converted.
copyraw
m_Params = Map();
m_Params.put("approved","both");
m_Params.put("converted","both");
r_InvoiceDetails = invokeurl
[
	url :"https://www.zohoapis.eu/crm/v2.1/Invoices/" + p_InvoiceID
	type :GET
	parameters: m_Params
	connection:"joels_connector"
];
info r_InvoiceDetails;
  1.  m_Params = Map()
  2.  m_Params.put("approved","both")
  3.  m_Params.put("converted","both")
  4.  r_InvoiceDetails = invokeUrl 
  5.  [ 
  6.      url :"https://www.zohoapis.eu/crm/v2.1/Invoices/" + p_InvoiceID 
  7.      type :GET 
  8.      parameters: m_Params 
  9.      connection:"joels_connector" 
  10.  ]
  11.  info r_InvoiceDetails; 

Common Error(s):
Category: Zoho :: Article: 744