@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
@@ -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
- const data = params || event.queryStringParameters || {};
35
- const logId = log("getExternalSync", "Params", data);
34
+ const data = params || event.queryStringParameters || {};
35
+ const logId = log("getExternalSync", "Params", data);
36
36
 
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
- }
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
- 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
- }
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
- if (!request) {
60
- log("getExternalSync", "RequestNotFound", { id: data.id }, logId);
61
- return {
62
- status: 404,
63
- data: { error: "Request not found" },
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
- // 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
- );
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
- const assignAuthorised = await isValidAssignee(
79
- event,
80
- request.site,
81
- request.AssigneeId
82
- );
78
+ const assignAuthorised = await isValidAssignee(
79
+ event,
80
+ request.site,
81
+ request.AssigneeId,
82
+ );
83
83
 
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
- }
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
- log("getExternalSync", "Authorised", true, logId);
101
+ log("getExternalSync", "Authorised", true, logId);
102
102
 
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
- });
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
- log("getExternalSync", "Results", query.Items.length, logId);
112
+ log("getExternalSync", "Results", query.Items.length, logId);
113
113
 
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
- };
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
- log("getExternalSync", "FoundSync", externalSync, logId);
124
- }
123
+ log("getExternalSync", "FoundSync", externalSync, logId);
124
+ }
125
125
 
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
- }
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
- 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
- }
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
+ };
@@ -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
- 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
- }
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
- 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
- }
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
- 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
- };
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
  };