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 & Xero: Function to pull most recent invoices

ZohoCRM & Xero: Function to pull most recent invoices

What?
Thought I already had an article on this and I know my article Zoho Deluge - Connect to Xero API covered a quick query to pull some invoices but this one documents a pull and mapping into CRM invoices.

Why?
This took me a whole afternoon so I wanted a template function I could use in future when I get this request again. The additional benefit of having this template is that it includes creating contacts, accounts, and products on-the-fly as well as recording payments and checks as to which record is more up to date between ZohoCRM and Xero.

How?
The access token is generated by a function documented in my previously mentioned article Zoho Deluge - Connect to Xero API so here's the pull of the first page of invoices by most recent first (invoice date).

the Code
copyraw
string standalone.fn_Xero_GetInvoices()
{
/* *******************************************************************************
	Function:       string standalone.fn_Xero_GetInvoices()
	Label:          Fn - Xero - Get Invoices
	Trigger:        Standalone / On-Demand / Callable
	Purpose:	 	Function to get the first page of most recent invoices from Xero and pull them into ZohoCRM
	Inputs:         -
	Outputs:        -

	Date Created:   2025-10-09 (Joel Lipman)
					- Initial release
	Date Modified:	???
					- ???

	More Information:
					http://www.joellipman.com/articles/crm/zoho/zoho-deluge-sync-to-xero-api.html

	******************************************************************************* */
	//
	// init
	v_OutputMessage = "ERROR: No Access Token or Tenant Connection specified.";
	v_Count_FoundInXero = 0;
	v_Count_Created = 0;
	v_Count_Updated = 0;
	v_AccessToken = "";
	l_Pages = {1};
	v_PageSize = 1;
	//
	// Xero Invoice Statuses vs your CRM Invoice Statuses
	m_TranslateStatuses = Map();
	m_TranslateStatuses.put("DRAFT","Draft");
	m_TranslateStatuses.put("SUBMITTED","Pending Approval");
	m_TranslateStatuses.put("AUTHORISED","Sent to Customer");
	m_TranslateStatuses.put("PAID","Paid in Full");
	m_TranslateStatuses.put("DELETED","Cancelled");
	m_TranslateStatuses.put("VOIDED","Cancelled");
	//
	// get token details
	v_ModuleName = "Integrations";
	//
	// enter the CRM record ID of your integrations record (Xero Integration API)
	v_TokenCrmID = 123456000000789012;
	r_TokenDetails = zoho.crm.getRecordById("Integrations",v_TokenCrmID);
	v_DataEndpoint = ifnull(r_TokenDetails.get("Data_Endpoint"),"");
	v_TenantID = ifnull(r_TokenDetails.get("Tenant_ID"),"");
	//
	// get access token (does not need REST API url as we're calling it from within CRM)
	v_AccessToken = standalone.fn_API_GetXeroAccessToken();
	//
	// do Xero stuff
	if(v_AccessToken != "" && v_TenantID != "")
	{
		// set header
		m_Header = Map();
		m_Header.put("Authorization","Bearer " + v_AccessToken);
		m_Header.put("Accept","application/json");
		m_Header.put("Xero-tenant-id",v_TenantID);
		//
		// get Xero invoices (page 1 - first 100 - default order is updated date asc)
		for each  v_Page in l_Pages
		{
			m_Params = Map();
			m_Params.put("page",v_Page);
			//
			// keep the page size low as this function will be creating contacts and products if required
			m_Params.put("pageSize",v_PageSize);
			//
			// order by date descending (most recent first) - sometimes need to use Date%20DESC
			m_Params.put("order","Date DESC");
			//
			// get the first page of Xero invoices
			v_FilterReceivables = "?where=" + zoho.encryption.urlEncode("Type=\"ACCREC\"");
			r_AllXeroInvoices = invokeurl
			[
				url :v_DataEndpoint + "/Invoices" + v_FilterReceivables
				type :GET
				parameters:m_Params
				headers:m_Header
			];
			info r_AllXeroInvoices;
			if(!isnull(r_AllXeroInvoices.get("Invoices")))
			{
				for each  m_ThisInvoice in r_AllXeroInvoices.get("Invoices")
				{
					if(!isnull(m_ThisInvoice.get("InvoiceID")))
					{
						//
						// counter
						v_Count_FoundInXero = v_Count_FoundInXero + 1;
						//
						// Xero Invoice identifier
						v_XeroInvoiceID = m_ThisInvoice.get("InvoiceID");
						info v_XeroInvoiceID;
						//
						m_UpsertCrmInvoice = Map();
						m_UpsertCrmInvoice.put("Subject",m_ThisInvoice.get("InvoiceNumber"));
						//
						// some standard CRM invoice fields we can populate
						v_CrmInvoiceStatus = m_TranslateStatuses.get(m_ThisInvoice.get("Status"));
						if(m_ThisInvoice.get("Status")=="PAID")
						{
							v_InvoiceTotal = m_ThisInvoice.get("Total");
							v_PaidTotal = 0.0;
							// we have a partially paid status in crm so let's check those payments
							for each m_XeroPayment in m_ThisInvoice.get("Payments")
							{
								if(!isNull(m_XeroPayment.get("PaymentID")))
								{
									v_PaidTotal = v_PaidTotal + m_XeroPayment.get("Amount");
								}
							}
							v_CrmInvoiceStatus = if(v_PaidTotal == v_InvoiceTotal, "Paid in Full", "Partially Paid");
						}
						m_UpsertCrmInvoice.put("Status",v_CrmInvoiceStatus);
						v_XeroInvoiceDate = m_ThisInvoice.get("Date");
						d_XeroInvoiceDate = v_XeroInvoiceDate.subString(v_XeroInvoiceDate.indexOf("(") + 1,v_XeroInvoiceDate.indexOf("+")).toLong().toTime();
						m_UpsertCrmInvoice.put("Invoice_Date",d_XeroInvoiceDate.toString("yyyy-MM-dd"));
						v_XeroInvoiceDueDate = m_ThisInvoice.get("DueDate");
						d_XeroInvoiceDueDate = v_XeroInvoiceDueDate.subString(v_XeroInvoiceDueDate.indexOf("(") + 1,v_XeroInvoiceDueDate.indexOf("+")).toLong().toTime();
						m_UpsertCrmInvoice.put("Due_Date",d_XeroInvoiceDueDate.toString("yyyy-MM-dd"));
						m_UpsertCrmInvoice.put("Currency",m_ThisInvoice.get("CurrencyCode"));
						//
						// some custom fields I created in CRM to store the data
						m_UpsertCrmInvoice.put("Xero_Ref_ID",m_ThisInvoice.get("InvoiceID"));
						m_UpsertCrmInvoice.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"));
						m_UpsertCrmInvoice.put("Amount_Paid",m_ThisInvoice.get("AmountPaid"));
						m_UpsertCrmInvoice.put("Amount_Credited",m_ThisInvoice.get("AmountCredited"));
						if(!isNull(m_ThisInvoice.get("FullyPaidOnDate")))
						{
							v_XeroFullyPaidDate = m_ThisInvoice.get("FullyPaidOnDate");
							d_XeroFullyPaidDate = v_XeroFullyPaidDate.subString(v_XeroFullyPaidDate.indexOf("(") + 1,v_XeroFullyPaidDate.indexOf("+")).toLong().toTime();
							m_UpsertCrmInvoice.put("Date_Fully_Paid",d_XeroFullyPaidDate.toString("yyyy-MM-dd"));
						}
						m_UpsertCrmInvoice.put("Reference",m_ThisInvoice.get("Reference"));
						//
						// -------------------------------- Invoice Customer --------------------------------
						//
						// initialize
						v_CrmAccountID = "";
						v_CrmContactID = "";
						v_CrmPhone = "";
						v_CrmMobile = "";
						b_CreateAccount = true;
						b_CreateContact = true;
						//
						// set date/time of account last sync'd to Xero (100 years ago by default - so that it will be oldest)
						d_CrmAccountLastUpdated = zoho.currenttime.toString("yyyy-MM-dd HH:mm:ss").toTime().subYear(100);
						v_XeroContactID = m_ThisInvoice.get("Contact").get("ContactID");
						v_XeroContactName = m_ThisInvoice.get("Contact").get("Name");
						//
						// search CRM for this account/customer
						l_SearchAccounts = zoho.crm.searchRecords("Accounts","Xero_Ref_ID:equals:" + v_XeroContactID,1,2,{"approved":"both","converted":"both"});
						for each  m_SearchAccount in l_SearchAccounts
						{
							if(!isNull(m_SearchAccount.get("id")))
							{
								b_CreateAccount = false;
								v_CrmAccountID = m_SearchAccount.get("id");
								//
								// if sync'd before then let's use that date/time
								d_CrmAccountLastUpdated = ifnull(m_SearchAccount.get("Xero_Updated"),zoho.currenttime).toString("yyyy-MM-dd HH:mm:ss","Europe/London").toTime();
								info "Found CRM Account: " + v_CrmAccountID;
							}
						}
						//
						// get account/contact details from Xero (invoice doesn't necessarily hold the details: address, phone, etc)
						r_XeroContact = invokeurl
						[
							url :v_DataEndpoint + "/Contacts/" + v_XeroContactID
							type :GET
							parameters:m_Params
							headers:m_Header
						];
						l_XeroContacts = ifnull(r_XeroContact.get("Contacts"),List());
						for each  m_XeroContact in l_XeroContacts
						{
							if(!isNull(m_XeroContact.get("ContactID")))
							{
								//
								// to check if we want to update the CRM record for the account
								v_XeroTime = m_XeroContact.get("UpdatedDateUTC");
								d_XeroAccountLastUpdated = v_XeroTime.subString(v_XeroTime.indexOf("(") + 1,v_XeroTime.indexOf("+")).toLong().toTime();
								//
								// build upsert for CRM account
								m_CrmAccount = Map();
								m_CrmAccount.put("Account_Name",m_ThisInvoice.get("Contact").get("Name"));
								m_CrmAccount.put("Xero_Ref_ID",m_XeroContact.get("ContactID"));
								m_CrmAccount.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"));
								//
								// addresses
								for each  m_XeroAddress in m_XeroContact.get("Addresses")
								{
									if(!isNull(m_XeroAddress.get("AddressLine1")))
									{
										v_XeroAddressLine1 = m_XeroAddress.get("AddressLine1");
										v_XeroAddressLine2 = m_XeroAddress.get("AddressLine2");
										v_XeroAddressCity = m_XeroAddress.get("City");
										v_XeroAddressZip = m_XeroAddress.get("PostalCode");
										v_XeroAddressAttn = m_XeroAddress.get("AttentionTo");
									}
								}
								//
								l_AddressStreet = List({v_XeroAddressLine1});
								if(!isBlank(v_XeroAddressLine2))
								{
									l_AddressStreet.add(v_XeroAddressLine2);
								}
								m_CrmAccount.put("Billing_Street",l_AddressStreet.toString(", "));
								m_CrmAccount.put("Billing_City",v_XeroAddressCity);
								m_CrmAccount.put("Billing_Code",v_XeroAddressZip);
								//
								// loop through phones
								for each  m_XeroPhone in m_XeroContact.get("Phones")
								{
									if(!isNull(m_XeroPhone.get("PhoneNumber")))
									{
										v_XeroPhoneType = m_XeroPhone.get("PhoneType");
										l_XeroFullPhoneNumberParts = List();
										if(!isNull(m_XeroPhone.get("PhoneCountryCode")))
										{
											l_XeroFullPhoneNumberParts.add(m_XeroPhone.get("PhoneCountryCode"));
										}
										if(!isNull(m_XeroPhone.get("PhoneAreaCode")))
										{
											l_XeroFullPhoneNumberParts.add(m_XeroPhone.get("PhoneAreaCode"));
										}
										if(!isNull(m_XeroPhone.get("PhoneNumber")))
										{
											l_XeroFullPhoneNumberParts.add(m_XeroPhone.get("PhoneNumber"));
										}
										v_XeroFullPhoneNumber = l_XeroFullPhoneNumberParts.toString(" ");
										if(v_XeroPhoneType == "DEFAULT" || v_XeroPhoneType == "PHONE")
										{
											v_CrmPhone = v_XeroFullPhoneNumber;
										}
										else if(v_XeroPhoneType == "MOBILE")
										{
											v_CrmMobile = v_XeroFullPhoneNumber;
										}
									}
								}
								m_CrmAccount.put("Phone",v_CrmPhone);
								//
								// balances
								v_XeroReceivables = 0.0;
								v_XeroPayables = 0.0;
								for each  m_XeroBalance in m_XeroContact.get("Balances")
								{
									if(!isNull(m_XeroBalance.get("AccountsReceivable")))
									{
										v_XeroReceivables = m_XeroBalance.get("AccountsReceivable").get("Outstanding");
										v_XeroReceivables = v_XeroReceivables + m_XeroBalance.get("AccountsReceivable").get("Overdue");
										v_XeroReceivables = v_XeroReceivables * -1;
									}
									if(!isNull(m_XeroBalance.get("AccountsPayable")))
									{
										v_XeroPayables = m_XeroBalance.get("AccountsPayable").get("Outstanding");
										v_XeroPayables = v_XeroPayables + m_XeroBalance.get("AccountsReceivable").get("Overdue");
									}
								}
								v_XeroBalance = v_XeroPayables - v_XeroReceivables;
								m_CrmAccount.put("Xero_Balance",v_XeroBalance);
								//
								// create CRM account for other contact records
								if(b_CreateAccount)
								{
									r_CreateAccount = zoho.crm.createRecord("Accounts",m_CrmAccount);
									info "Creating CRM Account: " + r_CreateAccount;
									if(!isNull(r_CreateAccount.get("id")))
									{
										v_CrmAccountID = r_CreateAccount.get("id");
									}
								}
								//
								// use a contact
								v_SearchContactsCriteria = "Email:equals:" + if(isBlank(m_XeroContact.get("EmailAddress")),"Unknown",m_XeroContact.get("EmailAddress"));
								l_SearchContacts = zoho.crm.searchRecords("Contacts",v_SearchContactsCriteria);
								info "Searching Contacts: " + l_SearchContacts;
								for each  m_SearchContact in l_SearchContacts
								{
									if(!isNull(m_SearchContact.get("id")))
									{
										b_CreateContact = false;
										v_CrmContactID = m_SearchContact.get("id");
										info "Found CRM Contact: " + v_CrmContactID;
									}
								}
								//
								// build upsert for CRM contact
								m_CrmContact = Map();
								m_CrmContact.put("First_Name",m_XeroContact.get("FirstName"));
								// last name is mandatory for a CRM contact so we're going to put a placeholder one if this is not given
								v_CrmContactLastName = ifnull(m_XeroContact.get("LastName"), "-Unknown-");
								m_CrmContact.put("Last_Name",v_CrmContactLastName);
								m_CrmContact.put("Email",m_XeroContact.get("EmailAddress"));
								m_CrmContact.put("Phone",v_CrmPhone);
								m_CrmContact.put("Mobile",v_CrmMobile);
								m_CrmContact.put("Xero_Ref_ID",m_XeroContact.get("ContactID"));
								m_CrmContact.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"));
								m_CrmContact.put("Mailing_Street",l_AddressStreet.toString(", "));
								m_CrmContact.put("Mailing_City",v_XeroAddressCity);
								m_CrmContact.put("Mailing_Zip",v_XeroAddressZip);
								m_CrmContact.put("Account_Name",v_CrmAccountID);
								// last name is mandatory, let's not bother if it wasn't provided
								if(b_CreateContact && v_CrmContactLastName != "-Unknown-")
								{
									r_CreateContact = zoho.crm.createRecord("Contacts",m_CrmContact);
									info "Creating Primary Contact: " + r_CreateContact;
									if(!isNull(r_CreateContact.get("id")))
									{
										v_CrmContactID = r_CreateContact.get("id");
									}
									//
									// create other contacts (retain the map and only change first name, last name, and email)
									for each  m_OtherContact in m_XeroContact.get("ContactPersons")
									{
										m_CrmContact.put("First_Name",m_OtherContact.get("FirstName"));
										m_CrmContact.put("Last_Name",m_OtherContact.get("LastName"));
										m_CrmContact.put("Email",m_OtherContact.get("EmailAddress"));
										r_CreateContact2 = zoho.crm.createRecord("Contacts",m_CrmContact);
										info "Creating Secondary Contact: " + r_CreateContact2;
									}
								}
							}
						}
						//
						// if Xero record is more recently updated than the CRM one, then update the account
						if(d_XeroAccountLastUpdated >= d_CrmAccountLastUpdated)
						{
							r_UpdateCrmAccount = zoho.crm.updateRecord("Accounts",v_CrmAccountID,m_CrmAccount);
							r_UpdateCrmContact = zoho.crm.updateRecord("Contacts",v_CrmContactID,m_CrmContact);
						}
						//
						// add account/contact to the invoice
						m_UpsertCrmInvoice.put("Account_Name",v_CrmAccountID);
						m_UpsertCrmInvoice.put("Contact_Name",v_CrmContactID);
						//
						// -------------------------------- Invoice Line Items --------------------------------
						//
						// initializing
						l_CrmLineItems = List();
						//
						// loop through line items on the Xero invoice
						for each  m_XeroLineItem in m_ThisInvoice.get("LineItems")
						{
							//
							// initialize
							v_CrmProductID = "";
							//
							// checking this is a valid line item and not an error message by it having an ItemCode
							v_CrmProductName = ifnull(m_XeroLineItem.get("ItemCode"), m_XeroLineItem.get("Description"));
							if(!isBlank(v_CrmProductName))
							{
								v_CrmProductCode = ifnull(m_XeroLineItem.get("ItemCode"), "-Unknown-");
								v_CrmProductCodeSafe = zoho.encryption.urlEncode(v_CrmProductCode);
								v_CrmProductName = if(v_CrmProductName.length()>= 200, v_CrmProductName.subString(0,199), v_CrmProductName);
								v_CrmProductNameSafe = zoho.encryption.urlEncode(v_CrmProductName);
								v_SearchCriteria = "((Product_Code:equals:"+v_CrmProductCodeSafe+")or(Product_Name:equals:" + v_CrmProductNameSafe + "))";
								l_SearchProducts = zoho.crm.searchRecords("Products",v_SearchCriteria,1,2,{"approved":"both"});
								info "Searching CRM Products: " + v_SearchCriteria;
								for each  m_SearchProduct in l_SearchProducts
								{
									if(!isNull(m_SearchProduct.get("id")))
									{
										v_CrmProductID = m_SearchProduct.get("id");
									}
								}
								//
								// couldn't find it so let's create it
								if(v_CrmProductID == "")
								{
									m_CrmProduct = Map();
									//
									// some companies don't use the product lookup in Xero so you would need a placeholder product from CRM.
									v_CrmProductName = m_XeroLineItem.get("Item").get("Name");
									if(!isNull(m_XeroLineItem.get("Item")))
									{
										v_CrmProductName = m_XeroLineItem.get("Item").get("Name");
										m_CrmProduct.put("Xero_Ref_ID",m_XeroLineItem.get("Item").get("ItemID"));
										m_CrmProduct.put("Product_Code",m_XeroLineItem.get("Item").get("Code"));
									}
									m_CrmProduct.put("Product_Name",v_CrmProductName);
									m_CrmProduct.put("Product_Active",true);
									m_CrmProduct.put("Description",m_XeroLineItem.get("Description"));
									m_CrmProduct.put("Unit_Price",m_XeroLineItem.get("UnitAmount"));
									m_CrmProduct.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"));
									r_CreateCrmProduct = zoho.crm.createRecord("Products",m_CrmProduct);
									info "Creating CRM Product: " + r_CreateCrmProduct;
									if(!isNull(r_CreateCrmProduct.get("id")))
									{
										v_CrmProductID = r_CreateCrmProduct.get("id");
									}
									else if (r_CreateCrmProduct.get("code").equalsIgnoreCase("DUPLICATE_DATA"))
									{
										v_CrmProductID = r_CreateCrmProduct.get("details").get("id");
									}
								}
								//
								// let's do the rest of the line item (note that we are going to upsert using CRM API v8)
								m_CrmLineItem = Map();
								m_CrmLineItem.put("Product_Name",v_CrmProductID);
								m_CrmLineItem.put("Description",m_XeroLineItem.get("Description"));
								m_CrmLineItem.put("List_Price",m_XeroLineItem.get("UnitAmount"));
								m_CrmLineItem.put("Quantity",m_XeroLineItem.get("Quantity"));
								v_DiscountPercent = ifnull(m_XeroLineItem.get("DiscountRate"),0.0);
								v_DiscountAmount = ifnull(m_XeroLineItem.get("DiscountAmount"),0.0);
								if(v_DiscountPercent != 0)
								{
									// just qty vs unit excluding discount and tax
									v_LineItemTotal = m_XeroLineItem.get("Quantity") * m_XeroLineItem.get("UnitAmount");
									v_DiscountFactor = v_DiscountPercent / 100;
									v_DiscountAmount = v_LineItemTotal * v_DiscountFactor;
								}
								m_CrmLineItem.put("Discount",v_DiscountAmount);
								m_CrmLineItem.put("Tax",m_XeroLineItem.get("TaxAmount"));
								l_CrmLineItems.add(m_CrmLineItem);
							}
						}
						//
						// if the CRM invoice already exists, we are going to upsert so we need to remove the current line items in the CRM invoice
						l_SearchInvoices = zoho.crm.searchRecords("Invoices","Xero_Ref_ID:equals:" + v_XeroInvoiceID);
						for each  m_InvoiceResult in l_SearchInvoices
						{
							if(!isNull(m_InvoiceResult.get("id")))
							{
								for each  m_ExistingLineItem in m_InvoiceResult.get("Product_Details")
								{
									m_MiniDeleteMe = Map();
									m_MiniDeleteMe.put("id",m_ExistingLineItem.get("id"));
									m_MiniDeleteMe.put("_delete",null);
									l_CrmLineItems.add(m_MiniDeleteMe);
								}
							}
						}
						//
						// add line items to the invoice
						m_UpsertCrmInvoice.put("Invoiced_Items",l_CrmLineItems);
						//
						// let's add the billing address retrieved earlier to the invoice
						m_UpsertCrmInvoice.put("Billing_Street",l_AddressStreet.toString(", "));
						m_UpsertCrmInvoice.put("Billing_City",v_XeroAddressCity);
						m_UpsertCrmInvoice.put("Billing_Code",v_XeroAddressZip);
						//
						// let's upsert
						info m_UpsertCrmInvoice;
						m_Data = Map();
						m_Data.put("data",List({m_UpsertCrmInvoice}));
						m_Data.put("trigger",{"workflow","approval","blueprint"});
						r_UpsertInvoice = invokeurl
						[
							url :"https://www.zohoapis.eu/crm/v8/Invoices/upsert"
							type :POST
							parameters:m_Data.toString()
							connection:"ab_crm"
						];
						info "Upserting Invoice: " + m_ThisInvoice.get("InvoiceNumber");
						info r_UpsertInvoice;
						l_ResponseData = ifnull(r_UpsertInvoice.get("data"),List());
						for each  m_ResponseData in l_ResponseData
						{
							if(!isNull(m_ResponseData.get("code")))
							{
								v_Action = m_ResponseData.get("action");
							}
						}
						if(v_Action == "insert")
						{
							v_Count_Created = v_Count_Created + 1;
						}
						else if(v_Action == "update")
						{
							v_Count_Updated = v_Count_Updated + 1;
						}
					}
				}
			}
		}
		v_OutputMessage = "Created " + v_Count_Created + " and Updated " + v_Count_Updated + " from " + v_Count_FoundInXero;
	}
	return v_OutputMessage;
}
  1.  string standalone.fn_Xero_GetInvoices() 
  2.  { 
  3.  /* ******************************************************************************* 
  4.      Function:       string standalone.fn_Xero_GetInvoices() 
  5.      Label:          Fn - Xero - Get Invoices 
  6.      Trigger:        Standalone / On-Demand / Callable 
  7.      Purpose:         Function to get the first page of most recent invoices from Xero and pull them into ZohoCRM 
  8.      Inputs:         - 
  9.      Outputs:        - 
  10.   
  11.      Date Created:   2025-10-09 (Joel Lipman) 
  12.                      - Initial release 
  13.      Date Modified:    ??? 
  14.                      - ??? 
  15.   
  16.      More Information: 
  17.                      http://www.joellipman.com/articles/crm/zoho/zoho-deluge-sync-to-xero-api.html 
  18.   
  19.      ******************************************************************************* */ 
  20.      // 
  21.      // init 
  22.      v_OutputMessage = "ERROR: No Access Token or Tenant Connection specified."
  23.      v_Count_FoundInXero = 0
  24.      v_Count_Created = 0
  25.      v_Count_Updated = 0
  26.      v_AccessToken = ""
  27.      l_Pages = {1}
  28.      v_PageSize = 1
  29.      // 
  30.      // Xero Invoice Statuses vs your CRM Invoice Statuses 
  31.      m_TranslateStatuses = Map()
  32.      m_TranslateStatuses.put("DRAFT","Draft")
  33.      m_TranslateStatuses.put("SUBMITTED","Pending Approval")
  34.      m_TranslateStatuses.put("AUTHORISED","Sent to Customer")
  35.      m_TranslateStatuses.put("PAID","Paid in Full")
  36.      m_TranslateStatuses.put("DELETED","Cancelled")
  37.      m_TranslateStatuses.put("VOIDED","Cancelled")
  38.      // 
  39.      // get token details 
  40.      v_ModuleName = "Integrations"
  41.      // 
  42.      // enter the CRM record ID of your integrations record (Xero Integration API) 
  43.      v_TokenCrmID = 123456000000789012
  44.      r_TokenDetails = zoho.crm.getRecordById("Integrations",v_TokenCrmID)
  45.      v_DataEndpoint = ifnull(r_TokenDetails.get("Data_Endpoint"),"")
  46.      v_TenantID = ifnull(r_TokenDetails.get("Tenant_ID"),"")
  47.      // 
  48.      // get access token (does not need REST API url as we're calling it from within CRM) 
  49.      v_AccessToken = standalone.fn_API_GetXeroAccessToken()
  50.      // 
  51.      // do Xero stuff 
  52.      if(v_AccessToken != "" && v_TenantID != "") 
  53.      { 
  54.          // set header 
  55.          m_Header = Map()
  56.          m_Header.put("Authorization","Bearer " + v_AccessToken)
  57.          m_Header.put("Accept","application/json")
  58.          m_Header.put("Xero-tenant-id",v_TenantID)
  59.          // 
  60.          // get Xero invoices (page 1 - first 100 - default order is updated date asc) 
  61.          for each  v_Page in l_Pages 
  62.          { 
  63.              m_Params = Map()
  64.              m_Params.put("page",v_Page)
  65.              // 
  66.              // keep the page size low as this function will be creating contacts and products if required 
  67.              m_Params.put("pageSize",v_PageSize)
  68.              // 
  69.              // order by date descending (most recent first) - sometimes need to use Date%20DESC 
  70.              m_Params.put("order","Date DESC")
  71.              // 
  72.              // get the first page of Xero invoices 
  73.              v_FilterReceivables = "?where=" + zoho.encryption.urlEncode("Type=\"ACCREC\"")
  74.              r_AllXeroInvoices = invokeUrl 
  75.              [ 
  76.                  url :v_DataEndpoint + "/Invoices" + v_FilterReceivables 
  77.                  type :GET 
  78.                  parameters:m_Params 
  79.                  headers:m_Header 
  80.              ]
  81.              info r_AllXeroInvoices; 
  82.              if(!isnull(r_AllXeroInvoices.get("Invoices"))) 
  83.              { 
  84.                  for each  m_ThisInvoice in r_AllXeroInvoices.get("Invoices") 
  85.                  { 
  86.                      if(!isnull(m_ThisInvoice.get("InvoiceID"))) 
  87.                      { 
  88.                          // 
  89.                          // counter 
  90.                          v_Count_FoundInXero = v_Count_FoundInXero + 1
  91.                          // 
  92.                          // Xero Invoice identifier 
  93.                          v_XeroInvoiceID = m_ThisInvoice.get("InvoiceID")
  94.                          info v_XeroInvoiceID; 
  95.                          // 
  96.                          m_UpsertCrmInvoice = Map()
  97.                          m_UpsertCrmInvoice.put("Subject",m_ThisInvoice.get("InvoiceNumber"))
  98.                          // 
  99.                          // some standard CRM invoice fields we can populate 
  100.                          v_CrmInvoiceStatus = m_TranslateStatuses.get(m_ThisInvoice.get("Status"))
  101.                          if(m_ThisInvoice.get("Status")=="PAID") 
  102.                          { 
  103.                              v_InvoiceTotal = m_ThisInvoice.get("Total")
  104.                              v_PaidTotal = 0.0
  105.                              // we have a partially paid status in crm so let's check those payments 
  106.                              for each m_XeroPayment in m_ThisInvoice.get("Payments") 
  107.                              { 
  108.                                  if(!isNull(m_XeroPayment.get("PaymentID"))) 
  109.                                  { 
  110.                                      v_PaidTotal = v_PaidTotal + m_XeroPayment.get("Amount")
  111.                                  } 
  112.                              } 
  113.                              v_CrmInvoiceStatus = if(v_PaidTotal == v_InvoiceTotal, "Paid in Full", "Partially Paid")
  114.                          } 
  115.                          m_UpsertCrmInvoice.put("Status",v_CrmInvoiceStatus)
  116.                          v_XeroInvoiceDate = m_ThisInvoice.get("Date")
  117.                          d_XeroInvoiceDate = v_XeroInvoiceDate.subString(v_XeroInvoiceDate.indexOf("(") + 1,v_XeroInvoiceDate.indexOf("+")).toLong().toTime()
  118.                          m_UpsertCrmInvoice.put("Invoice_Date",d_XeroInvoiceDate.toString("yyyy-MM-dd"))
  119.                          v_XeroInvoiceDueDate = m_ThisInvoice.get("DueDate")
  120.                          d_XeroInvoiceDueDate = v_XeroInvoiceDueDate.subString(v_XeroInvoiceDueDate.indexOf("(") + 1,v_XeroInvoiceDueDate.indexOf("+")).toLong().toTime()
  121.                          m_UpsertCrmInvoice.put("Due_Date",d_XeroInvoiceDueDate.toString("yyyy-MM-dd"))
  122.                          m_UpsertCrmInvoice.put("Currency",m_ThisInvoice.get("CurrencyCode"))
  123.                          // 
  124.                          // some custom fields I created in CRM to store the data 
  125.                          m_UpsertCrmInvoice.put("Xero_Ref_ID",m_ThisInvoice.get("InvoiceID"))
  126.                          m_UpsertCrmInvoice.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"))
  127.                          m_UpsertCrmInvoice.put("Amount_Paid",m_ThisInvoice.get("AmountPaid"))
  128.                          m_UpsertCrmInvoice.put("Amount_Credited",m_ThisInvoice.get("AmountCredited"))
  129.                          if(!isNull(m_ThisInvoice.get("FullyPaidOnDate"))) 
  130.                          { 
  131.                              v_XeroFullyPaidDate = m_ThisInvoice.get("FullyPaidOnDate")
  132.                              d_XeroFullyPaidDate = v_XeroFullyPaidDate.subString(v_XeroFullyPaidDate.indexOf("(") + 1,v_XeroFullyPaidDate.indexOf("+")).toLong().toTime()
  133.                              m_UpsertCrmInvoice.put("Date_Fully_Paid",d_XeroFullyPaidDate.toString("yyyy-MM-dd"))
  134.                          } 
  135.                          m_UpsertCrmInvoice.put("Reference",m_ThisInvoice.get("Reference"))
  136.                          // 
  137.                          // -------------------------------- Invoice Customer -------------------------------- 
  138.                          // 
  139.                          // initialize 
  140.                          v_CrmAccountID = ""
  141.                          v_CrmContactID = ""
  142.                          v_CrmPhone = ""
  143.                          v_CrmMobile = ""
  144.                          b_CreateAccount = true
  145.                          b_CreateContact = true
  146.                          // 
  147.                          // set date/time of account last sync'd to Xero (100 years ago by default - so that it will be oldest) 
  148.                          d_CrmAccountLastUpdated = zoho.currenttime.toString("yyyy-MM-dd HH:mm:ss").toTime().subYear(100)
  149.                          v_XeroContactID = m_ThisInvoice.get("Contact").get("ContactID")
  150.                          v_XeroContactName = m_ThisInvoice.get("Contact").get("Name")
  151.                          // 
  152.                          // search CRM for this account/customer 
  153.                          l_SearchAccounts = zoho.crm.searchRecords("Accounts","Xero_Ref_ID:equals:" + v_XeroContactID,1,2,{"approved":"both","converted":"both"})
  154.                          for each  m_SearchAccount in l_SearchAccounts 
  155.                          { 
  156.                              if(!isNull(m_SearchAccount.get("id"))) 
  157.                              { 
  158.                                  b_CreateAccount = false
  159.                                  v_CrmAccountID = m_SearchAccount.get("id")
  160.                                  // 
  161.                                  // if sync'd before then let's use that date/time 
  162.                                  d_CrmAccountLastUpdated = ifnull(m_SearchAccount.get("Xero_Updated"),zoho.currenttime).toString("yyyy-MM-dd HH:mm:ss","Europe/London").toTime()
  163.                                  info "Found CRM Account: " + v_CrmAccountID; 
  164.                              } 
  165.                          } 
  166.                          // 
  167.                          // get account/contact details from Xero (invoice doesn't necessarily hold the details: address, phone, etc) 
  168.                          r_XeroContact = invokeUrl 
  169.                          [ 
  170.                              url :v_DataEndpoint + "/Contacts/" + v_XeroContactID 
  171.                              type :GET 
  172.                              parameters:m_Params 
  173.                              headers:m_Header 
  174.                          ]
  175.                          l_XeroContacts = ifnull(r_XeroContact.get("Contacts"),List())
  176.                          for each  m_XeroContact in l_XeroContacts 
  177.                          { 
  178.                              if(!isNull(m_XeroContact.get("ContactID"))) 
  179.                              { 
  180.                                  // 
  181.                                  // to check if we want to update the CRM record for the account 
  182.                                  v_XeroTime = m_XeroContact.get("UpdatedDateUTC")
  183.                                  d_XeroAccountLastUpdated = v_XeroTime.subString(v_XeroTime.indexOf("(") + 1,v_XeroTime.indexOf("+")).toLong().toTime()
  184.                                  // 
  185.                                  // build upsert for CRM account 
  186.                                  m_CrmAccount = Map()
  187.                                  m_CrmAccount.put("Account_Name",m_ThisInvoice.get("Contact").get("Name"))
  188.                                  m_CrmAccount.put("Xero_Ref_ID",m_XeroContact.get("ContactID"))
  189.                                  m_CrmAccount.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"))
  190.                                  // 
  191.                                  // addresses 
  192.                                  for each  m_XeroAddress in m_XeroContact.get("Addresses") 
  193.                                  { 
  194.                                      if(!isNull(m_XeroAddress.get("AddressLine1"))) 
  195.                                      { 
  196.                                          v_XeroAddressLine1 = m_XeroAddress.get("AddressLine1")
  197.                                          v_XeroAddressLine2 = m_XeroAddress.get("AddressLine2")
  198.                                          v_XeroAddressCity = m_XeroAddress.get("City")
  199.                                          v_XeroAddressZip = m_XeroAddress.get("PostalCode")
  200.                                          v_XeroAddressAttn = m_XeroAddress.get("AttentionTo")
  201.                                      } 
  202.                                  } 
  203.                                  // 
  204.                                  l_AddressStreet = List({v_XeroAddressLine1})
  205.                                  if(!isBlank(v_XeroAddressLine2)) 
  206.                                  { 
  207.                                      l_AddressStreet.add(v_XeroAddressLine2)
  208.                                  } 
  209.                                  m_CrmAccount.put("Billing_Street",l_AddressStreet.toString(", "))
  210.                                  m_CrmAccount.put("Billing_City",v_XeroAddressCity)
  211.                                  m_CrmAccount.put("Billing_Code",v_XeroAddressZip)
  212.                                  // 
  213.                                  // loop through phones 
  214.                                  for each  m_XeroPhone in m_XeroContact.get("Phones") 
  215.                                  { 
  216.                                      if(!isNull(m_XeroPhone.get("PhoneNumber"))) 
  217.                                      { 
  218.                                          v_XeroPhoneType = m_XeroPhone.get("PhoneType")
  219.                                          l_XeroFullPhoneNumberParts = List()
  220.                                          if(!isNull(m_XeroPhone.get("PhoneCountryCode"))) 
  221.                                          { 
  222.                                              l_XeroFullPhoneNumberParts.add(m_XeroPhone.get("PhoneCountryCode"))
  223.                                          } 
  224.                                          if(!isNull(m_XeroPhone.get("PhoneAreaCode"))) 
  225.                                          { 
  226.                                              l_XeroFullPhoneNumberParts.add(m_XeroPhone.get("PhoneAreaCode"))
  227.                                          } 
  228.                                          if(!isNull(m_XeroPhone.get("PhoneNumber"))) 
  229.                                          { 
  230.                                              l_XeroFullPhoneNumberParts.add(m_XeroPhone.get("PhoneNumber"))
  231.                                          } 
  232.                                          v_XeroFullPhoneNumber = l_XeroFullPhoneNumberParts.toString(" ")
  233.                                          if(v_XeroPhoneType == "DEFAULT" || v_XeroPhoneType == "PHONE") 
  234.                                          { 
  235.                                              v_CrmPhone = v_XeroFullPhoneNumber; 
  236.                                          } 
  237.                                          else if(v_XeroPhoneType == "MOBILE") 
  238.                                          { 
  239.                                              v_CrmMobile = v_XeroFullPhoneNumber; 
  240.                                          } 
  241.                                      } 
  242.                                  } 
  243.                                  m_CrmAccount.put("Phone",v_CrmPhone)
  244.                                  // 
  245.                                  // balances 
  246.                                  v_XeroReceivables = 0.0
  247.                                  v_XeroPayables = 0.0
  248.                                  for each  m_XeroBalance in m_XeroContact.get("Balances") 
  249.                                  { 
  250.                                      if(!isNull(m_XeroBalance.get("AccountsReceivable"))) 
  251.                                      { 
  252.                                          v_XeroReceivables = m_XeroBalance.get("AccountsReceivable").get("Outstanding")
  253.                                          v_XeroReceivables = v_XeroReceivables + m_XeroBalance.get("AccountsReceivable").get("Overdue")
  254.                                          v_XeroReceivables = v_XeroReceivables * -1
  255.                                      } 
  256.                                      if(!isNull(m_XeroBalance.get("AccountsPayable"))) 
  257.                                      { 
  258.                                          v_XeroPayables = m_XeroBalance.get("AccountsPayable").get("Outstanding")
  259.                                          v_XeroPayables = v_XeroPayables + m_XeroBalance.get("AccountsReceivable").get("Overdue")
  260.                                      } 
  261.                                  } 
  262.                                  v_XeroBalance = v_XeroPayables - v_XeroReceivables; 
  263.                                  m_CrmAccount.put("Xero_Balance",v_XeroBalance)
  264.                                  // 
  265.                                  // create CRM account for other contact records 
  266.                                  if(b_CreateAccount) 
  267.                                  { 
  268.                                      r_CreateAccount = zoho.crm.createRecord("Accounts",m_CrmAccount)
  269.                                      info "Creating CRM Account: " + r_CreateAccount; 
  270.                                      if(!isNull(r_CreateAccount.get("id"))) 
  271.                                      { 
  272.                                          v_CrmAccountID = r_CreateAccount.get("id")
  273.                                      } 
  274.                                  } 
  275.                                  // 
  276.                                  // use a contact 
  277.                                  v_SearchContactsCriteria = "Email:equals:" + if(isBlank(m_XeroContact.get("EmailAddress")),"Unknown",m_XeroContact.get("EmailAddress"))
  278.                                  l_SearchContacts = zoho.crm.searchRecords("Contacts",v_SearchContactsCriteria)
  279.                                  info "Searching Contacts: " + l_SearchContacts; 
  280.                                  for each  m_SearchContact in l_SearchContacts 
  281.                                  { 
  282.                                      if(!isNull(m_SearchContact.get("id"))) 
  283.                                      { 
  284.                                          b_CreateContact = false
  285.                                          v_CrmContactID = m_SearchContact.get("id")
  286.                                          info "Found CRM Contact: " + v_CrmContactID; 
  287.                                      } 
  288.                                  } 
  289.                                  // 
  290.                                  // build upsert for CRM contact 
  291.                                  m_CrmContact = Map()
  292.                                  m_CrmContact.put("First_Name",m_XeroContact.get("FirstName"))
  293.                                  // last name is mandatory for a CRM contact so we're going to put a placeholder one if this is not given 
  294.                                  v_CrmContactLastName = ifnull(m_XeroContact.get("LastName"), "-Unknown-")
  295.                                  m_CrmContact.put("Last_Name",v_CrmContactLastName)
  296.                                  m_CrmContact.put("Email",m_XeroContact.get("EmailAddress"))
  297.                                  m_CrmContact.put("Phone",v_CrmPhone)
  298.                                  m_CrmContact.put("Mobile",v_CrmMobile)
  299.                                  m_CrmContact.put("Xero_Ref_ID",m_XeroContact.get("ContactID"))
  300.                                  m_CrmContact.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"))
  301.                                  m_CrmContact.put("Mailing_Street",l_AddressStreet.toString(", "))
  302.                                  m_CrmContact.put("Mailing_City",v_XeroAddressCity)
  303.                                  m_CrmContact.put("Mailing_Zip",v_XeroAddressZip)
  304.                                  m_CrmContact.put("Account_Name",v_CrmAccountID)
  305.                                  // last name is mandatory, let's not bother if it wasn't provided 
  306.                                  if(b_CreateContact && v_CrmContactLastName != "-Unknown-") 
  307.                                  { 
  308.                                      r_CreateContact = zoho.crm.createRecord("Contacts",m_CrmContact)
  309.                                      info "Creating Primary Contact: " + r_CreateContact; 
  310.                                      if(!isNull(r_CreateContact.get("id"))) 
  311.                                      { 
  312.                                          v_CrmContactID = r_CreateContact.get("id")
  313.                                      } 
  314.                                      // 
  315.                                      // create other contacts (retain the map and only change first name, last name, and email) 
  316.                                      for each  m_OtherContact in m_XeroContact.get("ContactPersons") 
  317.                                      { 
  318.                                          m_CrmContact.put("First_Name",m_OtherContact.get("FirstName"))
  319.                                          m_CrmContact.put("Last_Name",m_OtherContact.get("LastName"))
  320.                                          m_CrmContact.put("Email",m_OtherContact.get("EmailAddress"))
  321.                                          r_CreateContact2 = zoho.crm.createRecord("Contacts",m_CrmContact)
  322.                                          info "Creating Secondary Contact: " + r_CreateContact2; 
  323.                                      } 
  324.                                  } 
  325.                              } 
  326.                          } 
  327.                          // 
  328.                          // if Xero record is more recently updated than the CRM one, then update the account 
  329.                          if(d_XeroAccountLastUpdated >= d_CrmAccountLastUpdated) 
  330.                          { 
  331.                              r_UpdateCrmAccount = zoho.crm.updateRecord("Accounts",v_CrmAccountID,m_CrmAccount)
  332.                              r_UpdateCrmContact = zoho.crm.updateRecord("Contacts",v_CrmContactID,m_CrmContact)
  333.                          } 
  334.                          // 
  335.                          // add account/contact to the invoice 
  336.                          m_UpsertCrmInvoice.put("Account_Name",v_CrmAccountID)
  337.                          m_UpsertCrmInvoice.put("Contact_Name",v_CrmContactID)
  338.                          // 
  339.                          // -------------------------------- Invoice Line Items -------------------------------- 
  340.                          // 
  341.                          // initializing 
  342.                          l_CrmLineItems = List()
  343.                          // 
  344.                          // loop through line items on the Xero invoice 
  345.                          for each  m_XeroLineItem in m_ThisInvoice.get("LineItems") 
  346.                          { 
  347.                              // 
  348.                              // initialize 
  349.                              v_CrmProductID = ""
  350.                              // 
  351.                              // checking this is a valid line item and not an error message by it having an ItemCode 
  352.                              v_CrmProductName = ifnull(m_XeroLineItem.get("ItemCode"), m_XeroLineItem.get("Description"))
  353.                              if(!isBlank(v_CrmProductName)) 
  354.                              { 
  355.                                  v_CrmProductCode = ifnull(m_XeroLineItem.get("ItemCode"), "-Unknown-")
  356.                                  v_CrmProductCodeSafe = zoho.encryption.urlEncode(v_CrmProductCode)
  357.                                  v_CrmProductName = if(v_CrmProductName.length()>= 200, v_CrmProductName.subString(0,199), v_CrmProductName)
  358.                                  v_CrmProductNameSafe = zoho.encryption.urlEncode(v_CrmProductName)
  359.                                  v_SearchCriteria = "((Product_Code:equals:"+v_CrmProductCodeSafe+")or(Product_Name:equals:" + v_CrmProductNameSafe + "))"
  360.                                  l_SearchProducts = zoho.crm.searchRecords("Products",v_SearchCriteria,1,2,{"approved":"both"})
  361.                                  info "Searching CRM Products: " + v_SearchCriteria; 
  362.                                  for each  m_SearchProduct in l_SearchProducts 
  363.                                  { 
  364.                                      if(!isNull(m_SearchProduct.get("id"))) 
  365.                                      { 
  366.                                          v_CrmProductID = m_SearchProduct.get("id")
  367.                                      } 
  368.                                  } 
  369.                                  // 
  370.                                  // couldn't find it so let's create it 
  371.                                  if(v_CrmProductID == "") 
  372.                                  { 
  373.                                      m_CrmProduct = Map()
  374.                                      // 
  375.                                      // some companies don't use the product lookup in Xero so you would need a placeholder product from CRM. 
  376.                                      v_CrmProductName = m_XeroLineItem.get("Item").get("Name")
  377.                                      if(!isNull(m_XeroLineItem.get("Item"))) 
  378.                                      { 
  379.                                          v_CrmProductName = m_XeroLineItem.get("Item").get("Name")
  380.                                          m_CrmProduct.put("Xero_Ref_ID",m_XeroLineItem.get("Item").get("ItemID"))
  381.                                          m_CrmProduct.put("Product_Code",m_XeroLineItem.get("Item").get("Code"))
  382.                                      } 
  383.                                      m_CrmProduct.put("Product_Name",v_CrmProductName)
  384.                                      m_CrmProduct.put("Product_Active",true)
  385.                                      m_CrmProduct.put("Description",m_XeroLineItem.get("Description"))
  386.                                      m_CrmProduct.put("Unit_Price",m_XeroLineItem.get("UnitAmount"))
  387.                                      m_CrmProduct.put("Xero_Updated",zoho.currenttime.toString("yyyy-MM-dd'T'HH:mm:ss","Europe/London"))
  388.                                      r_CreateCrmProduct = zoho.crm.createRecord("Products",m_CrmProduct)
  389.                                      info "Creating CRM Product: " + r_CreateCrmProduct; 
  390.                                      if(!isNull(r_CreateCrmProduct.get("id"))) 
  391.                                      { 
  392.                                          v_CrmProductID = r_CreateCrmProduct.get("id")
  393.                                      } 
  394.                                      else if (r_CreateCrmProduct.get("code").equalsIgnoreCase("DUPLICATE_DATA")) 
  395.                                      { 
  396.                                          v_CrmProductID = r_CreateCrmProduct.get("details").get("id")
  397.                                      } 
  398.                                  } 
  399.                                  // 
  400.                                  // let's do the rest of the line item (note that we are going to upsert using CRM API v8) 
  401.                                  m_CrmLineItem = Map()
  402.                                  m_CrmLineItem.put("Product_Name",v_CrmProductID)
  403.                                  m_CrmLineItem.put("Description",m_XeroLineItem.get("Description"))
  404.                                  m_CrmLineItem.put("List_Price",m_XeroLineItem.get("UnitAmount"))
  405.                                  m_CrmLineItem.put("Quantity",m_XeroLineItem.get("Quantity"))
  406.                                  v_DiscountPercent = ifnull(m_XeroLineItem.get("DiscountRate"),0.0)
  407.                                  v_DiscountAmount = ifnull(m_XeroLineItem.get("DiscountAmount"),0.0)
  408.                                  if(v_DiscountPercent != 0) 
  409.                                  { 
  410.                                      // just qty vs unit excluding discount and tax 
  411.                                      v_LineItemTotal = m_XeroLineItem.get("Quantity") * m_XeroLineItem.get("UnitAmount")
  412.                                      v_DiscountFactor = v_DiscountPercent / 100
  413.                                      v_DiscountAmount = v_LineItemTotal * v_DiscountFactor; 
  414.                                  } 
  415.                                  m_CrmLineItem.put("Discount",v_DiscountAmount)
  416.                                  m_CrmLineItem.put("Tax",m_XeroLineItem.get("TaxAmount"))
  417.                                  l_CrmLineItems.add(m_CrmLineItem)
  418.                              } 
  419.                          } 
  420.                          // 
  421.                          // if the CRM invoice already exists, we are going to upsert so we need to remove the current line items in the CRM invoice 
  422.                          l_SearchInvoices = zoho.crm.searchRecords("Invoices","Xero_Ref_ID:equals:" + v_XeroInvoiceID)
  423.                          for each  m_InvoiceResult in l_SearchInvoices 
  424.                          { 
  425.                              if(!isNull(m_InvoiceResult.get("id"))) 
  426.                              { 
  427.                                  for each  m_ExistingLineItem in m_InvoiceResult.get("Product_Details") 
  428.                                  { 
  429.                                      m_MiniDeleteMe = Map()
  430.                                      m_MiniDeleteMe.put("id",m_ExistingLineItem.get("id"))
  431.                                      m_MiniDeleteMe.put("_delete",null)
  432.                                      l_CrmLineItems.add(m_MiniDeleteMe)
  433.                                  } 
  434.                              } 
  435.                          } 
  436.                          // 
  437.                          // add line items to the invoice 
  438.                          m_UpsertCrmInvoice.put("Invoiced_Items",l_CrmLineItems)
  439.                          // 
  440.                          // let's add the billing address retrieved earlier to the invoice 
  441.                          m_UpsertCrmInvoice.put("Billing_Street",l_AddressStreet.toString(", "))
  442.                          m_UpsertCrmInvoice.put("Billing_City",v_XeroAddressCity)
  443.                          m_UpsertCrmInvoice.put("Billing_Code",v_XeroAddressZip)
  444.                          // 
  445.                          // let's upsert 
  446.                          info m_UpsertCrmInvoice; 
  447.                          m_Data = Map()
  448.                          m_Data.put("data",List({m_UpsertCrmInvoice}))
  449.                          m_Data.put("trigger",{"workflow","approval","blueprint"})
  450.                          r_UpsertInvoice = invokeUrl 
  451.                          [ 
  452.                              url :"https://www.zohoapis.eu/crm/v8/Invoices/upsert" 
  453.                              type :POST 
  454.                              parameters:m_Data.toString() 
  455.                              connection:"ab_crm" 
  456.                          ]
  457.                          info "Upserting Invoice: " + m_ThisInvoice.get("InvoiceNumber")
  458.                          info r_UpsertInvoice; 
  459.                          l_ResponseData = ifnull(r_UpsertInvoice.get("data"),List())
  460.                          for each  m_ResponseData in l_ResponseData 
  461.                          { 
  462.                              if(!isNull(m_ResponseData.get("code"))) 
  463.                              { 
  464.                                  v_Action = m_ResponseData.get("action")
  465.                              } 
  466.                          } 
  467.                          if(v_Action == "insert") 
  468.                          { 
  469.                              v_Count_Created = v_Count_Created + 1
  470.                          } 
  471.                          else if(v_Action == "update") 
  472.                          { 
  473.                              v_Count_Updated = v_Count_Updated + 1
  474.                          } 
  475.                      } 
  476.                  } 
  477.              } 
  478.          } 
  479.          v_OutputMessage = "Created " + v_Count_Created + " and Updated " + v_Count_Updated + " from " + v_Count_FoundInXero; 
  480.      } 
  481.      return v_OutputMessage; 
  482.  } 


Category: Zoho :: Article: 913

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

Accreditation

Badge - Zoho Creator Certified Developer Associate
Badge - Zoho Deluge Certified Developer
Badge - Certified Zoho CRM Developer

Donate & Support

If you like my content, and would like to support this sharing site, feel free to donate using a method below:

Paypal:
Donate to Joel Lipman via PayPal

Bitcoin:
Donate to Joel Lipman with Bitcoin bc1qf6elrdxc968h0k673l2djc9wrpazhqtxw8qqp4

Ethereum:
Donate to Joel Lipman with Ethereum 0xb038962F3809b425D661EF5D22294Cf45E02FebF

Please publish modules in offcanvas position.