@stamhoofd/backend 2.110.0 → 2.112.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/LICENSE.md +32 -0
- package/package.json +15 -12
- package/src/boot.ts +1 -0
- package/src/email-recipient-loaders/documents.ts +66 -0
- package/src/endpoints/auth/PatchUserEndpoint.test.ts +56 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +701 -4
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +21 -10
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +661 -4
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +17 -6
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +291 -8
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +22 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.ts +16 -18
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesCountEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +219 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
- package/src/endpoints/organization/shared/GetUitpasNumberDetailsEndpoint.ts +72 -0
- package/src/endpoints/organization/webshops/RetrieveUitpasSocialTariffPriceEndpoint.ts +3 -2
- package/src/excel-loaders/members.ts +27 -27
- package/src/helpers/AdminPermissionChecker.ts +30 -10
- package/src/helpers/AuthenticatedStructures.ts +24 -5
- package/src/helpers/StripeHelper.ts +11 -1
- package/src/helpers/StripePayoutChecker.ts +7 -0
- package/src/helpers/UitpasTokenRepository.ts +7 -5
- package/src/helpers/passthroughFetch.ts +24 -0
- package/src/helpers/updateMemberDetailsUitpasNumber.ts +149 -0
- package/src/seeds/data/default-email-templates.sql +2 -1
- package/src/seeds/wip/1769088653-uitpas-status.ts +129 -0
- package/src/services/InvoiceService.ts +114 -0
- package/src/services/uitpas/PassholderEndpoints.ts +190 -0
- package/src/services/uitpas/UitpasService.ts +37 -12
- package/src/services/uitpas/checkUitpasNumbers.ts +16 -140
- package/src/services/uitpas/handleUitpasResponse.ts +89 -0
- package/src/sql-filters/invoiced-balance-items.ts +20 -0
- package/src/sql-filters/invoices.ts +122 -0
- package/src/sql-filters/payments.ts +11 -1
- package/src/sql-sorters/invoices.ts +83 -0
- package/src/sql-sorters/payments.ts +33 -0
- package/tests/e2e/bundle-discounts.test.ts +8 -8
- package/tests/e2e/tests-disable-net-connect.test.ts +5 -0
- package/tests/helpers/StripeMocker.ts +5 -5
- package/tests/helpers/UitpasApiMocker.ts +175 -0
- package/tests/helpers/index.ts +1 -0
- package/tests/helpers/resetNock.ts +7 -0
- package/tests/init/index.ts +1 -0
- package/tests/init/initPayconiq.ts +2 -2
- package/tests/init/initStripe.ts +1 -1
- package/tests/init/initUitpasApi.ts +14 -0
- package/tests/jest.global.setup.ts +6 -4
- package/tests/jest.setup.ts +12 -6
- package/LICENSE +0 -665
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { Invoice } from '@stamhoofd/models';
|
|
5
|
+
import { applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
|
|
6
|
+
import { CountFilteredRequest, InvoiceStruct, LimitedFilteredRequest, PaginatedResponse, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
|
|
7
|
+
|
|
8
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
9
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
10
|
+
import { invoiceFilterCompilers } from '../../../../sql-filters/invoices.js';
|
|
11
|
+
import { invoiceSorters } from '../../../../sql-sorters/invoices.js';
|
|
12
|
+
|
|
13
|
+
type Params = Record<string, never>;
|
|
14
|
+
type Query = LimitedFilteredRequest;
|
|
15
|
+
type Body = undefined;
|
|
16
|
+
type ResponseBody = PaginatedResponse<InvoiceStruct[], LimitedFilteredRequest>;
|
|
17
|
+
|
|
18
|
+
const filterCompilers = invoiceFilterCompilers;
|
|
19
|
+
const sorters = invoiceSorters;
|
|
20
|
+
|
|
21
|
+
export class GetInvoicesEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
22
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>;
|
|
23
|
+
|
|
24
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
25
|
+
if (request.method !== 'GET') {
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const params = Endpoint.parseParameters(request.url, '/invoices', {});
|
|
30
|
+
|
|
31
|
+
if (params) {
|
|
32
|
+
return [true, params as Params];
|
|
33
|
+
}
|
|
34
|
+
return [false];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static async buildQuery(q: CountFilteredRequest | LimitedFilteredRequest) {
|
|
38
|
+
const organization = Context.organization;
|
|
39
|
+
let scopeFilter: StamhoofdFilter | undefined = undefined;
|
|
40
|
+
|
|
41
|
+
if (!organization) {
|
|
42
|
+
throw Context.auth.error();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!await Context.auth.canManageFinances(organization.id)) {
|
|
46
|
+
throw Context.auth.error();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
scopeFilter = {
|
|
50
|
+
organizationId: organization.id,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const query = Invoice
|
|
54
|
+
.select()
|
|
55
|
+
.setMaxExecutionTime(10 * 1000);
|
|
56
|
+
|
|
57
|
+
if (scopeFilter) {
|
|
58
|
+
query.where(await compileToSQLFilter(scopeFilter, filterCompilers));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (q.filter) {
|
|
62
|
+
query.where(await compileToSQLFilter(q.filter, filterCompilers));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (q.search) {
|
|
66
|
+
// todo
|
|
67
|
+
|
|
68
|
+
let searchFilter: StamhoofdFilter | null = null;
|
|
69
|
+
searchFilter = {
|
|
70
|
+
$or: [
|
|
71
|
+
{
|
|
72
|
+
customer: {
|
|
73
|
+
name: {
|
|
74
|
+
$contains: q.search,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
customer: {
|
|
80
|
+
company: {
|
|
81
|
+
name: {
|
|
82
|
+
$contains: q.search,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
items: {
|
|
89
|
+
$elemMatch: {
|
|
90
|
+
$or: [
|
|
91
|
+
{
|
|
92
|
+
name: {
|
|
93
|
+
$contains: q.search,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
description: {
|
|
98
|
+
$contains: q.search,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (q.search.includes('@')) {
|
|
109
|
+
searchFilter = {
|
|
110
|
+
$or: [
|
|
111
|
+
{
|
|
112
|
+
customer: {
|
|
113
|
+
email: {
|
|
114
|
+
$contains: q.search,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
customer: {
|
|
120
|
+
company: {
|
|
121
|
+
administrationEmail: {
|
|
122
|
+
$contains: q.search,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (searchFilter) {
|
|
132
|
+
query.where(await compileToSQLFilter(searchFilter, filterCompilers));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
137
|
+
if (q.pageFilter) {
|
|
138
|
+
query.where(await compileToSQLFilter(q.pageFilter, filterCompilers));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
q.sort = assertSort(q.sort, [{ key: 'id' }]);
|
|
142
|
+
applySQLSorter(query, q.sort, sorters);
|
|
143
|
+
query.limit(q.limit);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return query;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
150
|
+
const query = await this.buildQuery(requestQuery);
|
|
151
|
+
let invoices: Invoice[];
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
invoices = await query.fetch();
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error.message.includes('ER_QUERY_TIMEOUT')) {
|
|
158
|
+
throw new SimpleError({
|
|
159
|
+
code: 'timeout',
|
|
160
|
+
message: 'Query took too long',
|
|
161
|
+
human: $t(`dce51638-6129-448b-8a15-e6d778f3a76a`),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let next: LimitedFilteredRequest | undefined;
|
|
168
|
+
|
|
169
|
+
if (invoices.length >= requestQuery.limit) {
|
|
170
|
+
const lastObject = invoices[invoices.length - 1];
|
|
171
|
+
const nextFilter = getSortFilter(lastObject, sorters, requestQuery.sort);
|
|
172
|
+
|
|
173
|
+
next = new LimitedFilteredRequest({
|
|
174
|
+
filter: requestQuery.filter,
|
|
175
|
+
pageFilter: nextFilter,
|
|
176
|
+
sort: requestQuery.sort,
|
|
177
|
+
limit: requestQuery.limit,
|
|
178
|
+
search: requestQuery.search,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (JSON.stringify(nextFilter) === JSON.stringify(requestQuery.pageFilter)) {
|
|
182
|
+
console.error('Found infinite loading loop for', requestQuery);
|
|
183
|
+
next = undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return new PaginatedResponse<InvoiceStruct[], LimitedFilteredRequest>({
|
|
188
|
+
results: await AuthenticatedStructures.invoices(invoices),
|
|
189
|
+
next,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
194
|
+
await Context.setOrganizationScope();
|
|
195
|
+
await Context.authenticate();
|
|
196
|
+
|
|
197
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
198
|
+
|
|
199
|
+
if (request.query.limit > maxLimit) {
|
|
200
|
+
throw new SimpleError({
|
|
201
|
+
code: 'invalid_field',
|
|
202
|
+
field: 'limit',
|
|
203
|
+
message: 'Limit can not be more than ' + maxLimit,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (request.query.limit < 1) {
|
|
208
|
+
throw new SimpleError({
|
|
209
|
+
code: 'invalid_field',
|
|
210
|
+
field: 'limit',
|
|
211
|
+
message: 'Limit can not be less than 1',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return new Response(
|
|
216
|
+
await GetInvoicesEndpoint.buildData(request.query),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -5,14 +5,14 @@ import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform
|
|
|
5
5
|
import { BuckarooSettings, Company, MemberResponsibility, OrganizationMetaData, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PermissionRoleDetailed, PermissionRoleForResponsibility, PermissionsResourceType, ResourcePermissions, UitpasClientCredentialsStatus } from '@stamhoofd/structures';
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
|
-
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
9
|
-
import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
|
|
10
|
-
import { Context } from '../../../../helpers/Context';
|
|
11
|
-
import { MemberUserSyncer } from '../../../../helpers/MemberUserSyncer';
|
|
12
|
-
import { SetupStepUpdater } from '../../../../helpers/SetupStepUpdater';
|
|
13
|
-
import { TagHelper } from '../../../../helpers/TagHelper';
|
|
14
|
-
import { ViesHelper } from '../../../../helpers/ViesHelper';
|
|
15
|
-
import { UitpasService } from '../../../../services/uitpas/UitpasService';
|
|
8
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
9
|
+
import { BuckarooHelper } from '../../../../helpers/BuckarooHelper.js';
|
|
10
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
11
|
+
import { MemberUserSyncer } from '../../../../helpers/MemberUserSyncer.js';
|
|
12
|
+
import { SetupStepUpdater } from '../../../../helpers/SetupStepUpdater.js';
|
|
13
|
+
import { TagHelper } from '../../../../helpers/TagHelper.js';
|
|
14
|
+
import { ViesHelper } from '../../../../helpers/ViesHelper.js';
|
|
15
|
+
import { UitpasService } from '../../../../services/uitpas/UitpasService.js';
|
|
16
16
|
|
|
17
17
|
type Params = Record<string, never>;
|
|
18
18
|
type Query = undefined;
|
|
@@ -5,8 +5,8 @@ import { BalanceItem, Member, Order, User } from '@stamhoofd/models';
|
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
6
|
import { BalanceItemStatus, BalanceItemType, BalanceItemWithPayments, PermissionLevel } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
|
-
import { Context } from '../../../../helpers/Context';
|
|
9
|
-
import { BalanceItemService } from '../../../../services/BalanceItemService';
|
|
8
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
9
|
+
import { BalanceItemService } from '../../../../services/BalanceItemService.js';
|
|
10
10
|
|
|
11
11
|
type Params = Record<string, never>;
|
|
12
12
|
type Query = undefined;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { UitpasNumberDetails, UitpasNumbersGetDetailsRequest } from '@stamhoofd/structures';
|
|
3
|
+
|
|
4
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
5
|
+
import { isSimpleError, isSimpleErrors, SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
6
|
+
import { uitpasApiResponseToSocialTariff } from '../../../helpers/updateMemberDetailsUitpasNumber.js';
|
|
7
|
+
import { UitpasService } from '../../../services/uitpas/UitpasService.js';
|
|
8
|
+
|
|
9
|
+
type Params = Record<string, never>;
|
|
10
|
+
type Query = UitpasNumbersGetDetailsRequest;
|
|
11
|
+
type Body = undefined;
|
|
12
|
+
type ResponseBody = UitpasNumberDetails[];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the details such as the social tariff for a list of uitpas numbers.
|
|
16
|
+
*/
|
|
17
|
+
export class GetUitpasNumberDetailsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
18
|
+
queryDecoder = UitpasNumbersGetDetailsRequest as Decoder<UitpasNumbersGetDetailsRequest>;
|
|
19
|
+
|
|
20
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
21
|
+
if (request.method !== 'GET') {
|
|
22
|
+
return [false];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const params = Endpoint.parseParameters(request.url, '/uitpas/details', {});
|
|
26
|
+
|
|
27
|
+
if (params) {
|
|
28
|
+
return [true, params as Params];
|
|
29
|
+
}
|
|
30
|
+
return [false];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
34
|
+
const uitpasNumbers: string[] = request.query.uitpasNumbers;
|
|
35
|
+
if (uitpasNumbers.length > 5) {
|
|
36
|
+
throw new SimpleError({
|
|
37
|
+
code: 'maximum_limit',
|
|
38
|
+
message: 'Please only request up to 5 numbers at the same time',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const results: UitpasNumberDetails[] = [];
|
|
43
|
+
const simpleErrors = new SimpleErrors();
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < uitpasNumbers.length; i++) {
|
|
46
|
+
const uitpasNumber = uitpasNumbers[i];
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const result = await UitpasService.getPassByUitpasNumber(uitpasNumber);
|
|
50
|
+
const socialTariff = uitpasApiResponseToSocialTariff(result);
|
|
51
|
+
results.push(UitpasNumberDetails.create({
|
|
52
|
+
uitpasNumber,
|
|
53
|
+
socialTariff,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (isSimpleError(error) || isSimpleErrors(error)) {
|
|
58
|
+
error.addNamespace(i.toString());
|
|
59
|
+
error.addNamespace('uitpasNumbers');
|
|
60
|
+
simpleErrors.addError(error);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
simpleErrors.throwIfNotEmpty();
|
|
69
|
+
|
|
70
|
+
return new Response(results);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -3,8 +3,9 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
3
3
|
import { UitpasPriceCheckRequest, UitpasPriceCheckResponse } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Decoder } from '@simonbackx/simple-encoding';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { Context } from '../../../helpers/Context.js';
|
|
7
|
+
import { UitpasService } from '../../../services/uitpas/UitpasService.js';
|
|
8
|
+
|
|
8
9
|
type Params = Record<string, never>;
|
|
9
10
|
type Query = undefined;
|
|
10
11
|
type Body = UitpasPriceCheckRequest;
|
|
@@ -122,7 +122,7 @@ export const baseMemberColumns: XlsxTransformerColumn<PlatformMember>[] = [
|
|
|
122
122
|
name: $t(`87c1a48c-fef5-44c3-ae56-c83463fcfb84`),
|
|
123
123
|
width: 20,
|
|
124
124
|
getValue: ({ patchedMember: object }: PlatformMember) => ({
|
|
125
|
-
value: object.details.uitpasNumber,
|
|
125
|
+
value: object.details.uitpasNumberDetails?.uitpasNumber ?? null,
|
|
126
126
|
}),
|
|
127
127
|
},
|
|
128
128
|
{
|
|
@@ -160,6 +160,32 @@ export const baseMemberColumns: XlsxTransformerColumn<PlatformMember>[] = [
|
|
|
160
160
|
};
|
|
161
161
|
},
|
|
162
162
|
},
|
|
163
|
+
{
|
|
164
|
+
id: 'organization',
|
|
165
|
+
name: $t(`afd7843d-f355-445b-a158-ddacf469a5b1`),
|
|
166
|
+
width: 40,
|
|
167
|
+
getValue: (member: PlatformMember) => {
|
|
168
|
+
const organizations = member.filterOrganizations({ currentPeriod: true, types: [GroupType.Membership] });
|
|
169
|
+
const str = Formatter.joinLast(organizations.map(o => o.name).sort(), ', ', ' ' + $t(`c1843768-2bf4-42f2-baa4-42f49028463d`) + ' ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f');
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
value: str,
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'uri',
|
|
178
|
+
name: $t(`27cfaf26-6b88-4ebc-a50a-627a9f0f9e64`),
|
|
179
|
+
width: 30,
|
|
180
|
+
getValue: (member: PlatformMember) => {
|
|
181
|
+
const organizations = member.filterOrganizations({ currentPeriod: true, types: [GroupType.Membership] });
|
|
182
|
+
const str = Formatter.joinLast(organizations.map(o => o.uri).sort(), ', ', ' ' + $t(`c1843768-2bf4-42f2-baa4-42f49028463d`) + ' ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f');
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
value: str,
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
},
|
|
163
189
|
|
|
164
190
|
...XlsxTransformerColumnHelper.creatColumnsForParents(),
|
|
165
191
|
|
|
@@ -216,32 +242,6 @@ const sheet: XlsxTransformerSheet<PlatformMember, PlatformMember> = {
|
|
|
216
242
|
name: $t(`fb35c140-e936-4e91-aa92-ef4dfc59fb51`),
|
|
217
243
|
columns: [
|
|
218
244
|
...baseMemberColumns,
|
|
219
|
-
{
|
|
220
|
-
id: 'organization',
|
|
221
|
-
name: $t(`afd7843d-f355-445b-a158-ddacf469a5b1`),
|
|
222
|
-
width: 40,
|
|
223
|
-
getValue: (member: PlatformMember) => {
|
|
224
|
-
const organizations = member.filterOrganizations({ currentPeriod: true, types: [GroupType.Membership] });
|
|
225
|
-
const str = Formatter.joinLast(organizations.map(o => o.name).sort(), ', ', ' ' + $t(`c1843768-2bf4-42f2-baa4-42f49028463d`) + ' ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f');
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
value: str,
|
|
229
|
-
};
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
id: 'uri',
|
|
234
|
-
name: $t(`27cfaf26-6b88-4ebc-a50a-627a9f0f9e64`),
|
|
235
|
-
width: 30,
|
|
236
|
-
getValue: (member: PlatformMember) => {
|
|
237
|
-
const organizations = member.filterOrganizations({ currentPeriod: true, types: [GroupType.Membership] });
|
|
238
|
-
const str = Formatter.joinLast(organizations.map(o => o.uri).sort(), ', ', ' ' + $t(`c1843768-2bf4-42f2-baa4-42f49028463d`) + ' ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f');
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
value: str,
|
|
242
|
-
};
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
245
|
{
|
|
246
246
|
id: 'group',
|
|
247
247
|
name: $t(`0c230001-c3be-4a8e-8eab-23dc3fd96e52`),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, PatchMap } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { BalanceItem, CachedBalance, Document, Email, EmailTemplate, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
|
|
4
|
-
import { AccessRight, EmailTemplate as EmailTemplateStruct, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, GroupType, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, ReceivableBalanceType, RecordSettings, ResourcePermissions } from '@stamhoofd/structures';
|
|
4
|
+
import { AccessRight, EmailTemplate as EmailTemplateStruct, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, GroupType, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, ReceivableBalanceType, RecordSettings, ResourcePermissions, UitpasNumberDetails, UitpasSocialTariff, UitpasSocialTariffStatus } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
|
-
import { MemberRecordStore } from '../services/MemberRecordStore';
|
|
7
|
-
import { addTemporaryMemberAccess, hasTemporaryMemberAccess } from './TemporaryMemberAccess';
|
|
6
|
+
import { MemberRecordStore } from '../services/MemberRecordStore.js';
|
|
7
|
+
import { addTemporaryMemberAccess, hasTemporaryMemberAccess } from './TemporaryMemberAccess.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* One class with all the responsabilities of checking permissions to each resource in the system by a given user, possibly in an organization context.
|
|
@@ -1478,7 +1478,7 @@ export class AdminPermissionChecker {
|
|
|
1478
1478
|
// Has financial read access?
|
|
1479
1479
|
if (!options?.forAdminCartCalculation && !await this.hasFinancialMemberAccess(member, PermissionLevel.Read)) {
|
|
1480
1480
|
cloned.details.requiresFinancialSupport = null;
|
|
1481
|
-
cloned.details.
|
|
1481
|
+
cloned.details.uitpasNumberDetails = null;
|
|
1482
1482
|
cloned.outstandingBalance = 0;
|
|
1483
1483
|
|
|
1484
1484
|
for (const registration of cloned.registrations) {
|
|
@@ -1507,7 +1507,7 @@ export class AdminPermissionChecker {
|
|
|
1507
1507
|
/**
|
|
1508
1508
|
* Only for creating new members
|
|
1509
1509
|
*/
|
|
1510
|
-
filterMemberPut(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob, options: {asUserManager: boolean}) {
|
|
1510
|
+
filterMemberPut(member: MemberWithRegistrations, data: MemberWithRegistrationsBlob, options: { asUserManager: boolean }) {
|
|
1511
1511
|
if (options.asUserManager || STAMHOOFD.userMode === 'platform') {
|
|
1512
1512
|
// A user manager cannot choose the member number + in platform mode, nobody can choose the member number
|
|
1513
1513
|
data.details.memberNumber = null;
|
|
@@ -1515,7 +1515,12 @@ export class AdminPermissionChecker {
|
|
|
1515
1515
|
|
|
1516
1516
|
// Do not allow setting the security code
|
|
1517
1517
|
data.details.securityCode = null;
|
|
1518
|
-
|
|
1518
|
+
if (data.details.uitpasNumberDetails) {
|
|
1519
|
+
data.details.uitpasNumberDetails.socialTariff = UitpasSocialTariff.create({
|
|
1520
|
+
status: UitpasSocialTariffStatus.Unknown,
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1519
1524
|
|
|
1520
1525
|
async filterMemberPatch(member: MemberWithRegistrations, data: AutoEncoderPatchType<MemberWithRegistrationsBlob>): Promise<AutoEncoderPatchType<MemberWithRegistrationsBlob>> {
|
|
1521
1526
|
if (!data.details) {
|
|
@@ -1537,10 +1542,9 @@ export class AdminPermissionChecker {
|
|
|
1537
1542
|
});
|
|
1538
1543
|
}
|
|
1539
1544
|
|
|
1540
|
-
|
|
1541
1545
|
const hasRecordAnswers = !!data.details.recordAnswers;
|
|
1542
1546
|
const hasNotes = data.details.notes !== undefined;
|
|
1543
|
-
const isSetFinancialSupportTrue = data.details.
|
|
1547
|
+
const isSetFinancialSupportTrue = data.details.didSetManualFinancialSupport;
|
|
1544
1548
|
|
|
1545
1549
|
if (data.details.securityCode !== undefined || data.details.trackingYear !== undefined) {
|
|
1546
1550
|
const hasFullAccess = await this.canAccessMember(member, PermissionLevel.Full);
|
|
@@ -1561,6 +1565,22 @@ export class AdminPermissionChecker {
|
|
|
1561
1565
|
}
|
|
1562
1566
|
}
|
|
1563
1567
|
|
|
1568
|
+
if (data.details.uitpasNumberDetails && data.details.uitpasNumberDetails.socialTariff !== undefined) {
|
|
1569
|
+
if (data.details.uitpasNumberDetails.uitpasNumber === undefined) {
|
|
1570
|
+
data.details.uitpasNumberDetails = undefined;
|
|
1571
|
+
}
|
|
1572
|
+
else if (data.details.uitpasNumberDetails.uitpasNumber !== member.details.uitpasNumberDetails?.uitpasNumber) {
|
|
1573
|
+
// if uitpas number did change -> status should be reset
|
|
1574
|
+
data.details.uitpasNumberDetails = UitpasNumberDetails.create({
|
|
1575
|
+
uitpasNumber: data.details.uitpasNumberDetails.uitpasNumber,
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
else {
|
|
1579
|
+
// if uitpas number did not change
|
|
1580
|
+
data.details.uitpasNumberDetails.socialTariff = member.details.uitpasNumberDetails?.socialTariff;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1564
1584
|
if (hasRecordAnswers) {
|
|
1565
1585
|
if (!(data.details.recordAnswers instanceof PatchMap)) {
|
|
1566
1586
|
throw new SimpleError({
|
|
@@ -1594,7 +1614,7 @@ export class AdminPermissionChecker {
|
|
|
1594
1614
|
// A user manager cannot choose the member number + in platform mode, nobody can choose the member number
|
|
1595
1615
|
delete data.details.memberNumber;
|
|
1596
1616
|
}
|
|
1597
|
-
|
|
1617
|
+
|
|
1598
1618
|
if (hasNotes && isUserManager && !(await this.canAccessMember(member, PermissionLevel.Full))) {
|
|
1599
1619
|
throw new SimpleError({
|
|
1600
1620
|
code: 'permission_denied',
|
|
@@ -1633,7 +1653,7 @@ export class AdminPermissionChecker {
|
|
|
1633
1653
|
}
|
|
1634
1654
|
|
|
1635
1655
|
if (!isUserManager) {
|
|
1636
|
-
if (data.details.
|
|
1656
|
+
if (data.details.uitpasNumberDetails) {
|
|
1637
1657
|
throw new SimpleError({
|
|
1638
1658
|
code: 'permission_denied',
|
|
1639
1659
|
message: 'Je hebt geen toegangsrechten om het UiTPAS-nummer van dit lid aan te passen',
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
-
import { AuditLog, BalanceItem, CachedBalance, Document, Event, EventNotification, Group, 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, 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';
|
|
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';
|
|
4
4
|
import { Sorter } from '@stamhoofd/utility';
|
|
5
5
|
|
|
6
6
|
import { SQL } from '@stamhoofd/sql';
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
|
-
import { BalanceItemService } from '../services/BalanceItemService';
|
|
9
|
-
import { Context } from './Context';
|
|
8
|
+
import { BalanceItemService } from '../services/BalanceItemService.js';
|
|
9
|
+
import { Context } from './Context.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Builds authenticated structures for the current user
|
|
@@ -28,9 +28,10 @@ export class AuthenticatedStructures {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const { balanceItemPayments, balanceItems } = await Payment.loadBalanceItems(payments);
|
|
31
|
-
const { registrations, orders } = await Payment.loadBalanceItemRelations(balanceItems);
|
|
32
31
|
|
|
33
32
|
if (checkPermissions) {
|
|
33
|
+
const { registrations, orders } = await Payment.loadBalanceItemRelations(balanceItems);
|
|
34
|
+
|
|
34
35
|
// Note: permission checking is moved here for performacne to avoid loading the data multiple times
|
|
35
36
|
if (!(await Context.auth.canAccessBalanceItems(balanceItems, PermissionLevel.Read, { registrations, orders }))) {
|
|
36
37
|
throw new SimpleError({
|
|
@@ -53,6 +54,24 @@ export class AuthenticatedStructures {
|
|
|
53
54
|
}, includeSettlements);
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
static async invoices(invoices: Invoice[]): Promise<InvoiceStruct[]> {
|
|
58
|
+
if (invoices.length === 0) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { invoicedBalanceItems } = await Invoice.loadBalanceItems(invoices);
|
|
63
|
+
|
|
64
|
+
return invoices.map((invoice) => {
|
|
65
|
+
const items = invoicedBalanceItems.filter(i => i.invoiceId === invoice.id);
|
|
66
|
+
return InvoiceStruct.create({
|
|
67
|
+
...invoice,
|
|
68
|
+
items: items.map((item) => {
|
|
69
|
+
return InvoicedBalanceItem.create(item);
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
static async group(group: Group) {
|
|
57
76
|
return (await this.groups([group]))[0];
|
|
58
77
|
}
|
|
@@ -4,6 +4,7 @@ import { BalanceItem, BalanceItemPayment, Organization, Payment, StripeAccount,
|
|
|
4
4
|
import { calculateVATPercentage, PaymentMethod, PaymentMethodHelper, PaymentStatus } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import Stripe from 'stripe';
|
|
7
|
+
import { passthroughFetch } from './passthroughFetch.js';
|
|
7
8
|
|
|
8
9
|
export class StripeHelper {
|
|
9
10
|
static get notConfiguredError() {
|
|
@@ -18,7 +19,16 @@ export class StripeHelper {
|
|
|
18
19
|
if (!STAMHOOFD.STRIPE_SECRET_KEY) {
|
|
19
20
|
throw this.notConfiguredError;
|
|
20
21
|
}
|
|
21
|
-
return new Stripe(STAMHOOFD.STRIPE_SECRET_KEY, {
|
|
22
|
+
return new Stripe(STAMHOOFD.STRIPE_SECRET_KEY, {
|
|
23
|
+
apiVersion: '2024-06-20',
|
|
24
|
+
typescript: true,
|
|
25
|
+
maxNetworkRetries: 0,
|
|
26
|
+
timeout: 10000,
|
|
27
|
+
stripeAccount: accountId ?? undefined,
|
|
28
|
+
httpClient: STAMHOOFD.environment === 'test'
|
|
29
|
+
? Stripe.createFetchHttpClient(passthroughFetch)
|
|
30
|
+
: undefined,
|
|
31
|
+
});
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
static async saveChargeInfo(model: StripePaymentIntent | StripeCheckoutSession, charge: Stripe.Charge, payment: Payment) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Order, Payment, StripeCheckoutSession, StripePaymentIntent } from '@stamhoofd/models';
|
|
2
2
|
import { Settlement } from '@stamhoofd/structures';
|
|
3
3
|
import Stripe from 'stripe';
|
|
4
|
+
import { passthroughFetch } from './passthroughFetch.js';
|
|
4
5
|
|
|
5
6
|
export class StripePayoutChecker {
|
|
6
7
|
private stripe: Stripe;
|
|
@@ -14,6 +15,9 @@ export class StripePayoutChecker {
|
|
|
14
15
|
maxNetworkRetries: 1,
|
|
15
16
|
timeout: 10000,
|
|
16
17
|
stripeAccount,
|
|
18
|
+
httpClient: STAMHOOFD.environment === 'test'
|
|
19
|
+
? Stripe.createFetchHttpClient(passthroughFetch)
|
|
20
|
+
: undefined,
|
|
17
21
|
});
|
|
18
22
|
|
|
19
23
|
this.stripePlatform = new Stripe(
|
|
@@ -22,6 +26,9 @@ export class StripePayoutChecker {
|
|
|
22
26
|
typescript: true,
|
|
23
27
|
maxNetworkRetries: 1,
|
|
24
28
|
timeout: 10000,
|
|
29
|
+
httpClient: STAMHOOFD.environment === 'test'
|
|
30
|
+
? Stripe.createFetchHttpClient(passthroughFetch)
|
|
31
|
+
: undefined,
|
|
25
32
|
});
|
|
26
33
|
}
|
|
27
34
|
|
|
@@ -73,10 +73,6 @@ export class UitpasTokenRepository {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
private static async getModelFromDb(organizationId: string | null) {
|
|
76
|
-
let model = await UitpasClientCredential.select().where('organizationId', organizationId).first(false);
|
|
77
|
-
if (model) {
|
|
78
|
-
return model; // found in database
|
|
79
|
-
}
|
|
80
76
|
if (organizationId === null) {
|
|
81
77
|
// platform client id and secret are not yet in the database, but should be configured in the environment variables
|
|
82
78
|
if (!STAMHOOFD.UITPAS_API_CLIENT_ID || !STAMHOOFD.UITPAS_API_CLIENT_SECRET) {
|
|
@@ -86,12 +82,18 @@ export class UitpasTokenRepository {
|
|
|
86
82
|
human: $t('71a8218b-c58e-4e95-9626-551b80eb8367'),
|
|
87
83
|
});
|
|
88
84
|
}
|
|
89
|
-
model = new UitpasClientCredential();
|
|
85
|
+
const model = new UitpasClientCredential();
|
|
90
86
|
model.clientId = STAMHOOFD.UITPAS_API_CLIENT_ID;
|
|
91
87
|
model.clientSecret = STAMHOOFD.UITPAS_API_CLIENT_SECRET;
|
|
92
88
|
model.organizationId = null; // null means platform
|
|
93
89
|
return model;
|
|
94
90
|
}
|
|
91
|
+
|
|
92
|
+
const model = await UitpasClientCredential.select().where('organizationId', organizationId).first(false);
|
|
93
|
+
if (model) {
|
|
94
|
+
return model; // found in database
|
|
95
|
+
}
|
|
96
|
+
|
|
95
97
|
return null; // not found in database
|
|
96
98
|
}
|
|
97
99
|
|