@stamhoofd/backend 2.22.0 → 2.24.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/.env.template.json +5 -0
- package/package.json +10 -10
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/DeleteUserEndpoint.ts +58 -0
- package/src/endpoints/auth/SignupEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +28 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +8 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +4 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +60 -23
- package/src/helpers/AuthenticatedStructures.ts +31 -2
- package/src/helpers/MemberUserSyncer.ts +2 -2
- package/src/helpers/PeriodHelper.ts +70 -0
- package/src/helpers/SetupStepsUpdater.ts +50 -8
package/.env.template.json
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
"BE": "www.be.stamhoofd",
|
|
13
13
|
"NL": "www.nl.stamhoofd"
|
|
14
14
|
},
|
|
15
|
+
"documentation": {
|
|
16
|
+
"": "www.be.stamhoofd/docs",
|
|
17
|
+
"BE": "www.be.stamhoofd/docs",
|
|
18
|
+
"NL": "www.nl.stamhoofd/docs"
|
|
19
|
+
},
|
|
15
20
|
"webshop": {
|
|
16
21
|
"": "shop.be.stamhoofd",
|
|
17
22
|
"BE": "shop.be.stamhoofd",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"@simonbackx/simple-encoding": "2.15.0",
|
|
37
37
|
"@simonbackx/simple-endpoints": "1.14.0",
|
|
38
38
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
39
|
-
"@stamhoofd/backend-i18n": "2.
|
|
40
|
-
"@stamhoofd/backend-middleware": "2.
|
|
41
|
-
"@stamhoofd/email": "2.
|
|
42
|
-
"@stamhoofd/models": "2.
|
|
43
|
-
"@stamhoofd/queues": "2.
|
|
44
|
-
"@stamhoofd/sql": "2.
|
|
45
|
-
"@stamhoofd/structures": "2.
|
|
46
|
-
"@stamhoofd/utility": "2.
|
|
39
|
+
"@stamhoofd/backend-i18n": "2.24.0",
|
|
40
|
+
"@stamhoofd/backend-middleware": "2.24.0",
|
|
41
|
+
"@stamhoofd/email": "2.24.0",
|
|
42
|
+
"@stamhoofd/models": "2.24.0",
|
|
43
|
+
"@stamhoofd/queues": "2.24.0",
|
|
44
|
+
"@stamhoofd/sql": "2.24.0",
|
|
45
|
+
"@stamhoofd/structures": "2.24.0",
|
|
46
|
+
"@stamhoofd/utility": "2.24.0",
|
|
47
47
|
"archiver": "^7.0.1",
|
|
48
48
|
"aws-sdk": "^2.885.0",
|
|
49
49
|
"axios": "1.6.8",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"postmark": "4.0.2",
|
|
61
61
|
"stripe": "^16.6.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "bad7d2adfa412af0b0de101274e46ed0b539ae38"
|
|
64
64
|
}
|
|
@@ -7,7 +7,7 @@ type Query = undefined;
|
|
|
7
7
|
type Body = undefined;
|
|
8
8
|
type ResponseBody = undefined;
|
|
9
9
|
|
|
10
|
-
export class
|
|
10
|
+
export class DeleteTokenEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
11
11
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
12
12
|
if (request.method != "DELETE") {
|
|
13
13
|
return [false];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
+
|
|
3
|
+
import { getDefaultEmailFrom, sendEmailTemplate } from '@stamhoofd/models';
|
|
4
|
+
import { EmailTemplateType, Recipient } from '@stamhoofd/structures';
|
|
5
|
+
import { Context } from '../../helpers/Context';
|
|
6
|
+
|
|
7
|
+
type Params = Record<string, never>;
|
|
8
|
+
type Query = undefined;
|
|
9
|
+
type Body = undefined;
|
|
10
|
+
type ResponseBody = undefined;
|
|
11
|
+
|
|
12
|
+
export class DeleteUserEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
14
|
+
if (request.method != "DELETE") {
|
|
15
|
+
return [false];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const params = Endpoint.parseParameters(request.url, "/user", {});
|
|
19
|
+
|
|
20
|
+
if (params) {
|
|
21
|
+
return [true, params as Params];
|
|
22
|
+
}
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
27
|
+
const organization = await Context.setOptionalOrganizationScope()
|
|
28
|
+
const {user, token} = await Context.authenticate({allowWithoutAccount: true})
|
|
29
|
+
|
|
30
|
+
// Send an e-mail to inform everyone about this action
|
|
31
|
+
|
|
32
|
+
// Delete the account
|
|
33
|
+
|
|
34
|
+
const bcc = (await getDefaultEmailFrom(null, {
|
|
35
|
+
template: {}
|
|
36
|
+
}))
|
|
37
|
+
await sendEmailTemplate(organization, {
|
|
38
|
+
recipients: [
|
|
39
|
+
Recipient.create({
|
|
40
|
+
email: user.email
|
|
41
|
+
})
|
|
42
|
+
],
|
|
43
|
+
singleBcc: bcc.replyTo || bcc.from,
|
|
44
|
+
template: {
|
|
45
|
+
type: EmailTemplateType.DeleteAccountConfirmation,
|
|
46
|
+
},
|
|
47
|
+
type: 'transactional'
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Soft delete until processed manually
|
|
51
|
+
user.verified = false;
|
|
52
|
+
user.password = null;
|
|
53
|
+
await user.save()
|
|
54
|
+
await token.delete()
|
|
55
|
+
|
|
56
|
+
return new Response(undefined)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -64,7 +64,7 @@ export class SignupEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
|
|
|
64
64
|
replyTo: undefined
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const footer = (!user.permissions && organization ? "\n\n—\n\nOnze ledenadministratie werkt via het Stamhoofd platform, op maat van verenigingen. Probeer het ook via https://"+request.i18n
|
|
67
|
+
const footer = (!user.permissions && organization ? "\n\n—\n\nOnze ledenadministratie werkt via het Stamhoofd platform, op maat van verenigingen. Probeer het ook via https://"+request.i18n.localizedDomains.marketing()+"/ledenadministratie\n\n" : '')
|
|
68
68
|
|
|
69
69
|
const name = organization ? organization.name : 'Stamhoofd'
|
|
70
70
|
// Send email
|
|
@@ -3,7 +3,7 @@ import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, Patch
|
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
4
4
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
5
5
|
import { BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Platform, Registration, User } from '@stamhoofd/models';
|
|
6
|
-
import { MemberWithRegistrationsBlob, MembersBlob, PermissionLevel } from "@stamhoofd/structures";
|
|
6
|
+
import { GroupType, MemberWithRegistrationsBlob, MembersBlob, PermissionLevel } from "@stamhoofd/structures";
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -267,6 +267,33 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
267
267
|
})
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
const hasRegistration = member.registrations.some(registration => {
|
|
271
|
+
if (platformResponsibility) {
|
|
272
|
+
if (registration.group.defaultAgeGroupId === null) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (org) {
|
|
278
|
+
if (registration.periodId !== org.periodId) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
if (registration.periodId !== platform.periodId) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return registration.deactivatedAt === null && registration.registeredAt !== null && registration.group.type === GroupType.Membership
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
if (!hasRegistration) {
|
|
290
|
+
throw new SimpleError({
|
|
291
|
+
code: "invalid_field",
|
|
292
|
+
message: "Invalid organization",
|
|
293
|
+
human: "Je kan een functie enkel toekennen aan leden die zijn ingeschreven in het huidige werkjaar",
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
270
297
|
const model = new MemberResponsibilityRecord()
|
|
271
298
|
model.memberId = member.id
|
|
272
299
|
model.responsibilityId = responsibility.id
|
|
@@ -6,6 +6,7 @@ import { MemberResponsibility, PlatformConfig, PlatformPremiseType, Platform as
|
|
|
6
6
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
7
7
|
import { Context } from "../../../helpers/Context";
|
|
8
8
|
import { SetupStepUpdater } from "../../../helpers/SetupStepsUpdater";
|
|
9
|
+
import { PeriodHelper } from "../../../helpers/PeriodHelper";
|
|
9
10
|
|
|
10
11
|
type Params = Record<string, never>;
|
|
11
12
|
type Query = undefined;
|
|
@@ -73,6 +74,7 @@ export class PatchPlatformEndpoint extends Endpoint<
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
let shouldUpdateSetupSteps = false;
|
|
77
|
+
let shouldMoveToPeriod: RegistrationPeriod | null = null;
|
|
76
78
|
|
|
77
79
|
if (request.body.config) {
|
|
78
80
|
if (!Context.auth.hasPlatformFullAccess()) {
|
|
@@ -113,6 +115,8 @@ export class PatchPlatformEndpoint extends Endpoint<
|
|
|
113
115
|
});
|
|
114
116
|
}
|
|
115
117
|
platform.periodId = period.id;
|
|
118
|
+
shouldUpdateSetupSteps = true;
|
|
119
|
+
shouldMoveToPeriod = period;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
if (request.body.membershipOrganizationId !== undefined) {
|
|
@@ -155,7 +159,10 @@ export class PatchPlatformEndpoint extends Endpoint<
|
|
|
155
159
|
|
|
156
160
|
await platform.save();
|
|
157
161
|
|
|
158
|
-
if(
|
|
162
|
+
if (shouldMoveToPeriod) {
|
|
163
|
+
PeriodHelper.moveAllOrganizationsToPeriod(shouldMoveToPeriod).catch(console.error)
|
|
164
|
+
} else if(shouldUpdateSetupSteps) {
|
|
165
|
+
// Do not call this right away when moving to a period, because this needs to happen AFTER moving to the period
|
|
159
166
|
SetupStepUpdater.updateSetupStepsForAllOrganizationsInCurrentPeriod().catch(console.error);
|
|
160
167
|
}
|
|
161
168
|
|
|
@@ -101,6 +101,10 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
101
101
|
|
|
102
102
|
if (request.body.privateMeta && request.body.privateMeta.isPatch()) {
|
|
103
103
|
organization.privateMeta.emails = request.body.privateMeta.emails.applyTo(organization.privateMeta.emails)
|
|
104
|
+
if(request.body.privateMeta.emails) {
|
|
105
|
+
shouldUpdateSetupSteps = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
104
108
|
organization.privateMeta.premises = patchObject(organization.privateMeta.premises, request.body.privateMeta.premises);
|
|
105
109
|
if(request.body.privateMeta.premises) {
|
|
106
110
|
shouldUpdateSetupSteps = true;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
-
import { Group as GroupStruct,
|
|
2
|
+
import { GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from "@stamhoofd/structures";
|
|
3
3
|
|
|
4
4
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from "@simonbackx/simple-encoding";
|
|
5
|
-
import { Context } from "../../../../helpers/Context";
|
|
6
|
-
import { Group, Member, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
7
5
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
6
|
+
import { Group, Member, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
8
7
|
import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
|
|
8
|
+
import { Context } from "../../../../helpers/Context";
|
|
9
9
|
|
|
10
10
|
type Params = Record<string, never>;
|
|
11
11
|
type Query = undefined;
|
|
@@ -46,9 +46,12 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
46
46
|
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
47
47
|
throw Context.auth.error()
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
periods.push(await PatchOrganizationRegistrationPeriodsEndpoint.createOrganizationPeriod(organization, put));
|
|
50
|
+
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
for (const patch of request.body.getPatches()) {
|
|
53
|
+
const organizationPeriod = await OrganizationRegistrationPeriod.getByID(patch.id);
|
|
54
|
+
if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
|
|
52
55
|
throw new SimpleError({
|
|
53
56
|
code: "not_found",
|
|
54
57
|
message: "Period not found",
|
|
@@ -56,38 +59,33 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
56
59
|
})
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
const
|
|
60
|
-
organizationPeriod.id = put.id;
|
|
61
|
-
organizationPeriod.organizationId = organization.id;
|
|
62
|
-
organizationPeriod.periodId = put.period.id;
|
|
63
|
-
organizationPeriod.settings = put.settings;
|
|
64
|
-
await organizationPeriod.save();
|
|
62
|
+
const period = await RegistrationPeriod.getByID(organizationPeriod.periodId);
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
if (!period) {
|
|
65
|
+
throw new SimpleError({
|
|
66
|
+
code: "not_found",
|
|
67
|
+
message: "Period not found",
|
|
68
|
+
statusCode: 404
|
|
69
|
+
})
|
|
68
70
|
}
|
|
69
|
-
const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
|
|
70
|
-
|
|
71
|
-
// Delete unreachable categories first
|
|
72
|
-
await organizationPeriod.cleanCategories(groups);
|
|
73
|
-
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
74
|
-
periods.push(organizationPeriod);
|
|
75
|
-
}
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
const organizationPeriod = await OrganizationRegistrationPeriod.getByID(patch.id);
|
|
79
|
-
if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
|
|
72
|
+
if (period.locked) {
|
|
80
73
|
throw new SimpleError({
|
|
81
74
|
code: "not_found",
|
|
82
75
|
message: "Period not found",
|
|
76
|
+
human: 'Je kan geen wijzigingen meer aanbrengen in ' + period.getStructure().name + ' omdat deze is afgesloten',
|
|
83
77
|
statusCode: 404
|
|
84
78
|
})
|
|
85
79
|
}
|
|
80
|
+
|
|
86
81
|
let deleteUnreachable = false
|
|
87
82
|
const allowedIds: string[] = []
|
|
88
83
|
|
|
89
84
|
if (await Context.auth.hasFullAccess(organization.id)) {
|
|
90
85
|
if (patch.settings) {
|
|
86
|
+
if(patch.settings.categories) {
|
|
87
|
+
deleteUnreachable = true;
|
|
88
|
+
}
|
|
91
89
|
organizationPeriod.settings.patchOrPut(patch.settings);
|
|
92
90
|
}
|
|
93
91
|
} else {
|
|
@@ -174,6 +172,36 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
174
172
|
})
|
|
175
173
|
}
|
|
176
174
|
|
|
175
|
+
static async createOrganizationPeriod(organization: Organization, struct: OrganizationRegistrationPeriodStruct) {
|
|
176
|
+
const period = await RegistrationPeriod.getByID(struct.period.id);
|
|
177
|
+
|
|
178
|
+
if (!period || period.locked) {
|
|
179
|
+
throw new SimpleError({
|
|
180
|
+
code: "not_found",
|
|
181
|
+
message: "Period not found",
|
|
182
|
+
statusCode: 404
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const organizationPeriod = new OrganizationRegistrationPeriod();
|
|
187
|
+
organizationPeriod.id = struct.id;
|
|
188
|
+
organizationPeriod.organizationId = organization.id;
|
|
189
|
+
organizationPeriod.periodId = struct.period.id;
|
|
190
|
+
organizationPeriod.settings = struct.settings;
|
|
191
|
+
await organizationPeriod.save();
|
|
192
|
+
|
|
193
|
+
for (const s of struct.groups) {
|
|
194
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(s, organization.id, organizationPeriod.periodId)
|
|
195
|
+
}
|
|
196
|
+
const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
|
|
197
|
+
|
|
198
|
+
// Delete unreachable categories first
|
|
199
|
+
await organizationPeriod.cleanCategories(groups);
|
|
200
|
+
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
201
|
+
|
|
202
|
+
return organizationPeriod
|
|
203
|
+
}
|
|
204
|
+
|
|
177
205
|
static async deleteGroup(id: string) {
|
|
178
206
|
const model = await Group.getByID(id)
|
|
179
207
|
if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
@@ -263,6 +291,15 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
263
291
|
})
|
|
264
292
|
}
|
|
265
293
|
|
|
294
|
+
if (existing.periodId !== model.periodId) {
|
|
295
|
+
throw new SimpleError({
|
|
296
|
+
code: 'invalid_field',
|
|
297
|
+
field: 'waitingList',
|
|
298
|
+
message: 'Waiting list group is already used in another period',
|
|
299
|
+
human: 'Een wachtlijst kan momenteel niet gedeeld worden tussen verschillende werkjaren'
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
266
303
|
model.waitingListId = existing.id
|
|
267
304
|
} else {
|
|
268
305
|
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
@@ -262,14 +262,43 @@ export class AuthenticatedStructures {
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
const organizationStructs = await Promise.all([...organizations.values()].filter(o => o.active).map(o => this.organization(o)))
|
|
266
|
+
|
|
267
|
+
// Load missing groups
|
|
268
|
+
const allGroups = new Map<string, GroupStruct>()
|
|
269
|
+
for (const organization of organizationStructs) {
|
|
270
|
+
for (const group of organization.period.groups) {
|
|
271
|
+
allGroups.set(group.id, group)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
265
275
|
for (const blob of memberBlobs) {
|
|
266
|
-
|
|
276
|
+
for (const registration of blob.registrations) {
|
|
277
|
+
if (registration.group) {
|
|
278
|
+
allGroups.set(registration.group.id, registration.group)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const groupIds = Formatter.uniqueArray(responsibilities.map(r => r.groupId).filter(id => id !== null)).filter(id => !allGroups.has(id))
|
|
284
|
+
const groups = groupIds.length > 0 ? await Group.getByIDs(...groupIds) : []
|
|
285
|
+
const groupStructs = await this.groups(groups)
|
|
286
|
+
|
|
287
|
+
for (const group of groupStructs) {
|
|
288
|
+
allGroups.set(group.id, group)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
for (const blob of memberBlobs) {
|
|
292
|
+
blob.responsibilities = responsibilities.filter(r => r.memberId == blob.id).map(r => {
|
|
293
|
+
const group = allGroups.get(r.groupId ?? '') ?? null
|
|
294
|
+
return r.getStructure(group)
|
|
295
|
+
})
|
|
267
296
|
blob.platformMemberships = platformMemberships.filter(r => r.memberId == blob.id).map(r => MemberPlatformMembershipStruct.create(r))
|
|
268
297
|
}
|
|
269
298
|
|
|
270
299
|
return MembersBlob.create({
|
|
271
300
|
members: memberBlobs,
|
|
272
|
-
organizations:
|
|
301
|
+
organizations: organizationStructs
|
|
273
302
|
})
|
|
274
303
|
}
|
|
275
304
|
|
|
@@ -116,14 +116,14 @@ export class MemberUserSyncerStatic {
|
|
|
116
116
|
if (organizationId === null) {
|
|
117
117
|
const patch = user.permissions.convertPlatformPatch(
|
|
118
118
|
Permissions.patch({
|
|
119
|
-
responsibilities: (responsibilitiesByOrganization.get(organizationId) ?? []).map(r => r.
|
|
119
|
+
responsibilities: (responsibilitiesByOrganization.get(organizationId) ?? []).map(r => r.getBaseStructure()) as any
|
|
120
120
|
})
|
|
121
121
|
)
|
|
122
122
|
user.permissions = user.permissions.patch(patch)
|
|
123
123
|
} else {
|
|
124
124
|
const patch = user.permissions.convertPatch(
|
|
125
125
|
Permissions.patch({
|
|
126
|
-
responsibilities: (responsibilitiesByOrganization.get(organizationId) ?? []).map(r => r.
|
|
126
|
+
responsibilities: (responsibilitiesByOrganization.get(organizationId) ?? []).map(r => r.getBaseStructure()) as any
|
|
127
127
|
}),
|
|
128
128
|
organizationId
|
|
129
129
|
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
|
|
2
|
+
import { Organization, OrganizationRegistrationPeriod, RegistrationPeriod } from "@stamhoofd/models";
|
|
3
|
+
import { AuthenticatedStructures } from "./AuthenticatedStructures";
|
|
4
|
+
import { PatchOrganizationRegistrationPeriodsEndpoint } from "../endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint";
|
|
5
|
+
import { QueueHandler } from "@stamhoofd/queues";
|
|
6
|
+
import { SetupStepUpdater } from "./SetupStepsUpdater";
|
|
7
|
+
|
|
8
|
+
export class PeriodHelper {
|
|
9
|
+
static async moveOrganizationToPeriod(organization: Organization, period: RegistrationPeriod) {
|
|
10
|
+
console.log('moveOrganizationToPeriod', organization.id, period.id)
|
|
11
|
+
|
|
12
|
+
await this.createOrganizationPeriodForPeriod(organization, period)
|
|
13
|
+
organization.periodId = period.id
|
|
14
|
+
await organization.save()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static async createOrganizationPeriodForPeriod(organization: Organization, period: RegistrationPeriod) {
|
|
18
|
+
const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: period.id, organizationId: organization.id }, {limit: 1})
|
|
19
|
+
|
|
20
|
+
if (oPeriods.length) {
|
|
21
|
+
// Already created
|
|
22
|
+
return oPeriods[0]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const currentPeriod = await organization.getPeriod()
|
|
26
|
+
if (currentPeriod.periodId === period.id) {
|
|
27
|
+
return currentPeriod
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const struct = await AuthenticatedStructures.organizationRegistrationPeriod(currentPeriod)
|
|
31
|
+
|
|
32
|
+
const duplicate = struct.duplicate(period.getStructure())
|
|
33
|
+
return await PatchOrganizationRegistrationPeriodsEndpoint.createOrganizationPeriod(organization, duplicate)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static async moveAllOrganizationsToPeriod(period: RegistrationPeriod) {
|
|
37
|
+
const tag = "moveAllOrganizationsToPeriod";
|
|
38
|
+
const batchSize = 10;
|
|
39
|
+
QueueHandler.cancel(tag);
|
|
40
|
+
|
|
41
|
+
await QueueHandler.schedule(tag, async () => {
|
|
42
|
+
let lastId = "";
|
|
43
|
+
|
|
44
|
+
while (true) {
|
|
45
|
+
const organizations = await Organization.where(
|
|
46
|
+
{
|
|
47
|
+
id: { sign: ">", value: lastId },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
limit: batchSize,
|
|
51
|
+
sort: ["id"]
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
for (const organization of organizations) {
|
|
56
|
+
await this.moveOrganizationToPeriod(organization, period);
|
|
57
|
+
lastId = organization.id;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (organizations.length < batchSize) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// When done: update setup steps
|
|
68
|
+
await SetupStepUpdater.updateSetupStepsForAllOrganizationsInCurrentPeriod()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Group,
|
|
3
|
+
Member,
|
|
3
4
|
MemberResponsibilityRecord,
|
|
4
5
|
Organization,
|
|
5
6
|
OrganizationRegistrationPeriod,
|
|
@@ -8,11 +9,13 @@ import {
|
|
|
8
9
|
import { QueueHandler } from "@stamhoofd/queues";
|
|
9
10
|
import { SQL, SQLWhereSign } from "@stamhoofd/sql";
|
|
10
11
|
import {
|
|
12
|
+
GroupType,
|
|
11
13
|
MemberResponsibility,
|
|
12
14
|
Platform as PlatformStruct,
|
|
13
15
|
SetupStepType,
|
|
14
16
|
SetupSteps
|
|
15
17
|
} from "@stamhoofd/structures";
|
|
18
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
16
19
|
|
|
17
20
|
type SetupStepOperation = (setupSteps: SetupSteps, organization: Organization, platform: PlatformStruct) => void | Promise<void>;
|
|
18
21
|
|
|
@@ -25,6 +28,8 @@ export class SetupStepUpdater {
|
|
|
25
28
|
[SetupStepType.Companies]: this.updateStepCompanies,
|
|
26
29
|
[SetupStepType.Groups]: this.updateStepGroups,
|
|
27
30
|
[SetupStepType.Premises]: this.updateStepPremises,
|
|
31
|
+
[SetupStepType.Emails]: this.updateStepEmails,
|
|
32
|
+
[SetupStepType.Payment]: this.updateStepPayment
|
|
28
33
|
};
|
|
29
34
|
|
|
30
35
|
static async updateSetupStepsForAllOrganizationsInCurrentPeriod({
|
|
@@ -242,12 +247,21 @@ export class SetupStepUpdater {
|
|
|
242
247
|
|
|
243
248
|
const responsibilityIds = organizationBasedResponsibilitiesWithRestriction.map(r => r.id);
|
|
244
249
|
|
|
245
|
-
const
|
|
250
|
+
const allRecords = await MemberResponsibilityRecord.select()
|
|
246
251
|
.where('responsibilityId', responsibilityIds)
|
|
247
252
|
.where('organizationId', organization.id)
|
|
248
253
|
.where(SQL.where('endDate', SQLWhereSign.Greater, now).or('endDate', null))
|
|
249
254
|
.fetch();
|
|
250
255
|
|
|
256
|
+
// Remove invalid responsibilities: members that are not registered in the current period
|
|
257
|
+
const memberIds = Formatter.uniqueArray(allRecords.map(r => r.memberId));
|
|
258
|
+
const members = await Member.getBlobByIds(...memberIds);
|
|
259
|
+
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));
|
|
260
|
+
|
|
261
|
+
const validMembersIds = validMembers.map(m => m.id);
|
|
262
|
+
|
|
263
|
+
const records = allRecords.filter(r => validMembersIds.includes(r.memberId));
|
|
264
|
+
|
|
251
265
|
let totalSteps = 0;
|
|
252
266
|
let finishedSteps = 0;
|
|
253
267
|
|
|
@@ -277,11 +291,11 @@ export class SetupStepUpdater {
|
|
|
277
291
|
for(const {responsibility, group} of flatResponsibilities) {
|
|
278
292
|
const { minimumMembers: min, maximumMembers: max } = responsibility;
|
|
279
293
|
|
|
280
|
-
if (min === null
|
|
294
|
+
if (min === null) {
|
|
281
295
|
continue;
|
|
282
296
|
}
|
|
283
297
|
|
|
284
|
-
totalSteps
|
|
298
|
+
totalSteps += min;
|
|
285
299
|
|
|
286
300
|
const responsibilityId = responsibility.id;
|
|
287
301
|
let totalRecordsWithThisResponsibility = 0;
|
|
@@ -301,14 +315,11 @@ export class SetupStepUpdater {
|
|
|
301
315
|
}
|
|
302
316
|
|
|
303
317
|
if (max !== null && totalRecordsWithThisResponsibility > max) {
|
|
318
|
+
// Not added
|
|
304
319
|
continue;
|
|
305
320
|
}
|
|
306
321
|
|
|
307
|
-
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
finishedSteps++;
|
|
322
|
+
finishedSteps += Math.min(min, totalRecordsWithThisResponsibility);
|
|
312
323
|
}
|
|
313
324
|
|
|
314
325
|
setupSteps.update(SetupStepType.Responsibilities, {
|
|
@@ -316,4 +327,35 @@ export class SetupStepUpdater {
|
|
|
316
327
|
finishedSteps,
|
|
317
328
|
});
|
|
318
329
|
}
|
|
330
|
+
|
|
331
|
+
private static updateStepEmails(setupSteps: SetupSteps,
|
|
332
|
+
organization: Organization,
|
|
333
|
+
_platform: PlatformStruct) {
|
|
334
|
+
|
|
335
|
+
const totalSteps = 1;
|
|
336
|
+
let finishedSteps = 0;
|
|
337
|
+
|
|
338
|
+
const emails = organization.privateMeta.emails;
|
|
339
|
+
|
|
340
|
+
// organization should have 1 default email
|
|
341
|
+
if(emails.some(e => e.default)) {
|
|
342
|
+
finishedSteps = 1;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
setupSteps.update(SetupStepType.Emails, {
|
|
346
|
+
totalSteps,
|
|
347
|
+
finishedSteps,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
setupSteps.markReviewed(SetupStepType.Emails, {userId: 'backend', userName: 'backend'});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private static updateStepPayment(setupSteps: SetupSteps,
|
|
354
|
+
_organization: Organization,
|
|
355
|
+
_platform: PlatformStruct) {
|
|
356
|
+
setupSteps.update(SetupStepType.Payment, {
|
|
357
|
+
totalSteps: 0,
|
|
358
|
+
finishedSteps: 0,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
319
361
|
}
|