@stamhoofd/backend 2.78.4 → 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/index.ts +1 -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 +1964 -4
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +175 -2
- 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 +7 -1
- 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/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +164 -0
- package/src/helpers/AdminPermissionChecker.ts +22 -1
- 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/PlatformMembershipService.ts +7 -2
- package/src/services/SSOService.ts +1 -1
- package/tests/e2e/register.test.ts +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
|
-
import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { AutoEncoderPatchType, ConvertArrayToPatchableArray, Decoder, isEmptyPatch, isPatchableArray, PatchableArray, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
5
|
import { AuditLog, BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, mergeTwoMembers, Organization, Platform, RateLimiter, Registration, RegistrationPeriod, User } from '@stamhoofd/models';
|
|
6
|
-
import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, GroupType, MembersBlob, MemberWithRegistrationsBlob, PermissionLevel } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType, EmergencyContact, GroupType, MemberDetails, MemberResponsibility, MembersBlob, MemberWithRegistrationsBlob, Parent, PermissionLevel } from '@stamhoofd/structures';
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
import { Email } from '@stamhoofd/email';
|
|
@@ -207,6 +207,11 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
207
207
|
|
|
208
208
|
await member.save();
|
|
209
209
|
|
|
210
|
+
// If parents changed or emergeny contacts: fetch family and merge data
|
|
211
|
+
if (patch.details && (!isEmptyPatch(patch.details?.parents) || !isEmptyPatch(patch.details?.emergencyContacts))) {
|
|
212
|
+
await PatchOrganizationMembersEndpoint.mergeDuplicateRelations(member, patch.details);
|
|
213
|
+
}
|
|
214
|
+
|
|
210
215
|
// Update documents
|
|
211
216
|
await Document.updateForMember(member.id);
|
|
212
217
|
|
|
@@ -257,6 +262,11 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
257
262
|
responsibilityRecord.startDate = patchResponsibility.startDate;
|
|
258
263
|
}
|
|
259
264
|
|
|
265
|
+
// Check maximum
|
|
266
|
+
if (responsibility) {
|
|
267
|
+
await this.checkResponsbilityLimits(responsibilityRecord, responsibility);
|
|
268
|
+
}
|
|
269
|
+
|
|
260
270
|
await responsibilityRecord.save();
|
|
261
271
|
shouldUpdateSetupSteps = true;
|
|
262
272
|
}
|
|
@@ -389,6 +399,9 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
389
399
|
|
|
390
400
|
model.startDate = put.startDate;
|
|
391
401
|
|
|
402
|
+
// Check maximum
|
|
403
|
+
await this.checkResponsbilityLimits(model, responsibility);
|
|
404
|
+
|
|
392
405
|
await model.save();
|
|
393
406
|
shouldUpdateSetupSteps = true;
|
|
394
407
|
}
|
|
@@ -731,6 +744,129 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
731
744
|
}
|
|
732
745
|
}
|
|
733
746
|
|
|
747
|
+
static async mergeDuplicateRelations(member: MemberWithRegistrations, patch: AutoEncoderPatchType<MemberDetails> | MemberDetails) {
|
|
748
|
+
const _familyMembers = await Member.getFamilyWithRegistrations(member.id);
|
|
749
|
+
const familyMembers: typeof _familyMembers = [];
|
|
750
|
+
// Only modify members if we have write access to them (this avoids issues with overriding data)
|
|
751
|
+
for (const member of _familyMembers) {
|
|
752
|
+
if (await Context.auth.canAccessMember(member, PermissionLevel.Write)) {
|
|
753
|
+
familyMembers.push(member);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Replace member with member
|
|
758
|
+
const memberIndex = familyMembers.findIndex(m => m.id === member.id);
|
|
759
|
+
if (memberIndex !== -1 && familyMembers.length >= 2) {
|
|
760
|
+
familyMembers[memberIndex] = member;
|
|
761
|
+
const parentMergeMap = MemberDetails.mergeParents(
|
|
762
|
+
familyMembers.map(m => m.details),
|
|
763
|
+
true, // Allow deletes
|
|
764
|
+
);
|
|
765
|
+
const contactsMergeMap = MemberDetails.mergeEmergencyContacts(
|
|
766
|
+
familyMembers.map(m => m.details),
|
|
767
|
+
true, // Allow deletes
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// If there were patches or puts of parents or emergency contacts
|
|
771
|
+
// Make sure that those patches have been applied even after a potential merge
|
|
772
|
+
// E.g. you only changed the email, but there was a more recent parent object in a different member
|
|
773
|
+
// -> avoid losing the email change
|
|
774
|
+
const parentPatches = isPatchableArray(patch.parents) ? patch.parents.getPatches() : [];
|
|
775
|
+
|
|
776
|
+
// Add puts
|
|
777
|
+
const parentPuts = isPatchableArray(patch.parents) ? patch.parents.getPuts().map(p => p.put) : patch.parents;
|
|
778
|
+
for (const put of parentPuts) {
|
|
779
|
+
if (!parentMergeMap.get(put.id)) {
|
|
780
|
+
// This one has not been merged
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const alternativeEmailsArr = new PatchableArray() as PatchableArray<string, string, string>;
|
|
785
|
+
for (const alternativeEmail of put.alternativeEmails) {
|
|
786
|
+
alternativeEmailsArr.addPut(alternativeEmail);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const p = Parent.patch({
|
|
790
|
+
...put,
|
|
791
|
+
alternativeEmails: alternativeEmailsArr,
|
|
792
|
+
createdAt: undefined, // Not allowed to change (should have already happened + the merge method will already chose the right value)
|
|
793
|
+
updatedAt: undefined, // Not allowed to change (should have already happened + the merge method will already chose the right value)
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
// Delete null values or empty strings
|
|
797
|
+
for (const key in p) {
|
|
798
|
+
if (p[key] === null || p[key] === '') {
|
|
799
|
+
delete p[key];
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
parentPatches.push(p);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Same for emergency contacts
|
|
807
|
+
const contactsPatches = isPatchableArray(patch.emergencyContacts) ? patch.emergencyContacts.getPatches() : [];
|
|
808
|
+
|
|
809
|
+
// Add puts
|
|
810
|
+
const contactsPuts = isPatchableArray(patch.emergencyContacts) ? patch.emergencyContacts.getPuts().map(p => p.put) : patch.emergencyContacts;
|
|
811
|
+
for (const put of contactsPuts) {
|
|
812
|
+
if (!contactsMergeMap.get(put.id)) {
|
|
813
|
+
// This one has not been merged
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const p = EmergencyContact.patch({
|
|
817
|
+
...put,
|
|
818
|
+
createdAt: undefined, // Not allowed to change (should have already happened + the merge method will already chose the right value)
|
|
819
|
+
updatedAt: undefined, // Not allowed to change (should have already happened + the merge method will already chose the right value)
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// Delete null values or empty strings
|
|
823
|
+
for (const key in p) {
|
|
824
|
+
if (p[key] === null || p[key] === '') {
|
|
825
|
+
delete p[key];
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
contactsPatches.push(p);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Apply patches
|
|
833
|
+
for (const parentPatch of parentPatches) {
|
|
834
|
+
for (const m of familyMembers) {
|
|
835
|
+
const arr = new PatchableArray() as PatchableArrayAutoEncoder<Parent>;
|
|
836
|
+
parentPatch.id = parentMergeMap.get(parentPatch.id) ?? parentPatch.id;
|
|
837
|
+
arr.addPatch(parentPatch);
|
|
838
|
+
m.details = m.details.patch({
|
|
839
|
+
parents: arr,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Apply patches
|
|
845
|
+
for (const contactPatch of contactsPatches) {
|
|
846
|
+
for (const m of familyMembers) {
|
|
847
|
+
const arr = new PatchableArray() as PatchableArrayAutoEncoder<EmergencyContact>;
|
|
848
|
+
contactPatch.id = contactsMergeMap.get(contactPatch.id) ?? contactPatch.id;
|
|
849
|
+
arr.addPatch(contactPatch);
|
|
850
|
+
m.details = m.details.patch({
|
|
851
|
+
emergencyContacts: arr,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
for (const m of familyMembers) {
|
|
857
|
+
m.details.cleanData();
|
|
858
|
+
|
|
859
|
+
if (await m.save() && m.id !== member.id) {
|
|
860
|
+
// Auto link users based on data
|
|
861
|
+
await MemberUserSyncer.onChangeMember(m);
|
|
862
|
+
|
|
863
|
+
// Update documents
|
|
864
|
+
await Document.updateForMember(m.id);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
734
870
|
static async findExistingMember(member: Member) {
|
|
735
871
|
if (!member.details.birthDay) {
|
|
736
872
|
return;
|
|
@@ -863,4 +999,41 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
863
999
|
organization,
|
|
864
1000
|
}).createMultiple(count);
|
|
865
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
async checkResponsbilityLimits(model: MemberResponsibilityRecord, responsibility: MemberResponsibility) {
|
|
1004
|
+
if (responsibility.maximumMembers !== null) {
|
|
1005
|
+
if (!model.getBaseStructure().isActive) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const query = MemberResponsibilityRecord.select()
|
|
1010
|
+
.where('responsibilityId', responsibility.id)
|
|
1011
|
+
.andWhere('organizationId', model.organizationId)
|
|
1012
|
+
.andWhere('groupId', model.groupId)
|
|
1013
|
+
.andWhere(MemberResponsibilityRecord.whereActive);
|
|
1014
|
+
|
|
1015
|
+
if (model.existsInDatabase) {
|
|
1016
|
+
query.andWhere('id', '!=', model.id);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const count = (await query.count()) + 1;
|
|
1020
|
+
|
|
1021
|
+
// Because it should be possible to move around responsibilities, we allow 1 extra
|
|
1022
|
+
const actualLimit = responsibility.maximumMembers <= 1 ? 2 : responsibility.maximumMembers;
|
|
1023
|
+
|
|
1024
|
+
if (count > actualLimit) {
|
|
1025
|
+
throw new SimpleError({
|
|
1026
|
+
code: 'invalid_field',
|
|
1027
|
+
message: 'Maximum members reached',
|
|
1028
|
+
human: responsibility.maximumMembers === 1
|
|
1029
|
+
? (model.groupId
|
|
1030
|
+
? $t('Je kan maar één lid hebben met de functie {responsibility} in deze leeftijdsgroep', { responsibility: responsibility.name })
|
|
1031
|
+
: $t('Je kan maar één lid hebben met de functie {responsibility}', { responsibility: responsibility.name }))
|
|
1032
|
+
: (model.groupId
|
|
1033
|
+
? $t('Je kan maximum {count} leden hebben met de functie {responsibility} in deze leeftijdsgroep', { count: responsibility.maximumMembers.toFixed(), responsibility: responsibility.name })
|
|
1034
|
+
: $t('Je kan maximum {count} leden hebben met de functie {responsibility}', { count: responsibility.maximumMembers.toFixed(), responsibility: responsibility.name })),
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
866
1039
|
}
|
|
@@ -49,7 +49,7 @@ export class PatchPlatformEndpoint extends Endpoint<
|
|
|
49
49
|
throw Context.auth.error();
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const platform = await Platform.
|
|
52
|
+
const platform = await Platform.getForEditing();
|
|
53
53
|
let shouldUpdateUserPermissions = false;
|
|
54
54
|
|
|
55
55
|
if (request.body.privateConfig) {
|
|
@@ -295,7 +295,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
295
295
|
],
|
|
296
296
|
});
|
|
297
297
|
|
|
298
|
-
const platform = await Platform.
|
|
298
|
+
const platform = await Platform.getForEditing();
|
|
299
299
|
platform.config.recordsConfiguration.recordCategories.push(recordCategory);
|
|
300
300
|
await platform.save();
|
|
301
301
|
|
|
@@ -355,7 +355,7 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
355
355
|
],
|
|
356
356
|
});
|
|
357
357
|
|
|
358
|
-
const platform = await Platform.
|
|
358
|
+
const platform = await Platform.getForEditing();
|
|
359
359
|
platform.config.recordsConfiguration.recordCategories.push(recordCategory);
|
|
360
360
|
await platform.save();
|
|
361
361
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
1
|
+
import { AutoEncoderPatchType, Decoder, isEmptyPatch, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Document, Member, RateLimiter } from '@stamhoofd/models';
|
|
@@ -124,6 +124,12 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
await member.save();
|
|
127
|
+
|
|
128
|
+
// If parents changed or emergeny contacts: fetch family and merge data
|
|
129
|
+
if (struct.details && (!isEmptyPatch(struct.details?.parents) || !isEmptyPatch(struct.details?.emergencyContacts))) {
|
|
130
|
+
await PatchOrganizationMembersEndpoint.mergeDuplicateRelations(member, struct.details);
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
await MemberUserSyncer.onChangeMember(member);
|
|
128
134
|
|
|
129
135
|
// Update documents
|
|
@@ -33,10 +33,8 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
33
33
|
period = await new RegistrationPeriodFactory({
|
|
34
34
|
startDate: new Date(2023, 0, 1),
|
|
35
35
|
endDate: new Date(2023, 11, 31),
|
|
36
|
+
previousPeriodId: previousPeriod.id,
|
|
36
37
|
}).create();
|
|
37
|
-
|
|
38
|
-
period.previousPeriodId = previousPeriod.id;
|
|
39
|
-
await period.save();
|
|
40
38
|
});
|
|
41
39
|
|
|
42
40
|
afterEach(() => {
|
|
@@ -2199,34 +2197,16 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
2199
2197
|
groupPrice: groupPrice1,
|
|
2200
2198
|
}).create();
|
|
2201
2199
|
|
|
2202
|
-
const group2 = await new GroupFactory({
|
|
2203
|
-
organization,
|
|
2204
|
-
price: 30,
|
|
2205
|
-
stock: 5,
|
|
2206
|
-
}).create();
|
|
2207
|
-
|
|
2208
|
-
const groupPrice = group2.settings.prices[0];
|
|
2209
|
-
|
|
2210
2200
|
const body = IDRegisterCheckout.create({
|
|
2211
2201
|
cart: IDRegisterCart.create({
|
|
2212
|
-
items: [
|
|
2213
|
-
IDRegisterItem.create({
|
|
2214
|
-
id: uuidv4(),
|
|
2215
|
-
replaceRegistrationIds: [],
|
|
2216
|
-
options: [],
|
|
2217
|
-
groupPrice,
|
|
2218
|
-
organizationId: organization.id,
|
|
2219
|
-
groupId: group2.id,
|
|
2220
|
-
memberId: member.id,
|
|
2221
|
-
}),
|
|
2222
|
-
],
|
|
2202
|
+
items: [],
|
|
2223
2203
|
balanceItems: [],
|
|
2224
2204
|
deleteRegistrationIds: [registration.id],
|
|
2225
2205
|
}),
|
|
2226
2206
|
administrationFee: 0,
|
|
2227
2207
|
freeContribution: 0,
|
|
2228
2208
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
2229
|
-
totalPrice:
|
|
2209
|
+
totalPrice: 0,
|
|
2230
2210
|
customer: null,
|
|
2231
2211
|
asOrganizationId: organization.id,
|
|
2232
2212
|
});
|
|
@@ -241,6 +241,18 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
241
241
|
});
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
if (request.body.asOrganizationId) {
|
|
245
|
+
// Do you have write access to this group?
|
|
246
|
+
if (!await Context.auth.canRegisterMembersInGroup(group, request.body.asOrganizationId)) {
|
|
247
|
+
throw new SimpleError({
|
|
248
|
+
code: 'forbidden',
|
|
249
|
+
message: 'No permission to register in this group',
|
|
250
|
+
human: $t('Je hebt geen toegangsrechten om een lid in te schrijven voor {group}', { group: group.settings.name }),
|
|
251
|
+
statusCode: 403,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
244
256
|
// Check if this member is already registered in this group?
|
|
245
257
|
const existingRegistrations = await Registration.where({ memberId: member.id, groupId: item.groupId, cycle: group.cycle, periodId: group.periodId, registeredAt: { sign: '!=', value: null } });
|
|
246
258
|
|
|
@@ -285,7 +285,7 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
285
285
|
tags: request.body.meta.tags as any,
|
|
286
286
|
});
|
|
287
287
|
|
|
288
|
-
const platform
|
|
288
|
+
const platform = await Platform.getShared();
|
|
289
289
|
const patchedMeta: OrganizationMetaData = organization.meta.patch(cleanedPatch);
|
|
290
290
|
for (const tag of patchedMeta.tags) {
|
|
291
291
|
if (!platform.config.tags.find(t => t.id === tag)) {
|
|
@@ -293,8 +293,7 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
|
|
297
|
-
organization.meta.tags = TagHelper.getAllTagsFromHierarchy(patchedMeta.tags, platformConfig.tags);
|
|
296
|
+
organization.meta.tags = TagHelper.getAllTagsFromHierarchy(patchedMeta.tags, platform.config.tags);
|
|
298
297
|
|
|
299
298
|
updateTags = true;
|
|
300
299
|
}
|
|
@@ -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';
|
|
@@ -185,6 +185,15 @@ export class AdminPermissionChecker {
|
|
|
185
185
|
return true;
|
|
186
186
|
}
|
|
187
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
|
+
|
|
188
197
|
// Check parent categories
|
|
189
198
|
const organizationPeriod = await this.getOrganizationCurrentPeriod(organization);
|
|
190
199
|
const parentCategories = group.getParentCategories(organizationPeriod.settings.categories);
|
|
@@ -197,6 +206,18 @@ export class AdminPermissionChecker {
|
|
|
197
206
|
return false;
|
|
198
207
|
}
|
|
199
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
|
+
|
|
200
221
|
/**
|
|
201
222
|
* Will throw error if not allowed to edit/add/delete this event
|
|
202
223
|
* @param event
|