@stamhoofd/models 2.36.2 → 2.38.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 (62) hide show
  1. package/dist/src/factories/GroupFactory.d.ts +2 -0
  2. package/dist/src/factories/GroupFactory.d.ts.map +1 -1
  3. package/dist/src/factories/GroupFactory.js +12 -0
  4. package/dist/src/factories/GroupFactory.js.map +1 -1
  5. package/dist/src/factories/OrganizationFactory.d.ts +2 -0
  6. package/dist/src/factories/OrganizationFactory.d.ts.map +1 -1
  7. package/dist/src/factories/OrganizationFactory.js +4 -0
  8. package/dist/src/factories/OrganizationFactory.js.map +1 -1
  9. package/dist/src/factories/RegistrationFactory.d.ts +3 -0
  10. package/dist/src/factories/RegistrationFactory.d.ts.map +1 -1
  11. package/dist/src/factories/RegistrationFactory.js +4 -0
  12. package/dist/src/factories/RegistrationFactory.js.map +1 -1
  13. package/dist/src/factories/RegistrationPeriodFactory.d.ts +11 -0
  14. package/dist/src/factories/RegistrationPeriodFactory.d.ts.map +1 -0
  15. package/dist/src/factories/RegistrationPeriodFactory.js +23 -0
  16. package/dist/src/factories/RegistrationPeriodFactory.js.map +1 -0
  17. package/dist/src/index.d.ts +1 -0
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js +1 -0
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/migrations/1726668114-maximum-free-amount.sql +2 -0
  22. package/dist/src/migrations/1726668115-free-amount.sql +2 -0
  23. package/dist/src/migrations/1726668116-price-without-discount.sql +2 -0
  24. package/dist/src/models/BalanceItem.js +2 -2
  25. package/dist/src/models/BalanceItem.js.map +1 -1
  26. package/dist/src/models/CachedOutstandingBalance.d.ts.map +1 -1
  27. package/dist/src/models/CachedOutstandingBalance.js +6 -0
  28. package/dist/src/models/CachedOutstandingBalance.js.map +1 -1
  29. package/dist/src/models/Email.d.ts +1 -0
  30. package/dist/src/models/Email.d.ts.map +1 -1
  31. package/dist/src/models/Email.js +54 -1
  32. package/dist/src/models/Email.js.map +1 -1
  33. package/dist/src/models/Member.d.ts.map +1 -1
  34. package/dist/src/models/Member.js +3 -17
  35. package/dist/src/models/Member.js.map +1 -1
  36. package/dist/src/models/MemberPlatformMembership.d.ts +8 -0
  37. package/dist/src/models/MemberPlatformMembership.d.ts.map +1 -1
  38. package/dist/src/models/MemberPlatformMembership.js +64 -2
  39. package/dist/src/models/MemberPlatformMembership.js.map +1 -1
  40. package/dist/src/models/Organization.d.ts +3 -1
  41. package/dist/src/models/Organization.d.ts.map +1 -1
  42. package/dist/src/models/Organization.js +2 -2
  43. package/dist/src/models/Organization.js.map +1 -1
  44. package/dist/src/models/Platform.d.ts.map +1 -1
  45. package/dist/src/models/Platform.js +5 -2
  46. package/dist/src/models/Platform.js.map +1 -1
  47. package/package.json +2 -2
  48. package/src/factories/GroupFactory.ts +14 -2
  49. package/src/factories/OrganizationFactory.ts +8 -2
  50. package/src/factories/RegistrationFactory.ts +5 -0
  51. package/src/factories/RegistrationPeriodFactory.ts +23 -0
  52. package/src/index.ts +1 -0
  53. package/src/migrations/1726668114-maximum-free-amount.sql +2 -0
  54. package/src/migrations/1726668115-free-amount.sql +2 -0
  55. package/src/migrations/1726668116-price-without-discount.sql +2 -0
  56. package/src/models/BalanceItem.ts +2 -2
  57. package/src/models/CachedOutstandingBalance.ts +7 -0
  58. package/src/models/Email.ts +64 -2
  59. package/src/models/Member.ts +4 -22
  60. package/src/models/MemberPlatformMembership.ts +78 -6
  61. package/src/models/Organization.ts +2 -2
  62. package/src/models/Platform.ts +7 -2
@@ -1,5 +1,5 @@
1
1
  import { Factory } from "@simonbackx/simple-database";
2
- import { GroupCategory, OldGroupPrice, OldGroupPrices,GroupSettings, PermissionsByRole } from "@stamhoofd/structures";
2
+ import { GroupPrice, GroupSettings, OldGroupPrice, OldGroupPrices, PermissionsByRole, ReduceablePrice } from "@stamhoofd/structures";
3
3
 
4
4
  import { Group } from "../models/Group";
5
5
  import { Organization } from "../models/Organization";
@@ -8,13 +8,15 @@ import { OrganizationFactory } from './OrganizationFactory';
8
8
  class Options {
9
9
  organization?: Organization;
10
10
  price?: number;
11
- reducedPrice?: number
11
+ reducedPrice?: number;
12
+ stock?: number;
12
13
 
13
14
  delayDate?: Date
14
15
  delayPrice?: number
15
16
  delayReducedPrice?: number
16
17
  skipCategory?: boolean
17
18
  permissions?: PermissionsByRole
19
+ maxMembers?: number | null
18
20
  }
19
21
 
20
22
  export class GroupFactory extends Factory<Options, Group> {
@@ -38,6 +40,16 @@ export class GroupFactory extends Factory<Options, Group> {
38
40
  })],
39
41
  })
40
42
  ],
43
+ prices: [
44
+ GroupPrice.create({
45
+ price: ReduceablePrice.create({
46
+ price: this.options.price ?? 400,
47
+ reducedPrice: this.options.reducedPrice ?? null
48
+ }),
49
+ stock: this.options.stock ?? null
50
+ })
51
+ ],
52
+ maxMembers: this.options.maxMembers === undefined ? null : this.options.maxMembers
41
53
  })
42
54
 
43
55
  if (this.options.delayPrice !== undefined) {
@@ -1,8 +1,10 @@
1
1
  import { Factory } from "@simonbackx/simple-database";
2
- import { Address,Country,OrganizationMetaData, OrganizationType, PermissionRoleDetailed } from "@stamhoofd/structures";
3
- import { Formatter } from "@stamhoofd/utility";
2
+ import { Address, Country, OrganizationMetaData, OrganizationType, PermissionRoleDetailed } from "@stamhoofd/structures";
3
+ import { Formatter } from "@stamhoofd/utility";
4
4
 
5
5
  import { Organization } from "../models/Organization";
6
+ import { RegistrationPeriod } from "../models/RegistrationPeriod";
7
+ import { RegistrationPeriodFactory } from "./RegistrationPeriodFactory";
6
8
 
7
9
  class Options {
8
10
  uri?: string;
@@ -11,6 +13,7 @@ class Options {
11
13
  name?: string;
12
14
  city?: string;
13
15
  roles?: PermissionRoleDetailed[];
16
+ period?: RegistrationPeriod;
14
17
  }
15
18
 
16
19
  export class OrganizationFactory extends Factory<Options, Organization> {
@@ -35,6 +38,9 @@ export class OrganizationFactory extends Factory<Options, Organization> {
35
38
  country: Country.Belgium
36
39
  })
37
40
 
41
+ const period = this.options.period ?? await new RegistrationPeriodFactory({}).create();
42
+ organization.periodId = period.id;
43
+
38
44
  if (this.options.roles) {
39
45
  organization.privateMeta.roles = this.options.roles;
40
46
  }
@@ -1,5 +1,6 @@
1
1
  import { Factory } from "@simonbackx/simple-database";
2
2
 
3
+ import { GroupPrice } from "@stamhoofd/structures";
3
4
  import { Group } from "../models/Group";
4
5
  import { Member } from '../models/Member';
5
6
  import { Registration } from '../models/Registration';
@@ -7,6 +8,8 @@ import { Registration } from '../models/Registration';
7
8
  class Options {
8
9
  member: Member;
9
10
  group: Group;
11
+ groupPrice: GroupPrice
12
+ price?: number
10
13
  }
11
14
 
12
15
  export class RegistrationFactory extends Factory<Options, Registration> {
@@ -18,6 +21,8 @@ export class RegistrationFactory extends Factory<Options, Registration> {
18
21
  registration.organizationId = this.options.group.organizationId
19
22
  registration.registeredAt = new Date()
20
23
  registration.registeredAt.setMilliseconds(0)
24
+ registration.groupPrice = this.options.groupPrice
25
+ registration.price = this.options.price === undefined ? registration.groupPrice.price.price : this.options.price
21
26
 
22
27
  await registration.save()
23
28
  return registration;
@@ -0,0 +1,23 @@
1
+ import { Factory } from "@simonbackx/simple-database";
2
+ import { RegistrationPeriodSettings } from "@stamhoofd/structures";
3
+
4
+ import { RegistrationPeriod } from "../models";
5
+
6
+ class Options {
7
+ startDate?: Date;
8
+ endDate?: Date;
9
+ }
10
+
11
+ export class RegistrationPeriodFactory extends Factory<Options, RegistrationPeriod> {
12
+ async create(): Promise<RegistrationPeriod> {
13
+ const period = new RegistrationPeriod();
14
+
15
+ period.organizationId = null;
16
+ period.startDate = this.options.startDate ?? new Date(2024,0,1,0,0,0,0);
17
+ period.endDate = this.options.endDate ?? new Date(2024,11,31,59,59,59,999);
18
+ period.settings = RegistrationPeriodSettings.create({});
19
+
20
+ await period.save();
21
+ return period;
22
+ }
23
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./factories/RegisterCodeFactory"
9
9
  export * from "./factories/RegistrationFactory"
10
10
  export * from "./factories/UserFactory"
11
11
  export * from "./factories/WebshopFactory"
12
+ export * from "./factories/RegistrationPeriodFactory"
12
13
 
13
14
  // Helpers
14
15
  export * from "./helpers/EmailBuilder"
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `member_platform_memberships`
2
+ ADD COLUMN `maximumFreeAmount` int NOT NULL DEFAULT '1';
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `member_platform_memberships`
2
+ ADD COLUMN `freeAmount` int NOT NULL DEFAULT '0';
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `member_platform_memberships`
2
+ ADD COLUMN `priceWithoutDiscount` int NOT NULL DEFAULT '0';
@@ -237,7 +237,7 @@ export class BalanceItem extends Model {
237
237
 
238
238
  // Set other items to zero (the balance item payments keep the real price)
239
239
  for (const item of items) {
240
- needsUpdate = needsUpdate || (item.price > 0 && item.status !== BalanceItemStatus.Hidden)
240
+ needsUpdate = true
241
241
 
242
242
  // Don't change status of items that are already paid or are partially paid
243
243
  // Not using item.paidPrice, since this is cached
@@ -264,7 +264,7 @@ export class BalanceItem extends Model {
264
264
  for (const item of items) {
265
265
  if (item.status === BalanceItemStatus.Hidden) {
266
266
  item.status = BalanceItemStatus.Pending
267
- needsUpdate = needsUpdate || item.price > 0
267
+ needsUpdate = true
268
268
  await item.save()
269
269
  }
270
270
  }
@@ -154,6 +154,13 @@ export class CachedOutstandingBalance extends Model {
154
154
 
155
155
  results.push([objectId, {amount, amountPending}]);
156
156
  }
157
+
158
+ // Add missing object ids (with 0 amount, otherwise we don't reset the amounts back to zero when all the balance items are hidden)
159
+ for (const objectId of objectIds) {
160
+ if (!results.find(([id]) => id === objectId)) {
161
+ results.push([objectId, {amount: 0, amountPending: 0}]);
162
+ }
163
+ }
157
164
 
158
165
  return results;
159
166
  }
@@ -4,6 +4,7 @@ import { v4 as uuidv4 } from "uuid";
4
4
 
5
5
  import { AnyDecoder, ArrayDecoder } from '@simonbackx/simple-encoding';
6
6
  import { SimpleError } from '@simonbackx/simple-errors';
7
+ import { I18n } from '@stamhoofd/backend-i18n';
7
8
  import { Email as EmailClass } from "@stamhoofd/email";
8
9
  import { QueueHandler } from '@stamhoofd/queues';
9
10
  import { SQL, SQLWhereSign } from '@stamhoofd/sql';
@@ -11,7 +12,6 @@ import { Formatter } from '@stamhoofd/utility';
11
12
  import { getEmailBuilder } from '../helpers/EmailBuilder';
12
13
  import { EmailRecipient } from './EmailRecipient';
13
14
  import { Organization } from './Organization';
14
- import { I18n } from '@stamhoofd/backend-i18n';
15
15
 
16
16
  export class Email extends Model {
17
17
  static table = "emails";
@@ -132,6 +132,46 @@ export class Email extends Model {
132
132
  human: 'Vul een afzender in voor je een e-mail verstuurt'
133
133
  })
134
134
  }
135
+
136
+ this.validateAttachments()
137
+ }
138
+
139
+ validateAttachments() {
140
+ // Validate attachments
141
+ const size = this.attachments.reduce((value: number, attachment) => {
142
+ return value + attachment.bytes
143
+ }, 0)
144
+
145
+ if (size > 9.5*1024*1024) {
146
+ throw new SimpleError({
147
+ code: "too_big_attachments",
148
+ message: "Too big attachments",
149
+ human: "Jouw bericht is te groot. Grote bijlages verstuur je beter niet via e-mail, je plaatst dan best een link naar de locatie in bv. Google Drive. De maximale grootte van een e-mail is 10MB, inclusief het bericht. Als je grote bestanden verstuurt kan je ze ook proberen te verkleinen.",
150
+ field: "attachments"
151
+ })
152
+ }
153
+
154
+ const safeContentTypes = [
155
+ "application/msword",
156
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
157
+ "application/vnd.ms-excel",
158
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
159
+ "application/pdf",
160
+ "image/jpeg",
161
+ "image/png",
162
+ "image/gif"
163
+ ]
164
+
165
+ for (const attachment of this.attachments) {
166
+ if (attachment.contentType && !safeContentTypes.includes(attachment.contentType)) {
167
+ throw new SimpleError({
168
+ code: "content_type_not_supported",
169
+ message: "Content-Type not supported",
170
+ human: "Het bestandstype van jouw bijlage wordt niet ondersteund of is onveilig om in een e-mail te plaatsen. Overweeg om je bestand op bv. Google Drive te zetten en de link in jouw e-mail te zetten.",
171
+ field: "attachments"
172
+ })
173
+ }
174
+ }
135
175
  }
136
176
 
137
177
  getFromAddress() {
@@ -172,7 +212,7 @@ export class Email extends Model {
172
212
  await this.save();
173
213
 
174
214
  const id = this.id;
175
- return await QueueHandler.schedule('send-email', async function () {
215
+ return await QueueHandler.schedule('send-email', async function (this: unknown) {
176
216
  let upToDate = await Email.getByID(id);
177
217
  if (!upToDate) {
178
218
  throw new SimpleError({
@@ -253,6 +293,27 @@ export class Email extends Model {
253
293
  const batchSize = 100;
254
294
  const recipientsSet = new Set<string>();
255
295
 
296
+ const attachments = upToDate.attachments.map((attachment, index) => {
297
+ let filename = "bijlage-"+index;
298
+
299
+ if (attachment.contentType == "application/pdf") {
300
+ // tmp solution for pdf only
301
+ filename += ".pdf"
302
+ }
303
+
304
+ // Correct file name if needed
305
+ if (attachment.filename) {
306
+ filename = attachment.filename.toLowerCase().replace(/[^a-z0-9.]+/g, "-").replace(/^-+/, "").replace(/-+$/, "")
307
+ }
308
+
309
+ return {
310
+ filename: filename,
311
+ content: attachment.content,
312
+ contentType: attachment.contentType ?? undefined,
313
+ encoding: "base64"
314
+ }
315
+ })
316
+
256
317
  // eslint-disable-next-line no-constant-condition
257
318
  while (true) {
258
319
  const data = await SQL.select()
@@ -315,6 +376,7 @@ export class Email extends Model {
315
376
  subject: upToDate.subject!,
316
377
  html: upToDate.html!,
317
378
  type: "broadcast",
379
+ attachments,
318
380
  callback(error: Error|null ) {
319
381
  callback(error).catch(console.error)
320
382
  },
@@ -498,18 +498,9 @@ export class Member extends Model {
498
498
 
499
499
  if (activeMembershipsUndeletable.length) {
500
500
  // Skip automatic additions
501
- for (const m of activeMembershipsUndeletable) {
502
- const beforePrice = m.price
503
-
501
+ for (const m of activeMembershipsUndeletable) {
504
502
  await m.calculatePrice(me)
505
-
506
- if (beforePrice !== m.price) {
507
- await m.save()
508
-
509
- console.log('Updated price for membership: ' + me.id + ' - membership ' + m.id)
510
- } else if (!silent) {
511
- console.log('Skipping automatic membership for: ' + me.id, ' - already has active memberships - no price change')
512
- }
503
+ await m.save()
513
504
  }
514
505
  return
515
506
  }
@@ -541,18 +532,9 @@ export class Member extends Model {
541
532
  // Check if already have the same membership
542
533
  for (const m of activeMemberships) {
543
534
  if (m.membershipTypeId === cheapestMembership.membership.id) {
544
- // Update the price of this active membership (could have changed)
545
- const beforePrice = m.price
546
-
535
+ // Update the price of this active membership (could have changed)
547
536
  await m.calculatePrice(me)
548
-
549
- if (beforePrice !== m.price) {
550
- await m.save()
551
-
552
- console.log('Updated price for membership: ' + me.id + ' - membership ' + m.id)
553
- } else if (!silent) {
554
- console.log('Skipping automatic membership for: ' + me.id, ' - already has this membership - no price change')
555
- }
537
+ await m.save()
556
538
  return
557
539
  }
558
540
  }
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { column, Model, SQLResultNamespacedRow } from "@simonbackx/simple-database";
3
3
  import { SimpleError } from "@simonbackx/simple-errors";
4
- import { SQL, SQLSelect } from "@stamhoofd/sql";
4
+ import { SQL, SQLSelect, SQLWhereSign } from "@stamhoofd/sql";
5
5
  import { PlatformMembershipTypeBehaviour } from "@stamhoofd/structures";
6
6
  import { Formatter } from "@stamhoofd/utility";
7
7
  import { v4 as uuidv4 } from "uuid";
@@ -48,6 +48,19 @@ export class MemberPlatformMembership extends Model {
48
48
  @column({ type: "integer" })
49
49
  price = 0;
50
50
 
51
+ @column({ type: "integer" })
52
+ priceWithoutDiscount = 0;
53
+
54
+ /**
55
+ * Contains the amount of days, or either 1/0 to count the amount of 'free' days maximum could be awarded.
56
+ * Set to 0 when already a different 'free' membership was created, so it shouldn't count for a free day
57
+ */
58
+ @column({ type: "integer" })
59
+ maximumFreeAmount = 0;
60
+
61
+ @column({ type: "integer" })
62
+ freeAmount = 0;
63
+
51
64
  /**
52
65
  * Whether this was added automatically by the system
53
66
  */
@@ -93,7 +106,7 @@ export class MemberPlatformMembership extends Model {
93
106
  }
94
107
 
95
108
  async calculatePrice(member: Member) {
96
- const platform = await Platform.getShared();
109
+ const platform = await Platform.getSharedPrivateStruct();
97
110
  const membershipType = platform.config.membershipTypes.find(m => m.id == this.membershipTypeId);
98
111
 
99
112
  if (!membershipType) {
@@ -115,7 +128,7 @@ export class MemberPlatformMembership extends Model {
115
128
  }
116
129
 
117
130
  const organization = await Organization.getByID(this.organizationId);
118
- if(!organization) {
131
+ if (!organization) {
119
132
  throw new SimpleError({
120
133
  //todo
121
134
  code: 'not_found',
@@ -126,9 +139,40 @@ export class MemberPlatformMembership extends Model {
126
139
 
127
140
  const tagIds = organization.meta.tags;
128
141
  const shouldApplyReducedPrice = member.details.shouldApplyReducedPrice;
129
-
130
- const priceConfig = periodConfig.getPriceConfigForDate(membershipType.behaviour === PlatformMembershipTypeBehaviour.Days ? this.startDate : (this.createdAt ?? new Date()));
131
142
 
143
+ const priceConfig = periodConfig.getPriceConfigForDate(membershipType.behaviour === PlatformMembershipTypeBehaviour.Days ? this.startDate : (this.createdAt ?? new Date()));
144
+ const earliestPriceConfig = periodConfig.getPriceConfigForDate(membershipType.behaviour === PlatformMembershipTypeBehaviour.Days ? new Date(1950, 0, 1) : (new Date(1950, 0, 1)));
145
+
146
+ let freeDays = 0;
147
+
148
+ const d = this.createdAt ??new Date();
149
+
150
+ if (periodConfig.amountFree) {
151
+ // Check if this organization has rights to free memberships
152
+ const alreadyUsed = await MemberPlatformMembership.select()
153
+ .where('organizationId', this.organizationId)
154
+ .where('membershipTypeId', this.membershipTypeId)
155
+ .where('periodId', this.periodId)
156
+ .where('deletedAt', null)
157
+ .whereNot('id', this.id)
158
+ .where(
159
+ SQL.where('createdAt', SQLWhereSign.Less, d)
160
+ .or(
161
+ SQL.where('createdAt', SQLWhereSign.Equal, d)
162
+ .and('id', SQLWhereSign.Less, this.id)
163
+ )
164
+ )
165
+ .sum(SQL.column('maximumFreeAmount'));
166
+
167
+ if (alreadyUsed < periodConfig.amountFree) {
168
+ freeDays = periodConfig.amountFree - alreadyUsed;
169
+ console.log('Free membership created for ', this.id, periodConfig.amountFree, alreadyUsed);
170
+ } else {
171
+ console.log('No free membership created for', this.id, periodConfig.amountFree, alreadyUsed);
172
+ }
173
+ }
174
+
175
+
132
176
  if (membershipType.behaviour === PlatformMembershipTypeBehaviour.Days) {
133
177
  // Make sure time is equal between start and end date
134
178
  let startBrussels = Formatter.luxon(this.startDate);
@@ -141,20 +185,48 @@ export class MemberPlatformMembership extends Model {
141
185
  this.expireDate = null
142
186
 
143
187
  const days = Math.round((this.endDate.getTime() - this.startDate.getTime()) / (1000 * 60 * 60 * 24));
144
- this.price = priceConfig.calculatePrice(tagIds, shouldApplyReducedPrice, days);
188
+ this.maximumFreeAmount = days;
189
+ this.priceWithoutDiscount = earliestPriceConfig.calculatePrice(tagIds, false, days);
190
+ this.price = priceConfig.calculatePrice(tagIds, shouldApplyReducedPrice, Math.max(0, days - freeDays));
191
+ this.freeAmount = Math.min(days, freeDays);
145
192
  } else {
193
+ this.priceWithoutDiscount = earliestPriceConfig.getBasePrice(tagIds, false);
146
194
  this.price = priceConfig.getBasePrice(tagIds, shouldApplyReducedPrice);
147
195
  this.startDate = periodConfig.startDate;
148
196
  this.endDate = periodConfig.endDate;
149
197
  this.expireDate = periodConfig.expireDate;
198
+ this.maximumFreeAmount = this.price > 0 ? 1 : 0;
199
+ this.freeAmount = 0;
200
+
201
+ if (freeDays > 0) {
202
+ this.price = 0;
203
+ this.freeAmount = 1;
204
+ }
150
205
  }
151
206
 
152
207
  if (this.balanceItemId) {
208
+ this.maximumFreeAmount = this.freeAmount;
209
+
153
210
  // Also update the balance item
154
211
  const balanceItem = await BalanceItem.getByID(this.balanceItemId);
155
212
  if (balanceItem) {
156
213
  balanceItem.unitPrice = this.price;
157
214
  await balanceItem.save();
215
+
216
+ await BalanceItem.updateOutstanding([balanceItem]);
217
+ }
218
+ }
219
+ }
220
+
221
+ async doDelete() {
222
+ this.deletedAt = new Date()
223
+ await this.save()
224
+
225
+ if (this.balanceItemId) {
226
+ // Also update the balance item
227
+ const balanceItem = await BalanceItem.getByID(this.balanceItemId);
228
+ if (balanceItem) {
229
+ await BalanceItem.deleteItems([balanceItem])
158
230
  }
159
231
  }
160
232
  }
@@ -196,7 +196,7 @@ export class Organization extends Model {
196
196
  * Get an Organization by looking at the host of a request
197
197
  * Format is 2331c59a-0cbe-4279-871c-ea9d0474cd54.api.stamhoofd.app
198
198
  */
199
- static async fromApiHost(host: string): Promise<Organization> {
199
+ static async fromApiHost(host: string, options?: {allowInactive?: boolean}): Promise<Organization> {
200
200
  const splitted = host.split('.')
201
201
  if (splitted.length < 2) {
202
202
  throw new SimpleError({
@@ -213,7 +213,7 @@ export class Organization extends Model {
213
213
  });
214
214
  }
215
215
 
216
- if (!organization.active) {
216
+ if (!organization.active && !options?.allowInactive) {
217
217
  throw new SimpleError({
218
218
  code: "archived",
219
219
  message: "This organization is archived",
@@ -77,7 +77,12 @@ export class Platform extends Model {
77
77
  }
78
78
 
79
79
  async save() {
80
- Platform.sharedStruct = null
81
- return await super.save()
80
+ Platform.clearCache()
81
+ const s = await super.save()
82
+
83
+ // Force update cache immediately
84
+ await Platform.getSharedStruct();
85
+
86
+ return s;
82
87
  }
83
88
  }