@stamhoofd/models 2.4.0 → 2.6.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 (86) hide show
  1. package/dist/src/migrations/1722269236-group-waitinglist-id.sql +4 -0
  2. package/dist/src/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
  3. package/dist/src/migrations/1722525787-depending-balance-item.sql +2 -0
  4. package/dist/src/migrations/1722845608-registration-stock-reservations.sql +2 -0
  5. package/dist/src/migrations/1722845609-group-stock-reservations.sql +2 -0
  6. package/dist/src/migrations/1722852362-stripe-intents-account-id.sql +2 -0
  7. package/dist/src/migrations/1722852363-stripe-checkout-sessions-account-id.sql +2 -0
  8. package/dist/src/models/BalanceItem.d.ts +8 -0
  9. package/dist/src/models/BalanceItem.d.ts.map +1 -1
  10. package/dist/src/models/BalanceItem.js +70 -44
  11. package/dist/src/models/BalanceItem.js.map +1 -1
  12. package/dist/src/models/DocumentTemplate.js +1 -1
  13. package/dist/src/models/DocumentTemplate.js.map +1 -1
  14. package/dist/src/models/Event.d.ts +7 -0
  15. package/dist/src/models/Event.d.ts.map +1 -1
  16. package/dist/src/models/Event.js +28 -0
  17. package/dist/src/models/Event.js.map +1 -1
  18. package/dist/src/models/Group.d.ts +13 -3
  19. package/dist/src/models/Group.d.ts.map +1 -1
  20. package/dist/src/models/Group.js +45 -6
  21. package/dist/src/models/Group.js.map +1 -1
  22. package/dist/src/models/Member.d.ts +1 -1
  23. package/dist/src/models/Member.d.ts.map +1 -1
  24. package/dist/src/models/Member.js +12 -9
  25. package/dist/src/models/Member.js.map +1 -1
  26. package/dist/src/models/Order.js +1 -1
  27. package/dist/src/models/Order.js.map +1 -1
  28. package/dist/src/models/Organization.d.ts +3 -11
  29. package/dist/src/models/Organization.d.ts.map +1 -1
  30. package/dist/src/models/Organization.js +4 -28
  31. package/dist/src/models/Organization.js.map +1 -1
  32. package/dist/src/models/Payment.d.ts +5 -7
  33. package/dist/src/models/Payment.d.ts.map +1 -1
  34. package/dist/src/models/Payment.js +8 -13
  35. package/dist/src/models/Payment.js.map +1 -1
  36. package/dist/src/models/Registration.d.ts +17 -2
  37. package/dist/src/models/Registration.d.ts.map +1 -1
  38. package/dist/src/models/Registration.js +59 -7
  39. package/dist/src/models/Registration.js.map +1 -1
  40. package/dist/src/models/StripeCheckoutSession.d.ts +4 -0
  41. package/dist/src/models/StripeCheckoutSession.d.ts.map +1 -1
  42. package/dist/src/models/StripeCheckoutSession.js +7 -0
  43. package/dist/src/models/StripeCheckoutSession.js.map +1 -1
  44. package/dist/src/models/StripePaymentIntent.d.ts +4 -0
  45. package/dist/src/models/StripePaymentIntent.d.ts.map +1 -1
  46. package/dist/src/models/StripePaymentIntent.js +7 -0
  47. package/dist/src/models/StripePaymentIntent.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/migrations/1722269236-group-waitinglist-id.sql +4 -0
  50. package/src/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
  51. package/src/migrations/1722525787-depending-balance-item.sql +2 -0
  52. package/src/migrations/1722845608-registration-stock-reservations.sql +2 -0
  53. package/src/migrations/1722845609-group-stock-reservations.sql +2 -0
  54. package/src/migrations/1722852362-stripe-intents-account-id.sql +2 -0
  55. package/src/migrations/1722852363-stripe-checkout-sessions-account-id.sql +2 -0
  56. package/src/models/BalanceItem.ts +78 -47
  57. package/src/models/DocumentTemplate.ts +1 -1
  58. package/src/models/Event.ts +31 -0
  59. package/src/models/Group.ts +53 -14
  60. package/src/models/Member.ts +13 -10
  61. package/src/models/Order.ts +2 -2
  62. package/src/models/Organization.ts +5 -34
  63. package/src/models/Payment.ts +10 -16
  64. package/src/models/Registration.ts +71 -11
  65. package/src/models/StripeAccount.ts +1 -1
  66. package/src/models/StripeCheckoutSession.ts +6 -0
  67. package/src/models/StripePaymentIntent.ts +6 -0
  68. package/dist/src/assets/assets/Metropolis-Black.woff2 +0 -0
  69. package/dist/src/assets/assets/Metropolis-BlackItalic.woff2 +0 -0
  70. package/dist/src/assets/assets/Metropolis-Bold.woff2 +0 -0
  71. package/dist/src/assets/assets/Metropolis-BoldItalic.woff2 +0 -0
  72. package/dist/src/assets/assets/Metropolis-ExtraBold.woff2 +0 -0
  73. package/dist/src/assets/assets/Metropolis-ExtraBoldItalic.woff2 +0 -0
  74. package/dist/src/assets/assets/Metropolis-ExtraLight.woff2 +0 -0
  75. package/dist/src/assets/assets/Metropolis-ExtraLightItalic.woff2 +0 -0
  76. package/dist/src/assets/assets/Metropolis-Light.woff2 +0 -0
  77. package/dist/src/assets/assets/Metropolis-LightItalic.woff2 +0 -0
  78. package/dist/src/assets/assets/Metropolis-Medium.woff2 +0 -0
  79. package/dist/src/assets/assets/Metropolis-MediumItalic.woff2 +0 -0
  80. package/dist/src/assets/assets/Metropolis-Regular.woff2 +0 -0
  81. package/dist/src/assets/assets/Metropolis-RegularItalic.woff2 +0 -0
  82. package/dist/src/assets/assets/Metropolis-SemiBold.woff2 +0 -0
  83. package/dist/src/assets/assets/Metropolis-SemiBoldItalic.woff2 +0 -0
  84. package/dist/src/assets/assets/Metropolis-Thin.woff2 +0 -0
  85. package/dist/src/assets/assets/Metropolis-ThinItalic.woff2 +0 -0
  86. package/dist/src/assets/assets/logo.png +0 -0
@@ -110,39 +110,37 @@ export class Payment extends Model {
110
110
  }
111
111
 
112
112
  const {balanceItemPayments, balanceItems} = await Payment.loadBalanceItems(payments)
113
- const {registrations, orders, members, groups} = await Payment.loadBalanceItemRelations(balanceItems);
113
+ const {registrations, orders, groups} = await Payment.loadBalanceItemRelations(balanceItems);
114
114
 
115
- return this.getGeneralStructureFromRelations({
115
+ return await this.getGeneralStructureFromRelations({
116
116
  payments,
117
117
  registrations,
118
118
  orders,
119
- members,
120
119
  balanceItemPayments,
121
120
  balanceItems,
122
121
  groups
123
122
  }, includeSettlements)
124
123
  }
125
124
 
126
- static getGeneralStructureFromRelations({payments, registrations, orders, members, balanceItemPayments, balanceItems, groups}: {
125
+ static async getGeneralStructureFromRelations({payments, registrations, orders, balanceItemPayments, balanceItems, groups}: {
127
126
  payments: Payment[];
128
- registrations: import("./Registration").Registration[];
127
+ registrations: import("./Member").RegistrationWithMember[];
129
128
  orders: import("./Order").Order[];
130
- members: import("./Member").Member[];
131
129
  balanceItemPayments: import("./BalanceItemPayment").BalanceItemPayment[];
132
130
  balanceItems: import("./BalanceItem").BalanceItem[];
133
131
  groups: import("./Group").Group[];
134
- }, includeSettlements = false): PaymentGeneral[] {
132
+ }, includeSettlements = false): Promise<PaymentGeneral[]> {
135
133
  if (payments.length === 0) {
136
134
  return []
137
135
  }
136
+ const {Member} = (await import("./Member"));
138
137
 
139
138
  return payments.map(payment => {
140
139
  return PaymentGeneral.create({
141
140
  ...payment,
142
141
  balanceItemPayments: balanceItemPayments.filter(item => item.paymentId === payment.id).map((item) => {
143
142
  const balanceItem = balanceItems.find(b => b.id === item.balanceItemId)
144
- const registration = balanceItem?.registrationId && registrations.find(r => r.id === balanceItem.registrationId)
145
- const member = balanceItem?.memberId ? members.find(r => r.id === balanceItem.memberId) : undefined
143
+ const registration = balanceItem?.registrationId ? registrations.find(r => r.id === balanceItem.registrationId) : null
146
144
  const order = balanceItem?.orderId && orders.find(r => r.id === balanceItem.orderId)
147
145
  const group = registration ? groups.find(g => g.id === registration.groupId) : null
148
146
 
@@ -154,8 +152,7 @@ export class Payment extends Model {
154
152
  ...item,
155
153
  balanceItem: BalanceItemDetailed.create({
156
154
  ...balanceItem,
157
- registration: registration ? registration.setRelation(Registration.group, group!).getStructure() : null,
158
- member: member ? MemberStruct.create(member) : null,
155
+ registration: registration ? Member.getRegistrationWithMemberStructure(registration.setRelation(Registration.group, group!)) : null,
159
156
  order: order ? OrderStruct.create({...order, payment: null}) : null
160
157
  })
161
158
  })
@@ -195,22 +192,19 @@ export class Payment extends Model {
195
192
  }
196
193
 
197
194
  static async loadBalanceItemRelations(balanceItems: import("./BalanceItem").BalanceItem[]) {
198
- const {Registration} = await import("./Registration");
199
195
  const {Order} = await import("./Order");
200
196
  const {Member} = await import("./Member");
201
197
 
202
198
  // Load members and orders
203
199
  const registrationIds = Formatter.uniqueArray(balanceItems.flatMap(b => b.registrationId ? [b.registrationId] : []))
204
200
  const orderIds = Formatter.uniqueArray(balanceItems.flatMap(b => b.orderId ? [b.orderId] : []))
205
- const memberIds = Formatter.uniqueArray(balanceItems.flatMap(b => b.memberId ? [b.memberId] : []))
206
201
 
207
- const registrations = await Registration.getByIDs(...registrationIds)
202
+ const registrations = await Member.getRegistrationWithMembersByIDs(registrationIds)
208
203
  const orders = await Order.getByIDs(...orderIds)
209
- const members = await Member.getByIDs(...memberIds)
210
204
 
211
205
  const groupIds = Formatter.uniqueArray(registrations.map(r => r.groupId))
212
206
  const groups = await (await import("./Group")).Group.getByIDs(...groupIds)
213
207
 
214
- return {registrations, orders, members, groups}
208
+ return {registrations, orders, groups}
215
209
  }
216
210
  }
@@ -1,11 +1,13 @@
1
1
  import { column, Database, ManyToOneRelation, Model } from '@simonbackx/simple-database';
2
2
  import { Email } from '@stamhoofd/email';
3
- import { EmailTemplateType, PaymentMethod, PaymentMethodHelper, Recipient, Registration as RegistrationStructure, Replacement } from '@stamhoofd/structures';
3
+ import { EmailTemplateType, PaymentMethod, PaymentMethodHelper, Recipient, Registration as RegistrationStructure, Replacement, StockReservation } from '@stamhoofd/structures';
4
4
  import { Formatter } from '@stamhoofd/utility';
5
5
  import { v4 as uuidv4 } from "uuid";
6
6
 
7
7
  import { getEmailBuilder } from '../helpers/EmailBuilder';
8
- import { Document, EmailTemplate, Organization, User } from './';
8
+ import { Document, EmailTemplate, Group, Organization, User } from './';
9
+ import { ArrayDecoder } from '@simonbackx/simple-encoding';
10
+ import { QueueHandler } from '@stamhoofd/queues';
9
11
 
10
12
  export class Registration extends Model {
11
13
  static table = "registrations"
@@ -69,6 +71,9 @@ export class Registration extends Model {
69
71
  @column({ type: "datetime", nullable: true })
70
72
  reservedUntil: Date | null = null
71
73
 
74
+ /**
75
+ * @deprecated - replaced by group type
76
+ */
72
77
  @column({ type: "boolean" })
73
78
  waitingList = false
74
79
 
@@ -88,6 +93,12 @@ export class Registration extends Model {
88
93
  @column({ type: "integer" })
89
94
  pricePaid = 0
90
95
 
96
+ /**
97
+ * Set to null if no reservations are made, to help faster querying
98
+ */
99
+ @column({ type: "json", decoder: new ArrayDecoder(StockReservation), nullable: true })
100
+ stockReservations: StockReservation[] = []
101
+
91
102
  static group: ManyToOneRelation<"group", import('./Group').Group>
92
103
 
93
104
  getStructure(this: Registration & {group: import('./Group').Group}) {
@@ -101,7 +112,7 @@ export class Registration extends Model {
101
112
  /**
102
113
  * Update the outstanding balance of multiple members in one go (or all members)
103
114
  */
104
- static async updateOutstandingBalance(registrationIds: string[] | 'all', organizationId: string) {
115
+ static async updateOutstandingBalance(registrationIds: string[] | 'all', organizationId?: string) {
105
116
  if (registrationIds !== 'all' && registrationIds.length == 0) {
106
117
  return
107
118
  }
@@ -136,7 +147,7 @@ export class Registration extends Model {
136
147
 
137
148
  await Database.update(query, params)
138
149
 
139
- if (registrationIds !== 'all') {
150
+ if (registrationIds !== 'all' && organizationId) {
140
151
  await Document.updateForRegistrations(registrationIds, organizationId)
141
152
  }
142
153
  }
@@ -149,7 +160,7 @@ export class Registration extends Model {
149
160
  const query = `
150
161
  SELECT COUNT(DISTINCT \`${Registration.table}\`.memberId) as c FROM \`${Registration.table}\`
151
162
  JOIN \`groups\` ON \`groups\`.id = \`${Registration.table}\`.groupId
152
- WHERE \`groups\`.organizationId = ? AND \`${Registration.table}\`.cycle = \`groups\`.cycle AND \`groups\`.deletedAt is null AND \`groups\`.status != 'Archived' AND \`${Registration.table}\`.registeredAt is not null AND \`${Registration.table}\`.waitingList = 0`
163
+ WHERE \`groups\`.organizationId = ? AND \`${Registration.table}\`.cycle = \`groups\`.cycle AND \`groups\`.deletedAt is null AND \`${Registration.table}\`.registeredAt is not null AND \`${Registration.table}\`.deactivatedAt is null`
153
164
 
154
165
  const [results] = await Database.select(query, [organizationId])
155
166
  const count = results[0]['']['c'];
@@ -163,19 +174,17 @@ export class Registration extends Model {
163
174
  }
164
175
 
165
176
  async markValid(this: Registration) {
166
- if (this.registeredAt !== null) {
177
+ if (this.registeredAt !== null && this.deactivatedAt === null) {
167
178
  await this.save();
168
179
  return false;
169
180
  }
170
181
 
171
- if (this.waitingList && this.canRegister) {
172
- this.waitingList = false
173
- }
174
-
175
182
  this.reservedUntil = null
176
- this.registeredAt = new Date()
183
+ this.registeredAt = this.registeredAt ?? new Date()
184
+ this.deactivatedAt = null
177
185
  this.canRegister = false
178
186
  await this.save();
187
+ this.scheduleStockUpdate()
179
188
 
180
189
  await this.sendEmailTemplate({
181
190
  type: EmailTemplateType.RegistrationConfirmation
@@ -408,4 +417,55 @@ export class Registration extends Model {
408
417
 
409
418
  Email.schedule(builder)
410
419
  }
420
+
421
+ shouldIncludeStock() {
422
+ return (this.registeredAt !== null && this.deactivatedAt === null) || this.canRegister || (this.reservedUntil && this.reservedUntil > new Date())
423
+ }
424
+
425
+
426
+
427
+ /**
428
+ * Adds or removes the order to the stock of the webshop (if it wasn't already included). If amounts were changed, only those
429
+ * changes will get added
430
+ * Should always happen in the webshop-stock queue to prevent multiple webshop writes at the same time
431
+ * + in combination with validation and reading the webshop
432
+ */
433
+ scheduleStockUpdate() {
434
+ const id = this.id;
435
+
436
+ QueueHandler.cancel('registration-stock-update-'+id);
437
+ QueueHandler.schedule('registration-stock-update-'+id, async function(this: undefined) {
438
+ const updated = await Registration.getByID(id);
439
+
440
+ if (!updated) {
441
+ return;
442
+ }
443
+
444
+ // Start with clearing all the stock reservations we've already made
445
+ if (updated.stockReservations) {
446
+ const groupIds = Formatter.uniqueArray(updated.stockReservations.flatMap(r => r.objectType === 'Group' ? [r.objectId] : []));
447
+ for (const groupId of groupIds) {
448
+ const stocks = StockReservation.filter('Group', groupId, updated.stockReservations);
449
+
450
+ // Technically we don't need to await this, but okay...
451
+ await Group.freeStockReservations(groupId, stocks);
452
+ }
453
+ }
454
+
455
+ if (updated.shouldIncludeStock()) {
456
+ const myStockReservations: StockReservation[] = [];
457
+
458
+ // todo: build
459
+
460
+ updated.stockReservations = myStockReservations;
461
+ await updated.save();
462
+ } else {
463
+ if (updated.stockReservations.length) {
464
+ updated.stockReservations = [];
465
+ await updated.save();
466
+ }
467
+ }
468
+
469
+ }).catch(console.error)
470
+ }
411
471
  }
@@ -68,4 +68,4 @@ export class StripeAccount extends Model {
68
68
  bank_account_bank_name: account.external_accounts?.data[0]?.bank_name ?? this.meta.bank_account_bank_name ?? "",
69
69
  });
70
70
  }
71
- }
71
+ }
@@ -19,4 +19,10 @@ export class StripeCheckoutSession extends Model {
19
19
 
20
20
  @column({ type: "string", nullable: true })
21
21
  organizationId: string | null = null;
22
+
23
+ /**
24
+ * For direct charges, this should be set
25
+ */
26
+ @column({ type: "string", nullable: true })
27
+ accountId: string|null = null
22
28
  }
@@ -19,4 +19,10 @@ export class StripePaymentIntent extends Model {
19
19
 
20
20
  @column({ type: "string", nullable: true })
21
21
  organizationId: string | null = null;
22
+
23
+ /**
24
+ * For direct charges, this should be set
25
+ */
26
+ @column({ type: "string", nullable: true })
27
+ accountId: string|null = null
22
28
  }
Binary file