@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.
Files changed (38) hide show
  1. package/index.ts +2 -2
  2. package/package.json +20 -20
  3. package/src/crons/disable-auto-update-documents.test.ts +164 -0
  4. package/src/crons/disable-auto-update-documents.ts +82 -0
  5. package/src/endpoints/global/members/GetMembersEndpoint.ts +5 -5
  6. package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +8 -7
  7. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +9 -8
  8. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesCountEndpoint.ts +48 -0
  9. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +95 -19
  10. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplatesEndpoint.test.ts +282 -0
  11. package/src/endpoints/organization/dashboard/documents/{PatchDocumentTemplateEndpoint.ts → PatchDocumentTemplatesEndpoint.ts} +56 -3
  12. package/src/excel-loaders/members.ts +61 -7
  13. package/src/excel-loaders/registrations.ts +123 -2
  14. package/src/helpers/LimitedFilteredRequestHelper.ts +24 -0
  15. package/src/helpers/SQLTranslatedString.ts +14 -0
  16. package/src/helpers/TagHelper.test.ts +9 -9
  17. package/src/helpers/outstandingBalanceJoin.ts +49 -0
  18. package/src/seeds/1765896674-document-update-year.test.ts +179 -0
  19. package/src/seeds/1765896674-document-update-year.ts +66 -0
  20. package/src/seeds/1766150402-document-published-at.test.ts +46 -0
  21. package/src/seeds/1766150402-document-published-at.ts +20 -0
  22. package/src/services/PaymentService.ts +14 -32
  23. package/src/sql-filters/base-registration-filter-compilers.ts +51 -4
  24. package/src/sql-filters/document-templates.ts +45 -0
  25. package/src/sql-filters/documents.ts +1 -1
  26. package/src/sql-filters/events.ts +6 -6
  27. package/src/sql-filters/groups.ts +7 -6
  28. package/src/sql-filters/members.ts +31 -26
  29. package/src/sql-filters/orders.ts +16 -16
  30. package/src/sql-filters/organizations.ts +11 -11
  31. package/src/sql-filters/payments.ts +10 -10
  32. package/src/sql-filters/registrations.ts +14 -6
  33. package/src/sql-sorters/document-templates.ts +79 -0
  34. package/src/sql-sorters/documents.ts +1 -1
  35. package/src/sql-sorters/members.ts +22 -0
  36. package/src/sql-sorters/orders.ts +5 -5
  37. package/src/sql-sorters/organizations.ts +3 -3
  38. package/src/sql-sorters/registrations.ts +186 -15
@@ -1,5 +1,5 @@
1
1
  import { OrganizationTag } from '@stamhoofd/structures';
2
- import { TagHelper } from './TagHelper';
2
+ import { TagHelper } from './TagHelper.js';
3
3
 
4
4
  // todo: move tests for methods of shared package to shared package
5
5
  describe('TagHelper', () => {
@@ -123,13 +123,13 @@ describe('TagHelper', () => {
123
123
 
124
124
  // assert
125
125
  expect(result).toHaveLength(6);
126
- expect(result).toInclude('id5');
127
- expect(result).toInclude('id3');
128
- expect(result).toInclude('id4');
129
- expect(result).toInclude('id6');
130
- expect(result).toInclude('id7');
131
- expect(result).toInclude('id0');
132
- expect(result).not.toInclude('unknownTagId');
126
+ expect(result).toContain('id5');
127
+ expect(result).toContain('id3');
128
+ expect(result).toContain('id4');
129
+ expect(result).toContain('id6');
130
+ expect(result).toContain('id7');
131
+ expect(result).toContain('id0');
132
+ expect(result).not.toContain('unknownTagId');
133
133
  });
134
134
  });
135
135
 
@@ -199,7 +199,7 @@ describe('TagHelper', () => {
199
199
  // assert
200
200
  expect(tag5.childTags).toHaveLength(0);
201
201
  expect(tag7.childTags).toHaveLength(3);
202
- expect(tag7.childTags).not.toInclude('doesNotExist1');
202
+ expect(tag7.childTags).not.toContain('doesNotExist1');
203
203
  });
204
204
 
205
205
  it('should return array of tags in the correct order', () => {
@@ -0,0 +1,49 @@
1
+ import { CachedBalance, Registration } from '@stamhoofd/models';
2
+ import { SQL, SQLAlias, SQLCalculation, SQLNamedExpression, SQLPlusSign, SQLSelectAs, SQLSum } from '@stamhoofd/sql';
3
+
4
+ export const memberCachedBalanceForOrganizationJoin = SQL.leftJoin(
5
+ SQL.select('objectId', 'organizationId',
6
+ new SQLSelectAs(
7
+ new SQLSum(
8
+ SQL.column('amountOpen'),
9
+ ),
10
+ new SQLAlias('amountOpen'),
11
+ ),
12
+ )
13
+ .from(CachedBalance.table)
14
+ .where(SQL.column(CachedBalance.table, 'objectType'), 'member')
15
+ .groupBy(SQL.column(CachedBalance.table, 'objectId'), SQL.column(CachedBalance.table, 'organizationId')).as('memberCachedBalance') as SQLNamedExpression, 'memberCachedBalance',
16
+ )
17
+ .where(SQL.column('objectId'), SQL.column(Registration.table, 'memberId'))
18
+ .andWhere(SQL.column('organizationId'), SQL.column(Registration.table, 'organizationId'));
19
+
20
+ export const registrationCachedBalanceJoin = SQL.leftJoin(
21
+ SQL.select('objectId', 'organizationId',
22
+ new SQLSelectAs(
23
+ new SQLSum(
24
+ new SQLCalculation(
25
+ SQL.column('amountOpen'),
26
+ new SQLPlusSign(),
27
+ SQL.column('amountPending'),
28
+ ),
29
+ ),
30
+ new SQLAlias('toPay'),
31
+ ),
32
+ new SQLSelectAs(
33
+ new SQLSum(
34
+ new SQLCalculation(
35
+ SQL.column('amountOpen'),
36
+ new SQLPlusSign(),
37
+ SQL.column('amountPaid'),
38
+ new SQLPlusSign(),
39
+ SQL.column('amountPending'),
40
+ ),
41
+ ),
42
+ new SQLAlias('price'),
43
+ ),
44
+ )
45
+ .from(CachedBalance.table)
46
+ .where(SQL.column(CachedBalance.table, 'objectType'), 'registration')
47
+ .groupBy(SQL.column(CachedBalance.table, 'objectId'), SQL.column(CachedBalance.table, 'organizationId')).as('registrationCachedBalance') as SQLNamedExpression, 'registrationCachedBalance',
48
+ )
49
+ .where(SQL.column('objectId'), SQL.column(Registration.table, 'id'));
@@ -0,0 +1,179 @@
1
+ import { DocumentTemplate, DocumentTemplateFactory, GroupFactory, OrganizationFactory, RegistrationPeriod, RegistrationPeriodFactory } from '@stamhoofd/models';
2
+ import { migrateDocumentYears } from './1765896674-document-update-year.js';
3
+
4
+ describe('migration.document-update-year', () => {
5
+ describe('should use most frequent year of groups', () => {
6
+ test('groups with date in period', async () => {
7
+ const organization = await new OrganizationFactory({
8
+ }).create();
9
+
10
+ const period1 = await new RegistrationPeriodFactory({
11
+ startDate: new Date(2021, 0, 1),
12
+ endDate: new Date(2021, 11, 31),
13
+ organization,
14
+ }).create();
15
+
16
+ organization.periodId = period1.id;
17
+ await organization.save();
18
+
19
+ const period2 = await new RegistrationPeriodFactory({
20
+ startDate: new Date(2019, 0, 1),
21
+ endDate: new Date(2019, 11, 31),
22
+ organization,
23
+ }).create();
24
+
25
+ const createGroup = async (startDate: Date, endDate: Date, period: RegistrationPeriod) => {
26
+ const group = await new GroupFactory({ organization, period }).create();
27
+ group.settings.startDate = startDate;
28
+ group.settings.endDate = endDate;
29
+ await group.save();
30
+ return group;
31
+ };
32
+
33
+ // groups
34
+ const group1 = await createGroup(new Date(2021, 0, 1), new Date(2021, 11, 31), period1);
35
+ const group2 = await createGroup(new Date(2021, 0, 1), new Date(2021, 11, 31), period1);
36
+ const group3 = await createGroup(new Date(2019, 0, 1), new Date(2019, 11, 31), period2);
37
+
38
+ // document created in 2021
39
+ const document1 = await new DocumentTemplateFactory({
40
+ groups: [group1, group2, group3],
41
+ year: 0,
42
+ }).create();
43
+
44
+ document1.createdAt = new Date(2022, 0, 1);
45
+ await document1.save();
46
+
47
+ // document created in 2020
48
+ const document2 = await new DocumentTemplateFactory({
49
+ groups: [group1, group2, group3],
50
+ year: 0,
51
+ }).create();
52
+
53
+ document2.createdAt = new Date(2021, 0, 1);
54
+ await document2.save();
55
+
56
+ // document created in 2019
57
+ const document3 = await new DocumentTemplateFactory({
58
+ groups: [group1, group2, group3],
59
+ year: 0,
60
+ }).create();
61
+
62
+ document3.createdAt = new Date(2020, 0, 1);
63
+ await document3.save();
64
+
65
+ // act
66
+ await migrateDocumentYears();
67
+
68
+ // assert
69
+ const updatedDocument1 = await DocumentTemplate.getByID(document1.id);
70
+ const updatedDocument2 = await DocumentTemplate.getByID(document2.id);
71
+ const updatedDocument3 = await DocumentTemplate.getByID(document3.id);
72
+
73
+ // take most frequent year and prefer date of document creation
74
+ expect(updatedDocument1?.year).toBe(2021);
75
+ expect(updatedDocument2?.year).toBe(2021);
76
+ expect(updatedDocument3?.year).toBe(2021);
77
+ });
78
+
79
+ test('groups overlapping period', async () => {
80
+ const organization = await new OrganizationFactory({
81
+ }).create();
82
+
83
+ const period1 = await new RegistrationPeriodFactory({
84
+ startDate: new Date(2021, 0, 1),
85
+ endDate: new Date(2021, 11, 31),
86
+ organization,
87
+ }).create();
88
+
89
+ organization.periodId = period1.id;
90
+ await organization.save();
91
+
92
+ const period2 = await new RegistrationPeriodFactory({
93
+ startDate: new Date(2020, 0, 1),
94
+ endDate: new Date(2020, 11, 31),
95
+ organization,
96
+ }).create();
97
+
98
+ const period3 = await new RegistrationPeriodFactory({
99
+ startDate: new Date(2019, 0, 1),
100
+ endDate: new Date(2019, 11, 31),
101
+ organization,
102
+ }).create();
103
+
104
+ const createGroup = async (startDate: Date, endDate: Date, period: RegistrationPeriod) => {
105
+ const group = await new GroupFactory({ organization, period }).create();
106
+ group.settings.startDate = startDate;
107
+ group.settings.endDate = endDate;
108
+ await group.save();
109
+ return group;
110
+ };
111
+
112
+ // groups
113
+ const group1 = await createGroup(new Date(2020, 6, 1), new Date(2021, 5, 30), period1);
114
+ const group2 = await createGroup(new Date(2020, 7, 5), new Date(2021, 4, 3), period1);
115
+ const group3 = await createGroup(new Date(2018, 5, 1), new Date(2019, 10, 14), period3);
116
+
117
+ // document created in 2021
118
+ const document1 = await new DocumentTemplateFactory({
119
+ groups: [group1, group2, group3],
120
+ year: 0,
121
+ }).create();
122
+
123
+ document1.createdAt = new Date(2022, 0, 1);
124
+ await document1.save();
125
+
126
+ // document created in 2020
127
+ const document2 = await new DocumentTemplateFactory({
128
+ groups: [group1, group2, group3],
129
+ year: 0,
130
+ }).create();
131
+
132
+ document2.createdAt = new Date(2021, 0, 1);
133
+ await document2.save();
134
+
135
+ // document created in 2019
136
+ const document3 = await new DocumentTemplateFactory({
137
+ groups: [group1, group2, group3],
138
+ year: 0,
139
+ }).create();
140
+
141
+ document3.createdAt = new Date(2020, 0, 1);
142
+ await document3.save();
143
+
144
+ // act
145
+ await migrateDocumentYears();
146
+
147
+ // assert
148
+ const updatedDocument1 = await DocumentTemplate.getByID(document1.id);
149
+ const updatedDocument2 = await DocumentTemplate.getByID(document2.id);
150
+ const updatedDocument3 = await DocumentTemplate.getByID(document3.id);
151
+
152
+ // take most frequent year and prefer date of document creation
153
+ expect(updatedDocument1?.year).toBe(2020);
154
+ // should take 2020 because document was created in 2020
155
+ expect(updatedDocument2?.year).toBe(2020);
156
+ expect(updatedDocument3?.year).toBe(2020);
157
+ });
158
+ });
159
+
160
+ test('no groups should use year before creation', async () => {
161
+ // create document
162
+ const document = await new DocumentTemplateFactory({
163
+ groups: [],
164
+ year: 0,
165
+ }).create();
166
+
167
+ document.createdAt = new Date(2025, 0, 1);
168
+ await document.save();
169
+
170
+ // act
171
+ await migrateDocumentYears();
172
+
173
+ // assert
174
+ const updatedDocument = await DocumentTemplate.getByID(document.id);
175
+
176
+ // year should be year of creation
177
+ expect(updatedDocument?.year).toBe(2024);
178
+ });
179
+ });
@@ -0,0 +1,66 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { DocumentTemplate, Group } from '@stamhoofd/models';
3
+ import { SQL } from '@stamhoofd/sql';
4
+
5
+ export async function migrateDocumentYears() {
6
+ let c = 0;
7
+ const totalDocuments = await DocumentTemplate.select().count();
8
+
9
+ for await (const document of DocumentTemplate.select().all()) {
10
+ c++;
11
+ if (c % 1000 === 0) {
12
+ console.log('Processed', c, 'of', totalDocuments);
13
+ }
14
+
15
+ // check
16
+ if (!document.year) {
17
+ // by default use the year before creation
18
+ let year: number = document.createdAt.getFullYear() - 1;
19
+
20
+ const groups = document.privateSettings.groups;
21
+ const groupIds: string[] = [...new Set(groups.map(g => g.group.id))];
22
+
23
+ if (groupIds.length > 0) {
24
+ const query = Group.select().where(SQL.where('id', groupIds));
25
+ const groupModels = await query.fetch();
26
+
27
+ // count the number of groups per year
28
+ const yearMap = new Map<number, number>();
29
+
30
+ for (const group of groupModels) {
31
+ const y = group.settings.startDate.getFullYear();
32
+
33
+ const count = yearMap.get(y) ?? 0;
34
+ yearMap.set(y, count + 1);
35
+ }
36
+
37
+ // find the year with the highest count
38
+ let topYear = 0;
39
+ let topCount = 0;
40
+
41
+ for (const [year, count] of yearMap) {
42
+ if (count > topCount) {
43
+ topYear = year;
44
+ topCount = count;
45
+ }
46
+ }
47
+
48
+ // use the year with the highest count
49
+ if (topCount > 0) {
50
+ year = topYear;
51
+ }
52
+ }
53
+
54
+ document.year = year;
55
+ await document.save();
56
+ }
57
+ }
58
+ };
59
+
60
+ export default new Migration(async () => {
61
+ process.stdout.write('\n');
62
+ console.log('Start updating year of document templates.');
63
+ await migrateDocumentYears();
64
+
65
+ return Promise.resolve();
66
+ });
@@ -0,0 +1,46 @@
1
+ import { DocumentTemplate, DocumentTemplateFactory } from '@stamhoofd/models';
2
+ import { SQL } from '@stamhoofd/sql';
3
+ import { DocumentStatus } from '@stamhoofd/structures';
4
+ import { initPublishedAtForPublishedDocuments } from './1766150402-document-published-at.js';
5
+
6
+ describe('migration.document-published-at', () => {
7
+ it('should init published at for published documents', async () => {
8
+ // arrange
9
+ // published documents
10
+ const document1 = await createDocument(DocumentStatus.Published, new Date(2022, 0, 1));
11
+ const document2 = await createDocument(DocumentStatus.Published, new Date(2023, 5, 17));
12
+ const document3 = await createDocument(DocumentStatus.Published, new Date(2025, 3, 9));
13
+
14
+ // draft documents
15
+ const document4 = await createDocument(DocumentStatus.Draft, new Date(2022, 0, 1));
16
+ const document5 = await createDocument(DocumentStatus.Draft, new Date(2023, 5, 17));
17
+ const document6 = await createDocument(DocumentStatus.Draft, new Date(2025, 3, 9));
18
+
19
+ // reset published at (save does not update published at automatically)
20
+ await SQL.update(DocumentTemplate.table).set('publishedAt', null).where('id', [document1.id, document2.id, document3.id, document4.id, document5.id, document6.id]).update();
21
+
22
+ // act
23
+ await initPublishedAtForPublishedDocuments();
24
+
25
+ // assert
26
+ const updatedDocument1 = await DocumentTemplate.getByID(document1.id);
27
+ const updatedDocument2 = await DocumentTemplate.getByID(document2.id);
28
+ const updatedDocument3 = await DocumentTemplate.getByID(document3.id);
29
+
30
+ const updatedDocument4 = await DocumentTemplate.getByID(document4.id);
31
+ const updatedDocument5 = await DocumentTemplate.getByID(document5.id);
32
+ const updatedDocument6 = await DocumentTemplate.getByID(document6.id);
33
+
34
+ expect(updatedDocument1?.publishedAt?.getTime()).toEqual(document1.createdAt.getTime());
35
+ expect(updatedDocument2?.publishedAt?.getTime()).toEqual(document2.createdAt.getTime());
36
+ expect(updatedDocument3?.publishedAt?.getTime()).toEqual(document3.createdAt.getTime());
37
+
38
+ expect(updatedDocument4?.publishedAt).toBeNull();
39
+ expect(updatedDocument5?.publishedAt).toBeNull();
40
+ expect(updatedDocument6?.publishedAt).toBeNull();
41
+ });
42
+ });
43
+
44
+ async function createDocument(status: DocumentStatus, createdAt: Date) {
45
+ return await new DocumentTemplateFactory({ groups: [], status, createdAt }).create();
46
+ };
@@ -0,0 +1,20 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { DocumentTemplate } from '@stamhoofd/models';
3
+ import { SQL } from '@stamhoofd/sql';
4
+ import { DocumentStatus } from '@stamhoofd/structures';
5
+
6
+ export async function initPublishedAtForPublishedDocuments() {
7
+ await SQL.update(DocumentTemplate.table)
8
+ .set('publishedAt', SQL.column('createdAt'))
9
+ .where('publishedAt', null)
10
+ .where('status', DocumentStatus.Published)
11
+ .update();
12
+ };
13
+
14
+ export default new Migration(async () => {
15
+ process.stdout.write('\n');
16
+ console.log('Start init published at for published documents.');
17
+ await initPublishedAtForPublishedDocuments();
18
+
19
+ return Promise.resolve();
20
+ });
@@ -2,11 +2,11 @@ import createMollieClient, { PaymentStatus as MolliePaymentStatus } from '@molli
2
2
  import { BalanceItem, BalanceItemPayment, MolliePayment, MollieToken, Organization, PayconiqPayment, Payment } from '@stamhoofd/models';
3
3
  import { QueueHandler } from '@stamhoofd/queues';
4
4
  import { AuditLogSource, BalanceItemRelation, BalanceItemStatus, BalanceItemType, PaymentMethod, PaymentProvider, PaymentStatus } from '@stamhoofd/structures';
5
- import { BuckarooHelper } from '../helpers/BuckarooHelper';
6
- import { StripeHelper } from '../helpers/StripeHelper';
7
- import { AuditLogService } from './AuditLogService';
8
- import { BalanceItemPaymentService } from './BalanceItemPaymentService';
9
- import { BalanceItemService } from './BalanceItemService';
5
+ import { BuckarooHelper } from '../helpers/BuckarooHelper.js';
6
+ import { StripeHelper } from '../helpers/StripeHelper.js';
7
+ import { AuditLogService } from './AuditLogService.js';
8
+ import { BalanceItemPaymentService } from './BalanceItemPaymentService.js';
9
+ import { BalanceItemService } from './BalanceItemService.js';
10
10
 
11
11
  export const PaymentService = {
12
12
  async handlePaymentStatusUpdate(payment: Payment, organization: Organization, status: PaymentStatus) {
@@ -336,46 +336,28 @@ export const PaymentService = {
336
336
  * Say the total amount to pay is 15,238 because (e.g. because of VAT). In that case,
337
337
  * we'll need to round the payment to 1 cent. That can cause issues in the financial statements because
338
338
  * the total amount of balances does not match the total amount received/paid.
339
- *
339
+ *
340
340
  * To fix that, we create an extra balance item with the difference. So the rounding always matches.
341
+ *
342
+ * TODO: update this method to generate a virtual invoice and use the price of the invoice instead of the rounded payment price, so we don't get differences in calculation
341
343
  */
342
344
  async round(payment: Payment) {
343
- if (!payment.organizationId) {
344
- throw new Error('Cannot round payments without organizationId')
345
- }
346
-
347
345
  const amount = payment.price;
348
- const rounded = Payment.roundPrice(payment.price)
346
+ const rounded = Payment.roundPrice(payment.price);
349
347
  const difference = rounded - amount;
350
348
 
351
349
  if (difference === 0) {
352
350
  return;
353
351
  }
354
352
 
355
- if (difference > 100) {
356
- throw new Error('Unexpected rounding difference.')
353
+ if (difference > 100 || difference < -100) {
354
+ throw new Error('Unexpected rounding difference of ' + difference + ' for payment ' + payment.id);
357
355
  }
358
356
 
359
- const balanceItem = new BalanceItem();
360
- balanceItem.type = BalanceItemType.Rounding;
361
- balanceItem.userId = payment.payingUserId;
362
- balanceItem.payingOrganizationId = payment.payingOrganizationId
363
- balanceItem.unitPrice = difference;
364
- balanceItem.pricePaid = 0;
365
- balanceItem.organizationId = payment.organizationId;
366
- balanceItem.status = BalanceItemStatus.Hidden;
367
- await balanceItem.save();
368
-
369
- // Create balance item payment
370
- const balanceItemPayment = new BalanceItemPayment();
371
- balanceItemPayment.organizationId = payment.organizationId;
372
- balanceItemPayment.balanceItemId = balanceItem.id;
373
- balanceItemPayment.price = difference;
374
- balanceItemPayment.paymentId = payment.id;
375
- await balanceItemPayment.save();
357
+ // payment.roundingAmount = difference;
376
358
 
377
359
  // Change payment total price
378
- payment.price += difference
360
+ payment.price += difference;
379
361
  await payment.save();
380
- }
362
+ },
381
363
  };
@@ -1,8 +1,10 @@
1
1
  import { SimpleError } from '@simonbackx/simple-errors';
2
- import { baseSQLFilterCompilers, createColumnFilter, createExistsFilter, SQL, SQLFilterDefinitions, SQLValueType } from '@stamhoofd/sql';
2
+ import { baseSQLFilterCompilers, createColumnFilter, createExistsFilter, createJoinedRelationFilter, SQL, SQLFilterDefinitions, SQLIfNull, SQLValueType } from '@stamhoofd/sql';
3
3
  import { FilterWrapperMarker, PermissionLevel, StamhoofdFilter, unwrapFilter } from '@stamhoofd/structures';
4
- import { Context } from '../helpers/Context';
5
- import { organizationFilterCompilers } from './organizations';
4
+ import { Context } from '../helpers/Context.js';
5
+ import { memberCachedBalanceForOrganizationJoin, registrationCachedBalanceJoin } from '../helpers/outstandingBalanceJoin.js';
6
+ import { SQLTranslatedString } from '../helpers/SQLTranslatedString.js';
7
+ import { organizationFilterCompilers } from './organizations.js';
6
8
 
7
9
  async function checkGroupIdFilterAccess(filter: StamhoofdFilter, permissionLevel: PermissionLevel) {
8
10
  const groupIds = typeof filter === 'string'
@@ -77,6 +79,11 @@ export const baseRegistrationFilterCompilers: SQLFilterDefinitions = {
77
79
  type: SQLValueType.Number,
78
80
  nullable: false,
79
81
  }),
82
+ groupPrice: createColumnFilter({
83
+ expression: new SQLTranslatedString(SQL.column('groupPrice'), '$.value.name'),
84
+ type: SQLValueType.String,
85
+ nullable: true,
86
+ }),
80
87
  canRegister: createColumnFilter({
81
88
  expression: SQL.column('canRegister'),
82
89
  type: SQLValueType.Boolean,
@@ -110,6 +117,21 @@ export const baseRegistrationFilterCompilers: SQLFilterDefinitions = {
110
117
  type: SQLValueType.Datetime,
111
118
  nullable: true,
112
119
  }),
120
+ trialUntil: createColumnFilter({
121
+ expression: SQL.column('registrations', 'trialUntil'),
122
+ type: SQLValueType.Datetime,
123
+ nullable: true,
124
+ }),
125
+ startDate: createColumnFilter({
126
+ expression: SQL.column('registrations', 'startDate'),
127
+ type: SQLValueType.Datetime,
128
+ nullable: true,
129
+ }),
130
+ endDate: createColumnFilter({
131
+ expression: SQL.column('registrations', 'endDate'),
132
+ type: SQLValueType.Datetime,
133
+ nullable: true,
134
+ }),
113
135
  group: {
114
136
  ...baseSQLFilterCompilers,
115
137
  id: createColumnFilter({
@@ -126,7 +148,7 @@ export const baseRegistrationFilterCompilers: SQLFilterDefinitions = {
126
148
  nullable: false,
127
149
  }),
128
150
  name: createColumnFilter({
129
- expression: SQL.jsonValue(SQL.column('groups', 'settings'), '$.value.name'),
151
+ expression: SQL.jsonExtract(SQL.column('groups', 'settings'), '$.value.name'),
130
152
  type: SQLValueType.JSONString,
131
153
  nullable: false,
132
154
  }),
@@ -150,4 +172,29 @@ export const baseRegistrationFilterCompilers: SQLFilterDefinitions = {
150
172
  ),
151
173
  organizationFilterCompilers,
152
174
  ),
175
+ memberCachedBalance: createJoinedRelationFilter(
176
+ memberCachedBalanceForOrganizationJoin,
177
+ {
178
+ amountOpen: createColumnFilter({
179
+ expression: new SQLIfNull(SQL.column('memberCachedBalance', 'amountOpen'), 0),
180
+ type: SQLValueType.Number,
181
+ nullable: false,
182
+ }),
183
+ },
184
+ ),
185
+ registrationCachedBalance: createJoinedRelationFilter(
186
+ registrationCachedBalanceJoin,
187
+ {
188
+ toPay: createColumnFilter({
189
+ expression: new SQLIfNull(SQL.column('registrationCachedBalance', 'toPay'), 0),
190
+ type: SQLValueType.Number,
191
+ nullable: false,
192
+ }),
193
+ price: createColumnFilter({
194
+ expression: new SQLIfNull(SQL.column('registrationCachedBalance', 'price'), 0),
195
+ type: SQLValueType.Number,
196
+ nullable: false,
197
+ }),
198
+ },
199
+ ),
153
200
  };
@@ -0,0 +1,45 @@
1
+ import { baseSQLFilterCompilers, createColumnFilter, SQL, SQLFilterDefinitions, SQLValueType } from '@stamhoofd/sql';
2
+
3
+ export const documentTemplateFilterCompilers: SQLFilterDefinitions = {
4
+ ...baseSQLFilterCompilers,
5
+ id: createColumnFilter({
6
+ expression: SQL.column('id'),
7
+ type: SQLValueType.String,
8
+ nullable: false,
9
+ }),
10
+ name: createColumnFilter({
11
+ expression: SQL.jsonExtract(SQL.column('settings'), '$.value.name'),
12
+ type: SQLValueType.JSONString,
13
+ nullable: false,
14
+ }),
15
+ year: createColumnFilter({
16
+ expression: SQL.column('year'),
17
+ type: SQLValueType.Number,
18
+ nullable: false,
19
+ }),
20
+ organizationId: createColumnFilter({
21
+ expression: SQL.column('organizationId'),
22
+ type: SQLValueType.String,
23
+ nullable: false,
24
+ }),
25
+ updatedAt: createColumnFilter({
26
+ expression: SQL.column('updatedAt'),
27
+ type: SQLValueType.Datetime,
28
+ nullable: false,
29
+ }),
30
+ createdAt: createColumnFilter({
31
+ expression: SQL.column('createdAt'),
32
+ type: SQLValueType.Datetime,
33
+ nullable: false,
34
+ }),
35
+ status: createColumnFilter({
36
+ expression: SQL.column('status'),
37
+ type: SQLValueType.String,
38
+ nullable: false,
39
+ }),
40
+ type: createColumnFilter({
41
+ expression: SQL.jsonExtract(SQL.column('privateSettings'), '$.value.templateDefinition.type'),
42
+ type: SQLValueType.JSONString,
43
+ nullable: false,
44
+ }),
45
+ };
@@ -8,7 +8,7 @@ export const documentFilterCompilers: SQLFilterDefinitions = {
8
8
  nullable: false,
9
9
  }),
10
10
  description: createColumnFilter({
11
- expression: SQL.jsonValue(SQL.column('data'), '$.value.description'),
11
+ expression: SQL.jsonExtract(SQL.column('data'), '$.value.description'),
12
12
  type: SQLValueType.JSONString,
13
13
  nullable: false,
14
14
  }),
@@ -28,7 +28,7 @@ export const eventFilterCompilers: SQLFilterDefinitions = {
28
28
  nullable: false,
29
29
  }),
30
30
  'groupIds': createColumnFilter({
31
- expression: SQL.jsonValue(SQL.column('meta'), '$.value.groups[*].id'),
31
+ expression: SQL.jsonExtract(SQL.column('meta'), '$.value.groups[*].id'),
32
32
  type: SQLValueType.JSONArray,
33
33
  nullable: true,
34
34
  }),
@@ -43,27 +43,27 @@ export const eventFilterCompilers: SQLFilterDefinitions = {
43
43
  nullable: false,
44
44
  }),
45
45
  'defaultAgeGroupIds': createColumnFilter({
46
- expression: SQL.jsonValue(SQL.column('meta'), '$.value.defaultAgeGroupIds'),
46
+ expression: SQL.jsonExtract(SQL.column('meta'), '$.value.defaultAgeGroupIds'),
47
47
  type: SQLValueType.JSONArray,
48
48
  nullable: true,
49
49
  }),
50
50
  'organizationTagIds': createColumnFilter({
51
- expression: SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds'),
51
+ expression: SQL.jsonExtract(SQL.column('meta'), '$.value.organizationTagIds'),
52
52
  type: SQLValueType.JSONArray,
53
53
  nullable: true,
54
54
  }),
55
55
  'minAge': createColumnFilter({
56
- expression: SQL.jsonValue(SQL.column('meta'), '$.value.minAge'),
56
+ expression: SQL.jsonExtract(SQL.column('meta'), '$.value.minAge'),
57
57
  type: SQLValueType.JSONNumber,
58
58
  nullable: true,
59
59
  }),
60
60
  'maxAge': createColumnFilter({
61
- expression: SQL.jsonValue(SQL.column('meta'), '$.value.maxAge'),
61
+ expression: SQL.jsonExtract(SQL.column('meta'), '$.value.maxAge'),
62
62
  type: SQLValueType.JSONNumber,
63
63
  nullable: true,
64
64
  }),
65
65
  'meta.visible': createColumnFilter({
66
- expression: SQL.jsonValue(SQL.column('meta'), '$.value.visible'),
66
+ expression: SQL.jsonExtract(SQL.column('meta'), '$.value.visible'),
67
67
  type: SQLValueType.JSONBoolean,
68
68
  nullable: false,
69
69
  }),