@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
@@ -0,0 +1,1197 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import { BalanceItemFactory, GroupFactory, MemberFactory, MemberWithRegistrations, Organization, OrganizationFactory, OrganizationRegistrationPeriod, Platform, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
3
+ import { AdministrationFeeSettings, BalanceItemCartItem, BalanceItemStatus, BalanceItemType, BooleanStatus, DefaultAgeGroup, FreeContributionSettings, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, PaymentMethod, PermissionLevel, Permissions, PlatformMembershipType, PlatformMembershipTypeConfig, ReceivableBalanceType, ReduceablePrice, RegisterItemOption, Version } from '@stamhoofd/structures';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { GetMemberFamilyEndpoint } from '../../src/endpoints/global/members/GetMemberFamilyEndpoint';
6
+ import { RegisterMembersEndpoint } from '../../src/endpoints/global/registration/RegisterMembersEndpoint';
7
+ import { GetMemberBalanceEndpoint } from '../../src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint';
8
+ import { GetReceivableBalanceEndpoint } from '../../src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint';
9
+ import { testServer } from '../helpers/TestServer';
10
+
11
+ describe('E2E.Register', () => {
12
+ // #region global
13
+ const registerEndpoint = new RegisterMembersEndpoint();
14
+ const memberBalanceEndpoint = new GetMemberBalanceEndpoint();
15
+ const receivableBalancesEndpoint = new GetReceivableBalanceEndpoint();
16
+ const getMemberFamilyEndpoint = new GetMemberFamilyEndpoint();
17
+
18
+ let period: RegistrationPeriod;
19
+
20
+ // #region helpers
21
+ const register = async (body: IDRegisterCheckout, organization: Organization, token: Token) => {
22
+ const request = Request.buildJson('POST', `/v${Version}/members/register`, organization.getApiHost(), body);
23
+ request.headers.authorization = 'Bearer ' + token.accessToken;
24
+ return await testServer.test(registerEndpoint, request);
25
+ };
26
+
27
+ const getBalance = async (memberId: string, organization: Organization, token: Token) => {
28
+ const request = Request.buildJson('GET', `/v${Version}/organization/members/${memberId}/balance`, organization.getApiHost());
29
+ request.headers.authorization = 'Bearer ' + token.accessToken;
30
+ return await testServer.test(memberBalanceEndpoint, request);
31
+ };
32
+
33
+ const getReceivableBalance = async (type: ReceivableBalanceType, id: string, organization: Organization, token: Token) => {
34
+ const request = Request.buildJson('GET', `/v${Version}/receivable-balances/${type}/${id}`, organization.getApiHost());
35
+ request.headers.authorization = 'Bearer ' + token.accessToken;
36
+ return await testServer.test(receivableBalancesEndpoint, request);
37
+ };
38
+
39
+ const getMemberFamily = async (memberId: string, organization: Organization, token: Token) => {
40
+ const request = Request.buildJson('GET', `/v${Version}/organization/members/${memberId}/family`, organization.getApiHost());
41
+ request.headers.authorization = 'Bearer ' + token.accessToken;
42
+ return await testServer.test(getMemberFamilyEndpoint, request);
43
+ };
44
+ // #endregion
45
+
46
+ // #endregion
47
+
48
+ beforeAll(async () => {
49
+ const previousPeriod = await new RegistrationPeriodFactory({
50
+ startDate: new Date(2022, 0, 1),
51
+ endDate: new Date(2022, 11, 31),
52
+ }).create();
53
+
54
+ period = await new RegistrationPeriodFactory({
55
+ startDate: new Date(2023, 0, 1),
56
+ endDate: new Date(2023, 11, 31),
57
+ }).create();
58
+
59
+ period.previousPeriodId = previousPeriod.id;
60
+ await period.save();
61
+ });
62
+
63
+ const initData = async ({ otherMemberAmount = 0, permissionLevel = PermissionLevel.Full }: { otherMemberAmount?: number; permissionLevel?: PermissionLevel } = {}) => {
64
+ const organization = await new OrganizationFactory({ period })
65
+ .create();
66
+
67
+ const user = await new UserFactory({
68
+ organization,
69
+ permissions: Permissions.create({
70
+ level: permissionLevel,
71
+ }),
72
+ })
73
+ .create();
74
+
75
+ const token = await Token.createToken(user);
76
+
77
+ const member = await new MemberFactory({ organization, user })
78
+ .create();
79
+
80
+ const otherMembers: MemberWithRegistrations[] = [];
81
+
82
+ for (let i = 0; i < otherMemberAmount; i++) {
83
+ otherMembers.push(await new MemberFactory({ organization, user })
84
+ .create());
85
+ }
86
+
87
+ const group = await new GroupFactory({
88
+ organization,
89
+ price: 25,
90
+ reducedPrice: 21,
91
+ stock: 5,
92
+ })
93
+ .create();
94
+
95
+ const groupPrice = group.settings.prices[0];
96
+
97
+ return {
98
+ organization,
99
+ user,
100
+ token,
101
+ member,
102
+ otherMembers,
103
+ group,
104
+ groupPrice,
105
+ };
106
+ };
107
+
108
+ beforeEach(async () => {
109
+ });
110
+
111
+ describe('Register', () => {
112
+ test('Register by member should create balance for member', async () => {
113
+ // #region arrange
114
+ const { organization, group, groupPrice, token, member } = await initData();
115
+
116
+ const body = IDRegisterCheckout.create({
117
+ cart: IDRegisterCart.create({
118
+ items: [
119
+ IDRegisterItem.create({
120
+ id: uuidv4(),
121
+ replaceRegistrationIds: [],
122
+ options: [],
123
+ groupPrice,
124
+ organizationId: organization.id,
125
+ groupId: group.id,
126
+ memberId: member.id,
127
+ }),
128
+ ],
129
+ balanceItems: [
130
+ ],
131
+ deleteRegistrationIds: [],
132
+ }),
133
+ administrationFee: 0,
134
+ freeContribution: 0,
135
+ paymentMethod: PaymentMethod.PointOfSale,
136
+ totalPrice: 25,
137
+ customer: null,
138
+ });
139
+ // #endregion
140
+
141
+ // #region act and assert
142
+ const balanceBefore = await getBalance(member.id, organization, token);
143
+ expect(balanceBefore).toBeDefined();
144
+ expect(balanceBefore.body.length).toBe(0);
145
+
146
+ await register(body, organization, token);
147
+
148
+ const balance = await getBalance(member.id, organization, token);
149
+ expect(balance).toBeDefined();
150
+ expect(balance.body.length).toBe(1);
151
+ expect(balance.body[0].price).toBe(25);
152
+ expect(balance.body[0].pricePaid).toBe(0);
153
+ // #endregion
154
+ });
155
+
156
+ test('Should create balance items for options', async () => {
157
+ // #region arrange
158
+ const { organization, group, groupPrice, token, member } = await initData();
159
+
160
+ const option1 = GroupOption.create({
161
+ name: 'option 1',
162
+ price: ReduceablePrice.create({
163
+ price: 5,
164
+ reducedPrice: 3,
165
+ }),
166
+ });
167
+
168
+ const option2 = GroupOption.create({
169
+ name: 'option 2',
170
+ price: ReduceablePrice.create({
171
+ price: 3,
172
+ reducedPrice: 1,
173
+ }),
174
+ });
175
+
176
+ const optionMenu = GroupOptionMenu.create({
177
+ name: 'option menu 1',
178
+ multipleChoice: true,
179
+ options: [option1, option2],
180
+ });
181
+
182
+ group.settings.optionMenus = [
183
+ optionMenu,
184
+ ];
185
+
186
+ await group.save();
187
+
188
+ const body = IDRegisterCheckout.create({
189
+ cart: IDRegisterCart.create({
190
+ items: [
191
+ IDRegisterItem.create({
192
+ id: uuidv4(),
193
+ replaceRegistrationIds: [],
194
+ options: [
195
+ RegisterItemOption.create({
196
+ option: option1,
197
+ amount: 2,
198
+ optionMenu,
199
+ }),
200
+ RegisterItemOption.create({
201
+ option: option2,
202
+ amount: 5,
203
+ optionMenu,
204
+ }),
205
+ ],
206
+ groupPrice,
207
+ organizationId: organization.id,
208
+ groupId: group.id,
209
+ memberId: member.id,
210
+ }),
211
+ ],
212
+ balanceItems: [
213
+ ],
214
+ deleteRegistrationIds: [],
215
+ }),
216
+ administrationFee: 0,
217
+ freeContribution: 0,
218
+ paymentMethod: PaymentMethod.PointOfSale,
219
+ totalPrice: 50,
220
+ customer: null,
221
+ });
222
+ // #endregion
223
+
224
+ // #region act and assert
225
+ const balanceBefore = await getBalance(member.id, organization, token);
226
+ expect(balanceBefore).toBeDefined();
227
+ expect(balanceBefore.body.length).toBe(0);
228
+
229
+ await register(body, organization, token);
230
+
231
+ const balance = await getBalance(member.id, organization, token);
232
+ expect(balance).toBeDefined();
233
+ expect(balance.body.length).toBe(3);
234
+ expect(balance.body).toEqual(expect.arrayContaining([
235
+ expect.objectContaining({
236
+ price: 25,
237
+ pricePaid: 0,
238
+ }),
239
+ expect.objectContaining({
240
+ price: 15,
241
+ pricePaid: 0,
242
+ }),
243
+ expect.objectContaining({
244
+ price: 10,
245
+ pricePaid: 0,
246
+ }),
247
+ ]));
248
+ // #endregion
249
+ });
250
+
251
+ test('Should reset free contribution if no options on organization', async () => {
252
+ // #region arrange
253
+ const { organization, group, groupPrice, token, member, user } = await initData();
254
+
255
+ const body = IDRegisterCheckout.create({
256
+ cart: IDRegisterCart.create({
257
+ items: [
258
+ IDRegisterItem.create({
259
+ id: uuidv4(),
260
+ replaceRegistrationIds: [],
261
+ options: [],
262
+ groupPrice,
263
+ organizationId: organization.id,
264
+ groupId: group.id,
265
+ memberId: member.id,
266
+ }),
267
+ ],
268
+ balanceItems: [
269
+ ],
270
+ deleteRegistrationIds: [],
271
+ }),
272
+ administrationFee: 0,
273
+ freeContribution: 31,
274
+ paymentMethod: PaymentMethod.PointOfSale,
275
+ totalPrice: 25,
276
+ customer: null,
277
+ });
278
+ // #endregion
279
+
280
+ // #region act and assert
281
+ const receivableBalanceBefore = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
282
+ expect(receivableBalanceBefore).toBeDefined();
283
+ expect(receivableBalanceBefore.body.balanceItems.length).toBe(0);
284
+ expect(receivableBalanceBefore.body.amountOpen).toBe(0);
285
+
286
+ await register(body, organization, token);
287
+
288
+ const receivableBalanceAfter = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
289
+ expect(receivableBalanceAfter).toBeDefined();
290
+ expect(receivableBalanceAfter.body.balanceItems.length).toBe(1);
291
+ expect(receivableBalanceAfter.body.amountOpen).toBe(0);
292
+ expect(receivableBalanceAfter.body.balanceItems).toEqual(expect.arrayContaining([
293
+ expect.objectContaining({
294
+ price: 25,
295
+ pricePaid: 0,
296
+ }),
297
+ ]));
298
+ // #endregion
299
+ });
300
+
301
+ test('Should create balance item for free contribution', async () => {
302
+ // #region arrange
303
+ const { organization, group, groupPrice, token, member, user } = await initData();
304
+
305
+ organization.meta.recordsConfiguration.freeContribution = FreeContributionSettings.create({
306
+ description: 'free contribution settings',
307
+ amounts: [30, 20],
308
+ });
309
+
310
+ await organization.save();
311
+
312
+ const body = IDRegisterCheckout.create({
313
+ cart: IDRegisterCart.create({
314
+ items: [
315
+ IDRegisterItem.create({
316
+ id: uuidv4(),
317
+ replaceRegistrationIds: [],
318
+ options: [],
319
+ groupPrice,
320
+ organizationId: organization.id,
321
+ groupId: group.id,
322
+ memberId: member.id,
323
+ }),
324
+ ],
325
+ balanceItems: [
326
+ ],
327
+ deleteRegistrationIds: [],
328
+ }),
329
+ administrationFee: 0,
330
+ freeContribution: 30,
331
+ paymentMethod: PaymentMethod.PointOfSale,
332
+ totalPrice: 55,
333
+ customer: null,
334
+ });
335
+ // #endregion
336
+
337
+ // #region act and assert
338
+ const receivableBalanceBefore = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
339
+ expect(receivableBalanceBefore).toBeDefined();
340
+ expect(receivableBalanceBefore.body.balanceItems.length).toBe(0);
341
+ expect(receivableBalanceBefore.body.amountOpen).toBe(0);
342
+
343
+ await register(body, organization, token);
344
+
345
+ const receivableBalanceAfter = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
346
+ expect(receivableBalanceAfter).toBeDefined();
347
+ expect(receivableBalanceAfter.body.balanceItems.length).toBe(2);
348
+ expect(receivableBalanceAfter.body.amountPending).toBe(55);
349
+
350
+ expect(receivableBalanceAfter.body.balanceItems).toEqual(expect.arrayContaining([
351
+ expect.objectContaining({
352
+ price: 30,
353
+ pricePaid: 0,
354
+ type: BalanceItemType.FreeContribution,
355
+ }),
356
+ expect.objectContaining({
357
+ price: 25,
358
+ pricePaid: 0,
359
+ }),
360
+ ]));
361
+ // #endregion
362
+ });
363
+
364
+ test('Should create balance item for free administration fee if register by member', async () => {
365
+ // #region arrange
366
+ const { organization, group, groupPrice, token, member, user } = await initData();
367
+
368
+ organization.meta.registrationPaymentConfiguration.administrationFee = AdministrationFeeSettings.create({
369
+ fixed: 33,
370
+ });
371
+
372
+ await organization.save();
373
+
374
+ const body = IDRegisterCheckout.create({
375
+ cart: IDRegisterCart.create({
376
+ items: [
377
+ IDRegisterItem.create({
378
+ id: uuidv4(),
379
+ replaceRegistrationIds: [],
380
+ options: [],
381
+ groupPrice,
382
+ organizationId: organization.id,
383
+ groupId: group.id,
384
+ memberId: member.id,
385
+ }),
386
+ ],
387
+ balanceItems: [
388
+ ],
389
+ deleteRegistrationIds: [],
390
+ }),
391
+ administrationFee: 33,
392
+ freeContribution: 0,
393
+ paymentMethod: PaymentMethod.PointOfSale,
394
+ totalPrice: 58,
395
+ });
396
+ // #endregion
397
+
398
+ // #region act and assert
399
+ const receivableBalanceBefore = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization
400
+ , token);
401
+ expect(receivableBalanceBefore).toBeDefined();
402
+ expect(receivableBalanceBefore.body.balanceItems.length).toBe(0);
403
+ expect(receivableBalanceBefore.body.amountOpen).toBe(0);
404
+
405
+ await register(body, organization, token);
406
+
407
+ const receivableBalanceAfter = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
408
+ expect(receivableBalanceAfter).toBeDefined();
409
+ expect(receivableBalanceAfter.body.balanceItems.length).toBe(2);
410
+ expect(receivableBalanceAfter.body.amountPending).toBe(58);
411
+
412
+ expect(receivableBalanceAfter.body.balanceItems).toEqual(expect.arrayContaining([
413
+ expect.objectContaining({
414
+ price: 25,
415
+ pricePaid: 0,
416
+ type: BalanceItemType.Registration,
417
+ }),
418
+ expect.objectContaining({
419
+ price: 33,
420
+ pricePaid: 0,
421
+ type: BalanceItemType.AdministrationFee,
422
+ }),
423
+ ]));
424
+ // #endregion
425
+ });
426
+
427
+ test('Should create balance item for cart item', async () => {
428
+ // #region arrange
429
+ const { organization, group, groupPrice, token, member, user } = await initData();
430
+
431
+ const balanceItem1 = await new BalanceItemFactory({
432
+ organizationId: organization.id,
433
+ memberId: member.id,
434
+ userId: user.id,
435
+ payingOrganizationId: organization.id,
436
+ type: BalanceItemType.Registration,
437
+ amount: 10,
438
+ unitPrice: 2,
439
+ }).create();
440
+
441
+ const cartItem = BalanceItemCartItem.create({
442
+ item: balanceItem1.getStructure(),
443
+ price: 10,
444
+ });
445
+
446
+ const body = IDRegisterCheckout.create({
447
+ cart: IDRegisterCart.create({
448
+ items: [
449
+ IDRegisterItem.create({
450
+ id: uuidv4(),
451
+ replaceRegistrationIds: [],
452
+ options: [],
453
+ groupPrice,
454
+ organizationId: organization.id,
455
+ groupId: group.id,
456
+ memberId: member.id,
457
+ }),
458
+ ],
459
+ balanceItems: [
460
+ cartItem,
461
+ ],
462
+ deleteRegistrationIds: [],
463
+ }),
464
+ administrationFee: 0,
465
+ freeContribution: 0,
466
+ paymentMethod: PaymentMethod.PointOfSale,
467
+ totalPrice: 35,
468
+ });
469
+
470
+ // #endregion
471
+
472
+ // #region act and assert
473
+ const response = await register(body, organization, token);
474
+ expect(response.body.registrations.length).toBe(1);
475
+
476
+ const receivableBalanceAfter = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
477
+
478
+ expect(receivableBalanceAfter.body.balanceItems.length).toBe(2);
479
+ expect(receivableBalanceAfter.body.amountPending).toBe(35);
480
+ expect(receivableBalanceAfter.body.amountOpen).toBe(10);
481
+
482
+ expect(receivableBalanceAfter.body.balanceItems).toEqual(expect.arrayContaining([
483
+ expect.objectContaining({
484
+ pricePending: 10,
485
+ }),
486
+ expect.objectContaining({
487
+ pricePending: 25,
488
+ }),
489
+ ]));
490
+ // #endregion
491
+ });
492
+
493
+ test('Should apply reduced price if member requires financial support', async () => {
494
+ // #region arrange
495
+ const { organization, group, groupPrice, token, member } = await initData();
496
+ member.details.requiresFinancialSupport = BooleanStatus.create({
497
+ value: true,
498
+ });
499
+
500
+ await member.save();
501
+
502
+ const body = IDRegisterCheckout.create({
503
+ cart: IDRegisterCart.create({
504
+ items: [
505
+ IDRegisterItem.create({
506
+ id: uuidv4(),
507
+ replaceRegistrationIds: [],
508
+ options: [],
509
+ groupPrice,
510
+ organizationId: organization.id,
511
+ groupId: group.id,
512
+ memberId: member.id,
513
+ }),
514
+ ],
515
+ balanceItems: [
516
+ ],
517
+ deleteRegistrationIds: [],
518
+ }),
519
+ administrationFee: 0,
520
+ freeContribution: 0,
521
+ paymentMethod: PaymentMethod.PointOfSale,
522
+ totalPrice: 21,
523
+ customer: null,
524
+ });
525
+ // #endregion
526
+
527
+ // #region act and assert
528
+ const balanceBefore = await getBalance(member.id, organization, token);
529
+ expect(balanceBefore).toBeDefined();
530
+ expect(balanceBefore.body.length).toBe(0);
531
+
532
+ await register(body, organization, token);
533
+
534
+ const balance = await getBalance(member.id, organization, token);
535
+ expect(balance).toBeDefined();
536
+ expect(balance.body.length).toBe(1);
537
+ expect(balance.body[0].price).toBe(21);
538
+ expect(balance.body[0].pricePaid).toBe(0);
539
+ // #endregion
540
+ });
541
+
542
+ test('Should apply reduced price for options if member requires financial support', async () => {
543
+ // #region arrange
544
+ const { organization, group, groupPrice, token, member } = await initData();
545
+ member.details.requiresFinancialSupport = BooleanStatus.create({
546
+ value: true,
547
+ });
548
+
549
+ await member.save();
550
+
551
+ const option1 = GroupOption.create({
552
+ name: 'option 1',
553
+ price: ReduceablePrice.create({
554
+ price: 5,
555
+ reducedPrice: 3,
556
+ }),
557
+ });
558
+
559
+ const option2 = GroupOption.create({
560
+ name: 'option 2',
561
+ price: ReduceablePrice.create({
562
+ price: 3,
563
+ reducedPrice: 1,
564
+ }),
565
+ });
566
+
567
+ const optionMenu = GroupOptionMenu.create({
568
+ name: 'option menu 1',
569
+ multipleChoice: true,
570
+ options: [option1, option2],
571
+ });
572
+
573
+ group.settings.optionMenus = [
574
+ optionMenu,
575
+ ];
576
+
577
+ await group.save();
578
+
579
+ const body = IDRegisterCheckout.create({
580
+ cart: IDRegisterCart.create({
581
+ items: [
582
+ IDRegisterItem.create({
583
+ id: uuidv4(),
584
+ replaceRegistrationIds: [],
585
+ options: [
586
+ RegisterItemOption.create({
587
+ option: option1,
588
+ amount: 2,
589
+ optionMenu,
590
+ }),
591
+ RegisterItemOption.create({
592
+ option: option2,
593
+ amount: 5,
594
+ optionMenu,
595
+ }),
596
+ ],
597
+ groupPrice,
598
+ organizationId: organization.id,
599
+ groupId: group.id,
600
+ memberId: member.id,
601
+ }),
602
+ ],
603
+ balanceItems: [
604
+ ],
605
+ deleteRegistrationIds: [],
606
+ }),
607
+ administrationFee: 0,
608
+ freeContribution: 0,
609
+ paymentMethod: PaymentMethod.PointOfSale,
610
+ totalPrice: 32,
611
+ customer: null,
612
+ });
613
+ // #endregion
614
+
615
+ // #region act and assert
616
+ const balanceBefore = await getBalance(member.id, organization, token);
617
+ expect(balanceBefore).toBeDefined();
618
+ expect(balanceBefore.body.length).toBe(0);
619
+
620
+ await register(body, organization, token);
621
+
622
+ const balance = await getBalance(member.id, organization, token);
623
+ expect(balance).toBeDefined();
624
+ expect(balance.body.length).toBe(3);
625
+ expect.arrayContaining([
626
+ expect.objectContaining({
627
+ price: 6,
628
+ pricePaid: 0,
629
+ status: BalanceItemStatus.Due,
630
+ }),
631
+ expect.objectContaining({
632
+ price: 5,
633
+ pricePaid: 0,
634
+ status: BalanceItemStatus.Due,
635
+ }),
636
+ expect.objectContaining({
637
+ price: 25,
638
+ pricePaid: 0,
639
+ status: BalanceItemStatus.Due,
640
+ }),
641
+ ]);
642
+ // #endregion
643
+ });
644
+ });
645
+
646
+ describe('Delete registrations', () => {
647
+ test('Should cancel balance item for deleted registration', async () => {
648
+ // #region arrange
649
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
650
+
651
+ const body1 = IDRegisterCheckout.create({
652
+ cart: IDRegisterCart.create({
653
+ items: [
654
+ IDRegisterItem.create({
655
+ id: uuidv4(),
656
+ replaceRegistrationIds: [],
657
+ options: [],
658
+ groupPrice: groupPrice1,
659
+ organizationId: organization.id,
660
+ groupId: group1.id,
661
+ memberId: member.id,
662
+ }),
663
+ ],
664
+ balanceItems: [
665
+ ],
666
+ deleteRegistrationIds: [],
667
+ }),
668
+ administrationFee: 0,
669
+ freeContribution: 0,
670
+ paymentMethod: PaymentMethod.PointOfSale,
671
+ totalPrice: 25,
672
+ customer: null,
673
+ asOrganizationId: organization.id,
674
+ });
675
+
676
+ const response1 = await register(body1, organization, token);
677
+ expect(response1.body).toBeDefined();
678
+ expect(response1.body.registrations.length).toBe(1);
679
+
680
+ const registrationToDelete = response1.body.registrations[0];
681
+
682
+ const group = await new GroupFactory({
683
+ organization,
684
+ price: 30,
685
+ stock: 5,
686
+ }).create();
687
+
688
+ const groupPrice = group.settings.prices[0];
689
+
690
+ const body2 = IDRegisterCheckout.create({
691
+ cart: IDRegisterCart.create({
692
+ items: [
693
+ IDRegisterItem.create({
694
+ id: uuidv4(),
695
+ replaceRegistrationIds: [],
696
+ options: [],
697
+ groupPrice,
698
+ organizationId: organization.id,
699
+ groupId: group.id,
700
+ memberId: member.id,
701
+ }),
702
+ ],
703
+ balanceItems: [
704
+ ],
705
+ deleteRegistrationIds: [registrationToDelete.id],
706
+ }),
707
+ administrationFee: 0,
708
+ freeContribution: 0,
709
+ paymentMethod: PaymentMethod.PointOfSale,
710
+ totalPrice: 30,
711
+ customer: null,
712
+ asOrganizationId: organization.id,
713
+ });
714
+ // #endregion
715
+
716
+ // #region act and assert
717
+ const balanceBefore = await getBalance(member.id, organization, token);
718
+ expect(balanceBefore).toBeDefined();
719
+ expect(balanceBefore.body.length).toBe(1);
720
+ expect(balanceBefore.body[0].price).toBe(25);
721
+ expect(balanceBefore.body[0].pricePaid).toBe(0);
722
+
723
+ await register(body2, organization, token);
724
+
725
+ const balance = await getBalance(member.id, organization, token);
726
+ expect(balance).toBeDefined();
727
+ expect(balance.body.length).toBe(2);
728
+
729
+ expect.arrayContaining([
730
+ expect.objectContaining({
731
+ price: 25,
732
+ pricePaid: 0,
733
+ status: BalanceItemStatus.Canceled,
734
+ }),
735
+ expect.objectContaining({
736
+ price: 30,
737
+ pricePaid: 0,
738
+ status: BalanceItemStatus.Due,
739
+ }),
740
+ ]);
741
+ // #endregion
742
+ });
743
+
744
+ test('Should cancel all related balance item for deleted registration', async () => {
745
+ // #region arrange
746
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
747
+
748
+ const option1 = GroupOption.create({
749
+ name: 'option 1',
750
+ price: ReduceablePrice.create({
751
+ price: 5,
752
+ reducedPrice: 3,
753
+ }),
754
+ });
755
+
756
+ const option2 = GroupOption.create({
757
+ name: 'option 2',
758
+ price: ReduceablePrice.create({
759
+ price: 3,
760
+ reducedPrice: 1,
761
+ }),
762
+ });
763
+
764
+ const optionMenu = GroupOptionMenu.create({
765
+ name: 'option menu 1',
766
+ multipleChoice: true,
767
+ options: [option1, option2],
768
+ });
769
+
770
+ group1.settings.optionMenus = [
771
+ optionMenu,
772
+ ];
773
+
774
+ await group1.save();
775
+
776
+ const body1 = IDRegisterCheckout.create({
777
+ cart: IDRegisterCart.create({
778
+ items: [
779
+ IDRegisterItem.create({
780
+ id: uuidv4(),
781
+ replaceRegistrationIds: [],
782
+ options: [
783
+ RegisterItemOption.create({
784
+ option: option1,
785
+ amount: 2,
786
+ optionMenu,
787
+ }),
788
+ RegisterItemOption.create({
789
+ option: option2,
790
+ amount: 5,
791
+ optionMenu,
792
+ }),
793
+ ],
794
+ groupPrice: groupPrice1,
795
+ organizationId: organization.id,
796
+ groupId: group1.id,
797
+ memberId: member.id,
798
+ }),
799
+ ],
800
+ balanceItems: [
801
+ ],
802
+ deleteRegistrationIds: [],
803
+ }),
804
+ administrationFee: 0,
805
+ freeContribution: 0,
806
+ paymentMethod: PaymentMethod.PointOfSale,
807
+ totalPrice: 50,
808
+ customer: null,
809
+ asOrganizationId: organization.id,
810
+ });
811
+
812
+ const response1 = await register(body1, organization, token);
813
+ expect(response1.body).toBeDefined();
814
+ expect(response1.body.registrations.length).toBe(1);
815
+
816
+ const registrationToDelete = response1.body.registrations[0];
817
+
818
+ const group = await new GroupFactory({
819
+ organization,
820
+ price: 30,
821
+ stock: 5,
822
+ }).create();
823
+
824
+ const groupPrice = group.settings.prices[0];
825
+
826
+ const body2 = IDRegisterCheckout.create({
827
+ cart: IDRegisterCart.create({
828
+ items: [
829
+ IDRegisterItem.create({
830
+ id: uuidv4(),
831
+ replaceRegistrationIds: [],
832
+ options: [],
833
+ groupPrice,
834
+ organizationId: organization.id,
835
+ groupId: group.id,
836
+ memberId: member.id,
837
+ }),
838
+ ],
839
+ balanceItems: [
840
+ ],
841
+ deleteRegistrationIds: [registrationToDelete.id],
842
+ }),
843
+ administrationFee: 0,
844
+ freeContribution: 0,
845
+ paymentMethod: PaymentMethod.PointOfSale,
846
+ totalPrice: 30,
847
+ customer: null,
848
+ asOrganizationId: organization.id,
849
+ });
850
+ // #endregion
851
+
852
+ // #region act and assert
853
+ const balanceBefore = await getBalance(member.id, organization, token);
854
+ expect(balanceBefore).toBeDefined();
855
+ expect(balanceBefore.body.length).toBe(3);
856
+
857
+ await register(body2, organization, token);
858
+
859
+ const balance = await getBalance(member.id, organization, token);
860
+ expect(balance).toBeDefined();
861
+ expect(balance.body.length).toBe(4);
862
+
863
+ expect.arrayContaining([
864
+ expect.objectContaining({
865
+ price: 10,
866
+ pricePaid: 0,
867
+ status: BalanceItemStatus.Canceled,
868
+ }),
869
+ expect.objectContaining({
870
+ price: 15,
871
+ pricePaid: 0,
872
+ status: BalanceItemStatus.Canceled,
873
+ }),
874
+ expect.objectContaining({
875
+ price: 25,
876
+ pricePaid: 0,
877
+ status: BalanceItemStatus.Canceled,
878
+ }),
879
+ expect.objectContaining({
880
+ price: 30,
881
+ pricePaid: 0,
882
+ status: BalanceItemStatus.Due,
883
+ }),
884
+ ]);
885
+ // #endregion
886
+ });
887
+
888
+ test('Should apply cancelation fee', async () => {
889
+ // #region arrange
890
+ const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
891
+
892
+ const body1 = IDRegisterCheckout.create({
893
+ cart: IDRegisterCart.create({
894
+ items: [
895
+ IDRegisterItem.create({
896
+ id: uuidv4(),
897
+ replaceRegistrationIds: [],
898
+ options: [],
899
+ groupPrice: groupPrice1,
900
+ organizationId: organization.id,
901
+ groupId: group1.id,
902
+ memberId: member.id,
903
+ }),
904
+ ],
905
+ balanceItems: [
906
+ ],
907
+ deleteRegistrationIds: [],
908
+ }),
909
+ administrationFee: 0,
910
+ freeContribution: 0,
911
+ paymentMethod: PaymentMethod.PointOfSale,
912
+ totalPrice: 25,
913
+ customer: null,
914
+ asOrganizationId: organization.id,
915
+ });
916
+
917
+ const response1 = await register(body1, organization, token);
918
+ expect(response1.body).toBeDefined();
919
+ expect(response1.body.registrations.length).toBe(1);
920
+
921
+ const registrationToDelete = response1.body.registrations[0];
922
+
923
+ const group = await new GroupFactory({
924
+ organization,
925
+ price: 30,
926
+ stock: 5,
927
+ }).create();
928
+
929
+ const groupPrice = group.settings.prices[0];
930
+
931
+ const body2 = IDRegisterCheckout.create({
932
+ cart: IDRegisterCart.create({
933
+ items: [
934
+ IDRegisterItem.create({
935
+ id: uuidv4(),
936
+ replaceRegistrationIds: [],
937
+ options: [],
938
+ groupPrice,
939
+ organizationId: organization.id,
940
+ groupId: group.id,
941
+ memberId: member.id,
942
+ }),
943
+ ],
944
+ balanceItems: [
945
+ ],
946
+ deleteRegistrationIds: [registrationToDelete.id],
947
+ }),
948
+ administrationFee: 0,
949
+ freeContribution: 0,
950
+ paymentMethod: PaymentMethod.PointOfSale,
951
+ totalPrice: 30,
952
+ customer: null,
953
+ asOrganizationId: organization.id,
954
+ cancellationFeePercentage: 2000,
955
+ });
956
+ // #endregion
957
+
958
+ // #region act and assert
959
+ const balanceBefore = await getBalance(member.id, organization, token);
960
+ expect(balanceBefore).toBeDefined();
961
+ expect(balanceBefore.body.length).toBe(1);
962
+ expect(balanceBefore.body[0].price).toBe(25);
963
+ expect(balanceBefore.body[0].pricePaid).toBe(0);
964
+
965
+ await register(body2, organization, token);
966
+
967
+ const balance = await getBalance(member.id, organization, token);
968
+ expect(balance).toBeDefined();
969
+ expect(balance.body.length).toBe(3);
970
+
971
+ expect.arrayContaining([
972
+ expect.objectContaining({
973
+ price: 25,
974
+ pricePaid: 0,
975
+ status: BalanceItemStatus.Canceled,
976
+ }),
977
+ expect.objectContaining({
978
+ price: 30,
979
+ pricePaid: 0,
980
+ status: BalanceItemStatus.Due,
981
+ }),
982
+ expect.objectContaining({
983
+ price: 5,
984
+ pricePaid: 0,
985
+ type: BalanceItemType.CancellationFee,
986
+ status: BalanceItemStatus.Due,
987
+ }),
988
+ ]);
989
+ // #endregion
990
+ });
991
+ });
992
+
993
+ describe('Register for group with default age group', () => {
994
+ test('Should create membership', async () => {
995
+ // #region arrange
996
+ const date = new Date('2023-05-14');
997
+ jest.useFakeTimers().setSystemTime(date);
998
+
999
+ try {
1000
+ const platformMembershipTypeConfig = PlatformMembershipTypeConfig.create({
1001
+ startDate: period.startDate,
1002
+ endDate: period.endDate,
1003
+ });
1004
+
1005
+ const platformMembershipType = PlatformMembershipType.create({
1006
+ name: 'werkjaar',
1007
+ periods: new Map([
1008
+ [period.id, platformMembershipTypeConfig],
1009
+ ]),
1010
+ });
1011
+
1012
+ const platform = await Platform.getShared();
1013
+
1014
+ platform.config.membershipTypes = [
1015
+ platformMembershipType,
1016
+ ];
1017
+
1018
+ const defaultAgeGroup = DefaultAgeGroup.create({
1019
+ names: ['test groep'],
1020
+ defaultMembershipTypeId: platformMembershipType.id,
1021
+ });
1022
+
1023
+ platform.config.defaultAgeGroups = [defaultAgeGroup];
1024
+
1025
+ await platform.save();
1026
+
1027
+ const { member, group, groupPrice, organization, token } = await initData();
1028
+
1029
+ // todo: remove from initData
1030
+ member.organizationId = null;
1031
+ await member.save();
1032
+
1033
+ group.defaultAgeGroupId = defaultAgeGroup.id;
1034
+ await group.save();
1035
+
1036
+ const organizationPeriod = new OrganizationRegistrationPeriod();
1037
+ organizationPeriod.organizationId = organization.id;
1038
+ organizationPeriod.periodId = period.id;
1039
+ await organizationPeriod.save();
1040
+
1041
+ const body = IDRegisterCheckout.create({
1042
+ cart: IDRegisterCart.create({
1043
+ items: [
1044
+ IDRegisterItem.create({
1045
+ id: uuidv4(),
1046
+ replaceRegistrationIds: [],
1047
+ options: [],
1048
+ groupPrice: groupPrice,
1049
+ organizationId: organization.id,
1050
+ groupId: group.id,
1051
+ memberId: member.id,
1052
+ trial: false,
1053
+ }),
1054
+ ],
1055
+ balanceItems: [],
1056
+ deleteRegistrationIds: [],
1057
+ }),
1058
+ administrationFee: 0,
1059
+ freeContribution: 0,
1060
+ paymentMethod: PaymentMethod.PointOfSale,
1061
+ totalPrice: 25,
1062
+ asOrganizationId: organization.id,
1063
+ customer: null,
1064
+ });
1065
+ // #endregion
1066
+
1067
+ // act and assert
1068
+ const familyBefore = await getMemberFamily(member.id, organization, token);
1069
+ expect(familyBefore).toBeDefined();
1070
+ expect(familyBefore.body.members.length).toBe(1);
1071
+ expect(familyBefore.body.members[0]).toBeDefined();
1072
+ expect(familyBefore.body.members[0].platformMemberships.length).toBe(0);
1073
+
1074
+ const response = await register(body, organization, token);
1075
+
1076
+ expect(response.body).toBeDefined();
1077
+ expect(response.body.registrations.length).toBe(1);
1078
+
1079
+ const familyAfter = await getMemberFamily(member.id, organization, token);
1080
+ expect(familyAfter).toBeDefined();
1081
+ expect(familyAfter.body.members.length).toBe(1);
1082
+ expect(familyAfter.body.members[0]).toBeDefined();
1083
+ expect(familyAfter.body.members[0].platformMemberships.length).toBe(1);
1084
+ expect(familyAfter.body.members[0].platformMemberships[0].membershipTypeId).toBe(platformMembershipType.id);
1085
+ }
1086
+ finally {
1087
+ jest.useFakeTimers().resetAllMocks();
1088
+ }
1089
+ });
1090
+
1091
+ test('Should set trial until on membership if trial', async () => {
1092
+ // #region arrange
1093
+ const date = new Date('2023-05-14');
1094
+ jest.useFakeTimers().setSystemTime(date);
1095
+
1096
+ try {
1097
+ const platformMembershipTypeConfig = PlatformMembershipTypeConfig.create({
1098
+ startDate: period.startDate,
1099
+ endDate: period.endDate,
1100
+ trialDays: 10,
1101
+ });
1102
+
1103
+ const platformMembershipType = PlatformMembershipType.create({
1104
+ name: 'werkjaar',
1105
+ periods: new Map([
1106
+ [period.id, platformMembershipTypeConfig],
1107
+ ]),
1108
+ });
1109
+
1110
+ const platform = await Platform.getShared();
1111
+
1112
+ platform.config.membershipTypes = [
1113
+ platformMembershipType,
1114
+ ];
1115
+
1116
+ const defaultAgeGroup = DefaultAgeGroup.create({
1117
+ names: ['test groep'],
1118
+ defaultMembershipTypeId: platformMembershipType.id,
1119
+ });
1120
+
1121
+ platform.config.defaultAgeGroups = [defaultAgeGroup];
1122
+
1123
+ await platform.save();
1124
+
1125
+ const { member, group, groupPrice, organization, token } = await initData();
1126
+
1127
+ // todo: remove from initData
1128
+ member.organizationId = null;
1129
+ await member.save();
1130
+
1131
+ group.settings.trialDays = 5;
1132
+ group.defaultAgeGroupId = defaultAgeGroup.id;
1133
+ await group.save();
1134
+
1135
+ const organizationPeriod = new OrganizationRegistrationPeriod();
1136
+ organizationPeriod.organizationId = organization.id;
1137
+ organizationPeriod.periodId = period.id;
1138
+ await organizationPeriod.save();
1139
+
1140
+ const body = IDRegisterCheckout.create({
1141
+ cart: IDRegisterCart.create({
1142
+ items: [
1143
+ IDRegisterItem.create({
1144
+ id: uuidv4(),
1145
+ replaceRegistrationIds: [],
1146
+ options: [],
1147
+ groupPrice: groupPrice,
1148
+ organizationId: organization.id,
1149
+ groupId: group.id,
1150
+ memberId: member.id,
1151
+ trial: true,
1152
+ }),
1153
+ ],
1154
+ balanceItems: [],
1155
+ deleteRegistrationIds: [],
1156
+ }),
1157
+ administrationFee: 0,
1158
+ freeContribution: 0,
1159
+ paymentMethod: PaymentMethod.PointOfSale,
1160
+ totalPrice: 0,
1161
+ asOrganizationId: organization.id,
1162
+ customer: null,
1163
+ });
1164
+ // #endregion
1165
+
1166
+ // act and assert
1167
+ const familyBefore = await getMemberFamily(member.id, organization, token);
1168
+ expect(familyBefore).toBeDefined();
1169
+ expect(familyBefore.body.members.length).toBe(1);
1170
+ expect(familyBefore.body.members[0]).toBeDefined();
1171
+ expect(familyBefore.body.members[0].platformMemberships.length).toBe(0);
1172
+
1173
+ const response = await register(body, organization, token);
1174
+
1175
+ expect(response.body).toBeDefined();
1176
+ expect(response.body.registrations.length).toBe(1);
1177
+
1178
+ const familyAfter = await getMemberFamily(member.id, organization, token);
1179
+ expect(familyAfter).toBeDefined();
1180
+ expect(familyAfter.body.members.length).toBe(1);
1181
+ expect(familyAfter.body.members[0]).toBeDefined();
1182
+ expect(familyAfter.body.members[0].platformMemberships.length).toBe(1);
1183
+ expect(familyAfter.body.members[0].platformMemberships[0].membershipTypeId).toBe(platformMembershipType.id);
1184
+
1185
+ const trialUntil = familyAfter.body.members[0].platformMemberships[0].trialUntil;
1186
+
1187
+ expect(trialUntil).not.toBeNull();
1188
+ expect(trialUntil!.getFullYear()).toBe(2023);
1189
+ expect(trialUntil!.getMonth()).toBe(4);
1190
+ expect(trialUntil!.getDate()).toBe(24);
1191
+ }
1192
+ finally {
1193
+ jest.useFakeTimers().resetAllMocks();
1194
+ }
1195
+ });
1196
+ });
1197
+ });