@stamhoofd/models 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 (296) hide show
  1. package/dist/factories/GroupFactory.d.ts.map +1 -1
  2. package/dist/factories/GroupFactory.js +1 -1
  3. package/dist/factories/GroupFactory.js.map +1 -1
  4. package/dist/factories/OrganizationFactory.d.ts +2 -1
  5. package/dist/factories/OrganizationFactory.d.ts.map +1 -1
  6. package/dist/factories/OrganizationFactory.js +9 -1
  7. package/dist/factories/OrganizationFactory.js.map +1 -1
  8. package/dist/factories/RegistrationInvitationFactory.d.ts +15 -0
  9. package/dist/factories/RegistrationInvitationFactory.d.ts.map +1 -0
  10. package/dist/factories/RegistrationInvitationFactory.js +18 -0
  11. package/dist/factories/RegistrationInvitationFactory.js.map +1 -0
  12. package/dist/factories/STPackageFactory.js.map +1 -1
  13. package/dist/factories/UserFactory.d.ts.map +1 -1
  14. package/dist/factories/UserFactory.js +2 -2
  15. package/dist/factories/UserFactory.js.map +1 -1
  16. package/dist/factories/index.d.ts +1 -0
  17. package/dist/factories/index.d.ts.map +1 -1
  18. package/dist/factories/index.js +1 -0
  19. package/dist/factories/index.js.map +1 -1
  20. package/dist/helpers/EmailBuilder.d.ts.map +1 -1
  21. package/dist/helpers/EmailBuilder.js +8 -8
  22. package/dist/helpers/EmailBuilder.js.map +1 -1
  23. package/dist/helpers/Handlebars.d.ts.map +1 -1
  24. package/dist/helpers/Handlebars.js +10 -1
  25. package/dist/helpers/Handlebars.js.map +1 -1
  26. package/dist/helpers/InvoiceCounter.d.ts +24 -0
  27. package/dist/helpers/InvoiceCounter.d.ts.map +1 -0
  28. package/dist/helpers/InvoiceCounter.js +133 -0
  29. package/dist/helpers/InvoiceCounter.js.map +1 -0
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +0 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/migrations/1605262045-import-postcodes.d.ts.map +1 -1
  35. package/dist/migrations/1605262045-import-postcodes.js +58 -24
  36. package/dist/migrations/1605262045-import-postcodes.js.map +1 -1
  37. package/dist/migrations/1605262046-import-postcodes-nl.d.ts.map +1 -1
  38. package/dist/migrations/1605262046-import-postcodes-nl.js +54 -17
  39. package/dist/migrations/1605262046-import-postcodes-nl.js.map +1 -1
  40. package/dist/migrations/1719567881-organization-periodId.sql +2 -0
  41. package/dist/migrations/1719567882-groups-periodId.sql +2 -0
  42. package/dist/migrations/1720080975-convert-charset.d.ts +4 -0
  43. package/dist/migrations/1720080975-convert-charset.d.ts.map +1 -0
  44. package/dist/migrations/1720080975-convert-charset.js +26 -0
  45. package/dist/migrations/1720080975-convert-charset.js.map +1 -0
  46. package/dist/migrations/1720080976-convert-charset-leads.d.ts.map +1 -1
  47. package/dist/migrations/1720080976-convert-charset-leads.js +11 -10
  48. package/dist/migrations/1720080976-convert-charset-leads.js.map +1 -1
  49. package/dist/migrations/1721400546-users-memberId.sql +2 -0
  50. package/dist/migrations/1722269236-group-waitinglist-id.sql +2 -1
  51. package/dist/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
  52. package/dist/migrations/1722525787-depending-balance-item.sql +2 -0
  53. package/dist/migrations/1722963554-registration-group-price-and-options.sql +1 -1
  54. package/dist/migrations/1723652797-payments-paying-organization-id-fk.sql +2 -0
  55. package/dist/migrations/1733317908-added-missing-organization-fk-on-registrations.sql +2 -0
  56. package/dist/migrations/1733317910-paying-organization-id-fk.sql +2 -0
  57. package/dist/migrations/1733504881-negative-invoice-id.sql +6 -0
  58. package/dist/migrations/1733994455-balance-item-status-open.d.ts +4 -0
  59. package/dist/migrations/1733994455-balance-item-status-open.d.ts.map +1 -0
  60. package/dist/migrations/1733994455-balance-item-status-open.js +28 -0
  61. package/dist/migrations/1733994455-balance-item-status-open.js.map +1 -0
  62. package/dist/migrations/1763216320-bigint-balance-item-payments.sql +2 -0
  63. package/dist/migrations/1763216320-bigint-balance-items.sql +5 -0
  64. package/dist/migrations/1763216320-bigint-orders.sql +2 -0
  65. package/dist/migrations/1763216320-bigint-payments.sql +2 -0
  66. package/dist/migrations/1763216332-bigint-balance-item-price-total.sql +2 -0
  67. package/dist/migrations/1769087808-corrected-invoice-user-agent.sql +2 -0
  68. package/dist/migrations/1769087809-payments-invoice-id.sql +2 -0
  69. package/dist/migrations/1772033555-balance-item-package-id.sql +2 -0
  70. package/dist/migrations/1776873089-create-registration-invitations-table.sql +13 -0
  71. package/dist/migrations/1778657958-payments-create-mandate.sql +2 -0
  72. package/dist/migrations/1778657959-payments-mandate-id.sql +2 -0
  73. package/dist/migrations/1778796615-payments-reversing-payment-id.sql +5 -0
  74. package/dist/migrations/1778950642-price-invoiced.sql +2 -0
  75. package/dist/migrations/1779443446-transfer-fees.sql +3 -0
  76. package/dist/migrations/1779709174-used-register-code-balance-item-id.sql +5 -0
  77. package/dist/migrations/1779968328-payments-admin-user-id.sql +5 -0
  78. package/dist/migrations/1779970611-payments-refunded-amount.sql +2 -0
  79. package/dist/migrations/1779972640-balance-items-failed-at.sql +2 -0
  80. package/dist/migrations/1780328285-document-template-locked.sql +2 -0
  81. package/dist/migrations/1780328286-document-locked.sql +2 -0
  82. package/dist/migrations/1780412083-documents-set-locked.d.ts +4 -0
  83. package/dist/migrations/1780412083-documents-set-locked.d.ts.map +1 -0
  84. package/dist/migrations/1780412083-documents-set-locked.js +14 -0
  85. package/dist/migrations/1780412083-documents-set-locked.js.map +1 -0
  86. package/dist/migrations/1780928401-v1-groups-migration-data.d.ts +4 -0
  87. package/dist/migrations/1780928401-v1-groups-migration-data.d.ts.map +1 -0
  88. package/dist/migrations/1780928401-v1-groups-migration-data.js +44 -0
  89. package/dist/migrations/1780928401-v1-groups-migration-data.js.map +1 -0
  90. package/dist/models/BalanceItem.d.ts +14 -1
  91. package/dist/models/BalanceItem.d.ts.map +1 -1
  92. package/dist/models/BalanceItem.js +91 -41
  93. package/dist/models/BalanceItem.js.map +1 -1
  94. package/dist/models/CachedBalance.d.ts +6 -1
  95. package/dist/models/CachedBalance.d.ts.map +1 -1
  96. package/dist/models/CachedBalance.js +3 -2
  97. package/dist/models/CachedBalance.js.map +1 -1
  98. package/dist/models/Document.d.ts +4 -0
  99. package/dist/models/Document.d.ts.map +1 -1
  100. package/dist/models/Document.js +26 -3
  101. package/dist/models/Document.js.map +1 -1
  102. package/dist/models/DocumentTemplate.d.ts +4 -0
  103. package/dist/models/DocumentTemplate.d.ts.map +1 -1
  104. package/dist/models/DocumentTemplate.js +37 -1
  105. package/dist/models/DocumentTemplate.js.map +1 -1
  106. package/dist/models/Email.d.ts.map +1 -1
  107. package/dist/models/Email.js +1 -1
  108. package/dist/models/Email.js.map +1 -1
  109. package/dist/models/EmailVerificationCode.d.ts.map +1 -1
  110. package/dist/models/EmailVerificationCode.js +3 -13
  111. package/dist/models/EmailVerificationCode.js.map +1 -1
  112. package/dist/models/Event.d.ts +2 -1
  113. package/dist/models/Event.d.ts.map +1 -1
  114. package/dist/models/Event.js +3 -0
  115. package/dist/models/Event.js.map +1 -1
  116. package/dist/models/EventNotification.d.ts.map +1 -1
  117. package/dist/models/EventNotification.js +5 -5
  118. package/dist/models/EventNotification.js.map +1 -1
  119. package/dist/models/Group.d.ts +4 -0
  120. package/dist/models/Group.d.ts.map +1 -1
  121. package/dist/models/Group.js +17 -0
  122. package/dist/models/Group.js.map +1 -1
  123. package/dist/models/Invoice.d.ts +1 -0
  124. package/dist/models/Invoice.d.ts.map +1 -1
  125. package/dist/models/Invoice.js +8 -8
  126. package/dist/models/Invoice.js.map +1 -1
  127. package/dist/models/MemberPlatformMembership.d.ts.map +1 -1
  128. package/dist/models/MemberPlatformMembership.js +9 -0
  129. package/dist/models/MemberPlatformMembership.js.map +1 -1
  130. package/dist/models/MollieToken.d.ts +4 -8
  131. package/dist/models/MollieToken.d.ts.map +1 -1
  132. package/dist/models/MollieToken.js +37 -90
  133. package/dist/models/MollieToken.js.map +1 -1
  134. package/dist/models/Order.d.ts.map +1 -1
  135. package/dist/models/Order.js +1 -0
  136. package/dist/models/Order.js.map +1 -1
  137. package/dist/models/Organization.d.ts +30 -23
  138. package/dist/models/Organization.d.ts.map +1 -1
  139. package/dist/models/Organization.js +113 -61
  140. package/dist/models/Organization.js.map +1 -1
  141. package/dist/models/PasswordToken.d.ts +5 -1
  142. package/dist/models/PasswordToken.d.ts.map +1 -1
  143. package/dist/models/PasswordToken.js +18 -17
  144. package/dist/models/PasswordToken.js.map +1 -1
  145. package/dist/models/Payment.d.ts +35 -3
  146. package/dist/models/Payment.d.ts.map +1 -1
  147. package/dist/models/Payment.js +66 -3
  148. package/dist/models/Payment.js.map +1 -1
  149. package/dist/models/Registration.d.ts +1 -0
  150. package/dist/models/Registration.d.ts.map +1 -1
  151. package/dist/models/Registration.js +4 -3
  152. package/dist/models/Registration.js.map +1 -1
  153. package/dist/models/RegistrationInvitation.d.ts +14 -0
  154. package/dist/models/RegistrationInvitation.d.ts.map +1 -0
  155. package/dist/models/RegistrationInvitation.js +45 -0
  156. package/dist/models/RegistrationInvitation.js.map +1 -0
  157. package/dist/models/STCredit.d.ts +4 -0
  158. package/dist/models/STCredit.d.ts.map +1 -1
  159. package/dist/models/STCredit.js +28 -0
  160. package/dist/models/STCredit.js.map +1 -1
  161. package/dist/models/STInvoice.d.ts +7 -1
  162. package/dist/models/STInvoice.d.ts.map +1 -1
  163. package/dist/models/STInvoice.js +9 -0
  164. package/dist/models/STInvoice.js.map +1 -1
  165. package/dist/models/STPackage.d.ts +4 -0
  166. package/dist/models/STPackage.d.ts.map +1 -1
  167. package/dist/models/STPackage.js +12 -1
  168. package/dist/models/STPackage.js.map +1 -1
  169. package/dist/models/UsedRegisterCode.d.ts +9 -0
  170. package/dist/models/UsedRegisterCode.d.ts.map +1 -1
  171. package/dist/models/UsedRegisterCode.js +31 -0
  172. package/dist/models/UsedRegisterCode.js.map +1 -1
  173. package/dist/models/User.d.ts +1 -1
  174. package/dist/models/User.d.ts.map +1 -1
  175. package/dist/models/User.js +1 -1
  176. package/dist/models/User.js.map +1 -1
  177. package/dist/models/_relations.js +25 -0
  178. package/dist/models/_relations.js.map +1 -1
  179. package/dist/models/addresses/City.d.ts +4 -4
  180. package/dist/models/addresses/City.d.ts.map +1 -1
  181. package/dist/models/addresses/City.js +6 -6
  182. package/dist/models/addresses/City.js.map +1 -1
  183. package/dist/models/addresses/PostalCode.d.ts +2 -2
  184. package/dist/models/addresses/PostalCode.d.ts.map +1 -1
  185. package/dist/models/addresses/PostalCode.js +4 -3
  186. package/dist/models/addresses/PostalCode.js.map +1 -1
  187. package/dist/models/addresses/Street.d.ts +3 -3
  188. package/dist/models/addresses/Street.d.ts.map +1 -1
  189. package/dist/models/addresses/Street.js +4 -4
  190. package/dist/models/addresses/Street.js.map +1 -1
  191. package/dist/models/index.d.ts +2 -0
  192. package/dist/models/index.d.ts.map +1 -1
  193. package/dist/models/index.js +2 -0
  194. package/dist/models/index.js.map +1 -1
  195. package/dist/models/v1GroupMigrationData.d.ts +22 -0
  196. package/dist/models/v1GroupMigrationData.d.ts.map +1 -0
  197. package/dist/models/v1GroupMigrationData.js +48 -0
  198. package/dist/models/v1GroupMigrationData.js.map +1 -0
  199. package/package.json +41 -13
  200. package/src/factories/GroupFactory.ts +4 -6
  201. package/src/factories/OrganizationFactory.ts +12 -4
  202. package/src/factories/RegistrationInvitationFactory.ts +24 -0
  203. package/src/factories/STPackageFactory.ts +2 -2
  204. package/src/factories/UserFactory.ts +4 -5
  205. package/src/factories/index.ts +1 -0
  206. package/src/helpers/EmailBuilder.ts +19 -28
  207. package/src/helpers/Handlebars.ts +10 -1
  208. package/src/helpers/InvoiceCounter.test.ts +220 -0
  209. package/src/helpers/InvoiceCounter.ts +162 -0
  210. package/src/index.ts +0 -1
  211. package/src/migrations/1605262045-import-postcodes.ts +62 -25
  212. package/src/migrations/1605262046-import-postcodes-nl.ts +58 -17
  213. package/src/migrations/1719567881-organization-periodId.sql +2 -0
  214. package/src/migrations/1719567882-groups-periodId.sql +2 -0
  215. package/src/migrations/1720080975-convert-charset.ts +34 -0
  216. package/src/migrations/1720080976-convert-charset-leads.ts +16 -13
  217. package/src/migrations/1721400546-users-memberId.sql +2 -0
  218. package/src/migrations/1722269236-group-waitinglist-id.sql +2 -1
  219. package/src/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
  220. package/src/migrations/1722525787-depending-balance-item.sql +2 -0
  221. package/src/migrations/1722963554-registration-group-price-and-options.sql +1 -1
  222. package/src/migrations/1723652797-payments-paying-organization-id-fk.sql +2 -0
  223. package/src/migrations/1733317908-added-missing-organization-fk-on-registrations.sql +2 -0
  224. package/src/migrations/1733317910-paying-organization-id-fk.sql +2 -0
  225. package/src/migrations/1733504881-negative-invoice-id.sql +6 -0
  226. package/src/migrations/1733994455-balance-item-status-open.ts +30 -0
  227. package/src/migrations/1763216320-bigint-balance-item-payments.sql +2 -0
  228. package/src/migrations/1763216320-bigint-balance-items.sql +5 -0
  229. package/src/migrations/1763216320-bigint-orders.sql +2 -0
  230. package/src/migrations/1763216320-bigint-payments.sql +2 -0
  231. package/src/migrations/1763216332-bigint-balance-item-price-total.sql +2 -0
  232. package/src/migrations/1769087808-corrected-invoice-user-agent.sql +2 -0
  233. package/src/migrations/1769087809-payments-invoice-id.sql +2 -0
  234. package/src/migrations/1772033555-balance-item-package-id.sql +2 -0
  235. package/src/migrations/1776873089-create-registration-invitations-table.sql +13 -0
  236. package/src/migrations/1778657958-payments-create-mandate.sql +2 -0
  237. package/src/migrations/1778657959-payments-mandate-id.sql +2 -0
  238. package/src/migrations/1778796615-payments-reversing-payment-id.sql +5 -0
  239. package/src/migrations/1778950642-price-invoiced.sql +2 -0
  240. package/src/migrations/1779443446-transfer-fees.sql +3 -0
  241. package/src/migrations/1779709174-used-register-code-balance-item-id.sql +5 -0
  242. package/src/migrations/1779968328-payments-admin-user-id.sql +5 -0
  243. package/src/migrations/1779970611-payments-refunded-amount.sql +2 -0
  244. package/src/migrations/1779972640-balance-items-failed-at.sql +2 -0
  245. package/src/migrations/1780328285-document-template-locked.sql +2 -0
  246. package/src/migrations/1780328286-document-locked.sql +2 -0
  247. package/src/migrations/1780412083-documents-set-locked.ts +18 -0
  248. package/src/migrations/1780928401-v1-groups-migration-data.ts +50 -0
  249. package/src/models/BalanceItem.ts +98 -43
  250. package/src/models/CachedBalance.test.ts +46 -46
  251. package/src/models/CachedBalance.ts +7 -7
  252. package/src/models/Document.ts +34 -13
  253. package/src/models/DocumentTemplate.ts +56 -17
  254. package/src/models/Email.test.ts +3 -3
  255. package/src/models/Email.ts +28 -49
  256. package/src/models/EmailVerificationCode.ts +8 -22
  257. package/src/models/Event.ts +6 -4
  258. package/src/models/EventNotification.ts +6 -6
  259. package/src/models/Group.ts +24 -3
  260. package/src/models/Invoice.ts +10 -9
  261. package/src/models/MemberPlatformMembership.test.ts +70 -0
  262. package/src/models/MemberPlatformMembership.ts +16 -12
  263. package/src/models/MollieToken.ts +42 -102
  264. package/src/models/Order.ts +14 -26
  265. package/src/models/Organization.ts +143 -86
  266. package/src/models/PasswordToken.ts +21 -21
  267. package/src/models/Payment.ts +61 -4
  268. package/src/models/Registration.ts +6 -5
  269. package/src/models/RegistrationInvitation.ts +40 -0
  270. package/src/models/STCredit.ts +32 -0
  271. package/src/models/STInvoice.ts +11 -5
  272. package/src/models/STPackage.ts +19 -14
  273. package/src/models/UsedRegisterCode.ts +34 -0
  274. package/src/models/User.ts +6 -7
  275. package/src/models/_relations.ts +29 -0
  276. package/src/models/addresses/City.ts +8 -6
  277. package/src/models/addresses/PostalCode.test.ts +1 -0
  278. package/src/models/addresses/PostalCode.ts +5 -3
  279. package/src/models/addresses/Street.ts +6 -4
  280. package/src/models/index.ts +3 -0
  281. package/src/models/v1GroupMigrationData.ts +43 -0
  282. package/dist/helpers/MemberMerger.d.ts +0 -14
  283. package/dist/helpers/MemberMerger.d.ts.map +0 -1
  284. package/dist/helpers/MemberMerger.js +0 -364
  285. package/dist/helpers/MemberMerger.js.map +0 -1
  286. package/dist/migrations/1720080975-convert-charset.sql +0 -85
  287. package/dist/migrations/1723202126-member-number-index.sql +0 -2
  288. package/dist/models/OneTimeToken.d.ts +0 -38
  289. package/dist/models/OneTimeToken.d.ts.map +0 -1
  290. package/dist/models/OneTimeToken.js +0 -125
  291. package/dist/models/OneTimeToken.js.map +0 -1
  292. package/src/helpers/MemberMerger.test.ts +0 -782
  293. package/src/helpers/MemberMerger.ts +0 -577
  294. package/src/migrations/1720080975-convert-charset.sql +0 -85
  295. package/src/migrations/1723202126-member-number-index.sql +0 -2
  296. package/src/models/OneTimeToken.ts +0 -133
@@ -896,7 +896,7 @@ describe('Model.Email', () => {
896
896
  organizationId: organization.id,
897
897
  type: BalanceItemType.Other,
898
898
  amount: 2,
899
- unitPrice: 12_99,
899
+ unitPrice: 12_99_00,
900
900
  description: 'Test balance item',
901
901
  }).create();
902
902
  await CachedBalance.updateForUsers(organization.id, [existingUser.id]);
@@ -940,12 +940,12 @@ describe('Model.Email', () => {
940
940
  // Check if the table is correct
941
941
  expect(EmailMocker.getSucceededEmail(0).html).toMatch('<table');
942
942
  expect(EmailMocker.getSucceededEmail(0).html).toMatch('2 x '); // amount
943
- expect(EmailMocker.getSucceededEmail(0).html).toMatch(Formatter.price(12_99)); // unit price
943
+ expect(EmailMocker.getSucceededEmail(0).html).toMatch(Formatter.price(12_99_00)); // unit price
944
944
  expect(EmailMocker.getSucceededEmail(0).html).toMatch('<td>' + expectedAmount); // total price in table
945
945
  expect(EmailMocker.getSucceededEmail(0).html).toMatch('Test balance item'); // description
946
946
 
947
947
  expect(EmailMocker.getSucceededEmail(0).text).toMatch('2 x '); // amount
948
- expect(EmailMocker.getSucceededEmail(0).text).toMatch(Formatter.price(12_99)); // unit price
948
+ expect(EmailMocker.getSucceededEmail(0).text).toMatch(Formatter.price(12_99_00)); // unit price
949
949
  expect(EmailMocker.getSucceededEmail(0).text).toMatch(expectedAmount); // total price in table
950
950
  expect(EmailMocker.getSucceededEmail(0).text?.toLowerCase()).toMatch('test balance item'); // description
951
951
  }, 15_000);
@@ -23,11 +23,9 @@ type Attachment = { filename: string; path?: string; href?: string; content?: st
23
23
  function errorToSimpleErrors(e: unknown) {
24
24
  if (isSimpleErrors(e)) {
25
25
  return e;
26
- }
27
- else if (isSimpleError(e)) {
26
+ } else if (isSimpleError(e)) {
28
27
  return new SimpleErrors(e);
29
- }
30
- else {
28
+ } else {
31
29
  return new SimpleErrors(
32
30
  new SimpleError({
33
31
  code: 'unknown_error',
@@ -471,22 +469,22 @@ export class Email extends QueryableModel {
471
469
  case 'hard-bounce': {
472
470
  base.set('hardBouncesCount',
473
471
  SQL.calculation(SQL.column('hardBouncesCount'))
474
- .add(readDynamicSQLExpression(1))
475
- );
472
+ .add(readDynamicSQLExpression(1)),
473
+ );
476
474
  break;
477
475
  }
478
476
  case 'soft-bounce': {
479
477
  base.set('softBouncesCount',
480
478
  SQL.calculation(SQL.column('softBouncesCount'))
481
- .add(readDynamicSQLExpression(1))
482
- );
479
+ .add(readDynamicSQLExpression(1)),
480
+ );
483
481
  break;
484
482
  }
485
483
  case 'complaint': {
486
484
  base.set('spamComplaintsCount',
487
485
  SQL.calculation(SQL.column('spamComplaintsCount'))
488
- .add(readDynamicSQLExpression(1))
489
- );
486
+ .add(readDynamicSQLExpression(1)),
487
+ );
490
488
  break;
491
489
  }
492
490
  }
@@ -604,8 +602,7 @@ export class Email extends QueryableModel {
604
602
  });
605
603
  if (waitForSending) {
606
604
  return await this.resumeSending();
607
- }
608
- else {
605
+ } else {
609
606
  this.resumeSending().catch(console.error);
610
607
  }
611
608
  return this;
@@ -642,8 +639,7 @@ export class Email extends QueryableModel {
642
639
  contentType: attachment.contentType ?? undefined,
643
640
  encoding: 'base64',
644
641
  });
645
- }
646
- else {
642
+ } else {
647
643
  // Note: because we send lots of emails, we better download the file here so we can reuse it in every email instead of downloading it every time
648
644
  const withSigned = await attachment.file!.withSignedUrl();
649
645
  if (!withSigned || !withSigned.signedUrl) {
@@ -655,12 +651,11 @@ export class Email extends QueryableModel {
655
651
  }
656
652
 
657
653
  const filePath = withSigned.signedUrl;
658
- let fileBuffer: Buffer | null = null;
654
+ let fileBuffer: Buffer | null;
659
655
  try {
660
656
  const response = await fetch(filePath);
661
657
  fileBuffer = Buffer.from(await response.arrayBuffer());
662
- }
663
- catch (e) {
658
+ } catch (e) {
664
659
  throw new SimpleError({
665
660
  code: 'attachment_not_found',
666
661
  message: 'Attachment not found',
@@ -707,8 +702,7 @@ export class Email extends QueryableModel {
707
702
  // Update repacements that have been generated
708
703
  recipient.replacements = virtualRecipient.replacements;
709
704
  await recipient.save();
710
- }
711
- else {
705
+ } else {
712
706
  recipient.failCount += 1;
713
707
  recipient.failErrorMessage = error.message;
714
708
  if (recipient.failError) {
@@ -719,8 +713,7 @@ export class Email extends QueryableModel {
719
713
  recipient.lastFailedAt = new Date();
720
714
  await recipient.save();
721
715
  }
722
- }
723
- catch (e) {
716
+ } catch (e) {
724
717
  console.error(e);
725
718
  }
726
719
  promiseResolve();
@@ -761,8 +754,7 @@ export class Email extends QueryableModel {
761
754
  console.log('Email already sent, skipping...', upToDate.id);
762
755
  return upToDate;
763
756
  }
764
- }
765
- else {
757
+ } else {
766
758
  if (singleRecipientId) {
767
759
  // Not possible
768
760
  throw new SimpleError({
@@ -786,8 +778,7 @@ export class Email extends QueryableModel {
786
778
  await upToDate.save();
787
779
  return upToDate;
788
780
  }
789
- }
790
- else if (upToDate.status !== EmailStatus.Queued) {
781
+ } else if (upToDate.status !== EmailStatus.Queued) {
791
782
  console.error('Email is not queued or sending, cannot send', upToDate.id, upToDate.status);
792
783
  return upToDate;
793
784
  }
@@ -888,8 +879,7 @@ export class Email extends QueryableModel {
888
879
 
889
880
  try {
890
881
  await upToDate.save();
891
- }
892
- finally {
882
+ } finally {
893
883
  isSavingStatus = false;
894
884
  }
895
885
  }
@@ -938,8 +928,7 @@ export class Email extends QueryableModel {
938
928
  // Failed or soft-failed
939
929
  if (recipient.failError && isSoftEmailRecipientError(recipient.failError)) {
940
930
  softFailedCount += 1;
941
- }
942
- else {
931
+ } else {
943
932
  failedCount += 1;
944
933
  }
945
934
  skipped++;
@@ -957,13 +946,11 @@ export class Email extends QueryableModel {
957
946
  if (recipient.sentAt) {
958
947
  succeededCount += 1;
959
948
  await saveStatus();
960
- }
961
- else {
949
+ } else {
962
950
  // Failed or soft-failed
963
951
  if (recipient.failError && isSoftEmailRecipientError(recipient.failError)) {
964
952
  softFailedCount += 1;
965
- }
966
- else {
953
+ } else {
967
954
  failedCount += 1;
968
955
  }
969
956
  await saveStatus();
@@ -973,14 +960,12 @@ export class Email extends QueryableModel {
973
960
 
974
961
  if (sendingPromises.length > 0 || skipped > 0) {
975
962
  await Promise.all(sendingPromises);
976
- }
977
- else {
963
+ } else {
978
964
  break;
979
965
  }
980
966
  }
981
967
  }
982
- }
983
- catch (e) {
968
+ } catch (e) {
984
969
  if (!upToDate) {
985
970
  throw e;
986
971
  }
@@ -1023,8 +1008,7 @@ export class Email extends QueryableModel {
1023
1008
  human: $t(`%1EG`),
1024
1009
  }),
1025
1010
  );
1026
- }
1027
- else {
1011
+ } else {
1028
1012
  upToDate.status = EmailStatus.Sent;
1029
1013
  upToDate.emailErrors = null;
1030
1014
  }
@@ -1115,8 +1099,7 @@ export class Email extends QueryableModel {
1115
1099
  await upToDate.save();
1116
1100
 
1117
1101
  console.log('Updated recipient count for email', id, 'to', count);
1118
- }
1119
- catch (e) {
1102
+ } catch (e) {
1120
1103
  if (isAbortedError(e)) {
1121
1104
  return;
1122
1105
  }
@@ -1263,8 +1246,7 @@ export class Email extends QueryableModel {
1263
1246
  recipient.replacements = merged;
1264
1247
 
1265
1248
  break;
1266
- }
1267
- else {
1249
+ } else {
1268
1250
  console.log('Could not merge duplicate email recipient', item.email, other.id, 'keeping both', other.replacements, item.replacements);
1269
1251
  }
1270
1252
  }
@@ -1283,8 +1265,7 @@ export class Email extends QueryableModel {
1283
1265
 
1284
1266
  if (!recipient.email || duplicateOfRecipientId) {
1285
1267
  countWithoutEmail += 1;
1286
- }
1287
- else {
1268
+ } else {
1288
1269
  count += 1;
1289
1270
  }
1290
1271
  }
@@ -1299,8 +1280,7 @@ export class Email extends QueryableModel {
1299
1280
  upToDate.recipientsErrors = null;
1300
1281
  upToDate.membersCount = membersSet.size;
1301
1282
  await upToDate.save();
1302
- }
1303
- catch (e: unknown) {
1283
+ } catch (e: unknown) {
1304
1284
  console.error('Failed to build recipients for email', id);
1305
1285
  console.error(e);
1306
1286
  upToDate.recipientsStatus = EmailRecipientsStatus.NotCreated;
@@ -1381,8 +1361,7 @@ export class Email extends QueryableModel {
1381
1361
  }
1382
1362
 
1383
1363
  console.warn('No example recipient found for email', id);
1384
- }
1385
- catch (e) {
1364
+ } catch (e) {
1386
1365
  console.error('Failed to build example recipient for email', id);
1387
1366
  console.error(e);
1388
1367
  }
@@ -1,15 +1,15 @@
1
1
  import { column } from '@simonbackx/simple-database';
2
2
  import { SimpleError } from '@simonbackx/simple-errors';
3
- import type {I18n} from '@stamhoofd/backend-i18n';
3
+ import type { I18n } from '@stamhoofd/backend-i18n';
4
4
  import { QueryableModel } from '@stamhoofd/sql';
5
- import { EmailTemplateType, Recipient, Replacement } from '@stamhoofd/structures';
5
+ import { appToUri, EmailTemplateType, getAppHost, Recipient, Replacement } from '@stamhoofd/structures';
6
6
  import basex from 'base-x';
7
7
  import crypto from 'crypto';
8
8
  import { v4 as uuidv4 } from 'uuid';
9
9
  import { sendEmailTemplate } from '../helpers/EmailBuilder.js';
10
10
  import { Platform } from './Platform.js';
11
- import type {User} from './User.js';
12
- import type {Organization} from './Organization.js';
11
+ import type { User } from './User.js';
12
+ import type { Organization } from './Organization.js';
13
13
 
14
14
  const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
15
15
  const bs58 = basex(ALPHABET);
@@ -121,20 +121,8 @@ export class EmailVerificationCode extends QueryableModel {
121
121
  }
122
122
 
123
123
  getEmailVerificationUrl(user: User, organization: Organization | null, i18n: I18n) {
124
- let host: string;
125
- if (user.permissions || !organization || STAMHOOFD.userMode === 'platform') {
126
- host = 'https://' + (STAMHOOFD.domains.dashboard ?? 'stamhoofd.app') + '/' + i18n.locale;
127
- }
128
- else {
129
- // Add language if different than default
130
- host = 'https://' + organization.getHost();
131
-
132
- if (i18n.language !== organization.i18n.language) {
133
- host += '/' + i18n.language;
134
- }
135
- }
136
-
137
- return host + '/verify-email' + (user.organizationPermissions && this.organizationId ? '/' + encodeURIComponent(this.organizationId) : '') + '?code=' + encodeURIComponent(this.code) + '&token=' + encodeURIComponent(this.token);
124
+ const host = getAppHost('verify-email', organization, !!user.permissions, i18n);
125
+ return 'https://' + host + '?code=' + encodeURIComponent(this.code) + '&token=' + encodeURIComponent(this.token) + '&email=' + encodeURIComponent(this.email);
138
126
  }
139
127
 
140
128
  /**
@@ -283,8 +271,7 @@ export class EmailVerificationCode extends QueryableModel {
283
271
  },
284
272
  type: 'transactional',
285
273
  });
286
- }
287
- else {
274
+ } else {
288
275
  await sendEmailTemplate(organization, {
289
276
  recipients: [
290
277
  Recipient.create({
@@ -359,8 +346,7 @@ export class EmailVerificationCode extends QueryableModel {
359
346
 
360
347
  // Expire in 3 hours
361
348
  verificationCode.expiresAt = new Date(new Date().getTime() + 1000 * 60 * 60 * 3);
362
- }
363
- else {
349
+ } else {
364
350
  verificationCode = verificationCodes[0];
365
351
 
366
352
  if (verificationCode.email !== email || verificationCode.expiresAt < new Date(new Date().getTime() - 15 * 60 * 1000) || verificationCode.tries >= EmailVerificationCode.MAX_TRIES) {
@@ -68,6 +68,10 @@ export class Event extends QueryableModel {
68
68
  });
69
69
  }
70
70
 
71
+ get slug() {
72
+ return this.getStructure().slug;
73
+ }
74
+
71
75
  /**
72
76
  * @deprecated
73
77
  */
@@ -101,8 +105,7 @@ export class Event extends QueryableModel {
101
105
  group.settings.requireOrganizationIds = [this.organizationId];
102
106
  group.settings.requireOrganizationTags = [];
103
107
  group.settings.requirePlatformMembershipOn = null;
104
- }
105
- else {
108
+ } else {
106
109
  group.settings.requireOrganizationTags = this.meta.organizationTagIds ?? [];
107
110
 
108
111
  // Everyone can register
@@ -118,8 +121,7 @@ export class Event extends QueryableModel {
118
121
  if (waitingList) {
119
122
  if (group.settings.allowRegistrationsByOrganization) {
120
123
  waitingList.settings.allowRegistrationsByOrganization = true;
121
- }
122
- else {
124
+ } else {
123
125
  waitingList.settings.allowRegistrationsByOrganization = false;
124
126
  }
125
127
  await this.syncGroupRequirements(waitingList);
@@ -1,8 +1,8 @@
1
1
  import { column, ManyToManyRelation } from '@simonbackx/simple-database';
2
- import { MapDecoder, StringDecoder } from '@simonbackx/simple-encoding';
2
+ import { EnumDecoder } from '@simonbackx/simple-encoding';
3
3
  import { QueryableModel } from '@stamhoofd/sql';
4
- import type { RecordAnswer} from '@stamhoofd/structures';
5
- import { EventNotificationStatus, RecordAnswerDecoder } from '@stamhoofd/structures';
4
+ import type { RecordAnswer } from '@stamhoofd/structures';
5
+ import { EventNotificationStatus, RecordAnswerMapDecoder } from '@stamhoofd/structures';
6
6
  import { v4 as uuidv4 } from 'uuid';
7
7
  import { Event } from './Event.js';
8
8
 
@@ -26,7 +26,7 @@ export class EventNotification extends QueryableModel {
26
26
  @column({ type: 'datetime' })
27
27
  endDate: Date;
28
28
 
29
- @column({ type: 'string' })
29
+ @column({ type: 'string', decoder: new EnumDecoder(EventNotificationStatus) })
30
30
  status = EventNotificationStatus.Draft;
31
31
 
32
32
  /**
@@ -39,13 +39,13 @@ export class EventNotification extends QueryableModel {
39
39
  @column({ type: 'string' })
40
40
  organizationId: string;
41
41
 
42
- @column({ type: 'json', decoder: new MapDecoder(StringDecoder, RecordAnswerDecoder) })
42
+ @column({ type: 'json', decoder: RecordAnswerMapDecoder })
43
43
  recordAnswers: Map<string, RecordAnswer> = new Map();
44
44
 
45
45
  /**
46
46
  * Contains the answers of an event notification that were accepted
47
47
  */
48
- @column({ type: 'json', decoder: new MapDecoder(StringDecoder, RecordAnswerDecoder) })
48
+ @column({ type: 'json', decoder: RecordAnswerMapDecoder })
49
49
  acceptedRecordAnswers: Map<string, RecordAnswer> = new Map();
50
50
 
51
51
  @column({ type: 'string', nullable: true })
@@ -1,12 +1,12 @@
1
- import { column, Database, ManyToOneRelation } from '@simonbackx/simple-database';
2
- import type { GroupCategory} from '@stamhoofd/structures';
1
+ import { column, Database } from '@simonbackx/simple-database';
2
+ import type { GroupCategory } from '@stamhoofd/structures';
3
3
  import { GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType, StockReservation } from '@stamhoofd/structures';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
6
6
  import { ArrayDecoder } from '@simonbackx/simple-encoding';
7
7
  import { QueueHandler } from '@stamhoofd/queues';
8
8
  import { QueryableModel } from '@stamhoofd/sql';
9
- import type {OrganizationRegistrationPeriod} from './OrganizationRegistrationPeriod.js';
9
+ import type { OrganizationRegistrationPeriod } from './OrganizationRegistrationPeriod.js';
10
10
  import { Registration } from './Registration.js';
11
11
 
12
12
  if (Registration === undefined) {
@@ -90,6 +90,27 @@ export class Group extends QueryableModel {
90
90
  @column({ type: 'json', decoder: new ArrayDecoder(StockReservation) })
91
91
  stockReservations: StockReservation[] = [];
92
92
 
93
+ /**
94
+ * No registrations and waiting list registrations are possible if closed
95
+ */
96
+ get closed() {
97
+ if (this.status !== GroupStatus.Open) {
98
+ return true;
99
+ }
100
+
101
+ if (this.settings.notYetOpen) {
102
+ // Start date or pre registration date are in the future
103
+ return true;
104
+ }
105
+
106
+ const now = new Date();
107
+ if (this.settings.registrationEndDate && this.settings.registrationEndDate < now) {
108
+ return true;
109
+ }
110
+
111
+ return false;
112
+ }
113
+
93
114
  static async getAll(organizationId: string, periodId: string | null, active = true, types: GroupType[] = [GroupType.Membership]): Promise<Group[]> {
94
115
  const query = Group.select()
95
116
  .where('organizationId', organizationId);
@@ -1,10 +1,11 @@
1
1
  import { column } from '@simonbackx/simple-database';
2
- import { Company, File, Invoice as InvoiceStruct, PaymentCustomer, VATSubtotal } from '@stamhoofd/structures';
2
+ import { Company, File, PaymentCustomer, VATSubtotal } from '@stamhoofd/structures';
3
3
  import { v4 as uuidv4 } from 'uuid';
4
4
 
5
5
  import { ArrayDecoder } from '@simonbackx/simple-encoding';
6
6
  import { QueryableModel } from '@stamhoofd/sql';
7
7
  import { InvoicedBalanceItem } from './InvoicedBalanceItem.js';
8
+ import { Formatter } from '@stamhoofd/utility';
8
9
 
9
10
  export class Invoice extends QueryableModel {
10
11
  static table = 'invoices';
@@ -109,14 +110,6 @@ export class Invoice extends QueryableModel {
109
110
  @column({
110
111
  type: 'datetime',
111
112
  nullable: true,
112
- beforeSave(old?: any) {
113
- if (old !== undefined || !this.number) {
114
- return old;
115
- }
116
- const date = new Date();
117
- date.setMilliseconds(0);
118
- return date;
119
- },
120
113
  })
121
114
  invoicedAt: Date | null = null;
122
115
 
@@ -161,4 +154,12 @@ export class Invoice extends QueryableModel {
161
154
  const invoicedBalanceItems = await InvoicedBalanceItem.select().where('invoiceId', invoices.map(i => i.id)).fetch();
162
155
  return { invoicedBalanceItems };
163
156
  }
157
+
158
+ generateCustomerFilename(ext: 'pdf' | 'xml') {
159
+ if (!this.number || !this.invoicedAt) {
160
+ return this.id + '.' + ext;
161
+ }
162
+ const date = this.invoicedAt
163
+ return Formatter.dateIso(date) + ' - ' + (this.totalWithVAT < 0 ? $t('%1aE') : $t('%1Zw')) + ' ' + this.number + ' - ' + Formatter.fileSlug(this.seller.name) + '.' + ext;
164
+ }
164
165
  }
@@ -0,0 +1,70 @@
1
+ import { PlatformMembershipType, PlatformMembershipTypeBehaviour, PlatformMembershipTypeConfig } from '@stamhoofd/structures';
2
+
3
+ import { MemberFactory } from '../factories/MemberFactory.js';
4
+ import { OrganizationFactory } from '../factories/OrganizationFactory.js';
5
+ import { RegistrationPeriodFactory } from '../factories/RegistrationPeriodFactory.js';
6
+ import { MemberPlatformMembership } from './MemberPlatformMembership.js';
7
+ import { Platform } from './Platform.js';
8
+
9
+ describe('MemberPlatformMembership', () => {
10
+ beforeEach(() => {
11
+ vitest.useFakeTimers({ toFake: ['Date'] }).setSystemTime(new Date(2024, 4, 1, 0, 0, 0, 0));
12
+ });
13
+
14
+ afterEach(() => {
15
+ vitest.useRealTimers();
16
+ });
17
+
18
+ async function setupDaysMembership(maximumDays: number) {
19
+ const period = await new RegistrationPeriodFactory({
20
+ startDate: new Date(2024, 0, 1, 0, 0, 0, 0),
21
+ endDate: new Date(2024, 11, 31, 23, 59, 59, 999),
22
+ }).create();
23
+ const organization = await new OrganizationFactory({ period }).create();
24
+ const member = await new MemberFactory({ organization }).create();
25
+ const membershipType = PlatformMembershipType.create({
26
+ name: 'Days membership',
27
+ behaviour: PlatformMembershipTypeBehaviour.Days,
28
+ periods: new Map([
29
+ [period.id, PlatformMembershipTypeConfig.create({
30
+ startDate: period.startDate,
31
+ endDate: period.endDate,
32
+ maximumDays,
33
+ })],
34
+ ]),
35
+ });
36
+
37
+ const platform = await Platform.getForEditing();
38
+ platform.periodId = period.id;
39
+ platform.config.membershipTypes = [membershipType];
40
+ await platform.save();
41
+
42
+ const membership = new MemberPlatformMembership();
43
+ membership.memberId = member.id;
44
+ membership.membershipTypeId = membershipType.id;
45
+ membership.organizationId = organization.id;
46
+ membership.periodId = period.id;
47
+
48
+ return { member, membership };
49
+ }
50
+
51
+ test('allows the inclusive maximum number of days', async () => {
52
+ const { member, membership } = await setupDaysMembership(2);
53
+ membership.startDate = new Date(2024, 4, 1, 0, 0, 0, 0);
54
+ membership.endDate = new Date(2024, 4, 2, 0, 0, 0, 0);
55
+
56
+ await expect(membership.calculatePrice(member)).resolves.toBeUndefined();
57
+ expect(membership.maximumFreeAmount).toBe(2);
58
+ });
59
+
60
+ test('rejects days memberships that exceed maximum days', async () => {
61
+ const { member, membership } = await setupDaysMembership(2);
62
+ membership.startDate = new Date(2024, 4, 1, 0, 0, 0, 0);
63
+ membership.endDate = new Date(2024, 4, 3, 0, 0, 0, 0);
64
+
65
+ await expect(membership.calculatePrice(member)).rejects.toMatchObject({
66
+ code: 'invalid_field',
67
+ field: 'endDate',
68
+ });
69
+ });
70
+ });
@@ -207,8 +207,7 @@ export class MemberPlatformMembership extends QueryableModel {
207
207
  this.startDate = startBrussels.toJSDate();
208
208
  this.endDate = endBrussels.toJSDate();
209
209
  this.expireDate = null;
210
- }
211
- else {
210
+ } else {
212
211
  this.endDate = Formatter.luxon(periodConfig.endDate).set({ hour: 23, minute: 59, second: 59, millisecond: 0 }).toJSDate();
213
212
  this.expireDate = periodConfig.expireDate ? Formatter.luxon(periodConfig.expireDate).set({ hour: 23, minute: 59, second: 59, millisecond: 0 }).toJSDate() : null;
214
213
 
@@ -222,8 +221,7 @@ export class MemberPlatformMembership extends QueryableModel {
222
221
  startBrussels = startBrussels.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
223
222
  this.startDate = startBrussels.toJSDate();
224
223
  }
225
- }
226
- else {
224
+ } else {
227
225
  // Keep the set date, but make sure it is at 0:00 in CET
228
226
  let startBrussels = Formatter.luxon(this.startDate);
229
227
  startBrussels = startBrussels.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
@@ -248,6 +246,16 @@ export class MemberPlatformMembership extends QueryableModel {
248
246
  this.endDate = Formatter.luxon(this.startDate).set({ hour: 23, minute: 59, second: 59, millisecond: 0 }).toJSDate();
249
247
  }
250
248
 
249
+ const maximumEndDate = periodConfig.getMaximumEndDate(this.startDate, membershipType.behaviour);
250
+ if (this.endDate > maximumEndDate) {
251
+ throw new SimpleError({
252
+ code: 'invalid_field',
253
+ field: 'endDate',
254
+ message: 'End date is after the maximum allowed end date',
255
+ human: $t('%15C', { date: Formatter.date(maximumEndDate) }),
256
+ });
257
+ }
258
+
251
259
  if (periodConfig.trialDays) {
252
260
  // Check whether you are elegible for a trial
253
261
  const latestTrialDate = await this.isElegibleForTrial(member);
@@ -267,12 +275,10 @@ export class MemberPlatformMembership extends QueryableModel {
267
275
  }
268
276
 
269
277
  this.trialUntil = trialUntil.toJSDate();
270
- }
271
- else {
278
+ } else {
272
279
  this.trialUntil = null;
273
280
  }
274
- }
275
- else {
281
+ } else {
276
282
  // No trial
277
283
  this.trialUntil = null;
278
284
  }
@@ -347,8 +353,7 @@ export class MemberPlatformMembership extends QueryableModel {
347
353
  if (alreadyUsed < periodConfig.amountFree) {
348
354
  freeDays = periodConfig.amountFree - alreadyUsed;
349
355
  console.log('Free membership created for ', this.id, periodConfig.amountFree, alreadyUsed);
350
- }
351
- else {
356
+ } else {
352
357
  console.log('No free membership created for', this.id, periodConfig.amountFree, alreadyUsed);
353
358
  }
354
359
  }
@@ -359,8 +364,7 @@ export class MemberPlatformMembership extends QueryableModel {
359
364
  this.priceWithoutDiscount = earliestPriceConfig.calculatePrice(tagIds, false, days);
360
365
  this.price = priceConfig.calculatePrice(tagIds, shouldApplyReducedPrice, Math.max(0, days - freeDays));
361
366
  this.freeAmount = Math.min(days, freeDays);
362
- }
363
- else {
367
+ } else {
364
368
  this.priceWithoutDiscount = earliestPriceConfig.getBasePrice(tagIds, false);
365
369
  this.price = priceConfig.getBasePrice(tagIds, shouldApplyReducedPrice);
366
370
  this.maximumFreeAmount = this.price > 0 ? 1 : 0;