@stamhoofd/backend 2.1.2 → 2.2.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.json +2 -2
- package/package.json +2 -2
- package/src/endpoints/admin/invoices/GetInvoicesCountEndpoint.ts +47 -0
- package/src/endpoints/admin/invoices/GetInvoicesEndpoint.ts +185 -0
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +88 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +4 -2
- package/src/endpoints/global/members/GetMembersEndpoint.ts +53 -2
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +145 -5
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +13 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +9 -2
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +4 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +32 -2
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +68 -5
- package/src/helpers/AuthenticatedStructures.ts +5 -3
package/.env.json
CHANGED
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"demoApi": "api.stamhoofd",
|
|
24
24
|
"rendererApi": "renderer.stamhoofd"
|
|
25
25
|
},
|
|
26
|
-
"translationNamespace": "
|
|
26
|
+
"translationNamespace": "digit",
|
|
27
27
|
"userMode": "organization",
|
|
28
28
|
|
|
29
29
|
"PORT": 9091,
|
|
30
30
|
"DB_HOST": "127.0.0.1",
|
|
31
31
|
"DB_USER": "root",
|
|
32
32
|
"DB_PASS": "root",
|
|
33
|
-
"DB_DATABASE": "
|
|
33
|
+
"DB_DATABASE": "stamhoofd",
|
|
34
34
|
|
|
35
35
|
"SMTP_HOST": "0.0.0.0",
|
|
36
36
|
"SMTP_USERNAME": "username",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"postmark": "4.0.2",
|
|
51
51
|
"stripe": "^11.5.0"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "d67b2ac7c63880195902d29df682a2f2a709b82b"
|
|
54
54
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
|
|
4
|
+
|
|
5
|
+
import { Context } from '../../../helpers/Context';
|
|
6
|
+
import { GetInvoicesEndpoint } from './GetInvoicesEndpoint';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = CountFilteredRequest;
|
|
10
|
+
type Body = undefined;
|
|
11
|
+
type ResponseBody = CountResponse;
|
|
12
|
+
|
|
13
|
+
export class GetInvoicesCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>
|
|
15
|
+
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method != "GET") {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, "/admin/invoices/count", {});
|
|
22
|
+
|
|
23
|
+
if (params) {
|
|
24
|
+
return [true, params as Params];
|
|
25
|
+
}
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
30
|
+
await Context.authenticate()
|
|
31
|
+
|
|
32
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
33
|
+
throw Context.auth.error()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const query = GetInvoicesEndpoint.buildQuery(request.query)
|
|
37
|
+
|
|
38
|
+
const count = await query
|
|
39
|
+
.count();
|
|
40
|
+
|
|
41
|
+
return new Response(
|
|
42
|
+
CountResponse.create({
|
|
43
|
+
count
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { Organization, Payment, STInvoice } from '@stamhoofd/models';
|
|
6
|
+
import { SQL, SQLFilterDefinitions, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions, baseSQLFilterCompilers, compileToSQLFilter, compileToSQLSorter, createSQLExpressionFilterCompiler } from "@stamhoofd/sql";
|
|
7
|
+
import { CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, Payment as PaymentStruct, STInvoicePrivate, StamhoofdFilter, getSortFilter } from '@stamhoofd/structures';
|
|
8
|
+
|
|
9
|
+
import { Context } from '../../../helpers/Context';
|
|
10
|
+
|
|
11
|
+
type Params = Record<string, never>;
|
|
12
|
+
type Query = LimitedFilteredRequest;
|
|
13
|
+
type Body = undefined;
|
|
14
|
+
type ResponseBody = PaginatedResponse<STInvoicePrivate[], LimitedFilteredRequest>
|
|
15
|
+
|
|
16
|
+
export const filterCompilers: SQLFilterDefinitions = {
|
|
17
|
+
...baseSQLFilterCompilers,
|
|
18
|
+
id: createSQLExpressionFilterCompiler(
|
|
19
|
+
SQL.column('stamhoofd_invoices', 'id')
|
|
20
|
+
),
|
|
21
|
+
number: createSQLExpressionFilterCompiler(
|
|
22
|
+
SQL.column('stamhoofd_invoices', 'number')
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const sorters: SQLSortDefinitions<STInvoice> = {
|
|
27
|
+
'id': {
|
|
28
|
+
getValue(a) {
|
|
29
|
+
return a.id
|
|
30
|
+
},
|
|
31
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
32
|
+
return new SQLOrderBy({
|
|
33
|
+
column: SQL.column('id'),
|
|
34
|
+
direction
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
'number': {
|
|
39
|
+
getValue(a) {
|
|
40
|
+
return a.number
|
|
41
|
+
},
|
|
42
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
43
|
+
return new SQLOrderBy({
|
|
44
|
+
column: SQL.column('number'),
|
|
45
|
+
direction
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class GetInvoicesEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
52
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>
|
|
53
|
+
|
|
54
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
55
|
+
if (request.method != "GET") {
|
|
56
|
+
return [false];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const params = Endpoint.parseParameters(request.url, "/admin/invoices", {});
|
|
60
|
+
|
|
61
|
+
if (params) {
|
|
62
|
+
return [true, params as Params];
|
|
63
|
+
}
|
|
64
|
+
return [false];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static buildQuery(q: CountFilteredRequest|LimitedFilteredRequest) {
|
|
68
|
+
const query = SQL
|
|
69
|
+
.select(
|
|
70
|
+
SQL.wildcard('stamhoofd_invoices')
|
|
71
|
+
)
|
|
72
|
+
.from(
|
|
73
|
+
SQL.table('stamhoofd_invoices')
|
|
74
|
+
)
|
|
75
|
+
.whereNot(SQL.column('stamhoofd_invoices', 'number'), null);
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if (q.filter) {
|
|
79
|
+
query.where(compileToSQLFilter(q.filter, filterCompilers))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (q.search) {
|
|
83
|
+
let searchFilter: StamhoofdFilter|null = null
|
|
84
|
+
|
|
85
|
+
// todo: auto detect e-mailaddresses and search on admins
|
|
86
|
+
searchFilter = {
|
|
87
|
+
name: {
|
|
88
|
+
$contains: q.search
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (searchFilter) {
|
|
93
|
+
query.where(compileToSQLFilter(searchFilter, filterCompilers))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
98
|
+
if (q.pageFilter) {
|
|
99
|
+
query.where(compileToSQLFilter(q.pageFilter, filterCompilers))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
query.orderBy(compileToSQLSorter(q.sort, sorters))
|
|
103
|
+
query.limit(q.limit)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return query
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
110
|
+
await Context.authenticate()
|
|
111
|
+
|
|
112
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
113
|
+
throw Context.auth.error()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
117
|
+
|
|
118
|
+
if (request.query.limit > maxLimit) {
|
|
119
|
+
throw new SimpleError({
|
|
120
|
+
code: 'invalid_field',
|
|
121
|
+
field: 'limit',
|
|
122
|
+
message: 'Limit can not be more than ' + maxLimit
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (request.query.limit < 1) {
|
|
127
|
+
throw new SimpleError({
|
|
128
|
+
code: 'invalid_field',
|
|
129
|
+
field: 'limit',
|
|
130
|
+
message: 'Limit can not be less than 1'
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = await GetInvoicesEndpoint.buildQuery(request.query).fetch()
|
|
135
|
+
const invoices = STInvoice.fromRows(data, 'stamhoofd_invoices');
|
|
136
|
+
|
|
137
|
+
let next: LimitedFilteredRequest|undefined;
|
|
138
|
+
|
|
139
|
+
if (invoices.length >= request.query.limit) {
|
|
140
|
+
const lastObject = invoices[invoices.length - 1];
|
|
141
|
+
const nextFilter = getSortFilter(lastObject, sorters, request.query.sort);
|
|
142
|
+
|
|
143
|
+
next = new LimitedFilteredRequest({
|
|
144
|
+
filter: request.query.filter,
|
|
145
|
+
pageFilter: nextFilter,
|
|
146
|
+
sort: request.query.sort,
|
|
147
|
+
limit: request.query.limit,
|
|
148
|
+
search: request.query.search
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
if (JSON.stringify(nextFilter) === JSON.stringify(request.query.pageFilter)) {
|
|
152
|
+
console.error('Found infinite loading loop for', request.query);
|
|
153
|
+
next = undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Get payments + organizations
|
|
158
|
+
const paymentIds = invoices.flatMap(i => i.paymentId ? [i.paymentId] : [])
|
|
159
|
+
const organizationIds = invoices.flatMap(i => i.organizationId ? [i.organizationId] : [])
|
|
160
|
+
|
|
161
|
+
const payments = await Payment.getByIDs(...paymentIds)
|
|
162
|
+
const organizations = await Organization.getByIDs(...organizationIds)
|
|
163
|
+
|
|
164
|
+
const structures: STInvoicePrivate[] = []
|
|
165
|
+
for (const invoice of invoices) {
|
|
166
|
+
const payment = payments.find(p => p.id === invoice.paymentId)
|
|
167
|
+
const organization = organizations.find(p => p.id === invoice.organizationId)
|
|
168
|
+
structures.push(
|
|
169
|
+
STInvoicePrivate.create({
|
|
170
|
+
...invoice,
|
|
171
|
+
payment: payment ? PaymentStruct.create(payment) : null,
|
|
172
|
+
organization: organization ? (await organization.getStructure({emptyGroups: true})) : undefined,
|
|
173
|
+
settlement: payment?.settlement ?? null,
|
|
174
|
+
})
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return new Response(
|
|
179
|
+
new PaginatedResponse<STInvoicePrivate[], LimitedFilteredRequest>({
|
|
180
|
+
results: structures,
|
|
181
|
+
next
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { SQL, SQLAlias, SQLSum, SQLCount, SQLDistinct, SQLSelectAs } from '@stamhoofd/sql';
|
|
3
|
+
import { ChargeMembershipsSummary } from '@stamhoofd/structures';
|
|
4
|
+
import { Context } from '../../../helpers/Context';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
type Params = Record<string, never>;
|
|
8
|
+
type Query = Record<string, never>;
|
|
9
|
+
type Body = undefined;
|
|
10
|
+
type ResponseBody = ChargeMembershipsSummary;
|
|
11
|
+
|
|
12
|
+
export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
14
|
+
if (request.method != "GET") {
|
|
15
|
+
return [false];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const params = Endpoint.parseParameters(request.url, "/admin/charge-memberships/summary", {});
|
|
19
|
+
|
|
20
|
+
if (params) {
|
|
21
|
+
return [true, params as Params];
|
|
22
|
+
}
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
27
|
+
await Context.authenticate()
|
|
28
|
+
|
|
29
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
30
|
+
throw Context.auth.error()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const query = SQL
|
|
34
|
+
.select(
|
|
35
|
+
new SQLSelectAs(
|
|
36
|
+
new SQLCount(
|
|
37
|
+
new SQLDistinct(
|
|
38
|
+
SQL.column('member_platform_memberships', 'id')
|
|
39
|
+
)
|
|
40
|
+
),
|
|
41
|
+
new SQLAlias('data__memberships')
|
|
42
|
+
),
|
|
43
|
+
new SQLSelectAs(
|
|
44
|
+
new SQLCount(
|
|
45
|
+
new SQLDistinct(
|
|
46
|
+
SQL.column('member_platform_memberships', 'memberId')
|
|
47
|
+
)
|
|
48
|
+
),
|
|
49
|
+
new SQLAlias('data__members')
|
|
50
|
+
),
|
|
51
|
+
new SQLSelectAs(
|
|
52
|
+
new SQLCount(
|
|
53
|
+
new SQLDistinct(
|
|
54
|
+
SQL.column('member_platform_memberships', 'organizationId')
|
|
55
|
+
)
|
|
56
|
+
),
|
|
57
|
+
new SQLAlias('data__organizations')
|
|
58
|
+
),
|
|
59
|
+
new SQLSelectAs(
|
|
60
|
+
new SQLSum(
|
|
61
|
+
SQL.column('member_platform_memberships', 'price')
|
|
62
|
+
),
|
|
63
|
+
new SQLAlias('data__price')
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
.from(
|
|
67
|
+
SQL.table('member_platform_memberships')
|
|
68
|
+
)
|
|
69
|
+
.where(SQL.column('invoiceId'), null)
|
|
70
|
+
.andWhere(SQL.column('invoiceItemDetailId'), null);
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
const result = await query.fetch();
|
|
74
|
+
const members = result[0]['data']['members'] as number;
|
|
75
|
+
const memberships = result[0]['data']['memberships'] as number;
|
|
76
|
+
const organizations = result[0]['data']['organizations'] as number;
|
|
77
|
+
const price = result[0]['data']['price'] as number;
|
|
78
|
+
|
|
79
|
+
return new Response(
|
|
80
|
+
ChargeMembershipsSummary.create({
|
|
81
|
+
memberships: memberships ?? 0,
|
|
82
|
+
members: members ?? 0,
|
|
83
|
+
price: price ?? 0,
|
|
84
|
+
organizations: organizations ?? 0
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -271,11 +271,13 @@ export class GetOrganizationsEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
271
271
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
272
272
|
await Context.authenticate()
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
275
|
+
|
|
276
|
+
if (request.query.limit > maxLimit) {
|
|
275
277
|
throw new SimpleError({
|
|
276
278
|
code: 'invalid_field',
|
|
277
279
|
field: 'limit',
|
|
278
|
-
message: 'Limit can not be more than
|
|
280
|
+
message: 'Limit can not be more than ' + maxLimit
|
|
279
281
|
})
|
|
280
282
|
}
|
|
281
283
|
|
|
@@ -48,6 +48,7 @@ const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
|
48
48
|
status: createSQLExpressionFilterCompiler(
|
|
49
49
|
SQL.column('groups', 'status')
|
|
50
50
|
),
|
|
51
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
|
|
51
52
|
})
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -125,6 +126,49 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
125
126
|
}
|
|
126
127
|
),
|
|
127
128
|
|
|
129
|
+
responsibilities: createSQLRelationFilterCompiler(
|
|
130
|
+
SQL.select()
|
|
131
|
+
.from(
|
|
132
|
+
SQL.table('member_responsibility_records')
|
|
133
|
+
)
|
|
134
|
+
.where(
|
|
135
|
+
SQL.column('memberId'),
|
|
136
|
+
SQL.column('members', 'id'),
|
|
137
|
+
),
|
|
138
|
+
{
|
|
139
|
+
...baseSQLFilterCompilers,
|
|
140
|
+
// Alias for responsibilityId
|
|
141
|
+
"id": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
|
|
142
|
+
"responsibilityId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
|
|
143
|
+
"organizationId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'organizationId')),
|
|
144
|
+
"startDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'startDate')),
|
|
145
|
+
"endDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'endDate')),
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
|
|
149
|
+
platformMemberships: createSQLRelationFilterCompiler(
|
|
150
|
+
SQL.select()
|
|
151
|
+
.from(
|
|
152
|
+
SQL.table('member_platform_memberships')
|
|
153
|
+
)
|
|
154
|
+
.where(
|
|
155
|
+
SQL.column('memberId'),
|
|
156
|
+
SQL.column('members', 'id'),
|
|
157
|
+
),
|
|
158
|
+
{
|
|
159
|
+
...baseSQLFilterCompilers,
|
|
160
|
+
"id": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'id')),
|
|
161
|
+
"membershipTypeId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'membershipTypeId')),
|
|
162
|
+
"organizationId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'organizationId')),
|
|
163
|
+
"periodId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'periodId')),
|
|
164
|
+
"price": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'price')),
|
|
165
|
+
"invoiceId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'invoiceId')),
|
|
166
|
+
"startDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'startDate')),
|
|
167
|
+
"endDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'endDate')),
|
|
168
|
+
"expireDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'expireDate')),
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
|
|
128
172
|
/**
|
|
129
173
|
* @deprecated?
|
|
130
174
|
*/
|
|
@@ -270,6 +314,11 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
270
314
|
periodId: platform.periodId,
|
|
271
315
|
registeredAt: {
|
|
272
316
|
$neq: null
|
|
317
|
+
},
|
|
318
|
+
group: {
|
|
319
|
+
defaultAgeGroupId: {
|
|
320
|
+
$neq: null
|
|
321
|
+
}
|
|
273
322
|
}
|
|
274
323
|
}
|
|
275
324
|
}
|
|
@@ -363,11 +412,13 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
363
412
|
await Context.setOptionalOrganizationScope();
|
|
364
413
|
await Context.authenticate()
|
|
365
414
|
|
|
366
|
-
|
|
415
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
416
|
+
|
|
417
|
+
if (request.query.limit > maxLimit) {
|
|
367
418
|
throw new SimpleError({
|
|
368
419
|
code: 'invalid_field',
|
|
369
420
|
field: 'limit',
|
|
370
|
-
message: 'Limit can not be more than
|
|
421
|
+
message: 'Limit can not be more than ' + maxLimit
|
|
371
422
|
})
|
|
372
423
|
}
|
|
373
424
|
|
|
@@ -2,8 +2,8 @@ import { OneToManyRelation } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
4
4
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
5
|
-
import { BalanceItem, BalanceItemPayment, Document, Group, Member, MemberFactory, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Payment, Platform, Registration, User } from '@stamhoofd/models';
|
|
6
|
-
import { BalanceItemStatus, MemberWithRegistrationsBlob, MembersBlob, PaymentMethod, PaymentStatus, PermissionLevel, Registration as RegistrationStruct, User as UserStruct } from "@stamhoofd/structures";
|
|
5
|
+
import { BalanceItem, MemberPlatformMembership, BalanceItemPayment, Document, Group, Member, MemberFactory, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Payment, Platform, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
6
|
+
import { BalanceItemStatus, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, PaymentMethod, PaymentStatus, PermissionLevel, Registration as RegistrationStruct, User as UserStruct } from "@stamhoofd/structures";
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -52,6 +52,8 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
52
52
|
|
|
53
53
|
const members: MemberWithRegistrations[] = []
|
|
54
54
|
|
|
55
|
+
const platform = await Platform.getShared()
|
|
56
|
+
|
|
55
57
|
// Cache
|
|
56
58
|
const groups: Group[] = []
|
|
57
59
|
|
|
@@ -71,6 +73,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
71
73
|
|
|
72
74
|
const balanceItemMemberIds: string[] = []
|
|
73
75
|
const balanceItemRegistrationIdsPerOrganization: Map<string, string[]> = new Map()
|
|
76
|
+
const updateMembershipMemberIds = new Set<string>()
|
|
74
77
|
|
|
75
78
|
function addBalanceItemRegistrationId(organizationId: string, registrationId: string) {
|
|
76
79
|
const existing = balanceItemRegistrationIdsPerOrganization.get(organizationId);
|
|
@@ -121,6 +124,14 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
121
124
|
throw Context.auth.notFoundOrNoAccess("Je hebt niet voldoende rechten om leden toe te voegen in deze groep")
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
const period = await RegistrationPeriod.getByID(group.periodId)
|
|
128
|
+
if (!period || period.locked) {
|
|
129
|
+
throw new SimpleError({
|
|
130
|
+
code: "period_locked",
|
|
131
|
+
message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
124
135
|
// Set organization id of member based on registrations
|
|
125
136
|
if (!organization && STAMHOOFD.userMode !== 'platform' && !member.organizationId) {
|
|
126
137
|
member.organizationId = group.organizationId
|
|
@@ -161,6 +172,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
161
172
|
await member.save()
|
|
162
173
|
members.push(member)
|
|
163
174
|
balanceItemMemberIds.push(member.id)
|
|
175
|
+
updateMembershipMemberIds.add(member.id)
|
|
164
176
|
|
|
165
177
|
// Add registrations
|
|
166
178
|
for (const registrationStruct of struct.registrations) {
|
|
@@ -224,7 +236,12 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
224
236
|
}
|
|
225
237
|
|
|
226
238
|
let group: Group | null = null
|
|
227
|
-
|
|
239
|
+
|
|
240
|
+
console.log('Patch registration', patchRegistration)
|
|
241
|
+
|
|
242
|
+
if (patchRegistration.group) {
|
|
243
|
+
patchRegistration.groupId = patchRegistration.group.id
|
|
244
|
+
}
|
|
228
245
|
|
|
229
246
|
if (patchRegistration.groupId) {
|
|
230
247
|
group = await getGroup(patchRegistration.groupId)
|
|
@@ -263,6 +280,14 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
263
280
|
})
|
|
264
281
|
}
|
|
265
282
|
|
|
283
|
+
const period = await RegistrationPeriod.getByID(group.periodId)
|
|
284
|
+
if (!period || period.locked) {
|
|
285
|
+
throw new SimpleError({
|
|
286
|
+
code: "period_locked",
|
|
287
|
+
message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
266
291
|
// TODO: allow group changes
|
|
267
292
|
registration.waitingList = patchRegistration.waitingList ?? registration.waitingList
|
|
268
293
|
|
|
@@ -275,6 +300,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
275
300
|
}
|
|
276
301
|
registration.cycle = patchRegistration.cycle ?? registration.cycle
|
|
277
302
|
registration.groupId = patchRegistration.groupId ?? registration.groupId
|
|
303
|
+
registration.group = group
|
|
278
304
|
registration.organizationId = patchRegistration.organizationId ?? registration.organizationId
|
|
279
305
|
|
|
280
306
|
// Check if we should create a placeholder payment?
|
|
@@ -322,6 +348,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
322
348
|
}
|
|
323
349
|
|
|
324
350
|
await registration.save()
|
|
351
|
+
updateMembershipMemberIds.add(member.id)
|
|
325
352
|
}
|
|
326
353
|
|
|
327
354
|
for (const deleteId of patch.registrations.getDeletes()) {
|
|
@@ -337,13 +364,21 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
337
364
|
if (!await Context.auth.canAccessRegistration(registration, PermissionLevel.Write)) {
|
|
338
365
|
throw Context.auth.error("Je hebt niet voldoende rechten om deze inschrijving te verwijderen")
|
|
339
366
|
}
|
|
367
|
+
const oldGroup = await getGroup(registration.groupId)
|
|
368
|
+
const period = oldGroup && await RegistrationPeriod.getByID(oldGroup.periodId)
|
|
369
|
+
if (!period || period.locked) {
|
|
370
|
+
throw new SimpleError({
|
|
371
|
+
code: "period_locked",
|
|
372
|
+
message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
|
|
373
|
+
})
|
|
374
|
+
}
|
|
340
375
|
|
|
341
|
-
balanceItemMemberIds.push(member.id)
|
|
376
|
+
balanceItemMemberIds.push(member.id)
|
|
377
|
+
updateMembershipMemberIds.add(member.id)
|
|
342
378
|
await BalanceItem.deleteForDeletedRegistration(registration.id)
|
|
343
379
|
await registration.delete()
|
|
344
380
|
member.registrations = member.registrations.filter(r => r.id !== deleteId)
|
|
345
381
|
|
|
346
|
-
const oldGroup = await getGroup(registration.groupId)
|
|
347
382
|
if (oldGroup) {
|
|
348
383
|
// We need to update this group occupancy because we moved one member away from it
|
|
349
384
|
updateGroups.set(oldGroup.id, oldGroup)
|
|
@@ -358,9 +393,17 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
358
393
|
if (!group || group.organizationId !== struct.organizationId || !await Context.auth.canAccessGroup(group, PermissionLevel.Write)) {
|
|
359
394
|
throw Context.auth.error("Je hebt niet voldoende rechten om inschrijvingen in deze groep te maken")
|
|
360
395
|
}
|
|
396
|
+
const period = await RegistrationPeriod.getByID(group.periodId)
|
|
397
|
+
if (!period || period.locked) {
|
|
398
|
+
throw new SimpleError({
|
|
399
|
+
code: "period_locked",
|
|
400
|
+
message: "Deze inschrijvingsperiode is afgesloten en staat geen wijzigingen meer toe.",
|
|
401
|
+
})
|
|
402
|
+
}
|
|
361
403
|
|
|
362
404
|
const reg = await this.addRegistration(member, struct, group)
|
|
363
405
|
balanceItemMemberIds.push(member.id)
|
|
406
|
+
updateMembershipMemberIds.add(member.id)
|
|
364
407
|
addBalanceItemRegistrationId(reg.organizationId, reg.id)
|
|
365
408
|
|
|
366
409
|
// We need to update this group occupancy because we moved one member away from it
|
|
@@ -476,6 +519,97 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
476
519
|
await PatchOrganizationMembersEndpoint.updateManagers(member)
|
|
477
520
|
}
|
|
478
521
|
|
|
522
|
+
// Add platform memberships
|
|
523
|
+
for (const {put} of patch.platformMemberships.getPuts()) {
|
|
524
|
+
if (put.periodId !== platform.periodId) {
|
|
525
|
+
throw new SimpleError({
|
|
526
|
+
code: "invalid_field",
|
|
527
|
+
message: "Invalid period",
|
|
528
|
+
human: "Je kan geen aansluitingen maken voor een andere werkjaar dan het actieve werkjaar",
|
|
529
|
+
field: "periodId"
|
|
530
|
+
})
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (organization && put.organizationId !== organization.id) {
|
|
534
|
+
throw new SimpleError({
|
|
535
|
+
code: "invalid_field",
|
|
536
|
+
message: "Invalid organization",
|
|
537
|
+
human: "Je kan geen aansluitingen maken voor een andere vereniging",
|
|
538
|
+
field: "organizationId"
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (!await Context.auth.hasFullAccess(put.organizationId)) {
|
|
543
|
+
throw Context.auth.error("Je hebt niet voldoende rechten om deze aansluiting toe te voegen")
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!platform.config.membershipTypes.find(t => t.id === put.membershipTypeId)) {
|
|
547
|
+
throw new SimpleError({
|
|
548
|
+
code: "invalid_field",
|
|
549
|
+
field: "membershipTypeId",
|
|
550
|
+
message: "Invalid membership type",
|
|
551
|
+
human: "Dit aansluitingstype bestaat niet"
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Check duplicate memberships
|
|
556
|
+
|
|
557
|
+
// Check dates
|
|
558
|
+
|
|
559
|
+
// Calculate prices
|
|
560
|
+
|
|
561
|
+
const membership = new MemberPlatformMembership()
|
|
562
|
+
membership.id = put.id
|
|
563
|
+
membership.memberId = member.id
|
|
564
|
+
membership.membershipTypeId = put.membershipTypeId
|
|
565
|
+
membership.organizationId = put.organizationId
|
|
566
|
+
membership.periodId = put.periodId
|
|
567
|
+
|
|
568
|
+
membership.startDate = put.startDate
|
|
569
|
+
membership.endDate = put.endDate
|
|
570
|
+
membership.expireDate = put.expireDate
|
|
571
|
+
|
|
572
|
+
await membership.calculatePrice()
|
|
573
|
+
await membership.save()
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Delete platform memberships
|
|
577
|
+
for (const id of patch.platformMemberships.getDeletes()) {
|
|
578
|
+
const membership = await MemberPlatformMembership.getByID(id)
|
|
579
|
+
|
|
580
|
+
if (!membership || membership.memberId !== member.id) {
|
|
581
|
+
throw new SimpleError({
|
|
582
|
+
code: "invalid_field",
|
|
583
|
+
field: "id",
|
|
584
|
+
message: "Invalid id",
|
|
585
|
+
human: "Deze aansluiting bestaat niet"
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (!await Context.auth.hasFullAccess(membership.organizationId)) {
|
|
590
|
+
throw Context.auth.error("Je hebt niet voldoende rechten om deze aansluiting te verwijderen")
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (membership.periodId !== platform.periodId) {
|
|
594
|
+
throw new SimpleError({
|
|
595
|
+
code: "invalid_field",
|
|
596
|
+
message: "Invalid period",
|
|
597
|
+
human: "Je kan geen aansluitingen meer verwijderen voor een ander werkjaar dan het actieve werkjaar",
|
|
598
|
+
field: "periodId"
|
|
599
|
+
})
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (membership.invoiceId || membership.invoiceItemDetailId) {
|
|
603
|
+
throw new SimpleError({
|
|
604
|
+
code: "invalid_field",
|
|
605
|
+
message: "Invalid invoice",
|
|
606
|
+
human: "Je kan geen aansluiting verwijderen die al werd gefactureerd",
|
|
607
|
+
})
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
await membership.delete()
|
|
611
|
+
}
|
|
612
|
+
|
|
479
613
|
if (!members.find(m => m.id === member.id)) {
|
|
480
614
|
members.push(member)
|
|
481
615
|
}
|
|
@@ -523,6 +657,12 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
523
657
|
}
|
|
524
658
|
}
|
|
525
659
|
|
|
660
|
+
for (const member of members) {
|
|
661
|
+
if (updateMembershipMemberIds.has(member.id)) {
|
|
662
|
+
await member.updateMemberships()
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
526
666
|
return new Response(
|
|
527
667
|
await AuthenticatedStructures.membersBlob(members)
|
|
528
668
|
);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, patchObject } from "@simonbackx/simple-encoding";
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
-
import { Platform } from "@stamhoofd/models";
|
|
3
|
+
import { Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
4
4
|
import { Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
5
5
|
|
|
6
6
|
import { Context } from "../../../helpers/Context";
|
|
7
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
7
8
|
|
|
8
9
|
type Params = Record<string, never>;
|
|
9
10
|
type Query = undefined;
|
|
@@ -57,6 +58,17 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
57
58
|
platform.config = patchObject(platform.config, request.body.config)
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
if (request.body.period && request.body.period.id !== platform.periodId) {
|
|
62
|
+
const period = await RegistrationPeriod.getByID(request.body.period.id)
|
|
63
|
+
if (!period || period.organizationId) {
|
|
64
|
+
throw new SimpleError({
|
|
65
|
+
code: "invalid_period",
|
|
66
|
+
message: "Invalid period"
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
platform.periodId = period.id
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
await platform.save()
|
|
61
73
|
return new Response(await Platform.getSharedPrivateStruct());
|
|
62
74
|
}
|
|
@@ -200,11 +200,17 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
200
200
|
|
|
201
201
|
if (item.waitingList) {
|
|
202
202
|
registration.waitingList = true
|
|
203
|
+
registration.canRegister = false
|
|
203
204
|
registration.reservedUntil = null
|
|
204
205
|
await registration.save()
|
|
205
206
|
} else {
|
|
206
|
-
registration.waitingList
|
|
207
|
-
|
|
207
|
+
if (registration.waitingList && registration.canRegister) {
|
|
208
|
+
// Keep data: otherwise people cannot retry if the payment fails
|
|
209
|
+
// We'll mark the registration as valid after the payment
|
|
210
|
+
} else {
|
|
211
|
+
registration.waitingList = false
|
|
212
|
+
registration.canRegister = false
|
|
213
|
+
}
|
|
208
214
|
registration.price = item.calculatedPrice
|
|
209
215
|
payRegistrations.push({
|
|
210
216
|
registration,
|
|
@@ -286,6 +292,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
286
292
|
// registration.paymentId = payment.id
|
|
287
293
|
|
|
288
294
|
registration.reservedUntil = null
|
|
295
|
+
registration.canRegister = false
|
|
289
296
|
|
|
290
297
|
if (payment.method == PaymentMethod.Transfer || payment.method == PaymentMethod.PointOfSale || payment.status == PaymentStatus.Succeeded) {
|
|
291
298
|
await registration.markValid()
|
|
@@ -4,7 +4,7 @@ import { RegistrationPeriod as RegistrationPeriodStruct } from "@stamhoofd/struc
|
|
|
4
4
|
|
|
5
5
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
6
6
|
import { Context } from '../../../helpers/Context';
|
|
7
|
-
import { RegistrationPeriod } from '@stamhoofd/models';
|
|
7
|
+
import { Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
8
8
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
9
9
|
|
|
10
10
|
type Params = Record<string, never>;
|
|
@@ -107,6 +107,9 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
107
107
|
await model.delete();
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// Clear platform cache
|
|
111
|
+
Platform.clearCache()
|
|
112
|
+
|
|
110
113
|
return new Response(
|
|
111
114
|
periods.map(p => p.getStructure())
|
|
112
115
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, ObjectData, patchObject } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
3
|
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
4
|
-
import {
|
|
5
|
-
import { BuckarooSettings,
|
|
4
|
+
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, User, Webshop } from '@stamhoofd/models';
|
|
5
|
+
import { BuckarooSettings, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, UserPermissions } from "@stamhoofd/structures";
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
@@ -297,6 +297,36 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
297
297
|
organization.uri = request.body.uri
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
if (request.body.period && request.body.period.id !== organization.periodId) {
|
|
301
|
+
const organizationPeriod = await OrganizationRegistrationPeriod.getByID(request.body.period.id)
|
|
302
|
+
if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
|
|
303
|
+
throw new SimpleError({
|
|
304
|
+
code: "invalid_field",
|
|
305
|
+
message: "De periode die je wilt instellen bestaat niet (meer)",
|
|
306
|
+
field: "period"
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const period = await RegistrationPeriod.getByID(organizationPeriod.periodId)
|
|
311
|
+
if (!period || (period.organizationId && period.organizationId !== organization.id)) {
|
|
312
|
+
throw new SimpleError({
|
|
313
|
+
code: "invalid_field",
|
|
314
|
+
message: "De periode die je wilt instellen bestaat niet (meer)",
|
|
315
|
+
field: "period"
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (period.locked) {
|
|
320
|
+
throw new SimpleError({
|
|
321
|
+
code: "invalid_field",
|
|
322
|
+
message: "De periode die je wilt instellen is reeds afgesloten",
|
|
323
|
+
field: "period"
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
organization.periodId = period.id
|
|
328
|
+
}
|
|
329
|
+
|
|
300
330
|
// Save the organization
|
|
301
331
|
await organization.save()
|
|
302
332
|
} else {
|
|
@@ -3,7 +3,7 @@ import { GroupPrivateSettings, OrganizationRegistrationPeriod as OrganizationReg
|
|
|
3
3
|
|
|
4
4
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from "@simonbackx/simple-encoding";
|
|
5
5
|
import { Context } from "../../../../helpers/Context";
|
|
6
|
-
import { Group, OrganizationRegistrationPeriod, RegistrationPeriod } from "@stamhoofd/models";
|
|
6
|
+
import { Group, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
7
7
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
8
8
|
|
|
9
9
|
type Params = Record<string, never>;
|
|
@@ -37,10 +37,70 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
37
37
|
|
|
38
38
|
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
39
39
|
throw Context.auth.error()
|
|
40
|
-
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const platform = await Platform.getShared()
|
|
43
|
+
|
|
44
|
+
function validateDefaultGroupId(id: string|null): string|null {
|
|
45
|
+
if (id === null) {
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (platform.config.defaultAgeGroups.find(g => g.id === id)) {
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new SimpleError({
|
|
54
|
+
code: "invalid_default_age_group",
|
|
55
|
+
message: "Invalid default age group",
|
|
56
|
+
human: "De standaard leeftijdsgroep is ongeldig",
|
|
57
|
+
statusCode: 400
|
|
58
|
+
})
|
|
59
|
+
}
|
|
41
60
|
|
|
42
61
|
const structs: OrganizationRegistrationPeriodStruct[] = [];
|
|
43
62
|
|
|
63
|
+
for (const {put} of request.body.getPuts()) {
|
|
64
|
+
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
65
|
+
throw Context.auth.error()
|
|
66
|
+
}
|
|
67
|
+
const period = await RegistrationPeriod.getByID(put.period.id);
|
|
68
|
+
|
|
69
|
+
if (!period) {
|
|
70
|
+
throw new SimpleError({
|
|
71
|
+
code: "not_found",
|
|
72
|
+
message: "Period not found",
|
|
73
|
+
statusCode: 404
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const organizationPeriod = new OrganizationRegistrationPeriod();
|
|
78
|
+
organizationPeriod.id = put.id;
|
|
79
|
+
organizationPeriod.organizationId = organization.id;
|
|
80
|
+
organizationPeriod.periodId = put.period.id;
|
|
81
|
+
organizationPeriod.settings = put.settings;
|
|
82
|
+
await organizationPeriod.save();
|
|
83
|
+
|
|
84
|
+
for (const struct of put.groups) {
|
|
85
|
+
const model = new Group()
|
|
86
|
+
model.id = struct.id
|
|
87
|
+
model.organizationId = organization.id
|
|
88
|
+
model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
|
|
89
|
+
model.periodId = organizationPeriod.periodId
|
|
90
|
+
model.settings = struct.settings
|
|
91
|
+
model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
|
|
92
|
+
model.status = struct.status
|
|
93
|
+
await model.updateOccupancy()
|
|
94
|
+
await model.save();
|
|
95
|
+
}
|
|
96
|
+
const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
|
|
97
|
+
|
|
98
|
+
// Delete unreachable categories first
|
|
99
|
+
await organizationPeriod.cleanCategories(groups);
|
|
100
|
+
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
101
|
+
structs.push(organizationPeriod.getStructure(period, groups));
|
|
102
|
+
}
|
|
103
|
+
|
|
44
104
|
for (const patch of request.body.getPatches()) {
|
|
45
105
|
const organizationPeriod = await OrganizationRegistrationPeriod.getByID(patch.id);
|
|
46
106
|
if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
|
|
@@ -112,6 +172,7 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
112
172
|
const model = new Group()
|
|
113
173
|
model.id = struct.id
|
|
114
174
|
model.organizationId = organization.id
|
|
175
|
+
model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
|
|
115
176
|
model.periodId = organizationPeriod.periodId
|
|
116
177
|
model.settings = struct.settings
|
|
117
178
|
model.privateSettings = struct.privateSettings ?? GroupPrivateSettings.create({})
|
|
@@ -181,6 +242,10 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
181
242
|
if (struct.deletedAt !== undefined) {
|
|
182
243
|
model.deletedAt = struct.deletedAt
|
|
183
244
|
}
|
|
245
|
+
|
|
246
|
+
if (struct.defaultAgeGroupId !== undefined) {
|
|
247
|
+
model.defaultAgeGroupId = validateDefaultGroupId(struct.defaultAgeGroupId)
|
|
248
|
+
}
|
|
184
249
|
|
|
185
250
|
await model.updateOccupancy()
|
|
186
251
|
await model.save();
|
|
@@ -191,15 +256,13 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
191
256
|
|
|
192
257
|
if (deleteUnreachable) {
|
|
193
258
|
// Delete unreachable categories first
|
|
194
|
-
await
|
|
259
|
+
await organizationPeriod.cleanCategories(groups);
|
|
195
260
|
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
196
261
|
}
|
|
197
262
|
|
|
198
263
|
if (period) {
|
|
199
|
-
|
|
200
264
|
structs.push(organizationPeriod.getStructure(period, groups));
|
|
201
265
|
}
|
|
202
|
-
|
|
203
266
|
}
|
|
204
267
|
|
|
205
268
|
return new Response(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
2
|
-
import { Group, MemberResponsibilityRecord, MemberWithRegistrations, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, User, Webshop } from "@stamhoofd/models";
|
|
3
|
-
import {
|
|
2
|
+
import { Group, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, User, Webshop } from "@stamhoofd/models";
|
|
3
|
+
import { MemberPlatformMembership as MemberPlatformMembershipStruct, MemberResponsibilityRecord as MemberResponsibilityRecordStruct, MemberWithRegistrationsBlob, MembersBlob, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, PrivateWebshop, User as UserStruct, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Context } from "./Context";
|
|
6
6
|
|
|
@@ -144,10 +144,12 @@ export class AuthenticatedStructures {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Load responsibilities
|
|
147
|
-
const responsibilities = await MemberResponsibilityRecord.where({ memberId: { sign: 'IN', value: members.map(m => m.id) } })
|
|
147
|
+
const responsibilities = members.length > 0 ? await MemberResponsibilityRecord.where({ memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
|
|
148
|
+
const platformMemberships = members.length > 0 ? await MemberPlatformMembership.where({ memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
|
|
148
149
|
|
|
149
150
|
for (const blob of memberBlobs) {
|
|
150
151
|
blob.responsibilities = responsibilities.filter(r => r.memberId == blob.id).map(r => MemberResponsibilityRecordStruct.create(r))
|
|
152
|
+
blob.platformMemberships = platformMemberships.filter(r => r.memberId == blob.id).map(r => MemberPlatformMembershipStruct.create(r))
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
return MembersBlob.create({
|