@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.
@@ -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.22.0",
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.22.0",
40
- "@stamhoofd/backend-middleware": "2.22.0",
41
- "@stamhoofd/email": "2.22.0",
42
- "@stamhoofd/models": "2.22.0",
43
- "@stamhoofd/queues": "2.22.0",
44
- "@stamhoofd/sql": "2.22.0",
45
- "@stamhoofd/structures": "2.22.0",
46
- "@stamhoofd/utility": "2.22.0",
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": "b3590dcb37d7f65be2663b11a6f025cf5f36a278"
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 CreateTokenEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
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.$t("shared.domains.marketing")+"/ledenadministratie\n\n" : '')
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(shouldUpdateSetupSteps) {
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, GroupPrivateSettings, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version, GroupType } from "@stamhoofd/structures";
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
- const period = await RegistrationPeriod.getByID(put.period.id);
49
+ periods.push(await PatchOrganizationRegistrationPeriodsEndpoint.createOrganizationPeriod(organization, put));
50
+ }
50
51
 
51
- if (!period) {
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 organizationPeriod = new OrganizationRegistrationPeriod();
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
- for (const struct of put.groups) {
67
- await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(struct, organization.id, organizationPeriod.periodId)
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
- for (const patch of request.body.getPatches()) {
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
- blob.responsibilities = responsibilities.filter(r => r.memberId == blob.id).map(r => r.getStructure())
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: await Promise.all([...organizations.values()].filter(o => o.active).map(o => this.organization(o)))
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.getStructure()) as any
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.getStructure()) as any
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 records = await MemberResponsibilityRecord.select()
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 && max === 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
- if (min !== null && totalRecordsWithThisResponsibility < min) {
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
  }