@universis/accommodation 1.0.0

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 (134) hide show
  1. package/.gitlab-ci.yml +23 -0
  2. package/LICENSE +163 -0
  3. package/README.md +3 -0
  4. package/dist/AccommodationSchemaLoader.js +35 -0
  5. package/dist/AccommodationSchemaLoader.js.map +1 -0
  6. package/dist/AccommodationService.js +68 -0
  7. package/dist/AccommodationService.js.map +1 -0
  8. package/dist/DepartmentReplacer.js +54 -0
  9. package/dist/DepartmentReplacer.js.map +1 -0
  10. package/dist/StudentReplacer.js +75 -0
  11. package/dist/StudentReplacer.js.map +1 -0
  12. package/dist/config/models/Accommodation.json +96 -0
  13. package/dist/config/models/AccommodationCardEffectiveStatusType.json +107 -0
  14. package/dist/config/models/AccommodationCardEffectiveStatusTypeLocale.json +85 -0
  15. package/dist/config/models/AccommodationChildFinancialAttribute.json +46 -0
  16. package/dist/config/models/AccommodationGuardianFinancialAttribute.json +46 -0
  17. package/dist/config/models/AccommodationInscriptionModeTag.json +100 -0
  18. package/dist/config/models/AccommodationRequestAction.json +500 -0
  19. package/dist/config/models/AccommodationRequestActionMessage.json +46 -0
  20. package/dist/config/models/AccommodationRequestActionReview.json +31 -0
  21. package/dist/config/models/AccommodationRequestAttachmentConfiguration.json +72 -0
  22. package/dist/config/models/AccommodationRequestCategory.json +50 -0
  23. package/dist/config/models/AccommodationRequestEffectiveStatusType.json +212 -0
  24. package/dist/config/models/AccommodationRequestEffectiveStatusTypeLocale.json +85 -0
  25. package/dist/config/models/AccommodationRequestEvent.json +117 -0
  26. package/dist/config/models/AccommodationRequestFinancialAttribute.json +53 -0
  27. package/dist/config/models/AccommodationRoom.json +151 -0
  28. package/dist/config/models/AccommodationSiblingFinancialAttribute.json +57 -0
  29. package/dist/config/models/CancelAccommodationCardAction.json +32 -0
  30. package/dist/config/models/GuardianType.json +78 -0
  31. package/dist/config/models/Review.json +50 -0
  32. package/dist/config/models/RoomType.json +320 -0
  33. package/dist/config/models/RoomTypeLocale.json +86 -0
  34. package/dist/config/models/StudentAccommodationCard.json +158 -0
  35. package/dist/config/models/TestAction.json +50 -0
  36. package/dist/config/scope.access.js +148 -0
  37. package/dist/config/scope.access.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +3 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/listeners/AddDefaultAttachmentConfiguration.js +44 -0
  42. package/dist/listeners/AddDefaultAttachmentConfiguration.js.map +1 -0
  43. package/dist/listeners/DefaultAttachmentConfigurations.json +277 -0
  44. package/dist/listeners/DefaultAttachmentTypes.json +523 -0
  45. package/dist/listeners/GenerateRequestNumberListener.js +87 -0
  46. package/dist/listeners/GenerateRequestNumberListener.js.map +1 -0
  47. package/dist/listeners/OnActionCompleteListener.js +96 -0
  48. package/dist/listeners/OnActionCompleteListener.js.map +1 -0
  49. package/dist/listeners/OnCancelAccommodationCardListener.js +87 -0
  50. package/dist/listeners/OnCancelAccommodationCardListener.js.map +1 -0
  51. package/dist/listeners/OnReactivateAccommodationCardListener.js +87 -0
  52. package/dist/listeners/OnReactivateAccommodationCardListener.js.map +1 -0
  53. package/dist/listeners/SendEmailAfterActionStatusChangeListener.js +98 -0
  54. package/dist/listeners/SendEmailAfterActionStatusChangeListener.js.map +1 -0
  55. package/dist/listeners/SendEmailAfterCardStatusChangeListener.js +139 -0
  56. package/dist/listeners/SendEmailAfterCardStatusChangeListener.js.map +1 -0
  57. package/dist/listeners/StudentAccommodationCardSaveListener.js +90 -0
  58. package/dist/listeners/StudentAccommodationCardSaveListener.js.map +1 -0
  59. package/dist/listeners/UpdateStudentAccommodationCardListener.js +70 -0
  60. package/dist/listeners/UpdateStudentAccommodationCardListener.js.map +1 -0
  61. package/dist/listeners/ValidateActionListener.js +348 -0
  62. package/dist/listeners/ValidateActionListener.js.map +1 -0
  63. package/dist/listeners/data-localization.js +209 -0
  64. package/dist/listeners/data-localization.js.map +1 -0
  65. package/dist/listeners/lookup-cache-listener.js +90 -0
  66. package/dist/listeners/lookup-cache-listener.js.map +1 -0
  67. package/dist/models/Accommodation.js +53 -0
  68. package/dist/models/Accommodation.js.map +1 -0
  69. package/dist/models/AccommodationRequestAction.js +214 -0
  70. package/dist/models/AccommodationRequestAction.js.map +1 -0
  71. package/dist/models/AccommodationRequestEvent.js +277 -0
  72. package/dist/models/AccommodationRequestEvent.js.map +1 -0
  73. package/dist/models/EnableAttachmentModel.js +210 -0
  74. package/dist/models/EnableAttachmentModel.js.map +1 -0
  75. package/dist/models/StudentAccommodationCard.js +93 -0
  76. package/dist/models/StudentAccommodationCard.js.map +1 -0
  77. package/dist/models/TestAction.d.ts +5 -0
  78. package/dist/models/TestAction.js +5 -0
  79. package/dist/models/TestAction.js.map +1 -0
  80. package/dist/templates/mail-after-dining-request-status-change/html.ejs +27 -0
  81. package/package.json +57 -0
  82. package/src/AccommodationSchemaLoader.js +35 -0
  83. package/src/AccommodationService.js +67 -0
  84. package/src/DepartmentReplacer.js +53 -0
  85. package/src/StudentReplacer.js +74 -0
  86. package/src/config/models/Accommodation.json +96 -0
  87. package/src/config/models/AccommodationCardEffectiveStatusType.json +107 -0
  88. package/src/config/models/AccommodationCardEffectiveStatusTypeLocale.json +85 -0
  89. package/src/config/models/AccommodationChildFinancialAttribute.json +46 -0
  90. package/src/config/models/AccommodationGuardianFinancialAttribute.json +46 -0
  91. package/src/config/models/AccommodationInscriptionModeTag.json +100 -0
  92. package/src/config/models/AccommodationRequestAction.json +500 -0
  93. package/src/config/models/AccommodationRequestActionMessage.json +46 -0
  94. package/src/config/models/AccommodationRequestActionReview.json +31 -0
  95. package/src/config/models/AccommodationRequestAttachmentConfiguration.json +72 -0
  96. package/src/config/models/AccommodationRequestCategory.json +50 -0
  97. package/src/config/models/AccommodationRequestEffectiveStatusType.json +212 -0
  98. package/src/config/models/AccommodationRequestEffectiveStatusTypeLocale.json +85 -0
  99. package/src/config/models/AccommodationRequestEvent.json +117 -0
  100. package/src/config/models/AccommodationRequestFinancialAttribute.json +53 -0
  101. package/src/config/models/AccommodationRoom.json +151 -0
  102. package/src/config/models/AccommodationSiblingFinancialAttribute.json +57 -0
  103. package/src/config/models/CancelAccommodationCardAction.json +32 -0
  104. package/src/config/models/GuardianType.json +78 -0
  105. package/src/config/models/Review.json +50 -0
  106. package/src/config/models/RoomType.json +320 -0
  107. package/src/config/models/RoomTypeLocale.json +86 -0
  108. package/src/config/models/StudentAccommodationCard.json +158 -0
  109. package/src/config/models/TestAction.json +50 -0
  110. package/src/config/scope.access.js +148 -0
  111. package/src/index.d.ts +2 -0
  112. package/src/index.js +2 -0
  113. package/src/listeners/AddDefaultAttachmentConfiguration.js +43 -0
  114. package/src/listeners/DefaultAttachmentConfigurations.json +277 -0
  115. package/src/listeners/DefaultAttachmentTypes.json +523 -0
  116. package/src/listeners/GenerateRequestNumberListener.js +86 -0
  117. package/src/listeners/OnActionCompleteListener.js +95 -0
  118. package/src/listeners/OnCancelAccommodationCardListener.js +86 -0
  119. package/src/listeners/OnReactivateAccommodationCardListener.js +86 -0
  120. package/src/listeners/SendEmailAfterActionStatusChangeListener.js +97 -0
  121. package/src/listeners/SendEmailAfterCardStatusChangeListener.js +138 -0
  122. package/src/listeners/StudentAccommodationCardSaveListener.js +89 -0
  123. package/src/listeners/UpdateStudentAccommodationCardListener.js +69 -0
  124. package/src/listeners/ValidateActionListener.js +347 -0
  125. package/src/listeners/data-localization.js +214 -0
  126. package/src/listeners/lookup-cache-listener.js +89 -0
  127. package/src/models/Accommodation.js +52 -0
  128. package/src/models/AccommodationRequestAction.js +195 -0
  129. package/src/models/AccommodationRequestEvent.js +276 -0
  130. package/src/models/EnableAttachmentModel.js +209 -0
  131. package/src/models/StudentAccommodationCard.js +92 -0
  132. package/src/models/TestAction.d.ts +5 -0
  133. package/src/models/TestAction.js +10 -0
  134. package/src/templates/mail-after-dining-request-status-change/html.ejs +27 -0
@@ -0,0 +1,195 @@
1
+ import { DataError, HttpForbiddenError } from "@themost/common";
2
+ import { EdmMapping, EdmType } from "@themost/data";
3
+ import EnableAttachmentModel from './EnableAttachmentModel';
4
+
5
+ class AccommodationRequestAction extends EnableAttachmentModel {
6
+ constructor() {
7
+ super();
8
+ }
9
+ /**
10
+ * Adds an attachment
11
+ * @param {*} file
12
+ * @param {*=} extraAttributes
13
+ */
14
+ @EdmMapping.param('extraAttributes', 'Object', true, true)
15
+ @EdmMapping.param('file', EdmType.EdmStream, false)
16
+ @EdmMapping.action('AddAttachment', 'Attachment')
17
+ async addAttachment(file, extraAttributes) {
18
+ const attachment = Object.assign({
19
+ name: file.contentFileName
20
+ }, file, extraAttributes);
21
+ return await super.addAttachment(attachment);
22
+ }
23
+ /**
24
+ * Removes an attachment
25
+ * @param {*} attachment
26
+ */
27
+ @EdmMapping.param('attachment', 'Attachment', true, true)
28
+ @EdmMapping.action('RemoveAttachment', 'Attachment')
29
+ async removeAttachment(attachment) {
30
+ return await super.removeAttachment(attachment.id);
31
+ }
32
+
33
+ /**
34
+ * Rejects an attachment
35
+ * @param {*} attachment
36
+ */
37
+ @EdmMapping.param('attachment', 'Attachment', true, true)
38
+ @EdmMapping.action('RejectAttachment', 'Attachment')
39
+ async rejectAttachment(attachment) {
40
+ return await super.rejectAttachment(attachment.id);
41
+ }
42
+
43
+ /**
44
+ * Reverts (re-accepts) an attachment
45
+ * @param {*} attachment
46
+ */
47
+ @EdmMapping.param('attachment', 'Attachment', true, true)
48
+ @EdmMapping.action('RevertAttachment', 'Attachment')
49
+ async revertAttachment(attachment) {
50
+ return await super.revertAttachment(attachment.id);
51
+ }
52
+
53
+ /**
54
+ * Add action messages
55
+ * @param {Array<any>} items
56
+ */
57
+ @EdmMapping.param('item', 'AccommodationRequestActionMessage', true, true)
58
+ @EdmMapping.action('messages', 'AccommodationRequestActionMessage')
59
+ async postMessages(item) {
60
+ // set initiator
61
+ item.action = this.getId();
62
+ return this.context.model('AccommodationRequestActionMessage').save(item);
63
+ }
64
+
65
+ /**
66
+ * Gets item review
67
+ */
68
+ @EdmMapping.func('review', 'AccommodationRequestActionReview')
69
+ getReview() {
70
+ return this.context.model('AccommodationRequestActionReview')
71
+ .where('itemReviewed').equal(this.getId()).prepare();
72
+ }
73
+
74
+ /**
75
+ * Set item review
76
+ * @param {*} item
77
+ */
78
+ @EdmMapping.param('item', 'AccommodationRequestActionReview', true, true)
79
+ @EdmMapping.action('review', 'AccommodationRequestActionReview')
80
+ async setReview(item) {
81
+ const AccommodationRequestActionReviews = this.context.model('AccommodationRequestActionReview');
82
+ // infer object state
83
+ const currentReview = await AccommodationRequestActionReviews.where('itemReviewed').equal(this.getId()).getItem();
84
+ if (currentReview == null) {
85
+ if (item == null) {
86
+ return;
87
+ }
88
+ // a new item is going to be inserted
89
+ delete item.id;
90
+ // set reviewed item
91
+ item.itemReviewed = this.getId();
92
+ } else {
93
+ if (item == null) {
94
+ // delete review
95
+ AccommodationRequestActionReviews.remove(currentReview);
96
+ }
97
+ // up
98
+ item.id = currentReview.id;
99
+ // set reviewed item
100
+ item.itemReviewed = this.getId();
101
+ }
102
+ return AccommodationRequestActionReviews.save(item);
103
+ }
104
+
105
+ /**
106
+ * Adds an attachment
107
+ * @param {{contentFileName:string}|*} attachment
108
+ * @param {*=} message
109
+ */
110
+ @EdmMapping.param('message', 'Object', true, true)
111
+ @EdmMapping.param('attachment', EdmType.EdmStream, true)
112
+ @EdmMapping.action('SendMessage', 'AccommodationRequestActionMessage')
113
+ sendMessage(attachment, message) {
114
+ const thisArg = this;
115
+ return new Promise((resolve, reject) => {
116
+ this.context.db.executeInTransaction((cb) => {
117
+ (async () => {
118
+ const AccommodationRequestActionMessage = thisArg.context.model('AccommodationRequestActionMessage');
119
+ if (message.recipient) {
120
+ // try to find the message recipient by bypassing privileges
121
+ // note: Students do not-and should not-have read access to Account or User models.
122
+ // this process will help to identify an account by its name, e.g. recipient: 'AccommodationUsers'
123
+ const recipient = await thisArg.context.model('Account')
124
+ .where('name').equal(message.recipient.name || message.recipient)
125
+ .select('id')
126
+ .silent()
127
+ .value()
128
+ ?? await thisArg.context.model('Account')
129
+ .where('id').equal(message.recipient.id || message.recipient)
130
+ .select('id')
131
+ .silent()
132
+ .value()
133
+ if (recipient == null) {
134
+ throw new DataError('The specified message recipient (User/Account) cannot be found.');
135
+ }
136
+ // and override the message recipient
137
+ message.recipient = recipient;
138
+ }
139
+ const newMessage = AccommodationRequestActionMessage.convert(message);
140
+ Object.assign(newMessage, {
141
+ action: thisArg.getId()
142
+ });
143
+ // add message
144
+ await AccommodationRequestActionMessage.silent().save(newMessage);
145
+ // if message has an attachment
146
+ if (attachment) {
147
+ // prepare attachment data
148
+ const newAttachment = Object.assign({
149
+ name: attachment.contentFileName
150
+ }, attachment);
151
+ // and add attachment
152
+ await newMessage.addAttachment(newAttachment)
153
+ }
154
+ })().then(() => {
155
+ return cb();
156
+ }).catch((err) => {
157
+ return cb(err);
158
+ });
159
+ }, (err) => {
160
+ if (err) {
161
+ return reject(err);
162
+ }
163
+ return resolve(message);
164
+ })
165
+ });
166
+ }
167
+
168
+ //remove accommodation request action
169
+ @EdmMapping.action('Cancel', 'AccommodationRequestAction')
170
+ async cancel() {
171
+ /**
172
+ * @type {import('@themost/data').DataModel}
173
+ */
174
+
175
+ const model = this.getModel();
176
+
177
+ const item = await model.where('owner/name')
178
+ .equal(this.context.user.name)
179
+ .and('actionStatus/alternateName')
180
+ .equal('PotentialActionStatus')
181
+ .and('id')
182
+ .equal(this.getId())
183
+ .getItem();
184
+
185
+ if (item == null) {
186
+ throw new HttpForbiddenError();
187
+ }
188
+
189
+ return model.silent().remove(item); // silent() is used to bypass privileges
190
+ }
191
+ }
192
+
193
+
194
+
195
+ module.exports = AccommodationRequestAction;
@@ -0,0 +1,276 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import { DataError } from "@themost/common";
3
+ import { DataObject, EdmMapping } from "@themost/data";
4
+
5
+
6
+ @EdmMapping.entityType('AccommodationRequestEvent')
7
+ class AccommodationRequestEvent extends DataObject {
8
+ /**
9
+ * @constructor
10
+ */
11
+ constructor() {
12
+ super();
13
+ }
14
+
15
+ /**
16
+ * POST AccommodationRequestEvents/:id/Remove
17
+ */
18
+
19
+ //remove accommodation request event
20
+ @EdmMapping.param('data', 'Object', false, true)
21
+ @EdmMapping.action('remove', 'AccommodationRequestEvent')
22
+ async remove(data) {
23
+ /**
24
+ * @type {import('@themost/data').DataModel}
25
+ */
26
+
27
+ const model = this.getModel();
28
+
29
+ const item = await model.where('id')
30
+ .equal(this.getId())
31
+ .getItem();
32
+ const translateService = this.context.application.getStrategy(function TranslateService() { });
33
+
34
+ if (item == null) {
35
+ throw new DataError('E_ACCOMMODATION_REQUEST_EVENT', translateService.translate('The specified AccommodationRequestEvent cannot be found or is inaccessible.'));
36
+ }
37
+
38
+ const hasActions = await this.context.model('AccommodationRequestAction')
39
+ .where('accommodationRequestEvent').equal(item.id).count();
40
+
41
+ if (hasActions) {
42
+ throw new DataError('E_ACCOMMODATION_REQUESTS_EXISTS', translateService.translate('Cannot remove accommodation request event. Accommodation request event is related to requests.'), null, 'AccommodationRequestEvent');
43
+ }
44
+
45
+ return model.silent().remove(item);
46
+ }
47
+
48
+
49
+ /**
50
+ * POST AccommodationRequestEvents/:id/Extend
51
+ */
52
+
53
+ /**
54
+ * @returns {AccommodationRequestEvent}
55
+ */
56
+
57
+ //extend accommodation request event
58
+ @EdmMapping.param("data", "Object", false, true)
59
+ @EdmMapping.action('extend', 'AccommodationRequestEvent')
60
+ async extend(data) {
61
+
62
+ const model = this.getModel();
63
+
64
+ const previous = await model.where('id')
65
+ .equal(this.getId())
66
+ .getItem();
67
+
68
+ const target = data.event;
69
+ const translateService = this.context.application.getStrategy(function TranslateService() { });
70
+ if (previous == null) {
71
+ throw new DataError('E_ACCOMMODATION_REQUEST_EVENT', translateService.translate('The specified AccommodationRequestEvent cannot be found or is inaccessible.'));
72
+ }
73
+
74
+
75
+ if (previous.eventStatus && previous.eventStatus.alternateName !== 'EventOpened' && target.eventStatus && target.eventStatus.alternateName !== 'EventOpened') {
76
+ throw new DataError('E_EVENT_STATUS', translateService.translate('The AccommodationRequestEvent can only be extended when it is open.'));
77
+ }
78
+
79
+ if ((target.validThrough && target.validFrom)
80
+ && target.validThrough < target.validFrom) {
81
+ throw new DataError('E_DATES', translateService.translate('ValidThrough date cannot be earlier than validFrom date.'));
82
+ }
83
+
84
+ const today = new Date().toISOString();
85
+ if (previous.validThrough && previous.validThrough <= today) {
86
+ throw new DataError('E_DATES', translateService.translate('The AccommodationRequestEvent can only be extended if it has not already expired.'));
87
+ }
88
+
89
+ if (previous.eventStatus && previous.eventStatus.alternateName === 'EventOpened') {
90
+
91
+ const validThroughChange = previous.validThrough && previous.validThrough.toISOString() !== target.validThrough.toISOString();
92
+ const endDateChange = previous.endDate && previous.endDate.toISOString() !== target.endDate.toISOString();
93
+
94
+ if (validThroughChange || endDateChange) {
95
+ const actionItem = {
96
+ object: previous.id,
97
+ currentValidThrough: validThroughChange === true ? previous.validThrough : null,
98
+ validThrough: validThroughChange === true ? target.validThrough : null,
99
+ currentEndDate: endDateChange === true ? previous.endDate : null,
100
+ endDate: endDateChange === true ? target.endDate : null,
101
+ status: previous.eventStatus,
102
+ description: validThroughChange === true ? (endDateChange === true ? translateService.translate('END_DATE_VALID_THROUGH_DATE') : translateService.translate('VALID_THROUGH_DATE')) : translateService.translate('END_DATE')
103
+ };
104
+ const action = await this.context.model('UpdateAccommodationRequestEventAction').save(actionItem);
105
+ if (action) {
106
+ return await this.context.model('AccommodationRequestEvent').save(target).then(async res => {
107
+ if (res) {
108
+ const response = res;
109
+ let sseUser;
110
+ sseUser = this.context.user || await this.context.model('User');
111
+ // check if there are active cards to extend
112
+ const cards = await this.context.model('StudentAccommodationCards')
113
+ .where('student/studentStatus/alternateName').equal('active')
114
+ .or('student/studentStatus/alternateName').equal('declared')
115
+ .prepare()
116
+ .and('active').equal(true)
117
+ .and('action/accommodationRequestEvent/id').equal(previous.id)
118
+ .and('action/actionStatus/alternateName').equal('CompletedActionStatus')
119
+ .count();
120
+ if (validThroughChange === true && cards) {
121
+ Promise.all([
122
+ action,
123
+ this.handleStudentAccommodationCards(res, action)
124
+ ]).then(([actionResult]) => {
125
+ this.sendSse(sseUser, this.context, actionResult, null);
126
+ return;
127
+ }).catch(err => {
128
+ this.sendSse(sseUser, this.context, action, err);
129
+ });
130
+ } else {
131
+ action.totalCards = action.totalCardsAffected = 0;
132
+ action.actionResult = translateService.translate('The process was completed successfully.');
133
+ action.actionStatus = { alternateName: 'CompletedActionStatus' };
134
+ await this.context.model('UpdateAccommodationRequestEventAction').save(action);
135
+ this.sendSse(sseUser, this.context, action, null);
136
+ }
137
+ return response;
138
+ } else {
139
+ //set action status fail
140
+ action.actionResult = translateService.translate('Error occured while trying to extend AccommodationRequestEvent.')
141
+ action.actionStatus = { alternateName: 'FailedActionStatus' };
142
+ await this.context.model('UpdateAccommodationRequestEventAction').save(action);
143
+ return false;
144
+ }
145
+ })
146
+ }
147
+ } else {
148
+ return false;
149
+ }
150
+ }
151
+ }
152
+
153
+ sendSse(user, context, updateEventAction, error) {
154
+ // try to find ServerSentEventService
155
+ const sse = context.getApplication().getService(function ServerSentEventService() {});
156
+ // only if it exists - it is not required
157
+ if (sse != null) {
158
+ if (user != null && updateEventAction != null) {
159
+ // create an event
160
+ const event = {
161
+ entitySet: "UpdateAccommodationRequestEventActions",
162
+ entityType: "UpdateAccommodationRequestEventAction",
163
+ target: {
164
+ id: updateEventAction.id,
165
+ object: updateEventAction.object.id || updateEventAction.object,
166
+ actionResult: updateEventAction.actionResult
167
+ },
168
+ status: {
169
+ success: true,
170
+ },
171
+ dateCreated: new Date()
172
+ }
173
+ if (error) {
174
+ Object.assign(event, {
175
+ status: {
176
+ success: false
177
+ },
178
+ error: {
179
+ message: error.code || error.message
180
+ }
181
+ })
182
+ }
183
+ // and send it to the user that is performing the approval (accommodation scope)
184
+ sse.emit(user.name, user.authenticationScope || "accommodation", event);
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * @returns Promise<ValidationResult[]>
191
+ */
192
+ async handleStudentAccommodationCards(event, action) {
193
+
194
+ const translateService = this.context.application.getStrategy(function TranslateService() { });
195
+ const model = this.getModel();
196
+ const eventNext = await model.where('id')
197
+ .equal(event.id)
198
+ .getItem();
199
+
200
+ if (!eventNext) {
201
+ action.actionResult = translateService.translate('The specified AccommodationRequestEvent cannot be found or is inaccessible.')
202
+ action.actionStatus = { alternateName: 'FailedActionStatus' };
203
+ await this.context.model('UpdateAccommodationRequestEventAction').save(action);
204
+ throw new DataError('E_ACCOMMODATION_REQUEST_EVENT', translateService.translate('The specified AccommodationRequestEvent cannot be found or is inaccessible.'));
205
+ }
206
+
207
+ /**
208
+ * Try to update valid through date of active student accommodation cards
209
+ * Do not throw errors, but catch the procedure errors
210
+ */
211
+ let actionItem = action;
212
+ let cardResults = [];
213
+ const accommodationCardsToChange = await this.context.model('StudentAccommodationCards')
214
+ .where('student/studentStatus/alternateName').equal('active')
215
+ .or('student/studentStatus/alternateName').equal('declared')
216
+ .prepare()
217
+ .and('active').equal(true)
218
+ .and('action/accommodationRequestEvent/id').equal(eventNext.id)
219
+ .and('action/actionStatus/alternateName').equal('CompletedActionStatus')
220
+ .expand({
221
+ name: 'student',
222
+ options: {
223
+ $expand: 'studentStatus'
224
+ }
225
+ })
226
+ .getItems();
227
+
228
+ // extend all active accommodationCards
229
+ // let index = 0; // debugging purposes
230
+ for (const item of accommodationCardsToChange) {
231
+ try {
232
+ // update only validThrough date
233
+ // property student is required
234
+ // check if studentStatus allows card to be updated (active and declared actionStatus)
235
+ if (item && item.student && item.student.studentStatus && item.student.studentStatus.alternateName
236
+ && (item.student.studentStatus.alternateName === 'active' || item.student.studentStatus.alternateName === 'declared')){
237
+ const itemNext = {
238
+ id: item.id,
239
+ validThrough: eventNext.validThrough,
240
+ student: item.student
241
+ };
242
+ await this.context.model('StudentAccommodationCards').save(itemNext);
243
+ }
244
+ } catch (err) {
245
+ // if err catch on cardResults
246
+ const res = item && item.action && item.action.requestNumber
247
+ cardResults.push(res)
248
+ }
249
+ // index ++; // debugging purposes
250
+ }
251
+
252
+ // return result
253
+ try {
254
+ actionItem.totalCards = accommodationCardsToChange.length;
255
+ actionItem.totalCardsAffected = accommodationCardsToChange.length - cardResults.length;
256
+ if (cardResults && cardResults.length === 0) {
257
+ //set action status completed
258
+ actionItem.actionStatus = { alternateName: 'CompletedActionStatus' };
259
+ actionItem.actionResult = translateService.translate('The process was completed successfully.');
260
+ } else {
261
+ //set action status fail
262
+ actionItem.actionStatus = { alternateName: 'FailedActionStatus' };
263
+ actionItem.actionResult = translateService.translate('Error occured while trying to update student accommodation cards') + '<br>' + cardResults.join("<br>");
264
+ }
265
+ const res = await this.context.model('UpdateAccommodationRequestEventActions').save(actionItem);
266
+
267
+ } catch (err) {
268
+ let sseUser;
269
+ sseUser = this.context.user || await this.context.model('User');
270
+ this.sendSse(sseUser, this.context, action, null);
271
+ throw new DataError('E_UPDATE_ACTION', 'Error occured while trying to update the UpdateAccommodationRequestEventAction.');
272
+ }
273
+ }
274
+ }
275
+
276
+ module.exports = AccommodationRequestEvent;
@@ -0,0 +1,209 @@
1
+ import {DataObject} from '@themost/data';
2
+ import {TraceUtils, DataNotFoundError, HttpServerError, HttpBadRequestError} from "@themost/common";
3
+ import path from 'path';
4
+
5
+ class MimeTypeError extends HttpBadRequestError {
6
+ constructor(message) {
7
+ super(message);
8
+ this.code = "EMIME";
9
+ }
10
+ }
11
+
12
+ class EnableAttachmentModel extends DataObject {
13
+
14
+ constructor() {
15
+ super();
16
+ }
17
+
18
+ /**
19
+ * @returns {Promise<*>}
20
+ */
21
+ getRecipient() {
22
+ if (this.getModel().getAttribute('recipient')) {
23
+ return this.getModel().where('id').equal(this.id).select('recipient').silent().value();
24
+ }
25
+ return Promise.resolve();
26
+ }
27
+
28
+ addAttachment(file) {
29
+ const context = this.context;
30
+ const self = this;
31
+ let finalResult;
32
+ return new Promise((resolve, reject)=> {
33
+ this.context.db.executeInTransaction((callback)=> {
34
+ if (typeof file.originalname === 'string') {
35
+ file.name = file.originalname.replace(/\.[A-Z0-9]+$/ig, function(x) { return x.toLowerCase(); });
36
+ }
37
+ const mime = file.mimetype || file.contentType;
38
+ if (mime == null) {
39
+ return reject(new MimeTypeError('The specified file type is not supported by the system.'))
40
+ }
41
+ const addAttributes ={};
42
+ const attributeNames = context.model('Attachment').attributeNames;
43
+ attributeNames.forEach((attribute)=> {
44
+ if (Object.prototype.hasOwnProperty.call(file, attribute)) {
45
+ addAttributes[attribute] = file[attribute];
46
+ }
47
+ });
48
+ const newAttachment = Object.assign(addAttributes, { contentType:mime });
49
+ const svc = context.getApplication().getService(function PrivateContentService() { });
50
+ //add attachment to attachments
51
+ context.unattended((cb)=> {
52
+ // set attachment ownership to recipient
53
+ EnableAttachmentModel.prototype.getRecipient.call(self).then( recipient => {
54
+ if (recipient) {
55
+ newAttachment.owner = recipient;
56
+ }
57
+ let filePath;
58
+ if (file.destination && file.filename) {
59
+ filePath = path.resolve(file.destination, file.filename);
60
+ } else {
61
+ filePath = file.path;
62
+ }
63
+ svc.copyFrom(context, filePath, newAttachment, function(err) {
64
+ if (err) {
65
+ TraceUtils.error(err);
66
+ return cb(new HttpServerError());
67
+ }
68
+ return cb();
69
+ });
70
+ }).catch( err=> {
71
+ return cb(err);
72
+ });
73
+
74
+ }, (err)=> {
75
+ if (err) {
76
+ return callback(err);
77
+ }
78
+ const attachments = self.property('attachments');
79
+ // set attachment ownership
80
+ attachments.insert(newAttachment, (err)=> {
81
+ if (err) {
82
+ return callback(err);
83
+ }
84
+ svc.resolveUrl(context, newAttachment, function(err, url) {
85
+ if (err) {
86
+ callback(err);
87
+ }
88
+ else {
89
+ finalResult =Object.assign({
90
+ }, newAttachment, {
91
+ url: url,
92
+ name: newAttachment.filename
93
+ });
94
+ callback();
95
+ }
96
+ });
97
+ });
98
+ });
99
+
100
+ }, (err)=> {
101
+ if (err) {
102
+ return reject(err);
103
+ }
104
+ return resolve(finalResult);
105
+ });
106
+ })
107
+
108
+ }
109
+
110
+ removeAttachment(id) {
111
+ const context = this.context;
112
+ const self = this;
113
+ let result;
114
+ return new Promise((resolve, reject)=> {
115
+ context.db.executeInTransaction((callback)=> {
116
+ //get attachment
117
+ return context.model('Attachment').where('id').equal(id).getItem().then((attachment)=> {
118
+ if (attachment) {
119
+ //remove attachment connection
120
+ return self.property('attachments').remove(attachment,(err)=> {
121
+ //remove attachment
122
+ if (err) {
123
+ return callback(err);
124
+ }
125
+ return context.model('Attachment').silent().remove(attachment).then(()=> {
126
+ result = attachment;
127
+ return callback();
128
+ }).catch((err)=> {
129
+ return callback(err);
130
+ });
131
+ });
132
+ }
133
+ return callback(new DataNotFoundError());
134
+ }).catch((err)=> {
135
+ return callback(err);
136
+ });
137
+ }, (err)=> {
138
+ if (err) {
139
+ return reject(err);
140
+ }
141
+ return resolve(result);
142
+ });
143
+ });
144
+ }
145
+
146
+ rejectAttachment(id) {
147
+ const context = this.context;
148
+ const self = this;
149
+ let result;
150
+ return new Promise((resolve, reject)=> {
151
+ context.db.executeInTransaction((callback)=> {
152
+ //get attachment
153
+ return context.model('Attachment').where('id').equal(id).getItem().then((attachment)=> {
154
+ if (attachment) {
155
+ //reject attachment
156
+ attachment.published = false;
157
+ return context.model('Attachment').silent().save(attachment).then(()=> {
158
+ result = attachment;
159
+ return callback();
160
+ }).catch((err)=> {
161
+ return callback(err);
162
+ });
163
+ }
164
+ return callback(new DataNotFoundError());
165
+ }).catch((err)=> {
166
+ return callback(err);
167
+ });
168
+ }, (err)=> {
169
+ if (err) {
170
+ return reject(err);
171
+ }
172
+ return resolve(result);
173
+ });
174
+ });
175
+ }
176
+
177
+ revertAttachment(id) {
178
+ const context = this.context;
179
+ const self = this;
180
+ let result;
181
+ return new Promise((resolve, reject)=> {
182
+ context.db.executeInTransaction((callback)=> {
183
+ //get attachment
184
+ return context.model('Attachment').where('id').equal(id).getItem().then((attachment)=> {
185
+ if (attachment) {
186
+ //revert attachment
187
+ attachment.published = true;
188
+ return context.model('Attachment').silent().save(attachment).then(()=> {
189
+ result = attachment;
190
+ return callback();
191
+ }).catch((err)=> {
192
+ return callback(err);
193
+ });
194
+ }
195
+ return callback(new DataNotFoundError());
196
+ }).catch((err)=> {
197
+ return callback(err);
198
+ });
199
+ }, (err)=> {
200
+ if (err) {
201
+ return reject(err);
202
+ }
203
+ return resolve(result);
204
+ });
205
+ });
206
+ }
207
+ }
208
+
209
+ module.exports = EnableAttachmentModel;