@stamhoofd/backend 2.71.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/audit-logs/OrganizationLogger.ts +1 -1
- package/src/audit-logs/PlatformLogger.ts +1 -0
- 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/auth/CreateTokenEndpoint.ts +11 -1
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +26 -2
- package/src/endpoints/auth/PatchUserEndpoint.ts +24 -2
- package/src/endpoints/auth/SignupEndpoint.ts +1 -1
- 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 +9 -7
- 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/global/sso/GetSSOEndpoint.ts +8 -1
- package/src/endpoints/global/sso/SetSSOEndpoint.ts +4 -0
- 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/AdminPermissionChecker.ts +4 -3
- 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/services/SSOService.ts +68 -4
- 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
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { XlsxBuiltInNumberFormat, XlsxTransformerColumn, XlsxTransformerConcreteColumn } from '@stamhoofd/excel-writer';
|
|
2
|
+
import { BalanceItemRelationType, BalanceItemWithPayments, DetailedReceivableBalance, ExcelExportType, getBalanceItemRelationTypeName, getBalanceItemStatusName, getBalanceItemTypeName, getReceivableBalanceTypeNameNotTranslated, PaginatedResponse, ReceivableBalance } from '@stamhoofd/structures';
|
|
3
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
4
|
+
import { ExportToExcelEndpoint } from '../endpoints/global/files/ExportToExcelEndpoint';
|
|
5
|
+
import { GetReceivableBalancesEndpoint } from '../endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint';
|
|
6
|
+
|
|
7
|
+
type ReceivableBalanceWithItem = {
|
|
8
|
+
receivableBalance: DetailedReceivableBalance;
|
|
9
|
+
balanceItem: BalanceItemWithPayments;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
ExportToExcelEndpoint.loaders.set(ExcelExportType.ReceivableBalances, {
|
|
13
|
+
fetch: async (requestQuery) => {
|
|
14
|
+
const data = await GetReceivableBalancesEndpoint.buildDetailedData(requestQuery);
|
|
15
|
+
|
|
16
|
+
return new PaginatedResponse({
|
|
17
|
+
...data,
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
sheets: [
|
|
21
|
+
{
|
|
22
|
+
id: 'receivableBalances',
|
|
23
|
+
name: 'Te ontvangen bedragen',
|
|
24
|
+
columns: getGeneralColumns(),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'balanceItems',
|
|
28
|
+
name: 'Lijnen',
|
|
29
|
+
transform: (data: DetailedReceivableBalance): ReceivableBalanceWithItem[] => data.balanceItems.map(balanceItem => ({
|
|
30
|
+
receivableBalance: data,
|
|
31
|
+
balanceItem,
|
|
32
|
+
})),
|
|
33
|
+
columns: [
|
|
34
|
+
...getBalanceItemColumns(),
|
|
35
|
+
|
|
36
|
+
// Repeating columns need to de-transform again
|
|
37
|
+
...getGeneralColumns()
|
|
38
|
+
.map((c) => {
|
|
39
|
+
return {
|
|
40
|
+
...c,
|
|
41
|
+
id: `receivableBalance.${c.id}`,
|
|
42
|
+
getValue: (object: ReceivableBalanceWithItem) => {
|
|
43
|
+
return c.getValue(object.receivableBalance);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}),
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function getBalanceItemColumns(): XlsxTransformerColumn<ReceivableBalanceWithItem>[] {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
id: 'id',
|
|
56
|
+
name: 'ID',
|
|
57
|
+
width: 40,
|
|
58
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
59
|
+
value: object.balanceItem.id,
|
|
60
|
+
style: {
|
|
61
|
+
font: {
|
|
62
|
+
bold: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'type',
|
|
69
|
+
name: 'Type',
|
|
70
|
+
width: 30,
|
|
71
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
72
|
+
value: getBalanceItemTypeName(object.balanceItem.type),
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'category',
|
|
77
|
+
name: 'Categorie',
|
|
78
|
+
width: 30,
|
|
79
|
+
getValue: (object: ReceivableBalanceWithItem) => {
|
|
80
|
+
return {
|
|
81
|
+
value: Formatter.capitalizeFirstLetter(object.balanceItem.category),
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'description',
|
|
87
|
+
name: 'Beschrijving',
|
|
88
|
+
width: 40,
|
|
89
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
90
|
+
value: object.balanceItem.description,
|
|
91
|
+
}),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'amount',
|
|
95
|
+
name: 'Aantal',
|
|
96
|
+
width: 20,
|
|
97
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
98
|
+
value: object.balanceItem.amount,
|
|
99
|
+
style: {
|
|
100
|
+
numberFormat: {
|
|
101
|
+
id: XlsxBuiltInNumberFormat.Number,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'unitPrice',
|
|
108
|
+
name: 'Eenheidsprijs',
|
|
109
|
+
width: 20,
|
|
110
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
111
|
+
value: object.balanceItem.unitPrice / 100,
|
|
112
|
+
style: {
|
|
113
|
+
numberFormat: {
|
|
114
|
+
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'price',
|
|
121
|
+
name: 'Prijs',
|
|
122
|
+
width: 20,
|
|
123
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
124
|
+
value: object.balanceItem.price / 100,
|
|
125
|
+
style: {
|
|
126
|
+
numberFormat: {
|
|
127
|
+
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'pricePaid',
|
|
134
|
+
name: 'Betaald bedrag',
|
|
135
|
+
width: 20,
|
|
136
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
137
|
+
value: object.balanceItem.pricePaid / 100,
|
|
138
|
+
style: {
|
|
139
|
+
numberFormat: {
|
|
140
|
+
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'pricePending',
|
|
147
|
+
name: 'In verwerking',
|
|
148
|
+
width: 20,
|
|
149
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
150
|
+
value: object.balanceItem.pricePending / 100,
|
|
151
|
+
style: {
|
|
152
|
+
numberFormat: {
|
|
153
|
+
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'priceOpen',
|
|
160
|
+
name: 'Openstaand bedrag',
|
|
161
|
+
width: 20,
|
|
162
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
163
|
+
value: object.balanceItem.priceOpen / 100,
|
|
164
|
+
style: {
|
|
165
|
+
numberFormat: {
|
|
166
|
+
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'createdAt',
|
|
173
|
+
name: 'Aangemaakt op',
|
|
174
|
+
width: 20,
|
|
175
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
176
|
+
value: object.balanceItem.createdAt,
|
|
177
|
+
style: {
|
|
178
|
+
numberFormat: {
|
|
179
|
+
id: XlsxBuiltInNumberFormat.DateSlash,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'dueAt',
|
|
186
|
+
name: 'Verschuldigd vanaf',
|
|
187
|
+
width: 20,
|
|
188
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
189
|
+
value: object.balanceItem.dueAt,
|
|
190
|
+
style: {
|
|
191
|
+
numberFormat: {
|
|
192
|
+
id: XlsxBuiltInNumberFormat.DateSlash,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
}),
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'status',
|
|
199
|
+
name: 'Status',
|
|
200
|
+
width: 20,
|
|
201
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
202
|
+
value: getBalanceItemStatusName(object.balanceItem.status),
|
|
203
|
+
}),
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
{
|
|
207
|
+
match: (id) => {
|
|
208
|
+
if (id.startsWith('relations.')) {
|
|
209
|
+
const type = id.split('.')[1] as BalanceItemRelationType;
|
|
210
|
+
if (Object.values(BalanceItemRelationType).includes(type)) {
|
|
211
|
+
return [
|
|
212
|
+
{
|
|
213
|
+
id: `relations.${type}`,
|
|
214
|
+
name: getBalanceItemRelationTypeName(type),
|
|
215
|
+
width: 35,
|
|
216
|
+
getValue: (object: ReceivableBalanceWithItem) => ({
|
|
217
|
+
value: object.balanceItem.relations.get(type)?.name || '',
|
|
218
|
+
}),
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getGeneralColumns(): XlsxTransformerConcreteColumn<ReceivableBalance>[] {
|
|
229
|
+
return [
|
|
230
|
+
{
|
|
231
|
+
id: 'id',
|
|
232
|
+
name: 'ID schuldenaar',
|
|
233
|
+
width: 40,
|
|
234
|
+
getValue: (object: ReceivableBalance) => ({
|
|
235
|
+
value: object.id,
|
|
236
|
+
style: {
|
|
237
|
+
font: {
|
|
238
|
+
bold: true,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
}),
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: 'name',
|
|
245
|
+
name: 'Schuldenaar',
|
|
246
|
+
width: 40,
|
|
247
|
+
getValue: (object: ReceivableBalance) => ({
|
|
248
|
+
value: object.object.name,
|
|
249
|
+
}),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'uri',
|
|
253
|
+
name: 'Groepsnummer',
|
|
254
|
+
width: 16,
|
|
255
|
+
getValue: (object: ReceivableBalance) => ({
|
|
256
|
+
value: object.object.uri,
|
|
257
|
+
}),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: 'amountOpen',
|
|
261
|
+
name: 'Openstaand bedrag',
|
|
262
|
+
width: 10,
|
|
263
|
+
getValue: (object: ReceivableBalance) => ({
|
|
264
|
+
value: object.amountOpen / 100,
|
|
265
|
+
style: {
|
|
266
|
+
numberFormat: {
|
|
267
|
+
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'amountPending',
|
|
274
|
+
name: 'In verwerking',
|
|
275
|
+
width: 18,
|
|
276
|
+
getValue: (object: ReceivableBalance) => ({
|
|
277
|
+
value: object.amountPending / 100,
|
|
278
|
+
style: {
|
|
279
|
+
numberFormat: {
|
|
280
|
+
id: XlsxBuiltInNumberFormat.Currency2DecimalWithRed,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
}),
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'objectType',
|
|
287
|
+
name: 'type',
|
|
288
|
+
width: 10,
|
|
289
|
+
getValue: (object: ReceivableBalance) => ({
|
|
290
|
+
value: getReceivableBalanceTypeNameNotTranslated(object.objectType),
|
|
291
|
+
}),
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
}
|
|
@@ -538,6 +538,10 @@ export class AdminPermissionChecker {
|
|
|
538
538
|
}
|
|
539
539
|
|
|
540
540
|
async canEditUserName(user: User) {
|
|
541
|
+
if (user.hasAccount() && !user.hasPasswordBasedAccount()) {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
|
|
541
545
|
if (user.id === this.user.id) {
|
|
542
546
|
return true;
|
|
543
547
|
}
|
|
@@ -556,9 +560,6 @@ export class AdminPermissionChecker {
|
|
|
556
560
|
}
|
|
557
561
|
|
|
558
562
|
async canEditUserEmail(user: User) {
|
|
559
|
-
if (user.meta?.loginProviderIds?.size) {
|
|
560
|
-
return false;
|
|
561
|
-
}
|
|
562
563
|
return this.canEditUserName(user);
|
|
563
564
|
}
|
|
564
565
|
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
import { DecodedRequest, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { isSimpleError, isSimpleErrors, SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { Organization, Platform, Token, User, Webshop } from '@stamhoofd/models';
|
|
4
|
-
import { LoginProviderType, OpenIDClientConfiguration, StartOpenIDFlowStruct, Token as TokenStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { LoginMethod, LoginProviderType, OpenIDClientConfiguration, StartOpenIDFlowStruct, Token as TokenStruct } from '@stamhoofd/structures';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
6
|
import { generators, Issuer } from 'openid-client';
|
|
7
7
|
import { Context } from '../helpers/Context';
|
|
@@ -81,8 +81,8 @@ export class SSOService {
|
|
|
81
81
|
const platform = await Platform.getShared();
|
|
82
82
|
|
|
83
83
|
const service = new SSOService({ provider, platform, organization, user: Context.user });
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
service.validate();
|
|
85
|
+
|
|
86
86
|
return service;
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -117,6 +117,51 @@ export class SSOService {
|
|
|
117
117
|
return configuration;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
get loginConfiguration() {
|
|
121
|
+
if (this.organization) {
|
|
122
|
+
throw new SimpleError({
|
|
123
|
+
code: 'invalid_client',
|
|
124
|
+
message: 'Login configuration not yet supported for organization users',
|
|
125
|
+
statusCode: 400,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const loginConfiguration = this.platform.config.loginMethods.get(this.provider as unknown as LoginMethod);
|
|
130
|
+
if (!loginConfiguration) {
|
|
131
|
+
throw new SimpleError({
|
|
132
|
+
code: 'invalid_client',
|
|
133
|
+
message: 'SSO not configured (correctly)',
|
|
134
|
+
statusCode: 400,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return loginConfiguration;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
validate() {
|
|
142
|
+
// Validate configuration exists
|
|
143
|
+
const _ = this.configuration;
|
|
144
|
+
const __ = this.loginConfiguration;
|
|
145
|
+
|
|
146
|
+
if (this.user) {
|
|
147
|
+
this.validateEmail(this.user.email);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
validateEmail(email: string) {
|
|
152
|
+
// Validate configuration
|
|
153
|
+
const loginConfiguration = this.loginConfiguration;
|
|
154
|
+
|
|
155
|
+
if (!loginConfiguration.isEnabledForEmail(email)) {
|
|
156
|
+
throw new SimpleError({
|
|
157
|
+
code: 'invalid_user',
|
|
158
|
+
message: 'User not allowed to use this login method',
|
|
159
|
+
human: 'Je kan deze inlogmethode niet gebruiken',
|
|
160
|
+
statusCode: 400,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
120
165
|
async setConfiguration(configuration: OpenIDClientConfiguration) {
|
|
121
166
|
if (this.provider === LoginProviderType.SSO) {
|
|
122
167
|
if (this.organization) {
|
|
@@ -289,8 +334,15 @@ export class SSOService {
|
|
|
289
334
|
const client = await this.getClient();
|
|
290
335
|
await SSOService.storeSession(response, session);
|
|
291
336
|
|
|
337
|
+
const scopes = ['openid', 'email', 'profile'];
|
|
338
|
+
|
|
339
|
+
if (this.provider === LoginProviderType.SSO) {
|
|
340
|
+
// Google doesn't support this scope
|
|
341
|
+
scopes.push('offline_access');
|
|
342
|
+
}
|
|
343
|
+
|
|
292
344
|
const redirect = client.authorizationUrl({
|
|
293
|
-
scope: '
|
|
345
|
+
scope: scopes.join(' '),
|
|
294
346
|
code_challenge,
|
|
295
347
|
code_challenge_method: 'S256',
|
|
296
348
|
response_mode: 'form_post',
|
|
@@ -300,6 +352,9 @@ export class SSOService {
|
|
|
300
352
|
prompt: prompt ?? undefined,
|
|
301
353
|
login_hint: this.user?.email ?? undefined,
|
|
302
354
|
redirect_uri: this.externalRedirectUri,
|
|
355
|
+
|
|
356
|
+
// Google has this instead of the offline_access scope
|
|
357
|
+
access_type: this.provider === LoginProviderType.Google ? 'offline' : undefined,
|
|
303
358
|
});
|
|
304
359
|
|
|
305
360
|
response.headers['location'] = redirect;
|
|
@@ -387,6 +442,13 @@ export class SSOServiceWithSession {
|
|
|
387
442
|
}
|
|
388
443
|
}
|
|
389
444
|
|
|
445
|
+
if (tokenSet.refresh_token) {
|
|
446
|
+
console.log('OK. Refresh token received!');
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
console.log('No refresh token');
|
|
450
|
+
}
|
|
451
|
+
|
|
390
452
|
if (!claims.email) {
|
|
391
453
|
throw new SimpleError({
|
|
392
454
|
code: 'invalid_user',
|
|
@@ -403,6 +465,8 @@ export class SSOServiceWithSession {
|
|
|
403
465
|
});
|
|
404
466
|
}
|
|
405
467
|
|
|
468
|
+
this.service.validateEmail(claims.email);
|
|
469
|
+
|
|
406
470
|
// Get user from database
|
|
407
471
|
let user = await User.getForRegister(this.service.organization?.id ?? null, claims.email);
|
|
408
472
|
if (!user) {
|
|
@@ -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'),
|