@stamhoofd/backend 2.120.6 → 2.122.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/output-2.txt +1 -0
  2. package/output.txt +1 -0
  3. package/package.json +70 -53
  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 +46 -0
  8. package/src/audit-logs/init.ts +9 -1
  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 +7 -0
  19. package/src/crons/invoices.ts +160 -0
  20. package/src/crons/members-fees.ts +50 -0
  21. package/src/crons/mollie-chargebacks.ts +86 -0
  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 +54 -74
  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 +87 -46
  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 +51 -0
  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 +32 -13
  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 +38 -33
  122. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +45 -0
  123. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +494 -0
  124. package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +213 -0
  125. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +404 -0
  126. package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +167 -0
  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 +19 -6
  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/{global → organization/dashboard}/billing/DeactivatePackageEndpoint.ts +4 -9
  142. package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +62 -0
  143. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +56 -0
  144. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +41 -18
  145. package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +64 -0
  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 +12 -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 +311 -0
  152. package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +94 -0
  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 +15 -4
  168. package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +11 -2
  169. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +8 -8
  170. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +10 -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 +67 -20
  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 +24 -17
  185. package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +134 -0
  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 +32 -22
  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 +29 -8
  215. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +21 -28
  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 +15 -13
  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 +28 -50
  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 +60 -65
  246. package/src/helpers/AuthenticatedStructures.ts +153 -51
  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 +21 -0
  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 +741 -0
  268. package/src/helpers/RecordAnswerHelper.ts +116 -0
  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 +15 -38
  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 +11 -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/migrations/1750090030-records-configuration.ts +92 -0
  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 +95 -27
  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/1779121239-default-invoice-email-template.sql +3 -0
  300. package/src/migrations/1779121240-invoice-email-template.sql +6 -0
  301. package/src/migrations/1779986893-default-payment-failed-email-templates.sql +4 -0
  302. package/src/migrations/1780578166-convert-legacy-billing.ts +13 -0
  303. package/src/{seeds/1755876819-remove-duplicate-members.ts → migrations/1780665426-remove-duplicate-members.ts} +23 -4
  304. package/src/migrations/1780933598-document-groups.ts +88 -0
  305. package/src/migrations/1781692342-balance-item-relations.ts +13 -0
  306. package/src/migrations/1781857866-platform-admins.ts +103 -0
  307. package/src/migrations/1781857867-rm-old-platform-admins.ts +25 -0
  308. package/src/seeds/0000000001-throw.ts +5 -0
  309. package/src/seeds/1760702452-update-balance-price-paid.ts +19 -0
  310. package/src/seeds/1760702453-update-balance-invoiced.ts +19 -0
  311. package/src/seeds/1760702454-update-cached-outstanding-balance-from-items.ts +51 -14
  312. package/src/seeds/1773754928-force-save-members.ts +41 -8
  313. package/src/seeds/1780665427-schedule-stock-updates.ts +45 -0
  314. package/src/seeds/1780665428-group-update-occupancy.ts +31 -0
  315. package/src/seeds/1780915001-fill-payment-customer.ts +70 -0
  316. package/src/seeds/1780915002-update-orders.ts +39 -0
  317. package/src/seeds/1782047708-organization-company-address.ts +44 -0
  318. package/src/seeds-temporary/1769088653-uitpas-status.ts +1 -2
  319. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.test.ts +69 -0
  320. package/src/seeds-temporary/1780416000-migrate-audit-log-legacy-enums.ts +85 -0
  321. package/src/services/AuditLogService.test.ts +51 -0
  322. package/src/services/AuditLogService.ts +45 -0
  323. package/src/services/BalanceItemService.ts +148 -55
  324. package/src/services/CpuService.ts +3 -6
  325. package/src/services/DatabaseCollationService.test.ts +1 -2
  326. package/src/services/DocumentService.ts +1 -2
  327. package/src/services/EventNotificationService.ts +1 -1
  328. package/src/services/FileSignService.ts +2 -4
  329. package/src/services/InvoicePdfService.ts +286 -0
  330. package/src/services/InvoiceService.ts +330 -77
  331. package/src/services/InvoiceXMLService.ts +424 -0
  332. package/src/services/MemberNumberService.ts +6 -11
  333. package/src/services/MemberRecordStore.ts +8 -10
  334. package/src/services/MollieService.ts +530 -0
  335. package/src/services/PaymentMandateService.ts +214 -0
  336. package/src/services/PaymentReallocationService.test.ts +1 -1
  337. package/src/services/PaymentReallocationService.ts +7 -9
  338. package/src/services/PaymentService.ts +820 -328
  339. package/src/services/PlatformMembershipService.ts +5 -9
  340. package/src/services/ReferralService.ts +223 -0
  341. package/src/services/RegistrationService.ts +125 -54
  342. package/src/services/SSOService.test.ts +119 -0
  343. package/src/services/SSOService.ts +56 -49
  344. package/src/services/STPackageService.ts +97 -68
  345. package/src/services/UniqueMemberNumberService.ts +69 -0
  346. package/src/services/UniqueUserService.ts +3 -6
  347. package/src/services/VATService.ts +85 -0
  348. package/src/services/data/invoice.hbs.html +686 -0
  349. package/src/services/uitpas/PassholderEndpoints.ts +3 -6
  350. package/src/services/uitpas/UitpasService.ts +6 -10
  351. package/src/services/uitpas/checkUitpasNumbers.ts +2 -4
  352. package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +3 -6
  353. package/src/services/uitpas/searchUitpasEvents.ts +1 -2
  354. package/src/sql-filters/audit-logs.ts +1 -1
  355. package/src/sql-filters/balance-item-payments.ts +1 -1
  356. package/src/sql-filters/balance-items.ts +1 -1
  357. package/src/sql-filters/base-registration-filter-compilers.ts +5 -33
  358. package/src/sql-filters/document-templates.ts +1 -1
  359. package/src/sql-filters/documents.ts +1 -1
  360. package/src/sql-filters/email-recipients.ts +1 -1
  361. package/src/sql-filters/emails.ts +1 -1
  362. package/src/sql-filters/event-notifications.ts +1 -1
  363. package/src/sql-filters/events.ts +6 -1
  364. package/src/sql-filters/groups.ts +16 -1
  365. package/src/sql-filters/invoiced-balance-items.ts +1 -1
  366. package/src/sql-filters/invoices.ts +1 -1
  367. package/src/sql-filters/member-responsibility-records.ts +1 -1
  368. package/src/sql-filters/members.ts +1 -1
  369. package/src/sql-filters/orders.ts +4 -4
  370. package/src/sql-filters/organization-registration-periods.ts +1 -1
  371. package/src/sql-filters/organizations.ts +1 -1
  372. package/src/sql-filters/payments.ts +5 -0
  373. package/src/sql-filters/platform-memberships.ts +3 -3
  374. package/src/sql-filters/receivable-balances.ts +1 -1
  375. package/src/sql-filters/registration-invitations.ts +90 -0
  376. package/src/sql-filters/registration-periods.ts +1 -1
  377. package/src/sql-filters/registrations.ts +1 -1
  378. package/src/sql-filters/tickets.ts +1 -1
  379. package/src/sql-filters/users.ts +1 -1
  380. package/src/sql-sorters/groups.ts +23 -0
  381. package/src/sql-sorters/platform-memberships.ts +16 -16
  382. package/src/sql-sorters/registration-invitations.ts +36 -0
  383. package/tests/actions/patchOrganizationMember.ts +1 -1
  384. package/tests/actions/patchUserMember.ts +1 -1
  385. package/tests/assertions/assertBalances.ts +6 -12
  386. package/tests/e2e/api-rate-limits.test.ts +12 -24
  387. package/tests/e2e/bundle-discounts.test.ts +4 -1
  388. package/tests/e2e/charge-members.test.ts +13 -11
  389. package/tests/e2e/documents.test.ts +4 -1
  390. package/tests/e2e/register.test.ts +7 -9
  391. package/tests/e2e/stock.test.ts +4 -3
  392. package/tests/e2e/tickets.test.ts +1 -1
  393. package/tests/helpers/MollieMocker.ts +462 -0
  394. package/tests/helpers/PayconiqMocker.ts +138 -45
  395. package/tests/helpers/StripeMocker.ts +9 -6
  396. package/tests/helpers/index.ts +4 -0
  397. package/tests/init/initAdmin.ts +2 -2
  398. package/tests/init/initBundleDiscount.ts +1 -1
  399. package/tests/init/initMembershipOrganization.ts +13 -0
  400. package/tests/init/initPermissionRole.ts +3 -4
  401. package/tests/vitest.global.setup.ts +1 -1
  402. package/vitest.config.js +7 -0
  403. package/.nvmrc +0 -1
  404. package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +0 -216
  405. package/src/helpers/GroupBuilder.ts +0 -418
  406. package/src/seeds/0000000000-example.ts +0 -11
  407. package/src/seeds/1722256498-group-update-occupancy.ts +0 -52
  408. package/src/seeds/1726572303-schedule-stock-updates.ts +0 -52
  409. package/src/seeds/1726847064-setup-steps.ts +0 -16
  410. package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +0 -31
  411. package/src/seeds/1729253172-update-orders.ts +0 -21
  412. package/src/seeds/1733319079-fill-paying-organization-ids.ts +0 -68
  413. package/src/seeds/1733748412-update-deleted-order-numbers.ts +0 -109
  414. package/src/seeds/1733994455-balance-item-status-open.ts +0 -30
  415. package/src/seeds/1740046783-update-membership.ts +0 -17
  416. package/src/seeds/1741008870-fix-auditlog-description.ts +0 -50
  417. package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +0 -88
  418. package/src/seeds/1741268179-fix-group-dates-of-events.ts +0 -32
  419. package/src/seeds/1750090030-records-configuration.ts +0 -26
  420. package/src/seeds/1751445358-upload-email-attachments.ts +0 -106
  421. package/src/seeds/1755532883-update-email-sender-ids.ts +0 -47
  422. package/src/seeds/1755790070-fill-email-recipient-errors.ts +0 -96
  423. package/src/seeds/1756115432-remove-old-drafts.ts +0 -16
  424. package/src/seeds/1756115433-fill-email-recipient-organization-id.ts +0 -30
  425. package/src/seeds/1756303697-update-email-counts.ts +0 -76
  426. package/src/seeds/1761665607-sync-member-users.ts +0 -59
  427. package/src/seeds/1765896674-document-update-year.ts +0 -66
  428. /package/src/{seeds → migrations}/0000000001-development-user.ts +0 -0
  429. /package/src/{seeds → migrations}/0000000002-clear-stamhoofd-email-templates.ts +0 -0
  430. /package/src/{seeds → migrations}/0000000003-default-email-templates.ts +0 -0
  431. /package/src/{seeds → migrations}/1752848561-groups-registration-periods.test.ts +0 -0
  432. /package/src/{seeds → migrations}/1766150402-document-published-at.test.ts +0 -0
  433. /package/src/{seeds → migrations}/1766150402-document-published-at.ts +0 -0
  434. /package/src/{seeds → migrations}/data/default-email-templates.sql +0 -0
@@ -0,0 +1,69 @@
1
+ import { AuditLog } from '@stamhoofd/models';
2
+ import { AuditLogPatchItem, AuditLogReplacement, AuditLogReplacementType, EventNotificationStatus, getLegacyAuditLogEnumRegistrations, getLegacyAuditLogEnumReplacement } from '@stamhoofd/structures';
3
+
4
+ import { migrateAuditLogLegacyEnums } from './1780416000-migrate-audit-log-legacy-enums.js';
5
+
6
+ describe('Seed.migrate-audit-log-legacy-enums', () => {
7
+ test('It converts all legacy enum audit log values without changing their rendered names', () => {
8
+ let migratedValues = 0;
9
+
10
+ for (const registration of getLegacyAuditLogEnumRegistrations()) {
11
+ for (const value of Object.values(registration.enumObject)) {
12
+ if (typeof value !== 'string') {
13
+ continue;
14
+ }
15
+
16
+ if (!getLegacyAuditLogEnumReplacement(value)) {
17
+ continue;
18
+ }
19
+
20
+ const legacyReplacement = AuditLogReplacement.key(value);
21
+ const legacyName = legacyReplacement.toString();
22
+ const log = new AuditLog();
23
+ log.replacements = new Map([
24
+ ['value', legacyReplacement.clone()],
25
+ ]);
26
+ log.patchList = [
27
+ AuditLogPatchItem.create({
28
+ key: legacyReplacement.clone(),
29
+ oldValue: legacyReplacement.clone(),
30
+ value: legacyReplacement.clone(),
31
+ }),
32
+ ];
33
+
34
+ expect(migrateAuditLogLegacyEnums(log)).toBe(true);
35
+
36
+ const replacement = log.replacements.get('value');
37
+ expect(replacement?.type).toBe(AuditLogReplacementType.Enum);
38
+ expect(replacement?.toString()).toBe(legacyName);
39
+
40
+ expect(log.patchList[0].key.type).toBe(AuditLogReplacementType.Enum);
41
+ expect(log.patchList[0].key.toString()).toBe(legacyName);
42
+ expect(log.patchList[0].oldValue?.type).toBe(AuditLogReplacementType.Enum);
43
+ expect(log.patchList[0].oldValue?.toString()).toBe(legacyName);
44
+ expect(log.patchList[0].value?.type).toBe(AuditLogReplacementType.Enum);
45
+ expect(log.patchList[0].value?.toString()).toBe(legacyName);
46
+ migratedValues += 1;
47
+ }
48
+ }
49
+
50
+ expect(migratedValues).toBeGreaterThan(0);
51
+ });
52
+
53
+ test('It does not migrate event notification statuses through the legacy mapping', () => {
54
+ expect(getLegacyAuditLogEnumRegistrations().some(({ type }) => type === 'EventNotificationStatus')).toBe(false);
55
+
56
+ const legacyReplacement = AuditLogReplacement.key(EventNotificationStatus.Accepted);
57
+ const log = new AuditLog();
58
+ log.replacements = new Map([
59
+ ['value', legacyReplacement],
60
+ ]);
61
+
62
+ expect(migrateAuditLogLegacyEnums(log)).toBe(false);
63
+
64
+ expect(log.replacements.get('value')).toMatchObject({
65
+ value: EventNotificationStatus.Accepted,
66
+ type: AuditLogReplacementType.Key,
67
+ });
68
+ });
69
+ });
@@ -0,0 +1,85 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { AuditLog } from '@stamhoofd/models';
3
+ import { AuditLogReplacement, AuditLogReplacementType, getLegacyAuditLogEnumReplacement } from '@stamhoofd/structures';
4
+
5
+ export function migrateAuditLogReplacementLegacyEnums(replacement: AuditLogReplacement | undefined): { replacement: AuditLogReplacement | undefined; changed: boolean } {
6
+ if (!replacement) {
7
+ return { replacement, changed: false };
8
+ }
9
+
10
+ if (replacement.type === AuditLogReplacementType.Key) {
11
+ const migrated = getLegacyAuditLogEnumReplacement(replacement.value);
12
+ if (migrated) {
13
+ return { replacement: migrated, changed: true };
14
+ }
15
+ }
16
+
17
+ if (replacement.type === AuditLogReplacementType.Array) {
18
+ let changed = false;
19
+ const values = replacement.values.map((value) => {
20
+ const result = migrateAuditLogReplacementLegacyEnums(value);
21
+ changed ||= result.changed;
22
+ return result.replacement ?? value;
23
+ });
24
+
25
+ if (changed) {
26
+ const clone = replacement.clone();
27
+ clone.values = values;
28
+ return { replacement: clone, changed: true };
29
+ }
30
+ }
31
+
32
+ return { replacement, changed: false };
33
+ }
34
+
35
+ export function migrateAuditLogLegacyEnums(log: AuditLog): boolean {
36
+ let changed = false;
37
+
38
+ for (const [key, replacement] of log.replacements) {
39
+ const result = migrateAuditLogReplacementLegacyEnums(replacement);
40
+ if (result.changed && result.replacement) {
41
+ log.replacements.set(key, result.replacement);
42
+ changed = true;
43
+ }
44
+ }
45
+
46
+ for (const item of log.patchList) {
47
+ const key = migrateAuditLogReplacementLegacyEnums(item.key);
48
+ if (key.changed && key.replacement) {
49
+ item.key = key.replacement;
50
+ changed = true;
51
+ }
52
+
53
+ const oldValue = migrateAuditLogReplacementLegacyEnums(item.oldValue);
54
+ if (oldValue.changed) {
55
+ item.oldValue = oldValue.replacement;
56
+ changed = true;
57
+ }
58
+
59
+ const value = migrateAuditLogReplacementLegacyEnums(item.value);
60
+ if (value.changed) {
61
+ item.value = value.replacement;
62
+ changed = true;
63
+ }
64
+ }
65
+
66
+ return changed;
67
+ }
68
+
69
+ export default new Migration(async () => {
70
+ console.log('Start migrating legacy audit log enum replacements.');
71
+
72
+ const batchSize = 100;
73
+ let count = 0;
74
+
75
+ for await (const log of AuditLog.select().limit(batchSize).all()) {
76
+ if (!migrateAuditLogLegacyEnums(log)) {
77
+ continue;
78
+ }
79
+
80
+ await log.save();
81
+ count += 1;
82
+ }
83
+
84
+ console.log('Finished migrating legacy audit log enum replacements for ' + count + ' audit logs.');
85
+ });
@@ -0,0 +1,51 @@
1
+ import { column, Model } from '@simonbackx/simple-database';
2
+ import { EnumDecoder } from '@simonbackx/simple-encoding';
3
+ import { PaymentStatus } from '@stamhoofd/structures';
4
+
5
+ import { AuditLogService, modelLogDefinitions } from './AuditLogService.js';
6
+
7
+ enum UnregisteredAuditLogStatus {
8
+ Pending = 'Pending',
9
+ }
10
+
11
+ class UnregisteredAuditLogModel extends Model {
12
+ static table = 'unregistered_audit_log_models';
13
+
14
+ @column({ type: 'string', decoder: new EnumDecoder(UnregisteredAuditLogStatus) })
15
+ status = UnregisteredAuditLogStatus.Pending;
16
+ }
17
+
18
+ class RegisteredAuditLogModel extends Model {
19
+ static table = 'registered_audit_log_models';
20
+
21
+ @column({ type: 'string', decoder: new EnumDecoder(PaymentStatus) })
22
+ status = PaymentStatus.Pending;
23
+ }
24
+
25
+ describe('AuditLogService', () => {
26
+ const originalModelLogDefinitions = new Map(modelLogDefinitions);
27
+
28
+ afterEach(() => {
29
+ modelLogDefinitions.clear();
30
+ for (const [model, definition] of originalModelLogDefinitions) {
31
+ modelLogDefinitions.set(model, definition);
32
+ }
33
+ });
34
+
35
+ test('It throws when an audited enum column has no registered translation helper', () => {
36
+ modelLogDefinitions.clear();
37
+ modelLogDefinitions.set(UnregisteredAuditLogModel, {
38
+ async logEvent() {
39
+ // Not needed for validation.
40
+ },
41
+ });
42
+ modelLogDefinitions.set(RegisteredAuditLogModel, {
43
+ async logEvent() {
44
+ // Not needed for validation.
45
+ },
46
+ });
47
+
48
+ expect(() => AuditLogService.validateAuditLogEnumTranslations())
49
+ .toThrow('Missing audit log enum translation registration for: UnregisteredAuditLogModel.status');
50
+ });
51
+ });
@@ -1,5 +1,7 @@
1
1
  import type { ModelEvent } from '@simonbackx/simple-database';
2
2
  import { Model } from '@simonbackx/simple-database';
3
+ import { ArrayDecoder, EnumDecoder, MapDecoder, SymbolDecoder } from '@simonbackx/simple-encoding';
4
+ import { getAuditLogEnumDecoderObject, getAuditLogEnumType } from '@stamhoofd/structures';
3
5
  import type { AuditLogSource } from '@stamhoofd/structures';
4
6
  import { AsyncLocalStorage } from 'node:async_hooks';
5
7
 
@@ -15,6 +17,26 @@ export type AuditLogContextSettings = {
15
17
  };
16
18
  export const modelLogDefinitions = new Map<typeof Model, { logEvent: (event: ModelEvent) => Promise<any> }>();
17
19
 
20
+ function getEnumDecoders(decoder: unknown): EnumDecoder<any>[] {
21
+ if (decoder instanceof EnumDecoder) {
22
+ return [decoder];
23
+ }
24
+
25
+ if (decoder instanceof SymbolDecoder) {
26
+ return getEnumDecoders(decoder.decoder);
27
+ }
28
+
29
+ if (decoder instanceof ArrayDecoder) {
30
+ return getEnumDecoders(decoder.decoder);
31
+ }
32
+
33
+ if (decoder instanceof MapDecoder) {
34
+ return [...getEnumDecoders(decoder.keyDecoder), ...getEnumDecoders(decoder.valueDecoder)];
35
+ }
36
+
37
+ return [];
38
+ }
39
+
18
40
  export class AuditLogService {
19
41
  private constructor() { }
20
42
  static disableLocalStore = new AsyncLocalStorage<AuditLogContextSettings>();
@@ -47,6 +69,29 @@ export class AuditLogService {
47
69
 
48
70
  static listening = false;
49
71
 
72
+ static validateAuditLogEnumTranslations() {
73
+ const missing: string[] = [];
74
+
75
+ for (const [model] of modelLogDefinitions) {
76
+ const columns = model.columns;
77
+ if (!columns) {
78
+ continue;
79
+ }
80
+
81
+ for (const [key, column] of columns) {
82
+ for (const enumDecoder of getEnumDecoders(column.decoder)) {
83
+ if (!getAuditLogEnumType(getAuditLogEnumDecoderObject(enumDecoder))) {
84
+ missing.push(`${model.name}.${key}`);
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ if (missing.length > 0) {
91
+ throw new Error(`Missing audit log enum translation registration for: ${missing.join(', ')}`);
92
+ }
93
+ }
94
+
50
95
  static listen() {
51
96
  if (this.listening) {
52
97
  return;
@@ -1,7 +1,9 @@
1
1
  import { Model } from '@simonbackx/simple-database';
2
- import type { Organization, Payment} from '@stamhoofd/models';
3
- import { BalanceItem, CachedBalance, Document, MemberUser, Order, Webshop } from '@stamhoofd/models';
4
- import { AuditLogSource, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentStatus, ReceivableBalanceType } from '@stamhoofd/structures';
2
+ import type { Organization } from '@stamhoofd/models';
3
+ import { BalanceItem, CachedBalance, Document, MemberUser, Order, Payment, Platform, Webshop } from '@stamhoofd/models';
4
+ import { SQL } from '@stamhoofd/sql';
5
+ import { AuditLogSource, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentStatus, PaymentType, ReceivableBalanceType } from '@stamhoofd/structures';
6
+ import { Formatter } from '@stamhoofd/utility';
5
7
  import { GroupedThrottledQueue } from '../helpers/GroupedThrottledQueue.js';
6
8
  import { ThrottledQueue } from '../helpers/ThrottledQueue.js';
7
9
  import { AuditLogService } from './AuditLogService.js';
@@ -32,11 +34,21 @@ const userUpdateQueue = new GroupedThrottledQueue(async (organizationId: string,
32
34
  }, { maxDelay: 10_000 });
33
35
 
34
36
  const organizationUpdateQueue = new GroupedThrottledQueue(async (organizationId: string, organizationIds: string[]) => {
35
- await CachedBalance.updateForOrganizations(organizationId, organizationIds);
37
+ const results = await CachedBalance.updateForOrganizations(organizationId, organizationIds);
36
38
 
37
39
  for (const payingOrganizationId of organizationIds) {
38
40
  await PaymentReallocationService.reallocate(organizationId, payingOrganizationId, ReceivableBalanceType.organization);
39
41
  }
42
+
43
+ const platform = await Platform.getShared();
44
+ if (organizationId === platform.membershipOrganizationId) {
45
+ // Clear failed payments
46
+ for (const [organizationId, result] of results) {
47
+ if (result.amountOpen === 0 && result.amountPending === 0) {
48
+ await STPackageService.markBalanceRestored(organizationId);
49
+ }
50
+ }
51
+ }
40
52
  }, { maxDelay: 60_000 });
41
53
 
42
54
  export const registrationUpdateQueue = new GroupedThrottledQueue(async (organizationId: string, registrationIds: string[]) => {
@@ -50,10 +62,10 @@ const bundleDiscountsUpdateQueue = new ThrottledQueue(async (registrationIds: st
50
62
  }
51
63
  }, { maxDelay: 10_000 });
52
64
 
53
- export const BalanceItemService = {
54
- listening: false,
65
+ export class BalanceItemService {
66
+ static listening = false;
55
67
 
56
- listen() {
68
+ static listen() {
57
69
  if (this.listening) {
58
70
  return;
59
71
  }
@@ -64,7 +76,7 @@ export const BalanceItemService = {
64
76
  }
65
77
 
66
78
  if (event.type === 'created' || event.type === 'deleted') {
67
- await this.scheduleUpdate(event.model);
79
+ this.scheduleUpdate(event.model);
68
80
  return;
69
81
  }
70
82
 
@@ -79,10 +91,10 @@ export const BalanceItemService = {
79
91
  || 'payingOrganizationId' in event.changedFields
80
92
  || 'registrationId' in event.changedFields
81
93
  ) {
82
- await this.scheduleUpdate(event.model);
94
+ this.scheduleUpdate(event.model);
83
95
  }
84
96
  });
85
- },
97
+ }
86
98
 
87
99
  /**
88
100
  * Schedule an update for the balance item:
@@ -90,8 +102,8 @@ export const BalanceItemService = {
90
102
  *
91
103
  * Does not execute the update immediately, but schedules it to be run in the background.
92
104
  */
93
- async scheduleUpdate(item: BalanceItem) {
94
- await this.scheduleUpdates([item]);
105
+ static scheduleUpdate(item: BalanceItem) {
106
+ this.scheduleUpdates([item]);
95
107
 
96
108
  // Todo: optimize
97
109
  if (item.type === BalanceItemType.RegistrationBundleDiscount) {
@@ -100,49 +112,58 @@ export const BalanceItemService = {
100
112
  bundleDiscountsUpdateQueue.addItem(item.registrationId);
101
113
  }
102
114
  }
103
- },
115
+ }
104
116
 
105
117
  /**
106
118
  * Call this when a payment or payment balance items have changed.
107
119
  * It will also call updateOutstanding automatically, so no need to call that separately again
108
120
  */
109
- async updatePaidAndPending(items: BalanceItem[]) {
121
+ static async updatePaidAndPending(items: BalanceItem[]) {
110
122
  console.log('updatePaidAndPending for', items.length, 'items');
111
- await BalanceItem.updatePricePaid(items.map(i => i.id));
112
- await this.scheduleUpdates(items);
113
- },
123
+ await BalanceItem.updatePricePaid(Formatter.uniqueArray(items.map(i => i.id)));
124
+ this.scheduleUpdates(items);
125
+ }
126
+
127
+ /**
128
+ * Call this when a payment or payment balance items have changed.
129
+ * It will also call updateOutstanding automatically, so no need to call that separately again
130
+ */
131
+ static async updateInvoiced(items: (BalanceItem | string)[]) {
132
+ console.log('updateInvoiced for', items.length, 'items');
133
+ await BalanceItem.updateInvoiced(Formatter.uniqueArray(items.map(i => typeof i === 'string' ? i : i.id)));
134
+ }
114
135
 
115
136
  /**
116
137
  * In some situations we need immediate updates
117
138
  */
118
- async flushRegistrationDiscountsCache() {
139
+ static async flushRegistrationDiscountsCache() {
119
140
  await bundleDiscountsUpdateQueue.flushAndWait();
120
- },
141
+ }
121
142
 
122
143
  /**
123
144
  * Make sure all the pending changes for cached balances are run
124
145
  */
125
- async flushCaches(organizationId: string) {
146
+ static async flushCaches(organizationId: string) {
126
147
  await memberUpdateQueue.flushGroupAndWait(organizationId);
127
148
  await userUpdateQueue.flushGroupAndWait(organizationId);
128
149
  await organizationUpdateQueue.flushGroupAndWait(organizationId);
129
150
  await registrationUpdateQueue.flushGroupAndWait(organizationId);
130
151
  await bundleDiscountsUpdateQueue.flushAndWait();
131
- },
152
+ }
132
153
 
133
- async flushAll() {
154
+ static async flushAll() {
134
155
  await memberUpdateQueue.flushAndWait();
135
156
  await userUpdateQueue.flushAndWait();
136
157
  await organizationUpdateQueue.flushAndWait();
137
158
  await registrationUpdateQueue.flushAndWait();
138
159
  await bundleDiscountsUpdateQueue.flushAndWait();
139
- },
160
+ }
140
161
 
141
162
  /**
142
163
  * Update how many every object in the system owes or needs to be reimbursed
143
164
  * and also updates the pricePaid/pricePending cached values in Balance items and members
144
165
  */
145
- async scheduleUpdates(items: BalanceItem[]) {
166
+ static scheduleUpdates(items: BalanceItem[]) {
146
167
  console.log('Schedule outstanding balance for', items.length, 'items');
147
168
 
148
169
  for (const item of items) {
@@ -162,21 +183,21 @@ export const BalanceItemService = {
162
183
  registrationUpdateQueue.addItem(item.organizationId, item.registrationId);
163
184
  }
164
185
  }
165
- },
186
+ }
166
187
 
167
- scheduleUserUpdate(organizationId: string, userId: string) {
188
+ static scheduleUserUpdate(organizationId: string, userId: string) {
168
189
  userUpdateQueue.addItem(organizationId, userId);
169
- },
190
+ }
170
191
 
171
- scheduleMemberUpdate(organizationId: string, memberId: string) {
192
+ static cheduleMemberUpdate(organizationId: string, memberId: string) {
172
193
  memberUpdateQueue.addItem(organizationId, memberId);
173
- },
194
+ }
174
195
 
175
- scheduleOrganizationUpdate(organizationId: string, payingOrganizationId: string) {
196
+ static scheduleOrganizationUpdate(organizationId: string, payingOrganizationId: string) {
176
197
  organizationUpdateQueue.addItem(organizationId, payingOrganizationId);
177
- },
198
+ }
178
199
 
179
- async markDue(balanceItem: BalanceItem) {
200
+ static async markDue(balanceItem: BalanceItem) {
180
201
  if (balanceItem.status === BalanceItemStatus.Hidden) {
181
202
  balanceItem.status = BalanceItemStatus.Due;
182
203
  await balanceItem.save();
@@ -190,9 +211,21 @@ export const BalanceItemService = {
190
211
  await balanceItem.save();
191
212
  }
192
213
  }
193
- },
214
+ }
215
+
216
+ static async markPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
217
+ if (payment && payment.type === PaymentType.Chargeback) {
218
+ const realPayment = payment.reversingPaymentId ? (await Payment.getByID(payment.reversingPaymentId) ?? null) : null;
219
+ await this.handleChargeback(balanceItem, payment, realPayment, organization);
220
+ return;
221
+ }
222
+
223
+ if (payment && payment.price !== 0 && payment.refundedAmount === payment.price) {
224
+ // Charged back, already handled
225
+ // First cancel the chargeback if the payment should resucceed again
226
+ return;
227
+ }
194
228
 
195
- async markPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
196
229
  await this.markDue(balanceItem);
197
230
  let shouldMarkUpdated = true;
198
231
 
@@ -250,9 +283,9 @@ export const BalanceItemService = {
250
283
 
251
284
  balanceItem.paidAt = new Date();
252
285
  await balanceItem.save();
253
- },
286
+ }
254
287
 
255
- async markUpdated(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
288
+ static async markUpdated(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
256
289
  // For orders: mark order as changed (so they are refetched in front ends)
257
290
  if (balanceItem.orderId) {
258
291
  await AuditLogService.setContext({ source: AuditLogSource.Payment }, async () => {
@@ -264,9 +297,9 @@ export const BalanceItemService = {
264
297
  }
265
298
  });
266
299
  }
267
- },
300
+ }
268
301
 
269
- async undoPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
302
+ static async undoPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
270
303
  // If order
271
304
  if (balanceItem.orderId) {
272
305
  const order = await Order.getByID(balanceItem.orderId);
@@ -275,16 +308,24 @@ export const BalanceItemService = {
275
308
  await order.undoPaid(payment, organization);
276
309
  }
277
310
  }
311
+ }
278
312
 
279
- // If a rounded payment was canceled, make sure the balance item is hidden again (will become visible again when marking paid)
280
- if (this.type === BalanceItemType.Rounding && balanceItem.status !== BalanceItemStatus.Hidden) {
281
- // Mark undue
282
- balanceItem.status = BalanceItemStatus.Hidden;
283
- await balanceItem.save();
313
+ /**
314
+ *
315
+ */
316
+ static async handleChargeback(balanceItem: BalanceItem, chargeback: Payment | null, payment: Payment | null, organization: Organization) {
317
+ if (balanceItem.failedAt) {
318
+ // Already ran side effects
319
+ return;
284
320
  }
285
- },
286
321
 
287
- async markFailed(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
322
+ // todo
323
+
324
+ balanceItem.failedAt = new Date();
325
+ await balanceItem.save();
326
+ }
327
+
328
+ static async markFailed(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
288
329
  // If order
289
330
  if (balanceItem.orderId) {
290
331
  const order = await Order.getByID(balanceItem.orderId);
@@ -297,16 +338,10 @@ export const BalanceItemService = {
297
338
  }
298
339
  }
299
340
  }
341
+ await this.handleChargeback(balanceItem, null, payment, organization);
342
+ }
300
343
 
301
- // If a rounded payment was canceled, make sure the balance item is hidden again (will become visible again when marking paid)
302
- if (this.type === BalanceItemType.Rounding && balanceItem.status !== BalanceItemStatus.Hidden) {
303
- // Mark undue
304
- balanceItem.status = BalanceItemStatus.Hidden;
305
- await balanceItem.save();
306
- }
307
- },
308
-
309
- async undoFailed(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
344
+ static async undoFailed(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
310
345
  // If order
311
346
  if (balanceItem.orderId) {
312
347
  const order = await Order.getByID(balanceItem.orderId);
@@ -314,5 +349,63 @@ export const BalanceItemService = {
314
349
  await order.undoPaymentFailed(payment, organization);
315
350
  }
316
351
  }
317
- },
352
+ }
353
+
354
+ /**
355
+ * Apply discounts to the balance a user is going to checkout
356
+ */
357
+ static async applyDiscountsToCheckout({ minimumAmount, totalPrice, balanceItems, sellingOrganizationId, payingOrganizationId, payingUserId }: {
358
+ balanceItems: Map<BalanceItem, number>;
359
+ totalPrice: number;
360
+ minimumAmount?: number;
361
+ sellingOrganizationId: string;
362
+ payingOrganizationId?: string | null | undefined;
363
+ payingUserId?: string | null | undefined;
364
+ }) {
365
+ if (totalPrice <= (minimumAmount ?? 0)) {
366
+ return {
367
+ totalPrice,
368
+ };
369
+ }
370
+
371
+ let items: BalanceItem[];
372
+ if (payingOrganizationId) {
373
+ items = await BalanceItem.select()
374
+ .where('organizationId', sellingOrganizationId)
375
+ .where('payingOrganizationId', payingOrganizationId)
376
+ .whereNot('status', BalanceItemStatus.Hidden)
377
+ .where('priceOpen', '<', 0)
378
+ .fetch();
379
+ } else if (payingUserId) {
380
+ const memberUsers = await MemberUser.select().where('usersId', payingUserId).fetch();
381
+ const memberIds = Formatter.uniqueArray(memberUsers.map(mu => mu.membersId));
382
+
383
+ items = await BalanceItem.select()
384
+ .where('organizationId', sellingOrganizationId)
385
+ .where(
386
+ SQL.where('userId', payingUserId)
387
+ .or('memberId', memberIds),
388
+ )
389
+ .whereNot('status', BalanceItemStatus.Hidden)
390
+ .where('priceOpen', '<', 0)
391
+ .fetch();
392
+ } else {
393
+ return {
394
+ totalPrice,
395
+ };
396
+ }
397
+
398
+ for (const item of items) {
399
+ const remove = Math.min(totalPrice - (minimumAmount ?? 0), -item.priceOpen);
400
+ if (remove === 0) {
401
+ break;
402
+ }
403
+ balanceItems.set(item, -remove);
404
+ totalPrice -= remove;
405
+ }
406
+
407
+ return {
408
+ totalPrice,
409
+ };
410
+ }
318
411
  };
@@ -56,8 +56,7 @@ class StaticCpuService {
56
56
  }
57
57
  if (size === this.maxSamples) {
58
58
  return this.samples.reduce((a, b) => a + b, 0) / size;
59
- }
60
- else {
59
+ } else {
61
60
  // To read before current index
62
61
  const toReadBeforeIndex = Math.min(size, this.currentIndex);
63
62
  const toReadFromEnd = size - toReadBeforeIndex;
@@ -89,13 +88,11 @@ class StaticCpuService {
89
88
  if (five > 80) {
90
89
  // Danger zone, in this case we don't want to log all slow queries any longer because the information won't be trustworthy.
91
90
  SQLLogger.slowQueryThresholdMs = null;
92
- }
93
- else {
91
+ } else {
94
92
  if (five < 20) {
95
93
  // No load, safe to log all slow queries
96
94
  SQLLogger.slowQueryThresholdMs = 300;
97
- }
98
- else {
95
+ } else {
99
96
  SQLLogger.slowQueryThresholdMs = 500;
100
97
  }
101
98
  }
@@ -10,8 +10,7 @@ describe('DatabaseCollationService', () => {
10
10
  expect(error).toContain('MySQL collation mismatch');
11
11
  expect(error).toContain('expected utf8mb4_0900_ai_ci');
12
12
  expect(error).toContain('collation_connection=utf8mb4_general_ci');
13
- }
14
- finally {
13
+ } finally {
15
14
  await Database.statement('SET collation_connection = DEFAULT');
16
15
  }
17
16
  });
@@ -33,8 +33,7 @@ export class DocumentService {
33
33
  // If a group is changed - update the documents for it
34
34
  await Document.updateForGroup(event.model.id, event.model.organizationId);
35
35
  }
36
- }
37
- catch (e) {
36
+ } catch (e) {
38
37
  console.error('Failed to update documents after group change', e);
39
38
  }
40
39
  });
@@ -1,6 +1,6 @@
1
1
  import { SimpleError } from '@simonbackx/simple-errors';
2
2
  import { EventNotification, Member, MemberResponsibilityRecord, Organization, Platform, sendEmailTemplate, User } from '@stamhoofd/models';
3
- import type { EmailTemplateType} from '@stamhoofd/structures';
3
+ import type { EmailTemplateType } from '@stamhoofd/structures';
4
4
  import { PermissionLevel, Recipient, RecordCategory, Replacement } from '@stamhoofd/structures';
5
5
  import { Formatter } from '@stamhoofd/utility';
6
6
  import { AdminPermissionChecker } from '../helpers/AdminPermissionChecker.js';