@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.112.0",
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.112.0",
58
- "@stamhoofd/backend-middleware": "2.112.0",
59
- "@stamhoofd/email": "2.112.0",
60
- "@stamhoofd/models": "2.112.0",
61
- "@stamhoofd/queues": "2.112.0",
62
- "@stamhoofd/sql": "2.112.0",
63
- "@stamhoofd/structures": "2.112.0",
64
- "@stamhoofd/utility": "2.112.0",
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": "8734fff2c389052210c5da51bd3c4beaf149bfd1"
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
- const totalPrice = request.body.totalPrice;
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.requiresFinancialSupport?.value),
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
- throw new Error('Unexpected missing organization in AdminPermissionChecker.getOrganization');
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: 'Onbekend',
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
- contacts: [
944
- ...(member.details.getMemberEmails().length
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
- contacts: [
987
- ...(member.details.getMemberEmails().length
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 ?? '',
@@ -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
- const url = 'https://account-test.uitid.be/realms/uitid/protocol/openid-connect/token';
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 '../../helpers/updateMemberDetailsUitpasNumber.js';
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
- // where there is an uitpas number
38
- .where(SQL.jsonValue(SQL.column('details'), '$.value.uitpasNumberDetails'), '!=', null);
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
- async round(payment: Payment) {
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
- // payment.roundingAmount = difference;
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;