@plusscommunities/pluss-maintenance-aws-forms 2.1.45 → 2.1.47

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 (44) hide show
  1. package/createJob.js +207 -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 +137 -65
  9. package/editJobStatus.js +57 -57
  10. package/editJobType.js +61 -61
  11. package/editNote.js +117 -117
  12. package/feature.config.js +283 -270
  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/helper/hasRequestPermission.js +12 -12
  30. package/requests/helper/isValidAssignee.js +10 -10
  31. package/requests/retrySync.js +90 -90
  32. package/requests/setExternalJobId.js +113 -113
  33. package/requests/updatePriority.js +33 -33
  34. package/scheduleJobImport.js +63 -63
  35. package/sendJobEmail.js +105 -104
  36. package/updateData.js +34 -34
  37. package/values.config.a.js +26 -25
  38. package/values.config.default.js +30 -28
  39. package/values.config.enquiry.js +222 -221
  40. package/values.config.feedback.js +194 -193
  41. package/values.config.food.js +29 -28
  42. package/values.config.forms.js +29 -28
  43. package/values.config.js +29 -28
  44. package/watchJobs.js +154 -154
package/createJob.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const _ = require("lodash");
2
2
  const config = require("./config.json");
3
3
  const { init } = require("@plusscommunities/pluss-core-aws/config");
4
- const { getBody } = require("@plusscommunities/pluss-core-aws/helper");
4
+ const { getBody, log } = require("@plusscommunities/pluss-core-aws/helper");
5
5
  const generateJsonResponse = require("@plusscommunities/pluss-core-aws/helper/generateJsonResponse");
6
6
  const addMaintenanceJob = require("./db/maintenance/addMaintenanceJob");
7
7
  const validateSiteAccess = require("@plusscommunities/pluss-core-aws/helper/auth/validateSiteAccess");
@@ -11,126 +11,219 @@ const getUserPreview = require("@plusscommunities/pluss-core-aws/helper/getUserP
11
11
  const publishActivity = require("@plusscommunities/pluss-core-aws/db/activity/publishActivity");
12
12
  const sendJobEmail = require("./sendJobEmail");
13
13
  const logAnalyticsActivity = require("@plusscommunities/pluss-core-aws/db/analytics/logAnalyticsActivity");
14
+ const {
15
+ PropertyRepository,
16
+ } = require("@plusscommunities/pluss-core-aws/db/property/PropertyRepository");
14
17
  const { values } = require("./values.config");
15
18
 
19
+ const properties = new PropertyRepository();
20
+
21
+ // Property linking also needs the Aurora wiring gs injects only in
22
+ // environments with the database stack; without it, requests keep the
23
+ // pre-property behaviour (free-text room required).
24
+ const propertyLinkingEnabled =
25
+ values.linkToProperty === true && !!process.env.POSTGRES_SECRET_ARN;
26
+
16
27
  module.exports.createJob = (event, context, callback) => {
17
- init(config);
18
- const data = getBody(event);
28
+ // Avoids waiting on the pooled DB connection to close before returning.
29
+ context.callbackWaitsForEmptyEventLoop = false;
30
+ init(config);
31
+ const data = getBody(event);
32
+
33
+ validateSiteAccess(event, data.location)
34
+ .then(async (authorised) => {
35
+ if (!authorised) {
36
+ return callback(
37
+ null,
38
+ generateJsonResponse(403, { fail: true, error: "not authorised" }),
39
+ );
40
+ }
41
+ // With linking on, location is resolved later; without it, room is required now.
42
+ const hasLocation = propertyLinkingEnabled || !_.isUndefined(data.room);
43
+ if (
44
+ !data.userID ||
45
+ _.isUndefined(data.userName) ||
46
+ !hasLocation ||
47
+ _.isUndefined(data.title)
48
+ ) {
49
+ log("createJob", "InsufficientInput", data.userID);
50
+ return callback(
51
+ null,
52
+ generateJsonResponse(422, { error: "Insufficient input" }),
53
+ );
54
+ }
55
+ let user = await getUserPreviewFromReq(event);
56
+ // Capture submitter before `user` is reassigned to the subject below.
57
+ const submittingUserId = user.id;
58
+ const isOnBehalf = data.userID !== "undefined" && user.id !== data.userID;
59
+ if (isOnBehalf) {
60
+ const canRequest = await validateMasterAuth(
61
+ event,
62
+ values.permissionMaintenanceTracking,
63
+ data.location,
64
+ );
65
+ if (!canRequest) {
66
+ return callback(
67
+ null,
68
+ generateJsonResponse(422, {
69
+ fail: true,
70
+ error: "cannot request on behalf",
71
+ }),
72
+ );
73
+ }
19
74
 
20
- validateSiteAccess(event, data.location)
21
- .then(async (authorised) => {
22
- if (!authorised) {
23
- return callback(
24
- null,
25
- generateJsonResponse(403, { fail: true, error: "not authorised" })
26
- );
27
- }
28
- if (
29
- !data.userID ||
30
- _.isUndefined(data.userName) ||
31
- _.isUndefined(data.room) ||
32
- _.isUndefined(data.title)
33
- ) {
34
- console.error("insufficient input", data.userID);
35
- return callback(
36
- null,
37
- generateJsonResponse(422, { error: "Insufficient input" })
38
- );
39
- }
40
- let user = await getUserPreviewFromReq(event);
41
- if (data.userID !== "undefined" && user.id !== data.userID) {
42
- const canRequest = await validateMasterAuth(
43
- event,
44
- values.permissionMaintenanceTracking,
45
- data.location
46
- );
47
- if (!canRequest) {
48
- return callback(
49
- null,
50
- generateJsonResponse(422, {
51
- fail: true,
52
- error: "cannot request on behalf",
53
- })
54
- );
55
- }
75
+ user = await getUserPreview(data.userID);
76
+ }
56
77
 
57
- user = await getUserPreview(data.userID);
58
- }
78
+ // Resolved server-side, never taken from the request, so a resident cannot spoof a property.
79
+ let resolvedProperty = null;
80
+ if (propertyLinkingEnabled) {
81
+ // An explicit propertyId is honoured for any actor holding the tracking
82
+ // permission (staff can also submit as themselves); it is client input,
83
+ // so resolution fails closed.
84
+ const picksProperty =
85
+ !!data.propertyId &&
86
+ (isOnBehalf ||
87
+ (await validateMasterAuth(
88
+ event,
89
+ values.permissionMaintenanceTracking,
90
+ data.location,
91
+ )));
92
+ if (picksProperty) {
93
+ try {
94
+ resolvedProperty = await properties.byId({
95
+ site: data.location,
96
+ propertyId: data.propertyId,
97
+ });
98
+ } catch (error) {
99
+ // A resolver outage is not invalid input.
100
+ log("createJob", "PropertyResolveError", error.message);
101
+ return callback(
102
+ null,
103
+ generateJsonResponse(500, { error: "Internal Error" }),
104
+ );
105
+ }
106
+ if (!resolvedProperty) {
107
+ return callback(
108
+ null,
109
+ generateJsonResponse(422, {
110
+ error: "Invalid property for this site",
111
+ }),
112
+ );
113
+ }
114
+ } else if (!isOnBehalf) {
115
+ // Self-submission: resolve the submitter's own link, failing open so a
116
+ // resolver outage cannot block residents from reporting.
117
+ try {
118
+ resolvedProperty = await properties.forUser({
119
+ site: data.location,
120
+ userId: submittingUserId,
121
+ });
122
+ } catch (error) {
123
+ log("createJob", "PropertyResolveError", error.message);
124
+ resolvedProperty = null;
125
+ }
126
+ }
127
+ }
59
128
 
60
- addMaintenanceJob(
61
- user.id,
62
- user.displayName,
63
- user,
64
- data.phone,
65
- data.room,
66
- data.title,
67
- data.description,
68
- data.type,
69
- data.isHome,
70
- data.homeText,
71
- data.images || data.image,
72
- data.location,
73
- data.audience,
74
- data.customFields
75
- ).then((id) => {
76
- sendJobEmail({
77
- userID: user.id,
78
- userName: user.displayName,
79
- phone: data.phone,
80
- room: data.room,
81
- title: data.title,
82
- description: data.description,
83
- type: data.type,
84
- isHome: data.isHome,
85
- homeText: data.homeText,
86
- image: data.image,
87
- images: data.images,
88
- site: data.location,
89
- audience: data.audience,
90
- id,
91
- customFields: data.customFields,
92
- });
129
+ // Record and email use the same resolved name so they never disagree.
130
+ const propertyId = resolvedProperty ? resolvedProperty.id : undefined;
131
+ const propertyName = resolvedProperty ? resolvedProperty.name : undefined;
132
+ const room = propertyName || data.room;
93
133
 
94
- publishActivity(
95
- values.activityAddMaintenanceJob,
96
- data.location,
97
- id,
98
- user,
99
- {
100
- title: data.title,
101
- description: data.description,
102
- }
103
- );
134
+ // No property link and no room means no location.
135
+ if (propertyLinkingEnabled && !room) {
136
+ log("createJob", "InsufficientInput", data.userID);
137
+ return callback(
138
+ null,
139
+ generateJsonResponse(422, { error: "Insufficient input" }),
140
+ );
141
+ }
104
142
 
105
- logAnalyticsActivity(
106
- `Request`,
107
- values.entityKey,
108
- data.location,
109
- user,
110
- id
111
- );
143
+ return addMaintenanceJob(
144
+ user.id,
145
+ user.displayName,
146
+ user,
147
+ data.phone,
148
+ room,
149
+ data.title,
150
+ data.description,
151
+ data.type,
152
+ data.isHome,
153
+ data.homeText,
154
+ data.images || data.image,
155
+ data.location,
156
+ data.audience,
157
+ data.customFields,
158
+ propertyId,
159
+ propertyName,
160
+ ).then(async (id) => {
161
+ // Awaited (allSettled) — the frozen event loop would drop fire-and-forget
162
+ // work, one failure must not strand the others, and a failed side effect
163
+ // must not fail the create.
164
+ const settled = await Promise.allSettled([
165
+ sendJobEmail({
166
+ userID: user.id,
167
+ userName: user.displayName,
168
+ phone: data.phone,
169
+ room,
170
+ title: data.title,
171
+ description: data.description,
172
+ type: data.type,
173
+ isHome: data.isHome,
174
+ homeText: data.homeText,
175
+ image: data.image,
176
+ images: data.images,
177
+ site: data.location,
178
+ audience: data.audience,
179
+ id,
180
+ customFields: data.customFields,
181
+ }),
182
+ publishActivity(
183
+ values.activityAddMaintenanceJob,
184
+ data.location,
185
+ id,
186
+ user,
187
+ {
188
+ title: data.title,
189
+ description: data.description,
190
+ },
191
+ ),
192
+ logAnalyticsActivity(
193
+ `Request`,
194
+ values.entityKey,
195
+ data.location,
196
+ user,
197
+ id,
198
+ ),
199
+ ]);
200
+ settled
201
+ .filter((result) => result.status === "rejected")
202
+ .forEach((result) => {
203
+ log("createJob", "SideEffectError", result.reason);
204
+ });
112
205
 
113
- return callback(
114
- null,
115
- generateJsonResponse(200, {
116
- success: true,
117
- id,
118
- searchResult: id,
119
- })
120
- );
121
- });
122
- })
123
- .catch((error) => {
124
- console.error(
125
- "Failed to save maintenance node --> email successful",
126
- data.userID
127
- );
128
- console.log(error);
129
- return callback(
130
- null,
131
- generateJsonResponse(422, {
132
- error,
133
- })
134
- );
135
- });
206
+ return callback(
207
+ null,
208
+ generateJsonResponse(200, {
209
+ success: true,
210
+ id,
211
+ searchResult: id,
212
+ }),
213
+ );
214
+ });
215
+ })
216
+ .catch((error) => {
217
+ console.error(
218
+ "Failed to save maintenance node --> email successful",
219
+ data.userID,
220
+ );
221
+ console.log(error);
222
+ return callback(
223
+ null,
224
+ generateJsonResponse(422, {
225
+ error,
226
+ }),
227
+ );
228
+ });
136
229
  };
package/createJobType.js CHANGED
@@ -8,57 +8,57 @@ const updateRef = require("@plusscommunities/pluss-core-aws/db/common/updateRef"
8
8
  const { values } = require("./values.config");
9
9
 
10
10
  module.exports.createJobType = (event, context, callback) => {
11
- init(config);
12
- const data = getBody(event);
11
+ init(config);
12
+ const data = getBody(event);
13
13
 
14
- if (!data.site || !data.name || !data.email || !data.description) {
15
- return callback(
16
- null,
17
- generateJsonResponse(422, { error: "No site, type or email attached" })
18
- );
19
- }
14
+ if (!data.site || !data.name || !data.email || !data.description) {
15
+ return callback(
16
+ null,
17
+ generateJsonResponse(422, { error: "No site, type or email attached" }),
18
+ );
19
+ }
20
20
 
21
- validateMasterAuth(event, values.permissionMaintenanceTypes).then(
22
- (authorised) => {
23
- if (!authorised) {
24
- console.error("Authorization not valid");
25
- return callback(
26
- null,
27
- generateJsonResponse(422, {
28
- error: { message: "Admin - Create User - not authorized." },
29
- })
30
- );
31
- }
21
+ validateMasterAuth(event, values.permissionMaintenanceTypes).then(
22
+ (authorised) => {
23
+ if (!authorised) {
24
+ console.error("Authorization not valid");
25
+ return callback(
26
+ null,
27
+ generateJsonResponse(422, {
28
+ error: { message: "Admin - Create User - not authorized." },
29
+ }),
30
+ );
31
+ }
32
32
 
33
- const jobType = {
34
- id: createGuid().substring(0, 8),
35
- email: data.email,
36
- typeName: data.name,
37
- description: data.description,
38
- level: data.level,
39
- site: data.site,
40
- hasCustomFields: data.hasCustomFields ?? false,
41
- customFields: data.customFields ?? [],
42
- };
33
+ const jobType = {
34
+ id: createGuid().substring(0, 8),
35
+ email: data.email,
36
+ typeName: data.name,
37
+ description: data.description,
38
+ level: data.level,
39
+ site: data.site,
40
+ hasCustomFields: data.hasCustomFields ?? false,
41
+ customFields: data.customFields ?? [],
42
+ };
43
43
 
44
- updateRef(values.tableNameJobTypes, jobType)
45
- .then((result) => {
46
- return callback(
47
- null,
48
- generateJsonResponse(200, {
49
- success: true,
50
- jobType: result,
51
- })
52
- );
53
- })
54
- .catch((error) => {
55
- return callback(
56
- null,
57
- generateJsonResponse(422, {
58
- error,
59
- })
60
- );
61
- });
62
- }
63
- );
44
+ updateRef(values.tableNameJobTypes, jobType)
45
+ .then((result) => {
46
+ return callback(
47
+ null,
48
+ generateJsonResponse(200, {
49
+ success: true,
50
+ jobType: result,
51
+ }),
52
+ );
53
+ })
54
+ .catch((error) => {
55
+ return callback(
56
+ null,
57
+ generateJsonResponse(422, {
58
+ error,
59
+ }),
60
+ );
61
+ });
62
+ },
63
+ );
64
64
  };
@@ -7,100 +7,110 @@ const { getConfig } = require("@plusscommunities/pluss-core-aws/config");
7
7
  const { values } = require("../../values.config");
8
8
 
9
9
  module.exports = async (
10
- userID,
11
- userName,
12
- user,
13
- phone,
14
- room,
15
- title,
16
- description,
17
- type,
18
- isHome,
19
- homeText,
20
- images,
21
- location,
22
- audience,
23
- customFields
10
+ userID,
11
+ userName,
12
+ user,
13
+ phone,
14
+ room,
15
+ title,
16
+ description,
17
+ type,
18
+ isHome,
19
+ homeText,
20
+ images,
21
+ location,
22
+ audience,
23
+ customFields,
24
+ propertyId,
25
+ propertyName,
24
26
  ) => {
25
- const requestToSave = {
26
- userID,
27
- userName,
28
- userProfilePic: user ? user.profilePic : null,
29
- phone,
30
- room,
31
- title,
32
- description,
33
- type,
34
- isHome,
35
- homeText,
36
- images,
37
- site: location,
38
- audience,
39
- customFields,
40
- };
27
+ // Property name overrides room so existing readers of room keep working.
28
+ const resolvedRoom = propertyName || room;
41
29
 
42
- if (requestToSave.images) {
43
- const thumbSource = Array.isArray(requestToSave.images)
44
- ? requestToSave.images[0]
45
- : requestToSave.images;
46
- if (thumbSource) {
47
- requestToSave.thumbnail = thumbSource
48
- .replace("/general1400/", "/general300/")
49
- .replace("/uploads1400/", "/uploads300/");
50
- }
51
- }
52
- if (_.isEmpty(requestToSave.audience)) {
53
- requestToSave.audience = [];
54
- }
55
- requestToSave.status = getConfig().maintenanceInstantComplete
56
- ? "Completed"
57
- : "Unassigned";
58
- requestToSave.history = [
59
- {
60
- timestamp: moment.utc().valueOf(),
61
- status: requestToSave.status,
62
- user,
63
- },
64
- ];
30
+ const requestToSave = {
31
+ userID,
32
+ userName,
33
+ userProfilePic: user ? user.profilePic : null,
34
+ phone,
35
+ room: resolvedRoom,
36
+ title,
37
+ description,
38
+ type,
39
+ isHome,
40
+ homeText,
41
+ images,
42
+ site: location,
43
+ audience,
44
+ customFields,
45
+ };
65
46
 
66
- requestToSave.id = uuid.v1();
67
- requestToSave.createdTime = moment.utc().toISOString();
68
- requestToSave.createdUnix = moment.utc().valueOf();
69
- if (!requestToSave.jobId) {
70
- const jobNoQ = {
71
- IndexName: "MaintenanceSiteJobNoIndex",
72
- KeyConditionExpression: "site = :site",
73
- ExpressionAttributeValues: {
74
- ":site": requestToSave.site,
75
- },
76
- Limit: 1,
77
- ScanIndexForward: false,
78
- };
47
+ // Omit propertyId when absent so the property index stays sparse (linked rows only).
48
+ if (propertyId) {
49
+ requestToSave.propertyId = propertyId;
50
+ }
79
51
 
80
- const queryRes = await indexQuery(values.tableNameMaintenance, jobNoQ);
52
+ if (requestToSave.images) {
53
+ const thumbSource = Array.isArray(requestToSave.images)
54
+ ? requestToSave.images[0]
55
+ : requestToSave.images;
56
+ if (thumbSource) {
57
+ requestToSave.thumbnail = thumbSource
58
+ .replace("/general1400/", "/general300/")
59
+ .replace("/uploads1400/", "/uploads300/");
60
+ }
61
+ }
62
+ if (_.isEmpty(requestToSave.audience)) {
63
+ requestToSave.audience = [];
64
+ }
65
+ requestToSave.status = getConfig().maintenanceInstantComplete
66
+ ? "Completed"
67
+ : "Unassigned";
68
+ requestToSave.history = [
69
+ {
70
+ timestamp: moment.utc().valueOf(),
71
+ status: requestToSave.status,
72
+ user,
73
+ },
74
+ ];
81
75
 
82
- if (!_.isEmpty(queryRes.Items)) {
83
- requestToSave.jobNo = queryRes.Items[0].jobNo + 1;
84
- } else {
85
- const query = {
86
- IndexName: "MaintenanceSiteJobIdIndex",
87
- KeyConditionExpression: "site = :site",
88
- ExpressionAttributeValues: {
89
- ":site": requestToSave.site,
90
- },
91
- Limit: 1,
92
- ScanIndexForward: false,
93
- };
94
- const { Items } = await indexQuery(values.tableNameMaintenance, query);
95
- requestToSave.jobNo =
96
- !_.isEmpty(Items) && Items[0].jobId
97
- ? Number.parseInt(Items[0].jobId) + 1
98
- : 0;
99
- }
100
- requestToSave.jobId = (requestToSave.jobNo || 0) + "";
101
- }
76
+ requestToSave.id = uuid.v1();
77
+ requestToSave.createdTime = moment.utc().toISOString();
78
+ requestToSave.createdUnix = moment.utc().valueOf();
79
+ if (!requestToSave.jobId) {
80
+ const jobNoQ = {
81
+ IndexName: "MaintenanceSiteJobNoIndex",
82
+ KeyConditionExpression: "site = :site",
83
+ ExpressionAttributeValues: {
84
+ ":site": requestToSave.site,
85
+ },
86
+ Limit: 1,
87
+ ScanIndexForward: false,
88
+ };
102
89
 
103
- requestToSave.priority = "Low";
104
- await updateRef(values.tableNameMaintenance, requestToSave);
105
- return requestToSave.id;
90
+ const queryRes = await indexQuery(values.tableNameMaintenance, jobNoQ);
91
+
92
+ if (!_.isEmpty(queryRes.Items)) {
93
+ requestToSave.jobNo = queryRes.Items[0].jobNo + 1;
94
+ } else {
95
+ const query = {
96
+ IndexName: "MaintenanceSiteJobIdIndex",
97
+ KeyConditionExpression: "site = :site",
98
+ ExpressionAttributeValues: {
99
+ ":site": requestToSave.site,
100
+ },
101
+ Limit: 1,
102
+ ScanIndexForward: false,
103
+ };
104
+ const { Items } = await indexQuery(values.tableNameMaintenance, query);
105
+ requestToSave.jobNo =
106
+ !_.isEmpty(Items) && Items[0].jobId
107
+ ? Number.parseInt(Items[0].jobId) + 1
108
+ : 0;
109
+ }
110
+ requestToSave.jobId = (requestToSave.jobNo || 0) + "";
111
+ }
112
+
113
+ requestToSave.priority = "Low";
114
+ await updateRef(values.tableNameMaintenance, requestToSave);
115
+ return requestToSave.id;
106
116
  };
@@ -1,18 +1,28 @@
1
1
  const moment = require("moment");
2
+ const _ = require("lodash");
2
3
  const editRef = require("@plusscommunities/pluss-core-aws/db/common/editRef");
4
+ const getRef = require("@plusscommunities/pluss-core-aws/db/common/getRef");
5
+ const updateRef = require("@plusscommunities/pluss-core-aws/db/common/updateRef");
3
6
  const { values } = require("../../values.config");
4
7
 
5
- module.exports = (job) => {
6
- return new Promise((resolve, reject) => {
7
- job.lastActivity = moment.utc().toISOString();
8
- job.lastActivityUnix = moment.utc().valueOf();
8
+ module.exports = async (job) => {
9
+ job.lastActivity = moment.utc().toISOString();
10
+ job.lastActivityUnix = moment.utc().valueOf();
9
11
 
10
- editRef(values.tableNameMaintenance, "id", job.id, job)
11
- .then((result) => {
12
- resolve(result);
13
- })
14
- .catch((error) => {
15
- reject(error);
16
- });
17
- });
12
+ const clearsProperty =
13
+ !_.isUndefined(job.propertyId) && job.propertyId == null;
14
+ if (!clearsProperty) {
15
+ return editRef(values.tableNameMaintenance, "id", job.id, job);
16
+ }
17
+
18
+ const item = await getRef(values.tableNameMaintenance, "id", job.id);
19
+ if (!item) {
20
+ throw new Error(`Maintenance job not found: ${job.id}`);
21
+ }
22
+ // GSI hash keys can't be null; drop propertyId/propertyName in the same
23
+ // single put as the edit so a partial failure can't leave the pair split.
24
+ const merged = { ...item, ...job, id: job.id };
25
+ delete merged.propertyId;
26
+ delete merged.propertyName;
27
+ return updateRef(values.tableNameMaintenance, merged);
18
28
  };