@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.
- package/output-2.txt +1 -0
- package/output.txt +1 -0
- package/package.json +69 -52
- package/src/audit-logs/EventNotificationLogger.ts +40 -0
- package/src/audit-logs/ModelLogger.ts +19 -15
- package/src/audit-logs/OrganizationLogger.ts +1 -1
- package/src/audit-logs/RegistrationInvitationLogger.ts +24 -24
- package/src/audit-logs/init.ts +8 -2
- package/src/boot.ts +19 -18
- package/src/crons/amazon-ses.ts +13 -26
- package/src/crons/balance-emails.ts +3 -5
- package/src/crons/clearExcelCache.test.ts +1 -1
- package/src/crons/clearExcelCache.ts +1 -2
- package/src/crons/drip-emails.ts +42 -0
- package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +4 -0
- package/src/crons/helpers/isOutside.ts +11 -0
- package/src/crons/helpers/useSavedIterator.ts +32 -0
- package/src/crons/index.ts +5 -0
- package/src/crons/invoices.ts +32 -38
- package/src/crons/members-fees.ts +50 -0
- package/src/crons/mollie-chargebacks.ts +14 -15
- package/src/crons/service-fees.ts +152 -0
- package/src/crons/stripe-invoices.ts +48 -0
- package/src/crons/transfer-fees.ts +176 -0
- package/src/crons/update-cached-balances.ts +4 -4
- package/src/crons.ts +7 -64
- package/src/debug.ts +4 -2
- package/src/email-recipient-loaders/documents.ts +1 -1
- package/src/email-recipient-loaders/members.ts +1 -1
- package/src/email-recipient-loaders/payments.ts +4 -6
- package/src/email-recipient-loaders/registrations.ts +1 -1
- package/src/email-replacements/getEmailReplacementsForPayment.ts +5 -9
- package/src/endpoints/admin/members/ChargeMembersEndpoint.ts +1 -1
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -1
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +3 -5
- package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +1 -1
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +1 -1
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +13 -7
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +4 -6
- package/src/endpoints/admin/organizations/PatchPackagesEndpoint.test.ts +10 -6
- package/src/endpoints/admin/organizations/PatchPackagesEndpoint.ts +39 -21
- package/src/endpoints/admin/registrations/ChargeRegistrationsEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateAdminEndpoint.ts +23 -5
- package/src/endpoints/auth/CreateTokenEndpoint.ts +2 -2
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/DeleteUserEndpoint.ts +3 -3
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
- package/src/endpoints/auth/GetOtherUserEndpoint.ts +1 -1
- package/src/endpoints/auth/GetUserEndpoint.ts +1 -1
- package/src/endpoints/auth/OpenIDConnectAuthTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/OpenIDConnectStartEndpoint.ts +1 -1
- package/src/endpoints/auth/PatchUserEndpoint.ts +5 -8
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +1 -1
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +1 -1
- package/src/endpoints/auth/SignupEndpoint.ts +2 -3
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +20 -3
- package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +2 -1
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +2 -4
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +1 -1
- package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +5 -7
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +2 -2
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +3 -4
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +1 -2
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +4 -6
- package/src/endpoints/global/email/GetEmailEndpoint.ts +1 -1
- package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email/GetUserEmailsEndpoint.ts +1 -1
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +6 -10
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +4 -7
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +6 -9
- package/src/endpoints/global/email-recipients/GetEmailRecipientsCountEndpoint.ts +1 -1
- package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.ts +1 -1
- package/src/endpoints/global/email-recipients/RetryEmailRecipientEndpoint.ts +2 -2
- package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +1 -1
- package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +3 -3
- package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -3
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +49 -4
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +5 -8
- package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +12 -44
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +128 -127
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -9
- package/src/endpoints/global/files/GetFileCache.ts +3 -4
- package/src/endpoints/global/files/UploadFile.ts +3 -4
- package/src/endpoints/global/files/UploadImage.ts +3 -4
- package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +1 -1
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +1 -1
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +4 -2
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +2 -3
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +1 -1
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/members/GetMembersEndpoint.ts +10 -12
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +195 -12
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +16 -22
- package/src/endpoints/global/members/helpers/validateGroupFilter.ts +6 -30
- package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +1 -1
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +1 -1
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +40 -8
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +2 -2
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -2
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +4 -1
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +4 -5
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +9 -16
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +1 -1
- package/src/endpoints/global/platform/GetPlatformEndpoint.ts +1 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +1 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +11 -11
- package/src/endpoints/global/platform-memberships/GetPlatformMembershipsCountEndpoint.ts +31 -31
- package/src/endpoints/global/platform-memberships/GetPlatformMembershipsEndpoint.ts +18 -20
- package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +1 -1
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1 -1
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +6 -10
- package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +35 -6
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +2 -2
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +1 -1
- package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +1 -1
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +90 -6
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +35 -34
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +1 -1
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +62 -63
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +6 -9
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +51 -52
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +6 -7
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsCountEndpoint.ts +43 -0
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +1 -1
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +13 -5
- package/src/endpoints/global/sso/GetSSOEndpoint.ts +2 -3
- package/src/endpoints/global/sso/SetSSOEndpoint.ts +1 -1
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
- package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +1 -2
- package/src/endpoints/global/webshops/GetWebshopsEndpoint.ts +2 -4
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.ts +3 -4
- package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +7 -9
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +73 -0
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.ts +89 -0
- package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +1 -5
- package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +11 -11
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +9 -9
- package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableBalanceEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableInvoicesEndpoint.ts +68 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +4 -4
- package/src/endpoints/organization/dashboard/billing/GetRegisterCodeEndpoint.ts +66 -0
- package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.test.ts +949 -0
- package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +60 -57
- package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +20 -20
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentsCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +10 -2
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +10 -8
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +5 -6
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +10 -6
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +11 -2
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +4 -5
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +5 -5
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +7 -7
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +1 -1
- package/src/endpoints/organization/dashboard/organization/GetUitpasClientIdEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +50 -12
- package/src/endpoints/organization/dashboard/organization/SearchUitpasOrganizersEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +5 -10
- package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +3 -4
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +21 -14
- package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +35 -28
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +19 -18
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +4 -5
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.ts +4 -6
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +10 -15
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +11 -2
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +4 -6
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +22 -7
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +20 -27
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +95 -0
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -2
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +1 -1
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
- package/src/endpoints/organization/shared/GetUitpasNumberDetailsEndpoint.ts +3 -5
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +1 -1
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +4 -1
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +14 -3
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -2
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +37 -16
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +34 -2
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +4 -2
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +74 -3
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +16 -33
- package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +3 -5
- package/src/endpoints/organization/webshops/WebshopAuthHelper.ts +36 -0
- package/src/endpoints/system/HealthEndpoint.ts +1 -1
- package/src/excel-loaders/balance-items.ts +1 -1
- package/src/excel-loaders/event-notifications.ts +1 -1
- package/src/excel-loaders/members.ts +1 -1
- package/src/excel-loaders/organizations.ts +3 -3
- package/src/excel-loaders/payments.ts +31 -1
- package/src/excel-loaders/platform-memberships.ts +13 -14
- package/src/excel-loaders/receivable-balances.ts +2 -2
- package/src/excel-loaders/registrations.ts +2 -3
- package/src/helpers/AddressValidator.ts +7 -14
- package/src/helpers/AdminPermissionChecker.ts +50 -63
- package/src/helpers/AuthenticatedStructures.ts +69 -55
- package/src/helpers/BalanceItemRelationsBackfiller.test.ts +174 -0
- package/src/helpers/BalanceItemRelationsBackfiller.ts +163 -0
- package/src/helpers/BuckarooHelper.ts +2 -4
- package/src/helpers/CheckSettlements.ts +12 -24
- package/src/helpers/Context.ts +15 -10
- package/src/helpers/CookieHelper.ts +1 -2
- package/src/helpers/EmailResumer.ts +1 -2
- package/src/helpers/FileCache.ts +2 -3
- package/src/helpers/FinancialSupportHelper.ts +2 -2
- package/src/helpers/FlagMomentCleanup.ts +1 -2
- package/src/helpers/ForwardHandler.ts +5 -10
- package/src/helpers/GlobalHelper.ts +3 -3
- package/src/helpers/LegacyBillingConverter.ts +787 -0
- package/src/helpers/MemberMerger.test.ts +782 -0
- package/src/helpers/MemberMerger.ts +600 -0
- package/src/helpers/MemberUserSyncer.ts +6 -12
- package/src/helpers/MembershipCharger.ts +2 -4
- package/src/helpers/PaymentCustomerResolver.test.ts +203 -0
- package/src/helpers/PaymentCustomerResolver.ts +144 -0
- package/src/helpers/PeriodHelper.ts +2 -3
- package/src/helpers/RecordAnswerHelper.test.ts +105 -110
- package/src/helpers/RecordAnswerHelper.ts +7 -7
- package/src/helpers/SeedTools.ts +106 -0
- package/src/helpers/ServiceFeeHelper.ts +125 -15
- package/src/helpers/SetupStepUpdater.ts +38 -32
- package/src/helpers/StamhoofdFilterAccessHelper.ts +52 -0
- package/src/helpers/StripeHelper.ts +14 -36
- package/src/helpers/StripeInvoicer.ts +419 -0
- package/src/helpers/StripePayoutChecker.ts +5 -10
- package/src/helpers/TagHelper.ts +7 -8
- package/src/helpers/TemporaryMemberAccess.ts +1 -2
- package/src/helpers/ThrottledQueue.ts +1 -2
- package/src/helpers/UitpasTokenRepository.ts +1 -2
- package/src/helpers/ViesHelper.ts +7 -11
- package/src/helpers/XlsxTransformerColumnHelper.ts +1 -1
- package/src/helpers/outstandingBalanceJoin.ts +2 -2
- package/src/helpers/updateMemberDetailsUitpasNumber.ts +3 -5
- package/src/migrate.ts +12 -5
- package/src/{seeds → migrations}/0000000004-single-organization.ts +10 -1
- package/src/migrations/0000000006-default-organization.ts +139 -0
- package/src/{seeds → migrations}/1715028563-user-permissions.ts +17 -27
- package/src/{seeds/1765896674-document-update-year.test.ts → migrations/1750090029-document-update-year.test.ts} +27 -26
- package/src/migrations/1750090029-document-update-year.ts +103 -0
- package/src/{seeds → migrations}/1750090030-records-configuration.ts +33 -32
- package/src/migrations/1750090031-records-configuration-webshops.ts +36 -0
- package/src/migrations/1752848540-national-numbers.ts +585 -0
- package/src/migrations/1752848550-records-configuration-group-filter.ts +1064 -0
- package/src/{seeds → migrations}/1752848561-groups-registration-periods.ts +70 -26
- package/src/{seeds → migrations}/1754560914-groups-prices.test.ts +1 -1
- package/src/{seeds → migrations}/1754560914-groups-prices.ts +9 -15
- package/src/{seeds → migrations}/1756293699-fill-previous-next-period-id.ts +1 -2
- package/src/migrations/1761665607-sync-member-users.ts +40 -0
- package/src/migrations/1779121240-invoice-email-template.sql +6 -0
- package/src/migrations/1779986893-default-payment-failed-email-templates.sql +4 -0
- package/src/migrations/1780578166-convert-legacy-billing.ts +13 -0
- package/src/{seeds/1755876819-remove-duplicate-members.ts → migrations/1780665426-remove-duplicate-members.ts} +23 -4
- package/src/migrations/1780933598-document-groups.ts +88 -0
- package/src/migrations/1781692342-balance-item-relations.ts +13 -0
- package/src/migrations/1781857866-platform-admins.ts +103 -0
- package/src/migrations/1781857867-rm-old-platform-admins.ts +25 -0
- package/src/seeds/0000000001-throw.ts +5 -0
- package/src/seeds/1760702452-update-balance-price-paid.ts +19 -0
- package/src/seeds/1760702453-update-balance-invoiced.ts +19 -0
- package/src/seeds/1760702454-update-cached-outstanding-balance-from-items.ts +51 -14
- package/src/seeds/1773754928-force-save-members.ts +41 -8
- package/src/seeds/1780665427-schedule-stock-updates.ts +45 -0
- package/src/seeds/1780665428-group-update-occupancy.ts +31 -0
- package/src/seeds/1780915001-fill-payment-customer.ts +70 -0
- package/src/seeds/1780915002-update-orders.ts +39 -0
- package/src/seeds/1782047708-organization-company-address.ts +44 -0
- package/src/seeds-temporary/1769088653-uitpas-status.ts +1 -2
- package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.test.ts +69 -0
- package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.ts +85 -0
- package/src/services/AuditLogService.test.ts +51 -0
- package/src/services/AuditLogService.ts +45 -0
- package/src/services/BalanceItemService.ts +141 -44
- package/src/services/CpuService.ts +3 -6
- package/src/services/DatabaseCollationService.test.ts +1 -2
- package/src/services/DocumentService.ts +1 -2
- package/src/services/EventNotificationService.ts +1 -1
- package/src/services/FileSignService.ts +2 -4
- package/src/services/InvoicePdfService.ts +286 -0
- package/src/services/InvoiceService.ts +204 -251
- package/src/services/InvoiceXMLService.ts +424 -0
- package/src/services/MemberNumberService.ts +6 -11
- package/src/services/MemberRecordStore.ts +8 -10
- package/src/services/MollieService.ts +114 -121
- package/src/services/PaymentMandateService.ts +45 -45
- package/src/services/PaymentReallocationService.test.ts +1 -1
- package/src/services/PaymentReallocationService.ts +7 -9
- package/src/services/PaymentService.ts +444 -308
- package/src/services/PlatformMembershipService.ts +4 -8
- package/src/services/ReferralService.ts +223 -0
- package/src/services/RegistrationService.ts +63 -53
- package/src/services/SSOService.test.ts +119 -0
- package/src/services/SSOService.ts +56 -49
- package/src/services/STPackageService.ts +97 -61
- package/src/services/UniqueMemberNumberService.ts +69 -0
- package/src/services/UniqueUserService.ts +3 -6
- package/src/services/VATService.ts +85 -0
- package/src/services/uitpas/PassholderEndpoints.ts +3 -6
- package/src/services/uitpas/UitpasService.ts +6 -10
- package/src/services/uitpas/checkUitpasNumbers.ts +2 -4
- package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +3 -6
- package/src/services/uitpas/searchUitpasEvents.ts +1 -2
- package/src/sql-filters/audit-logs.ts +1 -1
- package/src/sql-filters/balance-item-payments.ts +1 -1
- package/src/sql-filters/balance-items.ts +1 -1
- package/src/sql-filters/base-registration-filter-compilers.ts +5 -33
- package/src/sql-filters/document-templates.ts +1 -1
- package/src/sql-filters/documents.ts +1 -1
- package/src/sql-filters/email-recipients.ts +1 -1
- package/src/sql-filters/emails.ts +1 -1
- package/src/sql-filters/event-notifications.ts +1 -1
- package/src/sql-filters/events.ts +6 -1
- package/src/sql-filters/groups.ts +5 -0
- package/src/sql-filters/invoiced-balance-items.ts +1 -1
- package/src/sql-filters/invoices.ts +1 -1
- package/src/sql-filters/member-responsibility-records.ts +1 -1
- package/src/sql-filters/members.ts +1 -1
- package/src/sql-filters/orders.ts +4 -4
- package/src/sql-filters/organization-registration-periods.ts +1 -1
- package/src/sql-filters/organizations.ts +1 -1
- package/src/sql-filters/platform-memberships.ts +3 -3
- package/src/sql-filters/receivable-balances.ts +1 -1
- package/src/sql-filters/registration-periods.ts +1 -1
- package/src/sql-filters/registrations.ts +1 -1
- package/src/sql-filters/tickets.ts +1 -1
- package/src/sql-filters/users.ts +1 -1
- package/src/sql-sorters/groups.ts +23 -0
- package/src/sql-sorters/platform-memberships.ts +16 -16
- package/src/sql-sorters/registration-invitations.ts +2 -2
- package/tests/actions/patchOrganizationMember.ts +1 -1
- package/tests/actions/patchUserMember.ts +1 -1
- package/tests/assertions/assertBalances.ts +6 -12
- package/tests/e2e/api-rate-limits.test.ts +12 -24
- package/tests/e2e/bundle-discounts.test.ts +4 -1
- package/tests/e2e/charge-members.test.ts +13 -11
- package/tests/e2e/documents.test.ts +4 -1
- package/tests/e2e/register.test.ts +7 -9
- package/tests/e2e/stock.test.ts +4 -3
- package/tests/e2e/tickets.test.ts +1 -1
- package/tests/helpers/MollieMocker.ts +462 -0
- package/tests/helpers/PayconiqMocker.ts +138 -45
- package/tests/helpers/StripeMocker.ts +9 -6
- package/tests/helpers/index.ts +4 -0
- package/tests/init/initAdmin.ts +2 -2
- package/tests/init/initBundleDiscount.ts +1 -1
- package/tests/init/initMembershipOrganization.ts +13 -0
- package/tests/init/initPermissionRole.ts +3 -4
- package/tests/vitest.global.setup.ts +1 -1
- package/vitest.config.js +6 -0
- package/.nvmrc +0 -1
- package/src/helpers/GroupBuilder.ts +0 -418
- package/src/seeds/0000000000-example.ts +0 -11
- package/src/seeds/1722256498-group-update-occupancy.ts +0 -52
- package/src/seeds/1726572303-schedule-stock-updates.ts +0 -52
- package/src/seeds/1726847064-setup-steps.ts +0 -16
- package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +0 -31
- package/src/seeds/1729253172-update-orders.ts +0 -21
- package/src/seeds/1733319079-fill-paying-organization-ids.ts +0 -68
- package/src/seeds/1733748412-update-deleted-order-numbers.ts +0 -109
- package/src/seeds/1733994455-balance-item-status-open.ts +0 -30
- package/src/seeds/1740046783-update-membership.ts +0 -17
- package/src/seeds/1741008870-fix-auditlog-description.ts +0 -50
- package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +0 -88
- package/src/seeds/1741268179-fix-group-dates-of-events.ts +0 -32
- package/src/seeds/1751445358-upload-email-attachments.ts +0 -106
- package/src/seeds/1755532883-update-email-sender-ids.ts +0 -47
- package/src/seeds/1755790070-fill-email-recipient-errors.ts +0 -96
- package/src/seeds/1756115432-remove-old-drafts.ts +0 -16
- package/src/seeds/1756115433-fill-email-recipient-organization-id.ts +0 -30
- package/src/seeds/1756303697-update-email-counts.ts +0 -76
- package/src/seeds/1761665607-sync-member-users.ts +0 -59
- package/src/seeds/1765896674-document-update-year.ts +0 -66
- /package/src/{seeds → migrations}/0000000001-development-user.ts +0 -0
- /package/src/{seeds → migrations}/0000000002-clear-stamhoofd-email-templates.ts +0 -0
- /package/src/{seeds → migrations}/0000000003-default-email-templates.ts +0 -0
- /package/src/{seeds → migrations}/1752848561-groups-registration-periods.test.ts +0 -0
- /package/src/{seeds → migrations}/1766150402-document-published-at.test.ts +0 -0
- /package/src/{seeds → migrations}/1766150402-document-published-at.ts +0 -0
- /package/src/{seeds → migrations}/1779121239-default-invoice-email-template.sql +0 -0
- /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
|
+
}
|