@stamhoofd/backend 2.72.0 → 2.73.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/orders.ts +1 -1
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +1 -1
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +7 -7
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +23 -3
- package/src/endpoints/global/audit-logs/GetAuditLogsEndpoint.ts +3 -3
- package/src/endpoints/global/events/GetEventsEndpoint.ts +6 -6
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +36 -4
- package/src/endpoints/global/members/GetMembersEndpoint.ts +4 -4
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +24 -14
- package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +34 -0
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +11 -1
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +20 -12
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +11 -3
- package/src/endpoints/organization/dashboard/documents/GetDocumentsCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +6 -6
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +5 -5
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +51 -9
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersCountEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +6 -6
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +6 -6
- package/src/excel-loaders/members.ts +8 -0
- package/src/excel-loaders/receivable-balances.ts +294 -0
- package/src/helpers/AuthenticatedStructures.ts +32 -6
- package/src/helpers/SetupStepUpdater.ts +10 -4
- package/src/helpers/xlsxAddressTransformerColumnFactory.ts +8 -0
- package/src/services/PaymentReallocationService.ts +3 -2
- package/src/services/PaymentService.ts +17 -1
- package/src/sql-filters/members.ts +20 -1
- package/src/sql-filters/organizations.ts +1 -0
- package/src/sql-filters/receivable-balances.ts +53 -1
- package/src/sql-sorters/organizations.ts +11 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
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';
|
|
3
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, 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';
|
|
@@ -399,7 +399,7 @@ export class AuthenticatedStructures {
|
|
|
399
399
|
const platformMemberships = members.length > 0 ? await MemberPlatformMembership.where({ deletedAt: null, memberId: { sign: 'IN', value: members.map(m => m.id) } }) : [];
|
|
400
400
|
|
|
401
401
|
// Load missing organizations
|
|
402
|
-
const organizationIds = Formatter.uniqueArray(responsibilities.map(r => r.organizationId).filter(id => id !== null));
|
|
402
|
+
const organizationIds = Formatter.uniqueArray(responsibilities.map(r => r.organizationId).concat(platformMemberships.map(r => r.organizationId)).filter(id => id !== null));
|
|
403
403
|
for (const id of organizationIds) {
|
|
404
404
|
if (includeContextOrganization || id !== Context.auth.organization?.id) {
|
|
405
405
|
const found = organizations.get(id);
|
|
@@ -575,6 +575,10 @@ export class AuthenticatedStructures {
|
|
|
575
575
|
}
|
|
576
576
|
|
|
577
577
|
static async receivableBalances(balances: CachedBalance[]): Promise<ReceivableBalanceStruct[]> {
|
|
578
|
+
return (await this.receivableBalancesHelper(balances)).map(({ balance, object }) => ReceivableBalanceStruct.create({ ...balance, object }));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private static async receivableBalancesHelper(balances: CachedBalance[]): Promise< { balance: CachedBalance; object: ReceivableBalanceObject }[]> {
|
|
578
582
|
if (balances.length === 0) {
|
|
579
583
|
return [];
|
|
580
584
|
}
|
|
@@ -608,7 +612,8 @@ export class AuthenticatedStructures {
|
|
|
608
612
|
]);
|
|
609
613
|
const users = userIds.length > 0 ? await User.getByIDs(...userIds) : [];
|
|
610
614
|
|
|
611
|
-
const result:
|
|
615
|
+
const result: { balance: CachedBalance; object: ReceivableBalanceObject }[] = [];
|
|
616
|
+
|
|
612
617
|
for (const balance of balances) {
|
|
613
618
|
let object = ReceivableBalanceObject.create({
|
|
614
619
|
id: balance.objectId,
|
|
@@ -632,6 +637,7 @@ export class AuthenticatedStructures {
|
|
|
632
637
|
object = ReceivableBalanceObject.create({
|
|
633
638
|
id: balance.objectId,
|
|
634
639
|
name: organization.name,
|
|
640
|
+
uri: organization.uri,
|
|
635
641
|
contacts: thisMembers.map(({ member, responsibilities }) => ReceivableBalanceObjectContact.create({
|
|
636
642
|
firstName: member.firstName ?? '',
|
|
637
643
|
lastName: member.lastName ?? '',
|
|
@@ -749,15 +755,35 @@ export class AuthenticatedStructures {
|
|
|
749
755
|
}
|
|
750
756
|
}
|
|
751
757
|
|
|
752
|
-
|
|
758
|
+
result.push({
|
|
759
|
+
balance,
|
|
760
|
+
object,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return result;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
static async detailedReceivableBalances(organizationId: string, balances: CachedBalance[]): Promise<DetailedReceivableBalance[]> {
|
|
768
|
+
const items = await this.receivableBalancesHelper(balances);
|
|
769
|
+
const results: DetailedReceivableBalance[] = [];
|
|
770
|
+
|
|
771
|
+
for (const { balance, object } of items) {
|
|
772
|
+
const balanceItems = await CachedBalance.balanceForObjects(organizationId, [balance.objectId], balance.objectType, true);
|
|
773
|
+
const balanceItemsWithPayments = await BalanceItem.getStructureWithPayments(balanceItems);
|
|
774
|
+
|
|
775
|
+
const result = DetailedReceivableBalance.create({
|
|
753
776
|
...balance,
|
|
754
777
|
object,
|
|
778
|
+
balanceItems: balanceItemsWithPayments,
|
|
779
|
+
// todo!!!
|
|
780
|
+
payments: [],
|
|
755
781
|
});
|
|
756
782
|
|
|
757
|
-
|
|
783
|
+
results.push(result);
|
|
758
784
|
}
|
|
759
785
|
|
|
760
|
-
return
|
|
786
|
+
return results;
|
|
761
787
|
}
|
|
762
788
|
|
|
763
789
|
static async auditLogs(logs: AuditLog[]): Promise<AuditLogStruct[]> {
|
|
@@ -283,10 +283,16 @@ export class SetupStepUpdater {
|
|
|
283
283
|
finishedSteps++;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
if (premiseTypes.length > 0) {
|
|
287
|
+
setupSteps.update(SetupStepType.Premises, {
|
|
288
|
+
totalSteps,
|
|
289
|
+
finishedSteps,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// if no premise types, remove step
|
|
294
|
+
setupSteps.remove(SetupStepType.Premises);
|
|
295
|
+
}
|
|
290
296
|
}
|
|
291
297
|
|
|
292
298
|
private static updateStepGroups(
|
|
@@ -90,6 +90,14 @@ export class XlsxTransformerColumnHelper {
|
|
|
90
90
|
value: getParent(member)?.email ?? '',
|
|
91
91
|
}),
|
|
92
92
|
},
|
|
93
|
+
{
|
|
94
|
+
id: getId('nationalRegisterNumber'),
|
|
95
|
+
name: getName('Rijksregisternummer'),
|
|
96
|
+
width: 20,
|
|
97
|
+
getValue: (member: PlatformMember) => ({
|
|
98
|
+
value: getParent(member)?.nationalRegisterNumber?.toString() ?? '',
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
93
101
|
XlsxTransformerColumnHelper.createAddressColumns<PlatformMember>({
|
|
94
102
|
matchId: getId('address'),
|
|
95
103
|
getAddress: member => getParent(member)?.address,
|
|
@@ -40,13 +40,13 @@ export const PaymentReallocationService = {
|
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const similarDueItems = balanceItems.filter(b => b.id !== balanceItem.id && b.status === BalanceItemStatus.Due && doBalanceItemRelationsMatch(b.relations, balanceItem.relations, 0));
|
|
43
|
+
const similarDueItems = balanceItems.filter(b => b.id !== balanceItem.id && b.type === balanceItem.type && b.status === BalanceItemStatus.Due && doBalanceItemRelationsMatch(b.relations, balanceItem.relations, 0));
|
|
44
44
|
|
|
45
45
|
if (similarDueItems.length) {
|
|
46
46
|
// Not possible to merge into one: there are 2 due items
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
-
const similarCanceledItems = balanceItems.filter(b => b.id !== balanceItem.id && b.status !== BalanceItemStatus.Due && doBalanceItemRelationsMatch(b.relations, balanceItem.relations, 0));
|
|
49
|
+
const similarCanceledItems = balanceItems.filter(b => b.id !== balanceItem.id && b.type === balanceItem.type && b.status !== BalanceItemStatus.Due && doBalanceItemRelationsMatch(b.relations, balanceItem.relations, 0));
|
|
50
50
|
|
|
51
51
|
if (similarCanceledItems.length) {
|
|
52
52
|
await this.mergeBalanceItems(balanceItem, similarCanceledItems);
|
|
@@ -190,6 +190,7 @@ export const PaymentReallocationService = {
|
|
|
190
190
|
payment.type = PaymentType.Reallocation;
|
|
191
191
|
payment.method = PaymentMethod.Unknown;
|
|
192
192
|
payment.status = PaymentStatus.Succeeded;
|
|
193
|
+
payment.paidAt = new Date();
|
|
193
194
|
await payment.save();
|
|
194
195
|
|
|
195
196
|
// Create balance item payments
|
|
@@ -161,7 +161,7 @@ export const PaymentService = {
|
|
|
161
161
|
if (token) {
|
|
162
162
|
try {
|
|
163
163
|
const mollieClient = createMollieClient({ accessToken: await token.getAccessToken() });
|
|
164
|
-
|
|
164
|
+
let mollieData = await mollieClient.payments.get(molliePayment.mollieId, {
|
|
165
165
|
testmode: organization.privateMeta.useTestPayments ?? STAMHOOFD.environment !== 'production',
|
|
166
166
|
});
|
|
167
167
|
|
|
@@ -187,6 +187,22 @@ export const PaymentService = {
|
|
|
187
187
|
else if (mollieData.status === MolliePaymentStatus.failed || mollieData.status === MolliePaymentStatus.expired || mollieData.status === MolliePaymentStatus.canceled) {
|
|
188
188
|
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
189
189
|
}
|
|
190
|
+
else if ((cancel || this.shouldTryToCancel(payment.status, payment)) && mollieData.isCancelable) {
|
|
191
|
+
console.log('Cancelling Mollie payment on request', payment.id);
|
|
192
|
+
mollieData = await mollieClient.payments.cancel(molliePayment.mollieId);
|
|
193
|
+
|
|
194
|
+
if (mollieData.status === MolliePaymentStatus.paid) {
|
|
195
|
+
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Succeeded);
|
|
196
|
+
}
|
|
197
|
+
else if (mollieData.status === MolliePaymentStatus.failed || mollieData.status === MolliePaymentStatus.expired || mollieData.status === MolliePaymentStatus.canceled) {
|
|
198
|
+
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
199
|
+
}
|
|
200
|
+
else if (this.isManualExpired(payment.status, payment)) {
|
|
201
|
+
// Mollie still returning pending after 1 day: mark as failed
|
|
202
|
+
console.error('Manually marking Mollie payment as expired', payment.id);
|
|
203
|
+
await this.handlePaymentStatusUpdate(payment, organization, PaymentStatus.Failed);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
190
206
|
else if (this.isManualExpired(payment.status, payment)) {
|
|
191
207
|
// Mollie still returning pending after 1 day: mark as failed
|
|
192
208
|
console.error('Manually marking Mollie payment as expired', payment.id);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
1
2
|
import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLScalar, SQLValueType, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler } from '@stamhoofd/sql';
|
|
3
|
+
import { AccessRight } from '@stamhoofd/structures';
|
|
2
4
|
import { Formatter } from '@stamhoofd/utility';
|
|
5
|
+
import { Context } from '../helpers/Context';
|
|
3
6
|
import { organizationFilterCompilers } from './organizations';
|
|
4
7
|
import { registrationFilterCompilers } from './registrations';
|
|
5
8
|
|
|
@@ -44,7 +47,23 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
44
47
|
|
|
45
48
|
'details.requiresFinancialSupport': createSQLExpressionFilterCompiler(
|
|
46
49
|
SQL.jsonValue(SQL.column('details'), '$.value.requiresFinancialSupport.value'),
|
|
47
|
-
{ isJSONValue: true, type: SQLValueType.JSONBoolean
|
|
50
|
+
{ isJSONValue: true, type: SQLValueType.JSONBoolean, checkPermission: async () => {
|
|
51
|
+
const organization = Context.organization;
|
|
52
|
+
if (!organization) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const permissions = await Context.auth.getOrganizationPermissions(organization);
|
|
57
|
+
|
|
58
|
+
if (!permissions || !permissions.hasAccessRight(AccessRight.MemberReadFinancialData)) {
|
|
59
|
+
throw new SimpleError({
|
|
60
|
+
code: 'permission_denied',
|
|
61
|
+
message: 'No permissions for financial support filter (organization scope).',
|
|
62
|
+
human: 'Je hebt geen toegangsrechten om deze filter te gebruiken.',
|
|
63
|
+
statusCode: 400,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
} },
|
|
48
67
|
),
|
|
49
68
|
|
|
50
69
|
'email': createSQLExpressionFilterCompiler(
|
|
@@ -22,6 +22,7 @@ import { SetupStepType } from '@stamhoofd/structures';
|
|
|
22
22
|
export const organizationFilterCompilers: SQLFilterDefinitions = {
|
|
23
23
|
...baseSQLFilterCompilers,
|
|
24
24
|
id: createSQLExpressionFilterCompiler(SQL.column('organizations', 'id')),
|
|
25
|
+
uriPadded: createSQLExpressionFilterCompiler(SQL.lpad(SQL.column('organizations', 'uri'), 100, '0')),
|
|
25
26
|
uri: createSQLExpressionFilterCompiler(SQL.column('organizations', 'uri')),
|
|
26
27
|
name: createSQLExpressionFilterCompiler(
|
|
27
28
|
SQL.column('organizations', 'name'),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { SQL, SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler } from '@stamhoofd/sql';
|
|
1
|
+
import { SQL, SQLConcat, SQLFilterDefinitions, SQLScalar, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLRelationFilterCompiler } from '@stamhoofd/sql';
|
|
2
|
+
import { memberFilterCompilers } from './members';
|
|
3
|
+
import { organizationFilterCompilers } from './organizations';
|
|
2
4
|
import { EmailRelationFilterCompilers } from './shared/EmailRelationFilterCompilers';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -22,6 +24,56 @@ export const receivableBalanceFilterCompilers: SQLFilterDefinitions = {
|
|
|
22
24
|
).then(1).else(0),
|
|
23
25
|
{ isJSONValue: false, isJSONObject: false },
|
|
24
26
|
),
|
|
27
|
+
organizations: createSQLRelationFilterCompiler(
|
|
28
|
+
SQL.select()
|
|
29
|
+
.from(SQL.table('organizations'))
|
|
30
|
+
.where(
|
|
31
|
+
SQL.column(
|
|
32
|
+
'organizations',
|
|
33
|
+
'id',
|
|
34
|
+
),
|
|
35
|
+
SQL.column('cached_outstanding_balances', 'objectId'),
|
|
36
|
+
).where(
|
|
37
|
+
SQL.column('cached_outstanding_balances', 'objectType'),
|
|
38
|
+
'organization'),
|
|
39
|
+
organizationFilterCompilers,
|
|
40
|
+
),
|
|
41
|
+
members: createSQLRelationFilterCompiler(
|
|
42
|
+
SQL.select()
|
|
43
|
+
.from(SQL.table('members'))
|
|
44
|
+
.where(
|
|
45
|
+
SQL.column(
|
|
46
|
+
'members',
|
|
47
|
+
'id',
|
|
48
|
+
),
|
|
49
|
+
SQL.column('cached_outstanding_balances', 'objectId'),
|
|
50
|
+
).where(
|
|
51
|
+
SQL.column('cached_outstanding_balances', 'objectType'),
|
|
52
|
+
'member'),
|
|
53
|
+
memberFilterCompilers,
|
|
54
|
+
),
|
|
55
|
+
users: createSQLRelationFilterCompiler(
|
|
56
|
+
SQL.select()
|
|
57
|
+
.from(SQL.table('users'))
|
|
58
|
+
.where(
|
|
59
|
+
SQL.column(
|
|
60
|
+
'users',
|
|
61
|
+
'id',
|
|
62
|
+
),
|
|
63
|
+
SQL.column('cached_outstanding_balances', 'objectId'),
|
|
64
|
+
).where(
|
|
65
|
+
SQL.column('cached_outstanding_balances', 'objectType'),
|
|
66
|
+
'user'),
|
|
67
|
+
{
|
|
68
|
+
name: createSQLExpressionFilterCompiler(
|
|
69
|
+
new SQLConcat(
|
|
70
|
+
SQL.column('firstName'),
|
|
71
|
+
new SQLScalar(' '),
|
|
72
|
+
SQL.column('lastName'),
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
),
|
|
25
77
|
|
|
26
78
|
// Allowed to filter by recent emails
|
|
27
79
|
...EmailRelationFilterCompilers,
|
|
@@ -43,6 +43,17 @@ export const organizationSorters: SQLSortDefinitions<Organization> = {
|
|
|
43
43
|
});
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
|
+
uriPadded: {
|
|
47
|
+
getValue(a) {
|
|
48
|
+
return a.uri.padStart(100, '0');
|
|
49
|
+
},
|
|
50
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
51
|
+
return new SQLOrderBy({
|
|
52
|
+
column: SQL.lpad(SQL.column('uri'), 100, '0'),
|
|
53
|
+
direction,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
},
|
|
46
57
|
type: {
|
|
47
58
|
getValue(a) {
|
|
48
59
|
return a.meta.type;
|