@stamhoofd/backend 2.90.2 → 2.91.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 (35) hide show
  1. package/package.json +10 -10
  2. package/src/audit-logs/ModelLogger.ts +0 -1
  3. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +14 -0
  4. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +2 -0
  5. package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -3
  6. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +6 -4
  7. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -7
  8. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +112 -105
  9. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
  10. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +5 -5
  11. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +1 -1
  12. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +1 -1
  13. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
  14. package/src/helpers/AdminPermissionChecker.ts +0 -5
  15. package/src/helpers/FlagMomentCleanup.ts +13 -1
  16. package/src/helpers/GroupedThrottledQueue.ts +5 -3
  17. package/src/helpers/PeriodHelper.ts +10 -137
  18. package/src/helpers/SetupStepUpdater.ts +54 -7
  19. package/src/helpers/UitpasTokenRepository.ts +3 -3
  20. package/src/seeds/1750090030-records-configuration.ts +5 -1
  21. package/src/services/BalanceItemService.ts +12 -7
  22. package/src/services/DocumentService.ts +0 -1
  23. package/src/services/RegistrationService.ts +30 -1
  24. package/src/services/uitpas/UitpasService.ts +1 -1
  25. package/src/services/uitpas/cancelTicketSales.ts +1 -1
  26. package/src/services/uitpas/checkPermissionsFor.ts +9 -9
  27. package/src/services/uitpas/checkUitpasNumbers.ts +2 -2
  28. package/src/services/uitpas/getSocialTariffForEvent.ts +4 -4
  29. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +5 -5
  30. package/src/services/uitpas/registerTicketSales.ts +4 -4
  31. package/src/services/uitpas/searchUitpasEvents.ts +3 -3
  32. package/src/services/uitpas/searchUitpasOrganizers.ts +3 -3
  33. package/src/sql-filters/members.ts +1 -1
  34. package/src/sql-filters/organizations.ts +52 -0
  35. package/tests/e2e/register.test.ts +1 -1
@@ -1,11 +1,10 @@
1
1
  import { SimpleError } from '@simonbackx/simple-errors';
2
- import { Group, Member, MemberResponsibilityRecord, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from '@stamhoofd/models';
2
+ import { Group, Organization, OrganizationRegistrationPeriod, RegistrationPeriod } from '@stamhoofd/models';
3
3
  import { QueueHandler } from '@stamhoofd/queues';
4
- import { AuditLogSource, Group as GroupStruct, PermissionLevel } from '@stamhoofd/structures';
4
+ import { AuditLogSource, Group as GroupStruct } from '@stamhoofd/structures';
5
5
  import { PatchOrganizationRegistrationPeriodsEndpoint } from '../endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint';
6
- import { AuthenticatedStructures } from './AuthenticatedStructures';
7
- import { MemberUserSyncer } from './MemberUserSyncer';
8
6
  import { AuditLogService } from '../services/AuditLogService';
7
+ import { AuthenticatedStructures } from './AuthenticatedStructures';
9
8
  import { SetupStepUpdater } from './SetupStepUpdater';
10
9
 
11
10
  export class PeriodHelper {
@@ -18,88 +17,6 @@ export class PeriodHelper {
18
17
  });
19
18
  }
20
19
 
21
- static async stopAllResponsibilities() {
22
- console.log('Stopping all responsibilities');
23
- const platform = await Platform.getSharedPrivateStruct();
24
- const keepPlatformResponsibilityIds = platform.config.responsibilities.filter(r => !r.organizationBased).map(r => r.id);
25
- const keepResponsibilityIds = platform.config.responsibilities.filter(r => !r.organizationBased || r.permissions?.level === PermissionLevel.Full).map(r => r.id);
26
- const batchSize = 100;
27
-
28
- let lastId = '';
29
- let c = 0;
30
-
31
- while (true) {
32
- const records = await MemberResponsibilityRecord.where(
33
- {
34
- id: { sign: '>', value: lastId },
35
- endDate: null,
36
- },
37
- {
38
- limit: batchSize,
39
- sort: ['id'],
40
- },
41
- );
42
-
43
- for (const record of records) {
44
- lastId = record.id;
45
-
46
- const invalid = keepPlatformResponsibilityIds.includes(record.responsibilityId) && record.organizationId;
47
-
48
- if (!keepResponsibilityIds.includes(record.responsibilityId) || invalid) {
49
- record.endDate = new Date();
50
- await record.save();
51
- c++;
52
- }
53
- }
54
-
55
- if (records.length < batchSize) {
56
- break;
57
- }
58
- }
59
-
60
- console.log('Done: stopped all responsibilities: ' + c);
61
- }
62
-
63
- static async syncAllMemberUsers() {
64
- console.log('Syncing all members');
65
-
66
- let c = 0;
67
- let lastId: string = '';
68
-
69
- while (true) {
70
- const rawMembers = await Member.where({
71
- id: {
72
- value: lastId,
73
- sign: '>',
74
- },
75
- }, { limit: 500, sort: ['id'] });
76
-
77
- if (rawMembers.length === 0) {
78
- break;
79
- }
80
-
81
- const membersWithRegistrations = await Member.getBlobByIds(...rawMembers.map(m => m.id));
82
-
83
- const promises: Promise<any>[] = [];
84
-
85
- for (const memberWithRegistrations of membersWithRegistrations) {
86
- promises.push((async () => {
87
- await MemberUserSyncer.onChangeMember(memberWithRegistrations);
88
- c++;
89
-
90
- if (c % 10000 === 0) {
91
- console.log('Synced ' + c + ' members');
92
- }
93
- })());
94
- }
95
-
96
- await Promise.all(promises);
97
- lastId = rawMembers[rawMembers.length - 1].id;
98
- }
99
-
100
- console.log('Done: synced all members: ' + c);
101
- }
102
-
103
20
  static async createOrganizationPeriodForPeriod(organization: Organization, period: RegistrationPeriod) {
104
21
  const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: period.id, organizationId: organization.id }, { limit: 1 });
105
22
 
@@ -128,38 +45,15 @@ export class PeriodHelper {
128
45
  });
129
46
  }
130
47
 
131
- const batchSize = 10;
132
48
  await QueueHandler.schedule(tag, async () => {
133
- let lastId = '';
134
-
135
- while (true) {
136
- const organizations = await Organization.where(
137
- {
138
- id: { sign: '>', value: lastId },
139
- },
140
- {
141
- limit: batchSize,
142
- sort: ['id'],
143
- },
144
- );
145
-
146
- for (const organization of organizations) {
147
- try {
148
- await this.moveOrganizationToPeriod(organization, period);
149
- }
150
- catch (error) {
151
- console.error('Error moving organization to period', organization.id, error);
152
- }
153
- lastId = organization.id;
49
+ for await (const organization of Organization.select().all()) {
50
+ try {
51
+ await this.moveOrganizationToPeriod(organization, period);
154
52
  }
155
-
156
- if (organizations.length < batchSize) {
157
- break;
53
+ catch (error) {
54
+ console.error('Error moving organization to period', organization.id, error);
158
55
  }
159
56
  }
160
-
161
- await this.stopAllResponsibilities();
162
- await this.syncAllMemberUsers();
163
57
  });
164
58
 
165
59
  // When done: update setup steps
@@ -175,31 +69,10 @@ export class PeriodHelper {
175
69
 
176
70
  console.log(tag);
177
71
 
178
- const batchSize = 100;
179
72
  await QueueHandler.schedule(tag, async () => {
180
73
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
181
- let lastId = '';
182
-
183
- while (true) {
184
- const groups = await Group.where(
185
- {
186
- id: { sign: '>', value: lastId },
187
- periodId: period.id,
188
- },
189
- {
190
- limit: batchSize,
191
- sort: ['id'],
192
- },
193
- );
194
-
195
- for (const group of groups) {
196
- await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: group.id }), period);
197
- lastId = group.id;
198
- }
199
-
200
- if (groups.length < batchSize) {
201
- break;
202
- }
74
+ for await (const group of Group.select().where('periodId', period.id).all()) {
75
+ await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: group.id }), period);
203
76
  }
204
77
  });
205
78
  });
@@ -7,6 +7,7 @@ import {
7
7
  Organization,
8
8
  OrganizationRegistrationPeriod,
9
9
  Platform,
10
+ Registration,
10
11
  } from '@stamhoofd/models';
11
12
  import { QueueHandler } from '@stamhoofd/queues';
12
13
  import { SQL, SQLWhereSign } from '@stamhoofd/sql';
@@ -15,11 +16,14 @@ import {
15
16
  GroupType,
16
17
  MemberResponsibility,
17
18
  Platform as PlatformStruct,
19
+ RecordCategory,
18
20
  SetupStepType,
19
21
  SetupSteps,
20
22
  } from '@stamhoofd/structures';
21
23
  import { Formatter } from '@stamhoofd/utility';
22
24
  import { AuditLogService } from '../services/AuditLogService';
25
+ import { GroupedThrottledQueue } from './GroupedThrottledQueue';
26
+ import { AuthenticatedStructures } from './AuthenticatedStructures';
23
27
 
24
28
  type SetupStepOperation = (setupSteps: SetupSteps, organization: Organization, platform: PlatformStruct) => void | Promise<void>;
25
29
 
@@ -81,10 +85,25 @@ export class SetupStepUpdater {
81
85
  if (before !== after) {
82
86
  console.log('Updating setups steps for organization', event.model.organizationId, 'because of change in members in group', event.model.id, 'from', before, 'to', after, '(limited)');
83
87
  // We need to do a recalculation
84
- SetupStepUpdater.updateForOrganizationId(event.model.organizationId)
88
+ SetupStepUpdater.updateForOrganizationId(event.model.organizationId, { types: [SetupStepType.Registrations] })
85
89
  .catch(console.error);
86
90
  }
87
91
  });
92
+
93
+ const updateResponsibilitiesQueue = new GroupedThrottledQueue(async (organizationId: string) => {
94
+ console.log('(delayed) Updating setups steps for organization', organizationId, 'because of change in registrations');
95
+ SetupStepUpdater.updateForOrganizationId(organizationId, { types: [SetupStepType.Responsibilities] }).catch(console.error);
96
+ }, { maxDelay: 3_000 });
97
+
98
+ Model.modelEventBus.addListener({}, async (event) => {
99
+ if (!(event.model instanceof Registration)) {
100
+ return;
101
+ }
102
+
103
+ if ((event.type === 'updated' && ('registeredAt' in event.changedFields || 'deactivatedAt' in event.changedFields)) || (event.type === 'created' && event.model.registeredAt)) {
104
+ updateResponsibilitiesQueue.addItem(event.model.organizationId, 1);
105
+ }
106
+ });
88
107
  }
89
108
 
90
109
  private static readonly STEP_TYPE_OPERATIONS: Record<
@@ -170,7 +189,7 @@ export class SetupStepUpdater {
170
189
  });
171
190
  }
172
191
 
173
- static async updateForOrganizationId(id: string) {
192
+ static async updateForOrganizationId(id: string, options?: { types?: SetupStepType[] }) {
174
193
  const organization = await Organization.getByID(id);
175
194
  if (!organization) {
176
195
  throw new SimpleError({
@@ -180,7 +199,7 @@ export class SetupStepUpdater {
180
199
  });
181
200
  }
182
201
 
183
- await this.updateForOrganization(organization);
202
+ await this.updateForOrganization(organization, options);
184
203
  }
185
204
 
186
205
  static async updateForOrganization(
@@ -188,9 +207,11 @@ export class SetupStepUpdater {
188
207
  {
189
208
  platform,
190
209
  organizationRegistrationPeriod,
210
+ types,
191
211
  }: {
192
212
  platform?: PlatformStruct;
193
213
  organizationRegistrationPeriod?: OrganizationRegistrationPeriod;
214
+ types?: SetupStepType[];
194
215
  } = {},
195
216
  ) {
196
217
  if (!platform) {
@@ -222,6 +243,7 @@ export class SetupStepUpdater {
222
243
  organizationRegistrationPeriod,
223
244
  platform,
224
245
  organization,
246
+ types,
225
247
  );
226
248
  }
227
249
 
@@ -229,12 +251,13 @@ export class SetupStepUpdater {
229
251
  organizationRegistrationPeriod: OrganizationRegistrationPeriod,
230
252
  platform: PlatformStruct,
231
253
  organization: Organization,
254
+ types?: SetupStepType[],
232
255
  ) {
233
256
  console.log('Updating setup steps for organization', organization.id);
234
257
  const setupSteps = organizationRegistrationPeriod.setupSteps;
235
258
 
236
259
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
237
- for (const stepType of Object.values(SetupStepType)) {
260
+ for (const stepType of types ?? Object.values(SetupStepType)) {
238
261
  const operation = this.STEP_TYPE_OPERATIONS[stepType];
239
262
  await operation(setupSteps, organization, platform);
240
263
  }
@@ -309,15 +332,28 @@ export class SetupStepUpdater {
309
332
  private static updateStepCompanies(
310
333
  setupSteps: SetupSteps,
311
334
  organization: Organization,
312
- _platform: PlatformStruct,
335
+ platform: PlatformStruct,
313
336
  ) {
314
337
  const totalSteps = 1;
315
338
  let finishedSteps = 0;
316
339
 
317
340
  if (organization.meta.companies.length) {
318
341
  finishedSteps = 1;
342
+
343
+ try {
344
+ RecordCategory.validate(
345
+ platform.config.organizationLevelRecordsConfiguration.recordCategories,
346
+ organization.getBaseStructureWithPrivateMeta(), // private data needed for answers
347
+ );
348
+ }
349
+ catch (error) {
350
+ console.error('Error validating record categories for organization', organization.id, error);
351
+ finishedSteps = 0;
352
+ }
319
353
  }
320
354
 
355
+ console.log('Updating companies step for organization', organization.id, 'with total steps', totalSteps, 'and finished steps', finishedSteps);
356
+
321
357
  setupSteps.update(SetupStepType.Companies, {
322
358
  totalSteps,
323
359
  finishedSteps,
@@ -346,11 +382,14 @@ export class SetupStepUpdater {
346
382
  // Remove invalid responsibilities: members that are not registered in the current period
347
383
  const memberIds = Formatter.uniqueArray(allRecords.map(r => r.memberId));
348
384
  const members = await Member.getBlobByIds(...memberIds);
349
- const validMembers = members.filter(m => m.registrations.some(r => r.organizationId === organization.id && r.periodId === organization.periodId && r.group.type === GroupType.Membership && r.deactivatedAt === null && r.registeredAt !== null));
385
+ const validMembers = members.filter(m => m.registrations.some(r => r.organizationId === organization.id && r.periodId === organization.periodId && r.group.defaultAgeGroupId && r.group.type === GroupType.Membership && r.deactivatedAt === null && r.registeredAt !== null));
386
+ const invalidMembers = members.filter(m => !m.registrations.some(r => r.organizationId === organization.id && r.periodId === organization.periodId && r.group.defaultAgeGroupId && r.group.type === GroupType.Membership && r.deactivatedAt === null && r.registeredAt !== null));
350
387
 
351
388
  const validMembersIds = validMembers.map(m => m.id);
389
+ const invalidMembersIds = invalidMembers.map(m => m.id);
352
390
 
353
391
  const records = allRecords.filter(r => validMembersIds.includes(r.memberId));
392
+ const invalidRecords = allRecords.filter(r => invalidMembersIds.includes(r.memberId));
354
393
 
355
394
  let totalSteps = 0;
356
395
  let finishedSteps = 0;
@@ -381,6 +420,13 @@ export class SetupStepUpdater {
381
420
  for (const { responsibility, group } of flatResponsibilities) {
382
421
  const { minimumMembers: min, maximumMembers: max } = responsibility;
383
422
 
423
+ const invalidMembersWithThisResponsibility = !!invalidRecords.find(r => r.responsibilityId === responsibility.id && (group ? r.groupId === group.id : true));
424
+ if (invalidMembersWithThisResponsibility) {
425
+ totalSteps += Math.max(min ?? 1, 1);
426
+ // Step not okay: we have an invalid member with this responsibility
427
+ continue;
428
+ }
429
+
384
430
  if (min === null) {
385
431
  continue;
386
432
  }
@@ -406,13 +452,14 @@ export class SetupStepUpdater {
406
452
  }
407
453
 
408
454
  if (max !== null && totalRecordsWithThisResponsibility > max) {
409
- // Not added
455
+ // Not added (not okay)
410
456
  continue;
411
457
  }
412
458
 
413
459
  finishedSteps += Math.min(min, totalRecordsWithThisResponsibility);
414
460
  }
415
461
 
462
+ console.log('Updating responsibilities step for organization', organization.id, 'with total steps', totalSteps, 'and finished steps', finishedSteps);
416
463
  setupSteps.update(SetupStepType.Responsibilities, {
417
464
  totalSteps,
418
465
  finishedSteps,
@@ -83,7 +83,7 @@ export class UitpasTokenRepository {
83
83
  throw new SimpleError({
84
84
  code: 'uitpas_api_not_configured_for_platform',
85
85
  message: 'UiTPAS api is not configured for the platform',
86
- human: $t('UiTPAS is niet volledig geconfigureerd, contacteer de platformbeheerder.'),
86
+ human: $t('71a8218b-c58e-4e95-9626-551b80eb8367'),
87
87
  });
88
88
  }
89
89
  model = new UitpasClientCredential();
@@ -123,7 +123,7 @@ export class UitpasTokenRepository {
123
123
  throw new SimpleError({
124
124
  code: 'invalid_uitpas_client_credentials',
125
125
  message: `Invalid UiTPAS client credentials`,
126
- human: $t(`De opgegeven UiTPAS client credentials zijn ongeldig. Controleer ze en probeer opnieuw.`),
126
+ human: $t(`1086bb24-5df4-4faf-9dc0-ab5a955b0d8f`),
127
127
  });
128
128
  }
129
129
  console.error(`Unsuccessful response when fetching UiTPAS token for organization with id ${this.uitpasClientCredential.organizationId}:`, response.statusText);
@@ -193,7 +193,7 @@ export class UitpasTokenRepository {
193
193
  throw new SimpleError({
194
194
  code: 'uitpas_api_not_configured_for_this_organization',
195
195
  message: `UiTPAS api not configured for organization with id ${organizationId}`,
196
- human: $t('UiTPAS is nog niet volledig geconfigureerd voor deze organisatie.'),
196
+ human: $t('6b333cb4-21fd-42be-b0c9-6d899f2fd348'),
197
197
  });
198
198
  }
199
199
  repo = UitpasTokenRepository.setRepoInMemory(organizationId, new UitpasTokenRepository(model)); // store in memory
@@ -1,10 +1,14 @@
1
1
  import { Migration } from '@simonbackx/simple-database';
2
- import { Organization } from '@stamhoofd/models';
2
+ import { Organization, Webshop } from '@stamhoofd/models';
3
3
 
4
4
  export async function startRecordsConfigurationMigration() {
5
5
  for await (const organization of Organization.select().all()) {
6
6
  await organization.save();
7
7
  }
8
+
9
+ for await (const webshop of Webshop.select().all()) {
10
+ await webshop.save();
11
+ }
8
12
  }
9
13
 
10
14
  export default new Migration(async () => {
@@ -1,12 +1,11 @@
1
+ import { Model } from '@simonbackx/simple-database';
1
2
  import { BalanceItem, CachedBalance, Document, MemberUser, Order, Organization, Payment, Webshop } from '@stamhoofd/models';
2
- import { AuditLogSource, BalanceItemStatus, BalanceItemType, OrderStatus, ReceivableBalanceType } from '@stamhoofd/structures';
3
- import { Formatter } from '@stamhoofd/utility';
3
+ import { AuditLogSource, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentStatus, ReceivableBalanceType } from '@stamhoofd/structures';
4
+ import { GroupedThrottledQueue } from '../helpers/GroupedThrottledQueue';
5
+ import { ThrottledQueue } from '../helpers/ThrottledQueue';
4
6
  import { AuditLogService } from './AuditLogService';
5
7
  import { PaymentReallocationService } from './PaymentReallocationService';
6
8
  import { RegistrationService } from './RegistrationService';
7
- import { Model } from '@simonbackx/simple-database';
8
- import { GroupedThrottledQueue } from '../helpers/GroupedThrottledQueue';
9
- import { ThrottledQueue } from '../helpers/ThrottledQueue';
10
9
 
11
10
  const memberUpdateQueue = new GroupedThrottledQueue(async (organizationId: string, memberIds: string[]) => {
12
11
  await CachedBalance.updateForMembers(organizationId, memberIds);
@@ -210,6 +209,12 @@ export const BalanceItemService = {
210
209
  if (shouldMarkUpdated) {
211
210
  await this.markUpdated(balanceItem, payment, organization);
212
211
  }
212
+
213
+ if (balanceItem.registrationId) {
214
+ if (balanceItem.type === BalanceItemType.Registration && !!payment && payment.status === PaymentStatus.Succeeded) {
215
+ await RegistrationService.markRepeatedPaid(balanceItem.registrationId);
216
+ }
217
+ }
213
218
  return;
214
219
  }
215
220
 
@@ -220,7 +225,7 @@ export const BalanceItemService = {
220
225
  // If registration
221
226
  if (balanceItem.registrationId) {
222
227
  if (balanceItem.type === BalanceItemType.Registration) {
223
- await RegistrationService.markValid(balanceItem.registrationId);
228
+ await RegistrationService.markValid(balanceItem.registrationId, { paid: !!payment && payment.status === PaymentStatus.Succeeded });
224
229
  }
225
230
  }
226
231
 
@@ -228,7 +233,7 @@ export const BalanceItemService = {
228
233
  await balanceItem.save();
229
234
  },
230
235
 
231
- async markUpdated(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
236
+ async markUpdated(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
232
237
  // For orders: mark order as changed (so they are refetched in front ends)
233
238
  if (balanceItem.orderId) {
234
239
  await AuditLogService.setContext({ source: AuditLogSource.Payment }, async () => {
@@ -27,7 +27,6 @@ export class DocumentService {
27
27
  const newFields = getGroupFieldsAffectingDocuments(event.model);
28
28
 
29
29
  if (JSON.stringify(oldFields) === JSON.stringify(newFields)) {
30
- console.log('Group changes cannot affect documents');
31
30
  return;
32
31
  }
33
32
 
@@ -9,16 +9,45 @@ import { Formatter } from '@stamhoofd/utility';
9
9
  import { encodeObject } from '@simonbackx/simple-encoding';
10
10
 
11
11
  export const RegistrationService = {
12
- async markValid(registrationId: string) {
12
+ /**
13
+ * If the registration was marked valid, and later paid, we'll still shorten the trail until period
14
+ */
15
+ async markRepeatedPaid(registrationId: string) {
16
+ const registration = await Registration.getByID(registrationId);
17
+ if (!registration) {
18
+ throw new Error('Registration not found');
19
+ }
20
+
21
+ if (registration.registeredAt !== null && registration.deactivatedAt === null) {
22
+ // Already valid
23
+ if (registration.trialUntil && registration.trialUntil > new Date()) {
24
+ registration.trialUntil = new Date();
25
+ await registration.save();
26
+ await PlatformMembershipService.updateMembershipsForId(registration.memberId);
27
+ }
28
+ }
29
+ // do nothing: possible that we canceled the registration and don't want to reactivate it when we mark something paid
30
+ },
31
+
32
+ async markValid(registrationId: string, options?: { paid?: boolean }) {
13
33
  const registration = await Registration.getByID(registrationId);
14
34
  if (!registration) {
15
35
  throw new Error('Registration not found');
16
36
  }
17
37
 
18
38
  if (registration.registeredAt !== null && registration.deactivatedAt === null) {
39
+ // Already valid
40
+ if (options?.paid && registration.trialUntil && registration.trialUntil > new Date()) {
41
+ registration.trialUntil = new Date();
42
+ await registration.save();
43
+ await PlatformMembershipService.updateMembershipsForId(registration.memberId);
44
+ }
19
45
  return false;
20
46
  }
21
47
 
48
+ if (options?.paid && registration.trialUntil && registration.trialUntil > new Date()) {
49
+ registration.trialUntil = new Date();
50
+ }
22
51
  registration.reservedUntil = null;
23
52
  registration.registeredAt = registration.registeredAt ?? new Date();
24
53
  registration.deactivatedAt = null;
@@ -77,7 +77,7 @@ function getUitpasTicketSales(order: Order): UitpasTicketSale[] {
77
77
  throw new SimpleError({
78
78
  code: 'missing_uitpas_base_product_price',
79
79
  message: `Missing UiTPAS base product price`,
80
- human: $t(`Er is een fout opgetreden bij het registreren van de UiTPAS ticket verkoop. Probeer het later opnieuw.`),
80
+ human: $t(`f7eea411-fb92-458f-bf3e-e36ed870591b`),
81
81
  });
82
82
  }
83
83
  const label = makeBaseProductPriceLabel(item.product, baseProductPrice);
@@ -24,7 +24,7 @@ async function cancelTicketSale(access_token: string, ticketSaleId: string) {
24
24
  throw new SimpleError({
25
25
  code: 'unsuccessful_response_registering_ticket_sales',
26
26
  message: `Unsuccessful response when registering UiTPAS ticket sales`,
27
- human: $t(`Er is een fout opgetreden bij het verbinden met UiTPAS. Probeer het later opnieuw.`),
27
+ human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
28
28
  });
29
29
  }
30
30
  return ticketSaleId;
@@ -16,7 +16,7 @@ function assertIsPermissionsResponse(json: unknown): asserts json is Permissions
16
16
  throw new SimpleError({
17
17
  code: 'invalid_permissions_response',
18
18
  message: 'Invalid response format for permissions',
19
- human: $t('Er is iets misgelopen bij het ophalen van je rechten. Probeer het later opnieuw.'),
19
+ human: $t('7d3a6b57-f81a-4d58-bc2b-babb2261c40b'),
20
20
  });
21
21
  }
22
22
 
@@ -35,7 +35,7 @@ function assertIsPermissionsResponse(json: unknown): asserts json is Permissions
35
35
  throw new SimpleError({
36
36
  code: 'invalid_permissions_response',
37
37
  message: 'Invalid response format for permissions',
38
- human: $t('Er is iets misgelopen bij het ophalen van je rechten. Probeer het later opnieuw.'),
38
+ human: $t('7d3a6b57-f81a-4d58-bc2b-babb2261c40b'),
39
39
  });
40
40
  }
41
41
  }
@@ -54,7 +54,7 @@ export async function checkPermissionsFor(access_token: string, organizationId:
54
54
  throw new SimpleError({
55
55
  code: 'uitpas_unreachable_checking_permissions',
56
56
  message: `Network issue when checking UiTPAS permissions`,
57
- human: $t(`We konden UiTPAS niet bereiken om de rechten te controleren. Probeer het later opnieuw.`),
57
+ human: $t(`542b793c-3edf-4505-b33d-199ea409bbda`),
58
58
  });
59
59
  });
60
60
  if (!response.ok) {
@@ -62,7 +62,7 @@ export async function checkPermissionsFor(access_token: string, organizationId:
62
62
  throw new SimpleError({
63
63
  code: 'unsuccessful_response_checking_permissions',
64
64
  message: `Unsuccesful response when checking UiTPAS permissions`,
65
- human: $t(`Er is een fout opgetreden bij het verbinden met UiTPAS. Probeer het later opnieuw.`),
65
+ human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
66
66
  });
67
67
  }
68
68
  const json = await response.json().catch(() => {
@@ -70,7 +70,7 @@ export async function checkPermissionsFor(access_token: string, organizationId:
70
70
  throw new SimpleError({
71
71
  code: 'invalid_json_checking_permissions',
72
72
  message: `Invalid json when checking UiTPAS permissions`,
73
- human: $t(`Er is een fout opgetreden bij het communiceren met UiTPAS. Probeer het later opnieuw.`),
73
+ human: $t(`93004d03-955a-4a9a-937d-2f30841dc5cc`),
74
74
  });
75
75
  });
76
76
  assertIsPermissionsResponse(json);
@@ -91,18 +91,18 @@ export async function checkPermissionsFor(access_token: string, organizationId:
91
91
  }];
92
92
  const item = json.find(item => item.organizer.id === uitpasOrganizerId);
93
93
  if (!item) {
94
- const organizers = Formatter.joinLast(json.map(i => i.organizer.name), ', ', ' ' + $t(' en ') + ' ');
94
+ const organizers = Formatter.joinLast(json.map(i => i.organizer.name), ', ', ' ' + $t('6d35156d-e452-4b0f-80f4-b1e9024d08ee') + ' ');
95
95
  return {
96
96
  status: UitpasClientCredentialsStatus.NoPermissions,
97
- human: $t('Jouw UiTPAS-integratie heeft geen toegansrechten tot de geselecteerde UiTPAS-organisator, maar wel tot ') + organizers,
97
+ human: $t('96c8a719-dba5-47ce-bb61-ee0754a5f776') + organizers,
98
98
  };
99
99
  }
100
100
  const missingPermissions = neededPermissions.filter(needed => !item.permissions.includes(needed.permission));
101
101
  if (missingPermissions.length > 0) {
102
- const missingPermissionsHuman = Formatter.joinLast(missingPermissions.map(p => p.human), ', ', ' ' + $t(' en ') + ' ');
102
+ const missingPermissionsHuman = Formatter.joinLast(missingPermissions.map(p => p.human), ', ', ' ' + $t('6d35156d-e452-4b0f-80f4-b1e9024d08ee') + ' ');
103
103
  return {
104
104
  status: UitpasClientCredentialsStatus.MissingPermissions,
105
- human: $t('Jouw UiTPAS-integratie mist de volgende toegangsrechten voor de geselecteerde UiTPAS-organisator: ') + missingPermissionsHuman,
105
+ human: $t('040fa935-5cbc-4a85-b578-354bf9d7fc04') + missingPermissionsHuman,
106
106
  };
107
107
  }
108
108
  return {
@@ -36,7 +36,7 @@ function assertIsUitpasNumberSuccessfulResponse(
36
36
  throw new SimpleError({
37
37
  code: 'invalid_response_retrieving_pass_by_uitpas_number',
38
38
  message: `Invalid response when retrieving pass by UiTPAS number`,
39
- human: $t(`Er is een fout opgetreden bij het ophalen van je UiTPAS. Kijk je het nummer even na?`),
39
+ human: $t(`4c6482ff-e6d9-4ea1-b11d-e12d697b4b7b`),
40
40
  });
41
41
  }
42
42
  }
@@ -110,7 +110,7 @@ async function checkUitpasNumber(access_token: string, uitpasNumber: string) {
110
110
  throw new SimpleError({
111
111
  code: 'unsuccessful_and_unexpected_response_retrieving_pass_by_uitpas_number',
112
112
  message: `Unsuccesful response without message when retrieving pass by UiTPAS number`,
113
- human: $t(`Er is een fout opgetreden bij het ophalen van je UiTPAS. Kijk je het nummer even na?`),
113
+ human: $t(`4c6482ff-e6d9-4ea1-b11d-e12d697b4b7b`),
114
114
  });
115
115
  }
116
116
 
@@ -21,7 +21,7 @@ function assertsIsStaticSocialTariffResponse(json: unknown): asserts json is Sta
21
21
  throw new SimpleError({
22
22
  code: 'invalid_response_getting_static_uitpas_social_tariff',
23
23
  message: `Invalid response when getting static UiTPAS social tariff`,
24
- human: $t(`Er is een fout opgetreden bij het ophalen van het kansentarief voor dit evenement. Probeer het later opnieuw.`),
24
+ human: $t(`a56854af-464a-45a4-8c39-10a9264b6ce4`),
25
25
  });
26
26
  }
27
27
  }
@@ -35,7 +35,7 @@ export async function getSocialTariffForEvent(access_token: string, basePrice: n
35
35
  throw new SimpleError({
36
36
  code: 'invalid_uitpas_event_url',
37
37
  message: `Invalid UiTPAS event URL: ${uitpasEventUrl}`,
38
- human: $t(`De opgegeven UiTPAS-evenement URL is ongeldig.`),
38
+ human: $t(`85fb6e02-9b69-43cc-acf7-96a576461560`),
39
39
  });
40
40
  }
41
41
  params.append('eventId', eventId);
@@ -61,7 +61,7 @@ export async function getSocialTariffForEvent(access_token: string, basePrice: n
61
61
  throw new SimpleError({
62
62
  code: 'unsuccessful_response_getting_static_uitpas_social_tariff',
63
63
  message: `Unsuccessful response when getting static UiTPAS social tariff`,
64
- human: $t(`Er is een fout opgetreden bij het verbinden met UiTPAS. Probeer het later opnieuw.`),
64
+ human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
65
65
  });
66
66
  }
67
67
  const json = await response.json().catch(() => {
@@ -80,7 +80,7 @@ export async function getSocialTariffForEvent(access_token: string, basePrice: n
80
80
  throw new SimpleError({
81
81
  code: 'no_social_tariff_available',
82
82
  message: `No social tariff available for event ${eventId}`,
83
- human: $t(`Er is geen kansentarief beschikbaar voor dit evenement.`),
83
+ human: $t(`ccd8e8b4-01a7-4e7c-8ae0-92d2a4c659eb`),
84
84
  });
85
85
  }
86
86
  if (json.available.length > 1) {