@stamhoofd/backend 2.18.0 → 2.20.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 +1 -1
- package/package.json +5 -5
- package/src/crons.ts +3 -67
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +49 -0
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +10 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +10 -1
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +0 -47
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +14 -16
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +1 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +171 -36
- package/src/endpoints/organization/dashboard/billing/GetDetailedBillingStatusEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +2 -1
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +28 -32
- package/src/helpers/AdminPermissionChecker.ts +22 -5
- package/src/helpers/AuthenticatedStructures.ts +3 -2
- package/src/helpers/MembershipCharger.ts +126 -0
- package/src/helpers/SetupStepsUpdater.ts +115 -13
- package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +0 -153
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +0 -64
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +0 -84
- package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +0 -65
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, patchObject } from "@simonbackx/simple-encoding";
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
3
|
import { Organization, Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
4
|
-
import { PlatformPremiseType, Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
4
|
+
import { MemberResponsibility, PlatformConfig, PlatformPremiseType, Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
5
5
|
|
|
6
6
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
7
7
|
import { Context } from "../../../helpers/Context";
|
|
@@ -12,8 +12,15 @@ type Query = undefined;
|
|
|
12
12
|
type Body = AutoEncoderPatchType<PlatformStruct>;
|
|
13
13
|
type ResponseBody = PlatformStruct;
|
|
14
14
|
|
|
15
|
-
export class PatchPlatformEndpoint extends Endpoint<
|
|
16
|
-
|
|
15
|
+
export class PatchPlatformEndpoint extends Endpoint<
|
|
16
|
+
Params,
|
|
17
|
+
Query,
|
|
18
|
+
Body,
|
|
19
|
+
ResponseBody
|
|
20
|
+
> {
|
|
21
|
+
bodyDecoder = PlatformStruct.patchType() as Decoder<
|
|
22
|
+
AutoEncoderPatchType<PlatformStruct>
|
|
23
|
+
>;
|
|
17
24
|
|
|
18
25
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
19
26
|
if (request.method != "PATCH") {
|
|
@@ -29,65 +36,102 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
32
|
-
await Context.authenticate()
|
|
39
|
+
await Context.authenticate();
|
|
33
40
|
|
|
34
41
|
// Fast throw first (more in depth checking for patches later)
|
|
35
42
|
if (!Context.auth.hasPlatformFullAccess()) {
|
|
36
|
-
throw Context.auth.error()
|
|
43
|
+
throw Context.auth.error();
|
|
37
44
|
}
|
|
38
45
|
|
|
39
|
-
const platform = await Platform.getShared()
|
|
46
|
+
const platform = await Platform.getShared();
|
|
40
47
|
|
|
41
48
|
if (request.body.privateConfig) {
|
|
42
49
|
// Did we patch roles?
|
|
43
50
|
if (request.body.privateConfig.roles) {
|
|
44
51
|
if (!Context.auth.canManagePlatformAdmins()) {
|
|
45
|
-
throw Context.auth.error()
|
|
52
|
+
throw Context.auth.error();
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
// Update roles
|
|
49
|
-
platform.privateConfig.roles = patchObject(
|
|
56
|
+
platform.privateConfig.roles = patchObject(
|
|
57
|
+
platform.privateConfig.roles,
|
|
58
|
+
request.body.privateConfig.roles
|
|
59
|
+
);
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
if (request.body.privateConfig.emails) {
|
|
53
63
|
if (!Context.auth.hasPlatformFullAccess()) {
|
|
54
|
-
throw Context.auth.error()
|
|
64
|
+
throw Context.auth.error();
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
// Update roles
|
|
58
|
-
platform.privateConfig.emails = patchObject(
|
|
68
|
+
platform.privateConfig.emails = patchObject(
|
|
69
|
+
platform.privateConfig.emails,
|
|
70
|
+
request.body.privateConfig.emails
|
|
71
|
+
);
|
|
59
72
|
}
|
|
60
73
|
}
|
|
61
74
|
|
|
75
|
+
let shouldUpdateSetupSteps = false;
|
|
76
|
+
|
|
62
77
|
if (request.body.config) {
|
|
63
78
|
if (!Context.auth.hasPlatformFullAccess()) {
|
|
64
|
-
throw Context.auth.error()
|
|
79
|
+
throw Context.auth.error();
|
|
65
80
|
}
|
|
66
81
|
|
|
82
|
+
const newConfig = request.body.config;
|
|
83
|
+
|
|
67
84
|
// Update config
|
|
68
|
-
if(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
if (newConfig) {
|
|
86
|
+
if (newConfig.premiseTypes || newConfig.responsibilities) {
|
|
87
|
+
const oldConfig = platform.config.clone();
|
|
88
|
+
platform.config = patchObject(platform.config, newConfig);
|
|
89
|
+
const currentConfig = platform.config;
|
|
90
|
+
|
|
91
|
+
shouldUpdateSetupSteps = this.shouldUpdateSetupSteps(
|
|
92
|
+
currentConfig,
|
|
93
|
+
newConfig,
|
|
94
|
+
oldConfig
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
platform.config = patchObject(platform.config, newConfig);
|
|
76
98
|
}
|
|
77
|
-
} else {
|
|
78
|
-
platform.config = patchObject(platform.config, request.body.config)
|
|
79
99
|
}
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
if (
|
|
83
|
-
|
|
102
|
+
if (
|
|
103
|
+
request.body.period &&
|
|
104
|
+
request.body.period.id !== platform.periodId
|
|
105
|
+
) {
|
|
106
|
+
const period = await RegistrationPeriod.getByID(
|
|
107
|
+
request.body.period.id
|
|
108
|
+
);
|
|
84
109
|
if (!period || period.organizationId) {
|
|
85
110
|
throw new SimpleError({
|
|
86
111
|
code: "invalid_period",
|
|
87
|
-
message: "Invalid period"
|
|
88
|
-
})
|
|
112
|
+
message: "Invalid period",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
platform.periodId = period.id;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (request.body.membershipOrganizationId !== undefined) {
|
|
119
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
120
|
+
throw Context.auth.error()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (request.body.membershipOrganizationId) {
|
|
124
|
+
const organization = await Organization.getByID(request.body.membershipOrganizationId)
|
|
125
|
+
if (!organization) {
|
|
126
|
+
throw new SimpleError({
|
|
127
|
+
code: "invalid_organization",
|
|
128
|
+
message: "Invalid organization"
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
platform.membershipOrganizationId = organization.id
|
|
132
|
+
} else {
|
|
133
|
+
platform.membershipOrganizationId = null
|
|
89
134
|
}
|
|
90
|
-
platform.periodId = period.id
|
|
91
135
|
}
|
|
92
136
|
|
|
93
137
|
if (request.body.membershipOrganizationId !== undefined) {
|
|
@@ -109,35 +153,126 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
109
153
|
}
|
|
110
154
|
}
|
|
111
155
|
|
|
112
|
-
await platform.save()
|
|
156
|
+
await platform.save();
|
|
157
|
+
|
|
158
|
+
if(shouldUpdateSetupSteps) {
|
|
159
|
+
SetupStepUpdater.updateSetupStepsForAllOrganizationsInCurrentPeriod().catch(console.error);
|
|
160
|
+
}
|
|
161
|
+
|
|
113
162
|
return new Response(await Platform.getSharedPrivateStruct());
|
|
114
163
|
}
|
|
115
164
|
|
|
116
|
-
private
|
|
117
|
-
|
|
165
|
+
private shouldUpdateSetupSteps(
|
|
166
|
+
currentConfig: PlatformConfig,
|
|
167
|
+
newConfig: PlatformConfig | AutoEncoderPatchType<PlatformConfig>,
|
|
168
|
+
oldConfig: PlatformConfig
|
|
169
|
+
): boolean {
|
|
170
|
+
let shouldUpdate = false;
|
|
171
|
+
const premiseTypes: PlatformPremiseType[] = currentConfig.premiseTypes;
|
|
172
|
+
const responsibilities: MemberResponsibility[] =
|
|
173
|
+
currentConfig.responsibilities;
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
newConfig.premiseTypes &&
|
|
177
|
+
this.shouldUpdateSetupStepPremise(
|
|
178
|
+
premiseTypes,
|
|
179
|
+
oldConfig.premiseTypes
|
|
180
|
+
)
|
|
181
|
+
) {
|
|
182
|
+
shouldUpdate = true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
!shouldUpdate &&
|
|
187
|
+
newConfig.responsibilities &&
|
|
188
|
+
this.shouldUpdateSetupStepFunctions(
|
|
189
|
+
responsibilities,
|
|
190
|
+
oldConfig.responsibilities
|
|
191
|
+
)
|
|
192
|
+
) {
|
|
193
|
+
shouldUpdate = true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return shouldUpdate;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private shouldUpdateSetupStepPremise(
|
|
200
|
+
newPremiseTypes: PlatformPremiseType[],
|
|
201
|
+
oldPremiseTypes: PlatformPremiseType[]
|
|
202
|
+
) {
|
|
203
|
+
for (const premiseType of newPremiseTypes) {
|
|
118
204
|
const id = premiseType.id;
|
|
119
|
-
const oldVersion = oldPremiseTypes.find(x => x.id === id);
|
|
205
|
+
const oldVersion = oldPremiseTypes.find((x) => x.id === id);
|
|
120
206
|
|
|
121
207
|
// if premise type is not new
|
|
122
|
-
if(oldVersion) {
|
|
123
|
-
if(
|
|
208
|
+
if (oldVersion) {
|
|
209
|
+
if (
|
|
210
|
+
oldVersion.min !== premiseType.min ||
|
|
211
|
+
oldVersion.max !== premiseType.max
|
|
212
|
+
) {
|
|
124
213
|
return true;
|
|
125
214
|
}
|
|
126
215
|
continue;
|
|
127
216
|
}
|
|
128
217
|
|
|
129
218
|
// if premise type is new
|
|
130
|
-
if(premiseType.min || premiseType.max) {
|
|
219
|
+
if (premiseType.min || premiseType.max) {
|
|
131
220
|
return true;
|
|
132
221
|
}
|
|
133
222
|
}
|
|
134
223
|
|
|
135
|
-
for(const oldPremiseType of oldPremiseTypes) {
|
|
224
|
+
for (const oldPremiseType of oldPremiseTypes) {
|
|
136
225
|
const id = oldPremiseType.id;
|
|
137
226
|
|
|
138
227
|
// if premise type is removed
|
|
139
|
-
if(!newPremiseTypes.some(x => x.id === id)) {
|
|
140
|
-
if(oldPremiseType.min || oldPremiseType.max) {
|
|
228
|
+
if (!newPremiseTypes.some((x) => x.id === id)) {
|
|
229
|
+
if (oldPremiseType.min || oldPremiseType.max) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private shouldUpdateSetupStepFunctions(
|
|
237
|
+
newResponsibilities: MemberResponsibility[],
|
|
238
|
+
oldResponsibilities: MemberResponsibility[]
|
|
239
|
+
) {
|
|
240
|
+
for (const responsibility of newResponsibilities) {
|
|
241
|
+
const id = responsibility.id;
|
|
242
|
+
const oldVersion = oldResponsibilities.find((x) => x.id === id);
|
|
243
|
+
|
|
244
|
+
// if responsibility is not new
|
|
245
|
+
if (oldVersion) {
|
|
246
|
+
// if restrictions changed
|
|
247
|
+
if (
|
|
248
|
+
oldVersion.minimumMembers !==
|
|
249
|
+
responsibility.minimumMembers ||
|
|
250
|
+
oldVersion.maximumMembers !== responsibility.maximumMembers
|
|
251
|
+
) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// if responsibility is new
|
|
258
|
+
if (
|
|
259
|
+
responsibility.minimumMembers ||
|
|
260
|
+
responsibility.maximumMembers
|
|
261
|
+
) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for (const oldResponsibility of oldResponsibilities) {
|
|
267
|
+
const id = oldResponsibility.id;
|
|
268
|
+
|
|
269
|
+
// if responsibility is removed
|
|
270
|
+
if (!newResponsibilities.some((x) => x.id === id)) {
|
|
271
|
+
// if responsibility had restrictions
|
|
272
|
+
if (
|
|
273
|
+
oldResponsibility.minimumMembers ||
|
|
274
|
+
oldResponsibility.maximumMembers
|
|
275
|
+
) {
|
|
141
276
|
return true;
|
|
142
277
|
}
|
|
143
278
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
-
import { OrganizationDetailedBillingStatus, OrganizationDetailedBillingStatusItem, PaymentMethod } from "@stamhoofd/structures";
|
|
2
|
+
import { OrganizationDetailedBillingStatus, OrganizationDetailedBillingStatusItem, PaymentMethod, PaymentStatus } from "@stamhoofd/structures";
|
|
3
3
|
|
|
4
4
|
import { BalanceItem, Organization, Payment } from "@stamhoofd/models";
|
|
5
5
|
import { SQL } from "@stamhoofd/sql";
|
|
@@ -41,8 +41,7 @@ export class GetDetailedBillingStatusEndpoint extends Endpoint<Params, Query, Bo
|
|
|
41
41
|
const paymentModels = await Payment.select()
|
|
42
42
|
.where('payingOrganizationId', organization.id)
|
|
43
43
|
.andWhere(
|
|
44
|
-
SQL.whereNot('
|
|
45
|
-
.or('method', [PaymentMethod.Transfer, PaymentMethod.DirectDebit, PaymentMethod.PointOfSale, PaymentMethod.Unknown])
|
|
44
|
+
SQL.whereNot('status', PaymentStatus.Failed)
|
|
46
45
|
)
|
|
47
46
|
.fetch()
|
|
48
47
|
|
|
@@ -3,6 +3,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
3
3
|
import jwt from 'jsonwebtoken';
|
|
4
4
|
|
|
5
5
|
import { Context } from '../../../../helpers/Context';
|
|
6
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
7
|
|
|
7
8
|
type Params = Record<string, never>;
|
|
8
9
|
type Query = undefined;
|
|
@@ -39,7 +40,7 @@ export class CreateNoltTokenEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
39
40
|
if (!await Context.auth.hasSomeAccess(organization.id)) {
|
|
40
41
|
throw Context.auth.error()
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
// Create token
|
|
44
45
|
const payload = {
|
|
45
46
|
// The ID that you use in your app for this user
|
|
@@ -2,19 +2,15 @@ import { createMollieClient } from '@mollie/api-client';
|
|
|
2
2
|
import { AutoEncoder, BooleanDecoder, Decoder, field } 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, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment
|
|
5
|
+
import { BalanceItem, BalanceItemPayment, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment } from '@stamhoofd/models';
|
|
6
6
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
7
|
-
import { PaymentGeneral, PaymentMethod,
|
|
7
|
+
import { PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus } from "@stamhoofd/structures";
|
|
8
8
|
|
|
9
9
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
10
10
|
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
11
11
|
import { Context } from '../../../helpers/Context';
|
|
12
12
|
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
13
13
|
|
|
14
|
-
function calculateFee(totalPrice: number, fixed: number, percentageTimes100: number) {
|
|
15
|
-
return Math.round(fixed + Math.max(1, totalPrice * percentageTimes100 / 100 / 100)); // € 0,21 + 0,2%
|
|
16
|
-
}
|
|
17
|
-
|
|
18
14
|
type Params = {id: string};
|
|
19
15
|
class Query extends AutoEncoder {
|
|
20
16
|
@field({ decoder: BooleanDecoder, optional: true })
|
|
@@ -77,7 +73,7 @@ export class ExchangePaymentEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
77
73
|
if (payment.status === status) {
|
|
78
74
|
return;
|
|
79
75
|
}
|
|
80
|
-
const wasPaid = payment.paidAt !== null
|
|
76
|
+
// const wasPaid = payment.paidAt !== null
|
|
81
77
|
if (status === PaymentStatus.Succeeded) {
|
|
82
78
|
payment.status = PaymentStatus.Succeeded
|
|
83
79
|
payment.paidAt = new Date()
|
|
@@ -97,31 +93,31 @@ export class ExchangePaymentEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
97
93
|
await BalanceItem.updateOutstanding(balanceItemPayments.map(p => p.balanceItem), organization.id)
|
|
98
94
|
})
|
|
99
95
|
|
|
100
|
-
if (!wasPaid && payment.provider === PaymentProvider.Buckaroo && payment.method) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
96
|
+
//if (!wasPaid && payment.provider === PaymentProvider.Buckaroo && payment.method) {
|
|
97
|
+
// // Charge transaction fees
|
|
98
|
+
// let fee = 0
|
|
99
|
+
//
|
|
100
|
+
// if (payment.method === PaymentMethod.iDEAL) {
|
|
101
|
+
// fee = calculateFee(payment.price, 21, 20); // € 0,21 + 0,2%
|
|
102
|
+
// } else if (payment.method === PaymentMethod.Bancontact || payment.method === PaymentMethod.Payconiq) {
|
|
103
|
+
// fee = calculateFee(payment.price, 24, 20); // € 0,24 + 0,2%
|
|
104
|
+
// } else {
|
|
105
|
+
// fee = calculateFee(payment.price, 25, 150); // € 0,25 + 1,5%
|
|
106
|
+
// }
|
|
107
|
+
//
|
|
108
|
+
// const name = "Transactiekosten voor "+PaymentMethodHelper.getName(payment.method)
|
|
109
|
+
// const item = STInvoiceItem.create({
|
|
110
|
+
// name,
|
|
111
|
+
// description: "Via Buckaroo",
|
|
112
|
+
// amount: 1,
|
|
113
|
+
// unitPrice: fee,
|
|
114
|
+
// canUseCredits: false
|
|
115
|
+
// })
|
|
116
|
+
// console.log("Scheduling transaction fee charge for ", payment.id, item)
|
|
117
|
+
// await QueueHandler.schedule("billing/invoices-"+organization.id, async () => {
|
|
118
|
+
// await STPendingInvoice.addItems(organization, [item])
|
|
119
|
+
// });
|
|
120
|
+
//}
|
|
125
121
|
return;
|
|
126
122
|
}
|
|
127
123
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, PatchMap } from "@simonbackx/simple-encoding"
|
|
2
2
|
import { SimpleError } from "@simonbackx/simple-errors"
|
|
3
|
-
import { BalanceItem, Document, DocumentTemplate, EmailTemplate, Event, Group, Member, MemberWithRegistrations, Order, Organization, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
3
|
+
import { BalanceItem, Document, DocumentTemplate, EmailTemplate, Event, Group, Member, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
4
4
|
import { AccessRight, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from "@stamhoofd/structures"
|
|
5
5
|
import { Formatter } from "@stamhoofd/utility"
|
|
6
6
|
|
|
@@ -75,6 +75,11 @@ export class AdminPermissionChecker {
|
|
|
75
75
|
return result;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
async getOrganizationCurrentPeriod(id: string|Organization): Promise<OrganizationRegistrationPeriod> {
|
|
79
|
+
const organization = await this.getOrganization(id);
|
|
80
|
+
return await organization.getPeriod()
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
error(message?: string): SimpleError {
|
|
79
84
|
return new SimpleError({
|
|
80
85
|
code: "permission_denied",
|
|
@@ -171,7 +176,8 @@ export class AdminPermissionChecker {
|
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
// Check parent categories
|
|
174
|
-
const
|
|
179
|
+
const organizationPeriod = await this.getOrganizationCurrentPeriod(organization)
|
|
180
|
+
const parentCategories = group.getParentCategories(organizationPeriod.settings.categories)
|
|
175
181
|
for (const category of parentCategories) {
|
|
176
182
|
if (organizationPermissions.hasResourceAccess(PermissionsResourceType.GroupCategories, category.id, permissionLevel)) {
|
|
177
183
|
return true
|
|
@@ -677,11 +683,22 @@ export class AdminPermissionChecker {
|
|
|
677
683
|
return false;
|
|
678
684
|
}
|
|
679
685
|
|
|
680
|
-
if (
|
|
681
|
-
return
|
|
686
|
+
if (organizationPermissions.hasResourceAccessRight(PermissionsResourceType.GroupCategories, category.id, AccessRight.OrganizationCreateGroups)) {
|
|
687
|
+
return true;
|
|
682
688
|
}
|
|
683
689
|
|
|
684
|
-
|
|
690
|
+
// Check parents
|
|
691
|
+
const organization = await this.getOrganization(organizationId)
|
|
692
|
+
const organizationPeriod = await this.getOrganizationCurrentPeriod(organization)
|
|
693
|
+
const parentCategories = category.getParentCategories(organizationPeriod.settings.categories)
|
|
694
|
+
|
|
695
|
+
for (const parentCategory of parentCategories) {
|
|
696
|
+
if (organizationPermissions.hasResourceAccessRight(PermissionsResourceType.GroupCategories, parentCategory.id, AccessRight.OrganizationCreateGroups)) {
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return false;
|
|
685
702
|
}
|
|
686
703
|
|
|
687
704
|
canUpload() {
|
|
@@ -238,11 +238,12 @@ export class AuthenticatedStructures {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
-
|
|
241
|
+
member.registrations = member.registrations.filter(r => (Context.auth.organization && Context.auth.organization.active && r.organizationId === Context.auth.organization.id) || (organizations.get(r.organizationId)?.active ?? false))
|
|
242
242
|
const blob = member.getStructureWithRegistrations()
|
|
243
243
|
memberBlobs.push(
|
|
244
244
|
await Context.auth.filterMemberData(member, blob)
|
|
245
245
|
)
|
|
246
|
+
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
// Load responsibilities
|
|
@@ -268,7 +269,7 @@ export class AuthenticatedStructures {
|
|
|
268
269
|
|
|
269
270
|
return MembersBlob.create({
|
|
270
271
|
members: memberBlobs,
|
|
271
|
-
organizations: await Promise.all([...organizations.values()].map(o => this.organization(o)))
|
|
272
|
+
organizations: await Promise.all([...organizations.values()].filter(o => o.active).map(o => this.organization(o)))
|
|
272
273
|
})
|
|
273
274
|
}
|
|
274
275
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
2
|
+
import { BalanceItem, Member, MemberPlatformMembership, Platform } from "@stamhoofd/models";
|
|
3
|
+
import { SQL, SQLOrderBy, SQLWhereSign } from "@stamhoofd/sql";
|
|
4
|
+
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemType } from "@stamhoofd/structures";
|
|
5
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
6
|
+
|
|
7
|
+
export const MembershipCharger = {
|
|
8
|
+
async charge() {
|
|
9
|
+
console.log('Charging memberships...')
|
|
10
|
+
|
|
11
|
+
// Loop all
|
|
12
|
+
let lastId = "";
|
|
13
|
+
const platform = await Platform.getShared()
|
|
14
|
+
const chargeVia = platform.membershipOrganizationId
|
|
15
|
+
|
|
16
|
+
if (!chargeVia) {
|
|
17
|
+
throw new SimpleError({
|
|
18
|
+
code: 'missing_membership_organization',
|
|
19
|
+
message: 'Missing membershipOrganizationId',
|
|
20
|
+
human: 'Er is geen lokale groep verantwoordelijk voor de aanrekening van aansluitingen geconfigureerd'
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getType(id: string) {
|
|
25
|
+
return platform.config.membershipTypes.find(t => t.id === id)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let createdCount = 0;
|
|
29
|
+
let createdPrice = 0;
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line no-constant-condition
|
|
32
|
+
while (true) {
|
|
33
|
+
const memberships = await MemberPlatformMembership.select()
|
|
34
|
+
.where('id', SQLWhereSign.Greater, lastId)
|
|
35
|
+
.where('balanceItemId', null)
|
|
36
|
+
.limit(100)
|
|
37
|
+
.orderBy(
|
|
38
|
+
new SQLOrderBy({
|
|
39
|
+
column: SQL.column('id'),
|
|
40
|
+
direction: 'ASC'
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
.fetch();
|
|
44
|
+
|
|
45
|
+
if (memberships.length === 0) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const memberIds = Formatter.uniqueArray(memberships.map(m => m.memberId))
|
|
50
|
+
const members = await Member.getByIDs(...memberIds)
|
|
51
|
+
const createdBalanceItems: BalanceItem[] = []
|
|
52
|
+
|
|
53
|
+
for (const membership of memberships) {
|
|
54
|
+
// charge
|
|
55
|
+
if (membership.balanceItemId) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const type = getType(membership.membershipTypeId);
|
|
59
|
+
if (!type) {
|
|
60
|
+
console.error('Unknown membership type id ', membership.membershipTypeId)
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (membership.organizationId === chargeVia) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const member = members.find(m => m.id === membership.memberId)
|
|
69
|
+
|
|
70
|
+
if (!member) {
|
|
71
|
+
console.error('Unexpected missing member id ', membership.memberId, 'for membership', membership.id)
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const balanceItem = new BalanceItem();
|
|
76
|
+
balanceItem.unitPrice = membership.price
|
|
77
|
+
balanceItem.amount = 1
|
|
78
|
+
balanceItem.description = Formatter.dateNumber(membership.startDate, true) + " tot " + Formatter.dateNumber(membership.expireDate ?? membership.endDate, true)
|
|
79
|
+
balanceItem.relations = new Map([
|
|
80
|
+
[
|
|
81
|
+
BalanceItemRelationType.Member,
|
|
82
|
+
BalanceItemRelation.create({
|
|
83
|
+
id: member.id,
|
|
84
|
+
name: member.details.name
|
|
85
|
+
})
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
BalanceItemRelationType.MembershipType,
|
|
89
|
+
BalanceItemRelation.create({
|
|
90
|
+
id: type.id,
|
|
91
|
+
name: type.name
|
|
92
|
+
})
|
|
93
|
+
]
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
balanceItem.type = BalanceItemType.PlatformMembership
|
|
97
|
+
balanceItem.organizationId = chargeVia
|
|
98
|
+
balanceItem.payingOrganizationId = membership.organizationId
|
|
99
|
+
|
|
100
|
+
await balanceItem.save();
|
|
101
|
+
membership.balanceItemId = balanceItem.id;
|
|
102
|
+
await membership.save()
|
|
103
|
+
|
|
104
|
+
createdBalanceItems.push(balanceItem)
|
|
105
|
+
|
|
106
|
+
createdCount += 1;
|
|
107
|
+
createdPrice += membership.price
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await BalanceItem.updateOutstanding(createdBalanceItems)
|
|
111
|
+
|
|
112
|
+
if (memberships.length < 100) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const z = lastId;
|
|
117
|
+
lastId = memberships[memberships.length - 1].id;
|
|
118
|
+
|
|
119
|
+
if (lastId === z) {
|
|
120
|
+
throw new Error('Unexpected infinite loop found in MembershipCharger')
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log('Charged ' + Formatter.integer(createdCount) +' memberships, for a total value of ' + Formatter.price(createdPrice))
|
|
125
|
+
}
|
|
126
|
+
};
|