@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.
- package/index.ts +4 -0
- package/package.json +10 -10
- package/src/audit-logs/DocumentTemplateLogger.ts +22 -0
- package/src/audit-logs/MemberPlatformMembershipLogger.ts +6 -3
- package/src/audit-logs/ModelLogger.ts +3 -2
- package/src/audit-logs/OrderLogger.ts +1 -1
- package/src/audit-logs/OrganizationLogger.ts +1 -11
- package/src/crons/updateSetupSteps.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +3 -3
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +2 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +38 -25
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +3 -11
- package/src/helpers/PeriodHelper.ts +2 -1
- package/src/helpers/SetupStepUpdater.ts +503 -0
- package/src/seeds/1726847064-setup-steps.ts +1 -1
- package/src/seeds/1733319079-fill-paying-organization-ids.ts +68 -0
- package/src/services/AuditLogService.ts +11 -14
- package/src/services/DocumentService.ts +43 -0
- package/src/services/RegistrationService.ts +2 -0
- package/src/services/diff.ts +512 -0
- package/src/sql-filters/events.ts +13 -1
- package/src/services/explainPatch.ts +0 -851
|
@@ -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
|
+
}
|
|
@@ -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 {
|
|
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 {
|
|
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
|
+
};
|