Another article on something I learned today despite never running into this issue before; but sending a billing/shipping address included in a request to create or update an estimate in ZohoBooks will fail...
Why?
I have a function to push a ZohoCRM quote to a ZohoBooks estimate and a client asked that the address on the CRM record 'pulls through'. Sending the address in the same request however gives me the following error:
{ "code": 15, "message": "Please ensure that the billing_address has less than 100 characters." }
How?
So the quick answer is 2 separate API calls after you have sent the code to either create or update the estimate in ZohoBooks. You will need the returned estimate ID and I'm not 100% sure the attention/phone number goes along as my client didn't include these in her estimate template... But I'm sending them anyway.
Note(s)
- The following code accesses APIs on the EU datacenter.
- I have a connection called "zbooks" which has the necessary scope(s) to create and update an estimate as well as read settings and contacts.
- I have a connection called "zcrm" which has the necessary scope(s) to read from and write to a CRM quote record.
The code:
This is the code used in a workflow triggered within ZohoCRM when a Quote is created or modified:
copyraw
	
//
// initialize
v_BooksOrgID = 123456789;
v_BooksCustomerID = 0;
m_CreateEstimate = Map();
l_CrmBillingAddress = {"Billing_Street","Billing_Street_2","Billing_City","Billing_State","Billing_Code","Billing_Country"};
l_CrmShippingAddress = {"Shipping_Street","Shipping_Street_2","Shipping_City","Shipping_State","Shipping_Code","Shipping_Country"};
l_BooksAddress = {"address","street2","city","state","zip","country"};
//
// evaluate
r_QuoteDetails = zoho.crm.getRecordById("Quotes",123456789012345678);
//
// push to ZohoBooks estimate
if(true)
{
	//
	// ---------------------- GET ZOHO BOOKS TAX IDs ----------------------
	m_Taxes = Map();
	r_Taxes = invokeurl
	[
		url :"https://books.zoho.eu/api/v3/settings/taxes?organization_id=" + v_BooksOrgID
		type :GET
		connection:"zbooks"
	];
	for each  r_Tax in r_Taxes.get("taxes")
	{
		m_Taxes.put(r_Tax.get("tax_percentage").toString(),r_Tax.get("tax_id"));
	}
	//
	// ---------------------- DETERMINE ZOHOBOOKS CUSTOMER ID ----------------------
	if(!isnull(r_QuoteDetails.get("Account_Name")))
	{
		v_AccountID = r_QuoteDetails.get("Account_Name").get("id");
		r_SearchResults = zoho.books.getRecords("Contacts",v_BooksOrgID,"zcrm_account_id=" + v_AccountID,"zbooks");
		if(!isnull(r_SearchResults.get("contacts")))
		{
			// tried sync contacts from Account record
			for each  r_Result in r_SearchResults.get("contacts")
			{
				if(!isnull(r_Result.get("contact_id")))
				{
					v_BooksCustomerID = r_Result.get("contact_id").toLong();
					break;
				}
			}
		}
	}
	if(v_BooksCustomerID != 0)
	{
		m_CreateEstimate.put("customer_id",v_BooksCustomerID);
	}
	//
	// ---------------------- QUOTE CONTACT NAME/PHONE ----------------------
	if(!isnull(r_QuoteDetails.get("Contact_Name")))
	{
		v_ContactID = r_QuoteDetails.get("Contact_Name").get("id");
		v_ContactName = r_QuoteDetails.get("Contact_Name").get("name");
		r_ContactDetails = zoho.crm.getRecordById("Contacts",v_ContactID);
		v_ContactNumber = ifnull(r_ContactDetails.get("Mobile"),r_ContactDetails.get("Phone"));
	}
	//
	// ---------------------- QUOTE DATES ----------------------
	m_CreateEstimate.put("date",r_QuoteDetails.get("Created_Time").subString(0,10));
	if(!isnull(r_QuoteDetails.get("Valid_Till")))
	{
		m_CreateEstimate.put("expiry_date",r_QuoteDetails.get("Valid_Till").subString(0,10));
	}
	//
	// ---------------------- QUOTE LINKED POTENTIAL/OPPORTUNITY ----------------------
	if(!isnull(r_QuoteDetails.get("Deal_Name")))
	{
		m_CreateEstimate.put("zcrm_potential_id",r_QuoteDetails.get("Deal_Name").get("id").toLong());
	}
	//
	// ---------------------- QUOTE LINE ITEMS ----------------------
	// line items get via api v2.1 or greater
	l_BooksLineItems = List();
	r_CrmProductLineItems = invokeurl
	[
		url :"https://www.zohoapis.eu/crm/v2.1/Quotes/" + p_QuoteID
		type :GET
		parameters:m_ApprovedConverted
		connection:"zcrm"
	];
	// should be checking here that data contains any rows but I'm going on the basis that this quote exists in CRM and at least 1 line item is mandatory
	l_CrmProductLineItems = r_CrmProductLineItems.get("data").get(0).get("Quoted_Items");
	for each  r_CrmLineItem in l_CrmProductLineItems
	{
		m_BooksLineItem = Map();
		v_BooksItemID = 0;
		v_CrmProductID = r_CrmLineItem.get("Product_Name").get("id");
		v_CrmProductName = r_CrmLineItem.get("Product_Name").get("name");
		// if the product doesn't exist in Books (sync hasn't happened yet), then you'll need code here to create the item if not found
		r_SearchResults2 = zoho.books.getRecords("items",v_BooksOrgID,"zcrm_product_id=" + v_CrmProductID,"zbooks");
		if(!isnull(r_SearchResults2.get("items")))
		{
			for each  r_Result2 in r_SearchResults2.get("items")
			{
				if(r_Result2.get("item_name") == v_CrmProductName)
				{
					m_BooksLineItem.put("item_id",r_Result2.get("item_id").toLong());
					m_BooksLineItem.put("name",v_CrmProductName);
					m_BooksLineItem.put("description",r_CrmLineItem.get("Description"));
					m_BooksLineItem.put("quantity",r_CrmLineItem.get("Quantity"));
					m_BooksLineItem.put("rate",r_CrmLineItem.get("List_Price").toDecimal().round(2));
					m_BooksLineItem.put("discount",ifnull(r_CrmLineItem.get("Discount_Percent"),0) + "%");
					m_BooksLineItem.put("item_total",r_CrmLineItem.get("Net_Total").toDecimal().round(2));
					v_CrmVatPercent = r_CrmLineItem.get("VAT2").replaceAll("[^0-9]","");
					v_BooksTaxID = ifnull(m_Taxes.get(v_CrmVatPercent),m_Taxes.get("20"));
					m_BooksLineItem.put("tax_id",v_BooksTaxID);
					m_BooksLineItem.put("tax_percentage",v_CrmVatPercent);
					break;
				}
			}
		}
		l_BooksLineItems.add(m_BooksLineItem);
	}
	m_CreateEstimate.put("line_items",l_BooksLineItems);
	//
	// ---------------------- QUOTE SHIPPING CHARGE ----------------------
	if(r_QuoteDetails.get("Apply_VAT_to_Shinpping_Charges") && v_ShippingCharges > 0)
	{
		m_CreateEstimate.put("shipping_charge_tax_id",m_Taxes.get("20"));
	}
	else
	{
		m_CreateEstimate.put("shipping_charge_tax_id","");
	}
	m_CreateEstimate.put("shipping_charge",v_ShippingCharges);
	//
	// ---------------------- ESTIMATE CREATE OR UPDATE ----------------------
	v_BooksEstimateID = 0;
	if(!isnull(r_QuoteDetails.get("Books_Ref")))
	{
		r_SearchResults3 = zoho.books.getRecords("estimates",v_BooksOrgID,"estimate_number=" + r_QuoteDetails.get("Books_Ref"),"zbooks");
		if(!isnull(r_SearchResults3.get("estimates")))
		{
			for each  r_BooksEstimate in r_SearchResults3.get("estimates")
			{
				if(!isnull(r_BooksEstimate.get("estimate_id")))
				{
					v_BooksEstimateID = r_BooksEstimate.get("estimate_id").toLong();
				}
			}
		}
	}
	if(v_BooksEstimateID == 0)
	{
		r_BooksEstimate = zoho.books.createRecord("estimates",v_BooksOrgID,m_CreateEstimate,"zbooks");
	}
	else
	{
		r_BooksEstimate = zoho.books.updateRecord("estimates",v_BooksOrgID,v_BooksEstimateID,m_CreateEstimate,"zbooks");
	}
	info "ZohoBooks Estimate REQUEST: ";
	info m_CreateEstimate;
	info "ZohoBooks Estimate RESPONSE: ";
	info r_BooksEstimate;
	//
	// ---------------------- UPDATE THE CRM QUOTE WITH ZOHOBOOKS ESTIMATE REF ----------------------
	m_UpdateCrmQuote = Map();
	if(!isnull(r_BooksEstimate.get("estimate")))
	{
		m_UpdateCrmQuote.put("Books_Ref",r_BooksEstimate.get("estimate").get("estimate_number"));
		v_BooksEstimateID = r_BooksEstimate.get("estimate").get("estimate_id").toLong();
	}
	if(m_UpdateCrmQuote.size() > 0)
	{
		r_UpdateCrmQuote = zoho.crm.updateRecord("Quotes",p_QuoteID,m_UpdateCrmQuote);
	}
	//
	// ---------------------- ADDRESS ON QUOTE/ESTIMATE ----------------------
	// if quote address is not blank, then push this to estimate
	if(v_BooksEstimateID != 0)
	{
		if(!isNull(r_QuoteDetails.get("Billing_Street")))
		{
			m_BooksBillingAddress = Map();
			for each index v_AddressIndex1 in l_CrmBillingAddress
			{
				m_BooksBillingAddress.put(l_BooksAddress.get(v_AddressIndex1),r_QuoteDetails.get(l_CrmBillingAddress.get(v_AddressIndex1)));
			}
			v_EndpointBilling = "https://www.zohoapis.eu/books/v3/estimates/" + v_BooksEstimateID + "/address/billing?organization_id=" + v_BooksOrgID;
			r_EstimateBillingAddress = invokeurl
			[
				url :v_EndpointBilling
				type :PUT
				parameters:m_BooksBillingAddress.toString()
				connection:"zbooks"
			];
			info "ZohoBooks Estimate Billing Address RESPONSE: ";
			info r_EstimateBillingAddress;
		}
		if(!isNull(r_QuoteDetails.get("Shipping_Street")))
		{
			m_BooksShippingAddress = Map();
			for each index v_AddressIndex2 in l_CrmShippingAddress
			{
				m_BooksShippingAddress.put(l_BooksAddress.get(v_AddressIndex2),r_QuoteDetails.get(l_CrmShippingAddress.get(v_AddressIndex2)));
			}
			m_BooksShippingAddress.put("attention",v_ContactName);
			if(!isNull(v_ContactNumber))
			{
				m_BooksShippingAddress.put("phone",v_ContactNumber);
			}
			v_EndpointShipping = "https://www.zohoapis.eu/books/v3/estimates/" + v_BooksEstimateID + "/address/shipping?organization_id=" + v_BooksOrgID;
			r_EstimateShippingAddress = invokeurl
			[
				url :v_EndpointShipping
				type :PUT
				parameters:m_BooksShippingAddress.toString()
				connection:"zbooks"
			];
			info "ZohoBooks Estimate Shipping Address RESPONSE: ";
			info r_EstimateShippingAddress;
		}
	}
}
	- //
- // initialize
- v_BooksOrgID = 123456789;
- v_BooksCustomerID = 0;
- m_CreateEstimate = Map();
- l_CrmBillingAddress = {"Billing_Street","Billing_Street_2","Billing_City","Billing_State","Billing_Code","Billing_Country"};
- l_CrmShippingAddress = {"Shipping_Street","Shipping_Street_2","Shipping_City","Shipping_State","Shipping_Code","Shipping_Country"};
- l_BooksAddress = {"address","street2","city","state","zip","country"};
- //
- // evaluate
- r_QuoteDetails = zoho.crm.getRecordById("Quotes",123456789012345678);
- //
- // push to ZohoBooks estimate
- if(true)
- {
- //
- // ---------------------- GET ZOHO BOOKS TAX IDs ----------------------
- m_Taxes = Map();
- r_Taxes = invokeUrl
- [
- url :"https://books.zoho.eu/api/v3/settings/taxes?organization_id=" + v_BooksOrgID
- type :GET
- connection:"zbooks"
- ];
- for each r_Tax in r_Taxes.get("taxes")
- {
- m_Taxes.put(r_Tax.get("tax_percentage").toString(),r_Tax.get("tax_id"));
- }
- //
- // ---------------------- DETERMINE ZOHOBOOKS CUSTOMER ID ----------------------
- if(!isnull(r_QuoteDetails.get("Account_Name")))
- {
- v_AccountID = r_QuoteDetails.get("Account_Name").get("id");
- r_SearchResults = zoho.books.getRecords("Contacts",v_BooksOrgID,"zcrm_account_id=" + v_AccountID,"zbooks");
- if(!isnull(r_SearchResults.get("contacts")))
- {
- // tried sync contacts from Account record
- for each r_Result in r_SearchResults.get("contacts")
- {
- if(!isnull(r_Result.get("contact_id")))
- {
- v_BooksCustomerID = r_Result.get("contact_id").toLong();
- break;
- }
- }
- }
- }
- if(v_BooksCustomerID != 0)
- {
- m_CreateEstimate.put("customer_id",v_BooksCustomerID);
- }
- //
- // ---------------------- QUOTE CONTACT NAME/PHONE ----------------------
- if(!isnull(r_QuoteDetails.get("Contact_Name")))
- {
- v_ContactID = r_QuoteDetails.get("Contact_Name").get("id");
- v_ContactName = r_QuoteDetails.get("Contact_Name").get("name");
- r_ContactDetails = zoho.crm.getRecordById("Contacts",v_ContactID);
- v_ContactNumber = ifnull(r_ContactDetails.get("Mobile"),r_ContactDetails.get("Phone"));
- }
- //
- // ---------------------- QUOTE DATES ----------------------
- m_CreateEstimate.put("date",r_QuoteDetails.get("Created_Time").subString(0,10));
- if(!isnull(r_QuoteDetails.get("Valid_Till")))
- {
- m_CreateEstimate.put("expiry_date",r_QuoteDetails.get("Valid_Till").subString(0,10));
- }
- //
- // ---------------------- QUOTE LINKED POTENTIAL/OPPORTUNITY ----------------------
- if(!isnull(r_QuoteDetails.get("Deal_Name")))
- {
- m_CreateEstimate.put("zcrm_potential_id",r_QuoteDetails.get("Deal_Name").get("id").toLong());
- }
- //
- // ---------------------- QUOTE LINE ITEMS ----------------------
- // line items get via api v2.1 or greater
- l_BooksLineItems = List();
- r_CrmProductLineItems = invokeUrl
- [
- url :"https://www.zohoapis.eu/crm/v2.1/Quotes/" + p_QuoteID
- type :GET
- parameters:m_ApprovedConverted
- connection:"zcrm"
- ];
- // should be checking here that data contains any rows but I'm going on the basis that this quote exists in CRM and at least 1 line item is mandatory
- l_CrmProductLineItems = r_CrmProductLineItems.get("data").get(0).get("Quoted_Items");
- for each r_CrmLineItem in l_CrmProductLineItems
- {
- m_BooksLineItem = Map();
- v_BooksItemID = 0;
- v_CrmProductID = r_CrmLineItem.get("Product_Name").get("id");
- v_CrmProductName = r_CrmLineItem.get("Product_Name").get("name");
- // if the product doesn't exist in Books (sync hasn't happened yet), then you'll need code here to create the item if not found
- r_SearchResults2 = zoho.books.getRecords("items",v_BooksOrgID,"zcrm_product_id=" + v_CrmProductID,"zbooks");
- if(!isnull(r_SearchResults2.get("items")))
- {
- for each r_Result2 in r_SearchResults2.get("items")
- {
- if(r_Result2.get("item_name") == v_CrmProductName)
- {
- m_BooksLineItem.put("item_id",r_Result2.get("item_id").toLong());
- m_BooksLineItem.put("name",v_CrmProductName);
- m_BooksLineItem.put("description",r_CrmLineItem.get("Description"));
- m_BooksLineItem.put("quantity",r_CrmLineItem.get("Quantity"));
- m_BooksLineItem.put("rate",r_CrmLineItem.get("List_Price").toDecimal().round(2));
- m_BooksLineItem.put("discount",ifnull(r_CrmLineItem.get("Discount_Percent"),0) + "%");
- m_BooksLineItem.put("item_total",r_CrmLineItem.get("Net_Total").toDecimal().round(2));
- v_CrmVatPercent = r_CrmLineItem.get("VAT2").replaceAll("[^0-9]","");
- v_BooksTaxID = ifnull(m_Taxes.get(v_CrmVatPercent),m_Taxes.get("20"));
- m_BooksLineItem.put("tax_id",v_BooksTaxID);
- m_BooksLineItem.put("tax_percentage",v_CrmVatPercent);
- break;
- }
- }
- }
- l_BooksLineItems.add(m_BooksLineItem);
- }
- m_CreateEstimate.put("line_items",l_BooksLineItems);
- //
- // ---------------------- QUOTE SHIPPING CHARGE ----------------------
- if(r_QuoteDetails.get("Apply_VAT_to_Shinpping_Charges") && v_ShippingCharges > 0)
- {
- m_CreateEstimate.put("shipping_charge_tax_id",m_Taxes.get("20"));
- }
- else
- {
- m_CreateEstimate.put("shipping_charge_tax_id","");
- }
- m_CreateEstimate.put("shipping_charge",v_ShippingCharges);
- //
- // ---------------------- ESTIMATE CREATE OR UPDATE ----------------------
- v_BooksEstimateID = 0;
- if(!isnull(r_QuoteDetails.get("Books_Ref")))
- {
- r_SearchResults3 = zoho.books.getRecords("estimates",v_BooksOrgID,"estimate_number=" + r_QuoteDetails.get("Books_Ref"),"zbooks");
- if(!isnull(r_SearchResults3.get("estimates")))
- {
- for each r_BooksEstimate in r_SearchResults3.get("estimates")
- {
- if(!isnull(r_BooksEstimate.get("estimate_id")))
- {
- v_BooksEstimateID = r_BooksEstimate.get("estimate_id").toLong();
- }
- }
- }
- }
- if(v_BooksEstimateID == 0)
- {
- r_BooksEstimate = zoho.books.createRecord("estimates",v_BooksOrgID,m_CreateEstimate,"zbooks");
- }
- else
- {
- r_BooksEstimate = zoho.books.updateRecord("estimates",v_BooksOrgID,v_BooksEstimateID,m_CreateEstimate,"zbooks");
- }
- info "ZohoBooks Estimate REQUEST: ";
- info m_CreateEstimate;
- info "ZohoBooks Estimate RESPONSE: ";
- info r_BooksEstimate;
- //
- // ---------------------- UPDATE THE CRM QUOTE WITH ZOHOBOOKS ESTIMATE REF ----------------------
- m_UpdateCrmQuote = Map();
- if(!isnull(r_BooksEstimate.get("estimate")))
- {
- m_UpdateCrmQuote.put("Books_Ref",r_BooksEstimate.get("estimate").get("estimate_number"));
- v_BooksEstimateID = r_BooksEstimate.get("estimate").get("estimate_id").toLong();
- }
- if(m_UpdateCrmQuote.size() > 0)
- {
- r_UpdateCrmQuote = zoho.crm.updateRecord("Quotes",p_QuoteID,m_UpdateCrmQuote);
- }
- //
- // ---------------------- ADDRESS ON QUOTE/ESTIMATE ----------------------
- // if quote address is not blank, then push this to estimate
- if(v_BooksEstimateID != 0)
- {
- if(!isNull(r_QuoteDetails.get("Billing_Street")))
- {
- m_BooksBillingAddress = Map();
- for each index v_AddressIndex1 in l_CrmBillingAddress
- {
- m_BooksBillingAddress.put(l_BooksAddress.get(v_AddressIndex1),r_QuoteDetails.get(l_CrmBillingAddress.get(v_AddressIndex1)));
- }
- v_EndpointBilling = "https://www.zohoapis.eu/books/v3/estimates/" + v_BooksEstimateID + "/address/billing?organization_id=" + v_BooksOrgID;
- r_EstimateBillingAddress = invokeUrl
- [
- url :v_EndpointBilling
- type :PUT
- parameters:m_BooksBillingAddress.toString()
- connection:"zbooks"
- ];
- info "ZohoBooks Estimate Billing Address RESPONSE: ";
- info r_EstimateBillingAddress;
- }
- if(!isNull(r_QuoteDetails.get("Shipping_Street")))
- {
- m_BooksShippingAddress = Map();
- for each index v_AddressIndex2 in l_CrmShippingAddress
- {
- m_BooksShippingAddress.put(l_BooksAddress.get(v_AddressIndex2),r_QuoteDetails.get(l_CrmShippingAddress.get(v_AddressIndex2)));
- }
- m_BooksShippingAddress.put("attention",v_ContactName);
- if(!isNull(v_ContactNumber))
- {
- m_BooksShippingAddress.put("phone",v_ContactNumber);
- }
- v_EndpointShipping = "https://www.zohoapis.eu/books/v3/estimates/" + v_BooksEstimateID + "/address/shipping?organization_id=" + v_BooksOrgID;
- r_EstimateShippingAddress = invokeUrl
- [
- url :v_EndpointShipping
- type :PUT
- parameters:m_BooksShippingAddress.toString()
- connection:"zbooks"
- ];
- info "ZohoBooks Estimate Shipping Address RESPONSE: ";
- info r_EstimateShippingAddress;
- }
- }
- }
Source(s):
- Zoho Books Documentation / Update Billing Address
- Zoho Community / Zoho Inventory / General / Error: Please ensure that the billing_address has less than 100 characters
Category: Zoho :: Article: 857
	

 
			      
						  
                 
						  
                 
						  
                 
						  
                 
						  
                 
 
 

 
 
Add comment