@stamhoofd/backend 2.90.3 → 2.91.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/package.json +10 -10
- package/src/audit-logs/ModelLogger.ts +0 -1
- package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +14 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +2 -0
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -3
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +6 -4
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -7
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +112 -105
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/organization/SetUitpasClientCredentialsEndpoint.ts +5 -5
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/SearchUitpasEventsEndpoint.ts +1 -1
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
- package/src/helpers/AdminPermissionChecker.ts +0 -5
- package/src/helpers/FlagMomentCleanup.ts +13 -1
- package/src/helpers/GroupedThrottledQueue.ts +5 -3
- package/src/helpers/PeriodHelper.ts +10 -137
- package/src/helpers/SetupStepUpdater.ts +54 -7
- package/src/helpers/UitpasTokenRepository.ts +3 -3
- package/src/seeds/1750090030-records-configuration.ts +5 -1
- package/src/services/BalanceItemService.ts +12 -7
- package/src/services/DocumentService.ts +0 -1
- package/src/services/RegistrationService.ts +30 -1
- package/src/services/uitpas/UitpasService.ts +1 -1
- package/src/services/uitpas/cancelTicketSales.ts +1 -1
- package/src/services/uitpas/checkPermissionsFor.ts +9 -9
- package/src/services/uitpas/checkUitpasNumbers.ts +2 -2
- package/src/services/uitpas/getSocialTariffForEvent.ts +4 -4
- package/src/services/uitpas/getSocialTariffForUitpasNumbers.ts +5 -5
- package/src/services/uitpas/registerTicketSales.ts +4 -4
- package/src/services/uitpas/searchUitpasEvents.ts +3 -3
- package/src/services/uitpas/searchUitpasOrganizers.ts +3 -3
- package/src/sql-filters/members.ts +1 -1
- package/src/sql-filters/organizations.ts +52 -0
- package/tests/e2e/register.test.ts +1 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
-
import { Group,
|
|
2
|
+
import { Group, Organization, OrganizationRegistrationPeriod, RegistrationPeriod } from '@stamhoofd/models';
|
|
3
3
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
4
|
-
import { AuditLogSource, Group as GroupStruct
|
|
4
|
+
import { AuditLogSource, Group as GroupStruct } from '@stamhoofd/structures';
|
|
5
5
|
import { PatchOrganizationRegistrationPeriodsEndpoint } from '../endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint';
|
|
6
|
-
import { AuthenticatedStructures } from './AuthenticatedStructures';
|
|
7
|
-
import { MemberUserSyncer } from './MemberUserSyncer';
|
|
8
6
|
import { AuditLogService } from '../services/AuditLogService';
|
|
7
|
+
import { AuthenticatedStructures } from './AuthenticatedStructures';
|
|
9
8
|
import { SetupStepUpdater } from './SetupStepUpdater';
|
|
10
9
|
|
|
11
10
|
export class PeriodHelper {
|
|
@@ -18,88 +17,6 @@ export class PeriodHelper {
|
|
|
18
17
|
});
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
static async stopAllResponsibilities() {
|
|
22
|
-
console.log('Stopping all responsibilities');
|
|
23
|
-
const platform = await Platform.getSharedPrivateStruct();
|
|
24
|
-
const keepPlatformResponsibilityIds = platform.config.responsibilities.filter(r => !r.organizationBased).map(r => r.id);
|
|
25
|
-
const keepResponsibilityIds = platform.config.responsibilities.filter(r => !r.organizationBased || r.permissions?.level === PermissionLevel.Full).map(r => r.id);
|
|
26
|
-
const batchSize = 100;
|
|
27
|
-
|
|
28
|
-
let lastId = '';
|
|
29
|
-
let c = 0;
|
|
30
|
-
|
|
31
|
-
while (true) {
|
|
32
|
-
const records = await MemberResponsibilityRecord.where(
|
|
33
|
-
{
|
|
34
|
-
id: { sign: '>', value: lastId },
|
|
35
|
-
endDate: null,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
limit: batchSize,
|
|
39
|
-
sort: ['id'],
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
for (const record of records) {
|
|
44
|
-
lastId = record.id;
|
|
45
|
-
|
|
46
|
-
const invalid = keepPlatformResponsibilityIds.includes(record.responsibilityId) && record.organizationId;
|
|
47
|
-
|
|
48
|
-
if (!keepResponsibilityIds.includes(record.responsibilityId) || invalid) {
|
|
49
|
-
record.endDate = new Date();
|
|
50
|
-
await record.save();
|
|
51
|
-
c++;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (records.length < batchSize) {
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
console.log('Done: stopped all responsibilities: ' + c);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static async syncAllMemberUsers() {
|
|
64
|
-
console.log('Syncing all members');
|
|
65
|
-
|
|
66
|
-
let c = 0;
|
|
67
|
-
let lastId: string = '';
|
|
68
|
-
|
|
69
|
-
while (true) {
|
|
70
|
-
const rawMembers = await Member.where({
|
|
71
|
-
id: {
|
|
72
|
-
value: lastId,
|
|
73
|
-
sign: '>',
|
|
74
|
-
},
|
|
75
|
-
}, { limit: 500, sort: ['id'] });
|
|
76
|
-
|
|
77
|
-
if (rawMembers.length === 0) {
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const membersWithRegistrations = await Member.getBlobByIds(...rawMembers.map(m => m.id));
|
|
82
|
-
|
|
83
|
-
const promises: Promise<any>[] = [];
|
|
84
|
-
|
|
85
|
-
for (const memberWithRegistrations of membersWithRegistrations) {
|
|
86
|
-
promises.push((async () => {
|
|
87
|
-
await MemberUserSyncer.onChangeMember(memberWithRegistrations);
|
|
88
|
-
c++;
|
|
89
|
-
|
|
90
|
-
if (c % 10000 === 0) {
|
|
91
|
-
console.log('Synced ' + c + ' members');
|
|
92
|
-
}
|
|
93
|
-
})());
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
await Promise.all(promises);
|
|
97
|
-
lastId = rawMembers[rawMembers.length - 1].id;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
console.log('Done: synced all members: ' + c);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
20
|
static async createOrganizationPeriodForPeriod(organization: Organization, period: RegistrationPeriod) {
|
|
104
21
|
const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: period.id, organizationId: organization.id }, { limit: 1 });
|
|
105
22
|
|
|
@@ -128,38 +45,15 @@ export class PeriodHelper {
|
|
|
128
45
|
});
|
|
129
46
|
}
|
|
130
47
|
|
|
131
|
-
const batchSize = 10;
|
|
132
48
|
await QueueHandler.schedule(tag, async () => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const organizations = await Organization.where(
|
|
137
|
-
{
|
|
138
|
-
id: { sign: '>', value: lastId },
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
limit: batchSize,
|
|
142
|
-
sort: ['id'],
|
|
143
|
-
},
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
for (const organization of organizations) {
|
|
147
|
-
try {
|
|
148
|
-
await this.moveOrganizationToPeriod(organization, period);
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
console.error('Error moving organization to period', organization.id, error);
|
|
152
|
-
}
|
|
153
|
-
lastId = organization.id;
|
|
49
|
+
for await (const organization of Organization.select().all()) {
|
|
50
|
+
try {
|
|
51
|
+
await this.moveOrganizationToPeriod(organization, period);
|
|
154
52
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
break;
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('Error moving organization to period', organization.id, error);
|
|
158
55
|
}
|
|
159
56
|
}
|
|
160
|
-
|
|
161
|
-
await this.stopAllResponsibilities();
|
|
162
|
-
await this.syncAllMemberUsers();
|
|
163
57
|
});
|
|
164
58
|
|
|
165
59
|
// When done: update setup steps
|
|
@@ -175,31 +69,10 @@ export class PeriodHelper {
|
|
|
175
69
|
|
|
176
70
|
console.log(tag);
|
|
177
71
|
|
|
178
|
-
const batchSize = 100;
|
|
179
72
|
await QueueHandler.schedule(tag, async () => {
|
|
180
73
|
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
while (true) {
|
|
184
|
-
const groups = await Group.where(
|
|
185
|
-
{
|
|
186
|
-
id: { sign: '>', value: lastId },
|
|
187
|
-
periodId: period.id,
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
limit: batchSize,
|
|
191
|
-
sort: ['id'],
|
|
192
|
-
},
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
for (const group of groups) {
|
|
196
|
-
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: group.id }), period);
|
|
197
|
-
lastId = group.id;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (groups.length < batchSize) {
|
|
201
|
-
break;
|
|
202
|
-
}
|
|
74
|
+
for await (const group of Group.select().where('periodId', period.id).all()) {
|
|
75
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: group.id }), period);
|
|
203
76
|
}
|
|
204
77
|
});
|
|
205
78
|
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Organization,
|
|
8
8
|
OrganizationRegistrationPeriod,
|
|
9
9
|
Platform,
|
|
10
|
+
Registration,
|
|
10
11
|
} from '@stamhoofd/models';
|
|
11
12
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
12
13
|
import { SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
@@ -15,11 +16,14 @@ import {
|
|
|
15
16
|
GroupType,
|
|
16
17
|
MemberResponsibility,
|
|
17
18
|
Platform as PlatformStruct,
|
|
19
|
+
RecordCategory,
|
|
18
20
|
SetupStepType,
|
|
19
21
|
SetupSteps,
|
|
20
22
|
} from '@stamhoofd/structures';
|
|
21
23
|
import { Formatter } from '@stamhoofd/utility';
|
|
22
24
|
import { AuditLogService } from '../services/AuditLogService';
|
|
25
|
+
import { GroupedThrottledQueue } from './GroupedThrottledQueue';
|
|
26
|
+
import { AuthenticatedStructures } from './AuthenticatedStructures';
|
|
23
27
|
|
|
24
28
|
type SetupStepOperation = (setupSteps: SetupSteps, organization: Organization, platform: PlatformStruct) => void | Promise<void>;
|
|
25
29
|
|
|
@@ -81,10 +85,25 @@ export class SetupStepUpdater {
|
|
|
81
85
|
if (before !== after) {
|
|
82
86
|
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
87
|
// We need to do a recalculation
|
|
84
|
-
SetupStepUpdater.updateForOrganizationId(event.model.organizationId)
|
|
88
|
+
SetupStepUpdater.updateForOrganizationId(event.model.organizationId, { types: [SetupStepType.Registrations] })
|
|
85
89
|
.catch(console.error);
|
|
86
90
|
}
|
|
87
91
|
});
|
|
92
|
+
|
|
93
|
+
const updateResponsibilitiesQueue = new GroupedThrottledQueue(async (organizationId: string) => {
|
|
94
|
+
console.log('(delayed) Updating setups steps for organization', organizationId, 'because of change in registrations');
|
|
95
|
+
SetupStepUpdater.updateForOrganizationId(organizationId, { types: [SetupStepType.Responsibilities] }).catch(console.error);
|
|
96
|
+
}, { maxDelay: 3_000 });
|
|
97
|
+
|
|
98
|
+
Model.modelEventBus.addListener({}, async (event) => {
|
|
99
|
+
if (!(event.model instanceof Registration)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if ((event.type === 'updated' && ('registeredAt' in event.changedFields || 'deactivatedAt' in event.changedFields)) || (event.type === 'created' && event.model.registeredAt)) {
|
|
104
|
+
updateResponsibilitiesQueue.addItem(event.model.organizationId, 1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
private static readonly STEP_TYPE_OPERATIONS: Record<
|
|
@@ -170,7 +189,7 @@ export class SetupStepUpdater {
|
|
|
170
189
|
});
|
|
171
190
|
}
|
|
172
191
|
|
|
173
|
-
static async updateForOrganizationId(id: string) {
|
|
192
|
+
static async updateForOrganizationId(id: string, options?: { types?: SetupStepType[] }) {
|
|
174
193
|
const organization = await Organization.getByID(id);
|
|
175
194
|
if (!organization) {
|
|
176
195
|
throw new SimpleError({
|
|
@@ -180,7 +199,7 @@ export class SetupStepUpdater {
|
|
|
180
199
|
});
|
|
181
200
|
}
|
|
182
201
|
|
|
183
|
-
await this.updateForOrganization(organization);
|
|
202
|
+
await this.updateForOrganization(organization, options);
|
|
184
203
|
}
|
|
185
204
|
|
|
186
205
|
static async updateForOrganization(
|
|
@@ -188,9 +207,11 @@ export class SetupStepUpdater {
|
|
|
188
207
|
{
|
|
189
208
|
platform,
|
|
190
209
|
organizationRegistrationPeriod,
|
|
210
|
+
types,
|
|
191
211
|
}: {
|
|
192
212
|
platform?: PlatformStruct;
|
|
193
213
|
organizationRegistrationPeriod?: OrganizationRegistrationPeriod;
|
|
214
|
+
types?: SetupStepType[];
|
|
194
215
|
} = {},
|
|
195
216
|
) {
|
|
196
217
|
if (!platform) {
|
|
@@ -222,6 +243,7 @@ export class SetupStepUpdater {
|
|
|
222
243
|
organizationRegistrationPeriod,
|
|
223
244
|
platform,
|
|
224
245
|
organization,
|
|
246
|
+
types,
|
|
225
247
|
);
|
|
226
248
|
}
|
|
227
249
|
|
|
@@ -229,12 +251,13 @@ export class SetupStepUpdater {
|
|
|
229
251
|
organizationRegistrationPeriod: OrganizationRegistrationPeriod,
|
|
230
252
|
platform: PlatformStruct,
|
|
231
253
|
organization: Organization,
|
|
254
|
+
types?: SetupStepType[],
|
|
232
255
|
) {
|
|
233
256
|
console.log('Updating setup steps for organization', organization.id);
|
|
234
257
|
const setupSteps = organizationRegistrationPeriod.setupSteps;
|
|
235
258
|
|
|
236
259
|
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
237
|
-
for (const stepType of Object.values(SetupStepType)) {
|
|
260
|
+
for (const stepType of types ?? Object.values(SetupStepType)) {
|
|
238
261
|
const operation = this.STEP_TYPE_OPERATIONS[stepType];
|
|
239
262
|
await operation(setupSteps, organization, platform);
|
|
240
263
|
}
|
|
@@ -309,15 +332,28 @@ export class SetupStepUpdater {
|
|
|
309
332
|
private static updateStepCompanies(
|
|
310
333
|
setupSteps: SetupSteps,
|
|
311
334
|
organization: Organization,
|
|
312
|
-
|
|
335
|
+
platform: PlatformStruct,
|
|
313
336
|
) {
|
|
314
337
|
const totalSteps = 1;
|
|
315
338
|
let finishedSteps = 0;
|
|
316
339
|
|
|
317
340
|
if (organization.meta.companies.length) {
|
|
318
341
|
finishedSteps = 1;
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
RecordCategory.validate(
|
|
345
|
+
platform.config.organizationLevelRecordsConfiguration.recordCategories,
|
|
346
|
+
organization.getBaseStructureWithPrivateMeta(), // private data needed for answers
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error('Error validating record categories for organization', organization.id, error);
|
|
351
|
+
finishedSteps = 0;
|
|
352
|
+
}
|
|
319
353
|
}
|
|
320
354
|
|
|
355
|
+
console.log('Updating companies step for organization', organization.id, 'with total steps', totalSteps, 'and finished steps', finishedSteps);
|
|
356
|
+
|
|
321
357
|
setupSteps.update(SetupStepType.Companies, {
|
|
322
358
|
totalSteps,
|
|
323
359
|
finishedSteps,
|
|
@@ -346,11 +382,14 @@ export class SetupStepUpdater {
|
|
|
346
382
|
// Remove invalid responsibilities: members that are not registered in the current period
|
|
347
383
|
const memberIds = Formatter.uniqueArray(allRecords.map(r => r.memberId));
|
|
348
384
|
const members = await Member.getBlobByIds(...memberIds);
|
|
349
|
-
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));
|
|
385
|
+
const validMembers = members.filter(m => m.registrations.some(r => r.organizationId === organization.id && r.periodId === organization.periodId && r.group.defaultAgeGroupId && r.group.type === GroupType.Membership && r.deactivatedAt === null && r.registeredAt !== null));
|
|
386
|
+
const invalidMembers = members.filter(m => !m.registrations.some(r => r.organizationId === organization.id && r.periodId === organization.periodId && r.group.defaultAgeGroupId && r.group.type === GroupType.Membership && r.deactivatedAt === null && r.registeredAt !== null));
|
|
350
387
|
|
|
351
388
|
const validMembersIds = validMembers.map(m => m.id);
|
|
389
|
+
const invalidMembersIds = invalidMembers.map(m => m.id);
|
|
352
390
|
|
|
353
391
|
const records = allRecords.filter(r => validMembersIds.includes(r.memberId));
|
|
392
|
+
const invalidRecords = allRecords.filter(r => invalidMembersIds.includes(r.memberId));
|
|
354
393
|
|
|
355
394
|
let totalSteps = 0;
|
|
356
395
|
let finishedSteps = 0;
|
|
@@ -381,6 +420,13 @@ export class SetupStepUpdater {
|
|
|
381
420
|
for (const { responsibility, group } of flatResponsibilities) {
|
|
382
421
|
const { minimumMembers: min, maximumMembers: max } = responsibility;
|
|
383
422
|
|
|
423
|
+
const invalidMembersWithThisResponsibility = !!invalidRecords.find(r => r.responsibilityId === responsibility.id && (group ? r.groupId === group.id : true));
|
|
424
|
+
if (invalidMembersWithThisResponsibility) {
|
|
425
|
+
totalSteps += Math.max(min ?? 1, 1);
|
|
426
|
+
// Step not okay: we have an invalid member with this responsibility
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
384
430
|
if (min === null) {
|
|
385
431
|
continue;
|
|
386
432
|
}
|
|
@@ -406,13 +452,14 @@ export class SetupStepUpdater {
|
|
|
406
452
|
}
|
|
407
453
|
|
|
408
454
|
if (max !== null && totalRecordsWithThisResponsibility > max) {
|
|
409
|
-
// Not added
|
|
455
|
+
// Not added (not okay)
|
|
410
456
|
continue;
|
|
411
457
|
}
|
|
412
458
|
|
|
413
459
|
finishedSteps += Math.min(min, totalRecordsWithThisResponsibility);
|
|
414
460
|
}
|
|
415
461
|
|
|
462
|
+
console.log('Updating responsibilities step for organization', organization.id, 'with total steps', totalSteps, 'and finished steps', finishedSteps);
|
|
416
463
|
setupSteps.update(SetupStepType.Responsibilities, {
|
|
417
464
|
totalSteps,
|
|
418
465
|
finishedSteps,
|
|
@@ -83,7 +83,7 @@ export class UitpasTokenRepository {
|
|
|
83
83
|
throw new SimpleError({
|
|
84
84
|
code: 'uitpas_api_not_configured_for_platform',
|
|
85
85
|
message: 'UiTPAS api is not configured for the platform',
|
|
86
|
-
human: $t('
|
|
86
|
+
human: $t('71a8218b-c58e-4e95-9626-551b80eb8367'),
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
model = new UitpasClientCredential();
|
|
@@ -123,7 +123,7 @@ export class UitpasTokenRepository {
|
|
|
123
123
|
throw new SimpleError({
|
|
124
124
|
code: 'invalid_uitpas_client_credentials',
|
|
125
125
|
message: `Invalid UiTPAS client credentials`,
|
|
126
|
-
human: $t(`
|
|
126
|
+
human: $t(`1086bb24-5df4-4faf-9dc0-ab5a955b0d8f`),
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
console.error(`Unsuccessful response when fetching UiTPAS token for organization with id ${this.uitpasClientCredential.organizationId}:`, response.statusText);
|
|
@@ -193,7 +193,7 @@ export class UitpasTokenRepository {
|
|
|
193
193
|
throw new SimpleError({
|
|
194
194
|
code: 'uitpas_api_not_configured_for_this_organization',
|
|
195
195
|
message: `UiTPAS api not configured for organization with id ${organizationId}`,
|
|
196
|
-
human: $t('
|
|
196
|
+
human: $t('6b333cb4-21fd-42be-b0c9-6d899f2fd348'),
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
199
|
repo = UitpasTokenRepository.setRepoInMemory(organizationId, new UitpasTokenRepository(model)); // store in memory
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
-
import { Organization } from '@stamhoofd/models';
|
|
2
|
+
import { Organization, Webshop } from '@stamhoofd/models';
|
|
3
3
|
|
|
4
4
|
export async function startRecordsConfigurationMigration() {
|
|
5
5
|
for await (const organization of Organization.select().all()) {
|
|
6
6
|
await organization.save();
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
for await (const webshop of Webshop.select().all()) {
|
|
10
|
+
await webshop.save();
|
|
11
|
+
}
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
export default new Migration(async () => {
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import { Model } from '@simonbackx/simple-database';
|
|
1
2
|
import { BalanceItem, CachedBalance, Document, MemberUser, Order, Organization, Payment, Webshop } from '@stamhoofd/models';
|
|
2
|
-
import { AuditLogSource, BalanceItemStatus, BalanceItemType, OrderStatus, ReceivableBalanceType } from '@stamhoofd/structures';
|
|
3
|
-
import {
|
|
3
|
+
import { AuditLogSource, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentStatus, ReceivableBalanceType } from '@stamhoofd/structures';
|
|
4
|
+
import { GroupedThrottledQueue } from '../helpers/GroupedThrottledQueue';
|
|
5
|
+
import { ThrottledQueue } from '../helpers/ThrottledQueue';
|
|
4
6
|
import { AuditLogService } from './AuditLogService';
|
|
5
7
|
import { PaymentReallocationService } from './PaymentReallocationService';
|
|
6
8
|
import { RegistrationService } from './RegistrationService';
|
|
7
|
-
import { Model } from '@simonbackx/simple-database';
|
|
8
|
-
import { GroupedThrottledQueue } from '../helpers/GroupedThrottledQueue';
|
|
9
|
-
import { ThrottledQueue } from '../helpers/ThrottledQueue';
|
|
10
9
|
|
|
11
10
|
const memberUpdateQueue = new GroupedThrottledQueue(async (organizationId: string, memberIds: string[]) => {
|
|
12
11
|
await CachedBalance.updateForMembers(organizationId, memberIds);
|
|
@@ -210,6 +209,12 @@ export const BalanceItemService = {
|
|
|
210
209
|
if (shouldMarkUpdated) {
|
|
211
210
|
await this.markUpdated(balanceItem, payment, organization);
|
|
212
211
|
}
|
|
212
|
+
|
|
213
|
+
if (balanceItem.registrationId) {
|
|
214
|
+
if (balanceItem.type === BalanceItemType.Registration && !!payment && payment.status === PaymentStatus.Succeeded) {
|
|
215
|
+
await RegistrationService.markRepeatedPaid(balanceItem.registrationId);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
213
218
|
return;
|
|
214
219
|
}
|
|
215
220
|
|
|
@@ -220,7 +225,7 @@ export const BalanceItemService = {
|
|
|
220
225
|
// If registration
|
|
221
226
|
if (balanceItem.registrationId) {
|
|
222
227
|
if (balanceItem.type === BalanceItemType.Registration) {
|
|
223
|
-
await RegistrationService.markValid(balanceItem.registrationId);
|
|
228
|
+
await RegistrationService.markValid(balanceItem.registrationId, { paid: !!payment && payment.status === PaymentStatus.Succeeded });
|
|
224
229
|
}
|
|
225
230
|
}
|
|
226
231
|
|
|
@@ -228,7 +233,7 @@ export const BalanceItemService = {
|
|
|
228
233
|
await balanceItem.save();
|
|
229
234
|
},
|
|
230
235
|
|
|
231
|
-
async markUpdated(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
|
|
236
|
+
async markUpdated(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
|
|
232
237
|
// For orders: mark order as changed (so they are refetched in front ends)
|
|
233
238
|
if (balanceItem.orderId) {
|
|
234
239
|
await AuditLogService.setContext({ source: AuditLogSource.Payment }, async () => {
|
|
@@ -9,16 +9,45 @@ import { Formatter } from '@stamhoofd/utility';
|
|
|
9
9
|
import { encodeObject } from '@simonbackx/simple-encoding';
|
|
10
10
|
|
|
11
11
|
export const RegistrationService = {
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* If the registration was marked valid, and later paid, we'll still shorten the trail until period
|
|
14
|
+
*/
|
|
15
|
+
async markRepeatedPaid(registrationId: string) {
|
|
16
|
+
const registration = await Registration.getByID(registrationId);
|
|
17
|
+
if (!registration) {
|
|
18
|
+
throw new Error('Registration not found');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (registration.registeredAt !== null && registration.deactivatedAt === null) {
|
|
22
|
+
// Already valid
|
|
23
|
+
if (registration.trialUntil && registration.trialUntil > new Date()) {
|
|
24
|
+
registration.trialUntil = new Date();
|
|
25
|
+
await registration.save();
|
|
26
|
+
await PlatformMembershipService.updateMembershipsForId(registration.memberId);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// do nothing: possible that we canceled the registration and don't want to reactivate it when we mark something paid
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async markValid(registrationId: string, options?: { paid?: boolean }) {
|
|
13
33
|
const registration = await Registration.getByID(registrationId);
|
|
14
34
|
if (!registration) {
|
|
15
35
|
throw new Error('Registration not found');
|
|
16
36
|
}
|
|
17
37
|
|
|
18
38
|
if (registration.registeredAt !== null && registration.deactivatedAt === null) {
|
|
39
|
+
// Already valid
|
|
40
|
+
if (options?.paid && registration.trialUntil && registration.trialUntil > new Date()) {
|
|
41
|
+
registration.trialUntil = new Date();
|
|
42
|
+
await registration.save();
|
|
43
|
+
await PlatformMembershipService.updateMembershipsForId(registration.memberId);
|
|
44
|
+
}
|
|
19
45
|
return false;
|
|
20
46
|
}
|
|
21
47
|
|
|
48
|
+
if (options?.paid && registration.trialUntil && registration.trialUntil > new Date()) {
|
|
49
|
+
registration.trialUntil = new Date();
|
|
50
|
+
}
|
|
22
51
|
registration.reservedUntil = null;
|
|
23
52
|
registration.registeredAt = registration.registeredAt ?? new Date();
|
|
24
53
|
registration.deactivatedAt = null;
|
|
@@ -77,7 +77,7 @@ function getUitpasTicketSales(order: Order): UitpasTicketSale[] {
|
|
|
77
77
|
throw new SimpleError({
|
|
78
78
|
code: 'missing_uitpas_base_product_price',
|
|
79
79
|
message: `Missing UiTPAS base product price`,
|
|
80
|
-
human: $t(`
|
|
80
|
+
human: $t(`f7eea411-fb92-458f-bf3e-e36ed870591b`),
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
const label = makeBaseProductPriceLabel(item.product, baseProductPrice);
|
|
@@ -24,7 +24,7 @@ async function cancelTicketSale(access_token: string, ticketSaleId: string) {
|
|
|
24
24
|
throw new SimpleError({
|
|
25
25
|
code: 'unsuccessful_response_registering_ticket_sales',
|
|
26
26
|
message: `Unsuccessful response when registering UiTPAS ticket sales`,
|
|
27
|
-
human: $t(`
|
|
27
|
+
human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
return ticketSaleId;
|
|
@@ -16,7 +16,7 @@ function assertIsPermissionsResponse(json: unknown): asserts json is Permissions
|
|
|
16
16
|
throw new SimpleError({
|
|
17
17
|
code: 'invalid_permissions_response',
|
|
18
18
|
message: 'Invalid response format for permissions',
|
|
19
|
-
human: $t('
|
|
19
|
+
human: $t('7d3a6b57-f81a-4d58-bc2b-babb2261c40b'),
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -35,7 +35,7 @@ function assertIsPermissionsResponse(json: unknown): asserts json is Permissions
|
|
|
35
35
|
throw new SimpleError({
|
|
36
36
|
code: 'invalid_permissions_response',
|
|
37
37
|
message: 'Invalid response format for permissions',
|
|
38
|
-
human: $t('
|
|
38
|
+
human: $t('7d3a6b57-f81a-4d58-bc2b-babb2261c40b'),
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -54,7 +54,7 @@ export async function checkPermissionsFor(access_token: string, organizationId:
|
|
|
54
54
|
throw new SimpleError({
|
|
55
55
|
code: 'uitpas_unreachable_checking_permissions',
|
|
56
56
|
message: `Network issue when checking UiTPAS permissions`,
|
|
57
|
-
human: $t(`
|
|
57
|
+
human: $t(`542b793c-3edf-4505-b33d-199ea409bbda`),
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
60
|
if (!response.ok) {
|
|
@@ -62,7 +62,7 @@ export async function checkPermissionsFor(access_token: string, organizationId:
|
|
|
62
62
|
throw new SimpleError({
|
|
63
63
|
code: 'unsuccessful_response_checking_permissions',
|
|
64
64
|
message: `Unsuccesful response when checking UiTPAS permissions`,
|
|
65
|
-
human: $t(`
|
|
65
|
+
human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
const json = await response.json().catch(() => {
|
|
@@ -70,7 +70,7 @@ export async function checkPermissionsFor(access_token: string, organizationId:
|
|
|
70
70
|
throw new SimpleError({
|
|
71
71
|
code: 'invalid_json_checking_permissions',
|
|
72
72
|
message: `Invalid json when checking UiTPAS permissions`,
|
|
73
|
-
human: $t(`
|
|
73
|
+
human: $t(`93004d03-955a-4a9a-937d-2f30841dc5cc`),
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
assertIsPermissionsResponse(json);
|
|
@@ -91,18 +91,18 @@ export async function checkPermissionsFor(access_token: string, organizationId:
|
|
|
91
91
|
}];
|
|
92
92
|
const item = json.find(item => item.organizer.id === uitpasOrganizerId);
|
|
93
93
|
if (!item) {
|
|
94
|
-
const organizers = Formatter.joinLast(json.map(i => i.organizer.name), ', ', ' ' + $t('
|
|
94
|
+
const organizers = Formatter.joinLast(json.map(i => i.organizer.name), ', ', ' ' + $t('6d35156d-e452-4b0f-80f4-b1e9024d08ee') + ' ');
|
|
95
95
|
return {
|
|
96
96
|
status: UitpasClientCredentialsStatus.NoPermissions,
|
|
97
|
-
human: $t('
|
|
97
|
+
human: $t('96c8a719-dba5-47ce-bb61-ee0754a5f776') + organizers,
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
const missingPermissions = neededPermissions.filter(needed => !item.permissions.includes(needed.permission));
|
|
101
101
|
if (missingPermissions.length > 0) {
|
|
102
|
-
const missingPermissionsHuman = Formatter.joinLast(missingPermissions.map(p => p.human), ', ', ' ' + $t('
|
|
102
|
+
const missingPermissionsHuman = Formatter.joinLast(missingPermissions.map(p => p.human), ', ', ' ' + $t('6d35156d-e452-4b0f-80f4-b1e9024d08ee') + ' ');
|
|
103
103
|
return {
|
|
104
104
|
status: UitpasClientCredentialsStatus.MissingPermissions,
|
|
105
|
-
human: $t('
|
|
105
|
+
human: $t('040fa935-5cbc-4a85-b578-354bf9d7fc04') + missingPermissionsHuman,
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
return {
|
|
@@ -36,7 +36,7 @@ function assertIsUitpasNumberSuccessfulResponse(
|
|
|
36
36
|
throw new SimpleError({
|
|
37
37
|
code: 'invalid_response_retrieving_pass_by_uitpas_number',
|
|
38
38
|
message: `Invalid response when retrieving pass by UiTPAS number`,
|
|
39
|
-
human: $t(`
|
|
39
|
+
human: $t(`4c6482ff-e6d9-4ea1-b11d-e12d697b4b7b`),
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -110,7 +110,7 @@ async function checkUitpasNumber(access_token: string, uitpasNumber: string) {
|
|
|
110
110
|
throw new SimpleError({
|
|
111
111
|
code: 'unsuccessful_and_unexpected_response_retrieving_pass_by_uitpas_number',
|
|
112
112
|
message: `Unsuccesful response without message when retrieving pass by UiTPAS number`,
|
|
113
|
-
human: $t(`
|
|
113
|
+
human: $t(`4c6482ff-e6d9-4ea1-b11d-e12d697b4b7b`),
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -21,7 +21,7 @@ function assertsIsStaticSocialTariffResponse(json: unknown): asserts json is Sta
|
|
|
21
21
|
throw new SimpleError({
|
|
22
22
|
code: 'invalid_response_getting_static_uitpas_social_tariff',
|
|
23
23
|
message: `Invalid response when getting static UiTPAS social tariff`,
|
|
24
|
-
human: $t(`
|
|
24
|
+
human: $t(`a56854af-464a-45a4-8c39-10a9264b6ce4`),
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -35,7 +35,7 @@ export async function getSocialTariffForEvent(access_token: string, basePrice: n
|
|
|
35
35
|
throw new SimpleError({
|
|
36
36
|
code: 'invalid_uitpas_event_url',
|
|
37
37
|
message: `Invalid UiTPAS event URL: ${uitpasEventUrl}`,
|
|
38
|
-
human: $t(`
|
|
38
|
+
human: $t(`85fb6e02-9b69-43cc-acf7-96a576461560`),
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
params.append('eventId', eventId);
|
|
@@ -61,7 +61,7 @@ export async function getSocialTariffForEvent(access_token: string, basePrice: n
|
|
|
61
61
|
throw new SimpleError({
|
|
62
62
|
code: 'unsuccessful_response_getting_static_uitpas_social_tariff',
|
|
63
63
|
message: `Unsuccessful response when getting static UiTPAS social tariff`,
|
|
64
|
-
human: $t(`
|
|
64
|
+
human: $t(`ed4e876c-6a40-49a7-ab65-2a4d5f31c13f`),
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
const json = await response.json().catch(() => {
|
|
@@ -80,7 +80,7 @@ export async function getSocialTariffForEvent(access_token: string, basePrice: n
|
|
|
80
80
|
throw new SimpleError({
|
|
81
81
|
code: 'no_social_tariff_available',
|
|
82
82
|
message: `No social tariff available for event ${eventId}`,
|
|
83
|
-
human: $t(`
|
|
83
|
+
human: $t(`ccd8e8b4-01a7-4e7c-8ae0-92d2a4c659eb`),
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
if (json.available.length > 1) {
|