@plusscommunities/pluss-maintenance-aws 2.1.44 → 2.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/createJob.js +201 -114
  2. package/createJobType.js +49 -49
  3. package/db/maintenance/addMaintenanceJob.js +100 -90
  4. package/db/maintenance/editMaintenanceJob.js +22 -12
  5. package/db/maintenance/getJobEmail.js +22 -22
  6. package/deleteJob.js +58 -58
  7. package/deleteJobType.js +40 -40
  8. package/editJob.js +126 -65
  9. package/editJobStatus.js +57 -57
  10. package/editJobType.js +61 -61
  11. package/editNote.js +117 -117
  12. package/feature.config.js +279 -262
  13. package/getData.js +48 -41
  14. package/getJob.js +4 -4
  15. package/getJobType.js +35 -35
  16. package/getJobTypes.js +66 -66
  17. package/getJobs.js +48 -48
  18. package/integration/IntegrationStrategy.js +52 -52
  19. package/integration/archibus/ArchibusStrategy.js +42 -28
  20. package/integration/index.js +19 -19
  21. package/jobChanged.js +88 -89
  22. package/jobTypesChanged.js +52 -39
  23. package/package.json +3 -3
  24. package/requests/assignRequest.js +81 -81
  25. package/requests/getAssignees.js +27 -27
  26. package/requests/getExternalSync.js +100 -100
  27. package/requests/getPropertyRequests.js +89 -0
  28. package/requests/getRequest.js +77 -77
  29. package/requests/getRequests.js +210 -144
  30. package/requests/helper/hasRequestPermission.js +12 -12
  31. package/requests/helper/isValidAssignee.js +10 -10
  32. package/requests/retrySync.js +90 -90
  33. package/requests/setExternalJobId.js +113 -113
  34. package/requests/updatePriority.js +33 -33
  35. package/scheduleJobImport.js +63 -63
  36. package/sendJobEmail.js +105 -104
  37. package/updateData.js +34 -34
  38. package/values.config.a.js +26 -25
  39. package/values.config.default.js +30 -28
  40. package/values.config.enquiry.js +222 -221
  41. package/values.config.feedback.js +194 -193
  42. package/values.config.food.js +29 -28
  43. package/values.config.forms.js +29 -28
  44. package/values.config.js +30 -28
  45. package/watchJobs.js +154 -154
@@ -3,21 +3,18 @@ const getSessionUserFromReqAuthKey = require("@plusscommunities/pluss-core-aws/h
3
3
  const validateMasterAuth = require("@plusscommunities/pluss-core-aws/helper/auth/validateMasterAuth");
4
4
  const validateSiteAccess = require("@plusscommunities/pluss-core-aws/helper/auth/validateSiteAccess");
5
5
  const indexQuery = require("@plusscommunities/pluss-core-aws/db/common/indexQuery");
6
+ const indexQueryRecursive = require("@plusscommunities/pluss-core-aws/db/common/indexQueryRecursive");
6
7
  const { values } = require("../values.config");
7
8
 
8
9
  // Normalise legacy/null status to "Open" (null = very old jobs, "Unassigned" = pre-rename)
9
10
  const STATUS_OPEN = "Open";
10
11
  const normalizeStatus = (status) =>
11
- !status || status === "Unassigned" ? STATUS_OPEN : status;
12
-
13
- // HACK: Lax "complete" match handles minor label variations across sites
14
- const isCompleted = (status) =>
15
- status && status.toLowerCase().includes("complete");
12
+ !status || status === "Unassigned" ? STATUS_OPEN : status;
16
13
 
17
14
  // Priority defaults to "Low" when not set (not set on creation)
18
15
  const DEFAULT_PRIORITY = "Low";
19
16
  const normalizePriority = (priority) =>
20
- priority == null ? DEFAULT_PRIORITY : priority;
17
+ priority == null ? DEFAULT_PRIORITY : priority;
21
18
 
22
19
  // Minimum number of filtered results to return before stopping.
23
20
  // Because DynamoDB pages are unfiltered and we filter after query,
@@ -30,151 +27,220 @@ const MIN_RESULT_COUNT = 250;
30
27
  const MIN_RESULT_COUNT_FILTERED = Infinity;
31
28
 
32
29
  const hasActiveFilters = (qParams) =>
33
- qParams.status || qParams.priority || qParams.type || qParams.search || qParams.startTime || qParams.endTime;
30
+ qParams.status ||
31
+ qParams.priority ||
32
+ qParams.type ||
33
+ qParams.search ||
34
+ qParams.startTime ||
35
+ qParams.endTime;
34
36
 
35
37
  /**
36
38
  * Apply all post-query filters to a set of jobs.
37
39
  * Extracted so the same logic runs on every auto-fill page.
38
40
  */
39
41
  const filterJobs = (jobs, qParams, authorised, assigneeTracking, userId) => {
40
- let filtered = jobs;
41
-
42
- if (qParams.status) {
43
- if (qParams.status === "Incomplete") {
44
- filtered = filtered.filter((j) => !isCompleted(normalizeStatus(j.status)));
45
- } else {
46
- filtered = filtered.filter((j) => qParams.status.includes(normalizeStatus(j.status)));
47
- }
48
- }
49
-
50
- if (qParams.priority) {
51
- filtered = filtered.filter((j) => qParams.priority.includes(normalizePriority(j.priority)));
52
- }
53
-
54
- if (qParams.type) {
55
- filtered = filtered.filter((j) => qParams.type.includes(j.type));
56
- }
57
-
58
- if (!authorised && assigneeTracking) {
59
- filtered = filtered.filter((j) => j.AssigneeId === userId || j.userID === userId);
60
- }
61
-
62
- if (qParams.startTime) {
63
- const startTime = parseInt(qParams.startTime, 10);
64
- filtered = filtered.filter((j) => j.createdUnix >= startTime);
65
- }
66
- if (qParams.endTime) {
67
- const endTime = parseInt(qParams.endTime, 10);
68
- filtered = filtered.filter((j) => j.createdUnix <= endTime);
69
- }
70
-
71
- if (qParams.search) {
72
- const searchLower = qParams.search.toLowerCase();
73
- filtered = filtered.filter((j) => {
74
- if (j.jobId && j.jobId === qParams.search) return true;
75
- if (j.room && j.room.toLowerCase().indexOf(searchLower) > -1) return true;
76
- if (j.title && j.title.toLowerCase().indexOf(searchLower) > -1) return true;
77
- return false;
78
- });
79
- }
80
-
81
- return filtered;
42
+ let filtered = jobs;
43
+
44
+ if (qParams.status) {
45
+ filtered = filtered.filter((j) =>
46
+ qParams.status.includes(normalizeStatus(j.status)),
47
+ );
48
+ }
49
+
50
+ if (qParams.priority) {
51
+ filtered = filtered.filter((j) =>
52
+ qParams.priority.includes(normalizePriority(j.priority)),
53
+ );
54
+ }
55
+
56
+ if (qParams.type) {
57
+ filtered = filtered.filter((j) => qParams.type.includes(j.type));
58
+ }
59
+
60
+ if (!authorised && assigneeTracking) {
61
+ filtered = filtered.filter(
62
+ (j) => j.AssigneeId === userId || j.userID === userId,
63
+ );
64
+ }
65
+
66
+ if (qParams.startTime) {
67
+ const startTime = parseInt(qParams.startTime, 10);
68
+ filtered = filtered.filter((j) => j.createdUnix >= startTime);
69
+ }
70
+ if (qParams.endTime) {
71
+ const endTime = parseInt(qParams.endTime, 10);
72
+ filtered = filtered.filter((j) => j.createdUnix <= endTime);
73
+ }
74
+
75
+ if (qParams.search) {
76
+ const searchLower = qParams.search.toLowerCase();
77
+ filtered = filtered.filter((j) => {
78
+ if (j.jobId && j.jobId === qParams.search) return true;
79
+ if (j.room && j.room.toLowerCase().indexOf(searchLower) > -1) return true;
80
+ if (j.title && j.title.toLowerCase().indexOf(searchLower) > -1)
81
+ return true;
82
+ return false;
83
+ });
84
+ }
85
+
86
+ return filtered;
82
87
  };
83
88
 
84
89
  module.exports = async (event) => {
85
- const qParams = event.queryStringParameters;
86
- const logId = log("getRequests", "Params", qParams);
87
-
88
- // insufficient input
89
- if (!qParams.site) {
90
- return { status: 422, data: { error: "Insufficient input" } };
91
- }
92
- log("getRequests", "SufficientInput", true, logId);
93
-
94
- // no access to site
95
- const valid = await validateSiteAccess(event, qParams.site);
96
- log("getRequests", "valid", valid, logId);
97
- if (!valid) {
98
- return { status: 403, data: { error: "Not authorised" } };
99
- }
100
-
101
- // check auth level to determine whether to fetch all requests or only matching requests
102
- const authorised = await validateMasterAuth(
103
- event,
104
- values.permissionMaintenanceTracking,
105
- qParams.site
106
- );
107
- let assigneeTracking = false;
108
- if (!authorised) {
109
- assigneeTracking = await validateMasterAuth(
110
- event,
111
- values.permissionMaintenanceAssignment,
112
- qParams.site
113
- );
114
- }
115
-
116
- log("getRequests", "authorised", authorised, logId);
117
- const userId = authorised ? null : await getSessionUserFromReqAuthKey(event);
118
-
119
- log("getRequests", "userId", userId, logId);
120
-
121
- const query =
122
- authorised || assigneeTracking
123
- ? {
124
- IndexName: "MaintenanceSiteIndex",
125
- KeyConditionExpression: "site = :site",
126
- ExpressionAttributeValues: {
127
- ":site": qParams.site,
128
- },
129
- }
130
- : {
131
- IndexName: "MaintenanceSiteUserIdIndex",
132
- KeyConditionExpression: "site = :site AND userID = :userId",
133
- ExpressionAttributeValues: {
134
- ":site": qParams.site,
135
- ":userId": userId,
136
- },
137
- };
138
-
139
- // Use the assignee GSI when filtering by assignee (avoids fetching the entire site's jobs)
140
- if (qParams.assignee && (authorised || assigneeTracking)) {
141
- query.IndexName = "MaintenanceSiteAssigneeIndex";
142
- query.KeyConditionExpression = "site = :site AND AssigneeId = :assigneeId";
143
- query.ExpressionAttributeValues[":assigneeId"] = qParams.assignee;
144
- }
145
- log("getRequests", "query", query, logId);
146
-
147
- // check whether pagination is applied
148
- if (qParams.lastKey) {
149
- try {
150
- query.ExclusiveStartKey = JSON.parse(qParams.lastKey);
151
- } catch (e) {}
152
- }
153
-
154
- // get first page of jobs
155
- let result = await indexQuery(values.tableNameMaintenance, query);
156
- let allJobs = filterJobs(result.Items, qParams, authorised, assigneeTracking, userId);
157
- let lastKey = result.LastEvaluatedKey;
158
-
159
- log("getRequests", "LastEvaluatedKey", lastKey, logId);
160
- log("getRequests", "FirstPageFiltered", allJobs.length, logId);
161
-
162
- const minResults = hasActiveFilters(qParams) ? MIN_RESULT_COUNT_FILTERED : MIN_RESULT_COUNT;
163
-
164
- // auto-fill: keep fetching pages until we have enough filtered results
165
- while (lastKey && allJobs.length < minResults) {
166
- const nextQuery = { ...query, ExclusiveStartKey: lastKey };
167
- result = await indexQuery(values.tableNameMaintenance, nextQuery);
168
- const filtered = filterJobs(result.Items, qParams, authorised, assigneeTracking, userId);
169
- allJobs = [...allJobs, ...filtered];
170
- lastKey = result.LastEvaluatedKey;
171
- }
172
-
173
- log("getRequests", "TotalFiltered", allJobs.length, logId);
174
- log("getRequests", "PagesFetched", lastKey ? "more available" : "exhausted", logId);
175
-
176
- // compile results
177
- const results = { Items: allJobs, LastKey: lastKey };
178
- log("getRequests", "Done", true, logId);
179
- return { status: 200, data: results };
90
+ const qParams = event.queryStringParameters;
91
+ const logId = log("getRequests", "Params", qParams);
92
+
93
+ // insufficient input
94
+ if (!qParams.site) {
95
+ return { status: 422, data: { error: "Insufficient input" } };
96
+ }
97
+ log("getRequests", "SufficientInput", true, logId);
98
+
99
+ // no access to site
100
+ const valid = await validateSiteAccess(event, qParams.site);
101
+ log("getRequests", "valid", valid, logId);
102
+ if (!valid) {
103
+ return { status: 403, data: { error: "Not authorised" } };
104
+ }
105
+
106
+ // check auth level to determine whether to fetch all requests or only matching requests
107
+ const authorised = await validateMasterAuth(
108
+ event,
109
+ values.permissionMaintenanceTracking,
110
+ qParams.site,
111
+ );
112
+ let assigneeTracking = false;
113
+ if (!authorised) {
114
+ assigneeTracking = await validateMasterAuth(
115
+ event,
116
+ values.permissionMaintenanceAssignment,
117
+ qParams.site,
118
+ );
119
+ }
120
+
121
+ log("getRequests", "authorised", authorised, logId);
122
+ const userId = authorised ? null : await getSessionUserFromReqAuthKey(event);
123
+
124
+ log("getRequests", "userId", userId, logId);
125
+
126
+ // Queries each status value in parallel for speed.
127
+ // Each job has one status, so no dedup needed.
128
+ const useStatusGsi =
129
+ qParams.status && !qParams.assignee && (authorised || assigneeTracking);
130
+
131
+ if (useStatusGsi) {
132
+ const statusValues = Array.isArray(qParams.status)
133
+ ? qParams.status
134
+ : qParams.status.split(",");
135
+
136
+ // "Open" matches both "Open" and legacy "Unassigned" in the DB
137
+ const dbStatuses = statusValues.flatMap((s) =>
138
+ s === STATUS_OPEN ? [STATUS_OPEN, "Unassigned"] : [s],
139
+ );
140
+
141
+ log("getRequests", "StatusGsi", dbStatuses, logId);
142
+
143
+ const allResults = await Promise.all(
144
+ dbStatuses.map((s) =>
145
+ indexQueryRecursive(values.tableNameMaintenance, {
146
+ IndexName: "MaintenanceSiteStatusIndex",
147
+ KeyConditionExpression: "site = :site AND #status = :status",
148
+ ExpressionAttributeNames: { "#status": "status" },
149
+ ExpressionAttributeValues: {
150
+ ":site": qParams.site,
151
+ ":status": s,
152
+ },
153
+ }),
154
+ ),
155
+ );
156
+
157
+ const allJobs = filterJobs(
158
+ allResults.flat(),
159
+ qParams,
160
+ authorised,
161
+ assigneeTracking,
162
+ userId,
163
+ );
164
+ log("getRequests", "StatusGsiResult", allJobs.length, logId);
165
+ return { status: 200, data: { Items: allJobs } };
166
+ }
167
+
168
+ const query =
169
+ authorised || assigneeTracking
170
+ ? {
171
+ IndexName: "MaintenanceSiteJobNoIndex",
172
+ KeyConditionExpression: "site = :site",
173
+ ExpressionAttributeValues: {
174
+ ":site": qParams.site,
175
+ },
176
+ ScanIndexForward: false,
177
+ }
178
+ : {
179
+ IndexName: "MaintenanceSiteUserIdIndex",
180
+ KeyConditionExpression: "site = :site AND userID = :userId",
181
+ ExpressionAttributeValues: {
182
+ ":site": qParams.site,
183
+ ":userId": userId,
184
+ },
185
+ };
186
+
187
+ // Use the assignee GSI when filtering by assignee (avoids fetching the entire site's jobs)
188
+ if (qParams.assignee && (authorised || assigneeTracking)) {
189
+ query.IndexName = "MaintenanceSiteAssigneeIndex";
190
+ query.KeyConditionExpression = "site = :site AND AssigneeId = :assigneeId";
191
+ query.ExpressionAttributeValues[":assigneeId"] = qParams.assignee;
192
+ }
193
+ log("getRequests", "query", query, logId);
194
+
195
+ // check whether pagination is applied
196
+ if (qParams.lastKey) {
197
+ try {
198
+ query.ExclusiveStartKey = JSON.parse(qParams.lastKey);
199
+ } catch (e) {}
200
+ }
201
+
202
+ // get first page of jobs
203
+ let result = await indexQuery(values.tableNameMaintenance, query);
204
+ let allJobs = filterJobs(
205
+ result.Items,
206
+ qParams,
207
+ authorised,
208
+ assigneeTracking,
209
+ userId,
210
+ );
211
+ let lastKey = result.LastEvaluatedKey;
212
+
213
+ log("getRequests", "LastEvaluatedKey", lastKey, logId);
214
+ log("getRequests", "FirstPageFiltered", allJobs.length, logId);
215
+
216
+ const minResults = hasActiveFilters(qParams)
217
+ ? MIN_RESULT_COUNT_FILTERED
218
+ : MIN_RESULT_COUNT;
219
+
220
+ // auto-fill: keep fetching pages until we have enough filtered results
221
+ while (lastKey && allJobs.length < minResults) {
222
+ const nextQuery = { ...query, ExclusiveStartKey: lastKey };
223
+ result = await indexQuery(values.tableNameMaintenance, nextQuery);
224
+ const filtered = filterJobs(
225
+ result.Items,
226
+ qParams,
227
+ authorised,
228
+ assigneeTracking,
229
+ userId,
230
+ );
231
+ allJobs = [...allJobs, ...filtered];
232
+ lastKey = result.LastEvaluatedKey;
233
+ }
234
+
235
+ log("getRequests", "TotalFiltered", allJobs.length, logId);
236
+ log(
237
+ "getRequests",
238
+ "PagesFetched",
239
+ lastKey ? "more available" : "exhausted",
240
+ logId,
241
+ );
242
+
243
+ const results = { Items: allJobs, LastKey: lastKey };
244
+ log("getRequests", "Done", true, logId);
245
+ return { status: 200, data: results };
180
246
  };
@@ -10,16 +10,16 @@ const isValidAssignee = require("./isValidAssignee");
10
10
  const { values } = require("../../values.config");
11
11
 
12
12
  module.exports = async (event, job) => {
13
- const valid = await validateMasterAuth(
14
- event,
15
- values.permissionMaintenanceTracking,
16
- job.site
17
- );
18
- if (valid) {
19
- return true;
20
- }
21
- if (!job.AssigneeId) {
22
- return false;
23
- }
24
- return await isValidAssignee(event, job.site, job.AssigneeId);
13
+ const valid = await validateMasterAuth(
14
+ event,
15
+ values.permissionMaintenanceTracking,
16
+ job.site,
17
+ );
18
+ if (valid) {
19
+ return true;
20
+ }
21
+ if (!job.AssigneeId) {
22
+ return false;
23
+ }
24
+ return await isValidAssignee(event, job.site, job.AssigneeId);
25
25
  };
@@ -10,14 +10,14 @@ const { values } = require("../../values.config");
10
10
  * @returns {Boolean} true if valid
11
11
  */
12
12
  module.exports = async (event, site, userId) => {
13
- const valid = await validateMasterAuth(
14
- event,
15
- values.permissionMaintenanceAssignment,
16
- site
17
- );
18
- if (!valid) {
19
- return false;
20
- }
21
- const loggedInUser = await getSessionUserFromReqAuthKey(event);
22
- return loggedInUser === userId;
13
+ const valid = await validateMasterAuth(
14
+ event,
15
+ values.permissionMaintenanceAssignment,
16
+ site,
17
+ );
18
+ if (!valid) {
19
+ return false;
20
+ }
21
+ const loggedInUser = await getSessionUserFromReqAuthKey(event);
22
+ return loggedInUser === userId;
23
23
  };
@@ -20,30 +20,30 @@ const { getStrategy } = require("../integration");
20
20
  * @returns {Promise<boolean>} True if the job has already been synced
21
21
  */
22
22
  const isAlreadySynced = async (job, logId) => {
23
- const history = job.history || [];
24
- const hasSuccessfulSync = history.some(
25
- (entry) => entry.EntryType === "ExternalIDSet"
26
- );
23
+ const history = job.history || [];
24
+ const hasSuccessfulSync = history.some(
25
+ (entry) => entry.EntryType === "ExternalIDSet",
26
+ );
27
27
 
28
- if (hasSuccessfulSync) {
29
- log("isAlreadySynced", "FoundInHistory", true, logId);
30
- return true;
31
- }
28
+ if (hasSuccessfulSync) {
29
+ log("isAlreadySynced", "FoundInHistory", true, logId);
30
+ return true;
31
+ }
32
32
 
33
- try {
34
- const integrationStrategy = await getStrategy(job.site);
35
- if (integrationStrategy.getExternalId) {
36
- const externalId = await integrationStrategy.getExternalId(job);
37
- if (externalId) {
38
- log("isAlreadySynced", "FoundViaStrategy", externalId, logId);
39
- return true;
40
- }
41
- }
42
- } catch (error) {
43
- log("isAlreadySynced", "StrategyCheckError", error.message, logId);
44
- }
33
+ try {
34
+ const integrationStrategy = await getStrategy(job.site);
35
+ if (integrationStrategy.getExternalId) {
36
+ const externalId = await integrationStrategy.getExternalId(job);
37
+ if (externalId) {
38
+ log("isAlreadySynced", "FoundViaStrategy", externalId, logId);
39
+ return true;
40
+ }
41
+ }
42
+ } catch (error) {
43
+ log("isAlreadySynced", "StrategyCheckError", error.message, logId);
44
+ }
45
45
 
46
- return false;
46
+ return false;
47
47
  };
48
48
 
49
49
  /**
@@ -80,82 +80,82 @@ const isAlreadySynced = async (job, logId) => {
80
80
  * // 500 - Internal error
81
81
  */
82
82
  module.exports = async (event, data) => {
83
- const action = "retrySync";
84
- const logId = log(action, "Input", data);
83
+ const action = "retrySync";
84
+ const logId = log(action, "Input", data);
85
85
 
86
- try {
87
- // Validate required fields
88
- if (!data.id) {
89
- return {
90
- status: 422,
91
- data: { error: "Missing required field: id" },
92
- };
93
- }
86
+ try {
87
+ // Validate required fields
88
+ if (!data.id) {
89
+ return {
90
+ status: 422,
91
+ data: { error: "Missing required field: id" },
92
+ };
93
+ }
94
94
 
95
- // Fetch the job record
96
- let job;
97
- try {
98
- job = await getRef(values.tableNameMaintenance, "id", data.id);
99
- } catch (error) {
100
- log(action, "Error:JobNotFound", { error: error.message }, logId);
101
- return { status: 404, data: { error: "Job not found" } };
102
- }
95
+ // Fetch the job record
96
+ let job;
97
+ try {
98
+ job = await getRef(values.tableNameMaintenance, "id", data.id);
99
+ } catch (error) {
100
+ log(action, "Error:JobNotFound", { error: error.message }, logId);
101
+ return { status: 404, data: { error: "Job not found" } };
102
+ }
103
103
 
104
- if (!job) {
105
- log(action, "JobNotFound", { id: data.id }, logId);
106
- return { status: 404, data: { error: "Job not found" } };
107
- }
104
+ if (!job) {
105
+ log(action, "JobNotFound", { id: data.id }, logId);
106
+ return { status: 404, data: { error: "Job not found" } };
107
+ }
108
108
 
109
- // Validate user has permission to manage maintenance for this site
110
- const authorised = await validateMasterAuth(
111
- event,
112
- values.permissionMaintenanceTracking,
113
- job.site
114
- );
109
+ // Validate user has permission to manage maintenance for this site
110
+ const authorised = await validateMasterAuth(
111
+ event,
112
+ values.permissionMaintenanceTracking,
113
+ job.site,
114
+ );
115
115
 
116
- if (!authorised) {
117
- log(action, "NotAuthorised", { site: job.site }, logId);
118
- return { status: 403, data: { error: "Not authorised" } };
119
- }
116
+ if (!authorised) {
117
+ log(action, "NotAuthorised", { site: job.site }, logId);
118
+ return { status: 403, data: { error: "Not authorised" } };
119
+ }
120
120
 
121
- // Prevent duplicate syncs - check if already synced via history or external entity
122
- const alreadySynced = await isAlreadySynced(job, logId);
123
- if (alreadySynced) {
124
- log(action, "AlreadySynced", { id: job.id }, logId);
125
- return {
126
- status: 400,
127
- data: { error: "Job already synced to external system" },
128
- };
129
- }
121
+ // Prevent duplicate syncs - check if already synced via history or external entity
122
+ const alreadySynced = await isAlreadySynced(job, logId);
123
+ if (alreadySynced) {
124
+ log(action, "AlreadySynced", { id: job.id }, logId);
125
+ return {
126
+ status: 400,
127
+ data: { error: "Job already synced to external system" },
128
+ };
129
+ }
130
130
 
131
- // Set ExtCreateRetry to a new timestamp value.
132
- // The jobChanged stream handler detects this field change and
133
- // triggers pushRequestToIntegration() to retry the external sync.
134
- const retryTimestamp = moment.utc().valueOf();
135
- const updatedJob = await editRef(
136
- values.tableNameMaintenance,
137
- "id",
138
- job.id,
139
- {
140
- ExtCreateRetry: retryTimestamp,
141
- }
142
- );
131
+ // Set ExtCreateRetry to a new timestamp value.
132
+ // The jobChanged stream handler detects this field change and
133
+ // triggers pushRequestToIntegration() to retry the external sync.
134
+ const retryTimestamp = moment.utc().valueOf();
135
+ const updatedJob = await editRef(
136
+ values.tableNameMaintenance,
137
+ "id",
138
+ job.id,
139
+ {
140
+ ExtCreateRetry: retryTimestamp,
141
+ },
142
+ );
143
143
 
144
- log(action, "RetryTriggered", { ExtCreateRetry: retryTimestamp }, logId);
144
+ log(action, "RetryTriggered", { ExtCreateRetry: retryTimestamp }, logId);
145
145
 
146
- return {
147
- status: 200,
148
- data: {
149
- success: true,
150
- message: "Sync retry initiated. Check back shortly for results.",
151
- job: updatedJob,
152
- },
153
- };
154
- } catch (error) {
155
- log(action, "InternalError", error, logId);
156
- return {
157
- status: 500,
158
- data: { error: "Internal error", message: error.message },
159
- };
160
- }
146
+ return {
147
+ status: 200,
148
+ data: {
149
+ success: true,
150
+ message: "Sync retry initiated. Check back shortly for results.",
151
+ job: updatedJob,
152
+ },
153
+ };
154
+ } catch (error) {
155
+ log(action, "InternalError", error, logId);
156
+ return {
157
+ status: 500,
158
+ data: { error: "Internal error", message: error.message },
159
+ };
160
+ }
161
161
  };