@stamhoofd/backend 2.59.0 → 2.60.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.
@@ -0,0 +1,503 @@
1
+ import { Model } from '@simonbackx/simple-database';
2
+ import { SimpleError } from '@simonbackx/simple-errors';
3
+ import {
4
+ Group,
5
+ Member,
6
+ MemberResponsibilityRecord,
7
+ Organization,
8
+ OrganizationRegistrationPeriod,
9
+ Platform,
10
+ } from '@stamhoofd/models';
11
+ import { QueueHandler } from '@stamhoofd/queues';
12
+ import { SQL, SQLWhereSign } from '@stamhoofd/sql';
13
+ import {
14
+ AuditLogSource,
15
+ GroupType,
16
+ MemberResponsibility,
17
+ Platform as PlatformStruct,
18
+ SetupStepType,
19
+ SetupSteps,
20
+ } from '@stamhoofd/structures';
21
+ import { Formatter } from '@stamhoofd/utility';
22
+ import { AuditLogService } from '../services/AuditLogService';
23
+
24
+ type SetupStepOperation = (setupSteps: SetupSteps, organization: Organization, platform: PlatformStruct) => void | Promise<void>;
25
+
26
+ /**
27
+ * Helper function to detect whether we should update the setup steps if a group is changed
28
+ */
29
+ async function getGroupMemberCompletion(group: Group) {
30
+ const defaultAgeGroupId = group.defaultAgeGroupId;
31
+
32
+ if (!defaultAgeGroupId) {
33
+ // Not included = no step
34
+ return null;
35
+ }
36
+
37
+ if (group.deletedAt) {
38
+ // Not included = no step
39
+ return null;
40
+ }
41
+
42
+ const platform = await Platform.getSharedStruct();
43
+
44
+ if (group.periodId !== platform.period.id) {
45
+ // Not included = no step
46
+ return null;
47
+ }
48
+
49
+ const defaultAgeGroup = platform.config.defaultAgeGroups.find(g => g.id === defaultAgeGroupId);
50
+
51
+ if (!defaultAgeGroup) {
52
+ // Not included = no step
53
+ return null;
54
+ }
55
+
56
+ const required = defaultAgeGroup.minimumRequiredMembers;
57
+ if (required === 0) {
58
+ // Not included = no step
59
+ return null;
60
+ }
61
+
62
+ return Math.min(required, group.settings.registeredMembers ?? 0);
63
+ }
64
+
65
+ export class SetupStepUpdater {
66
+ static isListening = false;
67
+
68
+ static listen() {
69
+ if (this.isListening) {
70
+ return;
71
+ }
72
+ this.isListening = true;
73
+ Model.modelEventBus.addListener({}, async (event) => {
74
+ if (!(event.model instanceof Group)) {
75
+ return;
76
+ }
77
+
78
+ const before = (event.type === 'updated' ? await getGroupMemberCompletion(event.getOldModel() as Group) : (event.type === 'deleted' ? await getGroupMemberCompletion(event.model) : null));
79
+ const after = (event.type === 'updated' ? await getGroupMemberCompletion(event.model) : (event.type === 'created' ? await getGroupMemberCompletion(event.model) : null));
80
+
81
+ if (before !== after) {
82
+ console.log('Updating setups steps for organization', event.model.organizationId, 'because of change in members in group', event.model.id, 'from', before, 'to', after, '(limited)');
83
+ // We need to do a recalculation
84
+ SetupStepUpdater.updateForOrganizationId(event.model.organizationId)
85
+ .catch(console.error);
86
+ }
87
+ });
88
+ }
89
+
90
+ private static readonly STEP_TYPE_OPERATIONS: Record<
91
+ SetupStepType,
92
+ SetupStepOperation
93
+ > = {
94
+ [SetupStepType.Responsibilities]: this.updateStepResponsibilities,
95
+ [SetupStepType.Companies]: this.updateStepCompanies,
96
+ [SetupStepType.Groups]: this.updateStepGroups,
97
+ [SetupStepType.Premises]: this.updateStepPremises,
98
+ [SetupStepType.Emails]: this.updateStepEmails,
99
+ [SetupStepType.Payment]: this.updateStepPayment,
100
+ [SetupStepType.Registrations]: this.updateStepRegistrations,
101
+ };
102
+
103
+ static async updateSetupStepsForAllOrganizationsInCurrentPeriod({
104
+ batchSize,
105
+ }: { batchSize?: number } = {}) {
106
+ const tag = 'updateSetupStepsForAllOrganizationsInCurrentPeriod';
107
+ QueueHandler.cancel(tag);
108
+
109
+ await QueueHandler.schedule(tag, async () => {
110
+ const platform = (await Platform.getSharedPrivateStruct()).clone();
111
+
112
+ const periodId = platform.period.id;
113
+
114
+ let lastId = '';
115
+
116
+ while (true) {
117
+ const organizationRegistrationPeriods
118
+ = await OrganizationRegistrationPeriod.where(
119
+ {
120
+ id: { sign: '>', value: lastId },
121
+ periodId: periodId,
122
+ },
123
+ { limit: batchSize ?? 10, sort: ['id'] },
124
+ );
125
+
126
+ if (organizationRegistrationPeriods.length === 0) {
127
+ lastId = '';
128
+ break;
129
+ }
130
+
131
+ const organizationPeriodMap = new Map(
132
+ organizationRegistrationPeriods.map((period) => {
133
+ return [period.organizationId, period];
134
+ }),
135
+ );
136
+
137
+ const organizations = await Organization.getByIDs(
138
+ ...organizationPeriodMap.keys(),
139
+ );
140
+
141
+ for (const organization of organizations) {
142
+ const organizationId = organization.id;
143
+ const organizationRegistrationPeriod
144
+ = organizationPeriodMap.get(organizationId);
145
+
146
+ if (!organizationRegistrationPeriod) {
147
+ console.error(
148
+ `[FLAG-MOMENT] organizationRegistrationPeriod not found for organization with id ${organizationId}`,
149
+ );
150
+ continue;
151
+ }
152
+
153
+ console.log(
154
+ '[FLAG-MOMENT] checking flag moments for '
155
+ + organizationId,
156
+ );
157
+
158
+ await SetupStepUpdater.updateFor(
159
+ organizationRegistrationPeriod,
160
+ platform,
161
+ organization,
162
+ );
163
+ }
164
+
165
+ lastId
166
+ = organizationRegistrationPeriods[
167
+ organizationRegistrationPeriods.length - 1
168
+ ].id;
169
+ }
170
+ });
171
+ }
172
+
173
+ static async updateForOrganizationId(id: string) {
174
+ const organization = await Organization.getByID(id);
175
+ if (!organization) {
176
+ throw new SimpleError({
177
+ code: 'not_found',
178
+ message: 'Organization not found',
179
+ human: 'De organisatie werd niet gevonden',
180
+ });
181
+ }
182
+
183
+ await this.updateForOrganization(organization);
184
+ }
185
+
186
+ static async updateForOrganization(
187
+ organization: Organization,
188
+ {
189
+ platform,
190
+ organizationRegistrationPeriod,
191
+ }: {
192
+ platform?: PlatformStruct;
193
+ organizationRegistrationPeriod?: OrganizationRegistrationPeriod;
194
+ } = {},
195
+ ) {
196
+ if (!platform) {
197
+ platform = await Platform.getSharedPrivateStruct();
198
+ if (!platform) {
199
+ console.error('No platform not found');
200
+ return;
201
+ }
202
+ }
203
+
204
+ if (!organizationRegistrationPeriod) {
205
+ const periodId = platform.period.id;
206
+ organizationRegistrationPeriod = (
207
+ await OrganizationRegistrationPeriod.where({
208
+ organizationId: organization.id,
209
+ periodId: periodId,
210
+ })
211
+ )[0];
212
+
213
+ if (!organizationRegistrationPeriod) {
214
+ console.error(
215
+ `OrganizationRegistrationPeriod with organizationId ${organization.id} and periodId ${periodId} not found`,
216
+ );
217
+ return;
218
+ }
219
+ }
220
+
221
+ await this.updateFor(
222
+ organizationRegistrationPeriod,
223
+ platform,
224
+ organization,
225
+ );
226
+ }
227
+
228
+ private static async updateFor(
229
+ organizationRegistrationPeriod: OrganizationRegistrationPeriod,
230
+ platform: PlatformStruct,
231
+ organization: Organization,
232
+ ) {
233
+ console.log('Updating setup steps for organization', organization.id);
234
+ const setupSteps = organizationRegistrationPeriod.setupSteps;
235
+
236
+ await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
237
+ for (const stepType of Object.values(SetupStepType)) {
238
+ const operation = this.STEP_TYPE_OPERATIONS[stepType];
239
+ await operation(setupSteps, organization, platform);
240
+ }
241
+
242
+ await organizationRegistrationPeriod.save();
243
+ });
244
+ }
245
+
246
+ private static updateStepPremises(
247
+ setupSteps: SetupSteps,
248
+ organization: Organization,
249
+ platform: PlatformStruct,
250
+ ) {
251
+ let totalSteps = 0;
252
+ let finishedSteps = 0;
253
+
254
+ const premiseTypes = platform.config.premiseTypes;
255
+
256
+ for (const premiseType of premiseTypes) {
257
+ const { min, max } = premiseType;
258
+
259
+ // only add step if premise type has restrictions
260
+ if (min === null && max === null) {
261
+ continue;
262
+ }
263
+
264
+ totalSteps++;
265
+
266
+ const premiseTypeId = premiseType.id;
267
+ let totalPremisesOfThisType = 0;
268
+
269
+ for (const premise of organization.privateMeta.premises) {
270
+ if (premise.premiseTypeIds.includes(premiseTypeId)) {
271
+ totalPremisesOfThisType++;
272
+ }
273
+ }
274
+
275
+ if (max !== null && totalPremisesOfThisType > max) {
276
+ continue;
277
+ }
278
+
279
+ if (min !== null && totalPremisesOfThisType < min) {
280
+ continue;
281
+ }
282
+
283
+ finishedSteps++;
284
+ }
285
+
286
+ setupSteps.update(SetupStepType.Premises, {
287
+ totalSteps,
288
+ finishedSteps,
289
+ });
290
+ }
291
+
292
+ private static updateStepGroups(
293
+ setupSteps: SetupSteps,
294
+ _organization: Organization,
295
+ _platform: PlatformStruct,
296
+ ) {
297
+ setupSteps.update(SetupStepType.Groups, {
298
+ totalSteps: 0,
299
+ finishedSteps: 0,
300
+ });
301
+ }
302
+
303
+ private static updateStepCompanies(
304
+ setupSteps: SetupSteps,
305
+ organization: Organization,
306
+ _platform: PlatformStruct,
307
+ ) {
308
+ const totalSteps = 1;
309
+ let finishedSteps = 0;
310
+
311
+ if (organization.meta.companies.length) {
312
+ finishedSteps = 1;
313
+ }
314
+
315
+ setupSteps.update(SetupStepType.Companies, {
316
+ totalSteps,
317
+ finishedSteps,
318
+ });
319
+ }
320
+
321
+ private static async updateStepResponsibilities(
322
+ setupSteps: SetupSteps,
323
+ organization: Organization,
324
+ platform: PlatformStruct,
325
+ ) {
326
+ const now = new Date();
327
+ const organizationBasedResponsibilitiesWithRestriction = platform.config.responsibilities
328
+ .filter(r => r.organizationBased && (r.minimumMembers || r.maximumMembers));
329
+
330
+ const responsibilityIds = organizationBasedResponsibilitiesWithRestriction.map(r => r.id);
331
+
332
+ const allRecords = responsibilityIds.length === 0
333
+ ? []
334
+ : await MemberResponsibilityRecord.select()
335
+ .where('responsibilityId', responsibilityIds)
336
+ .where('organizationId', organization.id)
337
+ .where(SQL.where('endDate', SQLWhereSign.Greater, now).or('endDate', null))
338
+ .fetch();
339
+
340
+ // Remove invalid responsibilities: members that are not registered in the current period
341
+ const memberIds = Formatter.uniqueArray(allRecords.map(r => r.memberId));
342
+ const members = await Member.getBlobByIds(...memberIds);
343
+ const validMembers = members.filter(m => m.registrations.some(r => r.organizationId === organization.id && r.periodId === organization.periodId && r.group.type === GroupType.Membership && r.deactivatedAt === null && r.registeredAt !== null));
344
+
345
+ const validMembersIds = validMembers.map(m => m.id);
346
+
347
+ const records = allRecords.filter(r => validMembersIds.includes(r.memberId));
348
+
349
+ let totalSteps = 0;
350
+ let finishedSteps = 0;
351
+
352
+ const groups = await Group.getAll(organization.id, organization.periodId);
353
+
354
+ const flatResponsibilities: { responsibility: MemberResponsibility; group: Group | null }[] = organizationBasedResponsibilitiesWithRestriction
355
+ .flatMap((responsibility) => {
356
+ const defaultAgeGroupIds = responsibility.defaultAgeGroupIds;
357
+ if (defaultAgeGroupIds === null) {
358
+ const item: { responsibility: MemberResponsibility; group: Group | null } = {
359
+ responsibility,
360
+ group: null,
361
+ };
362
+ return [item];
363
+ }
364
+
365
+ return groups
366
+ .filter(g => g.defaultAgeGroupId !== null && defaultAgeGroupIds.includes(g.defaultAgeGroupId))
367
+ .map((group) => {
368
+ return {
369
+ responsibility,
370
+ group,
371
+ };
372
+ });
373
+ });
374
+
375
+ for (const { responsibility, group } of flatResponsibilities) {
376
+ const { minimumMembers: min, maximumMembers: max } = responsibility;
377
+
378
+ if (min === null) {
379
+ continue;
380
+ }
381
+
382
+ totalSteps += min;
383
+
384
+ const responsibilityId = responsibility.id;
385
+ let totalRecordsWithThisResponsibility = 0;
386
+
387
+ if (group === null) {
388
+ for (const record of records) {
389
+ if (record.responsibilityId === responsibilityId) {
390
+ totalRecordsWithThisResponsibility++;
391
+ }
392
+ }
393
+ }
394
+ else {
395
+ for (const record of records) {
396
+ if (record.responsibilityId === responsibilityId && record.groupId === group.id) {
397
+ totalRecordsWithThisResponsibility++;
398
+ }
399
+ }
400
+ }
401
+
402
+ if (max !== null && totalRecordsWithThisResponsibility > max) {
403
+ // Not added
404
+ continue;
405
+ }
406
+
407
+ finishedSteps += Math.min(min, totalRecordsWithThisResponsibility);
408
+ }
409
+
410
+ setupSteps.update(SetupStepType.Responsibilities, {
411
+ totalSteps,
412
+ finishedSteps,
413
+ });
414
+ }
415
+
416
+ private static updateStepEmails(setupSteps: SetupSteps,
417
+ organization: Organization,
418
+ _platform: PlatformStruct) {
419
+ const totalSteps = 1;
420
+ let finishedSteps = 0;
421
+
422
+ const emails = organization.privateMeta.emails;
423
+
424
+ // organization should have 1 default email
425
+ if (emails.some(e => e.default)) {
426
+ finishedSteps = 1;
427
+ }
428
+
429
+ setupSteps.update(SetupStepType.Emails, {
430
+ totalSteps,
431
+ finishedSteps,
432
+ });
433
+
434
+ if (finishedSteps >= totalSteps) {
435
+ setupSteps.markReviewed(SetupStepType.Emails, { userId: 'backend', userName: 'backend' });
436
+ }
437
+ else {
438
+ setupSteps.resetReviewed(SetupStepType.Emails);
439
+ }
440
+ }
441
+
442
+ private static updateStepPayment(setupSteps: SetupSteps,
443
+ _organization: Organization,
444
+ _platform: PlatformStruct) {
445
+ setupSteps.update(SetupStepType.Payment, {
446
+ totalSteps: 0,
447
+ finishedSteps: 0,
448
+ });
449
+ }
450
+
451
+ private static async updateStepRegistrations(setupSteps: SetupSteps,
452
+ organization: Organization,
453
+ platform: PlatformStruct) {
454
+ const defaultAgeGroupIds = platform.config.defaultAgeGroups.filter(g => g.minimumRequiredMembers > 0).map(x => x.id);
455
+
456
+ const groupsWithDefaultAgeGroups = defaultAgeGroupIds.length > 0
457
+ ? await Group.select()
458
+ .where('organizationId', organization.id)
459
+ .where('periodId', platform.period.id)
460
+ .where('defaultAgeGroupId', defaultAgeGroupIds)
461
+ .where('deletedAt', null)
462
+ .fetch()
463
+ : [];
464
+
465
+ let totalSteps = 0;
466
+
467
+ // Count per default age group, (e.g. minmium 10 means the total across all members with the same age group id should be 10, not 10 each)
468
+ const processedDefaultAgeGroupIds: Map<string, number> = new Map();
469
+
470
+ for (const group of groupsWithDefaultAgeGroups) {
471
+ const defaultAgeGroup = platform.config.defaultAgeGroups.find(g => g.id === group.defaultAgeGroupId);
472
+ if (!defaultAgeGroup) {
473
+ continue;
474
+ }
475
+ if (!processedDefaultAgeGroupIds.has(defaultAgeGroup.id)) {
476
+ totalSteps += defaultAgeGroup.minimumRequiredMembers;
477
+ processedDefaultAgeGroupIds.set(defaultAgeGroup.id, Math.min(defaultAgeGroup.minimumRequiredMembers, group.settings.registeredMembers ?? 0));
478
+ }
479
+ else {
480
+ processedDefaultAgeGroupIds.set(defaultAgeGroup.id,
481
+ Math.min(
482
+ defaultAgeGroup.minimumRequiredMembers,
483
+ processedDefaultAgeGroupIds.get(defaultAgeGroup.id)! + (group.settings.registeredMembers ?? 0),
484
+ ),
485
+ );
486
+ }
487
+ }
488
+
489
+ const finishedSteps = Array.from(processedDefaultAgeGroupIds.values()).reduce((a, b) => a + b, 0);
490
+
491
+ setupSteps.update(SetupStepType.Registrations, {
492
+ totalSteps,
493
+ finishedSteps,
494
+ });
495
+
496
+ if (finishedSteps >= totalSteps) {
497
+ setupSteps.markReviewed(SetupStepType.Registrations, { userId: 'backend', userName: 'backend' });
498
+ }
499
+ else {
500
+ setupSteps.resetReviewed(SetupStepType.Registrations);
501
+ }
502
+ }
503
+ }
@@ -1,5 +1,5 @@
1
1
  import { Migration } from '@simonbackx/simple-database';
2
- import { SetupStepUpdater } from '@stamhoofd/models';
2
+ import { SetupStepUpdater } from '../helpers/SetupStepUpdater';
3
3
 
4
4
  export default new Migration(async () => {
5
5
  if (STAMHOOFD.environment == 'test') {
@@ -0,0 +1,68 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { logger } from '@simonbackx/simple-logging';
3
+ import { BalanceItem, Registration } from '@stamhoofd/models';
4
+ import { Formatter } from '@stamhoofd/utility';
5
+
6
+ export default new Migration(async () => {
7
+ if (STAMHOOFD.environment == 'test') {
8
+ console.log('skipped in tests');
9
+ return;
10
+ }
11
+
12
+ process.stdout.write('\n');
13
+ let c = 0;
14
+ let id: string = '';
15
+
16
+ await logger.setContext({ tags: ['seed'] }, async () => {
17
+ while (true) {
18
+ const items = await BalanceItem.where({
19
+ id: {
20
+ value: id,
21
+ sign: '>',
22
+ },
23
+ registrationId: {
24
+ value: null,
25
+ sign: '!=',
26
+ },
27
+ payingOrganizationId: {
28
+ value: null,
29
+ sign: '!=',
30
+ },
31
+ }, { limit: 1000, sort: ['id'] });
32
+
33
+ if (items.length === 0) {
34
+ break;
35
+ }
36
+
37
+ process.stdout.write('.');
38
+
39
+ const registrationIds = Formatter.uniqueArray(items.map(i => i.registrationId!));
40
+ const registrations = await Registration.getByIDs(...registrationIds);
41
+
42
+ for (const item of items) {
43
+ if (!item.registrationId || !item.payingOrganizationId) {
44
+ continue;
45
+ }
46
+ const registration = registrations.find(r => r.id === item.registrationId);
47
+ if (!registration) {
48
+ continue;
49
+ }
50
+
51
+ registration.payingOrganizationId = item.payingOrganizationId;
52
+ if (await registration.save()) {
53
+ c++;
54
+ }
55
+ }
56
+
57
+ if (items.length < 1000) {
58
+ break;
59
+ }
60
+ id = items[items.length - 1].id;
61
+ }
62
+ });
63
+
64
+ console.log('Updated ' + c + ' registrations');
65
+
66
+ // Do something here
67
+ return Promise.resolve();
68
+ });
@@ -1,20 +1,21 @@
1
1
  import { Model, ModelEvent } from '@simonbackx/simple-database';
2
+ import { AuditLogSource } from '@stamhoofd/structures';
2
3
  import { AsyncLocalStorage } from 'node:async_hooks';
3
- import { RegistrationLogger } from '../audit-logs/RegistrationLogger';
4
+ import { DocumentTemplateLogger } from '../audit-logs/DocumentTemplateLogger';
5
+ import { EventLogger } from '../audit-logs/EventLogger';
4
6
  import { GroupLogger } from '../audit-logs/GroupLogger';
7
+ import { MemberLogger } from '../audit-logs/MemberLogger';
8
+ import { MemberPlatformMembershipLogger } from '../audit-logs/MemberPlatformMembershipLogger';
9
+ import { MemberResponsibilityRecordLogger } from '../audit-logs/MemberResponsibilityRecordLogger';
10
+ import { OrderLogger } from '../audit-logs/OrderLogger';
5
11
  import { OrganizationLogger } from '../audit-logs/OrganizationLogger';
12
+ import { OrganizationRegistrationPeriodLogger } from '../audit-logs/OrganizationRegistrationPeriodLogger';
13
+ import { PaymentLogger } from '../audit-logs/PaymentLogger';
6
14
  import { PlatformLogger } from '../audit-logs/PlatformLogger';
7
- import { EventLogger } from '../audit-logs/EventLogger';
15
+ import { RegistrationLogger } from '../audit-logs/RegistrationLogger';
8
16
  import { RegistrationPeriodLogger } from '../audit-logs/RegistrationPeriodLogger';
9
- import { OrganizationRegistrationPeriodLogger } from '../audit-logs/OrganizationRegistrationPeriodLogger';
10
17
  import { StripeAccountLogger } from '../audit-logs/StripeAccountLogger';
11
- import { MemberLogger } from '../audit-logs/MemberLogger';
12
18
  import { WebshopLogger } from '../audit-logs/WebshopLogger';
13
- import { OrderLogger } from '../audit-logs/OrderLogger';
14
- import { AuditLogSource } from '@stamhoofd/structures';
15
- import { PaymentLogger } from '../audit-logs/PaymentLogger';
16
- import { MemberPlatformMembershipLogger } from '../audit-logs/MemberPlatformMembershipLogger';
17
- import { MemberResponsibilityRecordLogger } from '../audit-logs/MemberResponsibilityRecordLogger';
18
19
 
19
20
  export type AuditLogContextSettings = {
20
21
  disable?: boolean;
@@ -72,11 +73,6 @@ export class AuditLogService {
72
73
  return;
73
74
  }
74
75
 
75
- console.log('Model event', {
76
- ...event,
77
- model: event.model.static.name,
78
- });
79
-
80
76
  if (this.isDisabled()) {
81
77
  console.log('Audit log disabled');
82
78
  return;
@@ -103,3 +99,4 @@ modelLogDefinitions.set(OrderLogger.model, OrderLogger);
103
99
  modelLogDefinitions.set(PaymentLogger.model, PaymentLogger);
104
100
  modelLogDefinitions.set(MemberPlatformMembershipLogger.model, MemberPlatformMembershipLogger);
105
101
  modelLogDefinitions.set(MemberResponsibilityRecordLogger.model, MemberResponsibilityRecordLogger);
102
+ modelLogDefinitions.set(DocumentTemplateLogger.model, DocumentTemplateLogger);
@@ -0,0 +1,43 @@
1
+ import { Model } from '@simonbackx/simple-database';
2
+ import { PlainObject } from '@simonbackx/simple-encoding';
3
+ import { Document, Group } from '@stamhoofd/models';
4
+
5
+ function getGroupFieldsAffectingDocuments(group: Group): PlainObject {
6
+ return {
7
+ type: group.type,
8
+ startDate: group.settings.startDate.getTime(),
9
+ endDate: group.settings.endDate.getTime(),
10
+ name: group.settings.name,
11
+ };
12
+ }
13
+
14
+ export class DocumentService {
15
+ static listening = false;
16
+
17
+ static listen() {
18
+ if (this.listening) {
19
+ return;
20
+ }
21
+ this.listening = true;
22
+ Model.modelEventBus.addListener(this, async (event) => {
23
+ try {
24
+ if (event.model instanceof Group && event.type === 'updated' && event.changedFields.settings && !event.model.deletedAt) {
25
+ const oldModel = event.getOldModel() as Group;
26
+ const oldFields = getGroupFieldsAffectingDocuments(oldModel);
27
+ const newFields = getGroupFieldsAffectingDocuments(event.model);
28
+
29
+ if (JSON.stringify(oldFields) === JSON.stringify(newFields)) {
30
+ console.log('Group changes cannot affect documents');
31
+ return;
32
+ }
33
+
34
+ // If a group is changed - update the documents for it
35
+ await Document.updateForGroup(event.model.id, event.model.organizationId);
36
+ }
37
+ }
38
+ catch (e) {
39
+ console.error('Failed to update documents after group change', e);
40
+ }
41
+ });
42
+ }
43
+ };
@@ -55,6 +55,8 @@ export const RegistrationService = {
55
55
  RegistrationService.scheduleStockUpdate(registration.id);
56
56
 
57
57
  await PlatformMembershipService.updateMembershipsForId(registration.memberId);
58
+
59
+ await Document.deleteForRegistrations([registration]);
58
60
  },
59
61
 
60
62
  /**