@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,14 +1,14 @@
1
1
  import { Request, Response } from '@simonbackx/simple-endpoints';
2
2
  import { GroupFactory, MemberFactory, Organization, OrganizationFactory, RegistrationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
3
3
  import { AccessRight, BalanceItemWithPayments, ChargeMembersRequest, LimitedFilteredRequest, PermissionLevel, PermissionRoleDetailed, Permissions, PermissionsResourceType, ResourcePermissions, StamhoofdFilter, Version } from '@stamhoofd/structures';
4
- import { TestUtils } from '@stamhoofd/test-utils';
4
+ import { STExpect, TestUtils } from '@stamhoofd/test-utils';
5
5
  import { ChargeMembersEndpoint } from '../../src/endpoints/admin/members/ChargeMembersEndpoint';
6
- import { GetMemberBalanceEndpoint } from '../../src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint';
7
6
  import { testServer } from '../helpers/TestServer';
7
+ import { GetReceivableBalanceEndpoint } from '../../src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint';
8
8
 
9
9
  describe('E2E.ChargeMembers', () => {
10
10
  const chargeMembersEndpoint = new ChargeMembersEndpoint();
11
- const memberBalanceEndpoint = new GetMemberBalanceEndpoint();
11
+ const getReceivableBalanceEndpoint = new GetReceivableBalanceEndpoint();
12
12
  let period: RegistrationPeriod;
13
13
  let organization: Organization;
14
14
  let otherOrganization: Organization;
@@ -27,10 +27,10 @@ describe('E2E.ChargeMembers', () => {
27
27
  return await testServer.test(chargeMembersEndpoint, request);
28
28
  };
29
29
 
30
- const getBalance = async (memberId: string, organization: Organization, token: Token): Promise<Response<BalanceItemWithPayments[]>> => {
31
- const request = Request.buildJson('GET', `/v${Version}/organization/members/${memberId}/balance`, organization.getApiHost());
30
+ const getBalance = async (memberId: string, organization: Organization, token: Token): Promise<BalanceItemWithPayments[]> => {
31
+ const request = Request.buildJson('GET', `/v${Version}/receivable-balances/member/${memberId}`, organization.getApiHost());
32
32
  request.headers.authorization = 'Bearer ' + token.accessToken;
33
- return await testServer.test(memberBalanceEndpoint, request);
33
+ return (await testServer.test(getReceivableBalanceEndpoint, request)).body.balanceItems;
34
34
  };
35
35
 
36
36
  const createUserData = async (permissions: Permissions | null | undefined, roles: PermissionRoleDetailed[]) => {
@@ -149,7 +149,7 @@ describe('E2E.ChargeMembers', () => {
149
149
 
150
150
  await expect(async () => await postCharge(filter, organization, body, token))
151
151
  .rejects
152
- .toThrow('You do not have permissions for this action');
152
+ .toThrow(STExpect.errorWithCode('permission_denied'));
153
153
  }
154
154
  });
155
155
 
@@ -181,10 +181,10 @@ describe('E2E.ChargeMembers', () => {
181
181
  expect(result.body).toBeUndefined();
182
182
 
183
183
  // act and assert
184
- const testBalanceResponse = (response: Response<BalanceItemWithPayments[]>) => {
184
+ const testBalanceResponse = (response: BalanceItemWithPayments[]) => {
185
185
  expect(response).toBeDefined();
186
- expect(response.body.length).toBe(1);
187
- const balanceItem1 = response.body[0];
186
+ expect(response.length).toBe(1);
187
+ const balanceItem1 = response[0];
188
188
  expect(balanceItem1.price).toEqual(12);
189
189
  expect(balanceItem1.amount).toEqual(body.amount);
190
190
  expect(balanceItem1.description).toEqual(body.description);
@@ -245,9 +245,9 @@ describe('E2E.ChargeMembers', () => {
245
245
  expect(result).toBeDefined();
246
246
  expect(result.body).toBeUndefined();
247
247
 
248
- const testBalanceResponse = (response: Response<BalanceItemWithPayments[]>) => {
248
+ const testBalanceResponse = (response: BalanceItemWithPayments[]) => {
249
249
  expect(response).toBeDefined();
250
- expect(response.body.length).toBe(0);
250
+ expect(response.length).toBe(0);
251
251
  };
252
252
 
253
253
  testBalanceResponse(await getBalance(member1.id, otherOrganization, otherFinancialDirectorToken));
@@ -340,8 +340,10 @@ describe('E2E.ChargeMembers', () => {
340
340
  await new RegistrationFactory({ member: member2, group }).create();
341
341
 
342
342
  const filter: StamhoofdFilter = {
343
- id: {
344
- $in: [member1.id, member2.id],
343
+ registrations: {
344
+ $elemMatch: {
345
+ groupId: group.id,
346
+ },
345
347
  },
346
348
  };
347
349
 
@@ -358,16 +360,15 @@ describe('E2E.ChargeMembers', () => {
358
360
  expect(result.body).toBeUndefined();
359
361
 
360
362
  // act and assert
361
- const testBalanceResponse = (response: Response<BalanceItemWithPayments[]>) => {
363
+ const testBalanceResponse = (response: BalanceItemWithPayments[]) => {
362
364
  expect(response).toBeDefined();
363
- expect(response.body.length).toBe(1);
364
- const balanceItem1 = response.body[0];
365
+ expect(response.length).toBe(1);
366
+ const balanceItem1 = response[0];
365
367
  expect(balanceItem1.price).toEqual(12);
366
- expect(balanceItem1.amount).toEqual(body.amount);
367
- expect(balanceItem1.description).toEqual(body.description);
368
+ expect(balanceItem1.amount).toEqual(4);
369
+ expect(balanceItem1.description).toEqual('test description');
368
370
  expect(balanceItem1.organizationId).toEqual(organization.id);
369
- // const dueAt = balanceItem1.dueAt!;
370
- expect(balanceItem1.dueAt).toEqual(body.dueAt);
371
+ expect(balanceItem1.dueAt).toEqual(new Date(2023, 0, 10));
371
372
  expect(balanceItem1.createdAt).toEqual(body.createdAt);
372
373
  };
373
374
 
@@ -408,8 +409,10 @@ describe('E2E.ChargeMembers', () => {
408
409
  await new RegistrationFactory({ member: member2, group }).create();
409
410
 
410
411
  const filter: StamhoofdFilter = {
411
- id: {
412
- $in: [member1.id, member2.id],
412
+ registrations: {
413
+ $elemMatch: {
414
+ groupId: group.id,
415
+ },
413
416
  },
414
417
  };
415
418
 
@@ -423,7 +426,7 @@ describe('E2E.ChargeMembers', () => {
423
426
 
424
427
  await expect(async () => await postCharge(filter, organization, body, token))
425
428
  .rejects
426
- .toThrow('You do not have permissions for this action');
429
+ .toThrow(STExpect.errorWithCode('permission_denied'));
427
430
  });
428
431
  });
429
432
  });
@@ -0,0 +1,398 @@
1
+ import { Request } from '@simonbackx/simple-endpoints';
2
+ import { Document, DocumentTemplateFactory, Group, GroupFactory, Member, MemberFactory, Organization, OrganizationFactory, OrganizationRegistrationPeriodFactory, Registration, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
3
+ import { IDRegisterCart, IDRegisterCheckout, IDRegisterItem, MemberDetails, MemberWithRegistrationsBlob, PaymentMethod } from '@stamhoofd/structures';
4
+ import { TestUtils } from '@stamhoofd/test-utils';
5
+ import { Formatter } from '@stamhoofd/utility';
6
+ import { RegisterMembersEndpoint } from '../../src/endpoints/global/registration/RegisterMembersEndpoint';
7
+ import { patchOrganizationMember } from '../actions/patchOrganizationMember';
8
+ import { markNotPaid, markPaid } from '../actions/patchPaymentStatus';
9
+ import { patchUserMember } from '../actions/patchUserMember';
10
+ import { testServer } from '../helpers/TestServer';
11
+ import { initAdmin } from '../init/initAdmin';
12
+ import { initStripe } from '../init/initStripe';
13
+ import { registrationUpdateQueue } from '../../src/services/BalanceItemService';
14
+
15
+ const baseUrl = `/members/register`;
16
+
17
+ describe('E2E.Documents', () => {
18
+ const endpoint = new RegisterMembersEndpoint();
19
+ let period: RegistrationPeriod;
20
+
21
+ const post = async (body: IDRegisterCheckout, organization: Organization, token: Token) => {
22
+ const request = Request.post({
23
+ path: baseUrl,
24
+ host: organization.getApiHost(),
25
+ body,
26
+ headers: {
27
+ authorization: 'Bearer ' + token.accessToken,
28
+ },
29
+ });
30
+ return await testServer.test(endpoint, request);
31
+ };
32
+
33
+ beforeAll(async () => {
34
+ const previousPeriod = await new RegistrationPeriodFactory({
35
+ startDate: new Date(2022, 0, 1),
36
+ endDate: new Date(2022, 11, 31),
37
+ }).create();
38
+
39
+ period = await new RegistrationPeriodFactory({
40
+ startDate: new Date(2023, 0, 1),
41
+ endDate: new Date(2030, 11, 31),
42
+ previousPeriodId: previousPeriod.id,
43
+ }).create();
44
+ });
45
+
46
+ beforeEach(async () => {
47
+ TestUtils.setEnvironment('userMode', 'platform');
48
+ });
49
+
50
+ afterEach(() => {
51
+ jest.restoreAllMocks();
52
+ });
53
+
54
+ const initOrganization = async (registrationPeriod: RegistrationPeriod = period) => {
55
+ const organization = await new OrganizationFactory({ period: registrationPeriod })
56
+ .create();
57
+
58
+ const organizationRegistrationPeriod = await new OrganizationRegistrationPeriodFactory({ organization, period: registrationPeriod }).create();
59
+
60
+ return { organization, organizationRegistrationPeriod };
61
+ };
62
+
63
+ async function initData(options?: { paidOnly?: boolean; maxAge?: number }) {
64
+ const { organization, organizationRegistrationPeriod } = await initOrganization(period);
65
+
66
+ const user = await new UserFactory({
67
+ organization,
68
+ permissions: null,
69
+ }).create();
70
+
71
+ const token = await Token.createToken(user);
72
+
73
+ const member = await new MemberFactory({ organization, user })
74
+ .create();
75
+
76
+ const group = await new GroupFactory({
77
+ organization,
78
+ price: 25_00,
79
+ }).create();
80
+
81
+ const group2 = await new GroupFactory({
82
+ organization,
83
+ price: 15_00,
84
+ }).create();
85
+
86
+ const documentTemplate = await new DocumentTemplateFactory({
87
+ groups: [
88
+ group,
89
+ group2,
90
+ ],
91
+ minPricePaid: options?.paidOnly ? 1 : null,
92
+ maxAge: options?.maxAge ?? null,
93
+ }).create();
94
+
95
+ return {
96
+ organization,
97
+ organizationRegistrationPeriod,
98
+ user,
99
+ token,
100
+ member,
101
+ group,
102
+ group2,
103
+ documentTemplate,
104
+ };
105
+ }
106
+
107
+ async function assertNoDocument(registration: { id: string }) {
108
+ await registrationUpdateQueue.flushAndWait();
109
+ const document = await Document.select().where('registrationId', registration.id).first(false);
110
+ expect(document).toBeNull();
111
+ }
112
+
113
+ async function assertDocument({ registration, organization, member, group, price, pricePaid }: { registration: { id: string }; organization: Organization; member: Member; group: Group; price: number; pricePaid?: number }) {
114
+ await registrationUpdateQueue.flushAndWait();
115
+ const document = await Document.select().where('registrationId', registration.id).first(false);
116
+ expect(document).not.toBeNull();
117
+
118
+ const html = await document!.getRenderedHtml(organization);
119
+
120
+ const registrationModel = (await Registration.getByID(registration.id))!;
121
+
122
+ const expectations = [
123
+ member.firstName,
124
+ 'Member name using other variable: ' + member.firstName,
125
+ member.lastName,
126
+ group.settings.name.toString(),
127
+ 'Price: ' + Formatter.price(price),
128
+ organization.name,
129
+ Formatter.dateNumber(registrationModel.startDate!, true),
130
+ Formatter.dateNumber(group.settings.endDate, true),
131
+ ];
132
+
133
+ if (pricePaid !== undefined) {
134
+ expectations.push('Price paid: ' + Formatter.price(pricePaid));
135
+ }
136
+
137
+ // Assert some things in the HTML
138
+ for (const expectation of expectations || []) {
139
+ expect(html).toContain(expectation);
140
+ }
141
+
142
+ return document!;
143
+ }
144
+
145
+ test('A document is created when a member registers and pays online', async () => {
146
+ const { organization, group, token, member } = await initData();
147
+ const { stripeMocker } = await initStripe({ organization });
148
+
149
+ // First register the member for group 1. No discount should be applied yet
150
+ const checkout1 = IDRegisterCheckout.create({
151
+ cart: IDRegisterCart.create({
152
+ items: [
153
+ IDRegisterItem.create({
154
+ groupPrice: group.settings.prices[0],
155
+ groupId: group.id,
156
+ organizationId: organization.id,
157
+ memberId: member.id,
158
+ }),
159
+ ],
160
+ }),
161
+ paymentMethod: PaymentMethod.Bancontact,
162
+ totalPrice: 25_00,
163
+ redirectUrl: new URL('https://example.com/redirect'),
164
+ cancelUrl: new URL('https://example.com/cancel'),
165
+ });
166
+
167
+ const response = await post(checkout1, organization, token);
168
+ const registration = response.body.registrations[0];
169
+ await assertNoDocument(registration);
170
+
171
+ // Check no document for this registration yet
172
+ await stripeMocker.succeedPayment(stripeMocker.getLastIntent());
173
+
174
+ const registrationModel = await Registration.getByID(registration.id);
175
+ expect(registrationModel).not.toBeNull();
176
+ expect(registrationModel?.registeredAt).not.toBeNull();
177
+
178
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 25_00 });
179
+ });
180
+
181
+ test('Documents are created for non-paid registrations by default', async () => {
182
+ const { organization, group, token, member } = await initData({ paidOnly: false });
183
+
184
+ // First register the member for group 1. No discount should be applied yet
185
+ const checkout1 = IDRegisterCheckout.create({
186
+ cart: IDRegisterCart.create({
187
+ items: [
188
+ IDRegisterItem.create({
189
+ groupPrice: group.settings.prices[0],
190
+ groupId: group.id,
191
+ organizationId: organization.id,
192
+ memberId: member.id,
193
+ }),
194
+ ],
195
+ }),
196
+ paymentMethod: PaymentMethod.PointOfSale,
197
+ totalPrice: 25_00,
198
+ });
199
+
200
+ const response = await post(checkout1, organization, token);
201
+ const registration = response.body.registrations[0];
202
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 0 });
203
+
204
+ // Mark paid
205
+ await markPaid({ payment: response.body.payment!, organization });
206
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 25_00 });
207
+
208
+ // Mark unpaid
209
+ await markNotPaid({ payment: response.body.payment!, organization });
210
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 0 });
211
+ });
212
+
213
+ test('A paid-only document is only created when a payment is marked as paid', async () => {
214
+ const { organization, group, token, member } = await initData({ paidOnly: true });
215
+
216
+ // First register the member for group 1. No discount should be applied yet
217
+ const checkout1 = IDRegisterCheckout.create({
218
+ cart: IDRegisterCart.create({
219
+ items: [
220
+ IDRegisterItem.create({
221
+ groupPrice: group.settings.prices[0],
222
+ groupId: group.id,
223
+ organizationId: organization.id,
224
+ memberId: member.id,
225
+ }),
226
+ ],
227
+ }),
228
+ paymentMethod: PaymentMethod.PointOfSale,
229
+ totalPrice: 25_00,
230
+ });
231
+
232
+ const response = await post(checkout1, organization, token);
233
+ const registration = response.body.registrations[0];
234
+ await assertNoDocument(registration);
235
+
236
+ await markPaid({ payment: response.body.payment!, organization });
237
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 25_00 });
238
+
239
+ // Should be deleted again
240
+ await markNotPaid({ payment: response.body.payment!, organization });
241
+ await assertNoDocument(registration);
242
+ });
243
+
244
+ test('A maximum age document is not created for older members', async () => {
245
+ const { organization, group, token, member, user } = await initData({ maxAge: 10 });
246
+ member.details.birthDay = new Date(2000, 0, 1); // Make the member older than the maximum age
247
+ await member.save();
248
+
249
+ // First register the member for group 1. No discount should be applied yet
250
+ const checkout1 = IDRegisterCheckout.create({
251
+ cart: IDRegisterCart.create({
252
+ items: [
253
+ IDRegisterItem.create({
254
+ groupPrice: group.settings.prices[0],
255
+ groupId: group.id,
256
+ organizationId: organization.id,
257
+ memberId: member.id,
258
+ }),
259
+ ],
260
+ }),
261
+ paymentMethod: PaymentMethod.PointOfSale,
262
+ totalPrice: 25_00,
263
+ });
264
+
265
+ const response = await post(checkout1, organization, token);
266
+ const registration = response.body.registrations[0];
267
+ await assertNoDocument(registration);
268
+ await markPaid({ payment: response.body.payment!, organization });
269
+ await assertNoDocument(registration);
270
+
271
+ // Change the age of this member via an API endpoint
272
+ await patchOrganizationMember({
273
+ organization,
274
+ patch: MemberWithRegistrationsBlob.patch({
275
+ id: member.id,
276
+ details: MemberDetails.patch({
277
+ birthDay: new Date(),
278
+ }),
279
+ }),
280
+ });
281
+
282
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 25_00 });
283
+ await markNotPaid({ payment: response.body.payment!, organization });
284
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 0 });
285
+
286
+ // Change age again
287
+ await patchOrganizationMember({
288
+ organization,
289
+ patch: MemberWithRegistrationsBlob.patch({
290
+ id: member.id,
291
+ details: MemberDetails.patch({
292
+ birthDay: new Date(2000, 0, 1), // Make the member older than the maximum age
293
+ }),
294
+ }),
295
+ });
296
+
297
+ await assertNoDocument(registration);
298
+
299
+ // Change age as user (different endpoint, but should also have the same side effect)
300
+ await patchUserMember({
301
+ organization,
302
+ user,
303
+ patch: MemberWithRegistrationsBlob.patch({
304
+ id: member.id,
305
+ details: MemberDetails.patch({
306
+ birthDay: new Date(),
307
+ }),
308
+ }),
309
+ });
310
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 0 });
311
+ });
312
+
313
+ test('A document is updated when a registration is moved by an admin', async () => {
314
+ const { organization, group, group2, token, member } = await initData({ paidOnly: false });
315
+ const { adminToken } = await initAdmin({ organization });
316
+
317
+ // First register the member for group 1. No discount should be applied yet
318
+ const checkout1 = IDRegisterCheckout.create({
319
+ cart: IDRegisterCart.create({
320
+ items: [
321
+ IDRegisterItem.create({
322
+ groupPrice: group.settings.prices[0],
323
+ groupId: group.id,
324
+ organizationId: organization.id,
325
+ memberId: member.id,
326
+ }),
327
+ ],
328
+ }),
329
+ paymentMethod: PaymentMethod.PointOfSale,
330
+ totalPrice: 25_00,
331
+ });
332
+
333
+ const response = await post(checkout1, organization, token);
334
+ const registration = response.body.registrations[0];
335
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 0 });
336
+
337
+ // Move
338
+ const checkout2 = IDRegisterCheckout.create({
339
+ cart: IDRegisterCart.create({
340
+ items: [
341
+ IDRegisterItem.create({
342
+ groupPrice: group2.settings.prices[0],
343
+ groupId: group2.id,
344
+ organizationId: organization.id,
345
+ memberId: member.id,
346
+ replaceRegistrationIds: [registration.id],
347
+ }),
348
+ ],
349
+ }),
350
+ paymentMethod: PaymentMethod.PointOfSale,
351
+ asOrganizationId: organization.id,
352
+ totalPrice: -10_00,
353
+ });
354
+
355
+ const response2 = await post(checkout2, organization, adminToken);
356
+ const registration2 = response2.body.registrations[0];
357
+ await assertDocument({ registration: registration2, organization, member, group: group2, price: 15_00, pricePaid: 0 });
358
+ await assertNoDocument(registration);
359
+ });
360
+
361
+ test('A document is deleted when a registration is deleted by an admin', async () => {
362
+ const { organization, group, token, member } = await initData({ paidOnly: false });
363
+ const { adminToken } = await initAdmin({ organization });
364
+
365
+ // First register the member for group 1. No discount should be applied yet
366
+ const checkout1 = IDRegisterCheckout.create({
367
+ cart: IDRegisterCart.create({
368
+ items: [
369
+ IDRegisterItem.create({
370
+ groupPrice: group.settings.prices[0],
371
+ groupId: group.id,
372
+ organizationId: organization.id,
373
+ memberId: member.id,
374
+ }),
375
+ ],
376
+ }),
377
+ paymentMethod: PaymentMethod.PointOfSale,
378
+ totalPrice: 25_00,
379
+ });
380
+
381
+ const response = await post(checkout1, organization, token);
382
+ const registration = response.body.registrations[0];
383
+ await assertDocument({ registration, organization, member, group, price: 25_00, pricePaid: 0 });
384
+
385
+ // Move
386
+ const checkout2 = IDRegisterCheckout.create({
387
+ cart: IDRegisterCart.create({
388
+ deleteRegistrationIds: [registration.id],
389
+ }),
390
+ paymentMethod: PaymentMethod.PointOfSale,
391
+ asOrganizationId: organization.id,
392
+ totalPrice: -25_00,
393
+ });
394
+
395
+ await post(checkout2, organization, adminToken);
396
+ await assertNoDocument(registration);
397
+ });
398
+ });