@stamhoofd/backend 2.118.1 → 2.120.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 (362) hide show
  1. package/package.json +32 -22
  2. package/src/audit-logs/ModelLogger.ts +4 -2
  3. package/src/audit-logs/OrderLogger.ts +1 -1
  4. package/src/boot.ts +32 -14
  5. package/src/crons/balance-emails.ts +4 -2
  6. package/src/crons/clearExcelCache.test.ts +8 -8
  7. package/src/crons/update-cached-balances.ts +40 -14
  8. package/src/debug.ts +3 -2
  9. package/src/decoders/StringArrayDecoder.ts +1 -1
  10. package/src/decoders/StringNullableDecoder.ts +1 -1
  11. package/src/email-recipient-loaders/documents.ts +2 -1
  12. package/src/email-recipient-loaders/members.ts +2 -1
  13. package/src/email-recipient-loaders/orders.ts +2 -1
  14. package/src/email-recipient-loaders/payments.ts +6 -3
  15. package/src/email-recipient-loaders/receivable-balances.ts +2 -1
  16. package/src/email-recipient-loaders/registrations.ts +2 -1
  17. package/src/email-replacements/getEmailReplacementsForPayment.ts +8 -7
  18. package/src/endpoints/admin/members/ChargeMembersEndpoint.ts +7 -6
  19. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +3 -2
  20. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +2 -1
  21. package/src/endpoints/admin/organizations/ChargeOrganizationsEndpoint.ts +4 -3
  22. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +3 -2
  23. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +7 -5
  24. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +12 -10
  25. package/src/endpoints/admin/registrations/ChargeRegistrationsEndpoint.ts +4 -3
  26. package/src/endpoints/auth/CreateAdminEndpoint.ts +8 -6
  27. package/src/endpoints/auth/CreateTokenEndpoint.ts +14 -12
  28. package/src/endpoints/auth/DeleteTokenEndpoint.ts +2 -1
  29. package/src/endpoints/auth/DeleteUserEndpoint.ts +2 -1
  30. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +5 -4
  31. package/src/endpoints/auth/GetOtherUserEndpoint.ts +3 -2
  32. package/src/endpoints/auth/GetUserEndpoint.ts +3 -2
  33. package/src/endpoints/auth/OpenIDConnectAuthTokenEndpoint.ts +2 -1
  34. package/src/endpoints/auth/OpenIDConnectCallbackEndpoint.ts +4 -2
  35. package/src/endpoints/auth/OpenIDConnectStartEndpoint.ts +3 -2
  36. package/src/endpoints/auth/PatchUserEndpoint.ts +15 -12
  37. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +3 -2
  38. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +3 -2
  39. package/src/endpoints/auth/SignupEndpoint.ts +5 -4
  40. package/src/endpoints/auth/VerifyEmailEndpoint.ts +6 -5
  41. package/src/endpoints/frontend/FrontendEnvironmentEndpoint.ts +3 -2
  42. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +8 -5
  43. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +5 -3
  44. package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +8 -5
  45. package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +9 -7
  46. package/src/endpoints/global/billing/DeactivatePackageEndpoint.ts +4 -3
  47. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +4 -2
  48. package/src/endpoints/global/email/CreateEmailEndpoint.ts +9 -7
  49. package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +2 -1
  50. package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +22 -19
  51. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +6 -4
  52. package/src/endpoints/global/email/GetEmailEndpoint.ts +4 -3
  53. package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +3 -2
  54. package/src/endpoints/global/email/GetUserEmailsEndpoint.ts +7 -4
  55. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +7 -5
  56. package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +11 -11
  57. package/src/endpoints/global/email/PatchEmailEndpoint.ts +14 -11
  58. package/src/endpoints/global/email-recipients/GetEmailRecipientsCountEndpoint.ts +3 -2
  59. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.test.ts +2 -1
  60. package/src/endpoints/global/email-recipients/GetEmailRecipientsEndpoint.ts +8 -5
  61. package/src/endpoints/global/email-recipients/RetryEmailRecipientEndpoint.ts +7 -5
  62. package/src/endpoints/global/email-recipients/helpers/validateEmailRecipientFilter.ts +4 -3
  63. package/src/endpoints/global/events/GetEventNotificationsCountEndpoint.ts +3 -2
  64. package/src/endpoints/global/events/GetEventNotificationsEndpoint.ts +9 -6
  65. package/src/endpoints/global/events/GetEventsEndpoint.ts +7 -4
  66. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +4 -2
  67. package/src/endpoints/global/events/PatchEventNotificationsEndpoint.ts +17 -15
  68. package/src/endpoints/global/events/PatchEventsEndpoint.test.ts +4 -2
  69. package/src/endpoints/global/events/PatchEventsEndpoint.ts +28 -26
  70. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +8 -5
  71. package/src/endpoints/global/files/GetFileCache.ts +5 -3
  72. package/src/endpoints/global/files/UploadFile.ts +10 -4
  73. package/src/endpoints/global/files/UploadImage.ts +4 -2
  74. package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +4 -2
  75. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +8 -5
  76. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +7 -5
  77. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +3 -2
  78. package/src/endpoints/global/members/GetMembersEndpoint.test.ts +4 -2
  79. package/src/endpoints/global/members/GetMembersEndpoint.ts +10 -8
  80. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +257 -6
  81. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +162 -107
  82. package/src/endpoints/global/members/helpers/validateGroupFilter.ts +4 -3
  83. package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +3 -2
  84. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +3 -2
  85. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +3 -2
  86. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +8 -7
  87. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +5 -3
  88. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +5 -3
  89. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +8 -5
  90. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +4 -2
  91. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +2 -1
  92. package/src/endpoints/global/platform/GetPlatformEndpoint.ts +3 -2
  93. package/src/endpoints/global/platform/PatchPlatformEnpoint.test.ts +3 -2
  94. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +7 -4
  95. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsCountEndpoint.ts +47 -0
  96. package/src/endpoints/global/platform-memberships/GetPlatformMembershipsEndpoint.ts +206 -0
  97. package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +3 -2
  98. package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +2 -1
  99. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +10 -7
  100. package/src/endpoints/global/registration/GetUserDetailedPayableBalanceEndpoint.ts +4 -2
  101. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +4 -2
  102. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +3 -2
  103. package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +2 -1
  104. package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -3
  105. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +13 -9
  106. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +50 -49
  107. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +25 -22
  108. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -5
  109. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +9 -7
  110. package/src/endpoints/global/sso/GetSSOEndpoint.ts +4 -2
  111. package/src/endpoints/global/sso/SetSSOEndpoint.ts +3 -2
  112. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +4 -2
  113. package/src/endpoints/global/webshops/GetWebshopsCountEndpoint.ts +43 -0
  114. package/src/endpoints/global/webshops/GetWebshopsEndpoint.test.ts +808 -0
  115. package/src/endpoints/global/webshops/GetWebshopsEndpoint.ts +221 -0
  116. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemEndpoint.ts +4 -3
  117. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsCountEndpoint.ts +3 -2
  118. package/src/endpoints/organization/dashboard/balance-items/GetBalanceItemsEndpoint.ts +6 -4
  119. package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +15 -13
  120. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +4 -2
  121. package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableBalanceEndpoint.ts +4 -2
  122. package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +2 -1
  123. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +3 -2
  124. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +3 -2
  125. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +7 -4
  126. package/src/endpoints/organization/dashboard/documents/GetDocumentsCountEndpoint.ts +3 -2
  127. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +7 -4
  128. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +9 -7
  129. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +4 -2
  130. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.ts +7 -5
  131. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +3 -2
  132. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +36 -1
  133. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +13 -4
  134. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +4 -2
  135. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +7 -5
  136. package/src/endpoints/organization/dashboard/invoices/GetInvoicesCountEndpoint.ts +3 -2
  137. package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +6 -4
  138. package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +5 -3
  139. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +4 -2
  140. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +6 -3
  141. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +4 -2
  142. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +2 -1
  143. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +2 -1
  144. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +4 -2
  145. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +3 -2
  146. package/src/endpoints/organization/dashboard/organization/GetUitpasClientIdEndpoint.ts +2 -1
  147. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +5 -3
  148. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +22 -19
  149. package/src/endpoints/organization/dashboard/organization/SearchUitpasOrganizersEndpoint.ts +5 -3
  150. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +11 -9
  151. package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +8 -7
  152. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +2 -1
  153. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +3 -2
  154. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +7 -5
  155. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +18 -16
  156. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +2 -1
  157. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesCountEndpoint.ts +3 -2
  158. package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +5 -3
  159. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +2 -1
  160. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +8 -5
  161. package/src/endpoints/organization/dashboard/registration-periods/MoveRegistrationPeriods.test.ts +4 -2
  162. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.test.ts +4 -2
  163. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +32 -29
  164. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +6 -3
  165. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +3 -2
  166. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +3 -2
  167. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +5 -3
  168. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +2 -1
  169. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +5 -3
  170. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +3 -2
  171. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +4 -3
  172. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +4 -3
  173. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +3 -2
  174. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +2 -1
  175. package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.ts +6 -4
  176. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +7 -6
  177. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +3 -2
  178. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +4 -2
  179. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +3 -2
  180. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +7 -4
  181. package/src/endpoints/organization/dashboard/webshops/{GetWebshopTicketsCountEndpoint → GetWebshopTicketsCountEndpoint.ts} +7 -7
  182. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +9 -6
  183. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +4 -2
  184. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +6 -4
  185. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +13 -12
  186. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +4 -2
  187. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +5 -3
  188. package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +6 -4
  189. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +2 -1
  190. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +6 -4
  191. package/src/endpoints/organization/shared/GetDocumentHtml.ts +4 -3
  192. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +4 -3
  193. package/src/endpoints/organization/shared/GetUitpasNumberDetailsEndpoint.ts +3 -2
  194. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +3 -2
  195. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +4 -3
  196. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +5 -4
  197. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +4 -3
  198. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +7 -5
  199. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +4 -3
  200. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +7 -3
  201. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +11 -10
  202. package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +5 -4
  203. package/src/endpoints/system/HealthEndpoint.test.ts +44 -0
  204. package/src/endpoints/system/HealthEndpoint.ts +14 -6
  205. package/src/excel-loaders/balance-items.ts +19 -17
  206. package/src/excel-loaders/event-notifications.ts +15 -13
  207. package/src/excel-loaders/index.ts +1 -0
  208. package/src/excel-loaders/members.ts +45 -43
  209. package/src/excel-loaders/organizations.ts +26 -25
  210. package/src/excel-loaders/payments.ts +44 -42
  211. package/src/excel-loaders/platform-memberships.ts +202 -0
  212. package/src/excel-loaders/receivable-balances.ts +25 -23
  213. package/src/excel-loaders/registrations.ts +30 -28
  214. package/src/helpers/AddressValidator.test.ts +2 -1
  215. package/src/helpers/AddressValidator.ts +13 -10
  216. package/src/helpers/AdminPermissionChecker.ts +193 -95
  217. package/src/helpers/AuthenticatedStructures.ts +13 -11
  218. package/src/helpers/BuckarooHelper.ts +3 -2
  219. package/src/helpers/Context.ts +8 -6
  220. package/src/helpers/CookieHelper.ts +2 -2
  221. package/src/helpers/FileCache.ts +9 -9
  222. package/src/helpers/ForwardHandler.ts +3 -2
  223. package/src/helpers/GlobalHelper.ts +2 -0
  224. package/src/helpers/GroupBuilder.ts +2 -1
  225. package/src/helpers/GroupedThrottledQueue.test.ts +19 -19
  226. package/src/helpers/LimitedFilteredRequestHelper.ts +1 -1
  227. package/src/helpers/MemberCharger.ts +2 -1
  228. package/src/helpers/MemberUserSyncer.ts +6 -3
  229. package/src/helpers/MembershipCharger.ts +2 -2
  230. package/src/helpers/OrganizationCharger.ts +2 -1
  231. package/src/helpers/PeriodHelper.ts +2 -1
  232. package/src/helpers/SQLTranslatedString.ts +3 -2
  233. package/src/helpers/ServiceFeeHelper.ts +1 -1
  234. package/src/helpers/SetupStepUpdater.ts +6 -5
  235. package/src/helpers/StripeHelper.ts +9 -8
  236. package/src/helpers/TagHelper.test.ts +5 -5
  237. package/src/helpers/TagHelper.ts +2 -1
  238. package/src/helpers/TemporaryMemberAccess.ts +2 -1
  239. package/src/helpers/ThrottledQueue.test.ts +20 -20
  240. package/src/helpers/UitpasTokenRepository.ts +7 -7
  241. package/src/helpers/ViesHelper.ts +5 -4
  242. package/src/helpers/XlsxTransformerColumnHelper.ts +21 -19
  243. package/src/helpers/email-html-helpers.ts +13 -12
  244. package/src/helpers/fetchToAsyncIterator.ts +1 -1
  245. package/src/helpers/outstandingBalanceJoin.ts +2 -1
  246. package/src/helpers/updateMemberDetailsUitpasNumber.ts +5 -4
  247. package/src/middleware/ContextMiddleware.ts +1 -1
  248. package/src/migrate.ts +21 -4
  249. package/src/seeds/0000000003-default-email-templates.ts +1 -1
  250. package/src/seeds/0000000004-single-organization.ts +2 -1
  251. package/src/seeds/1752848561-groups-registration-periods.ts +3 -2
  252. package/src/seeds/1754560914-groups-prices.test.ts +2 -1
  253. package/src/seeds/1754560914-groups-prices.ts +2 -1
  254. package/src/seeds/1755790070-fill-email-recipient-errors.ts +6 -6
  255. package/src/seeds/1755876819-remove-duplicate-members.ts +2 -1
  256. package/src/seeds/1765896674-document-update-year.test.ts +2 -1
  257. package/src/seeds/1773754928-force-save-members.ts +15 -0
  258. package/src/services/AuditLogService.ts +3 -2
  259. package/src/services/BalanceItemPaymentService.ts +2 -2
  260. package/src/services/BalanceItemService.ts +2 -1
  261. package/src/services/BootChecksService.test.ts +33 -0
  262. package/src/services/BootChecksService.ts +21 -0
  263. package/src/services/DatabaseCollationService.test.ts +18 -0
  264. package/src/services/DatabaseCollationService.ts +81 -0
  265. package/src/services/DocumentService.ts +1 -1
  266. package/src/services/EventNotificationService.ts +5 -4
  267. package/src/services/FileSignService.ts +2 -2
  268. package/src/services/InvoiceService.ts +3 -3
  269. package/src/services/MemberNumberService.ts +6 -4
  270. package/src/services/MemberRecordStore.ts +28 -19
  271. package/src/services/PaymentReallocationService.test.ts +2 -1
  272. package/src/services/PaymentReallocationService.ts +2 -1
  273. package/src/services/PaymentService.ts +28 -26
  274. package/src/services/RegistrationService.ts +65 -3
  275. package/src/services/SSOService.ts +13 -9
  276. package/src/services/STPackageService.ts +7 -5
  277. package/src/services/StartupHealthService.ts +15 -0
  278. package/src/services/uitpas/PassholderEndpoints.ts +2 -2
  279. package/src/services/uitpas/UitpasService.ts +11 -8
  280. package/src/services/uitpas/cancelTicketSales.ts +1 -1
  281. package/src/services/uitpas/checkPermissionsFor.ts +9 -9
  282. package/src/services/uitpas/getSocialTariffForEvent.ts +4 -4
  283. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +5 -5
  284. package/src/services/uitpas/handleUitpasResponse.ts +1 -1
  285. package/src/services/uitpas/registerTicketSales.ts +4 -4
  286. package/src/services/uitpas/searchUitpasEvents.ts +3 -3
  287. package/src/services/uitpas/searchUitpasOrganizers.ts +3 -3
  288. package/src/sql-filters/audit-logs.ts +2 -1
  289. package/src/sql-filters/balance-item-payments.ts +2 -1
  290. package/src/sql-filters/balance-items.ts +2 -1
  291. package/src/sql-filters/base-registration-filter-compilers.ts +6 -4
  292. package/src/sql-filters/document-templates.ts +2 -1
  293. package/src/sql-filters/documents.ts +2 -1
  294. package/src/sql-filters/email-recipients.ts +2 -1
  295. package/src/sql-filters/emails.ts +2 -1
  296. package/src/sql-filters/event-notifications.ts +2 -1
  297. package/src/sql-filters/events.ts +2 -1
  298. package/src/sql-filters/groups.ts +2 -1
  299. package/src/sql-filters/invoiced-balance-items.ts +2 -1
  300. package/src/sql-filters/invoices.ts +2 -1
  301. package/src/sql-filters/member-responsibility-records.ts +2 -1
  302. package/src/sql-filters/members.ts +8 -7
  303. package/src/sql-filters/orders.ts +3 -2
  304. package/src/sql-filters/organization-registration-periods.ts +2 -1
  305. package/src/sql-filters/organizations.ts +2 -1
  306. package/src/sql-filters/payments.ts +2 -1
  307. package/src/sql-filters/platform-memberships.ts +67 -0
  308. package/src/sql-filters/receivable-balances.ts +2 -1
  309. package/src/sql-filters/registration-periods.ts +2 -1
  310. package/src/sql-filters/registrations.ts +2 -1
  311. package/src/sql-filters/tickets.ts +2 -1
  312. package/src/sql-filters/users.ts +2 -1
  313. package/src/sql-filters/webshops.ts +38 -0
  314. package/src/sql-sorters/audit-logs.ts +3 -2
  315. package/src/sql-sorters/balance-items.ts +3 -2
  316. package/src/sql-sorters/document-templates.ts +3 -2
  317. package/src/sql-sorters/documents.ts +3 -2
  318. package/src/sql-sorters/email-recipients.ts +3 -2
  319. package/src/sql-sorters/emails.ts +3 -2
  320. package/src/sql-sorters/event-notifications.ts +3 -2
  321. package/src/sql-sorters/events.ts +3 -2
  322. package/src/sql-sorters/groups.ts +3 -2
  323. package/src/sql-sorters/invoices.ts +3 -2
  324. package/src/sql-sorters/members.ts +3 -2
  325. package/src/sql-sorters/orders.ts +3 -2
  326. package/src/sql-sorters/organization-registration-periods.ts +3 -2
  327. package/src/sql-sorters/organizations.ts +3 -2
  328. package/src/sql-sorters/payments.ts +3 -2
  329. package/src/sql-sorters/platform-memberships.ts +40 -0
  330. package/src/sql-sorters/receivable-balances.ts +3 -2
  331. package/src/sql-sorters/registration-periods.ts +3 -2
  332. package/src/sql-sorters/registrations.ts +3 -2
  333. package/src/sql-sorters/tickets.ts +3 -2
  334. package/src/sql-sorters/webshops.ts +40 -0
  335. package/tests/actions/patchOrganizationMember.ts +5 -4
  336. package/tests/actions/patchPaymentStatus.ts +2 -2
  337. package/tests/actions/patchUserMember.ts +6 -4
  338. package/tests/e2e/api-rate-limits.test.ts +4 -5
  339. package/tests/e2e/bundle-discounts.test.ts +3 -2
  340. package/tests/e2e/charge-members.test.ts +7 -5
  341. package/tests/e2e/documents.test.ts +3 -2
  342. package/tests/e2e/private-files.test.ts +11 -13
  343. package/tests/e2e/register.test.ts +6 -5
  344. package/tests/e2e/stock.test.ts +6 -8
  345. package/tests/e2e/tickets.test.ts +4 -2
  346. package/tests/helpers/StripeMocker.ts +3 -3
  347. package/tests/init/initAdmin.ts +4 -2
  348. package/tests/init/initBundleDiscount.ts +3 -2
  349. package/tests/init/initPayconiq.ts +1 -1
  350. package/tests/init/initPermissionRole.ts +4 -2
  351. package/tests/init/initPlatformRecordCategory.ts +1 -1
  352. package/tests/init/initStripe.ts +1 -1
  353. package/tests/vitest.global.setup.ts +26 -0
  354. package/tests/{jest.setup.ts → vitest.setup.ts} +4 -3
  355. package/tsconfig.build.json +17 -0
  356. package/tsconfig.json +10 -41
  357. package/tsconfig.test.json +17 -0
  358. package/vitest.config.js +13 -0
  359. package/eslint.config.mjs +0 -5
  360. package/jest.config.cjs +0 -27
  361. package/tests/jest.global.setup.ts +0 -33
  362. package/tests/toMatchMap.ts +0 -68
@@ -0,0 +1,18 @@
1
+ import { Database } from '@simonbackx/simple-database';
2
+ import { DatabaseCollationService } from './DatabaseCollationService.js';
3
+
4
+ describe('DatabaseCollationService', () => {
5
+ test('Detects collation_connection mismatch', async () => {
6
+ await Database.statement('SET collation_connection = \'utf8mb4_general_ci\'');
7
+
8
+ try {
9
+ const error = await DatabaseCollationService.getMismatchError();
10
+ expect(error).toContain('MySQL collation mismatch');
11
+ expect(error).toContain('expected utf8mb4_0900_ai_ci');
12
+ expect(error).toContain('collation_connection=utf8mb4_general_ci');
13
+ }
14
+ finally {
15
+ await Database.statement('SET collation_connection = DEFAULT');
16
+ }
17
+ });
18
+ });
@@ -0,0 +1,81 @@
1
+ import { Database } from '@simonbackx/simple-database';
2
+
3
+ const EXPECTED_COLLATION = 'utf8mb4_0900_ai_ci';
4
+
5
+ type CollationRow = {
6
+ collationConnection: string;
7
+ collationDatabase: string;
8
+ };
9
+
10
+ export class DatabaseCollationService {
11
+ static expectedCollation = EXPECTED_COLLATION;
12
+
13
+ static async getCurrentCollations() {
14
+ const connectionResult = await Database.statement("SHOW VARIABLES LIKE 'collation_connection'");
15
+ const databaseResult = await Database.statement("SHOW VARIABLES LIKE 'collation_database'");
16
+
17
+ const collationConnection = this.extractVariableValue(connectionResult);
18
+ const collationDatabase = this.extractVariableValue(databaseResult);
19
+
20
+ return {
21
+ collationConnection: typeof collationConnection === 'string' ? collationConnection : '',
22
+ collationDatabase: typeof collationDatabase === 'string' ? collationDatabase : '',
23
+ } as CollationRow;
24
+ }
25
+
26
+ private static extractVariableValue(result: unknown) {
27
+ if (result && typeof result === 'object' && 'rows' in result) {
28
+ return this.extractVariableValue((result as { rows: unknown }).rows);
29
+ }
30
+
31
+ if (Array.isArray(result) && result.length > 0 && Array.isArray(result[0])) {
32
+ return this.extractVariableValue(result[0]);
33
+ }
34
+
35
+ if (!Array.isArray(result) || result.length === 0) {
36
+ return '';
37
+ }
38
+
39
+ const firstRow = result[0] as Record<string, unknown> | undefined;
40
+
41
+ if (!firstRow) {
42
+ return '';
43
+ }
44
+
45
+ const value = firstRow.Value
46
+ ?? firstRow.value
47
+ ?? firstRow.VARIABLE_VALUE
48
+ ?? firstRow.variable_value;
49
+
50
+ if (typeof value === 'string') {
51
+ return value;
52
+ }
53
+
54
+ for (const candidate of Object.values(firstRow)) {
55
+ if (typeof candidate === 'string' && candidate.startsWith('utf8mb4_')) {
56
+ return candidate;
57
+ }
58
+ }
59
+
60
+ return '';
61
+ }
62
+
63
+ static async getMismatchError() {
64
+ const collations = await this.getCurrentCollations();
65
+ const mismatches: string[] = [];
66
+
67
+ if (collations.collationConnection !== this.expectedCollation) {
68
+ mismatches.push(`collation_connection=${collations.collationConnection}`);
69
+ }
70
+
71
+ if (collations.collationDatabase !== this.expectedCollation) {
72
+ mismatches.push(`collation_database=${collations.collationDatabase}`);
73
+ }
74
+
75
+ if (mismatches.length === 0) {
76
+ return null;
77
+ }
78
+
79
+ return `MySQL collation mismatch: expected ${this.expectedCollation}, got ${mismatches.join(', ')}`;
80
+ }
81
+ }
@@ -1,5 +1,5 @@
1
1
  import { Model } from '@simonbackx/simple-database';
2
- import { PlainObject } from '@simonbackx/simple-encoding';
2
+ import type { PlainObject } from '@simonbackx/simple-encoding';
3
3
  import { Document, Group } from '@stamhoofd/models';
4
4
 
5
5
  function getGroupFieldsAffectingDocuments(group: Group): PlainObject {
@@ -1,6 +1,7 @@
1
1
  import { SimpleError } from '@simonbackx/simple-errors';
2
2
  import { EventNotification, Member, MemberResponsibilityRecord, Organization, Platform, sendEmailTemplate, User } from '@stamhoofd/models';
3
- import { EmailTemplateType, PermissionLevel, Recipient, RecordCategory, Replacement } from '@stamhoofd/structures';
3
+ import type { EmailTemplateType} from '@stamhoofd/structures';
4
+ import { PermissionLevel, Recipient, RecordCategory, Replacement } from '@stamhoofd/structures';
4
5
  import { Formatter } from '@stamhoofd/utility';
5
6
  import { AdminPermissionChecker } from '../helpers/AdminPermissionChecker.js';
6
7
  import { Context } from '../helpers/Context.js';
@@ -15,7 +16,7 @@ export class EventNotificationService {
15
16
  throw new SimpleError({
16
17
  code: 'invalid_field',
17
18
  message: 'Invalid type',
18
- human: Context.i18n.$t('4d8be2b1-559a-4c16-a76f-67a8ba85de7f'),
19
+ human: Context.i18n.$t('%AT'),
19
20
  field: 'typeId',
20
21
  });
21
22
  }
@@ -132,7 +133,7 @@ export class EventNotificationService {
132
133
  }
133
134
  const events = EventNotification.events.isLoaded(notification) ? notification.events : await EventNotification.events.load(notification);
134
135
  const type = await this.validateType(notification);
135
- let submitterName = $t(`95c51d5c-0945-4fcf-90e9-764940e7f54d`);
136
+ let submitterName = $t(`%wq`);
136
137
 
137
138
  if (notification.submittedBy) {
138
139
  const user = await User.getByID(notification.submittedBy);
@@ -164,7 +165,7 @@ export class EventNotificationService {
164
165
  }),
165
166
  Replacement.create({
166
167
  token: 'feedbackText',
167
- html: notification.feedbackText ? `<p class="pre-wrap"><em>${Formatter.escapeHtml(notification.feedbackText)}</em></p>` : `<p class="pre-wrap"><em>${Formatter.escapeHtml($t('4c3149b3-e02a-4071-bf21-941711e0238d'))}</em></p>`,
168
+ html: notification.feedbackText ? `<p class="pre-wrap"><em>${Formatter.escapeHtml(notification.feedbackText)}</em></p>` : `<p class="pre-wrap"><em>${Formatter.escapeHtml($t('%Av'))}</em></p>`,
168
169
  }),
169
170
  ];
170
171
  }
@@ -2,7 +2,7 @@ import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; // ES Modules i
2
2
  import {
3
3
  getSignedUrl,
4
4
  } from '@aws-sdk/s3-request-presigner';
5
- import { DecodedRequest, Request, Response } from '@simonbackx/simple-endpoints';
5
+ import type { DecodedRequest, Request, Response } from '@simonbackx/simple-endpoints';
6
6
  import { SimpleError } from '@simonbackx/simple-errors';
7
7
  import { File } from '@stamhoofd/structures';
8
8
  import chalk from 'chalk';
@@ -191,7 +191,7 @@ export class FileSignService {
191
191
  throw new SimpleError({
192
192
  code: 'invalid_signature',
193
193
  message: 'Invalid signature for file',
194
- human: $t('479684ab-6c50-4ced-82d7-8245f4f475f4'),
194
+ human: $t('%B7'),
195
195
  });
196
196
  }
197
197
  return;
@@ -11,7 +11,7 @@ export class InvoiceService {
11
11
  code: 'missing_customer',
12
12
  message: 'Missing customer',
13
13
  field: 'customer',
14
- human: $t('cb28c759-7ab7-4a07-8972-9523393993ac'),
14
+ human: $t('%1Iw'),
15
15
  });
16
16
  }
17
17
 
@@ -38,7 +38,7 @@ export class InvoiceService {
38
38
  throw new SimpleError({
39
39
  code: 'missing_company',
40
40
  message: 'Missing invoice settings (companies)',
41
- human: $t('abd43002-de39-4d64-96c0-1c801c27e764', {
41
+ human: $t('%1Ix', {
42
42
  'organization-name': organization.name,
43
43
  }),
44
44
  });
@@ -137,7 +137,7 @@ export class InvoiceService {
137
137
  throw new SimpleError({
138
138
  code: 'invalid_field',
139
139
  message: 'payableRoundingAmount cannot be more than 10 cent',
140
- human: $t('69832bb4-18a2-428d-a84d-89675ae67016'),
140
+ human: $t('%1LG'),
141
141
  field: 'payableRoundingAmount',
142
142
  });
143
143
  }
@@ -1,7 +1,9 @@
1
1
  import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
2
- import { Member, MemberPlatformMembership, Organization } from '@stamhoofd/models';
2
+ import type { Member, MemberPlatformMembership} from '@stamhoofd/models';
3
+ import { Organization } from '@stamhoofd/models';
3
4
  import { QueueHandler } from '@stamhoofd/queues';
4
5
  import { scalarToSQLExpression, SQL, SQLCharLength, SQLWhereLike } from '@stamhoofd/sql';
6
+ import { MemberNumberAlgorithm } from '@stamhoofd/types/MemberNumberAlgorithm';
5
7
 
6
8
  export class MemberNumberService {
7
9
  static async assignMemberNumber(member: Member, membership: MemberPlatformMembership) {
@@ -36,7 +38,7 @@ export class MemberNumberService {
36
38
  throw new SimpleError({
37
39
  code: 'assign_member_number',
38
40
  message: error.message,
39
- human: $t(`3a2c3e9d-4ac8-44a1-9690-98e8e4623298`),
41
+ human: $t(`%Fw`),
40
42
  });
41
43
  }
42
44
  }
@@ -61,7 +63,7 @@ export class MemberNumberService {
61
63
  throw new SimpleError({
62
64
  code: 'assign_member_number',
63
65
  message: 'Missing birthDay',
64
- human: $t(`3e6429f3-1fc2-42ad-b585-4da7da164267`),
66
+ human: $t(`%Fx`),
65
67
  });
66
68
  }
67
69
 
@@ -123,7 +125,7 @@ export class MemberNumberService {
123
125
  throw new SimpleError({
124
126
  code: 'assign_member_number',
125
127
  message: `Duplicate member numbers (last try: ${memberNumber}, tries: ${tries})`,
126
- human: $t(`49742012-1ca8-4b91-a176-9ce3e17c1fe0`),
128
+ human: $t(`%Fy`),
127
129
  });
128
130
  }
129
131
  }
@@ -1,33 +1,31 @@
1
1
  import { Model } from '@simonbackx/simple-database';
2
2
  import { Organization, Platform } from '@stamhoofd/models';
3
3
  import { QueueHandler } from '@stamhoofd/queues';
4
- import { RecordSettings } from '@stamhoofd/structures';
4
+ import type { RecordSettings } from '@stamhoofd/structures';
5
5
 
6
6
  export type RecordCacheEntry = { record: RecordSettings; rootCategoryId: string; organizationId: string | null };
7
7
 
8
8
  /**
9
9
  * Service that caches all available member records in the system.
10
- * - It verified whether ids are unique system wide
10
+ * - It verifies whether ids are unique system wide
11
11
  * - It allows fast retrieving of record settings by id (for permission checking)
12
12
  */
13
- export class MemberRecordStore {
14
- private static cache = new Map<string, RecordCacheEntry>();
13
+ class MemberRecordStoreService {
14
+ private cache = new Map<string, RecordCacheEntry>();
15
15
 
16
- constructor() {
17
-
18
- }
16
+ init() {
17
+ // Should only be used in userMode platform
18
+ if (STAMHOOFD.userMode !== 'platform') {
19
+ return;
20
+ }
19
21
 
20
- static init() {
21
22
  // Load initial data
22
23
  this.loadIfNeeded().catch(console.error);
23
-
24
- // Create listeners to update data as organizations and platform is updated
25
- this.listen();
26
24
  }
27
25
 
28
- private static listening = false;
26
+ private listening = false;
29
27
 
30
- private static listen() {
28
+ private listen() {
31
29
  // Create listeners to update data as organizations and platform is updated
32
30
  if (this.listening) {
33
31
  return;
@@ -66,7 +64,7 @@ export class MemberRecordStore {
66
64
  }
67
65
 
68
66
  if (event.model instanceof Organization) {
69
- // Delete all records with organizationId = null
67
+ // Delete all records where organizationId equals the organization
70
68
  for (const [id, entry] of this.cache) {
71
69
  if (entry.organizationId === event.model.id) {
72
70
  this.cache.delete(id);
@@ -88,10 +86,10 @@ export class MemberRecordStore {
88
86
  });
89
87
  }
90
88
 
91
- static _loadAllPromise: Promise<void> | null = null;
92
- static _didLoadAll = false;
89
+ private _loadAllPromise: Promise<void> | null = null;
90
+ private _didLoadAll = false;
93
91
 
94
- static async loadIfNeeded() {
92
+ private async loadIfNeeded() {
95
93
  if (this._didLoadAll) {
96
94
  return;
97
95
  }
@@ -100,6 +98,11 @@ export class MemberRecordStore {
100
98
  await this._loadAllPromise;
101
99
  }
102
100
  else {
101
+ if (STAMHOOFD.userMode !== 'platform') {
102
+ // Important to throw an error because in tests the userMode can change in between tests
103
+ throw new Error('Should only be loaded in userMode platform.');
104
+ }
105
+
103
106
  this._loadAllPromise = (async () => {
104
107
  try {
105
108
  await this.loadAll();
@@ -116,7 +119,7 @@ export class MemberRecordStore {
116
119
  }
117
120
  }
118
121
 
119
- static async loadAll() {
122
+ private async loadAll() {
120
123
  this.cache = new Map<string, RecordCacheEntry>();
121
124
 
122
125
  // We use a queue here so we can abort this long running task properly
@@ -155,10 +158,16 @@ export class MemberRecordStore {
155
158
  abort.throwIfAborted();
156
159
  }
157
160
  });
161
+
162
+ // Create listeners to update data as organizations and platform is updated.
163
+ // Should be checked on every loadAll because the userMode can change for tests.
164
+ this.listen();
158
165
  }
159
166
 
160
- static async getRecord(id: string): Promise<RecordCacheEntry | null> {
167
+ async getRecord(id: string): Promise<RecordCacheEntry | null> {
161
168
  await this.loadIfNeeded();
162
169
  return this.cache.get(id) ?? null;
163
170
  }
164
171
  }
172
+
173
+ export const MemberRecordStore = new MemberRecordStoreService();
@@ -1,4 +1,5 @@
1
- import { BalanceItem, BalanceItemPayment, MemberFactory, Organization, OrganizationFactory, Payment } from '@stamhoofd/models';
1
+ import type { Organization} from '@stamhoofd/models';
2
+ import { BalanceItem, BalanceItemPayment, MemberFactory, OrganizationFactory, Payment } from '@stamhoofd/models';
2
3
  import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, PaymentMethod, PaymentStatus, ReceivableBalanceType, TranslatedString } from '@stamhoofd/structures';
3
4
  import { PaymentReallocationService } from './PaymentReallocationService.js';
4
5
  import { BalanceItemService } from './BalanceItemService.js';
@@ -1,6 +1,7 @@
1
1
  import { BalanceItem, BalanceItemPayment, CachedBalance, Payment } from '@stamhoofd/models';
2
2
  import { SQL } from '@stamhoofd/sql';
3
- import { BalanceItemStatus, doBalanceItemRelationsMatch, PaymentMethod, PaymentStatus, PaymentType, ReceivableBalanceType } from '@stamhoofd/structures';
3
+ import type { ReceivableBalanceType } from '@stamhoofd/structures';
4
+ import { BalanceItemStatus, doBalanceItemRelationsMatch, PaymentMethod, PaymentStatus, PaymentType } from '@stamhoofd/structures';
4
5
  import { Sorter } from '@stamhoofd/utility';
5
6
  import { BalanceItemService } from './BalanceItemService.js';
6
7
 
@@ -1,8 +1,10 @@
1
- import createMollieClient, { PaymentMethod as molliePaymentMethod, PaymentStatus as MolliePaymentStatus } from '@mollie/api-client';
1
+ import { createMollieClient, PaymentMethod as molliePaymentMethod, PaymentStatus as MolliePaymentStatus } from '@mollie/api-client';
2
2
  import { SimpleError } from '@simonbackx/simple-errors';
3
- import { BalanceItem, BalanceItemPayment, Group, Member, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, sendEmailTemplate, User } from '@stamhoofd/models';
3
+ import type { BalanceItem, Member, User } from '@stamhoofd/models';
4
+ import { BalanceItemPayment, Group, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, sendEmailTemplate } from '@stamhoofd/models';
4
5
  import { QueueHandler } from '@stamhoofd/queues';
5
- import { AuditLogSource, BalanceItemType, Checkoutable, Country, EmailTemplateType, PaymentConfiguration, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, PaymentType, Recipient, VATExcemptReason, Version } from '@stamhoofd/structures';
6
+ import type { Checkoutable, PaymentConfiguration } from '@stamhoofd/structures';
7
+ import { AuditLogSource, BalanceItemType, EmailTemplateType, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, PaymentType, Recipient, VATExcemptReason, Version } from '@stamhoofd/structures';
6
8
  import { Formatter } from '@stamhoofd/utility';
7
9
  import { buildReplacementOptions, getEmailReplacementsForPayment } from '../email-replacements/getEmailReplacementsForPayment.js';
8
10
  import { BuckarooHelper } from '../helpers/BuckarooHelper.js';
@@ -403,14 +405,14 @@ export class PaymentService {
403
405
  if (price > 0 && price > Math.max(balanceItem.priceOpen, balanceItem.priceDue - balanceItem.pricePaid)) {
404
406
  throw new SimpleError({
405
407
  code: 'invalid_data',
406
- message: $t(`38ddccb2-7cf6-4b47-aa71-d11ad73386d8`),
408
+ message: $t(`%vv`),
407
409
  });
408
410
  }
409
411
 
410
412
  if (price < 0 && price < Math.min(balanceItem.priceOpen, balanceItem.priceDue - balanceItem.pricePaid)) {
411
413
  throw new SimpleError({
412
414
  code: 'invalid_data',
413
- message: $t(`dd14a1d9-c569-4d5e-bb26-569ecede4c52`),
415
+ message: $t(`%vw`),
414
416
  });
415
417
  }
416
418
 
@@ -425,7 +427,7 @@ export class PaymentService {
425
427
  if (!member) {
426
428
  throw new SimpleError({
427
429
  code: 'invalid_data',
428
- message: $t(`e64b8269-1cda-434d-8d6f-35be23a9d6e9`),
430
+ message: $t(`%vx`),
429
431
  });
430
432
  }
431
433
  names.push({
@@ -440,7 +442,7 @@ export class PaymentService {
440
442
  // todo: try to make it non-negative by reducing some balance items
441
443
  throw new SimpleError({
442
444
  code: 'negative_price',
443
- message: $t(`725715e5-b0ac-43c1-adef-dd42b8907327`),
445
+ message: $t(`%vl`),
444
446
  });
445
447
  }
446
448
 
@@ -448,7 +450,7 @@ export class PaymentService {
448
450
  // Changed!
449
451
  throw new SimpleError({
450
452
  code: 'changed_price',
451
- message: $t(`e424d549-2bb8-4103-9a14-ac4063d7d454`, { total: Formatter.price(totalPrice) }),
453
+ message: $t(`%vk`, { total: Formatter.price(totalPrice) }),
452
454
  });
453
455
  }
454
456
 
@@ -477,7 +479,7 @@ export class PaymentService {
477
479
  throw new SimpleError({
478
480
  code: 'missing_fields',
479
481
  message: 'customer is required when paying as an organization',
480
- human: $t(`d483aa9a-289c-4c59-955f-d2f99ec533ab`),
482
+ human: $t(`%vz`),
481
483
  });
482
484
  }
483
485
 
@@ -485,7 +487,7 @@ export class PaymentService {
485
487
  throw new SimpleError({
486
488
  code: 'missing_fields',
487
489
  message: 'customer.company is required when paying as an organization',
488
- human: $t(`bc89861d-a799-4100-b06c-29d6808ba8d2`),
490
+ human: $t(`%w0`),
489
491
  });
490
492
  }
491
493
 
@@ -497,7 +499,7 @@ export class PaymentService {
497
499
  if (!foundCompany) {
498
500
  throw new SimpleError({
499
501
  code: 'invalid_data',
500
- message: $t(`0ab71307-8f4f-4701-b120-b552a1b6bdd0`),
502
+ message: $t(`%w1`),
501
503
  });
502
504
  }
503
505
 
@@ -545,7 +547,7 @@ export class PaymentService {
545
547
  throw new SimpleError({
546
548
  code: 'missing_fields',
547
549
  message: 'redirectUrl or cancelUrl is missing and is required for non-zero online payments',
548
- human: $t(`ebe54b63-2de6-4f22-a5ed-d3fe65194562`),
550
+ human: $t(`%vq`),
549
551
  });
550
552
  }
551
553
 
@@ -558,7 +560,7 @@ export class PaymentService {
558
560
  throw new SimpleError({
559
561
  code: 'no_iban',
560
562
  message: 'No IBAN',
561
- human: $t(`cc8b5066-a7e4-4eae-b556-f56de5d3502c`),
563
+ human: $t(`%w2`),
562
564
  });
563
565
  }
564
566
 
@@ -649,7 +651,7 @@ export class PaymentService {
649
651
  lineItems: balanceItemPayments,
650
652
  organization,
651
653
  customer: {
652
- name: user.name ?? names[0]?.name ?? $t(`bd1e59c8-3d4c-4097-ab35-0ce7b20d0e50`),
654
+ name: user.name ?? names[0]?.name ?? $t(`%Gr`),
653
655
  email: user.email,
654
656
  },
655
657
  });
@@ -661,14 +663,14 @@ export class PaymentService {
661
663
  if (!token) {
662
664
  throw new SimpleError({
663
665
  code: '',
664
- message: $t(`b77e1f68-8928-42a2-802b-059fa73bedc3`, { method: PaymentMethodHelper.getName(payment.method) }),
666
+ message: $t(`%w3`, { method: PaymentMethodHelper.getName(payment.method) }),
665
667
  });
666
668
  }
667
669
  const profileId = organization.privateMeta.mollieProfile?.id ?? await token.getProfileId(organization.getHost());
668
670
  if (!profileId) {
669
671
  throw new SimpleError({
670
672
  code: '',
671
- message: $t(`5574469f-8eee-47fe-9fb6-1b097142ac75`, { method: PaymentMethodHelper.getName(payment.method) }),
673
+ message: $t(`%w4`, { method: PaymentMethodHelper.getName(payment.method) }),
672
674
  });
673
675
  }
674
676
  const mollieClient = createMollieClient({ accessToken: await token.getAccessToken() });
@@ -712,7 +714,7 @@ export class PaymentService {
712
714
  if ((payment.status as any) === PaymentStatus.Failed) {
713
715
  throw new SimpleError({
714
716
  code: 'payment_failed',
715
- message: $t(`b77e1f68-8928-42a2-802b-059fa73bedc3`, { method: PaymentMethodHelper.getName(payment.method) }),
717
+ message: $t(`%w3`, { method: PaymentMethodHelper.getName(payment.method) }),
716
718
  });
717
719
  }
718
720
  }
@@ -799,7 +801,7 @@ export class PaymentService {
799
801
  else if (payment.method === PaymentMethod.Unknown) {
800
802
  throw new SimpleError({
801
803
  code: 'invalid_data',
802
- message: $t(`86c7b6f7-3ec9-4af3-a5e6-b5de6de80d73`),
804
+ message: $t(`%vy`),
803
805
  });
804
806
  }
805
807
  else {
@@ -812,7 +814,7 @@ export class PaymentService {
812
814
  if (!allowedPaymentMethods.includes(payment.method)) {
813
815
  throw new SimpleError({
814
816
  code: 'invalid_payment_method',
815
- message: $t(`2b1ca6a0-662e-4326-ada1-10239b6ddc6f`),
817
+ message: $t(`%vp`),
816
818
  });
817
819
  }
818
820
  }
@@ -827,7 +829,7 @@ export class PaymentService {
827
829
  throw new SimpleError({
828
830
  code: 'missing_field',
829
831
  message: 'Company address missing',
830
- human: $t('a7fbbe18-7c46-45df-b041-3c47ddd0794d'),
832
+ human: $t('%1LH'),
831
833
  field: 'customer.company.address',
832
834
  });
833
835
  }
@@ -840,7 +842,7 @@ export class PaymentService {
840
842
  throw new SimpleError({
841
843
  code: 'VAT_error',
842
844
  message: 'Intra community VAT reverse charge not supported for this purchase',
843
- human: $t('abba9d96-6089-4c49-b895-5c01cadd305a'),
845
+ human: $t('%1LI'),
844
846
  });
845
847
  }
846
848
 
@@ -850,7 +852,7 @@ export class PaymentService {
850
852
  throw new SimpleError({
851
853
  code: 'VAT_error',
852
854
  message: 'Intra community VAT reverse charge is not supported for this purchase because of missing VAT rates',
853
- human: $t('37947cd9-4661-4332-ada9-8ffde5db811d'),
855
+ human: $t('%1LJ'),
854
856
  });
855
857
  }
856
858
  }
@@ -862,7 +864,7 @@ export class PaymentService {
862
864
  throw new SimpleError({
863
865
  code: 'VAT_error',
864
866
  message: 'Unexpected reverse charge applied',
865
- human: $t('57ac8775-7a32-4fdc-a84b-628a27f8d43d'),
867
+ human: $t('%1LK'),
866
868
  });
867
869
  }
868
870
 
@@ -870,7 +872,7 @@ export class PaymentService {
870
872
  throw new SimpleError({
871
873
  code: 'VAT_error',
872
874
  message: 'Missing VAT percentage',
873
- human: $t('495255ae-3ec5-42ec-9887-f3fc4f016d96'),
875
+ human: $t('%1LL'),
874
876
  });
875
877
  }
876
878
  }
@@ -885,7 +887,7 @@ export class PaymentService {
885
887
  throw new SimpleError({
886
888
  code: 'VAT_error',
887
889
  message: 'Unexpected reverse charge applied',
888
- human: $t('57ac8775-7a32-4fdc-a84b-628a27f8d43d'),
890
+ human: $t('%1LK'),
889
891
  });
890
892
  }
891
893
 
@@ -894,7 +896,7 @@ export class PaymentService {
894
896
  throw new SimpleError({
895
897
  code: 'VAT_error',
896
898
  message: 'Missing VAT percentage',
897
- human: $t('495255ae-3ec5-42ec-9887-f3fc4f016d96'),
899
+ human: $t('%1LL'),
898
900
  });
899
901
  }
900
902
  }
@@ -1,6 +1,6 @@
1
1
  import { ManyToOneRelation } from '@simonbackx/simple-database';
2
- import { BalanceItem, Document, Group, Member, Registration } from '@stamhoofd/models';
3
- import { AppliedRegistrationDiscount, AuditLogSource, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, EmailTemplateType, StockReservation, TranslatedString, Version } from '@stamhoofd/structures';
2
+ import { BalanceItem, Document, Group, Member, Organization, Registration, sendEmailTemplate } from '@stamhoofd/models';
3
+ import { AppliedRegistrationDiscount, AuditLogSource, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, EmailTemplateType, Recipient, Replacement, StockReservation, TranslatedString, Version } from '@stamhoofd/structures';
4
4
  import { AuditLogService } from './AuditLogService.js';
5
5
  import { GroupService } from './GroupService.js';
6
6
  import { PlatformMembershipService } from './PlatformMembershipService.js';
@@ -58,7 +58,7 @@ export const RegistrationService = {
58
58
  await PlatformMembershipService.updateMembershipsForId(registration.memberId);
59
59
 
60
60
  if (registration.sendConfirmationEmail) {
61
- await registration.sendEmailTemplate({
61
+ await RegistrationService.sendEmailTemplate(registration, {
62
62
  type: EmailTemplateType.RegistrationConfirmation,
63
63
  });
64
64
  }
@@ -76,6 +76,68 @@ export const RegistrationService = {
76
76
  return true;
77
77
  },
78
78
 
79
+ async getRecipients(registration: Registration, organization: Organization, group: Group) {
80
+ const member = await Member.getByIdWithUsers(registration.memberId);
81
+
82
+ if (!member) {
83
+ return [];
84
+ }
85
+
86
+ const allowedEmails = member.details.getNotificationEmails();
87
+
88
+ return member.users.map(user => Recipient.create({
89
+ firstName: user.firstName,
90
+ lastName: user.lastName,
91
+ email: user.email,
92
+ userId: user.id,
93
+ replacements: [
94
+ Replacement.create({
95
+ token: 'firstNameMember',
96
+ value: member.details.firstName,
97
+ }),
98
+ Replacement.create({
99
+ token: 'lastNameMember',
100
+ value: member.details.lastName,
101
+ }),
102
+ Replacement.create({
103
+ token: 'registerUrl',
104
+ value: 'https://' + organization.getHost(),
105
+ }),
106
+ Replacement.create({
107
+ token: 'groupName',
108
+ value: group.settings.name.toString(),
109
+ }),
110
+ ],
111
+ })).filter(r => allowedEmails.includes(r.email.toLocaleLowerCase()));
112
+ },
113
+
114
+ async sendEmailTemplate(registration: Registration, data: {
115
+ type: EmailTemplateType;
116
+ }) {
117
+ const group = await Group.getByID(registration.groupId);
118
+
119
+ if (!group) {
120
+ return;
121
+ }
122
+
123
+ const organization = await Organization.getByID(group.organizationId);
124
+ if (!organization) {
125
+ return;
126
+ }
127
+
128
+ const recipients = await RegistrationService.getRecipients(registration, organization, group);
129
+
130
+ // Create e-mail builder
131
+ await sendEmailTemplate(organization, {
132
+ template: {
133
+ type: data.type,
134
+ group,
135
+ },
136
+ recipients,
137
+ type: 'transactional',
138
+ });
139
+ },
140
+
79
141
  async updateDiscounts(registrationId: string) {
80
142
  await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
81
143
  await QueueHandler.schedule('registration-discounts-update-' + registrationId, async function (this: undefined) {