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