@plusscommunities/pluss-maintenance-aws 2.1.45 → 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 -270
- 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/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
|
@@ -31,117 +31,117 @@ const { values } = require("../values.config");
|
|
|
31
31
|
* - 500: Internal server error
|
|
32
32
|
*/
|
|
33
33
|
module.exports = async (event, params) => {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const data = params || event.queryStringParameters || {};
|
|
35
|
+
const logId = log("getExternalSync", "Params", data);
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
// Validate input
|
|
38
|
+
if (!data.id) {
|
|
39
|
+
log("getExternalSync", "MissingId", true, logId);
|
|
40
|
+
return {
|
|
41
|
+
status: 422,
|
|
42
|
+
data: { error: "Missing required field: id" },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
try {
|
|
47
|
+
// Get the request to verify it exists and get site for permission check
|
|
48
|
+
let request;
|
|
49
|
+
try {
|
|
50
|
+
request = await getRef(values.tableNameMaintenance, "id", data.id);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
log("getExternalSync", "RequestNotFound", { id: data.id }, logId);
|
|
53
|
+
return {
|
|
54
|
+
status: 404,
|
|
55
|
+
data: { error: "Request not found" },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
if (!request) {
|
|
60
|
+
log("getExternalSync", "RequestNotFound", { id: data.id }, logId);
|
|
61
|
+
return {
|
|
62
|
+
status: 404,
|
|
63
|
+
data: { error: "Request not found" },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
// Check permissions (same pattern as getRequest.js)
|
|
68
|
+
// Three levels of access:
|
|
69
|
+
// 1. Full admin with maintenance tracking permission
|
|
70
|
+
// 2. User assigned to the request
|
|
71
|
+
// 3. User who created the request
|
|
72
|
+
const authorised = await validateMasterAuth(
|
|
73
|
+
event,
|
|
74
|
+
values.permissionMaintenanceTracking,
|
|
75
|
+
request.site,
|
|
76
|
+
);
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
const assignAuthorised = await isValidAssignee(
|
|
79
|
+
event,
|
|
80
|
+
request.site,
|
|
81
|
+
request.AssigneeId,
|
|
82
|
+
);
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
84
|
+
if (!authorised && !assignAuthorised) {
|
|
85
|
+
// Check if user is the request owner
|
|
86
|
+
const userId = await getSessionUserFromReqAuthKey(event);
|
|
87
|
+
if (userId !== request.userID) {
|
|
88
|
+
log(
|
|
89
|
+
"getExternalSync",
|
|
90
|
+
"NotAuthorised",
|
|
91
|
+
{ userId, requestOwner: request.userID },
|
|
92
|
+
logId,
|
|
93
|
+
);
|
|
94
|
+
return {
|
|
95
|
+
status: 403,
|
|
96
|
+
data: { error: "Not authorised" },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
log("getExternalSync", "Authorised", true, logId);
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
let externalSync = null;
|
|
104
|
+
const query = await indexQuery("externalentities", {
|
|
105
|
+
IndexName: "OnlyInternalIdIndex",
|
|
106
|
+
KeyConditionExpression: "InternalId = :internalId",
|
|
107
|
+
ExpressionAttributeValues: {
|
|
108
|
+
":internalId": request.id,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
log("getExternalSync", "Results", query.Items.length, logId);
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
if (query.Items && query.Items.length > 0) {
|
|
115
|
+
const item = query.Items[0];
|
|
116
|
+
externalSync = {
|
|
117
|
+
systemType: item.SystemType,
|
|
118
|
+
externalId: item.ExternalId,
|
|
119
|
+
syncedAt: item.LastUpdated,
|
|
120
|
+
trackedData: item.TrackedData || {},
|
|
121
|
+
};
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
log("getExternalSync", "FoundSync", externalSync, logId);
|
|
124
|
+
}
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
// If no external sync found, return 404
|
|
127
|
+
if (!externalSync) {
|
|
128
|
+
log("getExternalSync", "NoSyncFound", { id: data.id }, logId);
|
|
129
|
+
return {
|
|
130
|
+
status: 404,
|
|
131
|
+
data: { error: "No external sync found" },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
135
|
+
log("getExternalSync", "Success", externalSync, logId);
|
|
136
|
+
return {
|
|
137
|
+
status: 200,
|
|
138
|
+
data: externalSync,
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
log("getExternalSync", "InternalError", error.toString(), logId);
|
|
142
|
+
return {
|
|
143
|
+
status: 500,
|
|
144
|
+
data: { error: "Internal error", message: error.message },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
147
|
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const { log } = require("@plusscommunities/pluss-core-aws/helper");
|
|
2
|
+
const validateMasterAuth = require("@plusscommunities/pluss-core-aws/helper/auth/validateMasterAuth");
|
|
3
|
+
const validateSiteAccess = require("@plusscommunities/pluss-core-aws/helper/auth/validateSiteAccess");
|
|
4
|
+
const indexQuery = require("@plusscommunities/pluss-core-aws/db/common/indexQuery");
|
|
5
|
+
const { values } = require("../values.config");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shape a maintenance record into the trimmed request contract the admin consumes.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} record - raw maintenance DynamoDB item
|
|
11
|
+
* @returns {object} the trimmed request
|
|
12
|
+
*/
|
|
13
|
+
function toRequest(record) {
|
|
14
|
+
return {
|
|
15
|
+
id: record.id,
|
|
16
|
+
jobId: record.jobId ?? null,
|
|
17
|
+
jobNo: typeof record.jobNo === "number" ? record.jobNo : null,
|
|
18
|
+
title: record.title ?? null,
|
|
19
|
+
status: record.status ?? null,
|
|
20
|
+
type: record.type ?? null,
|
|
21
|
+
// Denormalised property name for linked requests, free-text otherwise.
|
|
22
|
+
room: record.room ?? null,
|
|
23
|
+
timeCreated: record.createdTime ?? null,
|
|
24
|
+
createdUnix:
|
|
25
|
+
typeof record.createdUnix === "number" ? record.createdUnix : null,
|
|
26
|
+
webPath: record.id
|
|
27
|
+
? values.routeEntityPath.replace(":id", record.id)
|
|
28
|
+
: null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* List maintenance requests linked to a property, newest first.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} event - API Gateway event
|
|
36
|
+
* @returns {Promise<{status: number, data: {requests: object[]}|{error: string}}>} request list or error payload
|
|
37
|
+
*/
|
|
38
|
+
module.exports = async (event) => {
|
|
39
|
+
const qParams = event.queryStringParameters || {};
|
|
40
|
+
const { site, propertyId } = qParams;
|
|
41
|
+
const logId = log("getPropertyRequests", "Params", qParams);
|
|
42
|
+
|
|
43
|
+
if (!site || !propertyId) {
|
|
44
|
+
return { status: 422, data: { error: "Insufficient input" } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasSiteAccess = await validateSiteAccess(event, site);
|
|
48
|
+
if (!hasSiteAccess) {
|
|
49
|
+
return { status: 403, data: { error: "Not authorised" } };
|
|
50
|
+
}
|
|
51
|
+
const authorised = await validateMasterAuth(
|
|
52
|
+
event,
|
|
53
|
+
values.permissionMaintenanceTracking,
|
|
54
|
+
site,
|
|
55
|
+
);
|
|
56
|
+
if (!authorised) {
|
|
57
|
+
return { status: 403, data: { error: "Not authorised" } };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Paginate fully so results aren't truncated at DynamoDB's 1MB scan boundary.
|
|
61
|
+
// Paged locally (not indexQueryRecursive) so a query failure rejects into the
|
|
62
|
+
// router's 500 instead of hanging the request.
|
|
63
|
+
const items = [];
|
|
64
|
+
let lastKey;
|
|
65
|
+
do {
|
|
66
|
+
const query = {
|
|
67
|
+
IndexName: "MaintenancePropertyIdIndex",
|
|
68
|
+
KeyConditionExpression: "propertyId = :propertyId",
|
|
69
|
+
FilterExpression: "site = :site",
|
|
70
|
+
ExpressionAttributeValues: {
|
|
71
|
+
":propertyId": propertyId,
|
|
72
|
+
":site": site,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
if (lastKey) {
|
|
76
|
+
query.ExclusiveStartKey = lastKey;
|
|
77
|
+
}
|
|
78
|
+
const page = await indexQuery(values.tableNameMaintenance, query);
|
|
79
|
+
items.push(...(page.Items || []));
|
|
80
|
+
lastKey = page.LastEvaluatedKey;
|
|
81
|
+
} while (lastKey);
|
|
82
|
+
// Newest first.
|
|
83
|
+
const requests = items
|
|
84
|
+
.map(toRequest)
|
|
85
|
+
.sort((a, b) => (b.createdUnix || 0) - (a.createdUnix || 0));
|
|
86
|
+
|
|
87
|
+
log("getPropertyRequests", "ResponseLength", requests.length, logId);
|
|
88
|
+
return { status: 200, data: { requests } };
|
|
89
|
+
};
|
package/requests/getRequest.js
CHANGED
|
@@ -7,83 +7,83 @@ const getSessionUserFromReqAuthKey = require("@plusscommunities/pluss-core-aws/h
|
|
|
7
7
|
const { values } = require("../values.config");
|
|
8
8
|
|
|
9
9
|
module.exports = async (event, params) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
const data = params || event.queryStringParameters;
|
|
11
|
+
const logId = log("getRequest", "Params", data);
|
|
12
|
+
if (!data.id && (!data.site || !data.jobId)) {
|
|
13
|
+
return {
|
|
14
|
+
status: 422,
|
|
15
|
+
data: {
|
|
16
|
+
error: "Insufficient input",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
try {
|
|
22
|
+
let result = null;
|
|
23
|
+
if (data.jobId) {
|
|
24
|
+
const query = {
|
|
25
|
+
IndexName: "MaintenanceSiteJobIdIndex",
|
|
26
|
+
KeyConditionExpression: "site = :site and jobId = :jobId",
|
|
27
|
+
ExpressionAttributeValues: {
|
|
28
|
+
":site": data.site,
|
|
29
|
+
":jobId": data.jobId,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const { Items } = await indexQuery(values.tableNameMaintenance, query);
|
|
33
|
+
result = Items[0];
|
|
34
|
+
}
|
|
35
|
+
if (!result) {
|
|
36
|
+
result = await getRef(
|
|
37
|
+
values.tableNameMaintenance,
|
|
38
|
+
"id",
|
|
39
|
+
data.id || data.jobId,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
43
|
+
const authorised = await validateMasterAuth(
|
|
44
|
+
event,
|
|
45
|
+
values.permissionMaintenanceTracking,
|
|
46
|
+
result.site,
|
|
47
|
+
);
|
|
48
|
+
const assignAuthorised = await isValidAssignee(
|
|
49
|
+
event,
|
|
50
|
+
result.site,
|
|
51
|
+
result.AssigneeId,
|
|
52
|
+
);
|
|
53
|
+
if (!authorised && !assignAuthorised) {
|
|
54
|
+
// Check if the job belongs to the user
|
|
55
|
+
const userId = await getSessionUserFromReqAuthKey(event);
|
|
56
|
+
if (userId !== result.userID) {
|
|
57
|
+
return {
|
|
58
|
+
status: 403,
|
|
59
|
+
data: {
|
|
60
|
+
error: "Not authorised",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (data.includeComments) {
|
|
66
|
+
const commentsQuery = {
|
|
67
|
+
IndexName: "CommentsEntityIdIndex",
|
|
68
|
+
KeyConditionExpression: "EntityId = :groupId",
|
|
69
|
+
ExpressionAttributeValues: {
|
|
70
|
+
":groupId": getRowId(result.id, values.entityKey),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
const commentsData = await indexQuery("comments", commentsQuery);
|
|
74
|
+
result.Comments = commentsData.Items;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
status: 200,
|
|
78
|
+
data: result,
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
log("getRequest", "Error", error.toString(), logId);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
status: 500,
|
|
85
|
+
data: {
|
|
86
|
+
error: "Internal error",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
89
|
};
|
|
@@ -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
|
};
|