@stamhoofd/backend 2.112.0 → 2.113.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/package.json +10 -10
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -0
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +73 -7
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +6 -4
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +10 -7
- package/src/excel-loaders/members.ts +1 -1
- package/src/helpers/AdminPermissionChecker.ts +5 -1
- package/src/helpers/AuthenticatedStructures.ts +69 -62
- package/src/helpers/Context.ts +1 -1
- package/src/helpers/UitpasTokenRepository.ts +6 -1
- package/src/seeds/{wip/1769088653-uitpas-status.ts → 1769088653-uitpas-status.ts} +5 -7
- package/src/services/PaymentService.ts +2 -3
- package/src/sql-sorters/registrations.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.113.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
"@simonbackx/simple-encoding": "2.23.1",
|
|
55
55
|
"@simonbackx/simple-endpoints": "1.20.1",
|
|
56
56
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
57
|
-
"@stamhoofd/backend-i18n": "2.
|
|
58
|
-
"@stamhoofd/backend-middleware": "2.
|
|
59
|
-
"@stamhoofd/email": "2.
|
|
60
|
-
"@stamhoofd/models": "2.
|
|
61
|
-
"@stamhoofd/queues": "2.
|
|
62
|
-
"@stamhoofd/sql": "2.
|
|
63
|
-
"@stamhoofd/structures": "2.
|
|
64
|
-
"@stamhoofd/utility": "2.
|
|
57
|
+
"@stamhoofd/backend-i18n": "2.113.0",
|
|
58
|
+
"@stamhoofd/backend-middleware": "2.113.0",
|
|
59
|
+
"@stamhoofd/email": "2.113.0",
|
|
60
|
+
"@stamhoofd/models": "2.113.0",
|
|
61
|
+
"@stamhoofd/queues": "2.113.0",
|
|
62
|
+
"@stamhoofd/sql": "2.113.0",
|
|
63
|
+
"@stamhoofd/structures": "2.113.0",
|
|
64
|
+
"@stamhoofd/utility": "2.113.0",
|
|
65
65
|
"archiver": "^7.0.1",
|
|
66
66
|
"axios": "^1.13.2",
|
|
67
67
|
"cookie": "^0.7.0",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"publishConfig": {
|
|
80
80
|
"access": "public"
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "e90b56b0e379e4bc7f7138cef336c267fb5e9bcf"
|
|
83
83
|
}
|
|
@@ -1057,6 +1057,8 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
1057
1057
|
payment.status = PaymentStatus.Created;
|
|
1058
1058
|
payment.paidAt = null;
|
|
1059
1059
|
payment.price = totalPrice;
|
|
1060
|
+
PaymentService.round(payment);
|
|
1061
|
+
totalPrice = payment.price;
|
|
1060
1062
|
|
|
1061
1063
|
if (totalPrice === 0) {
|
|
1062
1064
|
payment.status = PaymentStatus.Succeeded;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
1
|
+
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
-
import { BalanceItem, BalanceItemPayment, Payment } from '@stamhoofd/models';
|
|
4
|
+
import { BalanceItem, BalanceItemPayment, Payment, User } from '@stamhoofd/models';
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
|
-
import { PaymentGeneral, PaymentMethod, PaymentStatus, Payment as PaymentStruct, PaymentType, PermissionLevel } from '@stamhoofd/structures';
|
|
6
|
+
import { PaymentCustomer, PaymentGeneral, PaymentMethod, PaymentStatus, Payment as PaymentStruct, PaymentType, PermissionLevel } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
|
-
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
9
|
-
import { Context } from '../../../../helpers/Context';
|
|
10
|
-
import { BalanceItemService } from '../../../../services/BalanceItemService';
|
|
11
|
-
import { PaymentService } from '../../../../services/PaymentService';
|
|
8
|
+
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures.js';
|
|
9
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
10
|
+
import { BalanceItemService } from '../../../../services/BalanceItemService.js';
|
|
11
|
+
import { PaymentService } from '../../../../services/PaymentService.js';
|
|
12
|
+
import { ViesHelper } from '../../../../helpers/ViesHelper.js';
|
|
12
13
|
|
|
13
14
|
type Params = Record<string, never>;
|
|
14
15
|
type Query = undefined;
|
|
@@ -71,6 +72,38 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
71
72
|
payment.status = PaymentStatus.Created;
|
|
72
73
|
payment.method = put.method;
|
|
73
74
|
payment.customer = put.customer;
|
|
75
|
+
|
|
76
|
+
const payingOrganizationId = put.payingOrganizationId ?? put.payingOrganization?.id ?? null;
|
|
77
|
+
|
|
78
|
+
if (payingOrganizationId) {
|
|
79
|
+
if (Context.auth.hasSomePlatformAccess()) {
|
|
80
|
+
if (await Context.auth.hasFullAccess(payingOrganizationId, PermissionLevel.Full)) {
|
|
81
|
+
payment.payingOrganizationId = payingOrganizationId;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// silently ignore
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (put.payingUserId) {
|
|
90
|
+
const user = await User.getByID(put.payingUserId);
|
|
91
|
+
if (!user) {
|
|
92
|
+
throw new SimpleError({
|
|
93
|
+
code: 'user_not_found',
|
|
94
|
+
message: 'User not found',
|
|
95
|
+
field: 'payingUserId',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (await Context.auth.canAccessUser(user, PermissionLevel.Full)) {
|
|
99
|
+
// Allowed
|
|
100
|
+
payment.payingUserId = put.payingUserId;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (put.customer?.company) {
|
|
105
|
+
await ViesHelper.checkCompany(put.customer.company, put.customer.company);
|
|
106
|
+
}
|
|
74
107
|
payment.type = put.type;
|
|
75
108
|
|
|
76
109
|
if (payment.type === PaymentType.Reallocation) {
|
|
@@ -131,6 +164,7 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
131
164
|
// Check total price
|
|
132
165
|
const totalPrice = balanceItemPayments.reduce((total, item) => total + item.price, 0);
|
|
133
166
|
payment.price = totalPrice;
|
|
167
|
+
PaymentService.round(payment);
|
|
134
168
|
|
|
135
169
|
switch (payment.type) {
|
|
136
170
|
case PaymentType.Payment: {
|
|
@@ -265,6 +299,38 @@ export class PatchPaymentsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
265
299
|
payment.paidAt = patch.paidAt;
|
|
266
300
|
}
|
|
267
301
|
|
|
302
|
+
if (patch.customer) {
|
|
303
|
+
payment.customer = patchObject(payment.customer, patch.customer, { defaultValue: PaymentCustomer.create({}) });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const payingOrganizationId = patch.payingOrganizationId ?? patch.payingOrganization?.id ?? null;
|
|
307
|
+
|
|
308
|
+
if (payingOrganizationId) {
|
|
309
|
+
if (Context.auth.hasSomePlatformAccess()) {
|
|
310
|
+
if (await Context.auth.hasFullAccess(payingOrganizationId, PermissionLevel.Full)) {
|
|
311
|
+
payment.payingOrganizationId = payingOrganizationId;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
// silently ignore
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (patch.payingUserId) {
|
|
320
|
+
const user = await User.getByID(patch.payingUserId);
|
|
321
|
+
if (!user) {
|
|
322
|
+
throw new SimpleError({
|
|
323
|
+
code: 'user_not_found',
|
|
324
|
+
message: 'User not found',
|
|
325
|
+
field: 'payingUserId',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (await Context.auth.canAccessUser(user, PermissionLevel.Full)) {
|
|
329
|
+
// Allowed
|
|
330
|
+
payment.payingUserId = patch.payingUserId;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
268
334
|
await payment.save();
|
|
269
335
|
|
|
270
336
|
if (patch.status) {
|
|
@@ -5,10 +5,11 @@ import { BalanceItem, BalanceItemPayment, Order, Payment, Webshop, WebshopCounte
|
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
6
|
import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, TranslatedString, Webshop as WebshopStruct, WebshopTicketType } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
|
-
import { Context } from '../../../../helpers/Context';
|
|
9
|
-
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
10
|
-
import { shouldReserveUitpasNumbers, UitpasService } from '../../../../services/uitpas/UitpasService';
|
|
11
|
-
import { ServiceFeeHelper } from '../../../../helpers/ServiceFeeHelper';
|
|
8
|
+
import { Context } from '../../../../helpers/Context.js';
|
|
9
|
+
import { AuditLogService } from '../../../../services/AuditLogService.js';
|
|
10
|
+
import { shouldReserveUitpasNumbers, UitpasService } from '../../../../services/uitpas/UitpasService.js';
|
|
11
|
+
import { ServiceFeeHelper } from '../../../../helpers/ServiceFeeHelper.js';
|
|
12
|
+
import { PaymentService } from '../../../../services/PaymentService.js';
|
|
12
13
|
|
|
13
14
|
type Params = { id: string };
|
|
14
15
|
type Query = undefined;
|
|
@@ -154,6 +155,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
154
155
|
payment.method = struct.data.paymentMethod;
|
|
155
156
|
payment.status = PaymentStatus.Created;
|
|
156
157
|
payment.price = totalPrice;
|
|
158
|
+
PaymentService.round(payment);
|
|
157
159
|
payment.paidAt = null;
|
|
158
160
|
|
|
159
161
|
// Determine the payment provider (always null because no online payments here)
|
|
@@ -8,12 +8,13 @@ import { QueueHandler } from '@stamhoofd/queues';
|
|
|
8
8
|
import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderData, OrderResponse, Order as OrderStruct, PaymentCustomer, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Payment as PaymentStruct, TranslatedString, Version, WebshopAuthType, Webshop as WebshopStruct, WebshopTicketType } from '@stamhoofd/structures';
|
|
9
9
|
import { Formatter } from '@stamhoofd/utility';
|
|
10
10
|
|
|
11
|
-
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
12
|
-
import { Context } from '../../../helpers/Context';
|
|
13
|
-
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
14
|
-
import { AuditLogService } from '../../../services/AuditLogService';
|
|
15
|
-
import { UitpasService } from '../../../services/uitpas/UitpasService';
|
|
16
|
-
import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper';
|
|
11
|
+
import { BuckarooHelper } from '../../../helpers/BuckarooHelper.js';
|
|
12
|
+
import { Context } from '../../../helpers/Context.js';
|
|
13
|
+
import { StripeHelper } from '../../../helpers/StripeHelper.js';
|
|
14
|
+
import { AuditLogService } from '../../../services/AuditLogService.js';
|
|
15
|
+
import { UitpasService } from '../../../services/uitpas/UitpasService.js';
|
|
16
|
+
import { ServiceFeeHelper } from '../../../helpers/ServiceFeeHelper.js';
|
|
17
|
+
import { PaymentService } from '../../../services/PaymentService.js';
|
|
17
18
|
|
|
18
19
|
type Params = { id: string };
|
|
19
20
|
type Query = undefined;
|
|
@@ -154,7 +155,7 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
// The order now is valid, the stock is reserved for now (until the payment fails or expires)
|
|
157
|
-
|
|
158
|
+
let totalPrice = request.body.totalPrice;
|
|
158
159
|
|
|
159
160
|
if (totalPrice % 100 !== 0) {
|
|
160
161
|
throw new SimpleError({
|
|
@@ -178,6 +179,8 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
178
179
|
payment.method = request.body.paymentMethod;
|
|
179
180
|
payment.status = PaymentStatus.Created;
|
|
180
181
|
payment.price = totalPrice;
|
|
182
|
+
PaymentService.round(payment);
|
|
183
|
+
totalPrice = payment.price;
|
|
181
184
|
payment.paidAt = null;
|
|
182
185
|
payment.customer = PaymentCustomer.create({
|
|
183
186
|
firstName: request.body.customer.firstName,
|
|
@@ -131,7 +131,7 @@ export const baseMemberColumns: XlsxTransformerColumn<PlatformMember>[] = [
|
|
|
131
131
|
name: $t(`030be384-9014-410c-87ba-e04920c26111`),
|
|
132
132
|
width: 20,
|
|
133
133
|
getValue: ({ patchedMember: object }: PlatformMember) => ({
|
|
134
|
-
value: XlsxTransformerColumnHelper.formatBoolean(object.details.
|
|
134
|
+
value: XlsxTransformerColumnHelper.formatBoolean(object.details.hasFinancialSupportOrActiveUitpas),
|
|
135
135
|
}),
|
|
136
136
|
},
|
|
137
137
|
{
|
|
@@ -56,7 +56,11 @@ export class AdminPermissionChecker {
|
|
|
56
56
|
if (!result) {
|
|
57
57
|
console.error('Unexpected missing organization in AdminPermissionChecker.getOrganization', id);
|
|
58
58
|
this.organizationCache.delete(id);
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
throw new SimpleError({
|
|
61
|
+
code: 'organization_not_found',
|
|
62
|
+
message: 'Organization not found',
|
|
63
|
+
});
|
|
60
64
|
}
|
|
61
65
|
this.organizationCache.set(id, result);
|
|
62
66
|
return result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { AuditLog, BalanceItem, CachedBalance, Document, Event, EventNotification, Group, Invoice, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
|
|
3
|
-
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, GroupType, InvoicedBalanceItem, InvoiceStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MembersBlob, MemberWithRegistrationsBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, RegistrationsBlob, RegistrationWithMemberBlob, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, Company, DetailedReceivableBalance, Document as DocumentStruct, EventNotification as EventNotificationStruct, Event as EventStruct, GenericBalance, Group as GroupStruct, GroupType, InvoicedBalanceItem, InvoiceStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MembersBlob, MemberWithRegistrationsBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentCustomer, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, RegistrationsBlob, RegistrationWithMemberBlob, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
4
|
import { Sorter } from '@stamhoofd/utility';
|
|
5
5
|
|
|
6
6
|
import { SQL } from '@stamhoofd/sql';
|
|
@@ -896,10 +896,67 @@ export class AuthenticatedStructures {
|
|
|
896
896
|
|
|
897
897
|
const result: { balance: CachedBalance; object: ReceivableBalanceObject }[] = [];
|
|
898
898
|
|
|
899
|
+
function getMemberContacts(member: Member, balance: CachedBalance) {
|
|
900
|
+
const url = Context.organization && Context.organization.id === balance.organizationId ? 'https://' + Context.organization.getHost() : '';
|
|
901
|
+
return [
|
|
902
|
+
...(member.details.getMemberEmails().length
|
|
903
|
+
? [
|
|
904
|
+
ReceivableBalanceObjectContact.create({
|
|
905
|
+
firstName: member.details.firstName ?? '',
|
|
906
|
+
lastName: member.details.lastName ?? '',
|
|
907
|
+
emails: member.details.getMemberEmails(),
|
|
908
|
+
meta: {
|
|
909
|
+
type: 'member',
|
|
910
|
+
responsibilityIds: [],
|
|
911
|
+
url,
|
|
912
|
+
},
|
|
913
|
+
}),
|
|
914
|
+
]
|
|
915
|
+
: []),
|
|
916
|
+
|
|
917
|
+
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
918
|
+
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
919
|
+
firstName: p.firstName ?? '',
|
|
920
|
+
lastName: p.lastName ?? '',
|
|
921
|
+
emails: p.getEmails(),
|
|
922
|
+
meta: {
|
|
923
|
+
type: 'parent',
|
|
924
|
+
responsibilityIds: [],
|
|
925
|
+
url,
|
|
926
|
+
},
|
|
927
|
+
}))
|
|
928
|
+
: []),
|
|
929
|
+
];
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function getMemberCustomers(member: Member): PaymentCustomer[] {
|
|
933
|
+
return [
|
|
934
|
+
...(member.details.defaultAge >= 14 || member.details.parents.length === 0 || !member.details.calculatedParentsHaveAccess
|
|
935
|
+
? [
|
|
936
|
+
PaymentCustomer.create({
|
|
937
|
+
firstName: member.details.firstName ?? '',
|
|
938
|
+
lastName: member.details.lastName ?? '',
|
|
939
|
+
email: member.details.getMemberEmails()[0] ?? null,
|
|
940
|
+
phone: member.details.phone,
|
|
941
|
+
}),
|
|
942
|
+
]
|
|
943
|
+
: []),
|
|
944
|
+
|
|
945
|
+
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
946
|
+
? member.details.parents.map(parent => PaymentCustomer.create({
|
|
947
|
+
firstName: parent.firstName ?? '',
|
|
948
|
+
lastName: parent.lastName ?? '',
|
|
949
|
+
email: parent.getEmails()[0] ?? null,
|
|
950
|
+
phone: parent.phone,
|
|
951
|
+
}))
|
|
952
|
+
: []),
|
|
953
|
+
];
|
|
954
|
+
}
|
|
955
|
+
|
|
899
956
|
for (const balance of balances) {
|
|
900
957
|
let object = ReceivableBalanceObject.create({
|
|
901
958
|
id: balance.objectId,
|
|
902
|
-
name: '
|
|
959
|
+
name: $t('6c3e777c-7cd6-4566-9540-8a829c26212f'),
|
|
903
960
|
});
|
|
904
961
|
|
|
905
962
|
if (balance.objectType === ReceivableBalanceType.organization) {
|
|
@@ -920,6 +977,7 @@ export class AuthenticatedStructures {
|
|
|
920
977
|
id: balance.objectId,
|
|
921
978
|
name: organization.name,
|
|
922
979
|
uri: organization.uri,
|
|
980
|
+
customers: organization.defaultCompanies.map(company => PaymentCustomer.create({ company })),
|
|
923
981
|
contacts: thisMembers.map(({ member, responsibilities }) => ReceivableBalanceObjectContact.create({
|
|
924
982
|
firstName: member.firstName ?? '',
|
|
925
983
|
lastName: member.lastName ?? '',
|
|
@@ -936,39 +994,11 @@ export class AuthenticatedStructures {
|
|
|
936
994
|
else if (balance.objectType === ReceivableBalanceType.member) {
|
|
937
995
|
const member = members.find(m => m.id === balance.objectId) ?? null;
|
|
938
996
|
if (member) {
|
|
939
|
-
const url = Context.organization && Context.organization.id === balance.organizationId ? 'https://' + Context.organization.getHost() : '';
|
|
940
997
|
object = ReceivableBalanceObject.create({
|
|
941
998
|
id: balance.objectId,
|
|
942
999
|
name: member.details.name,
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
? [
|
|
946
|
-
ReceivableBalanceObjectContact.create({
|
|
947
|
-
firstName: member.details.firstName ?? '',
|
|
948
|
-
lastName: member.details.lastName ?? '',
|
|
949
|
-
emails: member.details.getMemberEmails(),
|
|
950
|
-
meta: {
|
|
951
|
-
type: 'member',
|
|
952
|
-
responsibilityIds: [],
|
|
953
|
-
url,
|
|
954
|
-
},
|
|
955
|
-
}),
|
|
956
|
-
]
|
|
957
|
-
: []),
|
|
958
|
-
|
|
959
|
-
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
960
|
-
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
961
|
-
firstName: p.firstName ?? '',
|
|
962
|
-
lastName: p.lastName ?? '',
|
|
963
|
-
emails: p.getEmails(),
|
|
964
|
-
meta: {
|
|
965
|
-
type: 'parent',
|
|
966
|
-
responsibilityIds: [],
|
|
967
|
-
url,
|
|
968
|
-
},
|
|
969
|
-
}))
|
|
970
|
-
: []),
|
|
971
|
-
],
|
|
1000
|
+
customers: getMemberCustomers(member),
|
|
1001
|
+
contacts: getMemberContacts(member, balance),
|
|
972
1002
|
});
|
|
973
1003
|
}
|
|
974
1004
|
}
|
|
@@ -979,39 +1009,11 @@ export class AuthenticatedStructures {
|
|
|
979
1009
|
}
|
|
980
1010
|
const member = members.find(m => m.id === registration.memberId) ?? null;
|
|
981
1011
|
if (member) {
|
|
982
|
-
const url = Context.organization && Context.organization.id === balance.organizationId ? 'https://' + Context.organization.getHost() : '';
|
|
983
1012
|
object = ReceivableBalanceObject.create({
|
|
984
1013
|
id: balance.objectId,
|
|
985
1014
|
name: member.details.name,
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
? [
|
|
989
|
-
ReceivableBalanceObjectContact.create({
|
|
990
|
-
firstName: member.details.firstName ?? '',
|
|
991
|
-
lastName: member.details.lastName ?? '',
|
|
992
|
-
emails: member.details.getMemberEmails(),
|
|
993
|
-
meta: {
|
|
994
|
-
type: 'member',
|
|
995
|
-
responsibilityIds: [],
|
|
996
|
-
url,
|
|
997
|
-
},
|
|
998
|
-
}),
|
|
999
|
-
]
|
|
1000
|
-
: []),
|
|
1001
|
-
|
|
1002
|
-
...((member.details.calculatedParentsHaveAccess || member.details.getMemberEmails().length === 0)
|
|
1003
|
-
? member.details.parents.filter(p => p.getEmails().length > 0).map(p => ReceivableBalanceObjectContact.create({
|
|
1004
|
-
firstName: p.firstName ?? '',
|
|
1005
|
-
lastName: p.lastName ?? '',
|
|
1006
|
-
emails: p.getEmails(),
|
|
1007
|
-
meta: {
|
|
1008
|
-
type: 'parent',
|
|
1009
|
-
responsibilityIds: [],
|
|
1010
|
-
url,
|
|
1011
|
-
},
|
|
1012
|
-
}))
|
|
1013
|
-
: []),
|
|
1014
|
-
],
|
|
1015
|
+
customers: getMemberCustomers(member),
|
|
1016
|
+
contacts: getMemberContacts(member, balance),
|
|
1015
1017
|
});
|
|
1016
1018
|
}
|
|
1017
1019
|
}
|
|
@@ -1022,6 +1024,11 @@ export class AuthenticatedStructures {
|
|
|
1022
1024
|
object = ReceivableBalanceObject.create({
|
|
1023
1025
|
id: balance.objectId,
|
|
1024
1026
|
name: user.name || user.email,
|
|
1027
|
+
customers: [PaymentCustomer.create({
|
|
1028
|
+
firstName: user.firstName,
|
|
1029
|
+
lastName: user.lastName,
|
|
1030
|
+
email: user.email,
|
|
1031
|
+
})],
|
|
1025
1032
|
contacts: [
|
|
1026
1033
|
ReceivableBalanceObjectContact.create({
|
|
1027
1034
|
firstName: user.firstName ?? '',
|
package/src/helpers/Context.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
6
6
|
|
|
7
7
|
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
8
8
|
import { ApiUserRateLimits } from '@stamhoofd/structures';
|
|
9
|
-
import { AdminPermissionChecker } from './AdminPermissionChecker';
|
|
9
|
+
import { AdminPermissionChecker } from './AdminPermissionChecker.js';
|
|
10
10
|
|
|
11
11
|
export const apiUserRateLimiter = new RateLimiter({
|
|
12
12
|
limits: [
|
|
@@ -98,7 +98,8 @@ export class UitpasTokenRepository {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
private async getNewAccessToken() {
|
|
101
|
-
|
|
101
|
+
console.log('UITPAS: Fetching new access token for', this.uitpasClientCredential.organizationId);
|
|
102
|
+
const url = STAMHOOFD.UITPAS_API_URL?.includes('test') ? 'https://account-test.uitid.be/realms/uitid/protocol/openid-connect/token' : 'https://account.uitid.be/realms/uitid/protocol/openid-connect/token';
|
|
102
103
|
const myHeaders = new Headers();
|
|
103
104
|
myHeaders.append('Content-Type', 'application/x-www-form-urlencoded');
|
|
104
105
|
const params = new URLSearchParams({
|
|
@@ -110,6 +111,7 @@ export class UitpasTokenRepository {
|
|
|
110
111
|
method: 'POST',
|
|
111
112
|
headers: myHeaders,
|
|
112
113
|
body: params.toString(),
|
|
114
|
+
signal: AbortSignal.timeout(5000),
|
|
113
115
|
};
|
|
114
116
|
const response = await fetch(url, requestOptions).catch(() => {
|
|
115
117
|
// Handle network errors
|
|
@@ -123,6 +125,7 @@ export class UitpasTokenRepository {
|
|
|
123
125
|
if (response.status === 401) {
|
|
124
126
|
// Unauthorized, credentials are invalid
|
|
125
127
|
throw new SimpleError({
|
|
128
|
+
statusCode: this.uitpasClientCredential.organizationId === null ? 500 : 400, // Internal, non visible error in case it is a built in credential
|
|
126
129
|
code: 'invalid_uitpas_client_credentials',
|
|
127
130
|
message: `Invalid UiTPAS client credentials`,
|
|
128
131
|
human: $t(`1086bb24-5df4-4faf-9dc0-ab5a955b0d8f`),
|
|
@@ -130,6 +133,7 @@ export class UitpasTokenRepository {
|
|
|
130
133
|
}
|
|
131
134
|
console.error(`Unsuccessful response when fetching UiTPAS token for organization with id ${this.uitpasClientCredential.organizationId}:`, response.statusText);
|
|
132
135
|
throw new SimpleError({
|
|
136
|
+
statusCode: this.uitpasClientCredential.organizationId === null ? 500 : 400, // Internal, non visible error in case it is a built in credential
|
|
133
137
|
code: 'unsuccessful_response_fetching_uitpas_token',
|
|
134
138
|
message: `Unsuccesful response when fetching UiTPAS token`,
|
|
135
139
|
human: $t(`dd9b30ca-860f-47aa-8cb1-527fd156d9ca`),
|
|
@@ -138,6 +142,7 @@ export class UitpasTokenRepository {
|
|
|
138
142
|
const json: unknown = await response.json().catch(() => {
|
|
139
143
|
// Handle JSON parsing errors
|
|
140
144
|
throw new SimpleError({
|
|
145
|
+
statusCode: this.uitpasClientCredential.organizationId === null ? 500 : 400, // Internal, non visible error in case it is a built in credential
|
|
141
146
|
code: 'invalid_json_fetching_uitpas_token',
|
|
142
147
|
message: `Invalid json when fetching UiTPAS token`,
|
|
143
148
|
human: $t(`8f217db0-c672-46f0-a8f7-6eba6f080947`),
|
|
@@ -5,7 +5,7 @@ import { Member } from '@stamhoofd/models';
|
|
|
5
5
|
import { SQL } from '@stamhoofd/sql';
|
|
6
6
|
import { UitpasSocialTariff, UitpasSocialTariffStatus } from '@stamhoofd/structures';
|
|
7
7
|
import { sleep } from '@stamhoofd/utility';
|
|
8
|
-
import { updateMemberDetailsUitpasNumber } from '
|
|
8
|
+
import { updateMemberDetailsUitpasNumber } from '../helpers/updateMemberDetailsUitpasNumber.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Seed to update the social tariff of all uitpas numbers.
|
|
@@ -34,17 +34,15 @@ let idOfLastUpdatedMember: string | null = null;
|
|
|
34
34
|
|
|
35
35
|
export async function migrateUitpasStatusOfAllMembers() {
|
|
36
36
|
let query = Member.select()
|
|
37
|
-
|
|
38
|
-
.where(SQL.jsonValue(SQL.column('details'), '$.value.
|
|
37
|
+
// where there is an uitpas number
|
|
38
|
+
.where(SQL.jsonValue(SQL.column('details'), '$.value.uitpasNumber'), '!=', null)
|
|
39
|
+
.orWhere(SQL.jsonValue(SQL.column('details'), '$.value.uitpasNumberDetails'), '!=', null);
|
|
39
40
|
|
|
40
41
|
if (idOfLastUpdatedMember !== null) {
|
|
41
42
|
console.log('Continue from member with id ', idOfLastUpdatedMember);
|
|
42
43
|
query = query.where('id', '>', idOfLastUpdatedMember);
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
const total = await query.clone().count();
|
|
46
|
-
|
|
47
|
-
console.log(`Start updating uitpas status of ${total} members.`);
|
|
45
|
+
console.log(`Start updating uitpas status members.`);
|
|
48
46
|
|
|
49
47
|
let c = 0;
|
|
50
48
|
|
|
@@ -341,7 +341,7 @@ export const PaymentService = {
|
|
|
341
341
|
*
|
|
342
342
|
* TODO: update this method to generate a virtual invoice and use the price of the invoice instead of the rounded payment price, so we don't get differences in calculation
|
|
343
343
|
*/
|
|
344
|
-
|
|
344
|
+
round(payment: Payment) {
|
|
345
345
|
const amount = payment.price;
|
|
346
346
|
const rounded = Payment.roundPrice(payment.price);
|
|
347
347
|
const difference = rounded - amount;
|
|
@@ -354,10 +354,9 @@ export const PaymentService = {
|
|
|
354
354
|
throw new Error('Unexpected rounding difference of ' + difference + ' for payment ' + payment.id);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
payment.roundingAmount = difference;
|
|
358
358
|
|
|
359
359
|
// Change payment total price
|
|
360
360
|
payment.price += difference;
|
|
361
|
-
await payment.save();
|
|
362
361
|
},
|
|
363
362
|
};
|
|
@@ -5,6 +5,7 @@ import { Formatter } from '@stamhoofd/utility';
|
|
|
5
5
|
import { memberCachedBalanceForOrganizationJoin, registrationCachedBalanceJoin } from '../helpers/outstandingBalanceJoin.js';
|
|
6
6
|
import { SQLTranslatedString } from '../helpers/SQLTranslatedString.js';
|
|
7
7
|
import { groupJoin, memberJoin, organizationJoin } from '../sql-filters/registrations.js';
|
|
8
|
+
import { Context, ContextInstance } from '../helpers/Context.js';
|
|
8
9
|
|
|
9
10
|
export class RegistrationSortData {
|
|
10
11
|
readonly registration: RegistrationWithMemberBlob;
|
|
@@ -17,8 +18,11 @@ export class RegistrationSortData {
|
|
|
17
18
|
|
|
18
19
|
get organization() {
|
|
19
20
|
const organization = this.organizations.find(o => o.id === this.registration.organizationId);
|
|
21
|
+
if (!organization && ContextInstance.optional?.organization && ContextInstance.optional?.organization.id === this.registration.organizationId) {
|
|
22
|
+
return Context.organization!;
|
|
23
|
+
}
|
|
20
24
|
if (!organization) {
|
|
21
|
-
throw new Error('Organization not found for registration');
|
|
25
|
+
throw new Error('Organization not found for registration ' + this.registration.id);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
return organization;
|