@stamhoofd/backend 2.121.0 → 2.122.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/output-2.txt +1 -0
  2. package/output.txt +1 -0
  3. package/package.json +69 -52
  4. package/src/audit-logs/EventNotificationLogger.ts +40 -0
  5. package/src/audit-logs/ModelLogger.ts +19 -15
  6. package/src/audit-logs/OrganizationLogger.ts +1 -1
  7. package/src/audit-logs/RegistrationInvitationLogger.ts +24 -24
  8. package/src/audit-logs/init.ts +8 -2
  9. package/src/boot.ts +19 -18
  10. package/src/crons/amazon-ses.ts +13 -26
  11. package/src/crons/balance-emails.ts +3 -5
  12. package/src/crons/clearExcelCache.test.ts +1 -1
  13. package/src/crons/clearExcelCache.ts +1 -2
  14. package/src/crons/drip-emails.ts +42 -0
  15. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +4 -0
  16. package/src/crons/helpers/isOutside.ts +11 -0
  17. package/src/crons/helpers/useSavedIterator.ts +32 -0
  18. package/src/crons/index.ts +5 -0
  19. package/src/crons/invoices.ts +32 -38
  20. package/src/crons/members-fees.ts +50 -0
  21. package/src/crons/mollie-chargebacks.ts +14 -15
  22. package/src/crons/service-fees.ts +152 -0
  23. package/src/crons/stripe-invoices.ts +48 -0
  24. package/src/crons/transfer-fees.ts +176 -0
  25. package/src/crons/update-cached-balances.ts +4 -4
  26. package/src/crons.ts +7 -64
  27. package/src/debug.ts +4 -2
  28. package/src/email-recipient-loaders/documents.ts +1 -1
  29. package/src/email-recipient-loaders/members.ts +1 -1
  30. package/src/email-recipient-loaders/payments.ts +4 -6
  31. package/src/email-recipient-loaders/registrations.ts +1 -1
  32. package/src/email-replacements/getEmailReplacementsForPayment.ts +5 -9
  33. package/src/endpoints/admin/members/ChargeMembersEndpoint.ts +1 -1
  34. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -1
  35. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +3 -5
  36. package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +1 -1
  37. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +1 -1
  38. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +13 -7
  39. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +4 -6
  40. package/src/endpoints/admin/organizations/PatchPackagesEndpoint.test.ts +10 -6
  41. package/src/endpoints/admin/organizations/PatchPackagesEndpoint.ts +39 -21
  42. package/src/endpoints/admin/registrations/ChargeRegistrationsEndpoint.ts +1 -1
  43. package/src/endpoints/auth/CreateAdminEndpoint.ts +23 -5
  44. package/src/endpoints/auth/CreateTokenEndpoint.ts +2 -2
  45. package/src/endpoints/auth/DeleteTokenEndpoint.ts +1 -1
  46. package/src/endpoints/auth/DeleteUserEndpoint.ts +3 -3
  47. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
  48. package/src/endpoints/auth/GetOtherUserEndpoint.ts +1 -1
  49. package/src/endpoints/auth/GetUserEndpoint.ts +1 -1
  50. package/src/endpoints/auth/OpenIDConnectAuthTokenEndpoint.ts +1 -1
  51. package/src/endpoints/auth/OpenIDConnectStartEndpoint.ts +1 -1
  52. package/src/endpoints/auth/PatchUserEndpoint.ts +5 -8
  53. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +1 -1
  54. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +1 -1
  55. package/src/endpoints/auth/SignupEndpoint.ts +2 -3
  56. package/src/endpoints/auth/VerifyEmailEndpoint.ts +20 -3
  57. package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +2 -1
  58. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +2 -4
  59. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +1 -1
  60. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +5 -7
  61. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +2 -2
  62. package/src/endpoints/global/email/CreateEmailEndpoint.ts +3 -4
  63. package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +1 -1
  64. package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +1 -2
  65. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +4 -6
  66. package/src/endpoints/global/email/GetEmailEndpoint.ts +1 -1
  67. package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +1 -1
  68. package/src/endpoints/global/email/GetUserEmailsEndpoint.ts +1 -1
  69. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +6 -10
  70. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +4 -7
  71. package/src/endpoints/global/email/PatchEmailEndpoint.ts +6 -9
  72. package/src/endpoints/global/email-recipients/GetEmailRecipientsCountEndpoint.ts +1 -1
  73. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +1 -1
  74. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.ts +1 -1
  75. package/src/endpoints/global/email-recipients/RetryEmailRecipientEndpoint.ts +2 -2
  76. package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +1 -1
  77. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +3 -3
  78. package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -3
  79. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +49 -4
  80. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +5 -8
  81. package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +12 -44
  82. package/src/endpoints/global/events/PatchEventsEndpoint.ts +128 -127
  83. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -9
  84. package/src/endpoints/global/files/GetFileCache.ts +3 -4
  85. package/src/endpoints/global/files/UploadFile.ts +3 -4
  86. package/src/endpoints/global/files/UploadImage.ts +3 -4
  87. package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +1 -1
  88. package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +1 -1
  89. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +4 -2
  90. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +2 -3
  91. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +1 -1
  92. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +2 -2
  93. package/src/endpoints/global/members/GetMembersEndpoint.ts +10 -12
  94. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +195 -12
  95. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +16 -22
  96. package/src/endpoints/global/members/helpers/validateGroupFilter.ts +6 -30
  97. package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +1 -1
  98. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +1 -1
  99. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +40 -8
  100. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +2 -2
  101. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -2
  102. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +4 -1
  103. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +4 -5
  104. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +9 -16
  105. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +1 -1
  106. package/src/endpoints/global/platform/GetPlatformEndpoint.ts +1 -1
  107. package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +1 -1
  108. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +11 -11
  109. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsCountEndpoint.ts +31 -31
  110. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsEndpoint.ts +18 -20
  111. package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +1 -1
  112. package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1 -1
  113. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +6 -10
  114. package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +35 -6
  115. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +2 -2
  116. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +1 -1
  117. package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +1 -1
  118. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
  119. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -3
  120. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +90 -6
  121. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +35 -34
  122. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +1 -1
  123. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +62 -63
  124. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +6 -9
  125. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +51 -52
  126. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +6 -7
  127. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsCountEndpoint.ts +43 -0
  128. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +1 -1
  129. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +13 -5
  130. package/src/endpoints/global/sso/GetSSOEndpoint.ts +2 -3
  131. package/src/endpoints/global/sso/SetSSOEndpoint.ts +1 -1
  132. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +2 -2
  133. package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +1 -2
  134. package/src/endpoints/global/webshops/GetWebshopsEndpoint.ts +2 -4
  135. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemEndpoint.ts +2 -2
  136. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsCountEndpoint.ts +1 -1
  137. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.ts +3 -4
  138. package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +7 -9
  139. package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.test.ts +73 -0
  140. package/src/endpoints/organization/dashboard/billing/ApplyRegisterCodeEndpoint.ts +89 -0
  141. package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +1 -5
  142. package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +11 -11
  143. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +2 -2
  144. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +8 -8
  145. package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +9 -9
  146. package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableBalanceEndpoint.ts +2 -2
  147. package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableInvoicesEndpoint.ts +68 -0
  148. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +4 -4
  149. package/src/endpoints/organization/dashboard/billing/GetRegisterCodeEndpoint.ts +66 -0
  150. package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.test.ts +949 -0
  151. package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +60 -57
  152. package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +20 -20
  153. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +1 -1
  154. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +1 -1
  155. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +1 -1
  156. package/src/endpoints/organization/dashboard/documents/GetDocumentsCountEndpoint.ts +1 -1
  157. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +1 -1
  158. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +10 -2
  159. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +2 -2
  160. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.ts +2 -2
  161. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +1 -1
  162. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +1 -1
  163. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +10 -8
  164. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +1 -1
  165. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +5 -6
  166. package/src/endpoints/organization/dashboard/invoices/GetInvoicesCountEndpoint.ts +1 -1
  167. package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +10 -6
  168. package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +11 -2
  169. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +4 -5
  170. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +5 -5
  171. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +2 -2
  172. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +7 -7
  173. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +1 -1
  174. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +2 -2
  175. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +1 -1
  176. package/src/endpoints/organization/dashboard/organization/GetUitpasClientIdEndpoint.ts +1 -1
  177. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -2
  178. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +50 -12
  179. package/src/endpoints/organization/dashboard/organization/SearchUitpasOrganizersEndpoint.ts +2 -2
  180. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +5 -10
  181. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +1 -1
  182. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +1 -1
  183. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +3 -4
  184. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +21 -14
  185. package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +35 -28
  186. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +2 -3
  187. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesCountEndpoint.ts +1 -1
  188. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -1
  189. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
  190. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +2 -3
  191. package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +1 -1
  192. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +1 -1
  193. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +19 -18
  194. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +4 -5
  195. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +1 -1
  196. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +2 -3
  197. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +2 -2
  198. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +1 -1
  199. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +2 -2
  200. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +1 -1
  201. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +1 -1
  202. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +1 -1
  203. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +1 -1
  204. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +1 -1
  205. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.ts +4 -6
  206. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +10 -15
  207. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +1 -1
  208. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +2 -2
  209. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +1 -1
  210. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +1 -1
  211. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +1 -1
  212. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +11 -2
  213. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +4 -6
  214. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +22 -7
  215. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +20 -27
  216. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +1 -1
  217. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +2 -2
  218. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +2 -3
  219. package/src/endpoints/organization/dashboard/webshops/WebshopReservedPathSegments.test.ts +95 -0
  220. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -2
  221. package/src/endpoints/organization/shared/GetDocumentHtml.ts +1 -1
  222. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
  223. package/src/endpoints/organization/shared/GetUitpasNumberDetailsEndpoint.ts +3 -5
  224. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +1 -1
  225. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +4 -1
  226. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +14 -3
  227. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -2
  228. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +37 -16
  229. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +34 -2
  230. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +4 -2
  231. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +74 -3
  232. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +16 -33
  233. package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +3 -5
  234. package/src/endpoints/organization/webshops/WebshopAuthHelper.ts +36 -0
  235. package/src/endpoints/system/HealthEndpoint.ts +1 -1
  236. package/src/excel-loaders/balance-items.ts +1 -1
  237. package/src/excel-loaders/event-notifications.ts +1 -1
  238. package/src/excel-loaders/members.ts +1 -1
  239. package/src/excel-loaders/organizations.ts +3 -3
  240. package/src/excel-loaders/payments.ts +31 -1
  241. package/src/excel-loaders/platform-memberships.ts +13 -14
  242. package/src/excel-loaders/receivable-balances.ts +2 -2
  243. package/src/excel-loaders/registrations.ts +2 -3
  244. package/src/helpers/AddressValidator.ts +7 -14
  245. package/src/helpers/AdminPermissionChecker.ts +50 -63
  246. package/src/helpers/AuthenticatedStructures.ts +69 -55
  247. package/src/helpers/BalanceItemRelationsBackfiller.test.ts +174 -0
  248. package/src/helpers/BalanceItemRelationsBackfiller.ts +163 -0
  249. package/src/helpers/BuckarooHelper.ts +2 -4
  250. package/src/helpers/CheckSettlements.ts +12 -24
  251. package/src/helpers/Context.ts +15 -10
  252. package/src/helpers/CookieHelper.ts +1 -2
  253. package/src/helpers/EmailResumer.ts +1 -2
  254. package/src/helpers/FileCache.ts +2 -3
  255. package/src/helpers/FinancialSupportHelper.ts +2 -2
  256. package/src/helpers/FlagMomentCleanup.ts +1 -2
  257. package/src/helpers/ForwardHandler.ts +5 -10
  258. package/src/helpers/GlobalHelper.ts +3 -3
  259. package/src/helpers/LegacyBillingConverter.ts +787 -0
  260. package/src/helpers/MemberMerger.test.ts +782 -0
  261. package/src/helpers/MemberMerger.ts +600 -0
  262. package/src/helpers/MemberUserSyncer.ts +6 -12
  263. package/src/helpers/MembershipCharger.ts +2 -4
  264. package/src/helpers/PaymentCustomerResolver.test.ts +203 -0
  265. package/src/helpers/PaymentCustomerResolver.ts +144 -0
  266. package/src/helpers/PeriodHelper.ts +2 -3
  267. package/src/helpers/RecordAnswerHelper.test.ts +105 -110
  268. package/src/helpers/RecordAnswerHelper.ts +7 -7
  269. package/src/helpers/SeedTools.ts +106 -0
  270. package/src/helpers/ServiceFeeHelper.ts +125 -15
  271. package/src/helpers/SetupStepUpdater.ts +38 -32
  272. package/src/helpers/StamhoofdFilterAccessHelper.ts +52 -0
  273. package/src/helpers/StripeHelper.ts +14 -36
  274. package/src/helpers/StripeInvoicer.ts +419 -0
  275. package/src/helpers/StripePayoutChecker.ts +5 -10
  276. package/src/helpers/TagHelper.ts +7 -8
  277. package/src/helpers/TemporaryMemberAccess.ts +1 -2
  278. package/src/helpers/ThrottledQueue.ts +1 -2
  279. package/src/helpers/UitpasTokenRepository.ts +1 -2
  280. package/src/helpers/ViesHelper.ts +7 -11
  281. package/src/helpers/XlsxTransformerColumnHelper.ts +1 -1
  282. package/src/helpers/outstandingBalanceJoin.ts +2 -2
  283. package/src/helpers/updateMemberDetailsUitpasNumber.ts +3 -5
  284. package/src/migrate.ts +12 -5
  285. package/src/{seeds → migrations}/0000000004-single-organization.ts +10 -1
  286. package/src/migrations/0000000006-default-organization.ts +139 -0
  287. package/src/{seeds → migrations}/1715028563-user-permissions.ts +17 -27
  288. package/src/{seeds/1765896674-document-update-year.test.ts → migrations/1750090029-document-update-year.test.ts} +27 -26
  289. package/src/migrations/1750090029-document-update-year.ts +103 -0
  290. package/src/{seeds → migrations}/1750090030-records-configuration.ts +33 -32
  291. package/src/migrations/1750090031-records-configuration-webshops.ts +36 -0
  292. package/src/migrations/1752848540-national-numbers.ts +585 -0
  293. package/src/migrations/1752848550-records-configuration-group-filter.ts +1064 -0
  294. package/src/{seeds → migrations}/1752848561-groups-registration-periods.ts +70 -26
  295. package/src/{seeds → migrations}/1754560914-groups-prices.test.ts +1 -1
  296. package/src/{seeds → migrations}/1754560914-groups-prices.ts +9 -15
  297. package/src/{seeds → migrations}/1756293699-fill-previous-next-period-id.ts +1 -2
  298. package/src/migrations/1761665607-sync-member-users.ts +40 -0
  299. package/src/migrations/1779121240-invoice-email-template.sql +6 -0
  300. package/src/migrations/1779986893-default-payment-failed-email-templates.sql +4 -0
  301. package/src/migrations/1780578166-convert-legacy-billing.ts +13 -0
  302. package/src/{seeds/1755876819-remove-duplicate-members.ts → migrations/1780665426-remove-duplicate-members.ts} +23 -4
  303. package/src/migrations/1780933598-document-groups.ts +88 -0
  304. package/src/migrations/1781692342-balance-item-relations.ts +13 -0
  305. package/src/migrations/1781857866-platform-admins.ts +103 -0
  306. package/src/migrations/1781857867-rm-old-platform-admins.ts +25 -0
  307. package/src/seeds/0000000001-throw.ts +5 -0
  308. package/src/seeds/1760702452-update-balance-price-paid.ts +19 -0
  309. package/src/seeds/1760702453-update-balance-invoiced.ts +19 -0
  310. package/src/seeds/1760702454-update-cached-outstanding-balance-from-items.ts +51 -14
  311. package/src/seeds/1773754928-force-save-members.ts +41 -8
  312. package/src/seeds/1780665427-schedule-stock-updates.ts +45 -0
  313. package/src/seeds/1780665428-group-update-occupancy.ts +31 -0
  314. package/src/seeds/1780915001-fill-payment-customer.ts +70 -0
  315. package/src/seeds/1780915002-update-orders.ts +39 -0
  316. package/src/seeds/1782047708-organization-company-address.ts +44 -0
  317. package/src/seeds-temporary/1769088653-uitpas-status.ts +1 -2
  318. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.test.ts +69 -0
  319. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.ts +85 -0
  320. package/src/services/AuditLogService.test.ts +51 -0
  321. package/src/services/AuditLogService.ts +45 -0
  322. package/src/services/BalanceItemService.ts +141 -44
  323. package/src/services/CpuService.ts +3 -6
  324. package/src/services/DatabaseCollationService.test.ts +1 -2
  325. package/src/services/DocumentService.ts +1 -2
  326. package/src/services/EventNotificationService.ts +1 -1
  327. package/src/services/FileSignService.ts +2 -4
  328. package/src/services/InvoicePdfService.ts +286 -0
  329. package/src/services/InvoiceService.ts +204 -251
  330. package/src/services/InvoiceXMLService.ts +424 -0
  331. package/src/services/MemberNumberService.ts +6 -11
  332. package/src/services/MemberRecordStore.ts +8 -10
  333. package/src/services/MollieService.ts +114 -121
  334. package/src/services/PaymentMandateService.ts +45 -45
  335. package/src/services/PaymentReallocationService.test.ts +1 -1
  336. package/src/services/PaymentReallocationService.ts +7 -9
  337. package/src/services/PaymentService.ts +444 -308
  338. package/src/services/PlatformMembershipService.ts +4 -8
  339. package/src/services/ReferralService.ts +223 -0
  340. package/src/services/RegistrationService.ts +63 -53
  341. package/src/services/SSOService.test.ts +119 -0
  342. package/src/services/SSOService.ts +56 -49
  343. package/src/services/STPackageService.ts +97 -61
  344. package/src/services/UniqueMemberNumberService.ts +69 -0
  345. package/src/services/UniqueUserService.ts +3 -6
  346. package/src/services/VATService.ts +85 -0
  347. package/src/services/uitpas/PassholderEndpoints.ts +3 -6
  348. package/src/services/uitpas/UitpasService.ts +6 -10
  349. package/src/services/uitpas/checkUitpasNumbers.ts +2 -4
  350. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +3 -6
  351. package/src/services/uitpas/searchUitpasEvents.ts +1 -2
  352. package/src/sql-filters/audit-logs.ts +1 -1
  353. package/src/sql-filters/balance-item-payments.ts +1 -1
  354. package/src/sql-filters/balance-items.ts +1 -1
  355. package/src/sql-filters/base-registration-filter-compilers.ts +5 -33
  356. package/src/sql-filters/document-templates.ts +1 -1
  357. package/src/sql-filters/documents.ts +1 -1
  358. package/src/sql-filters/email-recipients.ts +1 -1
  359. package/src/sql-filters/emails.ts +1 -1
  360. package/src/sql-filters/event-notifications.ts +1 -1
  361. package/src/sql-filters/events.ts +6 -1
  362. package/src/sql-filters/groups.ts +5 -0
  363. package/src/sql-filters/invoiced-balance-items.ts +1 -1
  364. package/src/sql-filters/invoices.ts +1 -1
  365. package/src/sql-filters/member-responsibility-records.ts +1 -1
  366. package/src/sql-filters/members.ts +1 -1
  367. package/src/sql-filters/orders.ts +4 -4
  368. package/src/sql-filters/organization-registration-periods.ts +1 -1
  369. package/src/sql-filters/organizations.ts +1 -1
  370. package/src/sql-filters/platform-memberships.ts +3 -3
  371. package/src/sql-filters/receivable-balances.ts +1 -1
  372. package/src/sql-filters/registration-periods.ts +1 -1
  373. package/src/sql-filters/registrations.ts +1 -1
  374. package/src/sql-filters/tickets.ts +1 -1
  375. package/src/sql-filters/users.ts +1 -1
  376. package/src/sql-sorters/groups.ts +23 -0
  377. package/src/sql-sorters/platform-memberships.ts +16 -16
  378. package/src/sql-sorters/registration-invitations.ts +2 -2
  379. package/tests/actions/patchOrganizationMember.ts +1 -1
  380. package/tests/actions/patchUserMember.ts +1 -1
  381. package/tests/assertions/assertBalances.ts +6 -12
  382. package/tests/e2e/api-rate-limits.test.ts +12 -24
  383. package/tests/e2e/bundle-discounts.test.ts +4 -1
  384. package/tests/e2e/charge-members.test.ts +13 -11
  385. package/tests/e2e/documents.test.ts +4 -1
  386. package/tests/e2e/register.test.ts +7 -9
  387. package/tests/e2e/stock.test.ts +4 -3
  388. package/tests/e2e/tickets.test.ts +1 -1
  389. package/tests/helpers/MollieMocker.ts +462 -0
  390. package/tests/helpers/PayconiqMocker.ts +138 -45
  391. package/tests/helpers/StripeMocker.ts +9 -6
  392. package/tests/helpers/index.ts +4 -0
  393. package/tests/init/initAdmin.ts +2 -2
  394. package/tests/init/initBundleDiscount.ts +1 -1
  395. package/tests/init/initMembershipOrganization.ts +13 -0
  396. package/tests/init/initPermissionRole.ts +3 -4
  397. package/tests/vitest.global.setup.ts +1 -1
  398. package/vitest.config.js +6 -0
  399. package/.nvmrc +0 -1
  400. package/src/helpers/GroupBuilder.ts +0 -418
  401. package/src/seeds/0000000000-example.ts +0 -11
  402. package/src/seeds/1722256498-group-update-occupancy.ts +0 -52
  403. package/src/seeds/1726572303-schedule-stock-updates.ts +0 -52
  404. package/src/seeds/1726847064-setup-steps.ts +0 -16
  405. package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +0 -31
  406. package/src/seeds/1729253172-update-orders.ts +0 -21
  407. package/src/seeds/1733319079-fill-paying-organization-ids.ts +0 -68
  408. package/src/seeds/1733748412-update-deleted-order-numbers.ts +0 -109
  409. package/src/seeds/1733994455-balance-item-status-open.ts +0 -30
  410. package/src/seeds/1740046783-update-membership.ts +0 -17
  411. package/src/seeds/1741008870-fix-auditlog-description.ts +0 -50
  412. package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +0 -88
  413. package/src/seeds/1741268179-fix-group-dates-of-events.ts +0 -32
  414. package/src/seeds/1751445358-upload-email-attachments.ts +0 -106
  415. package/src/seeds/1755532883-update-email-sender-ids.ts +0 -47
  416. package/src/seeds/1755790070-fill-email-recipient-errors.ts +0 -96
  417. package/src/seeds/1756115432-remove-old-drafts.ts +0 -16
  418. package/src/seeds/1756115433-fill-email-recipient-organization-id.ts +0 -30
  419. package/src/seeds/1756303697-update-email-counts.ts +0 -76
  420. package/src/seeds/1761665607-sync-member-users.ts +0 -59
  421. package/src/seeds/1765896674-document-update-year.ts +0 -66
  422. /package/src/{seeds → migrations}/0000000001-development-user.ts +0 -0
  423. /package/src/{seeds → migrations}/0000000002-clear-stamhoofd-email-templates.ts +0 -0
  424. /package/src/{seeds → migrations}/0000000003-default-email-templates.ts +0 -0
  425. /package/src/{seeds → migrations}/1752848561-groups-registration-periods.test.ts +0 -0
  426. /package/src/{seeds → migrations}/1766150402-document-published-at.test.ts +0 -0
  427. /package/src/{seeds → migrations}/1766150402-document-published-at.ts +0 -0
  428. /package/src/{seeds → migrations}/1779121239-default-invoice-email-template.sql +0 -0
  429. /package/src/{seeds → migrations}/data/default-email-templates.sql +0 -0
@@ -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
+ }
@@ -2,7 +2,7 @@ import { ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { encodeObject } from '@simonbackx/simple-encoding';
3
3
  import { BalanceItem, Document, Group, Member, Organization, Registration, RegistrationInvitation, sendEmailTemplate } from '@stamhoofd/models';
4
4
  import { QueueHandler } from '@stamhoofd/queues';
5
- import { AppliedRegistrationDiscount, AuditLogSource, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, EmailTemplateType, GroupType, Recipient, Replacement, StockReservation, Version } from '@stamhoofd/structures';
5
+ import { AppliedRegistrationDiscount, AuditLogSource, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, EmailTemplateType, getAppHost, GroupType, Recipient, Replacement, StockReservation, Version } from '@stamhoofd/structures';
6
6
  import { Formatter } from '@stamhoofd/utility';
7
7
  import { AuditLogService } from './AuditLogService.js';
8
8
  import { GroupService } from './GroupService.js';
@@ -81,7 +81,7 @@ export const RegistrationService = {
81
81
  /**
82
82
  * Delete all the invitations for the group and member linked to the registration.
83
83
  * Will not throw if it fails.
84
- * @param registration
84
+ * @param registration
85
85
  */
86
86
  async handleWaitingListRegistrationAndInvitation(registration: Registration): Promise<void> {
87
87
  const group = await Group.getByID(registration.groupId);
@@ -96,7 +96,7 @@ export const RegistrationService = {
96
96
  .where('groupId', registration.groupId)
97
97
  .andWhere('memberId', registration.memberId)
98
98
  .first(false);
99
-
99
+
100
100
  const waitingListIds = new Set<string>();
101
101
 
102
102
  if (group.waitingListId) {
@@ -124,7 +124,7 @@ export const RegistrationService = {
124
124
  .where('deactivatedAt', null)
125
125
  .whereNot('registeredAt', null)
126
126
  .first(false);
127
-
127
+
128
128
  if (waitingListRegistration) {
129
129
  // unsubscribe for waiting list (waiting lists cannot have a price -> balance items should not be updated)
130
130
  await RegistrationService.deactivate(waitingListRegistration);
@@ -162,7 +162,7 @@ export const RegistrationService = {
162
162
  }),
163
163
  Replacement.create({
164
164
  token: 'registerUrl',
165
- value: 'https://' + organization.getHost(),
165
+ value: 'https://' + getAppHost('registration', organization, user.permissions?.forOrganization(organization)?.isEmpty === false),
166
166
  }),
167
167
  Replacement.create({
168
168
  token: 'groupName',
@@ -284,9 +284,65 @@ export const RegistrationService = {
284
284
  * + in combination with validation and reading the webshop
285
285
  */
286
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) {
287
343
  QueueHandler.cancel('registration-stock-update-' + id);
288
344
 
289
- AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
345
+ await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
290
346
  await QueueHandler.schedule('registration-stock-update-' + id, async function (this: undefined) {
291
347
  const updated = await Registration.getByID(id);
292
348
 
@@ -294,53 +350,7 @@ export const RegistrationService = {
294
350
  return;
295
351
  }
296
352
 
297
- // Start with clearing all the stock reservations we've already made
298
- if (updated.stockReservations) {
299
- const groupIds = Formatter.uniqueArray(updated.stockReservations.flatMap(r => r.objectType === 'Group' ? [r.objectId] : []));
300
- for (const groupId of groupIds) {
301
- const stocks = StockReservation.filter('Group', groupId, updated.stockReservations);
302
-
303
- // Technically we don't need to await this, but okay...
304
- await Group.freeStockReservations(groupId, stocks);
305
- }
306
- }
307
-
308
- if (updated.shouldIncludeStock()) {
309
- const groupStockReservations: StockReservation[] = [
310
- // Group level stock reservations (stored in the group)
311
- StockReservation.create({
312
- objectId: updated.groupPrice.id,
313
- objectType: 'GroupPrice',
314
- amount: 1,
315
- }),
316
- ...updated.options.map((o) => {
317
- return StockReservation.create({
318
- objectId: o.option.id,
319
- objectType: 'GroupOption',
320
- amount: o.amount,
321
- });
322
- }),
323
- ];
324
-
325
- await Group.applyStockReservations(updated.groupId, groupStockReservations);
326
-
327
- updated.stockReservations = [
328
- // Global level stock reservations (stored in each group)
329
- StockReservation.create({
330
- objectId: updated.groupId,
331
- objectType: 'Group',
332
- amount: 1,
333
- children: groupStockReservations,
334
- }),
335
- ];
336
- await updated.save();
337
- }
338
- else {
339
- if (updated.stockReservations.length) {
340
- updated.stockReservations = [];
341
- await updated.save();
342
- }
343
- }
353
+ await RegistrationService.unsafeStockUpdate(updated);
344
354
  });
345
355
  }).catch(console.error);
346
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
+ }