@stamhoofd/backend 2.63.0 → 2.65.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 +8 -6
- 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 +2 -0
- package/src/crons/update-cached-balances.ts +39 -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/global/members/PatchOrganizationMembersEndpoint.ts +41 -16
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +2 -0
- package/src/endpoints/global/registration/GetUserPayableBalanceEndpoint.ts +6 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +48 -3
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +15 -1
- 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/payments/PatchBalanceItemsEndpoint.ts +9 -2
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +3 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +23 -2
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +0 -12
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +10 -2
- package/src/helpers/AdminPermissionChecker.ts +2 -1
- package/src/helpers/AuthenticatedStructures.ts +73 -3
- package/src/helpers/EmailResumer.ts +1 -5
- package/src/helpers/MemberUserSyncer.ts +22 -1
- package/src/helpers/MembershipCharger.ts +5 -0
- package/src/helpers/OrganizationCharger.ts +4 -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/{1733996431-update-cached-outstanding-balance-from-items.ts → 1734596144-fill-previous-period-id.ts} +21 -6
- package/src/seeds/{1726494420-update-cached-outstanding-balance-from-items.ts → 1735577912-update-cached-outstanding-balance-from-items.ts} +1 -14
- package/src/services/BalanceItemService.ts +22 -3
- package/src/services/PaymentReallocationService.test.ts +746 -0
- package/src/services/PaymentReallocationService.ts +339 -0
- package/src/services/PaymentService.ts +13 -0
- package/src/services/PlatformMembershipService.ts +167 -137
- package/src/sql-filters/members.ts +1 -0
- package/src/sql-filters/receivable-balances.ts +16 -2
- package/src/sql-filters/shared/EmailRelationFilterCompilers.ts +19 -0
- package/src/sql-sorters/receivable-balances.ts +3 -3
- 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
|
@@ -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
|
}
|
|
@@ -3,6 +3,7 @@ import { BalanceItem, Member, MemberPlatformMembership, Platform } from '@stamho
|
|
|
3
3
|
import { SQL, SQLOrderBy, SQLWhereSign } from '@stamhoofd/sql';
|
|
4
4
|
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemType } from '@stamhoofd/structures';
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
|
+
import { BalanceItemService } from '../services/BalanceItemService';
|
|
6
7
|
|
|
7
8
|
export const MembershipCharger = {
|
|
8
9
|
async charge() {
|
|
@@ -35,6 +36,7 @@ export const MembershipCharger = {
|
|
|
35
36
|
.where('balanceItemId', null)
|
|
36
37
|
.where('deletedAt', null)
|
|
37
38
|
.whereNot('organizationId', chargeVia)
|
|
39
|
+
.where(SQL.where('trialUntil', null).or('trialUntil', SQLWhereSign.LessEqual, new Date()))
|
|
38
40
|
.limit(chunkSize)
|
|
39
41
|
.orderBy(
|
|
40
42
|
new SQLOrderBy({
|
|
@@ -122,6 +124,9 @@ export const MembershipCharger = {
|
|
|
122
124
|
|
|
123
125
|
await BalanceItem.updateOutstanding(createdBalanceItems);
|
|
124
126
|
|
|
127
|
+
// Reallocate
|
|
128
|
+
await BalanceItemService.reallocate(createdBalanceItems, chargeVia);
|
|
129
|
+
|
|
125
130
|
if (memberships.length < chunkSize) {
|
|
126
131
|
break;
|
|
127
132
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { BalanceItem, Platform } from '@stamhoofd/models';
|
|
3
3
|
import { BalanceItemType, Organization as OrganizationStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { BalanceItemService } from '../services/BalanceItemService';
|
|
4
5
|
|
|
5
6
|
export class OrganizationCharger {
|
|
6
7
|
static async chargeFromPlatform(args: { organizationsToCharge: OrganizationStruct[]; price: number; amount?: number; description: string }) {
|
|
@@ -30,6 +31,9 @@ export class OrganizationCharger {
|
|
|
30
31
|
|
|
31
32
|
await Promise.all(balanceItems.map(balanceItem => balanceItem.save()));
|
|
32
33
|
await BalanceItem.updateOutstanding(balanceItems);
|
|
34
|
+
|
|
35
|
+
// Reallocate
|
|
36
|
+
await BalanceItemService.reallocate(balanceItems, chargingOrganizationId);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
private static createBalanceItem({ price, amount, description, chargingOrganizationId, organizationBeingCharged }: { price: number; amount?: number; description: string; chargingOrganizationId: string; organizationBeingCharged: OrganizationStruct }): BalanceItem {
|
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
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Migration } from '@simonbackx/simple-database';
|
|
2
2
|
import { logger } from '@simonbackx/simple-logging';
|
|
3
|
-
import {
|
|
3
|
+
import { Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
4
4
|
|
|
5
5
|
export default new Migration(async () => {
|
|
6
6
|
if (STAMHOOFD.environment == 'test') {
|
|
@@ -12,20 +12,28 @@ export default new Migration(async () => {
|
|
|
12
12
|
let c = 0;
|
|
13
13
|
let id: string = '';
|
|
14
14
|
|
|
15
|
-
await logger.setContext({ tags: ['
|
|
15
|
+
await logger.setContext({ tags: ['seed'] }, async () => {
|
|
16
16
|
while (true) {
|
|
17
|
-
const items = await
|
|
17
|
+
const items = await RegistrationPeriod.where({
|
|
18
18
|
id: {
|
|
19
19
|
value: id,
|
|
20
20
|
sign: '>',
|
|
21
21
|
},
|
|
22
22
|
}, { limit: 1000, sort: ['id'] });
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
if (items.length === 0) {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
25
27
|
|
|
26
|
-
c += items.length;
|
|
27
28
|
process.stdout.write('.');
|
|
28
29
|
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
await item.setPreviousPeriodId();
|
|
32
|
+
if (await item.save()) {
|
|
33
|
+
c += 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
if (items.length < 1000) {
|
|
30
38
|
break;
|
|
31
39
|
}
|
|
@@ -33,7 +41,14 @@ export default new Migration(async () => {
|
|
|
33
41
|
}
|
|
34
42
|
});
|
|
35
43
|
|
|
36
|
-
console.log('Updated
|
|
44
|
+
console.log('Updated ' + c + ' registration periods');
|
|
45
|
+
|
|
46
|
+
// Now update platform
|
|
47
|
+
const platform = await Platform.getShared();
|
|
48
|
+
await platform.setPreviousPeriodId();
|
|
49
|
+
await platform.save();
|
|
50
|
+
|
|
51
|
+
console.log('Updated platform');
|
|
37
52
|
|
|
38
53
|
// Do something here
|
|
39
54
|
return Promise.resolve();
|
|
@@ -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
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BalanceItem, Order, Organization, Payment, Webshop } from '@stamhoofd/models';
|
|
2
|
-
import { AuditLogSource, BalanceItemStatus, OrderStatus } from '@stamhoofd/structures';
|
|
3
|
-
import { RegistrationService } from './RegistrationService';
|
|
2
|
+
import { AuditLogSource, BalanceItemStatus, OrderStatus, ReceivableBalanceType } from '@stamhoofd/structures';
|
|
4
3
|
import { AuditLogService } from './AuditLogService';
|
|
4
|
+
import { RegistrationService } from './RegistrationService';
|
|
5
|
+
import { PaymentReallocationService } from './PaymentReallocationService';
|
|
6
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
5
7
|
|
|
6
8
|
export const BalanceItemService = {
|
|
7
9
|
async markPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
|
|
@@ -45,6 +47,24 @@ export const BalanceItemService = {
|
|
|
45
47
|
}
|
|
46
48
|
},
|
|
47
49
|
|
|
50
|
+
async reallocate(balanceItems: BalanceItem[], organizationId: string) {
|
|
51
|
+
const memberIds = Formatter.uniqueArray(balanceItems.map(b => b.memberId).filter(b => b !== null));
|
|
52
|
+
const payingOrganizationIds = Formatter.uniqueArray(balanceItems.map(b => b.payingOrganizationId).filter(b => b !== null));
|
|
53
|
+
const userIds = Formatter.uniqueArray(balanceItems.map(b => b.userId).filter(b => b !== null));
|
|
54
|
+
|
|
55
|
+
for (const memberId of memberIds) {
|
|
56
|
+
await PaymentReallocationService.reallocate(organizationId, memberId, ReceivableBalanceType.member);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const payingOrganizationId of payingOrganizationIds) {
|
|
60
|
+
await PaymentReallocationService.reallocate(organizationId, payingOrganizationId, ReceivableBalanceType.organization);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const userId of userIds) {
|
|
64
|
+
await PaymentReallocationService.reallocate(organizationId, userId, ReceivableBalanceType.user);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
48
68
|
async markUpdated(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
|
|
49
69
|
// For orders: mark order as changed (so they are refetched in front ends)
|
|
50
70
|
if (balanceItem.orderId) {
|
|
@@ -93,5 +113,4 @@ export const BalanceItemService = {
|
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
115
|
},
|
|
96
|
-
|
|
97
116
|
};
|