@stamhoofd/backend 2.74.0 → 2.75.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/index.ts +7 -2
  2. package/package.json +13 -13
  3. package/src/crons/update-cached-balances.ts +1 -2
  4. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +2 -2
  5. package/src/endpoints/auth/CreateAdminEndpoint.ts +4 -15
  6. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +2 -2
  7. package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +43 -0
  8. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +181 -0
  9. package/src/endpoints/global/events/GetEventsEndpoint.ts +2 -2
  10. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +288 -0
  11. package/src/endpoints/global/events/PatchEventsEndpoint.ts +2 -2
  12. package/src/endpoints/global/files/UploadFile.ts +56 -4
  13. package/src/endpoints/global/files/UploadImage.ts +9 -3
  14. package/src/endpoints/global/members/GetMembersEndpoint.ts +2 -2
  15. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +10 -1
  16. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +1 -5
  17. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +7 -0
  18. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +1 -1
  19. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +1756 -164
  20. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -2
  21. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +48 -2
  22. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +2 -2
  23. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
  24. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +2 -2
  25. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +2 -2
  26. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +8 -0
  27. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +3 -3
  28. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +2 -2
  29. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +2 -2
  30. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +1 -2
  31. package/src/helpers/AdminPermissionChecker.ts +80 -2
  32. package/src/helpers/AuthenticatedStructures.ts +88 -2
  33. package/src/helpers/FlagMomentCleanup.ts +1 -8
  34. package/src/helpers/GlobalHelper.ts +15 -0
  35. package/src/helpers/MembershipCharger.ts +2 -1
  36. package/src/services/EventNotificationService.ts +201 -0
  37. package/src/services/FileSignService.ts +227 -0
  38. package/src/sql-filters/event-notifications.ts +39 -0
  39. package/src/sql-filters/organizations.ts +1 -1
  40. package/src/sql-sorters/event-notifications.ts +96 -0
  41. package/src/sql-sorters/events.ts +2 -2
  42. package/src/sql-sorters/organizations.ts +2 -2
  43. package/tests/e2e/private-files.test.ts +497 -0
  44. package/tests/e2e/register.test.ts +762 -0
  45. package/tests/helpers/TestServer.ts +3 -0
  46. package/tests/jest.setup.ts +15 -2
  47. package/tsconfig.json +1 -0
@@ -5,7 +5,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
5
5
  import { SimpleError } from '@simonbackx/simple-errors';
6
6
  import { Email } from '@stamhoofd/email';
7
7
  import { BalanceItem, BalanceItemPayment, Group, Member, MemberWithRegistrations, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, Platform, RateLimiter, Registration, User } from '@stamhoofd/models';
8
- import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, BalanceItem as BalanceItemStruct, IDRegisterCheckout, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PermissionLevel, PlatformFamily, PlatformMember, RegisterItem, RegisterResponse, Version, PaymentType } from '@stamhoofd/structures';
8
+ import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItem as BalanceItemStruct, BalanceItemType, IDRegisterCheckout, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PaymentType, PermissionLevel, PlatformFamily, PlatformMember, RegisterItem, RegisterResponse, Version } from '@stamhoofd/structures';
9
9
  import { Formatter } from '@stamhoofd/utility';
10
10
 
11
11
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
@@ -388,7 +388,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
388
388
  if (!await Context.auth.canAccessRegistration(existingRegistration, PermissionLevel.Write)) {
389
389
  throw new SimpleError({
390
390
  code: 'forbidden',
391
- message: 'Je hebt geen toegaansrechten om deze inschrijving te verwijderen.',
391
+ message: 'Je hebt geen toegangsrechten om deze inschrijving te verwijderen.',
392
392
  statusCode: 403,
393
393
  });
394
394
  }
@@ -1,9 +1,9 @@
1
1
  import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder, patchObject } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
- import { AuditLogSource, AuditLogType, RegistrationPeriod as RegistrationPeriodStruct } from '@stamhoofd/structures';
3
+ import { AuditLogSource, RegistrationPeriod as RegistrationPeriodStruct } from '@stamhoofd/structures';
4
4
 
5
5
  import { SimpleError } from '@simonbackx/simple-errors';
6
- import { Platform, RegistrationPeriod } from '@stamhoofd/models';
6
+ import { Organization, Platform, RegistrationPeriod } from '@stamhoofd/models';
7
7
  import { Context } from '../../../helpers/Context';
8
8
  import { PeriodHelper } from '../../../helpers/PeriodHelper';
9
9
  import { AuditLogService } from '../../../services/AuditLogService';
@@ -34,6 +34,24 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
34
34
  return [false];
35
35
  }
36
36
 
37
+ static async isCurrentRegistrationPeriod(organizationId: string | null, periodId: string) {
38
+ if (organizationId === null) {
39
+ const platform = await Platform.getSharedStruct();
40
+ return platform.period.id === periodId;
41
+ }
42
+
43
+ const organization = await Organization.getByID(organizationId);
44
+ if (!organization) {
45
+ throw new SimpleError({
46
+ code: 'not_found',
47
+ statusCode: 404,
48
+ message: 'Organization not found',
49
+ });
50
+ }
51
+
52
+ return organization.periodId === periodId;
53
+ }
54
+
37
55
  async handle(request: DecodedRequest<Params, Query, Body>) {
38
56
  const organization = await Context.setUserOrganizationScope();
39
57
  await Context.authenticate();
@@ -60,6 +78,20 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
60
78
  period.locked = put.locked;
61
79
  period.settings = put.settings;
62
80
  period.organizationId = organization?.id ?? null;
81
+
82
+ if (put.locked === true) {
83
+ // current period cannot be locked
84
+ const isCurrentRegistrationPeriod = await PatchRegistrationPeriodsEndpoint.isCurrentRegistrationPeriod(period.organizationId, period.id);
85
+
86
+ if (isCurrentRegistrationPeriod) {
87
+ throw new SimpleError({
88
+ code: 'cannot_lock_current_period',
89
+ message: 'Current registration period cannot be locked',
90
+ human: 'Het huidige werkjaar kan niet vergrendeld worden',
91
+ });
92
+ }
93
+ }
94
+
63
95
  await period.setPreviousPeriodId();
64
96
 
65
97
  await period.save();
@@ -76,6 +108,20 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
76
108
  message: 'Registration period not found',
77
109
  });
78
110
  }
111
+
112
+ if (patch.locked === true) {
113
+ // current period cannot be locked
114
+ const isCurrentRegistrationPeriod = await PatchRegistrationPeriodsEndpoint.isCurrentRegistrationPeriod(model.organizationId, model.id);
115
+
116
+ if (isCurrentRegistrationPeriod) {
117
+ throw new SimpleError({
118
+ code: 'cannot_lock_current_period',
119
+ message: 'Current registration period cannot be locked',
120
+ human: 'Het huidige werkjaar kan niet vergrendeld worden',
121
+ });
122
+ }
123
+ }
124
+
79
125
  if (patch.startDate !== undefined) {
80
126
  model.startDate = patch.startDate;
81
127
  }
@@ -3,7 +3,7 @@ import { Document } from '@stamhoofd/models';
3
3
  import { assertSort, CountFilteredRequest, Document as DocumentStruct, getSortFilter, LimitedFilteredRequest, PaginatedResponse, SearchFilterFactory, StamhoofdFilter } from '@stamhoofd/structures';
4
4
 
5
5
  import { Decoder } from '@simonbackx/simple-encoding';
6
- import { compileToSQLFilter, compileToSQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
6
+ import { compileToSQLFilter, applySQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
7
7
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
8
8
  import { Context } from '../../../../helpers/Context';
9
9
  import { LimitedFilteredRequestHelper } from '../../../../helpers/LimitedFilteredRequestHelper';
@@ -68,7 +68,7 @@ export class GetDocumentsEndpoint extends Endpoint<Params, Query, Body, Response
68
68
  }
69
69
 
70
70
  q.sort = assertSort(q.sort, [{ key: 'id' }]);
71
- query.orderBy(compileToSQLSorter(q.sort, sorters));
71
+ applySQLSorter(query, q.sort, sorters);
72
72
  query.limit(q.limit);
73
73
  }
74
74
 
@@ -346,7 +346,7 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
346
346
  await organization.save();
347
347
  }
348
348
  else {
349
- if (request.body.name || request.body.address) {
349
+ if (request.body.name || request.body.address || request.body.website || request.body.meta?.companies || request.body.meta?.recordsConfiguration || request.body.meta?.registrationPaymentConfiguration) {
350
350
  throw new SimpleError({
351
351
  code: 'permission_denied',
352
352
  message: 'You do not have permissions to edit the organization settings',
@@ -2,7 +2,7 @@ import { Decoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Payment } from '@stamhoofd/models';
5
- import { SQL, compileToSQLFilter, compileToSQLSorter } from '@stamhoofd/sql';
5
+ import { SQL, compileToSQLFilter, applySQLSorter } from '@stamhoofd/sql';
6
6
  import { CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, PaymentGeneral, StamhoofdFilter, TransferSettings, assertSort, getSortFilter } from '@stamhoofd/structures';
7
7
 
8
8
  import { SQLResultNamespacedRow } from '@simonbackx/simple-database';
@@ -152,7 +152,7 @@ export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseB
152
152
  }
153
153
 
154
154
  q.sort = assertSort(q.sort, [{ key: 'id' }]);
155
- query.orderBy(compileToSQLSorter(q.sort, sorters));
155
+ applySQLSorter(query, q.sort, sorters);
156
156
  query.limit(q.limit);
157
157
  }
158
158
 
@@ -2,7 +2,7 @@ import { Decoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { CachedBalance } from '@stamhoofd/models';
5
- import { compileToSQLFilter, compileToSQLSorter } from '@stamhoofd/sql';
5
+ import { compileToSQLFilter, applySQLSorter } from '@stamhoofd/sql';
6
6
  import { assertSort, CountFilteredRequest, DetailedReceivableBalance, getSortFilter, LimitedFilteredRequest, PaginatedResponse, ReceivableBalance as ReceivableBalanceStruct, StamhoofdFilter } from '@stamhoofd/structures';
7
7
 
8
8
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
@@ -100,7 +100,7 @@ export class GetReceivableBalancesEndpoint extends Endpoint<Params, Query, Body,
100
100
  }
101
101
 
102
102
  q.sort = assertSort(q.sort, [{ key: 'id' }]);
103
- query.orderBy(compileToSQLSorter(q.sort, sorters));
103
+ applySQLSorter(query, q.sort, sorters);
104
104
  query.limit(q.limit);
105
105
  }
106
106
 
@@ -452,6 +452,14 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
452
452
  }
453
453
  }
454
454
 
455
+ if (period.locked) {
456
+ throw new SimpleError({
457
+ code: 'invalid_period',
458
+ message: 'Period is locked',
459
+ human: Context.i18n.$t('f544b972-416c-471d-8836-d7f3b16f947d'),
460
+ });
461
+ }
462
+
455
463
  const user = Context.auth.user;
456
464
 
457
465
  const model = new Group();
@@ -1,9 +1,9 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
2
  import { User } from '@stamhoofd/models';
3
- import { OrganizationAdmins, User as UserStruct } from '@stamhoofd/structures';
3
+ import { OrganizationAdmins } from '@stamhoofd/structures';
4
4
 
5
- import { Context } from '../../../../helpers/Context';
6
5
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
6
+ import { Context } from '../../../../helpers/Context';
7
7
  type Params = Record<string, never>;
8
8
  type Query = undefined;
9
9
  type Body = undefined;
@@ -33,7 +33,7 @@ export class GetOrganizationAdminsEndpoint extends Endpoint<Params, Query, Body,
33
33
  }
34
34
 
35
35
  // Get all admins
36
- const admins = await User.getAdmins([organization.id]);
36
+ const admins = await User.getAdmins(organization.id);
37
37
 
38
38
  return new Response(OrganizationAdmins.create({
39
39
  users: await AuthenticatedStructures.usersWithMembers(admins),
@@ -3,7 +3,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
3
3
  import { assertSort, CountFilteredRequest, getOrderSearchFilter, getSortFilter, LimitedFilteredRequest, PaginatedResponse, PrivateOrder, StamhoofdFilter } from '@stamhoofd/structures';
4
4
 
5
5
  import { Order } from '@stamhoofd/models';
6
- import { compileToSQLFilter, compileToSQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
6
+ import { compileToSQLFilter, applySQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
7
7
  import { parsePhoneNumber } from 'libphonenumber-js/max';
8
8
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
9
9
  import { Context } from '../../../../helpers/Context';
@@ -69,7 +69,7 @@ export class GetWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Resp
69
69
  }
70
70
 
71
71
  q.sort = assertSort(q.sort, [{ key: 'id' }]);
72
- query.orderBy(compileToSQLSorter(q.sort, sorters));
72
+ applySQLSorter(query, q.sort, sorters);
73
73
  query.limit(q.limit);
74
74
  }
75
75
 
@@ -3,7 +3,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
3
3
  import { Ticket } from '@stamhoofd/models';
4
4
  import { assertSort, CountFilteredRequest, getSortFilter, LimitedFilteredRequest, PaginatedResponse, TicketPrivate } from '@stamhoofd/structures';
5
5
 
6
- import { compileToSQLFilter, compileToSQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
6
+ import { compileToSQLFilter, applySQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
7
7
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
8
8
  import { Context } from '../../../../helpers/Context';
9
9
  import { LimitedFilteredRequestHelper } from '../../../../helpers/LimitedFilteredRequestHelper';
@@ -60,7 +60,7 @@ export class GetWebshopTicketsEndpoint extends Endpoint<Params, Query, Body, Res
60
60
  }
61
61
 
62
62
  q.sort = assertSort(q.sort, [{ key: 'id' }]);
63
- query.orderBy(compileToSQLSorter(q.sort, sorters));
63
+ applySQLSorter(query, q.sort, sorters);
64
64
  query.limit(q.limit);
65
65
  }
66
66
 
@@ -2,11 +2,10 @@ import { createMollieClient, PaymentMethod as molliePaymentMethod } from '@molli
2
2
  import { Decoder } from '@simonbackx/simple-encoding';
3
3
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
4
  import { SimpleError } from '@simonbackx/simple-errors';
5
- import { I18n } from '@stamhoofd/backend-i18n';
6
5
  import { Email } from '@stamhoofd/email';
7
6
  import { BalanceItem, BalanceItemPayment, MolliePayment, MollieToken, Order, PayconiqPayment, Payment, RateLimiter, Webshop, WebshopDiscountCode } from '@stamhoofd/models';
8
7
  import { QueueHandler } from '@stamhoofd/queues';
9
- import { BalanceItemStatus, Order as OrderStruct, OrderData, OrderResponse, Payment as PaymentStruct, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Version, Webshop as WebshopStruct, WebshopAuthType, BalanceItemType, BalanceItemRelationType, BalanceItemRelation, AuditLogSource, PaymentCustomer } from '@stamhoofd/structures';
8
+ import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderData, OrderResponse, Order as OrderStruct, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, Version, WebshopAuthType, Webshop as WebshopStruct } from '@stamhoofd/structures';
10
9
  import { Formatter } from '@stamhoofd/utility';
11
10
 
12
11
  import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
@@ -1,6 +1,6 @@
1
1
  import { AutoEncoderPatchType, PatchMap } from '@simonbackx/simple-encoding';
2
- import { SimpleError } from '@simonbackx/simple-errors';
3
- import { BalanceItem, CachedBalance, Document, EmailTemplate, Event, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
2
+ import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
3
+ import { BalanceItem, CachedBalance, Document, EmailTemplate, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
4
4
  import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from '@stamhoofd/structures';
5
5
  import { Formatter } from '@stamhoofd/utility';
6
6
  import { addTemporaryMemberAccess, hasTemporaryMemberAccess } from './TemporaryMemberAccess';
@@ -206,6 +206,38 @@ export class AdminPermissionChecker {
206
206
  });
207
207
  }
208
208
 
209
+ async canAccessEvent(event: Event): Promise<boolean> {
210
+ try {
211
+ await this.checkEventAccess(event);
212
+ return true;
213
+ }
214
+ catch (e) {
215
+ if (isSimpleError(e) || isSimpleErrors(e)) {
216
+ return false;
217
+ }
218
+ throw e;
219
+ }
220
+ }
221
+
222
+ async canAccessEventNotification(notification: EventNotification, permissionLevel: PermissionLevel = PermissionLevel.Read) {
223
+ // todo: check if user has access to this notification
224
+ const events = EventNotification.events.isLoaded(notification) ? notification.events : await EventNotification.events.load(notification);
225
+ for (const event of events) {
226
+ if (!await this.canAccessEvent(event)) {
227
+ return false;
228
+ }
229
+ }
230
+
231
+ if (events.length === 0 || permissionLevel === PermissionLevel.Full) {
232
+ // Requires `OrganizationEventNotificationReviewer` access right for the organization
233
+ if (!await this.canReviewEventNotification(notification)) {
234
+ return false;
235
+ }
236
+ }
237
+
238
+ return true;
239
+ }
240
+
209
241
  async canAccessArchivedGroups(organizationId: string) {
210
242
  return await this.hasFullAccess(organizationId);
211
243
  }
@@ -785,6 +817,23 @@ export class AdminPermissionChecker {
785
817
  return !this.user.isApiUser && (await this.hasFullAccess(organizationId));
786
818
  }
787
819
 
820
+ async canReviewEventNotification(eventNotification: { organizationId: string }) {
821
+ if (!this.checkScope(eventNotification.organizationId)) {
822
+ return false;
823
+ }
824
+
825
+ const organizationPermissions = await this.getOrganizationPermissions(eventNotification.organizationId);
826
+
827
+ if (!organizationPermissions) {
828
+ return false;
829
+ }
830
+
831
+ if (organizationPermissions.hasAccessRight(AccessRight.OrganizationEventNotificationReviewer)) {
832
+ return true;
833
+ }
834
+ return false;
835
+ }
836
+
788
837
  async hasFullAccess(organizationId: string, level = PermissionLevel.Full): Promise<boolean> {
789
838
  const organizationPermissions = await this.getOrganizationPermissions(organizationId);
790
839
 
@@ -1340,6 +1389,35 @@ export class AdminPermissionChecker {
1340
1389
  return tags;
1341
1390
  }
1342
1391
 
1392
+ getOrganizationTagsWithAccessRight(right: AccessRight): string[] | 'all' {
1393
+ if (!this.hasSomePlatformAccess()) {
1394
+ return [];
1395
+ }
1396
+
1397
+ if (this.hasPlatformFullAccess()) {
1398
+ return 'all';
1399
+ }
1400
+
1401
+ if (this.platformPermissions?.hasResourceAccessRight(PermissionsResourceType.OrganizationTags, '', right)) {
1402
+ return 'all';
1403
+ }
1404
+
1405
+ const allTags = this.platform.config.tags;
1406
+ const tags: string[] = [];
1407
+
1408
+ for (const tag of allTags) {
1409
+ if (this.platformPermissions?.hasResourceAccessRight(PermissionsResourceType.OrganizationTags, tag.id, right)) {
1410
+ tags.push(tag.id);
1411
+ }
1412
+ }
1413
+
1414
+ if (tags.length === allTags.length) {
1415
+ return 'all';
1416
+ }
1417
+
1418
+ return tags;
1419
+ }
1420
+
1343
1421
  hasSomePlatformAccess(): boolean {
1344
1422
  return !!this.platformPermissions && !this.platformPermissions.isEmpty;
1345
1423
  }
@@ -1,7 +1,8 @@
1
1
  import { SimpleError } from '@simonbackx/simple-errors';
2
- import { AuditLog, BalanceItem, CachedBalance, Document, Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
3
- import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, Document as DocumentStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
2
+ import { AuditLog, BalanceItem, CachedBalance, Document, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
3
+ import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
4
4
 
5
+ import { SQL } from '@stamhoofd/sql';
5
6
  import { Formatter } from '@stamhoofd/utility';
6
7
  import { Context } from './Context';
7
8
 
@@ -479,6 +480,91 @@ export class AuthenticatedStructures {
479
480
  return result;
480
481
  }
481
482
 
483
+ static async eventNotification(eventNotification: EventNotification): Promise<EventNotificationStruct> {
484
+ return (await this.eventNotifications([eventNotification]))[0];
485
+ }
486
+
487
+ static async eventNotifications(eventNotifications: EventNotification[]): Promise<EventNotificationStruct[]> {
488
+ if (eventNotifications.length === 0) {
489
+ return [];
490
+ }
491
+
492
+ // Load events
493
+ const rows = await SQL.select()
494
+ .from('_event_notifications_events')
495
+ .where('event_notificationsId', eventNotifications.map(n => n.id))
496
+ .fetch();
497
+
498
+ const eventIdsMapping = new Map<string, string[]>();
499
+ const allEvents = new Set<string>();
500
+
501
+ for (const row of rows) {
502
+ const notificationId = row['_event_notifications_events']['event_notificationsId'];
503
+ const eventId = row['_event_notifications_events']['eventsId'];
504
+
505
+ if (typeof eventId !== 'string') {
506
+ console.error('Invalid event id', eventId);
507
+ continue;
508
+ }
509
+
510
+ if (typeof notificationId !== 'string') {
511
+ console.error('Invalid notification id', notificationId);
512
+ continue;
513
+ }
514
+
515
+ const array = eventIdsMapping.get(notificationId);
516
+ if (array) {
517
+ array.push(eventId);
518
+ }
519
+ else {
520
+ eventIdsMapping.set(notificationId, [eventId]);
521
+ }
522
+
523
+ allEvents.add(eventId);
524
+ }
525
+
526
+ const events = allEvents.size > 0 ? await Event.getByIDs(...Array.from(allEvents)) : [];
527
+ const eventStructs = await this.events(events);
528
+ const organizationIds = Formatter.uniqueArray(eventNotifications.map(n => n.organizationId));
529
+ const organizationModels = organizationIds.length > 0 ? await Organization.getByIDs(...organizationIds) : [];
530
+ const userIds = Formatter.uniqueArray(eventNotifications.flatMap(n => [n.createdBy, n.submittedBy]).filter(id => id !== null));
531
+ const users = userIds.length > 0 ? await User.getByIDs(...userIds) : [];
532
+ const userStructs = users.map((user) => {
533
+ return NamedObject.create({
534
+ id: user.id,
535
+ name: user.name ?? user.email,
536
+ });
537
+ });
538
+
539
+ const result: EventNotificationStruct[] = [];
540
+
541
+ for (const notification of eventNotifications) {
542
+ const thisEventStructs = eventIdsMapping.get(notification.id)?.map(id => eventStructs.find(e => e.id === id)).filter(e => !!e) ?? [];
543
+ const organizationModel = organizationModels.find(o => o.id === notification.organizationId);
544
+ if (!organizationModel) {
545
+ throw new SimpleError({
546
+ code: 'organization_not_found',
547
+ message: 'Organization not found',
548
+ human: 'Organisatie niet gevonden',
549
+ });
550
+ }
551
+
552
+ const organizationStruct = organizationModel.getBaseStructure();
553
+
554
+ const struct = EventNotificationStruct.create({
555
+ ...notification,
556
+ organization: organizationStruct,
557
+ createdBy: notification.createdBy ? userStructs.find(u => u.id === notification.createdBy) : null,
558
+ submittedBy: notification.submittedBy ? userStructs.find(u => u.id === notification.submittedBy) : null,
559
+ events: thisEventStructs,
560
+ });
561
+
562
+ result.push(struct);
563
+ }
564
+
565
+ return result;
566
+ }
567
+
482
568
  static async orders(orders: Order[]): Promise<PrivateOrder[]> {
483
569
  const result: PrivateOrder[] = [];
484
570
  const webshopIds = new Set(orders.map(o => o.webshopId));
@@ -22,17 +22,10 @@ export class FlagMomentCleanup {
22
22
  static async getActiveMemberResponsibilityRecordsForOrganizationWithoutRegistrationInCurrentPeriod() {
23
23
  const currentPeriodId = (await Platform.getShared()).periodId;
24
24
 
25
- const now = new Date();
26
-
27
25
  return await MemberResponsibilityRecord.select()
28
26
  .whereNot('organizationId', null)
29
27
  .where(
30
- SQL.where('startDate', SQLWhereSign.LessEqual, now)
31
- .or('startDate', null),
32
- )
33
- .where(
34
- SQL.where('endDate', SQLWhereSign.GreaterEqual, now)
35
- .or('endDate', null),
28
+ MemberResponsibilityRecord.whereActive,
36
29
  )
37
30
  .whereNot(
38
31
  new SQLWhereExists(
@@ -0,0 +1,15 @@
1
+ import { I18n } from '@stamhoofd/backend-i18n';
2
+ import { FileSignService } from '../services/FileSignService';
3
+ import { Context } from './Context';
4
+
5
+ export class GlobalHelper {
6
+ static async load() {
7
+ await I18n.load();
8
+ this.loadGlobalTranslateFunction();
9
+ await FileSignService.load();
10
+ }
11
+
12
+ private static loadGlobalTranslateFunction() {
13
+ (global as any).$t = (key: string, replace?: Record<string, string>) => Context.i18n.$t(key, replace);
14
+ }
15
+ }
@@ -154,7 +154,8 @@ export const MembershipCharger = {
154
154
  const q = MemberPlatformMembership.select()
155
155
  .where('id', SQLWhereSign.Greater, lastId)
156
156
  .where('balanceItemId', null)
157
- .where('deletedAt', null);
157
+ .where('deletedAt', null)
158
+ .where('locked', false);
158
159
 
159
160
  if (organizationId) {
160
161
  q.where('organizationId', organizationId);