@stamhoofd/backend 2.79.6 → 2.79.8
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/.nvmrc +1 -0
- package/index.ts +1 -1
- package/package.json +10 -10
- package/src/endpoints/global/members/GetMembersEndpoint.ts +2 -2
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +185 -1
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +36 -18
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +1 -36
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +9 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +9 -0
- package/src/helpers/AuthenticatedStructures.ts +1 -1
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20.12
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import backendEnv from '@stamhoofd/backend-env';
|
|
2
|
-
backendEnv.load();
|
|
2
|
+
backendEnv.load({ service: 'api' });
|
|
3
3
|
|
|
4
4
|
import { Column, Database, Migration } from '@simonbackx/simple-database';
|
|
5
5
|
import { CORSPreflightEndpoint, Router, RouterServer } from '@simonbackx/simple-endpoints';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.79.
|
|
3
|
+
"version": "2.79.8",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"@simonbackx/simple-encoding": "2.21.0",
|
|
39
39
|
"@simonbackx/simple-endpoints": "1.19.1",
|
|
40
40
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
41
|
-
"@stamhoofd/backend-i18n": "2.79.
|
|
42
|
-
"@stamhoofd/backend-middleware": "2.79.
|
|
43
|
-
"@stamhoofd/email": "2.79.
|
|
44
|
-
"@stamhoofd/models": "2.79.
|
|
45
|
-
"@stamhoofd/queues": "2.79.
|
|
46
|
-
"@stamhoofd/sql": "2.79.
|
|
47
|
-
"@stamhoofd/structures": "2.79.
|
|
48
|
-
"@stamhoofd/utility": "2.79.
|
|
41
|
+
"@stamhoofd/backend-i18n": "2.79.8",
|
|
42
|
+
"@stamhoofd/backend-middleware": "2.79.8",
|
|
43
|
+
"@stamhoofd/email": "2.79.8",
|
|
44
|
+
"@stamhoofd/models": "2.79.8",
|
|
45
|
+
"@stamhoofd/queues": "2.79.8",
|
|
46
|
+
"@stamhoofd/sql": "2.79.8",
|
|
47
|
+
"@stamhoofd/structures": "2.79.8",
|
|
48
|
+
"@stamhoofd/utility": "2.79.8",
|
|
49
49
|
"archiver": "^7.0.1",
|
|
50
50
|
"aws-sdk": "^2.885.0",
|
|
51
51
|
"axios": "1.6.8",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"publishConfig": {
|
|
66
66
|
"access": "public"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "06e4690b6413a21c3466d6ab76da3a883e1441c8"
|
|
69
69
|
}
|
|
@@ -2,7 +2,7 @@ import { Decoder } from '@simonbackx/simple-encoding';
|
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Member, Platform } from '@stamhoofd/models';
|
|
5
|
-
import { SQL,
|
|
5
|
+
import { SQL, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
|
|
6
6
|
import { CountFilteredRequest, Country, CountryCode, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
|
|
7
7
|
import { DataValidator } from '@stamhoofd/utility';
|
|
8
8
|
|
|
@@ -168,7 +168,7 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
// Is lidnummer?
|
|
171
|
-
if (!searchFilter && (q.search.match(/^[0-9]{4}-[0-9]{6}-[0-9]{1,2}$/) || q.search.match(/^[0-9]{10}$/))) {
|
|
171
|
+
if (!searchFilter && (q.search.match(/^[0-9]{4}-[0-9]{6}-[0-9]{1,2}$/) || q.search.match(/^[0-9]{9,10}$/))) {
|
|
172
172
|
searchFilter = {
|
|
173
173
|
memberNumber: {
|
|
174
174
|
$eq: q.search,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import { OrganizationFactory } from '@stamhoofd/models';
|
|
2
|
+
import { Organization, OrganizationFactory } from '@stamhoofd/models';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
4
|
|
|
5
5
|
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
@@ -9,6 +9,10 @@ describe('Endpoint.SearchOrganization', () => {
|
|
|
9
9
|
// Test endpoint
|
|
10
10
|
const endpoint = new SearchOrganizationEndpoint();
|
|
11
11
|
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await Organization.delete();
|
|
14
|
+
});
|
|
15
|
+
|
|
12
16
|
test('Search for a given organization using exact search', async () => {
|
|
13
17
|
const organization = await new OrganizationFactory({
|
|
14
18
|
name: (uuidv4()).replace(/-/g, ''),
|
|
@@ -51,4 +55,184 @@ describe('Endpoint.SearchOrganization', () => {
|
|
|
51
55
|
expect(response.status).toEqual(200);
|
|
52
56
|
expect(response.body.map(o => o.id).sort()).toEqual(organizations.map(o => o.id).sort());
|
|
53
57
|
});
|
|
58
|
+
|
|
59
|
+
test('Search organization by name using word should return best match first', async () => {
|
|
60
|
+
const name = 'WAT?';
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < 2; i++) {
|
|
63
|
+
await new OrganizationFactory({
|
|
64
|
+
name: 'Some other organization ' + (i + 1),
|
|
65
|
+
city: 'Waterloo',
|
|
66
|
+
}).create();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < 2; i++) {
|
|
70
|
+
await new OrganizationFactory({
|
|
71
|
+
name: 'Some other organization 2 ' + (i + 1),
|
|
72
|
+
city: 'Wats',
|
|
73
|
+
}).create();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < 2; i++) {
|
|
77
|
+
await new OrganizationFactory({
|
|
78
|
+
name: 'De Watten ' + (i + 1),
|
|
79
|
+
}).create();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// should appear first in results
|
|
83
|
+
const targetOrganization = await new OrganizationFactory({
|
|
84
|
+
name,
|
|
85
|
+
}).create();
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < 2; i++) {
|
|
88
|
+
await new OrganizationFactory({
|
|
89
|
+
name: 'De Watten 2 ' + (i + 1),
|
|
90
|
+
}).create();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const r = Request.buildJson('GET', '/v1/organizations/search');
|
|
94
|
+
r.query = {
|
|
95
|
+
query: name,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const response = await testServer.test(endpoint, r);
|
|
99
|
+
expect(response.body).toBeDefined();
|
|
100
|
+
expect(response.body).toHaveLength(9);
|
|
101
|
+
expect(response.body[0].id).toEqual(targetOrganization.id);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('Search on organization by name using sentence should return best match first', async () => {
|
|
105
|
+
const query = 'Spaghetti Vreters';
|
|
106
|
+
|
|
107
|
+
for (const name of ['De Spaghetti Eters', 'Vreters', 'Spaghetti', 'De Spaghetti', 'De Spaghetti Vretersschool']) {
|
|
108
|
+
await new OrganizationFactory({
|
|
109
|
+
name,
|
|
110
|
+
}).create();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// should appear first in results
|
|
114
|
+
const targetOrganization = await new OrganizationFactory({
|
|
115
|
+
name: 'De Spaghetti Vreters',
|
|
116
|
+
}).create();
|
|
117
|
+
|
|
118
|
+
const r = Request.buildJson('GET', '/v1/organizations/search');
|
|
119
|
+
r.query = {
|
|
120
|
+
query,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const response = await testServer.test(endpoint, r);
|
|
124
|
+
expect(response.body).toBeDefined();
|
|
125
|
+
expect(response.body).toHaveLength(6);
|
|
126
|
+
expect(response.body[0].id).toEqual(targetOrganization.id);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('Search on organization by name using word should return organization with searchindex that starts with query first if limit reached', async () => {
|
|
130
|
+
const query = 'Gent';
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < 10; i++) {
|
|
133
|
+
await new OrganizationFactory({
|
|
134
|
+
name: 'Some other Gent organization ' + (i + 1),
|
|
135
|
+
city: 'Gent',
|
|
136
|
+
}).create();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < 5; i++) {
|
|
140
|
+
await new OrganizationFactory({
|
|
141
|
+
name: 'De Gentenaars ' + (i + 1),
|
|
142
|
+
}).create();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// should appear first in results
|
|
146
|
+
const targetOrganization = await new OrganizationFactory({
|
|
147
|
+
name: 'Gent',
|
|
148
|
+
}).create();
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < 3; i++) {
|
|
151
|
+
await new OrganizationFactory({
|
|
152
|
+
name: 'De Gentenaars 2 ' + (i + 1),
|
|
153
|
+
}).create();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < 10; i++) {
|
|
157
|
+
await new OrganizationFactory({
|
|
158
|
+
name: 'Some other organization 2 ' + (i + 1),
|
|
159
|
+
city: 'Gent',
|
|
160
|
+
}).create();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const r = Request.buildJson('GET', '/v1/organizations/search');
|
|
164
|
+
r.query = {
|
|
165
|
+
query,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const response = await testServer.test(endpoint, r);
|
|
169
|
+
expect(response.body).toBeDefined();
|
|
170
|
+
expect(response.body).toHaveLength(15);
|
|
171
|
+
expect(response.body[0].name).toEqual(targetOrganization.name);
|
|
172
|
+
expect(response.body[0].id).toEqual(targetOrganization.id);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('Search on organization by name using sentence should return organization with name that starts with query first if limit reached', async () => {
|
|
176
|
+
const query = 'De Spaghetti Vreters';
|
|
177
|
+
|
|
178
|
+
// without the where like (if the limit is reached), 'Spaghetti Vreters Spaghetti Vreters' would come first ('De' is a stopword)
|
|
179
|
+
for (const name of ['De Spaghetti Eters', 'Vreters', 'Spaghetti', 'De Spaghetti', 'Spaghetti Vreters', 'Spaghetti Vreters Spaghetti Vreters', 'De Spaghetti Vretersschool', 'Spaghetti 2', 'Spaghetti 3', 'Spaghetti 4', 'Spaghetti 5', 'Spaghetti 6', 'Spaghetti 7', 'Spaghetti 8', 'Spaghetti 9', 'Spaghetti 10']) {
|
|
180
|
+
await new OrganizationFactory({
|
|
181
|
+
name,
|
|
182
|
+
}).create();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let i = 1;
|
|
186
|
+
for (const city of ['De Spaghetti Eters', 'Vreters', 'Spaghetti', 'De Spaghetti', 'De Spaghetti Vretersschool']) {
|
|
187
|
+
await new OrganizationFactory({
|
|
188
|
+
name: 'name ' + i,
|
|
189
|
+
city,
|
|
190
|
+
}).create();
|
|
191
|
+
i = i + 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// should appear first in results
|
|
195
|
+
const targetOrganization = await new OrganizationFactory({
|
|
196
|
+
name: 'De Spaghetti Vreters',
|
|
197
|
+
}).create();
|
|
198
|
+
|
|
199
|
+
const r = Request.buildJson('GET', '/v1/organizations/search');
|
|
200
|
+
r.query = {
|
|
201
|
+
query,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const response = await testServer.test(endpoint, r);
|
|
205
|
+
expect(response.body).toBeDefined();
|
|
206
|
+
expect(response.body).toHaveLength(15);
|
|
207
|
+
expect(response.body[0].name).toEqual(targetOrganization.name);
|
|
208
|
+
expect(response.body[0].id).toEqual(targetOrganization.id);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('Search on organization by name and city should return organization that matches city and name first', async () => {
|
|
212
|
+
const query = 'De Spaghetti Vreters Gent';
|
|
213
|
+
|
|
214
|
+
for (let i = 0; i < 5; i++) {
|
|
215
|
+
await new OrganizationFactory({
|
|
216
|
+
name: 'De Spaghetti Vreters ' + (i + 1),
|
|
217
|
+
city: 'Wetteren',
|
|
218
|
+
}).create();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// should appear first in results
|
|
222
|
+
const targetOrganization = await new OrganizationFactory({
|
|
223
|
+
name: 'De Spaghetti Vreters 16',
|
|
224
|
+
city: 'Gent',
|
|
225
|
+
}).create();
|
|
226
|
+
|
|
227
|
+
const r = Request.buildJson('GET', '/v1/organizations/search');
|
|
228
|
+
r.query = {
|
|
229
|
+
query,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const response = await testServer.test(endpoint, r);
|
|
233
|
+
expect(response.body).toBeDefined();
|
|
234
|
+
expect(response.body).toHaveLength(6);
|
|
235
|
+
expect(response.body[0].name).toEqual(targetOrganization.name);
|
|
236
|
+
expect(response.body[0].id).toEqual(targetOrganization.id);
|
|
237
|
+
});
|
|
54
238
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { Organization } from '@stamhoofd/models';
|
|
4
|
+
import { scalarToSQLExpression, SQL, SQLMatch, SQLWhere, SQLWhereLike } from '@stamhoofd/sql';
|
|
4
5
|
import { Organization as OrganizationStruct } from '@stamhoofd/structures';
|
|
5
6
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
6
7
|
|
|
@@ -32,28 +33,45 @@ export class SearchOrganizationEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
32
33
|
|
|
33
34
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
34
35
|
// Escape query
|
|
35
|
-
const query = request.query.query.replace(/([-+><()~*"@\s]+)/g, ' ').replace(/[^\w\d]+$/, '');
|
|
36
|
-
if (query.length
|
|
36
|
+
const query = request.query.query.replace(/([-+><()~*"@\s]+)/g, ' ').replace(/[^\w\d]+$/, '').trim();
|
|
37
|
+
if (query.length === 0) {
|
|
37
38
|
// Do not try searching...
|
|
38
39
|
return new Response([]);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
let matchValue: string;
|
|
43
|
+
|
|
44
|
+
if (query.includes(' ')) {
|
|
45
|
+
// give higher relevance if the searchindex includes the exact sentence
|
|
46
|
+
// give lower relevance if the last word is not a complete match
|
|
47
|
+
matchValue = `>("${query}") (${query}) <(${query}*)`;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// give higher relevance if the searchindex includes the exact word
|
|
51
|
+
matchValue = `>${query} ${query}*`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const limit = 15;
|
|
55
|
+
|
|
56
|
+
const whereMatch: SQLWhere = new SQLMatch(SQL.column(Organization.table, 'searchIndex'), scalarToSQLExpression(matchValue));
|
|
57
|
+
|
|
58
|
+
let organizations = await Organization.select()
|
|
59
|
+
.where(whereMatch)
|
|
60
|
+
.orderBy(whereMatch, 'DESC')
|
|
61
|
+
.limit(limit).fetch();
|
|
62
|
+
|
|
63
|
+
// if the limit is reached it is possible that organizations where the name starts with the query are missing -> fetch them and add them at the start
|
|
64
|
+
if (organizations.length === limit) {
|
|
65
|
+
const organizationsStartingWith = await Organization.select()
|
|
66
|
+
.where(new SQLWhereLike(SQL.column(Organization.table, 'name'), scalarToSQLExpression(`${query}%`)))
|
|
67
|
+
// order by relevance
|
|
68
|
+
.orderBy(whereMatch, 'DESC')
|
|
69
|
+
.limit(limit).fetch();
|
|
70
|
+
|
|
71
|
+
const organizationsStartingWithIds = new Set(organizationsStartingWith.map(o => o.id));
|
|
72
|
+
|
|
73
|
+
organizations = organizationsStartingWith.concat(organizations.filter(o => !organizationsStartingWithIds.has(o.id)));
|
|
74
|
+
}
|
|
57
75
|
|
|
58
76
|
return new Response(await Promise.all(organizations.map(o => AuthenticatedStructures.organization(o))));
|
|
59
77
|
}
|
|
@@ -457,41 +457,9 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
457
457
|
balanceItem.organizationId = organization.id;
|
|
458
458
|
|
|
459
459
|
// Who is responsible for payment?
|
|
460
|
-
let balanceItem2: BalanceItem | null = null;
|
|
461
460
|
if (registration.payingOrganizationId) {
|
|
462
|
-
//
|
|
463
|
-
// this is not yet associated with a payment but will be added to the outstanding balance of the member
|
|
464
|
-
|
|
461
|
+
// We no longer also charge the member. This has been removed, ref STA-288
|
|
465
462
|
balanceItem.payingOrganizationId = registration.payingOrganizationId;
|
|
466
|
-
|
|
467
|
-
balanceItem2 = new BalanceItem();
|
|
468
|
-
|
|
469
|
-
// NOTE: we don't connect the registrationId here
|
|
470
|
-
// because otherwise the total price and pricePaid for the registration would be incorrect
|
|
471
|
-
// balanceItem2.registrationId = registration.id;
|
|
472
|
-
|
|
473
|
-
balanceItem2.unitPrice = unitPrice;
|
|
474
|
-
balanceItem2.amount = amount ?? 1;
|
|
475
|
-
balanceItem2.description = description;
|
|
476
|
-
balanceItem2.relations = relations;
|
|
477
|
-
balanceItem2.type = type;
|
|
478
|
-
|
|
479
|
-
// Who needs to receive this money?
|
|
480
|
-
balanceItem2.organizationId = registration.payingOrganizationId;
|
|
481
|
-
|
|
482
|
-
// Who is responsible for payment?
|
|
483
|
-
balanceItem2.memberId = registration.memberId;
|
|
484
|
-
|
|
485
|
-
if (registration.trialUntil) {
|
|
486
|
-
balanceItem2.dueAt = registration.trialUntil;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// If the paying organization hasn't paid yet, this should be hidden and move to pending as soon as the paying organization has paid
|
|
490
|
-
balanceItem2.status = BalanceItemStatus.Hidden;
|
|
491
|
-
await balanceItem2.save();
|
|
492
|
-
|
|
493
|
-
// do not add to createdBalanceItems array because we don't want to add this to the payment if we create a payment
|
|
494
|
-
unrelatedCreatedBalanceItems.push(balanceItem2);
|
|
495
463
|
}
|
|
496
464
|
else {
|
|
497
465
|
balanceItem.memberId = registration.memberId;
|
|
@@ -501,9 +469,6 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
501
469
|
balanceItem.status = BalanceItemStatus.Hidden;
|
|
502
470
|
balanceItem.pricePaid = 0;
|
|
503
471
|
|
|
504
|
-
// Connect the 'pay back' balance item to this balance item. As soon as this balance item is paid, we'll mark the other one as pending so the outstanding balance for the member increases
|
|
505
|
-
balanceItem.dependingBalanceItemId = balanceItem2?.id ?? null;
|
|
506
|
-
|
|
507
472
|
if (registration.trialUntil) {
|
|
508
473
|
balanceItem.dueAt = registration.trialUntil;
|
|
509
474
|
}
|
|
@@ -352,6 +352,15 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
const maximumStart = 1000 * 60 * 60 * 24 * 31 * 2; // 2 months in advance
|
|
356
|
+
if (period.startDate > new Date(Date.now() + maximumStart)) {
|
|
357
|
+
throw new SimpleError({
|
|
358
|
+
code: 'invalid_field',
|
|
359
|
+
message: 'Het werkjaar die je wilt instellen is nog niet gestart',
|
|
360
|
+
field: 'period',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
355
364
|
organization.periodId = period.id;
|
|
356
365
|
shouldUpdateSetupSteps = true;
|
|
357
366
|
}
|
|
@@ -280,6 +280,15 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
280
280
|
});
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
const maximumStart = 1000 * 60 * 60 * 24 * 31 * 2; // 2 months in advance
|
|
284
|
+
if (period.startDate > new Date(Date.now() + maximumStart)) {
|
|
285
|
+
throw new SimpleError({
|
|
286
|
+
code: 'invalid_field',
|
|
287
|
+
message: 'Het werkjaar die je wilt instellen is nog niet gestart',
|
|
288
|
+
field: 'period',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
283
292
|
const organizationPeriod = new OrganizationRegistrationPeriod();
|
|
284
293
|
organizationPeriod.id = struct.id;
|
|
285
294
|
organizationPeriod.organizationId = organization.id;
|
|
@@ -391,7 +391,7 @@ export class AuthenticatedStructures {
|
|
|
391
391
|
const organizations = new Map<string, Organization>();
|
|
392
392
|
|
|
393
393
|
const registrationIds = Formatter.uniqueArray(members.flatMap(m => m.registrations.map(r => r.id)));
|
|
394
|
-
const balances = await CachedBalance.getForObjects(registrationIds,
|
|
394
|
+
const balances = await CachedBalance.getForObjects(registrationIds, null);
|
|
395
395
|
|
|
396
396
|
if (includeUser) {
|
|
397
397
|
for (const organizationId of includeUser.permissions?.organizationPermissions.keys() ?? []) {
|