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 http://ascentbusiness.co.uk/zoho-support-2.  For larger projects, check our bespoke pricing structure and receive dedicated support from our hands-on project consultants and developers at http://ascentbusiness.co.uk/crm-solutions/zoho-crm-packages-prices.

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 http://ascentbusiness.co.uk.

ZohoRecruit: Create Linking Table for Candidates and Associated Job Openings

What?
This article is just in case it comes up again (has twice now) where a client wants a report on candidates and associated job openings in Zoho Recruit.

Why?
We're creating a custom module where our client wants to query Zoho Recruit via the API but couldn't find a database table to determine who has been associated to what job. In Zoho Recruit reports (Analytics), you can include the "Associate" module which then allows you to link the Candidates to the Job Openings; but my client needed to be able to query this via API and the "Associate" table or whatever it is, was not visible in API...

How?
So we created a custom module called "Candidates x JobOpenings" (with alternative API name as "CustomModule6"). Just to add to the complexity of the task, the client has their own reference for Candidates which they called "Candidate Ref" and their own reference for the JobOpening called "Vacancy Ref". The below outlines the custom module we created and how we populated with associated candidates:

The CustomModule6:
  • Candidate: Lookup to the Candidates module
  • Job Opening: Lookup to the Job Opening module (called "Vacancies" in client's system)
  • Candidate Ref: A single line text field
  • Vacancy Ref: Another single line text field
  • Candidate x JobOpening Name: Mandatory name field that we will use so as to create unique records
The connection
Within any syntax editor of ZohoRecruit (where you type the deluge code), there will be a "Connections" in the top grey bar that you should click on and create a connection with the scopes that you want (I select nearly all of them), and I'm going to call this one "jl_recruit".

The Deluge code for workflow
copyraw
/* *******************************************************************************
Function:       void fn_AB_AssociateVacancy2Candidate(int p_CandidateID) 
Workflow:       AB001: Associate Job Openings with Candidate
Trigger:        Workflow run whenever a candidate record is modified
Purpose:		Associates Job Openings with a Candidate when their record is modified.

Date Created:   2023-02-02 (JoelLipman.com - Joel Lipman)
                - Initial release
Date Modified:	????-??-?? (??? - ???)
                - 
******************************************************************************* */

v_CandidateZohoID = ifnull(p_CandidateID,0);
r_Associated = invokeurl
[
	url :"https://recruit.zoho.com/recruit/v2/Candidates/" + v_CandidateZohoID + "/associate"
	type :GET
	connection:"ab_recruit"
];
//info r_Associated;
if(!isnull(r_Associated.get("data")))
{
	for each  r_Data in r_Associated.get("data")
	{
		if(!isnull(r_Data.get("id")))
		{
			v_SearchEndpoint = "https://recruit.zoho.com/recruit/v2/Candidates_x_JobOpenings/search?criteria=(CustomModule6_Name:equals:" + v_CandidateZohoID + r_Data.get("id") + ")";
			l_CheckExists = invokeurl
			[
				url :v_SearchEndpoint
				type :GET
				connection:"ab_recruit"
			];
			v_CountExists = 0;
			if(!isnull(l_CheckExists.get("data")))
			{
				for each  r_Check in l_CheckExists.get("data")
				{
					if(!isnull(r_Check.get("id")))
					{
						v_CountExists = v_CountExists + 1;
						break;
					}
				}
			}
			if(v_CountExists == 0)
			{
				// 
				// prevent duplicates by updating record with existing name if exists
				v_Name = v_CandidateZohoID + "" + r_Data.get("id");
				// 
				// retrieve client's own candidate ref from candidate record
				r_CandidateDetails = zoho.recruit.getRecordById("Candidates",v_CandidateZohoID);
				v_CandidateRef = if(!isnull(r_CandidateDetails.get("Candidate Ref")),r_CandidateDetails.get("Candidate Ref"),"");
				// 
				// start building addrecord request
				m_Create = Map();
				// 
				// specify unique name to ensure we don't duplicate these
				m_Create.put("Candidate x JobOpening Name",v_Name);
				// 
				// specify the Zoho Recruit Candidate ID
				m_Create.put("Candidate_ID",v_CandidateZohoID.toLong());
				// 
				// specify the client's own Candidate Ref
				m_Create.put("Candidate Ref",v_CandidateRef);
				// 
				// specify the Zoho Recruit Job Opening ID
				m_Create.put("Job Opening_ID",r_Data.get("id"));
				// 
				// specify the client's own Job Opening Ref
				m_Create.put("Vacancy Ref",r_Data.get("Job_Opening_ID"));
				// 
				// update the record (note the API name being used here)
				r_Create = zoho.recruit.addRecord("CustomModule6",m_Create,0,false,"jl_recruit");
				//info r_Create;
			}
		}
	}
}
  1.  /* ******************************************************************************* 
  2.  Function:       void fn_AB_AssociateVacancy2Candidate(int p_CandidateID) 
  3.  Workflow:       AB001: Associate Job Openings with Candidate 
  4.  Trigger:        Workflow run whenever a candidate record is modified 
  5.  Purpose:        Associates Job Openings with a Candidate when their record is modified. 
  6.   
  7.  Date Created:   2023-02-02 (JoelLipman.com - Joel Lipman) 
  8.                  - Initial release 
  9.  Date Modified:    ????-??-?? (??? - ???) 
  10.                  - 
  11.  ******************************************************************************* */ 
  12.   
  13.  v_CandidateZohoID = ifnull(p_CandidateID,0)
  14.  r_Associated = invokeUrl 
  15.  [ 
  16.      url :"https://recruit.zoho.com/recruit/v2/Candidates/" + v_CandidateZohoID + "/associate" 
  17.      type :GET 
  18.      connection:"ab_recruit" 
  19.  ]
  20.  //info r_Associated; 
  21.  if(!isnull(r_Associated.get("data"))) 
  22.  { 
  23.      for each  r_Data in r_Associated.get("data") 
  24.      { 
  25.          if(!isnull(r_Data.get("id"))) 
  26.          { 
  27.              v_SearchEndpoint = "https://recruit.zoho.com/recruit/v2/Candidates_x_JobOpenings/search?criteria=(CustomModule6_Name:equals:" + v_CandidateZohoID + r_Data.get("id") + ")"
  28.              l_CheckExists = invokeUrl 
  29.              [ 
  30.                  url :v_SearchEndpoint 
  31.                  type :GET 
  32.                  connection:"ab_recruit" 
  33.              ]
  34.              v_CountExists = 0
  35.              if(!isnull(l_CheckExists.get("data"))) 
  36.              { 
  37.                  for each  r_Check in l_CheckExists.get("data") 
  38.                  { 
  39.                      if(!isnull(r_Check.get("id"))) 
  40.                      { 
  41.                          v_CountExists = v_CountExists + 1
  42.                          break
  43.                      } 
  44.                  } 
  45.              } 
  46.              if(v_CountExists == 0) 
  47.              { 
  48.                  // 
  49.                  // prevent duplicates by updating record with existing name if exists 
  50.                  v_Name = v_CandidateZohoID + "" + r_Data.get("id")
  51.                  // 
  52.                  // retrieve client's own candidate ref from candidate record 
  53.                  r_CandidateDetails = zoho.recruit.getRecordById("Candidates",v_CandidateZohoID)
  54.                  v_CandidateRef = if(!isnull(r_CandidateDetails.get("Candidate Ref")),r_CandidateDetails.get("Candidate Ref"),"")
  55.                  // 
  56.                  // start building addrecord request 
  57.                  m_Create = Map()
  58.                  // 
  59.                  // specify unique name to ensure we don't duplicate these 
  60.                  m_Create.put("Candidate x JobOpening Name",v_Name)
  61.                  // 
  62.                  // specify the Zoho Recruit Candidate ID 
  63.                  m_Create.put("Candidate_ID",v_CandidateZohoID.toLong())
  64.                  // 
  65.                  // specify the client's own Candidate Ref 
  66.                  m_Create.put("Candidate Ref",v_CandidateRef)
  67.                  // 
  68.                  // specify the Zoho Recruit Job Opening ID 
  69.                  m_Create.put("Job Opening_ID",r_Data.get("id"))
  70.                  // 
  71.                  // specify the client's own Job Opening Ref 
  72.                  m_Create.put("Vacancy Ref",r_Data.get("Job_Opening_ID"))
  73.                  // 
  74.                  // update the record (note the API name being used here) 
  75.                  r_Create = zoho.recruit.addRecord("CustomModule6",m_Create,0,false,"jl_recruit")
  76.                  //info r_Create; 
  77.              } 
  78.          } 
  79.      } 
  80.  } 

For historical records
The below snippet is what I used to bring all the past records into the custom module. The client would be able to determine what jobs have started/expired based on their Candidate reference and Vacancy reference:
copyraw
/* *******************************************************************************
Function:       void fn_AB_GenerateVacanciesCandidatesAssociations()
Workflow:       -
Trigger:        Standalone function triggered on execute
Purpose:		Loops through all candidates and creates records referring to the Candidate Ref and Job Opening

Date Created:   2023-02-02 (JoelLipman.com - Joel Lipman)
                - Initial release
Date Modified:	????-??-?? (??? - ???)
                - 
******************************************************************************* */
//
// list of pages to loop through (note: issues with multiple pages, do 1 at a time)
l_Pages = {1};
v_PerPage = 200;
for each  v_Page in l_Pages
{
	v_CountTotal = 0;
	v_CountProcessed = 0;
	v_FromIndex = (v_Page - 1) * v_PerPage;
	v_ToIndex = v_FromIndex + v_PerPage - 1;
	//
	// note that ordering here seemed to do nothing... it's in an order, not sure what order.  Let's loop through every page anyway.
	l_Candidates = zoho.recruit.getRecords("Candidates",v_FromIndex,v_ToIndex,"Modified_Time","CANDIDATEID","asc","jl_recruit");
	//info l_Candidates;
	//
	// sanity check to output that it did something
	info "Done: " + zoho.currenttime.toString("HH:mm:ss");
	//
	// loop through our list for this page
	for each  r_Candidate in l_Candidates
	{
		v_CountTotal = v_CountTotal + 1;
		v_CandidateZohoID = ifnull(r_Candidate.get("CANDIDATEID"),0);
		r_Associated = invokeurl
		[
			url :"https://recruit.zoho.com/recruit/v2/Candidates/" + v_CandidateZohoID + "/associate"
			type :GET
			connection:"jl_recruit"
		];
		//info r_Associated;
		if(!isnull(r_Associated.get("data")))
		{
			for each  r_Data in r_Associated.get("data")
			{
				if(!isnull(r_Data.get("id")))
				{
					// 
					// search by record name to see if we've already added this record before
					// note the endpoint and then the field comparison here
					v_SearchEndpoint = "https://recruit.zoho.com/recruit/v2/Candidates_x_JobOpenings/search?criteria=(CustomModule6_Name:equals:" + v_CandidateZohoID + r_Data.get("id") + ")";
					l_CheckExists = invokeurl
					[
						url :v_SearchEndpoint
						type :GET
						connection:"jl_recruit"
					];
					v_CountExists = 0;
					if(!isnull(l_CheckExists.get("data")))
					{
						for each  r_Check in l_CheckExists.get("data")
						{
							if(!isnull(r_Check.get("id")))
							{
								v_CountExists = v_CountExists + 1;
								break;
							}
						}
					}
					// 
					// doesn't exist, so let's add it
					if(v_CountExists == 0)
					{
						// 
						// prevent duplicates by updating record with existing name if exists
						v_Name = v_CandidateZohoID + "" + r_Data.get("id");
						// 
						// retrieve client's own candidate ref from candidate record
						r_CandidateDetails = zoho.recruit.getRecordById("Candidates",v_CandidateZohoID);
						v_CandidateRef = if(!isnull(r_CandidateDetails.get("Candidate Ref")),r_CandidateDetails.get("Candidate Ref"),"");
						// 
						// start building addrecord request
						m_Create = Map();
						// 
						// specify unique name to ensure we don't duplicate these
						m_Create.put("Candidate x JobOpening Name",v_Name);
						// 
						// specify the Zoho Recruit Candidate ID
						m_Create.put("Candidate_ID",v_CandidateZohoID.toLong());
						// 
						// specify the client's own Candidate Ref
						m_Create.put("Candidate Ref",v_CandidateRef);
						// 
						// specify the Zoho Recruit Job Opening ID
						m_Create.put("Job Opening_ID",r_Data.get("id"));
						// 
						// specify the client's own Job Opening Ref
						m_Create.put("Vacancy Ref",r_Data.get("Job_Opening_ID"));
						// 
						// update the record (note the API name being used here)
						r_Create = zoho.recruit.addRecord("CustomModule6",m_Create,0,false,"jl_recruit");
						//info r_Create;
						v_CountProcessed = v_CountProcessed + 1;
					}
				}
			}
		}
	}
	info "Page #" + v_Page + ": Processed " + v_CountProcessed + " of " + v_CountTotal;
}
  1.  /* ******************************************************************************* 
  2.  Function:       void fn_AB_GenerateVacanciesCandidatesAssociations() 
  3.  Workflow:       - 
  4.  Trigger:        Standalone function triggered on execute 
  5.  Purpose:        Loops through all candidates and creates records referring to the Candidate Ref and Job Opening 
  6.   
  7.  Date Created:   2023-02-02 (JoelLipman.com - Joel Lipman) 
  8.                  - Initial release 
  9.  Date Modified:    ????-??-?? (??? - ???) 
  10.                  - 
  11.  ******************************************************************************* */ 
  12.  // 
  13.  // list of pages to loop through (note: issues with multiple pages, do 1 at a time) 
  14.  l_Pages = {1}
  15.  v_PerPage = 200
  16.  for each  v_Page in l_Pages 
  17.  { 
  18.      v_CountTotal = 0
  19.      v_CountProcessed = 0
  20.      v_FromIndex = (v_Page - 1) * v_PerPage; 
  21.      v_ToIndex = v_FromIndex + v_PerPage - 1
  22.      // 
  23.      // note that ordering here seemed to do nothing... it's in an order, not sure what order.  Let's loop through every page anyway. 
  24.      l_Candidates = zoho.recruit.getRecords("Candidates",v_FromIndex,v_ToIndex,"Modified_Time","CANDIDATEID","asc","jl_recruit")
  25.      //info l_Candidates; 
  26.      // 
  27.      // sanity check to output that it did something 
  28.      info "Done: " + zoho.currenttime.toString("HH:mm:ss")
  29.      // 
  30.      // loop through our list for this page 
  31.      for each  r_Candidate in l_Candidates 
  32.      { 
  33.          v_CountTotal = v_CountTotal + 1
  34.          v_CandidateZohoID = ifnull(r_Candidate.get("CANDIDATEID"),0)
  35.          r_Associated = invokeUrl 
  36.          [ 
  37.              url :"https://recruit.zoho.com/recruit/v2/Candidates/" + v_CandidateZohoID + "/associate" 
  38.              type :GET 
  39.              connection:"jl_recruit" 
  40.          ]
  41.          //info r_Associated; 
  42.          if(!isnull(r_Associated.get("data"))) 
  43.          { 
  44.              for each  r_Data in r_Associated.get("data") 
  45.              { 
  46.                  if(!isnull(r_Data.get("id"))) 
  47.                  { 
  48.                      // 
  49.                      // search by record name to see if we've already added this record before 
  50.                      // note the endpoint and then the field comparison here 
  51.                      v_SearchEndpoint = "https://recruit.zoho.com/recruit/v2/Candidates_x_JobOpenings/search?criteria=(CustomModule6_Name:equals:" + v_CandidateZohoID + r_Data.get("id") + ")"
  52.                      l_CheckExists = invokeUrl 
  53.                      [ 
  54.                          url :v_SearchEndpoint 
  55.                          type :GET 
  56.                          connection:"jl_recruit" 
  57.                      ]
  58.                      v_CountExists = 0
  59.                      if(!isnull(l_CheckExists.get("data"))) 
  60.                      { 
  61.                          for each  r_Check in l_CheckExists.get("data") 
  62.                          { 
  63.                              if(!isnull(r_Check.get("id"))) 
  64.                              { 
  65.                                  v_CountExists = v_CountExists + 1
  66.                                  break
  67.                              } 
  68.                          } 
  69.                      } 
  70.                      // 
  71.                      // doesn't exist, so let's add it 
  72.                      if(v_CountExists == 0) 
  73.                      { 
  74.                          // 
  75.                          // prevent duplicates by updating record with existing name if exists 
  76.                          v_Name = v_CandidateZohoID + "" + r_Data.get("id")
  77.                          // 
  78.                          // retrieve client's own candidate ref from candidate record 
  79.                          r_CandidateDetails = zoho.recruit.getRecordById("Candidates",v_CandidateZohoID)
  80.                          v_CandidateRef = if(!isnull(r_CandidateDetails.get("Candidate Ref")),r_CandidateDetails.get("Candidate Ref"),"")
  81.                          // 
  82.                          // start building addrecord request 
  83.                          m_Create = Map()
  84.                          // 
  85.                          // specify unique name to ensure we don't duplicate these 
  86.                          m_Create.put("Candidate x JobOpening Name",v_Name)
  87.                          // 
  88.                          // specify the Zoho Recruit Candidate ID 
  89.                          m_Create.put("Candidate_ID",v_CandidateZohoID.toLong())
  90.                          // 
  91.                          // specify the client's own Candidate Ref 
  92.                          m_Create.put("Candidate Ref",v_CandidateRef)
  93.                          // 
  94.                          // specify the Zoho Recruit Job Opening ID 
  95.                          m_Create.put("Job Opening_ID",r_Data.get("id"))
  96.                          // 
  97.                          // specify the client's own Job Opening Ref 
  98.                          m_Create.put("Vacancy Ref",r_Data.get("Job_Opening_ID"))
  99.                          // 
  100.                          // update the record (note the API name being used here) 
  101.                          r_Create = zoho.recruit.addRecord("CustomModule6",m_Create,0,false,"jl_recruit")
  102.                          //info r_Create; 
  103.                          v_CountProcessed = v_CountProcessed + 1
  104.                      } 
  105.                  } 
  106.              } 
  107.          } 
  108.      } 
  109.      info "Page #" + v_Page + ": Processed " + v_CountProcessed + " of " + v_CountTotal; 
  110.  } 

Error(s) Encountered:
  • {"code":"4401","message":"Unable to populate data, please check if mandatory value is entered correctly."}: Check your connection string.
  • {"code":"9832","message":"Mandatory parameter(s) missing"}: Not sure why as the only mandatory field I had was the name field. Create a sample record with just the name field (placed at top of request) and comment out the other fields from the rest of the request to test. Once you have successfully created a record using only the name field (no underscores - as displayed on the record), uncomment other fields to add to the addrecord request.
  • Lookup field isn't being populated: Find the lookup field suffixed with _ID and send the correct record ID to this field instead. The lookup field (name only) can be omitted from the request.
Category: Zoho :: Article: 830

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

Related Articles

Joes Revolver Map

Accreditation

Badge - Certified Zoho Creator Associate
Badge - Certified Zoho Creator Associate

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
© 2024 Joel Lipman .com. All Rights Reserved.