@stamhoofd/backend 2.53.0 → 2.54.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/package.json +10 -10
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +10 -1
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +27 -4
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +25 -13
- package/src/excel-loaders/members.ts +147 -2
- package/src/excel-loaders/organizations.ts +252 -9
- package/src/excel-loaders/payments.ts +0 -2
- package/src/helpers/AdminPermissionChecker.ts +4 -1
- package/src/helpers/AuthenticatedStructures.ts +4 -0
- package/src/helpers/xlsxAddressTransformerColumnFactory.ts +8 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.54.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"@simonbackx/simple-encoding": "2.16.6",
|
|
37
37
|
"@simonbackx/simple-endpoints": "1.14.0",
|
|
38
38
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
39
|
-
"@stamhoofd/backend-i18n": "2.
|
|
40
|
-
"@stamhoofd/backend-middleware": "2.
|
|
41
|
-
"@stamhoofd/email": "2.
|
|
42
|
-
"@stamhoofd/models": "2.
|
|
43
|
-
"@stamhoofd/queues": "2.
|
|
44
|
-
"@stamhoofd/sql": "2.
|
|
45
|
-
"@stamhoofd/structures": "2.
|
|
46
|
-
"@stamhoofd/utility": "2.
|
|
39
|
+
"@stamhoofd/backend-i18n": "2.54.0",
|
|
40
|
+
"@stamhoofd/backend-middleware": "2.54.0",
|
|
41
|
+
"@stamhoofd/email": "2.54.0",
|
|
42
|
+
"@stamhoofd/models": "2.54.0",
|
|
43
|
+
"@stamhoofd/queues": "2.54.0",
|
|
44
|
+
"@stamhoofd/sql": "2.54.0",
|
|
45
|
+
"@stamhoofd/structures": "2.54.0",
|
|
46
|
+
"@stamhoofd/utility": "2.54.0",
|
|
47
47
|
"archiver": "^7.0.1",
|
|
48
48
|
"aws-sdk": "^2.885.0",
|
|
49
49
|
"axios": "1.6.8",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "d895bc5e468a87792398ca4e762d1216bf750baf"
|
|
67
67
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { Event, Group, Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
4
|
-
import { Event as EventStruct, GroupType, NamedObject } from '@stamhoofd/structures';
|
|
4
|
+
import { Event as EventStruct, GroupType, NamedObject, Group as GroupStruct } from '@stamhoofd/structures';
|
|
5
5
|
|
|
6
6
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
7
7
|
import { SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
@@ -218,6 +218,15 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
218
218
|
event.groupId = group.id;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
+
else {
|
|
222
|
+
if (patch.startDate || patch.endDate) {
|
|
223
|
+
// Correct period id if needed
|
|
224
|
+
const period = await RegistrationPeriod.getByDate(event.startDate);
|
|
225
|
+
if (event.groupId) {
|
|
226
|
+
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(GroupStruct.patch({ id: event.groupId }), period);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
221
230
|
|
|
222
231
|
if (type.isLocationRequired === true) {
|
|
223
232
|
PatchEventsEndpoint.throwIfAddressIsMissing(event);
|
|
@@ -8,6 +8,7 @@ import { Formatter } from '@stamhoofd/utility';
|
|
|
8
8
|
|
|
9
9
|
import { Email } from '@stamhoofd/email';
|
|
10
10
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
11
|
+
import { SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
11
12
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
12
13
|
import { Context } from '../../../helpers/Context';
|
|
13
14
|
import { MembershipCharger } from '../../../helpers/MembershipCharger';
|
|
@@ -440,10 +441,32 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
440
441
|
}
|
|
441
442
|
|
|
442
443
|
// Check duplicate memberships
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
const existing = await MemberPlatformMembership.select()
|
|
445
|
+
.where('memberId', member.id)
|
|
446
|
+
.where('membershipTypeId', put.membershipTypeId)
|
|
447
|
+
.where('periodId', put.periodId)
|
|
448
|
+
.where(
|
|
449
|
+
SQL.where('startDate', SQLWhereSign.LessEqual, put.startDate)
|
|
450
|
+
.and('endDate', SQLWhereSign.GreaterEqual, put.startDate),
|
|
451
|
+
)
|
|
452
|
+
.orWhere(
|
|
453
|
+
SQL.where('startDate', SQLWhereSign.LessEqual, put.endDate)
|
|
454
|
+
.and('endDate', SQLWhereSign.GreaterEqual, put.endDate),
|
|
455
|
+
)
|
|
456
|
+
.orWhere(
|
|
457
|
+
SQL.where('startDate', SQLWhereSign.GreaterEqual, put.startDate)
|
|
458
|
+
.and('endDate', SQLWhereSign.LessEqual, put.endDate),
|
|
459
|
+
)
|
|
460
|
+
.first(false);
|
|
461
|
+
|
|
462
|
+
if (existing) {
|
|
463
|
+
throw new SimpleError({
|
|
464
|
+
code: 'invalid_field',
|
|
465
|
+
field: 'startDate',
|
|
466
|
+
message: 'Invalid start date',
|
|
467
|
+
human: 'Je kan geen aansluiting toevoegen die overlapt met een bestaande aansluiting van hetzelfde type',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
447
470
|
|
|
448
471
|
const membership = new MemberPlatformMembership();
|
|
449
472
|
membership.id = put.id;
|
|
@@ -408,7 +408,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
408
408
|
balanceItem2.memberId = registration.memberId;
|
|
409
409
|
|
|
410
410
|
// If the paying organization hasn't paid yet, this should be hidden and move to pending as soon as the paying organization has paid
|
|
411
|
-
balanceItem2.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
411
|
+
balanceItem2.status = BalanceItemStatus.Hidden; // shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
412
412
|
await balanceItem2.save();
|
|
413
413
|
|
|
414
414
|
// do not add to createdBalanceItems array because we don't want to add this to the payment if we create a payment
|
|
@@ -419,7 +419,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
419
419
|
balanceItem.userId = user.id;
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
balanceItem.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
422
|
+
balanceItem.status = BalanceItemStatus.Hidden; // shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
423
423
|
balanceItem.pricePaid = 0;
|
|
424
424
|
|
|
425
425
|
// Connect the 'pay back' balance item to this balance item. As soon as this balance item is paid, we'll mark the other one as pending so the outstanding balance for the member increases
|
|
@@ -434,24 +434,24 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
434
434
|
const { item, registration } = bundle;
|
|
435
435
|
registration.reservedUntil = null;
|
|
436
436
|
|
|
437
|
-
if (shouldMarkValid) {
|
|
437
|
+
/* if (shouldMarkValid) {
|
|
438
438
|
await registration.markValid({ skipEmail: bundle.item.replaceRegistrations.length > 0 });
|
|
439
439
|
}
|
|
440
|
-
else {
|
|
441
|
-
|
|
442
|
-
|
|
440
|
+
else { */
|
|
441
|
+
// Reserve registration for 30 minutes (if needed)
|
|
442
|
+
const group = groups.find(g => g.id === registration.groupId);
|
|
443
443
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
await registration.save();
|
|
444
|
+
if (group && group.settings.maxMembers !== null) {
|
|
445
|
+
registration.reservedUntil = new Date(new Date().getTime() + 1000 * 60 * 30);
|
|
448
446
|
}
|
|
447
|
+
await registration.save();
|
|
448
|
+
// }
|
|
449
449
|
|
|
450
450
|
// Note: we should always create the balance items: even when the price is zero
|
|
451
451
|
// Otherwise we don't know which registrations to activate after payment
|
|
452
452
|
|
|
453
453
|
if (shouldMarkValid && item.calculatedPrice === 0) {
|
|
454
|
-
continue;
|
|
454
|
+
// continue;
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
// Create balance items
|
|
@@ -538,7 +538,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
538
538
|
if (oldestMember) {
|
|
539
539
|
balanceItem.memberId = oldestMember.id;
|
|
540
540
|
}
|
|
541
|
-
balanceItem.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
541
|
+
balanceItem.status = BalanceItemStatus.Hidden; // shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
542
542
|
await balanceItem.save();
|
|
543
543
|
createdBalanceItems.push(balanceItem);
|
|
544
544
|
}
|
|
@@ -563,7 +563,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
563
563
|
}
|
|
564
564
|
}
|
|
565
565
|
|
|
566
|
-
balanceItem.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
566
|
+
balanceItem.status = BalanceItemStatus.Hidden; // shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden;
|
|
567
567
|
await balanceItem.save();
|
|
568
568
|
|
|
569
569
|
createdBalanceItems.push(balanceItem);
|
|
@@ -580,6 +580,16 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
580
580
|
let paymentUrl: string | null = null;
|
|
581
581
|
let payment: Payment | null = null;
|
|
582
582
|
|
|
583
|
+
// Delaying markign as valid as late as possible so any errors will prevent creating valid balance items
|
|
584
|
+
async function markValidIfNeeded() {
|
|
585
|
+
if (shouldMarkValid) {
|
|
586
|
+
for (const balanceItem of [...createdBalanceItems, ...unrelatedCreatedBalanceItems]) {
|
|
587
|
+
// Mark vlaid
|
|
588
|
+
await balanceItem.markPaid(payment, organization);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
583
593
|
if (whoWillPayNow !== 'nobody') {
|
|
584
594
|
const mappedBalanceItems = new Map<BalanceItem, number>();
|
|
585
595
|
|
|
@@ -606,6 +616,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
606
616
|
checkout: request.body,
|
|
607
617
|
members,
|
|
608
618
|
});
|
|
619
|
+
await markValidIfNeeded();
|
|
609
620
|
|
|
610
621
|
if (response) {
|
|
611
622
|
paymentUrl = response.paymentUrl;
|
|
@@ -618,6 +629,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
618
629
|
}
|
|
619
630
|
}
|
|
620
631
|
else {
|
|
632
|
+
await markValidIfNeeded();
|
|
621
633
|
await BalanceItem.updateOutstanding([...createdBalanceItems, ...unrelatedCreatedBalanceItems]);
|
|
622
634
|
}
|
|
623
635
|
|
|
@@ -96,7 +96,6 @@ const sheet: XlsxTransformerSheet<PlatformMember, PlatformMember> = {
|
|
|
96
96
|
},
|
|
97
97
|
XlsxTransformerColumnHelper.createAddressColumns<PlatformMember>({
|
|
98
98
|
matchId: 'address',
|
|
99
|
-
identifier: 'Adres',
|
|
100
99
|
getAddress: ({ patchedMember: object }: PlatformMember) => {
|
|
101
100
|
// get member address if exists
|
|
102
101
|
const memberAddress = object.details.address;
|
|
@@ -227,7 +226,6 @@ const sheet: XlsxTransformerSheet<PlatformMember, PlatformMember> = {
|
|
|
227
226
|
},
|
|
228
227
|
...XlsxTransformerColumnHelper.createColumnsForAddresses<PlatformMember>({
|
|
229
228
|
matchIdStart: 'unverifiedAddresses',
|
|
230
|
-
identifier: 'Niet-geverifieerd adres',
|
|
231
229
|
getAddresses: object => object.patchedMember.details.unverifiedAddresses,
|
|
232
230
|
limit: 2,
|
|
233
231
|
}),
|
|
@@ -277,6 +275,153 @@ const sheet: XlsxTransformerSheet<PlatformMember, PlatformMember> = {
|
|
|
277
275
|
}
|
|
278
276
|
},
|
|
279
277
|
},
|
|
278
|
+
|
|
279
|
+
// Registration records
|
|
280
|
+
{
|
|
281
|
+
match(id) {
|
|
282
|
+
if (id.startsWith('groups.')) {
|
|
283
|
+
const splitted = id.split('.');
|
|
284
|
+
if (splitted.length < 3) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const groupId = splitted[1];
|
|
289
|
+
const recordName = splitted[2];
|
|
290
|
+
|
|
291
|
+
function getRegistration(object: PlatformMember) {
|
|
292
|
+
return object.filterRegistrations({ groupIds: [groupId] })[0] ?? null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (recordName === 'price') {
|
|
296
|
+
// Tarief
|
|
297
|
+
return [
|
|
298
|
+
{
|
|
299
|
+
id: `groups.${groupId}.${recordName}`,
|
|
300
|
+
name: 'Tarief',
|
|
301
|
+
width: 30,
|
|
302
|
+
getValue: (member: PlatformMember) => {
|
|
303
|
+
const registration = getRegistration(member);
|
|
304
|
+
if (!registration) {
|
|
305
|
+
return {
|
|
306
|
+
value: '',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
value: registration.groupPrice.name,
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (recordName === 'optionMenu') {
|
|
319
|
+
if (splitted.length < 4) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const menuId = splitted[3];
|
|
324
|
+
|
|
325
|
+
if (splitted.length > 4) {
|
|
326
|
+
const optionId = splitted[4];
|
|
327
|
+
const returnAmount = splitted.length > 5 && splitted[5] === 'amount';
|
|
328
|
+
|
|
329
|
+
// Option menu
|
|
330
|
+
return [
|
|
331
|
+
{
|
|
332
|
+
id: `groups.${groupId}.${recordName}.${menuId}.${optionId}${returnAmount ? '.amount' : ''}`,
|
|
333
|
+
name: 'Keuzemenu aantal',
|
|
334
|
+
width: 30,
|
|
335
|
+
getValue: (member: PlatformMember) => {
|
|
336
|
+
const registration = getRegistration(member);
|
|
337
|
+
if (!registration) {
|
|
338
|
+
return {
|
|
339
|
+
value: '',
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const options = registration.options.filter(o => o.optionMenu.id === menuId && o.option.id === optionId);
|
|
343
|
+
|
|
344
|
+
if (!options.length) {
|
|
345
|
+
return {
|
|
346
|
+
value: '',
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
style: options.length === 1 && returnAmount
|
|
352
|
+
? {
|
|
353
|
+
numberFormat: {
|
|
354
|
+
id: XlsxBuiltInNumberFormat.Number,
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
: {},
|
|
358
|
+
value: options.length === 1 && returnAmount ? options[0].amount : options.map(option => returnAmount ? option.amount : option).join(', '),
|
|
359
|
+
};
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Option menu
|
|
366
|
+
return [
|
|
367
|
+
{
|
|
368
|
+
id: `groups.${groupId}.${recordName}.${menuId}`,
|
|
369
|
+
name: 'Keuzemenu',
|
|
370
|
+
width: 30,
|
|
371
|
+
getValue: (member: PlatformMember) => {
|
|
372
|
+
const registration = getRegistration(member);
|
|
373
|
+
if (!registration) {
|
|
374
|
+
return {
|
|
375
|
+
value: '',
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const options = registration.options.filter(o => o.optionMenu.id === menuId);
|
|
379
|
+
|
|
380
|
+
if (!options.length) {
|
|
381
|
+
return {
|
|
382
|
+
value: '',
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
value: options.map(option => (option.amount > 1 ? `${option.amount}x ` : '') + option.option.name).join(', '),
|
|
388
|
+
};
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (recordName === 'recordAnswers') {
|
|
395
|
+
if (splitted.length < 4) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const recordId = splitted[3];
|
|
400
|
+
return [
|
|
401
|
+
{
|
|
402
|
+
id: `groups.${groupId}.${recordName}.${recordId}`,
|
|
403
|
+
name: 'Vraag',
|
|
404
|
+
width: 35,
|
|
405
|
+
getValue: (member: PlatformMember) => {
|
|
406
|
+
const registration = getRegistration(member);
|
|
407
|
+
if (!registration) {
|
|
408
|
+
return {
|
|
409
|
+
value: '',
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
value: registration.recordAnswers.get(recordId)?.excelValues[0]?.value ?? '',
|
|
415
|
+
};
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
},
|
|
280
425
|
],
|
|
281
426
|
};
|
|
282
427
|
|
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
import { XlsxTransformerSheet } from '@stamhoofd/excel-writer';
|
|
2
|
-
import { ExcelExportType, LimitedFilteredRequest, Organization as OrganizationStruct } from '@stamhoofd/structures';
|
|
2
|
+
import { Platform as PlatformStruct, ExcelExportType, LimitedFilteredRequest, Organization as OrganizationStruct, MemberResponsibilityRecord as MemberResponsibilityRecordStruct, PaginatedResponse, MemberWithRegistrationsBlob, Premise } from '@stamhoofd/structures';
|
|
3
3
|
import { GetOrganizationsEndpoint } from '../endpoints/admin/organizations/GetOrganizationsEndpoint';
|
|
4
4
|
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint';
|
|
5
|
+
import { XlsxTransformerColumnHelper } from '../helpers/xlsxAddressTransformerColumnFactory';
|
|
6
|
+
import { Group, Member, MemberResponsibilityRecord } from '@stamhoofd/models';
|
|
7
|
+
import { Formatter, Sorter } from '@stamhoofd/utility';
|
|
8
|
+
import { ArrayDecoder, field } from '@simonbackx/simple-encoding';
|
|
9
|
+
import { AuthenticatedStructures } from '../helpers/AuthenticatedStructures';
|
|
10
|
+
|
|
11
|
+
class MemberResponsibilityRecordWithMember extends MemberResponsibilityRecordStruct {
|
|
12
|
+
@field({ decoder: MemberWithRegistrationsBlob })
|
|
13
|
+
member: MemberWithRegistrationsBlob;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class OrganizationWithResponsibilities extends OrganizationStruct {
|
|
17
|
+
@field({ decoder: new ArrayDecoder(MemberResponsibilityRecordWithMember) })
|
|
18
|
+
responsibilities: MemberResponsibilityRecordWithMember[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class MemberResponsibilityRecordWithMemberAndOrganization extends MemberResponsibilityRecordWithMember {
|
|
22
|
+
@field({ decoder: OrganizationWithResponsibilities })
|
|
23
|
+
organization: OrganizationWithResponsibilities;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Object = OrganizationWithResponsibilities;
|
|
5
27
|
|
|
6
28
|
// Assign to a typed variable to assure we have correct type checking in place
|
|
7
|
-
const sheet: XlsxTransformerSheet<
|
|
29
|
+
const sheet: XlsxTransformerSheet<Object, Object> = {
|
|
8
30
|
id: 'organizations',
|
|
9
|
-
name: '
|
|
31
|
+
name: 'Groepen',
|
|
10
32
|
columns: [
|
|
11
33
|
{
|
|
12
34
|
id: 'id',
|
|
13
35
|
name: 'ID',
|
|
14
|
-
width:
|
|
15
|
-
getValue: (object:
|
|
36
|
+
width: 35,
|
|
37
|
+
getValue: (object: Object) => ({
|
|
16
38
|
value: object.id,
|
|
17
39
|
}),
|
|
18
40
|
},
|
|
@@ -20,7 +42,7 @@ const sheet: XlsxTransformerSheet<OrganizationStruct, OrganizationStruct> = {
|
|
|
20
42
|
id: 'uri',
|
|
21
43
|
name: 'Groepsnummer',
|
|
22
44
|
width: 20,
|
|
23
|
-
getValue: (object:
|
|
45
|
+
getValue: (object: Object) => ({
|
|
24
46
|
value: object.uri,
|
|
25
47
|
}),
|
|
26
48
|
},
|
|
@@ -28,20 +50,241 @@ const sheet: XlsxTransformerSheet<OrganizationStruct, OrganizationStruct> = {
|
|
|
28
50
|
id: 'name',
|
|
29
51
|
name: 'Naam',
|
|
30
52
|
width: 50,
|
|
31
|
-
getValue: (object:
|
|
53
|
+
getValue: (object: Object) => ({
|
|
32
54
|
value: object.name,
|
|
33
55
|
}),
|
|
34
56
|
},
|
|
57
|
+
{
|
|
58
|
+
id: 'tags',
|
|
59
|
+
name: 'Tags',
|
|
60
|
+
width: 50,
|
|
61
|
+
getValue: (object: Object) => {
|
|
62
|
+
const platform = PlatformStruct.shared;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
value: object.meta.tags.map(tag => platform.config.tags.find(t => t.id === tag)?.name ?? 'Onbekend').join(', '),
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
XlsxTransformerColumnHelper.createAddressColumns<OrganizationStruct>({
|
|
70
|
+
matchId: 'address',
|
|
71
|
+
getAddress: object => object.address,
|
|
72
|
+
}),
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const responsibilities: XlsxTransformerSheet<Object, MemberResponsibilityRecordWithMemberAndOrganization> = {
|
|
77
|
+
id: 'responsibilities',
|
|
78
|
+
name: 'Functies',
|
|
79
|
+
transform(organization) {
|
|
80
|
+
return organization.responsibilities.map(r => MemberResponsibilityRecordWithMemberAndOrganization.create({
|
|
81
|
+
...r,
|
|
82
|
+
organization,
|
|
83
|
+
}));
|
|
84
|
+
},
|
|
85
|
+
columns: [
|
|
86
|
+
{
|
|
87
|
+
id: 'organization.id',
|
|
88
|
+
name: 'ID',
|
|
89
|
+
width: 35,
|
|
90
|
+
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => ({
|
|
91
|
+
value: object.organization.id,
|
|
92
|
+
}),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'organization.uri',
|
|
96
|
+
name: 'Groepsnummer',
|
|
97
|
+
width: 20,
|
|
98
|
+
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => ({
|
|
99
|
+
value: object.organization.uri,
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'organization.name',
|
|
104
|
+
name: 'Groepsnaam',
|
|
105
|
+
width: 50,
|
|
106
|
+
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => ({
|
|
107
|
+
value: object.organization.name,
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'responsibility.name',
|
|
112
|
+
name: 'Functie',
|
|
113
|
+
width: 50,
|
|
114
|
+
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => {
|
|
115
|
+
const platform = PlatformStruct.shared;
|
|
116
|
+
const responsibility = platform.config.responsibilities.find(r => r.id === object.responsibilityId) ?? object.organization.privateMeta?.responsibilities.find(r => r.id === object.responsibilityId);
|
|
117
|
+
|
|
118
|
+
if (!responsibility) {
|
|
119
|
+
return {
|
|
120
|
+
value: 'Onbekende functie',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
value: responsibility.name + (responsibility.isGroupBased ? ' van ' + (object.group?.settings.name ?? 'Onbekende groep') : ''),
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'responsibility.member.firstName',
|
|
131
|
+
name: 'Voornaam',
|
|
132
|
+
width: 30,
|
|
133
|
+
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => ({
|
|
134
|
+
value: object.member.firstName,
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'responsibility.member.lastName',
|
|
139
|
+
name: 'Achternaam',
|
|
140
|
+
width: 30,
|
|
141
|
+
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => ({
|
|
142
|
+
value: object.member.details.lastName,
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'responsibility.member.email',
|
|
147
|
+
name: 'E-mailadres lid',
|
|
148
|
+
width: 50,
|
|
149
|
+
getValue: (object: MemberResponsibilityRecordWithMemberAndOrganization) => ({
|
|
150
|
+
value: object.member.details.email,
|
|
151
|
+
}),
|
|
152
|
+
},
|
|
153
|
+
XlsxTransformerColumnHelper.createAddressColumns<MemberResponsibilityRecordWithMemberAndOrganization>({
|
|
154
|
+
matchId: 'responsibility.member.address',
|
|
155
|
+
getAddress: object => object.member.details.address ?? object.member.details.parents[0]?.address ?? object.member.details.parents[1]?.address ?? null,
|
|
156
|
+
}),
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
type PremiseWithOrganization = { organization: Object; premise: Premise };
|
|
161
|
+
const premises: XlsxTransformerSheet<Object, PremiseWithOrganization> = {
|
|
162
|
+
id: 'premises',
|
|
163
|
+
name: 'Lokalen',
|
|
164
|
+
transform(organization) {
|
|
165
|
+
return organization.privateMeta?.premises.map(r => ({
|
|
166
|
+
organization,
|
|
167
|
+
premise: r,
|
|
168
|
+
})) ?? [];
|
|
169
|
+
},
|
|
170
|
+
columns: [
|
|
171
|
+
{
|
|
172
|
+
id: 'organization.id',
|
|
173
|
+
name: 'ID',
|
|
174
|
+
width: 35,
|
|
175
|
+
getValue: (object: PremiseWithOrganization) => ({
|
|
176
|
+
value: object.organization.id,
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'organization.uri',
|
|
181
|
+
name: 'Groepsnummer',
|
|
182
|
+
width: 20,
|
|
183
|
+
getValue: (object: PremiseWithOrganization) => ({
|
|
184
|
+
value: object.organization.uri,
|
|
185
|
+
}),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'organization.name',
|
|
189
|
+
name: 'Groepsnaam',
|
|
190
|
+
width: 50,
|
|
191
|
+
getValue: (object: PremiseWithOrganization) => ({
|
|
192
|
+
value: object.organization.name,
|
|
193
|
+
}),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'premise.name',
|
|
197
|
+
name: 'Naam',
|
|
198
|
+
width: 20,
|
|
199
|
+
getValue: (object: PremiseWithOrganization) => ({
|
|
200
|
+
value: object.premise.name,
|
|
201
|
+
}),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'premise.type',
|
|
205
|
+
name: 'Type',
|
|
206
|
+
width: 20,
|
|
207
|
+
getValue: (object: PremiseWithOrganization) => {
|
|
208
|
+
const ids = object.premise.premiseTypeIds;
|
|
209
|
+
const platform = PlatformStruct.shared;
|
|
210
|
+
return {
|
|
211
|
+
value: ids.map(id => platform.config.premiseTypes.find(t => t.id === id)?.name ?? 'Onbekend').join(', '),
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
XlsxTransformerColumnHelper.createAddressColumns<PremiseWithOrganization>({
|
|
216
|
+
matchId: 'premise.address',
|
|
217
|
+
getAddress: object => object.premise.address,
|
|
218
|
+
}),
|
|
35
219
|
],
|
|
36
220
|
};
|
|
37
221
|
|
|
38
222
|
ExportToExcelEndpoint.loaders.set(ExcelExportType.Organizations, {
|
|
39
223
|
fetch: async (query: LimitedFilteredRequest) => {
|
|
40
|
-
const
|
|
224
|
+
const organizations = await GetOrganizationsEndpoint.buildData(query);
|
|
225
|
+
|
|
226
|
+
// Now load all responsibilities with members with an active responsibility for theses organizations
|
|
227
|
+
const organizationIds = organizations.results.map(o => o.id);
|
|
228
|
+
const responsibilities = organizationIds.length
|
|
229
|
+
? await MemberResponsibilityRecord.select()
|
|
230
|
+
.where('organizationId', organizationIds)
|
|
231
|
+
.where('endDate', null)
|
|
232
|
+
.fetch()
|
|
233
|
+
: [];
|
|
234
|
+
|
|
235
|
+
// Load groups and members
|
|
236
|
+
const groupIds = Formatter.uniqueArray(responsibilities.map(o => o.groupId).filter(g => g !== null));
|
|
237
|
+
const memberIds = Formatter.uniqueArray(responsibilities.map(o => o.memberId).filter(m => m !== null));
|
|
238
|
+
|
|
239
|
+
const members = await Member.getBlobByIds(...memberIds);
|
|
240
|
+
const groups = await Group.getByIDs(...groupIds);
|
|
241
|
+
const memberStructs = await AuthenticatedStructures.members(members);
|
|
242
|
+
const groupStructs = await AuthenticatedStructures.groups(groups);
|
|
243
|
+
const platform = PlatformStruct.shared;
|
|
244
|
+
|
|
245
|
+
const mappedOrganizations = organizations.results.map((o) => {
|
|
246
|
+
const resp = responsibilities.filter(r => r.organizationId === o.id);
|
|
247
|
+
|
|
248
|
+
const mappedResponsibilities = resp.map((r) => {
|
|
249
|
+
const member = memberStructs.find(m => m.id === r.memberId);
|
|
250
|
+
const group = groupStructs.find(g => g.id === r.groupId);
|
|
251
|
+
|
|
252
|
+
return MemberResponsibilityRecordWithMember.create({
|
|
253
|
+
...r,
|
|
254
|
+
member: member,
|
|
255
|
+
group: group ? group : null,
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Sort responsibilites by index in platform config
|
|
260
|
+
// and, if the same, sort by order of default age group id if it has a group
|
|
261
|
+
mappedResponsibilities.sort((a, b) => {
|
|
262
|
+
const aIndex = platform.config.responsibilities.findIndex(r => r.id === a.responsibilityId);
|
|
263
|
+
const bIndex = platform.config.responsibilities.findIndex(r => r.id === b.responsibilityId);
|
|
264
|
+
|
|
265
|
+
const groupAIndex = platform.config.defaultAgeGroups.findIndex(g => g.id === a.group?.defaultAgeGroupId);
|
|
266
|
+
const groupBIndex = platform.config.defaultAgeGroups.findIndex(g => g.id === b.group?.defaultAgeGroupId);
|
|
267
|
+
|
|
268
|
+
return Sorter.stack(
|
|
269
|
+
aIndex - bIndex,
|
|
270
|
+
groupAIndex - groupBIndex,
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return OrganizationWithResponsibilities.create({
|
|
275
|
+
...o,
|
|
276
|
+
responsibilities: mappedResponsibilities,
|
|
277
|
+
});
|
|
278
|
+
});
|
|
41
279
|
|
|
42
|
-
return
|
|
280
|
+
return new PaginatedResponse({
|
|
281
|
+
results: mappedOrganizations,
|
|
282
|
+
next: organizations.next,
|
|
283
|
+
});
|
|
43
284
|
},
|
|
44
285
|
sheets: [
|
|
45
286
|
sheet,
|
|
287
|
+
responsibilities,
|
|
288
|
+
premises,
|
|
46
289
|
],
|
|
47
290
|
});
|
|
@@ -479,7 +479,6 @@ function getPayingOrganizationColumns(): XlsxTransformerColumn<PaymentGeneral>[]
|
|
|
479
479
|
XlsxTransformerColumnHelper.createAddressColumns<PaymentGeneralWithStripeAccount>({
|
|
480
480
|
matchId: 'payingOrganization.address',
|
|
481
481
|
getAddress: object => object.payingOrganization?.address,
|
|
482
|
-
identifier: 'Adres betalende groep',
|
|
483
482
|
}),
|
|
484
483
|
];
|
|
485
484
|
}
|
|
@@ -539,7 +538,6 @@ function getInvoiceColumns(): XlsxTransformerColumn<PaymentGeneral>[] {
|
|
|
539
538
|
XlsxTransformerColumnHelper.createAddressColumns<PaymentGeneralWithStripeAccount>({
|
|
540
539
|
matchId: 'customer.company.address',
|
|
541
540
|
getAddress: object => object.customer?.company?.address,
|
|
542
|
-
identifier: 'Adres',
|
|
543
541
|
}),
|
|
544
542
|
{
|
|
545
543
|
id: 'customer.company.administrationEmail',
|
|
@@ -131,7 +131,10 @@ export class AdminPermissionChecker {
|
|
|
131
131
|
if (organizationId) {
|
|
132
132
|
// If request is scoped to a different organization
|
|
133
133
|
if (this.organization && organizationId !== this.organization.id) {
|
|
134
|
-
|
|
134
|
+
if (STAMHOOFD.userMode === 'organization') {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// Otherwise allow for convenience
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
// If user is limited to scope
|
|
@@ -315,6 +315,10 @@ export class AuthenticatedStructures {
|
|
|
315
315
|
return structs;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
static async members(members: MemberWithRegistrations[]): Promise<MemberWithRegistrationsBlob[]> {
|
|
319
|
+
return (await this.membersBlob(members, false)).members;
|
|
320
|
+
}
|
|
321
|
+
|
|
318
322
|
static async membersBlob(members: MemberWithRegistrations[], includeContextOrganization = false, includeUser?: User): Promise<MembersBlob> {
|
|
319
323
|
if (members.length === 0 && !includeUser) {
|
|
320
324
|
return MembersBlob.create({ members: [], organizations: [] });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { XlsxTransformerColumn } from '@stamhoofd/excel-writer';
|
|
2
|
-
import { Address, CountryHelper,
|
|
2
|
+
import { Address, CountryHelper, Parent, ParentTypeHelper, PlatformMember } from '@stamhoofd/structures';
|
|
3
3
|
|
|
4
4
|
export class XlsxTransformerColumnHelper {
|
|
5
5
|
static formatBoolean(value: boolean | undefined | null): string {
|
|
@@ -21,14 +21,13 @@ export class XlsxTransformerColumnHelper {
|
|
|
21
21
|
];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
static createColumnsForAddresses<T>({ limit, getAddresses, matchIdStart
|
|
24
|
+
static createColumnsForAddresses<T>({ limit, getAddresses, matchIdStart }: { limit: number; getAddresses: (object: T) => Address[]; matchIdStart: string }): XlsxTransformerColumn<T>[] {
|
|
25
25
|
const result: XlsxTransformerColumn<unknown>[] = [];
|
|
26
26
|
|
|
27
27
|
for (let i = 0; i <= limit; i++) {
|
|
28
28
|
const column = this.createAddressColumns({
|
|
29
29
|
matchId: `${matchIdStart}.${i}`,
|
|
30
30
|
getAddress: (object: T) => getAddresses(object)[i],
|
|
31
|
-
identifier: `${identifier} ${i + 1}`,
|
|
32
31
|
});
|
|
33
32
|
|
|
34
33
|
result.push(column);
|
|
@@ -94,18 +93,12 @@ export class XlsxTransformerColumnHelper {
|
|
|
94
93
|
XlsxTransformerColumnHelper.createAddressColumns<PlatformMember>({
|
|
95
94
|
matchId: getId('address'),
|
|
96
95
|
getAddress: member => getParent(member)?.address,
|
|
97
|
-
identifier: getName('Adres'),
|
|
98
96
|
}),
|
|
99
97
|
];
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
static createAddressColumns<T>({ matchId,
|
|
100
|
+
static createAddressColumns<T>({ matchId, getAddress }: { matchId: string; getAddress: (object: T) => Address | null | undefined }): XlsxTransformerColumn<T> {
|
|
103
101
|
const getId = (value: string) => matchId + '.' + value;
|
|
104
|
-
const identifierText = identifier ? `${identifier} - ` : '';
|
|
105
|
-
const getName = (value: string) => {
|
|
106
|
-
const name = `${identifierText}${value}`;
|
|
107
|
-
return name[0].toUpperCase() + name.slice(1);
|
|
108
|
-
};
|
|
109
102
|
|
|
110
103
|
return {
|
|
111
104
|
match: (id) => {
|
|
@@ -113,7 +106,7 @@ export class XlsxTransformerColumnHelper {
|
|
|
113
106
|
return [
|
|
114
107
|
{
|
|
115
108
|
id: getId('street'),
|
|
116
|
-
name:
|
|
109
|
+
name: `Straat`,
|
|
117
110
|
width: 30,
|
|
118
111
|
getValue: (object: T) => {
|
|
119
112
|
const address = getAddress(object);
|
|
@@ -124,7 +117,7 @@ export class XlsxTransformerColumnHelper {
|
|
|
124
117
|
},
|
|
125
118
|
{
|
|
126
119
|
id: getId('number'),
|
|
127
|
-
name:
|
|
120
|
+
name: 'Nummer',
|
|
128
121
|
width: 20,
|
|
129
122
|
getValue: (object: T) => {
|
|
130
123
|
const address = getAddress(object);
|
|
@@ -135,7 +128,7 @@ export class XlsxTransformerColumnHelper {
|
|
|
135
128
|
},
|
|
136
129
|
{
|
|
137
130
|
id: getId('postalCode'),
|
|
138
|
-
name:
|
|
131
|
+
name: 'Postcode',
|
|
139
132
|
width: 20,
|
|
140
133
|
getValue: (object: T) => {
|
|
141
134
|
const address = getAddress(object);
|
|
@@ -146,7 +139,7 @@ export class XlsxTransformerColumnHelper {
|
|
|
146
139
|
},
|
|
147
140
|
{
|
|
148
141
|
id: getId('city'),
|
|
149
|
-
name:
|
|
142
|
+
name: 'Stad',
|
|
150
143
|
width: 20,
|
|
151
144
|
getValue: (object: T) => {
|
|
152
145
|
const address = getAddress(object);
|
|
@@ -157,7 +150,7 @@ export class XlsxTransformerColumnHelper {
|
|
|
157
150
|
},
|
|
158
151
|
{
|
|
159
152
|
id: getId('country'),
|
|
160
|
-
name:
|
|
153
|
+
name: 'Land',
|
|
161
154
|
width: 20,
|
|
162
155
|
getValue: (object: T) => {
|
|
163
156
|
const address = getAddress(object);
|