@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.
- package/createJob.js +201 -114
- package/createJobType.js +49 -49
- package/db/maintenance/addMaintenanceJob.js +100 -90
- package/db/maintenance/editMaintenanceJob.js +22 -12
- package/db/maintenance/getJobEmail.js +22 -22
- package/deleteJob.js +58 -58
- package/deleteJobType.js +40 -40
- package/editJob.js +126 -65
- package/editJobStatus.js +57 -57
- package/editJobType.js +61 -61
- package/editNote.js +117 -117
- package/feature.config.js +279 -262
- package/getData.js +48 -41
- package/getJob.js +4 -4
- package/getJobType.js +35 -35
- package/getJobTypes.js +66 -66
- package/getJobs.js +48 -48
- package/integration/IntegrationStrategy.js +52 -52
- package/integration/archibus/ArchibusStrategy.js +42 -28
- package/integration/index.js +19 -19
- package/jobChanged.js +88 -89
- package/jobTypesChanged.js +52 -39
- package/package.json +3 -3
- package/requests/assignRequest.js +81 -81
- package/requests/getAssignees.js +27 -27
- package/requests/getExternalSync.js +100 -100
- package/requests/getPropertyRequests.js +89 -0
- package/requests/getRequest.js +77 -77
- package/requests/getRequests.js +210 -144
- package/requests/helper/hasRequestPermission.js +12 -12
- package/requests/helper/isValidAssignee.js +10 -10
- package/requests/retrySync.js +90 -90
- package/requests/setExternalJobId.js +113 -113
- package/requests/updatePriority.js +33 -33
- package/scheduleJobImport.js +63 -63
- package/sendJobEmail.js +105 -104
- package/updateData.js +34 -34
- package/values.config.a.js +26 -25
- package/values.config.default.js +30 -28
- package/values.config.enquiry.js +222 -221
- package/values.config.feedback.js +194 -193
- package/values.config.food.js +29 -28
- package/values.config.forms.js +29 -28
- package/values.config.js +30 -28
- package/watchJobs.js +154 -154
package/requests/getRequests.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
};
|
package/requests/retrySync.js
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const history = job.history || [];
|
|
24
|
+
const hasSuccessfulSync = history.some(
|
|
25
|
+
(entry) => entry.EntryType === "ExternalIDSet",
|
|
26
|
+
);
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
if (hasSuccessfulSync) {
|
|
29
|
+
log("isAlreadySynced", "FoundInHistory", true, logId);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
83
|
+
const action = "retrySync";
|
|
84
|
+
const logId = log(action, "Input", data);
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
if (!authorised) {
|
|
117
|
+
log(action, "NotAuthorised", { site: job.site }, logId);
|
|
118
|
+
return { status: 403, data: { error: "Not authorised" } };
|
|
119
|
+
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
144
|
+
log(action, "RetryTriggered", { ExtCreateRetry: retryTimestamp }, logId);
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
};
|