@stamhoofd/backend 2.78.3 → 2.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.ci.json +19 -8
- package/index.ts +8 -1
- package/package.json +13 -12
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateAdminEndpoint.ts +1 -1
- package/src/endpoints/auth/CreateTokenEndpoint.ts +1 -1
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +1 -1
- package/src/endpoints/auth/PatchUserEndpoint.ts +1 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +2686 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +206 -20
- package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +9 -21
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +1 -1
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +2 -2
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +15 -14
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +3 -23
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +12 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
- package/src/helpers/AdminPermissionChecker.ts +41 -2
- package/src/helpers/AuthenticatedStructures.ts +77 -20
- package/src/helpers/ForwardHandler.test.ts +16 -5
- package/src/helpers/ForwardHandler.ts +21 -9
- package/src/helpers/MemberUserSyncer.test.ts +822 -0
- package/src/helpers/MemberUserSyncer.ts +137 -108
- package/src/helpers/TagHelper.ts +3 -3
- package/src/seeds/1734596144-fill-previous-period-id.ts +1 -1
- package/src/seeds/1741008870-fix-auditlog-description.ts +50 -0
- package/src/seeds/1741011468-fix-auditlog-description-uft16.ts +88 -0
- package/src/services/FileSignService.ts +1 -1
- package/src/services/PlatformMembershipService.ts +7 -2
- package/src/services/SSOService.ts +1 -1
- package/tests/e2e/register.test.ts +2 -2
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { GroupFactory, Organization, OrganizationFactory, OrganizationRegistrationPeriodFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
3
|
+
import { GroupType, PermissionLevel, Permissions, Version } from '@stamhoofd/structures';
|
|
4
|
+
import { testServer } from '../../../../../tests/helpers/TestServer';
|
|
5
|
+
import { PatchRegistrationPeriodsEndpoint } from './GetOrganizationRegistrationPeriodsEndpoint';
|
|
6
|
+
|
|
7
|
+
const baseUrl = `/v${Version}/organization/registration-periods`;
|
|
8
|
+
|
|
9
|
+
describe('Endpoint.GetOrganizationRegistrationPeriods', () => {
|
|
10
|
+
const endpoint = new PatchRegistrationPeriodsEndpoint();
|
|
11
|
+
|
|
12
|
+
let registrationPeriod1: RegistrationPeriod;
|
|
13
|
+
let registrationPeriod2: RegistrationPeriod;
|
|
14
|
+
|
|
15
|
+
const get = async (organization: Organization, token: Token) => {
|
|
16
|
+
const request = Request.buildJson('GET', baseUrl, organization.getApiHost());
|
|
17
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
18
|
+
return await testServer.test(endpoint, request);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
registrationPeriod2 = await new RegistrationPeriodFactory({
|
|
23
|
+
startDate: new Date(2022, 0, 1),
|
|
24
|
+
endDate: new Date(2022, 11, 31),
|
|
25
|
+
}).create();
|
|
26
|
+
|
|
27
|
+
registrationPeriod1 = await new RegistrationPeriodFactory({
|
|
28
|
+
startDate: new Date(2023, 0, 1),
|
|
29
|
+
endDate: new Date(2023, 11, 31),
|
|
30
|
+
previousPeriodId: registrationPeriod2.id,
|
|
31
|
+
}).create();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const initOrganization = async (registrationPeriod: RegistrationPeriod) => {
|
|
35
|
+
return await new OrganizationFactory({ period: registrationPeriod })
|
|
36
|
+
.create();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe('groups', () => {
|
|
40
|
+
test('Should contain waiting lists', async () => {
|
|
41
|
+
// arrange
|
|
42
|
+
const organization = await initOrganization(registrationPeriod1);
|
|
43
|
+
await new OrganizationRegistrationPeriodFactory({
|
|
44
|
+
organization,
|
|
45
|
+
period: registrationPeriod1,
|
|
46
|
+
}).create();
|
|
47
|
+
|
|
48
|
+
const user = await new UserFactory({
|
|
49
|
+
organization,
|
|
50
|
+
permissions: Permissions.create({
|
|
51
|
+
level: PermissionLevel.Full,
|
|
52
|
+
}),
|
|
53
|
+
})
|
|
54
|
+
.create();
|
|
55
|
+
const token = await Token.createToken(user);
|
|
56
|
+
|
|
57
|
+
const otherOrganization = await initOrganization(registrationPeriod1);
|
|
58
|
+
|
|
59
|
+
const waitingList1 = await new GroupFactory({
|
|
60
|
+
organization,
|
|
61
|
+
type: GroupType.WaitingList,
|
|
62
|
+
}).create();
|
|
63
|
+
|
|
64
|
+
const waitingList2 = await new GroupFactory({
|
|
65
|
+
organization,
|
|
66
|
+
type: GroupType.WaitingList,
|
|
67
|
+
}).create();
|
|
68
|
+
|
|
69
|
+
// waiting list of other organization
|
|
70
|
+
await new GroupFactory({
|
|
71
|
+
organization: otherOrganization,
|
|
72
|
+
type: GroupType.WaitingList,
|
|
73
|
+
}).create();
|
|
74
|
+
|
|
75
|
+
const groupWithWaitingList = await new GroupFactory({
|
|
76
|
+
organization,
|
|
77
|
+
}).create();
|
|
78
|
+
|
|
79
|
+
groupWithWaitingList.waitingListId = waitingList1.id;
|
|
80
|
+
await groupWithWaitingList.save();
|
|
81
|
+
|
|
82
|
+
// act
|
|
83
|
+
const result = await get(organization, token);
|
|
84
|
+
|
|
85
|
+
// assert
|
|
86
|
+
expect(result.body).toBeDefined();
|
|
87
|
+
expect(result.body.organizationPeriods.length).toBe(1);
|
|
88
|
+
const organizationPeriod = result.body.organizationPeriods[0];
|
|
89
|
+
const groups = organizationPeriod.groups;
|
|
90
|
+
expect(groups.length).toBe(3);
|
|
91
|
+
expect(groups).toEqual(expect.arrayContaining([
|
|
92
|
+
expect.objectContaining({
|
|
93
|
+
id: groupWithWaitingList.id,
|
|
94
|
+
}),
|
|
95
|
+
expect.objectContaining({
|
|
96
|
+
id: waitingList1.id,
|
|
97
|
+
}),
|
|
98
|
+
expect.objectContaining({
|
|
99
|
+
id: waitingList2.id,
|
|
100
|
+
}),
|
|
101
|
+
]));
|
|
102
|
+
expect(organizationPeriod.waitingLists.length).toBe(2);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('Should contain only groups of the organization registration period', async () => {
|
|
106
|
+
// arrange
|
|
107
|
+
const organization = await initOrganization(registrationPeriod1);
|
|
108
|
+
await new OrganizationRegistrationPeriodFactory({
|
|
109
|
+
organization,
|
|
110
|
+
period: registrationPeriod1,
|
|
111
|
+
}).create();
|
|
112
|
+
|
|
113
|
+
const user = await new UserFactory({
|
|
114
|
+
organization,
|
|
115
|
+
permissions: Permissions.create({
|
|
116
|
+
level: PermissionLevel.Full,
|
|
117
|
+
}),
|
|
118
|
+
})
|
|
119
|
+
.create();
|
|
120
|
+
|
|
121
|
+
const token = await Token.createToken(user);
|
|
122
|
+
|
|
123
|
+
const group1 = await new GroupFactory({
|
|
124
|
+
organization,
|
|
125
|
+
period: registrationPeriod1,
|
|
126
|
+
}).create();
|
|
127
|
+
|
|
128
|
+
const group2 = await new GroupFactory({
|
|
129
|
+
organization,
|
|
130
|
+
period: registrationPeriod1,
|
|
131
|
+
}).create();
|
|
132
|
+
|
|
133
|
+
// group list of other organization
|
|
134
|
+
const otherOrganization = await initOrganization(registrationPeriod1);
|
|
135
|
+
|
|
136
|
+
await new GroupFactory({
|
|
137
|
+
organization: otherOrganization,
|
|
138
|
+
}).create();
|
|
139
|
+
|
|
140
|
+
// group of other period
|
|
141
|
+
await new GroupFactory({
|
|
142
|
+
organization,
|
|
143
|
+
period: registrationPeriod2,
|
|
144
|
+
}).create();
|
|
145
|
+
|
|
146
|
+
// act
|
|
147
|
+
const result = await get(organization, token);
|
|
148
|
+
|
|
149
|
+
// assert
|
|
150
|
+
expect(result.body).toBeDefined();
|
|
151
|
+
expect(result.body.organizationPeriods.length).toBe(1);
|
|
152
|
+
const groups = result.body.organizationPeriods[0].groups;
|
|
153
|
+
expect(groups.length).toBe(2);
|
|
154
|
+
expect(groups).toEqual(expect.arrayContaining([
|
|
155
|
+
expect.objectContaining({
|
|
156
|
+
id: group1.id,
|
|
157
|
+
}),
|
|
158
|
+
expect.objectContaining({
|
|
159
|
+
id: group2.id,
|
|
160
|
+
}),
|
|
161
|
+
]));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
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, EmailTemplate, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from '@stamhoofd/models';
|
|
4
|
-
import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory, RecordSettings } from '@stamhoofd/structures';
|
|
4
|
+
import { AccessRight, EventPermissionChecker, FinancialSupportSettings, GroupCategory, GroupStatus, GroupType, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory, RecordSettings } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { addTemporaryMemberAccess, hasTemporaryMemberAccess } from './TemporaryMemberAccess';
|
|
7
7
|
import { MemberRecordStore } from '../services/MemberRecordStore';
|
|
@@ -91,6 +91,10 @@ export class AdminPermissionChecker {
|
|
|
91
91
|
});
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
memberNotFoundOrNoAccess(): SimpleError {
|
|
95
|
+
return this.notFoundOrNoAccess($t('Je hebt geen toegang tot dit lid of het bestaat niet'));
|
|
96
|
+
}
|
|
97
|
+
|
|
94
98
|
notFoundOrNoAccess(message?: string): SimpleError {
|
|
95
99
|
return new SimpleError({
|
|
96
100
|
code: 'not_found',
|
|
@@ -181,6 +185,15 @@ export class AdminPermissionChecker {
|
|
|
181
185
|
return true;
|
|
182
186
|
}
|
|
183
187
|
|
|
188
|
+
if (group.type === GroupType.EventRegistration) {
|
|
189
|
+
// Check if we can access the event
|
|
190
|
+
const event = await Event.select().where('groupId', group.id).first(false);
|
|
191
|
+
|
|
192
|
+
if (event && event.organizationId === group.organizationId && await this.canAccessEvent(event)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
184
197
|
// Check parent categories
|
|
185
198
|
const organizationPeriod = await this.getOrganizationCurrentPeriod(organization);
|
|
186
199
|
const parentCategories = group.getParentCategories(organizationPeriod.settings.categories);
|
|
@@ -193,6 +206,18 @@ export class AdminPermissionChecker {
|
|
|
193
206
|
return false;
|
|
194
207
|
}
|
|
195
208
|
|
|
209
|
+
async canRegisterMembersInGroup(group: Group, asOrganizationId: string | null) {
|
|
210
|
+
if (await this.canAccessGroup(group, PermissionLevel.Write)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (asOrganizationId) {
|
|
214
|
+
if (group.settings.allowRegistrationsByOrganization) {
|
|
215
|
+
return await this.hasFullAccess(asOrganizationId);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
196
221
|
/**
|
|
197
222
|
* Will throw error if not allowed to edit/add/delete this event
|
|
198
223
|
* @param event
|
|
@@ -1043,6 +1068,20 @@ export class AdminPermissionChecker {
|
|
|
1043
1068
|
};
|
|
1044
1069
|
}
|
|
1045
1070
|
|
|
1071
|
+
// It is possible that this is a platform admin, and inherits automatic permissions for tags. So'll need to loop all the organizations where this member has an active registration for
|
|
1072
|
+
if (!record.organizationId && this.platformPermissions) {
|
|
1073
|
+
const organizations = Formatter.uniqueArray(member.registrations.map(r => r.organizationId));
|
|
1074
|
+
for (const organizationId of organizations) {
|
|
1075
|
+
const organizationPermissions = await this.getOrganizationPermissions(organizationId);
|
|
1076
|
+
if (organizationPermissions && organizationPermissions.hasResourceAccess(PermissionsResourceType.RecordCategories, record.rootCategoryId, level)) {
|
|
1077
|
+
return {
|
|
1078
|
+
canAccess: true,
|
|
1079
|
+
record: record.record,
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1046
1085
|
return {
|
|
1047
1086
|
canAccess: false,
|
|
1048
1087
|
record: record.record,
|
|
@@ -1088,8 +1127,8 @@ export class AdminPermissionChecker {
|
|
|
1088
1127
|
if (isUserManager) {
|
|
1089
1128
|
// For a user manager without an organization, we don't delete data, because when registering a new member, it doesn't have any organizations yet...
|
|
1090
1129
|
if (!(await this.canAccessMember(member, PermissionLevel.Full))) {
|
|
1091
|
-
cloned.details.securityCode = null;
|
|
1092
1130
|
cloned.details.notes = null;
|
|
1131
|
+
// a user manager can see the security codes
|
|
1093
1132
|
}
|
|
1094
1133
|
|
|
1095
1134
|
return cloned;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
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, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
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, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, 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';
|
|
@@ -58,11 +58,29 @@ export class AuthenticatedStructures {
|
|
|
58
58
|
|
|
59
59
|
static async groups(groups: Group[]) {
|
|
60
60
|
const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null));
|
|
61
|
-
|
|
61
|
+
|
|
62
|
+
const waitingLists: Group[] = [];
|
|
63
|
+
const waitingListsToFetch: string[] = [];
|
|
64
|
+
|
|
65
|
+
for (const waitingListId of waitingListIds) {
|
|
66
|
+
const existingGroup = groups.find(g => g.id === waitingListId);
|
|
67
|
+
|
|
68
|
+
if (existingGroup) {
|
|
69
|
+
waitingLists.push(existingGroup);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
waitingListsToFetch.push(waitingListId);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (waitingListsToFetch.length) {
|
|
77
|
+
const fetchedWaitingLists = await Group.getByIDs(...waitingListsToFetch);
|
|
78
|
+
waitingLists.push(...fetchedWaitingLists);
|
|
79
|
+
}
|
|
62
80
|
|
|
63
81
|
const structs: GroupStruct[] = [];
|
|
64
82
|
for (const group of groups) {
|
|
65
|
-
const waitingList = waitingLists.find(g => g.id
|
|
83
|
+
const waitingList = waitingLists.find(g => g.id === group.waitingListId) ?? null;
|
|
66
84
|
const waitingListStruct = waitingList ? GroupStruct.create(waitingList) : null;
|
|
67
85
|
if (waitingList && waitingListStruct && !await Context.optionalAuth?.canAccessGroup(waitingList)) {
|
|
68
86
|
waitingListStruct.privateSettings = null;
|
|
@@ -88,31 +106,70 @@ export class AuthenticatedStructures {
|
|
|
88
106
|
return [];
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
const periodIds = periods ? Formatter.uniqueArray(periods.map(p => p.id)) : Formatter.uniqueArray(organizationRegistrationPeriods.map(p => p.periodId));
|
|
110
|
+
|
|
91
111
|
if (!periods) {
|
|
92
|
-
const periodIds = Formatter.uniqueArray(organizationRegistrationPeriods.map(p => p.periodId));
|
|
93
112
|
periods = await RegistrationPeriod.getByIDs(...periodIds);
|
|
94
113
|
}
|
|
95
114
|
|
|
115
|
+
const organizationIds = Formatter.uniqueArray(organizationRegistrationPeriods.map(r => r.organizationId));
|
|
116
|
+
|
|
96
117
|
const groupIds = Formatter.uniqueArray(organizationRegistrationPeriods.flatMap(p => p.settings.categories.flatMap(c => c.groupIds)));
|
|
97
|
-
|
|
118
|
+
|
|
119
|
+
let groups: Group[] = [];
|
|
120
|
+
|
|
121
|
+
const whereWaitingList = SQL.where('organizationId', organizationIds)
|
|
122
|
+
.and('periodId', periodIds)
|
|
123
|
+
.and('type', GroupType.WaitingList)
|
|
124
|
+
.and('deletedAt', null);
|
|
125
|
+
|
|
126
|
+
if (groupIds.length) {
|
|
127
|
+
const whereGroupIds = SQL.where('id', groupIds);
|
|
128
|
+
|
|
129
|
+
if (organizationIds.length && periodIds.length) {
|
|
130
|
+
groups = await Group.select()
|
|
131
|
+
.where(whereGroupIds)
|
|
132
|
+
.orWhere(whereWaitingList)
|
|
133
|
+
.fetch();
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
groups = await Group.select()
|
|
137
|
+
.where(whereGroupIds).fetch();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (organizationIds.length && periodIds.length) {
|
|
141
|
+
groups = await Group.select()
|
|
142
|
+
.where(whereWaitingList)
|
|
143
|
+
.fetch();
|
|
144
|
+
}
|
|
98
145
|
|
|
99
146
|
const groupStructs = await this.groups(groups);
|
|
100
147
|
|
|
101
148
|
const structs: OrganizationRegistrationPeriodStruct[] = [];
|
|
102
149
|
for (const organizationPeriod of organizationRegistrationPeriods) {
|
|
103
|
-
const period = periods.find(p => p.id
|
|
150
|
+
const period = periods.find(p => p.id === organizationPeriod.periodId) ?? null;
|
|
104
151
|
if (!period) {
|
|
105
152
|
continue;
|
|
106
153
|
}
|
|
107
|
-
const groupIds = Formatter.uniqueArray(organizationPeriod.settings.categories.flatMap(c => c.groupIds));
|
|
108
154
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
const struct = OrganizationRegistrationPeriodStruct.create({
|
|
156
|
+
...organizationPeriod,
|
|
157
|
+
period: period.getStructure(),
|
|
158
|
+
groups: groupStructs.filter((gg) => {
|
|
159
|
+
if (gg.organizationId !== organizationPeriod.organizationId) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (gg.periodId !== organizationPeriod.periodId) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return true;
|
|
168
|
+
})
|
|
169
|
+
.sort(GroupStruct.defaultSort),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
structs.push(struct);
|
|
116
173
|
}
|
|
117
174
|
|
|
118
175
|
return structs;
|
|
@@ -767,11 +824,11 @@ export class AuthenticatedStructures {
|
|
|
767
824
|
]
|
|
768
825
|
: []),
|
|
769
826
|
|
|
770
|
-
...(member.details.
|
|
771
|
-
? member.details.parents.filter(p =>
|
|
827
|
+
...((member.details.defaultAge <= 18 || member.details.getMemberEmails().length === 0)
|
|
828
|
+
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
772
829
|
firstName: p.firstName ?? '',
|
|
773
830
|
lastName: p.lastName ?? '',
|
|
774
|
-
emails:
|
|
831
|
+
emails: p.getEmails(),
|
|
775
832
|
meta: {
|
|
776
833
|
type: 'parent',
|
|
777
834
|
responsibilityIds: [],
|
|
@@ -810,11 +867,11 @@ export class AuthenticatedStructures {
|
|
|
810
867
|
]
|
|
811
868
|
: []),
|
|
812
869
|
|
|
813
|
-
...(member.details.
|
|
814
|
-
? member.details.parents.filter(p =>
|
|
870
|
+
...((member.details.defaultAge <= 18 || member.details.getMemberEmails().length === 0)
|
|
871
|
+
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
815
872
|
firstName: p.firstName ?? '',
|
|
816
873
|
lastName: p.lastName ?? '',
|
|
817
|
-
emails:
|
|
874
|
+
emails: p.getEmails(),
|
|
818
875
|
meta: {
|
|
819
876
|
type: 'parent',
|
|
820
877
|
responsibilityIds: [],
|
|
@@ -34,7 +34,9 @@ describe('ForwardHandler', () => {
|
|
|
34
34
|
},
|
|
35
35
|
],
|
|
36
36
|
subject: 'Hello',
|
|
37
|
-
replyTo:
|
|
37
|
+
replyTo: {
|
|
38
|
+
email: 'someone@example.com',
|
|
39
|
+
},
|
|
38
40
|
});
|
|
39
41
|
expect(options!.text).toContain('Content hier');
|
|
40
42
|
});
|
|
@@ -67,7 +69,9 @@ describe('ForwardHandler', () => {
|
|
|
67
69
|
},
|
|
68
70
|
],
|
|
69
71
|
subject: 'Hello',
|
|
70
|
-
replyTo:
|
|
72
|
+
replyTo: {
|
|
73
|
+
email: 'someone@example.com',
|
|
74
|
+
},
|
|
71
75
|
});
|
|
72
76
|
expect(options!.text).toContain('Content hier');
|
|
73
77
|
});
|
|
@@ -92,7 +96,9 @@ describe('ForwardHandler', () => {
|
|
|
92
96
|
},
|
|
93
97
|
],
|
|
94
98
|
subject: 'Hello',
|
|
95
|
-
replyTo:
|
|
99
|
+
replyTo: {
|
|
100
|
+
email: 'someone@example.com',
|
|
101
|
+
},
|
|
96
102
|
});
|
|
97
103
|
expect(options!.text).toContain('Content hier');
|
|
98
104
|
|
|
@@ -119,7 +125,9 @@ describe('ForwardHandler', () => {
|
|
|
119
125
|
|
|
120
126
|
expect(options).toMatchObject({
|
|
121
127
|
subject: 'Hello',
|
|
122
|
-
replyTo:
|
|
128
|
+
replyTo: {
|
|
129
|
+
email: 'someone@example.com',
|
|
130
|
+
},
|
|
123
131
|
});
|
|
124
132
|
expect(options!.to).toIncludeAllMembers([
|
|
125
133
|
{
|
|
@@ -207,7 +215,10 @@ describe('ForwardHandler', () => {
|
|
|
207
215
|
dmarcVerdict: { status: 'PASS' },
|
|
208
216
|
});
|
|
209
217
|
expect(options).toMatchObject({
|
|
210
|
-
to:
|
|
218
|
+
to: [{
|
|
219
|
+
email: 'hallo@stamhoofd.be',
|
|
220
|
+
name: 'Stamhoofd',
|
|
221
|
+
}],
|
|
211
222
|
subject: 'E-mail unsubscribe mislukt',
|
|
212
223
|
});
|
|
213
224
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
-
|
|
3
|
-
import { Email, EmailAddress, EmailInterfaceRecipient } from '@stamhoofd/email';
|
|
2
|
+
|
|
3
|
+
import { Email, EmailAddress, EmailInterface, EmailInterfaceRecipient } from '@stamhoofd/email';
|
|
4
4
|
import { Organization } from '@stamhoofd/models';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { simpleParser } from 'mailparser';
|
|
@@ -49,7 +49,7 @@ export class ForwardHandler {
|
|
|
49
49
|
// Forward
|
|
50
50
|
return {
|
|
51
51
|
from: Email.getWebmasterFromEmail(),
|
|
52
|
-
to: Email.getWebmasterToEmail(),
|
|
52
|
+
to: [Email.getWebmasterToEmail()],
|
|
53
53
|
subject: 'E-mail unsubscribe mislukt',
|
|
54
54
|
text: 'Beste,\n\nEr werd een unsubscribe gemeld op ' + email + ' die niet kon worden verwerkt. Gelieve dit na te kijken.\n\nStamhoofd',
|
|
55
55
|
};
|
|
@@ -64,7 +64,7 @@ export class ForwardHandler {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Send a new e-mail
|
|
67
|
-
let defaultEmail: EmailInterfaceRecipient[]
|
|
67
|
+
let defaultEmail: EmailInterfaceRecipient[] = [Email.getWebmasterToEmail()];
|
|
68
68
|
let organizationEmails: EmailInterfaceRecipient[] = [];
|
|
69
69
|
const extraDescription = 'Dit bericht werd verstuurd naar ' + email + ', en werd automatisch doorgestuurd naar alle beheerders. Stel in Stamhoofd de e-mailadressen in om ervoor te zorgen dat antwoorden naar een specifiek e-mailadres worden verstuurd.';
|
|
70
70
|
|
|
@@ -80,8 +80,12 @@ export class ForwardHandler {
|
|
|
80
80
|
|
|
81
81
|
// Send back to receiver without including the original message to avoid spam
|
|
82
82
|
return {
|
|
83
|
-
from: email
|
|
84
|
-
|
|
83
|
+
from: email
|
|
84
|
+
? {
|
|
85
|
+
email,
|
|
86
|
+
}
|
|
87
|
+
: Email.getWebmasterToEmail(),
|
|
88
|
+
to: [{ email: from }],
|
|
85
89
|
subject: 'Ongeldig e-mailadres',
|
|
86
90
|
text: 'Beste,\n\nDe vereniging die je probeert te bereiken via ' + email + ' is helaas niet bereikbaar via dit e-mailadres. Dit e-mailadres wordt enkel gebruikt voor het versturen van automatische e-mails in naam van een vereniging. Probeer de vereniging te contacteren via een ander e-mailadres.\n\nBedankt.',
|
|
87
91
|
};
|
|
@@ -123,10 +127,18 @@ export class ForwardHandler {
|
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
const options = {
|
|
127
|
-
from: email
|
|
130
|
+
const options: EmailInterface = {
|
|
131
|
+
from: email
|
|
132
|
+
? {
|
|
133
|
+
email,
|
|
134
|
+
}
|
|
135
|
+
: Email.getWebmasterToEmail(),
|
|
128
136
|
to: defaultEmail,
|
|
129
|
-
replyTo: parsed.from?.text
|
|
137
|
+
replyTo: parsed.from?.text
|
|
138
|
+
? {
|
|
139
|
+
email: parsed.from?.text,
|
|
140
|
+
}
|
|
141
|
+
: undefined,
|
|
130
142
|
subject: parsed.subject ?? 'Doorgestuurd bericht',
|
|
131
143
|
text: parsed.text ? extraDescription + '\n\n' + parsed.text : undefined,
|
|
132
144
|
html: html,
|