@stamhoofd/backend 2.125.0 → 2.125.2
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 +17 -17
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +8 -9
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +29 -50
- package/src/helpers/AdminPermissionChecker.ts +19 -7
- package/src/helpers/AuthenticatedStructures.ts +1 -3
- package/src/sql-filters/payments.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.125.
|
|
3
|
+
"version": "2.125.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -57,20 +57,20 @@
|
|
|
57
57
|
"@simonbackx/simple-endpoints": "1.21.1",
|
|
58
58
|
"@simonbackx/simple-errors": "1.5.0",
|
|
59
59
|
"@simonbackx/simple-logging": "1.0.1",
|
|
60
|
-
"@stamhoofd/backend-env": "2.125.
|
|
61
|
-
"@stamhoofd/backend-i18n": "2.125.
|
|
62
|
-
"@stamhoofd/backend-middleware": "2.125.
|
|
63
|
-
"@stamhoofd/crons": "2.125.
|
|
64
|
-
"@stamhoofd/email": "2.125.
|
|
65
|
-
"@stamhoofd/excel-writer": "2.125.
|
|
66
|
-
"@stamhoofd/logging": "2.125.
|
|
67
|
-
"@stamhoofd/models": "2.125.
|
|
68
|
-
"@stamhoofd/object-differ": "2.125.
|
|
69
|
-
"@stamhoofd/queues": "2.125.
|
|
70
|
-
"@stamhoofd/sql": "2.125.
|
|
71
|
-
"@stamhoofd/structures": "2.125.
|
|
72
|
-
"@stamhoofd/types": "2.125.
|
|
73
|
-
"@stamhoofd/utility": "2.125.
|
|
60
|
+
"@stamhoofd/backend-env": "2.125.2",
|
|
61
|
+
"@stamhoofd/backend-i18n": "2.125.2",
|
|
62
|
+
"@stamhoofd/backend-middleware": "2.125.2",
|
|
63
|
+
"@stamhoofd/crons": "2.125.2",
|
|
64
|
+
"@stamhoofd/email": "2.125.2",
|
|
65
|
+
"@stamhoofd/excel-writer": "2.125.2",
|
|
66
|
+
"@stamhoofd/logging": "2.125.2",
|
|
67
|
+
"@stamhoofd/models": "2.125.2",
|
|
68
|
+
"@stamhoofd/object-differ": "2.125.2",
|
|
69
|
+
"@stamhoofd/queues": "2.125.2",
|
|
70
|
+
"@stamhoofd/sql": "2.125.2",
|
|
71
|
+
"@stamhoofd/structures": "2.125.2",
|
|
72
|
+
"@stamhoofd/types": "2.125.2",
|
|
73
|
+
"@stamhoofd/utility": "2.125.2",
|
|
74
74
|
"archiver": "7.0.1",
|
|
75
75
|
"axios": "1.16.0",
|
|
76
76
|
"base-x": "3.0.11",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"stripe": "16.12.0"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
|
-
"@stamhoofd/test-utils": "2.125.
|
|
94
|
+
"@stamhoofd/test-utils": "2.125.2",
|
|
95
95
|
"@types/cookie": "0.6.0",
|
|
96
96
|
"@types/luxon": "3.7.1",
|
|
97
97
|
"@types/mailparser": "3.4.6",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"publishConfig": {
|
|
108
108
|
"access": "public"
|
|
109
109
|
},
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "bda32cee30660a98c7563c405ce29dc0da7cab65"
|
|
111
111
|
}
|
package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
|
2
2
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { DetailedReceivableBalance, PaymentStatus, PermissionLevel, ReceivableBalanceType } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
|
+
import type { MemberWithUsersAndRegistrations } from '@stamhoofd/models';
|
|
5
6
|
import { BalanceItem, BalanceItemPayment, CachedBalance, Member, MemberUser, Payment, Registration } from '@stamhoofd/models';
|
|
6
7
|
import { Context } from '../../../../helpers/Context.js';
|
|
7
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
@@ -36,20 +37,18 @@ export class GetReceivableBalanceEndpoint extends Endpoint<Params, Query, Body,
|
|
|
36
37
|
await Context.authenticate();
|
|
37
38
|
|
|
38
39
|
if (!await Context.auth.canManageFinances(organization.id)) {
|
|
39
|
-
|
|
40
|
-
if (request.params.type === ReceivableBalanceType.
|
|
41
|
-
const member = await Member.getByIdWithUsersAndRegistrations(request.params.id);
|
|
42
|
-
if (!member || !await Context.auth.hasFinancialMemberAccess(member, PermissionLevel.Read, organization.id)) {
|
|
43
|
-
throw Context.auth.error();
|
|
44
|
-
}
|
|
45
|
-
} else if (request.params.type === ReceivableBalanceType.registration) {
|
|
40
|
+
let member: MemberWithUsersAndRegistrations | null = null;
|
|
41
|
+
if (request.params.type === ReceivableBalanceType.registration) {
|
|
46
42
|
const registration = await Registration.select().where('id', request.params.id).first(false);
|
|
47
43
|
if (!registration) throw Context.auth.error();
|
|
48
|
-
|
|
44
|
+
member = await Member.getByIdWithUsersAndRegistrations(registration.memberId);
|
|
45
|
+
} else if (request.params.type === ReceivableBalanceType.member) {
|
|
46
|
+
member = await Member.getByIdWithUsersAndRegistrations(request.params.id);
|
|
49
47
|
if (!member || !await Context.auth.hasFinancialMemberAccess(member, PermissionLevel.Read, organization.id)) {
|
|
50
48
|
throw Context.auth.error();
|
|
51
49
|
}
|
|
52
|
-
}
|
|
50
|
+
}
|
|
51
|
+
if (!member || !await Context.auth.hasFinancialMemberAccess(member, PermissionLevel.Read, organization.id)) {
|
|
53
52
|
throw Context.auth.error();
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -316,7 +316,7 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
const maximumStart = 1000 * 60 * 60 * 24 * 31 * 8; // 8 months in advance
|
|
319
|
-
if (period.startDate > new Date(Date.now() + maximumStart)) {
|
|
319
|
+
if (period.startDate > new Date(Date.now() + maximumStart) && STAMHOOFD.userMode === 'platform') {
|
|
320
320
|
throw new SimpleError({
|
|
321
321
|
code: 'invalid_field',
|
|
322
322
|
message: 'Period start date is too far in the future',
|
|
@@ -326,7 +326,7 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
// Period has ended
|
|
329
|
-
if (STAMHOOFD.environment !== 'development' && period.endDate < new Date()) {
|
|
329
|
+
if (STAMHOOFD.environment !== 'development' && period.endDate < new Date() && STAMHOOFD.userMode === 'platform') {
|
|
330
330
|
throw new SimpleError({
|
|
331
331
|
code: 'invalid_field',
|
|
332
332
|
message: 'Period has ended',
|
|
@@ -565,29 +565,16 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
565
565
|
if (patch.waitingList === null) {
|
|
566
566
|
// delete
|
|
567
567
|
if (model.waitingListId) {
|
|
568
|
-
// for now don't delete, as waiting lists can be shared between multiple groups
|
|
569
|
-
// await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
|
|
570
568
|
model.waitingListId = null;
|
|
571
569
|
}
|
|
572
570
|
} else if (patch.waitingList.isPatch()) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
message: 'Cannot patch waiting list before it is created',
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
patch.waitingList.id = model.waitingListId;
|
|
581
|
-
patch.waitingList.type = GroupType.WaitingList;
|
|
582
|
-
await throwIfUpdateWaitingListPeriodWithMultipleGroups(patch.waitingList.id);
|
|
583
|
-
await PatchOrganizationRegistrationPeriodsEndpoint.patchGroup(patch.waitingList, period, {
|
|
584
|
-
allowPatchWaitingListPeriod: shouldUpdatePeriodIds,
|
|
585
|
-
isPatchingEvent,
|
|
571
|
+
throw new SimpleError({
|
|
572
|
+
code: 'invalid_field',
|
|
573
|
+
field: 'waitingList',
|
|
574
|
+
message: 'Cannot patch waitingList',
|
|
586
575
|
});
|
|
587
576
|
} else {
|
|
588
577
|
if (model.waitingListId) {
|
|
589
|
-
// for now don't delete, as waiting lists can be shared between multiple groups
|
|
590
|
-
// await PatchOrganizationRegistrationPeriodsEndpoint.deleteGroup(model.waitingListId)
|
|
591
578
|
model.waitingListId = null;
|
|
592
579
|
}
|
|
593
580
|
patch.waitingList.type = GroupType.WaitingList;
|
|
@@ -613,29 +600,13 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
613
600
|
|
|
614
601
|
model.waitingListId = existing.id;
|
|
615
602
|
} else {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
throw new SimpleError({
|
|
624
|
-
code: 'invalid_period',
|
|
625
|
-
message: 'Period has different organization id',
|
|
626
|
-
statusCode: 400,
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const group = await PatchOrganizationRegistrationPeriodsEndpoint.createGroup(
|
|
631
|
-
patch.waitingList,
|
|
632
|
-
model.organizationId,
|
|
633
|
-
requiredPeriod,
|
|
634
|
-
{
|
|
635
|
-
allowedIds: [patch.waitingList.id],
|
|
636
|
-
},
|
|
637
|
-
);
|
|
638
|
-
model.waitingListId = group.id;
|
|
603
|
+
throw new SimpleError({
|
|
604
|
+
code: 'invalid_waiting_list',
|
|
605
|
+
field: 'waitingList',
|
|
606
|
+
message: 'Waiting list not found',
|
|
607
|
+
human: $t('%ZZb'),
|
|
608
|
+
statusCode: 404,
|
|
609
|
+
});
|
|
639
610
|
}
|
|
640
611
|
}
|
|
641
612
|
} else if (shouldUpdatePeriodIds && model.waitingListId && period) {
|
|
@@ -761,16 +732,24 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
761
732
|
});
|
|
762
733
|
}
|
|
763
734
|
|
|
735
|
+
if (existing.periodId !== model.periodId) {
|
|
736
|
+
throw new SimpleError({
|
|
737
|
+
code: 'invalid_field',
|
|
738
|
+
field: 'waitingList',
|
|
739
|
+
message: 'Waiting list group is already used in another period',
|
|
740
|
+
human: $t(`%F9`),
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
764
744
|
model.waitingListId = existing.id;
|
|
765
745
|
} else {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
);
|
|
773
|
-
model.waitingListId = group.id;
|
|
746
|
+
throw new SimpleError({
|
|
747
|
+
code: 'invalid_waiting_list',
|
|
748
|
+
field: 'waitingList',
|
|
749
|
+
message: 'Waiting list not found',
|
|
750
|
+
human: $t('%ZZb'),
|
|
751
|
+
statusCode: 404,
|
|
752
|
+
});
|
|
774
753
|
}
|
|
775
754
|
}
|
|
776
755
|
|
|
@@ -702,12 +702,6 @@ export class AdminPermissionChecker {
|
|
|
702
702
|
|
|
703
703
|
if (this.user.permissions) {
|
|
704
704
|
// We grant permission for a whole payment when the user has at least permission for a part of that payment.
|
|
705
|
-
for (const registration of registrations) {
|
|
706
|
-
if (await this.canAccessRegistration(registration, permissionLevel)) {
|
|
707
|
-
return true;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
705
|
const webshopCache: Map<string, Webshop> = new Map();
|
|
712
706
|
|
|
713
707
|
for (const order of orders) {
|
|
@@ -720,6 +714,23 @@ export class AdminPermissionChecker {
|
|
|
720
714
|
}
|
|
721
715
|
}
|
|
722
716
|
}
|
|
717
|
+
|
|
718
|
+
for (const registration of registrations) {
|
|
719
|
+
if (await this.canAccessRegistration(registration, permissionLevel)) {
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const seenMemberIds = new Set<string>();
|
|
725
|
+
|
|
726
|
+
for (const balanceItem of balanceItems) {
|
|
727
|
+
if (!balanceItem.memberId || seenMemberIds.has(balanceItem.memberId)) continue;
|
|
728
|
+
seenMemberIds.add(balanceItem.memberId);
|
|
729
|
+
const member = await Member.getByIdWithUsersAndRegistrations(balanceItem.memberId);
|
|
730
|
+
if (member && await this.hasFinancialMemberAccess(member, permissionLevel)) {
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
723
734
|
}
|
|
724
735
|
|
|
725
736
|
if (permissionLevel === PermissionLevel.Read) {
|
|
@@ -1188,7 +1199,8 @@ export class AdminPermissionChecker {
|
|
|
1188
1199
|
return true;
|
|
1189
1200
|
}
|
|
1190
1201
|
|
|
1191
|
-
|
|
1202
|
+
// If you can't read the member, you can't have financial access
|
|
1203
|
+
if (!await this.canAccessMember(member, PermissionLevel.Read)) {
|
|
1192
1204
|
return false;
|
|
1193
1205
|
}
|
|
1194
1206
|
|
|
@@ -32,10 +32,8 @@ export class AuthenticatedStructures {
|
|
|
32
32
|
const { balanceItemPayments, balanceItems } = await Payment.loadBalanceItems(payments);
|
|
33
33
|
|
|
34
34
|
if (checkPermissions) {
|
|
35
|
-
const { registrations, orders } = await Payment.loadBalanceItemRelations(balanceItems);
|
|
36
|
-
|
|
37
35
|
// Note: permission checking is moved here for performacne to avoid loading the data multiple times
|
|
38
|
-
if (!(await Context.optionalAuth?.canAccessBalanceItems(balanceItems, PermissionLevel.Read
|
|
36
|
+
if (!(await Context.optionalAuth?.canAccessBalanceItems(balanceItems, PermissionLevel.Read))) {
|
|
39
37
|
throw new SimpleError({
|
|
40
38
|
code: 'permission_denied',
|
|
41
39
|
message: 'Permission denied',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Payment } from '@stamhoofd/models';
|
|
2
2
|
import type { SQLFilterDefinitions } from '@stamhoofd/sql';
|
|
3
|
-
import { baseSQLFilterCompilers, createColumnFilter, createExistsFilter, SQL, SQLCast, SQLConcat, SQLJsonUnquote, SQLScalar, SQLValueType } from '@stamhoofd/sql';
|
|
3
|
+
import { baseSQLFilterCompilers, createColumnFilter, createExistsFilter, createJoinedRelationFilter, SQL, SQLCast, SQLConcat, SQLJsonUnquote, SQLScalar, SQLValueType } from '@stamhoofd/sql';
|
|
4
4
|
import { balanceItemPaymentsCompilers } from './balance-item-payments.js';
|
|
5
|
+
import { organizationFilterCompilers } from './organizations.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Defines how to filter payments in the database from StamhoofdFilter objects
|
|
@@ -38,6 +39,10 @@ export const paymentFilterCompilers: SQLFilterDefinitions = {
|
|
|
38
39
|
type: SQLValueType.String,
|
|
39
40
|
nullable: true,
|
|
40
41
|
}),
|
|
42
|
+
payingOrganization: createJoinedRelationFilter(
|
|
43
|
+
SQL.join('organizations').where(SQL.column('organizations', 'id'), SQL.column(Payment.table, 'payingOrganizationId')),
|
|
44
|
+
organizationFilterCompilers,
|
|
45
|
+
),
|
|
41
46
|
invoiceId: createColumnFilter({
|
|
42
47
|
expression: SQL.column('invoiceId'),
|
|
43
48
|
type: SQLValueType.String,
|