CodeX Scripts for Time Logs | Online Help | Zoho Projects

CodeX Scripts for Time Logs

CodeX Scripts in Zoho Projects are advanced conditional logic scripts that help enforce specific rules and validations. Users can control how and when time logs can be created, updated, blocked or transitioned.

Use Cases

Restrict Time Logs Based on Task Work Hours

Restrict users from logging time that exceeds the total work hours allocated to a task. The script also blocks logging for tasks with no work hours assigned.
Code
  1. function main() {
  2.     try {
  3.         let url = "https://projects.zoho.com";
  4.         let records = current.record;
  5.         let violations = [];

  6.         function parseToMinutes(s) {
  7.             if (!s) return 0;
  8.             let str = String(s);
  9.             if (str.indexOf(":") === -1) return (parseFloat(str) || 0) * 60;
  10.             let p = str.split(":");
  11.             return (parseInt(p[0], 10) || 0) * 60 + (parseInt(p[1], 10) || 0);
  12.         }

  13.         function formatMinutes(m) {
  14.             let h = Math.floor(m / 60);
  15.             let mins = m % 60;
  16.             return h + ":" + (mins < 10 ? "0" : "") + mins;
  17.         }

  18.         let taskLogs = [];
  19.         let projectTaskMap = {};

  20.         for (let i = 0; i < records.length; i++) {
  21.             let record = records[i];
  22.             let linked = record.module_detail;
  23.             if (!linked || linked.name !== "Task") continue;

  24.             taskLogs.push(record);
  25.             let projId = String(record.project_id);
  26.             if (!projectTaskMap[projId]) projectTaskMap[projId] = {};
  27.             projectTaskMap[projId][linked.id] = true;
  28.         }

  29.         if (taskLogs.length === 0) return;

  30.         let allTasks = [];
  31.         let projectIds = Object.keys(projectTaskMap);

  32.         if (projectIds.length === 1) {
  33.             let projId = projectIds[0];
  34.             let taskIds = Object.keys(projectTaskMap[projId]);
  35.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: taskIds }], pattern: "1" });
  36.             let req = new HttpRequest();
  37.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  38.             req.method("GET");
  39.             req.connection("zprojects");
  40.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];
  41.         } else {
  42.             let uniqueMap = {};
  43.             for (let i = 0; i < projectIds.length; i++) {
  44.                 let ids = Object.keys(projectTaskMap[projectIds[i]]);
  45.                 for (let j = 0; j < ids.length; j++) uniqueMap[ids[j]] = true;
  46.             }
  47.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: Object.keys(uniqueMap) }], pattern: "1" });
  48.             let req = new HttpRequest();
  49.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  50.             req.method("GET");
  51.             req.connection("zprojects");
  52.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];

  53.             let returnedIds = {};
  54.             for (let i = 0; i < allTasks.length; i++) returnedIds[String(allTasks[i].id)] = true;

  55.             let missingByProject = {};
  56.             for (let i = 0; i < projectIds.length; i++) {
  57.                 let projId = projectIds[i];
  58.                 let taskIds = Object.keys(projectTaskMap[projId]);
  59.                 for (let j = 0; j < taskIds.length; j++) {
  60.                     if (!returnedIds[taskIds[j]]) {
  61.                         if (!missingByProject[projId]) missingByProject[projId] = [];
  62.                         missingByProject[projId].push(taskIds[j]);
  63.                     }
  64.                 }
  65.             }

  66.             let missingProjIds = Object.keys(missingByProject);
  67.             for (let i = 0; i < missingProjIds.length && i < 2; i++) {
  68.                 let projId = missingProjIds[i];
  69.                 let mFilter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: missingByProject[projId] }], pattern: "1" });
  70.                 let mReq = new HttpRequest();
  71.                 mReq.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(mFilter)}`);
  72.                 mReq.method("GET");
  73.                 mReq.connection("zprojects");
  74.                 let moreTasks = mReq.execute().asJson().statusMessage.responseText.tasks || [];
  75.                 for (let k = 0; k < moreTasks.length; k++) allTasks.push(moreTasks[k]);
  76.             }
  77.         }

  78.         let taskWorkMap = {};
  79.         let taskNameMap = {};
  80.         for (let i = 0; i < allTasks.length; i++) {
  81.             let task = allTasks[i];
  82.             let id = String(task.id);
  83.             taskNameMap[id] = task.name;
  84.             taskWorkMap[id] = {
  85.                 totalWork: parseToMinutes(task.owners_and_work.total_work),
  86.                 alreadyLogged: parseToMinutes(task.log_hours.total_hours)
  87.             };
  88.         }

  89.         let batchPerTask = {};
  90.         for (let i = 0; i < taskLogs.length; i++) {
  91.             let record = taskLogs[i];
  92.             let taskId = String(record.module_detail.id);
  93.             if (!batchPerTask[taskId]) batchPerTask[taskId] = { totalMinutes: 0, logIds: [] };
  94.             batchPerTask[taskId].totalMinutes += parseToMinutes(record.log_hour);
  95.             batchPerTask[taskId].logIds.push(record.id);
  96.         }

  97.         let batchTaskIds = Object.keys(batchPerTask);
  98.         for (let i = 0; i < batchTaskIds.length; i++) {
  99.             let taskId = batchTaskIds[i];
  100.             let info = taskWorkMap[taskId];
  101.             let batch = batchPerTask[taskId];
  102.             if (!info) continue;

  103.             if (info.totalWork === 0) {
  104.                 violations.push({ taskId: taskId, taskName: taskNameMap[taskId], totalWork: "00:00", reason: "No work hours allocated" });
  105.                 continue;
  106.             }

  107.             let totalAfterLog = info.alreadyLogged + batch.totalMinutes;
  108.             if (totalAfterLog > info.totalWork) {
  109.                 violations.push({ taskId: taskId, taskName: taskNameMap[taskId], totalWork: formatMinutes(info.totalWork), alreadyLogged: formatMinutes(info.alreadyLogged), newBatch: formatMinutes(batch.totalMinutes), reason: "Would exceed work hours" });
  110.             }
  111.         }

  112.         if (violations.length > 0) {
  113.             throw new ScriptError(JSON.stringify({ violations: violations }));
  114.         }
  115.     } catch (err) {
  116.         throw new ScriptError(err);
  117.     }
  118. }

 Restrict Time Logs for Unassigned Tasks or Issues

Allow time logging only for tasks or issues that have at least one owner assigned. Logs against unassigned work items are rejected.
Code
  1. function main() {
  2.     try {
  3.         let url = "https://projects.zoho.com";
  4.         let records = current.record;
  5.         let violations = [];

  6.         let taskLogs = [];
  7.         let projectTaskMap = {};

  8.         for (let i = 0; i < records.length; i++) {
  9.             let record = records[i];
  10.             let linked = record.module_detail;
  11.             if (!linked || linked.name !== "Task") continue;

  12.             taskLogs.push(record);
  13.             let projId = String(record.project_id);
  14.             if (!projectTaskMap[projId]) projectTaskMap[projId] = {};
  15.             projectTaskMap[projId][linked.id] = true;
  16.         }

  17.         if (taskLogs.length === 0) return;

  18.         let allTasks = [];
  19.         let projectIds = Object.keys(projectTaskMap);

  20.         if (projectIds.length === 1) {
  21.             let projId = projectIds[0];
  22.             let taskIds = Object.keys(projectTaskMap[projId]);
  23.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: taskIds }], pattern: "1" });
  24.             let req = new HttpRequest();
  25.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  26.             req.method("GET");
  27.             req.connection("zprojects");
  28.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];
  29.         } else {
  30.             let uniqueMap = {};
  31.             for (let i = 0; i < projectIds.length; i++) {
  32.                 let ids = Object.keys(projectTaskMap[projectIds[i]]);
  33.                 for (let j = 0; j < ids.length; j++) uniqueMap[ids[j]] = true;
  34.             }
  35.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: Object.keys(uniqueMap) }], pattern: "1" });
  36.             let req = new HttpRequest();
  37.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  38.             req.method("GET");
  39.             req.connection("zprojects");
  40.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];

  41.             let returnedIds = {};
  42.             for (let i = 0; i < allTasks.length; i++) returnedIds[String(allTasks[i].id)] = true;

  43.             let missingByProject = {};
  44.             for (let i = 0; i < projectIds.length; i++) {
  45.                 let projId = projectIds[i];
  46.                 let taskIds = Object.keys(projectTaskMap[projId]);
  47.                 for (let j = 0; j < taskIds.length; j++) {
  48.                     if (!returnedIds[taskIds[j]]) {
  49.                         if (!missingByProject[projId]) missingByProject[projId] = [];
  50.                         missingByProject[projId].push(taskIds[j]);
  51.                     }
  52.                 }
  53.             }

  54.             let missingProjIds = Object.keys(missingByProject);
  55.             for (let i = 0; i < missingProjIds.length && i < 2; i++) {
  56.                 let projId = missingProjIds[i];
  57.                 let mFilter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: missingByProject[projId] }], pattern: "1" });
  58.                 let mReq = new HttpRequest();
  59.                 mReq.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(mFilter)}`);
  60.                 mReq.method("GET");
  61.                 mReq.connection("zprojects");
  62.                 let moreTasks = mReq.execute().asJson().statusMessage.responseText.tasks || [];
  63.                 for (let k = 0; k < moreTasks.length; k++) allTasks.push(moreTasks[k]);
  64.             }
  65.         }

  66.         let taskOwnerMap = {};
  67.         for (let i = 0; i < allTasks.length; i++) {
  68.             let task = allTasks[i];
  69.             let owners = task.owners_and_work.owners || [];
  70.             let hasReal = false;
  71.             for (let j = 0; j < owners.length; j++) {
  72.                 if (owners[j].zuid && owners[j].zuid !== 0) { hasReal = true; break; }
  73.             }
  74.             taskOwnerMap[String(task.id)] = hasReal;
  75.         }

  76.         for (let i = 0; i < taskLogs.length; i++) {
  77.             let record = taskLogs[i];
  78.             let taskId = String(record.module_detail.id);
  79.             if (taskOwnerMap[taskId] === undefined) continue;

  80.             if (!taskOwnerMap[taskId]) {
  81.                 violations.push({ logId: record.id, taskId: taskId, user: record.user ? record.user.name : "Unknown", reason: "Task is unassigned" });
  82.             }
  83.         }

  84.         if (violations.length > 0) {
  85.             throw new ScriptError(JSON.stringify({ violations: violations }));
  86.         }
  87.     } catch (err) {
  88.         throw new ScriptError(err);
  89.     }
  90. }

 Restrict Weekend Time Logs

Restrict users from logging time on Saturdays and Sundays by validating the selected log date.
Code
  1. function main() {
  2.     try {
  3.         let records = current.record;
  4.         let violations = [];

  5.         for (let i = 0; i < records.length; i++) {
  6.             let record = records[i];
  7.             let logDate = record.log_date;
  8.             if (!logDate) continue;

  9.             let parts = logDate.split("-");
  10.             let date = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
  11.             let day = date.getDay();

  12.             if (day === 0 || day === 6) {
  13.                 violations.push({ logId: record.id, logDate: logDate, user: record.user ? record.user.name : "Unknown", day: day === 0 ? "Sunday" : "Saturday", reason: "Cannot log time on weekends" });
  14.             }
  15.         }

  16.         if (violations.length > 0) {
  17.             throw new ScriptError(JSON.stringify({ violations: violations }));
  18.         }
  19.     } catch (err) {
  20.         throw new ScriptError(err);
  21.     }
  22. }

Restrict Time Logs on Holidays 

Block time entries on organization holidays by retrieving holiday information from Zoho People and validating the selected log date.
Code
  1. function main() {
  2.     try {
  3.         let peopleUrl = "https://people.zoho.com";
  4.         let records = current.record;
  5.         let violations = [];

  6.         let monthMap = {"Jan":"01","Feb":"02","Mar":"03","Apr":"04","May":"05","Jun":"06","Jul":"07","Aug":"08","Sep":"09","Oct":"10","Nov":"11","Dec":"12"};

  7.         function peopleDateToISO(d) {
  8.             let p = d.split("-");
  9.             if (p.length !== 3) return d;
  10.             return p[2] + "-" + (monthMap[p[1]] || "01") + "-" + (p[0].length === 1 ? "0" + p[0] : p[0]);
  11.         }

  12.         let years = {};
  13.         for (let i = 0; i < records.length; i++) {
  14.             if (records[i].log_date) years[records[i].log_date.substring(0, 4)] = true;
  15.         }

  16.         let holidaySet = {};
  17.         let holidayNames = {};
  18.         let yearKeys = Object.keys(years);
  19.         for (let y = 0; y < yearKeys.length; y++) {
  20.             let year = yearKeys[y];
  21.             let req = new HttpRequest();
  22.             req.url(`${peopleUrl}/people/api/leave/v2/holidays/get?from=01-Jan-${year}&to=31-Dec-${year}`);
  23.             req.method("GET");
  24.             req.connection("zpeople");
  25.             let holidays = req.execute().asJson().statusMessage.responseText.data || [];

  26.             for (let i = 0; i < holidays.length; i++) {
  27.                 let isoDate = peopleDateToISO(holidays[i].Date);
  28.                 holidaySet[isoDate] = true;
  29.                 holidayNames[isoDate] = holidays[i].Name;
  30.             }
  31.         }

  32.         for (let i = 0; i < records.length; i++) {
  33.             let record = records[i];
  34.             let logDate = record.log_date;
  35.             if (!logDate) continue;

  36.             if (holidaySet[logDate]) {
  37.                 violations.push({ logId: record.id, logDate: logDate, user: record.user ? record.user.name : "Unknown", holiday: holidayNames[logDate], reason: logDate + " is a holiday (" + holidayNames[logDate] + ")" });
  38.             }
  39.         }

  40.         if (violations.length > 0) {
  41.             throw new ScriptError(JSON.stringify({ violations: violations }));
  42.         }
  43.     } catch (err) {
  44.         throw new ScriptError(err);
  45.     }
  46. }

 Restrict Time Logs During Leave or Time Off

Restrict users from logging time on days when they are on approved leave or time off, using leave records from Zoho People.
Code
  1. function main() {
  2.     try {
  3.         let peopleUrl = "https://people.zoho.com";
  4.         let records = current.record;
  5.         let violations = [];

  6.         let monthMap = {"Jan":"01","Feb":"02","Mar":"03","Apr":"04","May":"05","Jun":"06","Jul":"07","Aug":"08","Sep":"09","Oct":"10","Nov":"11","Dec":"12"};

  7.         function peopleDateToISO(d) {
  8.             let p = d.split("-");
  9.             if (p.length !== 3) return d;
  10.             return p[2] + "-" + (monthMap[p[1]] || "01") + "-" + (p[0].length === 1 ? "0" + p[0] : p[0]);
  11.         }

  12.         function isoToPeopleDate(d) {
  13.             let p = d.split("-");
  14.             let m = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
  15.             return p[2] + "-" + m[parseInt(p[1]) - 1] + "-" + p[0];
  16.         }

  17.         let minDate = null;
  18.         let maxDate = null;
  19.         let uniqueUsers = {};

  20.         for (let i = 0; i < records.length; i++) {
  21.             let record = records[i];
  22.             let logDate = record.log_date;
  23.             if (logDate) {
  24.                 if (!minDate || logDate < minDate) minDate = logDate;
  25.                 if (!maxDate || logDate > maxDate) maxDate = logDate;
  26.             }
  27.             if (record.user && record.user.zuid) {
  28.                 uniqueUsers[String(record.user.zuid)] = true;
  29.             }
  30.         }

  31.         if (!minDate) return;

  32.         let req = new HttpRequest();
  33.         req.url(`${peopleUrl}/api/v2/leavetracker/leaves/records?from=${encodeURIComponent(isoToPeopleDate(minDate))}&to=${encodeURIComponent(isoToPeopleDate(maxDate))}`);
  34.         req.method("GET");
  35.         req.connection("zpeople");
  36.         let leaveRecordsMap = req.execute().asJson().statusMessage.responseText.records || {};
  37.         let leaveIds = Object.keys(leaveRecordsMap);

  38.         let userLeaveDayMap = {};
  39.         for (let i = 0; i < leaveIds.length; i++) {
  40.             let lr = leaveRecordsMap[leaveIds[i]];
  41.             let zuid = String(lr.ZUID);
  42.             if (!uniqueUsers[zuid]) continue;

  43.             let days = lr.Days || {};
  44.             let dayKeys = Object.keys(days);
  45.             for (let d = 0; d < dayKeys.length; d++) {
  46.                 let leaveCount = parseFloat(days[dayKeys[d]].LeaveCount) || 0;
  47.                 if (leaveCount <= 0) continue;
  48.                 let isoDate = peopleDateToISO(dayKeys[d]);
  49.                 if (!userLeaveDayMap[zuid]) userLeaveDayMap[zuid] = {};
  50.                 userLeaveDayMap[zuid][isoDate] = { leaveType: lr.Leavetype || "", status: lr.ApprovalStatus || "" };
  51.             }
  52.         }

  53.         for (let i = 0; i < records.length; i++) {
  54.             let record = records[i];
  55.             let logDate = record.log_date;
  56.             if (!logDate) continue;
  57.             let recordZuid = record.user ? String(record.user.zuid) : "";
  58.             if (!recordZuid) continue;

  59.             let userDays = userLeaveDayMap[recordZuid];
  60.             if (!userDays) continue;
  61.             let dayLeave = userDays[logDate];
  62.             if (!dayLeave) continue;

  63.             violations.push({ logId: record.id, logDate: logDate, user: record.user.name, leaveType: dayLeave.leaveType, status: dayLeave.status, reason: "User is on " + dayLeave.leaveType + " (" + dayLeave.status + ")" });
  64.         }

  65.         if (violations.length > 0) {
  66.             throw new ScriptError(JSON.stringify({ violations: violations }));
  67.         }
  68.     } catch (err) {
  69.         throw new ScriptError(err);
  70.     }
  71. }

 Allow Time Logs Only for Task or Issue Owners

Ensure that only users assigned as owners of a task or issue can log time against it. Unassigned tasks and unauthorized users are blocked.
Code
  1. function main() {
  2.     try {

  3.         let url = "https://projects.zoho.com";
  4.         let records = current.record;
  5.         let violations = [];

  6.         let taskLogs = [];
  7.         let projectTaskMap = {};

  8.         for (let i = 0; i < records.length; i++) {
  9.             let record = records[i];
  10.             let linked = record.module_detail;
  11.             if (!linked || linked.name === "general") continue;

  12.             taskLogs.push(record);
  13.             let projId = String(record.project_id);
  14.             if (!projectTaskMap[projId]) projectTaskMap[projId] = {};
  15.             projectTaskMap[projId][linked.id] = true;
  16.         }

  17.         if (taskLogs.length === 0) return;

  18.         let allTasks = [];
  19.         let projectIds = Object.keys(projectTaskMap);

  20.         if (projectIds.length === 1) {
  21.             let projId = projectIds[0];
  22.             let taskIds = Object.keys(projectTaskMap[projId]);
  23.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: taskIds }], pattern: "1" });
  24.             let req = new HttpRequest();
  25.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  26.             req.method("GET");
  27.             req.connection("zprojects");
  28.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];
  29.         } else {
  30.             let uniqueMap = {};
  31.             for (let i = 0; i < projectIds.length; i++) {
  32.                 let ids = Object.keys(projectTaskMap[projectIds[i]]);
  33.                 for (let j = 0; j < ids.length; j++) uniqueMap[ids[j]] = true;
  34.             }
  35.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: Object.keys(uniqueMap) }], pattern: "1" });
  36.             let req = new HttpRequest();
  37.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  38.             req.method("GET");
  39.             req.connection("zprojects");
  40.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];

  41.             let returnedIds = {};
  42.             for (let i = 0; i < allTasks.length; i++) returnedIds[String(allTasks[i].id)] = true;

  43.             let missingByProject = {};
  44.             for (let i = 0; i < projectIds.length; i++) {
  45.                 let projId = projectIds[i];
  46.                 let taskIds = Object.keys(projectTaskMap[projId]);
  47.                 for (let j = 0; j < taskIds.length; j++) {
  48.                     if (!returnedIds[taskIds[j]]) {
  49.                         if (!missingByProject[projId]) missingByProject[projId] = [];
  50.                         missingByProject[projId].push(taskIds[j]);
  51.                     }
  52.                 }
  53.             }

  54.             let missingProjIds = Object.keys(missingByProject);
  55.             for (let i = 0; i < missingProjIds.length && i < 2; i++) {
  56.                 let projId = missingProjIds[i];
  57.                 let mFilter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: missingByProject[projId] }], pattern: "1" });
  58.                 let mReq = new HttpRequest();
  59.                 mReq.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(mFilter)}`);
  60.                 mReq.method("GET");
  61.                 mReq.connection("zprojects");
  62.                 let moreTasks = mReq.execute().asJson().statusMessage.responseText.tasks || [];
  63.                 for (let k = 0; k < moreTasks.length; k++) allTasks.push(moreTasks[k]);
  64.             }
  65.         }

  66.         let taskOwnerMap = {};
  67.         for (let i = 0; i < allTasks.length; i++) {
  68.             let task = allTasks[i];
  69.             let owners = task.owners_and_work.owners || [];
  70.             let realZpuids = [];
  71.             for (let j = 0; j < owners.length; j++) {
  72.                 if (owners[j].zuid && owners[j].zuid !== 0) realZpuids.push(String(owners[j].zpuid));
  73.             }
  74.             taskOwnerMap[String(task.id)] = realZpuids;
  75.         }

  76.         for (let i = 0; i < taskLogs.length; i++) {
  77.             let record = taskLogs[i];
  78.             let taskId = String(record.module_detail.id);
  79.             let userZpuid = record.user ? String(record.user.id) : String(current.user.id);
  80.             let ownerZpuids = taskOwnerMap[taskId];

  81.             if (!ownerZpuids) continue;

  82.             if (ownerZpuids.length === 0) {
  83.                 violations.push({ logId: record.id, taskId: taskId, user: record.user ? record.user.name : "Unknown", reason: "Task is unassigned" });
  84.             } else if (ownerZpuids.indexOf(userZpuid) === -1) {
  85.                 violations.push({ logId: record.id, taskId: taskId, user: record.user ? record.user.name : "Unknown", reason: "User is not the task owner" });
  86.             }
  87.         }

  88.         if (violations.length > 0) {
  89.             throw new ScriptError(JSON.stringify({ violations: violations }));
  90.         }
  91.     } catch (err) {
  92.         throw new ScriptError(err);
  93.     }
  94. }

Restrict Time Logs for Tasks in Review

Restrict users from logging time against tasks whose status is In Review, ensuring work is logged only during active execution stages.
Code
  1. function main() {
  2.     try {
  3.         
  4.         let url = " https://projects.zoho.com";
  5.         let records = current.record;
  6.         let violations = [];

  7.         let taskLogs = [];
  8.         let projectTaskMap = {};

  9.         for (let i = 0; i < records.length; i++) {
  10.             let record = records[i];
  11.             let linked = record.module_detail;
  12.             if (!linked || linked.name !== "Task") continue;

  13.             taskLogs.push(record);
  14.             let projId = String(record.project_id);
  15.             if (!projectTaskMap[projId]) projectTaskMap[projId] = {};
  16.             projectTaskMap[projId][linked.id] = true;
  17.         }

  18.         if (taskLogs.length === 0) return;

  19.         let allTasks = [];
  20.         let projectIds = Object.keys(projectTaskMap);

  21.         if (projectIds.length === 1) {
  22.             let projId = projectIds[0];
  23.             let taskIds = Object.keys(projectTaskMap[projId]);
  24.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: taskIds }], pattern: "1" });
  25.             let req = new HttpRequest();
  26.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  27.             req.method("GET");
  28.             req.connection("zprojects");
  29.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];
  30.         } else {
  31.             let uniqueMap = {};
  32.             for (let i = 0; i < projectIds.length; i++) {
  33.                 let ids = Object.keys(projectTaskMap[projectIds[i]]);
  34.                 for (let j = 0; j < ids.length; j++) uniqueMap[ids[j]] = true;
  35.             }
  36.             let filter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: Object.keys(uniqueMap) }], pattern: "1" });
  37.             let req = new HttpRequest();
  38.             req.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(filter)}`);
  39.             req.method("GET");
  40.             req.connection("zprojects");
  41.             allTasks = req.execute().asJson().statusMessage.responseText.tasks || [];

  42.             let returnedIds = {};
  43.             for (let i = 0; i < allTasks.length; i++) returnedIds[String(allTasks[i].id)] = true;

  44.             let missingByProject = {};
  45.             for (let i = 0; i < projectIds.length; i++) {
  46.                 let projId = projectIds[i];
  47.                 let taskIds = Object.keys(projectTaskMap[projId]);
  48.                 for (let j = 0; j < taskIds.length; j++) {
  49.                     if (!returnedIds[taskIds[j]]) {
  50.                         if (!missingByProject[projId]) missingByProject[projId] = [];
  51.                         missingByProject[projId].push(taskIds[j]);
  52.                     }
  53.                 }
  54.             }

  55.             let missingProjIds = Object.keys(missingByProject);
  56.             for (let i = 0; i < missingProjIds.length && i < 2; i++) {
  57.                 let projId = missingProjIds[i];
  58.                 let mFilter = JSON.stringify({ criteria: [{ field_name: "id", criteria_condition: "contains", value: missingByProject[projId] }], pattern: "1" });
  59.                 let mReq = new HttpRequest();
  60.                 mReq.url(`${url}/api/v3/portal/${current.org.zohoOrgId}/projects/${projId}/tasks?page=1&per_page=200&filter=${encodeURIComponent(mFilter)}`);
  61.                 mReq.method("GET");
  62.                 mReq.connection("zprojects");
  63.                 let moreTasks = mReq.execute().asJson().statusMessage.responseText.tasks || [];
  64.                 for (let k = 0; k < moreTasks.length; k++) allTasks.push(moreTasks[k]);
  65.             }
  66.         }

  67.         let taskStatusMap = {};
  68.         let taskNameMap = {};
  69.         for (let i = 0; i < allTasks.length; i++) {
  70.             let task = allTasks[i];
  71.             let id = String(task.id);
  72.             taskStatusMap[id] = task.status;
  73.             taskNameMap[id] = task.name;
  74.         }

  75.         for (let i = 0; i < taskLogs.length; i++) {
  76.             let record = taskLogs[i];
  77.             let taskId = String(record.module_detail.id);
  78.             let status = taskStatusMap[taskId];
  79.             if (!status) continue;

  80.             if (status.name === 'In Review') {
  81.                 violations.push({ logId: record.id, taskId: taskId, taskName: taskNameMap[taskId], status: status.name, reason: "Task is in review" });
  82.             }
  83.         }

  84.         if (violations.length > 0) {
  85.             throw new ScriptError(JSON.stringify({ violations: violations }));
  86.         }
  87.     } catch (err) {
  88.         throw new ScriptError(err);
  89.     }
  90. }

 Restrict General Time Logs

Block general time logs that are not linked to a task or issue, ensuring every time entry is associated with a work item.
Code
  1. function main() {
  2.     try {
  3.         let records = current.record;
  4.         let violations = [];

  5.         for (let i = 0; i < records.length; i++) {
  6.             let record = records[i];
  7.             let linked = record.module_detail;

  8.             if (linked && linked.name === "general") {
  9.                 violations.push({ logId: record.id, user: record.user ? record.user.name : "Unknown", reason: "General timelogs not allowed. Must link to a Task or Issue." });
  10.             }
  11.         }

  12.         if (violations.length > 0) {
  13.             throw new ScriptError(JSON.stringify({ violations: violations }));
  14.         }
  15.     } catch (err) {
  16.         throw new ScriptError(err);
  17.     }
  18. }

 Restrict Time Logs to the Current Day

Allow time logging only for the current date. The script blocks both backdated and future-dated time entries.
Code
  1. function main() {
  2.     try {
  3.         let records = current.record;
  4.         let violations = [];

  5.         let now = new Date();
  6.         let todayStr = now.getFullYear() + "-" +
  7.             (now.getMonth() + 1 < 10 ? "0" : "") + (now.getMonth() + 1) + "-" +
  8.             (now.getDate() < 10 ? "0" : "") + now.getDate();

  9.         for (let i = 0; i < records.length; i++) {
  10.             let record = records[i];
  11.             let logDate = record.log_date;
  12.             if (!logDate) continue;

  13.             if (logDate !== todayStr) {
  14.                 violations.push({ logId: record.id, logDate: logDate, today: todayStr, user: record.user ? record.user.name : "Unknown", reason: logDate < todayStr ? "Cannot log for past dates" : "Cannot log for future dates" });
  15.             }
  16.         }

  17.         if (violations.length > 0) {
  18.             throw new ScriptError(JSON.stringify({ violations: violations }));
  19.         }
  20.     } catch (err) {
  21.         throw new ScriptError(err);
  22.     }
  23. }

 Restrict Time Logs for Tasks

Restrict users from logging time against tasks. This can be used in scenarios where time logging is permitted only for issues or general logs.
Code
  1. function main() {
  2.     try {
  3.         let records = current.record;
  4.         let violations = [];

  5.         for (let i = 0; i < records.length; i++) {
  6.             let record = records[i];
  7.             let linked = record.module_detail;

  8.             if (linked && linked.name === "Task") {
  9.                 violations.push({ logId: record.id, user: record.user ? record.user.name : "Unknown", reason: "Logging time against Tasks is not allowed" });
  10.             }
  11.         }

  12.         if (violations.length > 0) {
  13.             throw new ScriptError(JSON.stringify({ violations: violations }));
  14.         }
  15.     } catch (err) {
  16.         throw new ScriptError(err);
  17.     }
  18. }