@stamhoofd/backend 2.108.0 → 2.110.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 +2 -2
- package/package.json +20 -20
- package/src/crons/disable-auto-update-documents.test.ts +164 -0
- package/src/crons/disable-auto-update-documents.ts +82 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +5 -5
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +8 -7
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +9 -8
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +48 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +95 -19
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +282 -0
- package/src/endpoints/organization/dashboard/documents/{PatchDocumentTemplateEndpoint.ts → PatchDocumentTemplatesEndpoint.ts} +56 -3
- package/src/excel-loaders/members.ts +61 -7
- package/src/excel-loaders/registrations.ts +123 -2
- package/src/helpers/LimitedFilteredRequestHelper.ts +24 -0
- package/src/helpers/SQLTranslatedString.ts +14 -0
- package/src/helpers/TagHelper.test.ts +9 -9
- package/src/helpers/outstandingBalanceJoin.ts +49 -0
- package/src/seeds/1765896674-document-update-year.test.ts +179 -0
- package/src/seeds/1765896674-document-update-year.ts +66 -0
- package/src/seeds/1766150402-document-published-at.test.ts +46 -0
- package/src/seeds/1766150402-document-published-at.ts +20 -0
- package/src/services/PaymentService.ts +14 -32
- package/src/sql-filters/base-registration-filter-compilers.ts +51 -4
- package/src/sql-filters/document-templates.ts +45 -0
- package/src/sql-filters/documents.ts +1 -1
- package/src/sql-filters/events.ts +6 -6
- package/src/sql-filters/groups.ts +7 -6
- package/src/sql-filters/members.ts +31 -26
- package/src/sql-filters/orders.ts +16 -16
- package/src/sql-filters/organizations.ts +11 -11
- package/src/sql-filters/payments.ts +10 -10
- package/src/sql-filters/registrations.ts +14 -6
- package/src/sql-sorters/document-templates.ts +79 -0
- package/src/sql-sorters/documents.ts +1 -1
- package/src/sql-sorters/members.ts +22 -0
- package/src/sql-sorters/orders.ts +5 -5
- package/src/sql-sorters/organizations.ts +3 -3
- package/src/sql-sorters/registrations.ts +186 -15
package/index.ts
CHANGED
|
@@ -5,10 +5,10 @@ backendEnv.load({ service: 'api' }).catch((error) => {
|
|
|
5
5
|
process.exit(1);
|
|
6
6
|
}).then(async () => {
|
|
7
7
|
if (STAMHOOFD.environment === 'development') {
|
|
8
|
-
const { run } = await import('./src/migrate');
|
|
8
|
+
const { run } = await import('./src/migrate.js');
|
|
9
9
|
await run();
|
|
10
10
|
}
|
|
11
|
-
const { boot } = await import('./src/boot');
|
|
11
|
+
const { boot } = await import('./src/boot.js');
|
|
12
12
|
|
|
13
13
|
boot({ killProcess: true }).catch((error) => {
|
|
14
14
|
console.error('unhandledRejection', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.110.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/cookie": "^0.6.0",
|
|
34
34
|
"@types/luxon": "3.4.2",
|
|
35
|
-
"@types/mailparser": "3
|
|
35
|
+
"@types/mailparser": "^3",
|
|
36
36
|
"@types/mysql": "^2.15.20",
|
|
37
37
|
"@types/node": "^22",
|
|
38
38
|
"nock": "^13.5.1",
|
|
@@ -40,33 +40,33 @@
|
|
|
40
40
|
"sinon": "^18.0.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@aws-sdk/client-s3": "3.
|
|
44
|
-
"@aws-sdk/client-ses": "3.
|
|
45
|
-
"@aws-sdk/client-sesv2": "3.
|
|
46
|
-
"@aws-sdk/client-sqs": "3.
|
|
47
|
-
"@aws-sdk/s3-request-presigner": "3.
|
|
43
|
+
"@aws-sdk/client-s3": "^3.839.0",
|
|
44
|
+
"@aws-sdk/client-ses": "^3.839.0",
|
|
45
|
+
"@aws-sdk/client-sesv2": "^3.839.0",
|
|
46
|
+
"@aws-sdk/client-sqs": "^3.839.0",
|
|
47
|
+
"@aws-sdk/s3-request-presigner": "^3.839.0",
|
|
48
48
|
"@bwip-js/node": "^4.5.1",
|
|
49
|
-
"@mollie/api-client": "3.
|
|
49
|
+
"@mollie/api-client": "4.3.3",
|
|
50
50
|
"@simonbackx/simple-database": "1.34.0",
|
|
51
51
|
"@simonbackx/simple-encoding": "2.22.0",
|
|
52
52
|
"@simonbackx/simple-endpoints": "1.20.1",
|
|
53
53
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
54
|
-
"@stamhoofd/backend-i18n": "2.
|
|
55
|
-
"@stamhoofd/backend-middleware": "2.
|
|
56
|
-
"@stamhoofd/email": "2.
|
|
57
|
-
"@stamhoofd/models": "2.
|
|
58
|
-
"@stamhoofd/queues": "2.
|
|
59
|
-
"@stamhoofd/sql": "2.
|
|
60
|
-
"@stamhoofd/structures": "2.
|
|
61
|
-
"@stamhoofd/utility": "2.
|
|
54
|
+
"@stamhoofd/backend-i18n": "2.110.0",
|
|
55
|
+
"@stamhoofd/backend-middleware": "2.110.0",
|
|
56
|
+
"@stamhoofd/email": "2.110.0",
|
|
57
|
+
"@stamhoofd/models": "2.110.0",
|
|
58
|
+
"@stamhoofd/queues": "2.110.0",
|
|
59
|
+
"@stamhoofd/sql": "2.110.0",
|
|
60
|
+
"@stamhoofd/structures": "2.110.0",
|
|
61
|
+
"@stamhoofd/utility": "2.110.0",
|
|
62
62
|
"archiver": "^7.0.1",
|
|
63
|
-
"axios": "^1.
|
|
63
|
+
"axios": "^1.13.2",
|
|
64
64
|
"cookie": "^0.7.0",
|
|
65
65
|
"formidable": "3.5.4",
|
|
66
66
|
"handlebars": "^4.7.7",
|
|
67
|
-
"jsonwebtoken": "9.0.
|
|
67
|
+
"jsonwebtoken": "9.0.3",
|
|
68
68
|
"luxon": "3.4.4",
|
|
69
|
-
"mailparser": "3.
|
|
69
|
+
"mailparser": "^3.9.1",
|
|
70
70
|
"mockdate": "^3.0.2",
|
|
71
71
|
"mysql2": "^3.14.1",
|
|
72
72
|
"node-rsa": "1.1.1",
|
|
@@ -76,5 +76,5 @@
|
|
|
76
76
|
"publishConfig": {
|
|
77
77
|
"access": "public"
|
|
78
78
|
},
|
|
79
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "b43604081c29f7d909dac1b47a2d1633b66670e5"
|
|
80
80
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { DocumentTemplate, DocumentTemplateFactory } from '@stamhoofd/models';
|
|
2
|
+
import { DocumentStatus } from '@stamhoofd/structures';
|
|
3
|
+
import { disableAutoUpdateForFiscalDocuments, disableAutoUpdateForOtherDocuments } from './disable-auto-update-documents.js';
|
|
4
|
+
|
|
5
|
+
describe('cron.disable-auto-update-documents', () => {
|
|
6
|
+
describe('disableAutoUpdateForFiscalDocuments', () => {
|
|
7
|
+
it('should only run on the 1st of March', async () => {
|
|
8
|
+
// arrange
|
|
9
|
+
const firstOfMarch2025 = new Date(2025, 2, 1);
|
|
10
|
+
|
|
11
|
+
const cases: { now: Date; expected: boolean }[] = [
|
|
12
|
+
{ now: new Date(firstOfMarch2025.getTime() - 1), expected: false },
|
|
13
|
+
{ now: new Date(firstOfMarch2025), expected: true },
|
|
14
|
+
{ now: new Date(2025, 2, 2), expected: false },
|
|
15
|
+
{ now: new Date(new Date(2026, 2, 1)), expected: true },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const { now, expected } of cases) {
|
|
19
|
+
// act
|
|
20
|
+
const result = await disableAutoUpdateForFiscalDocuments(now);
|
|
21
|
+
// assert
|
|
22
|
+
expect(result).toBe(expected);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should disable auto-update for fiscal documents of the previous year', async () => {
|
|
27
|
+
// // arrange
|
|
28
|
+
const firstOfMarch2025 = new Date(2025, 2, 1);
|
|
29
|
+
|
|
30
|
+
const document1 = await createDocument({
|
|
31
|
+
status: DocumentStatus.Published,
|
|
32
|
+
publishedAt: new Date(2025, 0, 1),
|
|
33
|
+
createdAt: new Date(2024, 11, 15),
|
|
34
|
+
year: 2024,
|
|
35
|
+
type: 'fiscal',
|
|
36
|
+
updatesEnabled: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// old year
|
|
40
|
+
const document2 = await createDocument({
|
|
41
|
+
status: DocumentStatus.Published,
|
|
42
|
+
publishedAt: new Date(2025, 0, 1),
|
|
43
|
+
createdAt: new Date(2024, 11, 15),
|
|
44
|
+
year: 2023,
|
|
45
|
+
type: 'fiscal',
|
|
46
|
+
updatesEnabled: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// current year
|
|
50
|
+
const document3 = await createDocument({
|
|
51
|
+
status: DocumentStatus.Published,
|
|
52
|
+
publishedAt: new Date(2025, 0, 1),
|
|
53
|
+
createdAt: new Date(2024, 11, 15),
|
|
54
|
+
year: 2025,
|
|
55
|
+
type: 'fiscal',
|
|
56
|
+
updatesEnabled: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// type participation
|
|
60
|
+
const document4 = await createDocument({
|
|
61
|
+
status: DocumentStatus.Published,
|
|
62
|
+
publishedAt: new Date(2025, 0, 1),
|
|
63
|
+
createdAt: new Date(2024, 11, 15),
|
|
64
|
+
year: 2024,
|
|
65
|
+
type: 'participation',
|
|
66
|
+
updatesEnabled: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// act
|
|
70
|
+
const didRun = await disableAutoUpdateForFiscalDocuments(firstOfMarch2025);
|
|
71
|
+
|
|
72
|
+
// assert
|
|
73
|
+
const updatedDocument1 = (await DocumentTemplate.getByID(document1.id))!;
|
|
74
|
+
const updatedDocument2 = (await DocumentTemplate.getByID(document2.id))!;
|
|
75
|
+
const updatedDocument3 = (await DocumentTemplate.getByID(document3.id))!;
|
|
76
|
+
const updatedDocument4 = (await DocumentTemplate.getByID(document4.id))!;
|
|
77
|
+
|
|
78
|
+
expect(didRun).toBe(true);
|
|
79
|
+
|
|
80
|
+
// should update because previous year, type is fiscal and updates enabled
|
|
81
|
+
expect(updatedDocument1.updatesEnabled).toBe(false);
|
|
82
|
+
// should not update because not previous year
|
|
83
|
+
expect(updatedDocument2.updatesEnabled).toBe(true);
|
|
84
|
+
// should not update because not previous year
|
|
85
|
+
expect(updatedDocument3.updatesEnabled).toBe(true);
|
|
86
|
+
// should not update because type is not fiscal
|
|
87
|
+
expect(updatedDocument4.updatesEnabled).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('disableAutoUpdateForOtherDocuments', () => {
|
|
92
|
+
it('should disable auto-update for documents, other than fiscal documents, that have been published 90 days ago', async () => {
|
|
93
|
+
// // arrange
|
|
94
|
+
const today = new Date(2025, 5, 15);
|
|
95
|
+
const ninetyDaysAgo = new Date(2025, 2, 17);
|
|
96
|
+
|
|
97
|
+
const cases: { document: DocumentTemplate; expected: boolean }[] = [
|
|
98
|
+
// should update because 90 days ago and type is not fiscal
|
|
99
|
+
{
|
|
100
|
+
document: await createDocument({
|
|
101
|
+
status: DocumentStatus.Published,
|
|
102
|
+
publishedAt: new Date(ninetyDaysAgo),
|
|
103
|
+
createdAt: new Date(2024, 11, 15),
|
|
104
|
+
year: 2024,
|
|
105
|
+
type: 'participation',
|
|
106
|
+
updatesEnabled: true,
|
|
107
|
+
}),
|
|
108
|
+
expected: false,
|
|
109
|
+
},
|
|
110
|
+
// day after
|
|
111
|
+
{
|
|
112
|
+
document: await createDocument({
|
|
113
|
+
status: DocumentStatus.Published,
|
|
114
|
+
publishedAt: new Date(2025, 2, 18),
|
|
115
|
+
createdAt: new Date(2024, 11, 15),
|
|
116
|
+
year: 2024,
|
|
117
|
+
type: 'participation',
|
|
118
|
+
updatesEnabled: true,
|
|
119
|
+
}),
|
|
120
|
+
expected: true,
|
|
121
|
+
},
|
|
122
|
+
// day before
|
|
123
|
+
{
|
|
124
|
+
document: await createDocument({
|
|
125
|
+
status: DocumentStatus.Published,
|
|
126
|
+
publishedAt: new Date(ninetyDaysAgo.getTime() - 1),
|
|
127
|
+
createdAt: new Date(2024, 11, 15),
|
|
128
|
+
year: 2024,
|
|
129
|
+
type: 'participation',
|
|
130
|
+
updatesEnabled: true,
|
|
131
|
+
}),
|
|
132
|
+
expected: true,
|
|
133
|
+
},
|
|
134
|
+
// type fiscal
|
|
135
|
+
{
|
|
136
|
+
document: await createDocument({
|
|
137
|
+
status: DocumentStatus.Published,
|
|
138
|
+
publishedAt: new Date(ninetyDaysAgo),
|
|
139
|
+
createdAt: new Date(2024, 11, 15),
|
|
140
|
+
year: 2024,
|
|
141
|
+
type: 'fiscal',
|
|
142
|
+
updatesEnabled: true,
|
|
143
|
+
}),
|
|
144
|
+
expected: true,
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
// act
|
|
149
|
+
await disableAutoUpdateForOtherDocuments(today);
|
|
150
|
+
|
|
151
|
+
// assert
|
|
152
|
+
for (const { document, expected } of cases) {
|
|
153
|
+
const updatedDocument = (await DocumentTemplate.getByID(document.id))!;
|
|
154
|
+
expect(updatedDocument.updatesEnabled).toBe(expected);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
async function createDocument({ year, status, createdAt, updatesEnabled, type, publishedAt }: { year: number; status: DocumentStatus; createdAt: Date; updatesEnabled: boolean; type: 'fiscal' | 'participation'; publishedAt: Date | null }) {
|
|
161
|
+
const document = await new DocumentTemplateFactory({ year, groups: [], status, updatesEnabled, publishedAt, createdAt, type }).create();
|
|
162
|
+
|
|
163
|
+
return document;
|
|
164
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { registerCron } from '@stamhoofd/crons';
|
|
2
|
+
import { DocumentTemplate } from '@stamhoofd/models';
|
|
3
|
+
import { SQL } from '@stamhoofd/sql';
|
|
4
|
+
|
|
5
|
+
let lastRunDate: number | null = null;
|
|
6
|
+
|
|
7
|
+
registerCron('disableAutoUpdateDocuments', disableAutoUpdateDocuments);
|
|
8
|
+
|
|
9
|
+
function shouldRun() {
|
|
10
|
+
const now = new Date();
|
|
11
|
+
|
|
12
|
+
if (now.getDate() === lastRunDate) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const hour = now.getHours();
|
|
17
|
+
|
|
18
|
+
// between 5 and 6 AM
|
|
19
|
+
if (hour !== 5 && STAMHOOFD.environment !== 'development') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function disableAutoUpdateDocuments() {
|
|
27
|
+
if (!shouldRun()) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const now = new Date();
|
|
32
|
+
lastRunDate = now.getDate();
|
|
33
|
+
|
|
34
|
+
await disableAutoUpdateForFiscalDocuments(now);
|
|
35
|
+
await disableAutoUpdateForOtherDocuments(now);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Disable auto-update for fiscal documents on the 1st of March.
|
|
40
|
+
* @returns if query was run
|
|
41
|
+
*/
|
|
42
|
+
export async function disableAutoUpdateForFiscalDocuments(now: Date): Promise<boolean> {
|
|
43
|
+
// only run on 1st of march
|
|
44
|
+
const isFirstOfMarch = now.getMonth() === 2 && now.getDate() === 1;
|
|
45
|
+
if (!isFirstOfMarch) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// set updates enabled to false
|
|
50
|
+
await SQL.update(DocumentTemplate.table).set('updatesEnabled', false)
|
|
51
|
+
// where previous year
|
|
52
|
+
.where('year', now.getFullYear() - 1)
|
|
53
|
+
// where updates enabled
|
|
54
|
+
.andWhere('updatesEnabled', true)
|
|
55
|
+
// where type is fiscal
|
|
56
|
+
.andWhere(SQL.jsonExtract(SQL.column('privateSettings'), '$.value.templateDefinition.type'), 'fiscal')
|
|
57
|
+
.update();
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Disable auto-update for documents, other then fiscal, that have been published 90 days ago.
|
|
64
|
+
*/
|
|
65
|
+
export async function disableAutoUpdateForOtherDocuments(now: Date) {
|
|
66
|
+
const min = new Date(now);
|
|
67
|
+
min.setDate(min.getDate() - 90);
|
|
68
|
+
|
|
69
|
+
const max = new Date(now);
|
|
70
|
+
max.setDate(max.getDate() - 89);
|
|
71
|
+
|
|
72
|
+
// set updates enabled to false
|
|
73
|
+
await SQL.update(DocumentTemplate.table).set('updatesEnabled', false)
|
|
74
|
+
// where updates enabled
|
|
75
|
+
.where('updatesEnabled', true)
|
|
76
|
+
// where published 90 days ago
|
|
77
|
+
.andWhere('publishedAt', '>=', min)
|
|
78
|
+
.andWhere('publishedAt', '<', max)
|
|
79
|
+
// where type is not fiscal
|
|
80
|
+
.whereNot(SQL.jsonExtract(SQL.column('privateSettings'), '$.value.templateDefinition.type'), 'fiscal')
|
|
81
|
+
.update();
|
|
82
|
+
}
|
|
@@ -8,11 +8,11 @@ import { DataValidator } from '@stamhoofd/utility';
|
|
|
8
8
|
|
|
9
9
|
import { SQLResultNamespacedRow } from '@simonbackx/simple-database';
|
|
10
10
|
import parsePhoneNumber from 'libphonenumber-js/max';
|
|
11
|
-
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
12
|
-
import { Context } from '../../../helpers/Context';
|
|
13
|
-
import { memberFilterCompilers } from '../../../sql-filters/members';
|
|
14
|
-
import { memberSorters } from '../../../sql-sorters/members';
|
|
15
|
-
import { validateGroupFilter } from './helpers/validateGroupFilter';
|
|
11
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
12
|
+
import { Context } from '../../../helpers/Context.js';
|
|
13
|
+
import { memberFilterCompilers } from '../../../sql-filters/members.js';
|
|
14
|
+
import { memberSorters } from '../../../sql-sorters/members.js';
|
|
15
|
+
import { validateGroupFilter } from './helpers/validateGroupFilter.js';
|
|
16
16
|
|
|
17
17
|
type Params = Record<string, never>;
|
|
18
18
|
type Query = LimitedFilteredRequest;
|
|
@@ -3,16 +3,15 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { Group, Member, Platform, Registration } from '@stamhoofd/models';
|
|
5
5
|
import { SQL, SQLExpression, SQLSelect, SQLSortDefinitions, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
|
|
6
|
-
import { CountFilteredRequest, GroupType, LimitedFilteredRequest, PaginatedResponse, PermissionLevel, StamhoofdFilter, assertSort } from '@stamhoofd/structures';
|
|
6
|
+
import { CountFilteredRequest, GroupType, LimitedFilteredRequest, PaginatedResponse, PermissionLevel, RegistrationWithMemberBlob, StamhoofdFilter, assertSort } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
8
|
import { SQLResultNamespacedRow } from '@simonbackx/simple-database';
|
|
9
9
|
import { RegistrationsBlob } from '@stamhoofd/structures/dist/src/members/RegistrationsBlob';
|
|
10
|
-
import { RegistrationWithMemberBlob } from '@stamhoofd/structures/dist/src/members/RegistrationWithMemberBlob';
|
|
11
10
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
12
11
|
import { Context } from '../../../helpers/Context.js';
|
|
13
12
|
import { LimitedFilteredRequestHelper } from '../../../helpers/LimitedFilteredRequestHelper.js';
|
|
14
13
|
import { groupJoin, registrationFilterCompilers } from '../../../sql-filters/registrations.js';
|
|
15
|
-
import { registrationSorters } from '../../../sql-sorters/registrations.js';
|
|
14
|
+
import { RegistrationSortData, registrationSorters } from '../../../sql-sorters/registrations.js';
|
|
16
15
|
import { GetMembersEndpoint } from '../members/GetMembersEndpoint.js';
|
|
17
16
|
import { validateGroupFilter } from '../members/helpers/validateGroupFilter.js';
|
|
18
17
|
|
|
@@ -21,7 +20,7 @@ type Query = LimitedFilteredRequest;
|
|
|
21
20
|
type Body = undefined;
|
|
22
21
|
type ResponseBody = PaginatedResponse<RegistrationsBlob, LimitedFilteredRequest>;
|
|
23
22
|
|
|
24
|
-
const sorters: SQLSortDefinitions<
|
|
23
|
+
const sorters: SQLSortDefinitions<RegistrationSortData> = registrationSorters;
|
|
25
24
|
const filterCompilers = registrationFilterCompilers;
|
|
26
25
|
|
|
27
26
|
export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
@@ -160,8 +159,6 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
160
159
|
.setMaxExecutionTime(15 * 1000)
|
|
161
160
|
.where('registeredAt', '!=', null);
|
|
162
161
|
|
|
163
|
-
query;
|
|
164
|
-
|
|
165
162
|
if (scopeFilter) {
|
|
166
163
|
query.where(await compileToSQLFilter(scopeFilter, filterCompilers));
|
|
167
164
|
}
|
|
@@ -236,9 +233,13 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
236
233
|
|
|
237
234
|
const registrationsBlob = await AuthenticatedStructures.registrationsBlob(data, members);
|
|
238
235
|
|
|
239
|
-
const next = LimitedFilteredRequestHelper.
|
|
236
|
+
const next = LimitedFilteredRequestHelper.fixInfiniteLoadingLoopWithTransform<RegistrationWithMemberBlob, RegistrationSortData>({
|
|
240
237
|
request: requestQuery,
|
|
241
238
|
results: registrationsBlob.registrations,
|
|
239
|
+
transformer: registration => new RegistrationSortData({
|
|
240
|
+
registration,
|
|
241
|
+
organizations: registrationsBlob.organizations,
|
|
242
|
+
}),
|
|
242
243
|
sorters,
|
|
243
244
|
});
|
|
244
245
|
|
|
@@ -8,14 +8,14 @@ import { BalanceItem, BalanceItemPayment, CachedBalance, Group, Member, MemberWi
|
|
|
8
8
|
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItem as BalanceItemStruct, BalanceItemType, IDRegisterCheckout, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, PaymentType, PermissionLevel, PlatformFamily, PlatformMember, ReceivableBalanceType, RegisterItem, RegisterResponse, TranslatedString, Version } from '@stamhoofd/structures';
|
|
9
9
|
import { Formatter } from '@stamhoofd/utility';
|
|
10
10
|
|
|
11
|
-
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
12
|
-
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
13
|
-
import { Context } from '../../../helpers/Context';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { PaymentService } from '../../../services/PaymentService';
|
|
18
|
-
import {
|
|
11
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
12
|
+
import { BuckarooHelper } from '../../../helpers/BuckarooHelper.js';
|
|
13
|
+
import { Context } from '../../../helpers/Context.js';
|
|
14
|
+
import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper.js';
|
|
15
|
+
import { StripeHelper } from '../../../helpers/StripeHelper.js';
|
|
16
|
+
import { BalanceItemService } from '../../../services/BalanceItemService.js';
|
|
17
|
+
import { PaymentService } from '../../../services/PaymentService.js';
|
|
18
|
+
import { RegistrationService } from '../../../services/RegistrationService.js';
|
|
19
19
|
type Params = Record<string, never>;
|
|
20
20
|
type Query = undefined;
|
|
21
21
|
type Body = IDRegisterCheckout;
|
|
@@ -381,6 +381,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
381
381
|
registration.options = item.options;
|
|
382
382
|
registration.recordAnswers = item.recordAnswers;
|
|
383
383
|
registration.startDate = startDate;
|
|
384
|
+
registration.endDate = item.calculatedEndDate;
|
|
384
385
|
|
|
385
386
|
// Clear if we are reusing an existing registration
|
|
386
387
|
registration.trialUntil = null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
|
|
4
|
+
|
|
5
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
6
|
+
import { GetDocumentTemplatesEndpoint } from './GetDocumentTemplatesEndpoint.js';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = CountFilteredRequest;
|
|
10
|
+
type Body = undefined;
|
|
11
|
+
type ResponseBody = CountResponse;
|
|
12
|
+
|
|
13
|
+
export class GetDocumentTemplatesCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>;
|
|
15
|
+
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method !== 'GET') {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, '/organization/document-templates/count', {});
|
|
22
|
+
|
|
23
|
+
if (params) {
|
|
24
|
+
return [true, params as Params];
|
|
25
|
+
}
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
30
|
+
const organization = await Context.setOrganizationScope();
|
|
31
|
+
await Context.authenticate();
|
|
32
|
+
|
|
33
|
+
if (!await Context.auth.canManageDocuments(organization.id)) {
|
|
34
|
+
throw Context.auth.error();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const query = await GetDocumentTemplatesEndpoint.buildQuery(request.query);
|
|
38
|
+
|
|
39
|
+
const count = await query
|
|
40
|
+
.count();
|
|
41
|
+
|
|
42
|
+
return new Response(
|
|
43
|
+
CountResponse.create({
|
|
44
|
+
count,
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { DocumentTemplatePrivate } from '@stamhoofd/structures';
|
|
2
|
+
import { DocumentTemplate } from '@stamhoofd/models';
|
|
3
|
+
import { assertSort, CountFilteredRequest, DocumentTemplatePrivate, LimitedFilteredRequest, PaginatedResponse, SearchFilterFactory, StamhoofdFilter } from '@stamhoofd/structures';
|
|
5
4
|
|
|
6
|
-
import {
|
|
5
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
6
|
+
import { applySQLSorter, compileToSQLFilter, SQL, SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
7
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
8
|
+
import { LimitedFilteredRequestHelper } from '../../../../helpers/LimitedFilteredRequestHelper.js';
|
|
9
|
+
import { documentTemplateFilterCompilers } from '../../../../sql-filters/document-templates.js';
|
|
10
|
+
import { documentTemplateSorters } from '../../../../sql-sorters/document-templates.js';
|
|
7
11
|
|
|
8
12
|
type Params = Record<string, never>;
|
|
9
|
-
type Query =
|
|
13
|
+
type Query = LimitedFilteredRequest;
|
|
10
14
|
type Body = undefined;
|
|
11
|
-
type ResponseBody = DocumentTemplatePrivate[]
|
|
15
|
+
type ResponseBody = PaginatedResponse<DocumentTemplatePrivate[], LimitedFilteredRequest>;
|
|
16
|
+
|
|
17
|
+
const filterCompilers: SQLFilterDefinitions = documentTemplateFilterCompilers;
|
|
18
|
+
const sorters: SQLSortDefinitions<DocumentTemplate> = documentTemplateSorters;
|
|
12
19
|
|
|
13
20
|
export class GetDocumentTemplatesEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
21
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>;
|
|
22
|
+
|
|
14
23
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
15
24
|
if (request.method !== 'GET') {
|
|
16
25
|
return [false];
|
|
@@ -24,7 +33,60 @@ export class GetDocumentTemplatesEndpoint extends Endpoint<Params, Query, Body,
|
|
|
24
33
|
return [false];
|
|
25
34
|
}
|
|
26
35
|
|
|
27
|
-
async
|
|
36
|
+
static async buildQuery(q: CountFilteredRequest | LimitedFilteredRequest) {
|
|
37
|
+
const organization = Context.organization!;
|
|
38
|
+
|
|
39
|
+
const templatesTable: string = DocumentTemplate.table;
|
|
40
|
+
|
|
41
|
+
const query = SQL
|
|
42
|
+
.select(SQL.wildcard(templatesTable))
|
|
43
|
+
.from(SQL.table(templatesTable))
|
|
44
|
+
.where('organizationId', organization.id);
|
|
45
|
+
|
|
46
|
+
if (q.filter) {
|
|
47
|
+
query.where(await compileToSQLFilter(q.filter, filterCompilers));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (q.search) {
|
|
51
|
+
const searchFilter: StamhoofdFilter | null = getDocumentTemplateSearchFilter(q.search);
|
|
52
|
+
|
|
53
|
+
if (searchFilter) {
|
|
54
|
+
query.where(await compileToSQLFilter(searchFilter, filterCompilers));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
59
|
+
if (q.pageFilter) {
|
|
60
|
+
query.where(await compileToSQLFilter(q.pageFilter, filterCompilers));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
q.sort = assertSort(q.sort, [{ key: 'id' }]);
|
|
64
|
+
applySQLSorter(query, q.sort, sorters);
|
|
65
|
+
query.limit(q.limit);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return query;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static async buildData(requestQuery: LimitedFilteredRequest) {
|
|
72
|
+
const query = await this.buildQuery(requestQuery);
|
|
73
|
+
const data = await query.fetch();
|
|
74
|
+
|
|
75
|
+
const templates: DocumentTemplate[] = DocumentTemplate.fromRows(data, DocumentTemplate.table);
|
|
76
|
+
|
|
77
|
+
const next = LimitedFilteredRequestHelper.fixInfiniteLoadingLoop({
|
|
78
|
+
request: requestQuery,
|
|
79
|
+
results: templates,
|
|
80
|
+
sorters,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return new PaginatedResponse<DocumentTemplatePrivate[], LimitedFilteredRequest>({
|
|
84
|
+
results: templates.map(t => t.getPrivateStructure()),
|
|
85
|
+
next,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
28
90
|
const organization = await Context.setOrganizationScope();
|
|
29
91
|
await Context.authenticate();
|
|
30
92
|
|
|
@@ -32,19 +94,33 @@ export class GetDocumentTemplatesEndpoint extends Endpoint<Params, Query, Body,
|
|
|
32
94
|
throw Context.auth.error();
|
|
33
95
|
}
|
|
34
96
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
sort: [{
|
|
41
|
-
column: 'createdAt',
|
|
42
|
-
direction: 'ASC',
|
|
43
|
-
}],
|
|
44
|
-
},
|
|
45
|
-
);
|
|
97
|
+
LimitedFilteredRequestHelper.throwIfInvalidLimit({
|
|
98
|
+
request: request.query,
|
|
99
|
+
maxLimit: Context.auth.hasSomePlatformAccess() ? 1000 : 100,
|
|
100
|
+
});
|
|
101
|
+
|
|
46
102
|
return new Response(
|
|
47
|
-
|
|
103
|
+
await GetDocumentTemplatesEndpoint.buildData(request.query),
|
|
48
104
|
);
|
|
49
105
|
}
|
|
50
106
|
}
|
|
107
|
+
|
|
108
|
+
function getDocumentTemplateSearchFilter(search: string | null): StamhoofdFilter | null {
|
|
109
|
+
if (search === null || search === undefined) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const numberFilter = SearchFilterFactory.getIntegerFilter(search);
|
|
114
|
+
|
|
115
|
+
if (numberFilter) {
|
|
116
|
+
return {
|
|
117
|
+
year: numberFilter,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
name: {
|
|
123
|
+
$contains: search,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|