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:
//
// 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):
Discussion
Comments
Questions, corrections and useful additions are reviewed before appearing here.
No comments yet. Start the discussion.