@stamhoofd/backend 2.120.6 → 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 +70 -53
- 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 +46 -0
- package/src/audit-logs/init.ts +9 -1
- 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 +7 -0
- package/src/crons/invoices.ts +160 -0
- package/src/crons/members-fees.ts +50 -0
- package/src/crons/mollie-chargebacks.ts +86 -0
- 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 +54 -74
- 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 +87 -46
- 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 +51 -0
- 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 +32 -13
- 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 +38 -33
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +45 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +494 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +213 -0
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +404 -0
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +167 -0
- 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 +19 -6
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +73 -0
- package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.ts +89 -0
- package/src/endpoints/{global → organization/dashboard}/billing/DeactivatePackageEndpoint.ts +4 -9
- package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +62 -0
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +56 -0
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +41 -18
- package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +64 -0
- 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 +12 -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 +311 -0
- package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +94 -0
- 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 +15 -4
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +11 -2
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +10 -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 +67 -20
- 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 +24 -17
- package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +134 -0
- 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 +32 -22
- 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 +29 -8
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +21 -28
- 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 +15 -13
- 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 +28 -50
- 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 +60 -65
- package/src/helpers/AuthenticatedStructures.ts +153 -51
- 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 +21 -0
- 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 +741 -0
- package/src/helpers/RecordAnswerHelper.ts +116 -0
- 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 +15 -38
- 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 +11 -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/migrations/1750090030-records-configuration.ts +92 -0
- 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 +95 -27
- 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/1779121239-default-invoice-email-template.sql +3 -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 +148 -55
- 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 +330 -77
- 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 +530 -0
- package/src/services/PaymentMandateService.ts +214 -0
- package/src/services/PaymentReallocationService.test.ts +1 -1
- package/src/services/PaymentReallocationService.ts +7 -9
- package/src/services/PaymentService.ts +820 -328
- package/src/services/PlatformMembershipService.ts +5 -9
- package/src/services/ReferralService.ts +223 -0
- package/src/services/RegistrationService.ts +125 -54
- package/src/services/SSOService.test.ts +119 -0
- package/src/services/SSOService.ts +56 -49
- package/src/services/STPackageService.ts +97 -68
- package/src/services/UniqueMemberNumberService.ts +69 -0
- package/src/services/UniqueUserService.ts +3 -6
- package/src/services/VATService.ts +85 -0
- package/src/services/data/invoice.hbs.html +686 -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 +16 -1
- 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/payments.ts +5 -0
- package/src/sql-filters/platform-memberships.ts +3 -3
- package/src/sql-filters/receivable-balances.ts +1 -1
- package/src/sql-filters/registration-invitations.ts +90 -0
- 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 +36 -0
- 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 +7 -0
- package/.nvmrc +0 -1
- package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +0 -216
- 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/1750090030-records-configuration.ts +0 -26
- 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}/data/default-email-templates.sql +0 -0
|
@@ -175,7 +175,7 @@ export class PlatformMembershipService {
|
|
|
175
175
|
}
|
|
176
176
|
const types = platform.config.membershipTypes.filter(m => m.behaviour === PlatformMembershipTypeBehaviour.Period).map(m => m.id);
|
|
177
177
|
if (types.length === 0) {
|
|
178
|
-
if (!silent) {
|
|
178
|
+
if (!silent && STAMHOOFD.environment !== 'test') {
|
|
179
179
|
console.warn('No membership types found for memberships');
|
|
180
180
|
}
|
|
181
181
|
return;
|
|
@@ -353,8 +353,7 @@ export class PlatformMembershipService {
|
|
|
353
353
|
// Update the price and dates of this active membership (could have changed)
|
|
354
354
|
try {
|
|
355
355
|
await m.calculatePrice(me, cheapestMembership.registration);
|
|
356
|
-
}
|
|
357
|
-
catch (e) {
|
|
356
|
+
} catch (e) {
|
|
358
357
|
// Ignore error: membership might not be available anymore
|
|
359
358
|
if (!silent) {
|
|
360
359
|
console.error('Failed to calculate price for active membership', m.id, e);
|
|
@@ -377,14 +376,12 @@ export class PlatformMembershipService {
|
|
|
377
376
|
console.log('Removing membership because cheaper membership found or duplicate, for: ' + me.id + ' - membership ' + m.id);
|
|
378
377
|
}
|
|
379
378
|
await m.doDelete();
|
|
380
|
-
}
|
|
381
|
-
else if (m.membershipTypeId === cheapestMembership.membership.id) {
|
|
379
|
+
} else if (m.membershipTypeId === cheapestMembership.membership.id) {
|
|
382
380
|
// Update price
|
|
383
381
|
if (!m.locked) {
|
|
384
382
|
try {
|
|
385
383
|
await m.calculatePrice(me);
|
|
386
|
-
}
|
|
387
|
-
catch (e) {
|
|
384
|
+
} catch (e) {
|
|
388
385
|
// Ignore error: membership might not be available anymore
|
|
389
386
|
if (!silent) {
|
|
390
387
|
console.error('Failed to calculate price for undeletable membership', m.id, e);
|
|
@@ -409,8 +406,7 @@ export class PlatformMembershipService {
|
|
|
409
406
|
if (me.details.memberNumber === null) {
|
|
410
407
|
try {
|
|
411
408
|
await MemberNumberService.assignMemberNumber(me, membership);
|
|
412
|
-
}
|
|
413
|
-
catch (error) {
|
|
409
|
+
} catch (error) {
|
|
414
410
|
console.error(`Failed to assign member number for id ${me.id}: ${error.message}`);
|
|
415
411
|
// If the assignment of the member number fails the membership is not created but the member is registered
|
|
416
412
|
continue;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { Model } from '@simonbackx/simple-database';
|
|
2
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
3
|
+
import type { EmailInterfaceBase } from '@stamhoofd/email';
|
|
4
|
+
import { Email } from '@stamhoofd/email';
|
|
5
|
+
import { BalanceItem, Organization, Platform, RegisterCode, UsedRegisterCode } from '@stamhoofd/models';
|
|
6
|
+
import { BalanceItemStatus, BalanceItemType } from '@stamhoofd/structures';
|
|
7
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
8
|
+
import { VATService } from './VATService.js';
|
|
9
|
+
|
|
10
|
+
export class ReferralService {
|
|
11
|
+
/**
|
|
12
|
+
* Prepares a register code for usage.
|
|
13
|
+
* Returns the models to save and the emails to send when succesful
|
|
14
|
+
*/
|
|
15
|
+
static async markUsed(organization: Organization, codeString: string): Promise<{ models: Model[]; emails: EmailInterfaceBase[] }> {
|
|
16
|
+
const alreadyUsed = await UsedRegisterCode.getFor(organization.id);
|
|
17
|
+
if (alreadyUsed) {
|
|
18
|
+
throw new SimpleError({
|
|
19
|
+
code: 'invalid_field',
|
|
20
|
+
message: 'Invalid register code',
|
|
21
|
+
human: $t('%1Ws'),
|
|
22
|
+
field: 'registerCode',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const code = await RegisterCode.getByID(codeString);
|
|
27
|
+
if (!code) {
|
|
28
|
+
throw new SimpleError({
|
|
29
|
+
code: 'invalid_field',
|
|
30
|
+
message: 'Invalid register code',
|
|
31
|
+
human: $t('%1Ua'),
|
|
32
|
+
field: 'registerCode',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const otherOrganization = code.organizationId ? await Organization.getByID(code.organizationId) : undefined;
|
|
37
|
+
|
|
38
|
+
const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId;
|
|
39
|
+
if (!membershipOrganizationId) {
|
|
40
|
+
throw new SimpleError({
|
|
41
|
+
code: 'invalid_field',
|
|
42
|
+
message: 'Referral codes unavailable',
|
|
43
|
+
human: $t('%1YF'),
|
|
44
|
+
field: 'registerCode',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const membershipOrganization = await Organization.getByID(membershipOrganizationId, true);
|
|
48
|
+
|
|
49
|
+
const delayEmails: EmailInterfaceBase[] = [];
|
|
50
|
+
let credit: BalanceItem | undefined = undefined;
|
|
51
|
+
|
|
52
|
+
if (code.value > 0) {
|
|
53
|
+
credit = new BalanceItem();
|
|
54
|
+
credit.type = BalanceItemType.ReferralDiscount;
|
|
55
|
+
credit.description = otherOrganization ? ('Tegoed gekregen van ' + otherOrganization.name) : code.description;
|
|
56
|
+
credit.payingOrganizationId = organization.id;
|
|
57
|
+
credit.organizationId = membershipOrganization.id;
|
|
58
|
+
credit.VATPercentage = 21;
|
|
59
|
+
credit.VATExcempt = VATService.getVATExcempt({
|
|
60
|
+
company: organization.defaultCompanies[0] ?? null,
|
|
61
|
+
sellingOrganization: membershipOrganization,
|
|
62
|
+
type: 'services',
|
|
63
|
+
});
|
|
64
|
+
credit.VATIncluded = false;
|
|
65
|
+
credit.quantity = 1;
|
|
66
|
+
credit.unitPrice = -code.value;
|
|
67
|
+
credit.createdAt = new Date();
|
|
68
|
+
credit.status = BalanceItemStatus.Due;
|
|
69
|
+
|
|
70
|
+
// Expire in one year (will get extended for every purchase or activation)
|
|
71
|
+
// credit.expireAt = new Date()
|
|
72
|
+
// credit.expireAt.setFullYear(credit.expireAt.getFullYear() + 1)
|
|
73
|
+
// credit.expireAt.setMilliseconds(0)
|
|
74
|
+
|
|
75
|
+
// Save later
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (otherOrganization) {
|
|
79
|
+
const admins = await otherOrganization.getAdminToEmails();
|
|
80
|
+
if (admins) {
|
|
81
|
+
if (code.invoiceValue) {
|
|
82
|
+
// Delay email until everything is validated and saved
|
|
83
|
+
delayEmails.push({
|
|
84
|
+
to: admins,
|
|
85
|
+
subject: organization.name + ' heeft jullie doorverwijzingslink gebruikt 🥳',
|
|
86
|
+
type: 'transactional',
|
|
87
|
+
text: 'Dag ' + otherOrganization.name + ',\n\nGoed nieuws! ' + organization.name + ' heeft jullie doorverwijzingslink gebruikt om zich op Stamhoofd te registreren.\n\n— Stamhoofd',
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
// Delay email until everything is validated and saved
|
|
91
|
+
delayEmails.push({
|
|
92
|
+
to: admins,
|
|
93
|
+
subject: organization.name + ' heeft jullie doorverwijzingslink gebruikt 🥳',
|
|
94
|
+
type: 'transactional',
|
|
95
|
+
text: 'Dag ' + otherOrganization.name + ',\n\nGoed nieuws! ' + organization.name + ' heeft jullie doorverwijzingslink gebruikt om zich op Stamhoofd te registreren. Als zij minstens 1 euro op Stamhoofd uitgeven ontvangen jullie een tegoed dat kan oplopen tot 100 euro per vereniging (zie daarvoor Stamhoofd > Instellingen). Lees zeker onze tips na om nog een groter bedrag te verzamelen.\n\n— Stamhoofd',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
delayEmails.push({
|
|
101
|
+
to: [
|
|
102
|
+
{ email: 'hallo@stamhoofd.be' },
|
|
103
|
+
],
|
|
104
|
+
subject: organization.name + ' heeft jullie doorverwijzingslink gebruikt 🥳',
|
|
105
|
+
type: 'transactional',
|
|
106
|
+
text: 'Dag Stamhoofd,\n\nGoed nieuws! ' + organization.name + ' heeft jullie doorverwijzingslink ' + code.code + ' gebruikt om zich op Stamhoofd te registreren. \n\n— Stamhoofd',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Save that we used this code (so we can reward the other organization)
|
|
111
|
+
const usedCode = new UsedRegisterCode();
|
|
112
|
+
usedCode.organizationId = organization.id;
|
|
113
|
+
usedCode.code = code.code;
|
|
114
|
+
|
|
115
|
+
// Save later
|
|
116
|
+
return {
|
|
117
|
+
models: credit ? [credit, usedCode] : [usedCode],
|
|
118
|
+
emails: delayEmails,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static async reward(usedCode: UsedRegisterCode) {
|
|
123
|
+
if (usedCode.balanceItemId) {
|
|
124
|
+
// Already received
|
|
125
|
+
console.error('Already rewarded for used register code ' + usedCode.id);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const code = await RegisterCode.getByID(usedCode.code);
|
|
130
|
+
if (!code || !code.organizationId) {
|
|
131
|
+
console.error("Couldn't find code " + usedCode.code + ' for used register code ' + usedCode.id);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const organization = await Organization.getByID(usedCode.organizationId);
|
|
136
|
+
if (!organization) {
|
|
137
|
+
console.error("Couldn't find organization with id " + usedCode.organizationId + ' for used register code ' + usedCode.id);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const receivingOrganization = await Organization.getByID(code.organizationId);
|
|
142
|
+
if (!receivingOrganization) {
|
|
143
|
+
console.error("Couldn't find receiving organization with id " + code.organizationId + ' for used register code ' + usedCode.id);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId!;
|
|
147
|
+
const membershipOrganization = await Organization.getByID(membershipOrganizationId, true);
|
|
148
|
+
|
|
149
|
+
const usedCount = await UsedRegisterCode.getUsedCount(usedCode.code) + 1;
|
|
150
|
+
|
|
151
|
+
const credit = new BalanceItem();
|
|
152
|
+
credit.type = BalanceItemType.ReferralDiscount;
|
|
153
|
+
credit.description = $t('%1ZE', { 'organization-name': organization.name });
|
|
154
|
+
credit.payingOrganizationId = code.organizationId;
|
|
155
|
+
credit.organizationId = (await Platform.getShared()).membershipOrganizationId!; // where do we get this discount from
|
|
156
|
+
credit.VATPercentage = 21;
|
|
157
|
+
credit.VATExcempt = VATService.getVATExcempt({
|
|
158
|
+
company: organization.defaultCompanies[0] ?? null,
|
|
159
|
+
sellingOrganization: membershipOrganization,
|
|
160
|
+
type: 'services',
|
|
161
|
+
});
|
|
162
|
+
credit.VATIncluded = false;
|
|
163
|
+
credit.quantity = 1;
|
|
164
|
+
credit.unitPrice = -(code.invoiceValue ? 0 : Math.min(100 * 100_00, usedCount * 10 * 100_00));
|
|
165
|
+
credit.createdAt = new Date();
|
|
166
|
+
credit.status = BalanceItemStatus.Due;
|
|
167
|
+
|
|
168
|
+
// Expire in one year (will get extended for every purchase or activation)
|
|
169
|
+
// credit.expireAt = new Date()
|
|
170
|
+
// credit.expireAt.setFullYear(credit.expireAt.getFullYear() + 1)
|
|
171
|
+
// credit.expireAt.setMilliseconds(0)
|
|
172
|
+
|
|
173
|
+
await credit.save();
|
|
174
|
+
|
|
175
|
+
usedCode.balanceItemId = credit.id;
|
|
176
|
+
await usedCode.save();
|
|
177
|
+
|
|
178
|
+
if (code.invoiceValue) {
|
|
179
|
+
// The receiving organization is invoiced instead of the organization for the usage of Stamhoofd.
|
|
180
|
+
const item = new BalanceItem();
|
|
181
|
+
item.type = BalanceItemType.Other;
|
|
182
|
+
item.description = 'Aankoop Stamhoofd voor ' + organization.name;
|
|
183
|
+
item.payingOrganizationId = receivingOrganization.id;
|
|
184
|
+
item.organizationId = membershipOrganizationId;
|
|
185
|
+
item.VATPercentage = 21;
|
|
186
|
+
item.VATExcempt = VATService.getVATExcempt({
|
|
187
|
+
company: organization.defaultCompanies[0] ?? null,
|
|
188
|
+
sellingOrganization: membershipOrganization,
|
|
189
|
+
type: 'services',
|
|
190
|
+
});
|
|
191
|
+
item.VATIncluded = false;
|
|
192
|
+
item.quantity = 1;
|
|
193
|
+
item.unitPrice = code.invoiceValue;
|
|
194
|
+
item.createdAt = new Date();
|
|
195
|
+
item.status = BalanceItemStatus.Due;
|
|
196
|
+
await item.save();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const admins = await receivingOrganization.getAdminRecipients();
|
|
200
|
+
if (admins) {
|
|
201
|
+
if (code.invoiceValue) {
|
|
202
|
+
Email.send({
|
|
203
|
+
to: admins,
|
|
204
|
+
from: Email.getQuickFromEmail(receivingOrganization.address.country),
|
|
205
|
+
subject: `${organization.name} heeft jullie tegoed gebruikt`,
|
|
206
|
+
text: 'Dag ' + receivingOrganization.name + ',\n\n' + organization.name + ' had jullie doorverwijzingslink gebruikt om zich op Stamhoofd te registreren, en nu hebben ze dit ook gebruikt. Zoals afgesproken wordt hiervoor ' + Formatter.price(code.invoiceValue) + ' aangerekend via jullie openstaand saldo in jullie Stamhoofd account.'
|
|
207
|
+
+ '\n\n— Stamhoofd',
|
|
208
|
+
});
|
|
209
|
+
} else {
|
|
210
|
+
// Delay email until everything is validated and saved
|
|
211
|
+
Email.send({
|
|
212
|
+
from: Email.getQuickFromEmail(receivingOrganization.address.country),
|
|
213
|
+
to: admins,
|
|
214
|
+
subject: 'Je hebt ' + Formatter.price(-credit.unitPrice) + ' tegoed ontvangen 💰',
|
|
215
|
+
text: 'Dag ' + receivingOrganization.name + ',\n\nGeweldig nieuws! ' + organization.name + ' had jullie doorverwijzingslink gebruikt om zich op Stamhoofd te registreren, en nu hebben ze ook voor het eerst minstens één euro uitgegeven. Daardoor ontvangen jullie ' + Formatter.price(-credit.unitPrice) + ' tegoed voor Stamhoofd (zie daarvoor Stamhoofd > Instellingen). '
|
|
216
|
+
+ (-credit.unitPrice <= 90 * 100_00 ? ('Bij de volgende vereniging ontvangen jullie nog meer: ' + Formatter.price(-credit.unitPrice + 10 * 100_00) + '. ') : '')
|
|
217
|
+
+ (-credit.unitPrice <= 80 * 100_00 ? ('En dat blijft oplopen tot € 100,00 per vereniging die je aanbrengt.') : '')
|
|
218
|
+
+ 'Doe zo verder! Lees zeker onze tips na om nog een groter bedrag te verzamelen.\n\n— Stamhoofd',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { encodeObject } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { BalanceItem, Document, Group, Member, Organization, Registration, RegistrationInvitation, sendEmailTemplate } from '@stamhoofd/models';
|
|
4
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
5
|
+
import { AppliedRegistrationDiscount, AuditLogSource, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, EmailTemplateType, getAppHost, GroupType, Recipient, Replacement, StockReservation, Version } from '@stamhoofd/structures';
|
|
6
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
4
7
|
import { AuditLogService } from './AuditLogService.js';
|
|
5
8
|
import { GroupService } from './GroupService.js';
|
|
6
9
|
import { PlatformMembershipService } from './PlatformMembershipService.js';
|
|
7
|
-
import { QueueHandler } from '@stamhoofd/queues';
|
|
8
|
-
import { Formatter } from '@stamhoofd/utility';
|
|
9
|
-
import { encodeObject } from '@simonbackx/simple-encoding';
|
|
10
10
|
|
|
11
11
|
export const RegistrationService = {
|
|
12
12
|
/**
|
|
@@ -55,6 +55,8 @@ export const RegistrationService = {
|
|
|
55
55
|
await registration.save();
|
|
56
56
|
RegistrationService.scheduleStockUpdate(registration.id);
|
|
57
57
|
|
|
58
|
+
await this.handleWaitingListRegistrationAndInvitation(registration);
|
|
59
|
+
|
|
58
60
|
await PlatformMembershipService.updateMembershipsForId(registration.memberId);
|
|
59
61
|
|
|
60
62
|
if (registration.sendConfirmationEmail) {
|
|
@@ -76,6 +78,65 @@ export const RegistrationService = {
|
|
|
76
78
|
return true;
|
|
77
79
|
},
|
|
78
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Delete all the invitations for the group and member linked to the registration.
|
|
83
|
+
* Will not throw if it fails.
|
|
84
|
+
* @param registration
|
|
85
|
+
*/
|
|
86
|
+
async handleWaitingListRegistrationAndInvitation(registration: Registration): Promise<void> {
|
|
87
|
+
const group = await Group.getByID(registration.groupId);
|
|
88
|
+
|
|
89
|
+
if (!group || group.type === GroupType.WaitingList) {
|
|
90
|
+
// skip if waiting list (waiting lists cannot have invitations)
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// get invitation for this group and member (can only be 1)
|
|
95
|
+
const invitation = await RegistrationInvitation.select()
|
|
96
|
+
.where('groupId', registration.groupId)
|
|
97
|
+
.andWhere('memberId', registration.memberId)
|
|
98
|
+
.first(false);
|
|
99
|
+
|
|
100
|
+
const waitingListIds = new Set<string>();
|
|
101
|
+
|
|
102
|
+
if (group.waitingListId) {
|
|
103
|
+
// we should check if the member is on the waiting list of this group
|
|
104
|
+
waitingListIds.add(group.waitingListId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (invitation) {
|
|
108
|
+
// delete the invitation
|
|
109
|
+
try {
|
|
110
|
+
await invitation.delete();
|
|
111
|
+
} catch (e) {
|
|
112
|
+
// an error should not stop other logic (impact of an error is low)
|
|
113
|
+
console.error(e);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// unsubscribe from waiting list
|
|
118
|
+
if (group.waitingListId) {
|
|
119
|
+
try {
|
|
120
|
+
// get the registration for the waiting list (should only be 1)
|
|
121
|
+
const waitingListRegistration = await Registration.select()
|
|
122
|
+
.where('groupId', group.waitingListId)
|
|
123
|
+
.where('memberId', registration.memberId)
|
|
124
|
+
.where('deactivatedAt', null)
|
|
125
|
+
.whereNot('registeredAt', null)
|
|
126
|
+
.first(false);
|
|
127
|
+
|
|
128
|
+
if (waitingListRegistration) {
|
|
129
|
+
// unsubscribe for waiting list (waiting lists cannot have a price -> balance items should not be updated)
|
|
130
|
+
await RegistrationService.deactivate(waitingListRegistration);
|
|
131
|
+
await GroupService.updateOccupancy(group.waitingListId);
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// an error should not stop other logic (impact of an error is low)
|
|
135
|
+
console.error(e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
79
140
|
async getRecipients(registration: Registration, organization: Organization, group: Group) {
|
|
80
141
|
const member = await Member.getByIdWithUsers(registration.memberId);
|
|
81
142
|
|
|
@@ -101,7 +162,7 @@ export const RegistrationService = {
|
|
|
101
162
|
}),
|
|
102
163
|
Replacement.create({
|
|
103
164
|
token: 'registerUrl',
|
|
104
|
-
value: 'https://' + organization.
|
|
165
|
+
value: 'https://' + getAppHost('registration', organization, user.permissions?.forOrganization(organization)?.isEmpty === false),
|
|
105
166
|
}),
|
|
106
167
|
Replacement.create({
|
|
107
168
|
token: 'groupName',
|
|
@@ -223,9 +284,65 @@ export const RegistrationService = {
|
|
|
223
284
|
* + in combination with validation and reading the webshop
|
|
224
285
|
*/
|
|
225
286
|
scheduleStockUpdate(id: string) {
|
|
287
|
+
this.scheduleStockUpdateAsync(id);
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Avoid calling this method directly. This can cause race conditions.
|
|
292
|
+
*/
|
|
293
|
+
async unsafeStockUpdate(updated: Registration) {
|
|
294
|
+
// Start with clearing all the stock reservations we've already made
|
|
295
|
+
if (updated.stockReservations) {
|
|
296
|
+
const groupIds = Formatter.uniqueArray(updated.stockReservations.flatMap(r => r.objectType === 'Group' ? [r.objectId] : []));
|
|
297
|
+
for (const groupId of groupIds) {
|
|
298
|
+
const stocks = StockReservation.filter('Group', groupId, updated.stockReservations);
|
|
299
|
+
|
|
300
|
+
// Technically we don't need to await this, but okay...
|
|
301
|
+
await Group.freeStockReservations(groupId, stocks);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (updated.shouldIncludeStock()) {
|
|
306
|
+
const groupStockReservations: StockReservation[] = [
|
|
307
|
+
// Group level stock reservations (stored in the group)
|
|
308
|
+
StockReservation.create({
|
|
309
|
+
objectId: updated.groupPrice.id,
|
|
310
|
+
objectType: 'GroupPrice',
|
|
311
|
+
amount: 1,
|
|
312
|
+
}),
|
|
313
|
+
...updated.options.map((o) => {
|
|
314
|
+
return StockReservation.create({
|
|
315
|
+
objectId: o.option.id,
|
|
316
|
+
objectType: 'GroupOption',
|
|
317
|
+
amount: o.amount,
|
|
318
|
+
});
|
|
319
|
+
}),
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
await Group.applyStockReservations(updated.groupId, groupStockReservations);
|
|
323
|
+
|
|
324
|
+
updated.stockReservations = [
|
|
325
|
+
// Global level stock reservations (stored in each group)
|
|
326
|
+
StockReservation.create({
|
|
327
|
+
objectId: updated.groupId,
|
|
328
|
+
objectType: 'Group',
|
|
329
|
+
amount: 1,
|
|
330
|
+
children: groupStockReservations,
|
|
331
|
+
}),
|
|
332
|
+
];
|
|
333
|
+
await updated.save();
|
|
334
|
+
} else {
|
|
335
|
+
if (updated.stockReservations.length) {
|
|
336
|
+
updated.stockReservations = [];
|
|
337
|
+
await updated.save();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
async scheduleStockUpdateAsync(id: string) {
|
|
226
343
|
QueueHandler.cancel('registration-stock-update-' + id);
|
|
227
344
|
|
|
228
|
-
AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
345
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
229
346
|
await QueueHandler.schedule('registration-stock-update-' + id, async function (this: undefined) {
|
|
230
347
|
const updated = await Registration.getByID(id);
|
|
231
348
|
|
|
@@ -233,53 +350,7 @@ export const RegistrationService = {
|
|
|
233
350
|
return;
|
|
234
351
|
}
|
|
235
352
|
|
|
236
|
-
|
|
237
|
-
if (updated.stockReservations) {
|
|
238
|
-
const groupIds = Formatter.uniqueArray(updated.stockReservations.flatMap(r => r.objectType === 'Group' ? [r.objectId] : []));
|
|
239
|
-
for (const groupId of groupIds) {
|
|
240
|
-
const stocks = StockReservation.filter('Group', groupId, updated.stockReservations);
|
|
241
|
-
|
|
242
|
-
// Technically we don't need to await this, but okay...
|
|
243
|
-
await Group.freeStockReservations(groupId, stocks);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (updated.shouldIncludeStock()) {
|
|
248
|
-
const groupStockReservations: StockReservation[] = [
|
|
249
|
-
// Group level stock reservations (stored in the group)
|
|
250
|
-
StockReservation.create({
|
|
251
|
-
objectId: updated.groupPrice.id,
|
|
252
|
-
objectType: 'GroupPrice',
|
|
253
|
-
amount: 1,
|
|
254
|
-
}),
|
|
255
|
-
...updated.options.map((o) => {
|
|
256
|
-
return StockReservation.create({
|
|
257
|
-
objectId: o.option.id,
|
|
258
|
-
objectType: 'GroupOption',
|
|
259
|
-
amount: o.amount,
|
|
260
|
-
});
|
|
261
|
-
}),
|
|
262
|
-
];
|
|
263
|
-
|
|
264
|
-
await Group.applyStockReservations(updated.groupId, groupStockReservations);
|
|
265
|
-
|
|
266
|
-
updated.stockReservations = [
|
|
267
|
-
// Global level stock reservations (stored in each group)
|
|
268
|
-
StockReservation.create({
|
|
269
|
-
objectId: updated.groupId,
|
|
270
|
-
objectType: 'Group',
|
|
271
|
-
amount: 1,
|
|
272
|
-
children: groupStockReservations,
|
|
273
|
-
}),
|
|
274
|
-
];
|
|
275
|
-
await updated.save();
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
if (updated.stockReservations.length) {
|
|
279
|
-
updated.stockReservations = [];
|
|
280
|
-
await updated.save();
|
|
281
|
-
}
|
|
282
|
-
}
|
|
353
|
+
await RegistrationService.unsafeStockUpdate(updated);
|
|
283
354
|
});
|
|
284
355
|
}).catch(console.error);
|
|
285
356
|
},
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { Platform } from '@stamhoofd/models';
|
|
4
|
+
import { LoginMethod, LoginMethodConfig, LoginProviderType, OpenIDClientConfiguration } from '@stamhoofd/structures';
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { ContextInstance } from '../helpers/Context.js';
|
|
8
|
+
import { SSOService } from './SSOService.js';
|
|
9
|
+
|
|
10
|
+
describe('SSOService', () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('fromContext', () => {
|
|
16
|
+
it('does not validate by default', async () => {
|
|
17
|
+
mockPlatform({
|
|
18
|
+
ssoConfiguration: OpenIDClientConfiguration.create({}),
|
|
19
|
+
loginMethods: new Map(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const service = await runWithContext(() => SSOService.fromContext(LoginProviderType.SSO));
|
|
23
|
+
|
|
24
|
+
expect(service).toBeInstanceOf(SSOService);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('validates when requested explicitly', async () => {
|
|
28
|
+
mockPlatform({
|
|
29
|
+
ssoConfiguration: OpenIDClientConfiguration.create({}),
|
|
30
|
+
loginMethods: new Map(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await expect(runWithContext(() => SSOService.fromContext(LoginProviderType.SSO, { validate: true }))).rejects.toThrow('SSO not configured (correctly)');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('validateConfiguration', () => {
|
|
38
|
+
it('validates the OpenID client configuration getter', () => {
|
|
39
|
+
const service = createService({
|
|
40
|
+
googleConfiguration: null,
|
|
41
|
+
loginMethods: new Map([[LoginMethod.Google, LoginMethodConfig.create({})]]),
|
|
42
|
+
provider: LoginProviderType.Google,
|
|
43
|
+
organization: {},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(() => service.validateConfiguration()).toThrow(SimpleError);
|
|
47
|
+
expect(() => service.validateConfiguration()).toThrow('SSO not configured');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('validates the login method configuration getter', () => {
|
|
51
|
+
const service = createService({
|
|
52
|
+
ssoConfiguration: OpenIDClientConfiguration.create({}),
|
|
53
|
+
loginMethods: new Map(),
|
|
54
|
+
provider: LoginProviderType.SSO,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(() => service.validateConfiguration()).toThrow(SimpleError);
|
|
58
|
+
expect(() => service.validateConfiguration()).toThrow('SSO not configured (correctly)');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('accepts complete SSO configuration', () => {
|
|
62
|
+
const service = createService({
|
|
63
|
+
ssoConfiguration: OpenIDClientConfiguration.create({}),
|
|
64
|
+
loginMethods: new Map([[LoginMethod.SSO, LoginMethodConfig.create({})]]),
|
|
65
|
+
provider: LoginProviderType.SSO,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(() => service.validateConfiguration()).not.toThrow();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
async function runWithContext<T>(handler: () => Promise<T>) {
|
|
74
|
+
const context = new ContextInstance(new Request({
|
|
75
|
+
method: 'GET',
|
|
76
|
+
url: '/',
|
|
77
|
+
host: '',
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
return await ContextInstance.asyncLocalStorage.run(context, handler);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function mockPlatform(options: {
|
|
84
|
+
loginMethods: Map<LoginMethod, LoginMethodConfig>;
|
|
85
|
+
ssoConfiguration?: OpenIDClientConfiguration | null;
|
|
86
|
+
googleConfiguration?: OpenIDClientConfiguration | null;
|
|
87
|
+
}) {
|
|
88
|
+
vi.spyOn(Platform, 'getForEditing').mockResolvedValue(createPlatform(options));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createService(options: {
|
|
92
|
+
provider: LoginProviderType;
|
|
93
|
+
loginMethods: Map<LoginMethod, LoginMethodConfig>;
|
|
94
|
+
ssoConfiguration?: OpenIDClientConfiguration | null;
|
|
95
|
+
googleConfiguration?: OpenIDClientConfiguration | null;
|
|
96
|
+
organization?: any;
|
|
97
|
+
}) {
|
|
98
|
+
return new SSOService({
|
|
99
|
+
provider: options.provider,
|
|
100
|
+
platform: createPlatform(options),
|
|
101
|
+
organization: options.organization,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createPlatform(options: {
|
|
106
|
+
loginMethods: Map<LoginMethod, LoginMethodConfig>;
|
|
107
|
+
ssoConfiguration?: OpenIDClientConfiguration | null;
|
|
108
|
+
googleConfiguration?: OpenIDClientConfiguration | null;
|
|
109
|
+
}) {
|
|
110
|
+
return {
|
|
111
|
+
config: {
|
|
112
|
+
loginMethods: options.loginMethods,
|
|
113
|
+
},
|
|
114
|
+
serverConfig: {
|
|
115
|
+
ssoConfiguration: options.ssoConfiguration,
|
|
116
|
+
googleConfiguration: options.googleConfiguration,
|
|
117
|
+
},
|
|
118
|
+
} as any;
|
|
119
|
+
}
|