@stamhoofd/models 2.63.0 → 2.65.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 (291) hide show
  1. package/dist/src/helpers/EmailBuilder.d.ts +6 -1
  2. package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
  3. package/dist/src/helpers/EmailBuilder.js +60 -20
  4. package/dist/src/helpers/EmailBuilder.js.map +1 -1
  5. package/dist/src/helpers/MemberMerger.d.ts.map +1 -1
  6. package/dist/src/helpers/MemberMerger.js +1 -2
  7. package/dist/src/helpers/MemberMerger.js.map +1 -1
  8. package/dist/src/migrations/1605262045-import-postcodes.d.ts +3 -3
  9. package/dist/src/migrations/1605262045-import-postcodes.js +10 -13
  10. package/dist/src/migrations/1734429094-registration-trial-until.sql +3 -0
  11. package/dist/src/migrations/1734429095-membership-trial-until.sql +2 -0
  12. package/dist/src/migrations/1734535120-registration-period-previous-period-id.sql +3 -0
  13. package/dist/src/migrations/1734535121-platform-previous-period-id.sql +3 -0
  14. package/dist/src/migrations/1734626607-cached-balance-amount-open.sql +2 -0
  15. package/dist/src/migrations/1734698906-cached-balance-amount-paid.sql +2 -0
  16. package/dist/src/migrations/1735573520-emails-email-type.sql +2 -0
  17. package/dist/src/migrations/1735573521-email-recipients-email-type.sql +4 -0
  18. package/dist/src/migrations/1735573522-emails-indexes.sql +3 -0
  19. package/dist/src/migrations/1735982691-cached-balance-email-reminder-counts.sql +4 -0
  20. package/dist/src/migrations/1735994471-default-email-templates.sql +5 -0
  21. package/dist/src/models/AuditLog.d.ts +2 -7
  22. package/dist/src/models/AuditLog.d.ts.map +1 -1
  23. package/dist/src/models/AuditLog.js +1 -15
  24. package/dist/src/models/AuditLog.js.map +1 -1
  25. package/dist/src/models/BalanceItem.d.ts +4 -13
  26. package/dist/src/models/BalanceItem.d.ts.map +1 -1
  27. package/dist/src/models/BalanceItem.js +21 -33
  28. package/dist/src/models/BalanceItem.js.map +1 -1
  29. package/dist/src/models/BalanceItemPayment.d.ts +3 -2
  30. package/dist/src/models/BalanceItemPayment.d.ts.map +1 -1
  31. package/dist/src/models/BalanceItemPayment.js +2 -1
  32. package/dist/src/models/BalanceItemPayment.js.map +1 -1
  33. package/dist/src/models/BuckarooPayment.d.ts +2 -2
  34. package/dist/src/models/BuckarooPayment.d.ts.map +1 -1
  35. package/dist/src/models/BuckarooPayment.js +2 -1
  36. package/dist/src/models/BuckarooPayment.js.map +1 -1
  37. package/dist/src/models/CachedBalance.d.ts +12 -10
  38. package/dist/src/models/CachedBalance.d.ts.map +1 -1
  39. package/dist/src/models/CachedBalance.js +122 -39
  40. package/dist/src/models/CachedBalance.js.map +1 -1
  41. package/dist/src/models/Document.d.ts +3 -3
  42. package/dist/src/models/Document.d.ts.map +1 -1
  43. package/dist/src/models/Document.js +2 -1
  44. package/dist/src/models/Document.js.map +1 -1
  45. package/dist/src/models/DocumentTemplate.d.ts +2 -2
  46. package/dist/src/models/DocumentTemplate.d.ts.map +1 -1
  47. package/dist/src/models/DocumentTemplate.js +38 -10
  48. package/dist/src/models/DocumentTemplate.js.map +1 -1
  49. package/dist/src/models/Email.d.ts +10 -4
  50. package/dist/src/models/Email.d.ts.map +1 -1
  51. package/dist/src/models/Email.js +68 -25
  52. package/dist/src/models/Email.js.map +1 -1
  53. package/dist/src/models/EmailRecipient.d.ts +14 -8
  54. package/dist/src/models/EmailRecipient.d.ts.map +1 -1
  55. package/dist/src/models/EmailRecipient.js +19 -14
  56. package/dist/src/models/EmailRecipient.js.map +1 -1
  57. package/dist/src/models/EmailTemplate.d.ts +2 -7
  58. package/dist/src/models/EmailTemplate.d.ts.map +1 -1
  59. package/dist/src/models/EmailTemplate.js +1 -15
  60. package/dist/src/models/EmailTemplate.js.map +1 -1
  61. package/dist/src/models/EmailVerificationCode.d.ts +2 -2
  62. package/dist/src/models/EmailVerificationCode.d.ts.map +1 -1
  63. package/dist/src/models/EmailVerificationCode.js +2 -1
  64. package/dist/src/models/EmailVerificationCode.js.map +1 -1
  65. package/dist/src/models/Event.d.ts +2 -2
  66. package/dist/src/models/Event.d.ts.map +1 -1
  67. package/dist/src/models/Event.js +2 -1
  68. package/dist/src/models/Event.js.map +1 -1
  69. package/dist/src/models/Group.d.ts +2 -7
  70. package/dist/src/models/Group.d.ts.map +1 -1
  71. package/dist/src/models/Group.js +1 -17
  72. package/dist/src/models/Group.js.map +1 -1
  73. package/dist/src/models/Image.d.ts +2 -2
  74. package/dist/src/models/Image.d.ts.map +1 -1
  75. package/dist/src/models/Image.js +2 -1
  76. package/dist/src/models/Image.js.map +1 -1
  77. package/dist/src/models/Member.d.ts +6 -9
  78. package/dist/src/models/Member.d.ts.map +1 -1
  79. package/dist/src/models/Member.js +3 -42
  80. package/dist/src/models/Member.js.map +1 -1
  81. package/dist/src/models/MemberPlatformMembership.d.ts +12 -8
  82. package/dist/src/models/MemberPlatformMembership.d.ts.map +1 -1
  83. package/dist/src/models/MemberPlatformMembership.js +80 -20
  84. package/dist/src/models/MemberPlatformMembership.js.map +1 -1
  85. package/dist/src/models/MemberResponsibilityRecord.d.ts +2 -7
  86. package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -1
  87. package/dist/src/models/MemberResponsibilityRecord.js +1 -15
  88. package/dist/src/models/MemberResponsibilityRecord.js.map +1 -1
  89. package/dist/src/models/MemberUser.d.ts +8 -0
  90. package/dist/src/models/MemberUser.d.ts.map +1 -0
  91. package/dist/src/models/MemberUser.js +26 -0
  92. package/dist/src/models/MemberUser.js.map +1 -0
  93. package/dist/src/models/MergedMember.d.ts +3 -3
  94. package/dist/src/models/MergedMember.d.ts.map +1 -1
  95. package/dist/src/models/MergedMember.js +3 -2
  96. package/dist/src/models/MergedMember.js.map +1 -1
  97. package/dist/src/models/MolliePayment.d.ts +2 -2
  98. package/dist/src/models/MolliePayment.d.ts.map +1 -1
  99. package/dist/src/models/MolliePayment.js +2 -1
  100. package/dist/src/models/MolliePayment.js.map +1 -1
  101. package/dist/src/models/MollieToken.d.ts +2 -2
  102. package/dist/src/models/MollieToken.d.ts.map +1 -1
  103. package/dist/src/models/MollieToken.js +2 -1
  104. package/dist/src/models/MollieToken.js.map +1 -1
  105. package/dist/src/models/OneTimeToken.d.ts +2 -2
  106. package/dist/src/models/OneTimeToken.d.ts.map +1 -1
  107. package/dist/src/models/OneTimeToken.js +2 -1
  108. package/dist/src/models/OneTimeToken.js.map +1 -1
  109. package/dist/src/models/Order.d.ts +3 -2
  110. package/dist/src/models/Order.d.ts.map +1 -1
  111. package/dist/src/models/Order.js +2 -1
  112. package/dist/src/models/Order.js.map +1 -1
  113. package/dist/src/models/Organization.d.ts +2 -2
  114. package/dist/src/models/Organization.d.ts.map +1 -1
  115. package/dist/src/models/Organization.js +2 -1
  116. package/dist/src/models/Organization.js.map +1 -1
  117. package/dist/src/models/OrganizationRegistrationPeriod.d.ts +2 -2
  118. package/dist/src/models/OrganizationRegistrationPeriod.d.ts.map +1 -1
  119. package/dist/src/models/OrganizationRegistrationPeriod.js +2 -1
  120. package/dist/src/models/OrganizationRegistrationPeriod.js.map +1 -1
  121. package/dist/src/models/PasswordToken.d.ts +3 -2
  122. package/dist/src/models/PasswordToken.d.ts.map +1 -1
  123. package/dist/src/models/PasswordToken.js +2 -1
  124. package/dist/src/models/PasswordToken.js.map +1 -1
  125. package/dist/src/models/PayconiqPayment.d.ts +2 -2
  126. package/dist/src/models/PayconiqPayment.d.ts.map +1 -1
  127. package/dist/src/models/PayconiqPayment.js +2 -1
  128. package/dist/src/models/PayconiqPayment.js.map +1 -1
  129. package/dist/src/models/Payment.d.ts +2 -7
  130. package/dist/src/models/Payment.d.ts.map +1 -1
  131. package/dist/src/models/Payment.js +1 -15
  132. package/dist/src/models/Payment.js.map +1 -1
  133. package/dist/src/models/Platform.d.ts +5 -3
  134. package/dist/src/models/Platform.d.ts.map +1 -1
  135. package/dist/src/models/Platform.js +11 -2
  136. package/dist/src/models/Platform.js.map +1 -1
  137. package/dist/src/models/RegisterCode.d.ts +2 -2
  138. package/dist/src/models/RegisterCode.d.ts.map +1 -1
  139. package/dist/src/models/RegisterCode.js +2 -1
  140. package/dist/src/models/RegisterCode.js.map +1 -1
  141. package/dist/src/models/Registration.d.ts +20 -7
  142. package/dist/src/models/Registration.d.ts.map +1 -1
  143. package/dist/src/models/Registration.js +25 -61
  144. package/dist/src/models/Registration.js.map +1 -1
  145. package/dist/src/models/RegistrationPeriod.d.ts +4 -2
  146. package/dist/src/models/RegistrationPeriod.d.ts.map +1 -1
  147. package/dist/src/models/RegistrationPeriod.js +23 -1
  148. package/dist/src/models/RegistrationPeriod.js.map +1 -1
  149. package/dist/src/models/STCredit.d.ts +2 -2
  150. package/dist/src/models/STCredit.d.ts.map +1 -1
  151. package/dist/src/models/STCredit.js +2 -1
  152. package/dist/src/models/STCredit.js.map +1 -1
  153. package/dist/src/models/STInvoice.d.ts +3 -2
  154. package/dist/src/models/STInvoice.d.ts.map +1 -1
  155. package/dist/src/models/STInvoice.js +2 -1
  156. package/dist/src/models/STInvoice.js.map +1 -1
  157. package/dist/src/models/STPackage.d.ts +2 -2
  158. package/dist/src/models/STPackage.d.ts.map +1 -1
  159. package/dist/src/models/STPackage.js +2 -1
  160. package/dist/src/models/STPackage.js.map +1 -1
  161. package/dist/src/models/STPendingInvoice.d.ts +3 -2
  162. package/dist/src/models/STPendingInvoice.d.ts.map +1 -1
  163. package/dist/src/models/STPendingInvoice.js +2 -1
  164. package/dist/src/models/STPendingInvoice.js.map +1 -1
  165. package/dist/src/models/StripeAccount.d.ts +2 -2
  166. package/dist/src/models/StripeAccount.d.ts.map +1 -1
  167. package/dist/src/models/StripeAccount.js +2 -1
  168. package/dist/src/models/StripeAccount.js.map +1 -1
  169. package/dist/src/models/StripeCheckoutSession.d.ts +2 -2
  170. package/dist/src/models/StripeCheckoutSession.d.ts.map +1 -1
  171. package/dist/src/models/StripeCheckoutSession.js +2 -1
  172. package/dist/src/models/StripeCheckoutSession.js.map +1 -1
  173. package/dist/src/models/StripePaymentIntent.d.ts +2 -2
  174. package/dist/src/models/StripePaymentIntent.d.ts.map +1 -1
  175. package/dist/src/models/StripePaymentIntent.js +2 -1
  176. package/dist/src/models/StripePaymentIntent.js.map +1 -1
  177. package/dist/src/models/Ticket.d.ts +3 -2
  178. package/dist/src/models/Ticket.d.ts.map +1 -1
  179. package/dist/src/models/Ticket.js +2 -1
  180. package/dist/src/models/Ticket.js.map +1 -1
  181. package/dist/src/models/Token.d.ts +3 -2
  182. package/dist/src/models/Token.d.ts.map +1 -1
  183. package/dist/src/models/Token.js +2 -1
  184. package/dist/src/models/Token.js.map +1 -1
  185. package/dist/src/models/UsedRegisterCode.d.ts +2 -2
  186. package/dist/src/models/UsedRegisterCode.d.ts.map +1 -1
  187. package/dist/src/models/UsedRegisterCode.js +2 -1
  188. package/dist/src/models/UsedRegisterCode.js.map +1 -1
  189. package/dist/src/models/User.d.ts +7 -2
  190. package/dist/src/models/User.d.ts.map +1 -1
  191. package/dist/src/models/User.js +27 -4
  192. package/dist/src/models/User.js.map +1 -1
  193. package/dist/src/models/UserPermissions.d.ts +3 -2
  194. package/dist/src/models/UserPermissions.d.ts.map +1 -1
  195. package/dist/src/models/UserPermissions.js +2 -1
  196. package/dist/src/models/UserPermissions.js.map +1 -1
  197. package/dist/src/models/Webshop.d.ts +3 -2
  198. package/dist/src/models/Webshop.d.ts.map +1 -1
  199. package/dist/src/models/Webshop.js +2 -1
  200. package/dist/src/models/Webshop.js.map +1 -1
  201. package/dist/src/models/WebshopDiscountCode.d.ts +2 -2
  202. package/dist/src/models/WebshopDiscountCode.d.ts.map +1 -1
  203. package/dist/src/models/WebshopDiscountCode.js +2 -1
  204. package/dist/src/models/WebshopDiscountCode.js.map +1 -1
  205. package/dist/src/models/addresses/City.d.ts +3 -2
  206. package/dist/src/models/addresses/City.d.ts.map +1 -1
  207. package/dist/src/models/addresses/City.js +2 -1
  208. package/dist/src/models/addresses/City.js.map +1 -1
  209. package/dist/src/models/addresses/PostalCode.d.ts +3 -2
  210. package/dist/src/models/addresses/PostalCode.d.ts.map +1 -1
  211. package/dist/src/models/addresses/PostalCode.js +2 -1
  212. package/dist/src/models/addresses/PostalCode.js.map +1 -1
  213. package/dist/src/models/addresses/Province.d.ts +2 -2
  214. package/dist/src/models/addresses/Province.d.ts.map +1 -1
  215. package/dist/src/models/addresses/Province.js +2 -1
  216. package/dist/src/models/addresses/Province.js.map +1 -1
  217. package/dist/src/models/addresses/Street.d.ts +3 -2
  218. package/dist/src/models/addresses/Street.d.ts.map +1 -1
  219. package/dist/src/models/addresses/Street.js +2 -1
  220. package/dist/src/models/addresses/Street.js.map +1 -1
  221. package/dist/src/models/index.d.ts +1 -0
  222. package/dist/src/models/index.d.ts.map +1 -1
  223. package/dist/src/models/index.js +1 -0
  224. package/dist/src/models/index.js.map +1 -1
  225. package/dist/tsconfig.tsbuildinfo +1 -1
  226. package/package.json +2 -2
  227. package/src/helpers/EmailBuilder.ts +82 -27
  228. package/src/helpers/MemberMerger.ts +2 -3
  229. package/src/migrations/1605262045-import-postcodes.ts +6 -9
  230. package/src/migrations/1734429094-registration-trial-until.sql +3 -0
  231. package/src/migrations/1734429095-membership-trial-until.sql +2 -0
  232. package/src/migrations/1734535120-registration-period-previous-period-id.sql +3 -0
  233. package/src/migrations/1734535121-platform-previous-period-id.sql +3 -0
  234. package/src/migrations/1734626607-cached-balance-amount-open.sql +2 -0
  235. package/src/migrations/1734698906-cached-balance-amount-paid.sql +2 -0
  236. package/src/migrations/1735573520-emails-email-type.sql +2 -0
  237. package/src/migrations/1735573521-email-recipients-email-type.sql +4 -0
  238. package/src/migrations/1735573522-emails-indexes.sql +3 -0
  239. package/src/migrations/1735982691-cached-balance-email-reminder-counts.sql +4 -0
  240. package/src/migrations/1735994471-default-email-templates.sql +5 -0
  241. package/src/models/AuditLog.ts +3 -21
  242. package/src/models/BalanceItem.ts +30 -46
  243. package/src/models/BalanceItemPayment.ts +3 -2
  244. package/src/models/BuckarooPayment.ts +3 -2
  245. package/src/models/CachedBalance.ts +166 -46
  246. package/src/models/Document.ts +4 -3
  247. package/src/models/DocumentTemplate.ts +43 -12
  248. package/src/models/Email.ts +80 -32
  249. package/src/models/EmailRecipient.ts +20 -20
  250. package/src/models/EmailTemplate.ts +3 -21
  251. package/src/models/EmailVerificationCode.ts +3 -2
  252. package/src/models/Event.ts +3 -2
  253. package/src/models/Group.ts +4 -23
  254. package/src/models/Image.ts +3 -2
  255. package/src/models/Member.ts +6 -52
  256. package/src/models/MemberPlatformMembership.ts +95 -26
  257. package/src/models/MemberResponsibilityRecord.ts +3 -21
  258. package/src/models/MemberUser.ts +18 -0
  259. package/src/models/MergedMember.ts +4 -3
  260. package/src/models/MolliePayment.ts +3 -2
  261. package/src/models/MollieToken.ts +3 -2
  262. package/src/models/OneTimeToken.ts +3 -2
  263. package/src/models/Order.ts +3 -2
  264. package/src/models/Organization.ts +3 -2
  265. package/src/models/OrganizationRegistrationPeriod.ts +3 -2
  266. package/src/models/PasswordToken.ts +3 -2
  267. package/src/models/PayconiqPayment.ts +3 -2
  268. package/src/models/Payment.ts +3 -21
  269. package/src/models/Platform.ts +13 -4
  270. package/src/models/RegisterCode.ts +3 -2
  271. package/src/models/Registration.ts +24 -68
  272. package/src/models/RegistrationPeriod.ts +30 -3
  273. package/src/models/STCredit.ts +3 -2
  274. package/src/models/STInvoice.ts +3 -2
  275. package/src/models/STPackage.ts +3 -2
  276. package/src/models/STPendingInvoice.ts +3 -2
  277. package/src/models/StripeAccount.ts +3 -2
  278. package/src/models/StripeCheckoutSession.ts +3 -2
  279. package/src/models/StripePaymentIntent.ts +3 -2
  280. package/src/models/Ticket.ts +3 -2
  281. package/src/models/Token.ts +3 -2
  282. package/src/models/UsedRegisterCode.ts +3 -2
  283. package/src/models/User.ts +31 -3
  284. package/src/models/UserPermissions.ts +3 -2
  285. package/src/models/Webshop.ts +3 -2
  286. package/src/models/WebshopDiscountCode.ts +3 -2
  287. package/src/models/addresses/City.ts +3 -2
  288. package/src/models/addresses/PostalCode.ts +3 -2
  289. package/src/models/addresses/Province.ts +3 -2
  290. package/src/models/addresses/Street.ts +3 -2
  291. package/src/models/index.ts +1 -0
@@ -1,9 +1,9 @@
1
1
  import { Email, EmailAddress, EmailBuilder } from '@stamhoofd/email';
2
- import { EmailTemplateType, OrganizationEmail, Recipient, Replacement } from '@stamhoofd/structures';
2
+ import { Platform as PlatformStruct, BalanceItem as BalanceItemStruct, EmailTemplateType, OrganizationEmail, ReceivableBalanceType, Recipient, Replacement } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
4
 
5
5
  import { SimpleError } from '@simonbackx/simple-errors';
6
- import { EmailTemplate, Group, Organization, Platform, User, Webshop } from '../models';
6
+ import { CachedBalance, EmailTemplate, Group, Organization, Platform, User, Webshop } from '../models';
7
7
  import { I18n } from '@stamhoofd/backend-i18n';
8
8
 
9
9
  export type EmailTemplateOptions = {
@@ -238,7 +238,7 @@ export function replaceText(text: string, replacements: Replacement[]) {
238
238
  * @param organization defines replacements and unsubsribe behaviour
239
239
  */
240
240
  export async function getEmailBuilder(organization: Organization | null, email: EmailBuilderOptions) {
241
- const platform = await Platform.getSharedStruct();
241
+ const platform = await Platform.getSharedPrivateStruct();
242
242
  // Update recipients
243
243
  const cleaned: Recipient[] = [];
244
244
  for (const recipient of email.recipients) {
@@ -305,38 +305,21 @@ export async function getEmailBuilder(organization: Organization | null, email:
305
305
  }
306
306
  email.recipients = cleaned;
307
307
 
308
+ const fromAddress = Email.parseEmailStr(email.from)[0];
309
+
308
310
  // Update recipients
309
311
  for (const recipient of email.recipients) {
310
312
  recipient.replacements = recipient.replacements.slice();
311
313
 
312
- // Default signInUrl
313
- let signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization.getHost() : STAMHOOFD.domains.dashboard) + '/login?email=' + encodeURIComponent(recipient.email);
314
-
315
- const recipientUser = await User.getForAuthentication(organization?.id ?? null, recipient.email);
316
- if (!recipientUser) {
317
- // We can create a special token
318
- signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization.getHost() : STAMHOOFD.domains.dashboard) + '/account-aanmaken?email=' + encodeURIComponent(recipient.email);
319
- }
320
-
321
- recipient.replacements.push(Replacement.create({
322
- token: 'signInUrl',
323
- value: signInUrl,
324
- }));
325
-
326
314
  if (email.defaultReplacements) {
327
315
  recipient.replacements.push(...email.defaultReplacements);
328
316
  }
329
317
 
330
- recipient.replacements.push(...recipient.getDefaultReplacements());
331
-
332
- if (organization) {
333
- const extra = organization.meta.getEmailReplacements();
334
- recipient.replacements.push(...extra);
335
- }
336
-
337
- // Defaults
338
- const extra = platform.config.getEmailReplacements();
339
- recipient.replacements.push(...extra);
318
+ await fillRecipientReplacements(recipient, {
319
+ organization,
320
+ platform,
321
+ fromAddress,
322
+ });
340
323
  }
341
324
 
342
325
  const queue = email.recipients.slice();
@@ -380,3 +363,75 @@ export async function getEmailBuilder(organization: Organization | null, email:
380
363
  };
381
364
  return builder;
382
365
  }
366
+
367
+ export async function fillRecipientReplacements(recipient: Recipient, options: {
368
+ organization: Organization | null;
369
+ platform?: PlatformStruct;
370
+ fromAddress: string | null;
371
+ }) {
372
+ if (!options.platform) {
373
+ options.platform = await Platform.getSharedPrivateStruct();
374
+ }
375
+ const { organization, platform, fromAddress } = options;
376
+ recipient.replacements = recipient.replacements.slice();
377
+
378
+ // Default signInUrl
379
+ let signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization.getHost() : STAMHOOFD.domains.dashboard) + '/login?email=' + encodeURIComponent(recipient.email);
380
+
381
+ const recipientUser = await User.getForAuthentication(organization?.id ?? null, recipient.email, { allowWithoutAccount: true });
382
+ if (!recipientUser || !recipientUser.hasAccount()) {
383
+ // We can create a special token
384
+ signInUrl = 'https://' + (organization && STAMHOOFD.userMode === 'organization' ? organization.getHost() : STAMHOOFD.domains.dashboard) + '/account-aanmaken?email=' + encodeURIComponent(recipient.email);
385
+ }
386
+
387
+ recipient.replacements.push(Replacement.create({
388
+ token: 'signInUrl',
389
+ value: signInUrl,
390
+ }));
391
+
392
+ recipient.replacements.push(
393
+ Replacement.create({
394
+ token: 'loginDetails',
395
+ value: '',
396
+ html: recipientUser && recipientUser.hasAccount() ? `<p class="description"><em>Je kan op het ledenportaal inloggen met <strong>${Formatter.escapeHtml(recipientUser.email)}</strong></em></p>` : `<p class="description"><em>Je kan op het ledenportaal een nieuw account aanmaken met het e-mailadres <strong>${Formatter.escapeHtml(recipient.email)}</strong>, dan krijg je automatisch toegang tot alle bestaande gegevens.</em></p>`,
397
+ }),
398
+ );
399
+
400
+ // Load balance of this user
401
+ // todo: only if detected it is used
402
+ if (organization && recipientUser && !recipient.replacements.find(r => r.token === 'balanceTable')) {
403
+ const balanceItemModels = await CachedBalance.balanceForObjects(organization.id, [recipientUser.id], ReceivableBalanceType.user, true);
404
+ const balanceItems = balanceItemModels.map(i => i.getStructure());
405
+
406
+ // Get members
407
+ recipient.replacements.push(
408
+ Replacement.create({
409
+ token: 'outstandingBalance',
410
+ value: Formatter.price(balanceItems.reduce((sum, i) => sum + i.priceOpen, 0)),
411
+ }),
412
+ Replacement.create({
413
+ token: 'balanceTable',
414
+ value: '',
415
+ html: BalanceItemStruct.getDetailsHTMLTable(balanceItems),
416
+ }),
417
+ );
418
+ }
419
+
420
+ if (fromAddress) {
421
+ recipient.replacements.push(Replacement.create({
422
+ token: 'fromAddress',
423
+ value: fromAddress,
424
+ }));
425
+ }
426
+
427
+ recipient.replacements.push(...recipient.getDefaultReplacements());
428
+
429
+ if (organization) {
430
+ const extra = organization.meta.getEmailReplacements(organization);
431
+ recipient.replacements.push(...extra);
432
+ }
433
+
434
+ // Defaults
435
+ const extra = platform.config.getEmailReplacements();
436
+ recipient.replacements.push(...extra);
437
+ }
@@ -1,5 +1,4 @@
1
- import { Model } from '@simonbackx/simple-database';
2
- import { SQL } from '@stamhoofd/sql';
1
+ import { QueryableModel, SQL } from '@stamhoofd/sql';
3
2
  import {
4
3
  Address,
5
4
  BooleanStatus,
@@ -191,7 +190,7 @@ async function mergeMemberPlatformMemberships(base: Member, other: Member) {
191
190
  await mergeModels(base, other, MemberPlatformMembership);
192
191
  }
193
192
 
194
- class ModelWithMemberId extends Model {
193
+ class ModelWithMemberId extends QueryableModel {
195
194
  memberId: string | null;
196
195
  }
197
196
 
@@ -1,21 +1,18 @@
1
- import { Migration } from '@simonbackx/simple-database';
2
- import { column, Model } from '@simonbackx/simple-database';
3
- import { StringCompare } from '@stamhoofd/utility';
4
- import { v4 as uuidv4 } from 'uuid';
1
+ import { column, Migration } from '@simonbackx/simple-database';
2
+ import { QueryableModel } from '@stamhoofd/sql';
5
3
  import { Country } from '@stamhoofd/structures';
4
+ import { StringCompare } from '@stamhoofd/utility';
6
5
  import { City } from '../models/addresses/City';
7
6
  import { PostalCode } from '../models/addresses/PostalCode';
8
7
  import { Province } from '../models/addresses/Province';
9
8
 
10
- export class Gemeente extends Model {
9
+ export class Gemeente extends QueryableModel {
11
10
  static table = 'gemeenten';
12
11
 
13
12
  @column({
14
- primary: true, type: 'string', beforeSave(value) {
15
- return value ?? uuidv4();
16
- },
13
+ primary: true, type: 'integer',
17
14
  })
18
- id!: string;
15
+ id!: number;
19
16
 
20
17
  @column({ type: 'string' })
21
18
  postcode: string;
@@ -0,0 +1,3 @@
1
+ ALTER TABLE `registrations`
2
+ ADD COLUMN `trialUntil` datetime NULL AFTER `registeredAt`,
3
+ ADD COLUMN `startDate` datetime NULL AFTER `registeredAt`;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `member_platform_memberships`
2
+ ADD COLUMN `trialUntil` datetime NULL AFTER `startDate`;
@@ -0,0 +1,3 @@
1
+ ALTER TABLE `registration_periods`
2
+ ADD COLUMN `previousPeriodId` varchar(36) NULL,
3
+ ADD FOREIGN KEY (`previousPeriodId`) REFERENCES `registration_periods` (`id`) ON UPDATE CASCADE ON DELETE SET NULL;
@@ -0,0 +1,3 @@
1
+ ALTER TABLE `platform`
2
+ ADD COLUMN `previousPeriodId` varchar(36) NULL AFTER `periodId`,
3
+ ADD FOREIGN KEY (`previousPeriodId`) REFERENCES `registration_periods` (`id`) ON UPDATE CASCADE ON DELETE SET NULL;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `cached_outstanding_balances`
2
+ CHANGE `amount` `amountOpen` int NOT NULL DEFAULT '0';
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `cached_outstanding_balances`
2
+ ADD COLUMN `amountPaid` int NOT NULL DEFAULT '0' AFTER `organizationId`;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `emails`
2
+ ADD COLUMN `emailType` varchar(255) NULL AFTER `recipientFilter`;
@@ -0,0 +1,4 @@
1
+ ALTER TABLE `email_recipients`
2
+ ADD COLUMN `emailType` varchar(255) NULL AFTER `emailId`,
3
+ ADD COLUMN `objectId` varchar(36) NULL AFTER `emailId`,
4
+ ADD INDEX `objectId` (`objectId`, `sentAt` DESC) USING BTREE;
@@ -0,0 +1,3 @@
1
+ ALTER TABLE `emails`
2
+ ADD INDEX `createdAt` (`createdAt` DESC) USING BTREE,
3
+ ADD INDEX `sentAt` (`sentAt` DESC) USING BTREE;
@@ -0,0 +1,4 @@
1
+ ALTER TABLE `cached_outstanding_balances`
2
+ ADD COLUMN `lastReminderEmail` datetime NULL,
3
+ ADD COLUMN `lastReminderAmountOpen` int NOT NULL DEFAULT '0',
4
+ ADD COLUMN `reminderEmailCount` int NOT NULL DEFAULT '0';
@@ -0,0 +1,5 @@
1
+ INSERT INTO `email_templates` (`id`, `subject`, `groupId`, `webshopId`, `organizationId`, `type`, `text`, `html`, `json`, `updatedAt`, `createdAt`) VALUES
2
+ ('8aec66ec-a39c-4849-9ebf-973e489f8017', 'Jouw openstaand saldo bij {{organizationName}}', NULL, NULL, NULL, 'OrganizationBalanceIncreaseNotification', '{{greeting}}Jouw groep ({{objectName}}) heeft een nieuw openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het beheerdersportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het beheerdersportaal.\n\n \n \n \n \n Betalen via het beheerdersportaal\n \n \n \n \n\nTwijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het beheerdersportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.Vragen over je openstaand bedrag? Neem dan contact op met {{organizationName}} via {{fromAddress}}OverzichtDit is een momentopname van jullie huidige openstaande bedrag. Je kan op elk moment naar het beheerdersportaal gaan om de huidige status te bekijken en te betalen.{{balanceTable}}Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via deze knop — hierna zal je nooit meer een e-mail van ons ontvangen.Uitschrijven', '<!DOCTYPE html>\n<html>\n\n<head>\n<meta charset=\"utf-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n<title>Jouw openstaand saldo bij {{organizationName}}</title>\n<style type=\"text/css\">body {\n color: #000716;\n color: var(--color-dark, #000716);\n font-family: -apple-system-body, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-size: 12pt;\n line-height: 1.4;\n}\n\np {\n margin: 0;\n padding: 0;\n line-height: 1.4;\n}\n\np.description {\n color: var(--color-gray-4, #5e5e5e);\n}\np.description a, p.description a:link, p.description a:visited, p.description a:active, p.description a:hover {\n text-decoration: underline;\n color: var(--color-gray-4, #5e5e5e);\n}\n\nstrong {\n font-weight: bold;\n}\n\nem {\n font-style: italic;\n}\n\nh1 {\n font-size: 30px;\n font-weight: bold;\n line-height: 1.2;\n margin: 0;\n padding: 0;\n}\n@media (max-width: 350px) {\n h1 {\n font-size: 24px;\n }\n}\n\nh2 {\n font-size: 20px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh3 {\n font-size: 16px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh4 {\n line-height: 1.2;\n font-weight: 500;\n margin: 0;\n padding: 0;\n}\n\nol, ul {\n list-style-position: outside;\n padding-left: 30px;\n}\n\nhr {\n height: 1px;\n background: var(--color-border, var(--color-gray-2, #dcdcdc));\n border-radius: 1px;\n padding: 0;\n margin: 20px 0;\n outline: none;\n border: 0;\n}\n\n.button {\n touch-action: inherit;\n user-select: auto;\n cursor: pointer;\n display: inline-block !important;\n line-height: 42px;\n font-size: 16px;\n font-weight: bold;\n}\n.button:active {\n transform: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n}\n\na, a:link, a:visited, a:active, a:hover {\n text-decoration: underline;\n color: blue;\n}\n\n.email-data-table {\n width: 100%;\n border-collapse: collapse;\n}\n.email-data-table th, .email-data-table td {\n text-align: left;\n padding: 10px 10px 10px 0;\n border-bottom: 1px solid var(--color-border, var(--color-gray-2, #dcdcdc));\n vertical-align: middle;\n}\n.email-data-table th:last-child, .email-data-table td:last-child {\n text-align: right;\n padding-right: 0;\n}\n.email-data-table thead {\n font-weight: bold;\n}\n.email-data-table thead th {\n font-size: 10pt;\n}\n.email-data-table h4 ~ p {\n padding-top: 3px;\n opacity: 0.8;\n font-size: 11pt;\n}\n\n.email-style-inline-code {\n font-family: monospace;\n white-space: pre-wrap;\n display: inline-block;\n}\n\n.email-style-description-small {\n font-size: 14px;\n line-height: 1.5;\n font-weight: normal;\n color: var(--color-gray-4, #5e5e5e);\n font-variation-settings: \"opsz\" 19;\n}\n\n.email-style-title-list {\n font-size: 16px;\n line-height: 1.3;\n font-weight: 500;\n}\n.email-style-title-list + p {\n padding-top: 3px;\n}\n\n.email-style-title-prefix-list {\n font-size: 11px;\n line-height: 1.5;\n font-weight: bold;\n color: {{primaryColor}};\n text-transform: uppercase;\n margin-bottom: 3px;\n}\n.email-style-title-prefix-list.error {\n color: #f0153d;\n}\n\n.email-style-price-base, .email-style-discount-price, .email-style-discount-old-price, .email-style-price {\n font-size: 15px;\n line-height: 1.4;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n}\n.email-style-price-base.disabled, .disabled.email-style-discount-price, .disabled.email-style-discount-old-price, .disabled.email-style-price {\n opacity: 0.6;\n}\n.email-style-price-base.negative, .negative.email-style-discount-price, .negative.email-style-discount-old-price, .negative.email-style-price {\n color: #f0153d;\n}\n\n.email-style-price {\n font-weight: bold;\n color: {{primaryColor}};\n}\n\n.email-style-discount-old-price {\n text-decoration: line-through;\n color: var(--color-gray-4, #5e5e5e);\n}\n\n.email-style-discount-price {\n font-weight: bold;\n color: #ff4747;\n margin-left: 5px;\n}\n\n.pre-wrap {\n white-space: pre-wrap;\n} hr {height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;} .button.primary { margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity; } .button.primary:active { transform: scale(0.95, 0.95); } .inline-link, .inline-link:link, .inline-link:visited, .inline-link:active, .inline-link:hover { margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation; } .inline-link:active { opacity: 0.5; } .description { color: #5e5e5e; } </style>\n</head>\n\n<body>\n<p style=\"margin: 0; padding: 0; line-height: 1.4;\">{{greeting}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Jouw groep ({{objectName}}) heeft een nieuw openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het beheerdersportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><strong>Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het beheerdersportaal.</strong></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartButton\" data-id=\"paymentUrl\"><table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"margin: 5px 0;\">\n<tbody><tr>\n <td>\n <table cellspacing=\"0\" cellpadding=\"0\">\n <tbody><tr>\n <td style=\"border-radius: 7px;\" bgcolor=\"{{primaryColor}}\">\n <a class=\"button primary\" href=\"{{paymentUrl}}\" target=\"\" style=\"margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity;\">Betalen via het beheerdersportaal</a>\n </td>\n </tr>\n </tbody></table>\n </td>\n</tr>\n</tbody></table></div><p class=\"description\" style=\"color: #5e5e5e;\"><em>Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het beheerdersportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><strong><em>Vragen over je openstaand bedrag</em></strong><em>? Neem dan contact op met </em>{{organizationName}} <em>via </em>{{fromAddress}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><hr style=\"height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;\"><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><h2 style=\"margin: 0; padding: 0;\">Overzicht</h2><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Dit is een momentopname van jullie huidige openstaande bedrag. Je kan op elk moment naar het beheerdersportaal gaan om de huidige status te bekijken en te betalen.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartVariableBlock\" data-id=\"balanceTable\">{{balanceTable}}</div><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><em>Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via </em><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\"><em>deze knop</em></a><em> — hierna zal je nooit meer een e-mail van ons ontvangen.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\">Uitschrijven</a></p>\n</body>\n\n</html>', '{\"value\": {\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"smartVariable\", \"attrs\": {\"id\": \"greeting\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Jouw groep (\", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"objectName\"}}, {\"text\": \") heeft een nieuw openstaand bedrag van \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"outstandingBalance\"}}, {\"text\": \" bij \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \". Je kan de betaling in orde brengen door naar het beheerdersportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het beheerdersportaal.\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartButton\", \"attrs\": {\"id\": \"paymentUrl\"}, \"content\": [{\"text\": \"Betalen via het beheerdersportaal\", \"type\": \"text\"}]}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het beheerdersportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Vragen over je openstaand bedrag\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}, {\"type\": \"italic\"}]}, {\"text\": \"? Neem dan contact op met \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \" \", \"type\": \"text\"}, {\"text\": \"via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"fromAddress\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"horizontalRule\"}, {\"type\": \"paragraph\"}, {\"type\": \"heading\", \"attrs\": {\"level\": 2}, \"content\": [{\"text\": \"Overzicht\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Dit is een momentopname van jullie huidige openstaande bedrag. Je kan op elk moment naar het beheerdersportaal gaan om de huidige status te bekijken en te betalen.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartVariableBlock\", \"attrs\": {\"id\": \"balanceTable\"}}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"deze knop\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"text\": \" — hierna zal je nooit meer een e-mail van ons ontvangen.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\", \"content\": [{\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"Uitschrijven\", \"type\": \"text\"}]}]}]}, \"version\": 354}', '2025-01-04 12:09:03', '2025-01-04 12:09:03'),
3
+ ('01f3c594-43e1-45de-9ca9-e450befb4426', '[Herinnering] Openstaand bedrag te betalen aan {{organizationName}}', NULL, NULL, NULL, 'OrganizationBalanceReminder', '{{greeting}}Jouw groep ({{objectName}}) heeft nog steeds een openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het beheerdersportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het beheerdersportaal.\n\n \n \n \n \n Betalen via het beheerdersportaal\n \n \n \n \n\nTwijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het beheerdersportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.Vragen over je openstaand bedrag? Neem dan contact op met {{organizationName}} via {{fromAddress}}OverzichtDit is een momentopname van jullie huidige openstaande bedrag. Je kan op elk moment naar het beheerdersportaal gaan om de huidige status te bekijken en te betalen.{{balanceTable}}Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via deze knop — hierna zal je nooit meer een e-mail van ons ontvangen.Uitschrijven', '<!DOCTYPE html>\n<html>\n\n<head>\n<meta charset=\"utf-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n<title>[Herinnering] Openstaand bedrag te betalen aan {{organizationName}}</title>\n<style type=\"text/css\">body {\n color: #000716;\n color: var(--color-dark, #000716);\n font-family: -apple-system-body, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-size: 12pt;\n line-height: 1.4;\n}\n\np {\n margin: 0;\n padding: 0;\n line-height: 1.4;\n}\n\np.description {\n color: var(--color-gray-4, #5e5e5e);\n}\np.description a, p.description a:link, p.description a:visited, p.description a:active, p.description a:hover {\n text-decoration: underline;\n color: var(--color-gray-4, #5e5e5e);\n}\n\nstrong {\n font-weight: bold;\n}\n\nem {\n font-style: italic;\n}\n\nh1 {\n font-size: 30px;\n font-weight: bold;\n line-height: 1.2;\n margin: 0;\n padding: 0;\n}\n@media (max-width: 350px) {\n h1 {\n font-size: 24px;\n }\n}\n\nh2 {\n font-size: 20px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh3 {\n font-size: 16px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh4 {\n line-height: 1.2;\n font-weight: 500;\n margin: 0;\n padding: 0;\n}\n\nol, ul {\n list-style-position: outside;\n padding-left: 30px;\n}\n\nhr {\n height: 1px;\n background: var(--color-border, var(--color-gray-2, #dcdcdc));\n border-radius: 1px;\n padding: 0;\n margin: 20px 0;\n outline: none;\n border: 0;\n}\n\n.button {\n touch-action: inherit;\n user-select: auto;\n cursor: pointer;\n display: inline-block !important;\n line-height: 42px;\n font-size: 16px;\n font-weight: bold;\n}\n.button:active {\n transform: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n}\n\na, a:link, a:visited, a:active, a:hover {\n text-decoration: underline;\n color: blue;\n}\n\n.email-data-table {\n width: 100%;\n border-collapse: collapse;\n}\n.email-data-table th, .email-data-table td {\n text-align: left;\n padding: 10px 10px 10px 0;\n border-bottom: 1px solid var(--color-border, var(--color-gray-2, #dcdcdc));\n vertical-align: middle;\n}\n.email-data-table th:last-child, .email-data-table td:last-child {\n text-align: right;\n padding-right: 0;\n}\n.email-data-table thead {\n font-weight: bold;\n}\n.email-data-table thead th {\n font-size: 10pt;\n}\n.email-data-table h4 ~ p {\n padding-top: 3px;\n opacity: 0.8;\n font-size: 11pt;\n}\n\n.email-style-inline-code {\n font-family: monospace;\n white-space: pre-wrap;\n display: inline-block;\n}\n\n.email-style-description-small {\n font-size: 14px;\n line-height: 1.5;\n font-weight: normal;\n color: var(--color-gray-4, #5e5e5e);\n font-variation-settings: \"opsz\" 19;\n}\n\n.email-style-title-list {\n font-size: 16px;\n line-height: 1.3;\n font-weight: 500;\n}\n.email-style-title-list + p {\n padding-top: 3px;\n}\n\n.email-style-title-prefix-list {\n font-size: 11px;\n line-height: 1.5;\n font-weight: bold;\n color: {{primaryColor}};\n text-transform: uppercase;\n margin-bottom: 3px;\n}\n.email-style-title-prefix-list.error {\n color: #f0153d;\n}\n\n.email-style-price-base, .email-style-discount-price, .email-style-discount-old-price, .email-style-price {\n font-size: 15px;\n line-height: 1.4;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n}\n.email-style-price-base.disabled, .disabled.email-style-discount-price, .disabled.email-style-discount-old-price, .disabled.email-style-price {\n opacity: 0.6;\n}\n.email-style-price-base.negative, .negative.email-style-discount-price, .negative.email-style-discount-old-price, .negative.email-style-price {\n color: #f0153d;\n}\n\n.email-style-price {\n font-weight: bold;\n color: {{primaryColor}};\n}\n\n.email-style-discount-old-price {\n text-decoration: line-through;\n color: var(--color-gray-4, #5e5e5e);\n}\n\n.email-style-discount-price {\n font-weight: bold;\n color: #ff4747;\n margin-left: 5px;\n}\n\n.pre-wrap {\n white-space: pre-wrap;\n} hr {height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;} .button.primary { margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity; } .button.primary:active { transform: scale(0.95, 0.95); } .inline-link, .inline-link:link, .inline-link:visited, .inline-link:active, .inline-link:hover { margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation; } .inline-link:active { opacity: 0.5; } .description { color: #5e5e5e; } </style>\n</head>\n\n<body>\n<p style=\"margin: 0; padding: 0; line-height: 1.4;\">{{greeting}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Jouw groep ({{objectName}}) heeft nog steeds een openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het beheerdersportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><strong>Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het beheerdersportaal.</strong></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartButton\" data-id=\"paymentUrl\"><table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"margin: 5px 0;\">\n<tbody><tr>\n <td>\n <table cellspacing=\"0\" cellpadding=\"0\">\n <tbody><tr>\n <td style=\"border-radius: 7px;\" bgcolor=\"{{primaryColor}}\">\n <a class=\"button primary\" href=\"{{paymentUrl}}\" target=\"\" style=\"margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity;\">Betalen via het beheerdersportaal</a>\n </td>\n </tr>\n </tbody></table>\n </td>\n</tr>\n</tbody></table></div><p class=\"description\" style=\"color: #5e5e5e;\"><em>Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het beheerdersportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><strong><em>Vragen over je openstaand bedrag</em></strong><em>? Neem dan contact op met </em>{{organizationName}} <em>via </em>{{fromAddress}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><hr style=\"height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;\"><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><h2 style=\"margin: 0; padding: 0;\">Overzicht</h2><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Dit is een momentopname van jullie huidige openstaande bedrag. Je kan op elk moment naar het beheerdersportaal gaan om de huidige status te bekijken en te betalen.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartVariableBlock\" data-id=\"balanceTable\">{{balanceTable}}</div><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><em>Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via </em><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\"><em>deze knop</em></a><em> — hierna zal je nooit meer een e-mail van ons ontvangen.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\">Uitschrijven</a></p>\n</body>\n\n</html>', '{\"value\": {\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"smartVariable\", \"attrs\": {\"id\": \"greeting\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Jouw groep (\", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"objectName\"}}, {\"text\": \") heeft nog steeds een openstaand bedrag van \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"outstandingBalance\"}}, {\"text\": \" bij \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \". Je kan de betaling in orde brengen door naar het beheerdersportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het beheerdersportaal.\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartButton\", \"attrs\": {\"id\": \"paymentUrl\"}, \"content\": [{\"text\": \"Betalen via het beheerdersportaal\", \"type\": \"text\"}]}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het beheerdersportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Vragen over je openstaand bedrag\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}, {\"type\": \"italic\"}]}, {\"text\": \"? Neem dan contact op met \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \" \", \"type\": \"text\"}, {\"text\": \"via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"fromAddress\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"horizontalRule\"}, {\"type\": \"paragraph\"}, {\"type\": \"heading\", \"attrs\": {\"level\": 2}, \"content\": [{\"text\": \"Overzicht\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Dit is een momentopname van jullie huidige openstaande bedrag. Je kan op elk moment naar het beheerdersportaal gaan om de huidige status te bekijken en te betalen.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartVariableBlock\", \"attrs\": {\"id\": \"balanceTable\"}}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"deze knop\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"text\": \" — hierna zal je nooit meer een e-mail van ons ontvangen.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\", \"content\": [{\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"Uitschrijven\", \"type\": \"text\"}]}]}]}, \"version\": 354}', '2025-01-04 12:09:03', '2025-01-04 12:09:03'),
4
+ ('b313f990-1f86-4571-931e-e3a46999cbea', '[Herinnering] Openstaand bedrag te betalen aan {{organizationName}}', NULL, NULL, NULL, 'UserBalanceReminder', '{{greeting}}Je hebt een openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het ledenportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het ledenportaal.\n\n \n \n \n \n Betalen via het ledenportaal\n \n \n \n \n\nTwijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het ledenportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.Vragen over je openstaand bedrag? Neem dan contact op met {{organizationName}} via {{fromAddress}}OverzichtDit is een momentopname van je huidige openstaande bedrag. Je kan op elk moment naar het ledenportaal gaan om de huidige status te bekijken en te betalen.{{balanceTable}}Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via deze knop — hierna zal je nooit meer een e-mail van ons ontvangen.Uitschrijven', '<!DOCTYPE html>\n<html>\n\n<head>\n<meta charset=\"utf-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n<title>[Herinnering] Openstaand bedrag te betalen aan {{organizationName}}</title>\n<style type=\"text/css\">body {\n color: #000716;\n color: var(--color-dark, #000716);\n font-family: -apple-system-body, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-size: 12pt;\n line-height: 1.4;\n}\n\np {\n margin: 0;\n padding: 0;\n line-height: 1.4;\n}\n\np.description {\n color: var(--color-gray-4, #5e5e5e);\n}\np.description a, p.description a:link, p.description a:visited, p.description a:active, p.description a:hover {\n text-decoration: underline;\n color: var(--color-gray-4, #5e5e5e);\n}\n\nstrong {\n font-weight: bold;\n}\n\nem {\n font-style: italic;\n}\n\nh1 {\n font-size: 30px;\n font-weight: bold;\n line-height: 1.2;\n margin: 0;\n padding: 0;\n}\n@media (max-width: 350px) {\n h1 {\n font-size: 24px;\n }\n}\n\nh2 {\n font-size: 20px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh3 {\n font-size: 16px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh4 {\n line-height: 1.2;\n font-weight: 500;\n margin: 0;\n padding: 0;\n}\n\nol, ul {\n list-style-position: outside;\n padding-left: 30px;\n}\n\nhr {\n height: 1px;\n background: var(--color-border, var(--color-gray-2, #dcdcdc));\n border-radius: 1px;\n padding: 0;\n margin: 20px 0;\n outline: none;\n border: 0;\n}\n\n.button {\n touch-action: inherit;\n user-select: auto;\n cursor: pointer;\n display: inline-block !important;\n line-height: 42px;\n font-size: 16px;\n font-weight: bold;\n}\n.button:active {\n transform: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n}\n\na, a:link, a:visited, a:active, a:hover {\n text-decoration: underline;\n color: blue;\n}\n\n.email-data-table {\n width: 100%;\n border-collapse: collapse;\n}\n.email-data-table th, .email-data-table td {\n text-align: left;\n padding: 10px 10px 10px 0;\n border-bottom: 1px solid var(--color-border, var(--color-gray-2, #dcdcdc));\n vertical-align: middle;\n}\n.email-data-table th:last-child, .email-data-table td:last-child {\n text-align: right;\n padding-right: 0;\n}\n.email-data-table thead {\n font-weight: bold;\n}\n.email-data-table thead th {\n font-size: 10pt;\n}\n.email-data-table h4 ~ p {\n padding-top: 3px;\n opacity: 0.8;\n font-size: 11pt;\n}\n\n.email-style-inline-code {\n font-family: monospace;\n white-space: pre-wrap;\n display: inline-block;\n}\n\n.email-style-description-small {\n font-size: 14px;\n line-height: 1.5;\n font-weight: normal;\n color: var(--color-gray-4, #5e5e5e);\n font-variation-settings: \"opsz\" 19;\n}\n\n.email-style-title-list {\n font-size: 16px;\n line-height: 1.3;\n font-weight: 500;\n}\n.email-style-title-list + p {\n padding-top: 3px;\n}\n\n.email-style-title-prefix-list {\n font-size: 11px;\n line-height: 1.5;\n font-weight: bold;\n color: {{primaryColor}};\n text-transform: uppercase;\n margin-bottom: 3px;\n}\n.email-style-title-prefix-list.error {\n color: #f0153d;\n}\n\n.email-style-price-base, .email-style-discount-price, .email-style-discount-old-price, .email-style-price {\n font-size: 15px;\n line-height: 1.4;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n}\n.email-style-price-base.disabled, .disabled.email-style-discount-price, .disabled.email-style-discount-old-price, .disabled.email-style-price {\n opacity: 0.6;\n}\n.email-style-price-base.negative, .negative.email-style-discount-price, .negative.email-style-discount-old-price, .negative.email-style-price {\n color: #f0153d;\n}\n\n.email-style-price {\n font-weight: bold;\n color: {{primaryColor}};\n}\n\n.email-style-discount-old-price {\n text-decoration: line-through;\n color: var(--color-gray-4, #5e5e5e);\n}\n\n.email-style-discount-price {\n font-weight: bold;\n color: #ff4747;\n margin-left: 5px;\n}\n\n.pre-wrap {\n white-space: pre-wrap;\n} hr {height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;} .button.primary { margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity; } .button.primary:active { transform: scale(0.95, 0.95); } .inline-link, .inline-link:link, .inline-link:visited, .inline-link:active, .inline-link:hover { margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation; } .inline-link:active { opacity: 0.5; } .description { color: #5e5e5e; } </style>\n</head>\n\n<body>\n<p style=\"margin: 0; padding: 0; line-height: 1.4;\">{{greeting}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Je hebt een openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het ledenportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><strong>Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het ledenportaal.</strong></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartButton\" data-id=\"paymentUrl\"><table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"margin: 5px 0;\">\n<tbody><tr>\n <td>\n <table cellspacing=\"0\" cellpadding=\"0\">\n <tbody><tr>\n <td style=\"border-radius: 7px;\" bgcolor=\"{{primaryColor}}\">\n <a class=\"button primary\" href=\"{{paymentUrl}}\" target=\"\" style=\"margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity;\">Betalen via het ledenportaal</a>\n </td>\n </tr>\n </tbody></table>\n </td>\n</tr>\n</tbody></table></div><p class=\"description\" style=\"color: #5e5e5e;\"><em>Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het ledenportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><strong><em>Vragen over je openstaand bedrag</em></strong><em>? Neem dan contact op met </em>{{organizationName}} <em>via </em>{{fromAddress}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><hr style=\"height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;\"><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><h2 style=\"margin: 0; padding: 0;\">Overzicht</h2><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Dit is een momentopname van je huidige openstaande bedrag. Je kan op elk moment naar het ledenportaal gaan om de huidige status te bekijken en te betalen.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartVariableBlock\" data-id=\"balanceTable\">{{balanceTable}}</div><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><em>Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via </em><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\"><em>deze knop</em></a><em> — hierna zal je nooit meer een e-mail van ons ontvangen.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\">Uitschrijven</a></p>\n</body>\n\n</html>', '{\"value\": {\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"smartVariable\", \"attrs\": {\"id\": \"greeting\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Je hebt een openstaand bedrag van \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"outstandingBalance\"}}, {\"text\": \" bij \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \". Je kan de betaling in orde brengen door naar het ledenportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het ledenportaal.\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartButton\", \"attrs\": {\"id\": \"paymentUrl\"}, \"content\": [{\"text\": \"Betalen via het ledenportaal\", \"type\": \"text\"}]}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het ledenportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Vragen over je openstaand bedrag\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}, {\"type\": \"italic\"}]}, {\"text\": \"? Neem dan contact op met \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \" \", \"type\": \"text\"}, {\"text\": \"via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"fromAddress\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"horizontalRule\"}, {\"type\": \"paragraph\"}, {\"type\": \"heading\", \"attrs\": {\"level\": 2}, \"content\": [{\"text\": \"Overzicht\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Dit is een momentopname van je huidige openstaande bedrag. Je kan op elk moment naar het ledenportaal gaan om de huidige status te bekijken en te betalen.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartVariableBlock\", \"attrs\": {\"id\": \"balanceTable\"}}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"deze knop\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"text\": \" — hierna zal je nooit meer een e-mail van ons ontvangen.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\", \"content\": [{\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"Uitschrijven\", \"type\": \"text\"}]}]}]}, \"version\": 354}', '2025-01-04 12:04:03', '2025-01-04 12:04:03'),
5
+ ('3624fe92-3021-4b70-a740-7acac32db6ef', 'Jouw openstaand saldo bij {{organizationName}}', NULL, NULL, NULL, 'UserBalanceIncreaseNotification', '{{greeting}}Je hebt een nieuw openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het ledenportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het ledenportaal.\n\n \n \n \n \n Betalen via het ledenportaal\n \n \n \n \n\nTwijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het ledenportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.Vragen over je openstaand bedrag? Neem dan contact op met {{organizationName}} via {{fromAddress}}OverzichtDit is een momentopname van je huidige openstaande bedrag. Je kan op elk moment naar het ledenportaal gaan om de huidige status te bekijken en te betalen.{{balanceTable}}Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via deze knop — hierna zal je nooit meer een e-mail van ons ontvangen.Uitschrijven', '<!DOCTYPE html>\n<html>\n\n<head>\n<meta charset=\"utf-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n<title>Jouw openstaand saldo bij {{organizationName}}</title>\n<style type=\"text/css\">body {\n color: #000716;\n color: var(--color-dark, #000716);\n font-family: -apple-system-body, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-size: 12pt;\n line-height: 1.4;\n}\n\np {\n margin: 0;\n padding: 0;\n line-height: 1.4;\n}\n\np.description {\n color: var(--color-gray-4, #5e5e5e);\n}\np.description a, p.description a:link, p.description a:visited, p.description a:active, p.description a:hover {\n text-decoration: underline;\n color: var(--color-gray-4, #5e5e5e);\n}\n\nstrong {\n font-weight: bold;\n}\n\nem {\n font-style: italic;\n}\n\nh1 {\n font-size: 30px;\n font-weight: bold;\n line-height: 1.2;\n margin: 0;\n padding: 0;\n}\n@media (max-width: 350px) {\n h1 {\n font-size: 24px;\n }\n}\n\nh2 {\n font-size: 20px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh3 {\n font-size: 16px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh4 {\n line-height: 1.2;\n font-weight: 500;\n margin: 0;\n padding: 0;\n}\n\nol, ul {\n list-style-position: outside;\n padding-left: 30px;\n}\n\nhr {\n height: 1px;\n background: var(--color-border, var(--color-gray-2, #dcdcdc));\n border-radius: 1px;\n padding: 0;\n margin: 20px 0;\n outline: none;\n border: 0;\n}\n\n.button {\n touch-action: inherit;\n user-select: auto;\n cursor: pointer;\n display: inline-block !important;\n line-height: 42px;\n font-size: 16px;\n font-weight: bold;\n}\n.button:active {\n transform: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n}\n\na, a:link, a:visited, a:active, a:hover {\n text-decoration: underline;\n color: blue;\n}\n\n.email-data-table {\n width: 100%;\n border-collapse: collapse;\n}\n.email-data-table th, .email-data-table td {\n text-align: left;\n padding: 10px 10px 10px 0;\n border-bottom: 1px solid var(--color-border, var(--color-gray-2, #dcdcdc));\n vertical-align: middle;\n}\n.email-data-table th:last-child, .email-data-table td:last-child {\n text-align: right;\n padding-right: 0;\n}\n.email-data-table thead {\n font-weight: bold;\n}\n.email-data-table thead th {\n font-size: 10pt;\n}\n.email-data-table h4 ~ p {\n padding-top: 3px;\n opacity: 0.8;\n font-size: 11pt;\n}\n\n.email-style-inline-code {\n font-family: monospace;\n white-space: pre-wrap;\n display: inline-block;\n}\n\n.email-style-description-small {\n font-size: 14px;\n line-height: 1.5;\n font-weight: normal;\n color: var(--color-gray-4, #5e5e5e);\n font-variation-settings: \"opsz\" 19;\n}\n\n.email-style-title-list {\n font-size: 16px;\n line-height: 1.3;\n font-weight: 500;\n}\n.email-style-title-list + p {\n padding-top: 3px;\n}\n\n.email-style-title-prefix-list {\n font-size: 11px;\n line-height: 1.5;\n font-weight: bold;\n color: {{primaryColor}};\n text-transform: uppercase;\n margin-bottom: 3px;\n}\n.email-style-title-prefix-list.error {\n color: #f0153d;\n}\n\n.email-style-price-base, .email-style-discount-price, .email-style-discount-old-price, .email-style-price {\n font-size: 15px;\n line-height: 1.4;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n}\n.email-style-price-base.disabled, .disabled.email-style-discount-price, .disabled.email-style-discount-old-price, .disabled.email-style-price {\n opacity: 0.6;\n}\n.email-style-price-base.negative, .negative.email-style-discount-price, .negative.email-style-discount-old-price, .negative.email-style-price {\n color: #f0153d;\n}\n\n.email-style-price {\n font-weight: bold;\n color: {{primaryColor}};\n}\n\n.email-style-discount-old-price {\n text-decoration: line-through;\n color: var(--color-gray-4, #5e5e5e);\n}\n\n.email-style-discount-price {\n font-weight: bold;\n color: #ff4747;\n margin-left: 5px;\n}\n\n.pre-wrap {\n white-space: pre-wrap;\n} hr {height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;} .button.primary { margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity; } .button.primary:active { transform: scale(0.95, 0.95); } .inline-link, .inline-link:link, .inline-link:visited, .inline-link:active, .inline-link:hover { margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation; } .inline-link:active { opacity: 0.5; } .description { color: #5e5e5e; } </style>\n</head>\n\n<body>\n<p style=\"margin: 0; padding: 0; line-height: 1.4;\">{{greeting}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Je hebt een nieuw openstaand bedrag van {{outstandingBalance}} bij {{organizationName}}. Je kan de betaling in orde brengen door naar het ledenportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><strong>Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het ledenportaal.</strong></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartButton\" data-id=\"paymentUrl\"><table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"margin: 5px 0;\">\n<tbody><tr>\n <td>\n <table cellspacing=\"0\" cellpadding=\"0\">\n <tbody><tr>\n <td style=\"border-radius: 7px;\" bgcolor=\"{{primaryColor}}\">\n <a class=\"button primary\" href=\"{{paymentUrl}}\" target=\"\" style=\"margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity;\">Betalen via het ledenportaal</a>\n </td>\n </tr>\n </tbody></table>\n </td>\n</tr>\n</tbody></table></div><p class=\"description\" style=\"color: #5e5e5e;\"><em>Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het ledenportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><strong><em>Vragen over je openstaand bedrag</em></strong><em>? Neem dan contact op met </em>{{organizationName}} <em>via </em>{{fromAddress}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><hr style=\"height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;\"><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><h2 style=\"margin: 0; padding: 0;\">Overzicht</h2><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Dit is een momentopname van je huidige openstaande bedrag. Je kan op elk moment naar het ledenportaal gaan om de huidige status te bekijken en te betalen.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartVariableBlock\" data-id=\"balanceTable\">{{balanceTable}}</div><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p class=\"description\" style=\"color: #5e5e5e;\"><em>Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via </em><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\"><em>deze knop</em></a><em> — hierna zal je nooit meer een e-mail van ons ontvangen.</em></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><a class=\"inline-link\" href=\"{{unsubscribeUrl}}\" target=\"\" style=\"margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation;\">Uitschrijven</a></p>\n</body>\n\n</html>', '{\"value\": {\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"smartVariable\", \"attrs\": {\"id\": \"greeting\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Je hebt een nieuw openstaand bedrag van \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"outstandingBalance\"}}, {\"text\": \" bij \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \". Je kan de betaling in orde brengen door naar het ledenportaal te gaan. Daar kan je ook een detail bekijken van het openstaande bedrag.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Gelieve niet zomaar geld over te schrijven op onze rekening, maar altijd te betalen via het ledenportaal.\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartButton\", \"attrs\": {\"id\": \"paymentUrl\"}, \"content\": [{\"text\": \"Betalen via het ledenportaal\", \"type\": \"text\"}]}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Twijfel je of één van onze e-mails wel echt is? Ga dan zelf rechtstreeks naar het ledenportaal via onze website zonder op een link in een e-mail te klikken. Via die weg kan je ook je openstaand bedrag bekijken en eventueel betalen - dan ben je altijd zeker dat je aan ons betaalt en het geen phishing is.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Vragen over je openstaand bedrag\", \"type\": \"text\", \"marks\": [{\"type\": \"bold\"}, {\"type\": \"italic\"}]}, {\"text\": \"? Neem dan contact op met \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}}, {\"text\": \" \", \"type\": \"text\"}, {\"text\": \"via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"fromAddress\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"horizontalRule\"}, {\"type\": \"paragraph\"}, {\"type\": \"heading\", \"attrs\": {\"level\": 2}, \"content\": [{\"text\": \"Overzicht\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Dit is een momentopname van je huidige openstaande bedrag. Je kan op elk moment naar het ledenportaal gaan om de huidige status te bekijken en te betalen.\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartVariableBlock\", \"attrs\": {\"id\": \"balanceTable\"}}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\"}, {\"type\": \"descriptiveText\", \"content\": [{\"text\": \"Komt deze e-mail bij jou terecht, maar weet je niet waarover dit gaat en denk je dat dit aan een andere persoon is gericht (bv. een typefout)? Dan schrijf je best uit voor onze e-mails via \", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}, {\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"deze knop\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"text\": \" — hierna zal je nooit meer een e-mail van ons ontvangen.\", \"type\": \"text\", \"marks\": [{\"type\": \"italic\"}]}]}, {\"type\": \"paragraph\", \"content\": [{\"type\": \"smartButtonInline\", \"attrs\": {\"id\": \"unsubscribeUrl\"}, \"content\": [{\"text\": \"Uitschrijven\", \"type\": \"text\"}]}]}]}, \"version\": 354}', '2025-01-04 10:09:48', '2025-01-04 10:09:48');
@@ -1,10 +1,10 @@
1
- import { column, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
2
  import { ArrayDecoder, Decoder, MapDecoder, StringDecoder } from '@simonbackx/simple-encoding';
3
- import { SQL, SQLSelect } from '@stamhoofd/sql';
3
+ import { QueryableModel } from '@stamhoofd/sql';
4
4
  import { AuditLogPatchItem, AuditLogReplacement, AuditLogSource, AuditLogType } from '@stamhoofd/structures';
5
5
  import { v7 as uuidv7 } from 'uuid';
6
6
 
7
- export class AuditLog extends Model {
7
+ export class AuditLog extends QueryableModel {
8
8
  static table = 'audit_logs';
9
9
 
10
10
  // Columns
@@ -67,22 +67,4 @@ export class AuditLog extends Model {
67
67
  },
68
68
  })
69
69
  createdAt: Date;
70
-
71
- /**
72
- * Experimental: needs to move to library
73
- */
74
- static select() {
75
- const transformer = (row: SQLResultNamespacedRow): AuditLog => {
76
- const d = (this as typeof AuditLog & typeof Model).fromRow(row[this.table] as any) as AuditLog | undefined;
77
-
78
- if (!d) {
79
- throw new Error('EmailTemplate not found');
80
- }
81
-
82
- return d;
83
- };
84
-
85
- const select = new SQLSelect(transformer, SQL.wildcard());
86
- return select.from(SQL.table(this.table));
87
- }
88
70
  }
@@ -1,17 +1,17 @@
1
- import { column, Database, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
- import { BalanceItem as BalanceItemStruct, BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, BalanceItemWithPayments, BalanceItemWithPrivatePayments, OrderStatus, Payment as PaymentStruct, PrivatePayment } from '@stamhoofd/structures';
1
+ import { column, Database } from '@simonbackx/simple-database';
2
+ import { BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItem as BalanceItemStruct, BalanceItemType, BalanceItemWithPayments, BalanceItemWithPrivatePayments, Payment as PaymentStruct, PrivatePayment } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
6
6
  import { EnumDecoder, MapDecoder } from '@simonbackx/simple-encoding';
7
- import { SQL, SQLSelect } from '@stamhoofd/sql';
8
- import { Document, Organization, Payment, Webshop } from './';
7
+ import { QueryableModel, SQL } from '@stamhoofd/sql';
8
+ import { Document, MemberUser, Payment } from './';
9
9
  import { CachedBalance } from './CachedBalance';
10
10
 
11
11
  /**
12
12
  * Keeps track of how much a member/user owes or needs to be reimbursed.
13
13
  */
14
- export class BalanceItem extends Model {
14
+ export class BalanceItem extends QueryableModel {
15
15
  static table = 'balance_items';
16
16
 
17
17
  @column({
@@ -220,8 +220,18 @@ export class BalanceItem extends Model {
220
220
  await this.deleteItems(items);
221
221
  }
222
222
 
223
- static async getForRegistration(registrationId: string) {
224
- const items = await BalanceItem.where({ registrationId });
223
+ static async getForRegistration(registrationId: string, organizationId?: string) {
224
+ let q = BalanceItem.select()
225
+ .where('registrationId', registrationId)
226
+ .whereNot('status', BalanceItemStatus.Hidden);
227
+
228
+ if (organizationId) {
229
+ q = q.where('organizationId', organizationId);
230
+ }
231
+
232
+ const items = await q
233
+ .fetch();
234
+
225
235
  return {
226
236
  items,
227
237
  ...(await this.loadPayments(items)),
@@ -237,15 +247,6 @@ export class BalanceItem extends Model {
237
247
 
238
248
  await BalanceItem.updatePricePaid(items.map(i => i.id));
239
249
 
240
- // Deprecated: the member balances have moved to CachedBalance
241
- // Update outstanding amount of related members and registrations
242
- const memberIds: string[] = Formatter.uniqueArray(items.map(p => p.memberId).filter(id => id !== null));
243
-
244
- const Member = (await import('./Member')).Member;
245
- await Member.updateOutstandingBalance(memberIds);
246
-
247
- const { Registration } = await import('./Registration');
248
-
249
250
  const organizationIds = Formatter.uniqueArray(items.map(p => p.organizationId));
250
251
  for (const organizationId of organizationIds) {
251
252
  const filteredItems = items.filter(i => i.organizationId === organizationId);
@@ -253,16 +254,22 @@ export class BalanceItem extends Model {
253
254
  const memberIds = Formatter.uniqueArray(filteredItems.map(p => p.memberId).filter(id => id !== null));
254
255
  await CachedBalance.updateForMembers(organizationId, memberIds);
255
256
 
256
- const userIds = Formatter.uniqueArray(filteredItems.filter(p => p.memberId === null && p.userId !== null).map(p => p.userId!));
257
+ let userIds = filteredItems.filter(p => p.userId !== null).map(p => p.userId!);
258
+
259
+ if (memberIds.length) {
260
+ // Now also include the userIds of the members
261
+ const userMemberIds = (await MemberUser.select().where('membersId', memberIds).fetch()).map(m => m.usersId);
262
+ userIds.push(...userMemberIds);
263
+ }
264
+ userIds = Formatter.uniqueArray(userIds);
265
+
257
266
  await CachedBalance.updateForUsers(organizationId, userIds);
258
267
 
259
268
  const organizationIds = Formatter.uniqueArray(filteredItems.map(p => p.payingOrganizationId).filter(id => id !== null));
260
269
  await CachedBalance.updateForOrganizations(organizationId, organizationIds);
261
270
 
262
271
  const registrationIds: string[] = Formatter.uniqueArray(filteredItems.map(p => p.registrationId).filter(id => id !== null));
263
-
264
- // Deprecated: we'll need to move the outstanding balance of registrations to CachedBalance
265
- await Registration.updateOutstandingBalance(registrationIds, organizationId);
272
+ await CachedBalance.updateForRegistrations(organizationId, registrationIds);
266
273
 
267
274
  if (registrationIds.length) {
268
275
  await Document.updateForRegistrations(registrationIds, organizationId);
@@ -330,13 +337,6 @@ export class BalanceItem extends Model {
330
337
  await Database.update(query, params);
331
338
  }
332
339
 
333
- /**
334
- * @deprecated
335
- */
336
- static async updatePricePending(balanceItemIds: string[] | 'all') {
337
- // deprecated
338
- }
339
-
340
340
  static async loadPayments(items: BalanceItem[]) {
341
341
  if (items.length == 0) {
342
342
  return { balanceItemPayments: [], payments: [] };
@@ -344,7 +344,9 @@ export class BalanceItem extends Model {
344
344
 
345
345
  // Load balance payment items
346
346
  const { BalanceItemPayment } = await import('./BalanceItemPayment');
347
- const balanceItemPayments = await BalanceItemPayment.where({ balanceItemId: { sign: 'IN', value: items.map(i => i.id) } });
347
+ const balanceItemPayments = await BalanceItemPayment.select()
348
+ .where('balanceItemId', items.map(i => i.id))
349
+ .fetch();
348
350
 
349
351
  const payments = await Payment.getByIDs(...balanceItemPayments.map(p => p.paymentId));
350
352
 
@@ -461,22 +463,4 @@ export class BalanceItem extends Model {
461
463
  .whereNot('status', BalanceItemStatus.Hidden)
462
464
  .fetch();
463
465
  }
464
-
465
- /**
466
- * Experimental: needs to move to library
467
- */
468
- static select() {
469
- const transformer = (row: SQLResultNamespacedRow): BalanceItem => {
470
- const d = (this as typeof BalanceItem & typeof Model).fromRow(row[this.table] as any) as BalanceItem | undefined;
471
-
472
- if (!d) {
473
- throw new Error('EmailTemplate not found');
474
- }
475
-
476
- return d;
477
- };
478
-
479
- const select = new SQLSelect(transformer, SQL.wildcard());
480
- return select.from(SQL.table(this.table));
481
- }
482
466
  }
@@ -1,13 +1,14 @@
1
- import { column, ManyToOneRelation, Model } from '@simonbackx/simple-database';
1
+ import { column, ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
 
4
4
  import { BalanceItem, Payment } from './';
5
+ import { QueryableModel } from '@stamhoofd/sql';
5
6
 
6
7
  /**
7
8
  * Keeps track of all the created payments of a balance item, which contains the (tries) to pay a balance item.
8
9
  * It also keeps track of how much a given payment is split between multiple balance items, which makes it possible to pay a balance item partially.
9
10
  */
10
- export class BalanceItemPayment extends Model {
11
+ export class BalanceItemPayment extends QueryableModel {
11
12
  static table = 'balance_item_payments';
12
13
 
13
14
  @column({
@@ -1,7 +1,8 @@
1
- import { column, Model } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
+ import { QueryableModel } from '@stamhoofd/sql';
2
3
  import { v4 as uuidv4 } from 'uuid';
3
4
 
4
- export class BuckarooPayment extends Model {
5
+ export class BuckarooPayment extends QueryableModel {
5
6
  static table = 'buckaroo_payments';
6
7
 
7
8
  @column({