@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,15 +1,17 @@
1
- import { column, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
2
  import { SimpleError } from '@simonbackx/simple-errors';
3
- import { SQL, SQLSelect, SQLWhereSign } from '@stamhoofd/sql';
3
+ import { QueryableModel, SQL, SQLWhereSign } from '@stamhoofd/sql';
4
4
  import { PlatformMembershipTypeBehaviour } from '@stamhoofd/structures';
5
5
  import { Formatter } from '@stamhoofd/utility';
6
6
  import { v4 as uuidv4 } from 'uuid';
7
+ import { BalanceItem } from './BalanceItem';
7
8
  import { Member } from './Member';
8
9
  import { Organization } from './Organization';
9
10
  import { Platform } from './Platform';
10
- import { BalanceItem } from './BalanceItem';
11
+ import { Registration } from './Registration';
12
+ import { RegistrationPeriod } from './RegistrationPeriod';
11
13
 
12
- export class MemberPlatformMembership extends Model {
14
+ export class MemberPlatformMembership extends QueryableModel {
13
15
  static table = 'member_platform_memberships';
14
16
 
15
17
  // Columns
@@ -38,6 +40,15 @@ export class MemberPlatformMembership extends Model {
38
40
  @column({ type: 'date' })
39
41
  endDate: Date;
40
42
 
43
+ /**
44
+ * This membership won't get charged before this day.
45
+ * The membership can still get removed before this day.
46
+ *
47
+ * If a membership is deleted during trial -> do not set deletedAt, but set price to 0 and set trialUntil and endDate to the current date
48
+ */
49
+ @column({ type: 'date', nullable: true })
50
+ trialUntil: Date | null = null;
51
+
41
52
  @column({ type: 'date', nullable: true })
42
53
  expireDate: Date | null = null;
43
54
 
@@ -104,14 +115,44 @@ export class MemberPlatformMembership extends Model {
104
115
  throw new Error('Cannot delete a membership. Use the deletedAt column.');
105
116
  }
106
117
 
107
- async calculatePrice(member: Member) {
118
+ async isElegibleForTrial(member: Member) {
119
+ const period = await RegistrationPeriod.getByID(this.periodId);
120
+ if (!period) {
121
+ return false;
122
+ }
123
+
124
+ if (!period.previousPeriodId) {
125
+ // We have no previous period = no data = no trials
126
+ return false;
127
+ }
128
+
129
+ const platform = await Platform.getSharedStruct();
130
+ const typeIds = platform.config.membershipTypes.filter(m => m.behaviour === PlatformMembershipTypeBehaviour.Period).map(m => m.id);
131
+
132
+ const membership = await MemberPlatformMembership.select()
133
+ .where('memberId', member.id)
134
+ .where('deletedAt', null)
135
+ .where('periodId', period.previousPeriodId)
136
+ .where('membershipTypeId', typeIds)
137
+ .first(false);
138
+
139
+ const hasBlockingMemberships = !!membership;
140
+
141
+ if (hasBlockingMemberships) {
142
+ return false;
143
+ }
144
+
145
+ return true;
146
+ }
147
+
148
+ async calculatePrice(member: Member, registration?: Registration) {
108
149
  const platform = await Platform.getSharedPrivateStruct();
109
- const membershipType = platform.config.membershipTypes.find(m => m.id == this.membershipTypeId);
150
+ const membershipType = platform.config.membershipTypes.find(m => m.id === this.membershipTypeId);
110
151
 
111
152
  if (!membershipType) {
112
153
  throw new SimpleError({
113
154
  code: 'invalid_membership_type',
114
- message: 'Uknown membership type',
155
+ message: 'Unknown membership type',
115
156
  human: 'Deze aansluiting is niet (meer) beschikbaar',
116
157
  });
117
158
  }
@@ -192,7 +233,6 @@ export class MemberPlatformMembership extends Model {
192
233
  else {
193
234
  this.priceWithoutDiscount = earliestPriceConfig.getBasePrice(tagIds, false);
194
235
  this.price = priceConfig.getBasePrice(tagIds, shouldApplyReducedPrice);
195
- this.startDate = periodConfig.startDate;
196
236
  this.endDate = periodConfig.endDate;
197
237
  this.expireDate = periodConfig.expireDate;
198
238
  this.maximumFreeAmount = this.price > 0 ? 1 : 0;
@@ -202,6 +242,53 @@ export class MemberPlatformMembership extends Model {
202
242
  this.price = 0;
203
243
  this.freeAmount = 1;
204
244
  }
245
+
246
+ // Alter start date
247
+ if (registration && registration.startDate) {
248
+ this.startDate = periodConfig.startDate;
249
+
250
+ if (registration.startDate > periodConfig.startDate && registration.startDate < periodConfig.endDate) {
251
+ let startBrussels = Formatter.luxon(registration.startDate);
252
+ startBrussels = startBrussels.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
253
+ this.startDate = startBrussels.toJSDate();
254
+ }
255
+ }
256
+ else {
257
+ let startBrussels = Formatter.luxon(this.startDate);
258
+ startBrussels = startBrussels.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
259
+ this.startDate = startBrussels.toJSDate();
260
+ }
261
+ }
262
+
263
+ if (periodConfig.trialDays) {
264
+ // Check whether you are elegible for a trial
265
+ if (await this.isElegibleForTrial(member)) {
266
+ // Allowed to set trial until, maximum periodConfig.trialDays after startDate
267
+ let trialUntil = Formatter.luxon(this.startDate).plus({ days: periodConfig.trialDays });
268
+ trialUntil = trialUntil.set({ hour: 23, minute: 59, second: 59, millisecond: 0 });
269
+
270
+ // Max end date
271
+ if (trialUntil.toJSDate() > this.endDate) {
272
+ trialUntil = Formatter.luxon(this.endDate).set({ hour: 23, minute: 59, second: 59, millisecond: 0 });
273
+ }
274
+
275
+ this.trialUntil = trialUntil.toJSDate();
276
+ }
277
+ else {
278
+ this.trialUntil = null;
279
+ }
280
+ }
281
+ else {
282
+ // No trial
283
+ this.trialUntil = null;
284
+ }
285
+
286
+ // Never charge itself
287
+ const chargeVia = platform.membershipOrganizationId;
288
+ if (this.organizationId === chargeVia) {
289
+ this.price = 0;
290
+ this.priceWithoutDiscount = 0;
291
+ this.freeAmount = 0;
205
292
  }
206
293
 
207
294
  if (this.balanceItemId) {
@@ -230,22 +317,4 @@ export class MemberPlatformMembership extends Model {
230
317
  }
231
318
  }
232
319
  }
233
-
234
- /**
235
- * Experimental: needs to move to library
236
- */
237
- static select() {
238
- const transformer = (row: SQLResultNamespacedRow): MemberPlatformMembership => {
239
- const d = (this as typeof MemberPlatformMembership & typeof Model).fromRow(row[this.table] as any) as MemberPlatformMembership | undefined;
240
-
241
- if (!d) {
242
- throw new Error('MemberPlatformMembership not found');
243
- }
244
-
245
- return d;
246
- };
247
-
248
- const select = new SQLSelect(transformer, SQL.wildcard());
249
- return select.from(SQL.table(this.table));
250
- }
251
320
  }
@@ -1,9 +1,9 @@
1
- import { column, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
- import { SQL, SQLSelect } from '@stamhoofd/sql';
1
+ import { column } from '@simonbackx/simple-database';
2
+ import { QueryableModel } from '@stamhoofd/sql';
3
3
  import { Group as GroupStruct, MemberResponsibilityRecordBase, MemberResponsibilityRecord as MemberResponsibilityRecordStruct } from '@stamhoofd/structures';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
6
- export class MemberResponsibilityRecord extends Model {
6
+ export class MemberResponsibilityRecord extends QueryableModel {
7
7
  static table = 'member_responsibility_records';
8
8
 
9
9
  // Columns
@@ -53,22 +53,4 @@ export class MemberResponsibilityRecord extends Model {
53
53
  group,
54
54
  });
55
55
  }
56
-
57
- /**
58
- * Experimental: needs to move to library
59
- */
60
- static select() {
61
- const transformer = (row: SQLResultNamespacedRow): MemberResponsibilityRecord => {
62
- const d = (this as typeof MemberResponsibilityRecord & typeof Model).fromRow(row[this.table] as any) as MemberResponsibilityRecord | undefined;
63
-
64
- if (!d) {
65
- throw new Error('MemberResponsibilityRecord not found');
66
- }
67
-
68
- return d;
69
- };
70
-
71
- const select = new SQLSelect(transformer, SQL.wildcard());
72
- return select.from(SQL.table(this.table));
73
- }
74
56
  }
@@ -0,0 +1,18 @@
1
+ import { column } from '@simonbackx/simple-database';
2
+ import { QueryableModel } from '@stamhoofd/sql';
3
+
4
+ export class MemberUser extends QueryableModel {
5
+ static table = '_members_users';
6
+
7
+ // Columns
8
+ @column({
9
+ primary: true, type: 'integer',
10
+ })
11
+ id!: number;
12
+
13
+ @column({ type: 'string' })
14
+ membersId: string;
15
+
16
+ @column({ type: 'string' })
17
+ usersId: string;
18
+ }
@@ -1,8 +1,9 @@
1
- import { Model, column } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
+ import { QueryableModel } from '@stamhoofd/sql';
2
3
  import { MemberDetails } from '@stamhoofd/structures';
3
4
  import { Member } from './';
4
5
 
5
- export class MergedMember extends Model {
6
+ export class MergedMember extends QueryableModel {
6
7
  static override table = 'merged_members';
7
8
 
8
9
  // #region Member columns
@@ -41,7 +42,7 @@ export class MergedMember extends Model {
41
42
  details: MemberDetails;
42
43
 
43
44
  /**
44
- * Not yet paid balance
45
+ * @deprecated
45
46
  */
46
47
  @column({ type: 'integer' })
47
48
  outstandingBalance = 0;
@@ -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 MolliePayment extends Model {
5
+ export class MolliePayment extends QueryableModel {
5
6
  static table = 'mollie_payments';
6
7
 
7
8
  @column({
@@ -1,6 +1,7 @@
1
- import { column, Model } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
2
  import { PartialWithoutMethods, PlainObject } from '@simonbackx/simple-encoding';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
+ import { QueryableModel } from '@stamhoofd/sql';
4
5
  import { MollieOnboarding, MollieProfile, MollieStatus } from '@stamhoofd/structures';
5
6
  import { IncomingMessage } from 'http';
6
7
  import https from 'https';
@@ -11,7 +12,7 @@ function sleep(ms: number) {
11
12
  return new Promise(resolve => setTimeout(resolve, ms));
12
13
  }
13
14
 
14
- export class MollieToken extends Model {
15
+ export class MollieToken extends QueryableModel {
15
16
  static table = 'mollie_tokens';
16
17
 
17
18
  @column({ primary: true, type: 'string' })
@@ -1,5 +1,6 @@
1
- import { column, Model } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
2
  import { AnyDecoder } from '@simonbackx/simple-encoding';
3
+ import { QueryableModel } from '@stamhoofd/sql';
3
4
  import basex from 'base-x';
4
5
  import crypto from 'crypto';
5
6
 
@@ -28,7 +29,7 @@ enum OneTimeTokenType {
28
29
  /**
29
30
  * Token that saves some information and can execute an action if you have access to the token (e.g. in an email)
30
31
  */
31
- export class OneTimeToken extends Model {
32
+ export class OneTimeToken extends QueryableModel {
32
33
  static table = 'one_time_tokens';
33
34
 
34
35
  // Columns
@@ -1,6 +1,7 @@
1
- import { column, ManyToOneRelation, Model } from '@simonbackx/simple-database';
1
+ import { column, ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { SimpleError } from '@simonbackx/simple-errors';
3
3
  import { QueueHandler } from '@stamhoofd/queues';
4
+ import { QueryableModel } from '@stamhoofd/sql';
4
5
  import { BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, BalanceItemWithPayments, BalanceItemWithPrivatePayments, EmailTemplateType, OrderData, OrderStatus, Order as OrderStruct, PaymentMethod, Payment as PaymentStruct, PrivateOrder, PrivatePayment, ProductType, Recipient, Replacement, WebshopPreview, WebshopStatus, WebshopTicketType, WebshopTimeSlot } from '@stamhoofd/structures';
5
6
  import { Formatter } from '@stamhoofd/utility';
6
7
  import { v4 as uuidv4 } from 'uuid';
@@ -9,7 +10,7 @@ import { sendEmailTemplate } from '../helpers/EmailBuilder';
9
10
  import { WebshopCounter } from '../helpers/WebshopCounter';
10
11
  import { BalanceItem, Organization, Payment, Ticket, Webshop, WebshopDiscountCode } from './';
11
12
 
12
- export class Order extends Model {
13
+ export class Order extends QueryableModel {
13
14
  static table = 'webshop_orders';
14
15
 
15
16
  // Columns
@@ -1,8 +1,9 @@
1
- import { column, Database, Model } from '@simonbackx/simple-database';
1
+ import { column, Database } from '@simonbackx/simple-database';
2
2
  import { DecodedRequest } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { I18n } from '@stamhoofd/backend-i18n';
5
5
  import { Email, EmailInterfaceRecipient } from '@stamhoofd/email';
6
+ import { QueryableModel } from '@stamhoofd/sql';
6
7
  import { Address, Country, DNSRecordStatus, EmailTemplateType, OrganizationEmail, OrganizationMetaData, OrganizationPrivateMetaData, Organization as OrganizationStruct, PaymentMethod, PaymentProvider, PrivatePaymentConfiguration, Recipient, Replacement, STPackageType, TransferSettings } from '@stamhoofd/structures';
7
8
  import { AWSError } from 'aws-sdk';
8
9
  import SES from 'aws-sdk/clients/sesv2';
@@ -15,7 +16,7 @@ import { getEmailBuilderForTemplate } from '../helpers/EmailBuilder';
15
16
  import { OrganizationServerMetaData } from '../structures/OrganizationServerMetaData';
16
17
  import { OrganizationRegistrationPeriod, StripeAccount } from './';
17
18
 
18
- export class Organization extends Model {
19
+ export class Organization extends QueryableModel {
19
20
  static table = 'organizations';
20
21
 
21
22
  @column({
@@ -1,10 +1,11 @@
1
- import { column, Model } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
+ import { QueryableModel } from '@stamhoofd/sql';
2
3
  import { Group as GroupStruct, OrganizationRegistrationPeriodSettings, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, SetupSteps } from '@stamhoofd/structures';
3
4
  import { Formatter } from '@stamhoofd/utility';
4
5
  import { v4 as uuidv4 } from 'uuid';
5
6
  import { Group, RegistrationPeriod } from '.';
6
7
 
7
- export class OrganizationRegistrationPeriod extends Model {
8
+ export class OrganizationRegistrationPeriod extends QueryableModel {
8
9
  static table = 'organization_registration_periods';
9
10
 
10
11
  @column({
@@ -1,5 +1,6 @@
1
- import { column, Database, ManyToOneRelation, Model } from '@simonbackx/simple-database';
1
+ import { column, Database, ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { I18n } from '@stamhoofd/backend-i18n';
3
+ import { QueryableModel } from '@stamhoofd/sql';
3
4
  import basex from 'base-x';
4
5
  import crypto from 'crypto';
5
6
 
@@ -21,7 +22,7 @@ async function randomBytes(size: number): Promise<Buffer> {
21
22
  });
22
23
  }
23
24
 
24
- export class PasswordToken extends Model {
25
+ export class PasswordToken extends QueryableModel {
25
26
  static table = 'password_tokens';
26
27
 
27
28
  // Columns
@@ -1,5 +1,6 @@
1
- import { column, Model } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
2
  import { SimpleError } from '@simonbackx/simple-errors';
3
+ import { QueryableModel } from '@stamhoofd/sql';
3
4
  import { PayconiqAccount, PaymentStatus, Version } from '@stamhoofd/structures';
4
5
  import { IncomingMessage } from 'http';
5
6
  import https from 'https';
@@ -7,7 +8,7 @@ import { v4 as uuidv4 } from 'uuid';
7
8
 
8
9
  import { Organization, Payment } from './';
9
10
 
10
- export class PayconiqPayment extends Model {
11
+ export class PayconiqPayment extends QueryableModel {
11
12
  static table = 'payconiq_payments';
12
13
 
13
14
  @column({
@@ -1,12 +1,12 @@
1
- import { column, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
2
  import { BalanceItemDetailed, BalanceItemPaymentDetailed, PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus, Settlement, TransferSettings, BaseOrganization, PaymentType } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
6
6
  import { Organization } from './';
7
- import { SQL, SQLSelect } from '@stamhoofd/sql';
7
+ import { QueryableModel } from '@stamhoofd/sql';
8
8
 
9
- export class Payment extends Model {
9
+ export class Payment extends QueryableModel {
10
10
  static table = 'payments';
11
11
 
12
12
  @column({
@@ -245,22 +245,4 @@ export class Payment extends Model {
245
245
 
246
246
  return { registrations, orders };
247
247
  }
248
-
249
- /**
250
- * Experimental: needs to move to library
251
- */
252
- static select() {
253
- const transformer = (row: SQLResultNamespacedRow): Payment => {
254
- const d = (this as typeof Payment & typeof Model).fromRow(row[this.table] as any) as Payment | undefined;
255
-
256
- if (!d) {
257
- throw new Error('EmailTemplate not found');
258
- }
259
-
260
- return d;
261
- };
262
-
263
- const select = new SQLSelect(transformer, SQL.wildcard(this.table));
264
- return select.from(SQL.table(this.table));
265
- }
266
248
  }
@@ -1,10 +1,11 @@
1
- import { column, Model } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
2
  import { QueueHandler } from '@stamhoofd/queues';
3
- import { Platform as PlatformStruct, PlatformConfig, PlatformPrivateConfig } from '@stamhoofd/structures';
3
+ import { QueryableModel } from '@stamhoofd/sql';
4
+ import { PlatformConfig, PlatformPrivateConfig, Platform as PlatformStruct } from '@stamhoofd/structures';
4
5
  import { v4 as uuidv4 } from 'uuid';
5
6
  import { RegistrationPeriod } from './RegistrationPeriod';
6
7
 
7
- export class Platform extends Model {
8
+ export class Platform extends QueryableModel {
8
9
  static table = 'platform';
9
10
 
10
11
  @column({
@@ -20,6 +21,9 @@ export class Platform extends Model {
20
21
  @column({ type: 'string' })
21
22
  periodId: string;
22
23
 
24
+ @column({ type: 'string', nullable: true })
25
+ previousPeriodId: string | null = null;
26
+
23
27
  @column({ type: 'string', nullable: true })
24
28
  membershipOrganizationId: string | null = null;
25
29
 
@@ -37,6 +41,11 @@ export class Platform extends Model {
37
41
  return clone;
38
42
  }
39
43
 
44
+ async setPreviousPeriodId() {
45
+ const period = await RegistrationPeriod.getByID(this.periodId);
46
+ this.previousPeriodId = period?.previousPeriodId ?? null;
47
+ }
48
+
40
49
  static async getSharedPrivateStruct(): Promise<PlatformStruct & { privateConfig: PlatformPrivateConfig }> {
41
50
  if (this.sharedStruct && this.sharedStruct.privateConfig) {
42
51
  return this.sharedStruct as any;
@@ -76,8 +85,8 @@ export class Platform extends Model {
76
85
  }
77
86
 
78
87
  async save() {
79
- Platform.clearCache();
80
88
  const s = await super.save();
89
+ Platform.clearCache();
81
90
 
82
91
  // Force update cache immediately
83
92
  await Platform.getSharedStruct();
@@ -1,4 +1,5 @@
1
- import { column, Model } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
+ import { QueryableModel } from '@stamhoofd/sql';
2
3
  import basex from 'base-x';
3
4
  import crypto from 'crypto';
4
5
 
@@ -17,7 +18,7 @@ async function randomBytes(size: number): Promise<Buffer> {
17
18
  });
18
19
  }
19
20
 
20
- export class RegisterCode extends Model {
21
+ export class RegisterCode extends QueryableModel {
21
22
  static table = 'register_codes';
22
23
 
23
24
  @column({ type: 'string', primary: true })
@@ -1,13 +1,14 @@
1
- import { column, Database, ManyToOneRelation, Model } from '@simonbackx/simple-database';
1
+ import { column, Database, ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { EmailTemplateType, GroupPrice, PaymentMethod, PaymentMethodHelper, Recipient, RecordAnswer, RecordAnswerDecoder, RegisterItemOption, Registration as RegistrationStructure, Replacement, StockReservation } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
6
6
  import { ArrayDecoder, MapDecoder, StringDecoder } from '@simonbackx/simple-encoding';
7
+ import { QueryableModel } from '@stamhoofd/sql';
7
8
  import { sendEmailTemplate } from '../helpers/EmailBuilder';
8
9
  import { Group, Organization, User } from './';
9
10
 
10
- export class Registration extends Model {
11
+ export class Registration extends QueryableModel {
11
12
  static table = 'registrations';
12
13
 
13
14
  @column({
@@ -53,6 +54,10 @@ export class Registration extends Model {
53
54
  @column({ type: 'integer' })
54
55
  cycle: number = 0;
55
56
 
57
+ /**
58
+ * @deprecated
59
+ * Moved to cached balances
60
+ */
56
61
  @column({ type: 'integer', nullable: true })
57
62
  price: number | null = null;
58
63
 
@@ -78,12 +83,27 @@ export class Registration extends Model {
78
83
  })
79
84
  updatedAt: Date;
80
85
 
86
+ /**
87
+ * The date when the registration was confirmed
88
+ */
81
89
  @column({ type: 'datetime', nullable: true })
82
90
  registeredAt: Date | null = null;
83
91
 
84
92
  @column({ type: 'datetime', nullable: true })
85
93
  reservedUntil: Date | null = null;
86
94
 
95
+ /**
96
+ * Start date of the registration. Defaults to the start date of the related group
97
+ */
98
+ @column({ type: 'datetime', nullable: true })
99
+ startDate: Date | null = null;
100
+
101
+ /**
102
+ * If this registration is under a trial, this is the end date of the trial
103
+ */
104
+ @column({ type: 'datetime', nullable: true })
105
+ trialUntil: Date | null = null;
106
+
87
107
  /**
88
108
  * @deprecated - replaced by group type
89
109
  */
@@ -101,7 +121,8 @@ export class Registration extends Model {
101
121
  deactivatedAt: Date | null = null;
102
122
 
103
123
  /**
104
- * Part of price that is paid
124
+ * @deprecated
125
+ * Moved to cached balances
105
126
  */
106
127
  @column({ type: 'integer' })
107
128
  pricePaid = 0;
@@ -122,45 +143,6 @@ export class Registration extends Model {
122
143
  });
123
144
  }
124
145
 
125
- /**
126
- * Update the outstanding balance of multiple members in one go (or all members)
127
- */
128
- static async updateOutstandingBalance(registrationIds: string[] | 'all', organizationId?: string) {
129
- if (registrationIds !== 'all' && registrationIds.length === 0) {
130
- return;
131
- }
132
-
133
- const params: any[] = [];
134
- let firstWhere = '';
135
- let secondWhere = '';
136
-
137
- if (registrationIds !== 'all') {
138
- firstWhere = ` AND registrationId IN (?)`;
139
- params.push(registrationIds);
140
-
141
- secondWhere = `WHERE registrations.id IN (?)`;
142
- params.push(registrationIds);
143
- }
144
-
145
- const query = `UPDATE
146
- registrations
147
- LEFT JOIN (
148
- SELECT
149
- registrationId,
150
- sum(unitPrice * amount) AS price,
151
- sum(pricePaid) AS pricePaid
152
- FROM
153
- balance_items
154
- WHERE status != 'Hidden'${firstWhere}
155
- GROUP BY
156
- registrationId
157
- ) i ON i.registrationId = registrations.id
158
- SET registrations.price = coalesce(i.price, 0), registrations.pricePaid = coalesce(i.pricePaid, 0)
159
- ${secondWhere}`;
160
-
161
- await Database.update(query, params);
162
- }
163
-
164
146
  /**
165
147
  * Get the number of active members that are currently registered
166
148
  * This is used for billing
@@ -198,14 +180,6 @@ export class Registration extends Model {
198
180
  email: user.email,
199
181
  userId: user.id,
200
182
  replacements: [
201
- Replacement.create({
202
- token: 'firstName',
203
- value: member.details.firstName,
204
- }),
205
- Replacement.create({
206
- token: 'lastName',
207
- value: member.details.lastName,
208
- }),
209
183
  Replacement.create({
210
184
  token: 'firstNameMember',
211
185
  value: member.details.firstName,
@@ -214,27 +188,14 @@ export class Registration extends Model {
214
188
  token: 'lastNameMember',
215
189
  value: member.details.lastName,
216
190
  }),
217
- Replacement.create({
218
- token: 'email',
219
- value: user.email,
220
- }),
221
191
  Replacement.create({
222
192
  token: 'registerUrl',
223
193
  value: 'https://' + organization.getHost(),
224
194
  }),
225
- Replacement.create({
226
- token: 'organizationName',
227
- value: organization.name,
228
- }),
229
195
  Replacement.create({
230
196
  token: 'groupName',
231
197
  value: group.settings.name,
232
198
  }),
233
- Replacement.create({
234
- token: 'loginDetails',
235
- value: '',
236
- html: user.hasAccount() ? `<p class="description"><em>Je kan op het ledenportaal inloggen met <strong>${Formatter.escapeHtml(user.email)}</strong></em></p>` : `<p class="description"><em>Je kan op het ledenportaal een nieuw account aanmaken met het e-mailadres <strong>${Formatter.escapeHtml(user.email)}</strong>, dan krijg je automatisch toegang tot alle bestaande gegevens.</em></p>`,
237
- }),
238
199
  ],
239
200
  }));
240
201
  }
@@ -329,11 +290,6 @@ export class Registration extends Model {
329
290
  token: 'organizationName',
330
291
  value: organization.name,
331
292
  }),
332
- Replacement.create({
333
- token: 'loginDetails',
334
- value: '',
335
- html: user.hasAccount() ? `<p class="description"><em>Je kan op het ledenportaal inloggen met <strong>${Formatter.escapeHtml(user.email)}</strong></em></p>` : `<p class="description"><em>Je kan op het ledenportaal een nieuw account aanmaken met het e-mailadres <strong>${Formatter.escapeHtml(user.email)}</strong>, dan krijg je automatisch toegang tot alle bestaande gegevens.</em></p>`,
336
- }),
337
293
  ],
338
294
  }),
339
295
  ];