@stamhoofd/backend 2.83.4 → 2.84.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/index.ts +19 -4
- package/package.json +18 -14
- package/src/crons/amazon-ses.ts +26 -5
- package/src/crons/balance-emails.ts +18 -17
- package/src/email-recipient-loaders/registrations.ts +87 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +5 -2
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +40 -40
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +28 -22
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +81 -49
- package/src/endpoints/global/files/UploadFile.ts +11 -16
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +234 -0
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +117 -43
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +1054 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +163 -141
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +6 -6
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +0 -16
- package/src/endpoints/global/members/helpers/validateGroupFilter.ts +73 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +1 -2
- package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +43 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1016 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +234 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -5
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +474 -554
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +191 -52
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +107 -9
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +89 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +9 -6
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +88 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +10 -6
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +10 -25
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +4 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +44 -19
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +140 -25
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +40 -10
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +4 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +2 -2
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
- package/src/excel-loaders/members.ts +233 -232
- package/src/excel-loaders/payments.ts +1 -1
- package/src/excel-loaders/receivable-balances.ts +1 -1
- package/src/excel-loaders/registrations.ts +153 -0
- package/src/helpers/AdminPermissionChecker.ts +65 -37
- package/src/helpers/AuthenticatedStructures.ts +43 -3
- package/src/helpers/Context.ts +29 -1
- package/src/helpers/GlobalHelper.ts +3 -1
- package/src/helpers/GroupedThrottledQueue.test.ts +219 -0
- package/src/helpers/GroupedThrottledQueue.ts +108 -0
- package/src/helpers/LimitedFilteredRequestHelper.ts +26 -1
- package/src/helpers/MemberCharger.ts +0 -5
- package/src/helpers/MembershipCharger.ts +3 -9
- package/src/helpers/OrganizationCharger.ts +0 -5
- package/src/helpers/ThrottledQueue.test.ts +194 -0
- package/src/helpers/ThrottledQueue.ts +145 -0
- package/src/helpers/XlsxTransformerColumnHelper.ts +44 -1
- package/src/middleware/ContextMiddleware.ts +1 -1
- package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/seeds/1735577912-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/services/BalanceItemPaymentService.ts +1 -33
- package/src/services/BalanceItemService.ts +167 -48
- package/src/services/FileSignService.ts +18 -13
- package/src/services/MemberRecordStore.ts +28 -19
- package/src/services/PaymentReallocationService.test.ts +25 -14
- package/src/services/PaymentReallocationService.ts +29 -10
- package/src/services/PaymentService.ts +4 -16
- package/src/services/PlatformMembershipService.ts +8 -4
- package/src/services/RegistrationService.ts +66 -2
- package/src/sql-filters/base-registration-filter-compilers.ts +43 -0
- package/src/sql-filters/groups.ts +67 -0
- package/src/sql-filters/members.ts +33 -58
- package/src/sql-filters/organization-registration-periods.ts +8 -0
- package/src/sql-filters/registration-periods.ts +8 -0
- package/src/sql-filters/registrations.ts +11 -22
- package/src/sql-sorters/groups.ts +24 -0
- package/src/sql-sorters/organization-registration-periods.ts +24 -0
- package/src/sql-sorters/registration-periods.ts +47 -0
- package/src/sql-sorters/registrations.ts +77 -0
- package/tests/actions/patchOrganizationMember.ts +27 -0
- package/tests/actions/patchPaymentStatus.ts +45 -0
- package/tests/actions/patchUserMember.ts +27 -0
- package/tests/assertions/assertBalances.ts +49 -0
- package/tests/e2e/api-rate-limits.test.ts +5 -5
- package/tests/e2e/bundle-discounts.test.ts +4060 -0
- package/tests/e2e/charge-members.test.ts +27 -24
- package/tests/e2e/documents.test.ts +398 -0
- package/tests/e2e/register.test.ts +292 -312
- package/tests/helpers/PayconiqMocker.ts +55 -0
- package/tests/init/index.ts +5 -0
- package/tests/init/initAdmin.ts +14 -0
- package/tests/init/initBundleDiscount.ts +47 -0
- package/tests/init/initPayconiq.ts +9 -0
- package/tests/init/initPlatformAdmin.ts +13 -0
- package/tests/init/initStripe.ts +21 -0
- package/tests/jest.setup.ts +29 -0
- package/src/seeds-temporary/1736266448-recall-balance-item-price-paid.ts +0 -70
|
@@ -49,11 +49,6 @@ export class GetEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
49
49
|
throw Context.auth.error();
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
else {
|
|
53
|
-
if (!Context.auth.hasPlatformFullAccess()) {
|
|
54
|
-
throw Context.auth.error();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
52
|
|
|
58
53
|
if (request.query.types?.length === 0) {
|
|
59
54
|
throw new SimpleError({
|
|
@@ -120,6 +115,14 @@ export class GetEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
120
115
|
defaultTemplates.unshift(...orgDefaults);
|
|
121
116
|
}
|
|
122
117
|
|
|
123
|
-
|
|
118
|
+
const allTemplates: EmailTemplate[] = [];
|
|
119
|
+
|
|
120
|
+
for (const template of templates.concat(defaultTemplates)) {
|
|
121
|
+
if (await Context.auth.canAccessEmailTemplate(template)) {
|
|
122
|
+
allTemplates.push(template);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return new Response(allTemplates.map(template => EmailTemplateStruct.create(template)));
|
|
124
127
|
}
|
|
125
128
|
}
|
package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { PatchableArray, PatchableArrayAutoEncoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { EmailTemplate, GroupFactory, Organization, OrganizationFactory, Platform, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
4
|
+
import { EmailTemplate as EmailTemplateStruct, EmailTemplateType, PermissionLevel, PermissionRoleDetailed, Permissions, PermissionsResourceType, ResourcePermissions, Version } from '@stamhoofd/structures';
|
|
5
|
+
import { TestUtils } from '@stamhoofd/test-utils';
|
|
6
|
+
import { testServer } from '../../../../../tests/helpers/TestServer';
|
|
7
|
+
import { PatchEmailTemplatesEndpoint } from './PatchEmailTemplatesEndpoint';
|
|
8
|
+
|
|
9
|
+
const baseUrl = `/v${Version}/email-templates`;
|
|
10
|
+
|
|
11
|
+
describe('Endpoint.PatchEmailTemplatesEndpoint', () => {
|
|
12
|
+
const endpoint = new PatchEmailTemplatesEndpoint();
|
|
13
|
+
let period: RegistrationPeriod;
|
|
14
|
+
|
|
15
|
+
const patchEmailTemplates = async (body: PatchableArrayAutoEncoder<EmailTemplateStruct>, token: Token, organization?: Organization) => {
|
|
16
|
+
const request = Request.buildJson('PATCH', baseUrl, organization?.getApiHost(), body);
|
|
17
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
18
|
+
return await testServer.test(endpoint, request);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
TestUtils.setEnvironment('userMode', 'platform');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
period = await new RegistrationPeriodFactory({
|
|
27
|
+
startDate: new Date(2023, 0, 1),
|
|
28
|
+
endDate: new Date(2023, 11, 31),
|
|
29
|
+
}).create();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('User with platform role who has full permission for all organizations', () => {
|
|
33
|
+
test('should be allowed to patch templates for organizations', async () => {
|
|
34
|
+
const role = PermissionRoleDetailed.create({
|
|
35
|
+
name: 'Beroepsmedewerker',
|
|
36
|
+
resources: new Map([[PermissionsResourceType.OrganizationTags, new Map([[
|
|
37
|
+
'',
|
|
38
|
+
ResourcePermissions.create({
|
|
39
|
+
level: PermissionLevel.Full,
|
|
40
|
+
}),
|
|
41
|
+
]])]]),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const globalPermissions = Permissions.create({
|
|
45
|
+
level: PermissionLevel.None,
|
|
46
|
+
roles: [
|
|
47
|
+
role,
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const platform = await Platform.getForEditing();
|
|
52
|
+
platform.privateConfig.roles.push(role);
|
|
53
|
+
await platform.save();
|
|
54
|
+
|
|
55
|
+
const user = await new UserFactory({
|
|
56
|
+
globalPermissions,
|
|
57
|
+
})
|
|
58
|
+
.create();
|
|
59
|
+
|
|
60
|
+
const organization = await new OrganizationFactory({ period }).create();
|
|
61
|
+
const group = await new GroupFactory({ organization }).create();
|
|
62
|
+
|
|
63
|
+
const token = await Token.createToken(user);
|
|
64
|
+
|
|
65
|
+
const template = new EmailTemplate();
|
|
66
|
+
template.subject = 'test template 1';
|
|
67
|
+
template.type = EmailTemplateType.RegistrationConfirmation;
|
|
68
|
+
template.json = {};
|
|
69
|
+
template.html = 'html test';
|
|
70
|
+
template.text = 'text test';
|
|
71
|
+
template.organizationId = organization.id;
|
|
72
|
+
template.groupId = group.id;
|
|
73
|
+
await template.save();
|
|
74
|
+
|
|
75
|
+
const body: PatchableArrayAutoEncoder<EmailTemplateStruct> = new PatchableArray();
|
|
76
|
+
|
|
77
|
+
body.addPatch(
|
|
78
|
+
EmailTemplateStruct.patch(({
|
|
79
|
+
id: template.id,
|
|
80
|
+
subject: 'new subject',
|
|
81
|
+
})),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const response = await patchEmailTemplates(body, token);
|
|
85
|
+
expect(response.body).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -4,7 +4,6 @@ import { EmailTemplate, Group, Webshop } from '@stamhoofd/models';
|
|
|
4
4
|
import { EmailTemplate as EmailTemplateStruct, PermissionLevel } from '@stamhoofd/structures';
|
|
5
5
|
|
|
6
6
|
import { Context } from '../../../../helpers/Context';
|
|
7
|
-
import { SimpleError } from '@simonbackx/simple-errors';
|
|
8
7
|
|
|
9
8
|
type Params = Record<string, never>;
|
|
10
9
|
type Body = PatchableArrayAutoEncoder<EmailTemplateStruct>;
|
|
@@ -37,11 +36,6 @@ export class PatchEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, R
|
|
|
37
36
|
throw Context.auth.error();
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
|
-
else {
|
|
41
|
-
if (!Context.auth.hasPlatformFullAccess()) {
|
|
42
|
-
throw Context.auth.error();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
39
|
|
|
46
40
|
const templates: EmailTemplate[] = [];
|
|
47
41
|
|
|
@@ -2,7 +2,7 @@ import { AutoEncoderPatchType, cloneObject, Decoder, isPatchableArray, isPatchMa
|
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, Webshop } from '@stamhoofd/models';
|
|
5
|
-
import { BuckarooSettings, Company, MemberResponsibility, OrganizationMetaData,
|
|
5
|
+
import { BuckarooSettings, Company, MemberResponsibility, OrganizationMetaData, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PermissionRoleDetailed, PermissionRoleForResponsibility, PermissionsResourceType, ResourcePermissions } from '@stamhoofd/structures';
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
@@ -23,7 +23,7 @@ type ResponseBody = OrganizationStruct;
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
26
|
-
bodyDecoder =
|
|
26
|
+
bodyDecoder = OrganizationStruct.patchType() as Decoder<AutoEncoderPatchType<OrganizationStruct>>;
|
|
27
27
|
|
|
28
28
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
29
29
|
if (request.method !== 'PATCH') {
|
|
@@ -330,7 +330,8 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
330
330
|
if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
|
|
331
331
|
throw new SimpleError({
|
|
332
332
|
code: 'invalid_field',
|
|
333
|
-
message: '
|
|
333
|
+
message: 'The period you want to set does not exist (anymore)',
|
|
334
|
+
human: $t('a3795bf6-ed50-4aa6-9caf-33820292c159'),
|
|
334
335
|
field: 'period',
|
|
335
336
|
});
|
|
336
337
|
}
|
|
@@ -339,7 +340,8 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
339
340
|
if (!period || (period.organizationId && period.organizationId !== organization.id)) {
|
|
340
341
|
throw new SimpleError({
|
|
341
342
|
code: 'invalid_field',
|
|
342
|
-
message: '
|
|
343
|
+
message: 'The period you want to set does not exist (anymore)',
|
|
344
|
+
human: $t('a3795bf6-ed50-4aa6-9caf-33820292c159'),
|
|
343
345
|
field: 'period',
|
|
344
346
|
});
|
|
345
347
|
}
|
|
@@ -347,7 +349,8 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
347
349
|
if (period.locked) {
|
|
348
350
|
throw new SimpleError({
|
|
349
351
|
code: 'invalid_field',
|
|
350
|
-
message: '
|
|
352
|
+
message: 'The period you want to set is already closed',
|
|
353
|
+
human: $t('b6bc2fef-71ac-43a1-b430-50945427a9e3'),
|
|
351
354
|
field: 'period',
|
|
352
355
|
});
|
|
353
356
|
}
|
|
@@ -356,7 +359,8 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
356
359
|
if (period.startDate > new Date(Date.now() + maximumStart)) {
|
|
357
360
|
throw new SimpleError({
|
|
358
361
|
code: 'invalid_field',
|
|
359
|
-
message: '
|
|
362
|
+
message: 'The period you want to set has not started yet',
|
|
363
|
+
human: $t('e0fff936-3f3c-46b8-adcf-c723c33907a2'),
|
|
360
364
|
field: 'period',
|
|
361
365
|
});
|
|
362
366
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import { BalanceItem, Member } from '@stamhoofd/models';
|
|
3
|
-
import { BalanceItemWithPayments } from '@stamhoofd/structures';
|
|
4
2
|
|
|
5
|
-
import {
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
4
|
|
|
7
5
|
type Params = { id: string };
|
|
8
6
|
type Query = undefined;
|
|
9
7
|
type Body = undefined;
|
|
10
|
-
type ResponseBody =
|
|
8
|
+
type ResponseBody = undefined;
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated
|
|
12
|
+
*/
|
|
13
13
|
export class GetMemberBalanceEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
14
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
15
15
|
if (request.method !== 'GET') {
|
|
@@ -24,25 +24,10 @@ export class GetMemberBalanceEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
24
24
|
return [false];
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async handle(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
throw Context.auth.error();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const member = (await Member.getWithRegistrations(request.params.id));
|
|
36
|
-
|
|
37
|
-
if (!member || !await Context.auth.hasFinancialMemberAccess(member)) {
|
|
38
|
-
throw Context.auth.notFoundOrNoAccess($t(`baf6fd8e-1937-4c8b-8510-fa811473c157`));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Get all balance items for this member or users
|
|
42
|
-
const balanceItems = await BalanceItem.balanceItemsForUsersAndMembers(organization.id, member.users.map(u => u.id), [member.id]);
|
|
43
|
-
|
|
44
|
-
return new Response(
|
|
45
|
-
await BalanceItem.getStructureWithPayments(balanceItems),
|
|
46
|
-
);
|
|
27
|
+
async handle(_: DecodedRequest<Params, Query, Body>): Promise<Response<ResponseBody>> {
|
|
28
|
+
throw new SimpleError({
|
|
29
|
+
code: 'moved',
|
|
30
|
+
message: 'This endpoint has been moved to /receivable-balances/member/@id',
|
|
31
|
+
});
|
|
47
32
|
}
|
|
48
33
|
}
|
|
@@ -231,11 +231,6 @@ export class PatchBalanceItemsEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
231
231
|
}
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
await BalanceItem.updateOutstanding(updateOutstandingBalance, additionalItems);
|
|
235
|
-
|
|
236
|
-
// Reallocate
|
|
237
|
-
await BalanceItemService.reallocate(updateOutstandingBalance, organization.id);
|
|
238
|
-
|
|
239
234
|
// Reload returnedModels
|
|
240
235
|
const returnedModelsReloaded = await BalanceItem.getByIDs(...returnedModels.map(m => m.id));
|
|
241
236
|
|
|
@@ -198,11 +198,6 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
198
198
|
for (const balanceItem of balanceItems) {
|
|
199
199
|
await BalanceItemService.markUpdated(balanceItem, payment, organization);
|
|
200
200
|
}
|
|
201
|
-
|
|
202
|
-
await BalanceItem.updateOutstanding(balanceItems);
|
|
203
|
-
|
|
204
|
-
// Reallocate
|
|
205
|
-
await BalanceItemService.reallocate(balanceItems, organization.id);
|
|
206
201
|
}
|
|
207
202
|
|
|
208
203
|
changedPayments.push(payment);
|
package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { BalanceItem, BalanceItemPayment, CachedBalance, Payment } from '@stamho
|
|
|
5
5
|
import { Context } from '../../../../helpers/Context';
|
|
6
6
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
7
7
|
import { SQL } from '@stamhoofd/sql';
|
|
8
|
+
import { BalanceItemService } from '../../../../services/BalanceItemService';
|
|
8
9
|
|
|
9
10
|
type Params = { id: string; type: ReceivableBalanceType };
|
|
10
11
|
type Query = undefined;
|
|
@@ -37,6 +38,9 @@ export class GetReceivableBalanceEndpoint extends Endpoint<Params, Query, Body,
|
|
|
37
38
|
throw Context.auth.error();
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// Flush caches (this makes sure that we do a reload in the frontend after a registration or change, we get the newest balances)
|
|
42
|
+
await BalanceItemService.flushCaches(organization.id);
|
|
43
|
+
|
|
40
44
|
const balanceItemModels = await CachedBalance.balanceForObjects(organization.id, [request.params.id], request.params.type, true);
|
|
41
45
|
let paymentModels: Payment[] = [];
|
|
42
46
|
|
package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStruct
|
|
|
9
9
|
import { Context } from '../../../../helpers/Context';
|
|
10
10
|
import { receivableBalanceFilterCompilers } from '../../../../sql-filters/receivable-balances';
|
|
11
11
|
import { receivableBalanceSorters } from '../../../../sql-sorters/receivable-balances';
|
|
12
|
+
import { BalanceItemService } from '../../../../services/BalanceItemService';
|
|
12
13
|
|
|
13
14
|
type Params = Record<string, never>;
|
|
14
15
|
type Query = LimitedFilteredRequest;
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { GroupFactory, Organization, OrganizationFactory, OrganizationRegistrationPeriodFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
3
|
-
import { GroupType, PermissionLevel, Permissions
|
|
3
|
+
import { GroupType, LimitedFilteredRequest, PermissionLevel, Permissions } from '@stamhoofd/structures';
|
|
4
4
|
import { testServer } from '../../../../../tests/helpers/TestServer';
|
|
5
|
-
import {
|
|
5
|
+
import { GetOrganizationRegistrationPeriodsEndpoint } from './GetOrganizationRegistrationPeriodsEndpoint';
|
|
6
6
|
|
|
7
|
-
const baseUrl = `/
|
|
7
|
+
const baseUrl = `/organization/registration-periods`;
|
|
8
8
|
|
|
9
9
|
describe('Endpoint.GetOrganizationRegistrationPeriods', () => {
|
|
10
|
-
const endpoint = new
|
|
10
|
+
const endpoint = new GetOrganizationRegistrationPeriodsEndpoint();
|
|
11
11
|
|
|
12
12
|
let registrationPeriod1: RegistrationPeriod;
|
|
13
13
|
let registrationPeriod2: RegistrationPeriod;
|
|
14
14
|
|
|
15
15
|
const get = async (organization: Organization, token: Token) => {
|
|
16
|
-
const request = Request.
|
|
17
|
-
|
|
16
|
+
const request = Request.get({
|
|
17
|
+
path: baseUrl,
|
|
18
|
+
host: organization.getApiHost(),
|
|
19
|
+
query: new LimitedFilteredRequest({
|
|
20
|
+
limit: 100,
|
|
21
|
+
}),
|
|
22
|
+
headers: {
|
|
23
|
+
authorization: 'Bearer ' + token.accessToken,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
18
26
|
return await testServer.test(endpoint, request);
|
|
19
27
|
};
|
|
20
28
|
|
|
@@ -36,11 +44,11 @@ describe('Endpoint.GetOrganizationRegistrationPeriods', () => {
|
|
|
36
44
|
.create();
|
|
37
45
|
};
|
|
38
46
|
|
|
39
|
-
describe('
|
|
47
|
+
describe('Groups', () => {
|
|
40
48
|
test('Should contain waiting lists', async () => {
|
|
41
49
|
// arrange
|
|
42
50
|
const organization = await initOrganization(registrationPeriod1);
|
|
43
|
-
await new OrganizationRegistrationPeriodFactory({
|
|
51
|
+
const organizationPeriod = await new OrganizationRegistrationPeriodFactory({
|
|
44
52
|
organization,
|
|
45
53
|
period: registrationPeriod1,
|
|
46
54
|
}).create();
|
|
@@ -74,19 +82,21 @@ describe('Endpoint.GetOrganizationRegistrationPeriods', () => {
|
|
|
74
82
|
|
|
75
83
|
const groupWithWaitingList = await new GroupFactory({
|
|
76
84
|
organization,
|
|
85
|
+
waitingListId: waitingList1.id,
|
|
77
86
|
}).create();
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
// Make sure the group is in a category of the organization period
|
|
89
|
+
organizationPeriod.settings.rootCategory?.groupIds.push(groupWithWaitingList.id);
|
|
90
|
+
await organizationPeriod.save();
|
|
81
91
|
|
|
82
92
|
// act
|
|
83
93
|
const result = await get(organization, token);
|
|
84
94
|
|
|
85
95
|
// assert
|
|
86
96
|
expect(result.body).toBeDefined();
|
|
87
|
-
expect(result.body.
|
|
88
|
-
const
|
|
89
|
-
const groups =
|
|
97
|
+
expect(result.body.results.length).toBe(1);
|
|
98
|
+
const organizationPeriodStruct = result.body.results[0];
|
|
99
|
+
const groups = organizationPeriodStruct.groups;
|
|
90
100
|
expect(groups.length).toBe(3);
|
|
91
101
|
expect(groups).toEqual(expect.arrayContaining([
|
|
92
102
|
expect.objectContaining({
|
|
@@ -99,13 +109,13 @@ describe('Endpoint.GetOrganizationRegistrationPeriods', () => {
|
|
|
99
109
|
id: waitingList2.id,
|
|
100
110
|
}),
|
|
101
111
|
]));
|
|
102
|
-
expect(
|
|
112
|
+
expect(organizationPeriodStruct.waitingLists.length).toBe(2);
|
|
103
113
|
});
|
|
104
114
|
|
|
105
|
-
test('Should contain
|
|
115
|
+
test('Should not contain groups of other organizations or periods', async () => {
|
|
106
116
|
// arrange
|
|
107
117
|
const organization = await initOrganization(registrationPeriod1);
|
|
108
|
-
await new OrganizationRegistrationPeriodFactory({
|
|
118
|
+
const organizationPeriod = await new OrganizationRegistrationPeriodFactory({
|
|
109
119
|
organization,
|
|
110
120
|
period: registrationPeriod1,
|
|
111
121
|
}).create();
|
|
@@ -130,13 +140,28 @@ describe('Endpoint.GetOrganizationRegistrationPeriods', () => {
|
|
|
130
140
|
period: registrationPeriod1,
|
|
131
141
|
}).create();
|
|
132
142
|
|
|
143
|
+
// Make sure the groups are in a category of the organization period
|
|
144
|
+
organizationPeriod.settings.rootCategory?.groupIds.push(group1.id);
|
|
145
|
+
organizationPeriod.settings.rootCategory?.groupIds.push(group2.id);
|
|
146
|
+
|
|
147
|
+
await organizationPeriod.save();
|
|
148
|
+
|
|
133
149
|
// group list of other organization
|
|
134
150
|
const otherOrganization = await initOrganization(registrationPeriod1);
|
|
151
|
+
const otherOrganizationPeriod = await new OrganizationRegistrationPeriodFactory({
|
|
152
|
+
organization: otherOrganization,
|
|
153
|
+
period: registrationPeriod1,
|
|
154
|
+
}).create();
|
|
135
155
|
|
|
136
|
-
await new GroupFactory({
|
|
156
|
+
const otherGroup1 = await new GroupFactory({
|
|
137
157
|
organization: otherOrganization,
|
|
158
|
+
period: registrationPeriod1,
|
|
138
159
|
}).create();
|
|
139
160
|
|
|
161
|
+
// Add the group to the other organization's period
|
|
162
|
+
otherOrganizationPeriod.settings.rootCategory?.groupIds.push(otherGroup1.id);
|
|
163
|
+
await otherOrganizationPeriod.save();
|
|
164
|
+
|
|
140
165
|
// group of other period
|
|
141
166
|
await new GroupFactory({
|
|
142
167
|
organization,
|
|
@@ -148,8 +173,8 @@ describe('Endpoint.GetOrganizationRegistrationPeriods', () => {
|
|
|
148
173
|
|
|
149
174
|
// assert
|
|
150
175
|
expect(result.body).toBeDefined();
|
|
151
|
-
expect(result.body.
|
|
152
|
-
const groups = result.body.
|
|
176
|
+
expect(result.body.results.length).toBe(1);
|
|
177
|
+
const groups = result.body.results[0].groups;
|
|
153
178
|
expect(groups.length).toBe(2);
|
|
154
179
|
expect(groups).toEqual(expect.arrayContaining([
|
|
155
180
|
expect.objectContaining({
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import {
|
|
2
|
+
import { assertSort, CountFilteredRequest, getSortFilter, LimitedFilteredRequest, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PaginatedResponse, StamhoofdFilter } from '@stamhoofd/structures';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { OrganizationRegistrationPeriod } from '@stamhoofd/models';
|
|
6
|
+
import { applySQLSorter, compileToSQLFilter, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
7
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
5
8
|
import { Context } from '../../../../helpers/Context';
|
|
6
|
-
import {
|
|
9
|
+
import { organizationRegistrationPeriodFilterCompilers } from '../../../../sql-filters/organization-registration-periods';
|
|
10
|
+
import { organizationRegistrationPeriodSorters } from '../../../../sql-sorters/organization-registration-periods';
|
|
11
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
7
12
|
|
|
8
13
|
type Params = Record<string, never>;
|
|
9
|
-
type Query =
|
|
14
|
+
type Query = LimitedFilteredRequest;
|
|
10
15
|
type Body = undefined;
|
|
11
|
-
type ResponseBody =
|
|
16
|
+
type ResponseBody = PaginatedResponse<OrganizationRegistrationPeriodStruct[], LimitedFilteredRequest>;
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
const filterCompilers: SQLFilterDefinitions = organizationRegistrationPeriodFilterCompilers;
|
|
19
|
+
const sorters: SQLSortDefinitions<OrganizationRegistrationPeriod> = organizationRegistrationPeriodSorters;
|
|
20
|
+
|
|
21
|
+
export class GetOrganizationRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
22
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>;
|
|
16
23
|
|
|
17
|
-
export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
18
24
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
19
25
|
if (request.method !== 'GET') {
|
|
20
26
|
return [false];
|
|
@@ -28,38 +34,147 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
28
34
|
return [false];
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
static async buildQuery(q: CountFilteredRequest | LimitedFilteredRequest) {
|
|
38
|
+
const organization = Context.organization;
|
|
39
|
+
let scopeFilter: StamhoofdFilter | undefined = undefined;
|
|
40
|
+
|
|
41
|
+
if (organization) {
|
|
42
|
+
scopeFilter = {
|
|
43
|
+
organizationId: organization.id,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const query = OrganizationRegistrationPeriod.select();
|
|
48
|
+
|
|
49
|
+
if (scopeFilter) {
|
|
50
|
+
query.where(await compileToSQLFilter(scopeFilter, filterCompilers));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (q.filter) {
|
|
54
|
+
query.where(await compileToSQLFilter(q.filter, filterCompilers));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (q.search) {
|
|
58
|
+
let searchFilter: StamhoofdFilter | null = null;
|
|
59
|
+
|
|
60
|
+
searchFilter = {
|
|
61
|
+
id: q.search,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (searchFilter) {
|
|
65
|
+
query.where(await compileToSQLFilter(searchFilter, filterCompilers));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
70
|
+
if (q.pageFilter) {
|
|
71
|
+
query.where(await compileToSQLFilter(q.pageFilter, filterCompilers));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
q.sort = assertSort(q.sort, [{ key: 'id' }]);
|
|
75
|
+
applySQLSorter(query, q.sort, sorters);
|
|
76
|
+
query.limit(q.limit);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return query;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
83
|
+
const query = await GetOrganizationRegistrationPeriodsEndpoint.buildQuery(requestQuery);
|
|
84
|
+
const organizationRegistrationPeriods = await query.fetch();
|
|
85
|
+
|
|
86
|
+
let next: LimitedFilteredRequest | undefined;
|
|
87
|
+
|
|
88
|
+
if (organizationRegistrationPeriods.length >= requestQuery.limit) {
|
|
89
|
+
const lastObject = organizationRegistrationPeriods[organizationRegistrationPeriods.length - 1];
|
|
90
|
+
const nextFilter = getSortFilter(lastObject, sorters, requestQuery.sort);
|
|
91
|
+
|
|
92
|
+
next = new LimitedFilteredRequest({
|
|
93
|
+
filter: requestQuery.filter,
|
|
94
|
+
pageFilter: nextFilter,
|
|
95
|
+
sort: requestQuery.sort,
|
|
96
|
+
limit: requestQuery.limit,
|
|
97
|
+
search: requestQuery.search,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (JSON.stringify(nextFilter) === JSON.stringify(requestQuery.pageFilter)) {
|
|
101
|
+
console.error('Found infinite loading loop for', requestQuery);
|
|
102
|
+
next = undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new PaginatedResponse<OrganizationRegistrationPeriodStruct[], LimitedFilteredRequest>({
|
|
107
|
+
results: await AuthenticatedStructures.organizationRegistrationPeriods(organizationRegistrationPeriods),
|
|
108
|
+
next,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
31
112
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
32
|
-
|
|
113
|
+
if (request.request.getVersion() < 371) {
|
|
114
|
+
throw new SimpleError({
|
|
115
|
+
code: 'client_update_required',
|
|
116
|
+
statusCode: 400,
|
|
117
|
+
message: 'Er is een noodzakelijke update beschikbaar. Herlaad de pagina en wis indien nodig de cache van jouw browser.',
|
|
118
|
+
human: $t(`adb0e7c8-aed7-43f5-bfcc-a350f03aaabe`),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
33
123
|
await Context.authenticate();
|
|
34
124
|
|
|
35
|
-
if (
|
|
36
|
-
|
|
125
|
+
if (organization) {
|
|
126
|
+
if (!await Context.auth.hasSomeAccess(organization.id)) {
|
|
127
|
+
throw Context.auth.error();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
132
|
+
throw Context.auth.error();
|
|
133
|
+
}
|
|
37
134
|
}
|
|
38
135
|
|
|
39
|
-
const
|
|
40
|
-
const periods = await RegistrationPeriod.all();
|
|
41
|
-
const groups = await Group.getAll(organization.id, null);
|
|
136
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
42
137
|
|
|
43
|
-
|
|
138
|
+
if (request.query.limit > maxLimit) {
|
|
139
|
+
throw new SimpleError({
|
|
140
|
+
code: 'invalid_field',
|
|
141
|
+
field: 'limit',
|
|
142
|
+
message: 'Limit can not be more than ' + maxLimit,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
44
145
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
146
|
+
if (request.query.limit < 1) {
|
|
147
|
+
throw new SimpleError({
|
|
148
|
+
code: 'invalid_field',
|
|
149
|
+
field: 'limit',
|
|
150
|
+
message: 'Limit can not be less than 1',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
50
153
|
|
|
51
|
-
|
|
52
|
-
|
|
154
|
+
return new Response(
|
|
155
|
+
await GetOrganizationRegistrationPeriodsEndpoint.buildData(request.query),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
160
|
+
const organization = await Context.setOrganizationScope();
|
|
161
|
+
await Context.authenticate();
|
|
162
|
+
|
|
163
|
+
if (!await Context.auth.hasSomeAccess(organization.id)) {
|
|
164
|
+
throw Context.auth.error();
|
|
53
165
|
}
|
|
54
166
|
|
|
167
|
+
const organizationPeriods = await OrganizationRegistrationPeriod.select().where('organizationId', organization.id).fetch();
|
|
168
|
+
const periods = await RegistrationPeriod.all();
|
|
169
|
+
|
|
55
170
|
// Sort
|
|
56
171
|
periods.sort((a, b) => Sorter.byDateValue(a.startDate, b.startDate));
|
|
57
172
|
|
|
58
173
|
return new Response(
|
|
59
174
|
RegistrationPeriodList.create({
|
|
60
|
-
organizationPeriods:
|
|
175
|
+
organizationPeriods: await AuthenticatedStructures.organizationRegistrationPeriods(organizationPeriods, periods),
|
|
61
176
|
periods: periods.map(p => p.getStructure()),
|
|
62
177
|
}),
|
|
63
178
|
);
|
|
64
|
-
}
|
|
179
|
+
} */
|
|
65
180
|
}
|