@stamhoofd/backend 2.4.0 → 2.5.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/package.json +2 -2
- package/src/endpoints/admin/invoices/GetInvoicesEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +21 -8
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +3 -2
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -1
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +2 -1
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +345 -176
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +5 -5
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +87 -11
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -1
- package/src/helpers/AdminPermissionChecker.ts +6 -0
- package/src/helpers/AuthenticatedStructures.ts +86 -24
- package/src/seeds/1722344160-update-membership.ts +57 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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": "07cc26af0e3036e4ae4309a268fbf57b7f6ac2ed"
|
|
54
54
|
}
|
|
@@ -169,7 +169,7 @@ export class GetInvoicesEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
169
169
|
STInvoicePrivate.create({
|
|
170
170
|
...invoice,
|
|
171
171
|
payment: payment ? PaymentStruct.create(payment) : null,
|
|
172
|
-
organization: organization ?
|
|
172
|
+
organization: organization ? organization.getBaseStructure() : undefined,
|
|
173
173
|
settlement: payment?.settlement ?? null,
|
|
174
174
|
})
|
|
175
175
|
)
|
|
@@ -106,16 +106,29 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
106
106
|
duplicate.details.merge(member.details)
|
|
107
107
|
member = duplicate
|
|
108
108
|
|
|
109
|
-
//
|
|
109
|
+
// You need write permissions, because a user can potentially earn write permissions on a member
|
|
110
|
+
// by registering it
|
|
111
|
+
if (!await Context.auth.canAccessMember(duplicate, PermissionLevel.Write)) {
|
|
112
|
+
throw new SimpleError({
|
|
113
|
+
code: "known_member_missing_rights",
|
|
114
|
+
message: "Creating known member without sufficient access rights",
|
|
115
|
+
human: "Dit lid is al bekend in het systeem, maar je hebt er geen toegang tot. Vraag iemand met de juiste toegangsrechten om dit lid voor jou toe te voegen, of vraag het lid om zelf in te schrijven via het ledenportaal.",
|
|
116
|
+
statusCode: 400
|
|
117
|
+
})
|
|
118
|
+
}
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
if (struct.registrations.length === 0) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
// We risk creating a new member without being able to access it manually afterwards
|
|
123
|
+
|
|
124
|
+
if ((organization && !await Context.auth.hasFullAccess(organization.id)) || (!organization && !Context.auth.hasPlatformFullAccess())) {
|
|
125
|
+
throw new SimpleError({
|
|
126
|
+
code: "missing_group",
|
|
127
|
+
message: "Missing group",
|
|
128
|
+
human: "Je moet hoofdbeheerder zijn om een lid toe te voegen zonder inschrijving in het systeem",
|
|
129
|
+
statusCode: 400
|
|
130
|
+
})
|
|
131
|
+
}
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
// Throw early
|
|
@@ -223,7 +236,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
223
236
|
// Update registrations
|
|
224
237
|
for (const patchRegistration of patch.registrations.getPatches()) {
|
|
225
238
|
const registration = member.registrations.find(r => r.id === patchRegistration.id)
|
|
226
|
-
if (!registration || registration.memberId != member.id) {
|
|
239
|
+
if (!registration || registration.memberId != member.id || (!await Context.auth.canAccessRegistration(registration, PermissionLevel.Write))) {
|
|
227
240
|
throw new SimpleError({
|
|
228
241
|
code: "permission_denied",
|
|
229
242
|
message: "You don't have permissions to access this endpoint",
|
|
@@ -4,6 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
4
4
|
import { Organization } from '@stamhoofd/models';
|
|
5
5
|
import { Organization as OrganizationStruct } from "@stamhoofd/structures";
|
|
6
6
|
import { GoogleTranslateHelper } from "@stamhoofd/utility";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
|
|
7
8
|
type Params = Record<string, never>;
|
|
8
9
|
|
|
9
10
|
class Query extends AutoEncoder {
|
|
@@ -60,7 +61,7 @@ export class GetOrganizationFromDomainEndpoint extends Endpoint<Params, Query, B
|
|
|
60
61
|
statusCode: 404
|
|
61
62
|
})
|
|
62
63
|
}
|
|
63
|
-
return new Response(await organization
|
|
64
|
+
return new Response(await AuthenticatedStructures.organization(organization));
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -75,6 +76,6 @@ export class GetOrganizationFromDomainEndpoint extends Endpoint<Params, Query, B
|
|
|
75
76
|
statusCode: 404
|
|
76
77
|
})
|
|
77
78
|
}
|
|
78
|
-
return new Response(await organization
|
|
79
|
+
return new Response(await AuthenticatedStructures.organization(organization));
|
|
79
80
|
}
|
|
80
81
|
}
|
|
@@ -4,6 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
4
4
|
import { Organization } from '@stamhoofd/models';
|
|
5
5
|
import { Organization as OrganizationStruct } from "@stamhoofd/structures";
|
|
6
6
|
import { GoogleTranslateHelper } from "@stamhoofd/utility";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
|
|
7
8
|
type Params = Record<string, never>;
|
|
8
9
|
|
|
9
10
|
class Query extends AutoEncoder {
|
|
@@ -44,6 +45,6 @@ export class GetOrganizationFromUriEndpoint extends Endpoint<Params, Query, Body
|
|
|
44
45
|
statusCode: 404
|
|
45
46
|
})
|
|
46
47
|
}
|
|
47
|
-
return new Response(await organization
|
|
48
|
+
return new Response(await AuthenticatedStructures.organization(organization));
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -2,6 +2,7 @@ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-e
|
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
3
|
import { Organization } from "@stamhoofd/models";
|
|
4
4
|
import { Organization as OrganizationStruct,OrganizationSimple } from "@stamhoofd/structures";
|
|
5
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
5
6
|
|
|
6
7
|
type Params = Record<string, never>;
|
|
7
8
|
|
|
@@ -57,6 +58,6 @@ export class SearchOrganizationEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
57
58
|
if (request.request.getVersion() < 169) {
|
|
58
59
|
return new Response(organizations.map(o => OrganizationSimple.create(o)));
|
|
59
60
|
}
|
|
60
|
-
return new Response(await Promise.all(organizations.map(o =>
|
|
61
|
+
return new Response(await Promise.all(organizations.map(o => AuthenticatedStructures.organization(o))));
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -5,8 +5,8 @@ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-
|
|
|
5
5
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
6
|
import { I18n } from '@stamhoofd/backend-i18n';
|
|
7
7
|
import { Email } from '@stamhoofd/email';
|
|
8
|
-
import { BalanceItem, BalanceItemPayment, Group, Member, MolliePayment, MollieToken, PayconiqPayment, Payment, Platform, RateLimiter, Registration } from '@stamhoofd/models';
|
|
9
|
-
import { BalanceItemStatus, IDRegisterCheckout, MemberBalanceItem, MemberDetails, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PlatformFamily, RegisterItem, RegisterResponse, Version } from "@stamhoofd/structures";
|
|
8
|
+
import { BalanceItem, BalanceItemPayment, Group, Member, MemberWithRegistrations, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment, Platform, RateLimiter, Registration, User } from '@stamhoofd/models';
|
|
9
|
+
import { BalanceItemStatus, IDRegisterCheckout, MemberBalanceItem, MemberDetails, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PermissionLevel, PlatformFamily, PlatformMember, RegisterItem, RegisterResponse, Version } from "@stamhoofd/structures";
|
|
10
10
|
import { Formatter } from '@stamhoofd/utility';
|
|
11
11
|
|
|
12
12
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -66,6 +66,17 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
66
66
|
const organization = await Context.setOrganizationScope();
|
|
67
67
|
const {user} = await Context.authenticate()
|
|
68
68
|
|
|
69
|
+
if (request.body.asOrganizationId && request.body.asOrganizationId !== organization.id) {
|
|
70
|
+
if (!await Context.auth.hasFullAccess(request.body.asOrganizationId)) {
|
|
71
|
+
throw new SimpleError({
|
|
72
|
+
code: "forbidden",
|
|
73
|
+
message: "No permission to register as this organization for a different organization",
|
|
74
|
+
human: 'Je hebt niet de juiste toegangsrechten om leden in te schrijven bij een andere organisatie.',
|
|
75
|
+
statusCode: 403
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
// For non paid organizations, limit amount of tests
|
|
70
81
|
if (!organization.meta.packages.isPaid) {
|
|
71
82
|
const limiter = demoLimiter
|
|
@@ -88,12 +99,53 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
101
|
|
|
91
|
-
const
|
|
92
|
-
const
|
|
102
|
+
const memberIds = Formatter.uniqueArray(request.body.cart.items.map(i => i.memberId))
|
|
103
|
+
const members = await Member.getBlobByIds(...memberIds)
|
|
104
|
+
const groupIds = Formatter.uniqueArray(request.body.cart.items.map(i => i.groupId))
|
|
105
|
+
const groups = await Group.getByIDs(...groupIds)
|
|
106
|
+
|
|
107
|
+
for (const group of groups) {
|
|
108
|
+
if (group.organizationId !== organization.id) {
|
|
109
|
+
throw new SimpleError({
|
|
110
|
+
code: "invalid_data",
|
|
111
|
+
message: "Oeps, één of meerdere groepen waarin je probeert in te schrijven lijken niet meer te bestaan. Herlaad de pagina en probeer opnieuw."
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const member of members) {
|
|
117
|
+
if (!await Context.auth.canAccessMember(member, PermissionLevel.Write)) {
|
|
118
|
+
throw new SimpleError({
|
|
119
|
+
code: "forbidden",
|
|
120
|
+
message: "No permission to register this member",
|
|
121
|
+
human: 'Je hebt niet de juiste toegangsrechten om dit lid in te schrijven.',
|
|
122
|
+
statusCode: 403
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
93
126
|
|
|
94
127
|
const blob = await AuthenticatedStructures.membersBlob(members, true)
|
|
95
|
-
const
|
|
96
|
-
|
|
128
|
+
const platformMembers: PlatformMember[] = []
|
|
129
|
+
|
|
130
|
+
if (request.body.asOrganizationId) {
|
|
131
|
+
const _m = PlatformFamily.createSingles(blob, {
|
|
132
|
+
platform: await Platform.getSharedStruct(),
|
|
133
|
+
contextOrganization: await AuthenticatedStructures.organization(organization)
|
|
134
|
+
})
|
|
135
|
+
platformMembers.push(..._m)
|
|
136
|
+
} else {
|
|
137
|
+
const family = PlatformFamily.create(blob, {
|
|
138
|
+
platform: await Platform.getSharedStruct(),
|
|
139
|
+
contextOrganization: await AuthenticatedStructures.organization(organization)
|
|
140
|
+
})
|
|
141
|
+
platformMembers.push(...family.members)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const checkout = request.body.hydrate({
|
|
145
|
+
members: platformMembers,
|
|
146
|
+
groups: await AuthenticatedStructures.groups(groups),
|
|
147
|
+
organizations: [await AuthenticatedStructures.organization(organization)]
|
|
148
|
+
})
|
|
97
149
|
|
|
98
150
|
const registrations: RegistrationWithMemberAndGroup[] = []
|
|
99
151
|
const payRegistrations: {registration: RegistrationWithMemberAndGroup, item: RegisterItem}[] = []
|
|
@@ -108,9 +160,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
108
160
|
// Update occupancies
|
|
109
161
|
// TODO: might not be needed in the future (for performance)
|
|
110
162
|
for (const group of groups) {
|
|
111
|
-
|
|
112
|
-
await group.updateOccupancy()
|
|
113
|
-
}
|
|
163
|
+
await group.updateOccupancy()
|
|
114
164
|
}
|
|
115
165
|
|
|
116
166
|
// Validate balance items (can only happen serverside)
|
|
@@ -135,6 +185,13 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
135
185
|
checkout.updatePrices()
|
|
136
186
|
|
|
137
187
|
const totalPrice = checkout.totalPrice
|
|
188
|
+
|
|
189
|
+
if (totalPrice !== request.body.totalPrice) {
|
|
190
|
+
throw new SimpleError({
|
|
191
|
+
code: "changed_price",
|
|
192
|
+
message: "Oeps! De prijs is gewijzigd terwijl je aan het afrekenen was (naar "+Formatter.price(totalPrice)+"). Herlaad de pagina even om ervoor te zorgen dat je alle aangepaste prijzen ziet. Contacteer de webmaster als je dit probleem blijft ondervinden na het te herladen."
|
|
193
|
+
})
|
|
194
|
+
}
|
|
138
195
|
|
|
139
196
|
if (totalPrice < 0) {
|
|
140
197
|
throw new SimpleError({
|
|
@@ -146,7 +203,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
146
203
|
const registrationMemberRelation = new ManyToOneRelation(Member, "member")
|
|
147
204
|
registrationMemberRelation.foreignKey = "memberId"
|
|
148
205
|
|
|
149
|
-
|
|
206
|
+
for (const item of checkout.cart.items) {
|
|
150
207
|
const member = members.find(m => m.id == item.memberId)
|
|
151
208
|
if (!member) {
|
|
152
209
|
throw new SimpleError({
|
|
@@ -171,18 +228,13 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
171
228
|
registration = existingRegistration
|
|
172
229
|
.setRelation(registrationMemberRelation, member as Member)
|
|
173
230
|
.setRelation(Registration.group, group)
|
|
231
|
+
|
|
174
232
|
|
|
175
|
-
if (existingRegistration.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!existingRegistration.waitingList && existingRegistration.registeredAt !== null) {
|
|
183
|
-
// already registered, no need to put it on the waiting list or register (and pay) again
|
|
184
|
-
registrations.push(registration)
|
|
185
|
-
continue mainLoop;
|
|
233
|
+
if (existingRegistration.registeredAt !== null) {
|
|
234
|
+
throw new SimpleError({
|
|
235
|
+
code: "already_registered",
|
|
236
|
+
message: "Dit lid is reeds ingeschreven. Herlaad de pagina en probeer opnieuw."
|
|
237
|
+
})
|
|
186
238
|
}
|
|
187
239
|
}
|
|
188
240
|
|
|
@@ -197,31 +249,29 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
197
249
|
registration.memberId = member.id
|
|
198
250
|
registration.groupId = group.id
|
|
199
251
|
registration.cycle = group.cycle
|
|
252
|
+
registration.price = item.calculatedPrice
|
|
253
|
+
|
|
254
|
+
payRegistrations.push({
|
|
255
|
+
registration,
|
|
256
|
+
item
|
|
257
|
+
});
|
|
200
258
|
|
|
201
|
-
if (item.waitingList) {
|
|
202
|
-
registration.waitingList = true
|
|
203
|
-
registration.canRegister = false
|
|
204
|
-
registration.reservedUntil = null
|
|
205
|
-
await registration.save()
|
|
206
|
-
} else {
|
|
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
|
-
}
|
|
214
|
-
registration.price = item.calculatedPrice
|
|
215
|
-
payRegistrations.push({
|
|
216
|
-
registration,
|
|
217
|
-
item
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
259
|
registrations.push(registration)
|
|
221
260
|
}
|
|
222
261
|
|
|
262
|
+
// Who is going to pay?
|
|
263
|
+
let whoWillPay: 'member'|'organization'|'nobody' = 'member' // if this is set to 'organization', there will also be created separate balance items so the member can pay back the paying organization
|
|
264
|
+
|
|
265
|
+
if (request.body.asOrganizationId && request.body.asOrganizationId === organization.id) {
|
|
266
|
+
// Will get added to the outstanding amount of the member
|
|
267
|
+
whoWillPay = 'nobody'
|
|
268
|
+
} else if (request.body.asOrganizationId && request.body.asOrganizationId !== organization.id) {
|
|
269
|
+
// The organization will pay to the organizing organization, and it will get added to the outstanding amount of the member towards the paying organization
|
|
270
|
+
whoWillPay = 'organization'
|
|
271
|
+
}
|
|
272
|
+
|
|
223
273
|
// Validate payment method
|
|
224
|
-
if (totalPrice > 0) {
|
|
274
|
+
if (totalPrice > 0 && whoWillPay !== 'nobody') {
|
|
225
275
|
const allowedPaymentMethods = organization.meta.registrationPaymentConfiguration.paymentMethods
|
|
226
276
|
|
|
227
277
|
if (!checkout.paymentMethod || !allowedPaymentMethods.includes(checkout.paymentMethod)) {
|
|
@@ -230,116 +280,104 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
230
280
|
message: "Oeps, je hebt geen geldige betaalmethode geselecteerd. Selecteer een betaalmethode en probeer opnieuw. Herlaad de pagina indien nodig."
|
|
231
281
|
})
|
|
232
282
|
}
|
|
233
|
-
} else {
|
|
234
|
-
checkout.paymentMethod = PaymentMethod.Unknown
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const payment = new Payment()
|
|
238
|
-
payment.userId = user.id
|
|
239
|
-
payment.organizationId = organization.id
|
|
240
|
-
payment.method = checkout.paymentMethod
|
|
241
|
-
payment.status = PaymentStatus.Created
|
|
242
|
-
payment.price = totalPrice
|
|
243
|
-
payment.freeContribution = checkout.freeContribution
|
|
244
|
-
|
|
245
|
-
if (payment.method == PaymentMethod.Transfer) {
|
|
246
|
-
// remark: we cannot add the lastnames, these will get added in the frontend when it is decrypted
|
|
247
|
-
payment.transferSettings = organization.mappedTransferSettings
|
|
248
283
|
|
|
249
|
-
if (!
|
|
284
|
+
if ((checkout.paymentMethod !== PaymentMethod.Transfer && checkout.paymentMethod !== PaymentMethod.PointOfSale) && (!request.body.redirectUrl || !request.body.cancelUrl)) {
|
|
250
285
|
throw new SimpleError({
|
|
251
|
-
code:
|
|
252
|
-
message:
|
|
253
|
-
human:
|
|
286
|
+
code: 'missing_fields',
|
|
287
|
+
message: 'redirectUrl or cancelUrl is missing and is required for non-zero online payments',
|
|
288
|
+
human: 'Er is iets mis. Contacteer de webmaster.'
|
|
254
289
|
})
|
|
255
290
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
payment.generateDescription(
|
|
259
|
-
organization,
|
|
260
|
-
Formatter.groupNamesByFamily(m),
|
|
261
|
-
{
|
|
262
|
-
name: Formatter.groupNamesByFamily(m),
|
|
263
|
-
naam: Formatter.groupNamesByFamily(m),
|
|
264
|
-
email: user.email
|
|
265
|
-
}
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
payment.paidAt = null
|
|
269
|
-
|
|
270
|
-
if (totalPrice == 0) {
|
|
271
|
-
payment.status = PaymentStatus.Succeeded
|
|
272
|
-
payment.method = PaymentMethod.Unknown
|
|
273
|
-
payment.paidAt = new Date()
|
|
291
|
+
} else {
|
|
292
|
+
checkout.paymentMethod = PaymentMethod.Unknown
|
|
274
293
|
}
|
|
275
294
|
|
|
276
|
-
|
|
277
|
-
// Throws if invalid
|
|
278
|
-
const {provider, stripeAccount} = await organization.getPaymentProviderFor(payment.method, organization.privateMeta.registrationPaymentConfiguration)
|
|
279
|
-
payment.provider = provider
|
|
280
|
-
payment.stripeAccountId = stripeAccount?.id ?? null
|
|
295
|
+
console.log('Registering members using whoWillPay', whoWillPay, checkout.paymentMethod, totalPrice)
|
|
281
296
|
|
|
282
|
-
await payment.save()
|
|
283
297
|
const items: BalanceItem[] = []
|
|
284
|
-
const
|
|
298
|
+
const shouldMarkValid = whoWillPay === 'nobody' || checkout.paymentMethod === PaymentMethod.Transfer || checkout.paymentMethod === PaymentMethod.PointOfSale
|
|
285
299
|
|
|
286
300
|
// Save registrations and add extra data if needed
|
|
287
301
|
for (const bundle of payRegistrations) {
|
|
288
302
|
const registration = bundle.registration;
|
|
289
303
|
|
|
290
|
-
|
|
291
|
-
// Replaced with balance items
|
|
292
|
-
// registration.paymentId = payment.id
|
|
304
|
+
registration.reservedUntil = null
|
|
293
305
|
|
|
294
|
-
|
|
295
|
-
registration.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
} else {
|
|
300
|
-
// Reserve registration for 30 minutes (if needed)
|
|
301
|
-
const group = groups.find(g => g.id === registration.groupId)
|
|
306
|
+
if (shouldMarkValid) {
|
|
307
|
+
await registration.markValid()
|
|
308
|
+
} else {
|
|
309
|
+
// Reserve registration for 30 minutes (if needed)
|
|
310
|
+
const group = groups.find(g => g.id === registration.groupId)
|
|
302
311
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
await registration.save()
|
|
312
|
+
if (group && group.settings.maxMembers !== null) {
|
|
313
|
+
registration.reservedUntil = new Date(new Date().getTime() + 1000*60*30)
|
|
307
314
|
}
|
|
315
|
+
await registration.save()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (bundle.item.calculatedPrice === 0) {
|
|
319
|
+
continue;
|
|
308
320
|
}
|
|
309
|
-
|
|
310
|
-
await registration.save()
|
|
311
321
|
|
|
312
322
|
// Create balance item
|
|
313
323
|
const balanceItem = new BalanceItem();
|
|
314
324
|
balanceItem.registrationId = registration.id;
|
|
315
325
|
balanceItem.price = bundle.item.calculatedPrice
|
|
316
326
|
balanceItem.description = `Inschrijving ${registration.group.settings.name}`
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
balanceItem.userId = user.id
|
|
327
|
+
|
|
328
|
+
// Who needs to receive this money?
|
|
320
329
|
balanceItem.organizationId = organization.id;
|
|
321
|
-
balanceItem.status = payment.status == PaymentStatus.Succeeded ? BalanceItemStatus.Paid : (payment.method == PaymentMethod.Transfer || payment.method == PaymentMethod.PointOfSale ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden);
|
|
322
|
-
await balanceItem.save();
|
|
323
330
|
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
+
// Who is responsible for payment?
|
|
332
|
+
let balanceItem2: BalanceItem | null = null
|
|
333
|
+
if (whoWillPay === 'organization' && request.body.asOrganizationId) {
|
|
334
|
+
// Create a separate balance item for this meber to pay back the paying organization
|
|
335
|
+
// this is not yet associated with a payment but will be added to the outstanding balance of the member
|
|
336
|
+
|
|
337
|
+
balanceItem.payingOrganizationId = request.body.asOrganizationId
|
|
338
|
+
|
|
339
|
+
balanceItem2 = new BalanceItem();
|
|
340
|
+
|
|
341
|
+
// NOTE: we don't connect the registrationId here
|
|
342
|
+
// because otherwise the total price and pricePaid for the registration would be incorrect
|
|
343
|
+
//balanceItem2.registrationId = registration.id;
|
|
344
|
+
|
|
345
|
+
balanceItem2.price = bundle.item.calculatedPrice
|
|
346
|
+
balanceItem2.description = `Inschrijving ${registration.group.settings.name}`
|
|
347
|
+
|
|
348
|
+
// Who needs to receive this money?
|
|
349
|
+
balanceItem2.organizationId = request.body.asOrganizationId;
|
|
350
|
+
|
|
351
|
+
// Who is responsible for payment?
|
|
352
|
+
balanceItem2.memberId = registration.memberId;
|
|
331
353
|
|
|
354
|
+
// If the paying organization hasn't paid yet, this should be hidden and move to pending as soon as the paying organization has paid
|
|
355
|
+
balanceItem2.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden
|
|
356
|
+
await balanceItem2.save();
|
|
357
|
+
|
|
358
|
+
// do not add to items array because we don't want to add this to the payment if we create a payment
|
|
359
|
+
} else {
|
|
360
|
+
balanceItem.memberId = registration.memberId;
|
|
361
|
+
balanceItem.userId = user.id
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
balanceItem.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden
|
|
365
|
+
balanceItem.pricePaid = 0
|
|
366
|
+
|
|
367
|
+
// 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
|
|
368
|
+
balanceItem.dependingBalanceItemId = balanceItem2?.id ?? null
|
|
369
|
+
|
|
370
|
+
await balanceItem.save();
|
|
332
371
|
items.push(balanceItem)
|
|
333
|
-
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
334
372
|
}
|
|
335
373
|
|
|
336
374
|
const oldestMember = members.slice().sort((a, b) => b.details.defaultAge - a.details.defaultAge)[0]
|
|
337
|
-
if (checkout.freeContribution) {
|
|
375
|
+
if (checkout.freeContribution && !request.body.asOrganizationId) {
|
|
338
376
|
// Create balance item
|
|
339
377
|
const balanceItem = new BalanceItem();
|
|
340
378
|
balanceItem.price = checkout.freeContribution
|
|
341
379
|
balanceItem.description = `Vrije bijdrage`
|
|
342
|
-
balanceItem.pricePaid =
|
|
380
|
+
balanceItem.pricePaid = 0;
|
|
343
381
|
balanceItem.userId = user.id
|
|
344
382
|
balanceItem.organizationId = organization.id;
|
|
345
383
|
|
|
@@ -348,67 +386,72 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
348
386
|
if (oldestMember) {
|
|
349
387
|
balanceItem.memberId = oldestMember.id;
|
|
350
388
|
}
|
|
351
|
-
balanceItem.status =
|
|
389
|
+
balanceItem.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden
|
|
352
390
|
await balanceItem.save();
|
|
353
|
-
|
|
354
|
-
// Create one balance item payment to pay it in one payment
|
|
355
|
-
const balanceItemPayment = new BalanceItemPayment()
|
|
356
|
-
balanceItemPayment.balanceItemId = balanceItem.id;
|
|
357
|
-
balanceItemPayment.paymentId = payment.id;
|
|
358
|
-
balanceItemPayment.organizationId = organization.id;
|
|
359
|
-
balanceItemPayment.price = balanceItem.price;
|
|
360
|
-
await balanceItemPayment.save();
|
|
361
|
-
|
|
362
391
|
items.push(balanceItem)
|
|
363
|
-
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
364
392
|
}
|
|
365
393
|
|
|
366
|
-
if (checkout.administrationFee) {
|
|
394
|
+
if (checkout.administrationFee && whoWillPay !== 'nobody') {
|
|
367
395
|
// Create balance item
|
|
368
396
|
const balanceItem = new BalanceItem();
|
|
369
397
|
balanceItem.price = checkout.administrationFee
|
|
370
398
|
balanceItem.description = `Administratiekosten`
|
|
371
|
-
balanceItem.pricePaid =
|
|
372
|
-
balanceItem.userId = user.id
|
|
399
|
+
balanceItem.pricePaid = 0;
|
|
373
400
|
balanceItem.organizationId = organization.id;
|
|
374
401
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
balanceItem.
|
|
402
|
+
if (request.body.asOrganizationId) {
|
|
403
|
+
balanceItem.payingOrganizationId = request.body.asOrganizationId
|
|
404
|
+
} else {
|
|
405
|
+
balanceItem.userId = user.id
|
|
406
|
+
// Connect this to the oldest member
|
|
407
|
+
if (oldestMember) {
|
|
408
|
+
balanceItem.memberId = oldestMember.id;
|
|
409
|
+
}
|
|
379
410
|
}
|
|
380
|
-
balanceItem.status = payment.status == PaymentStatus.Succeeded ? BalanceItemStatus.Paid : (payment.method == PaymentMethod.Transfer || payment.method == PaymentMethod.PointOfSale ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden);
|
|
381
|
-
await balanceItem.save();
|
|
382
411
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
balanceItemPayment.balanceItemId = balanceItem.id;
|
|
386
|
-
balanceItemPayment.paymentId = payment.id;
|
|
387
|
-
balanceItemPayment.organizationId = organization.id;
|
|
388
|
-
balanceItemPayment.price = balanceItem.price;
|
|
389
|
-
await balanceItemPayment.save();
|
|
412
|
+
balanceItem.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden
|
|
413
|
+
await balanceItem.save();
|
|
390
414
|
|
|
391
415
|
items.push(balanceItem)
|
|
392
|
-
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
393
416
|
}
|
|
394
417
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
418
|
+
if (checkout.cart.balanceItems.length && whoWillPay === 'nobody') {
|
|
419
|
+
throw new Error('Not possible to pay balance items when whoWillPay is nobody')
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
let paymentUrl: string | null = null
|
|
423
|
+
let payment: Payment | null = null
|
|
424
|
+
|
|
425
|
+
if (whoWillPay !== 'nobody') {
|
|
426
|
+
const mappedBalanceItems = new Map<BalanceItem, number>()
|
|
404
427
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
428
|
+
for (const item of items) {
|
|
429
|
+
mappedBalanceItems.set(item, item.price)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
for (const item of checkout.cart.balanceItems) {
|
|
433
|
+
const balanceItem = balanceItems.find(i => i.id === item.item.id)
|
|
434
|
+
if (!balanceItem) {
|
|
435
|
+
throw new Error('Balance item not found')
|
|
436
|
+
}
|
|
437
|
+
mappedBalanceItems.set(balanceItem, item.price)
|
|
438
|
+
items.push(balanceItem)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const response = await this.createPayment({
|
|
442
|
+
balanceItems: mappedBalanceItems,
|
|
443
|
+
organization,
|
|
444
|
+
user,
|
|
445
|
+
checkout: request.body,
|
|
446
|
+
members
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
if (response) {
|
|
450
|
+
paymentUrl = response.paymentUrl
|
|
451
|
+
payment = response.payment
|
|
408
452
|
}
|
|
409
|
-
itemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
410
453
|
}
|
|
411
|
-
|
|
454
|
+
|
|
412
455
|
await ExchangePaymentEndpoint.updateOutstanding(items, organization.id)
|
|
413
456
|
|
|
414
457
|
// Update occupancy
|
|
@@ -419,8 +462,126 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
419
462
|
}
|
|
420
463
|
}
|
|
421
464
|
|
|
422
|
-
|
|
465
|
+
return new Response(RegisterResponse.create({
|
|
466
|
+
payment: payment ? PaymentStruct.create(payment) : null,
|
|
467
|
+
members: await AuthenticatedStructures.membersBlob(members),
|
|
468
|
+
registrations: registrations.map(r => Member.getRegistrationWithMemberStructure(r)),
|
|
469
|
+
paymentUrl
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async createPayment({balanceItems, organization, user, checkout, members}: {balanceItems: Map<BalanceItem, number>, organization: Organization, user: User, checkout: IDRegisterCheckout, members: MemberWithRegistrations[]}) {
|
|
474
|
+
// Calculate total price to pay
|
|
475
|
+
let totalPrice = 0
|
|
476
|
+
const payMembers: MemberWithRegistrations[] = []
|
|
477
|
+
|
|
478
|
+
for (const [balanceItem, price] of balanceItems) {
|
|
479
|
+
if (organization.id !== balanceItem.organizationId) {
|
|
480
|
+
throw new Error('Unexpected balance item from other organization')
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (price < 0 || (price > 0 && price > balanceItem.price - balanceItem.pricePaid)) {
|
|
484
|
+
throw new SimpleError({
|
|
485
|
+
code: "invalid_data",
|
|
486
|
+
message: "Oeps, het bedrag dat je probeert te betalen is ongeldig. Herlaad de pagina en probeer opnieuw."
|
|
487
|
+
})
|
|
488
|
+
}
|
|
489
|
+
totalPrice += price
|
|
490
|
+
|
|
491
|
+
if (price > 0 && balanceItem.memberId) {
|
|
492
|
+
const member = members.find(m => m.id === balanceItem.memberId)
|
|
493
|
+
if (!member) {
|
|
494
|
+
throw new SimpleError({
|
|
495
|
+
code: "invalid_data",
|
|
496
|
+
message: "Oeps, het lid dat je probeert in te schrijven konden we niet meer terugvinden. Je herlaadt best even de pagina om opnieuw te proberen."
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
payMembers.push(member)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (totalPrice < 0) {
|
|
504
|
+
throw new SimpleError({
|
|
505
|
+
code: "empty_data",
|
|
506
|
+
message: "Oeps! De totaalprijs is negatief."
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (totalPrice === 0) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (!checkout.paymentMethod || checkout.paymentMethod === PaymentMethod.Unknown) {
|
|
515
|
+
throw new SimpleError({
|
|
516
|
+
code: "invalid_data",
|
|
517
|
+
message: "Oeps, je hebt geen betaalmethode geselecteerd. Selecteer een betaalmethode en probeer opnieuw."
|
|
518
|
+
})
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const payment = new Payment()
|
|
522
|
+
payment.userId = user.id
|
|
523
|
+
|
|
524
|
+
// Who will receive this money?
|
|
525
|
+
payment.organizationId = organization.id
|
|
526
|
+
|
|
527
|
+
payment.method = checkout.paymentMethod
|
|
528
|
+
payment.status = PaymentStatus.Created
|
|
529
|
+
payment.price = totalPrice
|
|
530
|
+
|
|
423
531
|
if (payment.method == PaymentMethod.Transfer) {
|
|
532
|
+
// remark: we cannot add the lastnames, these will get added in the frontend when it is decrypted
|
|
533
|
+
payment.transferSettings = organization.mappedTransferSettings
|
|
534
|
+
|
|
535
|
+
if (!payment.transferSettings.iban) {
|
|
536
|
+
throw new SimpleError({
|
|
537
|
+
code: "no_iban",
|
|
538
|
+
message: "No IBAN",
|
|
539
|
+
human: "Er is geen rekeningnummer ingesteld voor overschrijvingen. Contacteer de beheerder."
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const m = payMembers.map(r => r.details)
|
|
544
|
+
payment.generateDescription(
|
|
545
|
+
organization,
|
|
546
|
+
Formatter.groupNamesByFamily(m),
|
|
547
|
+
{
|
|
548
|
+
name: Formatter.groupNamesByFamily(m),
|
|
549
|
+
naam: Formatter.groupNamesByFamily(m),
|
|
550
|
+
email: user.email
|
|
551
|
+
}
|
|
552
|
+
)
|
|
553
|
+
}
|
|
554
|
+
payment.paidAt = null
|
|
555
|
+
|
|
556
|
+
// Determine the payment provider
|
|
557
|
+
// Throws if invalid
|
|
558
|
+
const {provider, stripeAccount} = await organization.getPaymentProviderFor(payment.method, organization.privateMeta.registrationPaymentConfiguration)
|
|
559
|
+
payment.provider = provider
|
|
560
|
+
payment.stripeAccountId = stripeAccount?.id ?? null
|
|
561
|
+
|
|
562
|
+
await payment.save()
|
|
563
|
+
|
|
564
|
+
// Create balance item payments
|
|
565
|
+
const balanceItemPayments: (BalanceItemPayment & { balanceItem: BalanceItem })[] = []
|
|
566
|
+
|
|
567
|
+
for (const [balanceItem, price] of balanceItems) {
|
|
568
|
+
// Create one balance item payment to pay it in one payment
|
|
569
|
+
const balanceItemPayment = new BalanceItemPayment()
|
|
570
|
+
balanceItemPayment.balanceItemId = balanceItem.id;
|
|
571
|
+
balanceItemPayment.paymentId = payment.id;
|
|
572
|
+
balanceItemPayment.organizationId = organization.id;
|
|
573
|
+
balanceItemPayment.price = price;
|
|
574
|
+
await balanceItemPayment.save();
|
|
575
|
+
|
|
576
|
+
balanceItemPayments.push(balanceItemPayment.setRelation(BalanceItemPayment.balanceItem, balanceItem))
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const description = 'Inschrijving '+organization.name
|
|
580
|
+
|
|
581
|
+
let paymentUrl: string | null = null
|
|
582
|
+
|
|
583
|
+
// Update balance items
|
|
584
|
+
if (payment.method === PaymentMethod.Transfer) {
|
|
424
585
|
// Send a small reminder email
|
|
425
586
|
try {
|
|
426
587
|
await Registration.sendTransferEmail(user, organization, payment)
|
|
@@ -428,13 +589,20 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
428
589
|
console.error("Failed to send transfer email")
|
|
429
590
|
console.error(e)
|
|
430
591
|
}
|
|
431
|
-
}
|
|
592
|
+
} else if (payment.method !== PaymentMethod.PointOfSale) {
|
|
593
|
+
if (!checkout.redirectUrl || !checkout.cancelUrl) {
|
|
594
|
+
throw new Error('Should have been caught earlier')
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const _redirectUrl = new URL(checkout.redirectUrl)
|
|
598
|
+
_redirectUrl.searchParams.set('id', payment.id);
|
|
599
|
+
|
|
600
|
+
const _cancelUrl = new URL(checkout.cancelUrl)
|
|
601
|
+
_cancelUrl.searchParams.set('id', payment.id);
|
|
602
|
+
|
|
603
|
+
const redirectUrl = _redirectUrl.href
|
|
604
|
+
const cancelUrl = _cancelUrl.href
|
|
432
605
|
|
|
433
|
-
let paymentUrl: string | null = null
|
|
434
|
-
const description = 'Inschrijving '+organization.name
|
|
435
|
-
if (payment.status != PaymentStatus.Succeeded) {
|
|
436
|
-
const redirectUrl = "https://"+organization.getHost()+'/payment?id='+encodeURIComponent(payment.id)
|
|
437
|
-
const cancelUrl = "https://"+organization.getHost()+'/payment?id='+encodeURIComponent(payment.id) + '&cancel=true'
|
|
438
606
|
const webhookUrl = 'https://'+organization.getApiHost()+"/v"+Version+"/payments/"+encodeURIComponent(payment.id)+"?exchange=true"
|
|
439
607
|
|
|
440
608
|
if (payment.provider === PaymentProvider.Stripe) {
|
|
@@ -449,11 +617,11 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
449
617
|
user: user.id,
|
|
450
618
|
payment: payment.id
|
|
451
619
|
},
|
|
452
|
-
i18n:
|
|
453
|
-
lineItems:
|
|
620
|
+
i18n: Context.i18n,
|
|
621
|
+
lineItems: balanceItemPayments,
|
|
454
622
|
organization,
|
|
455
623
|
customer: {
|
|
456
|
-
name: user.name ??
|
|
624
|
+
name: user.name ?? payMembers[0]?.details.name ?? 'Onbekend',
|
|
457
625
|
email: user.email,
|
|
458
626
|
}
|
|
459
627
|
});
|
|
@@ -502,9 +670,9 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
502
670
|
paymentUrl = await PayconiqPayment.createPayment(payment, organization, description, redirectUrl, webhookUrl)
|
|
503
671
|
} else if (payment.provider == PaymentProvider.Buckaroo) {
|
|
504
672
|
// Increase request timeout because buckaroo is super slow (in development)
|
|
505
|
-
|
|
673
|
+
Context.request.request?.setTimeout(60 * 1000)
|
|
506
674
|
const buckaroo = new BuckarooHelper(organization.privateMeta?.buckarooSettings?.key ?? "", organization.privateMeta?.buckarooSettings?.secret ?? "", organization.privateMeta.useTestPayments ?? STAMHOOFD.environment != 'production')
|
|
507
|
-
const ip =
|
|
675
|
+
const ip = Context.request.getIP()
|
|
508
676
|
paymentUrl = await buckaroo.createPayment(payment, ip, description, redirectUrl, webhookUrl)
|
|
509
677
|
await payment.save()
|
|
510
678
|
|
|
@@ -518,11 +686,12 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
518
686
|
}
|
|
519
687
|
}
|
|
520
688
|
|
|
521
|
-
return
|
|
522
|
-
payment
|
|
523
|
-
|
|
524
|
-
|
|
689
|
+
return {
|
|
690
|
+
payment,
|
|
691
|
+
balanceItemPayments,
|
|
692
|
+
provider,
|
|
693
|
+
stripeAccount,
|
|
525
694
|
paymentUrl
|
|
526
|
-
}
|
|
695
|
+
}
|
|
527
696
|
}
|
|
528
697
|
}
|
|
@@ -78,7 +78,7 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
return new Response(GetWebshopFromDomainResult.create({
|
|
81
|
-
organization:
|
|
81
|
+
organization: organization.getBaseStructure(),
|
|
82
82
|
webshop: WebshopStruct.create(webshop)
|
|
83
83
|
}));
|
|
84
84
|
}
|
|
@@ -113,14 +113,14 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
113
113
|
if (!webshop) {
|
|
114
114
|
// Return organization, so we know the locale + can do some custom logic
|
|
115
115
|
return new Response(GetWebshopFromDomainResult.create({
|
|
116
|
-
organization:
|
|
116
|
+
organization: organization.getBaseStructure(),
|
|
117
117
|
webshop: null,
|
|
118
118
|
webshops: []
|
|
119
119
|
}));
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
return new Response(GetWebshopFromDomainResult.create({
|
|
123
|
-
organization:
|
|
123
|
+
organization: organization.getBaseStructure(),
|
|
124
124
|
webshop: WebshopStruct.create(webshop)
|
|
125
125
|
}));
|
|
126
126
|
}
|
|
@@ -156,7 +156,7 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
156
156
|
|
|
157
157
|
// Return organization, and the known webshops on this domain
|
|
158
158
|
return new Response(GetWebshopFromDomainResult.create({
|
|
159
|
-
organization:
|
|
159
|
+
organization: organization.getBaseStructure(),
|
|
160
160
|
webshop: null,
|
|
161
161
|
webshops: webshops.map(w => WebshopPreview.create(w)).filter(w => w.isClosed(0) === false).sort((a, b) => Sorter.byStringValue(a.meta.name, b.meta.name))
|
|
162
162
|
}));
|
|
@@ -180,7 +180,7 @@ export class GetWebshopFromDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
return new Response(GetWebshopFromDomainResult.create({
|
|
183
|
-
organization:
|
|
183
|
+
organization: organization.getBaseStructure(),
|
|
184
184
|
webshop: WebshopStruct.create(webshop)
|
|
185
185
|
}));
|
|
186
186
|
}
|
|
@@ -1,10 +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 } from "@stamhoofd/structures";
|
|
2
|
+
import { Group as GroupStruct, GroupPrivateSettings, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version, GroupType } from "@stamhoofd/structures";
|
|
3
3
|
|
|
4
4
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from "@simonbackx/simple-encoding";
|
|
5
5
|
import { Context } from "../../../../helpers/Context";
|
|
6
6
|
import { Group, Member, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
7
7
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
8
|
+
import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
|
|
8
9
|
|
|
9
10
|
type Params = Record<string, never>;
|
|
10
11
|
type Query = undefined;
|
|
@@ -33,13 +34,13 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
33
34
|
|
|
34
35
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
35
36
|
const organization = await Context.setOrganizationScope();
|
|
36
|
-
|
|
37
|
+
await Context.authenticate()
|
|
37
38
|
|
|
38
39
|
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
39
40
|
throw Context.auth.error()
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
+
const periods: OrganizationRegistrationPeriod[] = [];
|
|
43
44
|
|
|
44
45
|
for (const {put} of request.body.getPuts()) {
|
|
45
46
|
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
@@ -70,7 +71,7 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
70
71
|
// Delete unreachable categories first
|
|
71
72
|
await organizationPeriod.cleanCategories(groups);
|
|
72
73
|
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
73
|
-
|
|
74
|
+
periods.push(organizationPeriod);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
for (const patch of request.body.getPatches()) {
|
|
@@ -137,22 +138,20 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
137
138
|
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(struct)
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
const period = await RegistrationPeriod.getByID(organizationPeriod.periodId);
|
|
141
|
-
const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
|
|
142
141
|
|
|
143
142
|
if (deleteUnreachable) {
|
|
143
|
+
const groups = await Group.getAll(organization.id, organizationPeriod.periodId)
|
|
144
|
+
|
|
144
145
|
// Delete unreachable categories first
|
|
145
146
|
await organizationPeriod.cleanCategories(groups);
|
|
146
147
|
await Group.deleteUnreachable(organization.id, organizationPeriod, groups)
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
|
|
150
|
-
structs.push(organizationPeriod.getPrivateStructure(period, groups));
|
|
151
|
-
}
|
|
150
|
+
periods.push(organizationPeriod);
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
return new Response(
|
|
155
|
-
|
|
154
|
+
await AuthenticatedStructures.organizationRegistrationPeriods(periods),
|
|
156
155
|
);
|
|
157
156
|
}
|
|
158
157
|
|
|
@@ -223,10 +222,64 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
223
222
|
if (struct.defaultAgeGroupId !== undefined) {
|
|
224
223
|
model.defaultAgeGroupId = await this.validateDefaultGroupId(struct.defaultAgeGroupId)
|
|
225
224
|
}
|
|
225
|
+
|
|
226
|
+
const patch = struct;
|
|
227
|
+
if (patch.waitingList !== undefined) {
|
|
228
|
+
if (patch.waitingList === null) {
|
|
229
|
+
// delete
|
|
230
|
+
if (model.waitingListId) {
|
|
231
|
+
// for now don't delete, as waiting lists can be shared between multiple groups
|
|
232
|
+
// await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
|
|
233
|
+
model.waitingListId = null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
} else if (patch.waitingList.isPatch()) {
|
|
237
|
+
if (!model.waitingListId) {
|
|
238
|
+
throw new SimpleError({
|
|
239
|
+
code: 'invalid_field',
|
|
240
|
+
field: 'waitingList',
|
|
241
|
+
message: 'Cannot patch waiting list before it is created'
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
patch.waitingList.id = model.waitingListId
|
|
245
|
+
patch.waitingList.type = GroupType.WaitingList
|
|
246
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.waitingList)
|
|
247
|
+
} else {
|
|
248
|
+
if (model.waitingListId) {
|
|
249
|
+
// for now don't delete, as waiting lists can be shared between multiple groups
|
|
250
|
+
// await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
|
|
251
|
+
model.waitingListId = null;
|
|
252
|
+
}
|
|
253
|
+
patch.waitingList.type = GroupType.WaitingList
|
|
254
|
+
|
|
255
|
+
const existing = await Group.getByID(patch.waitingList.id)
|
|
256
|
+
if (existing) {
|
|
257
|
+
if (existing.organizationId !== model.organizationId) {
|
|
258
|
+
throw new SimpleError({
|
|
259
|
+
code: 'invalid_field',
|
|
260
|
+
field: 'waitingList',
|
|
261
|
+
message: 'Waiting list group is already used in another organization'
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
model.waitingListId = existing.id
|
|
266
|
+
} else {
|
|
267
|
+
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
268
|
+
patch.waitingList,
|
|
269
|
+
model.organizationId,
|
|
270
|
+
model.periodId
|
|
271
|
+
)
|
|
272
|
+
model.waitingListId = group.id
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
226
276
|
|
|
227
277
|
await model.updateOccupancy()
|
|
228
278
|
await model.save();
|
|
229
|
-
|
|
279
|
+
|
|
280
|
+
if (struct.deletedAt !== undefined || struct.defaultAgeGroupId !== undefined) {
|
|
281
|
+
Member.updateMembershipsForGroupId(model.id)
|
|
282
|
+
}
|
|
230
283
|
}
|
|
231
284
|
|
|
232
285
|
|
|
@@ -278,6 +331,29 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
278
331
|
})
|
|
279
332
|
}
|
|
280
333
|
|
|
334
|
+
if (struct.waitingList) {
|
|
335
|
+
const existing = await Group.getByID(struct.waitingList.id)
|
|
336
|
+
if (existing) {
|
|
337
|
+
if (existing.organizationId !== model.organizationId) {
|
|
338
|
+
throw new SimpleError({
|
|
339
|
+
code: 'invalid_field',
|
|
340
|
+
field: 'waitingList',
|
|
341
|
+
message: 'Waiting list group is already used in another organization'
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
model.waitingListId = existing.id
|
|
346
|
+
} else {
|
|
347
|
+
struct.waitingList.type = GroupType.WaitingList
|
|
348
|
+
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
349
|
+
struct.waitingList,
|
|
350
|
+
model.organizationId,
|
|
351
|
+
model.periodId
|
|
352
|
+
)
|
|
353
|
+
model.waitingListId = group.id
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
281
357
|
await model.updateOccupancy()
|
|
282
358
|
await model.save();
|
|
283
359
|
return model;
|
|
@@ -101,8 +101,9 @@ export class ExchangePaymentEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
101
101
|
|
|
102
102
|
// Prevent concurrency issues
|
|
103
103
|
await QueueHandler.schedule("balance-item-update/"+organization.id, async () => {
|
|
104
|
+
const unloaded = (await BalanceItemPayment.where({paymentId: payment.id})).map(r => r.setRelation(BalanceItemPayment.payment, payment))
|
|
104
105
|
const balanceItemPayments = await BalanceItemPayment.balanceItem.load(
|
|
105
|
-
|
|
106
|
+
unloaded
|
|
106
107
|
);
|
|
107
108
|
|
|
108
109
|
for (const balanceItemPayment of balanceItemPayments) {
|
|
@@ -224,6 +224,12 @@ export class AdminPermissionChecker {
|
|
|
224
224
|
return true
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
if (member.registrations.length === 0 && permissionLevel !== PermissionLevel.Full && (this.organization && await this.hasFullAccess(this.organization.id, PermissionLevel.Full))) {
|
|
228
|
+
// Everyone with at least full access to at least one organization can access this member
|
|
229
|
+
// This allows organizations to register new members themselves
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
227
233
|
for (const registration of member.registrations) {
|
|
228
234
|
if (await this.canAccessRegistration(registration, permissionLevel)) {
|
|
229
235
|
return true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
2
2
|
import { Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, User, Webshop } from "@stamhoofd/models";
|
|
3
3
|
import { Event as EventStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberResponsibilityRecord as MemberResponsibilityRecordStruct, MemberWithRegistrationsBlob, MembersBlob, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, PrivateWebshop, User as UserStruct, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType } from '@stamhoofd/structures';
|
|
4
5
|
|
|
5
6
|
import { Context } from "./Context";
|
|
6
7
|
import { Formatter } from "@stamhoofd/utility";
|
|
@@ -52,10 +53,71 @@ export class AuthenticatedStructures {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
static async group(group: Group) {
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
return (await this.groups([group]))[0]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static async groups(groups: Group[]) {
|
|
60
|
+
const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null) as string[])
|
|
61
|
+
const waitingLists = waitingListIds.length > 0 ? await Group.getByIDs(...waitingListIds) : []
|
|
62
|
+
|
|
63
|
+
const structs: GroupStruct[] = []
|
|
64
|
+
for (const group of groups) {
|
|
65
|
+
const waitingList = waitingLists.find(g => g.id == group.waitingListId) ?? null
|
|
66
|
+
const waitingListStruct = waitingList ? GroupStruct.create(waitingList) : null
|
|
67
|
+
if (waitingList && waitingListStruct && !await Context.optionalAuth?.canAccessGroup(waitingList)) {
|
|
68
|
+
waitingListStruct.privateSettings = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const struct = GroupStruct.create({
|
|
72
|
+
...group,
|
|
73
|
+
waitingList: waitingListStruct
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if (!await Context.optionalAuth?.canAccessGroup(group)) {
|
|
77
|
+
struct.privateSettings = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
structs.push(struct)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return structs;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static async organizationRegistrationPeriods(organizationRegistrationPeriods: OrganizationRegistrationPeriod[]) {
|
|
87
|
+
if (organizationRegistrationPeriods.length === 0) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const periodIds = Formatter.uniqueArray(organizationRegistrationPeriods.map(p => p.periodId))
|
|
92
|
+
const periods = await RegistrationPeriod.getByIDs(...periodIds)
|
|
93
|
+
|
|
94
|
+
const groupIds = Formatter.uniqueArray(organizationRegistrationPeriods.flatMap(p => p.settings.categories.flatMap(c => c.groupIds)))
|
|
95
|
+
const groups = groupIds.length ? await Group.getByIDs(...groupIds) : []
|
|
96
|
+
|
|
97
|
+
const groupStructs = await this.groups(groups)
|
|
98
|
+
|
|
99
|
+
const structs: OrganizationRegistrationPeriodStruct[] = []
|
|
100
|
+
for (const organizationPeriod of organizationRegistrationPeriods) {
|
|
101
|
+
const period = periods.find(p => p.id == organizationPeriod.periodId) ?? null
|
|
102
|
+
if (!period) {
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
const groupIds = Formatter.uniqueArray(organizationPeriod.settings.categories.flatMap(c => c.groupIds))
|
|
106
|
+
|
|
107
|
+
structs.push(
|
|
108
|
+
OrganizationRegistrationPeriodStruct.create({
|
|
109
|
+
...organizationPeriod,
|
|
110
|
+
period: period.getStructure(),
|
|
111
|
+
groups: groupStructs.filter(gg => groupIds.includes(gg.id)).sort(GroupStruct.defaultSort)
|
|
112
|
+
})
|
|
113
|
+
)
|
|
57
114
|
}
|
|
58
|
-
|
|
115
|
+
|
|
116
|
+
return structs
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static async organizationRegistrationPeriod(organizationRegistrationPeriod: OrganizationRegistrationPeriod) {
|
|
120
|
+
return (await this.organizationRegistrationPeriods([organizationRegistrationPeriod]))[0]
|
|
59
121
|
}
|
|
60
122
|
|
|
61
123
|
static async webshop(webshop: Webshop) {
|
|
@@ -66,6 +128,8 @@ export class AuthenticatedStructures {
|
|
|
66
128
|
}
|
|
67
129
|
|
|
68
130
|
static async organization(organization: Organization): Promise<OrganizationStruct> {
|
|
131
|
+
const organizationPeriod = await organization.getPeriod()
|
|
132
|
+
|
|
69
133
|
if (await Context.optionalAuth?.canAccessPrivateOrganizationData(organization)) {
|
|
70
134
|
const webshops = await Webshop.where({ organizationId: organization.id }, { select: Webshop.selectColumnsWithout(undefined, "products", "categories")})
|
|
71
135
|
const webshopStructures: WebshopPreview[] = []
|
|
@@ -77,35 +141,29 @@ export class AuthenticatedStructures {
|
|
|
77
141
|
webshopStructures.push(WebshopPreview.create(w))
|
|
78
142
|
}
|
|
79
143
|
|
|
80
|
-
const {groups, organizationPeriod, period} = await organization.getPeriod({emptyGroups: false})
|
|
81
|
-
|
|
82
144
|
return OrganizationStruct.create({
|
|
83
|
-
|
|
84
|
-
name: organization.name,
|
|
85
|
-
meta: organization.meta,
|
|
86
|
-
address: organization.address,
|
|
87
|
-
registerDomain: organization.registerDomain,
|
|
88
|
-
uri: organization.uri,
|
|
89
|
-
website: organization.website,
|
|
145
|
+
...organization.getBaseStructure(),
|
|
90
146
|
privateMeta: organization.privateMeta,
|
|
91
147
|
webshops: webshopStructures,
|
|
92
|
-
|
|
93
|
-
period: organizationPeriod.getPrivateStructure(period, groups)
|
|
148
|
+
period: await this.organizationRegistrationPeriod(organizationPeriod)
|
|
94
149
|
})
|
|
95
150
|
}
|
|
96
151
|
|
|
97
|
-
return
|
|
152
|
+
return OrganizationStruct.create({
|
|
153
|
+
...organization.getBaseStructure(),
|
|
154
|
+
period: await this.organizationRegistrationPeriod(organizationPeriod)
|
|
155
|
+
})
|
|
98
156
|
}
|
|
99
157
|
|
|
100
158
|
static async adminOrganizations(organizations: Organization[]): Promise<OrganizationStruct[]> {
|
|
101
159
|
const structs: OrganizationStruct[] = [];
|
|
102
160
|
|
|
103
161
|
for (const organization of organizations) {
|
|
104
|
-
const base =
|
|
162
|
+
const base = organization.getBaseStructure()
|
|
105
163
|
structs.push(base)
|
|
106
164
|
}
|
|
107
165
|
|
|
108
|
-
return structs
|
|
166
|
+
return Promise.resolve(structs)
|
|
109
167
|
}
|
|
110
168
|
|
|
111
169
|
static async userWithMembers(user: User): Promise<UserWithMembers> {
|
|
@@ -114,7 +172,9 @@ export class AuthenticatedStructures {
|
|
|
114
172
|
return UserWithMembers.create({
|
|
115
173
|
...user,
|
|
116
174
|
hasAccount: user.hasAccount(),
|
|
117
|
-
|
|
175
|
+
|
|
176
|
+
// Always include the current context organization - because it is possible we switch organization and we don't want to refetch every time
|
|
177
|
+
members: await this.membersBlob(members, true, user)
|
|
118
178
|
})
|
|
119
179
|
}
|
|
120
180
|
|
|
@@ -207,17 +267,19 @@ export class AuthenticatedStructures {
|
|
|
207
267
|
// Load groups
|
|
208
268
|
const groupIds = events.map(e => e.groupId).filter(id => id !== null) as string[]
|
|
209
269
|
const groups = groupIds.length > 0 ? await Group.getByIDs(...groupIds) : []
|
|
270
|
+
const groupStructs = await this.groups(groups)
|
|
210
271
|
|
|
211
272
|
const result: EventStruct[] = []
|
|
212
273
|
|
|
213
274
|
for (const event of events) {
|
|
214
|
-
const group =
|
|
275
|
+
const group = groupStructs.find(g => g.id == event.groupId) ?? null
|
|
215
276
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
277
|
+
const struct = EventStruct.create({
|
|
278
|
+
...event,
|
|
279
|
+
group
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
result.push(struct)
|
|
221
283
|
}
|
|
222
284
|
|
|
223
285
|
return result
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { Member } from '@stamhoofd/models';
|
|
3
|
+
|
|
4
|
+
export default new Migration(async () => {
|
|
5
|
+
if (STAMHOOFD.environment == "test") {
|
|
6
|
+
console.log("skipped in tests")
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if(STAMHOOFD.userMode !== "platform") {
|
|
11
|
+
console.log("skipped seed update-membership because usermode not platform")
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
process.stdout.write('\n');
|
|
16
|
+
let c = 0;
|
|
17
|
+
let id: string = '';
|
|
18
|
+
|
|
19
|
+
while(true) {
|
|
20
|
+
const rawMembers = await Member.where({
|
|
21
|
+
id: {
|
|
22
|
+
value: id,
|
|
23
|
+
sign: '>'
|
|
24
|
+
}
|
|
25
|
+
}, {limit: 100, sort: ['id']});
|
|
26
|
+
|
|
27
|
+
// const members = await Member.getByIDs(...rawMembers.map(m => m.id));
|
|
28
|
+
|
|
29
|
+
for (const member of rawMembers) {
|
|
30
|
+
const memberWithRegistrations = await Member.getWithRegistrations(member.id);
|
|
31
|
+
if(memberWithRegistrations) {
|
|
32
|
+
await memberWithRegistrations.updateMemberships();
|
|
33
|
+
await memberWithRegistrations.save();
|
|
34
|
+
} else {
|
|
35
|
+
throw new Error("Member with registrations not found: " + member.id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
c++;
|
|
39
|
+
|
|
40
|
+
if (c%1000 === 0) {
|
|
41
|
+
process.stdout.write('.');
|
|
42
|
+
}
|
|
43
|
+
if (c%10000 === 0) {
|
|
44
|
+
process.stdout.write('\n');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (rawMembers.length === 0) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
id = rawMembers[rawMembers.length - 1].id;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Do something here
|
|
56
|
+
return Promise.resolve()
|
|
57
|
+
})
|