@stamhoofd/backend 2.14.0 → 2.16.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.
Files changed (31) hide show
  1. package/.env.template.json +2 -1
  2. package/index.ts +15 -1
  3. package/package.json +14 -12
  4. package/src/email-recipient-loaders/members.ts +61 -0
  5. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +1 -1
  6. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +5 -183
  7. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +163 -0
  8. package/src/endpoints/global/files/GetFileCache.ts +69 -0
  9. package/src/endpoints/global/files/UploadFile.ts +4 -1
  10. package/src/endpoints/global/files/UploadImage.ts +14 -2
  11. package/src/endpoints/global/members/GetMembersEndpoint.ts +12 -299
  12. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +22 -2
  13. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +6 -134
  14. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +5 -3
  15. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +5 -3
  16. package/src/excel-loaders/members.ts +101 -0
  17. package/src/excel-loaders/payments.ts +539 -0
  18. package/src/helpers/AdminPermissionChecker.ts +0 -3
  19. package/src/helpers/AuthenticatedStructures.ts +2 -0
  20. package/src/helpers/FileCache.ts +158 -0
  21. package/src/helpers/fetchToAsyncIterator.ts +34 -0
  22. package/src/sql-filters/balance-item-payments.ts +13 -0
  23. package/src/sql-filters/members.ts +179 -0
  24. package/src/sql-filters/organizations.ts +115 -0
  25. package/src/sql-filters/payments.ts +78 -0
  26. package/src/sql-filters/registrations.ts +24 -0
  27. package/src/sql-sorters/members.ts +46 -0
  28. package/src/sql-sorters/organizations.ts +71 -0
  29. package/src/sql-sorters/payments.ts +50 -0
  30. package/tsconfig.json +3 -4
  31. package/src/endpoints/organization/dashboard/payments/legacy/GetPaymentsEndpoint.ts +0 -170
@@ -0,0 +1,158 @@
1
+ import { SimpleError } from '@simonbackx/simple-errors';
2
+ import { Formatter } from '@stamhoofd/utility';
3
+ import basex from "base-x";
4
+ import crypto from "crypto";
5
+ import fs from 'node:fs';
6
+ import { Writable } from 'node:stream';
7
+ import { Readable } from 'stream';
8
+
9
+ const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
10
+ const baseEncoder = basex(ALPHABET)
11
+
12
+ async function randomBytes(size: number): Promise<Buffer> {
13
+ return new Promise((resolve, reject) => {
14
+ crypto.randomBytes(size, (err: Error | null, buf: Buffer) => {
15
+ if (err) {
16
+ reject(err);
17
+ return;
18
+ }
19
+ resolve(buf);
20
+ });
21
+ });
22
+ }
23
+
24
+
25
+ export class FileCache {
26
+ static async getWriteStream(extension: '.xlsx'): Promise<{
27
+ file: string,
28
+ stream: WritableStream
29
+ }> {
30
+ if (!STAMHOOFD.CACHE_PATH) {
31
+ throw new SimpleError({
32
+ code: "not_configured",
33
+ message: "CACHE_PATH environment variable is not configured",
34
+ statusCode: 500
35
+ })
36
+ }
37
+
38
+ // Generate a long and unguessable filename
39
+ const fileName = baseEncoder.encode(await randomBytes(100)).toLowerCase() + extension;
40
+ const path = Formatter.dateIso(new Date());
41
+
42
+ // Save in folder with the current day
43
+ // Since this contains the day, we can easily restrict access to files after 1 day
44
+ const folder = STAMHOOFD.CACHE_PATH + "/" + path;
45
+ await fs.promises.mkdir(folder, { recursive: true })
46
+
47
+ const s = fs.createWriteStream(folder + '/' + fileName, 'binary');
48
+
49
+ s.on('close', () => {
50
+ console.log('FileCache closed file: File written to disk', folder + '/' + fileName)
51
+ });
52
+
53
+ return {
54
+ file: path + '/' + fileName,
55
+ stream: Writable.toWeb(s)
56
+ }
57
+ }
58
+
59
+ static async read(file: string, dayTimeout = 1): Promise<{stream: Readable, contentLength: number, extension: string}> {
60
+ const splitted = file.split("/");
61
+ if (splitted.length != 2) {
62
+ throw new SimpleError({
63
+ code: "invalid_file",
64
+ message: "Invalid file",
65
+ human: "Ongeldig bestand",
66
+ statusCode: 400
67
+ })
68
+ }
69
+
70
+ const fileName = splitted[1];
71
+
72
+ const extension = fileName.substring(fileName.length - 5);
73
+ if (extension != '.xlsx') {
74
+ throw new SimpleError({
75
+ code: "invalid_file",
76
+ message: "Invalid file",
77
+ human: "Ongeldig bestand",
78
+ statusCode: 400
79
+ })
80
+ }
81
+
82
+ const fileNameWithoutExtension = fileName.substring(0, fileName.length - 5);
83
+
84
+ // Verify filename alphabet matching ALPHABET
85
+ for (const char of fileNameWithoutExtension) {
86
+ if (!ALPHABET.includes(char)) {
87
+ throw new SimpleError({
88
+ code: "invalid_file",
89
+ message: "Invalid file",
90
+ human: "Onbekend karakters in bestandsnaam",
91
+ statusCode: 400
92
+ })
93
+ }
94
+ }
95
+
96
+ const path = splitted[0];
97
+
98
+ // Verify date
99
+ if (path.length != 10) {
100
+ throw new SimpleError({
101
+ code: "invalid_file",
102
+ message: "Invalid file",
103
+ human: "Ongelidge datum",
104
+ statusCode: 400
105
+ })
106
+ }
107
+ const year = parseInt(path.substring(0, 4));
108
+ const month = parseInt(path.substring(5, 5 + 2));
109
+ const day = parseInt(path.substring(8, 8 + 2));
110
+
111
+ if (isNaN(year) || isNaN(month) || isNaN(day)) {
112
+ throw new SimpleError({
113
+ code: "invalid_file",
114
+ message: "Invalid file",
115
+ human: "Ongeldige datum",
116
+ statusCode: 400
117
+ })
118
+ }
119
+
120
+ const date = new Date(year, month - 1, day, 0, 0, 0);
121
+ const now = new Date();
122
+ now.setHours(0, 0, 0, 0);
123
+
124
+ const diff = now.getTime() - date.getTime()
125
+
126
+ if (date > now || diff > dayTimeout * 24 * 60 * 60 * 1000) {
127
+ throw new SimpleError({
128
+ code: "file_expired",
129
+ message: "File expired",
130
+ human: "Het bestand is verlopen",
131
+ statusCode: 404
132
+ })
133
+ }
134
+
135
+ const filePath = STAMHOOFD.CACHE_PATH + "/" + Formatter.dateIso(date) + "/" + fileName;
136
+
137
+ let stat: fs.Stats;
138
+ try {
139
+ stat = await fs.promises.stat(filePath);
140
+ } catch (e) {
141
+ if (e.code == 'ENOENT') {
142
+ throw new SimpleError({
143
+ code: "file_expired",
144
+ message: "File expired",
145
+ human: "Het bestand bestaat niet",
146
+ statusCode: 404
147
+ })
148
+ }
149
+ throw e;
150
+ }
151
+
152
+ return {
153
+ stream: fs.createReadStream(filePath),
154
+ contentLength: stat.size,
155
+ extension
156
+ }
157
+ }
158
+ }
@@ -0,0 +1,34 @@
1
+ import { EncodableObject } from "@simonbackx/simple-encoding";
2
+ import { LimitedFilteredRequest, PaginatedResponse } from "@stamhoofd/structures";
3
+
4
+ export function fetchToAsyncIterator<T extends EncodableObject>(
5
+ initialFilter: LimitedFilteredRequest,
6
+ loader: {
7
+ fetch(request: LimitedFilteredRequest): Promise<PaginatedResponse<T, LimitedFilteredRequest>>
8
+ }
9
+ ): AsyncIterable<T> {
10
+ return {
11
+ [Symbol.asyncIterator]: function () {
12
+ let request: LimitedFilteredRequest|null = initialFilter
13
+
14
+ return {
15
+ async next(): Promise<IteratorResult<T, undefined>> {
16
+ if (!request) {
17
+ return {
18
+ done: true,
19
+ value: undefined
20
+ }
21
+ }
22
+
23
+ const response = await loader.fetch(request);
24
+ request = response.next ?? null;
25
+
26
+ return {
27
+ done: false,
28
+ value: response.results
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,13 @@
1
+ import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler, SQL, createSQLFilterNamespace } from "@stamhoofd/sql";
2
+
3
+ export const balanceItemPaymentsCompilers: SQLFilterDefinitions = {
4
+ ...baseSQLFilterCompilers,
5
+ "id": createSQLColumnFilterCompiler(SQL.column('balance_item_payments', 'id')),
6
+ "price": createSQLColumnFilterCompiler(SQL.column('balance_item_payments', 'price')),
7
+
8
+ "balanceItem": createSQLFilterNamespace({
9
+ ...baseSQLFilterCompilers,
10
+ id: createSQLColumnFilterCompiler(SQL.column('balance_items', 'id')),
11
+ description: createSQLColumnFilterCompiler(SQL.column('balance_items', 'description')),
12
+ })
13
+ }
@@ -0,0 +1,179 @@
1
+ import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLScalar, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler } from "@stamhoofd/sql";
2
+ import { Formatter } from "@stamhoofd/utility";
3
+ import { organizationFilterCompilers } from "./organizations";
4
+ import { registrationFilterCompilers } from "./registrations";
5
+
6
+ /**
7
+ * Defines how to filter members in the database from StamhoofdFilter objects
8
+ */
9
+ export const memberFilterCompilers: SQLFilterDefinitions = {
10
+ ...baseSQLFilterCompilers,
11
+ id: createSQLColumnFilterCompiler('id'),
12
+ name: createSQLExpressionFilterCompiler(
13
+ new SQLConcat(
14
+ SQL.column('firstName'),
15
+ new SQLScalar(' '),
16
+ SQL.column('lastName'),
17
+ )
18
+ ),
19
+ age: createSQLExpressionFilterCompiler(
20
+ new SQLAge(SQL.column('birthDay')),
21
+ {nullable: true}
22
+ ),
23
+ gender: createSQLExpressionFilterCompiler(
24
+ SQL.jsonValue(SQL.column('details'), '$.value.gender'),
25
+ {isJSONValue: true}
26
+ ),
27
+ birthDay: createSQLColumnFilterCompiler('birthDay', {
28
+ normalizeValue: (d) => {
29
+ if (typeof d === 'number') {
30
+ const date = new Date(d)
31
+ return Formatter.dateIso(date);
32
+ }
33
+ return d;
34
+ }
35
+ }),
36
+ organizationName: createSQLExpressionFilterCompiler(
37
+ SQL.column('organizations', 'name')
38
+ ),
39
+
40
+ email: createSQLExpressionFilterCompiler(
41
+ SQL.jsonValue(SQL.column('details'), '$.value.email'),
42
+ {isJSONValue: true}
43
+ ),
44
+
45
+ parentEmail: createSQLExpressionFilterCompiler(
46
+ SQL.jsonValue(SQL.column('details'), '$.value.parents[*].email'),
47
+ {isJSONValue: true, isJSONObject: true}
48
+ ),
49
+
50
+ registrations: createSQLRelationFilterCompiler(
51
+ SQL.select()
52
+ .from(
53
+ SQL.table('registrations')
54
+ ).join(
55
+ SQL.join(
56
+ SQL.table('groups')
57
+ ).where(
58
+ SQL.column('groups', 'id'),
59
+ SQL.column('registrations', 'groupId')
60
+ )
61
+ )
62
+ .join(
63
+ SQL.join(
64
+ SQL.table('organizations')
65
+ ).where(
66
+ SQL.column('organizations', 'id'),
67
+ SQL.column('registrations', 'organizationId')
68
+ )
69
+ )
70
+ .where(
71
+ SQL.column('memberId'),
72
+ SQL.column('members', 'id'),
73
+ ).whereNot(
74
+ SQL.column('registeredAt'),
75
+ null,
76
+ ).where(
77
+ SQL.column('deactivatedAt'),
78
+ null,
79
+ ).where(
80
+ SQL.column('groups', 'deletedAt'),
81
+ null
82
+ ),
83
+ {
84
+ ...registrationFilterCompilers,
85
+ "organization": createSQLFilterNamespace(organizationFilterCompilers)
86
+ }
87
+ ),
88
+
89
+ responsibilities: createSQLRelationFilterCompiler(
90
+ SQL.select()
91
+ .from(
92
+ SQL.table('member_responsibility_records')
93
+ )
94
+ .join(
95
+ SQL.leftJoin(
96
+ SQL.table('groups')
97
+ ).where(
98
+ SQL.column('groups', 'id'),
99
+ SQL.column('member_responsibility_records', 'groupId')
100
+ )
101
+ )
102
+ .where(
103
+ SQL.column('memberId'),
104
+ SQL.column('members', 'id'),
105
+ ),
106
+ {
107
+ ...baseSQLFilterCompilers,
108
+ // Alias for responsibilityId
109
+ "id": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
110
+ "responsibilityId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
111
+ "organizationId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'organizationId')),
112
+ "startDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'startDate')),
113
+ "endDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'endDate')),
114
+ "group": createSQLFilterNamespace({
115
+ ...baseSQLFilterCompilers,
116
+ id: createSQLColumnFilterCompiler(SQL.column('groups', 'id')),
117
+ defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
118
+ })
119
+ }
120
+ ),
121
+
122
+ platformMemberships: createSQLRelationFilterCompiler(
123
+ SQL.select()
124
+ .from(
125
+ SQL.table('member_platform_memberships')
126
+ )
127
+ .where(
128
+ SQL.column('memberId'),
129
+ SQL.column('members', 'id'),
130
+ )
131
+ .where(
132
+ SQL.column('deletedAt'),
133
+ null,
134
+ ),
135
+ {
136
+ ...baseSQLFilterCompilers,
137
+ "id": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'id')),
138
+ "membershipTypeId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'membershipTypeId')),
139
+ "organizationId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'organizationId')),
140
+ "periodId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'periodId')),
141
+ "price": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'price')),
142
+ "invoiceId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'invoiceId')),
143
+ "startDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'startDate')),
144
+ "endDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'endDate')),
145
+ "expireDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'expireDate')),
146
+ }
147
+ ),
148
+
149
+ organizations: createSQLRelationFilterCompiler(
150
+ SQL.select()
151
+ .from(
152
+ SQL.table('registrations')
153
+ ).join(
154
+ SQL.join(
155
+ SQL.table('groups')
156
+ ).where(
157
+ SQL.column('groups', 'id'),
158
+ SQL.column('registrations', 'groupId')
159
+ )
160
+ ).join(
161
+ SQL.join(
162
+ SQL.table('organizations')
163
+ ).where(
164
+ SQL.column('organizations', 'id'),
165
+ SQL.column('registrations', 'organizationId')
166
+ )
167
+ ).where(
168
+ SQL.column('memberId'),
169
+ SQL.column('members', 'id'),
170
+ ).whereNot(
171
+ SQL.column('registeredAt'),
172
+ null,
173
+ ).where(
174
+ SQL.column('groups', 'deletedAt'),
175
+ null
176
+ ),
177
+ organizationFilterCompilers
178
+ ),
179
+ }
@@ -0,0 +1,115 @@
1
+ import { SQL, SQLConcat, SQLFilterDefinitions, SQLNow, SQLNull, SQLScalar, SQLWhereEqual, SQLWhereOr, SQLWhereSign, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLRelationFilterCompiler } from "@stamhoofd/sql";
2
+
3
+ export const organizationFilterCompilers: SQLFilterDefinitions = {
4
+ ...baseSQLFilterCompilers,
5
+ id: createSQLExpressionFilterCompiler(
6
+ SQL.column('organizations', 'id')
7
+ ),
8
+ uri: createSQLExpressionFilterCompiler(
9
+ SQL.column('organizations', 'uri')
10
+ ),
11
+ name: createSQLExpressionFilterCompiler(
12
+ SQL.column('organizations', 'name')
13
+ ),
14
+ active: createSQLExpressionFilterCompiler(
15
+ SQL.column('organizations', 'active')
16
+ ),
17
+ city: createSQLExpressionFilterCompiler(
18
+ SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.city'),
19
+ {isJSONValue: true}
20
+ ),
21
+ country: createSQLExpressionFilterCompiler(
22
+ SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.country'),
23
+ {isJSONValue: true}
24
+ ),
25
+ umbrellaOrganization: createSQLExpressionFilterCompiler(
26
+ SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.umbrellaOrganization'),
27
+ {isJSONValue: true}
28
+ ),
29
+ type: createSQLExpressionFilterCompiler(
30
+ SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.type'),
31
+ {isJSONValue: true}
32
+ ),
33
+ tags: createSQLExpressionFilterCompiler(
34
+ SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.tags'),
35
+ {isJSONValue: true, isJSONObject: true}
36
+ ),
37
+ packages: createSQLRelationFilterCompiler(
38
+ SQL.select().from(
39
+ SQL.table('stamhoofd_packages')
40
+ ).where(
41
+ SQL.column('organizationId'),
42
+ SQL.column('organizations', 'id'),
43
+ )
44
+ .where(
45
+ SQL.column('validAt'),
46
+ SQLWhereSign.NotEqual,
47
+ new SQLNull()
48
+ ).where(
49
+ new SQLWhereOr([
50
+ new SQLWhereEqual(
51
+ SQL.column('validUntil'),
52
+ SQLWhereSign.Equal,
53
+ new SQLNull()
54
+ ),
55
+ new SQLWhereEqual(
56
+ SQL.column('validUntil'),
57
+ SQLWhereSign.Greater,
58
+ new SQLNow()
59
+ )
60
+ ])
61
+ ).where(
62
+ new SQLWhereOr([
63
+ new SQLWhereEqual(
64
+ SQL.column('removeAt'),
65
+ SQLWhereSign.Equal,
66
+ new SQLNull()
67
+ ),
68
+ new SQLWhereEqual(
69
+ SQL.column('removeAt'),
70
+ SQLWhereSign.Greater,
71
+ new SQLNow()
72
+ )
73
+ ])
74
+ ),
75
+
76
+ // const pack1 = await STPackage.where({ organizationId, validAt: { sign: "!=", value: null }, removeAt: { sign: ">", value: new Date() }})
77
+ // const pack2 = await STPackage.where({ organizationId, validAt: { sign: "!=", value: null }, removeAt: null })
78
+ {
79
+ ...baseSQLFilterCompilers,
80
+ "type": createSQLExpressionFilterCompiler(
81
+ SQL.jsonValue(SQL.column('meta'), '$.value.type'),
82
+ {isJSONValue: true}
83
+ )
84
+ }
85
+ ),
86
+ members: createSQLRelationFilterCompiler(
87
+ SQL.select().from(
88
+ SQL.table('members')
89
+ ).join(
90
+ SQL.join(
91
+ SQL.table('registrations')
92
+ ).where(
93
+ SQL.column('members', 'id'),
94
+ SQL.column('registrations', 'memberId')
95
+ )
96
+ ).where(
97
+ SQL.column('registrations', 'organizationId'),
98
+ SQL.column('organizations', 'id'),
99
+ ),
100
+
101
+ {
102
+ ...baseSQLFilterCompilers,
103
+ name: createSQLExpressionFilterCompiler(
104
+ new SQLConcat(
105
+ SQL.column('firstName'),
106
+ new SQLScalar(' '),
107
+ SQL.column('lastName'),
108
+ )
109
+ ),
110
+ "firstName": createSQLColumnFilterCompiler('firstName'),
111
+ "lastName": createSQLColumnFilterCompiler('lastName'),
112
+ "email": createSQLColumnFilterCompiler('email')
113
+ }
114
+ ),
115
+ }
@@ -0,0 +1,78 @@
1
+ import { SQL, SQLCast, SQLConcat, SQLFilterDefinitions, SQLJsonUnquote, SQLScalar, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler } from "@stamhoofd/sql";
2
+ import { balanceItemPaymentsCompilers } from "./balance-item-payments";
3
+
4
+ /**
5
+ * Defines how to filter members in the database from StamhoofdFilter objects
6
+ */
7
+ export const paymentFilterCompilers: SQLFilterDefinitions = {
8
+ ...baseSQLFilterCompilers,
9
+ id: createSQLColumnFilterCompiler('id'),
10
+ method: createSQLColumnFilterCompiler('method'),
11
+ status: createSQLColumnFilterCompiler('status'),
12
+ organizationId: createSQLColumnFilterCompiler('organizationId'),
13
+ createdAt: createSQLColumnFilterCompiler('createdAt'),
14
+ updatedAt: createSQLColumnFilterCompiler('updatedAt'),
15
+ paidAt: createSQLColumnFilterCompiler('paidAt', {nullable: true}),
16
+ price: createSQLColumnFilterCompiler('price'),
17
+ provider: createSQLColumnFilterCompiler('provider', {nullable: true}),
18
+ customer: createSQLFilterNamespace({
19
+ ...baseSQLFilterCompilers,
20
+ email: createSQLExpressionFilterCompiler(
21
+ SQL.jsonValue(SQL.column('customer'), '$.value.email'),
22
+ {isJSONValue: true}
23
+ ),
24
+ firstName: createSQLExpressionFilterCompiler(
25
+ SQL.jsonValue(SQL.column('customer'), '$.value.firstName'),
26
+ {isJSONValue: true}
27
+ ),
28
+ lastName: createSQLExpressionFilterCompiler(
29
+ SQL.jsonValue(SQL.column('customer'), '$.value.lastName'),
30
+ {isJSONValue: true}
31
+ ),
32
+ name: createSQLExpressionFilterCompiler(
33
+ new SQLCast(
34
+ new SQLConcat(
35
+ new SQLJsonUnquote(SQL.jsonValue(SQL.column('customer'), '$.value.firstName')),
36
+ new SQLScalar(' '),
37
+ new SQLJsonUnquote(SQL.jsonValue(SQL.column('customer'), '$.value.lastName')),
38
+ ),
39
+ 'CHAR'
40
+ )
41
+ ),
42
+ company: createSQLFilterNamespace({
43
+ name: createSQLExpressionFilterCompiler(
44
+ SQL.jsonValue(SQL.column('customer'), '$.value.company.name'),
45
+ {isJSONValue: true}
46
+ ),
47
+ VATNumber: createSQLExpressionFilterCompiler(
48
+ SQL.jsonValue(SQL.column('customer'), '$.value.company.VATNumber'),
49
+ {isJSONValue: true}
50
+ ),
51
+ companyNumber: createSQLExpressionFilterCompiler(
52
+ SQL.jsonValue(SQL.column('customer'), '$.value.company.companyNumber'),
53
+ {isJSONValue: true}
54
+ ),
55
+ administrationEmail: createSQLExpressionFilterCompiler(
56
+ SQL.jsonValue(SQL.column('customer'), '$.value.company.administrationEmail'),
57
+ {isJSONValue: true}
58
+ ),
59
+ })
60
+ }),
61
+ balanceItemPayments: createSQLRelationFilterCompiler(
62
+ SQL.select()
63
+ .from(
64
+ SQL.table('balance_item_payments')
65
+ ).join(
66
+ SQL.join(
67
+ SQL.table('balance_items')
68
+ ).where(
69
+ SQL.column('balance_items', 'id'),
70
+ SQL.column('balance_item_payments', 'balanceItemId')
71
+ )
72
+ ).where(
73
+ SQL.column('paymentId'),
74
+ SQL.column('payments', 'id')
75
+ ),
76
+ balanceItemPaymentsCompilers
77
+ ),
78
+ }
@@ -0,0 +1,24 @@
1
+ import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler, SQL, createSQLFilterNamespace, createSQLExpressionFilterCompiler } from "@stamhoofd/sql";
2
+
3
+ export const registrationFilterCompilers: SQLFilterDefinitions = {
4
+ ...baseSQLFilterCompilers,
5
+ "price": createSQLColumnFilterCompiler('price', {nullable: true}),
6
+ "pricePaid": createSQLColumnFilterCompiler('pricePaid'),
7
+ "canRegister": createSQLColumnFilterCompiler('canRegister'),
8
+ "organizationId": createSQLColumnFilterCompiler('organizationId'),
9
+ "groupId": createSQLColumnFilterCompiler('groupId'),
10
+ "registeredAt": createSQLColumnFilterCompiler('registeredAt', {nullable: true}),
11
+ "periodId": createSQLColumnFilterCompiler(SQL.column('registrations', 'periodId')),
12
+
13
+ "group": createSQLFilterNamespace({
14
+ ...baseSQLFilterCompilers,
15
+ id: createSQLColumnFilterCompiler('groupId'),
16
+ name: createSQLExpressionFilterCompiler(
17
+ SQL.jsonValue(SQL.column('groups', 'settings'), '$.value.name')
18
+ ),
19
+ status: createSQLExpressionFilterCompiler(
20
+ SQL.column('groups', 'status')
21
+ ),
22
+ defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), {nullable: true}),
23
+ })
24
+ }
@@ -0,0 +1,46 @@
1
+ import { MemberWithRegistrations } from "@stamhoofd/models"
2
+ import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from "@stamhoofd/sql"
3
+ import { Formatter } from "@stamhoofd/utility"
4
+
5
+ export const memberSorters: SQLSortDefinitions<MemberWithRegistrations> = {
6
+ 'id': {
7
+ getValue(a) {
8
+ return a.id
9
+ },
10
+ toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
11
+ return new SQLOrderBy({
12
+ column: SQL.column('id'),
13
+ direction
14
+ })
15
+ }
16
+ },
17
+ 'name': {
18
+ getValue(a) {
19
+ return a.details.name
20
+ },
21
+ toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
22
+ return SQLOrderBy.combine([
23
+ new SQLOrderBy({
24
+ column: SQL.column('firstName'),
25
+ direction
26
+ }),
27
+ new SQLOrderBy({
28
+ column: SQL.column('lastName'),
29
+ direction
30
+ })
31
+ ])
32
+ }
33
+ },
34
+ 'birthDay': {
35
+ getValue(a) {
36
+ return a.details.birthDay ? Formatter.dateIso(a.details.birthDay) : null
37
+ },
38
+ toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
39
+ return new SQLOrderBy({
40
+ column: SQL.column('birthDay'),
41
+ direction
42
+ })
43
+ }
44
+ }
45
+ // Note: never add mapped sortings, that should happen in the frontend -> e.g. map age to birthDay
46
+ }