@stamhoofd/backend 2.75.2 → 2.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.75.2",
3
+ "version": "2.77.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -35,16 +35,16 @@
35
35
  "@mollie/api-client": "3.7.0",
36
36
  "@simonbackx/simple-database": "1.29.0",
37
37
  "@simonbackx/simple-encoding": "2.20.0",
38
- "@simonbackx/simple-endpoints": "1.19.0",
38
+ "@simonbackx/simple-endpoints": "1.19.1",
39
39
  "@simonbackx/simple-logging": "^1.0.1",
40
- "@stamhoofd/backend-i18n": "2.75.2",
41
- "@stamhoofd/backend-middleware": "2.75.2",
42
- "@stamhoofd/email": "2.75.2",
43
- "@stamhoofd/models": "2.75.2",
44
- "@stamhoofd/queues": "2.75.2",
45
- "@stamhoofd/sql": "2.75.2",
46
- "@stamhoofd/structures": "2.75.2",
47
- "@stamhoofd/utility": "2.75.2",
40
+ "@stamhoofd/backend-i18n": "2.77.0",
41
+ "@stamhoofd/backend-middleware": "2.77.0",
42
+ "@stamhoofd/email": "2.77.0",
43
+ "@stamhoofd/models": "2.77.0",
44
+ "@stamhoofd/queues": "2.77.0",
45
+ "@stamhoofd/sql": "2.77.0",
46
+ "@stamhoofd/structures": "2.77.0",
47
+ "@stamhoofd/utility": "2.77.0",
48
48
  "archiver": "^7.0.1",
49
49
  "aws-sdk": "^2.885.0",
50
50
  "axios": "1.6.8",
@@ -64,5 +64,5 @@
64
64
  "publishConfig": {
65
65
  "access": "public"
66
66
  },
67
- "gitHead": "aafa49d4e89f748e62f364b52a40efe339206779"
67
+ "gitHead": "209036d412e24fefe961eeeeaa5a8618297bfcd7"
68
68
  }
@@ -1,10 +1,9 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
+ import { Platform } from '@stamhoofd/models';
3
+ import { QueueHandler } from '@stamhoofd/queues';
2
4
  import { SQL, SQLAlias, SQLCount, SQLDistinct, SQLSelectAs, SQLSum, SQLWhereSign } from '@stamhoofd/sql';
3
5
  import { ChargeMembershipsSummary, ChargeMembershipsTypeSummary } from '@stamhoofd/structures';
4
6
  import { Context } from '../../../helpers/Context';
5
- import { QueueHandler } from '@stamhoofd/queues';
6
- import { Platform } from '@stamhoofd/models';
7
- import { SimpleError } from '@simonbackx/simple-errors';
8
7
 
9
8
  type Params = Record<string, never>;
10
9
  type Query = Record<string, never>;
@@ -102,6 +101,7 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
102
101
  .from('member_platform_memberships')
103
102
  .where('balanceItemId', null)
104
103
  .where('deletedAt', null)
104
+ .where('locked', false)
105
105
  .whereNot('organizationId', chargeVia);
106
106
 
107
107
  if (!trial) {
@@ -164,6 +164,7 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
164
164
  .from('member_platform_memberships')
165
165
  .where('balanceItemId', null)
166
166
  .where('deletedAt', null)
167
+ .where('locked', false)
167
168
  .where(trialQ)
168
169
  .whereNot('organizationId', chargeVia)
169
170
  .groupBy(
@@ -186,7 +186,7 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
186
186
  throw new SimpleError({
187
187
  code: 'failed_to_sign',
188
188
  message: 'Failed to sign file',
189
- human: $t('Er ging iet mis bij het uploaden van jouw bestand. Probeer het later opnieuw (foutcode: SIGN).'),
189
+ human: $t('509cdb4f-131a-42a6-b3b1-a63cca231e65'),
190
190
  statusCode: 500,
191
191
  });
192
192
  }
@@ -532,7 +532,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
532
532
  membership.startDate = new Date(Math.max(Date.now(), put.startDate.getTime()));
533
533
  membership.endDate = put.endDate;
534
534
  membership.expireDate = put.expireDate;
535
- membership.locked = put.locked;
535
+ membership.locked = false;
536
536
 
537
537
  await membership.calculatePrice(member);
538
538
  await membership.save();
@@ -540,6 +540,63 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
540
540
  updateMembershipMemberIds.add(member.id);
541
541
  }
542
542
 
543
+ for (const p of patch.platformMemberships.getPatches()) {
544
+ const membership = await MemberPlatformMembership.getByID(p.id);
545
+
546
+ if (!membership || membership.memberId !== member.id) {
547
+ throw new SimpleError({
548
+ code: 'invalid_field',
549
+ field: 'id',
550
+ message: 'Invalid id',
551
+ human: 'Deze aansluiting bestaat niet',
552
+ });
553
+ }
554
+
555
+ if (!await Context.auth.hasFullAccess(membership.organizationId)) {
556
+ throw Context.auth.error('Je hebt niet voldoende rechten om deze aansluiting aan te passen');
557
+ }
558
+
559
+ if (membership.periodId !== platform.periodId) {
560
+ const period = await RegistrationPeriod.getByID(membership.periodId);
561
+
562
+ if (!period) {
563
+ throw new SimpleError({
564
+ code: 'invalid_field',
565
+ message: 'Invalid period',
566
+ human: $t(`82af2364-c711-4e44-a871-9346c2cab66a`),
567
+ field: 'periodId',
568
+ });
569
+ }
570
+
571
+ if (period?.locked) {
572
+ throw new SimpleError({
573
+ code: 'invalid_field',
574
+ message: 'Invalid period',
575
+ human: $t(`92a41b40-9841-4326-abaf-a8a7d97e5d55`),
576
+ field: 'periodId',
577
+ });
578
+ }
579
+ }
580
+
581
+ // For now only alter 'locked'
582
+ if (Context.auth.hasPlatformFullAccess()) {
583
+ membership.locked = p.locked ?? membership.locked;
584
+ await membership.save();
585
+ }
586
+ else {
587
+ if (p.locked === true) {
588
+ throw Context.auth.error($t('bbc639c8-abdb-42d8-b5ed-f58084886ad9'));
589
+ }
590
+
591
+ if (p.locked === false) {
592
+ throw Context.auth.error($t('c6494677-86f0-4d2e-b9ac-bedfc9e87187'));
593
+ }
594
+ }
595
+
596
+ updateMembershipsForOrganizations.add(membership.organizationId); // can influence free memberships in other members of same organization
597
+ updateMembershipMemberIds.add(member.id);
598
+ }
599
+
543
600
  // Delete platform memberships
544
601
  for (const id of patch.platformMemberships.getDeletes()) {
545
602
  const membership = await MemberPlatformMembership.getByID(id);
@@ -0,0 +1,160 @@
1
+ import { PatchableArray } from '@simonbackx/simple-encoding';
2
+ import { Endpoint, Request } from '@simonbackx/simple-endpoints';
3
+ import { GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
4
+ import { MemberDetails, MemberWithRegistrationsBlob, Parent } from '@stamhoofd/structures';
5
+ import { testServer } from '../../../../tests/helpers/TestServer';
6
+ import { PatchUserMembersEndpoint } from './PatchUserMembersEndpoint';
7
+
8
+ const baseUrl = `/members`;
9
+ const endpoint = new PatchUserMembersEndpoint();
10
+ type EndpointType = typeof endpoint;
11
+ type Body = EndpointType extends Endpoint<any, any, infer B, any> ? B : never;
12
+
13
+ const firstName = 'John';
14
+ const lastName = 'Doe';
15
+ const birthDay = { year: 1993, month: 4, day: 5 };
16
+
17
+ const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
18
+
19
+ describe('Endpoint.PatchUserMembersEndpoint', () => {
20
+ describe('Duplicate members', () => {
21
+ test('The security code should be a requirement', async () => {
22
+ const organization = await new OrganizationFactory({ }).create();
23
+ const user = await new UserFactory({ organization }).create();
24
+ const existingMember = await new MemberFactory({
25
+ organization,
26
+ firstName,
27
+ lastName,
28
+ birthDay,
29
+ generateData: true,
30
+ }).create();
31
+
32
+ const token = await Token.createToken(user);
33
+
34
+ const arr: Body = new PatchableArray();
35
+ const put = MemberWithRegistrationsBlob.create({
36
+ details: MemberDetails.create({
37
+ firstName,
38
+ lastName,
39
+ birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
40
+ }),
41
+ });
42
+ arr.addPut(put);
43
+
44
+ const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
45
+ request.headers.authorization = 'Bearer ' + token.accessToken;
46
+ await expect(testServer.test(endpoint, request))
47
+ .rejects
48
+ .toThrow(errorWithCode('known_member_missing_rights'));
49
+ });
50
+
51
+ test('The security code is not a requirement for members without additional data', async () => {
52
+ const organization = await new OrganizationFactory({ }).create();
53
+ const user = await new UserFactory({ organization }).create();
54
+ const existingMember = await new MemberFactory({
55
+ organization,
56
+ firstName,
57
+ lastName,
58
+ birthDay,
59
+ generateData: false,
60
+ }).create();
61
+
62
+ const token = await Token.createToken(user);
63
+
64
+ const arr: Body = new PatchableArray();
65
+ const put = MemberWithRegistrationsBlob.create({
66
+ details: MemberDetails.create({
67
+ firstName,
68
+ lastName,
69
+ birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
70
+ email: 'anewemail@example.com',
71
+ }),
72
+ });
73
+ arr.addPut(put);
74
+
75
+ const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
76
+ request.headers.authorization = 'Bearer ' + token.accessToken;
77
+ const response = await testServer.test(endpoint, request);
78
+ expect(response.status).toBe(200);
79
+
80
+ // Check id of the returned memebr matches the existing member
81
+ expect(response.body.members.length).toBe(1);
82
+ expect(response.body.members[0].id).toBe(existingMember.id);
83
+
84
+ // Check data matches the original data + changes from the put
85
+ const member = response.body.members[0];
86
+ expect(member.details.firstName).toBe(firstName);
87
+ expect(member.details.lastName).toBe(lastName);
88
+ expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
89
+ expect(member.details.email).toBe('anewemail@example.com'); // this has been merged
90
+ });
91
+
92
+ test('A duplicate member with existing registrations returns those registrations after a merge', async () => {
93
+ const organization = await new OrganizationFactory({ }).create();
94
+ const user = await new UserFactory({ organization }).create();
95
+ const details = MemberDetails.create({
96
+ firstName,
97
+ lastName,
98
+ securityCode: 'ABC-123',
99
+ parents: [
100
+ Parent.create({
101
+ firstName: 'Jane',
102
+ lastName: 'Doe',
103
+ email: 'jane.doe@example.com',
104
+ }),
105
+ ],
106
+ });
107
+
108
+ const existingMember = await new MemberFactory({
109
+ organization,
110
+ birthDay,
111
+ details,
112
+ }).create();
113
+
114
+ // Create a registration for this member
115
+ const group = await new GroupFactory({ organization }).create();
116
+ const registration = await new RegistrationFactory({
117
+ member: existingMember,
118
+ group,
119
+ }).create();
120
+
121
+ const token = await Token.createToken(user);
122
+
123
+ const arr: Body = new PatchableArray();
124
+ const put = MemberWithRegistrationsBlob.create({
125
+ details: MemberDetails.create({
126
+ firstName,
127
+ lastName,
128
+ birthDay: new Date(existingMember.details.birthDay!.getTime() + 1),
129
+ securityCode: existingMember.details.securityCode,
130
+ email: 'anewemail@example.com',
131
+ }),
132
+ });
133
+ arr.addPut(put);
134
+
135
+ const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
136
+ request.headers.authorization = 'Bearer ' + token.accessToken;
137
+ const response = await testServer.test(endpoint, request);
138
+ expect(response.status).toBe(200);
139
+
140
+ // Check id of the returned memebr matches the existing member
141
+ expect(response.body.members.length).toBe(1);
142
+ expect(response.body.members[0].id).toBe(existingMember.id);
143
+
144
+ // Check data matches the original data + changes from the put
145
+ const member = response.body.members[0];
146
+ expect(member.details.firstName).toBe(firstName);
147
+ expect(member.details.lastName).toBe(lastName);
148
+ expect(member.details.birthDay).toEqual(existingMember.details.birthDay);
149
+ expect(member.details.email).toBe('anewemail@example.com'); // this has been merged
150
+
151
+ // Check the registration is still there
152
+ expect(member.registrations.length).toBe(1);
153
+ expect(member.registrations[0].id).toBe(registration.id);
154
+
155
+ // Check parent is still there
156
+ expect(member.details.parents.length).toBe(1);
157
+ expect(member.details.parents[0]).toEqual(existingMember.details.parents[0]);
158
+ });
159
+ });
160
+ });
@@ -147,6 +147,32 @@ function getBalanceItemColumns(): XlsxTransformerColumn<PaymentWithItem>[] {
147
147
  value: object.balanceItemPayment.balanceItem.description,
148
148
  }),
149
149
  },
150
+ {
151
+ id: 'balanceItem.createdAt',
152
+ name: 'Aangerekend op',
153
+ width: 16,
154
+ getValue: (object: PaymentWithItem) => ({
155
+ value: object.balanceItemPayment.balanceItem.createdAt,
156
+ style: {
157
+ numberFormat: {
158
+ id: XlsxBuiltInNumberFormat.DateTimeSlash,
159
+ },
160
+ },
161
+ }),
162
+ },
163
+ {
164
+ id: 'balanceItem.dueAt',
165
+ name: 'Verschuldigd vanaf',
166
+ width: 16,
167
+ getValue: (object: PaymentWithItem) => ({
168
+ value: object.balanceItemPayment.balanceItem.dueAt,
169
+ style: {
170
+ numberFormat: {
171
+ id: XlsxBuiltInNumberFormat.DateTimeSlash,
172
+ },
173
+ },
174
+ }),
175
+ },
150
176
  {
151
177
  match: (id) => {
152
178
  if (id.startsWith('balanceItem.relations.')) {
@@ -1,6 +1,7 @@
1
1
  import { SimpleError } from '@simonbackx/simple-errors';
2
2
  import { AuditLog, BalanceItem, CachedBalance, Document, Event, EventNotification, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
3
3
  import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
4
+ import { Sorter } from '@stamhoofd/utility';
4
5
 
5
6
  import { SQL } from '@stamhoofd/sql';
6
7
  import { Formatter } from '@stamhoofd/utility';
@@ -254,7 +255,7 @@ export class AuthenticatedStructures {
254
255
  ...baseStruct,
255
256
  period,
256
257
  privateMeta: organization.privateMeta,
257
- webshops: webshopPreviews.get(organization.id),
258
+ webshops: webshopPreviews.get(organization.id)?.sort((a, b) => Sorter.byDateValue(b.createdAt, a.createdAt)),
258
259
  });
259
260
  }
260
261
  else {
@@ -35,7 +35,7 @@ export const MembershipCharger = {
35
35
  .where('id', SQLWhereSign.Greater, lastId)
36
36
  .where('balanceItemId', null)
37
37
  .where('deletedAt', null)
38
- .whereNot('organizationId', chargeVia)
38
+ .where('locked', false)
39
39
  .where(SQL.where('trialUntil', null).or('trialUntil', SQLWhereSign.LessEqual, new Date()))
40
40
  .limit(chunkSize)
41
41
  .orderBy(
@@ -66,10 +66,6 @@ export const MembershipCharger = {
66
66
  continue;
67
67
  }
68
68
 
69
- if (membership.organizationId === chargeVia) {
70
- continue;
71
- }
72
-
73
69
  const member = members.find(m => m.id === membership.memberId);
74
70
 
75
71
  if (!member) {
@@ -86,6 +82,15 @@ export const MembershipCharger = {
86
82
  continue;
87
83
  }
88
84
 
85
+ // Lock membership
86
+ membership.locked = true;
87
+
88
+ if (membership.organizationId === chargeVia) {
89
+ // Do not charge
90
+ await membership.save();
91
+ continue;
92
+ }
93
+
89
94
  const balanceItem = new BalanceItem();
90
95
  balanceItem.unitPrice = membership.price;
91
96
  balanceItem.amount = 1;
@@ -175,7 +175,7 @@ export class FileSignService {
175
175
  throw new SimpleError({
176
176
  code: 'invalid_signature',
177
177
  message: 'Invalid signature for file',
178
- human: $t('Je probeert een bestand up te loaden dat niet door ons is gegenereerd. Probeer het opnieuw.'),
178
+ human: $t('479684ab-6c50-4ced-82d7-8245f4f475f4'),
179
179
  });
180
180
  }
181
181
  return;
@@ -1,7 +1,7 @@
1
1
  import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
2
2
  import { Member, MemberPlatformMembership, Organization } from '@stamhoofd/models';
3
3
  import { QueueHandler } from '@stamhoofd/queues';
4
- import { SQL, SQLWhereLike, scalarToSQLExpression } from '@stamhoofd/sql';
4
+ import { scalarToSQLExpression, SQL, SQLCharLength, SQLWhereLike } from '@stamhoofd/sql';
5
5
 
6
6
  export class MemberNumberService {
7
7
  static async assignMemberNumber(member: Member, membership: MemberPlatformMembership) {
@@ -10,9 +10,20 @@ export class MemberNumberService {
10
10
  return;
11
11
  }
12
12
 
13
+ if (!STAMHOOFD.MEMBER_NUMBER_ALGORITHM) {
14
+ console.warn('No member number algorithm set. Please set STAMHOOFD.MEMBER_NUMBER_ALGORITHM in the environment.');
15
+ return;
16
+ }
17
+
13
18
  return await QueueHandler.schedule('assignMemberNumber', async function (this: undefined) {
14
19
  try {
15
20
  const memberNumber = await MemberNumberService.createMemberNumber(member, membership);
21
+
22
+ if (memberNumber === undefined) {
23
+ console.warn('No valid member number algorithm set. Please set a valid STAMHOOFD.MEMBER_NUMBER_ALGORITHM in the environment.');
24
+ return;
25
+ }
26
+
16
27
  member.details.memberNumber = memberNumber;
17
28
  await member.save();
18
29
  }
@@ -32,7 +43,16 @@ export class MemberNumberService {
32
43
  });
33
44
  }
34
45
 
35
- static async createMemberNumber(member: Member, membership: MemberPlatformMembership): Promise<string> {
46
+ private static async createMemberNumber(member: Member, membership: MemberPlatformMembership): Promise<string | undefined> {
47
+ if (STAMHOOFD.MEMBER_NUMBER_ALGORITHM === MemberNumberAlgorithm.Incremental) {
48
+ return this.createIncrementalMemberNumber();
49
+ }
50
+ else if (STAMHOOFD.MEMBER_NUMBER_ALGORITHM === MemberNumberAlgorithm.KSA) {
51
+ return this.createKSAMemberNumber(member, membership);
52
+ }
53
+ }
54
+
55
+ private static async createKSAMemberNumber(member: Member, membership: MemberPlatformMembership): Promise<string> {
36
56
  // example: 5301-101012-1
37
57
 
38
58
  // #region get birth date part (ddmmjj)
@@ -117,4 +137,51 @@ export class MemberNumberService {
117
137
 
118
138
  return memberNumber;
119
139
  }
140
+
141
+ private static largestMemberNumberCache: number | null = null;
142
+
143
+ private static async createIncrementalMemberNumber(): Promise<string> {
144
+ const requiredLength = STAMHOOFD.MEMBER_NUMBER_ALGORITHM_LENGTH; // Required for reliable sorting (sorting strings with different length will cause unexpected results)
145
+
146
+ if (!requiredLength || typeof requiredLength !== 'number') {
147
+ throw new Error('When using the Incremental member number algorithm, you need to set STAMHOOFD.MEMBER_NUMBER_ALGORITHM_LENGTH in the environment.');
148
+ }
149
+
150
+ let nextNumber = 1;
151
+
152
+ if (this.largestMemberNumberCache !== null) {
153
+ nextNumber = this.largestMemberNumberCache + 1;
154
+ }
155
+ else {
156
+ // Find largest member number in the database
157
+ // The required length prevents string-sorting with different lengths, causing an unexpected order
158
+ const query = await SQL.select('memberNumber')
159
+ .from(SQL.table('members'))
160
+ .where(
161
+ new SQLCharLength(SQL.column('memberNumber')),
162
+ requiredLength,
163
+ )
164
+ .limit(1)
165
+ .orderBy('memberNumber', 'DESC')
166
+ .fetch();
167
+
168
+ const largestMemberNumber = query[0]?.['members']?.['memberNumber'];
169
+
170
+ if (largestMemberNumber && typeof largestMemberNumber === 'string') {
171
+ const parsed = parseInt(largestMemberNumber);
172
+ if (!isNaN(parsed)) {
173
+ nextNumber = parsed + 1;
174
+ }
175
+ }
176
+ }
177
+
178
+ this.largestMemberNumberCache = nextNumber;
179
+ const str = nextNumber.toString().padStart(requiredLength, '0');
180
+
181
+ if (str.length !== requiredLength) {
182
+ throw new Error('Reached maximum member number length ' + str);
183
+ }
184
+
185
+ return str;
186
+ }
120
187
  }
@@ -613,6 +613,7 @@ export class SSOServiceWithSession {
613
613
  if (!user.lastName || !user.hasPasswordBasedAccount()) {
614
614
  user.lastName = lastName;
615
615
  }
616
+ user.verified = true;
616
617
  user.linkLoginProvider(this.service.provider, claims.sub, !!this.session.userId);
617
618
  await user.save();
618
619
  }
@@ -257,11 +257,13 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
257
257
  organizationId: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'organizationId')),
258
258
  periodId: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'periodId')),
259
259
  price: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'price')),
260
- invoiceId: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'invoiceId')),
261
260
  startDate: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'startDate')),
262
261
  endDate: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'endDate')),
263
262
  expireDate: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'expireDate')),
264
263
  trialUntil: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'trialUntil')),
264
+ locked: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'locked')),
265
+ balanceItemId: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'balanceItemId')),
266
+ generated: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'generated')),
265
267
  },
266
268
  ),
267
269