@stamhoofd/backend 2.54.1 → 2.55.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 +1 -0
- package/package.json +10 -10
- package/src/email-recipient-loaders/receivable-balances.ts +59 -0
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +8 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +12 -10
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +14 -0
- package/src/helpers/AuthenticatedStructures.ts +31 -7
package/index.ts
CHANGED
|
@@ -91,6 +91,7 @@ const start = async () => {
|
|
|
91
91
|
// Register Email Recipient loaders
|
|
92
92
|
await import('./src/email-recipient-loaders/members');
|
|
93
93
|
await import('./src/email-recipient-loaders/orders');
|
|
94
|
+
await import('./src/email-recipient-loaders/receivable-balances');
|
|
94
95
|
|
|
95
96
|
routerServer.listen(STAMHOOFD.PORT ?? 9090);
|
|
96
97
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.55.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"@simonbackx/simple-encoding": "2.16.6",
|
|
37
37
|
"@simonbackx/simple-endpoints": "1.14.0",
|
|
38
38
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
39
|
-
"@stamhoofd/backend-i18n": "2.
|
|
40
|
-
"@stamhoofd/backend-middleware": "2.
|
|
41
|
-
"@stamhoofd/email": "2.
|
|
42
|
-
"@stamhoofd/models": "2.
|
|
43
|
-
"@stamhoofd/queues": "2.
|
|
44
|
-
"@stamhoofd/sql": "2.
|
|
45
|
-
"@stamhoofd/structures": "2.
|
|
46
|
-
"@stamhoofd/utility": "2.
|
|
39
|
+
"@stamhoofd/backend-i18n": "2.55.0",
|
|
40
|
+
"@stamhoofd/backend-middleware": "2.55.0",
|
|
41
|
+
"@stamhoofd/email": "2.55.0",
|
|
42
|
+
"@stamhoofd/models": "2.55.0",
|
|
43
|
+
"@stamhoofd/queues": "2.55.0",
|
|
44
|
+
"@stamhoofd/sql": "2.55.0",
|
|
45
|
+
"@stamhoofd/structures": "2.55.0",
|
|
46
|
+
"@stamhoofd/utility": "2.55.0",
|
|
47
47
|
"archiver": "^7.0.1",
|
|
48
48
|
"aws-sdk": "^2.885.0",
|
|
49
49
|
"axios": "1.6.8",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "0f1de4c457eb1d067a6df5621d8bb06e821f09e3"
|
|
67
67
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Email } from '@stamhoofd/models';
|
|
2
|
+
import { receivableBalanceObjectContactInMemoryFilterCompilers, compileToInMemoryFilter, EmailRecipient, EmailRecipientFilterType, LimitedFilteredRequest, PaginatedResponse, Replacement, StamhoofdFilter } from '@stamhoofd/structures';
|
|
3
|
+
import { GetReceivableBalancesEndpoint } from '../endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint';
|
|
4
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
5
|
+
|
|
6
|
+
async function fetch(query: LimitedFilteredRequest, subfilter: StamhoofdFilter | null) {
|
|
7
|
+
const result = await GetReceivableBalancesEndpoint.buildData(query);
|
|
8
|
+
|
|
9
|
+
// Map all contacts to recipients
|
|
10
|
+
const compiledFilter = compileToInMemoryFilter(subfilter, receivableBalanceObjectContactInMemoryFilterCompilers);
|
|
11
|
+
|
|
12
|
+
return new PaginatedResponse({
|
|
13
|
+
results: result.results.flatMap((balance) => {
|
|
14
|
+
return balance.object.contacts.filter(c => compiledFilter(c)).flatMap((contact) => {
|
|
15
|
+
return contact.emails.map(email => EmailRecipient.create({
|
|
16
|
+
firstName: contact.firstName,
|
|
17
|
+
lastName: contact.lastName,
|
|
18
|
+
email,
|
|
19
|
+
replacements: [
|
|
20
|
+
Replacement.create({
|
|
21
|
+
token: 'organizationName',
|
|
22
|
+
value: balance.object.name,
|
|
23
|
+
}),
|
|
24
|
+
Replacement.create({
|
|
25
|
+
token: 'outstandingBalance',
|
|
26
|
+
value: Formatter.price(balance.amountOpen),
|
|
27
|
+
}),
|
|
28
|
+
...(contact.meta && contact.meta.url && typeof contact.meta.url === 'string'
|
|
29
|
+
? [Replacement.create({
|
|
30
|
+
token: 'paymentUrl',
|
|
31
|
+
value: contact.meta.url,
|
|
32
|
+
})]
|
|
33
|
+
: []),
|
|
34
|
+
],
|
|
35
|
+
}));
|
|
36
|
+
});
|
|
37
|
+
}),
|
|
38
|
+
next: result.next,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Email.recipientLoaders.set(EmailRecipientFilterType.ReceivableBalances, {
|
|
43
|
+
fetch,
|
|
44
|
+
|
|
45
|
+
// For now: only count the number of organizations - not the amount of emails
|
|
46
|
+
count: async (query: LimitedFilteredRequest, subfilter: StamhoofdFilter | null) => {
|
|
47
|
+
const q = await GetReceivableBalancesEndpoint.buildQuery(query);
|
|
48
|
+
const base = await q.count();
|
|
49
|
+
|
|
50
|
+
if (base < 1000) {
|
|
51
|
+
// Do full scan
|
|
52
|
+
query.limit = 1000;
|
|
53
|
+
const result = await fetch(query, subfilter);
|
|
54
|
+
return result.results.length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return base;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -51,6 +51,14 @@ export class PatchOrganizationsEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
51
51
|
throw new SimpleError({ code: 'not_found', message: 'Organization not found', statusCode: 404 });
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
if (organization.id === (await Platform.getShared()).membershipOrganizationId) {
|
|
55
|
+
throw new SimpleError({
|
|
56
|
+
code: 'cannot_delete_membership_organization',
|
|
57
|
+
message: 'Cannot delete membership organization',
|
|
58
|
+
human: 'Je kan de hoofdgroep niet verwijderen. Als je dit toch wil doen, kan je eerst een andere vereniging instellen als hoofdgroep via \'Boekhouding en aansluitingen\'.',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
await organization.delete();
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -446,16 +446,18 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
446
446
|
.where('membershipTypeId', put.membershipTypeId)
|
|
447
447
|
.where('periodId', put.periodId)
|
|
448
448
|
.where(
|
|
449
|
-
SQL.where(
|
|
450
|
-
.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
.
|
|
449
|
+
SQL.where(
|
|
450
|
+
SQL.where('startDate', SQLWhereSign.LessEqual, put.startDate)
|
|
451
|
+
.and('endDate', SQLWhereSign.GreaterEqual, put.startDate),
|
|
452
|
+
)
|
|
453
|
+
.or(
|
|
454
|
+
SQL.where('startDate', SQLWhereSign.LessEqual, put.endDate)
|
|
455
|
+
.and('endDate', SQLWhereSign.GreaterEqual, put.endDate),
|
|
456
|
+
)
|
|
457
|
+
.or(
|
|
458
|
+
SQL.where('startDate', SQLWhereSign.GreaterEqual, put.startDate)
|
|
459
|
+
.and('endDate', SQLWhereSign.LessEqual, put.endDate),
|
|
460
|
+
)
|
|
459
461
|
)
|
|
460
462
|
.first(false);
|
|
461
463
|
|
|
@@ -95,7 +95,7 @@ export class GetEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
95
95
|
: []
|
|
96
96
|
);
|
|
97
97
|
|
|
98
|
-
const defaultTemplateTypes = organization ? types.filter(type => type
|
|
98
|
+
const defaultTemplateTypes = organization ? types.filter(type => EmailTemplateStruct.isSavedEmail(type)) : types;
|
|
99
99
|
const defaultTemplates = defaultTemplateTypes.length === 0
|
|
100
100
|
? []
|
|
101
101
|
: (await EmailTemplate.where({
|
|
@@ -121,6 +121,20 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// Check if we have a category with groups and categories combined
|
|
125
|
+
if (patch.settings) {
|
|
126
|
+
for (const category of organizationPeriod.settings.categories) {
|
|
127
|
+
if (category.groupIds.length && category.categoryIds.length) {
|
|
128
|
+
throw new SimpleError({
|
|
129
|
+
code: 'invalid_field',
|
|
130
|
+
field: 'categories',
|
|
131
|
+
message: 'Cannot have groups and categories combined',
|
|
132
|
+
human: 'Een categorie kan niet zowel groepen als subcategorieën bevatten. Mogelijks zijn meerdere mensen tegelijk aanpassingen aan het maken aan de categorieën. Herlaadt de pagina en probeer opnieuw.',
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
124
138
|
// #region handle locked categories
|
|
125
139
|
if (!Context.auth.hasPlatformFullAccess()) {
|
|
126
140
|
const categoriesAfterPatch = organizationPeriod.settings.categories;
|
|
@@ -560,10 +560,20 @@ export class AuthenticatedStructures {
|
|
|
560
560
|
|
|
561
561
|
const organizationIds = Formatter.uniqueArray(balances.filter(b => b.objectType === ReceivableBalanceType.organization).map(b => b.objectId));
|
|
562
562
|
const organizations = organizationIds.length > 0 ? await Organization.getByIDs(...organizationIds) : [];
|
|
563
|
-
|
|
563
|
+
|
|
564
|
+
const responsibilities = organizationIds.length > 0
|
|
565
|
+
? (await MemberResponsibilityRecord.select()
|
|
566
|
+
.where('organizationId', organizationIds)
|
|
567
|
+
.where('endDate', null)
|
|
568
|
+
.fetch())
|
|
569
|
+
: [];
|
|
570
|
+
|
|
564
571
|
const organizationStructs = await this.organizations(organizations);
|
|
565
572
|
|
|
566
|
-
const memberIds = Formatter.uniqueArray(
|
|
573
|
+
const memberIds = Formatter.uniqueArray([
|
|
574
|
+
...balances.filter(b => b.objectType === ReceivableBalanceType.member).map(b => b.objectId),
|
|
575
|
+
...responsibilities.map(r => r.memberId),
|
|
576
|
+
]);
|
|
567
577
|
const members = memberIds.length > 0 ? await Member.getBlobByIds(...memberIds) : [];
|
|
568
578
|
|
|
569
579
|
const result: ReceivableBalanceStruct[] = [];
|
|
@@ -576,14 +586,28 @@ export class AuthenticatedStructures {
|
|
|
576
586
|
if (balance.objectType === ReceivableBalanceType.organization) {
|
|
577
587
|
const organization = organizationStructs.find(o => o.id == balance.objectId) ?? null;
|
|
578
588
|
if (organization) {
|
|
579
|
-
const
|
|
589
|
+
const theseResponsibilities = responsibilities.filter(r => r.organizationId === organization.id);
|
|
590
|
+
const thisMembers = members.flatMap((m) => {
|
|
591
|
+
const resp = theseResponsibilities.filter(r => r.memberId === m.id);
|
|
592
|
+
return resp.length > 0
|
|
593
|
+
? [{
|
|
594
|
+
member: m,
|
|
595
|
+
responsibilities: resp,
|
|
596
|
+
}]
|
|
597
|
+
: [];
|
|
598
|
+
});
|
|
599
|
+
|
|
580
600
|
object = ReceivableBalanceObject.create({
|
|
581
601
|
id: balance.objectId,
|
|
582
602
|
name: organization.name,
|
|
583
|
-
contacts:
|
|
584
|
-
firstName:
|
|
585
|
-
lastName:
|
|
586
|
-
emails:
|
|
603
|
+
contacts: thisMembers.map(({ member, responsibilities }) => ReceivableBalanceObjectContact.create({
|
|
604
|
+
firstName: member.firstName ?? '',
|
|
605
|
+
lastName: member.lastName ?? '',
|
|
606
|
+
emails: member.details.getMemberEmails(),
|
|
607
|
+
meta: {
|
|
608
|
+
responsibilityIds: responsibilities.map(r => r.responsibilityId),
|
|
609
|
+
url: organization.dashboardUrl + '/boekhouding/openstaand/' + (Context.organization?.uri ?? ''),
|
|
610
|
+
},
|
|
587
611
|
})),
|
|
588
612
|
});
|
|
589
613
|
}
|