@stamhoofd/backend 2.101.0 → 2.103.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/crons.ts +21 -26
- package/src/email-recipient-loaders/receivable-balances.ts +18 -11
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +1 -0
- package/src/endpoints/global/members/helpers/validateGroupFilter.ts +12 -8
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +30 -7
- package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +4 -4
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +382 -31
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +47 -23
- package/src/endpoints/organization/dashboard/billing/GetOrganizationPayableBalanceEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +60 -8
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +6 -1
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +12 -9
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +4 -2
- package/src/excel-loaders/receivable-balances.ts +2 -2
- package/src/helpers/AdminPermissionChecker.ts +68 -10
- package/src/helpers/AuthenticatedStructures.ts +18 -10
- package/src/helpers/MemberUserSyncer.ts +2 -2
- package/src/seeds/{1735577912-update-cached-outstanding-balance-from-items.ts → 1760702454-update-cached-outstanding-balance-from-items.ts} +2 -0
- package/src/services/BalanceItemService.ts +12 -0
- package/src/services/PaymentService.ts +3 -0
- package/src/sql-filters/base-registration-filter-compilers.ts +59 -0
- package/src/sql-filters/orders.ts +97 -1
- package/src/sql-filters/receivable-balances.ts +7 -1
- package/tests/e2e/bundle-discounts.test.ts +327 -1
- package/tests/helpers/PayconiqMocker.ts +22 -0
- package/tests/init/index.ts +1 -0
- package/tests/init/initAdmin.ts +2 -2
- package/tests/init/initPermissionRole.ts +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.103.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
"@simonbackx/simple-encoding": "2.22.0",
|
|
46
46
|
"@simonbackx/simple-endpoints": "1.20.1",
|
|
47
47
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
48
|
-
"@stamhoofd/backend-i18n": "2.
|
|
49
|
-
"@stamhoofd/backend-middleware": "2.
|
|
50
|
-
"@stamhoofd/email": "2.
|
|
51
|
-
"@stamhoofd/models": "2.
|
|
52
|
-
"@stamhoofd/queues": "2.
|
|
53
|
-
"@stamhoofd/sql": "2.
|
|
54
|
-
"@stamhoofd/structures": "2.
|
|
55
|
-
"@stamhoofd/utility": "2.
|
|
48
|
+
"@stamhoofd/backend-i18n": "2.103.0",
|
|
49
|
+
"@stamhoofd/backend-middleware": "2.103.0",
|
|
50
|
+
"@stamhoofd/email": "2.103.0",
|
|
51
|
+
"@stamhoofd/models": "2.103.0",
|
|
52
|
+
"@stamhoofd/queues": "2.103.0",
|
|
53
|
+
"@stamhoofd/sql": "2.103.0",
|
|
54
|
+
"@stamhoofd/structures": "2.103.0",
|
|
55
|
+
"@stamhoofd/utility": "2.103.0",
|
|
56
56
|
"archiver": "^7.0.1",
|
|
57
57
|
"axios": "^1.8.2",
|
|
58
58
|
"cookie": "^0.7.0",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "81c0e08f64f5c9a32a513cf6cfd2d48e3601f1a8"
|
|
74
74
|
}
|
package/src/crons.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Database } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { Group, Organization, Payment, Registration, STPackage, Webshop } from '@stamhoofd/models';
|
|
3
3
|
import { PaymentMethod, PaymentProvider, PaymentStatus } from '@stamhoofd/structures';
|
|
4
4
|
import { Formatter } from '@stamhoofd/utility';
|
|
5
|
-
|
|
5
|
+
import { SQL } from '@stamhoofd/sql';
|
|
6
6
|
import { registerCron } from '@stamhoofd/crons';
|
|
7
7
|
import { checkSettlements } from './helpers/CheckSettlements';
|
|
8
8
|
import { PaymentService } from './services/PaymentService';
|
|
@@ -124,36 +124,31 @@ async function checkWebshopDNS() {
|
|
|
124
124
|
// Keep checking pending paymetns for 3 days
|
|
125
125
|
async function checkPayments() {
|
|
126
126
|
if (STAMHOOFD.environment === 'development') {
|
|
127
|
-
return;
|
|
127
|
+
// return;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const timeout = 60 * 1000 * 31;
|
|
131
131
|
|
|
132
132
|
// TODO: only select the ID + organizationId
|
|
133
|
-
const payments = await Payment.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
sort: [{
|
|
153
|
-
column: 'createdAt',
|
|
154
|
-
direction: 'ASC',
|
|
155
|
-
}],
|
|
156
|
-
});
|
|
133
|
+
const payments = await Payment.select()
|
|
134
|
+
.where(
|
|
135
|
+
SQL.where('method', [
|
|
136
|
+
PaymentMethod.Bancontact, PaymentMethod.iDEAL, PaymentMethod.Payconiq, PaymentMethod.CreditCard,
|
|
137
|
+
])
|
|
138
|
+
.and('status', [PaymentStatus.Created, PaymentStatus.Pending])
|
|
139
|
+
.and('createdAt', '<', new Date(new Date().getTime() - timeout)),
|
|
140
|
+
)
|
|
141
|
+
// For payconiq payments, we have a shorter timeout of 1 minute if they are still in the 'created' state (not scanned)
|
|
142
|
+
.orWhere(
|
|
143
|
+
SQL.where('method', [
|
|
144
|
+
PaymentMethod.Payconiq,
|
|
145
|
+
])
|
|
146
|
+
.and('status', [PaymentStatus.Created])
|
|
147
|
+
.and('createdAt', '<', new Date(new Date().getTime() - 60 * 1000)),
|
|
148
|
+
)
|
|
149
|
+
.orderBy('createdAt', 'ASC')
|
|
150
|
+
.limit(200)
|
|
151
|
+
.fetch();
|
|
157
152
|
|
|
158
153
|
console.log('[DELAYED PAYMENTS] Checking pending payments: ' + payments.length);
|
|
159
154
|
|
|
@@ -14,7 +14,7 @@ async function fetch(query: LimitedFilteredRequest, subfilter: StamhoofdFilter |
|
|
|
14
14
|
|
|
15
15
|
const recipients: EmailRecipient[] = [];
|
|
16
16
|
for (const balance of result.results) {
|
|
17
|
-
const balanceItemModels = await CachedBalance.balanceForObjects(balance.organizationId, [balance.object.id], balance.objectType
|
|
17
|
+
const balanceItemModels = balance.objectType === ReceivableBalanceType.organization ? (await CachedBalance.balanceForObjects(balance.organizationId, [balance.object.id], balance.objectType)) : [];
|
|
18
18
|
const balanceItems = balanceItemModels.map(i => i.getStructure());
|
|
19
19
|
|
|
20
20
|
const filteredContacts = balance.object.contacts.filter(c => compiledFilter(c));
|
|
@@ -22,7 +22,7 @@ async function fetch(query: LimitedFilteredRequest, subfilter: StamhoofdFilter |
|
|
|
22
22
|
for (const email of contact.emails) {
|
|
23
23
|
const recipient = EmailRecipient.create({
|
|
24
24
|
objectId: balance.id, // Note: not set member, user or organization id here - should be the queryable balance id
|
|
25
|
-
userId: balance.objectType === ReceivableBalanceType.user ? balance.object.id : null,
|
|
25
|
+
userId: balance.objectType === ReceivableBalanceType.user || balance.objectType === ReceivableBalanceType.userWithoutMembers ? balance.object.id : null,
|
|
26
26
|
memberId: balance.objectType === ReceivableBalanceType.member ? balance.object.id : null,
|
|
27
27
|
firstName: contact.firstName,
|
|
28
28
|
lastName: contact.lastName,
|
|
@@ -32,15 +32,22 @@ async function fetch(query: LimitedFilteredRequest, subfilter: StamhoofdFilter |
|
|
|
32
32
|
token: 'objectName',
|
|
33
33
|
value: balance.object.name,
|
|
34
34
|
}),
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
...(
|
|
36
|
+
balance.objectType === ReceivableBalanceType.organization
|
|
37
|
+
? [
|
|
38
|
+
Replacement.create({
|
|
39
|
+
token: 'outstandingBalance',
|
|
40
|
+
value: Formatter.price(balance.amountOpen),
|
|
41
|
+
}),
|
|
42
|
+
Replacement.create({
|
|
43
|
+
token: 'balanceTable',
|
|
44
|
+
value: '',
|
|
45
|
+
html: BalanceItemStruct.getDetailsHTMLTable(balanceItems),
|
|
46
|
+
}),
|
|
47
|
+
]
|
|
48
|
+
: []
|
|
49
|
+
),
|
|
50
|
+
|
|
44
51
|
...(contact.meta && contact.meta.url && typeof contact.meta.url === 'string'
|
|
45
52
|
? [Replacement.create({
|
|
46
53
|
token: 'paymentUrl',
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
-
import { Group } from '@stamhoofd/models';
|
|
3
2
|
import { FilterWrapperMarker, PermissionLevel, StamhoofdFilter, unwrapFilter, WrapperFilter } from '@stamhoofd/structures';
|
|
4
3
|
import { Context } from '../../../../helpers/Context';
|
|
5
4
|
|
|
@@ -55,17 +54,22 @@ export async function validateGroupFilter({ filter, permissionLevel, key }: { fi
|
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
const groups = await
|
|
59
|
-
Context.auth.cacheGroups(groups);
|
|
57
|
+
const groups = await Context.auth.getGroups(groupIds as string[]);
|
|
60
58
|
|
|
61
|
-
console.log('Fetching members for groups', groups.map(g => g.settings.name.toString()));
|
|
59
|
+
console.log('Fetching members for groups before', groups.map(g => g.settings.name.toString()));
|
|
62
60
|
|
|
63
61
|
for (const group of groups) {
|
|
64
62
|
if (!await Context.auth.canAccessGroup(group, permissionLevel)) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
if (permissionLevel !== PermissionLevel.Read || !group.settings.implicitlyAllowViewRegistrations) {
|
|
64
|
+
throw Context.auth.error({
|
|
65
|
+
message: 'You do not have access to this group',
|
|
66
|
+
human: $t(`45eedf49-0f0a-442c-a0bd-7881c2682698`, { groupName: group.settings.name }),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Return false so we add additional scope filters (only view overlap)
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -99,14 +99,26 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
99
99
|
if (await Context.auth.hasFullAccess(organization.id, permissionLevel)) {
|
|
100
100
|
// Can access full history for now
|
|
101
101
|
scopeFilter = {
|
|
102
|
-
|
|
102
|
+
member: {
|
|
103
|
+
registrations: {
|
|
104
|
+
$elemMatch: {
|
|
105
|
+
organizationId: organization.id,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
103
109
|
};
|
|
104
110
|
}
|
|
105
111
|
else {
|
|
106
112
|
// Can only access current period
|
|
107
113
|
scopeFilter = {
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
member: {
|
|
115
|
+
registrations: {
|
|
116
|
+
$elemMatch: {
|
|
117
|
+
organizationId: organization.id,
|
|
118
|
+
periodId: organization.periodId,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
110
122
|
};
|
|
111
123
|
}
|
|
112
124
|
}
|
|
@@ -130,8 +142,14 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
130
142
|
}
|
|
131
143
|
|
|
132
144
|
scopeFilter = {
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
member: {
|
|
146
|
+
registrations: {
|
|
147
|
+
$elemMatch: {
|
|
148
|
+
groupId: {
|
|
149
|
+
$in: groupIds,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
135
153
|
},
|
|
136
154
|
};
|
|
137
155
|
}
|
|
@@ -196,8 +214,13 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
196
214
|
}
|
|
197
215
|
|
|
198
216
|
for (const registration of data) {
|
|
199
|
-
if (
|
|
200
|
-
|
|
217
|
+
if (registration.group.settings.implicitlyAllowViewRegistrations) {
|
|
218
|
+
// okay, only need to check if we can access the members (next step)
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (!await Context.auth.canAccessRegistration(registration, permissionLevel)) {
|
|
222
|
+
throw Context.auth.error();
|
|
223
|
+
}
|
|
201
224
|
}
|
|
202
225
|
}
|
|
203
226
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { CachedBalance, Organization } from '@stamhoofd/models';
|
|
3
|
-
import { PayableBalance, PayableBalanceCollection } from '@stamhoofd/structures';
|
|
3
|
+
import { PayableBalance, PayableBalanceCollection, ReceivableBalanceType } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -31,12 +31,12 @@ export class GetUserPayableBalanceEndpoint extends Endpoint<Params, Query, Body,
|
|
|
31
31
|
const organization = await Context.setUserOrganizationScope();
|
|
32
32
|
const { user } = await Context.authenticate();
|
|
33
33
|
|
|
34
|
-
return new Response(await GetUserPayableBalanceEndpoint.getBillingStatusForObjects([user.id], organization));
|
|
34
|
+
return new Response(await GetUserPayableBalanceEndpoint.getBillingStatusForObjects([user.id], organization, ReceivableBalanceType.user));
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
static async getBillingStatusForObjects(objectIds: string[], organization
|
|
37
|
+
static async getBillingStatusForObjects(objectIds: string[], organization: Organization | null, objectType: ReceivableBalanceType) {
|
|
38
38
|
// Load cached balances
|
|
39
|
-
const receivableBalances = await CachedBalance.getForObjects(objectIds, organization?.id);
|
|
39
|
+
const receivableBalances = await CachedBalance.getForObjects(objectIds, organization?.id, objectType);
|
|
40
40
|
|
|
41
41
|
const organizationIds = Formatter.uniqueArray(receivableBalances.map(b => b.organizationId));
|
|
42
42
|
|