@stamhoofd/backend 2.64.0 → 2.65.1
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 +10 -10
- package/src/audit-logs/EmailLogger.ts +7 -1
- package/src/audit-logs/ModelLogger.ts +17 -2
- package/src/crons/balance-emails.ts +232 -0
- package/src/crons/index.ts +1 -0
- package/src/email-recipient-loaders/members.ts +14 -4
- package/src/email-recipient-loaders/receivable-balances.ts +29 -15
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +47 -12
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -18
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +15 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +0 -10
- package/src/helpers/EmailResumer.ts +1 -5
- package/src/helpers/MemberUserSyncer.ts +22 -1
- package/src/helpers/MembershipCharger.ts +1 -0
- package/src/helpers/TagHelper.ts +7 -14
- package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +4 -14
- package/src/seeds/1729253172-update-orders.ts +7 -18
- package/src/seeds/{1726494420-update-cached-outstanding-balance-from-items.ts → 1735577912-update-cached-outstanding-balance-from-items.ts} +1 -14
- package/src/seeds/1736266448-recall-balance-item-price-paid.ts +70 -0
- package/src/services/BalanceItemPaymentService.ts +14 -2
- package/src/services/BalanceItemService.ts +41 -1
- package/src/services/PlatformMembershipService.ts +5 -5
- package/src/sql-filters/members.ts +1 -0
- package/src/sql-filters/receivable-balances.ts +15 -1
- package/src/sql-filters/shared/EmailRelationFilterCompilers.ts +19 -0
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +0 -253
- package/src/helpers/ModelHelper.ts +0 -32
- package/src/seeds/1726055544-balance-item-paid.ts +0 -11
- package/src/seeds/1726055545-balance-item-pending.ts +0 -11
- package/src/seeds/1726494419-update-cached-outstanding-balance.ts +0 -53
- package/src/seeds/1728928973-balance-item-pending.ts +0 -11
- package/src/seeds/1734700082-update-cached-outstanding-balance-from-items.ts +0 -40
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Member, MemberResponsibilityRecord, MemberWithRegistrations, User } from '@stamhoofd/models';
|
|
1
|
+
import { CachedBalance, Member, MemberResponsibilityRecord, MemberWithRegistrations, User } from '@stamhoofd/models';
|
|
2
2
|
import { SQL } from '@stamhoofd/sql';
|
|
3
3
|
import { AuditLogSource, MemberDetails, Permissions, UserPermissions } from '@stamhoofd/structures';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import basex from 'base-x';
|
|
6
6
|
import { AuditLogService } from '../services/AuditLogService';
|
|
7
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
7
8
|
|
|
8
9
|
const ALPHABET = '123456789ABCDEFGHJKMNPQRSTUVWXYZ'; // Note: we removed 0, O, I and l to make it easier for humans
|
|
9
10
|
const customBase = basex(ALPHABET);
|
|
@@ -185,6 +186,9 @@ export class MemberUserSyncerStatic {
|
|
|
185
186
|
console.log('Removing access for ' + user.id + ' to member ' + member.id);
|
|
186
187
|
await Member.users.reverse('members').unlink(user, member);
|
|
187
188
|
|
|
189
|
+
// Update balance of this user, as it could have changed
|
|
190
|
+
await this.updateUserBalance(user.id, member.id);
|
|
191
|
+
|
|
188
192
|
if (user.memberId === member.id) {
|
|
189
193
|
user.memberId = null;
|
|
190
194
|
}
|
|
@@ -305,6 +309,23 @@ export class MemberUserSyncerStatic {
|
|
|
305
309
|
if (!member.users.find(u => u.id === user.id)) {
|
|
306
310
|
await Member.users.reverse('members').link(user, [member]);
|
|
307
311
|
member.users.push(user);
|
|
312
|
+
|
|
313
|
+
// Update balance of this user, as it could have changed
|
|
314
|
+
await this.updateUserBalance(user.id, member.id);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Update the balance after making a change in linked member/users
|
|
320
|
+
*/
|
|
321
|
+
async updateUserBalance(userId: string, memberId: string) {
|
|
322
|
+
// Update balance of this user, as it could have changed
|
|
323
|
+
const memberBalances = await CachedBalance.getForObjects([memberId]);
|
|
324
|
+
if (memberBalances.length > 0) {
|
|
325
|
+
const organizationIds = Formatter.uniqueArray(memberBalances.map(b => b.organizationId));
|
|
326
|
+
for (const organizationId of organizationIds) {
|
|
327
|
+
await CachedBalance.updateForUsers(organizationId, [userId]);
|
|
328
|
+
}
|
|
308
329
|
}
|
|
309
330
|
}
|
|
310
331
|
}
|
|
@@ -36,6 +36,7 @@ export const MembershipCharger = {
|
|
|
36
36
|
.where('balanceItemId', null)
|
|
37
37
|
.where('deletedAt', null)
|
|
38
38
|
.whereNot('organizationId', chargeVia)
|
|
39
|
+
.where(SQL.where('trialUntil', null).or('trialUntil', SQLWhereSign.LessEqual, new Date()))
|
|
39
40
|
.limit(chunkSize)
|
|
40
41
|
.orderBy(
|
|
41
42
|
new SQLOrderBy({
|
package/src/helpers/TagHelper.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Organization, Platform } from '@stamhoofd/models';
|
|
2
2
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
3
3
|
import { AuditLogSource, OrganizationTag, TagHelper as SharedTagHelper } from '@stamhoofd/structures';
|
|
4
|
-
import { ModelHelper } from './ModelHelper';
|
|
5
4
|
import { AuditLogService } from '../services/AuditLogService';
|
|
6
5
|
|
|
7
6
|
export class TagHelper extends SharedTagHelper {
|
|
@@ -14,17 +13,15 @@ export class TagHelper extends SharedTagHelper {
|
|
|
14
13
|
let platform = await Platform.getShared();
|
|
15
14
|
|
|
16
15
|
const tagCounts = new Map<string, number>();
|
|
17
|
-
await this.loopOrganizations(async (organizations) => {
|
|
18
|
-
for (const organization of organizations) {
|
|
19
|
-
organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, platform.config.tags);
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
}
|
|
17
|
+
for await (const organization of Organization.select().all()) {
|
|
18
|
+
organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, platform.config.tags);
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
for (const tag of organization.meta.tags) {
|
|
21
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
22
|
+
}
|
|
23
|
+
await organization.save();
|
|
24
|
+
}
|
|
28
25
|
|
|
29
26
|
// Reload platform to avoid race conditions
|
|
30
27
|
platform = await Platform.getShared();
|
|
@@ -39,10 +36,6 @@ export class TagHelper extends SharedTagHelper {
|
|
|
39
36
|
});
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
private static async loopOrganizations(onBatchReceived: (batch: Organization[]) => Promise<void>) {
|
|
43
|
-
await ModelHelper.loop(Organization, 'id', onBatchReceived, { limit: 10 });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
39
|
/**
|
|
47
40
|
* Removes child tag ids that do not exist and sorts the tags.
|
|
48
41
|
* @param platformTags
|
|
@@ -2,6 +2,9 @@ import { Migration } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { logger } from '@simonbackx/simple-logging';
|
|
3
3
|
import { BalanceItem } from '@stamhoofd/models';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* This migration is required to keep '1733994455-balance-item-status-open' working
|
|
7
|
+
*/
|
|
5
8
|
export default new Migration(async () => {
|
|
6
9
|
if (STAMHOOFD.environment == 'test') {
|
|
7
10
|
console.log('skipped in tests');
|
|
@@ -10,26 +13,13 @@ export default new Migration(async () => {
|
|
|
10
13
|
|
|
11
14
|
process.stdout.write('\n');
|
|
12
15
|
let c = 0;
|
|
13
|
-
let id: string = '';
|
|
14
16
|
|
|
15
17
|
await logger.setContext({ tags: ['silent-seed', 'seed'] }, async () => {
|
|
16
|
-
|
|
17
|
-
const items = await BalanceItem.where({
|
|
18
|
-
id: {
|
|
19
|
-
value: id,
|
|
20
|
-
sign: '>',
|
|
21
|
-
},
|
|
22
|
-
}, { limit: 1000, sort: ['id'] });
|
|
23
|
-
|
|
18
|
+
for await (const items of BalanceItem.select().limit(1000).allBatched()) {
|
|
24
19
|
await BalanceItem.updateOutstanding(items);
|
|
25
20
|
|
|
26
21
|
c += items.length;
|
|
27
22
|
process.stdout.write('.');
|
|
28
|
-
|
|
29
|
-
if (items.length < 1000) {
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
id = items[items.length - 1].id;
|
|
33
23
|
}
|
|
34
24
|
});
|
|
35
25
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Migration } from '@simonbackx/simple-database';
|
|
2
2
|
import { Order } from '@stamhoofd/models';
|
|
3
|
-
import { sleep } from '@stamhoofd/utility';
|
|
4
|
-
import { ModelHelper } from '../helpers/ModelHelper';
|
|
5
3
|
|
|
6
4
|
export default new Migration(async () => {
|
|
7
5
|
if (STAMHOOFD.environment === 'test') {
|
|
@@ -11,22 +9,13 @@ export default new Migration(async () => {
|
|
|
11
9
|
|
|
12
10
|
console.log('Start saving orders.');
|
|
13
11
|
|
|
14
|
-
const
|
|
15
|
-
let count =
|
|
12
|
+
const batchSize = 100;
|
|
13
|
+
let count = 0;
|
|
16
14
|
|
|
17
|
-
await
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
for (const order of batch) {
|
|
22
|
-
await order.save();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
count += limit;
|
|
26
|
-
},
|
|
27
|
-
{ limit });
|
|
28
|
-
|
|
29
|
-
await sleep(1000);
|
|
15
|
+
for await (const order of Order.select().limit(batchSize).all()) {
|
|
16
|
+
await order.save();
|
|
17
|
+
count += 1;
|
|
18
|
+
}
|
|
30
19
|
|
|
31
|
-
console.log('Finished saving orders.');
|
|
20
|
+
console.log('Finished saving ' + count + ' orders.');
|
|
32
21
|
});
|
|
@@ -10,26 +10,13 @@ export default new Migration(async () => {
|
|
|
10
10
|
|
|
11
11
|
process.stdout.write('\n');
|
|
12
12
|
let c = 0;
|
|
13
|
-
let id: string = '';
|
|
14
13
|
|
|
15
14
|
await logger.setContext({ tags: ['silent-seed', 'seed'] }, async () => {
|
|
16
|
-
|
|
17
|
-
const items = await BalanceItem.where({
|
|
18
|
-
id: {
|
|
19
|
-
value: id,
|
|
20
|
-
sign: '>',
|
|
21
|
-
},
|
|
22
|
-
}, { limit: 1000, sort: ['id'] });
|
|
23
|
-
|
|
15
|
+
for await (const items of BalanceItem.select().limit(1000).allBatched()) {
|
|
24
16
|
await BalanceItem.updateOutstanding(items);
|
|
25
17
|
|
|
26
18
|
c += items.length;
|
|
27
19
|
process.stdout.write('.');
|
|
28
|
-
|
|
29
|
-
if (items.length < 1000) {
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
id = items[items.length - 1].id;
|
|
33
20
|
}
|
|
34
21
|
});
|
|
35
22
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { logger } from '@simonbackx/simple-logging';
|
|
3
|
+
import { BalanceItem, BalanceItemPayment, Organization, Payment } from '@stamhoofd/models';
|
|
4
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
5
|
+
import { AuditLogSource, PaymentStatus } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogService } from '../services/AuditLogService';
|
|
7
|
+
import { BalanceItemPaymentService } from '../services/BalanceItemPaymentService';
|
|
8
|
+
|
|
9
|
+
export default new Migration(async () => {
|
|
10
|
+
if (STAMHOOFD.environment == 'test') {
|
|
11
|
+
console.log('skipped in tests');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
process.stdout.write('\n');
|
|
16
|
+
let c = 0;
|
|
17
|
+
|
|
18
|
+
await logger.setContext({ tags: ['silent-seed', 'seed'] }, async () => {
|
|
19
|
+
const q = Payment.select()
|
|
20
|
+
.where('status', PaymentStatus.Succeeded)
|
|
21
|
+
.where('createdAt', '>=', new Date('2024-12-12'))
|
|
22
|
+
.limit(100);
|
|
23
|
+
for await (const payment of q.all()) {
|
|
24
|
+
await fix(payment);
|
|
25
|
+
|
|
26
|
+
c += 1;
|
|
27
|
+
|
|
28
|
+
if (c % 1000 === 0) {
|
|
29
|
+
process.stdout.write('.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log('Updated ' + c + ' payments');
|
|
35
|
+
|
|
36
|
+
// Do something here
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
async function fix(payment: Payment) {
|
|
41
|
+
if (payment.status !== PaymentStatus.Succeeded) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!payment.organizationId) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const organization = await Organization.getByID(payment.organizationId);
|
|
50
|
+
|
|
51
|
+
if (!organization) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await AuditLogService.setContext({ fallbackUserId: payment.payingUserId, source: AuditLogSource.Payment, fallbackOrganizationId: payment.organizationId }, async () => {
|
|
56
|
+
// Prevent concurrency issues
|
|
57
|
+
await QueueHandler.schedule('balance-item-update/' + organization.id, async () => {
|
|
58
|
+
const unloaded = (await BalanceItemPayment.where({ paymentId: payment.id })).map(r => r.setRelation(BalanceItemPayment.payment, payment));
|
|
59
|
+
const balanceItemPayments = await BalanceItemPayment.balanceItem.load(
|
|
60
|
+
unloaded,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
for (const balanceItemPayment of balanceItemPayments) {
|
|
64
|
+
await BalanceItemPaymentService.markPaidRepeated(balanceItemPayment, organization);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await BalanceItem.updateOutstanding(balanceItemPayments.map(p => p.balanceItem));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -7,7 +7,7 @@ type Loaded<T> = (T) extends ManyToOneRelation<infer Key, infer Model> ? Record<
|
|
|
7
7
|
|
|
8
8
|
export const BalanceItemPaymentService = {
|
|
9
9
|
async markPaid(balanceItemPayment: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
10
|
-
const wasPaid = balanceItemPayment.balanceItem.
|
|
10
|
+
const wasPaid = balanceItemPayment.balanceItem.isPaid;
|
|
11
11
|
|
|
12
12
|
// Update cached amountPaid of the balance item (balanceItemPayment will get overwritten later, but we need it to calculate the status)
|
|
13
13
|
balanceItemPayment.balanceItem.pricePaid += balanceItemPayment.price;
|
|
@@ -17,7 +17,7 @@ export const BalanceItemPaymentService = {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
await balanceItemPayment.balanceItem.save();
|
|
20
|
-
const isPaid = balanceItemPayment.balanceItem.
|
|
20
|
+
const isPaid = balanceItemPayment.balanceItem.isPaid;
|
|
21
21
|
|
|
22
22
|
// Do logic of balance item
|
|
23
23
|
if (isPaid && !wasPaid && balanceItemPayment.price >= 0 && balanceItemPayment.balanceItem.status === BalanceItemStatus.Due) {
|
|
@@ -29,6 +29,18 @@ export const BalanceItemPaymentService = {
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Safe method to correct balance items that missed a markPaid call, but avoid double marking an order as valid.
|
|
34
|
+
*/
|
|
35
|
+
async markPaidRepeated(balanceItemPayment: BalanceItemPayment & Loaded<typeof BalanceItemPayment.balanceItem> & Loaded<typeof BalanceItemPayment.payment>, organization: Organization) {
|
|
36
|
+
const isPaid = balanceItemPayment.balanceItem.isPaid;
|
|
37
|
+
|
|
38
|
+
// Do logic of balance item
|
|
39
|
+
if (isPaid && balanceItemPayment.price >= 0 && balanceItemPayment.balanceItem.status === BalanceItemStatus.Due) {
|
|
40
|
+
await BalanceItemService.markPaidRepeated(balanceItemPayment.balanceItem, balanceItemPayment.payment, organization);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
32
44
|
/**
|
|
33
45
|
* Call balanceItemPayment once a earlier succeeded payment is no longer succeeded
|
|
34
46
|
*/
|
|
@@ -6,7 +6,43 @@ import { PaymentReallocationService } from './PaymentReallocationService';
|
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
export const BalanceItemService = {
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Safe method to correct balance items that missed a markPaid call, but avoid double marking an order as valid.
|
|
11
|
+
*/
|
|
12
|
+
async markPaidRepeated(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
|
|
13
|
+
if (balanceItem.pricePaid <= 0) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await this.markDue(balanceItem);
|
|
18
|
+
|
|
19
|
+
// Registrations are safe to mark valid multiple times
|
|
20
|
+
if (balanceItem.registrationId) {
|
|
21
|
+
await RegistrationService.markValid(balanceItem.registrationId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Orders aren't safe to mark paid twice - so only mark paid if not yet valid
|
|
25
|
+
// The only downside of this is that we won't send a paid email for transfer orders
|
|
26
|
+
// we should fix that in the future by introducing a paidAt timestamp for orders
|
|
27
|
+
if (balanceItem.orderId) {
|
|
28
|
+
const order = await Order.getByID(balanceItem.orderId);
|
|
29
|
+
if (order && !order.validAt) {
|
|
30
|
+
await order.markPaid(payment, organization);
|
|
31
|
+
|
|
32
|
+
// Save number in balance description
|
|
33
|
+
if (order.number !== null) {
|
|
34
|
+
const webshop = await Webshop.getByID(order.webshopId);
|
|
35
|
+
|
|
36
|
+
if (webshop) {
|
|
37
|
+
balanceItem.description = order.generateBalanceDescription(webshop);
|
|
38
|
+
await balanceItem.save();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async markDue(balanceItem: BalanceItem) {
|
|
10
46
|
if (balanceItem.status === BalanceItemStatus.Hidden) {
|
|
11
47
|
await BalanceItem.reactivateItems([balanceItem]);
|
|
12
48
|
}
|
|
@@ -18,6 +54,10 @@ export const BalanceItemService = {
|
|
|
18
54
|
await BalanceItem.reactivateItems([depending]);
|
|
19
55
|
}
|
|
20
56
|
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async markPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
|
|
60
|
+
await this.markDue(balanceItem);
|
|
21
61
|
|
|
22
62
|
// It is possible this balance item was earlier paid
|
|
23
63
|
// and later the regigstration / order has been canceled and it became a negative balance item - which as some point has been reembursed and marked as 'paid'
|
|
@@ -225,12 +225,12 @@ export class PlatformMembershipService {
|
|
|
225
225
|
|
|
226
226
|
const shouldApplyReducedPrice = me.details.shouldApplyReducedPrice;
|
|
227
227
|
|
|
228
|
-
const cheapestMembership = defaultMembershipsWithOrganization.sort((
|
|
229
|
-
const tagIdsA =
|
|
230
|
-
const tagIdsB =
|
|
231
|
-
const diff = a.getPrice(period.id,
|
|
228
|
+
const cheapestMembership = defaultMembershipsWithOrganization.sort((a, b) => {
|
|
229
|
+
const tagIdsA = a.organization?.meta.tags ?? [];
|
|
230
|
+
const tagIdsB = b.organization?.meta.tags ?? [];
|
|
231
|
+
const diff = a.membership.getPrice(period.id, a.registration.startDate ?? a.registration.registeredAt ?? now, tagIdsA, shouldApplyReducedPrice)! - b.membership.getPrice(period.id, a.registration.startDate ?? a.registration.registeredAt ?? now, tagIdsB, shouldApplyReducedPrice)!;
|
|
232
232
|
if (diff === 0) {
|
|
233
|
-
return Sorter.byDateValue(
|
|
233
|
+
return Sorter.byDateValue(b.registration.startDate ?? b.registration.createdAt, a.registration.startDate ?? a.registration.createdAt);
|
|
234
234
|
}
|
|
235
235
|
return diff;
|
|
236
236
|
})[0];
|
|
@@ -242,6 +242,7 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
242
242
|
startDate: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'startDate')),
|
|
243
243
|
endDate: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'endDate')),
|
|
244
244
|
expireDate: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'expireDate')),
|
|
245
|
+
trialUntil: createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'trialUntil')),
|
|
245
246
|
},
|
|
246
247
|
),
|
|
247
248
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler } from '@stamhoofd/sql';
|
|
1
|
+
import { SQL, SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler } from '@stamhoofd/sql';
|
|
2
|
+
import { EmailRelationFilterCompilers } from './shared/EmailRelationFilterCompilers';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Defines how to filter cached balance items in the database from StamhoofdFilter objects
|
|
@@ -11,4 +12,17 @@ export const receivableBalanceFilterCompilers: SQLFilterDefinitions = {
|
|
|
11
12
|
amountOpen: createSQLColumnFilterCompiler('amountOpen'),
|
|
12
13
|
amountPending: createSQLColumnFilterCompiler('amountPending'),
|
|
13
14
|
nextDueAt: createSQLColumnFilterCompiler('nextDueAt'),
|
|
15
|
+
lastReminderEmail: createSQLColumnFilterCompiler('lastReminderEmail'),
|
|
16
|
+
reminderEmailCount: createSQLColumnFilterCompiler('reminderEmailCount'),
|
|
17
|
+
reminderAmountIncreased: createSQLExpressionFilterCompiler(
|
|
18
|
+
SQL.if(
|
|
19
|
+
SQL.column('amountOpen'),
|
|
20
|
+
'>',
|
|
21
|
+
SQL.column('lastReminderAmountOpen'),
|
|
22
|
+
).then(1).else(0),
|
|
23
|
+
{ isJSONValue: false, isJSONObject: false },
|
|
24
|
+
),
|
|
25
|
+
|
|
26
|
+
// Allowed to filter by recent emails
|
|
27
|
+
...EmailRelationFilterCompilers,
|
|
14
28
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createSQLRelationFilterCompiler, SQL, SQLParentNamespace, baseSQLFilterCompilers, createSQLColumnFilterCompiler } from '@stamhoofd/sql';
|
|
2
|
+
|
|
3
|
+
export const EmailRelationFilterCompilers = {
|
|
4
|
+
emails: createSQLRelationFilterCompiler(
|
|
5
|
+
SQL.select()
|
|
6
|
+
.from(
|
|
7
|
+
SQL.table('email_recipients'),
|
|
8
|
+
)
|
|
9
|
+
.where(
|
|
10
|
+
SQL.column(SQLParentNamespace, 'id'),
|
|
11
|
+
SQL.column('objectId'),
|
|
12
|
+
),
|
|
13
|
+
{
|
|
14
|
+
...baseSQLFilterCompilers,
|
|
15
|
+
emailType: createSQLColumnFilterCompiler('emailType'),
|
|
16
|
+
sentAt: createSQLColumnFilterCompiler('sentAt'),
|
|
17
|
+
},
|
|
18
|
+
),
|
|
19
|
+
};
|