@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
@@ -1,10 +1,9 @@
1
- import type { DecodedRequest} from '@simonbackx/simple-endpoints';
1
+ import type { DecodedRequest } from '@simonbackx/simple-endpoints';
2
2
  import { Response } from '@simonbackx/simple-endpoints';
3
3
  import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
4
- import type { Organization} from '@stamhoofd/models';
5
- import { Platform, Token, User, Webshop } from '@stamhoofd/models';
6
- import type { LoginMethod, StartOpenIDFlowStruct} from '@stamhoofd/structures';
7
- import { LoginProviderType, OpenIDClientConfiguration, Token as TokenStruct } from '@stamhoofd/structures';
4
+ import { Organization, Platform, Token, User, Webshop } from '@stamhoofd/models';
5
+ import type { LoginMethod, StartOpenIDFlowStruct } from '@stamhoofd/structures';
6
+ import { getAppHost, LoginProviderType, OpenIDClientConfiguration, Token as TokenStruct } from '@stamhoofd/structures';
8
7
  import crypto from 'crypto';
9
8
  import { generators, Issuer } from 'openid-client';
10
9
  import { Context } from '../helpers/Context.js';
@@ -32,6 +31,7 @@ type SSOSessionContext = {
32
31
  redirectUri: string;
33
32
  spaState: string;
34
33
  providerType: LoginProviderType;
34
+ organizationId?: string | null;
35
35
 
36
36
  /**
37
37
  * Link this method to this existing user and don't create a new token
@@ -127,17 +127,19 @@ export class SSOService {
127
127
  let redirectUri = 'https://' + STAMHOOFD.domains.dashboard;
128
128
 
129
129
  if (this.organization) {
130
- redirectUri = 'https://' + this.organization.getHost();
130
+ redirectUri = 'https://' + getAppHost('registration', this.organization, true);
131
131
  }
132
132
  return redirectUri;
133
133
  }
134
134
 
135
- static async fromContext(provider: LoginProviderType) {
135
+ static async fromContext(provider: LoginProviderType, options: { validate?: boolean } = {}) {
136
136
  const organization = Context.organization;
137
137
  const platform = await Platform.getForEditing();
138
138
 
139
139
  const service = new SSOService({ provider, platform, organization });
140
- service.validateConfiguration();
140
+ if (options.validate ?? false) {
141
+ service.validateConfiguration();
142
+ }
141
143
 
142
144
  return service;
143
145
  }
@@ -148,17 +150,14 @@ export class SSOService {
148
150
  if (this.provider === LoginProviderType.SSO) {
149
151
  if (this.organization) {
150
152
  configuration = this.organization.serverMeta.ssoConfiguration ?? OpenIDClientConfiguration.create({});
151
- }
152
- else {
153
+ } else {
153
154
  configuration = this.platform.serverConfig.ssoConfiguration ?? OpenIDClientConfiguration.create({});
154
155
  }
155
- }
156
- else if (this.provider === LoginProviderType.Google) {
156
+ } else if (this.provider === LoginProviderType.Google) {
157
157
  if (this.organization) {
158
158
  // Not supported yet
159
159
  configuration = null;
160
- }
161
- else {
160
+ } else {
162
161
  configuration = this.platform.serverConfig.googleConfiguration ?? OpenIDClientConfiguration.create({});
163
162
  }
164
163
  }
@@ -174,15 +173,11 @@ export class SSOService {
174
173
  }
175
174
 
176
175
  get loginConfiguration() {
177
- if (this.organization) {
178
- throw new SimpleError({
179
- code: 'invalid_client',
180
- message: 'Login configuration not yet supported for organization users',
181
- statusCode: 400,
182
- });
183
- }
176
+ const loginMethods = this.organization
177
+ ? this.organization.meta.loginMethods
178
+ : this.platform.config.loginMethods;
184
179
 
185
- const loginConfiguration = this.platform.config.loginMethods.get(this.provider as unknown as LoginMethod);
180
+ const loginConfiguration = loginMethods.get(this.provider as unknown as LoginMethod);
186
181
  if (!loginConfiguration) {
187
182
  throw new SimpleError({
188
183
  code: 'invalid_client',
@@ -195,9 +190,9 @@ export class SSOService {
195
190
  }
196
191
 
197
192
  validateConfiguration() {
198
- // Validate configuration exists
199
- const _ = this.configuration;
200
- const __ = this.loginConfiguration;
193
+ // Trigger getters to validate both server and login method configuration.
194
+ void this.configuration;
195
+ void this.loginConfiguration;
201
196
  }
202
197
 
203
198
  validateEmail(email: string) {
@@ -218,21 +213,19 @@ export class SSOService {
218
213
  if (this.provider === LoginProviderType.SSO) {
219
214
  if (this.organization) {
220
215
  this.organization.serverMeta.ssoConfiguration = configuration;
221
- await this.getClient();
216
+ await this.validateClientConfiguration();
222
217
  await this.organization.save();
223
218
  return;
224
- }
225
- else {
219
+ } else {
226
220
  this.platform.serverConfig.ssoConfiguration = configuration;
227
- await this.getClient();
221
+ await this.validateClientConfiguration();
228
222
  await this.platform.save();
229
223
  return;
230
224
  }
231
- }
232
- else if (this.provider === LoginProviderType.Google) {
225
+ } else if (this.provider === LoginProviderType.Google) {
233
226
  if (!this.organization) {
234
227
  this.platform.serverConfig.googleConfiguration = configuration;
235
- await this.getClient();
228
+ await this.validateClientConfiguration();
236
229
  await this.platform.save();
237
230
  return;
238
231
  }
@@ -245,6 +238,20 @@ export class SSOService {
245
238
  });
246
239
  }
247
240
 
241
+ async validateClientConfiguration() {
242
+ try {
243
+ await this.getClient();
244
+ } catch (e) {
245
+ const message = e instanceof Error ? e.message : String(e);
246
+ throw new SimpleError({
247
+ code: 'invalid_sso_configuration',
248
+ message: 'Failed to validate SSO configuration: ' + message,
249
+ human: $t('%1Xe', { message }),
250
+ statusCode: 400,
251
+ });
252
+ }
253
+ }
254
+
248
255
  async getClient() {
249
256
  const issuer = await Issuer.discover(this.configuration.issuer);
250
257
  const client = new issuer.Client({
@@ -331,8 +338,7 @@ export class SSOService {
331
338
 
332
339
  if (allowedHost.host === givenUrl.host && givenUrl.protocol === 'https:') {
333
340
  redirectUri = givenUrl.href;
334
- }
335
- else {
341
+ } else {
336
342
  throw new SimpleError({
337
343
  code: 'redirect_uri_not_allowed',
338
344
  message: 'Redirect uri not allowed',
@@ -340,8 +346,7 @@ export class SSOService {
340
346
  statusCode: 400,
341
347
  });
342
348
  }
343
- }
344
- catch (e) {
349
+ } catch (e) {
345
350
  throw new SimpleError({
346
351
  code: 'invalid_redirect_uri',
347
352
  message: 'Invalid redirect uri',
@@ -385,8 +390,7 @@ export class SSOService {
385
390
  let parsed: URL;
386
391
  try {
387
392
  parsed = new URL(uri);
388
- }
389
- catch (e) {
393
+ } catch (e) {
390
394
  throw new SimpleError({
391
395
  code: 'invalid_redirect_uri',
392
396
  message: 'Invalid redirect uri',
@@ -429,6 +433,7 @@ export class SSOService {
429
433
  redirectUri,
430
434
  spaState, // this is the state frontend <-> backend (not backend <-> SSO provider)
431
435
  providerType: this.provider,
436
+ organizationId: this.organization?.id ?? null,
432
437
  userId: user?.id ?? null,
433
438
  };
434
439
 
@@ -465,8 +470,7 @@ export class SSOService {
465
470
  response.status = 302;
466
471
 
467
472
  return response;
468
- }
469
- catch (e) {
473
+ } catch (e) {
470
474
  const message = (isSimpleError(e) || isSimpleErrors(e) ? e.getHuman() : $t(`%G0`));
471
475
  console.error('Error in openID callback', e);
472
476
  return SSOServiceWithSession.getErrorRedirectResponse(session, message);
@@ -492,7 +496,14 @@ export class SSOServiceWithSession {
492
496
  throw new Error('Missing session');
493
497
  }
494
498
 
495
- const service = await SSOService.fromContext(session.providerType);
499
+ const platform = await Platform.getForEditing();
500
+ const organization = session.organizationId ? await Organization.getByID(session.organizationId) : null;
501
+ if (session.organizationId && !organization) {
502
+ throw new Error('Missing organization');
503
+ }
504
+
505
+ const service = new SSOService({ provider: session.providerType, platform, organization });
506
+ service.validateConfiguration();
496
507
 
497
508
  return new SSOServiceWithSession(session, service, request);
498
509
  }
@@ -548,8 +559,7 @@ export class SSOServiceWithSession {
548
559
 
549
560
  if (tokenSet.refresh_token) {
550
561
  console.log('OK. Refresh token received!');
551
- }
552
- else {
562
+ } else {
553
563
  console.log('No refresh token');
554
564
  }
555
565
 
@@ -600,8 +610,7 @@ export class SSOServiceWithSession {
600
610
  statusCode: 500,
601
611
  });
602
612
  }
603
- }
604
- else {
613
+ } else {
605
614
  if (this.session.userId && user.id !== this.session.userId) {
606
615
  throw new SimpleError({
607
616
  code: 'invalid_user',
@@ -640,8 +649,7 @@ export class SSOServiceWithSession {
640
649
  const st = new TokenStruct(token);
641
650
  redirectUri.searchParams.set('oid_rt', st.refreshToken);
642
651
  redirectUri.searchParams.set('s', session.spaState);
643
- }
644
- else {
652
+ } else {
645
653
  redirectUri.searchParams.set('s', session.spaState);
646
654
  redirectUri.searchParams.set('msg', 'Je account is succesvol gekoppeld');
647
655
  }
@@ -650,8 +658,7 @@ export class SSOServiceWithSession {
650
658
  response.status = 302;
651
659
 
652
660
  return response;
653
- }
654
- catch (e) {
661
+ } catch (e) {
655
662
  const message = (isSimpleError(e) || isSimpleErrors(e) ? e.getHuman() : $t(`%G0`));
656
663
  console.error('Error in openID callback', e);
657
664
  return SSOServiceWithSession.getErrorRedirectResponse(session, message);
@@ -1,11 +1,9 @@
1
- import { SimpleError } from '@simonbackx/simple-errors';
2
1
  import { BalanceItem, Organization, Platform, STPackage } from '@stamhoofd/models';
3
2
  import { SQL } from '@stamhoofd/sql';
4
- import type { PaymentCustomer, STPackageStatus, STPackageType } from '@stamhoofd/structures';
5
- import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, STPricingType, TranslatedString, VATExcemptReason } from '@stamhoofd/structures';
6
- import { Country } from '@stamhoofd/types/Country';
7
- import { Formatter } from '@stamhoofd/utility';
8
- import { GroupBuilder } from '../helpers/GroupBuilder.js';
3
+ import type { Company } from '@stamhoofd/structures';
4
+ import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, getPricingTypeName, STPackageStatus, STPackageType, STPackageTypeHelper, STPricingType, TranslatedString } from '@stamhoofd/structures';
5
+ import { Formatter, STMath } from '@stamhoofd/utility';
6
+ import { VATService } from './VATService.js';
9
7
 
10
8
  export class STPackageService {
11
9
  static async getActivePackages(organizationId: string) {
@@ -35,18 +33,13 @@ export class STPackageService {
35
33
  static async getChargeableQuantity(pack: STPackage) {
36
34
  // If type is
37
35
  let amount = 1;
38
- let membersCount: number | null = null;
39
-
40
- if (membersCount === null && pack.meta.pricingType === STPricingType.PerMember) {
41
- membersCount = await Organization.getActiveMembers(pack.organizationId);
42
- }
43
36
 
44
37
  if (pack.meta.pricingType === STPricingType.PerMember) {
45
- amount = membersCount ?? 1;
38
+ amount = await Organization.getActiveMembers(pack.organizationId);
46
39
  }
47
40
 
48
- if (amount < pack.meta.minimumAmount) {
49
- amount = pack.meta.minimumAmount;
41
+ if (amount < (pack.meta.minimumAmount || 1)) {
42
+ amount = (pack.meta.minimumAmount || 1);
50
43
  }
51
44
 
52
45
  return amount;
@@ -74,6 +67,17 @@ export class STPackageService {
74
67
  }
75
68
  }
76
69
 
70
+ // Stop trials
71
+ const trialType = STPackageTypeHelper.getTrial(pack.meta.type);
72
+ if (trialType) {
73
+ const activePackages = await this.getActivePackages(pack.organizationId);
74
+ for (const p of activePackages) {
75
+ if (p.meta.type === trialType) {
76
+ await this.deactivatePackage(p);
77
+ }
78
+ }
79
+ }
80
+
77
81
  await this.updateOrganizationPackages(pack.organizationId);
78
82
  }
79
83
 
@@ -83,6 +87,14 @@ export class STPackageService {
83
87
  await old.save();
84
88
  }
85
89
 
90
+ static async deactivatePackage(pack: STPackage) {
91
+ console.log('Deactivating package', pack.meta.type, 'with id', pack.id);
92
+ // Deactivate
93
+ pack.removeAt = new Date();
94
+ pack.removeAt.setTime(pack.removeAt.getTime() - 5_000);
95
+ await pack.save();
96
+ }
97
+
86
98
  static async getPaidOrPendingQuantity(pack: STPackage) {
87
99
  if (!pack.existsInDatabase || pack.createdAt.getTime() > Date.now() - 1_000) {
88
100
  return 0;
@@ -91,7 +103,7 @@ export class STPackageService {
91
103
  return await BalanceItem.select()
92
104
  .where('packageId', pack.id)
93
105
  .where('status', BalanceItemStatus.Due)
94
- .count(SQL.column('amount'));
106
+ .sum(SQL.column('amount'));
95
107
  }
96
108
 
97
109
  /**
@@ -99,42 +111,33 @@ export class STPackageService {
99
111
  *
100
112
  * Convertable into STInvoiceItem (or the diffence if amount is increased)
101
113
  * Use this to calculate prices or create an invoice
102
- * This will calculate the price to expand the package to the given amount.
114
+ * This will calculate the price to expand the package to the given amount
103
115
  * If you want to renew a package, you need to create a new package first
104
116
  */
105
- static async chargePackage(pack: STPackage, date?: Date, customer?: PaymentCustomer): Promise<BalanceItem | null> {
117
+ static async chargePackage(pack: STPackage, sellingOrganization: Organization, company: Company | null | undefined, date?: Date): Promise<BalanceItem | null> {
106
118
  let unitPrice = Math.round(pack.meta.unitPrice);
107
119
 
108
120
  let amount = await this.getChargeableQuantity(pack);
109
-
110
- if (amount < pack.meta.minimumAmount) {
111
- // Minimum should get applied first, because we might already have paid for the minimum (paid amount)
112
- amount = pack.meta.minimumAmount;
113
- }
114
-
115
121
  const paid = await this.getPaidOrPendingQuantity(pack);
116
122
  amount -= paid;
117
123
 
118
124
  if (amount <= 0) {
119
- amount = 0;
120
- }
121
-
122
- if (amount <= 0) {
125
+ console.log('Not charging package', pack.id, 'amount is', amount);
123
126
  return null;
124
127
  }
125
128
 
126
129
  /// When pricing type is memebrs, the price is calculated per year.
127
130
  /// If a shorter period is remaining, we give a discount in order
128
131
  /// to no need to handle it more complicated
129
- let now = date ?? new Date();
132
+ let now = date ?? Formatter.luxon().startOf('day').toJSDate();
130
133
  if (now < pack.meta.startDate) {
131
134
  // When creating a new package, we sometimes buy it for the future, so use that date instead of now
132
135
  now = pack.meta.startDate;
133
136
  }
134
137
 
135
- if (pack.validUntil && pack.meta.pricingType !== STPricingType.Fixed) {
136
- const totalDays = Math.round((pack.validUntil.getTime() - pack.meta.startDate.getTime()) / (1000 * 60 * 60 * 24));
137
- let remainingDays = Math.round((pack.validUntil.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
138
+ if (pack.endDate && pack.meta.pricingType !== STPricingType.Fixed) {
139
+ const totalDays = Math.round((pack.endDate.getTime() - pack.meta.startDate.getTime()) / (1000 * 60 * 60 * 24));
140
+ let remainingDays = Math.round((pack.endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
138
141
 
139
142
  /// First 3 months are full price
140
143
  const paidDays = 30 * 3;
@@ -150,8 +153,7 @@ export class STPackageService {
150
153
 
151
154
  if (pack.meta.pricingType === STPricingType.PerMember) {
152
155
  unitPrice = Math.round(Math.min(unitPrice, unitPrice * remainingDays / (Math.max(365, totalDays) - paidDays)));
153
- }
154
- else {
156
+ } else {
155
157
  unitPrice = Math.round(unitPrice);
156
158
  }
157
159
  }
@@ -159,46 +161,40 @@ export class STPackageService {
159
161
  const item = new BalanceItem();
160
162
  item.type = BalanceItemType.STPackage;
161
163
  item.description = pack.meta.name + ' ' + (
162
- pack.validUntil
163
- ? $t('%1LM', { startDate: Formatter.date(now, true), endDate: Formatter.date(pack.validUntil, true) })
164
- : $t('%1LN', { startDate: Formatter.date(pack.meta.startDate, true) })
164
+ pack.endDate
165
+ ? $t('%1LM', { startDate: Formatter.startDate(now, false, true), endDate: Formatter.endDate(pack.endDate, false, true) })
166
+ : $t('%1LN', { startDate: Formatter.startDate(pack.meta.startDate, false, true) })
165
167
  );
166
168
  item.relations.set(BalanceItemRelationType.STPackage, BalanceItemRelation.create({
167
169
  id: pack.id,
168
170
  name: TranslatedString.create(pack.meta.name),
169
171
  }));
172
+ item.relations.set(BalanceItemRelationType.STPricingType, BalanceItemRelation.create({
173
+ id: pack.meta.pricingType,
174
+ name: TranslatedString.create(getPricingTypeName(pack.meta.pricingType)),
175
+ }));
176
+
170
177
  item.packageId = pack.id;
171
178
  item.startDate = now;
172
179
 
173
- if (pack.validUntil) {
174
- item.endDate = pack.validUntil;
180
+ if (pack.endDate) {
181
+ item.endDate = pack.endDate;
175
182
  }
176
183
 
177
184
  item.payingOrganizationId = pack.organizationId;
178
- const membershipOrganizationId = (await Platform.getShared()).membershipOrganizationId;
179
- if (!membershipOrganizationId) {
180
- throw new SimpleError({
181
- code: 'unavailable',
182
- message: 'No membership organization id set on the platform',
183
- human: 'Package purchases are currently unavailable',
184
- });
185
- }
186
- item.organizationId = membershipOrganizationId;
185
+ item.organizationId = sellingOrganization.id;
187
186
  item.VATPercentage = 21;
188
187
  item.VATIncluded = false;
189
- item.VATExcempt = null;
188
+ item.VATExcempt = VATService.getVATExcempt({
189
+ company,
190
+ sellingOrganization,
191
+ type: 'services',
192
+ });
190
193
  item.quantity = amount;
191
- item.unitPrice = unitPrice;
192
- item.createdAt = now;
194
+ item.unitPrice = STMath.round(unitPrice / 100) * 100;
195
+ item.createdAt = new Date();
193
196
  item.status = BalanceItemStatus.Hidden;
194
197
 
195
- if (customer && customer.company && customer.company.address) {
196
- // Reverse charged vat applicable?
197
- if (customer.company.address.country !== Country.Belgium) {
198
- item.VATExcempt = VATExcemptReason.IntraCommunity;
199
- }
200
- }
201
-
202
198
  return item;
203
199
  }
204
200
 
@@ -213,15 +209,26 @@ export class STPackageService {
213
209
  }
214
210
 
215
211
  private static async getOrganizationPackagesMap(organizationId: string): Promise<Map<STPackageType, STPackageStatus>> {
212
+ const map = new Map<STPackageType, STPackageStatus>();
213
+
214
+ if (STAMHOOFD.userMode === 'platform' || organizationId === ((await Platform.getShared()).membershipOrganizationId)) {
215
+ map.set(STPackageType.Webshops, STPackageStatus.create({
216
+ startDate: new Date(0),
217
+ }));
218
+
219
+ map.set(STPackageType.Members, STPackageStatus.create({
220
+ startDate: new Date(0),
221
+ }));
222
+ return map;
223
+ }
224
+
216
225
  const packages = await this.getForOrganizationIncludingExpired(organizationId);
217
226
 
218
- const map = new Map<STPackageType, STPackageStatus>();
219
227
  for (const pack of packages) {
220
228
  const exist = map.get(pack.meta.type);
221
229
  if (exist) {
222
230
  exist.merge(pack.createStatus());
223
- }
224
- else {
231
+ } else {
225
232
  map.set(pack.meta.type, pack.createStatus());
226
233
  }
227
234
  }
@@ -235,18 +242,40 @@ export class STPackageService {
235
242
 
236
243
  const organization = await Organization.getByID(organizationId);
237
244
  if (organization) {
238
- const didUseMembers = organization.meta.packages.useMembers && organization.meta.packages.useActivities;
239
245
  organization.meta.packages.packages = map;
240
246
  await organization.save();
247
+ } else {
248
+ console.error("Couldn't find organization when updating packages " + organizationId);
249
+ }
250
+ }
241
251
 
242
- if (!didUseMembers && organization.meta.packages.useMembers && organization.meta.packages.useActivities) {
243
- console.log('Building groups and categories for ' + organization.id);
244
- const builder = new GroupBuilder(organization);
245
- await builder.build();
246
- }
252
+ static async markFailedPayment(organizationId: string) {
253
+ console.log('Marking packages with failed payment for ' + organizationId);
254
+
255
+ // Mark all active packages as failed
256
+ const activePackages = await this.getActivePackages(organizationId);
257
+ for (const pack of activePackages) {
258
+ console.log('Marking package with failed payment ' + pack.id);
259
+ pack.meta.firstFailedPayment = pack.meta.firstFailedPayment ?? new Date();
260
+ pack.meta.paymentFailedCount++;
261
+ await pack.save();
247
262
  }
248
- else {
249
- console.error("Couldn't find organization when updating packages " + organizationId);
263
+ await this.updateOrganizationPackages(organizationId);
264
+ }
265
+
266
+ static async markBalanceRestored(organizationId: string) {
267
+ console.log('Marking packages as restored for ' + organizationId);
268
+
269
+ // Mark all active packages as failed
270
+ const activePackages = await this.getActivePackages(organizationId);
271
+ for (const pack of activePackages) {
272
+ if (pack.meta.firstFailedPayment || pack.meta.paymentFailedCount) {
273
+ console.log('Marking package restored ' + pack.id);
274
+ pack.meta.paymentFailedCount = 0;
275
+ pack.meta.firstFailedPayment = null;
276
+ await pack.save();
277
+ }
250
278
  }
279
+ await this.updateOrganizationPackages(organizationId);
251
280
  }
252
281
  }
@@ -0,0 +1,69 @@
1
+ import { Database } from '@simonbackx/simple-database';
2
+ import { logger, StyledText } from '@simonbackx/simple-logging';
3
+ import chalk from 'chalk';
4
+
5
+ const UNIQUE_KEY_PLATFORM = 'memberNumber';
6
+ const UNIQUE_KEY_ORGANIZATION = 'memberNumber_organizationId';
7
+
8
+ /**
9
+ * This service is responsible for creating MySQL unique constraints on boot depending on the environment configuration.
10
+ * If STAMHOOFD.userMode = 'platform' then we'll create a unique constraint on the memberNumber column of the member table.
11
+ * If 'organization' we will not create a unique constraint on memberNumber and organizationId (we did in the past)
12
+ * */
13
+ export class UniqueMemberNumberService {
14
+ static async check() {
15
+ await logger.setContext({
16
+ prefixes: [
17
+ new StyledText(`[UniqueMemberNumberService] `).addClass('unique-member-number-service', 'tag'),
18
+ ],
19
+ tags: ['unique-member-number-service'],
20
+ }, async () => {
21
+ if (STAMHOOFD.userMode === 'platform') {
22
+ if (await this.hasUniqueConstraint(UNIQUE_KEY_ORGANIZATION)) {
23
+ console.warn('Unique constraint for userMode organization exists but should be removed. Deleting it now...');
24
+ await this.dropConstraint(UNIQUE_KEY_ORGANIZATION);
25
+ }
26
+
27
+ if (!(await this.hasUniqueConstraint(UNIQUE_KEY_PLATFORM))) {
28
+ console.warn('Unique constraint for userMode platform is missing. Creating it now...');
29
+ await this.createConstraintForPlatform();
30
+ }
31
+ } else {
32
+ if (await this.hasUniqueConstraint(UNIQUE_KEY_PLATFORM)) {
33
+ console.warn('Unique constraint for userMode platform exists but should be removed. Deleting it now...');
34
+ await this.dropConstraint(UNIQUE_KEY_PLATFORM);
35
+ }
36
+
37
+ if (await this.hasUniqueConstraint(UNIQUE_KEY_ORGANIZATION)) {
38
+ console.warn('Unique constraint for userMode organization exists but should be removed. Deleting it now...');
39
+ await this.dropConstraint(UNIQUE_KEY_ORGANIZATION);
40
+ }
41
+ }
42
+ });
43
+ }
44
+
45
+ private static async hasUniqueConstraint(key: string) {
46
+ const [results] = await Database.select('SHOW INDEX FROM `members` WHERE Key_name = ?', [key]);
47
+ return results.length > 0;
48
+ }
49
+
50
+ private static async dropConstraint(key: string) {
51
+ try {
52
+ await Database.statement('ALTER TABLE `members` DROP INDEX `' + key + '`;');
53
+ console.log('Unique constraint dropped.');
54
+ } catch (e) {
55
+ console.error(chalk.red(`Failed to drop unique constraint "${key}" of members table:`));
56
+ console.error(e);
57
+ }
58
+ }
59
+
60
+ private static async createConstraintForPlatform() {
61
+ try {
62
+ await Database.statement('ALTER TABLE `members` ADD UNIQUE INDEX `' + UNIQUE_KEY_PLATFORM + '` (`memberNumber`) USING BTREE;');
63
+ console.log('Unique constraint created.');
64
+ } catch (e) {
65
+ console.error(chalk.red('Failed to create unique constraint on memberNumber column of members table:'));
66
+ console.error(e);
67
+ }
68
+ }
69
+ }
@@ -27,8 +27,7 @@ export class UniqueUserService {
27
27
  console.warn('Unique constraint is missing. Creating it now...');
28
28
  await this.createConstraint();
29
29
  }
30
- }
31
- else {
30
+ } else {
32
31
  if (await this.hasUniqueConstraint()) {
33
32
  console.warn('Unique constraint exists but should be removed. Deleting it now...');
34
33
  await this.dropConstraint();
@@ -41,8 +40,7 @@ export class UniqueUserService {
41
40
  try {
42
41
  await Database.statement('ALTER TABLE `users` ADD UNIQUE INDEX `' + UNIQUE_KEY_NAME + '` (`email`) USING BTREE;');
43
42
  console.log('Unique constraint created.');
44
- }
45
- catch (e) {
43
+ } catch (e) {
46
44
  console.error(chalk.red('Failed to create unique constraint on email column of users table:'));
47
45
  console.error(e);
48
46
  }
@@ -52,8 +50,7 @@ export class UniqueUserService {
52
50
  try {
53
51
  await Database.statement('ALTER TABLE `users` DROP INDEX `' + UNIQUE_KEY_NAME + '`;');
54
52
  console.log('Unique constraint dropped.');
55
- }
56
- catch (e) {
53
+ } catch (e) {
57
54
  console.error(chalk.red('Failed to drop unique constraint on email column of users table:'));
58
55
  console.error(e);
59
56
  }