@stamhoofd/backend 2.74.0 → 2.75.1

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 (49) hide show
  1. package/index.ts +7 -2
  2. package/package.json +13 -13
  3. package/src/crons/update-cached-balances.ts +1 -2
  4. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +2 -2
  5. package/src/endpoints/auth/CreateAdminEndpoint.ts +4 -15
  6. package/src/endpoints/auth/OpenIDConnectStartEndpoint.ts +0 -5
  7. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +2 -2
  8. package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +43 -0
  9. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +181 -0
  10. package/src/endpoints/global/events/GetEventsEndpoint.ts +2 -2
  11. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +288 -0
  12. package/src/endpoints/global/events/PatchEventsEndpoint.ts +2 -2
  13. package/src/endpoints/global/files/UploadFile.ts +56 -4
  14. package/src/endpoints/global/files/UploadImage.ts +9 -3
  15. package/src/endpoints/global/members/GetMembersEndpoint.ts +2 -2
  16. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +10 -1
  17. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +1 -5
  18. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +7 -0
  19. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +1 -1
  20. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +2084 -164
  21. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -2
  22. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +48 -2
  23. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +2 -2
  24. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
  25. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +2 -2
  26. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +2 -2
  27. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +8 -0
  28. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +3 -3
  29. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +2 -2
  30. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +2 -2
  31. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +1 -2
  32. package/src/helpers/AdminPermissionChecker.ts +80 -2
  33. package/src/helpers/AuthenticatedStructures.ts +88 -2
  34. package/src/helpers/FlagMomentCleanup.ts +1 -8
  35. package/src/helpers/GlobalHelper.ts +15 -0
  36. package/src/helpers/MembershipCharger.ts +2 -1
  37. package/src/services/EventNotificationService.ts +201 -0
  38. package/src/services/FileSignService.ts +217 -0
  39. package/src/services/SSOService.ts +7 -2
  40. package/src/sql-filters/event-notifications.ts +39 -0
  41. package/src/sql-filters/organizations.ts +1 -1
  42. package/src/sql-sorters/event-notifications.ts +96 -0
  43. package/src/sql-sorters/events.ts +2 -2
  44. package/src/sql-sorters/organizations.ts +2 -2
  45. package/tests/e2e/private-files.test.ts +497 -0
  46. package/tests/e2e/register.test.ts +1197 -0
  47. package/tests/helpers/TestServer.ts +3 -0
  48. package/tests/jest.setup.ts +15 -2
  49. package/tsconfig.json +1 -0
@@ -1,6 +1,7 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
- import { Group, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, User, UserFactory } from '@stamhoofd/models';
3
- import { GroupPrice, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, PayconiqAccount, PaymentMethod, PermissionLevel, Permissions, Version } from '@stamhoofd/structures';
2
+ import { Email } from '@stamhoofd/email';
3
+ import { BalanceItemFactory, Group, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, Registration, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
4
+ import { BalanceItemCartItem, BalanceItemType, Company, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, OrganizationPackages, PayconiqAccount, PaymentCustomer, PaymentMethod, PermissionLevel, Permissions, ReduceablePrice, RegisterItemOption, STPackageStatus, STPackageType, UserPermissions, Version } from '@stamhoofd/structures';
4
5
  import nock from 'nock';
5
6
  import { v4 as uuidv4 } from 'uuid';
6
7
  import { testServer } from '../../../../tests/helpers/TestServer';
@@ -12,17 +13,9 @@ describe('Endpoint.RegisterMembers', () => {
12
13
  // #region global
13
14
  const endpoint = new RegisterMembersEndpoint();
14
15
  let period: RegistrationPeriod;
15
- let organization: Organization;
16
- let user: User;
17
- let token: Token;
18
- let member: MemberWithRegistrations;
19
- let group1: Group;
20
- let groupPrice1: GroupPrice;
21
- let group2: Group;
22
- let groupPrice2: GroupPrice;
23
16
 
24
17
  // #region helpers
25
- const post = async (body: IDRegisterCheckout) => {
18
+ const post = async (body: IDRegisterCheckout, organization: Organization, token: Token) => {
26
19
  const request = Request.buildJson('POST', baseUrl, organization.getApiHost(), body);
27
20
  request.headers.authorization = 'Bearer ' + token.accessToken;
28
21
  return await testServer.test(endpoint, request);
@@ -32,54 +25,162 @@ describe('Endpoint.RegisterMembers', () => {
32
25
  // #endregion
33
26
 
34
27
  beforeAll(async () => {
35
- period = await new RegistrationPeriodFactory({}).create();
36
- organization = await new OrganizationFactory({ period }).create();
37
- organization.meta.registrationPaymentConfiguration.paymentMethods = [PaymentMethod.PointOfSale, PaymentMethod.Payconiq];
38
-
39
- organization.privateMeta.payconiqAccounts = [PayconiqAccount.create({
40
- id: uuidv4(),
41
- apiKey: 'test',
42
- merchantId: 'test',
43
- profileId: 'test',
44
- name: 'test',
45
- iban: 'BE56587127952688', // = random IBAN
46
- callbackUrl: 'https://example.com',
47
- })];
48
-
49
- user = await new UserFactory({
28
+ const previousPeriod = await new RegistrationPeriodFactory({
29
+ startDate: new Date(2022, 0, 1),
30
+ endDate: new Date(2022, 11, 31),
31
+ }).create();
32
+
33
+ period = await new RegistrationPeriodFactory({
34
+ startDate: new Date(2023, 0, 1),
35
+ endDate: new Date(2023, 11, 31),
36
+ }).create();
37
+
38
+ period.previousPeriodId = previousPeriod.id;
39
+ await period.save();
40
+ });
41
+
42
+ afterEach(() => {
43
+ jest.restoreAllMocks();
44
+ });
45
+
46
+ const initOrganization = async (registrationPeriod: RegistrationPeriod = period) => {
47
+ return await new OrganizationFactory({ period: registrationPeriod })
48
+ .create();
49
+ };
50
+
51
+ const initData = async ({ otherMemberAmount = 0, permissionLevel = PermissionLevel.Full }: { otherMemberAmount?: number; permissionLevel?: PermissionLevel } = {}) => {
52
+ const organization = await initOrganization(period);
53
+
54
+ const user = await new UserFactory({
50
55
  organization,
51
56
  permissions: Permissions.create({
52
- level: PermissionLevel.Full,
57
+ level: permissionLevel,
53
58
  }),
54
- }).create();
55
- token = await Token.createToken(user);
56
- member = await new MemberFactory({ organization, user }).create();
57
- });
59
+ })
60
+ .create();
58
61
 
59
- beforeEach(async () => {
60
- // #region groups
61
- group1 = await new GroupFactory({
62
+ const token = await Token.createToken(user);
63
+
64
+ const member = await new MemberFactory({ organization, user })
65
+ .create();
66
+
67
+ const otherMembers: MemberWithRegistrations[] = [];
68
+
69
+ for (let i = 0; i < otherMemberAmount; i++) {
70
+ otherMembers.push(await new MemberFactory({ organization, user })
71
+ .create());
72
+ }
73
+
74
+ const group = await new GroupFactory({
62
75
  organization,
63
76
  price: 25,
64
77
  stock: 5,
65
- }).create();
78
+ })
79
+ .create();
66
80
 
67
- groupPrice1 = group1.settings.prices[0];
81
+ const groupPrice = group.settings.prices[0];
68
82
 
69
- group2 = await new GroupFactory({
83
+ return {
70
84
  organization,
71
- price: 15,
72
- stock: 4,
73
- maxMembers: 1,
74
- }).create();
85
+ user,
86
+ token,
87
+ member,
88
+ otherMembers,
89
+ group,
90
+ groupPrice,
91
+ };
92
+ };
75
93
 
76
- groupPrice2 = group2.settings.prices[0];
77
- // #endregion
78
- });
94
+ describe('Register', () => {
95
+ test('Should fail if cannot manage finances', async () => {
96
+ // #region arrange
97
+ const { member, group, groupPrice, organization, token } = await initData();
98
+ const organization2 = await initOrganization();
99
+
100
+ const registration = await new RegistrationFactory({
101
+ member,
102
+ group,
103
+ groupPrice,
104
+ }).create();
105
+
106
+ const body = IDRegisterCheckout.create({
107
+ cart: IDRegisterCart.create({
108
+ items: [
109
+ IDRegisterItem.create({
110
+ id: uuidv4(),
111
+ replaceRegistrationIds: [],
112
+ options: [],
113
+ groupPrice,
114
+ organizationId: organization.id,
115
+ groupId: group.id,
116
+ memberId: member.id,
117
+ }),
118
+ ],
119
+ balanceItems: [],
120
+ deleteRegistrationIds: [registration.id],
121
+ }),
122
+ administrationFee: 0,
123
+ freeContribution: 0,
124
+ paymentMethod: PaymentMethod.PointOfSale,
125
+ totalPrice: 5,
126
+ asOrganizationId: organization2.id,
127
+ customer: null,
128
+ });
129
+ // #endregion
130
+
131
+ // #region act and assert
132
+ await expect(async () => await post(body, organization, token))
133
+ .rejects
134
+ .toThrow('No permission to register as this organization for a different organization');
135
+ // #endregion
136
+ });
79
137
 
80
- describe('Register member', () => {
81
- test('Should update registered mmebers', async () => {
138
+ test('Should fail if demo limit reached', async () => {
82
139
  // #region arrange
140
+ (STAMHOOFD.userMode as string) = 'organization';
141
+
142
+ const spySendWebmaster = jest.spyOn(Email, 'sendWebmaster').mockImplementation(() => {
143
+ // do nothing
144
+ });
145
+
146
+ const { member, group, groupPrice, organization, token, otherMembers } = await initData({ otherMemberAmount: 10 });
147
+
148
+ organization.meta.packages = OrganizationPackages.create({
149
+ packages: new Map([
150
+ [STPackageType.TrialMembers, STPackageStatus.create({
151
+ startDate: new Date(),
152
+ })],
153
+ ]),
154
+ });
155
+
156
+ for (const member of otherMembers) {
157
+ const body = IDRegisterCheckout.create({
158
+ cart: IDRegisterCart.create({
159
+ items: [
160
+ IDRegisterItem.create({
161
+ id: uuidv4(),
162
+ replaceRegistrationIds: [],
163
+ options: [],
164
+ groupPrice,
165
+ organizationId: organization.id,
166
+ groupId: group.id,
167
+ memberId: member.id,
168
+ }),
169
+ ],
170
+ balanceItems: [],
171
+ deleteRegistrationIds: [],
172
+ }),
173
+ administrationFee: 0,
174
+ freeContribution: 0,
175
+ paymentMethod: PaymentMethod.PointOfSale,
176
+ totalPrice: 25,
177
+ asOrganizationId: organization.id,
178
+ customer: null,
179
+ });
180
+
181
+ await post(body, organization, token);
182
+ }
183
+
83
184
  const body = IDRegisterCheckout.create({
84
185
  cart: IDRegisterCart.create({
85
186
  items: [
@@ -87,9 +188,9 @@ describe('Endpoint.RegisterMembers', () => {
87
188
  id: uuidv4(),
88
189
  replaceRegistrationIds: [],
89
190
  options: [],
90
- groupPrice: groupPrice1,
191
+ groupPrice,
91
192
  organizationId: organization.id,
92
- groupId: group1.id,
193
+ groupId: group.id,
93
194
  memberId: member.id,
94
195
  }),
95
196
  ],
@@ -105,30 +206,1665 @@ describe('Endpoint.RegisterMembers', () => {
105
206
  });
106
207
  // #endregion
107
208
 
108
- // act
109
- const response = await post(body);
209
+ // #region act and assert
110
210
 
111
- // assert
112
- expect(response.body).toBeDefined();
113
- expect(response.body.registrations.length).toBe(1);
211
+ await expect(async () => await post(body, organization, token))
212
+ .rejects
213
+ .toThrow('Too many e-mails limited');
214
+
215
+ expect(spySendWebmaster).toHaveBeenCalledOnce();
216
+ // #endregion
217
+
218
+ (STAMHOOFD.userMode as string) = 'platform';
219
+ });
220
+
221
+ test('Should fail if balance items changed', async () => {
222
+ // #region arrange
223
+ const { member, group, user, groupPrice, organization, token } = await initData();
224
+
225
+ const balanceItem1 = await new BalanceItemFactory({
226
+ organizationId: organization.id,
227
+ memberId: member.id,
228
+ userId: user.id,
229
+ payingOrganizationId: organization.id,
230
+ type: BalanceItemType.Registration,
231
+ amount: 10,
232
+ unitPrice: 2,
233
+ }).create();
234
+
235
+ const cartItem = BalanceItemCartItem.create({
236
+ item: balanceItem1.getStructure(),
237
+ price: 20,
238
+ });
239
+
240
+ const body = IDRegisterCheckout.create({
241
+ cart: IDRegisterCart.create({
242
+ items: [
243
+ IDRegisterItem.create({
244
+ id: uuidv4(),
245
+ replaceRegistrationIds: [],
246
+ options: [],
247
+ groupPrice,
248
+ organizationId: organization.id,
249
+ groupId: group.id,
250
+ memberId: member.id,
251
+ }),
252
+ ],
253
+ balanceItems: [
254
+ cartItem,
255
+ ],
256
+ deleteRegistrationIds: [],
257
+ }),
258
+ administrationFee: 0,
259
+ freeContribution: 0,
260
+ paymentMethod: PaymentMethod.PointOfSale,
261
+ totalPrice: 45,
262
+ customer: null,
263
+ });
264
+ // #endregion
265
+
266
+ // #region act and assert
267
+ await balanceItem1.delete();
268
+
269
+ await expect(async () => await post(body, organization, token))
270
+ .rejects
271
+ .toThrow(new RegExp('Oeps, één of meerdere openstaande bedragen in jouw winkelmandje zijn aangepast'));
272
+ // #endregion
273
+ });
274
+
275
+ test('Should fail when pay balance item as organization', async () => {
276
+ // #region arrange
277
+ const { member, group, user, groupPrice, organization, token } = await initData();
278
+
279
+ const balanceItem1 = await new BalanceItemFactory({
280
+ organizationId: organization.id,
281
+ memberId: member.id,
282
+ userId: user.id,
283
+ payingOrganizationId: organization.id,
284
+ type: BalanceItemType.Registration,
285
+ amount: 10,
286
+ unitPrice: 2,
287
+ }).create();
288
+
289
+ const cartItem = BalanceItemCartItem.create({
290
+ item: balanceItem1.getStructure(),
291
+ price: 20,
292
+ });
293
+
294
+ const body = IDRegisterCheckout.create({
295
+ cart: IDRegisterCart.create({
296
+ items: [
297
+ IDRegisterItem.create({
298
+ id: uuidv4(),
299
+ replaceRegistrationIds: [],
300
+ options: [],
301
+ groupPrice,
302
+ organizationId: organization.id,
303
+ groupId: group.id,
304
+ memberId: member.id,
305
+ }),
306
+ ],
307
+ balanceItems: [
308
+ cartItem,
309
+ ],
310
+ deleteRegistrationIds: [],
311
+ }),
312
+ administrationFee: 0,
313
+ freeContribution: 0,
314
+ paymentMethod: PaymentMethod.PointOfSale,
315
+ totalPrice: 45,
316
+ asOrganizationId: organization.id,
317
+ customer: null,
318
+ });
319
+ // #endregion
320
+
321
+ // #region act and assert
322
+ await expect(async () => await post(body, organization, token))
323
+ .rejects
324
+ .toThrow(new RegExp('Not possible to pay balance items as the organization'));
325
+ // #endregion
326
+ });
327
+
328
+ test('Should fail if has no write access for member', async () => {
329
+ // #region arrange
330
+ const { organization, group, groupPrice, token } = await initData();
331
+ const { member: member2 } = await initData();
332
+
333
+ const body = IDRegisterCheckout.create({
334
+ cart: IDRegisterCart.create({
335
+ items: [
336
+ IDRegisterItem.create({
337
+ id: uuidv4(),
338
+ replaceRegistrationIds: [],
339
+ options: [],
340
+ groupPrice,
341
+ organizationId: organization.id,
342
+ groupId: group.id,
343
+ memberId: member2.id,
344
+ }),
345
+ ],
346
+ balanceItems: [
347
+ ],
348
+ deleteRegistrationIds: [],
349
+ }),
350
+ administrationFee: 0,
351
+ freeContribution: 0,
352
+ paymentMethod: PaymentMethod.PointOfSale,
353
+ totalPrice: 25,
354
+ customer: null,
355
+ });
356
+ // #endregion
357
+
358
+ // #region act and assert
359
+ await expect(async () => await post(body, organization, token))
360
+ .rejects
361
+ .toThrow(new RegExp('No permission to register this member'));
362
+ // #endregion
363
+ });
364
+
365
+ test('Should fail if empty cart', async () => {
366
+ // #region arrange
367
+ const { member, user, organization, token } = await initData();
368
+
369
+ const balanceItem1 = await new BalanceItemFactory({
370
+ organizationId: organization.id,
371
+ memberId: member.id,
372
+ userId: user.id,
373
+ payingOrganizationId: organization.id,
374
+ type: BalanceItemType.Registration,
375
+ amount: 10,
376
+ unitPrice: 2,
377
+ }).create();
378
+
379
+ const body = IDRegisterCheckout.create({
380
+ cart: IDRegisterCart.create({
381
+ items: [
382
+ ],
383
+ balanceItems: [
384
+ ],
385
+ deleteRegistrationIds: [],
386
+ }),
387
+ administrationFee: 0,
388
+ freeContribution: 0,
389
+ paymentMethod: PaymentMethod.PointOfSale,
390
+ totalPrice: 45,
391
+ customer: null,
392
+ });
393
+ // #endregion
394
+
395
+ // #region act and assert
396
+ await balanceItem1.delete();
397
+
398
+ await expect(async () => await post(body, organization, token))
399
+ .rejects
400
+ .toThrow(new RegExp('Oeps, jouw mandje is leeg.'));
401
+ // #endregion
402
+ });
403
+
404
+ test('Should fail if price changed', async () => {
405
+ // #region arrange
406
+ const { member, group, groupPrice, organization, token } = await initData();
407
+
408
+ const body = IDRegisterCheckout.create({
409
+ cart: IDRegisterCart.create({
410
+ items: [
411
+ IDRegisterItem.create({
412
+ id: uuidv4(),
413
+ replaceRegistrationIds: [],
414
+ options: [],
415
+ groupPrice,
416
+ organizationId: organization.id,
417
+ groupId: group.id,
418
+ memberId: member.id,
419
+ }),
420
+ ],
421
+ deleteRegistrationIds: [],
422
+ }),
423
+ administrationFee: 0,
424
+ freeContribution: 0,
425
+ paymentMethod: PaymentMethod.PointOfSale,
426
+ totalPrice: 30,
427
+ asOrganizationId: organization.id,
428
+ customer: null,
429
+ });
430
+ // #endregion
431
+
432
+ // #region act and assert
433
+
434
+ await expect(async () => await post(body, organization, token))
435
+ .rejects
436
+ .toThrow(new RegExp('Oeps! De prijs is gewijzigd terwijl je aan het afrekenen was'));
437
+ // #endregion
438
+ });
439
+
440
+ test('Should fail if member is already registered', async () => {
441
+ // #region arrange
442
+ const { organization, group, groupPrice, token, member } = await initData();
443
+
444
+ const body = IDRegisterCheckout.create({
445
+ cart: IDRegisterCart.create({
446
+ items: [
447
+ IDRegisterItem.create({
448
+ id: uuidv4(),
449
+ replaceRegistrationIds: [],
450
+ options: [],
451
+ groupPrice,
452
+ organizationId: organization.id,
453
+ groupId: group.id,
454
+ memberId: member.id,
455
+ }),
456
+ ],
457
+ balanceItems: [
458
+ ],
459
+ deleteRegistrationIds: [],
460
+ }),
461
+ administrationFee: 0,
462
+ freeContribution: 0,
463
+ paymentMethod: PaymentMethod.PointOfSale,
464
+ totalPrice: 25,
465
+ customer: null,
466
+ });
467
+ // #endregion
468
+
469
+ // #region act and assert
470
+ // register first time
471
+ await post(body, organization, token);
472
+
473
+ // second time should fail
474
+ await expect(async () => await post(body, organization, token))
475
+ .rejects
476
+ .toThrow(new RegExp('Already registered'));
477
+ // #endregion
478
+ });
479
+
480
+ test('Should fail if duplicate registration in cart', async () => {
481
+ // #region arrange
482
+ const { organization, group, groupPrice, token, member } = await initData();
483
+
484
+ const body = IDRegisterCheckout.create({
485
+ cart: IDRegisterCart.create({
486
+ items: [
487
+ IDRegisterItem.create({
488
+ id: uuidv4(),
489
+ replaceRegistrationIds: [],
490
+ options: [],
491
+ groupPrice,
492
+ organizationId: organization.id,
493
+ groupId: group.id,
494
+ memberId: member.id,
495
+ }),
496
+ IDRegisterItem.create({
497
+ id: uuidv4(),
498
+ replaceRegistrationIds: [],
499
+ options: [],
500
+ groupPrice,
501
+ organizationId: organization.id,
502
+ groupId: group.id,
503
+ memberId: member.id,
504
+ }),
505
+ ],
506
+ balanceItems: [
507
+ ],
508
+ deleteRegistrationIds: [],
509
+ }),
510
+ administrationFee: 0,
511
+ freeContribution: 0,
512
+ paymentMethod: PaymentMethod.PointOfSale,
513
+ totalPrice: 50,
514
+ customer: null,
515
+ });
516
+ // #endregion
517
+
518
+ // #region act and assert
519
+ await expect(async () => await post(body, organization, token))
520
+ .rejects
521
+ .toThrow(new RegExp('duplicate_register_item'));
522
+ // #endregion
523
+ });
524
+
525
+ test('Should fail register by other organization if disabled by group', async () => {
526
+ // #region arrange
527
+ const { organization, group, groupPrice, token, member, user } = await initData();
528
+
529
+ const { organization: organization2 } = await initData();
530
+
531
+ user.permissions = UserPermissions.create({
532
+ organizationPermissions: new Map([
533
+ [organization2.id, Permissions.create({
534
+ level: PermissionLevel.Full,
535
+ })],
536
+ ]),
537
+ });
538
+
539
+ await user.save();
540
+
541
+ const body = IDRegisterCheckout.create({
542
+ cart: IDRegisterCart.create({
543
+ items: [
544
+ IDRegisterItem.create({
545
+ id: uuidv4(),
546
+ replaceRegistrationIds: [],
547
+ options: [],
548
+ groupPrice,
549
+ organizationId: organization.id,
550
+ groupId: group.id,
551
+ memberId: member.id,
552
+ }),
553
+ ],
554
+ balanceItems: [
555
+ ],
556
+ deleteRegistrationIds: [],
557
+ }),
558
+ administrationFee: 0,
559
+ freeContribution: 0,
560
+ paymentMethod: PaymentMethod.PointOfSale,
561
+ totalPrice: 25,
562
+ customer: null,
563
+ asOrganizationId: organization2.id,
564
+ });
565
+ // #endregion
566
+
567
+ // #region act and assert
568
+ await expect(async () => await post(body, organization, token))
569
+ .rejects
570
+ .toThrow(new RegExp('allowRegistrationsByOrganization disabled'));
571
+ // #endregion
572
+ });
573
+
574
+ test('Should fail if invalid payment', async () => {
575
+ // #region arrange
576
+ const { organization, group, groupPrice, token, member } = await initData();
577
+
578
+ const body = IDRegisterCheckout.create({
579
+ cart: IDRegisterCart.create({
580
+ items: [
581
+ IDRegisterItem.create({
582
+ id: uuidv4(),
583
+ replaceRegistrationIds: [],
584
+ options: [],
585
+ groupPrice,
586
+ organizationId: organization.id,
587
+ groupId: group.id,
588
+ memberId: member.id,
589
+ }),
590
+ ],
591
+ balanceItems: [
592
+ ],
593
+ deleteRegistrationIds: [],
594
+ }),
595
+ administrationFee: 0,
596
+ freeContribution: 0,
597
+ paymentMethod: PaymentMethod.CreditCard,
598
+ totalPrice: 25,
599
+ customer: null,
600
+ });
601
+ // #endregion
602
+
603
+ // #region act and assert
604
+ await expect(async () => await post(body, organization, token))
605
+ .rejects
606
+ .toThrow(new RegExp('Oeps, je hebt geen geldige betaalmethode geselecteerd'));
607
+ // #endregion
608
+ });
609
+
610
+ test('Should fail if no redirect url for online payment', async () => {
611
+ // #region arrange
612
+ const { organization, group, groupPrice, token, member } = await initData();
613
+ organization.meta.registrationPaymentConfiguration.paymentMethods.push(PaymentMethod.Bancontact);
614
+ await organization.save();
615
+
616
+ const body = IDRegisterCheckout.create({
617
+ cart: IDRegisterCart.create({
618
+ items: [
619
+ IDRegisterItem.create({
620
+ id: uuidv4(),
621
+ replaceRegistrationIds: [],
622
+ options: [],
623
+ groupPrice,
624
+ organizationId: organization.id,
625
+ groupId: group.id,
626
+ memberId: member.id,
627
+ }),
628
+ ],
629
+ balanceItems: [
630
+ ],
631
+ deleteRegistrationIds: [],
632
+ }),
633
+ administrationFee: 0,
634
+ freeContribution: 0,
635
+ paymentMethod: PaymentMethod.Bancontact,
636
+ totalPrice: 25,
637
+ cancelUrl: new URL('https://www.stamhoofd.be'),
638
+ customer: null,
639
+ });
640
+ // #endregion
641
+
642
+ // #region act and assert
643
+ await expect(async () => await post(body, organization, token))
644
+ .rejects
645
+ .toThrow(new RegExp('redirectUrl or cancelUrl is missing'));
646
+ // #endregion
647
+ });
648
+
649
+ test('Should fail if no cancel url for online payment', async () => {
650
+ // #region arrange
651
+ const { organization, group, groupPrice, token, member } = await initData();
652
+ organization.meta.registrationPaymentConfiguration.paymentMethods.push(PaymentMethod.Bancontact);
653
+ await organization.save();
654
+
655
+ const body = IDRegisterCheckout.create({
656
+ cart: IDRegisterCart.create({
657
+ items: [
658
+ IDRegisterItem.create({
659
+ id: uuidv4(),
660
+ replaceRegistrationIds: [],
661
+ options: [],
662
+ groupPrice,
663
+ organizationId: organization.id,
664
+ groupId: group.id,
665
+ memberId: member.id,
666
+ }),
667
+ ],
668
+ balanceItems: [
669
+ ],
670
+ deleteRegistrationIds: [],
671
+ }),
672
+ administrationFee: 0,
673
+ freeContribution: 0,
674
+ paymentMethod: PaymentMethod.Bancontact,
675
+ totalPrice: 25,
676
+ redirectUrl: new URL('https://www.stamhoofd.be'),
677
+ customer: null,
678
+ });
679
+ // #endregion
680
+
681
+ // #region act and assert
682
+ await expect(async () => await post(body, organization, token))
683
+ .rejects
684
+ .toThrow(new RegExp('redirectUrl or cancelUrl is missing'));
685
+ // #endregion
686
+ });
687
+
688
+ test('Should reserve if group has max members', async () => {
689
+ // #region arrange
690
+ const { organization, group, groupPrice, token, member } = await initData();
691
+ group.settings.maxMembers = 5;
692
+ await group.save();
693
+
694
+ const body = IDRegisterCheckout.create({
695
+ cart: IDRegisterCart.create({
696
+ items: [
697
+ IDRegisterItem.create({
698
+ id: uuidv4(),
699
+ replaceRegistrationIds: [],
700
+ options: [],
701
+ groupPrice,
702
+ organizationId: organization.id,
703
+ groupId: group.id,
704
+ memberId: member.id,
705
+ }),
706
+ ],
707
+ balanceItems: [
708
+ ],
709
+ deleteRegistrationIds: [],
710
+ }),
711
+ administrationFee: 0,
712
+ freeContribution: 0,
713
+ paymentMethod: PaymentMethod.PointOfSale,
714
+ totalPrice: 25,
715
+ });
716
+ // #endregion
717
+
718
+ // #region act and assert
719
+ const response = await post(body, organization, token);
720
+ expect(response.body.registrations.length).toBe(1);
721
+ expect(response.body.registrations[0].reservedUntil).not.toBeNull();
722
+ // #endregion
723
+ });
724
+
725
+ test('Should reuse existing registration', async () => {
726
+ // #region arrange
727
+ const { organization, group, groupPrice, token, member, user } = await initData();
728
+ group.settings.allowRegistrationsByOrganization = true;
729
+ await group.save();
730
+
731
+ user.permissions = UserPermissions.create({
732
+ organizationPermissions: new Map([
733
+ [organization.id, Permissions.create({
734
+ level: PermissionLevel.Full,
735
+ })],
736
+ ]),
737
+ });
738
+
739
+ await user.save();
740
+
741
+ const group2 = await new GroupFactory({
742
+ organization,
743
+ price: 25,
744
+ stock: 5,
745
+ }).create();
746
+
747
+ const firstRegistration = await new RegistrationFactory({
748
+ member,
749
+ group: group2,
750
+ groupPrice: group2.settings.prices[0],
751
+ }).create();
752
+
753
+ const body = IDRegisterCheckout.create({
754
+ cart: IDRegisterCart.create({
755
+ items: [
756
+ IDRegisterItem.create({
757
+ id: uuidv4(),
758
+ replaceRegistrationIds: [firstRegistration.id],
759
+ options: [],
760
+ groupPrice,
761
+ organizationId: organization.id,
762
+ groupId: group.id,
763
+ memberId: member.id,
764
+ }),
765
+ ],
766
+ balanceItems: [
767
+ ],
768
+ deleteRegistrationIds: [],
769
+ }),
770
+ administrationFee: 0,
771
+ freeContribution: 0,
772
+ paymentMethod: PaymentMethod.PointOfSale,
773
+ totalPrice: 0,
774
+ asOrganizationId: organization.id,
775
+ });
776
+
777
+ // #endregion
778
+
779
+ // #region act and assert
780
+ const response = await post(body, organization, token);
781
+ expect(response.body.registrations.length).toBe(1);
782
+ expect(response.body.registrations[0].id).toEqual(firstRegistration.id);
783
+ // #endregion
784
+ });
785
+
786
+ test('Should reuse recently deactivated registration', async () => {
787
+ // #region arrange
788
+ const { organization, group, groupPrice, token, member, user } = await initData();
789
+ group.settings.allowRegistrationsByOrganization = true;
790
+ await group.save();
791
+
792
+ user.permissions = UserPermissions.create({
793
+ organizationPermissions: new Map([
794
+ [organization.id, Permissions.create({
795
+ level: PermissionLevel.Full,
796
+ })],
797
+ ]),
798
+ });
799
+
800
+ await user.save();
801
+
802
+ const firstRegistration = await new RegistrationFactory({
803
+ member,
804
+ group,
805
+ groupPrice,
806
+ }).create();
807
+
808
+ firstRegistration.deactivatedAt = new Date();
809
+ await firstRegistration.save();
810
+
811
+ const body = IDRegisterCheckout.create({
812
+ cart: IDRegisterCart.create({
813
+ items: [
814
+ IDRegisterItem.create({
815
+ id: uuidv4(),
816
+ replaceRegistrationIds: [],
817
+ options: [],
818
+ groupPrice,
819
+ organizationId: organization.id,
820
+ groupId: group.id,
821
+ memberId: member.id,
822
+ }),
823
+ ],
824
+ balanceItems: [
825
+ ],
826
+ deleteRegistrationIds: [],
827
+ }),
828
+ administrationFee: 0,
829
+ freeContribution: 0,
830
+ paymentMethod: PaymentMethod.PointOfSale,
831
+ totalPrice: 25,
832
+ asOrganizationId: organization.id,
833
+ });
834
+
835
+ // #endregion
836
+
837
+ // #region act and assert
838
+ const response = await post(body, organization, token);
839
+ expect(response.body.registrations.length).toBe(1);
840
+ expect(response.body.registrations[0].id).toEqual(firstRegistration.id);
841
+ // #endregion
842
+ });
843
+
844
+ test('Should update registered members', async () => {
845
+ // #region arrange
846
+ const { member, group, groupPrice, organization, token } = await initData();
847
+
848
+ const body = IDRegisterCheckout.create({
849
+ cart: IDRegisterCart.create({
850
+ items: [
851
+ IDRegisterItem.create({
852
+ id: uuidv4(),
853
+ replaceRegistrationIds: [],
854
+ options: [],
855
+ groupPrice,
856
+ organizationId: organization.id,
857
+ groupId: group.id,
858
+ memberId: member.id,
859
+ }),
860
+ ],
861
+ balanceItems: [],
862
+ deleteRegistrationIds: [],
863
+ }),
864
+ administrationFee: 0,
865
+ freeContribution: 0,
866
+ paymentMethod: PaymentMethod.PointOfSale,
867
+ totalPrice: 25,
868
+ asOrganizationId: organization.id,
869
+ customer: null,
870
+ });
871
+ // #endregion
872
+
873
+ // act
874
+ const response = await post(body, organization, token);
875
+
876
+ // assert
877
+ expect(response.body).toBeDefined();
878
+ expect(response.body.registrations.length).toBe(1);
879
+
880
+ const updatedGroup = await Group.getByID(group.id);
881
+ expect(updatedGroup!.settings.registeredMembers).toBe(1);
882
+ expect(updatedGroup!.settings.reservedMembers).toBe(0);
883
+ });
884
+
885
+ test('Should update reserved members', async () => {
886
+ // #region arrange
887
+ const { member, organization, token } = await initData();
888
+
889
+ organization.meta.registrationPaymentConfiguration.paymentMethods = [PaymentMethod.PointOfSale, PaymentMethod.Payconiq];
890
+
891
+ organization.privateMeta.payconiqAccounts = [PayconiqAccount.create({
892
+ id: uuidv4(),
893
+ apiKey: 'testKey',
894
+ merchantId: 'test',
895
+ profileId: 'test',
896
+ name: 'test',
897
+ iban: 'BE56587127952688', // = random IBAN
898
+ callbackUrl: 'https://www.example.com',
899
+ })];
900
+
901
+ await organization.save();
902
+
903
+ const group2 = await new GroupFactory({
904
+ organization,
905
+ price: 15,
906
+ stock: 4,
907
+ maxMembers: 1,
908
+ }).create();
909
+
910
+ const groupPrice2 = group2.settings.prices[0];
911
+
912
+ const body = IDRegisterCheckout.create({
913
+ cart: IDRegisterCart.create({
914
+ items: [
915
+ IDRegisterItem.create({
916
+ id: uuidv4(),
917
+ replaceRegistrationIds: [],
918
+ options: [],
919
+ groupPrice: groupPrice2,
920
+ organizationId: organization.id,
921
+ groupId: group2.id,
922
+ memberId: member.id,
923
+ }),
924
+ ],
925
+ balanceItems: [],
926
+ deleteRegistrationIds: [],
927
+ }),
928
+ administrationFee: 0,
929
+ freeContribution: 0,
930
+ paymentMethod: PaymentMethod.Payconiq,
931
+ redirectUrl: new URL('https://www.example.com'),
932
+ cancelUrl: new URL('https://www.example.com'),
933
+ totalPrice: 15,
934
+ customer: null,
935
+ });
936
+
937
+ nock('https://api.ext.payconiq.com')
938
+ .post('/v3/payments')
939
+ .reply(200, {
940
+ paymentId: 'testPaymentId',
941
+ _links: {
942
+ checkout: {
943
+ href: 'https://www.example.com',
944
+ },
945
+ },
946
+ });
947
+ // #endregion
948
+
949
+ // act
950
+ const response = await post(body, organization, token);
951
+
952
+ // assert
953
+ expect(response.body).toBeDefined();
954
+ expect(response.body.registrations.length).toBe(1);
955
+
956
+ const updatedGroup = await Group.getByID(group2.id);
957
+ expect(updatedGroup!.settings.registeredMembers).toBe(0);
958
+ expect(updatedGroup!.settings.reservedMembers).toBe(1);
959
+ });
960
+
961
+ test('Register for group with trial should set trail period', async () => {
962
+ // #region arrange
963
+ const date = new Date('2023-05-14');
964
+ jest.useFakeTimers().setSystemTime(date);
965
+
966
+ try {
967
+ const { member, group, groupPrice, organization, token } = await initData();
968
+ group.settings.trialDays = 5;
969
+ await group.save();
970
+
971
+ const body = IDRegisterCheckout.create({
972
+ cart: IDRegisterCart.create({
973
+ items: [
974
+ IDRegisterItem.create({
975
+ id: uuidv4(),
976
+ replaceRegistrationIds: [],
977
+ options: [],
978
+ groupPrice: groupPrice,
979
+ organizationId: organization.id,
980
+ groupId: group.id,
981
+ memberId: member.id,
982
+ trial: true,
983
+ }),
984
+ ],
985
+ balanceItems: [],
986
+ deleteRegistrationIds: [],
987
+ }),
988
+ administrationFee: 0,
989
+ freeContribution: 0,
990
+ paymentMethod: PaymentMethod.PointOfSale,
991
+ totalPrice: 0,
992
+ asOrganizationId: organization.id,
993
+ customer: null,
994
+ });
995
+ // #endregion
996
+
997
+ // act
998
+ const response = await post(body, organization, token);
999
+
1000
+ // assert
1001
+ expect(response.body).toBeDefined();
1002
+ expect(response.body.registrations.length).toBe(1);
1003
+ const trialUntil = response.body.registrations[0].trialUntil;
1004
+ expect(trialUntil).not.toBeNull();
1005
+ // 2023-05-14
1006
+ expect(trialUntil!.getFullYear()).toBe(2023);
1007
+ expect(trialUntil!.getMonth()).toBe(4);
1008
+ expect(trialUntil!.getDate()).toBe(19);
1009
+ }
1010
+ finally {
1011
+ jest.useFakeTimers().resetAllMocks();
1012
+ }
1013
+ });
1014
+
1015
+ test('Should update group stock reservations', async () => {
1016
+ // #region arrange
1017
+ const { organization, group, groupPrice, token, member } = await initData();
1018
+ groupPrice.stock = 5;
1019
+ await group.save();
1020
+
1021
+ const body = IDRegisterCheckout.create({
1022
+ cart: IDRegisterCart.create({
1023
+ items: [
1024
+ IDRegisterItem.create({
1025
+ id: uuidv4(),
1026
+ replaceRegistrationIds: [],
1027
+ options: [],
1028
+ groupPrice,
1029
+ organizationId: organization.id,
1030
+ groupId: group.id,
1031
+ memberId: member.id,
1032
+ }),
1033
+ ],
1034
+ balanceItems: [],
1035
+ deleteRegistrationIds: [],
1036
+ }),
1037
+ administrationFee: 0,
1038
+ freeContribution: 0,
1039
+ paymentMethod: PaymentMethod.PointOfSale,
1040
+ totalPrice: 25,
1041
+ asOrganizationId: organization.id,
1042
+ customer: null,
1043
+ });
1044
+ // #endregion
1045
+
1046
+ // #region act and assert
1047
+ expect(group?.stockReservations.length).toBe(0);
1048
+
1049
+ await post(body, organization, token);
1050
+
1051
+ const updatedGroup = await Group.getByID(group.id);
1052
+ expect(updatedGroup?.stockReservations.length).toBe(1);
1053
+ // #endregion
1054
+ });
1055
+
1056
+ test('Should fail if group price stock sold out', async () => {
1057
+ // #region arrange
1058
+ const { organization, group, groupPrice, token, member, otherMembers } = await initData({
1059
+ permissionLevel: PermissionLevel.Read, otherMemberAmount: 3 });
1060
+ groupPrice.stock = 2;
1061
+ await group.save();
1062
+
1063
+ const body = IDRegisterCheckout.create({
1064
+ cart: IDRegisterCart.create({
1065
+ items: [
1066
+ IDRegisterItem.create({
1067
+ id: uuidv4(),
1068
+ replaceRegistrationIds: [],
1069
+ options: [],
1070
+ groupPrice,
1071
+ organizationId: organization.id,
1072
+ groupId: group.id,
1073
+ memberId: member.id,
1074
+ }),
1075
+ ...otherMembers.map(m => IDRegisterItem.create({
1076
+ id: uuidv4(),
1077
+ replaceRegistrationIds: [],
1078
+ options: [],
1079
+ groupPrice,
1080
+ organizationId: organization.id,
1081
+ groupId: group.id,
1082
+ memberId: m.id,
1083
+ })),
1084
+ ],
1085
+ balanceItems: [],
1086
+ deleteRegistrationIds: [],
1087
+ }),
1088
+ administrationFee: 0,
1089
+ freeContribution: 0,
1090
+ paymentMethod: PaymentMethod.PointOfSale,
1091
+ totalPrice: 75,
1092
+ customer: null,
1093
+ });
1094
+ // #endregion
1095
+
1096
+ // #region act and assert
1097
+ expect(group?.stockReservations.length).toBe(0);
1098
+
1099
+ await expect(async () => await post(body, organization, token))
1100
+ .rejects
1101
+ .toThrow(new RegExp('Maximum reached'));
1102
+ // #endregion
1103
+ });
1104
+
1105
+ test('Should fail if option stock sold out', async () => {
1106
+ // #region arrange
1107
+ const { organization, group, groupPrice, token, member } = await initData();
1108
+
1109
+ const option1 = GroupOption.create({
1110
+ name: 'option 1',
1111
+ stock: 4,
1112
+ price: ReduceablePrice.create({
1113
+ price: 5,
1114
+ reducedPrice: 3,
1115
+ }),
1116
+ });
1117
+
1118
+ const option2 = GroupOption.create({
1119
+ name: 'option 2',
1120
+ stock: 4,
1121
+ price: ReduceablePrice.create({
1122
+ price: 3,
1123
+ reducedPrice: 1,
1124
+ }),
1125
+ });
1126
+
1127
+ const optionMenu = GroupOptionMenu.create({
1128
+ name: 'option menu 1',
1129
+ multipleChoice: true,
1130
+ options: [option1, option2],
1131
+ });
1132
+
1133
+ group.settings.optionMenus = [
1134
+ optionMenu,
1135
+ ];
1136
+
1137
+ await group.save();
1138
+
1139
+ const body = IDRegisterCheckout.create({
1140
+ cart: IDRegisterCart.create({
1141
+ items: [
1142
+ IDRegisterItem.create({
1143
+ id: uuidv4(),
1144
+ replaceRegistrationIds: [],
1145
+ options: [
1146
+ RegisterItemOption.create({
1147
+ option: option1,
1148
+ amount: 2,
1149
+ optionMenu,
1150
+ }),
1151
+ RegisterItemOption.create({
1152
+ option: option2,
1153
+ amount: 5,
1154
+ optionMenu,
1155
+ }),
1156
+ ],
1157
+ groupPrice,
1158
+ organizationId: organization.id,
1159
+ groupId: group.id,
1160
+ memberId: member.id,
1161
+ }),
1162
+ ],
1163
+ balanceItems: [
1164
+ ],
1165
+ deleteRegistrationIds: [],
1166
+ }),
1167
+ administrationFee: 0,
1168
+ freeContribution: 0,
1169
+ paymentMethod: PaymentMethod.PointOfSale,
1170
+ totalPrice: 50,
1171
+ customer: null,
1172
+ });
1173
+ // #endregion
1174
+
1175
+ // #region act and assert
1176
+ await expect(async () => await post(body, organization, token))
1177
+ .rejects
1178
+ .toThrow(new RegExp('Stock empty'));
1179
+ // #endregion
1180
+ });
1181
+
1182
+ test('Should fail if max option exceeded', async () => {
1183
+ // #region arrange
1184
+ const { organization, group, groupPrice, token, member } = await initData();
1185
+
1186
+ const option1 = GroupOption.create({
1187
+ name: 'option 1',
1188
+ stock: 4,
1189
+ maximum: 5,
1190
+ allowAmount: true,
1191
+ price: ReduceablePrice.create({
1192
+ price: 5,
1193
+ reducedPrice: 3,
1194
+ }),
1195
+ });
1196
+
1197
+ const option2 = GroupOption.create({
1198
+ name: 'option 2',
1199
+ stock: 5,
1200
+ maximum: 2,
1201
+ allowAmount: true,
1202
+ price: ReduceablePrice.create({
1203
+ price: 3,
1204
+ reducedPrice: 1,
1205
+ }),
1206
+ });
1207
+
1208
+ const optionMenu = GroupOptionMenu.create({
1209
+ name: 'option menu 1',
1210
+ multipleChoice: true,
1211
+ options: [option1, option2],
1212
+ });
1213
+
1214
+ group.settings.optionMenus = [
1215
+ optionMenu,
1216
+ ];
1217
+
1218
+ await group.save();
1219
+
1220
+ const body = IDRegisterCheckout.create({
1221
+ cart: IDRegisterCart.create({
1222
+ items: [
1223
+ IDRegisterItem.create({
1224
+ id: uuidv4(),
1225
+ replaceRegistrationIds: [],
1226
+ options: [
1227
+ RegisterItemOption.create({
1228
+ option: option1,
1229
+ amount: 2,
1230
+ optionMenu,
1231
+ }),
1232
+ RegisterItemOption.create({
1233
+ option: option2,
1234
+ amount: 5,
1235
+ optionMenu,
1236
+ }),
1237
+ ],
1238
+ groupPrice,
1239
+ organizationId: organization.id,
1240
+ groupId: group.id,
1241
+ memberId: member.id,
1242
+ }),
1243
+ ],
1244
+ balanceItems: [
1245
+ ],
1246
+ deleteRegistrationIds: [],
1247
+ }),
1248
+ administrationFee: 0,
1249
+ freeContribution: 0,
1250
+ paymentMethod: PaymentMethod.PointOfSale,
1251
+ totalPrice: 50,
1252
+ customer: null,
1253
+ });
1254
+ // #endregion
1255
+
1256
+ // #region act and assert
1257
+ await expect(async () => await post(body, organization, token))
1258
+ .rejects
1259
+ .toThrow(new RegExp('Option maximum exceeded'));
1260
+ // #endregion
1261
+ });
1262
+
1263
+ test('Should not fail if max option not exceeded', async () => {
1264
+ // #region arrange
1265
+ const { organization, group, groupPrice, token, member } = await initData();
1266
+
1267
+ const option1 = GroupOption.create({
1268
+ name: 'option 1',
1269
+ stock: 4,
1270
+ maximum: 5,
1271
+ allowAmount: true,
1272
+ price: ReduceablePrice.create({
1273
+ price: 5,
1274
+ reducedPrice: 3,
1275
+ }),
1276
+ });
1277
+
1278
+ const option2 = GroupOption.create({
1279
+ name: 'option 2',
1280
+ stock: 5,
1281
+ maximum: 5,
1282
+ allowAmount: true,
1283
+ price: ReduceablePrice.create({
1284
+ price: 3,
1285
+ reducedPrice: 1,
1286
+ }),
1287
+ });
1288
+
1289
+ const optionMenu = GroupOptionMenu.create({
1290
+ name: 'option menu 1',
1291
+ multipleChoice: true,
1292
+ options: [option1, option2],
1293
+ });
1294
+
1295
+ group.settings.optionMenus = [
1296
+ optionMenu,
1297
+ ];
1298
+
1299
+ await group.save();
1300
+
1301
+ const body = IDRegisterCheckout.create({
1302
+ cart: IDRegisterCart.create({
1303
+ items: [
1304
+ IDRegisterItem.create({
1305
+ id: uuidv4(),
1306
+ replaceRegistrationIds: [],
1307
+ options: [
1308
+ RegisterItemOption.create({
1309
+ option: option1,
1310
+ amount: 2,
1311
+ optionMenu,
1312
+ }),
1313
+ RegisterItemOption.create({
1314
+ option: option2,
1315
+ amount: 5,
1316
+ optionMenu,
1317
+ }),
1318
+ ],
1319
+ groupPrice,
1320
+ organizationId: organization.id,
1321
+ groupId: group.id,
1322
+ memberId: member.id,
1323
+ }),
1324
+ ],
1325
+ balanceItems: [
1326
+ ],
1327
+ deleteRegistrationIds: [],
1328
+ }),
1329
+ administrationFee: 0,
1330
+ freeContribution: 0,
1331
+ paymentMethod: PaymentMethod.PointOfSale,
1332
+ totalPrice: 50,
1333
+ customer: null,
1334
+ });
1335
+ // #endregion
1336
+
1337
+ // #region act and assert
1338
+ const result = await post(body, organization, token);
1339
+ expect(result).toBeDefined();
1340
+ // #endregion
1341
+ });
1342
+ });
1343
+
1344
+ describe('Register by other organization', () => {
1345
+ test('Should fail if disabled by group', async () => {
1346
+ // #region arrange
1347
+ const { organization, group, groupPrice, token, member, user } = await initData();
1348
+
1349
+ const { organization: organization2 } = await initData();
1350
+
1351
+ user.permissions = UserPermissions.create({
1352
+ organizationPermissions: new Map([
1353
+ [organization2.id, Permissions.create({
1354
+ level: PermissionLevel.Full,
1355
+ })],
1356
+ ]),
1357
+ });
1358
+
1359
+ await user.save();
1360
+
1361
+ const body = IDRegisterCheckout.create({
1362
+ cart: IDRegisterCart.create({
1363
+ items: [
1364
+ IDRegisterItem.create({
1365
+ id: uuidv4(),
1366
+ replaceRegistrationIds: [],
1367
+ options: [],
1368
+ groupPrice,
1369
+ organizationId: organization.id,
1370
+ groupId: group.id,
1371
+ memberId: member.id,
1372
+ }),
1373
+ ],
1374
+ balanceItems: [
1375
+ ],
1376
+ deleteRegistrationIds: [],
1377
+ }),
1378
+ administrationFee: 0,
1379
+ freeContribution: 0,
1380
+ paymentMethod: PaymentMethod.PointOfSale,
1381
+ totalPrice: 25,
1382
+ customer: null,
1383
+ asOrganizationId: organization2.id,
1384
+ });
1385
+ // #endregion
1386
+
1387
+ // #region act and assert
1388
+ await expect(async () => await post(body, organization, token))
1389
+ .rejects
1390
+ .toThrow(new RegExp('allowRegistrationsByOrganization disabled'));
1391
+ // #endregion
1392
+ });
1393
+
1394
+ test('Should fail if no customer', async () => {
1395
+ // #region arrange
1396
+ const { organization, group, groupPrice, token, member, user } = await initData();
1397
+ group.settings.allowRegistrationsByOrganization = true;
1398
+ await group.save();
1399
+
1400
+ const { organization: organization2 } = await initData();
1401
+
1402
+ user.permissions = UserPermissions.create({
1403
+ organizationPermissions: new Map([
1404
+ [organization2.id, Permissions.create({
1405
+ level: PermissionLevel.Full,
1406
+ })],
1407
+ ]),
1408
+ });
1409
+
1410
+ await user.save();
1411
+
1412
+ const body = IDRegisterCheckout.create({
1413
+ cart: IDRegisterCart.create({
1414
+ items: [
1415
+ IDRegisterItem.create({
1416
+ id: uuidv4(),
1417
+ replaceRegistrationIds: [],
1418
+ options: [],
1419
+ groupPrice,
1420
+ organizationId: organization.id,
1421
+ groupId: group.id,
1422
+ memberId: member.id,
1423
+ }),
1424
+ ],
1425
+ balanceItems: [
1426
+ ],
1427
+ deleteRegistrationIds: [],
1428
+ }),
1429
+ administrationFee: 0,
1430
+ freeContribution: 0,
1431
+ paymentMethod: PaymentMethod.PointOfSale,
1432
+ totalPrice: 25,
1433
+ customer: null,
1434
+ asOrganizationId: organization2.id,
1435
+ });
1436
+ // #endregion
1437
+
1438
+ // #region act and assert
1439
+ await expect(async () => await post(body, organization, token))
1440
+ .rejects
1441
+ .toThrow(new RegExp('customer is required when paying as an organization'));
1442
+ // #endregion
1443
+ });
1444
+
1445
+ test('Should fail if no company on customer', async () => {
1446
+ // #region arrange
1447
+ const { organization, group, groupPrice, token, member, user } = await initData();
1448
+ group.settings.allowRegistrationsByOrganization = true;
1449
+ await group.save();
1450
+
1451
+ const { organization: organization2 } = await initData();
1452
+
1453
+ user.permissions = UserPermissions.create({
1454
+ organizationPermissions: new Map([
1455
+ [organization2.id, Permissions.create({
1456
+ level: PermissionLevel.Full,
1457
+ })],
1458
+ ]),
1459
+ });
1460
+
1461
+ await user.save();
1462
+
1463
+ const body = IDRegisterCheckout.create({
1464
+ cart: IDRegisterCart.create({
1465
+ items: [
1466
+ IDRegisterItem.create({
1467
+ id: uuidv4(),
1468
+ replaceRegistrationIds: [],
1469
+ options: [],
1470
+ groupPrice,
1471
+ organizationId: organization.id,
1472
+ groupId: group.id,
1473
+ memberId: member.id,
1474
+ }),
1475
+ ],
1476
+ balanceItems: [
1477
+ ],
1478
+ deleteRegistrationIds: [],
1479
+ }),
1480
+ administrationFee: 0,
1481
+ freeContribution: 0,
1482
+ paymentMethod: PaymentMethod.PointOfSale,
1483
+ totalPrice: 25,
1484
+ customer: PaymentCustomer.create({
1485
+ company: null,
1486
+ }),
1487
+ asOrganizationId: organization2.id,
1488
+ });
1489
+ // #endregion
1490
+
1491
+ // #region act and assert
1492
+ await expect(async () => await post(body, organization, token))
1493
+ .rejects
1494
+ .toThrow(new RegExp('customer.company is required'));
1495
+ // #endregion
1496
+ });
1497
+
1498
+ test('Should fail if company does not exist on organization', async () => {
1499
+ // #region arrange
1500
+ const { organization, group, groupPrice, token, member, user } = await initData();
1501
+ group.settings.allowRegistrationsByOrganization = true;
1502
+ await group.save();
1503
+
1504
+ const { organization: organization2 } = await initData();
1505
+
1506
+ user.permissions = UserPermissions.create({
1507
+ organizationPermissions: new Map([
1508
+ [organization2.id, Permissions.create({
1509
+ level: PermissionLevel.Full,
1510
+ })],
1511
+ ]),
1512
+ });
1513
+
1514
+ await user.save();
1515
+
1516
+ const body = IDRegisterCheckout.create({
1517
+ cart: IDRegisterCart.create({
1518
+ items: [
1519
+ IDRegisterItem.create({
1520
+ id: uuidv4(),
1521
+ replaceRegistrationIds: [],
1522
+ options: [],
1523
+ groupPrice,
1524
+ organizationId: organization.id,
1525
+ groupId: group.id,
1526
+ memberId: member.id,
1527
+ }),
1528
+ ],
1529
+ balanceItems: [
1530
+ ],
1531
+ deleteRegistrationIds: [],
1532
+ }),
1533
+ administrationFee: 0,
1534
+ freeContribution: 0,
1535
+ paymentMethod: PaymentMethod.PointOfSale,
1536
+ totalPrice: 25,
1537
+ customer: PaymentCustomer.create({
1538
+ company: Company.create({
1539
+ name: 'test company',
1540
+ }),
1541
+ }),
1542
+ asOrganizationId: organization2.id,
1543
+ });
1544
+ // #endregion
1545
+
1546
+ // #region act and assert
1547
+ await expect(async () => await post(body, organization, token))
1548
+ .rejects
1549
+ .toThrow(new RegExp('Oeps, de facturatiegegevens die je probeerde te selecteren lijken niet meer te bestaan.'));
1550
+ // #endregion
1551
+ });
1552
+
1553
+ test('Should set paying organization id', async () => {
1554
+ // #region arrange
1555
+ const { organization, group, groupPrice, token, member, user } = await initData();
1556
+ group.settings.allowRegistrationsByOrganization = true;
1557
+ await group.save();
1558
+
1559
+ const { organization: organization2 } = await initData();
1560
+ const company = Company.create({
1561
+ name: 'test company',
1562
+ });
1563
+
1564
+ organization2.meta.companies.push(company);
1565
+ await organization2.save();
1566
+
1567
+ user.permissions = UserPermissions.create({
1568
+ organizationPermissions: new Map([
1569
+ [organization2.id, Permissions.create({
1570
+ level: PermissionLevel.Full,
1571
+ })],
1572
+ ]),
1573
+ });
1574
+
1575
+ await user.save();
1576
+
1577
+ const body = IDRegisterCheckout.create({
1578
+ cart: IDRegisterCart.create({
1579
+ items: [
1580
+ IDRegisterItem.create({
1581
+ id: uuidv4(),
1582
+ replaceRegistrationIds: [],
1583
+ options: [],
1584
+ groupPrice,
1585
+ organizationId: organization.id,
1586
+ groupId: group.id,
1587
+ memberId: member.id,
1588
+ }),
1589
+ ],
1590
+ balanceItems: [
1591
+ ],
1592
+ deleteRegistrationIds: [],
1593
+ }),
1594
+ administrationFee: 0,
1595
+ freeContribution: 0,
1596
+ paymentMethod: PaymentMethod.PointOfSale,
1597
+ totalPrice: 25,
1598
+ customer: PaymentCustomer.create({
1599
+ company,
1600
+ }),
1601
+ asOrganizationId: organization2.id,
1602
+ });
1603
+ // #endregion
1604
+
1605
+ // #region act and assert
1606
+ const response = await post(body, organization, token);
1607
+ expect(response.body.registrations.length).toBe(1);
1608
+ expect(response.body.registrations[0].payingOrganizationId).toEqual(organization2.id);
1609
+ // #endregion
1610
+ });
1611
+ });
1612
+
1613
+ describe('Replace registrations', () => {
1614
+ test('Should update registered members', async () => {
1615
+ // #region arrange
1616
+ const { organization, group: group1, groupPrice: groupPrice1, token, member } = await initData();
1617
+
1618
+ const registration = await new RegistrationFactory({
1619
+ member,
1620
+ group: group1,
1621
+ groupPrice: groupPrice1,
1622
+ })
1623
+ .create();
1624
+
1625
+ const group = await new GroupFactory({
1626
+ organization,
1627
+ price: 30,
1628
+ stock: 5,
1629
+ }).create();
1630
+
1631
+ const groupPrice = group.settings.prices[0];
1632
+
1633
+ const body = IDRegisterCheckout.create({
1634
+ cart: IDRegisterCart.create({
1635
+ items: [
1636
+ IDRegisterItem.create({
1637
+ id: uuidv4(),
1638
+ replaceRegistrationIds: [registration.id],
1639
+ options: [],
1640
+ groupPrice,
1641
+ organizationId: organization.id,
1642
+ groupId: group.id,
1643
+ memberId: member.id,
1644
+ }),
1645
+ ],
1646
+ balanceItems: [],
1647
+ deleteRegistrationIds: [],
1648
+ }),
1649
+ administrationFee: 0,
1650
+ freeContribution: 0,
1651
+ paymentMethod: PaymentMethod.PointOfSale,
1652
+ totalPrice: 5,
1653
+ asOrganizationId: organization.id,
1654
+ customer: null,
1655
+ });
1656
+ // #endregion
1657
+
1658
+ // #region act and assert
1659
+
1660
+ // update occupancy to be sure occupancy is 1
1661
+ await group1.updateOccupancy();
1662
+ expect(group1.settings.registeredMembers).toBe(1);
1663
+
1664
+ // send request and check occupancy
1665
+ const response = await post(body, organization, token);
1666
+
1667
+ expect(response.body).toBeDefined();
1668
+ expect(response.body.registrations.length).toBe(1);
1669
+
1670
+ const updatedGroup = await Group.getByID(group.id);
1671
+ expect(updatedGroup!.settings.registeredMembers).toBe(1);
1672
+ expect(updatedGroup!.settings.reservedMembers).toBe(0);
1673
+
1674
+ const updatedGroup1After = await Group.getByID(group1.id);
1675
+ // occupancy should go from 1 to 0 because the registration should be replaced
1676
+ expect(updatedGroup1After!.settings.registeredMembers).toBe(0);
1677
+ expect(updatedGroup1After!.settings.reservedMembers).toBe(0);
1678
+ // #endregion
1679
+ });
1680
+
1681
+ test('Should set paid as organization on new registration', async () => {
1682
+ // #region arrange
1683
+ const { organization, group: group1, groupPrice: groupPrice1, token, member, user } = await initData();
1684
+
1685
+ group1.settings.allowRegistrationsByOrganization = true;
1686
+ await group1.save();
1687
+ const organization2 = await initOrganization();
1688
+
1689
+ user.permissions = UserPermissions.create({
1690
+ organizationPermissions: new Map([
1691
+ [organization2.id, Permissions.create({
1692
+ level: PermissionLevel.Full,
1693
+ })],
1694
+ [organization.id, Permissions.create({
1695
+ level: PermissionLevel.Full,
1696
+ })],
1697
+ ]),
1698
+ });
1699
+
1700
+ await user.save();
1701
+
1702
+ const company = Company.create({
1703
+ name: 'test company',
1704
+ });
1705
+
1706
+ organization2.meta.companies.push(company);
1707
+ await organization2.save();
1708
+
1709
+ organization.meta.companies.push(company);
1710
+ await organization.save();
1711
+
1712
+ const body = IDRegisterCheckout.create({
1713
+ cart: IDRegisterCart.create({
1714
+ items: [
1715
+ IDRegisterItem.create({
1716
+ id: uuidv4(),
1717
+ replaceRegistrationIds: [],
1718
+ options: [],
1719
+ groupPrice: groupPrice1,
1720
+ organizationId: organization.id,
1721
+ groupId: group1.id,
1722
+ memberId: member.id,
1723
+ }),
1724
+ ],
1725
+ balanceItems: [],
1726
+ deleteRegistrationIds: [],
1727
+ }),
1728
+ administrationFee: 0,
1729
+ freeContribution: 0,
1730
+ paymentMethod: PaymentMethod.PointOfSale,
1731
+ totalPrice: 25,
1732
+ asOrganizationId: organization2.id,
1733
+ customer: PaymentCustomer.create({
1734
+ company,
1735
+ }),
1736
+ });
1737
+
1738
+ const response1 = await post(body, organization, token);
1739
+ const registration = response1.body.registrations[0];
1740
+ expect(registration).toBeDefined();
1741
+
1742
+ const group = await new GroupFactory({
1743
+ organization,
1744
+ price: 30,
1745
+ stock: 5,
1746
+ }).create();
1747
+
1748
+ const groupPrice = group.settings.prices[0];
1749
+
1750
+ const body2 = IDRegisterCheckout.create({
1751
+ cart: IDRegisterCart.create({
1752
+ items: [
1753
+ IDRegisterItem.create({
1754
+ id: uuidv4(),
1755
+ replaceRegistrationIds: [registration.id],
1756
+ options: [],
1757
+ groupPrice,
1758
+ organizationId: organization.id,
1759
+ groupId: group.id,
1760
+ memberId: member.id,
1761
+ }),
1762
+ ],
1763
+ balanceItems: [],
1764
+ deleteRegistrationIds: [],
1765
+ }),
1766
+ administrationFee: 0,
1767
+ freeContribution: 0,
1768
+ paymentMethod: PaymentMethod.PointOfSale,
1769
+ totalPrice: 30,
1770
+ asOrganizationId: organization.id,
1771
+ customer: PaymentCustomer.create({
1772
+ company,
1773
+ }),
1774
+ });
1775
+ // #endregion
1776
+
1777
+ // #region act and assert
1778
+ const response = await post(body2, organization, token);
1779
+
1780
+ expect(response.body).toBeDefined();
1781
+ expect(response.body.registrations.length).toBe(1);
1782
+
1783
+ // the payingOrganizationId should equal the id of the paying organization of the replaced registration
1784
+ expect(response.body.registrations[0].payingOrganizationId).toEqual(organization2.id);
1785
+ // #endregion
1786
+ });
1787
+
1788
+ test('Replace registration by registration of other member should fail', async () => {
1789
+ // #region arrange
1790
+ const { organization, group: group1, groupPrice: groupPrice1, token, member, otherMembers: [member2] } = await initData({ otherMemberAmount: 1 });
1791
+
1792
+ const registration = await new RegistrationFactory({
1793
+ member: member2,
1794
+ group: group1,
1795
+ groupPrice: groupPrice1,
1796
+ })
1797
+ .create();
1798
+
1799
+ const group = await new GroupFactory({
1800
+ organization,
1801
+ price: 30,
1802
+ stock: 5,
1803
+ }).create();
1804
+
1805
+ const groupPrice = group.settings.prices[0];
1806
+
1807
+ const body = IDRegisterCheckout.create({
1808
+ cart: IDRegisterCart.create({
1809
+ items: [
1810
+ IDRegisterItem.create({
1811
+ id: uuidv4(),
1812
+ replaceRegistrationIds: [registration.id],
1813
+ options: [],
1814
+ groupPrice,
1815
+ organizationId: organization.id,
1816
+ groupId: group.id,
1817
+ memberId: member.id,
1818
+ }),
1819
+ ],
1820
+ balanceItems: [],
1821
+ deleteRegistrationIds: [],
1822
+ }),
1823
+ administrationFee: 0,
1824
+ freeContribution: 0,
1825
+ paymentMethod: PaymentMethod.PointOfSale,
1826
+ totalPrice: 5,
1827
+ asOrganizationId: organization.id,
1828
+ customer: null,
1829
+ });
1830
+ // #endregion
1831
+
1832
+ // #region act and assert
1833
+ await expect(async () => await post(body, organization, token))
1834
+ .rejects
1835
+ .toThrow(new RegExp('Registration not found'));
1836
+ // #endregion
1837
+ });
1838
+
1839
+ test('Move registration should fail if admin of same organization', async () => {
1840
+ // #region arrange
1841
+ const { organization, group: group1, groupPrice: groupPrice1, token, member } = await initData();
1842
+
1843
+ const registration = await new RegistrationFactory({
1844
+ member,
1845
+ group: group1,
1846
+ groupPrice: groupPrice1,
1847
+ })
1848
+ .create();
1849
+
1850
+ const group = await new GroupFactory({
1851
+ organization,
1852
+ price: 30,
1853
+ stock: 5,
1854
+ }).create();
114
1855
 
115
- const updatedGroup = await Group.getByID(group1.id);
116
- expect(updatedGroup!.settings.registeredMembers).toBe(1);
117
- expect(updatedGroup!.settings.reservedMembers).toBe(0);
118
- });
1856
+ const groupPrice = group.settings.prices[0];
119
1857
 
120
- test('Should update reserved members', async () => {
121
- // #region arrange
122
1858
  const body = IDRegisterCheckout.create({
123
1859
  cart: IDRegisterCart.create({
124
1860
  items: [
125
1861
  IDRegisterItem.create({
126
1862
  id: uuidv4(),
127
- replaceRegistrationIds: [],
1863
+ replaceRegistrationIds: [registration.id],
128
1864
  options: [],
129
- groupPrice: groupPrice2,
1865
+ groupPrice,
130
1866
  organizationId: organization.id,
131
- groupId: group2.id,
1867
+ groupId: group.id,
132
1868
  memberId: member.id,
133
1869
  }),
134
1870
  ],
@@ -137,41 +1873,26 @@ describe('Endpoint.RegisterMembers', () => {
137
1873
  }),
138
1874
  administrationFee: 0,
139
1875
  freeContribution: 0,
140
- paymentMethod: PaymentMethod.Payconiq,
141
- redirectUrl: new URL('https://www.example.com'),
142
- cancelUrl: new URL('https://www.example.com'),
143
- totalPrice: 15,
1876
+ paymentMethod: PaymentMethod.PointOfSale,
1877
+ totalPrice: 5,
144
1878
  customer: null,
145
1879
  });
146
-
147
- nock('https://api.ext.payconiq.com')
148
- .post('/v3/payments')
149
- .reply(200, {
150
- paymentId: 'testPaymentId',
151
- _links: {
152
- checkout: {
153
- href: 'https://www.example.com',
154
- },
155
- },
156
- });
157
1880
  // #endregion
158
1881
 
159
- // act
160
- const response = await post(body);
1882
+ // #region act and assert
161
1883
 
162
- // assert
163
- expect(response.body).toBeDefined();
164
- expect(response.body.registrations.length).toBe(1);
1884
+ // send request and check occupancy
1885
+ await expect(async () => await post(body, organization, token)).rejects.toThrow('Not allowed to move registrations');
165
1886
 
166
- const updatedGroup = await Group.getByID(group2.id);
167
- expect(updatedGroup!.settings.registeredMembers).toBe(0);
168
- expect(updatedGroup!.settings.reservedMembers).toBe(1);
1887
+ // #endregion
169
1888
  });
170
1889
  });
171
1890
 
172
- describe('Register member with replace registration', () => {
1891
+ describe('Delete registrations', () => {
173
1892
  test('Should update registered members', async () => {
174
1893
  // #region arrange
1894
+ const { member, group: group1, groupPrice: groupPrice1, organization: organization1, token } = await initData();
1895
+
175
1896
  const registration = await new RegistrationFactory({
176
1897
  member,
177
1898
  group: group1,
@@ -180,7 +1901,7 @@ describe('Endpoint.RegisterMembers', () => {
180
1901
  .create();
181
1902
 
182
1903
  const group = await new GroupFactory({
183
- organization,
1904
+ organization: organization1,
184
1905
  price: 30,
185
1906
  stock: 5,
186
1907
  }).create();
@@ -192,22 +1913,22 @@ describe('Endpoint.RegisterMembers', () => {
192
1913
  items: [
193
1914
  IDRegisterItem.create({
194
1915
  id: uuidv4(),
195
- replaceRegistrationIds: [registration.id],
1916
+ replaceRegistrationIds: [],
196
1917
  options: [],
197
1918
  groupPrice,
198
- organizationId: organization.id,
1919
+ organizationId: organization1.id,
199
1920
  groupId: group.id,
200
1921
  memberId: member.id,
201
1922
  }),
202
1923
  ],
203
1924
  balanceItems: [],
204
- deleteRegistrationIds: [],
1925
+ deleteRegistrationIds: [registration.id],
205
1926
  }),
206
1927
  administrationFee: 0,
207
1928
  freeContribution: 0,
208
1929
  paymentMethod: PaymentMethod.PointOfSale,
209
1930
  totalPrice: 5,
210
- asOrganizationId: organization.id,
1931
+ asOrganizationId: organization1.id,
211
1932
  customer: null,
212
1933
  });
213
1934
  // #endregion
@@ -219,7 +1940,7 @@ describe('Endpoint.RegisterMembers', () => {
219
1940
  expect(group1.settings.registeredMembers).toBe(1);
220
1941
 
221
1942
  // send request and check occupancy
222
- const response = await post(body);
1943
+ const response = await post(body, organization1, token);
223
1944
 
224
1945
  expect(response.body).toBeDefined();
225
1946
  expect(response.body.registrations.length).toBe(1);
@@ -229,7 +1950,7 @@ describe('Endpoint.RegisterMembers', () => {
229
1950
  expect(updatedGroup!.settings.reservedMembers).toBe(0);
230
1951
 
231
1952
  const updatedGroup1After = await Group.getByID(group1.id);
232
- // occupancy should go from 1 to 0 because the registration should be replaced
1953
+ // occupancy should go from 1 to 0 because the registration should be deleted
233
1954
  expect(updatedGroup1After!.settings.registeredMembers).toBe(0);
234
1955
  expect(updatedGroup1After!.settings.reservedMembers).toBe(0);
235
1956
  // #endregion
@@ -237,6 +1958,8 @@ describe('Endpoint.RegisterMembers', () => {
237
1958
 
238
1959
  test('Should throw error if with payment', async () => {
239
1960
  // #region arrange
1961
+ const { member, group: group1, groupPrice: groupPrice1, organization: organization1, token } = await initData();
1962
+
240
1963
  const registration = await new RegistrationFactory({
241
1964
  member,
242
1965
  group: group1,
@@ -245,7 +1968,7 @@ describe('Endpoint.RegisterMembers', () => {
245
1968
  .create();
246
1969
 
247
1970
  const group = await new GroupFactory({
248
- organization,
1971
+ organization: organization1,
249
1972
  price: 30,
250
1973
  stock: 5,
251
1974
  maxMembers: 1,
@@ -258,16 +1981,16 @@ describe('Endpoint.RegisterMembers', () => {
258
1981
  items: [
259
1982
  IDRegisterItem.create({
260
1983
  id: uuidv4(),
261
- replaceRegistrationIds: [registration.id],
1984
+ replaceRegistrationIds: [],
262
1985
  options: [],
263
1986
  groupPrice,
264
- organizationId: organization.id,
1987
+ organizationId: organization1.id,
265
1988
  groupId: group.id,
266
1989
  memberId: member.id,
267
1990
  }),
268
1991
  ],
269
1992
  balanceItems: [],
270
- deleteRegistrationIds: [],
1993
+ deleteRegistrationIds: [registration.id],
271
1994
  }),
272
1995
  administrationFee: 0,
273
1996
  freeContribution: 0,
@@ -285,20 +2008,19 @@ describe('Endpoint.RegisterMembers', () => {
285
2008
  await group1.updateOccupancy();
286
2009
  expect(group1.settings.registeredMembers).toBe(1);
287
2010
 
288
- await expect(async () => await post(body)).rejects.toThrow('Not allowed to move registrations');
2011
+ await expect(async () => await post(body, organization1, token)).rejects.toThrow('Permission denied: you are not allowed to delete registrations');
289
2012
  // #endregion
290
2013
  });
291
- });
292
2014
 
293
- describe('Register member with delete registration', () => {
294
- test('Should update registered members', async () => {
2015
+ test('Should deactivate registration', async () => {
295
2016
  // #region arrange
2017
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
2018
+
296
2019
  const registration = await new RegistrationFactory({
297
2020
  member,
298
2021
  group: group1,
299
2022
  groupPrice: groupPrice1,
300
- })
301
- .create();
2023
+ }).create();
302
2024
 
303
2025
  const group = await new GroupFactory({
304
2026
  organization,
@@ -334,45 +2056,107 @@ describe('Endpoint.RegisterMembers', () => {
334
2056
  // #endregion
335
2057
 
336
2058
  // #region act and assert
2059
+ await post(body, organization, token);
337
2060
 
338
- // update occupancy to be sure occupancy is 1
339
- await group1.updateOccupancy();
340
- expect(group1.settings.registeredMembers).toBe(1);
2061
+ const updatedRegistration = await Registration.getByID(registration.id);
2062
+ expect(updatedRegistration).toBeDefined();
2063
+ expect(updatedRegistration!.deactivatedAt).not.toBe(null);
2064
+ // #endregion
2065
+ });
341
2066
 
342
- // send request and check occupancy
343
- const response = await post(body);
2067
+ test('Should fail if invalid cancelation fee', async () => {
2068
+ for (const cancellationFeePercentage of [10001, -1]) {
2069
+ // #region arrange
2070
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
344
2071
 
345
- expect(response.body).toBeDefined();
346
- expect(response.body.registrations.length).toBe(1);
2072
+ const body1 = IDRegisterCheckout.create({
2073
+ cart: IDRegisterCart.create({
2074
+ items: [
2075
+ IDRegisterItem.create({
2076
+ id: uuidv4(),
2077
+ replaceRegistrationIds: [],
2078
+ options: [],
2079
+ groupPrice: groupPrice1,
2080
+ organizationId: organization.id,
2081
+ groupId: group1.id,
2082
+ memberId: member.id,
2083
+ }),
2084
+ ],
2085
+ balanceItems: [],
2086
+ deleteRegistrationIds: [],
2087
+ }),
2088
+ administrationFee: 0,
2089
+ freeContribution: 0,
2090
+ paymentMethod: PaymentMethod.PointOfSale,
2091
+ totalPrice: 25,
2092
+ asOrganizationId: organization.id,
2093
+ customer: null,
2094
+ });
347
2095
 
348
- const updatedGroup = await Group.getByID(group.id);
349
- expect(updatedGroup!.settings.registeredMembers).toBe(1);
350
- expect(updatedGroup!.settings.reservedMembers).toBe(0);
2096
+ const group2 = await new GroupFactory({
2097
+ organization,
2098
+ price: 30,
2099
+ stock: 5,
2100
+ }).create();
351
2101
 
352
- const updatedGroup1After = await Group.getByID(group1.id);
353
- // occupancy should go from 1 to 0 because the registration should be deleted
354
- expect(updatedGroup1After!.settings.registeredMembers).toBe(0);
355
- expect(updatedGroup1After!.settings.reservedMembers).toBe(0);
2102
+ const groupPrice2 = group2.settings.prices[0];
2103
+
2104
+ const firstResponse = await post(body1, organization, token);
2105
+ expect(firstResponse).toBeDefined();
2106
+ expect(firstResponse.body.registrations.length).toBe(1);
2107
+ const registration = firstResponse.body.registrations[0];
2108
+
2109
+ const body2 = IDRegisterCheckout.create({
2110
+ cart: IDRegisterCart.create({
2111
+ items: [
2112
+ IDRegisterItem.create({
2113
+ id: uuidv4(),
2114
+ replaceRegistrationIds: [],
2115
+ options: [],
2116
+ groupPrice: groupPrice2,
2117
+ organizationId: organization.id,
2118
+ groupId: group2.id,
2119
+ memberId: member.id,
2120
+ }),
2121
+ ],
2122
+ balanceItems: [],
2123
+ deleteRegistrationIds: [registration.id],
2124
+ }),
2125
+ cancellationFeePercentage,
2126
+ administrationFee: 0,
2127
+ freeContribution: 0,
2128
+ paymentMethod: PaymentMethod.PointOfSale,
2129
+ totalPrice: 30,
2130
+ asOrganizationId: organization.id,
2131
+ customer: null,
2132
+ });
2133
+ // #endregion
2134
+
2135
+ // #region act and assert
2136
+ await expect(async () => await post(body2, organization, token))
2137
+ .rejects
2138
+ .toThrow(new RegExp('Invalid cancellation fee percentage.'));
356
2139
  // #endregion
2140
+ }
357
2141
  });
358
2142
 
359
- test('Should throw error if with payment', async () => {
2143
+ test('Delete by member should fail if no permission to delete registration', async () => {
360
2144
  // #region arrange
2145
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
2146
+
361
2147
  const registration = await new RegistrationFactory({
362
2148
  member,
363
2149
  group: group1,
364
2150
  groupPrice: groupPrice1,
365
- })
366
- .create();
2151
+ }).create();
367
2152
 
368
- const group = await new GroupFactory({
2153
+ const group2 = await new GroupFactory({
369
2154
  organization,
370
2155
  price: 30,
371
2156
  stock: 5,
372
- maxMembers: 1,
373
2157
  }).create();
374
2158
 
375
- const groupPrice = group.settings.prices[0];
2159
+ const groupPrice = group2.settings.prices[0];
376
2160
 
377
2161
  const body = IDRegisterCheckout.create({
378
2162
  cart: IDRegisterCart.create({
@@ -383,7 +2167,7 @@ describe('Endpoint.RegisterMembers', () => {
383
2167
  options: [],
384
2168
  groupPrice,
385
2169
  organizationId: organization.id,
386
- groupId: group.id,
2170
+ groupId: group2.id,
387
2171
  memberId: member.id,
388
2172
  }),
389
2173
  ],
@@ -392,59 +2176,195 @@ describe('Endpoint.RegisterMembers', () => {
392
2176
  }),
393
2177
  administrationFee: 0,
394
2178
  freeContribution: 0,
395
- paymentMethod: PaymentMethod.Payconiq,
396
- redirectUrl: new URL('https://www.example.com'),
397
- cancelUrl: new URL('https://www.example.com'),
2179
+ paymentMethod: PaymentMethod.PointOfSale,
398
2180
  totalPrice: 5,
399
2181
  customer: null,
400
2182
  });
401
2183
  // #endregion
402
2184
 
403
2185
  // #region act and assert
2186
+ await expect(async () => await post(body, organization, token))
2187
+ .rejects
2188
+ .toThrow(new RegExp('Permission denied: you are not allowed to delete registrations'));
2189
+ // #endregion
2190
+ });
404
2191
 
405
- // update occupancy to be sure occupancy is 1
406
- await group1.updateOccupancy();
407
- expect(group1.settings.registeredMembers).toBe(1);
2192
+ test('Delete by organization should fail if no permission to delete registration', async () => {
2193
+ // #region arrange
2194
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData({ permissionLevel: PermissionLevel.Read });
2195
+
2196
+ const registration = await new RegistrationFactory({
2197
+ member,
2198
+ group: group1,
2199
+ groupPrice: groupPrice1,
2200
+ }).create();
2201
+
2202
+ const group2 = await new GroupFactory({
2203
+ organization,
2204
+ price: 30,
2205
+ stock: 5,
2206
+ }).create();
2207
+
2208
+ const groupPrice = group2.settings.prices[0];
2209
+
2210
+ const body = IDRegisterCheckout.create({
2211
+ cart: IDRegisterCart.create({
2212
+ items: [
2213
+ IDRegisterItem.create({
2214
+ id: uuidv4(),
2215
+ replaceRegistrationIds: [],
2216
+ options: [],
2217
+ groupPrice,
2218
+ organizationId: organization.id,
2219
+ groupId: group2.id,
2220
+ memberId: member.id,
2221
+ }),
2222
+ ],
2223
+ balanceItems: [],
2224
+ deleteRegistrationIds: [registration.id],
2225
+ }),
2226
+ administrationFee: 0,
2227
+ freeContribution: 0,
2228
+ paymentMethod: PaymentMethod.PointOfSale,
2229
+ totalPrice: 5,
2230
+ customer: null,
2231
+ asOrganizationId: organization.id,
2232
+ });
2233
+ // #endregion
408
2234
 
409
- await expect(async () => await post(body)).rejects.toThrow('Permission denied: you are not allowed to delete registrations');
2235
+ // #region act and assert
2236
+ await expect(async () => await post(body, organization, token)).rejects.toThrow(new RegExp('Je hebt geen toegangsrechten om deze inschrijving te verwijderen'));
410
2237
  // #endregion
411
2238
  });
412
- });
413
2239
 
414
- it('Register member that is already registered should throw error', async () => {
415
- // create existing registration
416
- await new RegistrationFactory({
417
- member,
418
- group: group1,
419
- groupPrice: groupPrice1,
420
- })
421
- .create();
2240
+ test('Should fail if registration does not exist anymore', async () => {
2241
+ // #region arrange
2242
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
422
2243
 
423
- // register again
424
- const body = IDRegisterCheckout.create({
425
- cart: IDRegisterCart.create({
426
- items: [
427
- IDRegisterItem.create({
428
- id: uuidv4(),
429
- replaceRegistrationIds: [],
430
- options: [],
431
- groupPrice: groupPrice1,
432
- organizationId: organization.id,
433
- groupId: group1.id,
434
- memberId: member.id,
435
- }),
436
- ],
437
- balanceItems: [],
438
- deleteRegistrationIds: [],
439
- }),
440
- administrationFee: 0,
441
- freeContribution: 0,
442
- paymentMethod: PaymentMethod.PointOfSale,
443
- totalPrice: groupPrice1.price.price,
444
- asOrganizationId: organization.id,
445
- customer: null,
2244
+ const registration = await new RegistrationFactory({
2245
+ member,
2246
+ group: group1,
2247
+ groupPrice: groupPrice1,
2248
+ }).create();
2249
+
2250
+ const group2 = await new GroupFactory({
2251
+ organization,
2252
+ price: 30,
2253
+ stock: 5,
2254
+ }).create();
2255
+
2256
+ const groupPrice = group2.settings.prices[0];
2257
+
2258
+ const body = IDRegisterCheckout.create({
2259
+ cart: IDRegisterCart.create({
2260
+ items: [
2261
+ IDRegisterItem.create({
2262
+ id: uuidv4(),
2263
+ replaceRegistrationIds: [],
2264
+ options: [],
2265
+ groupPrice,
2266
+ organizationId: organization.id,
2267
+ groupId: group2.id,
2268
+ memberId: member.id,
2269
+ }),
2270
+ ],
2271
+ balanceItems: [],
2272
+ deleteRegistrationIds: [registration.id],
2273
+ }),
2274
+ administrationFee: 0,
2275
+ freeContribution: 0,
2276
+ paymentMethod: PaymentMethod.PointOfSale,
2277
+ totalPrice: 5,
2278
+ customer: null,
2279
+ asOrganizationId: organization.id,
2280
+ });
2281
+ // #endregion
2282
+
2283
+ // #region act and assert
2284
+ await registration.delete();
2285
+ await expect(async () => await post(body, organization, token)).rejects.toThrow(new RegExp('Registration not found'));
2286
+ // #endregion
446
2287
  });
447
2288
 
448
- await expect(async () => await post(body)).rejects.toThrow('Already registered');
2289
+ test('Should fail if already deleted', async () => {
2290
+ // #region arrange
2291
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
2292
+
2293
+ const registration = await new RegistrationFactory({
2294
+ member,
2295
+ group: group1,
2296
+ groupPrice: groupPrice1,
2297
+ }).create();
2298
+
2299
+ const group2 = await new GroupFactory({
2300
+ organization,
2301
+ price: 30,
2302
+ stock: 5,
2303
+ }).create();
2304
+
2305
+ const groupPrice2 = group2.settings.prices[0];
2306
+
2307
+ const group3 = await new GroupFactory({
2308
+ organization,
2309
+ price: 30,
2310
+ stock: 5,
2311
+ }).create();
2312
+
2313
+ const groupPrice3 = group3.settings.prices[0];
2314
+
2315
+ const body1 = IDRegisterCheckout.create({
2316
+ cart: IDRegisterCart.create({
2317
+ items: [
2318
+ IDRegisterItem.create({
2319
+ id: uuidv4(),
2320
+ replaceRegistrationIds: [],
2321
+ options: [],
2322
+ groupPrice: groupPrice2,
2323
+ organizationId: organization.id,
2324
+ groupId: group2.id,
2325
+ memberId: member.id,
2326
+ }),
2327
+ ],
2328
+ balanceItems: [],
2329
+ deleteRegistrationIds: [registration.id],
2330
+ }),
2331
+ administrationFee: 0,
2332
+ freeContribution: 0,
2333
+ paymentMethod: PaymentMethod.PointOfSale,
2334
+ totalPrice: 5,
2335
+ customer: null,
2336
+ asOrganizationId: organization.id,
2337
+ });
2338
+
2339
+ const body2 = IDRegisterCheckout.create({
2340
+ cart: IDRegisterCart.create({
2341
+ items: [
2342
+ IDRegisterItem.create({
2343
+ id: uuidv4(),
2344
+ replaceRegistrationIds: [],
2345
+ options: [],
2346
+ groupPrice: groupPrice3,
2347
+ organizationId: organization.id,
2348
+ groupId: group3.id,
2349
+ memberId: member.id,
2350
+ }),
2351
+ ],
2352
+ balanceItems: [],
2353
+ deleteRegistrationIds: [registration.id],
2354
+ }),
2355
+ administrationFee: 0,
2356
+ freeContribution: 0,
2357
+ paymentMethod: PaymentMethod.PointOfSale,
2358
+ totalPrice: 5,
2359
+ customer: null,
2360
+ asOrganizationId: organization.id,
2361
+ });
2362
+ // #endregion
2363
+
2364
+ // #region act and assert
2365
+ await post(body1, organization, token);
2366
+ await expect(async () => await post(body2, organization, token)).rejects.toThrow(new RegExp('Oeps, één of meerdere inschrijvingen die je probeert te verwijderen was al verwijderd. Herlaad de pagina en probeer opnieuw'));
2367
+ // #endregion
2368
+ });
449
2369
  });
450
2370
  });