@stamhoofd/backend 2.121.0 → 2.122.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 (429) hide show
  1. package/output-2.txt +1 -0
  2. package/output.txt +1 -0
  3. package/package.json +69 -52
  4. package/src/audit-logs/EventNotificationLogger.ts +40 -0
  5. package/src/audit-logs/ModelLogger.ts +19 -15
  6. package/src/audit-logs/OrganizationLogger.ts +1 -1
  7. package/src/audit-logs/RegistrationInvitationLogger.ts +24 -24
  8. package/src/audit-logs/init.ts +8 -2
  9. package/src/boot.ts +19 -18
  10. package/src/crons/amazon-ses.ts +13 -26
  11. package/src/crons/balance-emails.ts +3 -5
  12. package/src/crons/clearExcelCache.test.ts +1 -1
  13. package/src/crons/clearExcelCache.ts +1 -2
  14. package/src/crons/drip-emails.ts +42 -0
  15. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +4 -0
  16. package/src/crons/helpers/isOutside.ts +11 -0
  17. package/src/crons/helpers/useSavedIterator.ts +32 -0
  18. package/src/crons/index.ts +5 -0
  19. package/src/crons/invoices.ts +32 -38
  20. package/src/crons/members-fees.ts +50 -0
  21. package/src/crons/mollie-chargebacks.ts +14 -15
  22. package/src/crons/service-fees.ts +152 -0
  23. package/src/crons/stripe-invoices.ts +48 -0
  24. package/src/crons/transfer-fees.ts +176 -0
  25. package/src/crons/update-cached-balances.ts +4 -4
  26. package/src/crons.ts +7 -64
  27. package/src/debug.ts +4 -2
  28. package/src/email-recipient-loaders/documents.ts +1 -1
  29. package/src/email-recipient-loaders/members.ts +1 -1
  30. package/src/email-recipient-loaders/payments.ts +4 -6
  31. package/src/email-recipient-loaders/registrations.ts +1 -1
  32. package/src/email-replacements/getEmailReplacementsForPayment.ts +5 -9
  33. package/src/endpoints/admin/members/ChargeMembersEndpoint.ts +1 -1
  34. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -1
  35. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +3 -5
  36. package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +1 -1
  37. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +1 -1
  38. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +13 -7
  39. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +4 -6
  40. package/src/endpoints/admin/organizations/PatchPackagesEndpoint.test.ts +10 -6
  41. package/src/endpoints/admin/organizations/PatchPackagesEndpoint.ts +39 -21
  42. package/src/endpoints/admin/registrations/ChargeRegistrationsEndpoint.ts +1 -1
  43. package/src/endpoints/auth/CreateAdminEndpoint.ts +23 -5
  44. package/src/endpoints/auth/CreateTokenEndpoint.ts +2 -2
  45. package/src/endpoints/auth/DeleteTokenEndpoint.ts +1 -1
  46. package/src/endpoints/auth/DeleteUserEndpoint.ts +3 -3
  47. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
  48. package/src/endpoints/auth/GetOtherUserEndpoint.ts +1 -1
  49. package/src/endpoints/auth/GetUserEndpoint.ts +1 -1
  50. package/src/endpoints/auth/OpenIDConnectAuthTokenEndpoint.ts +1 -1
  51. package/src/endpoints/auth/OpenIDConnectStartEndpoint.ts +1 -1
  52. package/src/endpoints/auth/PatchUserEndpoint.ts +5 -8
  53. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +1 -1
  54. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +1 -1
  55. package/src/endpoints/auth/SignupEndpoint.ts +2 -3
  56. package/src/endpoints/auth/VerifyEmailEndpoint.ts +20 -3
  57. package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +2 -1
  58. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +2 -4
  59. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +1 -1
  60. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +5 -7
  61. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +2 -2
  62. package/src/endpoints/global/email/CreateEmailEndpoint.ts +3 -4
  63. package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +1 -1
  64. package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +1 -2
  65. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +4 -6
  66. package/src/endpoints/global/email/GetEmailEndpoint.ts +1 -1
  67. package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +1 -1
  68. package/src/endpoints/global/email/GetUserEmailsEndpoint.ts +1 -1
  69. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +6 -10
  70. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +4 -7
  71. package/src/endpoints/global/email/PatchEmailEndpoint.ts +6 -9
  72. package/src/endpoints/global/email-recipients/GetEmailRecipientsCountEndpoint.ts +1 -1
  73. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +1 -1
  74. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.ts +1 -1
  75. package/src/endpoints/global/email-recipients/RetryEmailRecipientEndpoint.ts +2 -2
  76. package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +1 -1
  77. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +3 -3
  78. package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -3
  79. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +49 -4
  80. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +5 -8
  81. package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +12 -44
  82. package/src/endpoints/global/events/PatchEventsEndpoint.ts +128 -127
  83. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -9
  84. package/src/endpoints/global/files/GetFileCache.ts +3 -4
  85. package/src/endpoints/global/files/UploadFile.ts +3 -4
  86. package/src/endpoints/global/files/UploadImage.ts +3 -4
  87. package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +1 -1
  88. package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +1 -1
  89. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +4 -2
  90. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +2 -3
  91. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +1 -1
  92. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +2 -2
  93. package/src/endpoints/global/members/GetMembersEndpoint.ts +10 -12
  94. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +195 -12
  95. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +16 -22
  96. package/src/endpoints/global/members/helpers/validateGroupFilter.ts +6 -30
  97. package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +1 -1
  98. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +1 -1
  99. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +40 -8
  100. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +2 -2
  101. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -2
  102. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +4 -1
  103. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +4 -5
  104. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +9 -16
  105. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +1 -1
  106. package/src/endpoints/global/platform/GetPlatformEndpoint.ts +1 -1
  107. package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +1 -1
  108. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +11 -11
  109. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsCountEndpoint.ts +31 -31
  110. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsEndpoint.ts +18 -20
  111. package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +1 -1
  112. package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1 -1
  113. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +6 -10
  114. package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +35 -6
  115. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +2 -2
  116. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +1 -1
  117. package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +1 -1
  118. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
  119. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -3
  120. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +90 -6
  121. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +35 -34
  122. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +1 -1
  123. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +62 -63
  124. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +6 -9
  125. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +51 -52
  126. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +6 -7
  127. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsCountEndpoint.ts +43 -0
  128. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +1 -1
  129. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +13 -5
  130. package/src/endpoints/global/sso/GetSSOEndpoint.ts +2 -3
  131. package/src/endpoints/global/sso/SetSSOEndpoint.ts +1 -1
  132. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
  133. package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +1 -2
  134. package/src/endpoints/global/webshops/GetWebshopsEndpoint.ts +2 -4
  135. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemEndpoint.ts +2 -2
  136. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsCountEndpoint.ts +1 -1
  137. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.ts +3 -4
  138. package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +7 -9
  139. package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +73 -0
  140. package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.ts +89 -0
  141. package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +1 -5
  142. package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +11 -11
  143. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +2 -2
  144. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +8 -8
  145. package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +9 -9
  146. package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableBalanceEndpoint.ts +2 -2
  147. package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableInvoicesEndpoint.ts +68 -0
  148. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +4 -4
  149. package/src/endpoints/organization/dashboard/billing/GetRegisterCodeEndpoint.ts +66 -0
  150. package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.test.ts +949 -0
  151. package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +60 -57
  152. package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +20 -20
  153. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +1 -1
  154. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +1 -1
  155. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +1 -1
  156. package/src/endpoints/organization/dashboard/documents/GetDocumentsCountEndpoint.ts +1 -1
  157. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +1 -1
  158. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +10 -2
  159. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +2 -2
  160. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.ts +2 -2
  161. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +1 -1
  162. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +1 -1
  163. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +10 -8
  164. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +1 -1
  165. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +5 -6
  166. package/src/endpoints/organization/dashboard/invoices/GetInvoicesCountEndpoint.ts +1 -1
  167. package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +10 -6
  168. package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +11 -2
  169. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +4 -5
  170. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +5 -5
  171. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +2 -2
  172. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +7 -7
  173. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +1 -1
  174. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +2 -2
  175. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +1 -1
  176. package/src/endpoints/organization/dashboard/organization/GetUitpasClientIdEndpoint.ts +1 -1
  177. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -2
  178. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +50 -12
  179. package/src/endpoints/organization/dashboard/organization/SearchUitpasOrganizersEndpoint.ts +2 -2
  180. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +5 -10
  181. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +1 -1
  182. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +1 -1
  183. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +3 -4
  184. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +21 -14
  185. package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +35 -28
  186. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +2 -3
  187. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesCountEndpoint.ts +1 -1
  188. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -1
  189. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
  190. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +2 -3
  191. package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +1 -1
  192. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
  193. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +19 -18
  194. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +4 -5
  195. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
  196. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +2 -3
  197. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +2 -2
  198. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +1 -1
  199. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +2 -2
  200. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +1 -1
  201. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +1 -1
  202. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +1 -1
  203. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +1 -1
  204. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +1 -1
  205. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.ts +4 -6
  206. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +10 -15
  207. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +1 -1
  208. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +2 -2
  209. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +1 -1
  210. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +1 -1
  211. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +1 -1
  212. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +11 -2
  213. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +4 -6
  214. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +22 -7
  215. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +20 -27
  216. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +1 -1
  217. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +2 -2
  218. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +2 -3
  219. package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +95 -0
  220. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -2
  221. package/src/endpoints/organization/shared/GetDocumentHtml.ts +1 -1
  222. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
  223. package/src/endpoints/organization/shared/GetUitpasNumberDetailsEndpoint.ts +3 -5
  224. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +1 -1
  225. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +4 -1
  226. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +14 -3
  227. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -2
  228. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +37 -16
  229. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +34 -2
  230. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +4 -2
  231. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +74 -3
  232. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +16 -33
  233. package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +3 -5
  234. package/src/endpoints/organization/webshops/WebshopAuthHelper.ts +36 -0
  235. package/src/endpoints/system/HealthEndpoint.ts +1 -1
  236. package/src/excel-loaders/balance-items.ts +1 -1
  237. package/src/excel-loaders/event-notifications.ts +1 -1
  238. package/src/excel-loaders/members.ts +1 -1
  239. package/src/excel-loaders/organizations.ts +3 -3
  240. package/src/excel-loaders/payments.ts +31 -1
  241. package/src/excel-loaders/platform-memberships.ts +13 -14
  242. package/src/excel-loaders/receivable-balances.ts +2 -2
  243. package/src/excel-loaders/registrations.ts +2 -3
  244. package/src/helpers/AddressValidator.ts +7 -14
  245. package/src/helpers/AdminPermissionChecker.ts +50 -63
  246. package/src/helpers/AuthenticatedStructures.ts +69 -55
  247. package/src/helpers/BalanceItemRelationsBackfiller.test.ts +174 -0
  248. package/src/helpers/BalanceItemRelationsBackfiller.ts +163 -0
  249. package/src/helpers/BuckarooHelper.ts +2 -4
  250. package/src/helpers/CheckSettlements.ts +12 -24
  251. package/src/helpers/Context.ts +15 -10
  252. package/src/helpers/CookieHelper.ts +1 -2
  253. package/src/helpers/EmailResumer.ts +1 -2
  254. package/src/helpers/FileCache.ts +2 -3
  255. package/src/helpers/FinancialSupportHelper.ts +2 -2
  256. package/src/helpers/FlagMomentCleanup.ts +1 -2
  257. package/src/helpers/ForwardHandler.ts +5 -10
  258. package/src/helpers/GlobalHelper.ts +3 -3
  259. package/src/helpers/LegacyBillingConverter.ts +787 -0
  260. package/src/helpers/MemberMerger.test.ts +782 -0
  261. package/src/helpers/MemberMerger.ts +600 -0
  262. package/src/helpers/MemberUserSyncer.ts +6 -12
  263. package/src/helpers/MembershipCharger.ts +2 -4
  264. package/src/helpers/PaymentCustomerResolver.test.ts +203 -0
  265. package/src/helpers/PaymentCustomerResolver.ts +144 -0
  266. package/src/helpers/PeriodHelper.ts +2 -3
  267. package/src/helpers/RecordAnswerHelper.test.ts +105 -110
  268. package/src/helpers/RecordAnswerHelper.ts +7 -7
  269. package/src/helpers/SeedTools.ts +106 -0
  270. package/src/helpers/ServiceFeeHelper.ts +125 -15
  271. package/src/helpers/SetupStepUpdater.ts +38 -32
  272. package/src/helpers/StamhoofdFilterAccessHelper.ts +52 -0
  273. package/src/helpers/StripeHelper.ts +14 -36
  274. package/src/helpers/StripeInvoicer.ts +419 -0
  275. package/src/helpers/StripePayoutChecker.ts +5 -10
  276. package/src/helpers/TagHelper.ts +7 -8
  277. package/src/helpers/TemporaryMemberAccess.ts +1 -2
  278. package/src/helpers/ThrottledQueue.ts +1 -2
  279. package/src/helpers/UitpasTokenRepository.ts +1 -2
  280. package/src/helpers/ViesHelper.ts +7 -11
  281. package/src/helpers/XlsxTransformerColumnHelper.ts +1 -1
  282. package/src/helpers/outstandingBalanceJoin.ts +2 -2
  283. package/src/helpers/updateMemberDetailsUitpasNumber.ts +3 -5
  284. package/src/migrate.ts +12 -5
  285. package/src/{seeds → migrations}/0000000004-single-organization.ts +10 -1
  286. package/src/migrations/0000000006-default-organization.ts +139 -0
  287. package/src/{seeds → migrations}/1715028563-user-permissions.ts +17 -27
  288. package/src/{seeds/1765896674-document-update-year.test.ts → migrations/1750090029-document-update-year.test.ts} +27 -26
  289. package/src/migrations/1750090029-document-update-year.ts +103 -0
  290. package/src/{seeds → migrations}/1750090030-records-configuration.ts +33 -32
  291. package/src/migrations/1750090031-records-configuration-webshops.ts +36 -0
  292. package/src/migrations/1752848540-national-numbers.ts +585 -0
  293. package/src/migrations/1752848550-records-configuration-group-filter.ts +1064 -0
  294. package/src/{seeds → migrations}/1752848561-groups-registration-periods.ts +70 -26
  295. package/src/{seeds → migrations}/1754560914-groups-prices.test.ts +1 -1
  296. package/src/{seeds → migrations}/1754560914-groups-prices.ts +9 -15
  297. package/src/{seeds → migrations}/1756293699-fill-previous-next-period-id.ts +1 -2
  298. package/src/migrations/1761665607-sync-member-users.ts +40 -0
  299. package/src/migrations/1779121240-invoice-email-template.sql +6 -0
  300. package/src/migrations/1779986893-default-payment-failed-email-templates.sql +4 -0
  301. package/src/migrations/1780578166-convert-legacy-billing.ts +13 -0
  302. package/src/{seeds/1755876819-remove-duplicate-members.ts → migrations/1780665426-remove-duplicate-members.ts} +23 -4
  303. package/src/migrations/1780933598-document-groups.ts +88 -0
  304. package/src/migrations/1781692342-balance-item-relations.ts +13 -0
  305. package/src/migrations/1781857866-platform-admins.ts +103 -0
  306. package/src/migrations/1781857867-rm-old-platform-admins.ts +25 -0
  307. package/src/seeds/0000000001-throw.ts +5 -0
  308. package/src/seeds/1760702452-update-balance-price-paid.ts +19 -0
  309. package/src/seeds/1760702453-update-balance-invoiced.ts +19 -0
  310. package/src/seeds/1760702454-update-cached-outstanding-balance-from-items.ts +51 -14
  311. package/src/seeds/1773754928-force-save-members.ts +41 -8
  312. package/src/seeds/1780665427-schedule-stock-updates.ts +45 -0
  313. package/src/seeds/1780665428-group-update-occupancy.ts +31 -0
  314. package/src/seeds/1780915001-fill-payment-customer.ts +70 -0
  315. package/src/seeds/1780915002-update-orders.ts +39 -0
  316. package/src/seeds/1782047708-organization-company-address.ts +44 -0
  317. package/src/seeds-temporary/1769088653-uitpas-status.ts +1 -2
  318. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.test.ts +69 -0
  319. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.ts +85 -0
  320. package/src/services/AuditLogService.test.ts +51 -0
  321. package/src/services/AuditLogService.ts +45 -0
  322. package/src/services/BalanceItemService.ts +141 -44
  323. package/src/services/CpuService.ts +3 -6
  324. package/src/services/DatabaseCollationService.test.ts +1 -2
  325. package/src/services/DocumentService.ts +1 -2
  326. package/src/services/EventNotificationService.ts +1 -1
  327. package/src/services/FileSignService.ts +2 -4
  328. package/src/services/InvoicePdfService.ts +286 -0
  329. package/src/services/InvoiceService.ts +204 -251
  330. package/src/services/InvoiceXMLService.ts +424 -0
  331. package/src/services/MemberNumberService.ts +6 -11
  332. package/src/services/MemberRecordStore.ts +8 -10
  333. package/src/services/MollieService.ts +114 -121
  334. package/src/services/PaymentMandateService.ts +45 -45
  335. package/src/services/PaymentReallocationService.test.ts +1 -1
  336. package/src/services/PaymentReallocationService.ts +7 -9
  337. package/src/services/PaymentService.ts +444 -308
  338. package/src/services/PlatformMembershipService.ts +4 -8
  339. package/src/services/ReferralService.ts +223 -0
  340. package/src/services/RegistrationService.ts +63 -53
  341. package/src/services/SSOService.test.ts +119 -0
  342. package/src/services/SSOService.ts +56 -49
  343. package/src/services/STPackageService.ts +97 -61
  344. package/src/services/UniqueMemberNumberService.ts +69 -0
  345. package/src/services/UniqueUserService.ts +3 -6
  346. package/src/services/VATService.ts +85 -0
  347. package/src/services/uitpas/PassholderEndpoints.ts +3 -6
  348. package/src/services/uitpas/UitpasService.ts +6 -10
  349. package/src/services/uitpas/checkUitpasNumbers.ts +2 -4
  350. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +3 -6
  351. package/src/services/uitpas/searchUitpasEvents.ts +1 -2
  352. package/src/sql-filters/audit-logs.ts +1 -1
  353. package/src/sql-filters/balance-item-payments.ts +1 -1
  354. package/src/sql-filters/balance-items.ts +1 -1
  355. package/src/sql-filters/base-registration-filter-compilers.ts +5 -33
  356. package/src/sql-filters/document-templates.ts +1 -1
  357. package/src/sql-filters/documents.ts +1 -1
  358. package/src/sql-filters/email-recipients.ts +1 -1
  359. package/src/sql-filters/emails.ts +1 -1
  360. package/src/sql-filters/event-notifications.ts +1 -1
  361. package/src/sql-filters/events.ts +6 -1
  362. package/src/sql-filters/groups.ts +5 -0
  363. package/src/sql-filters/invoiced-balance-items.ts +1 -1
  364. package/src/sql-filters/invoices.ts +1 -1
  365. package/src/sql-filters/member-responsibility-records.ts +1 -1
  366. package/src/sql-filters/members.ts +1 -1
  367. package/src/sql-filters/orders.ts +4 -4
  368. package/src/sql-filters/organization-registration-periods.ts +1 -1
  369. package/src/sql-filters/organizations.ts +1 -1
  370. package/src/sql-filters/platform-memberships.ts +3 -3
  371. package/src/sql-filters/receivable-balances.ts +1 -1
  372. package/src/sql-filters/registration-periods.ts +1 -1
  373. package/src/sql-filters/registrations.ts +1 -1
  374. package/src/sql-filters/tickets.ts +1 -1
  375. package/src/sql-filters/users.ts +1 -1
  376. package/src/sql-sorters/groups.ts +23 -0
  377. package/src/sql-sorters/platform-memberships.ts +16 -16
  378. package/src/sql-sorters/registration-invitations.ts +2 -2
  379. package/tests/actions/patchOrganizationMember.ts +1 -1
  380. package/tests/actions/patchUserMember.ts +1 -1
  381. package/tests/assertions/assertBalances.ts +6 -12
  382. package/tests/e2e/api-rate-limits.test.ts +12 -24
  383. package/tests/e2e/bundle-discounts.test.ts +4 -1
  384. package/tests/e2e/charge-members.test.ts +13 -11
  385. package/tests/e2e/documents.test.ts +4 -1
  386. package/tests/e2e/register.test.ts +7 -9
  387. package/tests/e2e/stock.test.ts +4 -3
  388. package/tests/e2e/tickets.test.ts +1 -1
  389. package/tests/helpers/MollieMocker.ts +462 -0
  390. package/tests/helpers/PayconiqMocker.ts +138 -45
  391. package/tests/helpers/StripeMocker.ts +9 -6
  392. package/tests/helpers/index.ts +4 -0
  393. package/tests/init/initAdmin.ts +2 -2
  394. package/tests/init/initBundleDiscount.ts +1 -1
  395. package/tests/init/initMembershipOrganization.ts +13 -0
  396. package/tests/init/initPermissionRole.ts +3 -4
  397. package/tests/vitest.global.setup.ts +1 -1
  398. package/vitest.config.js +6 -0
  399. package/.nvmrc +0 -1
  400. package/src/helpers/GroupBuilder.ts +0 -418
  401. package/src/seeds/0000000000-example.ts +0 -11
  402. package/src/seeds/1722256498-group-update-occupancy.ts +0 -52
  403. package/src/seeds/1726572303-schedule-stock-updates.ts +0 -52
  404. package/src/seeds/1726847064-setup-steps.ts +0 -16
  405. package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +0 -31
  406. package/src/seeds/1729253172-update-orders.ts +0 -21
  407. package/src/seeds/1733319079-fill-paying-organization-ids.ts +0 -68
  408. package/src/seeds/1733748412-update-deleted-order-numbers.ts +0 -109
  409. package/src/seeds/1733994455-balance-item-status-open.ts +0 -30
  410. package/src/seeds/1740046783-update-membership.ts +0 -17
  411. package/src/seeds/1741008870-fix-auditlog-description.ts +0 -50
  412. package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +0 -88
  413. package/src/seeds/1741268179-fix-group-dates-of-events.ts +0 -32
  414. package/src/seeds/1751445358-upload-email-attachments.ts +0 -106
  415. package/src/seeds/1755532883-update-email-sender-ids.ts +0 -47
  416. package/src/seeds/1755790070-fill-email-recipient-errors.ts +0 -96
  417. package/src/seeds/1756115432-remove-old-drafts.ts +0 -16
  418. package/src/seeds/1756115433-fill-email-recipient-organization-id.ts +0 -30
  419. package/src/seeds/1756303697-update-email-counts.ts +0 -76
  420. package/src/seeds/1761665607-sync-member-users.ts +0 -59
  421. package/src/seeds/1765896674-document-update-year.ts +0 -66
  422. /package/src/{seeds → migrations}/0000000001-development-user.ts +0 -0
  423. /package/src/{seeds → migrations}/0000000002-clear-stamhoofd-email-templates.ts +0 -0
  424. /package/src/{seeds → migrations}/0000000003-default-email-templates.ts +0 -0
  425. /package/src/{seeds → migrations}/1752848561-groups-registration-periods.test.ts +0 -0
  426. /package/src/{seeds → migrations}/1766150402-document-published-at.test.ts +0 -0
  427. /package/src/{seeds → migrations}/1766150402-document-published-at.ts +0 -0
  428. /package/src/{seeds → migrations}/1779121239-default-invoice-email-template.sql +0 -0
  429. /package/src/{seeds → migrations}/data/default-email-templates.sql +0 -0
@@ -0,0 +1,1064 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { cloneObject } from '@simonbackx/simple-encoding';
3
+ import { Group, Organization } from '@stamhoofd/models';
4
+ import type { OrganizationRecordsConfiguration, RecordCategory, StamhoofdCompareValue, StamhoofdFilter } from '@stamhoofd/structures';
5
+ import { mergeFilters, PropertyFilter } from '@stamhoofd/structures';
6
+ // import fs from 'fs';
7
+ import { SeedTools } from '../helpers/SeedTools.js';
8
+
9
+ /**
10
+ * IMPORTANT!
11
+ * This migration should be run before the groups-registration-periods migration.
12
+ */
13
+
14
+ export async function startMigration(dryRun = false) {
15
+ await SeedTools.loop({
16
+ batchSize: 100,
17
+ query: Organization.select(),
18
+ action: async (organization: Organization) => {
19
+ const organizationHandler = new OrganizationHandler(organization);
20
+ await organizationHandler.handle(dryRun);
21
+ },
22
+ });
23
+
24
+ if (dryRun) {
25
+ throw new Error('Migration did not finish because of dryRun');
26
+ }
27
+ }
28
+
29
+ export default new Migration(async () => {
30
+ if (STAMHOOFD.environment === 'test') {
31
+ console.log('skipped in tests');
32
+ return;
33
+ }
34
+
35
+ if (STAMHOOFD.platformName.toLowerCase() !== 'stamhoofd') {
36
+ console.log('skipped for platform (only runs for Stamhoofd): ' + STAMHOOFD.platformName);
37
+ return;
38
+ }
39
+
40
+ const dryRun = false;
41
+ await startMigration(dryRun);
42
+ });
43
+
44
+ class StamhoofdFilterHelper {
45
+ static mergeFiltersOfPropertyFilter(filters: (StamhoofdFilter | null)[], propertyFilterType: 'enabledWhen' | 'requiredWhen', mergeType?: '$and' | '$or'): StamhoofdFilter | null {
46
+ if (propertyFilterType === 'requiredWhen' && filters.some(filter => StamhoofdFilterHelper.isEmptyRecord(filter))) {
47
+ // always required if empty record
48
+ return {};
49
+ }
50
+
51
+ return mergeFilters(filters, mergeType);
52
+ }
53
+
54
+ static isRecordOrArray(value: StamhoofdFilter): value is { [key: string]: StamhoofdFilter } | StamhoofdFilter[] {
55
+ if (typeof value !== 'object' || value === null || value instanceof Date) {
56
+ return false;
57
+ }
58
+
59
+ return true;
60
+ }
61
+
62
+ static isRecord(value: StamhoofdFilter): value is { [key: string]: StamhoofdFilter } {
63
+ if (!this.isRecordOrArray(value) || Array.isArray(value)) {
64
+ return false;
65
+ }
66
+
67
+ return true;
68
+ }
69
+
70
+ static isRecordWithSingleEntry(value: StamhoofdFilter): value is { [key: string]: StamhoofdFilter } {
71
+ if (!this.isRecord(value)) {
72
+ return false;
73
+ }
74
+
75
+ return Object.entries(value).length === 1;
76
+ }
77
+
78
+ static isEmptyRecord(value: StamhoofdFilter): value is { [key: string]: never } {
79
+ if (!this.isRecord(value)) {
80
+ return false;
81
+ }
82
+
83
+ return Object.entries(value).length === 0;
84
+ }
85
+
86
+ static hasRegistrationsFilter(filter: StamhoofdFilter): boolean {
87
+ if (filter === null) {
88
+ return false;
89
+ }
90
+
91
+ if (typeof filter !== 'object') {
92
+ return false;
93
+ }
94
+
95
+ if (filter instanceof Date) {
96
+ return false;
97
+ }
98
+
99
+ if (Array.isArray(filter)) {
100
+ return filter.some(f => this.hasRegistrationsFilter(f));
101
+ }
102
+
103
+ if (this.isRegistrationFilter(filter)) {
104
+ return true;
105
+ }
106
+
107
+ // iterate properties
108
+ for (const [, value] of Object.entries(filter as object) as [string, StamhoofdFilter | StamhoofdCompareValue][]) {
109
+ if (this.hasRegistrationsFilter(value)) {
110
+ return true;
111
+ }
112
+ }
113
+
114
+ return false;
115
+ }
116
+
117
+ static isRegistrationFilter(filter: StamhoofdFilter): filter is { registrations: StamhoofdFilter } & StamhoofdFilter {
118
+ if (!StamhoofdFilterHelper.isRecord(filter)) {
119
+ return false;
120
+ }
121
+
122
+ return Object.entries(filter).some(entry => entry[0] === 'registrations');
123
+ }
124
+
125
+ static splitOrFilterFromRoot(filter: StamhoofdFilter): StamhoofdFilter[] {
126
+ if (filter === null) {
127
+ return [null];
128
+ }
129
+ if (!this.isRecord(filter)) {
130
+ throw new Error('The root should be a record: ' + JSON.stringify(filter));
131
+ }
132
+
133
+ const entries = Object.entries(filter);
134
+ if (entries.length > 1) {
135
+ // in theory could be possible but this is not expected based on the data -> we do not support this
136
+ throw new Error('The root unexepctedly has more than one entry');
137
+ }
138
+
139
+ if (entries.length === 0) {
140
+ return [filter];
141
+ }
142
+
143
+ const [key, value] = entries[0];
144
+ if (key === '$or') {
145
+ if (Array.isArray(value)) {
146
+ return value;
147
+ }
148
+
149
+ if (!this.isRecord(value)) {
150
+ throw new Error('The $or value is not a record');
151
+ }
152
+
153
+ return Object.entries(value).map(([key, value]) => {
154
+ return {
155
+ [key]: value,
156
+ };
157
+ });
158
+ }
159
+
160
+ return [filter];
161
+ }
162
+
163
+ static throwIfHasOrFilter(filter: StamhoofdFilter, skipKeys: string[]): void {
164
+ if (!this.isRecordOrArray(filter)) {
165
+ return;
166
+ }
167
+
168
+ if (Array.isArray(filter)) {
169
+ filter.forEach(f => this.throwIfHasOrFilter(f, skipKeys));
170
+ return;
171
+ }
172
+
173
+ Object.entries(filter).forEach(([key, value]) => {
174
+ if (key === '$or') {
175
+ throw new Error('The $or filter is not supported');
176
+ }
177
+ if (skipKeys.includes(key)) {
178
+ return;
179
+ }
180
+
181
+ this.throwIfHasOrFilter(value, skipKeys);
182
+ });
183
+ }
184
+ }
185
+
186
+ class RegistrationsFilterHandler {
187
+ private _isInverted: boolean = false;
188
+ private _groupdIds: Set<string> | null = null;
189
+ private _otherFilters: StamhoofdFilter[] | null = null;
190
+ private _didFindRegistrationsFilter = false;
191
+
192
+ get isInverted() {
193
+ return this._isInverted;
194
+ }
195
+
196
+ get groupIds() {
197
+ return this._groupdIds!;
198
+ }
199
+
200
+ get otherFilters() {
201
+ return this._otherFilters!;
202
+ }
203
+
204
+ constructor(private readonly filter: StamhoofdFilter) {
205
+ this.initFromFilter(filter);
206
+ }
207
+
208
+ private initFromFilter(filter: StamhoofdFilter) {
209
+ this._otherFilters = this.readAndFlattenFilter(filter, false);
210
+
211
+ if (this._groupdIds === null) {
212
+ throw new Error('groupdIds is null');
213
+ }
214
+
215
+ if (this._otherFilters === null) {
216
+ throw new Error('otherFilters is null');
217
+ }
218
+
219
+ if (this._didFindRegistrationsFilter === false) {
220
+ throw new Error('didFindRegistrationsFilter is false');
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Reads data from registration filter and return an array of other filters as a flat array
226
+ * @param filter
227
+ */
228
+ private readAndFlattenFilter(filter: StamhoofdFilter, isInverted: boolean): StamhoofdFilter[] {
229
+ // filter should not contain any $or filters
230
+
231
+ if (!StamhoofdFilterHelper.isRecordOrArray(filter)) {
232
+ throw new Error('Invalid filter: ' + JSON.stringify(filter));
233
+ }
234
+
235
+ if (Array.isArray(filter)) {
236
+ return filter.flatMap(item => this.readAndFlattenFilter(item, isInverted));
237
+ }
238
+
239
+ const all: StamhoofdFilter[] = [];
240
+
241
+ for (const [key, value] of Object.entries(filter)) {
242
+ if (key === '$or') {
243
+ throw new Error('The $or filter is not supported');
244
+ }
245
+
246
+ if (key === '$not') {
247
+ if (isInverted) {
248
+ throw new Error('Double inversion, check filter: ' + JSON.stringify(this.filter));
249
+ }
250
+ isInverted = true;
251
+ }
252
+
253
+ if (key === 'registrations') {
254
+ if (isInverted) {
255
+ this._isInverted = true;
256
+ }
257
+
258
+ this.readRegistrationFilter({ [key]: value });
259
+ this._didFindRegistrationsFilter = true;
260
+ continue;
261
+ }
262
+
263
+ if (!StamhoofdFilterHelper.isRecordOrArray(value)) {
264
+ all.push({ [key]: value });
265
+ continue;
266
+ }
267
+
268
+ if (Array.isArray(value)) {
269
+ if (value.length === 0) {
270
+ continue;
271
+ }
272
+
273
+ const isSomeRecordOrArray = value.some(item => StamhoofdFilterHelper.isRecordOrArray(item));
274
+
275
+ if (!isSomeRecordOrArray) {
276
+ all.push({ [key]: value });
277
+ continue;
278
+ }
279
+
280
+ for (const item of value) {
281
+ // ignore null
282
+ if (item === null) {
283
+ continue;
284
+ }
285
+
286
+ const isRecordOrArray = StamhoofdFilterHelper.isRecordOrArray(item);
287
+ if (!isRecordOrArray) {
288
+ throw new Error('Invalid filter: ' + JSON.stringify(filter));
289
+ }
290
+
291
+ all.push(...this.readAndFlattenFilter(item, isInverted).map(filter => ({ [key]: filter })));
292
+ }
293
+ continue;
294
+ }
295
+
296
+ all.push(...this.readAndFlattenFilter(value, isInverted).map(filter => ({ [key]: filter })));
297
+ }
298
+
299
+ return all;
300
+ }
301
+
302
+ private readRegistrationFilter(registrationFilter: { registrations: StamhoofdFilter }): void {
303
+ if (this._didFindRegistrationsFilter) {
304
+ throw new Error('Found more than one registrations filter: ' + JSON.stringify(this.filter));
305
+ }
306
+
307
+ if (!StamhoofdFilterHelper.isRecordWithSingleEntry(registrationFilter)) {
308
+ throw new Error('Invalid registration filter: ' + JSON.stringify(registrationFilter));
309
+ }
310
+
311
+ const entries = Object.entries(registrationFilter);
312
+
313
+ let currentEntry = entries[0];
314
+
315
+ while (true) {
316
+ const currentKey = currentEntry[0];
317
+
318
+ switch (currentKey) {
319
+ case 'registrations':
320
+ case '$elemMatch':
321
+ case '$or':
322
+ {
323
+ break;
324
+ }
325
+ case '$not': {
326
+ if (this._isInverted === true) {
327
+ throw new Error('Double inversion, check filter: ' + JSON.stringify(this.filter));
328
+ }
329
+ this._isInverted = true;
330
+ break;
331
+ }
332
+ case '$and': {
333
+ const array = currentEntry[1];
334
+
335
+ // only support 1 item
336
+ if (!Array.isArray(array)) {
337
+ throw new Error(`The $and filter is not an array, check filter: ${JSON.stringify(this.filter)}`);
338
+ }
339
+
340
+ // only support 1 item (because the mode does not matter in that case)
341
+ if (array.length < 2) {
342
+ break;
343
+ }
344
+
345
+ // or is default mode
346
+ throw new Error(`The $and filter is not supported, check filter: ${JSON.stringify(this.filter)}`);
347
+ }
348
+ default: throw new Error(`Invalid registration filter (currentKey: ${currentKey}): ` + JSON.stringify(registrationFilter));
349
+ }
350
+
351
+ const currentValue = currentEntry[1];
352
+
353
+ if (StamhoofdFilterHelper.isRecordWithSingleEntry(currentValue)) {
354
+ currentEntry = Object.entries(currentValue)[0];
355
+ } else if (Array.isArray(currentValue)) {
356
+ this._groupdIds = new Set(currentValue.map(item => RegistrationsFilterHandler.getGroupIdsFromGroupFilters(item)));
357
+ break;
358
+ } else {
359
+ throw new Error('Invalid registration filter: ' + JSON.stringify(registrationFilter));
360
+ }
361
+ }
362
+ }
363
+
364
+ private static getGroupIdsFromGroupFilters(filter: StamhoofdFilter) {
365
+ const groupId = (filter as { group?: { id?: { $eq?: string } } })?.group?.id?.$eq;
366
+ if (typeof groupId !== 'string') {
367
+ throw new Error('Invalid group filter: ' + JSON.stringify(filter));
368
+ }
369
+ return groupId;
370
+ }
371
+
372
+ static getGroupIdsFromRegistrationsFilter(registrationFilter: { registrations: StamhoofdFilter }): string[] {
373
+ if (!StamhoofdFilterHelper.isRecordWithSingleEntry(registrationFilter)) {
374
+ throw new Error('Invalid registration filter: ' + JSON.stringify(registrationFilter));
375
+ }
376
+
377
+ const entries = Object.entries(registrationFilter);
378
+
379
+ let currentEntry = entries[0];
380
+
381
+ while (true) {
382
+ const currentKey = currentEntry[0];
383
+
384
+ switch (currentKey) {
385
+ case 'registrations':
386
+ case '$elemMatch':
387
+ case '$or':
388
+ case '$and':
389
+ case '$not':
390
+ {
391
+ break;
392
+ }
393
+ default: throw new Error(`Invalid registration filter (currentKey: ${currentKey}): ` + JSON.stringify(registrationFilter));
394
+ }
395
+
396
+ const currentValue = currentEntry[1];
397
+
398
+ if (StamhoofdFilterHelper.isRecordWithSingleEntry(currentValue)) {
399
+ currentEntry = Object.entries(currentValue)[0];
400
+ } else if (Array.isArray(currentValue)) {
401
+ return currentValue.map(item => this.getGroupIdsFromGroupFilters(item));
402
+ } else {
403
+ throw new Error('Invalid registration filter: ' + JSON.stringify(registrationFilter));
404
+ }
405
+ }
406
+ }
407
+
408
+ static getGroupIdsFromFilter(filter: StamhoofdFilter): string[] {
409
+ if (!StamhoofdFilterHelper.isRecordOrArray(filter)) {
410
+ throw new Error('Invalid filter: ' + JSON.stringify(filter));
411
+ }
412
+
413
+ if (Array.isArray(filter)) {
414
+ return filter.flatMap(item => this.getGroupIdsFromFilter(item));
415
+ }
416
+
417
+ const all: string[] = [];
418
+
419
+ for (const [key, value] of Object.entries(filter)) {
420
+ if (key === 'registrations') {
421
+ all.push(...this.getGroupIdsFromRegistrationsFilter({ [key]: value }));
422
+ continue;
423
+ }
424
+ if (!StamhoofdFilterHelper.isRecordOrArray(value)) {
425
+ continue;
426
+ }
427
+
428
+ if (Array.isArray(value)) {
429
+ if (value.length === 0) {
430
+ continue;
431
+ }
432
+
433
+ const isSomeRecordOrArray = value.some(item => StamhoofdFilterHelper.isRecordOrArray(item));
434
+
435
+ if (!isSomeRecordOrArray) {
436
+ continue;
437
+ }
438
+
439
+ for (const item of value) {
440
+ // ignore null
441
+ if (item === null) {
442
+ continue;
443
+ }
444
+
445
+ const isRecordOrArray = StamhoofdFilterHelper.isRecordOrArray(item);
446
+ if (!isRecordOrArray) {
447
+ throw new Error('Invalid filter: ' + JSON.stringify(filter));
448
+ }
449
+
450
+ all.push(...this.getGroupIdsFromFilter(item));
451
+ }
452
+ continue;
453
+ }
454
+
455
+ all.push(...this.getGroupIdsFromFilter(value));
456
+ }
457
+
458
+ return all;
459
+ }
460
+ }
461
+
462
+ class PropertyFilterTracker {
463
+ enabledWhen?: StamhoofdFilter;
464
+ requiredWhen?: StamhoofdFilter;
465
+
466
+ set(filter: StamhoofdFilter, type: 'enabledWhen' | 'requiredWhen', mergeType: '$and' | '$or'): void {
467
+ if (this[type] === undefined) {
468
+ this[type] = filter;
469
+ return;
470
+ }
471
+
472
+ this[type] = StamhoofdFilterHelper.mergeFiltersOfPropertyFilter([this[type], filter], type, mergeType);
473
+ }
474
+
475
+ clone(): PropertyFilterTracker {
476
+ const result = new PropertyFilterTracker();
477
+ result.enabledWhen = cloneObject(this.enabledWhen);
478
+ result.requiredWhen = cloneObject(this.requiredWhen);
479
+ return result;
480
+ }
481
+
482
+ createPropertyFilter(): PropertyFilter {
483
+ return new PropertyFilter(this.enabledWhen === undefined ? null : this.enabledWhen, this.requiredWhen === undefined ? null : this.requiredWhen);
484
+ }
485
+
486
+ static mergeIntoNew(a: PropertyFilterTracker, b: PropertyFilterTracker): PropertyFilterTracker {
487
+ const enabledWhen = this.mergeFilters(a.enabledWhen, b.enabledWhen, { type: 'enabledWhen', mergeType: '$and' });
488
+ const requiredWhen = this.mergeFilters(a.requiredWhen, b.requiredWhen, { type: 'requiredWhen', mergeType: '$and' });
489
+
490
+ const result = new PropertyFilterTracker();
491
+ result.enabledWhen = enabledWhen;
492
+ result.requiredWhen = requiredWhen;
493
+ return result;
494
+ }
495
+
496
+ private static mergeFilters(a: StamhoofdFilter | undefined, b: StamhoofdFilter | undefined, { type, mergeType }: { type: 'enabledWhen' | 'requiredWhen'; mergeType: '$and' | '$or' }): StamhoofdFilter | undefined {
497
+ if (a === undefined && b === undefined) {
498
+ return undefined;
499
+ }
500
+
501
+ if (a === undefined) {
502
+ return b;
503
+ }
504
+
505
+ if (b === undefined) {
506
+ return a;
507
+ }
508
+
509
+ return StamhoofdFilterHelper.mergeFiltersOfPropertyFilter([a, b], type, mergeType);
510
+ }
511
+ }
512
+
513
+ class GroupsPropertyFiltersTracker {
514
+ static readonly GLOBAL_KEY = 'all';
515
+
516
+ // to apply in one of these groups (key can be GLOBAL_KEY if all groups)
517
+ private readonly groupPropertyFiltersMap: Map<string, PropertyFilterTracker> = new Map();
518
+
519
+ // to apply if not in one of these groups
520
+ private readonly invertedGroupPropertyFiltersMap: Map<string, PropertyFilterTracker> = new Map();
521
+
522
+ private hasEnabledWhenFilterOnlyRegistrationFilters: boolean | null = null;
523
+
524
+ constructor(propertyFilter: PropertyFilter) {
525
+ (['enabledWhen', 'requiredWhen'] as ('enabledWhen' | 'requiredWhen')[]).forEach(type => this.updateFromFilter(propertyFilter, type));
526
+ }
527
+
528
+ get shouldUpdateAllGroups() {
529
+ return this.groupPropertyFiltersMap.has(GroupsPropertyFiltersTracker.GLOBAL_KEY) || this.invertedGroupPropertyFiltersMap.size > 0;
530
+ }
531
+
532
+ get groupIdsToUpdate(): string[] {
533
+ return [...this.groupPropertyFiltersMap.keys()].filter(key => key !== GroupsPropertyFiltersTracker.GLOBAL_KEY);
534
+ }
535
+
536
+ private updateFromFilter(propertyFilter: PropertyFilter, type: 'enabledWhen' | 'requiredWhen') {
537
+ // clone to prevent side effects
538
+ const filter = cloneObject(propertyFilter[type]);
539
+ const orFilters = StamhoofdFilterHelper.splitOrFilterFromRoot(filter);
540
+
541
+ // make sure there are no other $or filters (else the filter possibly cannot be moved)
542
+ orFilters.forEach((filter) => {
543
+ // should not contain $or filter on deeper level than root (unless in registrations filter)
544
+ try {
545
+ StamhoofdFilterHelper.throwIfHasOrFilter(filter, ['registrations']);
546
+ } catch (e) {
547
+ console.error('Error:', e);
548
+ console.error(`${type}: ${JSON.stringify(filter)}`);
549
+ throw e;
550
+ }
551
+ });
552
+
553
+ const globalFilters: StamhoofdFilter[] = [];
554
+ const groupSpecificFilters: StamhoofdFilter[] = [];
555
+
556
+ for (const filter of orFilters) {
557
+ if (StamhoofdFilterHelper.hasRegistrationsFilter(filter)) {
558
+ groupSpecificFilters.push(filter);
559
+ } else {
560
+ globalFilters.push(filter);
561
+ }
562
+ }
563
+
564
+ if (globalFilters.length > 0) {
565
+ if (type === 'enabledWhen') {
566
+ this.set({
567
+ key: GroupsPropertyFiltersTracker.GLOBAL_KEY,
568
+ type,
569
+ filter: StamhoofdFilterHelper.mergeFiltersOfPropertyFilter(globalFilters, type, '$or'),
570
+ isInverted: false,
571
+ mergeType: '$or',
572
+ });
573
+ } else {
574
+ if (this.hasEnabledWhenFilterOnlyRegistrationFilters === null) {
575
+ throw new Error('hasEnabledWhenFilterOnlyRegistrationFilters is null, this should be set first');
576
+ }
577
+
578
+ if (this.hasEnabledWhenFilterOnlyRegistrationFilters) {
579
+ // override existing group specific filters
580
+ for (const [key, value] of this.groupPropertyFiltersMap) {
581
+ if (key === GroupsPropertyFiltersTracker.GLOBAL_KEY) {
582
+ continue;
583
+ }
584
+
585
+ value.requiredWhen = StamhoofdFilterHelper.mergeFiltersOfPropertyFilter(globalFilters, type, '$or');
586
+ }
587
+
588
+ // override existing inverted group specific filters
589
+ for (const [, value] of this.invertedGroupPropertyFiltersMap) {
590
+ value.requiredWhen = StamhoofdFilterHelper.mergeFiltersOfPropertyFilter(globalFilters, type, '$or');
591
+ }
592
+
593
+ if (groupSpecificFilters.length > 0) {
594
+ throw new Error('combination of group specific filters in enabledWhen and requiredWhen is not supported, :' + JSON.stringify(propertyFilter));
595
+ }
596
+
597
+ return;
598
+ } else {
599
+ this.set({
600
+ key: GroupsPropertyFiltersTracker.GLOBAL_KEY,
601
+ type,
602
+ filter: StamhoofdFilterHelper.mergeFiltersOfPropertyFilter(globalFilters, type, '$or'),
603
+ isInverted: false,
604
+ mergeType: '$or',
605
+ });
606
+ }
607
+ }
608
+ }
609
+
610
+ const defaultValue = type === 'enabledWhen' ? null : {};
611
+
612
+ for (const groupSpecificFilter of groupSpecificFilters) {
613
+ const registrationFilterHandler = new RegistrationsFilterHandler(groupSpecificFilter);
614
+
615
+ for (const groupId of registrationFilterHandler.groupIds.values()) {
616
+ const otherFilters: StamhoofdFilter = registrationFilterHandler.otherFilters;
617
+ const filter = Array.isArray(otherFilters) && otherFilters.length === 0 ? defaultValue : otherFilters;
618
+
619
+ this.set({
620
+ key: groupId,
621
+ type,
622
+ filter,
623
+ isInverted: registrationFilterHandler.isInverted,
624
+ mergeType: '$or',
625
+ });
626
+ }
627
+ }
628
+
629
+ if (type === 'enabledWhen') {
630
+ this.hasEnabledWhenFilterOnlyRegistrationFilters = globalFilters.length === 0 && groupSpecificFilters.length > 0;
631
+ }
632
+ }
633
+
634
+ private set({ type, key, filter, isInverted, mergeType }: { type: 'enabledWhen' | 'requiredWhen'; key: string; filter: StamhoofdFilter; isInverted: boolean; mergeType: '$and' | '$or' }) {
635
+ const map = isInverted ? this.invertedGroupPropertyFiltersMap : this.groupPropertyFiltersMap;
636
+ let propertyFilterTracker: PropertyFilterTracker | undefined = map.get(key);
637
+ if (propertyFilterTracker === undefined) {
638
+ propertyFilterTracker = new PropertyFilterTracker();
639
+ map.set(key, propertyFilterTracker);
640
+ }
641
+
642
+ propertyFilterTracker.set(filter, type, mergeType);
643
+ }
644
+
645
+ private get globalPropertyFilter(): PropertyFilterTracker | undefined {
646
+ return this.groupPropertyFiltersMap.get(GroupsPropertyFiltersTracker.GLOBAL_KEY);
647
+ }
648
+
649
+ private groupInvertedFiltersCache: Map<string, { tracker: PropertyFilterTracker; groups: string[] }> | null = null;
650
+
651
+ private groupInvertedFilters(): Map<string, { tracker: PropertyFilterTracker; groups: string[] }> {
652
+ if (this.groupInvertedFiltersCache) {
653
+ return this.groupInvertedFiltersCache;
654
+ }
655
+
656
+ const result = new Map<string, { tracker: PropertyFilterTracker; groups: string[] }>();
657
+ for (const [key, value] of this.invertedGroupPropertyFiltersMap) {
658
+ const stringified = JSON.stringify(value);
659
+ let data = result.get(stringified);
660
+ if (!data) {
661
+ data = { tracker: value, groups: [] };
662
+ result.set(stringified, data);
663
+ }
664
+ data.groups.push(key);
665
+ }
666
+
667
+ this.groupInvertedFiltersCache = result;
668
+
669
+ return result;
670
+ }
671
+
672
+ /**
673
+ * Merges all property filters for the given group
674
+ * @param group
675
+ * @returns
676
+ */
677
+ getPropertyFilterForGroup(group: Group): PropertyFilter | null {
678
+ const allPropertyFilterTrackers: PropertyFilterTracker[] = [];
679
+ const globalPropertyFilter = this.globalPropertyFilter;
680
+ if (globalPropertyFilter) {
681
+ allPropertyFilterTrackers.push(globalPropertyFilter);
682
+ }
683
+
684
+ const groupSpecific = this.groupPropertyFiltersMap.get(group.id);
685
+ if (groupSpecific) {
686
+ allPropertyFilterTrackers.push(groupSpecific);
687
+ }
688
+
689
+ if (this.invertedGroupPropertyFiltersMap.size > 0) {
690
+ const groupInvertedFilters = this.groupInvertedFilters();
691
+ for (const [, value] of groupInvertedFilters) {
692
+ if (!value.groups.includes(group.id)) {
693
+ allPropertyFilterTrackers.push(value.tracker);
694
+ }
695
+ }
696
+ }
697
+
698
+ if (allPropertyFilterTrackers.length === 0) {
699
+ return null;
700
+ }
701
+
702
+ const first = allPropertyFilterTrackers[0];
703
+ let result = first.clone();
704
+
705
+ for (const propertyFilter of allPropertyFilterTrackers.slice(1)) {
706
+ result = PropertyFilterTracker.mergeIntoNew(result, propertyFilter);
707
+ }
708
+
709
+ return result.createPropertyFilter();
710
+ }
711
+ }
712
+
713
+ class PropertyFilterHandler {
714
+ readonly name: string;
715
+ private readonly propertyFilter: PropertyFilter;
716
+ private readonly recordCategory: RecordCategory | null;
717
+ private readonly setPropertyFilterOnOrganization: (propertyFilter: PropertyFilter | null) => void;
718
+ private readonly setPropertyFilterOnGroup: (group: Group, propertyFilter: PropertyFilter) => void;
719
+ readonly groupPropertyFiltersTracker: GroupsPropertyFiltersTracker;
720
+
721
+ get original(): PropertyFilter {
722
+ return this.propertyFilter.clone();
723
+ }
724
+
725
+ constructor({ name, propertyFilter, setPropertyFilterOnOrganization, setPropertyFilterOnGroup, recordCategory }: {
726
+ // name is only used for logging
727
+ name: string; propertyFilter: PropertyFilter;
728
+ // to set or remove propertyFilter on the organization
729
+ setPropertyFilterOnOrganization: (propertyFilter: PropertyFilter | null) => void;
730
+ // to set the propertyFilter on the group
731
+ setPropertyFilterOnGroup: (group: Group, propertyFilter: PropertyFilter) => void;
732
+ // only if the property filter is for a record category
733
+ recordCategory: RecordCategory | null;
734
+ }) {
735
+ if (!PropertyFilterHandler.hasRegistrationFilter(propertyFilter)) {
736
+ throw new Error('No registration filter found');
737
+ }
738
+
739
+ this.name = name;
740
+ this.propertyFilter = propertyFilter;
741
+ this.setPropertyFilterOnOrganization = setPropertyFilterOnOrganization;
742
+ this.setPropertyFilterOnGroup = setPropertyFilterOnGroup;
743
+ this.recordCategory = recordCategory;
744
+ this.groupPropertyFiltersTracker = new GroupsPropertyFiltersTracker(propertyFilter);
745
+ }
746
+
747
+ static hasRegistrationFilter(propertyFilter: PropertyFilter): boolean {
748
+ return [propertyFilter.enabledWhen, propertyFilter.requiredWhen].some(filter => StamhoofdFilterHelper.hasRegistrationsFilter(filter));
749
+ }
750
+
751
+ static async shouldHandlePropertyFilter(propertyFilter: PropertyFilter): Promise<boolean> {
752
+ if (StamhoofdFilterHelper.hasRegistrationsFilter(propertyFilter.enabledWhen)) {
753
+ return true;
754
+ }
755
+
756
+ // to make sure logic is ok
757
+ if (!StamhoofdFilterHelper.hasRegistrationsFilter(propertyFilter.requiredWhen)) {
758
+ return false;
759
+ }
760
+
761
+ const groupIds = RegistrationsFilterHandler.getGroupIdsFromFilter(propertyFilter.requiredWhen);
762
+ if (groupIds.length === 0) {
763
+ // should never happen, if this happens there is a bug
764
+ throw new Error('No groupIds found: ' + JSON.stringify(propertyFilter));
765
+ }
766
+
767
+ // if only registration filters in requiredwhen -> check if at least one group exists
768
+ const groups = await Group.select().where('id', groupIds).where('deletedAt', null).count();
769
+
770
+ const shouldHandleFilter = groups !== 0;
771
+
772
+ if (!shouldHandleFilter) {
773
+ const orFilters = StamhoofdFilterHelper.splitOrFilterFromRoot(propertyFilter.requiredWhen);
774
+ if (orFilters.length > 1) {
775
+ // or filters are not supported for now
776
+ throw new Error('Only one or filter expected: ' + JSON.stringify(propertyFilter));
777
+ }
778
+ propertyFilter.requiredWhen = null;
779
+ }
780
+
781
+ return shouldHandleFilter;
782
+ }
783
+
784
+ handleOrganization() {
785
+ // property filter handler only can be created if there is a registration filter -> therefore no need to check
786
+ // the organization should always be updated
787
+
788
+ // if category -> set defaultEnabled to false
789
+ if (this.recordCategory) {
790
+ this.recordCategory.defaultEnabled = false;
791
+ this.setPropertyFilterOnOrganization(new PropertyFilter(null, this.propertyFilter.requiredWhen));
792
+ return;
793
+ }
794
+
795
+ this.setPropertyFilterOnOrganization(null);
796
+ }
797
+
798
+ handleGroup(group: Group): PropertyFilter | null {
799
+ const propertyFilter = this.groupPropertyFiltersTracker.getPropertyFilterForGroup(group);
800
+
801
+ if (propertyFilter) {
802
+ this.setPropertyFilterOnGroup(group, propertyFilter);
803
+ }
804
+
805
+ return propertyFilter;
806
+ }
807
+ }
808
+
809
+ /**
810
+ * Only for testing purposes
811
+ */
812
+ class PropertyChangesLoggerHelper {
813
+ private static readonly organizionIdsToLog = new Set();
814
+ private readonly logInfo = new Map<string, Map<string, Set<string>>>();
815
+ private static readonly FILE_PATH: string = 'property-filter-changes.txt';
816
+ static readonly SHOULD_LOG = false;
817
+ private shouldLog = PropertyChangesLoggerHelper.SHOULD_LOG;
818
+
819
+ static didInitOneTime = false;
820
+
821
+ constructor(private readonly organization: Organization, private readonly propertyFilterHandlers: PropertyFilterHandler[]) {
822
+ if (!PropertyChangesLoggerHelper.didInitOneTime) {
823
+ PropertyChangesLoggerHelper.didInitOneTime = true;
824
+ // clear output
825
+
826
+ // fs.writeFileSync(PropertyChangesLoggerHelper.FILE_PATH, '');
827
+ // throw new Error('not implemented');
828
+ }
829
+
830
+ if (!this.shouldLog) {
831
+ this.shouldLog = PropertyChangesLoggerHelper.organizionIdsToLog.has(this.organization.id);
832
+ }
833
+ }
834
+
835
+ add(name: string, group: Group, propertyFilter: PropertyFilter | null) {
836
+ if (!this.shouldLog) {
837
+ return;
838
+ }
839
+
840
+ let filterMap = this.logInfo.get(name);
841
+ if (!filterMap) {
842
+ filterMap = new Map<string, Set<string>>();
843
+ this.logInfo.set(name, filterMap);
844
+ }
845
+
846
+ const propertyFilterText = JSON.stringify(propertyFilter);
847
+
848
+ let groups = filterMap.get(propertyFilterText);
849
+ if (!groups) {
850
+ groups = new Set();
851
+ filterMap.set(propertyFilterText, groups);
852
+ }
853
+
854
+ groups.add(group.id);
855
+ }
856
+
857
+ private logTextToFile(text: string) {
858
+ try {
859
+ // fs.appendFileSync(PropertyChangesLoggerHelper.FILE_PATH, text);
860
+ throw new Error('not implemented');
861
+ } catch (err) {
862
+ console.error('Error writing file:', err);
863
+ }
864
+ }
865
+
866
+ static shouldLog(organization: Organization): boolean {
867
+ if (this.SHOULD_LOG) {
868
+ return true;
869
+ }
870
+
871
+ return PropertyChangesLoggerHelper.organizionIdsToLog.has(organization.id);
872
+ }
873
+
874
+ doLog() {
875
+ if (!this.shouldLog) {
876
+ return;
877
+ }
878
+
879
+ const changed = [...this.logInfo.entries()].map(([name, filterMap]) => {
880
+ const original = this.propertyFilterHandlers.find(handler => handler.name === name)!.original;
881
+
882
+ return {
883
+ [name]: {
884
+ original,
885
+ changes: [...filterMap.entries()].map(([propertyFilterText, groups]) => {
886
+ return {
887
+ propertyFilter: propertyFilterText,
888
+ groups: [...groups],
889
+ };
890
+ }) },
891
+ };
892
+ });
893
+
894
+ const allInfo = {
895
+ organization: {
896
+ id: this.organization.id,
897
+ name: this.organization.name,
898
+ },
899
+ changed,
900
+ };
901
+
902
+ this.logTextToFile(JSON.stringify(allInfo) + ',');
903
+ }
904
+ }
905
+
906
+ class OrganizationHandler {
907
+ constructor(readonly organization: Organization) {}
908
+
909
+ async handle(dryRun = false) {
910
+ const propertyFilterHandlers = await this.getAllPropertyFilterHandlers(this.organization);
911
+ if (propertyFilterHandlers.length === 0) {
912
+ return;
913
+ }
914
+
915
+ const query = Group.select().where('organizationId', this.organization.id).where('deletedAt', null);
916
+
917
+ let skipGroups = false;
918
+
919
+ if (!propertyFilterHandlers.some(handler => handler.groupPropertyFiltersTracker.shouldUpdateAllGroups)) {
920
+ const allGroupIds = new Set<string>(propertyFilterHandlers.flatMap(handler => handler.groupPropertyFiltersTracker.groupIdsToUpdate));
921
+ if (allGroupIds.size === 0) {
922
+ // can happen if an empty registrations filter
923
+ skipGroups = false;
924
+ } else {
925
+ query.where('id', [...allGroupIds]);
926
+ }
927
+ }
928
+
929
+ for (const propertyFilterHandler of propertyFilterHandlers) {
930
+ propertyFilterHandler.handleOrganization();
931
+ }
932
+
933
+ const changesLogger = new PropertyChangesLoggerHelper(this.organization, propertyFilterHandlers);
934
+
935
+ if (!skipGroups) {
936
+ // todo: take into account cycles (or do this before the registration periods migration for better performance)
937
+ for await (const group of query.all()) {
938
+ for (const propertyFilterHandler of propertyFilterHandlers) {
939
+ const propertyFilter = propertyFilterHandler.handleGroup(group);
940
+ changesLogger.add(propertyFilterHandler.name, group, propertyFilter);
941
+ }
942
+
943
+ if (!dryRun) {
944
+ await group.save();
945
+ }
946
+ }
947
+ }
948
+
949
+ if (!dryRun) {
950
+ await this.organization.save();
951
+ }
952
+
953
+ changesLogger.doLog();
954
+ }
955
+
956
+ private getAllRecordCategories(recordCategory: RecordCategory): RecordCategory[] {
957
+ const childCategories = recordCategory.childCategories.flatMap(c => this.getAllRecordCategories(c));
958
+ return [recordCategory, ...childCategories];
959
+ }
960
+
961
+ private async getAllPropertyFilterHandlers(organization: Organization): Promise<PropertyFilterHandler[]> {
962
+ const recordsConfig: OrganizationRecordsConfiguration = organization.meta.recordsConfiguration;
963
+
964
+ const propertyFilterWrappers: PropertyFilterHandler[] = [];
965
+
966
+ function handlePropertyFilterHandlerError(e: any, name: string) {
967
+ const test: Record<string, string[]> = {
968
+ // fixed manually
969
+ ['da9a913a-feea-4a82-9335-2788babe39a9']: ['nationalRegisterNumber', 'Gegevens Fiscale attesten kinderopvang', 'Rijden naar Bornem'],
970
+ // fixed manually
971
+ ['528a4d8e-7347-4483-ab99-07d0ece78d17']: ['Instrument'],
972
+ // fixed manually
973
+ ['6b4f8185-05ca-4b03-b244-fb9c7d5c81ae']: ['Privacy'],
974
+ // fixed manually
975
+ ['bf4600a6-e308-4e28-adcb-9976216cb73a']: ['Kind'],
976
+ };
977
+
978
+ const testSkip = new Set([...Object.entries(test)].flatMap(([k, v]) => v.map(x => k + x)));
979
+ const messagesToSkip = [
980
+ // todo: skipped for now but should be handled
981
+ // 'Found more than one registrations filter',
982
+ // todo: skipped for now but should be handled
983
+ // 'The $and filter is not supported',
984
+ ];
985
+ // todo: skipped for now but should be handled
986
+ if (messagesToSkip.some(m => e?.message?.includes?.(m)) || testSkip.has(organization.id + name)) {
987
+ console.error('ignored error:');
988
+ console.error(e);
989
+ return;
990
+ }
991
+
992
+ console.error('organization:', organization.id);
993
+ console.error('name:', name);
994
+
995
+ throw e;
996
+ }
997
+
998
+ for (const [key, value] of Object.entries(recordsConfig)) {
999
+ if (value instanceof PropertyFilter) {
1000
+ try {
1001
+ const shouldHandle = await PropertyFilterHandler.shouldHandlePropertyFilter(value);
1002
+ if (!shouldHandle) {
1003
+ continue;
1004
+ }
1005
+ } catch (e) {
1006
+ console.error('Error:', e);
1007
+ console.error('key:', key);
1008
+ throw e;
1009
+ }
1010
+
1011
+ try {
1012
+ propertyFilterWrappers.push(new PropertyFilterHandler({
1013
+ name: key,
1014
+ propertyFilter: value,
1015
+ setPropertyFilterOnOrganization: (propertyFilter) => {
1016
+ this.organization.meta.recordsConfiguration[key] = propertyFilter;
1017
+ },
1018
+ setPropertyFilterOnGroup: (group: Group, propertyFilter: PropertyFilter) => {
1019
+ group.settings.recordsConfiguration[key] = propertyFilter;
1020
+ },
1021
+ recordCategory: null,
1022
+ }));
1023
+ } catch (e) {
1024
+ handlePropertyFilterHandlerError(e, key);
1025
+ }
1026
+ }
1027
+ }
1028
+
1029
+ const recordCategoryFilters: PropertyFilterHandler[] = [];
1030
+
1031
+ // only loop parent categories because child categories can not be configured on group level
1032
+ for (const category of recordsConfig.recordCategories) {
1033
+ const propertyFilter = category.filter;
1034
+
1035
+ if (propertyFilter === null) {
1036
+ continue;
1037
+ }
1038
+
1039
+ const shouldHandle = await PropertyFilterHandler.shouldHandlePropertyFilter(propertyFilter);
1040
+ if (!shouldHandle) {
1041
+ continue;
1042
+ }
1043
+
1044
+ try {
1045
+ recordCategoryFilters.push(new PropertyFilterHandler({
1046
+ name: category.name.toString(),
1047
+ propertyFilter,
1048
+ setPropertyFilterOnOrganization: (propertyFilter) => {
1049
+ category.filter = propertyFilter;
1050
+ },
1051
+ setPropertyFilterOnGroup: (group: Group, propertyFilter: PropertyFilter) => {
1052
+ const map = group.settings.recordsConfiguration.inheritedRecordCategories;
1053
+ map.set(category.id, propertyFilter);
1054
+ },
1055
+ recordCategory: category,
1056
+ }));
1057
+ } catch (e) {
1058
+ handlePropertyFilterHandlerError(e, category.name.toString());
1059
+ }
1060
+ }
1061
+
1062
+ return [...propertyFilterWrappers, ...recordCategoryFilters];
1063
+ }
1064
+ }