@stamhoofd/backend 2.112.0 → 2.114.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 +10 -10
- package/src/email-recipient-loaders/registrations.ts +3 -3
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -2
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +12 -6
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -0
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +73 -7
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +6 -4
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +10 -7
- package/src/excel-loaders/members.ts +1 -1
- package/src/helpers/AdminPermissionChecker.ts +5 -1
- package/src/helpers/AuthenticatedStructures.ts +69 -62
- package/src/helpers/CheckSettlements.ts +2 -2
- package/src/helpers/Context.ts +1 -1
- package/src/helpers/StripePayoutChecker.ts +5 -5
- package/src/helpers/UitpasTokenRepository.ts +6 -1
- package/src/seeds/{wip/1769088653-uitpas-status.ts → 1769088653-uitpas-status.ts} +11 -15
- package/src/services/PaymentService.ts +2 -3
- package/src/sql-filters/members.ts +35 -23
- package/src/sql-sorters/registrations.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.114.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
"@simonbackx/simple-encoding": "2.23.1",
|
|
55
55
|
"@simonbackx/simple-endpoints": "1.20.1",
|
|
56
56
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
57
|
-
"@stamhoofd/backend-i18n": "2.
|
|
58
|
-
"@stamhoofd/backend-middleware": "2.
|
|
59
|
-
"@stamhoofd/email": "2.
|
|
60
|
-
"@stamhoofd/models": "2.
|
|
61
|
-
"@stamhoofd/queues": "2.
|
|
62
|
-
"@stamhoofd/sql": "2.
|
|
63
|
-
"@stamhoofd/structures": "2.
|
|
64
|
-
"@stamhoofd/utility": "2.
|
|
57
|
+
"@stamhoofd/backend-i18n": "2.114.0",
|
|
58
|
+
"@stamhoofd/backend-middleware": "2.114.0",
|
|
59
|
+
"@stamhoofd/email": "2.114.0",
|
|
60
|
+
"@stamhoofd/models": "2.114.0",
|
|
61
|
+
"@stamhoofd/queues": "2.114.0",
|
|
62
|
+
"@stamhoofd/sql": "2.114.0",
|
|
63
|
+
"@stamhoofd/structures": "2.114.0",
|
|
64
|
+
"@stamhoofd/utility": "2.114.0",
|
|
65
65
|
"archiver": "^7.0.1",
|
|
66
66
|
"axios": "^1.13.2",
|
|
67
67
|
"cookie": "^0.7.0",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"publishConfig": {
|
|
80
80
|
"access": "public"
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "89f437d82d2c21b1741c9c7efdbdafd6c8b98318"
|
|
83
83
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Email, Member } from '@stamhoofd/models';
|
|
2
2
|
import { SQL } from '@stamhoofd/sql';
|
|
3
3
|
import { EmailRecipient, EmailRecipientFilterType, LimitedFilteredRequest, PaginatedResponse, RegistrationsBlob, mergeFilters } from '@stamhoofd/structures';
|
|
4
|
-
import { GetRegistrationsEndpoint } from '../endpoints/global/registration/GetRegistrationsEndpoint';
|
|
5
|
-
import { memberJoin } from '../sql-filters/registrations';
|
|
4
|
+
import { GetRegistrationsEndpoint } from '../endpoints/global/registration/GetRegistrationsEndpoint.js';
|
|
5
|
+
import { memberJoin } from '../sql-filters/registrations.js';
|
|
6
6
|
|
|
7
7
|
async function getRecipients(result: PaginatedResponse<RegistrationsBlob, LimitedFilteredRequest>, type: 'member' | 'parents' | 'unverified') {
|
|
8
8
|
const recipients: EmailRecipient[] = [];
|
|
@@ -81,7 +81,7 @@ Email.recipientLoaders.set(EmailRecipientFilterType.RegistrationUnverified, {
|
|
|
81
81
|
const q = (await GetRegistrationsEndpoint.buildQuery(query)).join(memberJoin);
|
|
82
82
|
|
|
83
83
|
return await q.sum(
|
|
84
|
-
SQL.jsonLength(SQL.column('details'), '$.value.unverifiedEmails'),
|
|
84
|
+
SQL.jsonLength(SQL.column(Member.table, 'details'), '$.value.unverifiedEmails'),
|
|
85
85
|
);
|
|
86
86
|
},
|
|
87
87
|
});
|
|
@@ -3,7 +3,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
3
3
|
import { Email, Platform, RateLimiter } from '@stamhoofd/models';
|
|
4
4
|
import { EmailPreview, EmailStatus, Email as EmailStruct, EmailTemplate as EmailTemplateStruct } from '@stamhoofd/structures';
|
|
5
5
|
|
|
6
|
-
import { Context } from '../../../helpers/Context';
|
|
6
|
+
import { Context } from '../../../helpers/Context.js';
|
|
7
7
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
8
8
|
|
|
9
9
|
type Params = Record<string, never>;
|
|
@@ -128,7 +128,7 @@ export class CreateEmailEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
128
128
|
|
|
129
129
|
await model.save();
|
|
130
130
|
await model.buildExampleRecipient();
|
|
131
|
-
model.updateCount();
|
|
131
|
+
await model.updateCount();
|
|
132
132
|
|
|
133
133
|
if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent || request.body.status === EmailStatus.Queued) {
|
|
134
134
|
if (!await Context.auth.canSendEmail(model)) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { Email, Platform } from '@stamhoofd/models';
|
|
3
|
-
import { EmailPreview, EmailStatus, Email as EmailStruct, PermissionLevel } from '@stamhoofd/structures';
|
|
3
|
+
import { EmailPreview, EmailRecipientsStatus, EmailStatus, Email as EmailStruct, PermissionLevel } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { AutoEncoderPatchType, Decoder, patchObject } from '@simonbackx/simple-encoding';
|
|
6
6
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
7
|
-
import { Context } from '../../../helpers/Context';
|
|
7
|
+
import { Context } from '../../../helpers/Context.js';
|
|
8
8
|
|
|
9
9
|
type Params = { id: string };
|
|
10
10
|
type Query = undefined;
|
|
@@ -124,6 +124,15 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
if (model.recipientsStatus === EmailRecipientsStatus.Created) {
|
|
128
|
+
throw new SimpleError({
|
|
129
|
+
code: 'already_created',
|
|
130
|
+
message: 'Recipients already created',
|
|
131
|
+
human: $t(`457ecdaf-d1de-4136-9e82-682c18c5fa76`),
|
|
132
|
+
statusCode: 400,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
127
136
|
model.recipientFilter = patchObject(model.recipientFilter, request.body.recipientFilter);
|
|
128
137
|
rebuild = true;
|
|
129
138
|
}
|
|
@@ -168,10 +177,7 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
168
177
|
|
|
169
178
|
if (rebuild) {
|
|
170
179
|
await model.buildExampleRecipient();
|
|
171
|
-
|
|
172
|
-
// Force null - because we have stale data
|
|
173
|
-
model.emailRecipientsCount = null;
|
|
174
|
-
model.updateCount();
|
|
180
|
+
await model.updateCount();
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent || request.body.status === EmailStatus.Queued) {
|
|
@@ -1057,6 +1057,8 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
1057
1057
|
payment.status = PaymentStatus.Created;
|
|
1058
1058
|
payment.paidAt = null;
|
|
1059
1059
|
payment.price = totalPrice;
|
|
1060
|
+
PaymentService.round(payment);
|
|
1061
|
+
totalPrice = payment.price;
|
|
1060
1062
|
|
|
1061
1063
|
if (totalPrice === 0) {
|
|
1062
1064
|
payment.status = PaymentStatus.Succeeded;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
1
|
+
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
-
import { BalanceItem, BalanceItemPayment, Payment } from '@stamhoofd/models';
|
|
4
|
+
import { BalanceItem, BalanceItemPayment, Payment, User } from '@stamhoofd/models';
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
|
-
import { PaymentGeneral, PaymentMethod, PaymentStatus, Payment as PaymentStruct, PaymentType, PermissionLevel } from '@stamhoofd/structures';
|
|
6
|
+
import { PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentStatus, Payment as PaymentStruct, PaymentType, PermissionLevel } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
|
-
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
9
|
-
import { Context } from '../../../../helpers/Context';
|
|
10
|
-
import { BalanceItemService } from '../../../../services/BalanceItemService';
|
|
11
|
-
import { PaymentService } from '../../../../services/PaymentService';
|
|
8
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
9
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
10
|
+
import { BalanceItemService } from '../../../../services/BalanceItemService.js';
|
|
11
|
+
import { PaymentService } from '../../../../services/PaymentService.js';
|
|
12
|
+
import { ViesHelper } from '../../../../helpers/ViesHelper.js';
|
|
12
13
|
|
|
13
14
|
type Params = Record<string, never>;
|
|
14
15
|
type Query = undefined;
|
|
@@ -71,6 +72,38 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
71
72
|
payment.status = PaymentStatus.Created;
|
|
72
73
|
payment.method = put.method;
|
|
73
74
|
payment.customer = put.customer;
|
|
75
|
+
|
|
76
|
+
const payingOrganizationId = put.payingOrganizationId ?? put.payingOrganization?.id ?? null;
|
|
77
|
+
|
|
78
|
+
if (payingOrganizationId) {
|
|
79
|
+
if (Context.auth.hasSomePlatformAccess()) {
|
|
80
|
+
if (await Context.auth.hasFullAccess(payingOrganizationId, PermissionLevel.Full)) {
|
|
81
|
+
payment.payingOrganizationId = payingOrganizationId;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// silently ignore
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (put.payingUserId) {
|
|
90
|
+
const user = await User.getByID(put.payingUserId);
|
|
91
|
+
if (!user) {
|
|
92
|
+
throw new SimpleError({
|
|
93
|
+
code: 'user_not_found',
|
|
94
|
+
message: 'User not found',
|
|
95
|
+
field: 'payingUserId',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (await Context.auth.canAccessUser(user, PermissionLevel.Full)) {
|
|
99
|
+
// Allowed
|
|
100
|
+
payment.payingUserId = put.payingUserId;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (put.customer?.company) {
|
|
105
|
+
await ViesHelper.checkCompany(put.customer.company, put.customer.company);
|
|
106
|
+
}
|
|
74
107
|
payment.type = put.type;
|
|
75
108
|
|
|
76
109
|
if (payment.type === PaymentType.Reallocation) {
|
|
@@ -131,6 +164,7 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
131
164
|
// Check total price
|
|
132
165
|
const totalPrice = balanceItemPayments.reduce((total, item) => total + item.price, 0);
|
|
133
166
|
payment.price = totalPrice;
|
|
167
|
+
PaymentService.round(payment);
|
|
134
168
|
|
|
135
169
|
switch (payment.type) {
|
|
136
170
|
case PaymentType.Payment: {
|
|
@@ -265,6 +299,38 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
265
299
|
payment.paidAt = patch.paidAt;
|
|
266
300
|
}
|
|
267
301
|
|
|
302
|
+
if (patch.customer) {
|
|
303
|
+
payment.customer = patchObject(payment.customer, patch.customer, { defaultValue: PaymentCustomer.create({}) });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const payingOrganizationId = patch.payingOrganizationId ?? patch.payingOrganization?.id ?? null;
|
|
307
|
+
|
|
308
|
+
if (payingOrganizationId) {
|
|
309
|
+
if (Context.auth.hasSomePlatformAccess()) {
|
|
310
|
+
if (await Context.auth.hasFullAccess(payingOrganizationId, PermissionLevel.Full)) {
|
|
311
|
+
payment.payingOrganizationId = payingOrganizationId;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// silently ignore
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (patch.payingUserId) {
|
|
320
|
+
const user = await User.getByID(patch.payingUserId);
|
|
321
|
+
if (!user) {
|
|
322
|
+
throw new SimpleError({
|
|
323
|
+
code: 'user_not_found',
|
|
324
|
+
message: 'User not found',
|
|
325
|
+
field: 'payingUserId',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (await Context.auth.canAccessUser(user, PermissionLevel.Full)) {
|
|
329
|
+
// Allowed
|
|
330
|
+
payment.payingUserId = patch.payingUserId;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
268
334
|
await payment.save();
|
|
269
335
|
|
|
270
336
|
if (patch.status) {
|
|
@@ -5,10 +5,11 @@ import { BalanceItem, BalanceItemPayment, Order, Payment, Webshop, WebshopCounte
|
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
6
|
import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, TranslatedString, Webshop as WebshopStruct, WebshopTicketType } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
|
-
import { Context } from '../../../../helpers/Context';
|
|
9
|
-
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
10
|
-
import { shouldReserveUitpasNumbers, UitpasService } from '../../../../services/uitpas/UitpasService';
|
|
11
|
-
import { ServiceFeeHelper } from '../../../../helpers/ServiceFeeHelper';
|
|
8
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
9
|
+
import { AuditLogService } from '../../../../services/AuditLogService.js';
|
|
10
|
+
import { shouldReserveUitpasNumbers, UitpasService } from '../../../../services/uitpas/UitpasService.js';
|
|
11
|
+
import { ServiceFeeHelper } from '../../../../helpers/ServiceFeeHelper.js';
|
|
12
|
+
import { PaymentService } from '../../../../services/PaymentService.js';
|
|
12
13
|
|
|
13
14
|
type Params = { id: string };
|
|
14
15
|
type Query = undefined;
|
|
@@ -154,6 +155,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
154
155
|
payment.method = struct.data.paymentMethod;
|
|
155
156
|
payment.status = PaymentStatus.Created;
|
|
156
157
|
payment.price = totalPrice;
|
|
158
|
+
PaymentService.round(payment);
|
|
157
159
|
payment.paidAt = null;
|
|
158
160
|
|
|
159
161
|
// Determine the payment provider (always null because no online payments here)
|
|
@@ -8,12 +8,13 @@ import { QueueHandler } from '@stamhoofd/queues';
|
|
|
8
8
|
import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderData, OrderResponse, Order as OrderStruct, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, TranslatedString, Version, WebshopAuthType, Webshop as WebshopStruct, WebshopTicketType } from '@stamhoofd/structures';
|
|
9
9
|
import { Formatter } from '@stamhoofd/utility';
|
|
10
10
|
|
|
11
|
-
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
12
|
-
import { Context } from '../../../helpers/Context';
|
|
13
|
-
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
14
|
-
import { AuditLogService } from '../../../services/AuditLogService';
|
|
15
|
-
import { UitpasService } from '../../../services/uitpas/UitpasService';
|
|
16
|
-
import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper';
|
|
11
|
+
import { BuckarooHelper } from '../../../helpers/BuckarooHelper.js';
|
|
12
|
+
import { Context } from '../../../helpers/Context.js';
|
|
13
|
+
import { StripeHelper } from '../../../helpers/StripeHelper.js';
|
|
14
|
+
import { AuditLogService } from '../../../services/AuditLogService.js';
|
|
15
|
+
import { UitpasService } from '../../../services/uitpas/UitpasService.js';
|
|
16
|
+
import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper.js';
|
|
17
|
+
import { PaymentService } from '../../../services/PaymentService.js';
|
|
17
18
|
|
|
18
19
|
type Params = { id: string };
|
|
19
20
|
type Query = undefined;
|
|
@@ -154,7 +155,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
// The order now is valid, the stock is reserved for now (until the payment fails or expires)
|
|
157
|
-
|
|
158
|
+
let totalPrice = request.body.totalPrice;
|
|
158
159
|
|
|
159
160
|
if (totalPrice % 100 !== 0) {
|
|
160
161
|
throw new SimpleError({
|
|
@@ -178,6 +179,8 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
178
179
|
payment.method = request.body.paymentMethod;
|
|
179
180
|
payment.status = PaymentStatus.Created;
|
|
180
181
|
payment.price = totalPrice;
|
|
182
|
+
PaymentService.round(payment);
|
|
183
|
+
totalPrice = payment.price;
|
|
181
184
|
payment.paidAt = null;
|
|
182
185
|
payment.customer = PaymentCustomer.create({
|
|
183
186
|
firstName: request.body.customer.firstName,
|
|
@@ -131,7 +131,7 @@ export const baseMemberColumns: XlsxTransformerColumn<PlatformMember>[] = [
|
|
|
131
131
|
name: $t(`030be384-9014-410c-87ba-e04920c26111`),
|
|
132
132
|
width: 20,
|
|
133
133
|
getValue: ({ patchedMember: object }: PlatformMember) => ({
|
|
134
|
-
value: XlsxTransformerColumnHelper.formatBoolean(object.details.
|
|
134
|
+
value: XlsxTransformerColumnHelper.formatBoolean(object.details.hasFinancialSupportOrActiveUitpas),
|
|
135
135
|
}),
|
|
136
136
|
},
|
|
137
137
|
{
|
|
@@ -56,7 +56,11 @@ export class AdminPermissionChecker {
|
|
|
56
56
|
if (!result) {
|
|
57
57
|
console.error('Unexpected missing organization in AdminPermissionChecker.getOrganization', id);
|
|
58
58
|
this.organizationCache.delete(id);
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
throw new SimpleError({
|
|
61
|
+
code: 'organization_not_found',
|
|
62
|
+
message: 'Organization not found',
|
|
63
|
+
});
|
|
60
64
|
}
|
|
61
65
|
this.organizationCache.set(id, result);
|
|
62
66
|
return result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { AuditLog, BalanceItem, CachedBalance, Document, Event, EventNotification, Group, Invoice, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
|
|
3
|
-
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, GroupType, InvoicedBalanceItem, InvoiceStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MembersBlob, MemberWithRegistrationsBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, RegistrationsBlob, RegistrationWithMemberBlob, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, Company, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, GroupType, InvoicedBalanceItem, InvoiceStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MembersBlob, MemberWithRegistrationsBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentCustomer, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, RegistrationsBlob, RegistrationWithMemberBlob, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
4
|
import { Sorter } from '@stamhoofd/utility';
|
|
5
5
|
|
|
6
6
|
import { SQL } from '@stamhoofd/sql';
|
|
@@ -896,10 +896,67 @@ export class AuthenticatedStructures {
|
|
|
896
896
|
|
|
897
897
|
const result: { balance: CachedBalance; object: ReceivableBalanceObject }[] = [];
|
|
898
898
|
|
|
899
|
+
function getMemberContacts(member: Member, balance: CachedBalance) {
|
|
900
|
+
const url = Context.organization && Context.organization.id === balance.organizationId ? 'https://' + Context.organization.getHost() : '';
|
|
901
|
+
return [
|
|
902
|
+
...(member.details.getMemberEmails().length
|
|
903
|
+
? [
|
|
904
|
+
ReceivableBalanceObjectContact.create({
|
|
905
|
+
firstName: member.details.firstName ?? '',
|
|
906
|
+
lastName: member.details.lastName ?? '',
|
|
907
|
+
emails: member.details.getMemberEmails(),
|
|
908
|
+
meta: {
|
|
909
|
+
type: 'member',
|
|
910
|
+
responsibilityIds: [],
|
|
911
|
+
url,
|
|
912
|
+
},
|
|
913
|
+
}),
|
|
914
|
+
]
|
|
915
|
+
: []),
|
|
916
|
+
|
|
917
|
+
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
918
|
+
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
919
|
+
firstName: p.firstName ?? '',
|
|
920
|
+
lastName: p.lastName ?? '',
|
|
921
|
+
emails: p.getEmails(),
|
|
922
|
+
meta: {
|
|
923
|
+
type: 'parent',
|
|
924
|
+
responsibilityIds: [],
|
|
925
|
+
url,
|
|
926
|
+
},
|
|
927
|
+
}))
|
|
928
|
+
: []),
|
|
929
|
+
];
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function getMemberCustomers(member: Member): PaymentCustomer[] {
|
|
933
|
+
return [
|
|
934
|
+
...(member.details.defaultAge >= 14 || member.details.parents.length === 0 || !member.details.calculatedParentsHaveAccess
|
|
935
|
+
? [
|
|
936
|
+
PaymentCustomer.create({
|
|
937
|
+
firstName: member.details.firstName ?? '',
|
|
938
|
+
lastName: member.details.lastName ?? '',
|
|
939
|
+
email: member.details.getMemberEmails()[0] ?? null,
|
|
940
|
+
phone: member.details.phone,
|
|
941
|
+
}),
|
|
942
|
+
]
|
|
943
|
+
: []),
|
|
944
|
+
|
|
945
|
+
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
946
|
+
? member.details.parents.map(parent => PaymentCustomer.create({
|
|
947
|
+
firstName: parent.firstName ?? '',
|
|
948
|
+
lastName: parent.lastName ?? '',
|
|
949
|
+
email: parent.getEmails()[0] ?? null,
|
|
950
|
+
phone: parent.phone,
|
|
951
|
+
}))
|
|
952
|
+
: []),
|
|
953
|
+
];
|
|
954
|
+
}
|
|
955
|
+
|
|
899
956
|
for (const balance of balances) {
|
|
900
957
|
let object = ReceivableBalanceObject.create({
|
|
901
958
|
id: balance.objectId,
|
|
902
|
-
name: '
|
|
959
|
+
name: $t('6c3e777c-7cd6-4566-9540-8a829c26212f'),
|
|
903
960
|
});
|
|
904
961
|
|
|
905
962
|
if (balance.objectType === ReceivableBalanceType.organization) {
|
|
@@ -920,6 +977,7 @@ export class AuthenticatedStructures {
|
|
|
920
977
|
id: balance.objectId,
|
|
921
978
|
name: organization.name,
|
|
922
979
|
uri: organization.uri,
|
|
980
|
+
customers: organization.defaultCompanies.map(company => PaymentCustomer.create({ company })),
|
|
923
981
|
contacts: thisMembers.map(({ member, responsibilities }) => ReceivableBalanceObjectContact.create({
|
|
924
982
|
firstName: member.firstName ?? '',
|
|
925
983
|
lastName: member.lastName ?? '',
|
|
@@ -936,39 +994,11 @@ export class AuthenticatedStructures {
|
|
|
936
994
|
else if (balance.objectType === ReceivableBalanceType.member) {
|
|
937
995
|
const member = members.find(m => m.id === balance.objectId) ?? null;
|
|
938
996
|
if (member) {
|
|
939
|
-
const url = Context.organization && Context.organization.id === balance.organizationId ? 'https://' + Context.organization.getHost() : '';
|
|
940
997
|
object = ReceivableBalanceObject.create({
|
|
941
998
|
id: balance.objectId,
|
|
942
999
|
name: member.details.name,
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
? [
|
|
946
|
-
ReceivableBalanceObjectContact.create({
|
|
947
|
-
firstName: member.details.firstName ?? '',
|
|
948
|
-
lastName: member.details.lastName ?? '',
|
|
949
|
-
emails: member.details.getMemberEmails(),
|
|
950
|
-
meta: {
|
|
951
|
-
type: 'member',
|
|
952
|
-
responsibilityIds: [],
|
|
953
|
-
url,
|
|
954
|
-
},
|
|
955
|
-
}),
|
|
956
|
-
]
|
|
957
|
-
: []),
|
|
958
|
-
|
|
959
|
-
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
960
|
-
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
961
|
-
firstName: p.firstName ?? '',
|
|
962
|
-
lastName: p.lastName ?? '',
|
|
963
|
-
emails: p.getEmails(),
|
|
964
|
-
meta: {
|
|
965
|
-
type: 'parent',
|
|
966
|
-
responsibilityIds: [],
|
|
967
|
-
url,
|
|
968
|
-
},
|
|
969
|
-
}))
|
|
970
|
-
: []),
|
|
971
|
-
],
|
|
1000
|
+
customers: getMemberCustomers(member),
|
|
1001
|
+
contacts: getMemberContacts(member, balance),
|
|
972
1002
|
});
|
|
973
1003
|
}
|
|
974
1004
|
}
|
|
@@ -979,39 +1009,11 @@ export class AuthenticatedStructures {
|
|
|
979
1009
|
}
|
|
980
1010
|
const member = members.find(m => m.id === registration.memberId) ?? null;
|
|
981
1011
|
if (member) {
|
|
982
|
-
const url = Context.organization && Context.organization.id === balance.organizationId ? 'https://' + Context.organization.getHost() : '';
|
|
983
1012
|
object = ReceivableBalanceObject.create({
|
|
984
1013
|
id: balance.objectId,
|
|
985
1014
|
name: member.details.name,
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
? [
|
|
989
|
-
ReceivableBalanceObjectContact.create({
|
|
990
|
-
firstName: member.details.firstName ?? '',
|
|
991
|
-
lastName: member.details.lastName ?? '',
|
|
992
|
-
emails: member.details.getMemberEmails(),
|
|
993
|
-
meta: {
|
|
994
|
-
type: 'member',
|
|
995
|
-
responsibilityIds: [],
|
|
996
|
-
url,
|
|
997
|
-
},
|
|
998
|
-
}),
|
|
999
|
-
]
|
|
1000
|
-
: []),
|
|
1001
|
-
|
|
1002
|
-
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
1003
|
-
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
1004
|
-
firstName: p.firstName ?? '',
|
|
1005
|
-
lastName: p.lastName ?? '',
|
|
1006
|
-
emails: p.getEmails(),
|
|
1007
|
-
meta: {
|
|
1008
|
-
type: 'parent',
|
|
1009
|
-
responsibilityIds: [],
|
|
1010
|
-
url,
|
|
1011
|
-
},
|
|
1012
|
-
}))
|
|
1013
|
-
: []),
|
|
1014
|
-
],
|
|
1015
|
+
customers: getMemberCustomers(member),
|
|
1016
|
+
contacts: getMemberContacts(member, balance),
|
|
1015
1017
|
});
|
|
1016
1018
|
}
|
|
1017
1019
|
}
|
|
@@ -1022,6 +1024,11 @@ export class AuthenticatedStructures {
|
|
|
1022
1024
|
object = ReceivableBalanceObject.create({
|
|
1023
1025
|
id: balance.objectId,
|
|
1024
1026
|
name: user.name || user.email,
|
|
1027
|
+
customers: [PaymentCustomer.create({
|
|
1028
|
+
firstName: user.firstName,
|
|
1029
|
+
lastName: user.lastName,
|
|
1030
|
+
email: user.email,
|
|
1031
|
+
})],
|
|
1025
1032
|
contacts: [
|
|
1026
1033
|
ReceivableBalanceObjectContact.create({
|
|
1027
1034
|
firstName: user.firstName ?? '',
|
|
@@ -2,7 +2,7 @@ import { MolliePayment, MollieToken, Order, Organization, PayconiqPayment, Payme
|
|
|
2
2
|
import { Settlement } from '@stamhoofd/structures';
|
|
3
3
|
import axios from 'axios';
|
|
4
4
|
|
|
5
|
-
import { StripePayoutChecker } from './StripePayoutChecker';
|
|
5
|
+
import { StripePayoutChecker } from './StripePayoutChecker.js';
|
|
6
6
|
|
|
7
7
|
type MollieSettlement = {
|
|
8
8
|
id: string;
|
|
@@ -179,7 +179,7 @@ async function updateSettlement(token: string, settlement: MollieSettlement, fro
|
|
|
179
179
|
id: settlement.id,
|
|
180
180
|
reference: settlement.reference,
|
|
181
181
|
settledAt: new Date(settlement.settledAt),
|
|
182
|
-
amount: Math.round(parseFloat(settlement.amount.value) * 100),
|
|
182
|
+
amount: Math.round(parseFloat(settlement.amount.value) * 100) * 100,
|
|
183
183
|
});
|
|
184
184
|
const saved = await payment.save();
|
|
185
185
|
|
package/src/helpers/Context.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
6
6
|
|
|
7
7
|
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
8
8
|
import { ApiUserRateLimits } from '@stamhoofd/structures';
|
|
9
|
-
import { AdminPermissionChecker } from './AdminPermissionChecker';
|
|
9
|
+
import { AdminPermissionChecker } from './AdminPermissionChecker.js';
|
|
10
10
|
|
|
11
11
|
export const apiUserRateLimiter = new RateLimiter({
|
|
12
12
|
limits: [
|
|
@@ -171,8 +171,8 @@ export class StripePayoutChecker {
|
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
if (payment.price !== balanceItem.amount) {
|
|
175
|
-
console.log('Amount mismatch for payment ' + payment.id + ': ' + payment.price + ' !== ' + balanceItem.amount);
|
|
174
|
+
if (payment.price !== balanceItem.amount * 100) {
|
|
175
|
+
console.log('Amount mismatch for payment ' + payment.id + ': ' + payment.price + ' !== ' + (balanceItem.amount * 100));
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -180,13 +180,13 @@ export class StripePayoutChecker {
|
|
|
180
180
|
id: payout.id,
|
|
181
181
|
reference: payout.statement_descriptor ?? '',
|
|
182
182
|
settledAt: new Date(payout.arrival_date * 1000),
|
|
183
|
-
amount: payout.amount,
|
|
183
|
+
amount: payout.amount * 100,
|
|
184
184
|
// Set only if application fee is witheld
|
|
185
|
-
fee: totalFees,
|
|
185
|
+
fee: totalFees * 100,
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
payment.settlement = settlement;
|
|
189
|
-
payment.transferFee = totalFees - payment.serviceFeePayout;
|
|
189
|
+
payment.transferFee = totalFees * 100 - payment.serviceFeePayout;
|
|
190
190
|
|
|
191
191
|
// Force an updatedAt timestamp of the related order
|
|
192
192
|
// Mark order as 'updated', or the frontend won't pull in the updates
|
|
@@ -98,7 +98,8 @@ export class UitpasTokenRepository {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
private async getNewAccessToken() {
|
|
101
|
-
|
|
101
|
+
console.log('UITPAS: Fetching new access token for', this.uitpasClientCredential.organizationId);
|
|
102
|
+
const url = STAMHOOFD.UITPAS_API_URL?.includes('test') ? 'https://account-test.uitid.be/realms/uitid/protocol/openid-connect/token' : 'https://account.uitid.be/realms/uitid/protocol/openid-connect/token';
|
|
102
103
|
const myHeaders = new Headers();
|
|
103
104
|
myHeaders.append('Content-Type', 'application/x-www-form-urlencoded');
|
|
104
105
|
const params = new URLSearchParams({
|
|
@@ -110,6 +111,7 @@ export class UitpasTokenRepository {
|
|
|
110
111
|
method: 'POST',
|
|
111
112
|
headers: myHeaders,
|
|
112
113
|
body: params.toString(),
|
|
114
|
+
signal: AbortSignal.timeout(5000),
|
|
113
115
|
};
|
|
114
116
|
const response = await fetch(url, requestOptions).catch(() => {
|
|
115
117
|
// Handle network errors
|
|
@@ -123,6 +125,7 @@ export class UitpasTokenRepository {
|
|
|
123
125
|
if (response.status === 401) {
|
|
124
126
|
// Unauthorized, credentials are invalid
|
|
125
127
|
throw new SimpleError({
|
|
128
|
+
statusCode: this.uitpasClientCredential.organizationId === null ? 500 : 400, // Internal, non visible error in case it is a built in credential
|
|
126
129
|
code: 'invalid_uitpas_client_credentials',
|
|
127
130
|
message: `Invalid UiTPAS client credentials`,
|
|
128
131
|
human: $t(`1086bb24-5df4-4faf-9dc0-ab5a955b0d8f`),
|
|
@@ -130,6 +133,7 @@ export class UitpasTokenRepository {
|
|
|
130
133
|
}
|
|
131
134
|
console.error(`Unsuccessful response when fetching UiTPAS token for organization with id ${this.uitpasClientCredential.organizationId}:`, response.statusText);
|
|
132
135
|
throw new SimpleError({
|
|
136
|
+
statusCode: this.uitpasClientCredential.organizationId === null ? 500 : 400, // Internal, non visible error in case it is a built in credential
|
|
133
137
|
code: 'unsuccessful_response_fetching_uitpas_token',
|
|
134
138
|
message: `Unsuccesful response when fetching UiTPAS token`,
|
|
135
139
|
human: $t(`dd9b30ca-860f-47aa-8cb1-527fd156d9ca`),
|
|
@@ -138,6 +142,7 @@ export class UitpasTokenRepository {
|
|
|
138
142
|
const json: unknown = await response.json().catch(() => {
|
|
139
143
|
// Handle JSON parsing errors
|
|
140
144
|
throw new SimpleError({
|
|
145
|
+
statusCode: this.uitpasClientCredential.organizationId === null ? 500 : 400, // Internal, non visible error in case it is a built in credential
|
|
141
146
|
code: 'invalid_json_fetching_uitpas_token',
|
|
142
147
|
message: `Invalid json when fetching UiTPAS token`,
|
|
143
148
|
human: $t(`8f217db0-c672-46f0-a8f7-6eba6f080947`),
|
|
@@ -5,7 +5,7 @@ import { Member } from '@stamhoofd/models';
|
|
|
5
5
|
import { SQL } from '@stamhoofd/sql';
|
|
6
6
|
import { UitpasSocialTariff, UitpasSocialTariffStatus } from '@stamhoofd/structures';
|
|
7
7
|
import { sleep } from '@stamhoofd/utility';
|
|
8
|
-
import { updateMemberDetailsUitpasNumber } from '
|
|
8
|
+
import { updateMemberDetailsUitpasNumber } from '../helpers/updateMemberDetailsUitpasNumber.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Seed to update the social tariff of all uitpas numbers.
|
|
@@ -34,17 +34,15 @@ let idOfLastUpdatedMember: string | null = null;
|
|
|
34
34
|
|
|
35
35
|
export async function migrateUitpasStatusOfAllMembers() {
|
|
36
36
|
let query = Member.select()
|
|
37
|
-
|
|
38
|
-
.where(SQL.jsonValue(SQL.column('details'), '$.value.
|
|
37
|
+
// where there is an uitpas number
|
|
38
|
+
.where(SQL.jsonValue(SQL.column('details'), '$.value.uitpasNumber'), '!=', null)
|
|
39
|
+
.orWhere(SQL.jsonValue(SQL.column('details'), '$.value.uitpasNumberDetails'), '!=', null);
|
|
39
40
|
|
|
40
41
|
if (idOfLastUpdatedMember !== null) {
|
|
41
42
|
console.log('Continue from member with id ', idOfLastUpdatedMember);
|
|
42
43
|
query = query.where('id', '>', idOfLastUpdatedMember);
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
const total = await query.clone().count();
|
|
46
|
-
|
|
47
|
-
console.log(`Start updating uitpas status of ${total} members.`);
|
|
45
|
+
console.log(`Start updating uitpas status members.`);
|
|
48
46
|
|
|
49
47
|
let c = 0;
|
|
50
48
|
|
|
@@ -93,14 +91,9 @@ async function migrateMember(member: Member) {
|
|
|
93
91
|
if (error.hasCode('https://api.publiq.be/probs/uitpas/pass-not-found') || error.hasCode('https://api.publiq.be/probs/uitpas/invalid-uitpas-number')) {
|
|
94
92
|
console.log(`Uitpas number ${member.details.uitpasNumberDetails?.uitpasNumber} is not known by the uitpas api for member with id ${member.id}.`);
|
|
95
93
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
status: UitpasSocialTariffStatus.Unknown,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// remove review
|
|
94
|
+
// remove the uitpas number
|
|
95
|
+
member.details.uitpasNumberDetails = null;
|
|
96
|
+
member.details.cleanData();
|
|
104
97
|
member.details.reviewTimes.removeReview('uitpasNumber');
|
|
105
98
|
await member.save();
|
|
106
99
|
|
|
@@ -115,6 +108,9 @@ async function migrateMember(member: Member) {
|
|
|
115
108
|
// remove the uitpas number
|
|
116
109
|
member.details.uitpasNumberDetails = null;
|
|
117
110
|
member.details.cleanData();
|
|
111
|
+
|
|
112
|
+
// remove review
|
|
113
|
+
member.details.reviewTimes.removeReview('uitpasNumber');
|
|
118
114
|
await member.save();
|
|
119
115
|
|
|
120
116
|
// do not throw
|
|
@@ -341,7 +341,7 @@ export const PaymentService = {
|
|
|
341
341
|
*
|
|
342
342
|
* TODO: update this method to generate a virtual invoice and use the price of the invoice instead of the rounded payment price, so we don't get differences in calculation
|
|
343
343
|
*/
|
|
344
|
-
|
|
344
|
+
round(payment: Payment) {
|
|
345
345
|
const amount = payment.price;
|
|
346
346
|
const rounded = Payment.roundPrice(payment.price);
|
|
347
347
|
const difference = rounded - amount;
|
|
@@ -354,10 +354,9 @@ export const PaymentService = {
|
|
|
354
354
|
throw new Error('Unexpected rounding difference of ' + difference + ' for payment ' + payment.id);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
payment.roundingAmount = difference;
|
|
358
358
|
|
|
359
359
|
// Change payment total price
|
|
360
360
|
payment.price += difference;
|
|
361
|
-
await payment.save();
|
|
362
361
|
},
|
|
363
362
|
};
|
|
@@ -68,34 +68,20 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
68
68
|
type: SQLValueType.String,
|
|
69
69
|
nullable: false,
|
|
70
70
|
}),
|
|
71
|
+
'details.uitpasNumberDetails.socialTariff.status': createColumnFilter({
|
|
72
|
+
expression: SQL.jsonExtract(SQL.column(membersTable, 'details'), '$.value.uitpasNumberDetails.socialTariff.status'),
|
|
73
|
+
type: SQLValueType.JSONString,
|
|
74
|
+
nullable: true,
|
|
75
|
+
checkPermission: async () => {
|
|
76
|
+
await throwIfNoFinancialReadAccess();
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
71
79
|
'details.requiresFinancialSupport': createColumnFilter({
|
|
72
80
|
expression: SQL.jsonExtract(SQL.column(membersTable, 'details'), '$.value.requiresFinancialSupport.value'),
|
|
73
81
|
type: SQLValueType.JSONBoolean,
|
|
74
82
|
nullable: true,
|
|
75
83
|
checkPermission: async () => {
|
|
76
|
-
|
|
77
|
-
if (!organization) {
|
|
78
|
-
if (!Context.auth.hasPlatformFullAccess()) {
|
|
79
|
-
throw new SimpleError({
|
|
80
|
-
code: 'permission_denied',
|
|
81
|
-
message: 'No permissions for financial support filter.',
|
|
82
|
-
human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
|
|
83
|
-
statusCode: 400,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const permissions = await Context.auth.getOrganizationPermissions(organization);
|
|
90
|
-
|
|
91
|
-
if (!permissions || !permissions.hasAccessRight(AccessRight.MemberReadFinancialData)) {
|
|
92
|
-
throw new SimpleError({
|
|
93
|
-
code: 'permission_denied',
|
|
94
|
-
message: 'No permissions for financial support filter (organization scope).',
|
|
95
|
-
human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
|
|
96
|
-
statusCode: 400,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
84
|
+
await throwIfNoFinancialReadAccess();
|
|
99
85
|
},
|
|
100
86
|
}),
|
|
101
87
|
'email': createColumnFilter({
|
|
@@ -512,3 +498,29 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
512
498
|
),
|
|
513
499
|
},
|
|
514
500
|
};
|
|
501
|
+
|
|
502
|
+
async function throwIfNoFinancialReadAccess() {
|
|
503
|
+
const organization = Context.organization;
|
|
504
|
+
if (!organization) {
|
|
505
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
506
|
+
throw new SimpleError({
|
|
507
|
+
code: 'permission_denied',
|
|
508
|
+
message: 'No permissions for financial support filter.',
|
|
509
|
+
human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
|
|
510
|
+
statusCode: 400,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const permissions = await Context.auth.getOrganizationPermissions(organization);
|
|
517
|
+
|
|
518
|
+
if (!permissions || !permissions.hasAccessRight(AccessRight.MemberReadFinancialData)) {
|
|
519
|
+
throw new SimpleError({
|
|
520
|
+
code: 'permission_denied',
|
|
521
|
+
message: 'No permissions for financial support filter (organization scope).',
|
|
522
|
+
human: $t(`64d658fa-0727-4924-9448-b243fe8e10a1`),
|
|
523
|
+
statusCode: 400,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
@@ -5,6 +5,7 @@ import { Formatter } from '@stamhoofd/utility';
|
|
|
5
5
|
import { memberCachedBalanceForOrganizationJoin, registrationCachedBalanceJoin } from '../helpers/outstandingBalanceJoin.js';
|
|
6
6
|
import { SQLTranslatedString } from '../helpers/SQLTranslatedString.js';
|
|
7
7
|
import { groupJoin, memberJoin, organizationJoin } from '../sql-filters/registrations.js';
|
|
8
|
+
import { Context, ContextInstance } from '../helpers/Context.js';
|
|
8
9
|
|
|
9
10
|
export class RegistrationSortData {
|
|
10
11
|
readonly registration: RegistrationWithMemberBlob;
|
|
@@ -17,8 +18,11 @@ export class RegistrationSortData {
|
|
|
17
18
|
|
|
18
19
|
get organization() {
|
|
19
20
|
const organization = this.organizations.find(o => o.id === this.registration.organizationId);
|
|
21
|
+
if (!organization && ContextInstance.optional?.organization && ContextInstance.optional?.organization.id === this.registration.organizationId) {
|
|
22
|
+
return Context.organization!;
|
|
23
|
+
}
|
|
20
24
|
if (!organization) {
|
|
21
|
-
throw new Error('Organization not found for registration');
|
|
25
|
+
throw new Error('Organization not found for registration ' + this.registration.id);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
return organization;
|