@stamhoofd/backend 2.137.3 → 2.137.5
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 +23 -17
- package/src/crons/drip-emails.ts +2 -1
- package/src/crons.ts +4 -2
- package/src/email-recipient-loaders/payments.ts +3 -2
- package/src/endpoints/auth/CreateAdminEndpoint.ts +3 -2
- package/src/endpoints/auth/CreateTokenEndpoint.ts +2 -1
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +3 -2
- package/src/endpoints/auth/PatchUserEndpoint.ts +6 -3
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +2 -1
- package/src/endpoints/auth/SignupEndpoint.ts +4 -2
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -1
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email/GetAdminEmailsEndpoint.ts +2 -1
- package/src/endpoints/global/email/GetEmailEndpoint.ts +2 -1
- package/src/endpoints/global/email/GetUserEmailsEndpoint.test.ts +1 -1
- package/src/endpoints/global/email/GetUserEmailsEndpoint.ts +2 -1
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +2 -1
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -5
- package/src/endpoints/global/files/UploadFile.test.ts +206 -0
- package/src/endpoints/global/files/UploadFile.ts +31 -6
- package/src/endpoints/global/files/UploadImage.test.ts +177 -0
- package/src/endpoints/global/files/UploadImage.ts +23 -4
- package/src/endpoints/global/files/upload-security.test.ts +837 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +1 -1
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +2 -1
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +3 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.ts +5 -3
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +4 -3
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +2 -1
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +4 -3
- package/src/excel-loaders/balance-items.ts +1 -1
- package/src/excel-loaders/event-notifications.ts +6 -7
- package/src/excel-loaders/members.test.ts +2 -2
- package/src/excel-loaders/members.ts +9 -10
- package/src/excel-loaders/organizations.ts +14 -20
- package/src/excel-loaders/payments.ts +1 -1
- package/src/excel-loaders/platform-memberships.ts +7 -7
- package/src/excel-loaders/platform-sheets.test.ts +113 -0
- package/src/excel-loaders/receivable-balances.ts +1 -1
- package/src/excel-loaders/registrations.ts +9 -9
- package/src/helpers/AdminPermissionChecker.ts +1 -1
- package/src/helpers/AuthenticatedStructures.ts +12 -9
- package/src/helpers/MembershipCharger.ts +3 -2
- package/src/services/BalanceItemService.ts +2 -1
- package/src/services/DocumentRenderService.test.ts +229 -0
- package/src/services/DocumentRenderService.ts +180 -0
- package/src/services/EmailPreviewService.test.ts +300 -0
- package/src/services/EmailPreviewService.ts +239 -0
- package/src/services/FileSignService.test.ts +85 -0
- package/src/services/FileSignService.ts +23 -1
- package/src/services/OrderService.test.ts +308 -0
- package/src/services/OrderService.ts +214 -0
- package/src/services/OrganizationDNSService.test.ts +177 -0
- package/src/services/OrganizationDNSService.ts +282 -0
- package/src/services/OrganizationEmailService.test.ts +57 -0
- package/src/services/OrganizationEmailService.ts +211 -0
- package/src/services/PasswordForgotService.test.ts +99 -0
- package/src/services/PasswordForgotService.ts +38 -0
- package/src/services/PlatformMembershipService.test.ts +180 -0
- package/src/services/PlatformMembershipService.ts +281 -4
- package/src/services/RegistrationService.ts +3 -2
- package/src/services/STPackageService.test.ts +191 -0
- package/src/services/STPackageService.ts +114 -2
- package/src/services/VerificationCodeService.test.ts +71 -0
- package/src/services/VerificationCodeService.ts +100 -0
- package/tests/e2e/documents.test.ts +2 -1
- package/tests/e2e/private-files.test.ts +77 -14
|
@@ -585,7 +585,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
585
585
|
membership.locked = false;
|
|
586
586
|
|
|
587
587
|
// Correct price and dates
|
|
588
|
-
await
|
|
588
|
+
await PlatformMembershipService.calculatePrice(membership, member);
|
|
589
589
|
|
|
590
590
|
const incompatible = await MemberPlatformMembership.select()
|
|
591
591
|
.where('memberId', member.id)
|
|
@@ -12,6 +12,7 @@ import { Formatter } from '@stamhoofd/utility';
|
|
|
12
12
|
import { v4 as uuidv4 } from 'uuid';
|
|
13
13
|
import { AuditLogService } from '../../../services/AuditLogService.js';
|
|
14
14
|
import { ReferralService } from '../../../services/ReferralService.js';
|
|
15
|
+
import { VerificationCodeService } from '../../../services/VerificationCodeService.js';
|
|
15
16
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
16
17
|
import { Context } from '../../../helpers/Context.js';
|
|
17
18
|
|
|
@@ -160,7 +161,7 @@ export class CreateOrganizationEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
160
161
|
await AuditLog.update().where('type', AuditLogType.OrganizationAdded).where('objectId', organization.id).set('userId', user.id).set('source', AuditLogSource.User).update();
|
|
161
162
|
|
|
162
163
|
const code = await EmailVerificationCode.createFor(user, user.email);
|
|
163
|
-
|
|
164
|
+
VerificationCodeService.send(code, user, organization, request.i18n).catch(console.error);
|
|
164
165
|
|
|
165
166
|
for (const email of delayEmails) {
|
|
166
167
|
Email.send({
|
|
@@ -4,6 +4,7 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
4
4
|
import { DocumentTemplate } from '@stamhoofd/models';
|
|
5
5
|
|
|
6
6
|
import { Context } from '../../../../helpers/Context.js';
|
|
7
|
+
import { DocumentRenderService } from '../../../../services/DocumentRenderService.js';
|
|
7
8
|
|
|
8
9
|
type Params = { id: string };
|
|
9
10
|
type Query = undefined;
|
|
@@ -42,7 +43,7 @@ export class GetDocumentTemplateXMLEndpoint extends Endpoint<Params, Query, Body
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// Update documents
|
|
45
|
-
const xml = await
|
|
46
|
+
const xml = await DocumentRenderService.getRenderedXml(template, organization);
|
|
46
47
|
if (!xml) {
|
|
47
48
|
throw new SimpleError({
|
|
48
49
|
code: 'failed_generating',
|
|
@@ -9,6 +9,7 @@ import NodeRSA from 'node-rsa';
|
|
|
9
9
|
import { Formatter } from '@stamhoofd/utility';
|
|
10
10
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
11
11
|
import { Context } from '../../../../helpers/Context.js';
|
|
12
|
+
import { OrganizationDNSService } from '../../../../services/OrganizationDNSService.js';
|
|
12
13
|
|
|
13
14
|
type Params = Record<string, never>;
|
|
14
15
|
type Query = undefined;
|
|
@@ -169,7 +170,7 @@ export class SetOrganizationDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
169
170
|
}));
|
|
170
171
|
} else {
|
|
171
172
|
if (oldMailDomain) {
|
|
172
|
-
|
|
173
|
+
OrganizationDNSService.deleteAWSMailIdenitity(organization, oldMailDomain).catch(console.error);
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
if (organization.serverMeta.privateDKIMKey && organization.serverMeta.publicDKIMKey) {
|
|
@@ -188,7 +189,7 @@ export class SetOrganizationDomainEndpoint extends Endpoint<Params, Query, Body,
|
|
|
188
189
|
} else {
|
|
189
190
|
// Validate DNS-records if not empty
|
|
190
191
|
console.log('Validating domains');
|
|
191
|
-
await
|
|
192
|
+
await OrganizationDNSService.updateDNSRecords(organization);
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
console.log('Done.');
|
|
@@ -3,7 +3,7 @@ import { isPatch } from '@simonbackx/simple-encoding';
|
|
|
3
3
|
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
5
5
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
|
-
import { Token, User } from '@stamhoofd/models';
|
|
6
|
+
import { Platform, Token, User } from '@stamhoofd/models';
|
|
7
7
|
import { ApiUser, PermissionLevel, UserMeta, UserPermissions } from '@stamhoofd/structures';
|
|
8
8
|
import { Context } from '../../../../helpers/Context.js';
|
|
9
9
|
|
|
@@ -58,10 +58,12 @@ export class PatchApiUserEndpoint extends Endpoint<Params, Query, Body, Response
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
if (request.body.permissions) {
|
|
61
|
+
const platform = await Platform.getSharedStruct();
|
|
62
|
+
|
|
61
63
|
if (organization) {
|
|
62
64
|
editUser.permissions = UserPermissions.limitedPatch(editUser.permissions, request.body.permissions, organization.id);
|
|
63
65
|
|
|
64
|
-
if (editUser.id === user.id && (!editUser.permissions || !editUser.permissions.forOrganization(organization)?.hasFullAccess())) {
|
|
66
|
+
if (editUser.id === user.id && (!editUser.permissions || !editUser.permissions.forOrganization(organization, platform, { inheritFromPlatform: false })?.hasFullAccess())) {
|
|
65
67
|
throw new SimpleError({
|
|
66
68
|
code: 'permission_denied',
|
|
67
69
|
message: 'Je kan jezelf niet verwijderen als hoofdbeheerder',
|
|
@@ -74,7 +76,7 @@ export class PatchApiUserEndpoint extends Endpoint<Params, Query, Body, Response
|
|
|
74
76
|
editUser.permissions = request.body.permissions.isPut() ? request.body.permissions : null;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
if (editUser.id === user.id && !editUser.permissions?.platform?.hasFullAccess()) {
|
|
79
|
+
if (editUser.id === user.id && !editUser.permissions?.forPlatform(platform)?.hasFullAccess()) {
|
|
78
80
|
throw new SimpleError({
|
|
79
81
|
code: 'permission_denied',
|
|
80
82
|
message: 'Je kan jezelf niet verwijderen als hoofdbeheerder',
|
|
@@ -10,6 +10,7 @@ import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceIt
|
|
|
10
10
|
import { Context } from '../../../../helpers/Context.js';
|
|
11
11
|
import { ServiceFeeHelper } from '../../../../helpers/ServiceFeeHelper.js';
|
|
12
12
|
import { AuditLogService } from '../../../../services/AuditLogService.js';
|
|
13
|
+
import { OrderService } from '../../../../services/OrderService.js';
|
|
13
14
|
import { PaymentService } from '../../../../services/PaymentService.js';
|
|
14
15
|
import { shouldReserveUitpasNumbers, UitpasService } from '../../../../services/uitpas/UitpasService.js';
|
|
15
16
|
|
|
@@ -151,7 +152,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
151
152
|
order.data.paymentMethod = PaymentMethod.Unknown;
|
|
152
153
|
|
|
153
154
|
// Mark this order as paid
|
|
154
|
-
await
|
|
155
|
+
await OrderService.markPaid(order, null, organization, webshop);
|
|
155
156
|
await order.save();
|
|
156
157
|
} else {
|
|
157
158
|
const payment = new Payment();
|
|
@@ -217,12 +218,12 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
217
218
|
await balanceItemPayment.save();
|
|
218
219
|
|
|
219
220
|
if (payment.method === PaymentMethod.Transfer) {
|
|
220
|
-
await
|
|
221
|
+
await OrderService.markValid(order, payment, []);
|
|
221
222
|
await payment.save();
|
|
222
223
|
await order.save();
|
|
223
224
|
} else if (payment.method === PaymentMethod.PointOfSale) {
|
|
224
225
|
// Not really paid, but needed to create the tickets if needed
|
|
225
|
-
await
|
|
226
|
+
await OrderService.markPaid(order, payment, organization, webshop);
|
|
226
227
|
await payment.save();
|
|
227
228
|
await order.save();
|
|
228
229
|
} else {
|
|
@@ -5,6 +5,7 @@ import { signInternal } from '@stamhoofd/backend-env';
|
|
|
5
5
|
import { Document, Organization } from '@stamhoofd/models';
|
|
6
6
|
|
|
7
7
|
import { Context } from '../../../helpers/Context.js';
|
|
8
|
+
import { DocumentRenderService } from '../../../services/DocumentRenderService.js';
|
|
8
9
|
type Params = { id: string };
|
|
9
10
|
type Query = undefined;
|
|
10
11
|
type Body = undefined;
|
|
@@ -45,7 +46,7 @@ export class GetDocumentHtml extends Endpoint<Params, Query, Body, ResponseBody>
|
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
const html = await
|
|
49
|
+
const html = await DocumentRenderService.getRenderedHtml(document, organization);
|
|
49
50
|
if (!html) {
|
|
50
51
|
throw new SimpleError({
|
|
51
52
|
code: 'failed_generating',
|
|
@@ -16,6 +16,7 @@ import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper.js';
|
|
|
16
16
|
import { StripeHelper } from '../../../helpers/StripeHelper.js';
|
|
17
17
|
import { AuditLogService } from '../../../services/AuditLogService.js';
|
|
18
18
|
import { MollieService } from '../../../services/MollieService.js';
|
|
19
|
+
import { OrderService } from '../../../services/OrderService.js';
|
|
19
20
|
import { PaymentService } from '../../../services/PaymentService.js';
|
|
20
21
|
import { UitpasService } from '../../../services/uitpas/UitpasService.js';
|
|
21
22
|
import { WebshopAuthHelper } from './WebshopAuthHelper.js';
|
|
@@ -171,7 +172,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
171
172
|
order.data.paymentMethod = PaymentMethod.Unknown;
|
|
172
173
|
|
|
173
174
|
// Mark this order as paid
|
|
174
|
-
await
|
|
175
|
+
await OrderService.markPaid(order, null, organization, webshop);
|
|
175
176
|
await order.save();
|
|
176
177
|
} else {
|
|
177
178
|
const payment = new Payment();
|
|
@@ -242,7 +243,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
242
243
|
const description = webshop.meta.name + ' - ' + organization.name;
|
|
243
244
|
|
|
244
245
|
if (payment.method === PaymentMethod.Transfer) {
|
|
245
|
-
await
|
|
246
|
+
await OrderService.markValid(order, payment, []);
|
|
246
247
|
|
|
247
248
|
if (order.number) {
|
|
248
249
|
balanceItem.description = order.generateBalanceDescription(webshop);
|
|
@@ -253,7 +254,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
253
254
|
await payment.save();
|
|
254
255
|
} else if (payment.method === PaymentMethod.PointOfSale) {
|
|
255
256
|
// Not really paid, but needed to create the tickets if needed
|
|
256
|
-
await
|
|
257
|
+
await OrderService.markPaid(order, payment, organization, webshop);
|
|
257
258
|
|
|
258
259
|
if (order.number) {
|
|
259
260
|
balanceItem.description = order.generateBalanceDescription(webshop);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { XlsxTransformerSheet } from '@stamhoofd/excel-writer';
|
|
2
2
|
import { XlsxBuiltInNumberFormat } from '@stamhoofd/excel-writer';
|
|
3
|
-
import type { EventNotification, LimitedFilteredRequest } from '@stamhoofd/structures';
|
|
4
|
-
import { EventNotificationStatus, EventNotificationStatusHelper, ExcelExportType
|
|
3
|
+
import type { EventNotification, LimitedFilteredRequest, Platform as PlatformStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { EventNotificationStatus, EventNotificationStatusHelper, ExcelExportType } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { GetEventNotificationsEndpoint } from '../endpoints/global/events/GetEventNotificationsEndpoint.js';
|
|
7
7
|
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint.js';
|
|
8
8
|
import { XlsxTransformerColumnHelper } from '../helpers/XlsxTransformerColumnHelper.js';
|
|
9
9
|
|
|
10
10
|
// Assign to a typed variable to assure we have correct type checking in place
|
|
11
|
-
const
|
|
11
|
+
const getSheet = (platform: PlatformStruct): XlsxTransformerSheet<EventNotification, EventNotification> => ({
|
|
12
12
|
id: 'event-notifications',
|
|
13
13
|
name: $t(`%1FR`),
|
|
14
14
|
columns: [
|
|
@@ -118,18 +118,17 @@ const sheet: XlsxTransformerSheet<EventNotification, EventNotification> = {
|
|
|
118
118
|
matchId: 'recordAnswers',
|
|
119
119
|
getRecordAnswers: (notification: EventNotification) => notification.recordAnswers,
|
|
120
120
|
getRecordCategories: () => {
|
|
121
|
-
const platform = PlatformStruct.shared;
|
|
122
121
|
return platform.config.eventNotificationTypes.flatMap(r => r.recordCategories);
|
|
123
122
|
},
|
|
124
123
|
}),
|
|
125
124
|
],
|
|
126
|
-
};
|
|
125
|
+
});
|
|
127
126
|
|
|
128
127
|
ExportToExcelEndpoint.loaders.set(ExcelExportType.EventNotifications, {
|
|
129
128
|
fetch: async (query: LimitedFilteredRequest) => {
|
|
130
129
|
return await GetEventNotificationsEndpoint.buildData(query);
|
|
131
130
|
},
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
getSheets: platform => [
|
|
132
|
+
getSheet(platform),
|
|
134
133
|
],
|
|
135
134
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { XlsxTransformerConcreteColumn } from '@stamhoofd/excel-writer';
|
|
2
2
|
import type { PlatformMember } from '@stamhoofd/structures';
|
|
3
3
|
import { EmergencyContact, MemberDetails, MembersBlob, MemberWithRegistrationsBlob, Organization, Platform, PlatformFamily } from '@stamhoofd/structures';
|
|
4
|
-
import {
|
|
4
|
+
import { getBaseMemberColumns } from './members.js';
|
|
5
5
|
|
|
6
6
|
describe('Member excel export', () => {
|
|
7
7
|
describe('emergencyContacts column', () => {
|
|
@@ -21,7 +21,7 @@ describe('Member excel export', () => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function getColumn() {
|
|
24
|
-
const column =
|
|
24
|
+
const column = getBaseMemberColumns(Platform.create({})).find(c => 'id' in c && c.id === 'emergencyContacts') as XlsxTransformerConcreteColumn<PlatformMember> | undefined;
|
|
25
25
|
|
|
26
26
|
if (!column) {
|
|
27
27
|
throw new Error('Column emergencyContacts not found');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { XlsxTransformerColumn, XlsxTransformerSheet } from '@stamhoofd/excel-writer';
|
|
2
2
|
import { XlsxBuiltInNumberFormat } from '@stamhoofd/excel-writer';
|
|
3
3
|
import { Platform } from '@stamhoofd/models';
|
|
4
|
-
import type { LimitedFilteredRequest, PlatformMember } from '@stamhoofd/structures';
|
|
5
|
-
import { ExcelExportType, Gender, GroupType, MembershipStatus, PlatformFamily,
|
|
4
|
+
import type { LimitedFilteredRequest, PlatformMember, Platform as PlatformStruct } from '@stamhoofd/structures';
|
|
5
|
+
import { ExcelExportType, Gender, GroupType, MembershipStatus, PlatformFamily, UnencodeablePaginatedResponse } from '@stamhoofd/structures';
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint.js';
|
|
8
8
|
import { GetMembersEndpoint } from '../endpoints/global/members/GetMembersEndpoint.js';
|
|
@@ -10,7 +10,7 @@ import { AuthenticatedStructures } from '../helpers/AuthenticatedStructures.js';
|
|
|
10
10
|
import { Context } from '../helpers/Context.js';
|
|
11
11
|
import { XlsxTransformerColumnHelper } from '../helpers/XlsxTransformerColumnHelper.js';
|
|
12
12
|
|
|
13
|
-
export const
|
|
13
|
+
export const getBaseMemberColumns = (platform: PlatformStruct): XlsxTransformerColumn<PlatformMember>[] => [
|
|
14
14
|
{
|
|
15
15
|
id: 'id',
|
|
16
16
|
name: $t(`%d`),
|
|
@@ -244,7 +244,6 @@ export const baseMemberColumns: XlsxTransformerColumn<PlatformMember>[] = [
|
|
|
244
244
|
matchId: 'recordAnswers',
|
|
245
245
|
getRecordAnswers: ({ patchedMember: object }: PlatformMember) => object.details.recordAnswers,
|
|
246
246
|
getRecordCategories: () => {
|
|
247
|
-
const platform = PlatformStruct.shared;
|
|
248
247
|
const organization = Context.organization;
|
|
249
248
|
|
|
250
249
|
return [
|
|
@@ -256,11 +255,11 @@ export const baseMemberColumns: XlsxTransformerColumn<PlatformMember>[] = [
|
|
|
256
255
|
];
|
|
257
256
|
|
|
258
257
|
// Assign to a typed variable to assure we have correct type checking in place
|
|
259
|
-
const
|
|
258
|
+
const getSheet = (platform: PlatformStruct): XlsxTransformerSheet<PlatformMember, PlatformMember> => ({
|
|
260
259
|
id: 'members',
|
|
261
260
|
name: $t(`%1EH`),
|
|
262
261
|
columns: [
|
|
263
|
-
...
|
|
262
|
+
...getBaseMemberColumns(platform),
|
|
264
263
|
{
|
|
265
264
|
id: 'group',
|
|
266
265
|
name: $t(`%wH`),
|
|
@@ -281,7 +280,7 @@ const sheet: XlsxTransformerSheet<PlatformMember, PlatformMember> = {
|
|
|
281
280
|
getValue: (member: PlatformMember) => {
|
|
282
281
|
const groups = member.filterRegistrations({ currentPeriod: true, types: [GroupType.Membership], organizationId: Context.organization?.id });
|
|
283
282
|
const defaultAgeGroupIds = Formatter.uniqueArray(groups.filter(o => o.group.defaultAgeGroupId));
|
|
284
|
-
const defaultAgeGroups = defaultAgeGroupIds.map(o =>
|
|
283
|
+
const defaultAgeGroups = defaultAgeGroupIds.map(o => platform.config.defaultAgeGroups.find(g => g.id === o.group.defaultAgeGroupId)?.name ?? $t(`%wJ`));
|
|
285
284
|
const str = Formatter.joinLast(Formatter.uniqueArray(defaultAgeGroups).sort(), ', ', ' ' + $t(`%M1`) + ' ') || Context.i18n.$t('%5D');
|
|
286
285
|
|
|
287
286
|
return {
|
|
@@ -472,7 +471,7 @@ const sheet: XlsxTransformerSheet<PlatformMember, PlatformMember> = {
|
|
|
472
471
|
},
|
|
473
472
|
},
|
|
474
473
|
],
|
|
475
|
-
};
|
|
474
|
+
});
|
|
476
475
|
|
|
477
476
|
ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
478
477
|
fetch: async (query: LimitedFilteredRequest) => {
|
|
@@ -486,8 +485,8 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
486
485
|
next: result.next,
|
|
487
486
|
});
|
|
488
487
|
},
|
|
489
|
-
|
|
490
|
-
|
|
488
|
+
getSheets: platform => [
|
|
489
|
+
getSheet(platform),
|
|
491
490
|
],
|
|
492
491
|
});
|
|
493
492
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ArrayDecoder, field } from '@simonbackx/simple-encoding';
|
|
2
2
|
import type { XlsxTransformerSheet } from '@stamhoofd/excel-writer';
|
|
3
|
-
import { Group, Member, MemberResponsibilityRecord } from '@stamhoofd/models';
|
|
4
|
-
import type { LimitedFilteredRequest, Premise } from '@stamhoofd/structures';
|
|
5
|
-
import { ExcelExportType, MemberResponsibilityRecord as MemberResponsibilityRecordStruct, MemberWithRegistrationsBlob, Organization as OrganizationStruct, PaginatedResponse
|
|
3
|
+
import { Group, Member, MemberResponsibilityRecord, Platform } from '@stamhoofd/models';
|
|
4
|
+
import type { LimitedFilteredRequest, Platform as PlatformStruct, Premise } from '@stamhoofd/structures';
|
|
5
|
+
import { ExcelExportType, MemberResponsibilityRecord as MemberResponsibilityRecordStruct, MemberWithRegistrationsBlob, Organization as OrganizationStruct, PaginatedResponse } from '@stamhoofd/structures';
|
|
6
6
|
import { Formatter, Sorter } from '@stamhoofd/utility';
|
|
7
7
|
import { GetOrganizationsEndpoint } from '../endpoints/admin/organizations/GetOrganizationsEndpoint.js';
|
|
8
8
|
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint.js';
|
|
@@ -27,7 +27,7 @@ class MemberResponsibilityRecordWithMemberAndOrganization extends MemberResponsi
|
|
|
27
27
|
type Object = OrganizationWithResponsibilities;
|
|
28
28
|
|
|
29
29
|
// Assign to a typed variable to assure we have correct type checking in place
|
|
30
|
-
const
|
|
30
|
+
const getSheet = (platform: PlatformStruct): XlsxTransformerSheet<Object, Object> => ({
|
|
31
31
|
id: 'organizations',
|
|
32
32
|
name: $t(`%wP`),
|
|
33
33
|
columns: [
|
|
@@ -60,8 +60,6 @@ const sheet: XlsxTransformerSheet<Object, Object> = {
|
|
|
60
60
|
name: $t(`%13`),
|
|
61
61
|
width: 50,
|
|
62
62
|
getValue: (object: Object) => {
|
|
63
|
-
const platform = PlatformStruct.shared;
|
|
64
|
-
|
|
65
63
|
return {
|
|
66
64
|
value: object.meta.tags.map(tag => platform.config.tags.find(t => t.id === tag)?.name ?? $t(`%Gr`)).join(', '),
|
|
67
65
|
};
|
|
@@ -76,17 +74,15 @@ const sheet: XlsxTransformerSheet<Object, Object> = {
|
|
|
76
74
|
matchId: 'recordAnswers',
|
|
77
75
|
getRecordAnswers: (object: Object) => object.getRecordAnswers(),
|
|
78
76
|
getRecordCategories: () => {
|
|
79
|
-
const platform = PlatformStruct.shared;
|
|
80
|
-
|
|
81
77
|
return [
|
|
82
78
|
...platform.config.organizationLevelRecordsConfiguration.recordCategories,
|
|
83
79
|
];
|
|
84
80
|
},
|
|
85
81
|
}),
|
|
86
82
|
],
|
|
87
|
-
};
|
|
83
|
+
});
|
|
88
84
|
|
|
89
|
-
const
|
|
85
|
+
const getResponsibilitiesSheet = (platform: PlatformStruct): XlsxTransformerSheet<Object, MemberResponsibilityRecordWithMemberAndOrganization> => ({
|
|
90
86
|
id: 'responsibilities',
|
|
91
87
|
name: $t(`%7D`),
|
|
92
88
|
transform(organization) {
|
|
@@ -125,7 +121,6 @@ const responsibilities: XlsxTransformerSheet<Object, MemberResponsibilityRecordW
|
|
|
125
121
|
name: $t(`%H2`),
|
|
126
122
|
width: 50,
|
|
127
123
|
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => {
|
|
128
|
-
const platform = PlatformStruct.shared;
|
|
129
124
|
const responsibility = platform.config.responsibilities.find(r => r.id === object.responsibilityId) ?? object.organization.privateMeta?.responsibilities.find(r => r.id === object.responsibilityId);
|
|
130
125
|
|
|
131
126
|
if (!responsibility) {
|
|
@@ -168,10 +163,10 @@ const responsibilities: XlsxTransformerSheet<Object, MemberResponsibilityRecordW
|
|
|
168
163
|
getAddress: object => object.member.details.address ?? object.member.details.parents[0]?.address ?? object.member.details.parents[1]?.address ?? null,
|
|
169
164
|
}),
|
|
170
165
|
],
|
|
171
|
-
};
|
|
166
|
+
});
|
|
172
167
|
|
|
173
168
|
type PremiseWithOrganization = { organization: Object; premise: Premise };
|
|
174
|
-
const
|
|
169
|
+
const getPremisesSheet = (platform: PlatformStruct): XlsxTransformerSheet<Object, PremiseWithOrganization> => ({
|
|
175
170
|
id: 'premises',
|
|
176
171
|
name: $t(`%6c`),
|
|
177
172
|
transform(organization) {
|
|
@@ -219,7 +214,6 @@ const premises: XlsxTransformerSheet<Object, PremiseWithOrganization> = {
|
|
|
219
214
|
width: 20,
|
|
220
215
|
getValue: (object: PremiseWithOrganization) => {
|
|
221
216
|
const ids = object.premise.premiseTypeIds;
|
|
222
|
-
const platform = PlatformStruct.shared;
|
|
223
217
|
return {
|
|
224
218
|
value: ids.map(id => platform.config.premiseTypes.find(t => t.id === id)?.name ?? $t(`%Gr`)).join(', '),
|
|
225
219
|
};
|
|
@@ -230,7 +224,7 @@ const premises: XlsxTransformerSheet<Object, PremiseWithOrganization> = {
|
|
|
230
224
|
getAddress: object => object.premise.address,
|
|
231
225
|
}),
|
|
232
226
|
],
|
|
233
|
-
};
|
|
227
|
+
});
|
|
234
228
|
|
|
235
229
|
ExportToExcelEndpoint.loaders.set(ExcelExportType.Organizations, {
|
|
236
230
|
fetch: async (query: LimitedFilteredRequest) => {
|
|
@@ -253,7 +247,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Organizations, {
|
|
|
253
247
|
const groups = await Group.getByIDs(...groupIds);
|
|
254
248
|
const memberStructs = await AuthenticatedStructures.members(members);
|
|
255
249
|
const groupStructs = await AuthenticatedStructures.groups(groups);
|
|
256
|
-
const platform =
|
|
250
|
+
const platform = await Platform.getSharedStruct();
|
|
257
251
|
|
|
258
252
|
const mappedOrganizations = organizations.results.map((o) => {
|
|
259
253
|
const resp = responsibilities.filter(r => r.organizationId === o.id);
|
|
@@ -295,9 +289,9 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Organizations, {
|
|
|
295
289
|
next: organizations.next,
|
|
296
290
|
});
|
|
297
291
|
},
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
292
|
+
getSheets: platform => [
|
|
293
|
+
getSheet(platform),
|
|
294
|
+
getResponsibilitiesSheet(platform),
|
|
295
|
+
getPremisesSheet(platform),
|
|
302
296
|
],
|
|
303
297
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { XlsxTransformerSheet } from '@stamhoofd/excel-writer';
|
|
2
2
|
import { XlsxBuiltInNumberFormat } from '@stamhoofd/excel-writer';
|
|
3
|
-
import type { LimitedFilteredRequest, PlatformMembership } from '@stamhoofd/structures';
|
|
4
|
-
import { ExcelExportType, PaginatedResponse
|
|
3
|
+
import type { LimitedFilteredRequest, PlatformMembership, Platform as PlatformStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { ExcelExportType, PaginatedResponse } from '@stamhoofd/structures';
|
|
5
5
|
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint.js';
|
|
6
6
|
import { GetPlatformMembershipsEndpoint } from '../endpoints/global/platform-memberships/GetPlatformMembershipsEndpoint.js';
|
|
7
7
|
|
|
8
8
|
// Assign to a typed variable to assure we have correct type checking in place
|
|
9
|
-
const
|
|
9
|
+
const getSheet = (platform: PlatformStruct): XlsxTransformerSheet<PlatformMembership> => ({
|
|
10
10
|
id: 'platform-memberships',
|
|
11
11
|
name: $t('%1EI'),
|
|
12
12
|
columns: [
|
|
@@ -23,7 +23,7 @@ const sheet: XlsxTransformerSheet<PlatformMembership> = {
|
|
|
23
23
|
name: $t('%1LP'),
|
|
24
24
|
width: 40,
|
|
25
25
|
getValue: (membership: PlatformMembership) => {
|
|
26
|
-
const membershipType =
|
|
26
|
+
const membershipType = platform.config.membershipTypes.find(m => m.id === membership.membershipTypeId);
|
|
27
27
|
const value = membershipType ? membershipType.name : '';
|
|
28
28
|
return { value };
|
|
29
29
|
},
|
|
@@ -244,7 +244,7 @@ const sheet: XlsxTransformerSheet<PlatformMembership> = {
|
|
|
244
244
|
}),
|
|
245
245
|
},
|
|
246
246
|
],
|
|
247
|
-
};
|
|
247
|
+
});
|
|
248
248
|
|
|
249
249
|
ExportToExcelEndpoint.loaders.set(ExcelExportType.PlatformMemberships, {
|
|
250
250
|
fetch: async (query: LimitedFilteredRequest) => {
|
|
@@ -255,7 +255,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.PlatformMemberships, {
|
|
|
255
255
|
next: data.next,
|
|
256
256
|
});
|
|
257
257
|
},
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
getSheets: platform => [
|
|
259
|
+
getSheet(platform),
|
|
260
260
|
],
|
|
261
261
|
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { XlsxTransformerColumn, XlsxTransformerConcreteColumn } from '@stamhoofd/excel-writer';
|
|
2
|
+
import { isXlsxTransformerConcreteColumn } from '@stamhoofd/excel-writer';
|
|
3
|
+
import { EventNotificationType, ExcelExportType, Organization, OrganizationMetaData, OrganizationTag, Platform, PlatformConfig, PlatformMembership, PlatformMembershipMemberDetails, PlatformMembershipOrganizationDetails, PlatformMembershipType, RecordCategory, RecordSettings, RecordType, TranslatedString } from '@stamhoofd/structures';
|
|
4
|
+
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint.js';
|
|
5
|
+
import './index.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The sheets are built per export request, so every callback should read the platform it was built
|
|
9
|
+
* with instead of a process wide singleton.
|
|
10
|
+
*/
|
|
11
|
+
describe('Excel loaders platform argument', () => {
|
|
12
|
+
function getColumns(type: ExcelExportType, platform: Platform, sheetId: string): XlsxTransformerColumn<unknown>[] {
|
|
13
|
+
const loader = ExportToExcelEndpoint.loaders.get(type);
|
|
14
|
+
|
|
15
|
+
if (!loader) {
|
|
16
|
+
throw new Error('Loader ' + type + ' not registered');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sheet = loader.getSheets(platform).find(s => s.id === sheetId);
|
|
20
|
+
|
|
21
|
+
if (!sheet) {
|
|
22
|
+
throw new Error('Sheet ' + sheetId + ' not found');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return sheet.columns;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getColumn(type: ExcelExportType, platform: Platform, sheetId: string, columnId: string): XlsxTransformerConcreteColumn<unknown> {
|
|
29
|
+
const column = getColumns(type, platform, sheetId).find(c => isXlsxTransformerConcreteColumn(c) && c.id === columnId);
|
|
30
|
+
|
|
31
|
+
if (!column || !isXlsxTransformerConcreteColumn(column)) {
|
|
32
|
+
throw new Error('Column ' + columnId + ' not found');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return column;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createPlatformWithTag(tagName: string) {
|
|
39
|
+
return Platform.create({
|
|
40
|
+
config: PlatformConfig.create({
|
|
41
|
+
tags: [
|
|
42
|
+
OrganizationTag.create({ id: 'tag-1', name: tagName }),
|
|
43
|
+
],
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
it('reads the organization tags from the platform the sheets were built with', () => {
|
|
49
|
+
const organization = Organization.create({
|
|
50
|
+
meta: OrganizationMetaData.create({ tags: ['tag-1'] }),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const first = getColumn(ExcelExportType.Organizations, createPlatformWithTag('Gewest Noord'), 'organizations', 'tags');
|
|
54
|
+
expect(first.getValue(organization).value).toBe('Gewest Noord');
|
|
55
|
+
|
|
56
|
+
// Building the sheets again with another platform should not reuse the first platform
|
|
57
|
+
const second = getColumn(ExcelExportType.Organizations, createPlatformWithTag('Gewest Zuid'), 'organizations', 'tags');
|
|
58
|
+
expect(second.getValue(organization).value).toBe('Gewest Zuid');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('reads the membership type from the platform the sheets were built with', () => {
|
|
62
|
+
const platform = Platform.create({
|
|
63
|
+
config: PlatformConfig.create({
|
|
64
|
+
membershipTypes: [
|
|
65
|
+
PlatformMembershipType.create({ id: 'type-1', name: 'Aansluiting' }),
|
|
66
|
+
],
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const membership = PlatformMembership.create({
|
|
71
|
+
memberId: 'member-1',
|
|
72
|
+
membershipTypeId: 'type-1',
|
|
73
|
+
organizationId: 'organization-1',
|
|
74
|
+
periodId: 'period-1',
|
|
75
|
+
member: PlatformMembershipMemberDetails.create({ firstName: 'John', lastName: 'Doe', memberNumber: null }),
|
|
76
|
+
organization: PlatformMembershipOrganizationDetails.create({}),
|
|
77
|
+
balanceItem: null,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const column = getColumn(ExcelExportType.PlatformMemberships, platform, 'platform-memberships', 'type');
|
|
81
|
+
expect(column.getValue(membership).value).toBe('Aansluiting');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('reads the record categories of an event notification from the platform the sheets were built with', () => {
|
|
85
|
+
const platform = Platform.create({
|
|
86
|
+
config: PlatformConfig.create({
|
|
87
|
+
eventNotificationTypes: [
|
|
88
|
+
EventNotificationType.create({
|
|
89
|
+
recordCategories: [
|
|
90
|
+
RecordCategory.create({
|
|
91
|
+
name: new TranslatedString('Kamp'),
|
|
92
|
+
records: [
|
|
93
|
+
RecordSettings.create({
|
|
94
|
+
id: 'record-1',
|
|
95
|
+
name: new TranslatedString('Aantal deelnemers'),
|
|
96
|
+
type: RecordType.Text,
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
}),
|
|
100
|
+
],
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const columns = getColumns(ExcelExportType.EventNotifications, platform, 'event-notifications');
|
|
107
|
+
const matched = columns.flatMap(c => isXlsxTransformerConcreteColumn(c) ? [] : (c.match('recordAnswers.record-1') ?? []));
|
|
108
|
+
|
|
109
|
+
expect(matched).toHaveLength(1);
|
|
110
|
+
expect(matched[0].name).toBe('Aantal deelnemers');
|
|
111
|
+
expect(matched[0].category).toBe('Kamp');
|
|
112
|
+
});
|
|
113
|
+
});
|