@stamhoofd/backend 2.83.5 → 2.84.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 +19 -4
- package/package.json +18 -14
- package/src/crons/amazon-ses.ts +26 -5
- package/src/crons/balance-emails.ts +18 -17
- package/src/email-recipient-loaders/registrations.ts +87 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +5 -2
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +40 -40
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +28 -22
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +81 -49
- package/src/endpoints/global/files/UploadFile.ts +11 -16
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +234 -0
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +117 -43
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +1054 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +163 -141
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +6 -6
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +0 -16
- package/src/endpoints/global/members/helpers/validateGroupFilter.ts +73 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +1 -2
- package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +43 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1016 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +234 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -5
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +474 -554
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +191 -52
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +107 -9
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +89 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +9 -6
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +88 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +10 -6
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +10 -25
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +4 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +44 -19
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +140 -25
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +40 -10
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +4 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +2 -2
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
- package/src/excel-loaders/members.ts +233 -232
- package/src/excel-loaders/payments.ts +1 -1
- package/src/excel-loaders/receivable-balances.ts +1 -1
- package/src/excel-loaders/registrations.ts +153 -0
- package/src/helpers/AdminPermissionChecker.ts +65 -37
- package/src/helpers/AuthenticatedStructures.ts +43 -3
- package/src/helpers/Context.ts +29 -1
- package/src/helpers/GlobalHelper.ts +3 -1
- package/src/helpers/GroupedThrottledQueue.test.ts +219 -0
- package/src/helpers/GroupedThrottledQueue.ts +108 -0
- package/src/helpers/LimitedFilteredRequestHelper.ts +26 -1
- package/src/helpers/MemberCharger.ts +0 -5
- package/src/helpers/MembershipCharger.ts +3 -9
- package/src/helpers/OrganizationCharger.ts +0 -5
- package/src/helpers/ThrottledQueue.test.ts +194 -0
- package/src/helpers/ThrottledQueue.ts +145 -0
- package/src/helpers/XlsxTransformerColumnHelper.ts +44 -1
- package/src/middleware/ContextMiddleware.ts +1 -1
- package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/seeds/1735577912-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/services/BalanceItemPaymentService.ts +1 -33
- package/src/services/BalanceItemService.ts +167 -48
- package/src/services/FileSignService.ts +18 -13
- package/src/services/MemberRecordStore.ts +28 -19
- package/src/services/PaymentReallocationService.test.ts +25 -14
- package/src/services/PaymentReallocationService.ts +29 -10
- package/src/services/PaymentService.ts +4 -16
- package/src/services/PlatformMembershipService.ts +8 -4
- package/src/services/RegistrationService.ts +66 -2
- package/src/sql-filters/base-registration-filter-compilers.ts +43 -0
- package/src/sql-filters/groups.ts +67 -0
- package/src/sql-filters/members.ts +33 -58
- package/src/sql-filters/organization-registration-periods.ts +8 -0
- package/src/sql-filters/registration-periods.ts +8 -0
- package/src/sql-filters/registrations.ts +11 -22
- package/src/sql-sorters/groups.ts +24 -0
- package/src/sql-sorters/organization-registration-periods.ts +24 -0
- package/src/sql-sorters/registration-periods.ts +47 -0
- package/src/sql-sorters/registrations.ts +77 -0
- package/tests/actions/patchOrganizationMember.ts +27 -0
- package/tests/actions/patchPaymentStatus.ts +45 -0
- package/tests/actions/patchUserMember.ts +27 -0
- package/tests/assertions/assertBalances.ts +49 -0
- package/tests/e2e/api-rate-limits.test.ts +5 -5
- package/tests/e2e/bundle-discounts.test.ts +4060 -0
- package/tests/e2e/charge-members.test.ts +27 -24
- package/tests/e2e/documents.test.ts +398 -0
- package/tests/e2e/register.test.ts +292 -312
- package/tests/helpers/PayconiqMocker.ts +55 -0
- package/tests/init/index.ts +5 -0
- package/tests/init/initAdmin.ts +14 -0
- package/tests/init/initBundleDiscount.ts +47 -0
- package/tests/init/initPayconiq.ts +9 -0
- package/tests/init/initPlatformAdmin.ts +13 -0
- package/tests/init/initStripe.ts +21 -0
- package/tests/jest.setup.ts +29 -0
- package/src/seeds-temporary/1736266448-recall-balance-item-price-paid.ts +0 -70
|
@@ -31,10 +31,7 @@ export const PaymentService = {
|
|
|
31
31
|
await BalanceItemPaymentService.markPaid(balanceItemPayment, organization);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
// Reallocate
|
|
37
|
-
await BalanceItemService.reallocate(balanceItemPayments.map(p => p.balanceItem), organization.id);
|
|
34
|
+
await BalanceItemService.updatePaidAndPending(balanceItemPayments.map(p => p.balanceItem));
|
|
38
35
|
});
|
|
39
36
|
return;
|
|
40
37
|
}
|
|
@@ -58,10 +55,7 @@ export const PaymentService = {
|
|
|
58
55
|
await BalanceItemPaymentService.undoPaid(balanceItemPayment, organization);
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
await
|
|
62
|
-
|
|
63
|
-
// Reallocate
|
|
64
|
-
await BalanceItemService.reallocate(balanceItemPayments.map(p => p.balanceItem), organization.id);
|
|
58
|
+
await BalanceItemService.updatePaidAndPending(balanceItemPayments.map(p => p.balanceItem));
|
|
65
59
|
});
|
|
66
60
|
}
|
|
67
61
|
|
|
@@ -76,10 +70,7 @@ export const PaymentService = {
|
|
|
76
70
|
await BalanceItemPaymentService.markFailed(balanceItemPayment, organization);
|
|
77
71
|
}
|
|
78
72
|
|
|
79
|
-
await
|
|
80
|
-
|
|
81
|
-
// Reallocate
|
|
82
|
-
await BalanceItemService.reallocate(balanceItemPayments.map(p => p.balanceItem), organization.id);
|
|
73
|
+
await BalanceItemService.updatePaidAndPending(balanceItemPayments.map(p => p.balanceItem));
|
|
83
74
|
});
|
|
84
75
|
}
|
|
85
76
|
|
|
@@ -94,10 +85,7 @@ export const PaymentService = {
|
|
|
94
85
|
await BalanceItemPaymentService.undoFailed(balanceItemPayment, organization);
|
|
95
86
|
}
|
|
96
87
|
|
|
97
|
-
await
|
|
98
|
-
|
|
99
|
-
// Reallocate
|
|
100
|
-
await BalanceItemService.reallocate(balanceItemPayments.map(p => p.balanceItem), organization.id);
|
|
88
|
+
await BalanceItemService.updatePaidAndPending(balanceItemPayments.map(p => p.balanceItem));
|
|
101
89
|
});
|
|
102
90
|
}
|
|
103
91
|
});
|
|
@@ -138,9 +138,6 @@ export class PlatformMembershipService {
|
|
|
138
138
|
|
|
139
139
|
static async updateMembershipsForId(id: string, silent = false) {
|
|
140
140
|
if (STAMHOOFD.userMode === 'organization') {
|
|
141
|
-
if (!silent) {
|
|
142
|
-
console.warn('Skipping automatic membership for: ' + id, ' - organization mode');
|
|
143
|
-
}
|
|
144
141
|
return;
|
|
145
142
|
}
|
|
146
143
|
|
|
@@ -157,10 +154,17 @@ export class PlatformMembershipService {
|
|
|
157
154
|
}
|
|
158
155
|
return;
|
|
159
156
|
}
|
|
157
|
+
if (me.organizationId) {
|
|
158
|
+
if (!silent) {
|
|
159
|
+
console.warn('Cannot update members for a member with organization set', me.id);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
160
164
|
const platform = await Platform.getSharedStruct();
|
|
161
165
|
const periods = await RegistrationPeriod.select()
|
|
162
166
|
.where('locked', false)
|
|
163
|
-
.where('organizationId',
|
|
167
|
+
.where('organizationId', null)
|
|
164
168
|
.where('endDate', SQLWhereSign.GreaterEqual, new Date()) // Avoid updating the price of past periods that were not yet locked
|
|
165
169
|
.fetch();
|
|
166
170
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
|
-
import { Document, Group, Member, Registration } from '@stamhoofd/models';
|
|
3
|
-
import { AuditLogSource, EmailTemplateType, StockReservation } from '@stamhoofd/structures';
|
|
2
|
+
import { BalanceItem, Document, Group, Member, Registration } from '@stamhoofd/models';
|
|
3
|
+
import { AppliedRegistrationDiscount, AuditLogSource, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, EmailTemplateType, StockReservation, TranslatedString, Version } from '@stamhoofd/structures';
|
|
4
4
|
import { AuditLogService } from './AuditLogService';
|
|
5
5
|
import { GroupService } from './GroupService';
|
|
6
6
|
import { PlatformMembershipService } from './PlatformMembershipService';
|
|
7
7
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
8
8
|
import { Formatter } from '@stamhoofd/utility';
|
|
9
|
+
import { encodeObject, patchContainsChanges } from '@simonbackx/simple-encoding';
|
|
9
10
|
|
|
10
11
|
export const RegistrationService = {
|
|
11
12
|
async markValid(registrationId: string) {
|
|
@@ -44,6 +45,69 @@ export const RegistrationService = {
|
|
|
44
45
|
return true;
|
|
45
46
|
},
|
|
46
47
|
|
|
48
|
+
async updateDiscounts(registrationId: string) {
|
|
49
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
50
|
+
await QueueHandler.schedule('registration-discounts-update-' + registrationId, async function (this: undefined) {
|
|
51
|
+
const registration = await Registration.getByID(registrationId);
|
|
52
|
+
if (!registration) {
|
|
53
|
+
throw new Error('Registration not found');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fetch all discounts that have been granted to this registration
|
|
57
|
+
const discountBalanceItems = await BalanceItem.select()
|
|
58
|
+
.where('registrationId', registrationId)
|
|
59
|
+
.where('organizationId', registration.organizationId)
|
|
60
|
+
.where('type', BalanceItemType.RegistrationBundleDiscount)
|
|
61
|
+
.where('status', BalanceItemStatus.Due)
|
|
62
|
+
.fetch();
|
|
63
|
+
|
|
64
|
+
let before: string | undefined;
|
|
65
|
+
if (STAMHOOFD.environment !== 'production') {
|
|
66
|
+
// This is an expensive operation, so keep track of whether it was actually necessary so we can detect performance issues
|
|
67
|
+
before = JSON.stringify(encodeObject(registration.discounts, { version: Version }));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Reset registration discounts
|
|
71
|
+
registration.discounts = new Map();
|
|
72
|
+
|
|
73
|
+
for (const balanceItem of discountBalanceItems) {
|
|
74
|
+
const discount = balanceItem.relations.get(BalanceItemRelationType.Discount);
|
|
75
|
+
if (!discount) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let existing = registration.discounts.get(discount.id);
|
|
80
|
+
|
|
81
|
+
if (!existing) {
|
|
82
|
+
existing = AppliedRegistrationDiscount.create({
|
|
83
|
+
name: discount.name,
|
|
84
|
+
amount: 0,
|
|
85
|
+
});
|
|
86
|
+
registration.discounts.set(discount.id, existing);
|
|
87
|
+
}
|
|
88
|
+
existing.amount += -balanceItem.price; // price is negative means it has been discounted, and we store a positive amount with the discount
|
|
89
|
+
|
|
90
|
+
if (existing.amount === 0) {
|
|
91
|
+
// Delete the discount
|
|
92
|
+
registration.discounts.delete(discount.id);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log('Saving updated discounts for', registrationId, registration.discounts);
|
|
97
|
+
|
|
98
|
+
if (STAMHOOFD.environment !== 'production') {
|
|
99
|
+
const after = JSON.stringify(encodeObject(registration.discounts, { version: Version }));
|
|
100
|
+
if (before === after) {
|
|
101
|
+
console.warn('Unnecessary update of registration discounts', registrationId, before);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await registration.save();
|
|
106
|
+
return true;
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
|
|
47
111
|
async deactivate(registration: Registration, _group?: Group, _member?: Member) {
|
|
48
112
|
if (registration.deactivatedAt !== null) {
|
|
49
113
|
return;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLOneToOneRelationFilterCompiler, SQL, SQLFilterDefinitions, SQLValueType } from '@stamhoofd/sql';
|
|
2
|
+
import { organizationFilterCompilers } from './organizations';
|
|
3
|
+
|
|
4
|
+
export const baseRegistrationFilterCompilers: SQLFilterDefinitions = {
|
|
5
|
+
...baseSQLFilterCompilers,
|
|
6
|
+
id: createSQLColumnFilterCompiler('id'),
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated
|
|
9
|
+
*/
|
|
10
|
+
price: createSQLColumnFilterCompiler('price', { nullable: true }),
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated
|
|
13
|
+
*/
|
|
14
|
+
pricePaid: createSQLColumnFilterCompiler('pricePaid'),
|
|
15
|
+
canRegister: createSQLColumnFilterCompiler('canRegister'),
|
|
16
|
+
organizationId: createSQLColumnFilterCompiler('organizationId'),
|
|
17
|
+
groupId: createSQLColumnFilterCompiler('groupId'),
|
|
18
|
+
registeredAt: createSQLColumnFilterCompiler('registeredAt', { nullable: true }),
|
|
19
|
+
periodId: createSQLColumnFilterCompiler(SQL.column('registrations', 'periodId')),
|
|
20
|
+
deactivatedAt: createSQLColumnFilterCompiler(SQL.column('registrations', 'deactivatedAt'), { nullable: true }),
|
|
21
|
+
group: createSQLFilterNamespace({
|
|
22
|
+
...baseSQLFilterCompilers,
|
|
23
|
+
id: createSQLColumnFilterCompiler('groupId'),
|
|
24
|
+
name: createSQLExpressionFilterCompiler(
|
|
25
|
+
SQL.jsonValue(SQL.column('groups', 'settings'), '$.value.name'),
|
|
26
|
+
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
27
|
+
),
|
|
28
|
+
status: createSQLExpressionFilterCompiler(
|
|
29
|
+
SQL.column('groups', 'status'),
|
|
30
|
+
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
31
|
+
),
|
|
32
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), { nullable: true }),
|
|
33
|
+
}),
|
|
34
|
+
organization: createSQLOneToOneRelationFilterCompiler(
|
|
35
|
+
SQL.select()
|
|
36
|
+
.from(SQL.table('organizations'))
|
|
37
|
+
.where(
|
|
38
|
+
SQL.column('organizations', 'id'),
|
|
39
|
+
SQL.column('registrations', 'organizationId'),
|
|
40
|
+
),
|
|
41
|
+
organizationFilterCompilers,
|
|
42
|
+
),
|
|
43
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { SQL, createColumnFilter, SQLModernFilterDefinitions, SQLValueType, baseModernSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, SQLModernValueType, createWildcardColumnFilter, SQLJsonExtract } from '@stamhoofd/sql';
|
|
2
|
+
|
|
3
|
+
export const groupFilterCompilers: SQLModernFilterDefinitions = {
|
|
4
|
+
...baseModernSQLFilterCompilers,
|
|
5
|
+
id: createColumnFilter({
|
|
6
|
+
expression: SQL.column('id'),
|
|
7
|
+
type: SQLModernValueType.String,
|
|
8
|
+
nullable: false,
|
|
9
|
+
}),
|
|
10
|
+
organizationId: createColumnFilter({
|
|
11
|
+
expression: SQL.column('organizationId'),
|
|
12
|
+
type: SQLModernValueType.String,
|
|
13
|
+
nullable: false,
|
|
14
|
+
}),
|
|
15
|
+
periodId: createColumnFilter({
|
|
16
|
+
expression: SQL.column('periodId'),
|
|
17
|
+
type: SQLModernValueType.String,
|
|
18
|
+
nullable: false,
|
|
19
|
+
}),
|
|
20
|
+
name: createColumnFilter({
|
|
21
|
+
expression: SQL.jsonValue(SQL.column('settings'), '$.value.name'),
|
|
22
|
+
type: SQLModernValueType.JSONString,
|
|
23
|
+
nullable: false,
|
|
24
|
+
}),
|
|
25
|
+
status: createColumnFilter({
|
|
26
|
+
expression: SQL.column('status'),
|
|
27
|
+
type: SQLModernValueType.String,
|
|
28
|
+
nullable: false,
|
|
29
|
+
}),
|
|
30
|
+
defaultAgeGroupId: createColumnFilter({
|
|
31
|
+
expression: SQL.column('defaultAgeGroupId'),
|
|
32
|
+
type: SQLModernValueType.String,
|
|
33
|
+
nullable: true,
|
|
34
|
+
}),
|
|
35
|
+
bundleDiscounts: createWildcardColumnFilter(
|
|
36
|
+
(key: string) => ({
|
|
37
|
+
expression: SQL.jsonValue(SQL.column('settings'), `$.value.prices[*].bundleDiscounts.${SQLJsonExtract.escapePathComponent(key)}`, true),
|
|
38
|
+
type: SQLModernValueType.JSONArray,
|
|
39
|
+
nullable: true,
|
|
40
|
+
}),
|
|
41
|
+
(key: string) => ({
|
|
42
|
+
...baseModernSQLFilterCompilers,
|
|
43
|
+
name: createColumnFilter({
|
|
44
|
+
expression: SQL.jsonValue(SQL.column('settings'), `$.value.prices[*].bundleDiscounts.${SQLJsonExtract.escapePathComponent(key)}.name`, true),
|
|
45
|
+
type: SQLModernValueType.JSONArray,
|
|
46
|
+
nullable: true,
|
|
47
|
+
}),
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
|
|
51
|
+
/* name: createSQLExpressionFilterCompiler(
|
|
52
|
+
SQL.jsonValue(SQL.column('groups', 'settings'), '$.value.name'),
|
|
53
|
+
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
54
|
+
),
|
|
55
|
+
status: createSQLExpressionFilterCompiler(
|
|
56
|
+
SQL.column('groups', 'status'),
|
|
57
|
+
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
58
|
+
),
|
|
59
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), { nullable: true }),
|
|
60
|
+
|
|
61
|
+
bundleDiscountIds: createSQLExpressionFilterCompiler(
|
|
62
|
+
SQL.jsonKeys(
|
|
63
|
+
SQL.jsonValue(SQL.column('groups', 'settings'), '$.value.prices[0].bundleDiscounts'),
|
|
64
|
+
),
|
|
65
|
+
{ isJSONValue: true, isJSONObject: true },
|
|
66
|
+
), */
|
|
67
|
+
};
|
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
+
import { Member } from '@stamhoofd/models';
|
|
2
3
|
import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLScalar, SQLValueType, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler } from '@stamhoofd/sql';
|
|
3
4
|
import { AccessRight } from '@stamhoofd/structures';
|
|
4
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
5
6
|
import { Context } from '../helpers/Context';
|
|
7
|
+
import { baseRegistrationFilterCompilers } from './base-registration-filter-compilers';
|
|
6
8
|
import { organizationFilterCompilers } from './organizations';
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
const membersTable = SQL.table(Member.table);
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Defines how to filter members in the database from StamhoofdFilter objects
|
|
11
14
|
*/
|
|
12
15
|
export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
13
16
|
...baseSQLFilterCompilers,
|
|
14
|
-
'id': createSQLColumnFilterCompiler('id'),
|
|
15
|
-
'memberNumber': createSQLColumnFilterCompiler('memberNumber'),
|
|
16
|
-
'firstName': createSQLColumnFilterCompiler('firstName'),
|
|
17
|
-
'lastName': createSQLColumnFilterCompiler('lastName'),
|
|
17
|
+
'id': createSQLColumnFilterCompiler(SQL.column(membersTable, 'id')),
|
|
18
|
+
'memberNumber': createSQLColumnFilterCompiler(SQL.column(membersTable, 'memberNumber')),
|
|
19
|
+
'firstName': createSQLColumnFilterCompiler(SQL.column(membersTable, 'firstName')),
|
|
20
|
+
'lastName': createSQLColumnFilterCompiler(SQL.column(membersTable, 'lastName')),
|
|
18
21
|
'name': createSQLExpressionFilterCompiler(
|
|
19
22
|
new SQLConcat(
|
|
20
|
-
SQL.column('firstName'),
|
|
23
|
+
SQL.column(membersTable, 'firstName'),
|
|
21
24
|
new SQLScalar(' '),
|
|
22
|
-
SQL.column('lastName'),
|
|
25
|
+
SQL.column(membersTable, 'lastName'),
|
|
23
26
|
),
|
|
24
27
|
),
|
|
25
28
|
'age': createSQLExpressionFilterCompiler(
|
|
26
|
-
new SQLAge(SQL.column('birthDay')),
|
|
29
|
+
new SQLAge(SQL.column(membersTable, 'birthDay')),
|
|
27
30
|
{ nullable: true },
|
|
28
31
|
),
|
|
29
32
|
'gender': createSQLExpressionFilterCompiler(
|
|
30
|
-
SQL.jsonValue(SQL.column('details'), '$.value.gender'),
|
|
33
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.gender'),
|
|
31
34
|
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
32
35
|
),
|
|
33
|
-
|
|
34
|
-
'birthDay': createSQLColumnFilterCompiler('birthDay', {
|
|
36
|
+
'birthDay': createSQLColumnFilterCompiler(SQL.column(membersTable, 'birthDay'), {
|
|
35
37
|
normalizeValue: (d) => {
|
|
36
38
|
if (typeof d === 'number') {
|
|
37
39
|
const date = new Date(d);
|
|
@@ -40,13 +42,11 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
40
42
|
return d;
|
|
41
43
|
},
|
|
42
44
|
}),
|
|
43
|
-
|
|
44
45
|
'organizationName': createSQLExpressionFilterCompiler(
|
|
45
46
|
SQL.column('organizations', 'name'),
|
|
46
47
|
),
|
|
47
|
-
|
|
48
48
|
'details.requiresFinancialSupport': createSQLExpressionFilterCompiler(
|
|
49
|
-
SQL.jsonValue(SQL.column('details'), '$.value.requiresFinancialSupport.value'),
|
|
49
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.requiresFinancialSupport.value'),
|
|
50
50
|
{ isJSONValue: true, type: SQLValueType.JSONBoolean, checkPermission: async () => {
|
|
51
51
|
const organization = Context.organization;
|
|
52
52
|
if (!organization) {
|
|
@@ -65,97 +65,86 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
65
65
|
}
|
|
66
66
|
} },
|
|
67
67
|
),
|
|
68
|
-
|
|
69
68
|
'email': createSQLExpressionFilterCompiler(
|
|
70
|
-
SQL.jsonValue(SQL.column('details'), '$.value.email'),
|
|
69
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.email'),
|
|
71
70
|
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
72
71
|
),
|
|
73
|
-
|
|
74
72
|
'parentEmail': createSQLExpressionFilterCompiler(
|
|
75
|
-
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].email'),
|
|
73
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.parents[*].email'),
|
|
76
74
|
{ isJSONValue: true, isJSONObject: true, type: SQLValueType.JSONString },
|
|
77
75
|
),
|
|
78
|
-
|
|
79
76
|
'details.parents[0]': createSQLFilterNamespace({
|
|
80
77
|
name: createSQLExpressionFilterCompiler(
|
|
81
78
|
new SQLConcat(
|
|
82
|
-
SQL.jsonUnquotedValue(SQL.column('details'), '$.value.parents[0].firstName'),
|
|
79
|
+
SQL.jsonUnquotedValue(SQL.column(membersTable, 'details'), '$.value.parents[0].firstName'),
|
|
83
80
|
new SQLScalar(' '),
|
|
84
|
-
SQL.jsonUnquotedValue(SQL.column('details'), '$.value.parents[0].lastName'),
|
|
81
|
+
SQL.jsonUnquotedValue(SQL.column(membersTable, 'details'), '$.value.parents[0].lastName'),
|
|
85
82
|
),
|
|
86
83
|
{ isJSONValue: true, isJSONObject: false, type: SQLValueType.JSONString },
|
|
87
84
|
),
|
|
88
85
|
}),
|
|
89
|
-
|
|
90
86
|
'details.parents[1]': createSQLFilterNamespace({
|
|
91
87
|
name: createSQLExpressionFilterCompiler(
|
|
92
88
|
new SQLConcat(
|
|
93
|
-
SQL.jsonUnquotedValue(SQL.column('details'), '$.value.parents[1].firstName'),
|
|
89
|
+
SQL.jsonUnquotedValue(SQL.column(membersTable, 'details'), '$.value.parents[1].firstName'),
|
|
94
90
|
new SQLScalar(' '),
|
|
95
|
-
SQL.jsonUnquotedValue(SQL.column('details'), '$.value.parents[1].lastName'),
|
|
91
|
+
SQL.jsonUnquotedValue(SQL.column(membersTable, 'details'), '$.value.parents[1].lastName'),
|
|
96
92
|
),
|
|
97
93
|
{ isJSONValue: true, isJSONObject: false, type: SQLValueType.JSONString },
|
|
98
94
|
),
|
|
99
95
|
}),
|
|
100
|
-
|
|
101
96
|
'unverifiedEmail': createSQLExpressionFilterCompiler(
|
|
102
|
-
SQL.jsonValue(SQL.column('details'), '$.value.unverifiedEmails'),
|
|
97
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.unverifiedEmails'),
|
|
103
98
|
{ isJSONValue: true, isJSONObject: true, type: SQLValueType.JSONString },
|
|
104
99
|
),
|
|
105
|
-
|
|
106
100
|
'phone': createSQLExpressionFilterCompiler(
|
|
107
|
-
SQL.jsonValue(SQL.column('details'), '$.value.phone'),
|
|
101
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.phone'),
|
|
108
102
|
{ isJSONValue: true },
|
|
109
103
|
),
|
|
110
|
-
|
|
111
104
|
'details.address': createSQLFilterNamespace({
|
|
112
105
|
city: createSQLExpressionFilterCompiler(
|
|
113
|
-
SQL.jsonValue(SQL.column('details'), '$.value.address.city'),
|
|
106
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.address.city'),
|
|
114
107
|
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
115
108
|
),
|
|
116
109
|
postalCode: createSQLExpressionFilterCompiler(
|
|
117
|
-
SQL.jsonValue(SQL.column('details'), '$.value.address.postalCode'),
|
|
110
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.address.postalCode'),
|
|
118
111
|
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
119
112
|
),
|
|
120
113
|
street: createSQLExpressionFilterCompiler(
|
|
121
|
-
SQL.jsonValue(SQL.column('details'), '$.value.address.street'),
|
|
114
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.address.street'),
|
|
122
115
|
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
123
116
|
),
|
|
124
117
|
number: createSQLExpressionFilterCompiler(
|
|
125
|
-
SQL.jsonValue(SQL.column('details'), '$.value.address.number'),
|
|
118
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.address.number'),
|
|
126
119
|
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
127
120
|
),
|
|
128
121
|
}),
|
|
129
|
-
|
|
130
122
|
'details.parents[*].address': createSQLFilterNamespace({
|
|
131
123
|
city: createSQLExpressionFilterCompiler(
|
|
132
|
-
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].address.city'),
|
|
124
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.parents[*].address.city'),
|
|
133
125
|
{ isJSONValue: true, isJSONObject: true },
|
|
134
126
|
),
|
|
135
127
|
postalCode: createSQLExpressionFilterCompiler(
|
|
136
|
-
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].address.postalCode'),
|
|
128
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.parents[*].address.postalCode'),
|
|
137
129
|
{ isJSONValue: true, isJSONObject: true },
|
|
138
130
|
),
|
|
139
131
|
street: createSQLExpressionFilterCompiler(
|
|
140
|
-
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].address.street'),
|
|
132
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.parents[*].address.street'),
|
|
141
133
|
{ isJSONValue: true, isJSONObject: true },
|
|
142
134
|
),
|
|
143
135
|
number: createSQLExpressionFilterCompiler(
|
|
144
|
-
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].address.number'),
|
|
136
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.parents[*].address.number'),
|
|
145
137
|
{ isJSONValue: true, isJSONObject: true },
|
|
146
138
|
),
|
|
147
139
|
}),
|
|
148
|
-
|
|
149
140
|
'parentPhone': createSQLExpressionFilterCompiler(
|
|
150
|
-
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].phone'),
|
|
141
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.parents[*].phone'),
|
|
151
142
|
{ isJSONValue: true, isJSONObject: true },
|
|
152
143
|
),
|
|
153
|
-
|
|
154
144
|
'unverifiedPhone': createSQLExpressionFilterCompiler(
|
|
155
|
-
SQL.jsonValue(SQL.column('details'), '$.value.unverifiedPhones'),
|
|
145
|
+
SQL.jsonValue(SQL.column(membersTable, 'details'), '$.value.unverifiedPhones'),
|
|
156
146
|
{ isJSONValue: true, isJSONObject: true },
|
|
157
147
|
),
|
|
158
|
-
|
|
159
148
|
'registrations': createSQLRelationFilterCompiler(
|
|
160
149
|
SQL.select()
|
|
161
150
|
.from(
|
|
@@ -168,14 +157,6 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
168
157
|
SQL.column('registrations', 'groupId'),
|
|
169
158
|
),
|
|
170
159
|
)
|
|
171
|
-
.join(
|
|
172
|
-
SQL.join(
|
|
173
|
-
SQL.table('organizations'),
|
|
174
|
-
).where(
|
|
175
|
-
SQL.column('organizations', 'id'),
|
|
176
|
-
SQL.column('registrations', 'organizationId'),
|
|
177
|
-
),
|
|
178
|
-
)
|
|
179
160
|
.where(
|
|
180
161
|
SQL.column('memberId'),
|
|
181
162
|
SQL.column('members', 'id'),
|
|
@@ -189,12 +170,8 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
189
170
|
SQL.column('groups', 'deletedAt'),
|
|
190
171
|
null,
|
|
191
172
|
),
|
|
192
|
-
|
|
193
|
-
...registrationFilterCompilers,
|
|
194
|
-
organization: createSQLFilterNamespace(organizationFilterCompilers),
|
|
195
|
-
},
|
|
173
|
+
baseRegistrationFilterCompilers,
|
|
196
174
|
),
|
|
197
|
-
|
|
198
175
|
'responsibilities': createSQLRelationFilterCompiler(
|
|
199
176
|
SQL.select()
|
|
200
177
|
.from(
|
|
@@ -236,7 +213,6 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
236
213
|
organization: createSQLFilterNamespace(organizationFilterCompilers),
|
|
237
214
|
},
|
|
238
215
|
),
|
|
239
|
-
|
|
240
216
|
'platformMemberships': createSQLRelationFilterCompiler(
|
|
241
217
|
SQL.select()
|
|
242
218
|
.from(
|
|
@@ -267,7 +243,6 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
267
243
|
generated: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'generated')),
|
|
268
244
|
},
|
|
269
245
|
),
|
|
270
|
-
|
|
271
246
|
'organizations': createSQLRelationFilterCompiler(
|
|
272
247
|
SQL.select()
|
|
273
248
|
.from(
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler } from '@stamhoofd/sql';
|
|
2
|
+
|
|
3
|
+
export const organizationRegistrationPeriodFilterCompilers: SQLFilterDefinitions = {
|
|
4
|
+
...baseSQLFilterCompilers,
|
|
5
|
+
id: createSQLColumnFilterCompiler('id'),
|
|
6
|
+
organizationId: createSQLColumnFilterCompiler('organizationId'),
|
|
7
|
+
periodId: createSQLColumnFilterCompiler('periodId'),
|
|
8
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler } from '@stamhoofd/sql';
|
|
2
|
+
|
|
3
|
+
export const registrationPeriodFilterCompilers: SQLFilterDefinitions = {
|
|
4
|
+
...baseSQLFilterCompilers,
|
|
5
|
+
id: createSQLColumnFilterCompiler('id'),
|
|
6
|
+
startDate: createSQLColumnFilterCompiler('startDate'),
|
|
7
|
+
endDate: createSQLColumnFilterCompiler('endDate'),
|
|
8
|
+
};
|
|
@@ -1,26 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Member, Registration } from '@stamhoofd/models';
|
|
2
|
+
import { baseSQLFilterCompilers, createSQLJoinedRelationFilterCompiler, SQL, SQLFilterDefinitions } from '@stamhoofd/sql';
|
|
3
|
+
import { baseRegistrationFilterCompilers } from './base-registration-filter-compilers';
|
|
4
|
+
import { memberFilterCompilers } from './members';
|
|
5
|
+
|
|
6
|
+
export const memberJoin = SQL.join(Member.table).where(SQL.column(Member.table, 'id'), SQL.column(Registration.table, 'memberId'));
|
|
2
7
|
|
|
3
8
|
export const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
4
9
|
...baseSQLFilterCompilers,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
19
|
-
),
|
|
20
|
-
status: createSQLExpressionFilterCompiler(
|
|
21
|
-
SQL.column('groups', 'status'),
|
|
22
|
-
{ isJSONValue: true, type: SQLValueType.JSONString },
|
|
23
|
-
),
|
|
24
|
-
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), { nullable: true }),
|
|
25
|
-
}),
|
|
10
|
+
...baseRegistrationFilterCompilers,
|
|
11
|
+
member: createSQLJoinedRelationFilterCompiler(
|
|
12
|
+
memberJoin,
|
|
13
|
+
memberFilterCompilers,
|
|
14
|
+
),
|
|
26
15
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Group } from '@stamhoofd/models';
|
|
2
|
+
import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
3
|
+
|
|
4
|
+
export const groupSorters: SQLSortDefinitions<Group> = {
|
|
5
|
+
// WARNING! TEST NEW SORTERS THOROUGHLY!
|
|
6
|
+
// Try to avoid creating sorters on fields that er not 1:1 with the database, that often causes pagination issues if not thought through
|
|
7
|
+
// An example: sorting on 'name' is not a good idea, because it is a concatenation of two fields.
|
|
8
|
+
// You might be tempted to use ORDER BY firstName, lastName, but that will not work as expected and it needs to be ORDER BY CONCAT(firstName, ' ', lastName)
|
|
9
|
+
// Why? Because ORDER BY firstName, lastName produces a different order dan ORDER BY CONCAT(firstName, ' ', lastName) if there are multiple people with spaces in the first name
|
|
10
|
+
// And that again causes issues with pagination because the next query will append a filter of name > 'John Doe' - causing duplicate and/or skipped results
|
|
11
|
+
// What if you need mapping? simply map the sorters in the frontend: name -> firstname, lastname, age -> birthDay, etc.
|
|
12
|
+
|
|
13
|
+
id: {
|
|
14
|
+
getValue(a) {
|
|
15
|
+
return a.id;
|
|
16
|
+
},
|
|
17
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
18
|
+
return new SQLOrderBy({
|
|
19
|
+
column: SQL.column('id'),
|
|
20
|
+
direction,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { OrganizationRegistrationPeriod } from '@stamhoofd/models';
|
|
2
|
+
import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
3
|
+
|
|
4
|
+
export const organizationRegistrationPeriodSorters: SQLSortDefinitions<OrganizationRegistrationPeriod> = {
|
|
5
|
+
// WARNING! TEST NEW SORTERS THOROUGHLY!
|
|
6
|
+
// Try to avoid creating sorters on fields that er not 1:1 with the database, that often causes pagination issues if not thought through
|
|
7
|
+
// An example: sorting on 'name' is not a good idea, because it is a concatenation of two fields.
|
|
8
|
+
// You might be tempted to use ORDER BY firstName, lastName, but that will not work as expected and it needs to be ORDER BY CONCAT(firstName, ' ', lastName)
|
|
9
|
+
// Why? Because ORDER BY firstName, lastName produces a different order dan ORDER BY CONCAT(firstName, ' ', lastName) if there are multiple people with spaces in the first name
|
|
10
|
+
// And that again causes issues with pagination because the next query will append a filter of name > 'John Doe' - causing duplicate and/or skipped results
|
|
11
|
+
// What if you need mapping? simply map the sorters in the frontend: name -> firstname, lastname, age -> birthDay, etc.
|
|
12
|
+
|
|
13
|
+
id: {
|
|
14
|
+
getValue(a) {
|
|
15
|
+
return a.id;
|
|
16
|
+
},
|
|
17
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
18
|
+
return new SQLOrderBy({
|
|
19
|
+
column: SQL.column('id'),
|
|
20
|
+
direction,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { RegistrationPeriod } from '@stamhoofd/models';
|
|
2
|
+
import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
3
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
4
|
+
|
|
5
|
+
export const registrationPeriodSorters: SQLSortDefinitions<RegistrationPeriod> = {
|
|
6
|
+
// WARNING! TEST NEW SORTERS THOROUGHLY!
|
|
7
|
+
// Try to avoid creating sorters on fields that er not 1:1 with the database, that often causes pagination issues if not thought through
|
|
8
|
+
// An example: sorting on 'name' is not a good idea, because it is a concatenation of two fields.
|
|
9
|
+
// You might be tempted to use ORDER BY firstName, lastName, but that will not work as expected and it needs to be ORDER BY CONCAT(firstName, ' ', lastName)
|
|
10
|
+
// Why? Because ORDER BY firstName, lastName produces a different order dan ORDER BY CONCAT(firstName, ' ', lastName) if there are multiple people with spaces in the first name
|
|
11
|
+
// And that again causes issues with pagination because the next query will append a filter of name > 'John Doe' - causing duplicate and/or skipped results
|
|
12
|
+
// What if you need mapping? simply map the sorters in the frontend: name -> firstname, lastname, age -> birthDay, etc.
|
|
13
|
+
|
|
14
|
+
id: {
|
|
15
|
+
getValue(a) {
|
|
16
|
+
return a.id;
|
|
17
|
+
},
|
|
18
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
19
|
+
return new SQLOrderBy({
|
|
20
|
+
column: SQL.column('id'),
|
|
21
|
+
direction,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
startDate: {
|
|
26
|
+
getValue(a) {
|
|
27
|
+
return Formatter.dateTimeIso(a.startDate, 'UTC');
|
|
28
|
+
},
|
|
29
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
30
|
+
return new SQLOrderBy({
|
|
31
|
+
column: SQL.column('startDate'),
|
|
32
|
+
direction,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
endDate: {
|
|
37
|
+
getValue(a) {
|
|
38
|
+
return Formatter.dateTimeIso(a.endDate, 'UTC');
|
|
39
|
+
},
|
|
40
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
41
|
+
return new SQLOrderBy({
|
|
42
|
+
column: SQL.column('endDate'),
|
|
43
|
+
direction,
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|