@stamhoofd/backend 2.8.0 → 2.13.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 +3 -1
- package/package.json +11 -3
- package/src/crons.ts +3 -3
- package/src/decoders/StringArrayDecoder.ts +24 -0
- package/src/decoders/StringNullableDecoder.ts +18 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +20 -18
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +1 -0
- package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -9
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +21 -1
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +79 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +15 -62
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +2 -2
- package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +3 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +165 -35
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +20 -23
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +22 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +56 -3
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +3 -3
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +3 -3
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +292 -170
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +22 -37
- package/src/endpoints/organization/dashboard/payments/legacy/GetPaymentsEndpoint.ts +170 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +14 -4
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +12 -2
- package/src/helpers/AdminPermissionChecker.ts +95 -60
- package/src/helpers/AuthenticatedStructures.ts +16 -6
- package/src/helpers/Context.ts +21 -0
- package/src/helpers/EmailResumer.ts +22 -2
- package/src/helpers/MemberUserSyncer.ts +8 -2
- package/src/helpers/ViesHelper.ts +151 -0
- package/src/seeds/1722344160-update-membership.ts +19 -22
- package/src/seeds/1722344161-sync-member-users.ts +60 -0
- package/.env.json +0 -65
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { AutoEncoder, DateDecoder, EnumDecoder, field, IntegerDecoder, StringDecoder } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { Organization, Payment } from "@stamhoofd/models";
|
|
4
|
+
import { PaymentGeneral, PaymentMethod, PaymentProvider, PaymentStatus } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { StringArrayDecoder } from "../../../../../decoders/StringArrayDecoder";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../../../helpers/AuthenticatedStructures";
|
|
8
|
+
import { Context } from "../../../../../helpers/Context";
|
|
9
|
+
import { StringNullableDecoder } from "../../../../../decoders/StringNullableDecoder";
|
|
10
|
+
|
|
11
|
+
type Params = Record<string, never>;
|
|
12
|
+
type Body = undefined
|
|
13
|
+
type ResponseBody = PaymentGeneral[]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Query extends AutoEncoder {
|
|
17
|
+
/**
|
|
18
|
+
* Usage in combination with paidSince is special!
|
|
19
|
+
*/
|
|
20
|
+
@field({ decoder: StringDecoder, optional: true })
|
|
21
|
+
afterId?: string
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Return all payments that were paid after (and including) this date.
|
|
25
|
+
* Only returns orders **equal** to this date if afterId is not provided or if the id of those payments is also higher.
|
|
26
|
+
*/
|
|
27
|
+
@field({ decoder: DateDecoder, optional: true })
|
|
28
|
+
paidSince?: Date
|
|
29
|
+
|
|
30
|
+
@field({ decoder: DateDecoder, optional: true })
|
|
31
|
+
paidBefore?: Date
|
|
32
|
+
|
|
33
|
+
@field({ decoder: IntegerDecoder, optional: true })
|
|
34
|
+
limit?: number
|
|
35
|
+
|
|
36
|
+
@field({ decoder: new StringArrayDecoder(new EnumDecoder(PaymentMethod)), optional: true })
|
|
37
|
+
methods?: PaymentMethod[]
|
|
38
|
+
|
|
39
|
+
@field({ decoder: new StringArrayDecoder(new StringNullableDecoder(new EnumDecoder(PaymentProvider))), optional: true })
|
|
40
|
+
providers?: (PaymentProvider|null)[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
44
|
+
protected queryDecoder = Query;
|
|
45
|
+
|
|
46
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
47
|
+
if (request.method != "GET") {
|
|
48
|
+
return [false];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const params = Endpoint.parseParameters(request.url, "/organization/payments", {});
|
|
52
|
+
|
|
53
|
+
if (params) {
|
|
54
|
+
return [true, params as Params];
|
|
55
|
+
}
|
|
56
|
+
return [false];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
60
|
+
const organization = await Context.setOrganizationScope();
|
|
61
|
+
await Context.authenticate()
|
|
62
|
+
|
|
63
|
+
if (!await Context.auth.canManagePayments(organization.id)) {
|
|
64
|
+
throw Context.auth.error()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Response(
|
|
68
|
+
(await this.getPayments(organization, request.query))
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getPayments(organization: Organization, query: Query) {
|
|
73
|
+
const paidSince = query.paidSince ?? new Date(Date.now() - (24 * 60 * 60 * 1000 * 7 ))
|
|
74
|
+
paidSince.setMilliseconds(0)
|
|
75
|
+
const payments: Payment[] = []
|
|
76
|
+
|
|
77
|
+
if (query.afterId) {
|
|
78
|
+
// First return all payments with id > afterId and paidAt == paidSince
|
|
79
|
+
payments.push(...await Payment.where({
|
|
80
|
+
organizationId: organization.id,
|
|
81
|
+
paidAt: {
|
|
82
|
+
sign: '=',
|
|
83
|
+
value: paidSince
|
|
84
|
+
},
|
|
85
|
+
id: {
|
|
86
|
+
sign: '>',
|
|
87
|
+
value: query.afterId ?? ""
|
|
88
|
+
},
|
|
89
|
+
method: {
|
|
90
|
+
sign: 'IN',
|
|
91
|
+
value: query.methods ?? [PaymentMethod.Transfer]
|
|
92
|
+
},
|
|
93
|
+
provider: {
|
|
94
|
+
sign: 'IN',
|
|
95
|
+
value: query.providers ?? [null]
|
|
96
|
+
}
|
|
97
|
+
}, {
|
|
98
|
+
limit: query.limit ?? undefined,
|
|
99
|
+
sort: [{
|
|
100
|
+
column: "id",
|
|
101
|
+
direction: "ASC"
|
|
102
|
+
}]
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
payments.push(...await Payment.where({
|
|
107
|
+
organizationId: organization.id,
|
|
108
|
+
paidAt: query.paidBefore ? [{
|
|
109
|
+
sign: query.afterId ? '>' : '>=',
|
|
110
|
+
value: paidSince
|
|
111
|
+
}, {
|
|
112
|
+
sign: '<=',
|
|
113
|
+
value: query.paidBefore
|
|
114
|
+
}] : {
|
|
115
|
+
sign: query.afterId ? '>' : '>=',
|
|
116
|
+
value: paidSince
|
|
117
|
+
},
|
|
118
|
+
method: {
|
|
119
|
+
sign: 'IN',
|
|
120
|
+
value: query.methods ?? [PaymentMethod.Transfer]
|
|
121
|
+
},
|
|
122
|
+
provider: {
|
|
123
|
+
sign: 'IN',
|
|
124
|
+
value: query.providers ?? [null]
|
|
125
|
+
}
|
|
126
|
+
}, {
|
|
127
|
+
limit: query.limit ? (query.limit - payments.length) : undefined,
|
|
128
|
+
sort: [{
|
|
129
|
+
column: "paidAt",
|
|
130
|
+
direction: "ASC"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
column: "id",
|
|
134
|
+
direction: "ASC"
|
|
135
|
+
}]
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if (!query.paidSince && !query.methods && !query.providers) {
|
|
140
|
+
// Default behaviour is to return all not-paid transfer payments that are not yet paid
|
|
141
|
+
|
|
142
|
+
payments.push(...
|
|
143
|
+
await Payment.where({
|
|
144
|
+
organizationId: organization.id,
|
|
145
|
+
paidAt: null,
|
|
146
|
+
method: PaymentMethod.Transfer,
|
|
147
|
+
status: {
|
|
148
|
+
sign: '!=',
|
|
149
|
+
value: PaymentStatus.Failed
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
payments.push(...
|
|
155
|
+
await Payment.where({
|
|
156
|
+
organizationId: organization.id,
|
|
157
|
+
paidAt: null,
|
|
158
|
+
updatedAt: {
|
|
159
|
+
sign: '>',
|
|
160
|
+
value: new Date(Date.now() - (24 * 60 * 60 * 1000 * 7 ))
|
|
161
|
+
},
|
|
162
|
+
method: PaymentMethod.Transfer,
|
|
163
|
+
status: PaymentStatus.Failed
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return await AuthenticatedStructures.paymentsGeneral(payments, true)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -3,7 +3,7 @@ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-
|
|
|
3
3
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
4
4
|
import { BalanceItem, BalanceItemPayment, Order, Payment, Token, Webshop } from '@stamhoofd/models';
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
|
-
import { BalanceItemStatus, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, PrivatePayment,Webshop as WebshopStruct } from "@stamhoofd/structures";
|
|
6
|
+
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, PrivatePayment,Webshop as WebshopStruct } from "@stamhoofd/structures";
|
|
7
7
|
|
|
8
8
|
import { Context } from '../../../../helpers/Context';
|
|
9
9
|
|
|
@@ -157,11 +157,21 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
157
157
|
// Create balance item
|
|
158
158
|
const balanceItem = new BalanceItem();
|
|
159
159
|
balanceItem.orderId = order.id;
|
|
160
|
-
balanceItem.
|
|
160
|
+
balanceItem.type = BalanceItemType.Order;
|
|
161
|
+
balanceItem.unitPrice = totalPrice
|
|
161
162
|
balanceItem.description = webshop.meta.name
|
|
162
163
|
balanceItem.pricePaid = 0
|
|
163
164
|
balanceItem.organizationId = organization.id;
|
|
164
165
|
balanceItem.status = BalanceItemStatus.Pending;
|
|
166
|
+
balanceItem.relations = new Map([
|
|
167
|
+
[
|
|
168
|
+
BalanceItemRelationType.Webshop,
|
|
169
|
+
BalanceItemRelation.create({
|
|
170
|
+
id: webshop.id,
|
|
171
|
+
name: webshop.meta.name,
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
])
|
|
165
175
|
await balanceItem.save();
|
|
166
176
|
|
|
167
177
|
// Create one balance item payment to pay it in one payment
|
|
@@ -245,7 +255,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
245
255
|
const items = await BalanceItem.where({ orderId: model.id })
|
|
246
256
|
if (items.length >= 1) {
|
|
247
257
|
model.markUpdated()
|
|
248
|
-
items[0].
|
|
258
|
+
items[0].unitPrice = model.totalToPay
|
|
249
259
|
items[0].description = model.generateBalanceDescription(webshop)
|
|
250
260
|
items[0].updateStatus();
|
|
251
261
|
await items[0].save()
|
|
@@ -258,7 +268,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
258
268
|
model.markUpdated()
|
|
259
269
|
const balanceItem = new BalanceItem();
|
|
260
270
|
balanceItem.orderId = model.id;
|
|
261
|
-
balanceItem.
|
|
271
|
+
balanceItem.unitPrice = model.totalToPay
|
|
262
272
|
balanceItem.description = model.generateBalanceDescription(webshop)
|
|
263
273
|
balanceItem.pricePaid = 0
|
|
264
274
|
balanceItem.organizationId = organization.id;
|
|
@@ -6,7 +6,7 @@ import { I18n } from '@stamhoofd/backend-i18n';
|
|
|
6
6
|
import { Email } from '@stamhoofd/email';
|
|
7
7
|
import { BalanceItem, BalanceItemPayment, MolliePayment, MollieToken, Order, PayconiqPayment, Payment, RateLimiter, Webshop, WebshopDiscountCode } from '@stamhoofd/models';
|
|
8
8
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
9
|
-
import { BalanceItemStatus, Order as OrderStruct, OrderData, OrderResponse, Payment as PaymentStruct, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Version, Webshop as WebshopStruct,WebshopAuthType } from "@stamhoofd/structures";
|
|
9
|
+
import { BalanceItemStatus, Order as OrderStruct, OrderData, OrderResponse, Payment as PaymentStruct, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Version, Webshop as WebshopStruct,WebshopAuthType, BalanceItemType, BalanceItemRelationType, BalanceItemRelation } from "@stamhoofd/structures";
|
|
10
10
|
import { Formatter } from '@stamhoofd/utility';
|
|
11
11
|
|
|
12
12
|
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
@@ -182,12 +182,22 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
182
182
|
|
|
183
183
|
// Create balance item
|
|
184
184
|
const balanceItem = new BalanceItem();
|
|
185
|
+
balanceItem.type = BalanceItemType.Order;
|
|
185
186
|
balanceItem.orderId = order.id;
|
|
186
|
-
balanceItem.
|
|
187
|
+
balanceItem.unitPrice = totalPrice
|
|
187
188
|
balanceItem.description = webshop.meta.name
|
|
188
189
|
balanceItem.pricePaid = 0
|
|
189
190
|
balanceItem.organizationId = organization.id;
|
|
190
191
|
balanceItem.status = BalanceItemStatus.Hidden;
|
|
192
|
+
balanceItem.relations = new Map([
|
|
193
|
+
[
|
|
194
|
+
BalanceItemRelationType.Webshop,
|
|
195
|
+
BalanceItemRelation.create({
|
|
196
|
+
id: webshop.id,
|
|
197
|
+
name: webshop.meta.name,
|
|
198
|
+
})
|
|
199
|
+
]
|
|
200
|
+
])
|
|
191
201
|
await balanceItem.save();
|
|
192
202
|
|
|
193
203
|
// Create one balance item payment to pay it in one payment
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, PatchMap } from "@simonbackx/simple-encoding"
|
|
2
2
|
import { SimpleError } from "@simonbackx/simple-errors"
|
|
3
3
|
import { BalanceItem, Document, DocumentTemplate, EmailTemplate, Event, Group, Member, MemberWithRegistrations, Order, Organization, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
4
|
-
import { AccessRight, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from "@stamhoofd/structures"
|
|
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
|
|
|
7
7
|
/**
|
|
@@ -814,7 +814,7 @@ export class AdminPermissionChecker {
|
|
|
814
814
|
async hasFinancialMemberAccess(member: MemberWithRegistrations, level: PermissionLevel = PermissionLevel.Read): Promise<boolean> {
|
|
815
815
|
const isUserManager = this.isUserManager(member)
|
|
816
816
|
|
|
817
|
-
if (isUserManager) {
|
|
817
|
+
if (isUserManager && level === PermissionLevel.Read) {
|
|
818
818
|
return true;
|
|
819
819
|
}
|
|
820
820
|
|
|
@@ -846,6 +846,15 @@ export class AdminPermissionChecker {
|
|
|
846
846
|
continue;
|
|
847
847
|
}
|
|
848
848
|
|
|
849
|
+
if (isUserManager) {
|
|
850
|
+
// Requirements are higher: you need financial access to write your own financial
|
|
851
|
+
// data changes
|
|
852
|
+
if (permissions.hasAccessRight(AccessRight.OrganizationManagePayments)) {
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
|
|
849
858
|
if (permissions.hasAccessRight(level === PermissionLevel.Read ? AccessRight.MemberReadFinancialData : AccessRight.MemberWriteFinancialData)) {
|
|
850
859
|
return true;
|
|
851
860
|
}
|
|
@@ -854,8 +863,16 @@ export class AdminPermissionChecker {
|
|
|
854
863
|
// Platform data
|
|
855
864
|
const platformPermissions = this.platformPermissions
|
|
856
865
|
if (platformPermissions) {
|
|
857
|
-
if (
|
|
858
|
-
|
|
866
|
+
if (isUserManager) {
|
|
867
|
+
// Requirements are higher: you need financial access to write your own financial
|
|
868
|
+
// data changes
|
|
869
|
+
if (platformPermissions.hasAccessRight(AccessRight.OrganizationManagePayments)) {
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
if (platformPermissions.hasAccessRight(level === PermissionLevel.Read ? AccessRight.MemberReadFinancialData : AccessRight.MemberWriteFinancialData)) {
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
859
876
|
}
|
|
860
877
|
}
|
|
861
878
|
|
|
@@ -949,80 +966,98 @@ export class AdminPermissionChecker {
|
|
|
949
966
|
|
|
950
967
|
const hasRecordAnswers = !!data.details.recordAnswers;
|
|
951
968
|
const hasNotes = data.details.notes !== undefined;
|
|
969
|
+
const isSetFinancialSupportTrue = data.details.requiresFinancialSupport?.value === true;
|
|
970
|
+
const isUserManager = this.isUserManager(member);
|
|
952
971
|
|
|
953
|
-
if(hasRecordAnswers
|
|
954
|
-
|
|
972
|
+
if (hasRecordAnswers) {
|
|
973
|
+
if (!(data.details.recordAnswers instanceof PatchMap)) {
|
|
974
|
+
throw new SimpleError({
|
|
975
|
+
code: 'invalid_request',
|
|
976
|
+
message: 'Cannot PUT recordAnswers',
|
|
977
|
+
statusCode: 400
|
|
978
|
+
})
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const records = isUserManager ? new Set() : await this.getAccessibleRecordSet(member, PermissionLevel.Write)
|
|
955
982
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
const records = isUserManager ? new Set() : await this.getAccessibleRecordSet(member, PermissionLevel.Write)
|
|
966
|
-
|
|
967
|
-
for (const [key, value] of data.details.recordAnswers.entries()) {
|
|
968
|
-
let name: string | undefined = undefined
|
|
969
|
-
if (value) {
|
|
970
|
-
if (value.isPatch()) {
|
|
971
|
-
throw new SimpleError({
|
|
972
|
-
code: 'invalid_request',
|
|
973
|
-
message: 'Cannot PATCH a record answer object',
|
|
974
|
-
statusCode: 400
|
|
975
|
-
})
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const id = value.settings.id
|
|
979
|
-
|
|
980
|
-
if (id !== key) {
|
|
981
|
-
throw new SimpleError({
|
|
982
|
-
code: 'invalid_request',
|
|
983
|
-
message: 'Record answer key does not match record id',
|
|
984
|
-
statusCode: 400
|
|
985
|
-
})
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
name = value.settings.name
|
|
983
|
+
for (const [key, value] of data.details.recordAnswers.entries()) {
|
|
984
|
+
let name: string | undefined = undefined
|
|
985
|
+
if (value) {
|
|
986
|
+
if (value.isPatch()) {
|
|
987
|
+
throw new SimpleError({
|
|
988
|
+
code: 'invalid_request',
|
|
989
|
+
message: 'Cannot PATCH a record answer object',
|
|
990
|
+
statusCode: 400
|
|
991
|
+
})
|
|
989
992
|
}
|
|
990
993
|
|
|
991
|
-
|
|
994
|
+
const id = value.settings.id
|
|
995
|
+
|
|
996
|
+
if (id !== key) {
|
|
992
997
|
throw new SimpleError({
|
|
993
|
-
code: '
|
|
994
|
-
message:
|
|
998
|
+
code: 'invalid_request',
|
|
999
|
+
message: 'Record answer key does not match record id',
|
|
995
1000
|
statusCode: 400
|
|
996
1001
|
})
|
|
997
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
name = value.settings.name
|
|
998
1005
|
}
|
|
999
|
-
}
|
|
1000
1006
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
+
if (!isUserManager && !records.has(key)) {
|
|
1008
|
+
throw new SimpleError({
|
|
1009
|
+
code: 'permission_denied',
|
|
1010
|
+
message: `Je hebt geen toegangsrechten om het antwoord op ${name ?? 'deze vraag'} aan te passen voor dit lid`,
|
|
1011
|
+
statusCode: 400
|
|
1012
|
+
})
|
|
1013
|
+
}
|
|
1007
1014
|
}
|
|
1008
1015
|
}
|
|
1009
1016
|
|
|
1017
|
+
if (hasNotes && isUserManager) {
|
|
1018
|
+
throw new SimpleError({
|
|
1019
|
+
code: 'permission_denied',
|
|
1020
|
+
message: 'Cannot edit notes',
|
|
1021
|
+
statusCode: 400
|
|
1022
|
+
})
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1010
1025
|
// Has financial write access?
|
|
1011
1026
|
if (!await this.hasFinancialMemberAccess(member, PermissionLevel.Write)) {
|
|
1027
|
+
if (isUserManager && isSetFinancialSupportTrue) {
|
|
1028
|
+
const financialSupportSettings = this.platform.config.financialSupport;
|
|
1029
|
+
const preventSelfAssignment = financialSupportSettings?.preventSelfAssignment === true;
|
|
1030
|
+
|
|
1031
|
+
if(preventSelfAssignment) {
|
|
1032
|
+
throw new SimpleError({
|
|
1033
|
+
code: 'permission_denied',
|
|
1034
|
+
message: 'No permissions to enable financial support for your own members',
|
|
1035
|
+
human: financialSupportSettings.preventSelfAssignmentText ?? FinancialSupportSettings.defaultPreventSelfAssignmentText,
|
|
1036
|
+
statusCode: 400
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1012
1041
|
if (data.details.requiresFinancialSupport) {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1042
|
+
if (isUserManager) {
|
|
1043
|
+
// Already handled
|
|
1044
|
+
} else {
|
|
1045
|
+
throw new SimpleError({
|
|
1046
|
+
code: 'permission_denied',
|
|
1047
|
+
message: 'Je hebt geen toegangsrechten om de financiële status van dit lid aan te passen',
|
|
1048
|
+
statusCode: 400
|
|
1049
|
+
})
|
|
1050
|
+
}
|
|
1018
1051
|
}
|
|
1019
1052
|
|
|
1020
|
-
if (
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1053
|
+
if (!isUserManager) {
|
|
1054
|
+
if (data.details.uitpasNumber) {
|
|
1055
|
+
throw new SimpleError({
|
|
1056
|
+
code: 'permission_denied',
|
|
1057
|
+
message: 'Je hebt geen toegangsrechten om het UiTPAS-nummer van dit lid aan te passen',
|
|
1058
|
+
statusCode: 400
|
|
1059
|
+
})
|
|
1060
|
+
}
|
|
1026
1061
|
}
|
|
1027
1062
|
|
|
1028
1063
|
if (data.outstandingBalance) {
|
|
@@ -26,7 +26,7 @@ export class AuthenticatedStructures {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const {balanceItemPayments, balanceItems} = await Payment.loadBalanceItems(payments)
|
|
29
|
-
const {registrations, orders
|
|
29
|
+
const {registrations, orders} = await Payment.loadBalanceItemRelations(balanceItems);
|
|
30
30
|
|
|
31
31
|
if (checkPermissions) {
|
|
32
32
|
// Note: permission checking is moved here for performacne to avoid loading the data multiple times
|
|
@@ -41,13 +41,10 @@ export class AuthenticatedStructures {
|
|
|
41
41
|
|
|
42
42
|
const includeSettlements = checkPermissions && !!Context.user && !!Context.user.permissions
|
|
43
43
|
|
|
44
|
-
return
|
|
44
|
+
return Payment.getGeneralStructureFromRelations({
|
|
45
45
|
payments,
|
|
46
46
|
balanceItemPayments,
|
|
47
|
-
balanceItems
|
|
48
|
-
registrations,
|
|
49
|
-
orders,
|
|
50
|
-
groups
|
|
47
|
+
balanceItems
|
|
51
48
|
}, includeSettlements)
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -154,6 +151,19 @@ export class AuthenticatedStructures {
|
|
|
154
151
|
})
|
|
155
152
|
}
|
|
156
153
|
|
|
154
|
+
static async organizations(organizations: Organization[]): Promise<OrganizationStruct[]> {
|
|
155
|
+
// for now simple loop
|
|
156
|
+
if (organizations.length > 10) {
|
|
157
|
+
console.warn('Trying to load too many organizations at once: ' + organizations.length)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const structs: OrganizationStruct[] = [];
|
|
161
|
+
for (const organization of organizations) {
|
|
162
|
+
structs.push(await this.organization(organization))
|
|
163
|
+
}
|
|
164
|
+
return structs
|
|
165
|
+
}
|
|
166
|
+
|
|
157
167
|
static async adminOrganizations(organizations: Organization[]): Promise<OrganizationStruct[]> {
|
|
158
168
|
const structs: OrganizationStruct[] = [];
|
|
159
169
|
|
package/src/helpers/Context.ts
CHANGED
|
@@ -60,6 +60,27 @@ export class ContextInstance {
|
|
|
60
60
|
return c;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
static async startForUser<T>(user: User, organization: Organization|null, handler: () => Promise<T>): Promise<T> {
|
|
64
|
+
const request = new Request({
|
|
65
|
+
method: 'GET',
|
|
66
|
+
url: '/',
|
|
67
|
+
host: ''
|
|
68
|
+
})
|
|
69
|
+
const context = new ContextInstance(request);
|
|
70
|
+
|
|
71
|
+
if (organization) {
|
|
72
|
+
context.organization = organization
|
|
73
|
+
context.i18n.switchToLocale({ country: organization.address.country })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
context.user = user
|
|
77
|
+
context.#auth = new AdminPermissionChecker(user, await Platform.getSharedPrivateStruct(), context.organization);
|
|
78
|
+
|
|
79
|
+
return await this.asyncLocalStorage.run(context, async () => {
|
|
80
|
+
return await handler()
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
63
84
|
static async start<T>(request: Request, handler: () => Promise<T>): Promise<T> {
|
|
64
85
|
const context = new ContextInstance(request);
|
|
65
86
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Email } from "@stamhoofd/models";
|
|
1
|
+
import { Email, Organization, User } from "@stamhoofd/models";
|
|
2
2
|
import { SQL } from "@stamhoofd/sql";
|
|
3
3
|
import { EmailStatus } from "@stamhoofd/structures";
|
|
4
|
+
import { ContextInstance } from "./Context";
|
|
4
5
|
|
|
5
6
|
export async function resumeEmails() {
|
|
6
7
|
const query = SQL.select()
|
|
@@ -11,7 +12,26 @@ export async function resumeEmails() {
|
|
|
11
12
|
const emails = Email.fromRows(result, Email.table);
|
|
12
13
|
|
|
13
14
|
for (const email of emails) {
|
|
15
|
+
if (!email.userId) {
|
|
16
|
+
console.warn('Cannot retry sending email because userId is not set - which is required for setting the scope', email.id)
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
14
19
|
console.log('Resuming email that has sending status on boot', email.id);
|
|
15
|
-
|
|
20
|
+
|
|
21
|
+
const user = await User.getByID(email.userId);
|
|
22
|
+
if (!user) {
|
|
23
|
+
console.warn('Cannot retry sending email because user not found', email.id)
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const organization = email.organizationId ? (await Organization.getByID(email.organizationId)) : null;
|
|
28
|
+
if (organization === undefined) {
|
|
29
|
+
console.warn('Cannot retry sending email because organization not found', email.id)
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await ContextInstance.startForUser(user, organization, async () => {
|
|
34
|
+
await email.send()
|
|
35
|
+
})
|
|
16
36
|
}
|
|
17
37
|
}
|
|
@@ -36,8 +36,14 @@ export class MemberUserSyncerStatic {
|
|
|
36
36
|
} else {
|
|
37
37
|
// Only auto unlink users that do not have an account
|
|
38
38
|
for (const user of member.users) {
|
|
39
|
-
if (!
|
|
40
|
-
|
|
39
|
+
if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
|
|
40
|
+
if (!user.hasAccount()) {
|
|
41
|
+
await this.unlinkUser(user, member)
|
|
42
|
+
} else {
|
|
43
|
+
// Make sure only linked as a parent, not as user self
|
|
44
|
+
// This makes sure we don't inherit permissions and aren't counted as 'begin' the member
|
|
45
|
+
await this.linkUser(user.email, member, true)
|
|
46
|
+
}
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
}
|