@stamhoofd/backend 2.90.3 → 2.92.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 (47) hide show
  1. package/package.json +10 -10
  2. package/src/audit-logs/EmailLogger.ts +4 -4
  3. package/src/audit-logs/ModelLogger.ts +0 -1
  4. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +20 -0
  5. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +2 -0
  6. package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -7
  7. package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +207 -0
  8. package/src/endpoints/global/email/GetEmailEndpoint.ts +5 -1
  9. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +404 -8
  10. package/src/endpoints/global/email/PatchEmailEndpoint.ts +67 -22
  11. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +6 -4
  12. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -7
  13. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +112 -105
  14. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
  15. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +5 -5
  16. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +1 -1
  17. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +10 -1
  18. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +8 -1
  19. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +1 -1
  20. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -67
  21. package/src/helpers/AdminPermissionChecker.ts +81 -10
  22. package/src/helpers/FlagMomentCleanup.ts +13 -1
  23. package/src/helpers/GroupedThrottledQueue.ts +5 -3
  24. package/src/helpers/PeriodHelper.ts +10 -137
  25. package/src/helpers/SetupStepUpdater.ts +54 -7
  26. package/src/helpers/UitpasTokenRepository.ts +3 -3
  27. package/src/seeds/1750090030-records-configuration.ts +5 -1
  28. package/src/seeds/1752848560-groups-registration-periods.ts +768 -0
  29. package/src/seeds/1755181288-remove-duplicate-members.ts +145 -0
  30. package/src/seeds/1755532883-update-email-sender-ids.ts +47 -0
  31. package/src/services/BalanceItemService.ts +12 -7
  32. package/src/services/DocumentService.ts +0 -1
  33. package/src/services/RegistrationService.ts +30 -1
  34. package/src/services/uitpas/UitpasService.ts +72 -3
  35. package/src/services/uitpas/cancelTicketSales.ts +1 -1
  36. package/src/services/uitpas/checkPermissionsFor.ts +9 -9
  37. package/src/services/uitpas/checkUitpasNumbers.ts +3 -2
  38. package/src/services/uitpas/getSocialTariffForEvent.ts +4 -4
  39. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +5 -5
  40. package/src/services/uitpas/registerTicketSales.ts +4 -4
  41. package/src/services/uitpas/searchUitpasEvents.ts +3 -3
  42. package/src/services/uitpas/searchUitpasOrganizers.ts +3 -3
  43. package/src/sql-filters/emails.ts +65 -0
  44. package/src/sql-filters/members.ts +1 -1
  45. package/src/sql-filters/organizations.ts +52 -0
  46. package/src/sql-sorters/emails.ts +47 -0
  47. package/tests/e2e/register.test.ts +1 -1
@@ -0,0 +1,145 @@
1
+ import { Migration, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
+ import { Member, mergeTwoMembers } from '@stamhoofd/models';
3
+ import { SQL, SQLSelect } from '@stamhoofd/sql';
4
+ import { PatchOrganizationMembersEndpoint } from '../endpoints/global/members/PatchOrganizationMembersEndpoint';
5
+
6
+ type MergeType = {
7
+ a: {
8
+ id: string;
9
+ firstName: string;
10
+ lastName: string;
11
+ createdAt: Date;
12
+ };
13
+ b: {
14
+ id: string;
15
+ createdAt: Date;
16
+ };
17
+ };
18
+
19
+ export default new Migration(async () => {
20
+ if (STAMHOOFD.environment === 'test') {
21
+ console.log('skipped in tests');
22
+ return;
23
+ }
24
+
25
+ const q = new SQLSelect(
26
+ (row: SQLResultNamespacedRow): MergeType => {
27
+ return {
28
+ a: {
29
+ id: row['a'].id as string,
30
+ firstName: row['a'].firstName as string,
31
+ lastName: row['a'].lastName as string,
32
+ createdAt: row['a'].createdAt as Date,
33
+ },
34
+ b: {
35
+ id: row['b'].id as string,
36
+ createdAt: row['b'].createdAt as Date,
37
+ },
38
+ };
39
+ },
40
+ SQL.wildcard('a'),
41
+ SQL.wildcard('b'),
42
+ )
43
+ .from(Member.table, 'a')
44
+ .join(
45
+ SQL.join(Member.table, 'b')
46
+ .where(
47
+ SQL.column('b', 'id'),
48
+ '!=',
49
+ SQL.column('a', 'id'),
50
+ )
51
+ .andWhere(
52
+ SQL.column('b', 'firstName'),
53
+ SQL.column('a', 'firstName'),
54
+ )
55
+ .andWhere(
56
+ SQL.column('b', 'lastName'),
57
+ SQL.column('a', 'lastName'),
58
+ )
59
+ .andWhere(
60
+ SQL.column('b', 'birthDay'),
61
+ SQL.column('a', 'birthDay'),
62
+ ),
63
+ )
64
+ .where(
65
+ SQL.where(
66
+ SQL.column('a', 'createdAt'),
67
+ '<',
68
+ SQL.column('b', 'createdAt'),
69
+ ).or(
70
+ SQL.where(
71
+ SQL.column('b', 'createdAt'),
72
+ SQL.column('a', 'createdAt'),
73
+ ).and(
74
+ SQL.column('a', 'id'),
75
+ '<',
76
+ SQL.column('b', 'id'),
77
+ ),
78
+ ),
79
+ )
80
+ .orderBy(
81
+ SQL.column('a', 'createdAt'),
82
+ 'ASC',
83
+ );
84
+
85
+ if (STAMHOOFD.userMode === 'organization') {
86
+ q.where(
87
+ SQL.column('b', 'organizationId'),
88
+ SQL.column('organizationId'),
89
+ );
90
+ }
91
+
92
+ const duplicates = await q.fetch();
93
+
94
+ const deletedSet = new Set<string>();
95
+ const mergedIntoSet = new Set<string>();
96
+
97
+ for (const duplicate of duplicates) {
98
+ if (mergedIntoSet.has(duplicate.b.id)) {
99
+ console.log('Found chained duplicate in wrong order', duplicate.a.id, 'and', duplicate.b.id);
100
+ continue; // Already merged this one
101
+ }
102
+
103
+ if (deletedSet.has(duplicate.b.id)) {
104
+ continue; // Already deleted this one
105
+ }
106
+
107
+ if (deletedSet.has(duplicate.a.id)) {
108
+ console.log('Skipping duplicate', duplicate.a.id, 'because it was already deleted');
109
+ continue; // Already deleted this one
110
+ }
111
+
112
+ console.log(
113
+ 'Found duplicate member',
114
+ duplicate.a.id,
115
+ duplicate.a.createdAt,
116
+ 'and',
117
+ duplicate.b.id,
118
+ duplicate.b.createdAt,
119
+ 'with name',
120
+ duplicate.a.firstName, duplicate.a.lastName);
121
+ deletedSet.add(duplicate.b.id);
122
+ mergedIntoSet.add(duplicate.a.id);
123
+
124
+ // Run the merge
125
+ const [memberA] = await Member.getBlobByIds(duplicate.a.id);
126
+ const [memberB] = await Member.getBlobByIds(duplicate.b.id);
127
+
128
+ if (memberA.details.name !== memberB.details.name) {
129
+ console.warn('Member names do not match', memberA.details.name, 'and', memberB.details.name);
130
+ continue; // Names do not match, cannot merge
131
+ }
132
+
133
+ if (memberA.details.birthDayFormatted === null || memberA.details.birthDayFormatted !== memberB.details.birthDayFormatted) {
134
+ console.warn('Member birthday do not match', memberA.details.birthDayFormatted, 'and', memberB.details.birthDayFormatted);
135
+ continue; // Names do not match, cannot merge
136
+ }
137
+
138
+ if (!PatchOrganizationMembersEndpoint.shouldCheckIfMemberIsDuplicate(memberA)) {
139
+ console.log('Skipping merge because not eligible for duplicate check');
140
+ continue;
141
+ }
142
+
143
+ await mergeTwoMembers(memberA, memberB);
144
+ }
145
+ });
@@ -0,0 +1,47 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { Email, Organization, Platform } from '@stamhoofd/models';
3
+
4
+ export default new Migration(async () => {
5
+ if (STAMHOOFD.environment === 'test') {
6
+ console.log('skipped in tests');
7
+ return;
8
+ }
9
+
10
+ console.log('Start setting senderId of emails.');
11
+
12
+ const batchSize = 100;
13
+ let count = 0;
14
+ const platform = await Platform.getShared();
15
+
16
+ for await (const email of Email.select()
17
+ .where('senderId', null).limit(batchSize).all()) {
18
+ if (!email.fromAddress) {
19
+ continue;
20
+ }
21
+ const organization = email.organizationId ? await Organization.getByID(email.organizationId) : null;
22
+ if (!organization) {
23
+ const sender = platform.privateConfig.emails.find(s => s.email === email.fromAddress);
24
+ if (sender) {
25
+ email.senderId = sender.id;
26
+ await email.save();
27
+ count += 1;
28
+ }
29
+ else {
30
+ console.warn(`No sender found for email ${email.fromAddress} in platform config`);
31
+ }
32
+ }
33
+ else {
34
+ const sender = organization.privateMeta.emails.find(s => s.email === email.fromAddress);
35
+ if (sender) {
36
+ email.senderId = sender.id;
37
+ await email.save();
38
+ count += 1;
39
+ }
40
+ else {
41
+ console.warn(`No sender found for email ${email.fromAddress} in organization ${organization.id}`);
42
+ }
43
+ }
44
+ }
45
+
46
+ console.log('Finished saving ' + count + ' emails.');
47
+ });
@@ -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;
@@ -1,6 +1,6 @@
1
1
  import { Model } from '@simonbackx/simple-database';
2
2
  import { Order, WebshopUitpasNumber } from '@stamhoofd/models';
3
- import { OrderStatus, Product, ProductPrice, UitpasClientCredentialsStatus, UitpasOrganizersResponse } from '@stamhoofd/structures';
3
+ import { Cart, OrderStatus, Product, ProductPrice, UitpasClientCredentialsStatus, UitpasOrganizersResponse } from '@stamhoofd/structures';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import { UitpasTokenRepository } from '../../helpers/UitpasTokenRepository';
6
6
  import { searchUitpasOrganizers } from './searchUitpasOrganizers';
@@ -13,6 +13,7 @@ import { RegisterTicketSaleRequest, RegisterTicketSaleResponse, registerTicketSa
13
13
  import { cancelTicketSales } from './cancelTicketSales';
14
14
  import { SimpleError } from '@simonbackx/simple-errors';
15
15
  import { QueueHandler } from '@stamhoofd/queues';
16
+ import { Formatter } from '@stamhoofd/utility';
16
17
 
17
18
  type UitpasTicketSale = {
18
19
  basePrice: number;
@@ -39,7 +40,7 @@ type InsertUitpasNumber = {
39
40
  uitpasEventUrl: string | null; // null for non-official flow
40
41
  };
41
42
 
42
- function shouldReserveUitpasNumbers(status: OrderStatus): boolean {
43
+ export function shouldReserveUitpasNumbers(status: OrderStatus): boolean {
43
44
  return status !== OrderStatus.Canceled && status !== OrderStatus.Deleted;
44
45
  }
45
46
 
@@ -77,7 +78,7 @@ function getUitpasTicketSales(order: Order): UitpasTicketSale[] {
77
78
  throw new SimpleError({
78
79
  code: 'missing_uitpas_base_product_price',
79
80
  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.`),
81
+ human: $t(`f7eea411-fb92-458f-bf3e-e36ed870591b`),
81
82
  });
82
83
  }
83
84
  const label = makeBaseProductPriceLabel(item.product, baseProductPrice);
@@ -356,4 +357,72 @@ export class UitpasService {
356
357
  // Clear the uitpas client credentials for the organization
357
358
  await UitpasTokenRepository.clearClientCredentialsFor(organizationId);
358
359
  }
360
+
361
+ static async areThereRegisteredTicketSales(webshopId: string): Promise<boolean> {
362
+ return await WebshopUitpasNumber.areThereRegisteredTicketSales(webshopId);
363
+ }
364
+
365
+ static async validateCart(organizationId: string, webshopId: string, cart: Cart, exisitingOrderId?: string): Promise<Cart> {
366
+ let access_token_org: string | null = null;
367
+ let access_token_platform: string | null = null;
368
+ for (const item of cart.items) {
369
+ if (item.uitpasNumbers.length === 0) {
370
+ continue;
371
+ }
372
+
373
+ // verify the UiTPAS numbers are not already used for this product
374
+ const hasBeenUsed = await WebshopUitpasNumber.areUitpasNumbersUsed(webshopId, item.product.id, item.uitpasNumbers.map(p => p.uitpasNumber), item.product.uitpasEvent?.url, exisitingOrderId);
375
+ if (hasBeenUsed) {
376
+ throw new SimpleError({
377
+ code: 'uitpas_number_already_used',
378
+ message: 'One or more uitpas numbers are already used',
379
+ human: $t('f3daff19-a227-4e45-b19a-c770bd7a6687'),
380
+ field: 'uitpasNumbers',
381
+ });
382
+ }
383
+
384
+ if (item.product.uitpasEvent) {
385
+ // official flow
386
+ const basePrice = item.product.prices.filter(price => price.id === item.productPrice.uitpasBaseProductPriceId)[0]?.price;
387
+ if (!basePrice) {
388
+ throw new SimpleError({
389
+ code: 'missing_uitpas_base_product_price',
390
+ message: `Missing UiTPAS base product price`,
391
+ human: $t(`3d08a166-11a7-4429-8ff7-84458bbe3e9a`),
392
+ });
393
+ }
394
+
395
+ access_token_org = access_token_org ?? await UitpasTokenRepository.getAccessTokenFor(organizationId);
396
+ const verified = await getSocialTariffForUitpasNumbers(access_token_org, item.uitpasNumbers.map(p => p.uitpasNumber), basePrice, item.product.uitpasEvent.url);
397
+ if (verified.length < item.uitpasNumbers.length) {
398
+ throw new SimpleError({
399
+ code: 'uitpas_social_tariff_price_mismatch',
400
+ message: 'UiTPAS wrong number of prices returned',
401
+ human: $t('83c472b8-4bc5-4282-bbc9-1c6a2d382171'),
402
+ field: 'uitpasNumbers',
403
+ });
404
+ }
405
+ for (let i = 0; i < verified.length; i++) {
406
+ if (item.uitpasNumbers[i].uitpasTariffId !== verified[i].uitpasTariffId) {
407
+ // silently update
408
+ item.uitpasNumbers[i].uitpasTariffId = verified[i].uitpasTariffId;
409
+ }
410
+ if (item.uitpasNumbers[i].price !== verified[i].price) {
411
+ throw new SimpleError({
412
+ code: 'uitpas_social_tariff_price_mismatch',
413
+ message: 'UiTPAS social tariff have a different price',
414
+ human: $t('9a0ad099-99e3-4341-beac-f14feb3fb9d1', { correctPrice: Formatter.price(verified[i].price), orderPrice: Formatter.price(item.uitpasNumbers[i].price) }),
415
+ field: 'uitpasNumbers.' + i.toString(),
416
+ });
417
+ }
418
+ }
419
+ }
420
+ else {
421
+ // non-official flow
422
+ access_token_platform = access_token_platform ?? await UitpasTokenRepository.getAccessTokenFor();
423
+ await checkUitpasNumbers(access_token_platform, item.uitpasNumbers.map(p => p.uitpasNumber));
424
+ }
425
+ }
426
+ return cart;
427
+ }
359
428
  };
@@ -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
  }
@@ -64,6 +64,7 @@ async function checkUitpasNumber(access_token: string, uitpasNumber: string) {
64
64
  ),
65
65
  });
66
66
  }
67
+
67
68
  const baseUrl = 'https://api-test.uitpas.be'; // TO DO: Use the URL from environment variables
68
69
 
69
70
  const url = `${baseUrl}/passes/${uitpasNumber}`;
@@ -110,7 +111,7 @@ async function checkUitpasNumber(access_token: string, uitpasNumber: string) {
110
111
  throw new SimpleError({
111
112
  code: 'unsuccessful_and_unexpected_response_retrieving_pass_by_uitpas_number',
112
113
  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?`),
114
+ human: $t(`4c6482ff-e6d9-4ea1-b11d-e12d697b4b7b`),
114
115
  });
115
116
  }
116
117
 
@@ -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) {
@@ -34,7 +34,7 @@ function assertsIsSocialTariffResponse(json: unknown): asserts json is SocialTar
34
34
  throw new SimpleError({
35
35
  code: 'invalid_response_getting_uitpas_social_tariff',
36
36
  message: `Invalid response when getting UiTPAS social tariff`,
37
- human: $t(`Er is een fout opgetreden bij het ophalen van het kansentarief voor jouw UiTPAS-nummer. Probeer het later opnieuw.`),
37
+ human: $t(`466844b9-c042-4bc7-b77d-3f87376086b5`),
38
38
  });
39
39
  }
40
40
  }
@@ -60,7 +60,7 @@ async function getSocialTariffForUitpasNumber(access_token: string, uitpasNumber
60
60
  throw new SimpleError({
61
61
  code: 'invalid_uitpas_event_url',
62
62
  message: `Invalid UiTPAS event URL: ${uitpasEventUrl}`,
63
- human: $t(`De opgegeven UiTPAS-evenement URL is ongeldig.`),
63
+ human: $t(`85fb6e02-9b69-43cc-acf7-96a576461560`),
64
64
  });
65
65
  }
66
66
  params.append('eventId', eventId);
@@ -110,7 +110,7 @@ async function getSocialTariffForUitpasNumber(access_token: string, uitpasNumber
110
110
  throw new SimpleError({
111
111
  code: 'unsuccessful_and_unexpected_response_retrieving_social_tariff_by_uitpas_number',
112
112
  message: `Unsuccesful response without message when retrieving social tariff by UiTPAS number`,
113
- human: $t(`Er is een fout opgetreden bij het ophalen van je UiTPAS kansentarief. Kijk je het nummer even na?`),
113
+ human: $t(`1572d069-abda-4b31-a373-f0c3760c79b1`),
114
114
  });
115
115
  }
116
116
  const json = await response.json().catch(() => {
@@ -136,7 +136,7 @@ async function getSocialTariffForUitpasNumber(access_token: string, uitpasNumber
136
136
  throw new SimpleError({
137
137
  code: 'no_social_tariff_available',
138
138
  message: `No social tariff available for event ${uitpasEventUrl}`,
139
- human: $t(`Er is geen kansentarief beschikbaar voor dit evenement.`),
139
+ human: $t(`ccd8e8b4-01a7-4e7c-8ae0-92d2a4c659eb`),
140
140
  });
141
141
  }
142
142
  if (json.available.length > 1) {
@@ -150,7 +150,7 @@ async function getSocialTariffForUitpasNumber(access_token: string, uitpasNumber
150
150
  throw new SimpleError({
151
151
  code: 'no_remaining_social_tariff',
152
152
  message: `No remaining social tariff for event ${uitpasEventUrl} and UiTPAS number ${uitpasNumber}`,
153
- human: $t(`Je hebt recht op UiTPAS-kansentarief maar het overblijvend aantal is niet voldoende.`),
153
+ human: $t(`f9d6bc51-e7c9-4d3f-a13f-27871a018d83`),
154
154
  });
155
155
  }
156
156
  console.log('Social tariff for UiTPAS number', uitpasNumber, 'with event id', uitpasEventUrl, 'is', json.available[0].price, 'euros');
@@ -46,7 +46,7 @@ function assertisSuccessResponse(json: unknown): asserts json is SuccessResponse
46
46
  throw new SimpleError({
47
47
  code: 'invalid_register_ticket_sale_response',
48
48
  message: `Invalid register ticket sale response`,
49
- human: $t(`Er is een fout opgetreden bij het registreren van de UiTPAS-ticketverkoop.`),
49
+ human: $t(`f5cac12d-ced6-43e6-bbd9-81b381807154`),
50
50
  });
51
51
  }
52
52
  }
@@ -69,7 +69,7 @@ export async function registerTicketSales(access_token: string, registerTicketSa
69
69
  throw new SimpleError({
70
70
  code: 'invalid_uitpas_event_url',
71
71
  message: `Invalid UiTPAS event URL: ${ticketSale.uitpasEventUrl}`,
72
- human: $t(`De opgegeven UiTPAS-evenement URL is ongeldig.`),
72
+ human: $t(`85fb6e02-9b69-43cc-acf7-96a576461560`),
73
73
  });
74
74
  }
75
75
 
@@ -108,7 +108,7 @@ export async function registerTicketSales(access_token: string, registerTicketSa
108
108
  throw new SimpleError({
109
109
  code: 'unsuccessful_response_registering_ticket_sales',
110
110
  message: `Unsuccessful response when registering UiTPAS ticket sales`,
111
- human: $t(`Er is een fout opgetreden bij het verbinden met UiTPAS. Probeer het later opnieuw.`),
111
+ human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
112
112
  });
113
113
  }
114
114
  const json = await response.json().catch(() => {});
@@ -117,7 +117,7 @@ export async function registerTicketSales(access_token: string, registerTicketSa
117
117
  throw new SimpleError({
118
118
  code: 'invalid_response_registering_ticket_sales',
119
119
  message: `Invalid response when registering ticket sales`,
120
- human: $t(`Er is een fout opgetreden bij het registreren van de UiTPAS-ticketverkoop.`),
120
+ human: $t(`f5cac12d-ced6-43e6-bbd9-81b381807154`),
121
121
  });
122
122
  }
123
123
  const now = new Date();
@@ -49,7 +49,7 @@ function assertIsEventsResponse(json: unknown): asserts json is EventsResponse {
49
49
  throw new SimpleError({
50
50
  code: 'invalid_events_response',
51
51
  message: `Invalid events response`,
52
- human: $t(`Er is een fout opgetreden bij het ophalen van de UiTPAS-evenementen.`),
52
+ human: $t(`0b610eb8-551f-4ef6-acdc-fb3267dfc2a8`),
53
53
  });
54
54
  }
55
55
  }
@@ -61,7 +61,7 @@ export async function searchUitpasEvents(clientId: string, uitpasOrganizerId: st
61
61
  throw new SimpleError({
62
62
  code: 'no_client_id_for_uitpas_events',
63
63
  message: `No client ID configured for Uitpas events`,
64
- human: $t(`Er is geen UiTPAS client ID geconfigureerd voor deze organisatie.`),
64
+ human: $t(`0009cce2-9d13-4f03-9258-345d8f8e396e`),
65
65
  });
66
66
  }
67
67
  const baseUrl = 'https://search-test.uitdatabank.be/events';
@@ -97,7 +97,7 @@ export async function searchUitpasEvents(clientId: string, uitpasOrganizerId: st
97
97
  throw new SimpleError({
98
98
  code: 'unsuccessful_response_searching_uitpas_events',
99
99
  message: `Unsuccessful response when searching for UiTPAS events`,
100
- human: $t(`Er is een fout opgetreden bij het verbinden met UiTPAS. Probeer het later opnieuw.`),
100
+ human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
101
101
  });
102
102
  }
103
103
  const json = await response.json().catch(() => {