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.

Zoho People: Reject an Application for Leave in Deluge based on Criteria

What?
This article documents how to stop an employee from applying for leave when already 80% of their team have already booked the same date off.  My brief was:

  • Check the department this employee belongs to
  • Search for employees of the same department (team members) who have booked the same days off (ie count holiday clashes)
  • Prevent an employee for applying for leave if over 80% of the department have already booked the requested dates off.

In other words, if there are 10 people in a department/team, and 8 people have booked tomorrow off, prevent a 9th team member booking tomorrow off.

Why?
I've written this article as it took a while to achieve. The Zoho People application and its API are documented but there are problems with the documentation at the time that this article was written. One is that there are 2 sets of documentation and the other is that the majority of the documentation doesn't refer to OAuth 2.0/json and instead uses the deprecated authtoken v1 and XML.

How?
The trick in this solution was more about the process and what that process was.

Reading the brief, and having written many custom functions for other Zoho apps, it would stand to reason that the process would be: allow a user to apply for leave, then on save/submit, auto approve/reject the application based on a custom function that could calculate the percentage of staff who already booked the requested dates as leave.

[Warning: red herring ahead] For this process, we setup connections choosing to make use of the Zoho People API and we could update any of the fields EXCEPT for "ApprovalStatus" and would get the following response when attempting:
copyraw
{
  "response": {
    "message": "Error occurred",
    "uri": "/api/forms/json/P_ApplyLeave/updateRecord",
    "errors": {
      "code": 7013,
      "message": "Field name 'ApprovalStatus' is invalid "
    },
    "status": 1
  }
}
  1.  { 
  2.    "response": { 
  3.      "message": "Error occurred", 
  4.      "uri": "/api/forms/json/P_ApplyLeave/updateRecord", 
  5.      "errors": { 
  6.        "code": 7013, 
  7.        "message": "Field name 'ApprovalStatus' is invalid " 
  8.      }, 
  9.      "status": 1 
  10.    } 
  11.  } 

[Warning: Another red herring ahead] We tried various other options such as adding a tickbox that would be used in an approval workflow but ran into issues when trying to update the record:
copyraw
{
  "response": {
    "message": "Error occurred",
    "uri": "/api/forms/json/P_ApplyLeave/updateRecord",
    "errors": {
      "code": 7115,
      "message": {
        "From": "Sorry, this record cannot be edited."
      }
    },
    "status": 1
  }
}
  1.  { 
  2.    "response": { 
  3.      "message": "Error occurred", 
  4.      "uri": "/api/forms/json/P_ApplyLeave/updateRecord", 
  5.      "errors": { 
  6.        "code": 7115, 
  7.        "message": { 
  8.          "From": "Sorry, this record cannot be edited." 
  9.        } 
  10.      }, 
  11.      "status": 1 
  12.    } 
  13.  } 

[The working solution] I'll cut to the quick solution, which was not to try to update the form at all but instead to prevent the user from applying for leave in the first place:
  1. Create a validation custom function:
    1. Login to ZohoPeople as an admin, Go to Settings > Customization > Forms > Leave
    2. Under "Form Properties" > Tick the box under "Validation" for "Custom Validation" to create a function:
      Zoho People - Form Properties - Validation - Custom Function
  2. Ensure you select parameters to input rather than retrieving the record data:
    1. I'm calling the Function Name "RejectLeave" then click on Edit Params
      Zoho People - Creating a Function
    2. I'm calling the Method Name "OnValidate" and I add the following parameters:
      • p_RequestID specified as "ID (Leave)" ... this is the ID of the Leave form record.
      • p_Dept as the name of the "Department" ... this is found on the Employee form not the leave form.
      • p_LeaveFrom this is the value of "From" on the "Leave" form.
      • p_LeaveTill this is the value of "To" on the "Leave" form.
      • Giving you something as per the following screenshot:
        Zoho People - Setting function parameters
      • Click on "Save" to accept this
      Note: It is important to add these parameters rather than trying to retrieve the details with a zoho.people.getRecordByID("P_ApplyLeave",v_RequestId) in the function because on application, the record doesn't properly exist in the system yet and the delay causes errors further down the line. Just add these parameters as per my above instructions for a more robust solution.
  3. [See below for full code] Enter the code to determine total number of employees in this department/team:
    1. To determine the total number of employees, we will use zoho.people.getRecords as a search:
      copyraw
      // get total "team members" in department
      l_SearchCriterias = List();
      m_SearchCriteria = Map();
      m_SearchCriteria.put("searchField","Department");
      m_SearchCriteria.put("searchOperator","Is");
      m_SearchCriteria.put("searchText",v_DeptName);
      l_SearchCriterias.add(m_SearchCriteria);
      l_DeptEmployees = zoho.people.getRecords("P_Employee",0,200,l_SearchCriterias);
      1.  // get total "team members" in department 
      2.  l_SearchCriterias = List()
      3.  m_SearchCriteria = Map()
      4.  m_SearchCriteria.put("searchField","Department")
      5.  m_SearchCriteria.put("searchOperator","Is")
      6.  m_SearchCriteria.put("searchText",v_DeptName)
      7.  l_SearchCriterias.add(m_SearchCriteria)
      8.  l_DeptEmployees = zoho.people.getRecords("P_Employee",0,200,l_SearchCriterias)
  4. Enter the code to search for and count holiday clashes:
    1. First we need the code to search for other requests with these dates, we're going to use a range of 1 month before and 1 month after the requested date (so this works as long as no one books longer than a month in one go - increase the submonth/addmonth parameter if an employee can book longer than a month):
      copyraw
      // set criteria to search for clashing annual leave dates (pre/post 1 month check)
      l_SearchCriterias = List();
      m_SearchCriteria = Map();
      m_SearchCriteria.put("searchField","From");
      m_SearchCriteria.put("searchOperator","After");
      m_SearchCriteria.put("searchText",v_GivenDateFrom.subMonth(1).toString("dd-MMM-yyyy"));
      l_SearchCriterias.add(m_SearchCriteria);
      m_SearchCriteria = Map();
      m_SearchCriteria.put("searchField","To");
      m_SearchCriteria.put("searchOperator","Before");
      m_SearchCriteria.put("searchText",v_GivenDateTill.addMonth(1).toString("dd-MMM-yyyy"));
      l_SearchCriterias.add(m_SearchCriteria);
      m_SearchCriteria = Map();
      m_SearchCriteria.put("searchField","Leavetype");
      m_SearchCriteria.put("searchOperator","Contains");
      m_SearchCriteria.put("searchText","Leave");
      l_SearchCriterias.add(m_SearchCriteria);
      l_AllTimeOffs = zoho.people.getRecords("P_ApplyLeave",0,200,l_SearchCriterias);
      1.  // set criteria to search for clashing annual leave dates (pre/post 1 month check) 
      2.  l_SearchCriterias = List()
      3.  m_SearchCriteria = Map()
      4.  m_SearchCriteria.put("searchField","From")
      5.  m_SearchCriteria.put("searchOperator","After")
      6.  m_SearchCriteria.put("searchText",v_GivenDateFrom.subMonth(1).toString("dd-MMM-yyyy"))
      7.  l_SearchCriterias.add(m_SearchCriteria)
      8.  m_SearchCriteria = Map()
      9.  m_SearchCriteria.put("searchField","To")
      10.  m_SearchCriteria.put("searchOperator","Before")
      11.  m_SearchCriteria.put("searchText",v_GivenDateTill.addMonth(1).toString("dd-MMM-yyyy"))
      12.  l_SearchCriterias.add(m_SearchCriteria)
      13.  m_SearchCriteria = Map()
      14.  m_SearchCriteria.put("searchField","Leavetype")
      15.  m_SearchCriteria.put("searchOperator","Contains")
      16.  m_SearchCriteria.put("searchText","Leave")
      17.  l_SearchCriterias.add(m_SearchCriteria)
      18.  l_AllTimeOffs = zoho.people.getRecords("P_ApplyLeave",0,200,l_SearchCriterias)
    2. Then we need the code to loop through all these matching records (and approved) and determine if it clashes with the requested dates:
      copyraw
      for each  r_TimeOff in l_AllTimeOffs
      {
          if(!isnull(r_TimeOff.get("Employee_ID")))
          {
              v_ThisStatus = r_TimeOff.get("ApprovalStatus");
              v_ThisFrom = r_TimeOff.get("From").toDate();
              v_ThisTill = r_TimeOff.get("To").toDate();
              v_ClashOrNot = "NO Clash with ";
              if(v_ThisStatus!="Cancelled")
              {
                  if(v_CompareDateFrom <= v_ThisFrom && v_CompareDateTill >= v_ThisFrom || v_CompareDateFrom <= v_ThisTill && v_CompareDateTill >= v_ThisTill)
                  {
                      v_TotalClashes = v_TotalClashes + 1;
                      v_ClashOrNot = "Clashes with ";
                  }
                  info v_ClashOrNot + r_TimeOff.get("Employee_ID") + ":" + v_ThisFrom + "-" + v_ThisTill;
              }
          }
      }
      1.  for each  r_TimeOff in l_AllTimeOffs 
      2.  { 
      3.      if(!isnull(r_TimeOff.get("Employee_ID"))) 
      4.      { 
      5.          v_ThisStatus = r_TimeOff.get("ApprovalStatus")
      6.          v_ThisFrom = r_TimeOff.get("From").toDate()
      7.          v_ThisTill = r_TimeOff.get("To").toDate()
      8.          v_ClashOrNot = "NO Clash with "
      9.          if(v_ThisStatus!="Cancelled") 
      10.          { 
      11.              if(v_CompareDateFrom <= v_ThisFrom && v_CompareDateTill >= v_ThisFrom || v_CompareDateFrom <= v_ThisTill && v_CompareDateTill >= v_ThisTill) 
      12.              { 
      13.                  v_TotalClashes = v_TotalClashes + 1
      14.                  v_ClashOrNot = "Clashes with "
      15.              } 
      16.              info v_ClashOrNot + r_TimeOff.get("Employee_ID") + ":" + v_ThisFrom + "-" + v_ThisTill; 
      17.          } 
      18.      } 
      19.  } 
      Note: Play around with the Status, there are "Pending", "Cancelled" (UK spelling), "Approval Not Enabled" and I think "Approved", "Rejected". We're using the !="Cancelled" and probably !="Rejected" because we want pending ones to also be taken into account (first come first serve basis).
  5. Calculate the percentage of people already booked off:
    1. A very quick percentage calculation snippet of code to work this out:
      copyraw
      // calculate percentage
      v_Percentage = (v_TotalClashes / v_TotalDeptEmployees * 100).round(0);
      1.  // calculate percentage 
      2.  v_Percentage = (v_TotalClashes / v_TotalDeptEmployees * 100).round(0)
  6. Return an error if overbooked, no error if not:
    1. To return an error (I know we set the function to return "Map" and are using a "Collection" but a Collection is either a list or a map):
      copyraw
      // return response as error popup
          v_OutputMessage = "Sorry! Leave has been rejected due to too many employees booking the same dates off.";
          c_Response = Collection();
          c_Response.insert("EmployeeID":v_OutputMessage);
          c_Error = Collection();
          c_Error.insert("errors":c_Response);
          return c_Error;
      1.  // return response as error popup 
      2.      v_OutputMessage = "Sorry! Leave has been rejected due to too many employees booking the same dates off."
      3.      c_Response = Collection()
      4.      c_Response.insert("EmployeeID":v_OutputMessage)
      5.      c_Error = Collection()
      6.      c_Error.insert("errors":c_Response)
      7.      return c_Error; 
      Displays to the user as per the following screenshot AND prevents the user from submitting this until they provide a date that doesn't clash: Zoho People - Return Error
    2. To return an ok response, as in application accepted, we simply don't return an error so this might be completely optional however the Zoho People system will require a return value for this function:
      copyraw
      // return response as ok popup (doesn't really do anything: just says "Record added successfully")
          v_OutputMessage = "Thank you for submitting your application for leave.";
          c_Response = Collection();
          c_Response.insert("EmployeeID":v_OutputMessage);
          c_Ok = Collection();
          c_Ok.insert("response":c_Response);
          return c_Ok;
      1.  // return response as ok popup (doesn't really do anything: just says "Record added successfully") 
      2.      v_OutputMessage = "Thank you for submitting your application for leave."
      3.      c_Response = Collection()
      4.      c_Response.insert("EmployeeID":v_OutputMessage)
      5.      c_Ok = Collection()
      6.      c_Ok.insert("response":c_Response)
      7.      return c_Ok; 
      Displays to the user as per the following screenshot: Zoho People - Record added successfully

The full code for this function
As mentioned, you may need to tweak the search for records with status as approved/pending but this is what we use:
copyraw
/* *******************************************************************************
Function:       OnValidate
Trigger:        Function executed on the application of a leave
Inputs:         string p_RequestID,string p_Dept,string p_LeaveFrom,string p_LeaveTill
Outputs:        map/collection

Date Created:   2020-10-27 (Joel Lipman)
                - Initial release
                - Workflow to calculate 80% dept workforce
                - Returns response as popup
		- Cancels submit with error popup if over 80%, else success
******************************************************************************* */
//
// declare
v_TotalClashes = 0;
v_TotalDeptEmployees = 0;
v_OutputMessage = "";
//
// get record details
v_RequestId = ifnull(p_RequestID,0).toLong();
v_DeptName = ifnull(p_Dept,"");
v_GivenDateFrom = p_LeaveFrom;
v_GivenDateTill = p_LeaveTill;
v_CompareDateFrom = v_GivenDateFrom.toDate();
v_CompareDateTill = v_GivenDateTill.toDate();
//
// get total "team members" in department
l_SearchCriterias = List();
m_SearchCriteria = Map();
m_SearchCriteria.put("searchField","Department");
m_SearchCriteria.put("searchOperator","Is");
m_SearchCriteria.put("searchText",v_DeptName);
l_SearchCriterias.add(m_SearchCriteria);
l_DeptEmployees = zoho.people.getRecords("P_Employee",0,200,l_SearchCriterias);
v_TotalDeptEmployees = l_DeptEmployees.size();
// ensure total is not zero as we need to divide by it (defaulting to 2 because if 1 then it's 100%, as 2 it's 50% might have it booked off)
v_TotalDeptEmployees = if(v_TotalDeptEmployees1)
{
    //
    // return response as error popup
    v_OutputMessage = "Sorry! Leave has been rejected due to too many employees booking the same dates off.";
    c_Response = Collection();
    c_Response.insert("EmployeeID":v_OutputMessage);
    c_Error = Collection();
    c_Error.insert("errors":c_Response);
    return c_Error;
}
else
{
    //
    // return response as ok popup (doesn't really do anything: just says "Record added successfully")
    v_OutputMessage = "Thank you for submitting your application for leave.";
    c_Response = Collection();
    c_Response.insert("EmployeeID":v_OutputMessage);
    c_Ok = Collection();
    c_Ok.insert("response":c_Response);
    return c_Ok;
}
  1.  /* ******************************************************************************* 
  2.  Function:       OnValidate 
  3.  Trigger:        Function executed on the application of a leave 
  4.  Inputs:         string p_RequestID,string p_Dept,string p_LeaveFrom,string p_LeaveTill 
  5.  Outputs:        map/collection 
  6.   
  7.  Date Created:   2020-10-27 (Joel Lipman) 
  8.                  - Initial release 
  9.                  - Workflow to calculate 80% dept workforce 
  10.                  - Returns response as popup 
  11.          - Cancels submit with error popup if over 80%, else success 
  12.  ******************************************************************************* */ 
  13.  // 
  14.  // declare 
  15.  v_TotalClashes = 0
  16.  v_TotalDeptEmployees = 0
  17.  v_OutputMessage = ""
  18.  // 
  19.  // get record details 
  20.  v_RequestId = ifnull(p_RequestID,0).toLong()
  21.  v_DeptName = ifnull(p_Dept,"")
  22.  v_GivenDateFrom = p_LeaveFrom; 
  23.  v_GivenDateTill = p_LeaveTill; 
  24.  v_CompareDateFrom = v_GivenDateFrom.toDate()
  25.  v_CompareDateTill = v_GivenDateTill.toDate()
  26.  // 
  27.  // get total "team members" in department 
  28.  l_SearchCriterias = List()
  29.  m_SearchCriteria = Map()
  30.  m_SearchCriteria.put("searchField","Department")
  31.  m_SearchCriteria.put("searchOperator","Is")
  32.  m_SearchCriteria.put("searchText",v_DeptName)
  33.  l_SearchCriterias.add(m_SearchCriteria)
  34.  l_DeptEmployees = zoho.people.getRecords("P_Employee",0,200,l_SearchCriterias)
  35.  v_TotalDeptEmployees = l_DeptEmployees.size()
  36.  // ensure total is not zero as we need to divide by it (defaulting to 2 because if 1 then it's 100%, as 2 it's 50% might have it booked off) 
  37.  v_TotalDeptEmployees = if(v_TotalDeptEmployees1) 
  38.  { 
  39.      // 
  40.      // return response as error popup 
  41.      v_OutputMessage = "Sorry! Leave has been rejected due to too many employees booking the same dates off."
  42.      c_Response = Collection()
  43.      c_Response.insert("EmployeeID":v_OutputMessage)
  44.      c_Error = Collection()
  45.      c_Error.insert("errors":c_Response)
  46.      return c_Error; 
  47.  } 
  48.  else 
  49.  { 
  50.      // 
  51.      // return response as ok popup (doesn't really do anything: just says "Record added successfully") 
  52.      v_OutputMessage = "Thank you for submitting your application for leave."
  53.      c_Response = Collection()
  54.      c_Response.insert("EmployeeID":v_OutputMessage)
  55.      c_Ok = Collection()
  56.      c_Ok.insert("response":c_Response)
  57.      return c_Ok; 
  58.  } 

Additional:
We asked Zoho for a solution that might work and were given the following snippet of code (not tested yet!). It looks like we have to fill in the blanks and it's on the same path as we were. The main revelation was the function getEmployeesOnLeave and perhaps this can be combined with a function that gets all employees of a department. The following allows only 1 user in a department to apply for leave in a day.
copyraw
c_Response = Collection();
for each  d in dd.keys()
{
    c_Params= Collection();
    c_Params.insert("date":d);
    c_Params.insert("type":"3");
    c_Params.insert("userId":empid);
    r_ApiResponse= invokeurl
    [
	url :"https://people.zoho.com/people/api/leave/getEmployeesOnLeave" 	
	type :POST 	
	parameters: c_Params.toMap() 	
	connection: “peoplecf”
    ];
    v_EmployeeCount = r_ApiResponse.get("response").get("result").get("employeeCount");
    if(v_EmployeeCount >= 1)
    {
	c_Response.insert("err1":"You cannot apply leave as another employee has already applied leave in this period");
    }
}
c_Return = Collection();
c_Return.insert("errors":c_Response);
return c_Return;
  1.  c_Response = Collection()
  2.  for each  d in dd.keys() 
  3.  { 
  4.      c_Params= Collection()
  5.      c_Params.insert("date":d)
  6.      c_Params.insert("type":"3")
  7.      c_Params.insert("userId":empid)
  8.      r_ApiResponse= invokeUrl 
  9.      [ 
  10.      url :"https://people.zoho.com/people/api/leave/getEmployeesOnLeave" 
  11.      type :POST 
  12.      parameters: c_Params.toMap() 
  13.      connection: "peoplecf" 
  14.      ]
  15.      v_EmployeeCount = r_ApiResponse.get("response").get("result").get("employeeCount")
  16.      if(v_EmployeeCount >= 1) 
  17.      { 
  18.      c_Response.insert("err1":"You cannot apply leave as another employee has already applied leave in this period")
  19.      } 
  20.  } 
  21.  c_Return = Collection()
  22.  c_Return.insert("errors":c_Response)
  23.  return c_Return; 

Source(s):
Category: Zoho :: Article: 736

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.