Time Logs 用 CodeX スクリプト

Time Logs 用 CodeX スクリプト

お知らせ:当社は、お客様により充実したサポート情報を迅速に提供するため、本ページのコンテンツは機械翻訳を用いて日本語に翻訳しています。正確かつ最新のサポート情報をご覧いただくには、本内容の英語版を参照してください。

Zoho ProjectsのCodeXスクリプトは、特定のルールや検証を適用するための高度な条件ロジックスクリプトです。ユーザーは、工数を作成、更新、ブロック、移行できる方法やタイミングを制御できます。

使用例

タスクの作業時間に基づく工数の制限

タスクに割り当てられた合計作業時間を超える工数を登録できないようにします。作業時間が割り当てられていないタスクへの工数登録もブロックされます。
コード
  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/${##PROTECTED_0##.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/${##PROTECTED_0##.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/${##PROTECTED_0##.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: '作業時間が割り当てられていません' });
  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: '作業時間を超過します' });
  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. }

未割り当てのタスクまたは課題に対する工数記録の制限

担当者が1人以上割り当てられているタスクまたは課題にのみ、工数を記録できるようにします。担当者が割り当てられていない作業項目への工数記録は拒否されます。
コード
  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/${##PROTECTED_0##.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/${##PROTECTED_0##.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/${##PROTECTED_0##.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. }

週末の工数記録の制限

選択した記録日を検証し、土曜日と日曜日には工数を記録できないように制限します。
コード
  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 : '不明', day: day === 0 ? '日曜日' : '土曜日', reason: '週末には工数を記録できません' });
  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. }

祝日の工数記録の制限

Zoho Peopleから祝日情報を取得し、選択した記録日を検証することで、組織の祝日に工数を入力できないようにします。
コード
  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 : '不明', holiday: holidayNames[logDate], reason: logDate + 'は祝日です(' + 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. }

休暇または休業中の工数記録の制限

Zoho Peopleの休暇データを使用し、承認済みの休暇または休業日にユーザーが工数を記録できないようにします。
コード
  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. }

タスクまたは課題の担当者のみに工数記録を許可

タスクまたは課題の担当者として割り当てられたユーザーのみが、そのタスクまたは課題に工数を記録できるようにします。担当者が割り当てられていないタスクや、権限のないユーザーによる工数記録はブロックされます。
コード
  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/${##PROTECTED_0##.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/${##PROTECTED_0##.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/${##PROTECTED_0##.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. }

レビュー中のタスクへの工数登録の制限

ステータスがレビュー中のタスクには工数を登録できないようにし、実際に作業を行っている段階でのみ工数が登録されるようにします。
コード
  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/${##PROTECTED_0##.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/${##PROTECTED_0##.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/${##PROTECTED_0##.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. }

一般工数の登録制限

タスクや課題に関連付けられていない一般工数の登録を禁止し、すべての工数が作業項目に関連付けられるようにします。
コード
  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 : '不明', reason: '一般のタイムログは許可されていません。タスクまたは課題に関連付ける必要があります。' });
  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. }

時間記録を当日に制限

当日のみ時間を記録できるようにします。このスクリプトにより、過去の日付と未来の日付の時間記録がいずれもブロックされます。
コード
  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 : '不明', reason: logDate < todayStr ? '過去の日付には時間を記録できません' : '未来の日付には時間を記録できません' });
  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. }

タスクへの時間記録の制限

ユーザーがタスクに時間を記録できないようにします。時間の記録を課題または一般ログに対してのみ許可する場合に使用できます。
コード
  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 : '不明', reason: 'タスクに時間を記録することはできません' });
  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. }