@stamhoofd/backend 2.39.1 → 2.40.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 (198) hide show
  1. package/eslint.config.mjs +5 -0
  2. package/index.ts +81 -74
  3. package/jest.config.cjs +10 -0
  4. package/migrations.ts +16 -14
  5. package/package.json +11 -11
  6. package/src/crons/clear-excel-cache.test.ts +48 -50
  7. package/src/crons/clear-excel-cache.ts +18 -18
  8. package/src/crons/setup-steps.ts +2 -2
  9. package/src/crons.ts +325 -306
  10. package/src/decoders/StringArrayDecoder.ts +7 -7
  11. package/src/decoders/StringNullableDecoder.ts +1 -2
  12. package/src/email-recipient-loaders/members.ts +22 -22
  13. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
  14. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
  15. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
  16. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
  17. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
  18. package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
  19. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
  20. package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
  21. package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
  22. package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
  23. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
  24. package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
  25. package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
  26. package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
  27. package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
  28. package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
  29. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
  30. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
  31. package/src/endpoints/auth/SignupEndpoint.ts +37 -36
  32. package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
  33. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
  34. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
  35. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
  36. package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
  37. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
  38. package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
  39. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
  40. package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
  41. package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
  42. package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
  43. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
  44. package/src/endpoints/global/files/GetFileCache.ts +13 -13
  45. package/src/endpoints/global/files/UploadFile.ts +51 -54
  46. package/src/endpoints/global/files/UploadImage.ts +53 -53
  47. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
  48. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
  49. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
  50. package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
  51. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
  52. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
  53. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
  54. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
  55. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
  56. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
  57. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
  58. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
  59. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
  60. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
  61. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
  62. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
  63. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
  64. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
  65. package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
  66. package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
  67. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
  68. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
  69. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
  70. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
  71. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
  72. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
  73. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
  74. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
  75. package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
  76. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
  77. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
  78. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
  79. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
  80. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
  81. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
  82. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
  83. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
  84. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
  85. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
  86. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
  87. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
  88. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
  89. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
  90. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
  91. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
  92. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
  93. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
  94. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
  95. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
  96. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
  97. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
  98. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
  99. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
  100. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
  101. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
  102. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
  103. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
  104. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
  105. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
  106. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
  107. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
  108. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
  109. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
  110. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
  111. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
  112. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
  113. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
  114. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
  115. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
  116. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
  117. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
  118. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
  119. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
  120. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
  121. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
  122. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
  123. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
  124. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
  125. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
  126. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
  127. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
  128. package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
  129. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
  130. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
  131. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
  132. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
  133. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
  134. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
  135. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
  136. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
  137. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
  138. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
  139. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
  140. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
  141. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
  142. package/src/excel-loaders/members.ts +102 -103
  143. package/src/excel-loaders/payments.ts +155 -156
  144. package/src/helpers/AddressValidator.test.ts +32 -32
  145. package/src/helpers/AddressValidator.ts +128 -122
  146. package/src/helpers/AdminPermissionChecker.ts +339 -236
  147. package/src/helpers/AuthenticatedStructures.ts +233 -134
  148. package/src/helpers/BuckarooHelper.ts +134 -134
  149. package/src/helpers/CheckSettlements.ts +94 -88
  150. package/src/helpers/Context.ts +87 -86
  151. package/src/helpers/CookieHelper.ts +23 -22
  152. package/src/helpers/EmailResumer.ts +10 -10
  153. package/src/helpers/FileCache.ts +62 -62
  154. package/src/helpers/ForwardHandler.test.ts +122 -124
  155. package/src/helpers/ForwardHandler.ts +76 -70
  156. package/src/helpers/MemberUserSyncer.ts +101 -96
  157. package/src/helpers/MembershipCharger.ts +69 -69
  158. package/src/helpers/MembershipHelper.ts +11 -12
  159. package/src/helpers/OpenIDConnectHelper.ts +85 -82
  160. package/src/helpers/PeriodHelper.ts +65 -70
  161. package/src/helpers/StripeHelper.ts +146 -137
  162. package/src/helpers/StripePayoutChecker.ts +51 -52
  163. package/src/helpers/ViesHelper.ts +46 -44
  164. package/src/helpers/fetchToAsyncIterator.ts +14 -14
  165. package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
  166. package/src/middleware/ContextMiddleware.ts +5 -5
  167. package/src/migrations/1646578856-validate-addresses.ts +6 -9
  168. package/src/seeds/0000000000-example.ts +3 -5
  169. package/src/seeds/1715028563-user-permissions.ts +16 -18
  170. package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
  171. package/src/seeds/1722344162-sync-member-users.ts +14 -15
  172. package/src/seeds/1722344162-update-membership.ts +6 -6
  173. package/src/seeds/1726055544-balance-item-paid.ts +4 -4
  174. package/src/seeds/1726055545-balance-item-pending.ts +4 -4
  175. package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
  176. package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
  177. package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
  178. package/src/seeds/1726847064-setup-steps.ts +16 -0
  179. package/src/sql-filters/balance-item-payments.ts +7 -7
  180. package/src/sql-filters/events.ts +14 -14
  181. package/src/sql-filters/members.ts +96 -96
  182. package/src/sql-filters/organizations.ts +139 -75
  183. package/src/sql-filters/payments.ts +28 -28
  184. package/src/sql-filters/registrations.ts +14 -14
  185. package/src/sql-sorters/events.ts +25 -25
  186. package/src/sql-sorters/members.ts +26 -26
  187. package/src/sql-sorters/organizations.ts +36 -36
  188. package/src/sql-sorters/payments.ts +26 -26
  189. package/tests/e2e/stock.test.ts +616 -621
  190. package/tests/e2e/tickets.test.ts +255 -260
  191. package/tests/helpers/StripeMocker.ts +177 -179
  192. package/tests/helpers/TestServer.ts +9 -9
  193. package/tests/jest.global.setup.ts +14 -13
  194. package/tests/jest.setup.ts +33 -32
  195. package/.eslintrc.js +0 -61
  196. package/jest.config.js +0 -11
  197. package/src/helpers/SetupStepsUpdater.ts +0 -359
  198. package/src/seeds/1724076679-setup-steps.ts +0 -16
@@ -1,30 +1,30 @@
1
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
- import { GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from "@stamhoofd/structures";
1
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
+ import { GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from '@stamhoofd/structures';
3
3
 
4
- import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from "@simonbackx/simple-encoding";
5
- import { SimpleError } from "@simonbackx/simple-errors";
6
- import { Group, Member, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
7
- import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
8
- import { Context } from "../../../../helpers/Context";
4
+ import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
5
+ import { SimpleError } from '@simonbackx/simple-errors';
6
+ import { Group, Member, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from '@stamhoofd/models';
7
+ import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
8
+ import { Context } from '../../../../helpers/Context';
9
9
 
10
10
  type Params = Record<string, never>;
11
11
  type Query = undefined;
12
- type Body = PatchableArrayAutoEncoder<OrganizationRegistrationPeriodStruct>
13
- type ResponseBody = OrganizationRegistrationPeriodStruct[]
12
+ type Body = PatchableArrayAutoEncoder<OrganizationRegistrationPeriodStruct>;
13
+ type ResponseBody = OrganizationRegistrationPeriodStruct[];
14
14
 
15
15
  /**
16
16
  * One endpoint to create, patch and delete members and their registrations and payments
17
17
  */
18
18
 
19
19
  export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
20
- bodyDecoder = new PatchableArrayDecoder(OrganizationRegistrationPeriodStruct as Decoder<OrganizationRegistrationPeriodStruct>, OrganizationRegistrationPeriodStruct.patchType() as Decoder<AutoEncoderPatchType<OrganizationRegistrationPeriodStruct>>, StringDecoder)
20
+ bodyDecoder = new PatchableArrayDecoder(OrganizationRegistrationPeriodStruct as Decoder<OrganizationRegistrationPeriodStruct>, OrganizationRegistrationPeriodStruct.patchType() as Decoder<AutoEncoderPatchType<OrganizationRegistrationPeriodStruct>>, StringDecoder);
21
21
 
22
22
  protected doesMatch(request: Request): [true, Params] | [false] {
23
- if (request.method != "PATCH") {
23
+ if (request.method !== 'PATCH') {
24
24
  return [false];
25
25
  }
26
26
 
27
- const params = Endpoint.parseParameters(request.url, "/organization/registration-periods", {});
27
+ const params = Endpoint.parseParameters(request.url, '/organization/registration-periods', {});
28
28
 
29
29
  if (params) {
30
30
  return [true, params as Params];
@@ -34,17 +34,17 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
34
34
 
35
35
  async handle(request: DecodedRequest<Params, Query, Body>) {
36
36
  const organization = await Context.setOrganizationScope();
37
- await Context.authenticate()
37
+ await Context.authenticate();
38
38
 
39
39
  if (!await Context.auth.hasFullAccess(organization.id)) {
40
- throw Context.auth.error()
40
+ throw Context.auth.error();
41
41
  }
42
42
 
43
43
  const periods: OrganizationRegistrationPeriod[] = [];
44
44
 
45
- for (const {put} of request.body.getPuts()) {
45
+ for (const { put } of request.body.getPuts()) {
46
46
  if (!await Context.auth.hasFullAccess(organization.id)) {
47
- throw Context.auth.error()
47
+ throw Context.auth.error();
48
48
  }
49
49
  periods.push(await PatchOrganizationRegistrationPeriodsEndpoint.createOrganizationPeriod(organization, put));
50
50
  }
@@ -53,143 +53,144 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
53
53
  const organizationPeriod = await OrganizationRegistrationPeriod.getByID(patch.id);
54
54
  if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
55
55
  throw new SimpleError({
56
- code: "not_found",
57
- message: "Period not found",
58
- statusCode: 404
59
- })
56
+ code: 'not_found',
57
+ message: 'Period not found',
58
+ statusCode: 404,
59
+ });
60
60
  }
61
61
 
62
62
  const period = await RegistrationPeriod.getByID(organizationPeriod.periodId);
63
63
 
64
64
  if (!period) {
65
65
  throw new SimpleError({
66
- code: "not_found",
67
- message: "Period not found",
68
- statusCode: 404
69
- })
66
+ code: 'not_found',
67
+ message: 'Period not found',
68
+ statusCode: 404,
69
+ });
70
70
  }
71
71
 
72
72
  if (period.locked) {
73
73
  throw new SimpleError({
74
- code: "not_found",
75
- message: "Period not found",
74
+ code: 'not_found',
75
+ message: 'Period not found',
76
76
  human: 'Je kan geen wijzigingen meer aanbrengen in ' + period.getStructure().name + ' omdat deze is afgesloten',
77
- statusCode: 404
78
- })
77
+ statusCode: 404,
78
+ });
79
79
  }
80
80
 
81
- let deleteUnreachable = false
82
- const allowedIds: string[] = []
81
+ let deleteUnreachable = false;
82
+ const allowedIds: string[] = [];
83
83
 
84
- //#region prevent patch category lock if no full platform access
84
+ // #region prevent patch category lock if no full platform access
85
85
  const originalCategories = organizationPeriod.settings.categories;
86
86
 
87
87
  if (await Context.auth.hasFullAccess(organization.id)) {
88
88
  if (patch.settings) {
89
- if(patch.settings.categories) {
89
+ if (patch.settings.categories) {
90
90
  deleteUnreachable = true;
91
91
  }
92
92
  organizationPeriod.settings.patchOrPut(patch.settings);
93
93
  }
94
- } else {
94
+ }
95
+ else {
95
96
  /// Only allow editing category group ids
96
97
  if (patch.settings) {
97
98
  // Only allow adding groups if we have create permissions in a given category group
98
99
  if (patch.settings.categories && !Array.isArray(patch.settings.categories)) {
99
100
  for (const pp of patch.settings.categories.getPatches()) {
100
- const category = organizationPeriod.settings.categories.find(c => c.id === pp.id)
101
+ const category = organizationPeriod.settings.categories.find(c => c.id === pp.id);
101
102
  if (!category) {
102
103
  // Fail silently
103
- continue
104
+ continue;
104
105
  }
105
-
106
+
106
107
  if (!await Context.auth.canCreateGroupInCategory(organization.id, category)) {
107
- throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen in deze categorie')
108
+ throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen in deze categorie');
108
109
  }
109
-
110
+
110
111
  // Only process puts
111
- const ids = pp.groupIds.getPuts().map(p => p.put)
112
- allowedIds.push(...ids)
113
- category.groupIds.push(...ids)
112
+ const ids = pp.groupIds.getPuts().map(p => p.put);
113
+ allowedIds.push(...ids);
114
+ category.groupIds.push(...ids);
114
115
  }
115
-
116
+
116
117
  if (allowedIds.length > 0) {
117
- deleteUnreachable = true
118
+ deleteUnreachable = true;
118
119
  }
119
120
  }
120
121
  }
121
122
  }
122
123
 
123
- //#region handle locked categories
124
- if(!Context.auth.hasPlatformFullAccess()) {
124
+ // #region handle locked categories
125
+ if (!Context.auth.hasPlatformFullAccess()) {
125
126
  const categoriesAfterPatch = organizationPeriod.settings.categories;
126
127
 
127
- for(const categoryBefore of originalCategories) {
128
+ for (const categoryBefore of originalCategories) {
128
129
  const locked = categoryBefore.settings.locked;
129
130
 
130
- if(locked) {
131
+ if (locked) {
131
132
  // todo: use existing function, now a category could still be deleted if the category is moved to another category and that catetory is deleted
132
133
  const categoryId = categoryBefore.id;
133
134
  const refCountBefore = originalCategories.filter(c => c.categoryIds.includes(categoryId)).length;
134
135
  const refCountAfter = categoriesAfterPatch.filter(c => c.categoryIds.includes(categoryId)).length;
135
136
  const isDeleted = refCountAfter < refCountBefore;
136
137
 
137
- if(isDeleted) {
138
- throw Context.auth.error('Je hebt geen toegangsrechten om deze vergrendelde categorie te verwijderen.')
138
+ if (isDeleted) {
139
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze vergrendelde categorie te verwijderen.');
139
140
  }
140
141
  }
141
142
 
142
143
  const categoryAfter = categoriesAfterPatch.find(c => c.id === categoryBefore.id);
143
-
144
- if(!categoryAfter) {
145
- if(locked) {
146
- throw Context.auth.error('Je hebt geen toegangsrechten om deze vergrendelde categorie te verwijderen.')
144
+
145
+ if (!categoryAfter) {
146
+ if (locked) {
147
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze vergrendelde categorie te verwijderen.');
147
148
  }
148
- } else if(locked !== categoryAfter.settings.locked) {
149
- throw Context.auth.error('Je hebt geen toegangsrechten om deze categorie te vergrendelen of ontgrendelen.')
149
+ }
150
+ else if (locked !== categoryAfter.settings.locked) {
151
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze categorie te vergrendelen of ontgrendelen.');
150
152
  }
151
153
 
152
- if(!locked || !categoryAfter) {
154
+ if (!locked || !categoryAfter) {
153
155
  continue;
154
156
  }
155
157
 
156
158
  const settingsBefore = categoryBefore.settings;
157
159
  const settingsAfter = categoryAfter.settings;
158
160
 
159
- if(settingsBefore.name !== settingsAfter.name) {
160
- throw Context.auth.error('Je hebt geen toegangsrechten de naam van deze vergrendelde categorie te wijzigen.')
161
+ if (settingsBefore.name !== settingsAfter.name) {
162
+ throw Context.auth.error('Je hebt geen toegangsrechten de naam van deze vergrendelde categorie te wijzigen.');
161
163
  }
162
164
  }
163
165
  }
164
- //#endregion
166
+ // #endregion
165
167
 
166
168
  await organizationPeriod.save();
167
169
 
168
170
  // Check changes to groups
169
- const deleteGroups = patch.groups.getDeletes()
171
+ const deleteGroups = patch.groups.getDeletes();
170
172
  if (deleteGroups.length > 0) {
171
173
  for (const id of deleteGroups) {
172
- await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(id)
173
- deleteUnreachable = true
174
+ await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(id);
175
+ deleteUnreachable = true;
174
176
  }
175
177
  }
176
178
 
177
179
  for (const groupPut of patch.groups.getPuts()) {
178
- await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(groupPut.put, organization.id, period, {allowedIds})
179
- deleteUnreachable = true
180
+ await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(groupPut.put, organization.id, period, { allowedIds });
181
+ deleteUnreachable = true;
180
182
  }
181
183
 
182
184
  for (const struct of patch.groups.getPatches()) {
183
- await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(struct, period)
185
+ await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(struct, period);
184
186
  }
185
187
 
186
-
187
188
  if (deleteUnreachable) {
188
- const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
189
+ const groups = await Group.getAll(organization.id, organizationPeriod.periodId);
189
190
 
190
191
  // Delete unreachable categories first
191
192
  await organizationPeriod.cleanCategories(groups);
192
- await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
193
+ await Group.deleteUnreachable(organization.id, organizationPeriod, groups);
193
194
  }
194
195
 
195
196
  periods.push(organizationPeriod);
@@ -200,22 +201,22 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
200
201
  );
201
202
  }
202
203
 
203
- static async validateDefaultGroupId(id: string|null): Promise<string|null> {
204
+ static async validateDefaultGroupId(id: string | null): Promise<string | null> {
204
205
  if (id === null) {
205
206
  return id;
206
207
  }
207
- const platform = await Platform.getSharedStruct()
208
+ const platform = await Platform.getSharedStruct();
208
209
 
209
210
  if (platform.config.defaultAgeGroups.find(g => g.id === id)) {
210
211
  return id;
211
212
  }
212
213
 
213
214
  throw new SimpleError({
214
- code: "invalid_default_age_group",
215
- message: "Invalid default age group",
216
- human: "De standaard leeftijdsgroep is ongeldig",
217
- statusCode: 400
218
- })
215
+ code: 'invalid_default_age_group',
216
+ message: 'Invalid default age group',
217
+ human: 'De standaard leeftijdsgroep is ongeldig',
218
+ statusCode: 400,
219
+ });
219
220
  }
220
221
 
221
222
  static async createOrganizationPeriod(organization: Organization, struct: OrganizationRegistrationPeriodStruct) {
@@ -223,10 +224,10 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
223
224
 
224
225
  if (!period || period.locked) {
225
226
  throw new SimpleError({
226
- code: "not_found",
227
- message: "Period not found",
228
- statusCode: 404
229
- })
227
+ code: 'not_found',
228
+ message: 'Period not found',
229
+ statusCode: 404,
230
+ });
230
231
  }
231
232
 
232
233
  const organizationPeriod = new OrganizationRegistrationPeriod();
@@ -237,75 +238,75 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
237
238
  await organizationPeriod.save();
238
239
 
239
240
  for (const s of struct.groups) {
240
- await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(s, organization.id, period)
241
+ await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(s, organization.id, period);
241
242
  }
242
- const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
243
+ const groups = await Group.getAll(organization.id, organizationPeriod.periodId);
243
244
 
244
245
  // Delete unreachable categories first
245
246
  await organizationPeriod.cleanCategories(groups);
246
- await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
247
+ await Group.deleteUnreachable(organization.id, organizationPeriod, groups);
247
248
 
248
- return organizationPeriod
249
+ return organizationPeriod;
249
250
  }
250
251
 
251
252
  static async deleteGroup(id: string) {
252
- const model = await Group.getByID(id)
253
+ const model = await Group.getByID(id);
253
254
  if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
254
- throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te verwijderen')
255
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te verwijderen');
255
256
  }
256
257
 
257
- model.deletedAt = new Date()
258
- await model.save()
259
- Member.updateMembershipsForGroupId(id)
258
+ model.deletedAt = new Date();
259
+ await model.save();
260
+ Member.updateMembershipsForGroupId(id);
260
261
  }
261
262
 
262
263
  static async patchGroup(struct: AutoEncoderPatchType<GroupStruct>, period?: RegistrationPeriod | null) {
263
- const model = await Group.getByID(struct.id)
264
+ const model = await Group.getByID(struct.id);
264
265
 
265
266
  if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
266
- throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen')
267
+ throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen');
267
268
  }
268
269
 
269
270
  if (struct.settings) {
270
- struct.settings.period = undefined // Not allowed to patch manually
271
- model.settings.patchOrPut(struct.settings)
271
+ struct.settings.period = undefined; // Not allowed to patch manually
272
+ model.settings.patchOrPut(struct.settings);
272
273
  }
273
274
 
274
275
  if (struct.status) {
275
- model.status = struct.status
276
+ model.status = struct.status;
276
277
  }
277
-
278
+
278
279
  if (struct.privateSettings) {
279
- model.privateSettings.patchOrPut(struct.privateSettings)
280
+ model.privateSettings.patchOrPut(struct.privateSettings);
280
281
 
281
282
  if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
282
283
  throw new SimpleError({
283
- code: "missing_permissions",
284
- message: "You cannot restrict your own permissions",
285
- human: "Je kan je eigen volledige toegang tot deze inschrijvingsgroep niet verwijderen. Vraag aan een hoofdbeheerder om jouw toegang te verwijderen."
286
- })
284
+ code: 'missing_permissions',
285
+ message: 'You cannot restrict your own permissions',
286
+ human: 'Je kan je eigen volledige toegang tot deze inschrijvingsgroep niet verwijderen. Vraag aan een hoofdbeheerder om jouw toegang te verwijderen.',
287
+ });
287
288
  }
288
289
  }
289
290
 
290
291
  if (struct.cycle !== undefined) {
291
- model.cycle = struct.cycle
292
+ model.cycle = struct.cycle;
292
293
  }
293
294
 
294
295
  if (struct.deletedAt !== undefined) {
295
- model.deletedAt = struct.deletedAt
296
+ model.deletedAt = struct.deletedAt;
296
297
  }
297
298
 
298
299
  if (struct.defaultAgeGroupId !== undefined) {
299
- model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId)
300
+ model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId);
300
301
  }
301
302
 
302
303
  if (!period && !model.settings.period) {
303
- period = await RegistrationPeriod.getByID(model.periodId)
304
+ period = await RegistrationPeriod.getByID(model.periodId);
304
305
  }
305
306
 
306
307
  if (period) {
307
- model.periodId = period.id
308
- model.settings.period = period.getBaseStructure()
308
+ model.periodId = period.id;
309
+ model.settings.period = period.getBaseStructure();
309
310
  }
310
311
 
311
312
  const patch = struct;
@@ -317,34 +318,35 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
317
318
  // await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
318
319
  model.waitingListId = null;
319
320
  }
320
-
321
- } else if (patch.waitingList.isPatch()) {
321
+ }
322
+ else if (patch.waitingList.isPatch()) {
322
323
  if (!model.waitingListId) {
323
324
  throw new SimpleError({
324
325
  code: 'invalid_field',
325
326
  field: 'waitingList',
326
- message: 'Cannot patch waiting list before it is created'
327
- })
327
+ message: 'Cannot patch waiting list before it is created',
328
+ });
328
329
  }
329
- patch.waitingList.id = model.waitingListId
330
- patch.waitingList.type = GroupType.WaitingList
331
- await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.waitingList, period)
332
- } else {
330
+ patch.waitingList.id = model.waitingListId;
331
+ patch.waitingList.type = GroupType.WaitingList;
332
+ await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.waitingList, period);
333
+ }
334
+ else {
333
335
  if (model.waitingListId) {
334
336
  // for now don't delete, as waiting lists can be shared between multiple groups
335
337
  // await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
336
338
  model.waitingListId = null;
337
339
  }
338
- patch.waitingList.type = GroupType.WaitingList
340
+ patch.waitingList.type = GroupType.WaitingList;
339
341
 
340
- const existing = await Group.getByID(patch.waitingList.id)
342
+ const existing = await Group.getByID(patch.waitingList.id);
341
343
  if (existing) {
342
344
  if (existing.organizationId !== model.organizationId) {
343
345
  throw new SimpleError({
344
346
  code: 'invalid_field',
345
347
  field: 'waitingList',
346
- message: 'Waiting list group is already used in another organization'
347
- })
348
+ message: 'Waiting list group is already used in another organization',
349
+ });
348
350
  }
349
351
 
350
352
  if (existing.periodId !== model.periodId) {
@@ -352,111 +354,112 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
352
354
  code: 'invalid_field',
353
355
  field: 'waitingList',
354
356
  message: 'Waiting list group is already used in another period',
355
- human: 'Een wachtlijst kan momenteel niet gedeeld worden tussen verschillende werkjaren'
356
- })
357
+ human: 'Een wachtlijst kan momenteel niet gedeeld worden tussen verschillende werkjaren',
358
+ });
357
359
  }
358
360
 
359
- model.waitingListId = existing.id
360
- } else {
361
- const requiredPeriod = period ?? await RegistrationPeriod.getByID(model.periodId)
361
+ model.waitingListId = existing.id;
362
+ }
363
+ else {
364
+ const requiredPeriod = period ?? await RegistrationPeriod.getByID(model.periodId);
362
365
 
363
366
  if (!requiredPeriod) {
364
- throw new Error('Unexpected missing period when creating waiting list')
367
+ throw new Error('Unexpected missing period when creating waiting list');
365
368
  }
366
369
  const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
367
370
  patch.waitingList,
368
371
  model.organizationId,
369
- requiredPeriod
370
- )
371
- model.waitingListId = group.id
372
+ requiredPeriod,
373
+ );
374
+ model.waitingListId = group.id;
372
375
  }
373
376
  }
374
377
  }
375
378
 
376
-
377
- await model.updateOccupancy()
379
+ await model.updateOccupancy();
378
380
  await model.save();
379
381
 
380
382
  if (struct.deletedAt !== undefined || struct.defaultAgeGroupId !== undefined) {
381
- Member.updateMembershipsForGroupId(model.id)
383
+ Member.updateMembershipsForGroupId(model.id);
382
384
  }
383
385
  }
384
386
 
385
-
386
- static async createGroup(struct: GroupStruct, organizationId: string, period: RegistrationPeriod, options?: {allowedIds?: string[]}): Promise<Group> {
387
- const allowedIds = options?.allowedIds ?? []
387
+ static async createGroup(struct: GroupStruct, organizationId: string, period: RegistrationPeriod, options?: { allowedIds?: string[] }): Promise<Group> {
388
+ const allowedIds = options?.allowedIds ?? [];
388
389
 
389
390
  if (!await Context.auth.hasFullAccess(organizationId)) {
390
391
  if (allowedIds.includes(struct.id)) {
391
392
  // Ok
392
- } else {
393
- throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen')
393
+ }
394
+ else {
395
+ throw Context.auth.error('Je hebt geen toegangsrechten om groepen toe te voegen');
394
396
  }
395
397
  }
396
398
 
397
- const user = Context.auth.user
399
+ const user = Context.auth.user;
398
400
 
399
- const model = new Group()
400
- model.id = struct.id
401
- model.organizationId = organizationId
402
- model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId)
403
- model.periodId = period.id
404
- model.settings = struct.settings
405
- model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
406
- model.status = struct.status
407
- model.type = struct.type
408
- model.settings.period = period.getBaseStructure()
401
+ const model = new Group();
402
+ model.id = struct.id;
403
+ model.organizationId = organizationId;
404
+ model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId);
405
+ model.periodId = period.id;
406
+ model.settings = struct.settings;
407
+ model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({});
408
+ model.status = struct.status;
409
+ model.type = struct.type;
410
+ model.settings.period = period.getBaseStructure();
409
411
 
410
412
  if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
411
413
  // Create a temporary permission role for this user
412
- const organizationPermissions = user.permissions?.organizationPermissions?.get(organizationId)
414
+ const organizationPermissions = user.permissions?.organizationPermissions?.get(organizationId);
413
415
  if (!organizationPermissions) {
414
- throw new Error('Unexpected missing permissions')
416
+ throw new Error('Unexpected missing permissions');
415
417
  }
416
418
  const resourcePermissions = ResourcePermissions.create({
417
419
  resourceName: model.settings.name,
418
- level: PermissionLevel.Full
419
- })
420
- const patch = resourcePermissions.createInsertPatch(PermissionsResourceType.Groups, model.id, organizationPermissions)
421
- user.permissions!.organizationPermissions.set(organizationId, organizationPermissions.patch(patch))
422
- console.log('Automatically granted author full permissions to resource', 'group', model.id, 'user', user.id, 'patch', patch.encode({version: Version}))
423
- await user.save()
420
+ level: PermissionLevel.Full,
421
+ });
422
+ const patch = resourcePermissions.createInsertPatch(PermissionsResourceType.Groups, model.id, organizationPermissions);
423
+ user.permissions!.organizationPermissions.set(organizationId, organizationPermissions.patch(patch));
424
+ console.log('Automatically granted author full permissions to resource', 'group', model.id, 'user', user.id, 'patch', patch.encode({ version: Version }));
425
+ await user.save();
424
426
  }
425
427
 
426
428
  // Check if current user has permissions to this new group -> else fail with error
427
429
  if (!await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
428
430
  throw new SimpleError({
429
- code: "missing_permissions",
430
- message: "You cannot restrict your own permissions",
431
- human: "Je kan geen inschrijvingsgroep maken zonder dat je zelf volledige toegang hebt tot de nieuwe groep"
432
- })
431
+ code: 'missing_permissions',
432
+ message: 'You cannot restrict your own permissions',
433
+ human: 'Je kan geen inschrijvingsgroep maken zonder dat je zelf volledige toegang hebt tot de nieuwe groep',
434
+ });
433
435
  }
434
436
 
435
437
  if (struct.waitingList) {
436
- const existing = await Group.getByID(struct.waitingList.id)
438
+ const existing = await Group.getByID(struct.waitingList.id);
437
439
  if (existing) {
438
440
  if (existing.organizationId !== model.organizationId) {
439
441
  throw new SimpleError({
440
442
  code: 'invalid_field',
441
443
  field: 'waitingList',
442
- message: 'Waiting list group is already used in another organization'
443
- })
444
+ message: 'Waiting list group is already used in another organization',
445
+ });
444
446
  }
445
447
 
446
- model.waitingListId = existing.id
447
- } else {
448
- struct.waitingList.type = GroupType.WaitingList
448
+ model.waitingListId = existing.id;
449
+ }
450
+ else {
451
+ struct.waitingList.type = GroupType.WaitingList;
449
452
  const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
450
453
  struct.waitingList,
451
454
  model.organizationId,
452
- period
453
- )
454
- model.waitingListId = group.id
455
+ period,
456
+ );
457
+ model.waitingListId = group.id;
455
458
  }
456
459
  }
457
460
 
458
461
  await model.save();
462
+ await model.updateOccupancy({ isNew: true }); // Force update steps
459
463
  return model;
460
464
  }
461
-
462
465
  }