@stamhoofd/backend 2.125.0 → 2.125.1
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 +27 -48
- package/src/helpers/AdminPermissionChecker.ts +17 -6
- 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.1",
|
|
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.1",
|
|
61
|
+
"@stamhoofd/backend-i18n": "2.125.1",
|
|
62
|
+
"@stamhoofd/backend-middleware": "2.125.1",
|
|
63
|
+
"@stamhoofd/crons": "2.125.1",
|
|
64
|
+
"@stamhoofd/email": "2.125.1",
|
|
65
|
+
"@stamhoofd/excel-writer": "2.125.1",
|
|
66
|
+
"@stamhoofd/logging": "2.125.1",
|
|
67
|
+
"@stamhoofd/models": "2.125.1",
|
|
68
|
+
"@stamhoofd/object-differ": "2.125.1",
|
|
69
|
+
"@stamhoofd/queues": "2.125.1",
|
|
70
|
+
"@stamhoofd/sql": "2.125.1",
|
|
71
|
+
"@stamhoofd/structures": "2.125.1",
|
|
72
|
+
"@stamhoofd/types": "2.125.1",
|
|
73
|
+
"@stamhoofd/utility": "2.125.1",
|
|
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.1",
|
|
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": "7a5fada26c2b7e4d0cd47cd5f2ee95adceae8391"
|
|
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
|
}
|
|
@@ -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) {
|
|
@@ -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,
|