@stamhoofd/backend 2.120.6 → 2.121.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 (61) hide show
  1. package/package.json +12 -12
  2. package/src/audit-logs/RegistrationInvitationLogger.ts +46 -0
  3. package/src/audit-logs/init.ts +2 -0
  4. package/src/crons/index.ts +2 -0
  5. package/src/crons/invoices.ts +166 -0
  6. package/src/crons/mollie-chargebacks.ts +87 -0
  7. package/src/crons.ts +47 -10
  8. package/src/email-recipient-loaders/payments.ts +84 -41
  9. package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +51 -0
  10. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +22 -3
  11. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +4 -0
  12. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +45 -0
  13. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +495 -0
  14. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +216 -0
  15. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +405 -0
  16. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +168 -0
  17. package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +15 -0
  18. package/src/endpoints/{global → organization/dashboard}/billing/DeactivatePackageEndpoint.ts +3 -4
  19. package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +62 -0
  20. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +56 -0
  21. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +42 -19
  22. package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +64 -0
  23. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +11 -3
  24. package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +308 -0
  25. package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +94 -0
  26. package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +7 -0
  27. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +5 -4
  28. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +7 -2
  29. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +17 -8
  30. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +3 -3
  31. package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +127 -0
  32. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +13 -4
  33. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +7 -1
  34. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +1 -1
  35. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +13 -11
  36. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +14 -19
  37. package/src/helpers/AdminPermissionChecker.ts +11 -3
  38. package/src/helpers/AuthenticatedStructures.ts +94 -6
  39. package/src/helpers/FinancialSupportHelper.ts +21 -0
  40. package/src/helpers/RecordAnswerHelper.test.ts +746 -0
  41. package/src/helpers/RecordAnswerHelper.ts +116 -0
  42. package/src/helpers/StripeHelper.ts +2 -3
  43. package/src/helpers/ViesHelper.ts +7 -3
  44. package/src/seeds/1750090030-records-configuration.ts +68 -3
  45. package/src/seeds/1752848561-groups-registration-periods.ts +26 -2
  46. package/src/seeds/1779121239-default-invoice-email-template.sql +3 -0
  47. package/src/services/BalanceItemService.ts +12 -16
  48. package/src/services/InvoiceService.ts +372 -72
  49. package/src/services/MollieService.ts +537 -0
  50. package/src/services/PaymentMandateService.ts +214 -0
  51. package/src/services/PaymentService.ts +578 -222
  52. package/src/services/PlatformMembershipService.ts +1 -1
  53. package/src/services/RegistrationService.ts +66 -5
  54. package/src/services/STPackageService.ts +0 -7
  55. package/src/services/data/invoice.hbs.html +686 -0
  56. package/src/sql-filters/groups.ts +11 -1
  57. package/src/sql-filters/payments.ts +5 -0
  58. package/src/sql-filters/registration-invitations.ts +90 -0
  59. package/src/sql-sorters/registration-invitations.ts +36 -0
  60. package/vitest.config.js +1 -0
  61. package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +0 -216
@@ -0,0 +1,51 @@
1
+ import type { Decoder } from '@simonbackx/simple-encoding';
2
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
3
+ import { Endpoint, Response } from '@simonbackx/simple-endpoints';
4
+ import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
5
+
6
+ import { Context } from '../../../helpers/Context.js';
7
+ import { GetGroupsEndpoint } from './GetGroupsEndpoint.js';
8
+
9
+ type Params = Record<string, never>;
10
+ type Query = CountFilteredRequest;
11
+ type Body = undefined;
12
+ type ResponseBody = CountResponse;
13
+
14
+ export class GetGroupsCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
+ queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>;
16
+
17
+ protected doesMatch(request: Request): [true, Params] | [false] {
18
+ if (request.method !== 'GET') {
19
+ return [false];
20
+ }
21
+
22
+ const params = Endpoint.parseParameters(request.url, '/groups/count', {});
23
+
24
+ if (params) {
25
+ return [true, params as Params];
26
+ }
27
+ return [false];
28
+ }
29
+
30
+ async handle(request: DecodedRequest<Params, Query, Body>) {
31
+ const organization = await Context.setOptionalOrganizationScope();
32
+ await Context.optionalAuthenticate();
33
+
34
+ if (!organization) {
35
+ if (!Context.auth.hasSomePlatformAccess()) {
36
+ throw Context.auth.error();
37
+ }
38
+ }
39
+
40
+ const query = await GetGroupsEndpoint.buildQuery(request.query);
41
+
42
+ const count = await query
43
+ .count();
44
+
45
+ return new Response(
46
+ CountResponse.create({
47
+ count,
48
+ }),
49
+ );
50
+ }
51
+ }
@@ -1,9 +1,9 @@
1
- import type { AutoEncoderPatchType, Decoder} from '@simonbackx/simple-encoding';
1
+ import type { AutoEncoderPatchType, Decoder } from '@simonbackx/simple-encoding';
2
2
  import { isPatchableArray, patchObject } from '@simonbackx/simple-encoding';
3
- import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
3
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
4
4
  import { Endpoint, Response } from '@simonbackx/simple-endpoints';
5
5
  import { Organization, Platform, RegistrationPeriod } from '@stamhoofd/models';
6
- import type { MemberResponsibility, PlatformConfig, PlatformPremiseType} from '@stamhoofd/structures';
6
+ import type { EventNotificationType, MemberResponsibility, PlatformConfig, PlatformPremiseType } from '@stamhoofd/structures';
7
7
  import { Platform as PlatformStruct } from '@stamhoofd/structures';
8
8
 
9
9
  import { SimpleError } from '@simonbackx/simple-errors';
@@ -12,6 +12,7 @@ import { Context } from '../../../helpers/Context.js';
12
12
  import { MembershipCharger } from '../../../helpers/MembershipCharger.js';
13
13
  import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer.js';
14
14
  import { PeriodHelper } from '../../../helpers/PeriodHelper.js';
15
+ import { RecordAnswerHelper } from '../../../helpers/RecordAnswerHelper.js';
15
16
  import { SetupStepUpdater } from '../../../helpers/SetupStepUpdater.js';
16
17
  import { TagHelper } from '../../../helpers/TagHelper.js';
17
18
  import { PlatformMembershipService } from '../../../services/PlatformMembershipService.js';
@@ -95,6 +96,24 @@ export class PatchPlatformEndpoint extends Endpoint<
95
96
 
96
97
  if (request.body.config.organizationLevelRecordsConfiguration) {
97
98
  shouldUpdateSetupSteps = true;
99
+
100
+ RecordAnswerHelper.throwIfPatchOrPutIsInvalid(
101
+ platform.config.organizationLevelRecordsConfiguration.recordCategories,
102
+ request.body.config.organizationLevelRecordsConfiguration.recordCategories);
103
+ }
104
+
105
+ if (request.body.config.recordsConfiguration) {
106
+ RecordAnswerHelper.throwIfPatchOrPutIsInvalid(
107
+ platform.config.recordsConfiguration.recordCategories,
108
+ request.body.config.recordsConfiguration.recordCategories);
109
+ }
110
+
111
+ if (request.body.config.eventNotificationTypes) {
112
+ RecordAnswerHelper.throwIfPatchOrPutsAreInvalid<'recordCategories', string, EventNotificationType, AutoEncoderPatchType<EventNotificationType>>(
113
+ platform.config.eventNotificationTypes,
114
+ request.body.config.eventNotificationTypes,
115
+ 'recordCategories',
116
+ )
98
117
  }
99
118
 
100
119
  const newConfig = request.body.config;
@@ -841,6 +841,10 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
841
841
  members,
842
842
  serviceFeeType: 'members',
843
843
  payingOrganization,
844
+ createMandate: null,
845
+ useMandate: null,
846
+ paymentConfiguration: organization.meta.registrationPaymentConfiguration,
847
+ privatePaymentConfiguration: organization.privateMeta.registrationPaymentConfiguration
844
848
  });
845
849
 
846
850
  if (response) {
@@ -0,0 +1,45 @@
1
+ import type { Decoder } from '@simonbackx/simple-encoding';
2
+ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
3
+ import { Endpoint, Response } from '@simonbackx/simple-endpoints';
4
+ import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
5
+
6
+ import { Context } from '../../../helpers/Context.js';
7
+ import { GetRegistrationInvitationsEndpoint } from './GetRegistrationInvitationsEndpoint.js';
8
+
9
+ type Params = Record<string, never>;
10
+ type Query = CountFilteredRequest;
11
+ type Body = undefined;
12
+ type ResponseBody = CountResponse;
13
+
14
+ export class GetRegistrationInvitationsCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
+ queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>;
16
+
17
+ protected doesMatch(request: Request): [true, Params] | [false] {
18
+ if (request.method !== 'GET') {
19
+ return [false];
20
+ }
21
+
22
+ const params = Endpoint.parseParameters(request.url, '/registration-invitations/count', {});
23
+
24
+ if (params) {
25
+ return [true, params as Params];
26
+ }
27
+ return [false];
28
+ }
29
+
30
+ async handle(request: DecodedRequest<Params, Query, Body>) {
31
+ await Context.setOptionalOrganizationScope();
32
+ await Context.authenticate();
33
+
34
+ const query = await GetRegistrationInvitationsEndpoint.buildQuery(request.query);
35
+
36
+ const count = await query
37
+ .count();
38
+
39
+ return new Response(
40
+ CountResponse.create({
41
+ count,
42
+ }),
43
+ );
44
+ }
45
+ }
@@ -0,0 +1,495 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import type { Organization, User } from '@stamhoofd/models';
3
+ import { GroupFactory, MemberFactory, OrganizationFactory, RegistrationInvitation, RegistrationInvitationFactory, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
4
+ import type { PaginatedResponse, RegistrationInvitation as RegistrationInvitationStruct, StamhoofdFilter } from '@stamhoofd/structures';
5
+ import { LimitedFilteredRequest, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, TranslatedString } from '@stamhoofd/structures';
6
+ import { TestUtils } from '@stamhoofd/test-utils';
7
+ import { testServer } from '../../../../tests/helpers/TestServer.js';
8
+ import { GetRegistrationInvitationsEndpoint } from './GetRegistrationInvitationsEndpoint.js';
9
+
10
+ describe('Endpoint.GetRegistrationInvitationsEndpoint', () => {
11
+ const endpoint = new GetRegistrationInvitationsEndpoint();
12
+
13
+ const getInvitations = async ({ filter, organization, user }: { filter: StamhoofdFilter; organization: Organization | null; user: User | null }) => {
14
+ let authorization = '';
15
+
16
+ if (user) {
17
+ const token = await Token.createToken(user);
18
+ authorization = 'Bearer ' + token.accessToken;
19
+ }
20
+
21
+ const request = Request.get({
22
+ path: '/registration-invitations',
23
+ host: organization?.getApiHost(),
24
+ query: new LimitedFilteredRequest({
25
+ filter,
26
+ limit: 10,
27
+ }),
28
+ headers: {
29
+ authorization,
30
+ },
31
+ });
32
+
33
+ return testServer.test<PaginatedResponse<RegistrationInvitationStruct[], LimitedFilteredRequest>>(endpoint, request)
34
+ };
35
+
36
+ afterEach(async () => {
37
+ await RegistrationInvitation.delete();
38
+ });
39
+
40
+ describe('Permission checking', () => {
41
+ describe('read access for specific group', () => {
42
+ test('should only return invitations for that group', async () => {
43
+ const organization = await new OrganizationFactory({}).create();
44
+ const groupName = 'test groep';
45
+ const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
46
+ const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
47
+
48
+ const resources = new Map();
49
+
50
+ resources.set(
51
+ PermissionsResourceType.Groups, new Map([[
52
+ group.id,
53
+ ResourcePermissions.create({
54
+ level: PermissionLevel.Write,
55
+ }),
56
+ ]]),
57
+ );
58
+
59
+ const user = await new UserFactory({
60
+ organization,
61
+ permissions: Permissions.create({
62
+ level: PermissionLevel.None,
63
+ resources,
64
+ }),
65
+ }).create();
66
+
67
+ const member = await new MemberFactory({
68
+ organization, user,
69
+ firstName: 'John',
70
+ lastName: 'Doe',
71
+ birthDay: {year: 1994, month: 6, day: 24}
72
+ }).create();
73
+
74
+ const member2 = await new MemberFactory({
75
+ organization, user,
76
+ firstName: 'Test1',
77
+ lastName: 'Test1',
78
+ birthDay: {year: 1998, month: 3, day: 1}
79
+ }).create();
80
+
81
+ // invitations for group
82
+ await new RegistrationInvitationFactory({ group, member, organization }).create();
83
+ await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
84
+
85
+ // invitations for other group
86
+ const invitationForOtherGroup = await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
87
+
88
+ const response = await getInvitations({
89
+ filter: null,
90
+ organization,
91
+ user,
92
+ });
93
+
94
+ // assert
95
+ expect(response.status).toBe(200);
96
+ expect(response.body.results).toHaveLength(2);
97
+ expect(response.body.results.map((r) => r.id)).not.toContain(invitationForOtherGroup.id);
98
+ });
99
+
100
+ test('cannot read invitations for other group', async () => {
101
+ const organization = await new OrganizationFactory({}).create();
102
+ const groupName = 'test groep';
103
+ const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
104
+ const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
105
+
106
+ const resources = new Map();
107
+
108
+ resources.set(
109
+ PermissionsResourceType.Groups, new Map([[
110
+ group.id,
111
+ ResourcePermissions.create({
112
+ level: PermissionLevel.Write,
113
+ }),
114
+ ]]),
115
+ );
116
+
117
+ const user = await new UserFactory({
118
+ organization,
119
+ permissions: Permissions.create({
120
+ level: PermissionLevel.None,
121
+ resources,
122
+ }),
123
+ }).create();
124
+
125
+ const member = await new MemberFactory({
126
+ organization, user,
127
+ firstName: 'John',
128
+ lastName: 'Doe',
129
+ birthDay: {year: 1994, month: 6, day: 24}
130
+ }).create();
131
+
132
+ const member2 = await new MemberFactory({
133
+ organization, user,
134
+ firstName: 'Test1',
135
+ lastName: 'Test1',
136
+ birthDay: {year: 1998, month: 3, day: 1}
137
+ }).create();
138
+
139
+ // invitations for group
140
+ await new RegistrationInvitationFactory({ group, member, organization }).create();
141
+ await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
142
+
143
+ // invitations for other group
144
+ await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
145
+
146
+ // assert
147
+ await expect(getInvitations({
148
+ filter: {
149
+ groupId: group2.id
150
+ },
151
+ organization,
152
+ user,
153
+ })).rejects.toThrow('You do not have access to this group');
154
+ });
155
+ });
156
+
157
+ describe('read access for specific organization', () => {
158
+ test('can read invitations for any group of organization', async () => {
159
+ const organization = await new OrganizationFactory({}).create();
160
+ const groupName = 'test groep';
161
+ const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
162
+ const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
163
+
164
+ const user = await new UserFactory({
165
+ organization,
166
+ permissions: Permissions.create({ level: PermissionLevel.Read }),
167
+ }).create();
168
+
169
+ const member = await new MemberFactory({
170
+ organization, user,
171
+ firstName: 'John',
172
+ lastName: 'Doe',
173
+ birthDay: {year: 1994, month: 6, day: 24}
174
+ }).create();
175
+
176
+ const member2 = await new MemberFactory({
177
+ organization, user,
178
+ firstName: 'Test1',
179
+ lastName: 'Test1',
180
+ birthDay: {year: 1998, month: 3, day: 1}
181
+ }).create();
182
+
183
+ const organization2 = await new OrganizationFactory({}).create();
184
+ const groupOfOtherOrganization = await new GroupFactory({ organization: organization2 }).create();
185
+
186
+ // invitations for group
187
+ await new RegistrationInvitationFactory({ group, member, organization }).create();
188
+ await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
189
+
190
+ // invitations for other group
191
+ await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
192
+
193
+ // invitations for other organization
194
+ const invitationForOtherOrganization = await new RegistrationInvitationFactory({
195
+ group: groupOfOtherOrganization,
196
+ member,
197
+ organization: organization2
198
+ }).create();
199
+
200
+ const response = await getInvitations({
201
+ filter: null,
202
+ organization,
203
+ user,
204
+ });
205
+
206
+ // assert
207
+ expect(response.status).toBe(200);
208
+ // should only include registrations for organization of user
209
+ expect(response.body.results).toHaveLength(3);
210
+ expect(response.body.results.map((r) => r.id)).not.toContain(invitationForOtherOrganization.id);
211
+ });
212
+
213
+ test('cannot read invitations for other organization', async () => {
214
+ const organization = await new OrganizationFactory({}).create();
215
+ const groupName = 'test groep';
216
+ const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
217
+ const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
218
+
219
+ const user = await new UserFactory({
220
+ organization,
221
+ permissions: Permissions.create({ level: PermissionLevel.Read }),
222
+ }).create();
223
+
224
+ const member = await new MemberFactory({
225
+ organization, user,
226
+ firstName: 'John',
227
+ lastName: 'Doe',
228
+ birthDay: {year: 1994, month: 6, day: 24}
229
+ }).create();
230
+
231
+ const member2 = await new MemberFactory({
232
+ organization, user,
233
+ firstName: 'Test1',
234
+ lastName: 'Test1',
235
+ birthDay: {year: 1998, month: 3, day: 1}
236
+ }).create();
237
+
238
+ const organization2 = await new OrganizationFactory({}).create();
239
+ const groupOfOtherOrganization = await new GroupFactory({ organization: organization2 }).create();
240
+
241
+ // invitations for group
242
+ await new RegistrationInvitationFactory({ group, member, organization }).create();
243
+ await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
244
+
245
+ // invitations for other group
246
+ await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
247
+
248
+ // invitations for other organization
249
+ await new RegistrationInvitationFactory({
250
+ group: groupOfOtherOrganization,
251
+ member,
252
+ organization: organization2
253
+ }).create();
254
+
255
+ const response = await getInvitations({
256
+ filter: {
257
+ organizationId: organization2.id
258
+ },
259
+ organization,
260
+ user,
261
+ });
262
+
263
+ // assert
264
+ expect(response.status).toBe(200);
265
+ expect(response.body.results).toHaveLength(0);
266
+ });
267
+ });
268
+
269
+ describe('platform permissions', () => {
270
+ beforeEach(async () => {
271
+ TestUtils.setEnvironment('userMode', 'platform');
272
+ });
273
+
274
+ test('can read all invitations for read access for platform', async () => {
275
+ const organization = await new OrganizationFactory({}).create();
276
+ const groupName = 'test groep';
277
+ const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
278
+ const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
279
+
280
+ const user = await new UserFactory({
281
+ globalPermissions: Permissions.create({ level: PermissionLevel.Read }),
282
+ }).create();
283
+
284
+ const member = await new MemberFactory({
285
+ organization, user,
286
+ firstName: 'John',
287
+ lastName: 'Doe',
288
+ birthDay: {year: 1994, month: 6, day: 24}
289
+ }).create();
290
+
291
+ const member2 = await new MemberFactory({
292
+ organization, user,
293
+ firstName: 'Test1',
294
+ lastName: 'Test1',
295
+ birthDay: {year: 1998, month: 3, day: 1}
296
+ }).create();
297
+
298
+ const organization2 = await new OrganizationFactory({}).create();
299
+ const groupOfOtherOrganization = await new GroupFactory({ organization: organization2 }).create();
300
+
301
+ // invitations for group
302
+ await new RegistrationInvitationFactory({ group, member, organization }).create();
303
+ await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
304
+
305
+ // invitations for other group
306
+ await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
307
+
308
+ // invitations for other organization
309
+ const invitationForOtherOrganization = await new RegistrationInvitationFactory({
310
+ group: groupOfOtherOrganization,
311
+ member,
312
+ organization: organization2
313
+ }).create();
314
+
315
+ const response = await getInvitations({
316
+ filter: null,
317
+ organization,
318
+ user,
319
+ });
320
+
321
+ // assert
322
+ expect(response.status).toBe(200);
323
+ // should only include registrations for organization of user
324
+ expect(response.body.results).toHaveLength(4);
325
+ expect(response.body.results.map((r) => r.id)).toContain(invitationForOtherOrganization.id);
326
+ });
327
+
328
+ test('can only read invitations for group with tag if only access to groups with tags', async () => {
329
+ const organization1 = await new OrganizationFactory({}).create();
330
+ const period = await new RegistrationPeriodFactory({
331
+ organization: organization1,
332
+ startDate: new Date(2023, 0, 1),
333
+ endDate: new Date(2023, 11, 31),
334
+ }).create();
335
+
336
+ organization1.periodId = period.id;
337
+ await organization1.save();
338
+
339
+ const groupName = 'test groep';
340
+ const group = await new GroupFactory({ organization: organization1, name: new TranslatedString(groupName), period }).create();
341
+ const group2 = await new GroupFactory({ organization: organization1, name: new TranslatedString(groupName), period }).create();
342
+
343
+ const resources = new Map();
344
+
345
+ const tag = 'tagtest';
346
+
347
+ resources.set(
348
+ PermissionsResourceType.OrganizationTags, new Map([[
349
+ tag,
350
+ ResourcePermissions.create({
351
+ level: PermissionLevel.Full,
352
+ accessRights: [],
353
+ }),
354
+ ]]),
355
+ );
356
+
357
+ const user = await new UserFactory({
358
+ globalPermissions: Permissions.create({
359
+ level: PermissionLevel.None,
360
+ roles: [],
361
+ resources,
362
+ }),
363
+ }).create();
364
+
365
+ const member = await new MemberFactory({
366
+ organization: organization1, user,
367
+ firstName: 'John',
368
+ lastName: 'Doe',
369
+ birthDay: {year: 1994, month: 6, day: 24}
370
+ }).create();
371
+
372
+ const member2 = await new MemberFactory({
373
+ organization: organization1, user,
374
+ firstName: 'Test1',
375
+ lastName: 'Test1',
376
+ birthDay: {year: 1998, month: 3, day: 1}
377
+ }).create();
378
+
379
+ const organization2 = await new OrganizationFactory({tags: [tag], period}).create();
380
+ const groupOfOtherOrganization = await new GroupFactory({ organization: organization2, period }).create();
381
+
382
+ // invitations for group
383
+ await new RegistrationInvitationFactory({ group, member, organization: organization1 }).create();
384
+ await new RegistrationInvitationFactory({ group, member: member2, organization: organization1 }).create();
385
+
386
+ // invitations for other group
387
+ await new RegistrationInvitationFactory({ group: group2, member, organization: organization1 }).create();
388
+
389
+ // invitations for other organization
390
+ const invitationForOtherOrganization = await new RegistrationInvitationFactory({
391
+ group: groupOfOtherOrganization,
392
+ member,
393
+ organization: organization2
394
+ }).create();
395
+
396
+ const response = await getInvitations({
397
+ filter: null,
398
+ // organization scope is required
399
+ organization: organization2,
400
+ user,
401
+ });
402
+
403
+ // assert
404
+ expect(response.status).toBe(200);
405
+ // should only include registrations for organization of user
406
+ expect(response.body.results).toHaveLength(1);
407
+ expect(response.body.results.map((r) => r.id)).toContain(invitationForOtherOrganization.id);
408
+
409
+ // should not have access to all invitations of organization1
410
+ await expect(getInvitations({
411
+ filter: null,
412
+ // organization scope is required
413
+ organization: organization1,
414
+ user,
415
+ })).rejects.toThrow('You must filter on a group of the organization you are trying to access');
416
+ });
417
+
418
+ test('cannot get invitations if platform scope and no access to all organization tags', async () => {
419
+ const organization = await new OrganizationFactory({}).create();
420
+ const period = await new RegistrationPeriodFactory({
421
+ organization,
422
+ startDate: new Date(2023, 0, 1),
423
+ endDate: new Date(2023, 11, 31),
424
+ }).create();
425
+
426
+ organization.periodId = period.id;
427
+ await organization.save();
428
+
429
+ const groupName = 'test groep';
430
+ const group = await new GroupFactory({ organization, name: new TranslatedString(groupName), period }).create();
431
+ const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName), period }).create();
432
+
433
+ const resources = new Map();
434
+
435
+ const tag = 'tagtest';
436
+
437
+ resources.set(
438
+ PermissionsResourceType.OrganizationTags, new Map([[
439
+ tag,
440
+ ResourcePermissions.create({
441
+ level: PermissionLevel.Full,
442
+ accessRights: [],
443
+ }),
444
+ ]]),
445
+ );
446
+
447
+ const user = await new UserFactory({
448
+ globalPermissions: Permissions.create({
449
+ level: PermissionLevel.None,
450
+ roles: [],
451
+ resources,
452
+ }),
453
+ }).create();
454
+
455
+ const member = await new MemberFactory({
456
+ organization, user,
457
+ firstName: 'John',
458
+ lastName: 'Doe',
459
+ birthDay: {year: 1994, month: 6, day: 24}
460
+ }).create();
461
+
462
+ const member2 = await new MemberFactory({
463
+ organization, user,
464
+ firstName: 'Test1',
465
+ lastName: 'Test1',
466
+ birthDay: {year: 1998, month: 3, day: 1}
467
+ }).create();
468
+
469
+ const organization2 = await new OrganizationFactory({tags: [tag], period}).create();
470
+ const groupOfOtherOrganization = await new GroupFactory({ organization: organization2, period }).create();
471
+
472
+ // invitations for group
473
+ await new RegistrationInvitationFactory({ group, member, organization }).create();
474
+ await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
475
+
476
+ // invitations for other group
477
+ await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
478
+
479
+ // invitations for other organization
480
+ await new RegistrationInvitationFactory({
481
+ group: groupOfOtherOrganization,
482
+ member,
483
+ organization: organization2
484
+ }).create();
485
+
486
+ await expect(getInvitations({
487
+ filter: null,
488
+ organization: null,
489
+ user,
490
+ })).rejects.toThrow('You do not have permissions for this action');
491
+ });
492
+ });
493
+ });
494
+
495
+ });