@stamhoofd/models 2.121.0 → 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 (241) 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/STPackageFactory.js.map +1 -1
  9. package/dist/factories/UserFactory.d.ts.map +1 -1
  10. package/dist/factories/UserFactory.js +2 -2
  11. package/dist/factories/UserFactory.js.map +1 -1
  12. package/dist/helpers/EmailBuilder.d.ts.map +1 -1
  13. package/dist/helpers/EmailBuilder.js +8 -8
  14. package/dist/helpers/EmailBuilder.js.map +1 -1
  15. package/dist/helpers/Handlebars.d.ts.map +1 -1
  16. package/dist/helpers/Handlebars.js +7 -1
  17. package/dist/helpers/Handlebars.js.map +1 -1
  18. package/dist/index.d.ts +0 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +0 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/migrations/1605262045-import-postcodes.d.ts.map +1 -1
  23. package/dist/migrations/1605262045-import-postcodes.js +58 -24
  24. package/dist/migrations/1605262045-import-postcodes.js.map +1 -1
  25. package/dist/migrations/1605262046-import-postcodes-nl.d.ts.map +1 -1
  26. package/dist/migrations/1605262046-import-postcodes-nl.js +54 -17
  27. package/dist/migrations/1605262046-import-postcodes-nl.js.map +1 -1
  28. package/dist/migrations/1719567881-organization-periodId.sql +2 -0
  29. package/dist/migrations/1719567882-groups-periodId.sql +2 -0
  30. package/dist/migrations/1720080975-convert-charset.d.ts +4 -0
  31. package/dist/migrations/1720080975-convert-charset.d.ts.map +1 -0
  32. package/dist/migrations/1720080975-convert-charset.js +26 -0
  33. package/dist/migrations/1720080975-convert-charset.js.map +1 -0
  34. package/dist/migrations/1720080976-convert-charset-leads.d.ts.map +1 -1
  35. package/dist/migrations/1720080976-convert-charset-leads.js +11 -10
  36. package/dist/migrations/1720080976-convert-charset-leads.js.map +1 -1
  37. package/dist/migrations/1721400546-users-memberId.sql +2 -0
  38. package/dist/migrations/1722269236-group-waitinglist-id.sql +2 -1
  39. package/dist/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
  40. package/dist/migrations/1722525787-depending-balance-item.sql +2 -0
  41. package/dist/migrations/1722963554-registration-group-price-and-options.sql +1 -1
  42. package/dist/migrations/1723652797-payments-paying-organization-id-fk.sql +2 -0
  43. package/dist/migrations/1733317908-added-missing-organization-fk-on-registrations.sql +2 -0
  44. package/dist/migrations/1733317910-paying-organization-id-fk.sql +2 -0
  45. package/dist/migrations/1733504881-negative-invoice-id.sql +6 -0
  46. package/dist/migrations/1733994455-balance-item-status-open.d.ts +4 -0
  47. package/dist/migrations/1733994455-balance-item-status-open.d.ts.map +1 -0
  48. package/dist/migrations/1733994455-balance-item-status-open.js +28 -0
  49. package/dist/migrations/1733994455-balance-item-status-open.js.map +1 -0
  50. package/dist/migrations/1769087808-corrected-invoice-user-agent.sql +2 -0
  51. package/dist/migrations/1769087809-payments-invoice-id.sql +2 -0
  52. package/dist/migrations/1772033555-balance-item-package-id.sql +2 -0
  53. package/dist/migrations/1778796615-payments-reversing-payment-id.sql +2 -0
  54. package/dist/migrations/1779443446-transfer-fees.sql +3 -0
  55. package/dist/migrations/1779709174-used-register-code-balance-item-id.sql +5 -0
  56. package/dist/migrations/1779968328-payments-admin-user-id.sql +5 -0
  57. package/dist/migrations/1779970611-payments-refunded-amount.sql +2 -0
  58. package/dist/migrations/1779972640-balance-items-failed-at.sql +2 -0
  59. package/dist/migrations/1780328285-document-template-locked.sql +2 -0
  60. package/dist/migrations/1780328286-document-locked.sql +2 -0
  61. package/dist/migrations/1780412083-documents-set-locked.d.ts +4 -0
  62. package/dist/migrations/1780412083-documents-set-locked.d.ts.map +1 -0
  63. package/dist/migrations/1780412083-documents-set-locked.js +14 -0
  64. package/dist/migrations/1780412083-documents-set-locked.js.map +1 -0
  65. package/dist/migrations/1780928401-v1-groups-migration-data.d.ts +4 -0
  66. package/dist/migrations/1780928401-v1-groups-migration-data.d.ts.map +1 -0
  67. package/dist/migrations/1780928401-v1-groups-migration-data.js +44 -0
  68. package/dist/migrations/1780928401-v1-groups-migration-data.js.map +1 -0
  69. package/dist/models/BalanceItem.d.ts +7 -2
  70. package/dist/models/BalanceItem.d.ts.map +1 -1
  71. package/dist/models/BalanceItem.js +41 -38
  72. package/dist/models/BalanceItem.js.map +1 -1
  73. package/dist/models/CachedBalance.d.ts +6 -1
  74. package/dist/models/CachedBalance.d.ts.map +1 -1
  75. package/dist/models/CachedBalance.js +3 -2
  76. package/dist/models/CachedBalance.js.map +1 -1
  77. package/dist/models/Document.d.ts +4 -0
  78. package/dist/models/Document.d.ts.map +1 -1
  79. package/dist/models/Document.js +26 -3
  80. package/dist/models/Document.js.map +1 -1
  81. package/dist/models/DocumentTemplate.d.ts +4 -0
  82. package/dist/models/DocumentTemplate.d.ts.map +1 -1
  83. package/dist/models/DocumentTemplate.js +37 -1
  84. package/dist/models/DocumentTemplate.js.map +1 -1
  85. package/dist/models/Email.d.ts.map +1 -1
  86. package/dist/models/Email.js +1 -1
  87. package/dist/models/Email.js.map +1 -1
  88. package/dist/models/EmailVerificationCode.d.ts.map +1 -1
  89. package/dist/models/EmailVerificationCode.js +3 -13
  90. package/dist/models/EmailVerificationCode.js.map +1 -1
  91. package/dist/models/Event.d.ts +2 -1
  92. package/dist/models/Event.d.ts.map +1 -1
  93. package/dist/models/Event.js +3 -0
  94. package/dist/models/Event.js.map +1 -1
  95. package/dist/models/EventNotification.d.ts.map +1 -1
  96. package/dist/models/EventNotification.js +5 -5
  97. package/dist/models/EventNotification.js.map +1 -1
  98. package/dist/models/Invoice.d.ts +1 -0
  99. package/dist/models/Invoice.d.ts.map +1 -1
  100. package/dist/models/Invoice.js +8 -0
  101. package/dist/models/Invoice.js.map +1 -1
  102. package/dist/models/MemberPlatformMembership.d.ts.map +1 -1
  103. package/dist/models/MemberPlatformMembership.js +9 -0
  104. package/dist/models/MemberPlatformMembership.js.map +1 -1
  105. package/dist/models/Order.d.ts.map +1 -1
  106. package/dist/models/Order.js +1 -0
  107. package/dist/models/Order.js.map +1 -1
  108. package/dist/models/Organization.d.ts +23 -25
  109. package/dist/models/Organization.d.ts.map +1 -1
  110. package/dist/models/Organization.js +92 -64
  111. package/dist/models/Organization.js.map +1 -1
  112. package/dist/models/PasswordToken.d.ts +5 -1
  113. package/dist/models/PasswordToken.d.ts.map +1 -1
  114. package/dist/models/PasswordToken.js +18 -17
  115. package/dist/models/PasswordToken.js.map +1 -1
  116. package/dist/models/Payment.d.ts +21 -2
  117. package/dist/models/Payment.d.ts.map +1 -1
  118. package/dist/models/Payment.js +43 -2
  119. package/dist/models/Payment.js.map +1 -1
  120. package/dist/models/Registration.d.ts.map +1 -1
  121. package/dist/models/Registration.js +3 -3
  122. package/dist/models/Registration.js.map +1 -1
  123. package/dist/models/STCredit.d.ts +4 -0
  124. package/dist/models/STCredit.d.ts.map +1 -1
  125. package/dist/models/STCredit.js +28 -0
  126. package/dist/models/STCredit.js.map +1 -1
  127. package/dist/models/STInvoice.d.ts +7 -1
  128. package/dist/models/STInvoice.d.ts.map +1 -1
  129. package/dist/models/STInvoice.js +9 -0
  130. package/dist/models/STInvoice.js.map +1 -1
  131. package/dist/models/STPackage.d.ts +4 -0
  132. package/dist/models/STPackage.d.ts.map +1 -1
  133. package/dist/models/STPackage.js +12 -1
  134. package/dist/models/STPackage.js.map +1 -1
  135. package/dist/models/UsedRegisterCode.d.ts +9 -0
  136. package/dist/models/UsedRegisterCode.d.ts.map +1 -1
  137. package/dist/models/UsedRegisterCode.js +31 -0
  138. package/dist/models/UsedRegisterCode.js.map +1 -1
  139. package/dist/models/_relations.js +25 -0
  140. package/dist/models/_relations.js.map +1 -1
  141. package/dist/models/addresses/City.d.ts +4 -4
  142. package/dist/models/addresses/City.d.ts.map +1 -1
  143. package/dist/models/addresses/City.js +6 -6
  144. package/dist/models/addresses/City.js.map +1 -1
  145. package/dist/models/addresses/PostalCode.d.ts +2 -2
  146. package/dist/models/addresses/PostalCode.d.ts.map +1 -1
  147. package/dist/models/addresses/PostalCode.js +4 -3
  148. package/dist/models/addresses/PostalCode.js.map +1 -1
  149. package/dist/models/addresses/Street.d.ts +3 -3
  150. package/dist/models/addresses/Street.d.ts.map +1 -1
  151. package/dist/models/addresses/Street.js +4 -4
  152. package/dist/models/addresses/Street.js.map +1 -1
  153. package/dist/models/index.d.ts +1 -0
  154. package/dist/models/index.d.ts.map +1 -1
  155. package/dist/models/index.js +1 -0
  156. package/dist/models/index.js.map +1 -1
  157. package/dist/models/v1GroupMigrationData.d.ts +22 -0
  158. package/dist/models/v1GroupMigrationData.d.ts.map +1 -0
  159. package/dist/models/v1GroupMigrationData.js +48 -0
  160. package/dist/models/v1GroupMigrationData.js.map +1 -0
  161. package/package.json +32 -13
  162. package/src/factories/GroupFactory.ts +4 -6
  163. package/src/factories/OrganizationFactory.ts +12 -4
  164. package/src/factories/STPackageFactory.ts +2 -2
  165. package/src/factories/UserFactory.ts +4 -5
  166. package/src/helpers/EmailBuilder.ts +19 -28
  167. package/src/helpers/Handlebars.ts +6 -1
  168. package/src/index.ts +0 -1
  169. package/src/migrations/1605262045-import-postcodes.ts +62 -25
  170. package/src/migrations/1605262046-import-postcodes-nl.ts +58 -17
  171. package/src/migrations/1719567881-organization-periodId.sql +2 -0
  172. package/src/migrations/1719567882-groups-periodId.sql +2 -0
  173. package/src/migrations/1720080975-convert-charset.ts +34 -0
  174. package/src/migrations/1720080976-convert-charset-leads.ts +16 -13
  175. package/src/migrations/1721400546-users-memberId.sql +2 -0
  176. package/src/migrations/1722269236-group-waitinglist-id.sql +2 -1
  177. package/src/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
  178. package/src/migrations/1722525787-depending-balance-item.sql +2 -0
  179. package/src/migrations/1722963554-registration-group-price-and-options.sql +1 -1
  180. package/src/migrations/1723652797-payments-paying-organization-id-fk.sql +2 -0
  181. package/src/migrations/1733317908-added-missing-organization-fk-on-registrations.sql +2 -0
  182. package/src/migrations/1733317910-paying-organization-id-fk.sql +2 -0
  183. package/src/migrations/1733504881-negative-invoice-id.sql +6 -0
  184. package/src/migrations/1733994455-balance-item-status-open.ts +30 -0
  185. package/src/migrations/1769087808-corrected-invoice-user-agent.sql +2 -0
  186. package/src/migrations/1769087809-payments-invoice-id.sql +2 -0
  187. package/src/migrations/1772033555-balance-item-package-id.sql +2 -0
  188. package/src/migrations/1778796615-payments-reversing-payment-id.sql +2 -0
  189. package/src/migrations/1779443446-transfer-fees.sql +3 -0
  190. package/src/migrations/1779709174-used-register-code-balance-item-id.sql +5 -0
  191. package/src/migrations/1779968328-payments-admin-user-id.sql +5 -0
  192. package/src/migrations/1779970611-payments-refunded-amount.sql +2 -0
  193. package/src/migrations/1779972640-balance-items-failed-at.sql +2 -0
  194. package/src/migrations/1780328285-document-template-locked.sql +2 -0
  195. package/src/migrations/1780328286-document-locked.sql +2 -0
  196. package/src/migrations/1780412083-documents-set-locked.ts +18 -0
  197. package/src/migrations/1780928401-v1-groups-migration-data.ts +50 -0
  198. package/src/models/BalanceItem.ts +44 -43
  199. package/src/models/CachedBalance.test.ts +46 -46
  200. package/src/models/CachedBalance.ts +7 -7
  201. package/src/models/Document.ts +34 -13
  202. package/src/models/DocumentTemplate.ts +56 -17
  203. package/src/models/Email.test.ts +3 -3
  204. package/src/models/Email.ts +28 -49
  205. package/src/models/EmailVerificationCode.ts +8 -22
  206. package/src/models/Event.ts +6 -4
  207. package/src/models/EventNotification.ts +6 -6
  208. package/src/models/Invoice.ts +9 -0
  209. package/src/models/MemberPlatformMembership.test.ts +70 -0
  210. package/src/models/MemberPlatformMembership.ts +16 -12
  211. package/src/models/Order.ts +14 -26
  212. package/src/models/Organization.ts +122 -93
  213. package/src/models/PasswordToken.ts +21 -21
  214. package/src/models/Payment.ts +42 -4
  215. package/src/models/Registration.ts +3 -3
  216. package/src/models/STCredit.ts +32 -0
  217. package/src/models/STInvoice.ts +11 -5
  218. package/src/models/STPackage.ts +19 -14
  219. package/src/models/UsedRegisterCode.ts +34 -0
  220. package/src/models/_relations.ts +29 -0
  221. package/src/models/addresses/City.ts +8 -6
  222. package/src/models/addresses/PostalCode.test.ts +1 -0
  223. package/src/models/addresses/PostalCode.ts +5 -3
  224. package/src/models/addresses/Street.ts +6 -4
  225. package/src/models/index.ts +2 -0
  226. package/src/models/v1GroupMigrationData.ts +43 -0
  227. package/dist/helpers/MemberMerger.d.ts +0 -14
  228. package/dist/helpers/MemberMerger.d.ts.map +0 -1
  229. package/dist/helpers/MemberMerger.js +0 -364
  230. package/dist/helpers/MemberMerger.js.map +0 -1
  231. package/dist/migrations/1720080975-convert-charset.sql +0 -85
  232. package/dist/migrations/1723202126-member-number-index.sql +0 -2
  233. package/dist/models/OneTimeToken.d.ts +0 -38
  234. package/dist/models/OneTimeToken.d.ts.map +0 -1
  235. package/dist/models/OneTimeToken.js +0 -125
  236. package/dist/models/OneTimeToken.js.map +0 -1
  237. package/src/helpers/MemberMerger.test.ts +0 -782
  238. package/src/helpers/MemberMerger.ts +0 -577
  239. package/src/migrations/1720080975-convert-charset.sql +0 -85
  240. package/src/migrations/1723202126-member-number-index.sql +0 -2
  241. package/src/models/OneTimeToken.ts +0 -133
@@ -1,13 +1,15 @@
1
1
  import type { ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { column, Database } from '@simonbackx/simple-database';
3
- import type {I18n} from '@stamhoofd/backend-i18n';
3
+ import type { I18n } from '@stamhoofd/backend-i18n';
4
4
  import { QueryableModel } from '@stamhoofd/sql';
5
5
  import basex from 'base-x';
6
6
  import crypto from 'crypto';
7
7
 
8
- import type {Organization} from './Organization.js';
8
+ import type { Organization } from './Organization.js';
9
9
  import { User } from './User.js';
10
10
  import { SimpleError } from '@simonbackx/simple-errors';
11
+ import { getAppHost } from '@stamhoofd/structures';
12
+ import { Platform } from './Platform.js';
11
13
  const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
12
14
  const bs58 = basex(ALPHABET);
13
15
 
@@ -110,8 +112,7 @@ export class PasswordToken extends QueryableModel {
110
112
 
111
113
  if (validUntil) {
112
114
  token.validUntil = new Date(validUntil);
113
- }
114
- else {
115
+ } else {
115
116
  token.validUntil = new Date();
116
117
  token.validUntil.setTime(token.validUntil.getTime() + 3 * 3600 * 1000);
117
118
  }
@@ -122,29 +123,28 @@ export class PasswordToken extends QueryableModel {
122
123
  }
123
124
 
124
125
  static async getPasswordRecoveryUrl(user: User, organization: Organization | null, i18n: I18n, validUntil?: Date) {
125
- if (user.organizationId !== null && ((user.organizationId ?? null) !== (organization?.id ?? null))) {
126
- throw new Error('Unexpected mismatch in organization id for PasswordToken');
127
- }
128
126
  // Send an e-mail to say you already have an account + follow password forgot flow
129
127
  const token = await PasswordToken.createToken(user, validUntil);
128
+ return token.getPasswordRecoveryUrl(organization, i18n, user);
129
+ }
130
130
 
131
- let host: string;
132
- if (user.permissions || !organization || STAMHOOFD.userMode === 'platform') {
133
- host = 'https://' + (STAMHOOFD.domains.dashboard) + '/' + i18n.locale;
134
- if (user.organizationId && organization) {
135
- host += '/auto/' + encodeURIComponent(organization.uri);
136
- }
137
- return host + '/reset-password?token=' + encodeURIComponent(token.token);
131
+ /**
132
+ * Build the password recovery url for this (already created) token.
133
+ * Pass the user to avoid an extra query when it is already loaded.
134
+ */
135
+ async getPasswordRecoveryUrl(organization: Organization | null, i18n: I18n, user?: User) {
136
+ const tokenUser = user ?? await User.getByID(this.userId);
137
+ if (!tokenUser) {
138
+ throw new Error('PasswordToken without a valid user');
138
139
  }
139
140
 
140
- host = 'https://' + organization.getHost(i18n);
141
- return host + '/reset-password?token=' + encodeURIComponent(token.token);
142
- }
141
+ if (tokenUser.organizationId !== null && ((tokenUser.organizationId ?? null) !== (organization?.id ?? null))) {
142
+ throw new Error('Unexpected mismatch in organization id for PasswordToken');
143
+ }
143
144
 
144
- static async getMagicSignInUrl(user: User, organization: Organization) {
145
- // For now we don't add a token yet for security. We might add some sort of email validation thing later on
146
- const host = 'https://' + organization.getHost();
147
- return Promise.resolve(host + '/login' + '?email=' + encodeURIComponent(user.email) + '&hasAccount=' + (user.hasAccount() ? 1 : 0));
145
+ const hasOrganizationPermissions = organization ? tokenUser.permissions?.forOrganization(organization, await Platform.getSharedStruct())?.isEmpty === false : false;
146
+ const host = 'https://' + getAppHost(hasOrganizationPermissions ? 'dashboard' : 'registration', organization, hasOrganizationPermissions, i18n);
147
+ return host + '/reset-password?token=' + encodeURIComponent(this.token);
148
148
  }
149
149
 
150
150
  static async clearFor(userId: string) {
@@ -1,10 +1,9 @@
1
1
  import { column } from '@simonbackx/simple-database';
2
- import type { PaymentMethod, PaymentStatus } from '@stamhoofd/structures';
3
- import { BalanceItemDetailed, BalanceItemPaymentDetailed, BaseOrganization, PaymentCustomer, PaymentGeneral, PaymentProvider, PaymentType, Settlement, TransferSettings } from '@stamhoofd/structures';
2
+ import { BalanceItemDetailed, BalanceItemPaymentDetailed, BaseOrganization, PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus, PaymentType, Settlement, TransferSettings } from '@stamhoofd/structures';
4
3
  import { Formatter } from '@stamhoofd/utility';
5
4
  import { v4 as uuidv4 } from 'uuid';
6
5
 
7
- import { QueryableModel } from '@stamhoofd/sql';
6
+ import { QueryableModel, SQL } from '@stamhoofd/sql';
8
7
  import { CreateMandateSettings } from '@stamhoofd/structures/checkout/CreateMandateSettings.js';
9
8
  import type { BalanceItem } from './BalanceItem.js';
10
9
  import type { BalanceItemPayment } from './BalanceItemPayment.js';
@@ -98,10 +97,16 @@ export class Payment extends QueryableModel {
98
97
  @column({ type: 'integer' })
99
98
  price: number;
100
99
 
100
+ /**
101
+ * Total price refunded or charged back
102
+ */
103
+ @column({ type: 'integer' })
104
+ refundedAmount = 0;
105
+
101
106
  /**
102
107
  * The difference between the sum of the balance item payments price and the price of the payment, caused by rounding to 1 cent.
103
108
  * This cannot be >= 100 (= 0,01 euro) or <= -100 (=-0,01 euro)
104
- *
109
+ *
105
110
  * For understanding the sign of the value, regard it as an extra balance item to the payment.
106
111
  *
107
112
  * Just like all prices, this price is stored per ten thousand (1 = 0,0001 ). Storing smaller units is not possible because even in balance items, the price to pay cannot be smaller than 0,0001 euro
@@ -141,6 +146,20 @@ export class Payment extends QueryableModel {
141
146
  @column({ type: 'integer' })
142
147
  serviceFeeManualCharged = 0;
143
148
 
149
+ /**
150
+ * Transfer fee - if we need to charge this for a certain provider
151
+ *
152
+ * This EXCLUDES VAT
153
+ */
154
+ @column({ type: 'integer' })
155
+ transferFeeManual = 0;
156
+
157
+ /**
158
+ * Part of the transferFeeManual, that has been invoiced (added to outstanding balance)
159
+ */
160
+ @column({ type: 'integer' })
161
+ transferFeeManualCharged = 0;
162
+
144
163
  /**
145
164
  * Included in the total price
146
165
  */
@@ -156,6 +175,12 @@ export class Payment extends QueryableModel {
156
175
  @column({ type: 'string', nullable: true })
157
176
  invoiceId: string | null = null;
158
177
 
178
+ /**
179
+ * In case the payment was initiated by an admin or not.
180
+ */
181
+ @column({ type: 'string', nullable: true })
182
+ adminUserId: string | null = null;
183
+
159
184
  @column({
160
185
  type: 'datetime', beforeSave(old?: any) {
161
186
  if (old !== undefined) {
@@ -196,11 +221,24 @@ export class Payment extends QueryableModel {
196
221
  @column({ type: 'string', nullable: true })
197
222
  ibanName: string | null = null;
198
223
 
224
+ get canChangeStatus() {
225
+ return this.price !== 0 && (this.method === PaymentMethod.Transfer || this.method === PaymentMethod.PointOfSale || this.method === PaymentMethod.Unknown);
226
+ }
227
+
199
228
  generateDescription(organization: Organization, reference: string, replacements: { [key: string]: string } = {}) {
200
229
  const settings = this.transferSettings ?? organization.meta.transferSettings;
201
230
  this.transferDescription = settings.generateDescription(reference, organization.address.country, replacements);
202
231
  }
203
232
 
233
+ async updateRefundedAmount() {
234
+ this.refundedAmount = await Payment.select()
235
+ .where('organizationId', this.organizationId)
236
+ .where('reversingPaymentId', this.id)
237
+ .where('status', PaymentStatus.Succeeded)
238
+ .sum(SQL.column('price'));
239
+ await this.save();
240
+ }
241
+
204
242
  static roundPrice(price: number) {
205
243
  return Math.round(price / 100) * 100;
206
244
  }
@@ -1,7 +1,7 @@
1
1
  import type { ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { column } from '@simonbackx/simple-database';
3
3
  import type { RecordAnswer } from '@stamhoofd/structures';
4
- import { AppliedRegistrationDiscount, GroupPrice, RecordAnswerDecoder, RegisterItemOption, Registration as RegistrationStructure, StockReservation } from '@stamhoofd/structures';
4
+ import { AppliedRegistrationDiscount, GroupPrice, RecordAnswerMapDecoder, RegisterItemOption, Registration as RegistrationStructure, StockReservation } from '@stamhoofd/structures';
5
5
  import { v4 as uuidv4 } from 'uuid';
6
6
 
7
7
  import { ArrayDecoder, MapDecoder, StringDecoder } from '@simonbackx/simple-encoding';
@@ -39,7 +39,7 @@ export class Registration extends QueryableModel {
39
39
  @column({ type: 'json', decoder: new ArrayDecoder(RegisterItemOption) })
40
40
  options: RegisterItemOption[] = [];
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
  /**
@@ -166,6 +166,6 @@ export class Registration extends QueryableModel {
166
166
  }
167
167
 
168
168
  shouldIncludeStock() {
169
- return (this.registeredAt !== null && this.deactivatedAt === null) || this.canRegister || (this.reservedUntil && this.reservedUntil > new Date());
169
+ return (this.registeredAt !== null && this.deactivatedAt === null) || (this.reservedUntil && this.reservedUntil > new Date());
170
170
  }
171
171
  }
@@ -49,4 +49,36 @@ export class STCredit extends QueryableModel {
49
49
  skipUpdate: true,
50
50
  })
51
51
  updatedAt: Date;
52
+
53
+ static async getForOrganization(organizationId: string) {
54
+ return await STCredit.where({ organizationId }, {
55
+ sort: [{
56
+ column: 'createdAt',
57
+ direction: 'DESC',
58
+ }],
59
+ });
60
+ }
61
+
62
+ static async getBalance(organizationId: string) {
63
+ const now = new Date();
64
+ const credits = await this.getForOrganization(organizationId);
65
+ credits.reverse();
66
+ let balance = 0;
67
+
68
+ for (const credit of credits) {
69
+ if (credit.expireAt !== null && credit.expireAt <= now) {
70
+ continue;
71
+ }
72
+ // TODO: we can expire credits here
73
+ balance += credit.change;
74
+ if (balance < 0) {
75
+ // This is needed to make deleting credit and expiring credit work.
76
+ // At no point in time, the credits can get negative.
77
+ // E.g. Getting credits, using them, and later expiring 'getting the credits' won't have impact on future credits
78
+ balance = 0;
79
+ }
80
+ }
81
+
82
+ return { balance: balance };
83
+ }
52
84
  }
@@ -20,12 +20,14 @@ export class STInvoice extends QueryableModel {
20
20
  id!: string;
21
21
 
22
22
  /**
23
+ * @deprecated
23
24
  * Organization that made the invoice. Can be null if the organization was deleted and for the migration from V1 -> V2
24
25
  */
25
26
  @column({ type: 'string', nullable: true })
26
- organizationId: string | null;
27
+ protected organizationId: string | null;
27
28
 
28
29
  /**
30
+ *
29
31
  * Organization that is associated with this invoice (can be null if deleted or unknown)
30
32
  */
31
33
  @column({ type: 'string', nullable: true })
@@ -56,6 +58,12 @@ export class STInvoice extends QueryableModel {
56
58
  @column({ type: 'datetime', nullable: true })
57
59
  paidAt: Date | null = null;
58
60
 
61
+ /**
62
+ * If a invoice was refunded because of a cancellation, we store the negative invoice id here
63
+ */
64
+ @column({ type: 'string', nullable: true })
65
+ negativeInvoiceId: string | null = null;
66
+
59
67
  @column({
60
68
  type: 'datetime', beforeSave(old?: any) {
61
69
  if (old !== undefined) {
@@ -117,14 +125,12 @@ export class STInvoice extends QueryableModel {
117
125
  cardLabel: ('cardLabel' in details ? details.cardLabel : null),
118
126
  }),
119
127
  }));
120
- }
121
- catch (e) {
128
+ } catch (e) {
122
129
  console.error(e);
123
130
  }
124
131
  }
125
132
  }
126
- }
127
- catch (e) {
133
+ } catch (e) {
128
134
  console.error(e);
129
135
  }
130
136
  return mandates;
@@ -65,6 +65,19 @@ export class STPackage extends QueryableModel {
65
65
  @column({ type: 'datetime', nullable: true })
66
66
  lastEmailAt: Date | null = null;
67
67
 
68
+ /**
69
+ * Helper to handle edge cases where validUntil is null but removeAt is set
70
+ */
71
+ get endDate() {
72
+ if (!this.removeAt) {
73
+ return this.validUntil;
74
+ }
75
+ if (!this.validUntil) {
76
+ return this.removeAt;
77
+ }
78
+ return new Date(Math.min(this.validUntil.getTime(), this.removeAt.getTime()));
79
+ }
80
+
68
81
  async activate() {
69
82
  if (this.validAt !== null) {
70
83
  return;
@@ -150,8 +163,7 @@ export class STPackage extends QueryableModel {
150
163
  pack.meta.pricingType = STPricingType.Fixed;
151
164
  pack.validUntil = null;
152
165
  pack.removeAt = null;
153
- }
154
- else if (pack.meta.type === STPackageType.Members) {
166
+ } else if (pack.meta.type === STPackageType.Members) {
155
167
  pack.meta.serviceFeeFixed = 0;
156
168
  pack.meta.serviceFeePercentage = 0;
157
169
  pack.meta.serviceFeeMinimum = 0;
@@ -165,8 +177,6 @@ export class STPackage extends QueryableModel {
165
177
  }
166
178
 
167
179
  createStatus(): STPackageStatus {
168
- // TODO: if payment failed: temporary set valid until to 2 weeks after last/first failed payment
169
-
170
180
  return STPackageStatus.create({
171
181
  startDate: this.meta.startDate,
172
182
  validUntil: this.validUntil,
@@ -205,20 +215,16 @@ export class STPackage extends QueryableModel {
205
215
  if (this.meta.type === STPackageType.Members) {
206
216
  type = EmailTemplateType.MembersExpirationReminder;
207
217
  allowDays = 32;
208
- }
209
- else if (this.meta.type === STPackageType.Webshops) {
218
+ } else if (this.meta.type === STPackageType.Webshops) {
210
219
  type = EmailTemplateType.WebshopsExpirationReminder;
211
220
  allowDays = 32;
212
- }
213
- else if (this.meta.type === STPackageType.SingleWebshop) {
221
+ } else if (this.meta.type === STPackageType.SingleWebshop) {
214
222
  type = EmailTemplateType.SingleWebshopExpirationReminder;
215
223
  allowDays = 7;
216
- }
217
- else if (this.meta.type === STPackageType.TrialMembers) {
224
+ } else if (this.meta.type === STPackageType.TrialMembers) {
218
225
  type = EmailTemplateType.TrialMembersExpirationReminder;
219
226
  allowDays = 3;
220
- }
221
- else if (this.meta.type === STPackageType.TrialWebshops) {
227
+ } else if (this.meta.type === STPackageType.TrialWebshops) {
222
228
  type = EmailTemplateType.TrialWebshopsExpirationReminder;
223
229
  allowDays = 3;
224
230
  }
@@ -237,8 +243,7 @@ export class STPackage extends QueryableModel {
237
243
  });
238
244
  }
239
245
  this.lastEmailAt = new Date();
240
- }
241
- else {
246
+ } else {
242
247
  console.log('Skip sending expiration email for ' + this.id + ' (no type)');
243
248
  }
244
249
 
@@ -25,6 +25,13 @@ export class UsedRegisterCode extends QueryableModel {
25
25
  * Set if this has been rewarded
26
26
  */
27
27
  @column({ type: 'string', nullable: true })
28
+ balanceItemId: string | null = null;
29
+
30
+ /**
31
+ * @deprecated Migrated to balanceItemId
32
+ * Set if this has been rewarded
33
+ */
34
+ @column({ type: 'string', nullable: true })
28
35
  creditId: string | null = null;
29
36
 
30
37
  @column({
@@ -48,4 +55,31 @@ export class UsedRegisterCode extends QueryableModel {
48
55
  skipUpdate: true,
49
56
  })
50
57
  updatedAt: Date;
58
+
59
+ static async getFor(organizationId: string): Promise<UsedRegisterCode | undefined> {
60
+ const code = await this.where({ organizationId }, { limit: 1 });
61
+ return code[0] ?? undefined;
62
+ }
63
+
64
+ static async getAll(code: string) {
65
+ const used = await UsedRegisterCode.where({
66
+ code,
67
+ });
68
+ return used;
69
+ }
70
+
71
+ static async getUsed(code: string) {
72
+ const used = await UsedRegisterCode.select()
73
+ .where('code', code)
74
+ .andWhere('balanceItemId', '!=', null)
75
+ .fetch();
76
+ return used;
77
+ }
78
+
79
+ static async getUsedCount(code: string) {
80
+ return await UsedRegisterCode.select()
81
+ .where('code', code)
82
+ .andWhere('balanceItemId', '!=', null)
83
+ .count();
84
+ }
51
85
  }
@@ -22,6 +22,10 @@ import { Group } from './Group.js';
22
22
  import { Registration } from './Registration.js';
23
23
  import { Token } from './Token.js';
24
24
  import { PasswordToken } from './PasswordToken.js';
25
+ import { City } from './addresses/City.js';
26
+ import { Province } from './addresses/Province.js';
27
+ import { PostalCode } from './addresses/PostalCode.js';
28
+ import { Street } from './addresses/Street.js';
25
29
 
26
30
  if (User === undefined) {
27
31
  throw new Error('Unexpected missing User');
@@ -131,3 +135,28 @@ if (!PasswordToken.relations) {
131
135
  PasswordToken.user = new ManyToOneRelation(User, 'user');
132
136
  PasswordToken.user.foreignKey = 'userId';
133
137
  PasswordToken.relations.push(PasswordToken.user);
138
+
139
+ if (!City.relations) {
140
+ City.relations = [];
141
+ }
142
+ City.parentCity = new ManyToOneRelation(City, 'parentCity');
143
+ City.parentCity.foreignKey = 'parentCityId';
144
+ City.relations.push(City.parentCity);
145
+
146
+ City.province = new ManyToOneRelation(Province, 'province');
147
+ City.province.foreignKey = 'provinceId';
148
+ City.relations.push(City.province);
149
+
150
+ if (!PostalCode.relations) {
151
+ PostalCode.relations = [];
152
+ }
153
+ PostalCode.city = new ManyToOneRelation(City, 'city');
154
+ PostalCode.city.foreignKey = 'cityId';
155
+ PostalCode.relations.push(PostalCode.city);
156
+
157
+ if (!Street.relations) {
158
+ Street.relations = [];
159
+ }
160
+ Street.city = new ManyToOneRelation(City, 'city');
161
+ Street.city.foreignKey = 'cityId';
162
+ Street.relations.push(Street.city);
@@ -1,9 +1,10 @@
1
- import { column, ManyToOneRelation } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
+ import type { ManyToOneRelation } from '@simonbackx/simple-database';
2
3
  import type { Country } from '@stamhoofd/types/Country';
3
4
  import { v4 as uuidv4 } from 'uuid';
4
5
 
5
6
  import { QueryableModel } from '@stamhoofd/sql';
6
- import { Province } from './Province.js';
7
+ import type { Province } from './Province.js';
7
8
 
8
9
  export class City extends QueryableModel {
9
10
  static table = 'cities';
@@ -18,15 +19,16 @@ export class City extends QueryableModel {
18
19
  @column({ type: 'string' })
19
20
  name: string;
20
21
 
21
- @column({ type: 'string', foreignKey: City.province })
22
+ @column({ type: 'string' })
22
23
  provinceId: string;
23
24
 
24
- @column({ type: 'string', foreignKey: City.parentCity, nullable: true })
25
+ @column({ type: 'string', nullable: true })
25
26
  parentCityId: string | null = null;
26
27
 
27
28
  @column({ type: 'string' })
28
29
  country: Country;
29
30
 
30
- static parentCity = new ManyToOneRelation(City, 'parentCity');
31
- static province = new ManyToOneRelation(Province, 'province');
31
+ // Relations are wired up in _relations.ts (after the classes are defined) to avoid circular references.
32
+ static parentCity: ManyToOneRelation<'parentCity', City>;
33
+ static province: ManyToOneRelation<'province', Province>;
32
34
  }
@@ -3,6 +3,7 @@ import { Country } from '@stamhoofd/types/Country';
3
3
  import { City } from './City.js';
4
4
  import { PostalCode } from './PostalCode.js';
5
5
  import { Province } from './Province.js';
6
+ import '../_relations.js';
6
7
 
7
8
  describe('Model.PostalCode', () => {
8
9
  let oostVlaanderen!: Province;
@@ -1,4 +1,5 @@
1
- import { column, Database, ManyToOneRelation } from '@simonbackx/simple-database';
1
+ import { column, Database } from '@simonbackx/simple-database';
2
+ import type { ManyToOneRelation } from '@simonbackx/simple-database';
2
3
  import { SimpleError } from '@simonbackx/simple-errors';
3
4
  import { QueryableModel } from '@stamhoofd/sql';
4
5
  import { Country } from '@stamhoofd/types/Country';
@@ -20,13 +21,14 @@ export class PostalCode extends QueryableModel {
20
21
  @column({ type: 'string' })
21
22
  postalCode: string;
22
23
 
23
- @column({ type: 'string', foreignKey: PostalCode.city })
24
+ @column({ type: 'string' })
24
25
  cityId: string;
25
26
 
26
27
  @column({ type: 'string' })
27
28
  country: Country;
28
29
 
29
- static city = new ManyToOneRelation(City, 'city');
30
+ // Relation is wired up in _relations.ts (after the classes are defined) to avoid circular references.
31
+ static city: ManyToOneRelation<'city', City>;
30
32
 
31
33
  /**
32
34
  * Search for a given city via it's postal code and country, collecting information about the city (id), parentCity (id) and province (id)
@@ -1,8 +1,9 @@
1
- import { column, ManyToOneRelation } from '@simonbackx/simple-database';
1
+ import { column } from '@simonbackx/simple-database';
2
+ import type { ManyToOneRelation } from '@simonbackx/simple-database';
2
3
  import { QueryableModel } from '@stamhoofd/sql';
3
4
  import { v4 as uuidv4 } from 'uuid';
4
5
 
5
- import { City } from './City.js';
6
+ import type { City } from './City.js';
6
7
 
7
8
  export class Street extends QueryableModel {
8
9
  static table = 'streets';
@@ -17,8 +18,9 @@ export class Street extends QueryableModel {
17
18
  @column({ type: 'string' })
18
19
  name: string;
19
20
 
20
- @column({ type: 'string', foreignKey: Street.city })
21
+ @column({ type: 'string' })
21
22
  cityId: string;
22
23
 
23
- static city = new ManyToOneRelation(City, 'city');
24
+ // Relation is wired up in _relations.ts (after the classes are defined) to avoid circular references.
25
+ static city: ManyToOneRelation<'city', City>;
24
26
  }
@@ -63,4 +63,6 @@ export * from './Invoice.js';
63
63
  export * from './InvoicedBalanceItem.js';
64
64
  export * from './RegistrationInvitation.js';
65
65
 
66
+ export * from './v1GroupMigrationData.js';
67
+
66
68
  import './_relations.js';
@@ -0,0 +1,43 @@
1
+ import { column } from '@simonbackx/simple-database';
2
+
3
+ import { QueryableModel } from '@stamhoofd/sql';
4
+
5
+ /**
6
+ * Temporary table for the migration from v1 to v2.
7
+ * Keeps track of which combination of group id and cycle has been migrated to a new group.
8
+ */
9
+ export class V1GroupMigrationData extends QueryableModel {
10
+ static table = 'v1_groups_migration_data';
11
+
12
+ // new group id (can be same as old group id if no new group was created)
13
+ @column({ primary: true, type: 'string' })
14
+ newGroupId: string;
15
+
16
+ // old group id from v1
17
+ @column({ type: 'string' })
18
+ oldGroupId: string;
19
+
20
+ // old cycle from v1
21
+ @column({ type: 'integer' })
22
+ oldCycle = 0;
23
+ }
24
+
25
+ /**
26
+ * Temporary table for the migration from v1 to v2.
27
+ * Keeps track of which combination of waiting list group id and cycle has been migrated to a new group.
28
+ */
29
+ export class V1WaitingListMigrationData extends QueryableModel {
30
+ static table = 'v1_waiting_list_migration_data';
31
+
32
+ // new group id (can be same as old group id if no new group was created)
33
+ @column({ primary: true, type: 'string' })
34
+ newGroupId: string;
35
+
36
+ // old group id from v1
37
+ @column({ type: 'string' })
38
+ oldGroupId: string;
39
+
40
+ // old cycle from v1
41
+ @column({ type: 'integer' })
42
+ oldCycle = 0;
43
+ }
@@ -1,14 +0,0 @@
1
- import { Member } from '../models/index.js';
2
- export declare function mergeMultipleMembers(members: Member[]): Promise<void>;
3
- export declare function findEqualMembers({ firstName, lastName, birthDay, }: {
4
- firstName: string;
5
- lastName: string;
6
- birthDay: string;
7
- }): Promise<Member[]>;
8
- export declare function mergeTwoMembers(base: Member, other: Member): Promise<void>;
9
- export declare function mergeMemberDetails(base: Member, other: Member): void;
10
- export declare function selectBaseMember(members: Member[]): {
11
- base: Member;
12
- others: Member[];
13
- };
14
- //# sourceMappingURL=MemberMerger.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"MemberMerger.d.ts","sourceRoot":"","sources":["../../src/helpers/MemberMerger.ts"],"names":[],"mappings":"AAaA,OAAO,EAGH,MAAM,EAMT,MAAM,oBAAoB,CAAC;AAE5B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,iBAU3D;AAED,wBAAsB,gBAAgB,CAAC,EACnC,SAAS,EACT,QAAQ,EACR,QAAQ,GACX,EAAE;IACC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAMpB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ChF;AA8HD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAkGpE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB,CASA"}