@stamhoofd/backend 2.83.4 → 2.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/index.ts +19 -4
  2. package/package.json +18 -14
  3. package/src/crons/amazon-ses.ts +26 -5
  4. package/src/crons/balance-emails.ts +18 -17
  5. package/src/email-recipient-loaders/registrations.ts +87 -0
  6. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +5 -2
  7. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +40 -40
  8. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +28 -22
  9. package/src/endpoints/global/events/PatchEventsEndpoint.ts +81 -49
  10. package/src/endpoints/global/files/UploadFile.ts +11 -16
  11. package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +234 -0
  12. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +117 -43
  13. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +1054 -0
  14. package/src/endpoints/global/members/GetMembersEndpoint.ts +163 -141
  15. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +6 -6
  16. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +0 -16
  17. package/src/endpoints/global/members/helpers/validateGroupFilter.ts +73 -0
  18. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +1 -2
  19. package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +43 -0
  20. package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1016 -0
  21. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +234 -0
  22. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -5
  23. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +474 -554
  24. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +191 -52
  25. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +107 -9
  26. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +89 -0
  27. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +9 -6
  28. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +88 -0
  29. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +0 -6
  30. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +10 -6
  31. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +10 -25
  32. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +0 -5
  33. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +0 -5
  34. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +4 -0
  35. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -0
  36. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +44 -19
  37. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +140 -25
  38. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +40 -10
  39. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +2 -2
  40. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +2 -2
  41. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +4 -1
  42. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +2 -2
  43. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
  44. package/src/excel-loaders/members.ts +233 -232
  45. package/src/excel-loaders/payments.ts +1 -1
  46. package/src/excel-loaders/receivable-balances.ts +1 -1
  47. package/src/excel-loaders/registrations.ts +153 -0
  48. package/src/helpers/AdminPermissionChecker.ts +65 -37
  49. package/src/helpers/AuthenticatedStructures.ts +43 -3
  50. package/src/helpers/Context.ts +29 -1
  51. package/src/helpers/GlobalHelper.ts +3 -1
  52. package/src/helpers/GroupedThrottledQueue.test.ts +219 -0
  53. package/src/helpers/GroupedThrottledQueue.ts +108 -0
  54. package/src/helpers/LimitedFilteredRequestHelper.ts +26 -1
  55. package/src/helpers/MemberCharger.ts +0 -5
  56. package/src/helpers/MembershipCharger.ts +3 -9
  57. package/src/helpers/OrganizationCharger.ts +0 -5
  58. package/src/helpers/ThrottledQueue.test.ts +194 -0
  59. package/src/helpers/ThrottledQueue.ts +145 -0
  60. package/src/helpers/XlsxTransformerColumnHelper.ts +44 -1
  61. package/src/middleware/ContextMiddleware.ts +1 -1
  62. package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +2 -1
  63. package/src/seeds/1735577912-update-cached-outstanding-balance-from-items.ts +2 -1
  64. package/src/services/BalanceItemPaymentService.ts +1 -33
  65. package/src/services/BalanceItemService.ts +167 -48
  66. package/src/services/FileSignService.ts +18 -13
  67. package/src/services/MemberRecordStore.ts +28 -19
  68. package/src/services/PaymentReallocationService.test.ts +25 -14
  69. package/src/services/PaymentReallocationService.ts +29 -10
  70. package/src/services/PaymentService.ts +4 -16
  71. package/src/services/PlatformMembershipService.ts +8 -4
  72. package/src/services/RegistrationService.ts +66 -2
  73. package/src/sql-filters/base-registration-filter-compilers.ts +43 -0
  74. package/src/sql-filters/groups.ts +67 -0
  75. package/src/sql-filters/members.ts +33 -58
  76. package/src/sql-filters/organization-registration-periods.ts +8 -0
  77. package/src/sql-filters/registration-periods.ts +8 -0
  78. package/src/sql-filters/registrations.ts +11 -22
  79. package/src/sql-sorters/groups.ts +24 -0
  80. package/src/sql-sorters/organization-registration-periods.ts +24 -0
  81. package/src/sql-sorters/registration-periods.ts +47 -0
  82. package/src/sql-sorters/registrations.ts +77 -0
  83. package/tests/actions/patchOrganizationMember.ts +27 -0
  84. package/tests/actions/patchPaymentStatus.ts +45 -0
  85. package/tests/actions/patchUserMember.ts +27 -0
  86. package/tests/assertions/assertBalances.ts +49 -0
  87. package/tests/e2e/api-rate-limits.test.ts +5 -5
  88. package/tests/e2e/bundle-discounts.test.ts +4060 -0
  89. package/tests/e2e/charge-members.test.ts +27 -24
  90. package/tests/e2e/documents.test.ts +398 -0
  91. package/tests/e2e/register.test.ts +292 -312
  92. package/tests/helpers/PayconiqMocker.ts +55 -0
  93. package/tests/init/index.ts +5 -0
  94. package/tests/init/initAdmin.ts +14 -0
  95. package/tests/init/initBundleDiscount.ts +47 -0
  96. package/tests/init/initPayconiq.ts +9 -0
  97. package/tests/init/initPlatformAdmin.ts +13 -0
  98. package/tests/init/initStripe.ts +21 -0
  99. package/tests/jest.setup.ts +29 -0
  100. package/src/seeds-temporary/1736266448-recall-balance-item-price-paid.ts +0 -70
@@ -1,6 +1,6 @@
1
1
  import { Request } from '@simonbackx/simple-endpoints';
2
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';
3
+ import { AdministrationFeeSettings, BalanceItemCartItem, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, BooleanStatus, DefaultAgeGroup, FreeContributionSettings, GroupOption, GroupOptionMenu, IDRegisterCart, IDRegisterCheckout, IDRegisterItem, PaymentMethod, PermissionLevel, Permissions, PlatformMembershipType, PlatformMembershipTypeConfig, ReceivableBalanceType, ReduceablePrice, RegisterItemOption, TranslatedString, Version } from '@stamhoofd/structures';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import { GetMemberFamilyEndpoint } from '../../src/endpoints/global/members/GetMemberFamilyEndpoint';
6
6
  import { RegisterMembersEndpoint } from '../../src/endpoints/global/registration/RegisterMembersEndpoint';
@@ -9,30 +9,24 @@ import { GetReceivableBalanceEndpoint } from '../../src/endpoints/organization/d
9
9
  import { PlatformMembershipService } from '../../src/services/PlatformMembershipService';
10
10
  import { testServer } from '../helpers/TestServer';
11
11
  import { TestUtils } from '@stamhoofd/test-utils';
12
+ import { BalanceItemService } from '../../src/services/BalanceItemService';
13
+ import { assertBalances } from '../assertions/assertBalances';
12
14
 
13
15
  describe('E2E.Register', () => {
14
- // #region global
15
16
  const registerEndpoint = new RegisterMembersEndpoint();
16
- const memberBalanceEndpoint = new GetMemberBalanceEndpoint();
17
17
  const receivableBalancesEndpoint = new GetReceivableBalanceEndpoint();
18
18
  const getMemberFamilyEndpoint = new GetMemberFamilyEndpoint();
19
19
 
20
20
  let period: RegistrationPeriod;
21
21
 
22
- // #region helpers
23
22
  const register = async (body: IDRegisterCheckout, organization: Organization, token: Token) => {
24
23
  const request = Request.buildJson('POST', `/v${Version}/members/register`, organization.getApiHost(), body);
25
24
  request.headers.authorization = 'Bearer ' + token.accessToken;
26
25
  return await testServer.test(registerEndpoint, request);
27
26
  };
28
27
 
29
- const getBalance = async (memberId: string, organization: Organization, token: Token) => {
30
- const request = Request.buildJson('GET', `/v${Version}/organization/members/${memberId}/balance`, organization.getApiHost());
31
- request.headers.authorization = 'Bearer ' + token.accessToken;
32
- return await testServer.test(memberBalanceEndpoint, request);
33
- };
34
-
35
28
  const getReceivableBalance = async (type: ReceivableBalanceType, id: string, organization: Organization, token: Token) => {
29
+ await BalanceItemService.flushAll();
36
30
  const request = Request.buildJson('GET', `/v${Version}/receivable-balances/${type}/${id}`, organization.getApiHost());
37
31
  request.headers.authorization = 'Bearer ' + token.accessToken;
38
32
  return await testServer.test(receivableBalancesEndpoint, request);
@@ -43,15 +37,16 @@ describe('E2E.Register', () => {
43
37
  request.headers.authorization = 'Bearer ' + token.accessToken;
44
38
  return await testServer.test(getMemberFamilyEndpoint, request);
45
39
  };
46
- // #endregion
47
-
48
- // #endregion
49
40
 
50
41
  beforeEach(async () => {
51
42
  // These tests should run in platform mode
52
43
  TestUtils.setEnvironment('userMode', 'platform');
53
44
  });
54
45
 
46
+ afterEach(() => {
47
+ jest.useRealTimers();
48
+ });
49
+
55
50
  beforeAll(async () => {
56
51
  const previousPeriod = await new RegistrationPeriodFactory({
57
52
  startDate: new Date(2022, 0, 1),
@@ -60,7 +55,7 @@ describe('E2E.Register', () => {
60
55
 
61
56
  period = await new RegistrationPeriodFactory({
62
57
  startDate: new Date(2023, 0, 1),
63
- endDate: new Date(2023, 11, 31),
58
+ endDate: new Date(2050, 11, 31),
64
59
  }).create();
65
60
 
66
61
  period.previousPeriodId = previousPeriod.id;
@@ -81,14 +76,12 @@ describe('E2E.Register', () => {
81
76
 
82
77
  const token = await Token.createToken(user);
83
78
 
84
- const member = await new MemberFactory({ organization, user })
85
- .create();
79
+ const member = await new MemberFactory({ organization, user }).create();
86
80
 
87
81
  const otherMembers: MemberWithRegistrations[] = [];
88
82
 
89
83
  for (let i = 0; i < otherMemberAmount; i++) {
90
- otherMembers.push(await new MemberFactory({ organization, user })
91
- .create());
84
+ otherMembers.push(await new MemberFactory({ organization, user }).create());
92
85
  }
93
86
 
94
87
  const group = await new GroupFactory({
@@ -96,8 +89,7 @@ describe('E2E.Register', () => {
96
89
  price: 25,
97
90
  reducedPrice: 21,
98
91
  stock: 5,
99
- })
100
- .create();
92
+ }).create();
101
93
 
102
94
  const groupPrice = group.settings.prices[0];
103
95
 
@@ -117,7 +109,6 @@ describe('E2E.Register', () => {
117
109
 
118
110
  describe('Register prices and balances', () => {
119
111
  test('Register by member should create balance for member', async () => {
120
- // #region arrange
121
112
  const { organization, group, groupPrice, token, member } = await initData();
122
113
 
123
114
  const body = IDRegisterCheckout.create({
@@ -143,26 +134,25 @@ describe('E2E.Register', () => {
143
134
  totalPrice: 25,
144
135
  customer: null,
145
136
  });
146
- // #endregion
147
137
 
148
- // #region act and assert
149
- const balanceBefore = await getBalance(member.id, organization, token);
150
- expect(balanceBefore).toBeDefined();
151
- expect(balanceBefore.body.length).toBe(0);
138
+ await assertBalances({ member }, []);
152
139
 
153
140
  await register(body, organization, token);
154
141
 
155
- const balance = await getBalance(member.id, organization, token);
156
- expect(balance).toBeDefined();
157
- expect(balance.body.length).toBe(1);
158
- expect(balance.body[0].price).toBe(25);
159
- expect(balance.body[0].pricePaid).toBe(0);
160
- expect(balance.body[0].pricePending).toBe(25);
161
- // #endregion
142
+ await assertBalances({ member }, [
143
+ {
144
+ pricePaid: 0,
145
+ type: BalanceItemType.Registration,
146
+ status: BalanceItemStatus.Due,
147
+ unitPrice: 25,
148
+ amount: 1,
149
+ pricePending: 25,
150
+ priceOpen: 0,
151
+ },
152
+ ]);
162
153
  });
163
154
 
164
155
  test('Should create balance items for options', async () => {
165
- // #region arrange
166
156
  const { organization, group, groupPrice, token, member } = await initData();
167
157
 
168
158
  const option1 = GroupOption.create({
@@ -227,37 +217,94 @@ describe('E2E.Register', () => {
227
217
  totalPrice: 50,
228
218
  customer: null,
229
219
  });
230
- // #endregion
231
220
 
232
- // #region act and assert
233
- const balanceBefore = await getBalance(member.id, organization, token);
234
- expect(balanceBefore).toBeDefined();
235
- expect(balanceBefore.body.length).toBe(0);
221
+ await assertBalances({ member }, []);
236
222
 
237
223
  await register(body, organization, token);
238
224
 
239
- const balance = await getBalance(member.id, organization, token);
240
- expect(balance).toBeDefined();
241
- expect(balance.body.length).toBe(3);
242
- expect(balance.body).toEqual(expect.arrayContaining([
243
- expect.objectContaining({
244
- price: 25,
225
+ await assertBalances({ member }, [
226
+ {
227
+ type: BalanceItemType.Registration,
228
+ amount: 2,
229
+ unitPrice: 5,
230
+ price: 10,
245
231
  pricePaid: 0,
246
- }),
247
- expect.objectContaining({
232
+ pricePending: 10,
233
+ relations: new Map([
234
+ [
235
+ BalanceItemRelationType.Group,
236
+ BalanceItemRelation.create({
237
+ id: group.id,
238
+ name: group.settings.name,
239
+ }),
240
+ ],
241
+ [
242
+ BalanceItemRelationType.GroupOptionMenu,
243
+ BalanceItemRelation.create({
244
+ id: optionMenu.id,
245
+ name: new TranslatedString(optionMenu.name),
246
+ }),
247
+ ],
248
+ [
249
+ BalanceItemRelationType.GroupOption,
250
+ BalanceItemRelation.create({
251
+ id: option1.id,
252
+ name: new TranslatedString(option1.name),
253
+ }),
254
+ ],
255
+ ]),
256
+ },
257
+ {
258
+ type: BalanceItemType.Registration,
259
+ amount: 5,
260
+ unitPrice: 3,
248
261
  price: 15,
249
262
  pricePaid: 0,
250
- }),
251
- expect.objectContaining({
252
- price: 10,
263
+ pricePending: 15,
264
+ relations: new Map([
265
+ [
266
+ BalanceItemRelationType.Group,
267
+ BalanceItemRelation.create({
268
+ id: group.id,
269
+ name: group.settings.name,
270
+ }),
271
+ ],
272
+ [
273
+ BalanceItemRelationType.GroupOptionMenu,
274
+ BalanceItemRelation.create({
275
+ id: optionMenu.id,
276
+ name: new TranslatedString(optionMenu.name),
277
+ }),
278
+ ],
279
+ [
280
+ BalanceItemRelationType.GroupOption,
281
+ BalanceItemRelation.create({
282
+ id: option2.id,
283
+ name: new TranslatedString(option2.name),
284
+ }),
285
+ ],
286
+ ]),
287
+ },
288
+ {
289
+ type: BalanceItemType.Registration,
290
+ amount: 1,
291
+ price: 25,
253
292
  pricePaid: 0,
254
- }),
255
- ]));
256
- // #endregion
293
+ pricePending: 25,
294
+ relations: new Map([
295
+ [
296
+ BalanceItemRelationType.Group,
297
+ BalanceItemRelation.create({
298
+ id: group.id,
299
+ name: group.settings.name,
300
+ }),
301
+ ],
302
+ ]),
303
+ },
304
+ ]);
257
305
  });
258
306
 
259
307
  test('Should reset free contribution if no options on organization', async () => {
260
- // #region arrange
261
308
  const { organization, group, groupPrice, token, member, user } = await initData();
262
309
 
263
310
  const body = IDRegisterCheckout.create({
@@ -283,9 +330,7 @@ describe('E2E.Register', () => {
283
330
  totalPrice: 25,
284
331
  customer: null,
285
332
  });
286
- // #endregion
287
333
 
288
- // #region act and assert
289
334
  const receivableBalanceBefore = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
290
335
  expect(receivableBalanceBefore).toBeDefined();
291
336
  expect(receivableBalanceBefore.body.balanceItems.length).toBe(0);
@@ -303,11 +348,9 @@ describe('E2E.Register', () => {
303
348
  pricePaid: 0,
304
349
  }),
305
350
  ]));
306
- // #endregion
307
351
  });
308
352
 
309
353
  test('Should create balance item for free contribution', async () => {
310
- // #region arrange
311
354
  const { organization, group, groupPrice, token, member, user } = await initData();
312
355
 
313
356
  organization.meta.recordsConfiguration.freeContribution = FreeContributionSettings.create({
@@ -340,9 +383,7 @@ describe('E2E.Register', () => {
340
383
  totalPrice: 55,
341
384
  customer: null,
342
385
  });
343
- // #endregion
344
386
 
345
- // #region act and assert
346
387
  const receivableBalanceBefore = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization, token);
347
388
  expect(receivableBalanceBefore).toBeDefined();
348
389
  expect(receivableBalanceBefore.body.balanceItems.length).toBe(0);
@@ -366,11 +407,9 @@ describe('E2E.Register', () => {
366
407
  pricePaid: 0,
367
408
  }),
368
409
  ]));
369
- // #endregion
370
410
  });
371
411
 
372
412
  test('Should create balance item for free administration fee if register by member', async () => {
373
- // #region arrange
374
413
  const { organization, group, groupPrice, token, member, user } = await initData();
375
414
 
376
415
  organization.meta.registrationPaymentConfiguration.administrationFee = AdministrationFeeSettings.create({
@@ -401,9 +440,7 @@ describe('E2E.Register', () => {
401
440
  paymentMethod: PaymentMethod.PointOfSale,
402
441
  totalPrice: 58,
403
442
  });
404
- // #endregion
405
443
 
406
- // #region act and assert
407
444
  const receivableBalanceBefore = await getReceivableBalance(ReceivableBalanceType.user, user.id, organization
408
445
  , token);
409
446
  expect(receivableBalanceBefore).toBeDefined();
@@ -429,11 +466,9 @@ describe('E2E.Register', () => {
429
466
  type: BalanceItemType.AdministrationFee,
430
467
  }),
431
468
  ]));
432
- // #endregion
433
469
  });
434
470
 
435
471
  test('Should create balance item for cart item', async () => {
436
- // #region arrange
437
472
  const { organization, group, groupPrice, token, member, user } = await initData();
438
473
 
439
474
  const balanceItem1 = await new BalanceItemFactory({
@@ -475,9 +510,6 @@ describe('E2E.Register', () => {
475
510
  totalPrice: 35,
476
511
  });
477
512
 
478
- // #endregion
479
-
480
- // #region act and assert
481
513
  const response = await register(body, organization, token);
482
514
  expect(response.body.registrations.length).toBe(1);
483
515
 
@@ -495,11 +527,9 @@ describe('E2E.Register', () => {
495
527
  pricePending: 25,
496
528
  }),
497
529
  ]));
498
- // #endregion
499
530
  });
500
531
 
501
532
  test('Should apply reduced price if member requires financial support', async () => {
502
- // #region arrange
503
533
  const { organization, group, groupPrice, token, member } = await initData();
504
534
  member.details.requiresFinancialSupport = BooleanStatus.create({
505
535
  value: true,
@@ -530,25 +560,25 @@ describe('E2E.Register', () => {
530
560
  totalPrice: 21,
531
561
  customer: null,
532
562
  });
533
- // #endregion
534
563
 
535
- // #region act and assert
536
- const balanceBefore = await getBalance(member.id, organization, token);
537
- expect(balanceBefore).toBeDefined();
538
- expect(balanceBefore.body.length).toBe(0);
564
+ await assertBalances({ member }, []);
539
565
 
540
566
  await register(body, organization, token);
541
567
 
542
- const balance = await getBalance(member.id, organization, token);
543
- expect(balance).toBeDefined();
544
- expect(balance.body.length).toBe(1);
545
- expect(balance.body[0].price).toBe(21);
546
- expect(balance.body[0].pricePaid).toBe(0);
547
- // #endregion
568
+ await assertBalances({ member }, [
569
+ {
570
+ unitPrice: 21,
571
+ amount: 1,
572
+ pricePaid: 0,
573
+ pricePending: 21,
574
+ priceOpen: 0,
575
+ type: BalanceItemType.Registration,
576
+ status: BalanceItemStatus.Due,
577
+ },
578
+ ]);
548
579
  });
549
580
 
550
581
  test('Should apply reduced price for options if member requires financial support', async () => {
551
- // #region arrange
552
582
  const { organization, group, groupPrice, token, member } = await initData();
553
583
  member.details.requiresFinancialSupport = BooleanStatus.create({
554
584
  value: true,
@@ -618,48 +648,39 @@ describe('E2E.Register', () => {
618
648
  totalPrice: 32,
619
649
  customer: null,
620
650
  });
621
- // #endregion
622
651
 
623
- // #region act and assert
624
- const balanceBefore = await getBalance(member.id, organization, token);
625
- expect(balanceBefore).toBeDefined();
626
- expect(balanceBefore.body.length).toBe(0);
652
+ await assertBalances({ member }, []);
627
653
 
628
654
  await register(body, organization, token);
629
655
 
630
- const balance = await getBalance(member.id, organization, token);
631
- expect(balance).toBeDefined();
632
- expect(balance.body.length).toBe(3);
633
- expect(balance.body).toEqual(expect.arrayContaining([
634
- expect.objectContaining({
656
+ await assertBalances({ member }, [
657
+ {
635
658
  unitPrice: 3,
636
659
  amount: 2,
637
660
  pricePending: 6,
638
661
  pricePaid: 0,
639
662
  status: BalanceItemStatus.Due,
640
- }),
641
- expect.objectContaining({
663
+ },
664
+ {
642
665
  unitPrice: 21,
643
666
  pricePaid: 0,
644
667
  pricePending: 21,
645
668
  amount: 1,
646
669
  status: BalanceItemStatus.Due,
647
- }),
648
- expect.objectContaining({
670
+ },
671
+ {
649
672
  unitPrice: 1,
650
673
  pricePaid: 0,
651
674
  pricePending: 5,
652
675
  amount: 5,
653
676
  status: BalanceItemStatus.Due,
654
- }),
655
- ]));
656
- // #endregion
677
+ },
678
+ ]);
657
679
  });
658
680
  });
659
681
 
660
682
  describe('Delete registrations', () => {
661
683
  test('Should cancel balance item for deleted registration', async () => {
662
- // #region arrange
663
684
  const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
664
685
 
665
686
  const body1 = IDRegisterCheckout.create({
@@ -675,16 +696,12 @@ describe('E2E.Register', () => {
675
696
  memberId: member.id,
676
697
  }),
677
698
  ],
678
- balanceItems: [
679
- ],
680
- deleteRegistrationIds: [],
681
699
  }),
682
700
  administrationFee: 0,
683
701
  freeContribution: 0,
684
702
  paymentMethod: PaymentMethod.PointOfSale,
685
703
  totalPrice: 25,
686
704
  customer: null,
687
- asOrganizationId: organization.id,
688
705
  });
689
706
 
690
707
  const response1 = await register(body1, organization, token);
@@ -693,6 +710,17 @@ describe('E2E.Register', () => {
693
710
 
694
711
  const registrationToDelete = response1.body.registrations[0];
695
712
 
713
+ await assertBalances({ member }, [
714
+ {
715
+ unitPrice: 25,
716
+ pricePaid: 0,
717
+ status: BalanceItemStatus.Due,
718
+ pricePending: 25,
719
+ priceOpen: 0,
720
+ registrationId: registrationToDelete.id,
721
+ },
722
+ ]);
723
+
696
724
  const group = await new GroupFactory({
697
725
  organization,
698
726
  price: 30,
@@ -706,57 +734,41 @@ describe('E2E.Register', () => {
706
734
  items: [
707
735
  IDRegisterItem.create({
708
736
  id: uuidv4(),
709
- replaceRegistrationIds: [],
710
- options: [],
711
737
  groupPrice,
712
738
  organizationId: organization.id,
713
739
  groupId: group.id,
714
740
  memberId: member.id,
715
741
  }),
716
742
  ],
717
- balanceItems: [
718
- ],
719
743
  deleteRegistrationIds: [registrationToDelete.id],
720
744
  }),
721
- administrationFee: 0,
722
- freeContribution: 0,
723
- paymentMethod: PaymentMethod.PointOfSale,
724
- totalPrice: 30,
725
- customer: null,
745
+ totalPrice: 30 - 25,
726
746
  asOrganizationId: organization.id,
727
747
  });
728
- // #endregion
729
-
730
- // #region act and assert
731
- const balanceBefore = await getBalance(member.id, organization, token);
732
- expect(balanceBefore).toBeDefined();
733
- expect(balanceBefore.body.length).toBe(1);
734
- expect(balanceBefore.body[0].price).toBe(25);
735
- expect(balanceBefore.body[0].pricePaid).toBe(0);
736
748
 
737
749
  await register(body2, organization, token);
738
750
 
739
- const balance = await getBalance(member.id, organization, token);
740
- expect(balance).toBeDefined();
741
- expect(balance.body.length).toBe(2);
742
-
743
- expect(balance.body).toEqual(expect.arrayContaining([
744
- expect.objectContaining({
751
+ await assertBalances({ member }, [
752
+ {
745
753
  unitPrice: 25,
754
+ amount: 1,
746
755
  pricePaid: 0,
747
756
  status: BalanceItemStatus.Canceled,
748
- }),
749
- expect.objectContaining({
757
+ registrationId: registrationToDelete.id,
758
+ pricePending: 25,
759
+ priceOpen: -25,
760
+ },
761
+ {
750
762
  unitPrice: 30,
751
763
  pricePaid: 0,
752
764
  status: BalanceItemStatus.Due,
753
- }),
754
- ]));
755
- // #endregion
765
+ priceOpen: 30,
766
+ pricePending: 0,
767
+ },
768
+ ]);
756
769
  });
757
770
 
758
771
  test('Should cancel all related balance item for deleted registration', async () => {
759
- // #region arrange
760
772
  const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
761
773
 
762
774
  const option1 = GroupOption.create({
@@ -792,7 +804,6 @@ describe('E2E.Register', () => {
792
804
  items: [
793
805
  IDRegisterItem.create({
794
806
  id: uuidv4(),
795
- replaceRegistrationIds: [],
796
807
  options: [
797
808
  RegisterItemOption.create({
798
809
  option: option1,
@@ -811,13 +822,7 @@ describe('E2E.Register', () => {
811
822
  memberId: member.id,
812
823
  }),
813
824
  ],
814
- balanceItems: [
815
- ],
816
- deleteRegistrationIds: [],
817
825
  }),
818
- administrationFee: 0,
819
- freeContribution: 0,
820
- paymentMethod: PaymentMethod.PointOfSale,
821
826
  totalPrice: 50,
822
827
  customer: null,
823
828
  asOrganizationId: organization.id,
@@ -829,6 +834,32 @@ describe('E2E.Register', () => {
829
834
 
830
835
  const registrationToDelete = response1.body.registrations[0];
831
836
 
837
+ await assertBalances({ member }, [
838
+ {
839
+ unitPrice: 5,
840
+ amount: 2,
841
+ pricePending: 0,
842
+ priceOpen: 10,
843
+ status: BalanceItemStatus.Due,
844
+ registrationId: registrationToDelete.id,
845
+ },
846
+ {
847
+ unitPrice: 3,
848
+ amount: 5,
849
+ pricePending: 0,
850
+ priceOpen: 15,
851
+ status: BalanceItemStatus.Due,
852
+ registrationId: registrationToDelete.id,
853
+ },
854
+ {
855
+ unitPrice: 25,
856
+ pricePending: 0,
857
+ priceOpen: 25,
858
+ status: BalanceItemStatus.Due,
859
+ registrationId: registrationToDelete.id,
860
+ },
861
+ ]);
862
+
832
863
  const group = await new GroupFactory({
833
864
  organization,
834
865
  price: 30,
@@ -842,69 +873,56 @@ describe('E2E.Register', () => {
842
873
  items: [
843
874
  IDRegisterItem.create({
844
875
  id: uuidv4(),
845
- replaceRegistrationIds: [],
846
- options: [],
847
876
  groupPrice,
848
877
  organizationId: organization.id,
849
878
  groupId: group.id,
850
879
  memberId: member.id,
851
880
  }),
852
881
  ],
853
- balanceItems: [
854
- ],
855
882
  deleteRegistrationIds: [registrationToDelete.id],
856
883
  }),
857
- administrationFee: 0,
858
- freeContribution: 0,
859
- paymentMethod: PaymentMethod.PointOfSale,
860
- totalPrice: 30,
861
- customer: null,
884
+ totalPrice: 30 - 50,
862
885
  asOrganizationId: organization.id,
863
886
  });
864
- // #endregion
865
-
866
- // #region act and assert
867
- const balanceBefore = await getBalance(member.id, organization, token);
868
- expect(balanceBefore).toBeDefined();
869
- expect(balanceBefore.body.length).toBe(3);
870
887
 
871
888
  await register(body2, organization, token);
872
889
 
873
- const balance = await getBalance(member.id, organization, token);
874
- expect(balance).toBeDefined();
875
- expect(balance.body.length).toBe(4);
876
-
877
- expect(balance.body).toEqual(expect.arrayContaining([
878
- expect.objectContaining({
890
+ await assertBalances({ member }, [
891
+ {
879
892
  unitPrice: 5,
880
893
  amount: 2,
881
- pricePaid: 0,
894
+ pricePending: 0,
895
+ priceOpen: 0,
882
896
  status: BalanceItemStatus.Canceled,
883
- }),
884
- expect.objectContaining({
897
+ registrationId: registrationToDelete.id,
898
+ },
899
+ {
885
900
  unitPrice: 3,
886
- pricePaid: 0,
887
901
  amount: 5,
902
+ pricePending: 0,
903
+ priceOpen: 0,
888
904
  status: BalanceItemStatus.Canceled,
889
- }),
890
- expect.objectContaining({
905
+ registrationId: registrationToDelete.id,
906
+ },
907
+ {
891
908
  unitPrice: 25,
892
- pricePaid: 0,
893
- amount: 1,
909
+ pricePending: 0,
910
+ priceOpen: 0,
894
911
  status: BalanceItemStatus.Canceled,
895
- }),
896
- expect.objectContaining({
912
+ registrationId: registrationToDelete.id,
913
+ },
914
+ {
897
915
  unitPrice: 30,
898
916
  pricePaid: 0,
899
917
  amount: 1,
900
918
  status: BalanceItemStatus.Due,
901
- }),
902
- ]));
903
- // #endregion
919
+ pricePending: 0,
920
+ priceOpen: 30,
921
+ },
922
+ ]);
904
923
  });
905
924
 
906
925
  test('Should apply cancelation fee', async () => {
907
- // #region arrange
908
926
  const { member, group: group1, groupPrice: groupPrice1, organization, token } = await initData();
909
927
 
910
928
  const body1 = IDRegisterCheckout.create({
@@ -912,23 +930,14 @@ describe('E2E.Register', () => {
912
930
  items: [
913
931
  IDRegisterItem.create({
914
932
  id: uuidv4(),
915
- replaceRegistrationIds: [],
916
- options: [],
917
933
  groupPrice: groupPrice1,
918
934
  organizationId: organization.id,
919
935
  groupId: group1.id,
920
936
  memberId: member.id,
921
937
  }),
922
938
  ],
923
- balanceItems: [
924
- ],
925
- deleteRegistrationIds: [],
926
939
  }),
927
- administrationFee: 0,
928
- freeContribution: 0,
929
- paymentMethod: PaymentMethod.PointOfSale,
930
940
  totalPrice: 25,
931
- customer: null,
932
941
  asOrganizationId: organization.id,
933
942
  });
934
943
 
@@ -938,6 +947,18 @@ describe('E2E.Register', () => {
938
947
 
939
948
  const registrationToDelete = response1.body.registrations[0];
940
949
 
950
+ await assertBalances({ member }, [
951
+ {
952
+ unitPrice: 25,
953
+ pricePaid: 0,
954
+ status: BalanceItemStatus.Due,
955
+ type: BalanceItemType.Registration,
956
+ pricePending: 0,
957
+ priceOpen: 25,
958
+ registrationId: registrationToDelete.id,
959
+ },
960
+ ]);
961
+
941
962
  const group = await new GroupFactory({
942
963
  organization,
943
964
  price: 30,
@@ -959,167 +980,135 @@ describe('E2E.Register', () => {
959
980
  memberId: member.id,
960
981
  }),
961
982
  ],
962
- balanceItems: [
963
- ],
964
983
  deleteRegistrationIds: [registrationToDelete.id],
965
984
  }),
966
- administrationFee: 0,
967
- freeContribution: 0,
968
- paymentMethod: PaymentMethod.PointOfSale,
969
- totalPrice: 30,
970
- customer: null,
985
+ totalPrice: 30 - 25 + 5, // 20% of 25 is 5
971
986
  asOrganizationId: organization.id,
972
- cancellationFeePercentage: 2000,
987
+ cancellationFeePercentage: 20_00,
973
988
  });
974
- // #endregion
975
-
976
- // #region act and assert
977
- const balanceBefore = await getBalance(member.id, organization, token);
978
- expect(balanceBefore).toBeDefined();
979
- expect(balanceBefore.body.length).toBe(1);
980
- expect(balanceBefore.body[0].price).toBe(25);
981
- expect(balanceBefore.body[0].pricePaid).toBe(0);
982
989
 
983
990
  await register(body2, organization, token);
984
-
985
- const balance = await getBalance(member.id, organization, token);
986
- expect(balance).toBeDefined();
987
- expect(balance.body.length).toBe(3);
988
-
989
- expect(balance.body).toEqual(expect.arrayContaining([
990
- expect.objectContaining({
991
+ await assertBalances({ member }, [
992
+ {
991
993
  unitPrice: 25,
992
994
  pricePaid: 0,
993
- pricePending: 0,
994
- amount: 1,
995
995
  status: BalanceItemStatus.Canceled,
996
+ pricePending: 0,
997
+ priceOpen: 0,
996
998
  type: BalanceItemType.Registration,
997
- }),
998
- expect.objectContaining({
999
- unitPrice: 30,
999
+ registrationId: registrationToDelete.id,
1000
+ },
1001
+ {
1002
+ unitPrice: 5,
1000
1003
  pricePaid: 0,
1001
1004
  pricePending: 0,
1005
+ priceOpen: 5,
1002
1006
  amount: 1,
1007
+ type: BalanceItemType.CancellationFee,
1003
1008
  status: BalanceItemStatus.Due,
1004
- type: BalanceItemType.Registration,
1005
- }),
1006
- expect.objectContaining({
1007
- unitPrice: 5,
1009
+ registrationId: registrationToDelete.id,
1010
+ },
1011
+ {
1012
+ unitPrice: 30,
1008
1013
  pricePaid: 0,
1009
1014
  pricePending: 0,
1015
+ priceOpen: 30,
1010
1016
  amount: 1,
1011
- type: BalanceItemType.CancellationFee,
1012
1017
  status: BalanceItemStatus.Due,
1013
- }),
1014
- ]));
1015
- // #endregion
1018
+ type: BalanceItemType.Registration,
1019
+ },
1020
+ ]);
1016
1021
  });
1017
1022
  });
1018
1023
 
1019
1024
  describe('Register for group with default age group', () => {
1020
1025
  test('Should create membership', async () => {
1021
- // #region arrange
1022
1026
  const date = new Date('2023-05-14');
1023
- jest.useFakeTimers().setSystemTime(date);
1027
+ jest.useFakeTimers({ advanceTimers: true, doNotFake: ['setTimeout', 'clearTimeout', 'hrtime', 'nextTick', 'performance', 'queueMicrotask', 'setImmediate', 'clearImmediate'] }).setSystemTime(date);
1024
1028
 
1025
- try {
1026
- const platformMembershipTypeConfig = PlatformMembershipTypeConfig.create({
1027
- startDate: period.startDate,
1028
- endDate: period.endDate,
1029
- });
1029
+ const platformMembershipTypeConfig = PlatformMembershipTypeConfig.create({
1030
+ startDate: period.startDate,
1031
+ endDate: period.endDate,
1032
+ });
1030
1033
 
1031
- const platformMembershipType = PlatformMembershipType.create({
1032
- name: 'werkjaar',
1033
- periods: new Map([
1034
- [period.id, platformMembershipTypeConfig],
1035
- ]),
1036
- });
1034
+ const platformMembershipType = PlatformMembershipType.create({
1035
+ name: 'werkjaar',
1036
+ periods: new Map([
1037
+ [period.id, platformMembershipTypeConfig],
1038
+ ]),
1039
+ });
1037
1040
 
1038
- const platform = await Platform.getForEditing();
1041
+ const platform = await Platform.getForEditing();
1039
1042
 
1040
- platform.config.membershipTypes = [
1041
- platformMembershipType,
1042
- ];
1043
+ platform.config.membershipTypes = [
1044
+ platformMembershipType,
1045
+ ];
1043
1046
 
1044
- const defaultAgeGroup = DefaultAgeGroup.create({
1045
- names: ['test groep'],
1046
- defaultMembershipTypeId: platformMembershipType.id,
1047
- });
1047
+ const defaultAgeGroup = DefaultAgeGroup.create({
1048
+ names: ['test groep'],
1049
+ defaultMembershipTypeId: platformMembershipType.id,
1050
+ });
1048
1051
 
1049
- platform.config.defaultAgeGroups = [defaultAgeGroup];
1052
+ platform.config.defaultAgeGroups = [defaultAgeGroup];
1050
1053
 
1051
- await platform.save();
1054
+ await platform.save();
1052
1055
 
1053
- const { member, group, groupPrice, organization, token } = await initData();
1056
+ const { member, group, groupPrice, organization, token } = await initData();
1054
1057
 
1055
- // todo: remove from initData
1056
- member.organizationId = null;
1057
- await member.save();
1058
+ // todo: remove from initData
1059
+ member.organizationId = null;
1060
+ await member.save();
1058
1061
 
1059
- group.defaultAgeGroupId = defaultAgeGroup.id;
1060
- await group.save();
1062
+ group.defaultAgeGroupId = defaultAgeGroup.id;
1063
+ await group.save();
1061
1064
 
1062
- const organizationPeriod = new OrganizationRegistrationPeriod();
1063
- organizationPeriod.organizationId = organization.id;
1064
- organizationPeriod.periodId = period.id;
1065
- await organizationPeriod.save();
1065
+ const organizationPeriod = new OrganizationRegistrationPeriod();
1066
+ organizationPeriod.organizationId = organization.id;
1067
+ organizationPeriod.periodId = period.id;
1068
+ await organizationPeriod.save();
1066
1069
 
1067
- const body = IDRegisterCheckout.create({
1068
- cart: IDRegisterCart.create({
1069
- items: [
1070
- IDRegisterItem.create({
1071
- id: uuidv4(),
1072
- replaceRegistrationIds: [],
1073
- options: [],
1074
- groupPrice: groupPrice,
1075
- organizationId: organization.id,
1076
- groupId: group.id,
1077
- memberId: member.id,
1078
- trial: false,
1079
- }),
1080
- ],
1081
- balanceItems: [],
1082
- deleteRegistrationIds: [],
1083
- }),
1084
- administrationFee: 0,
1085
- freeContribution: 0,
1086
- paymentMethod: PaymentMethod.PointOfSale,
1087
- totalPrice: 25,
1088
- asOrganizationId: organization.id,
1089
- customer: null,
1090
- });
1091
- // #endregion
1070
+ const body = IDRegisterCheckout.create({
1071
+ cart: IDRegisterCart.create({
1072
+ items: [
1073
+ IDRegisterItem.create({
1074
+ id: uuidv4(),
1075
+ groupPrice: groupPrice,
1076
+ organizationId: organization.id,
1077
+ groupId: group.id,
1078
+ memberId: member.id,
1079
+ trial: false,
1080
+ }),
1081
+ ],
1082
+ }),
1083
+ totalPrice: 25,
1084
+ asOrganizationId: organization.id,
1085
+ });
1092
1086
 
1093
- // act and assert
1094
- const familyBefore = await getMemberFamily(member.id, organization, token);
1095
- expect(familyBefore).toBeDefined();
1096
- expect(familyBefore.body.members.length).toBe(1);
1097
- expect(familyBefore.body.members[0]).toBeDefined();
1098
- expect(familyBefore.body.members[0].platformMemberships.length).toBe(0);
1087
+ // act and assert
1088
+ const familyBefore = await getMemberFamily(member.id, organization, token);
1089
+ expect(familyBefore).toBeDefined();
1090
+ expect(familyBefore.body.members.length).toBe(1);
1091
+ expect(familyBefore.body.members[0]).toBeDefined();
1092
+ expect(familyBefore.body.members[0].platformMemberships.length).toBe(0);
1099
1093
 
1100
- const response = await register(body, organization, token);
1094
+ const response = await register(body, organization, token);
1101
1095
 
1102
- expect(response.body).toBeDefined();
1103
- expect(response.body.registrations.length).toBe(1);
1096
+ expect(response.body).toBeDefined();
1097
+ expect(response.body.registrations.length).toBe(1);
1104
1098
 
1105
- await PlatformMembershipService.updateMembershipsForId(member.id, false);
1099
+ await PlatformMembershipService.updateMembershipsForId(member.id, false);
1106
1100
 
1107
- const familyAfter = await getMemberFamily(member.id, organization, token);
1108
- expect(familyAfter).toBeDefined();
1109
- expect(familyAfter.body.members.length).toBe(1);
1110
- expect(familyAfter.body.members[0]).toBeDefined();
1111
- expect(familyAfter.body.members[0].platformMemberships.length).toBe(1);
1112
- expect(familyAfter.body.members[0].platformMemberships[0].membershipTypeId).toBe(platformMembershipType.id);
1113
- }
1114
- finally {
1115
- jest.useFakeTimers().resetAllMocks();
1116
- }
1117
- });
1101
+ const familyAfter = await getMemberFamily(member.id, organization, token);
1102
+ expect(familyAfter).toBeDefined();
1103
+ expect(familyAfter.body.members.length).toBe(1);
1104
+ expect(familyAfter.body.members[0]).toBeDefined();
1105
+ expect(familyAfter.body.members[0].platformMemberships.length).toBe(1);
1106
+ expect(familyAfter.body.members[0].platformMemberships[0].membershipTypeId).toBe(platformMembershipType.id);
1107
+ }, 20_000);
1118
1108
 
1119
1109
  test('Should set trial until on membership if trial', async () => {
1120
- // #region arrange
1121
1110
  const date = new Date('2023-05-14');
1122
- jest.useFakeTimers().setSystemTime(date);
1111
+ jest.useFakeTimers({ advanceTimers: true, doNotFake: ['setTimeout', 'clearTimeout', 'hrtime', 'nextTick', 'performance', 'queueMicrotask', 'setImmediate', 'clearImmediate'] }).setSystemTime(date);
1123
1112
 
1124
1113
  try {
1125
1114
  const platformMembershipTypeConfig = PlatformMembershipTypeConfig.create({
@@ -1170,8 +1159,6 @@ describe('E2E.Register', () => {
1170
1159
  items: [
1171
1160
  IDRegisterItem.create({
1172
1161
  id: uuidv4(),
1173
- replaceRegistrationIds: [],
1174
- options: [],
1175
1162
  groupPrice: groupPrice,
1176
1163
  organizationId: organization.id,
1177
1164
  groupId: group.id,
@@ -1179,17 +1166,10 @@ describe('E2E.Register', () => {
1179
1166
  trial: true,
1180
1167
  }),
1181
1168
  ],
1182
- balanceItems: [],
1183
- deleteRegistrationIds: [],
1184
1169
  }),
1185
- administrationFee: 0,
1186
- freeContribution: 0,
1187
- paymentMethod: PaymentMethod.PointOfSale,
1188
1170
  totalPrice: 0,
1189
1171
  asOrganizationId: organization.id,
1190
- customer: null,
1191
1172
  });
1192
- // #endregion
1193
1173
 
1194
1174
  // act and assert
1195
1175
  const familyBefore = await getMemberFamily(member.id, organization, token);
@@ -1218,7 +1198,7 @@ describe('E2E.Register', () => {
1218
1198
  expect(trialUntil!.getDate()).toBe(24);
1219
1199
  }
1220
1200
  finally {
1221
- jest.useFakeTimers().resetAllMocks();
1201
+ jest.useRealTimers().resetAllMocks();
1222
1202
  }
1223
1203
  });
1224
1204
  });