@stamhoofd/backend 2.89.1 → 2.90.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 (66) hide show
  1. package/package.json +12 -11
  2. package/src/boot.ts +2 -0
  3. package/src/crons/balance-emails.ts +1 -6
  4. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +1 -1
  5. package/src/endpoints/admin/organizations/SearchUitpasOrganizersEndpoint.ts +42 -0
  6. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +4 -4
  7. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +3 -3
  8. package/src/endpoints/global/events/GetEventsEndpoint.ts +2 -2
  9. package/src/endpoints/global/events/PatchEventsEndpoint.ts +23 -2
  10. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +6 -6
  11. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +8 -6
  12. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +2 -2
  13. package/src/endpoints/global/platform/GetPlatformEndpoint.ts +1 -0
  14. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +10 -8
  15. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +11 -0
  16. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +2 -2
  17. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +3 -6
  18. package/src/endpoints/organization/dashboard/organization/GetUitpasClientIdEndpoint.ts +38 -0
  19. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +31 -1
  20. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +108 -0
  21. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +1 -1
  22. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -2
  23. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +1 -1
  24. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +9 -1
  25. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +3 -2
  26. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +1 -1
  27. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +2 -9
  28. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +1 -7
  29. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +68 -1
  30. package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +27 -20
  31. package/src/helpers/AdminPermissionChecker.ts +129 -22
  32. package/src/helpers/AuthenticatedStructures.ts +13 -10
  33. package/src/helpers/Context.ts +1 -1
  34. package/src/helpers/UitpasTokenRepository.ts +125 -35
  35. package/src/helpers/ViesHelper.ts +2 -1
  36. package/src/seeds/0000000002-clear-stamhoofd-email-templates.ts +13 -0
  37. package/src/seeds/0000000003-default-email-templates.ts +20 -0
  38. package/src/seeds/data/default-email-templates.sql +55 -0
  39. package/src/services/RegistrationService.ts +6 -4
  40. package/src/services/uitpas/UitpasService.test.ts +23 -0
  41. package/src/services/uitpas/UitpasService.ts +222 -0
  42. package/src/services/uitpas/checkPermissionsFor.ts +111 -0
  43. package/src/services/uitpas/checkUitpasNumbers.ts +180 -0
  44. package/src/services/uitpas/getSocialTariffForEvent.ts +90 -0
  45. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +181 -0
  46. package/src/services/uitpas/searchUitpasOrganizers.ts +93 -0
  47. package/src/sql-filters/audit-logs.ts +26 -6
  48. package/src/sql-filters/balance-item-payments.ts +23 -8
  49. package/src/sql-filters/base-registration-filter-compilers.ts +74 -23
  50. package/src/sql-filters/documents.ts +46 -13
  51. package/src/sql-filters/event-notifications.ts +48 -12
  52. package/src/sql-filters/events.ts +62 -26
  53. package/src/sql-filters/groups.ts +12 -12
  54. package/src/sql-filters/members.ts +325 -137
  55. package/src/sql-filters/orders.ts +96 -48
  56. package/src/sql-filters/organization-registration-periods.ts +16 -4
  57. package/src/sql-filters/organizations.ts +105 -99
  58. package/src/sql-filters/payments.ts +97 -47
  59. package/src/sql-filters/receivable-balances.ts +56 -19
  60. package/src/sql-filters/registration-periods.ts +16 -4
  61. package/src/sql-filters/registrations.ts +2 -2
  62. package/src/sql-filters/shared/EmailRelationFilterCompilers.ts +14 -8
  63. package/src/sql-filters/tickets.ts +26 -6
  64. package/tests/e2e/charge-members.test.ts +1 -0
  65. package/src/helpers/UitpasNumberValidator.test.ts +0 -23
  66. package/src/helpers/UitpasNumberValidator.ts +0 -185
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.89.1",
3
+ "version": "2.90.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -12,7 +12,8 @@
12
12
  "dev": "wait-on ../../shared/middleware/dist/index.js && concurrently -r 'yarn -s build --watch --preserveWatchOutput' \"yarn -s dev:watch\"",
13
13
  "dev:watch": "wait-on ./dist/index.js && nodemon --quiet --inspect=5858 --watch dist --watch '../../../shared/*/dist/' --watch '../../shared/*/dist/' --ext .ts,.json,.sql,.js --delay 2000ms --exec 'node --enable-source-maps ./dist/index.js' --signal SIGTERM",
14
14
  "dev:backend": "yarn -s dev",
15
- "build": "rm -rf ./dist/src/migrations && rm -rf ./dist/src/seeds && tsc -b",
15
+ "build": "rm -rf ./dist/src/migrations && rm -rf ./dist/src/seeds && yarn -s copy-assets && tsc -b",
16
+ "copy-assets": "rsync --delete --mkpath --exclude='*.ts' --exclude='*.js' -r --checksum ./src/ ./dist/src/",
16
17
  "build:full": "yarn -s clear && yarn -s build",
17
18
  "clear": "rm -rf ./dist",
18
19
  "start": "yarn -s build && node --enable-source-maps ./dist/index.js",
@@ -44,14 +45,14 @@
44
45
  "@simonbackx/simple-encoding": "2.22.0",
45
46
  "@simonbackx/simple-endpoints": "1.20.1",
46
47
  "@simonbackx/simple-logging": "^1.0.1",
47
- "@stamhoofd/backend-i18n": "2.89.1",
48
- "@stamhoofd/backend-middleware": "2.89.1",
49
- "@stamhoofd/email": "2.89.1",
50
- "@stamhoofd/models": "2.89.1",
51
- "@stamhoofd/queues": "2.89.1",
52
- "@stamhoofd/sql": "2.89.1",
53
- "@stamhoofd/structures": "2.89.1",
54
- "@stamhoofd/utility": "2.89.1",
48
+ "@stamhoofd/backend-i18n": "2.90.0",
49
+ "@stamhoofd/backend-middleware": "2.90.0",
50
+ "@stamhoofd/email": "2.90.0",
51
+ "@stamhoofd/models": "2.90.0",
52
+ "@stamhoofd/queues": "2.90.0",
53
+ "@stamhoofd/sql": "2.90.0",
54
+ "@stamhoofd/structures": "2.90.0",
55
+ "@stamhoofd/utility": "2.90.0",
55
56
  "archiver": "^7.0.1",
56
57
  "axios": "^1.8.2",
57
58
  "cookie": "^0.7.0",
@@ -69,5 +70,5 @@
69
70
  "publishConfig": {
70
71
  "access": "public"
71
72
  },
72
- "gitHead": "538a0e198d2db14c1e7650c75a645113b4394e71"
73
+ "gitHead": "d360b2ce795e378363765863457cd595f4841b73"
73
74
  }
package/src/boot.ts CHANGED
@@ -20,6 +20,7 @@ import { PlatformMembershipService } from './services/PlatformMembershipService'
20
20
  import { UniqueUserService } from './services/UniqueUserService';
21
21
  import { QueueHandler } from '@stamhoofd/queues';
22
22
  import { SimpleError } from '@simonbackx/simple-errors';
23
+ import { UitpasService } from './services/uitpas/UitpasService';
23
24
 
24
25
  process.on('unhandledRejection', (error: Error) => {
25
26
  console.error('unhandledRejection');
@@ -229,6 +230,7 @@ const start = async () => {
229
230
  PlatformMembershipService.listen();
230
231
  DocumentService.listen();
231
232
  SetupStepUpdater.listen();
233
+ UitpasService.listen();
232
234
 
233
235
  startCrons();
234
236
  seeds().catch(console.error);
@@ -1,5 +1,5 @@
1
1
  import { registerCron } from '@stamhoofd/crons';
2
- import { CachedBalance, Email, EmailRecipient, Organization, Platform, User } from '@stamhoofd/models';
2
+ import { CachedBalance, Email, EmailRecipient, Organization, User } from '@stamhoofd/models';
3
3
  import { IterableSQLSelect, readDynamicSQLExpression, SQL, SQLCalculation, SQLPlusSign } from '@stamhoofd/sql';
4
4
  import { EmailRecipientFilter, EmailRecipientFilterType, EmailRecipientSubfilter, EmailTemplateType, OrganizationEmail, ReceivableBalanceType, StamhoofdFilter } from '@stamhoofd/structures';
5
5
  import { ContextInstance } from '../helpers/Context';
@@ -31,11 +31,6 @@ async function balanceEmails() {
31
31
  savedIterator = Organization.select().limit(10).all();
32
32
  }
33
33
 
34
- const platform = await Platform.getSharedPrivateStruct();
35
-
36
- if (!platform.config.featureFlags.includes('balance-emails')) {
37
- return;
38
- }
39
34
  const systemUser = await User.getSystem();
40
35
 
41
36
  for await (const organization of savedIterator.maxQueries(5)) {
@@ -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 { Organization } from '@stamhoofd/models';
5
- import { SQL, compileToSQLFilter, applySQLSorter } from '@stamhoofd/sql';
5
+ import { SQL, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
6
6
  import { CountFilteredRequest, LimitedFilteredRequest, Organization as OrganizationStruct, PaginatedResponse, PermissionLevel, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
7
7
 
8
8
  import { SQLResultNamespacedRow } from '@simonbackx/simple-database';
@@ -0,0 +1,42 @@
1
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
+ import { UitpasService } from '../../../services/uitpas/UitpasService';
3
+ import { UitpasOrganizersResponse } from '@stamhoofd/structures';
4
+ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
5
+ import { Context } from '../../../helpers/Context';
6
+
7
+ type Params = Record<string, never>;
8
+ class Query extends AutoEncoder {
9
+ @field({ decoder: StringDecoder })
10
+ name: string;
11
+ }
12
+ type Body = undefined;
13
+ type ResponseBody = UitpasOrganizersResponse;
14
+
15
+ export class SearchUitpasOrganizersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
16
+ queryDecoder = Query as Decoder<Query>;
17
+
18
+ protected doesMatch(request: Request): [true, Params] | [false] {
19
+ if (request.method !== 'GET') {
20
+ return [false];
21
+ }
22
+
23
+ const params = Endpoint.parseParameters(request.url, '/organization/search-uitpas-organizers', {});
24
+
25
+ if (params) {
26
+ return [true, params as Params];
27
+ }
28
+ return [false];
29
+ }
30
+
31
+ async handle(request: DecodedRequest<Params, Query, Body>) {
32
+ const organization = await Context.setOrganizationScope();
33
+ await Context.authenticate();
34
+
35
+ if (!await Context.auth.hasFullAccess(organization.id)) {
36
+ throw Context.auth.error();
37
+ }
38
+ const uitpasOrganizersResponse = await UitpasService.searchUitpasOrganizers(request.query.name);
39
+
40
+ return new Response(uitpasOrganizersResponse);
41
+ }
42
+ }
@@ -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 { AuditLog } from '@stamhoofd/models';
5
- import { SQL, SQLFilterDefinitions, SQLSortDefinitions, compileToSQLFilter, applySQLSorter } from '@stamhoofd/sql';
5
+ import { SQL, SQLSortDefinitions, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
6
6
  import { AuditLog as AuditLogStruct, CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
7
7
 
8
8
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
@@ -15,7 +15,7 @@ type Query = LimitedFilteredRequest;
15
15
  type Body = undefined;
16
16
  type ResponseBody = PaginatedResponse<AuditLogStruct[], LimitedFilteredRequest>;
17
17
 
18
- const filterCompilers: SQLFilterDefinitions = auditLogFilterCompilers;
18
+ const filterCompilers = auditLogFilterCompilers;
19
19
  const sorters: SQLSortDefinitions<AuditLog> = auditLogSorters;
20
20
 
21
21
  export class GetAuditLogsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
@@ -46,13 +46,13 @@ export class GetAuditLogsEndpoint extends Endpoint<Params, Query, Body, Response
46
46
  scopeFilter = {
47
47
  organizationId: organization.id,
48
48
  };
49
- } else {
49
+ }
50
+ else {
50
51
  if (!q.filter || typeof q.filter !== 'object' || !('objectId' in q.filter)) {
51
52
  scopeFilter = {
52
53
  organizationId: organization.id,
53
54
  };
54
55
  }
55
-
56
56
  }
57
57
  }
58
58
  else {
@@ -2,21 +2,21 @@ 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 { EventNotification } from '@stamhoofd/models';
5
- import { SQL, SQLFilterDefinitions, SQLSortDefinitions, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
5
+ import { SQL, SQLSortDefinitions, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
6
6
  import { AccessRight, CountFilteredRequest, EventNotification as EventNotificationStruct, LimitedFilteredRequest, PaginatedResponse, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
7
7
 
8
+ import { SQLResultNamespacedRow } from '@simonbackx/simple-database';
8
9
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
9
10
  import { Context } from '../../../helpers/Context';
10
11
  import { eventNotificationsFilterCompilers } from '../../../sql-filters/event-notifications';
11
12
  import { eventNotificationsSorters } from '../../../sql-sorters/event-notifications';
12
- import { SQLResultNamespacedRow } from '@simonbackx/simple-database';
13
13
 
14
14
  type Params = Record<string, never>;
15
15
  type Query = LimitedFilteredRequest;
16
16
  type Body = undefined;
17
17
  type ResponseBody = PaginatedResponse<EventNotificationStruct[], LimitedFilteredRequest>;
18
18
 
19
- const filterCompilers: SQLFilterDefinitions = eventNotificationsFilterCompilers;
19
+ const filterCompilers = eventNotificationsFilterCompilers;
20
20
  const sorters: SQLSortDefinitions<SQLResultNamespacedRow> = eventNotificationsSorters;
21
21
 
22
22
  export class GetEventNotificationsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
@@ -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 { Event } from '@stamhoofd/models';
5
- import { SQL, SQLFilterDefinitions, SQLSortDefinitions, compileToSQLFilter, applySQLSorter } from '@stamhoofd/sql';
5
+ import { SQL, SQLSortDefinitions, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
6
6
  import { CountFilteredRequest, Event as EventStruct, LimitedFilteredRequest, PaginatedResponse, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
7
7
 
8
8
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
@@ -15,7 +15,7 @@ type Query = LimitedFilteredRequest;
15
15
  type Body = undefined;
16
16
  type ResponseBody = PaginatedResponse<EventStruct[], LimitedFilteredRequest>;
17
17
 
18
- const filterCompilers: SQLFilterDefinitions = eventFilterCompilers;
18
+ const filterCompilers = eventFilterCompilers;
19
19
  const sorters: SQLSortDefinitions<Event> = eventSorters;
20
20
 
21
21
  export class GetEventsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
@@ -1,7 +1,7 @@
1
1
  import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
- import { Event, Group, Platform, RegistrationPeriod } from '@stamhoofd/models';
4
- import { AuditLogSource, Event as EventStruct, Group as GroupStruct, GroupType, NamedObject } from '@stamhoofd/structures';
3
+ import { Event, Group, Platform, RegistrationPeriod, Webshop } from '@stamhoofd/models';
4
+ import { AuditLogSource, Event as EventStruct, Group as GroupStruct, GroupType, NamedObject, PermissionLevel } from '@stamhoofd/structures';
5
5
 
6
6
  import { SimpleError } from '@simonbackx/simple-errors';
7
7
  import { SQL, SQLWhereSign } from '@stamhoofd/sql';
@@ -309,6 +309,27 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
309
309
  PatchEventsEndpoint.throwIfAddressIsMissing(event);
310
310
  }
311
311
 
312
+ if (patch.webshopId !== undefined) {
313
+ if (patch.webshopId === null) {
314
+ event.webshopId = null;
315
+ }
316
+ else {
317
+ const webshop = await Webshop.getByID(patch.webshopId);
318
+ if (!webshop || (event.organizationId && webshop.organizationId !== event.organizationId)) {
319
+ throw new SimpleError({
320
+ code: 'not_found',
321
+ message: 'Webshop not found',
322
+ human: $t(`c5f3d2c3-9d7a-473d-ba91-63ce104a2de5`),
323
+ field: 'webshopId',
324
+ });
325
+ }
326
+ if (!await Context.auth.canAccessWebshop(webshop, PermissionLevel.Read)) {
327
+ throw Context.auth.error($t('2ee1d364-8747-430d-8a33-094e01df465e'));
328
+ }
329
+ event.webshopId = webshop.id;
330
+ }
331
+ }
332
+
312
333
  await event.save();
313
334
 
314
335
  if (event.groupId) {
@@ -4,7 +4,7 @@ import { assertSort, CountFilteredRequest, getSortFilter, Group as GroupStruct,
4
4
  import { Decoder } from '@simonbackx/simple-encoding';
5
5
  import { SimpleError } from '@simonbackx/simple-errors';
6
6
  import { Group } from '@stamhoofd/models';
7
- import { applySQLSorter, compileToModernSQLFilter, SQLModernFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
7
+ import { applySQLSorter, compileToSQLFilter, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
8
8
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
9
9
  import { Context } from '../../../helpers/Context';
10
10
  import { groupFilterCompilers } from '../../../sql-filters/groups';
@@ -15,7 +15,7 @@ type Query = LimitedFilteredRequest;
15
15
  type Body = undefined;
16
16
  type ResponseBody = PaginatedResponse<GroupStruct[], LimitedFilteredRequest>;
17
17
 
18
- const filterCompilers: SQLModernFilterDefinitions = groupFilterCompilers;
18
+ const filterCompilers: SQLFilterDefinitions = groupFilterCompilers;
19
19
  const sorters: SQLSortDefinitions<Group> = groupSorters;
20
20
 
21
21
  export class GetGroupsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
@@ -47,11 +47,11 @@ export class GetGroupsEndpoint extends Endpoint<Params, Query, Body, ResponseBod
47
47
  const query = Group.select();
48
48
 
49
49
  if (scopeFilter) {
50
- query.where(await compileToModernSQLFilter(scopeFilter, filterCompilers));
50
+ query.where(await compileToSQLFilter(scopeFilter, filterCompilers));
51
51
  }
52
52
 
53
53
  if (q.filter) {
54
- query.where(await compileToModernSQLFilter(q.filter, filterCompilers));
54
+ query.where(await compileToSQLFilter(q.filter, filterCompilers));
55
55
  }
56
56
 
57
57
  if (q.search) {
@@ -62,13 +62,13 @@ export class GetGroupsEndpoint extends Endpoint<Params, Query, Body, ResponseBod
62
62
  };
63
63
 
64
64
  if (searchFilter) {
65
- query.where(await compileToModernSQLFilter(searchFilter, filterCompilers));
65
+ query.where(await compileToSQLFilter(searchFilter, filterCompilers));
66
66
  }
67
67
  }
68
68
 
69
69
  if (q instanceof LimitedFilteredRequest) {
70
70
  if (q.pageFilter) {
71
- query.where(await compileToModernSQLFilter(q.pageFilter, filterCompilers));
71
+ query.where(await compileToSQLFilter(q.pageFilter, filterCompilers));
72
72
  }
73
73
 
74
74
  q.sort = assertSort(q.sort, [{ key: 'id' }]);
@@ -77,11 +77,12 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
77
77
  const token = await Token.createToken(user);
78
78
 
79
79
  const arr: Body = new PatchableArray();
80
+ const newBirthDay = new Date(existingMember.details.birthDay!.getTime() + 1);
80
81
  const put = MemberWithRegistrationsBlob.create({
81
82
  details: MemberDetails.create({
82
83
  firstName,
83
84
  lastName,
84
- birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
85
+ birthDay: newBirthDay,
85
86
  email: 'anewemail@example.com',
86
87
  }),
87
88
  });
@@ -100,7 +101,7 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
100
101
  const member = response.body.members[0];
101
102
  expect(member.details.firstName).toBe(firstName);
102
103
  expect(member.details.lastName).toBe(lastName);
103
- expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
104
+ expect(member.details.birthDay).toEqual(newBirthDay);
104
105
  expect(member.details.email).toBe('anewemail@example.com'); // this has been merged
105
106
  expect(member.details.alternativeEmails).toHaveLength(0);
106
107
  });
@@ -141,11 +142,12 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
141
142
  const token = await Token.createToken(user);
142
143
 
143
144
  const arr: Body = new PatchableArray();
145
+ const newBirthDay = new Date(existingMember.details.birthDay!.getTime() + 1);
144
146
  const put = MemberWithRegistrationsBlob.create({
145
147
  details: MemberDetails.create({
146
148
  firstName,
147
149
  lastName,
148
- birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
150
+ birthDay: newBirthDay,
149
151
  securityCode: existingMember.details.securityCode,
150
152
  email: 'anewemail@example.com',
151
153
  }),
@@ -165,9 +167,9 @@ describe('Endpoint.PatchOrganizationMembersEndpoint', () => {
165
167
  const member = response.body.members[0];
166
168
  expect(member.details.firstName).toBe(firstName);
167
169
  expect(member.details.lastName).toBe(lastName);
168
- expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
169
- expect(member.details.email).toBe('original@example.com'); // this has been merged
170
- expect(member.details.alternativeEmails).toEqual(['anewemail@example.com']); // this has been merged
170
+ expect(member.details.birthDay).toEqual(newBirthDay);
171
+ expect(member.details.email).toBe('anewemail@example.com'); // this has been merged
172
+ expect(member.details.alternativeEmails).toEqual(['original@example.com']); // this has been merged
171
173
 
172
174
  // Check the registration is still there
173
175
  expect(member.registrations.length).toBe(1);
@@ -14,10 +14,10 @@ import { Context } from '../../../helpers/Context';
14
14
  import { MembershipCharger } from '../../../helpers/MembershipCharger';
15
15
  import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
16
16
  import { SetupStepUpdater } from '../../../helpers/SetupStepUpdater';
17
+ import { MemberNumberService } from '../../../services/MemberNumberService';
17
18
  import { PlatformMembershipService } from '../../../services/PlatformMembershipService';
18
19
  import { RegistrationService } from '../../../services/RegistrationService';
19
20
  import { shouldCheckIfMemberIsDuplicateForPatch } from './shouldCheckIfMemberIsDuplicate';
20
- import { MemberNumberService } from '../../../services/MemberNumberService';
21
21
 
22
22
  type Params = Record<string, never>;
23
23
  type Query = undefined;
@@ -957,7 +957,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
957
957
  throw new SimpleError({
958
958
  code: 'known_member_missing_rights',
959
959
  message: 'Creating known member without sufficient access rights',
960
- human: `${member.details.firstName} is al gekend in ons systeem, maar jouw e-mailadres niet. Om toegang te krijgen heb je de beveiligingscode nodig.`,
960
+ human: $t(`{member} is al gekend in ons systeem, maar jouw e-mailadres niet. Om toegang te krijgen heb je de beveiligingscode nodig.`, { member: member.details.firstName }),
961
961
  statusCode: 400,
962
962
  });
963
963
  }
@@ -24,6 +24,7 @@ export class GetPlatformEndpoint extends Endpoint<Params, Query, Body, ResponseB
24
24
  }
25
25
 
26
26
  async handle(_: DecodedRequest<Params, Query, Body>) {
27
+ await Context.setOptionalOrganizationScope({ willAuthenticate: true });
27
28
  await Context.optionalAuthenticate({ allowWithoutAccount: false });
28
29
 
29
30
  if (Context.optionalAuth?.hasSomePlatformAccess()) {
@@ -1,11 +1,11 @@
1
+ import { Database } from '@simonbackx/simple-database';
1
2
  import { PatchableArray, PatchMap } from '@simonbackx/simple-encoding';
2
3
  import { Endpoint, Request } from '@simonbackx/simple-endpoints';
3
4
  import { GroupFactory, MemberFactory, OrganizationFactory, Platform, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
4
5
  import { MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, OrganizationRecordsConfiguration, Parent, PatchAnswers, PermissionLevel, RecordCategory, RecordSettings, RecordTextAnswer, TranslatedString } from '@stamhoofd/structures';
6
+ import { STExpect, TestUtils } from '@stamhoofd/test-utils';
5
7
  import { testServer } from '../../../../tests/helpers/TestServer';
6
8
  import { PatchUserMembersEndpoint } from './PatchUserMembersEndpoint';
7
- import { Database } from '@simonbackx/simple-database';
8
- import { STExpect, TestUtils } from '@stamhoofd/test-utils';
9
9
 
10
10
  const baseUrl = `/members`;
11
11
  const endpoint = new PatchUserMembersEndpoint();
@@ -69,11 +69,12 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
69
69
  const token = await Token.createToken(user);
70
70
 
71
71
  const arr: Body = new PatchableArray();
72
+ const newBirthDay = new Date(existingMember.details.birthDay!.getTime() + 1);
72
73
  const put = MemberWithRegistrationsBlob.create({
73
74
  details: MemberDetails.create({
74
75
  firstName,
75
76
  lastName,
76
- birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
77
+ birthDay: newBirthDay,
77
78
  email: 'anewemail@example.com',
78
79
  }),
79
80
  });
@@ -92,7 +93,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
92
93
  const member = response.body.members[0];
93
94
  expect(member.details.firstName).toBe(firstName);
94
95
  expect(member.details.lastName).toBe(lastName);
95
- expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
96
+ expect(member.details.birthDay).toEqual(newBirthDay);
96
97
  expect(member.details.email).toBe('anewemail@example.com'); // this has been merged
97
98
  expect(member.details.alternativeEmails).toHaveLength(0);
98
99
  });
@@ -129,11 +130,12 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
129
130
  const token = await Token.createToken(user);
130
131
 
131
132
  const arr: Body = new PatchableArray();
133
+ const newBirthDay = new Date(existingMember.details.birthDay!.getTime() + 1);
132
134
  const put = MemberWithRegistrationsBlob.create({
133
135
  details: MemberDetails.create({
134
136
  firstName,
135
137
  lastName,
136
- birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
138
+ birthDay: newBirthDay,
137
139
  securityCode: existingMember.details.securityCode,
138
140
  email: 'anewemail@example.com',
139
141
  }),
@@ -153,9 +155,9 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
153
155
  const member = response.body.members[0];
154
156
  expect(member.details.firstName).toBe(firstName);
155
157
  expect(member.details.lastName).toBe(lastName);
156
- expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
157
- expect(member.details.email).toBe('original@example.com'); // this has been merged
158
- expect(member.details.alternativeEmails).toEqual(['anewemail@example.com']); // this has been merged
158
+ expect(member.details.birthDay).toEqual(newBirthDay);
159
+ expect(member.details.email).toBe('anewemail@example.com'); // this has been merged
160
+ expect(member.details.alternativeEmails).toEqual(['original@example.com']); // this has been merged
159
161
 
160
162
  // Check the registration is still there
161
163
  expect(member.registrations.length).toBe(1);
@@ -345,6 +345,17 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
345
345
  registration.pricePaid = 0;
346
346
  registration.payingOrganizationId = null;
347
347
 
348
+ if (checkout.isAdminFromSameOrganization) {
349
+ registration.sendConfirmationEmail = checkout.sendConfirmationEmail;
350
+ }
351
+ else {
352
+ registration.sendConfirmationEmail = true;
353
+ if (checkout.asOrganizationId) {
354
+ // use group default
355
+ registration.sendConfirmationEmail = group.privateSettings.sendConfirmationEmailForManualRegistrations;
356
+ }
357
+ }
358
+
348
359
  // NOTE: we don't reset deactivatedAt - registeredAt, because those will get reset when markValid is called later on (while keeping the original registeredAt date)
349
360
  // registration.deactivatedAt = null;
350
361
  // registration.registeredAt = null; // this is required to trigger platform membership updates
@@ -1,12 +1,12 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
- import { assertSort, CountFilteredRequest, getSortFilter, LimitedFilteredRequest, RegistrationPeriod as RegistrationPeriodStruct, PaginatedResponse, StamhoofdFilter } from '@stamhoofd/structures';
2
+ import { assertSort, CountFilteredRequest, getSortFilter, LimitedFilteredRequest, PaginatedResponse, RegistrationPeriod as RegistrationPeriodStruct, StamhoofdFilter } from '@stamhoofd/structures';
3
3
 
4
+ import { Decoder } from '@simonbackx/simple-encoding';
4
5
  import { SimpleError } from '@simonbackx/simple-errors';
5
6
  import { RegistrationPeriod } from '@stamhoofd/models';
6
7
  import { applySQLSorter, compileToSQLFilter, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
7
8
  import { registrationPeriodFilterCompilers } from '../../../sql-filters/registration-periods';
8
9
  import { registrationPeriodSorters } from '../../../sql-sorters/registration-periods';
9
- import { Decoder } from '@simonbackx/simple-encoding';
10
10
 
11
11
  type Params = Record<string, never>;
12
12
  type Query = LimitedFilteredRequest;
@@ -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, applySQLSorter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
6
+ import { applySQLSorter, compileToSQLFilter, 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';
@@ -46,9 +46,7 @@ export class GetDocumentsEndpoint extends Endpoint<Params, Query, Body, Response
46
46
  const query = SQL
47
47
  .select(SQL.wildcard(documentTable))
48
48
  .from(SQL.table(documentTable))
49
- .where(await compileToSQLFilter({
50
- organizationId: organization.id,
51
- }, filterCompilers));
49
+ .where('organizationId', organization.id);
52
50
 
53
51
  if (q.filter) {
54
52
  query.where(await compileToSQLFilter(q.filter, filterCompilers));
@@ -151,7 +149,6 @@ function getDocumentSearchFilter(search: string | null): StamhoofdFilter | null
151
149
  $contains: search,
152
150
  },
153
151
  },
154
- ]
155
- ,
152
+ ],
156
153
  };
157
154
  }
@@ -0,0 +1,38 @@
1
+ import { Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
+ import { UitpasGetClientIdResponse } from '@stamhoofd/structures';
3
+
4
+ import { Context } from '../../../../helpers/Context';
5
+ import { UitpasService } from '../../../../services/uitpas/UitpasService';
6
+
7
+ type Params = Record<string, never>;
8
+ type Query = undefined;
9
+ type Body = undefined;
10
+ type ResponseBody = UitpasGetClientIdResponse;
11
+
12
+ export class GetUitpasClientIdEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
13
+ protected doesMatch(request: Request): [true, Params] | [false] {
14
+ if (request.method !== 'GET') {
15
+ return [false];
16
+ }
17
+
18
+ const params = Endpoint.parseParameters(request.url, '/organization/uitpas-client-id', {});
19
+
20
+ if (params) {
21
+ return [true, params as Params];
22
+ }
23
+ return [false];
24
+ }
25
+
26
+ async handle() {
27
+ const organization = await Context.setOrganizationScope();
28
+ await Context.authenticate();
29
+
30
+ if (!await Context.auth.hasFullAccess(organization.id)) {
31
+ throw Context.auth.error();
32
+ }
33
+
34
+ const resp = new UitpasGetClientIdResponse();
35
+ resp.clientId = await UitpasService.getClientIdFor(organization.id);
36
+ return new Response(resp);
37
+ }
38
+ }
@@ -2,7 +2,7 @@ import { AutoEncoderPatchType, cloneObject, Decoder, isPatchableArray, isPatchMa
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
4
4
  import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, Webshop } from '@stamhoofd/models';
5
- import { BuckarooSettings, Company, MemberResponsibility, OrganizationMetaData, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PermissionRoleDetailed, PermissionRoleForResponsibility, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
5
+ import { BuckarooSettings, Company, MemberResponsibility, OrganizationMetaData, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PermissionRoleDetailed, PermissionRoleForResponsibility, PermissionsResourceType, ResourcePermissions, UitpasClientCredentialsStatus } from '@stamhoofd/structures';
6
6
  import { Formatter } from '@stamhoofd/utility';
7
7
 
8
8
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
@@ -12,6 +12,7 @@ import { MemberUserSyncer } from '../../../../helpers/MemberUserSyncer';
12
12
  import { SetupStepUpdater } from '../../../../helpers/SetupStepUpdater';
13
13
  import { TagHelper } from '../../../../helpers/TagHelper';
14
14
  import { ViesHelper } from '../../../../helpers/ViesHelper';
15
+ import { UitpasService } from '../../../../services/uitpas/UitpasService';
15
16
 
16
17
  type Params = Record<string, never>;
17
18
  type Query = undefined;
@@ -297,6 +298,35 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
297
298
 
298
299
  updateTags = true;
299
300
  }
301
+
302
+ if (request.body.meta.uitpasClientCredentialsStatus) {
303
+ throw new SimpleError({
304
+ code: 'invalid_field',
305
+ message: 'You cannot set the uitpasClientCredentialsStatus manually',
306
+ human: $t('Je kan de status van de UiTPAS-credentials niet handmatig instellen'),
307
+ });
308
+ }
309
+
310
+ if (request.body.meta.uitpasOrganizerId) {
311
+ const oldStatus = organization.meta.uitpasClientCredentialsStatus;
312
+ // re-evaluate the status
313
+ if (oldStatus !== UitpasClientCredentialsStatus.NotConfigured) {
314
+ organization.meta.uitpasClientCredentialsStatus = UitpasClientCredentialsStatus.NotChecked;
315
+ organization.meta.uitpasOrganizerId = request.body.meta.uitpasOrganizerId;
316
+ const { status } = await UitpasService.checkPermissionsFor(organization.id, organization.meta.uitpasOrganizerId);
317
+ organization.meta.uitpasClientCredentialsStatus = status;
318
+ }
319
+
320
+ // human message is ignored here
321
+ // if (human) {
322
+ // const e = new SimpleError({
323
+ // code: 'uitpas-client-credentials-error',
324
+ // message: 'set-uitpas-credentials-returned-human-message',
325
+ // human: human,
326
+ // })
327
+ // errors.addError(e)
328
+ // }
329
+ }
300
330
  }
301
331
 
302
332
  if (request.body.active !== undefined) {