@stamhoofd/backend 2.63.0 → 2.64.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/crons/index.ts +1 -0
- package/src/crons/update-cached-balances.ts +39 -0
- 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/payments/PatchBalanceItemsEndpoint.ts +9 -2
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +3 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +22 -1
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +6 -8
- 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/MembershipCharger.ts +4 -0
- package/src/helpers/OrganizationCharger.ts +4 -0
- package/src/seeds/1734596144-fill-previous-period-id.ts +55 -0
- 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/receivable-balances.ts +1 -1
- package/src/sql-sorters/receivable-balances.ts +3 -3
- /package/src/seeds/{1733996431-update-cached-outstanding-balance-from-items.ts → 1734700082-update-cached-outstanding-balance-from-items.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
-
import { AuditLog, BalanceItem, CachedBalance, Document, Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
|
|
3
|
-
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, Document as DocumentStruct, Event as EventStruct, 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';
|
|
2
|
+
import { AuditLog, BalanceItem, CachedBalance, Document, Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
|
|
3
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, Document as DocumentStruct, 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
4
|
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { Context } from './Context';
|
|
@@ -323,6 +323,9 @@ export class AuthenticatedStructures {
|
|
|
323
323
|
}
|
|
324
324
|
const organizations = new Map<string, Organization>();
|
|
325
325
|
|
|
326
|
+
const registrationIds = Formatter.uniqueArray(members.flatMap(m => m.registrations.map(r => r.id)));
|
|
327
|
+
const balances = await CachedBalance.getForObjects(registrationIds, Context.organization?.id ?? null);
|
|
328
|
+
|
|
326
329
|
if (includeUser) {
|
|
327
330
|
for (const organizationId of includeUser.permissions?.organizationPermissions.keys() ?? []) {
|
|
328
331
|
if (includeContextOrganization || organizationId !== Context.auth.organization?.id) {
|
|
@@ -365,7 +368,25 @@ export class AuthenticatedStructures {
|
|
|
365
368
|
}
|
|
366
369
|
}
|
|
367
370
|
member.registrations = member.registrations.filter(r => (Context.auth.organization && Context.auth.organization.active && r.organizationId === Context.auth.organization.id) || (organizations.get(r.organizationId)?.active ?? false));
|
|
368
|
-
const
|
|
371
|
+
const balancesPermission = await Context.auth.hasFinancialMemberAccess(member, PermissionLevel.Read);
|
|
372
|
+
|
|
373
|
+
const blob = MemberWithRegistrationsBlob.create({
|
|
374
|
+
...member,
|
|
375
|
+
registrations: member.registrations.map((r) => {
|
|
376
|
+
const base = r.getStructure();
|
|
377
|
+
|
|
378
|
+
base.balances = balancesPermission
|
|
379
|
+
? (balances.filter(b => r.id === b.objectId).map((b) => {
|
|
380
|
+
return GenericBalance.create(b);
|
|
381
|
+
}))
|
|
382
|
+
: [];
|
|
383
|
+
|
|
384
|
+
return base;
|
|
385
|
+
}),
|
|
386
|
+
details: member.details,
|
|
387
|
+
users: member.users.map(u => u.getStructure()),
|
|
388
|
+
});
|
|
389
|
+
|
|
369
390
|
memberBlobs.push(
|
|
370
391
|
await Context.auth.filterMemberData(member, blob),
|
|
371
392
|
);
|
|
@@ -568,9 +589,15 @@ export class AuthenticatedStructures {
|
|
|
568
589
|
|
|
569
590
|
const organizationStructs = await this.organizations(organizations);
|
|
570
591
|
|
|
592
|
+
const registrationIds = Formatter.uniqueArray([
|
|
593
|
+
...balances.filter(b => b.objectType === ReceivableBalanceType.registration).map(b => b.objectId),
|
|
594
|
+
]);
|
|
595
|
+
const registrations = await Registration.getByIDs(...registrationIds);
|
|
596
|
+
|
|
571
597
|
const memberIds = Formatter.uniqueArray([
|
|
572
598
|
...balances.filter(b => b.objectType === ReceivableBalanceType.member).map(b => b.objectId),
|
|
573
599
|
...responsibilities.map(r => r.memberId),
|
|
600
|
+
...registrations.map(r => r.memberId),
|
|
574
601
|
]);
|
|
575
602
|
const members = memberIds.length > 0 ? await Member.getByIDs(...memberIds) : [];
|
|
576
603
|
|
|
@@ -655,6 +682,49 @@ export class AuthenticatedStructures {
|
|
|
655
682
|
});
|
|
656
683
|
}
|
|
657
684
|
}
|
|
685
|
+
else if (balance.objectType === ReceivableBalanceType.registration) {
|
|
686
|
+
const registration = registrations.find(r => r.id === balance.objectId) ?? null;
|
|
687
|
+
if (!registration) {
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
const member = members.find(m => m.id === registration.memberId) ?? null;
|
|
691
|
+
if (member) {
|
|
692
|
+
const url = Context.organization && Context.organization.id === balance.organizationId ? 'https://' + Context.organization.getHost() : '';
|
|
693
|
+
object = ReceivableBalanceObject.create({
|
|
694
|
+
id: balance.objectId,
|
|
695
|
+
name: member.details.name,
|
|
696
|
+
contacts: [
|
|
697
|
+
...(member.details.getMemberEmails().length
|
|
698
|
+
? [
|
|
699
|
+
ReceivableBalanceObjectContact.create({
|
|
700
|
+
firstName: member.details.firstName ?? '',
|
|
701
|
+
lastName: member.details.lastName ?? '',
|
|
702
|
+
emails: member.details.getMemberEmails(),
|
|
703
|
+
meta: {
|
|
704
|
+
type: 'member',
|
|
705
|
+
responsibilityIds: [],
|
|
706
|
+
url,
|
|
707
|
+
},
|
|
708
|
+
}),
|
|
709
|
+
]
|
|
710
|
+
: []),
|
|
711
|
+
|
|
712
|
+
...(member.details.parentsHaveAccess
|
|
713
|
+
? member.details.parents.filter(p => !!p.email).map(p => ReceivableBalanceObjectContact.create({
|
|
714
|
+
firstName: p.firstName ?? '',
|
|
715
|
+
lastName: p.lastName ?? '',
|
|
716
|
+
emails: [p.email!],
|
|
717
|
+
meta: {
|
|
718
|
+
type: 'parent',
|
|
719
|
+
responsibilityIds: [],
|
|
720
|
+
url,
|
|
721
|
+
},
|
|
722
|
+
}))
|
|
723
|
+
: []),
|
|
724
|
+
],
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
658
728
|
else if (balance.objectType === ReceivableBalanceType.user) {
|
|
659
729
|
const user = users.find(m => m.id === balance.objectId) ?? null;
|
|
660
730
|
if (user) {
|
|
@@ -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() {
|
|
@@ -122,6 +123,9 @@ export const MembershipCharger = {
|
|
|
122
123
|
|
|
123
124
|
await BalanceItem.updateOutstanding(createdBalanceItems);
|
|
124
125
|
|
|
126
|
+
// Reallocate
|
|
127
|
+
await BalanceItemService.reallocate(createdBalanceItems, chargeVia);
|
|
128
|
+
|
|
125
129
|
if (memberships.length < chunkSize) {
|
|
126
130
|
break;
|
|
127
131
|
}
|
|
@@ -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 {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { logger } from '@simonbackx/simple-logging';
|
|
3
|
+
import { Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
4
|
+
|
|
5
|
+
export default new Migration(async () => {
|
|
6
|
+
if (STAMHOOFD.environment == 'test') {
|
|
7
|
+
console.log('skipped in tests');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
process.stdout.write('\n');
|
|
12
|
+
let c = 0;
|
|
13
|
+
let id: string = '';
|
|
14
|
+
|
|
15
|
+
await logger.setContext({ tags: ['seed'] }, async () => {
|
|
16
|
+
while (true) {
|
|
17
|
+
const items = await RegistrationPeriod.where({
|
|
18
|
+
id: {
|
|
19
|
+
value: id,
|
|
20
|
+
sign: '>',
|
|
21
|
+
},
|
|
22
|
+
}, { limit: 1000, sort: ['id'] });
|
|
23
|
+
|
|
24
|
+
if (items.length === 0) {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.stdout.write('.');
|
|
29
|
+
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
await item.setPreviousPeriodId();
|
|
32
|
+
if (await item.save()) {
|
|
33
|
+
c += 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (items.length < 1000) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
id = items[items.length - 1].id;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
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');
|
|
52
|
+
|
|
53
|
+
// Do something here
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
});
|
|
@@ -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
|
};
|