Use Case:
Step 1: Navigate to Zoho Writer → Automate → Merge Template
Step 2: Click on 'Convert'
Step 3: Select a data source to start merging
Step 4: Select 'Custom Source'
Step 5: Click on 'Use' next to the Projects Fields
Step 6: Once done, enter the Zoho Org ID i.e., ZOID within the portalID.
Step 7: Click on 'Connections' within the same page → Create a Connection with the mentioned scopes.
Step 8: Click on 'Manage Fields' → Drag and Drop the required fields.
Step 9: You can setup the Merge options as per your concern as in this screen shot
Step 10: Click on 'Merge & Share Fillable Link'
Step 11: Select the required field from the 'Fields & Buttons'
Step 12: Configure the 'Merge & share fillable link'
Step 13: Click on 'Configure from submit actions' → Webhook & Custom Functions → Execute Custom Functions.
The following functionality can be achieved using the code provided below. Additionally, two connections must be created as outlined
Zoho Projects Connection :: Create a connection for the Zoho Projects service with the following scopes 'ZohoProjects.projects.READ
, ZohoProjects.milestones.READ
, ZohoProjects.tasklists.READ
, ZohoProjects.tasks.READ
, ZohoProjects.users.READ
, and ZohoProjects.clients.READ'.
'xxxxxxxxx'
with the actual connection name in ZPConnection
.Zoho OAuth Connection :: Create a connection for the Zoho OAuth service with the following scopes 'ZohoWriter.documentEditor.ALL
and ZohoWriter.Merge.ALL'.
'yyyyyyyyy'
with the actual connection name in ZWConnection
.For detailed guidance on creating a connection, please refer to this link. The required code is provided below.
//connections for authenticationZPConnection = "xxxxxxxxx";ZWConnection = "yyyyyyyyy";headers = Map();// Mandatory header - Do not removeheaders.put("x-zoho-service","ZohoWriter");//Domain url for Projects and WriterprojectsDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/";milestoneDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/milestones/";taskListDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/tasklists/";taskDetailsUrl = projectsDomain + "restapi/portal/" + portalId + "/projects/" + projectId + "/tasks/";projectsUsersUrl = projectsDomain + "/api/v3/portal/" + portalId + "/projects/" + projectId + "/users";clientUsersUrl = projectsDomain + "api/v3/portal/" + portalId + "/projects/" + projectId + "/clients/users";mergeFieldsUrl = writerDomain + "/writer/api/v1/documents/" + templateId + "/fields";mergeAndStoreUrl = writerDomain + "/writer/api/v1/documents/" + templateId + "/merge/sharetofill";projectsCommentUrl = projectsDomain + "api/v3/portal/" + portalId + "/projects/" + projectId + "/comments";getAllFields = invokeurl[url :mergeFieldsUrltype :GETconnection:"writer"];if(!getAllFields.contains("error") && getAllFields.getJSON("merge").size() > 0){values_map = Map();projectDetails = invokeurl[url :projectsDetailsUrltype :GETheaders:headersconnection:"connectionprojects"];if(!projectDetails.contains("error")){projectFieldValues = projectDetails.getJson("projects").get(0);projectCF = false;if(projectFieldValues.contains("custom_fields")){projectCF = true;projectCustomfields = projectFieldValues.getJSON("custom_fields");}for each mergeField in getAllFields.getJson("merge"){if(projectFieldValues.containsKey(mergeField.getJSON("id"))){if(mergeField.getJSON("id") == "TAGS"){tagList = List();for each tag in projectFieldValues.getJSON("TAGS"){tagList.add(tag.getJSON("name"));}values_map.put(mergeField.get("id"),tagList);continue;}else{values_map.put(mergeField.get("id"),projectFieldValues.getJSON(mergeField.getJSON("id")));continue;}}// merge project customfieldsif(projectCF == true){for each field in projectCustomfields{if(field.containsKey(mergeField.getJSON("id"))){values_map.put(mergeField.get("id"),field.getJSON(mergeField.getJSON("id")));break;}}}// merge milestone detailsif(mergeField.get("id") == "milestone_details"){milestoneDetilList = List();MilestoneDetails = invokeurl[url :milestoneDetailsUrltype :GETheaders:headersconnection:"connectionprojects"];if(!MilestoneDetails.contains("error")){allMilestonedetails = MilestoneDetails.getJson("milestones");for each milestone in allMilestonedetails{milestoneDetilMap = Map();for each milestoneField in mergeField.getJSON("fields"){mergeFieldId = milestoneField.get("id");mergeFieldName = mergeFieldId.subString("milestone_details.".length(),mergeFieldId.length());//merge milestone fieldsif(milestone.containsKey(mergeFieldName)){if(mergeFieldName == "tags"){tagList = List();for each tag in milestone.getJSON(mergeFieldName){tagList.add(tag.getJSON("name"));}milestoneDetilMap.put(mergeFieldId,tagList);continue;}else if(mergeFieldName == "status"){milestoneDetilMap.put(mergeFieldId,milestone.getJson("status_det").getJson("name"));continue;}else{milestoneDetilMap.put(mergeFieldId,milestone.getJson(mergeFieldName));continue;}}}milestoneDetilList.add(milestoneDetilMap);}values_map.put("milestone_details",milestoneDetilList);}}// merge tasklist detailsif(mergeField.get("id") == "tasklist_details"){taskListDetilList = List();getTasklistDetails = invokeurl[url :taskListDetailsUrltype :GETheaders:headersconnection:"connectionprojects"];if(!getTasklistDetails.contains("error")){taskListdetails = getTasklistDetails.getJson("tasklists");for each tasklist in taskListdetails{taskListDetilMap = Map();for each taskListField in mergeField.getJSON("fields"){mergeFieldId = taskListField.get("id");mergeFieldName = mergeFieldId.subString("tasklist_details.".length(),mergeFieldId.length());//merge tasklist fieldsif(tasklist.containsKey(mergeFieldName)){if(mergeFieldName == "tags"){tagDetails = List();for each tag in tasklist.getJson(mergeFieldName){tagDetails.add(tag.getJSON("name"));}taskListDetilMap.put(mergeFieldId,tagDetails);continue;}else{taskListDetilMap.put(mergeFieldId,tasklist.getJson(mergeFieldName));continue;}}}taskListDetilList.add(taskListDetilMap);}values_map.put("tasklist_details",taskListDetilList);}}// merge task detailsif(mergeField.get("id") == "task_details"){taskDetilList = List();getTaskDetails = invokeurl[url :taskDetailsUrltype :GETheaders:headersconnection:"connectionprojects"];if(!getTaskDetails.contains("error")){taskdetails = getTaskDetails.getJson("tasks");for each taskdetail in taskdetails{taskDetilMap = Map();taskCFs = false;if(taskdetail.contains("custom_fields")){taskCFs = true;taskCustomfields = taskdetail.getJSON("custom_fields");}for each taskField in mergeField.getJSON("fields"){mergeFieldId = taskField.get("id");mergeFieldName = mergeFieldId.subString("task_details.".length(),mergeFieldId.length());//merge task fieldsif(taskdetail.containsKey(mergeFieldName)){if(mergeFieldName == "tasklist" || mergeFieldName == "status"){taskDetilMap.put(mergeFieldId,taskdetail.getJson(mergeFieldName).getJson("name"));continue;}else if(mergeFieldName == "tags"){tagDetails = List();for each tag in taskdetail.getJson(mergeFieldName){tagDetails.add(tag.getJSON("name"));}taskDetilMap.put(mergeFieldId,tagDetails);continue;}else{taskDetilMap.put(mergeFieldId,taskdetail.getJson(mergeFieldName));continue;}}else if(taskdetail.containsKey("details")){if(mergeFieldName == "owners"){ownerDetail = List();ownerList = taskdetail.getJson("details").getJSON("owners");for each owner in ownerList{ownerDetail.add(owner.getJson("name"));}taskDetilMap.put(mergeFieldId,ownerDetail);continue;}}else if(taskdetail.containsKey("log_hours")){if(mergeFieldName == "billable_hours" || mergeFieldName == "non_billable_hours"){taskDetilMap.put(mergeFieldId,taskdetail.getJson("log_hours").getJSON(mergeFieldName));continue;}}//merge task customfieldsif(taskCFs == true){for each taskCF in taskCustomfields{if(taskCF.containsKey(mergeFieldName)){taskDetilMap.put(mergeFieldId,taskCF.getJson(mergeFieldName));break;}}}}taskDetilList.add(taskDetilMap);}values_map.put("task_details",taskDetilList);}}// merge projectUsersif(mergeField.get("id") == "project_users"){projectUserList = List();getProjectUsers = invokeurl[url :projectsUsersUrltype :GETconnection:"connectionprojects"];if(!getProjectUsers.contains("error")){projectUserdetails = getProjectUsers.getJson("users");for each userDetails in projectUserdetails{projectUsersMap = Map();for each userField in mergeField.getJSON("fields"){mergeFieldId = userField.get("id");mergeFieldName = mergeFieldId.subString("project_users.".length(),mergeFieldId.length());//merge projectusers fieldsif(mergeFieldName == "role"){if(userDetails.get(mergeFieldName).size() > 0){projectUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName).getJSON("name"));continue;}}else if(mergeFieldName == "profile"){if(userDetails.get(mergeFieldName).size() > 0){projectUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName).getJSON("name"));continue;}}else{projectUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName));continue;}}projectUserList.add(projectUsersMap);}values_map.put("project_users",projectUserList);}}// merge clients usersif(mergeField.get("id") == "client_users"){clientUserList = List();getProjectClientUser = invokeurl[url :clientUsersUrltype :GETconnection:"connectionprojects"];if(!getProjectClientUser.contains("error")){clientUserdetails = getProjectClientUser.getJson("clients").getJSON("users");for each userDetails in clientUserdetails{clientUsersMap = Map();for each userField in mergeField.getJSON("fields"){mergeFieldId = userField.get("id");mergeFieldName = mergeFieldId.subString("client_users.".length(),mergeFieldId.length());// merge clientsUsersif(!mergeFieldName == "profile"){clientUsersMap.put(mergeFieldId,userDetails.getJSON(mergeFieldName));continue;}else{clientUsersMap.put(mergeFieldId,userDetails.getJSON("profile").getJson("name"));continue;}}clientUserList.add(clientUsersMap);}values_map.put("client_users",clientUserList);}}}mergeDataParams = Map();mergeDataParams.put("merge_data",{"data":values_map});mergeAndStore = invokeurl[url :mergeAndStoreUrltype :POSTparameters:mergeDataParamsconnection:"writer"];if(!mergeAndStore.containsKey("error") && mergeAndStore.containsKey("merge_report_url")){mailMessage = "Fill in the document " + mergeAndStore.get("records").get(0).get("fillable_link");sendmail[from :zoho.loginuseridto :tomailaddresssubject :"Fillable document link"message :mailMessage]}return mergeAndStore;}else{return projectDetails;}}else{return getAllFields;}
Creating custom functions in Zoho Projects is a straightforward process, supported by comprehensive documentation. Zoho offers a variety of built-in functions that serve as useful starting points, and you can also define your own functions using Zoho’s scripting language, Deluge. Implementing these functions can significantly enhance efficiency and improve productivity.
Stay tuned for additional examples and custom function scripts.