@stamhoofd/models 2.83.4 → 2.84.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.
Files changed (108) hide show
  1. package/dist/src/factories/BalanceItemFactory.d.ts +3 -1
  2. package/dist/src/factories/BalanceItemFactory.d.ts.map +1 -1
  3. package/dist/src/factories/BalanceItemFactory.js +6 -0
  4. package/dist/src/factories/BalanceItemFactory.js.map +1 -1
  5. package/dist/src/factories/DocumentTemplateFactory.d.ts +15 -0
  6. package/dist/src/factories/DocumentTemplateFactory.d.ts.map +1 -0
  7. package/dist/src/factories/DocumentTemplateFactory.js +125 -0
  8. package/dist/src/factories/DocumentTemplateFactory.js.map +1 -0
  9. package/dist/src/factories/EventFactory.d.ts +2 -0
  10. package/dist/src/factories/EventFactory.d.ts.map +1 -1
  11. package/dist/src/factories/EventFactory.js +5 -0
  12. package/dist/src/factories/EventFactory.js.map +1 -1
  13. package/dist/src/factories/GroupFactory.d.ts +7 -2
  14. package/dist/src/factories/GroupFactory.d.ts.map +1 -1
  15. package/dist/src/factories/GroupFactory.js +30 -5
  16. package/dist/src/factories/GroupFactory.js.map +1 -1
  17. package/dist/src/factories/MemberFactory.d.ts.map +1 -1
  18. package/dist/src/factories/MemberFactory.js +3 -1
  19. package/dist/src/factories/MemberFactory.js.map +1 -1
  20. package/dist/src/factories/RegistrationFactory.d.ts +3 -1
  21. package/dist/src/factories/RegistrationFactory.d.ts.map +1 -1
  22. package/dist/src/factories/RegistrationFactory.js +2 -0
  23. package/dist/src/factories/RegistrationFactory.js.map +1 -1
  24. package/dist/src/factories/UserFactory.js +1 -1
  25. package/dist/src/factories/UserFactory.js.map +1 -1
  26. package/dist/src/factories/index.d.ts +1 -0
  27. package/dist/src/factories/index.d.ts.map +1 -1
  28. package/dist/src/factories/index.js +1 -0
  29. package/dist/src/factories/index.js.map +1 -1
  30. package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
  31. package/dist/src/helpers/EmailBuilder.js +3 -1
  32. package/dist/src/helpers/EmailBuilder.js.map +1 -1
  33. package/dist/src/migrations/1747913433-registration-discounts.sql +2 -0
  34. package/dist/src/migrations/1747996262-balance-items-paid-at.sql +2 -0
  35. package/dist/src/migrations/1747996263-balance-items-paid-at-fill.sql +1 -0
  36. package/dist/src/models/BalanceItem.d.ts +9 -8
  37. package/dist/src/models/BalanceItem.d.ts.map +1 -1
  38. package/dist/src/models/BalanceItem.js +36 -42
  39. package/dist/src/models/BalanceItem.js.map +1 -1
  40. package/dist/src/models/CachedBalance.d.ts.map +1 -1
  41. package/dist/src/models/CachedBalance.js +3 -0
  42. package/dist/src/models/CachedBalance.js.map +1 -1
  43. package/dist/src/models/Document.d.ts.map +1 -1
  44. package/dist/src/models/Document.js +9 -1
  45. package/dist/src/models/Document.js.map +1 -1
  46. package/dist/src/models/Email.d.ts.map +1 -1
  47. package/dist/src/models/Email.js +10 -3
  48. package/dist/src/models/Email.js.map +1 -1
  49. package/dist/src/models/Email.test.js +40 -40
  50. package/dist/src/models/Email.test.js.map +1 -1
  51. package/dist/src/models/Group.d.ts +1 -1
  52. package/dist/src/models/Group.d.ts.map +1 -1
  53. package/dist/src/models/Group.js +9 -4
  54. package/dist/src/models/Group.js.map +1 -1
  55. package/dist/src/models/Image.d.ts +3 -0
  56. package/dist/src/models/Image.d.ts.map +1 -1
  57. package/dist/src/models/Image.js +28 -17
  58. package/dist/src/models/Image.js.map +1 -1
  59. package/dist/src/models/Member.d.ts +3 -3
  60. package/dist/src/models/Member.d.ts.map +1 -1
  61. package/dist/src/models/Member.js +2 -2
  62. package/dist/src/models/Member.js.map +1 -1
  63. package/dist/src/models/MemberPlatformMembership.js +1 -1
  64. package/dist/src/models/MemberPlatformMembership.js.map +1 -1
  65. package/dist/src/models/Organization.d.ts.map +1 -1
  66. package/dist/src/models/Organization.js +26 -24
  67. package/dist/src/models/Organization.js.map +1 -1
  68. package/dist/src/models/PayconiqPayment.d.ts.map +1 -1
  69. package/dist/src/models/PayconiqPayment.js +6 -1
  70. package/dist/src/models/PayconiqPayment.js.map +1 -1
  71. package/dist/src/models/Platform.d.ts.map +1 -1
  72. package/dist/src/models/Platform.js +5 -3
  73. package/dist/src/models/Platform.js.map +1 -1
  74. package/dist/src/models/Registration.d.ts +11 -1
  75. package/dist/src/models/Registration.d.ts.map +1 -1
  76. package/dist/src/models/Registration.js +14 -1
  77. package/dist/src/models/Registration.js.map +1 -1
  78. package/dist/src/models/User.d.ts.map +1 -1
  79. package/dist/src/models/User.js +1 -1
  80. package/dist/src/models/User.js.map +1 -1
  81. package/dist/tsconfig.tsbuildinfo +1 -1
  82. package/package.json +5 -5
  83. package/src/factories/BalanceItemFactory.ts +7 -1
  84. package/src/factories/DocumentTemplateFactory.ts +130 -0
  85. package/src/factories/EventFactory.ts +7 -0
  86. package/src/factories/GroupFactory.ts +35 -8
  87. package/src/factories/MemberFactory.ts +3 -1
  88. package/src/factories/RegistrationFactory.ts +9 -2
  89. package/src/factories/UserFactory.ts +1 -1
  90. package/src/factories/index.ts +1 -0
  91. package/src/helpers/EmailBuilder.ts +3 -1
  92. package/src/migrations/1747913433-registration-discounts.sql +2 -0
  93. package/src/migrations/1747996262-balance-items-paid-at.sql +2 -0
  94. package/src/migrations/1747996263-balance-items-paid-at-fill.sql +1 -0
  95. package/src/models/BalanceItem.ts +39 -54
  96. package/src/models/CachedBalance.ts +4 -0
  97. package/src/models/Document.ts +9 -1
  98. package/src/models/Email.test.ts +40 -40
  99. package/src/models/Email.ts +12 -4
  100. package/src/models/Group.ts +12 -4
  101. package/src/models/Image.ts +32 -18
  102. package/src/models/Member.ts +3 -3
  103. package/src/models/MemberPlatformMembership.ts +1 -1
  104. package/src/models/Organization.ts +35 -27
  105. package/src/models/PayconiqPayment.ts +7 -1
  106. package/src/models/Platform.ts +6 -3
  107. package/src/models/Registration.ts +14 -2
  108. package/src/models/User.ts +2 -2
@@ -4,9 +4,9 @@ import { I18n } from '@stamhoofd/backend-i18n';
4
4
  import { EmailInterfaceRecipient } from '@stamhoofd/email';
5
5
  import { QueryableModel } from '@stamhoofd/sql';
6
6
  import { Address, Country, DNSRecordStatus, EmailTemplateType, Language, OrganizationEmail, OrganizationMetaData, OrganizationPrivateMetaData, Organization as OrganizationStruct, PaymentMethod, PaymentProvider, PrivatePaymentConfiguration, Recipient, Replacement, STPackageType, TransferSettings } from '@stamhoofd/structures';
7
- import { AWSError } from 'aws-sdk';
8
- import SES from 'aws-sdk/clients/sesv2';
9
- import { PromiseResult } from 'aws-sdk/lib/request';
7
+
8
+ import { CreateEmailIdentityCommand, DeleteEmailIdentityCommand, GetEmailIdentityCommand, GetEmailIdentityCommandOutput, PutEmailIdentityFeedbackAttributesCommand, PutEmailIdentityMailFromAttributesCommand, SESv2Client } from '@aws-sdk/client-sesv2';
9
+
10
10
  import { v4 as uuidv4 } from 'uuid';
11
11
 
12
12
  import { QueueHandler } from '@stamhoofd/queues';
@@ -480,28 +480,27 @@ export class Organization extends QueryableModel {
480
480
  return;
481
481
  }
482
482
 
483
- const sesv2 = new SES();
483
+ const client = new SESv2Client({});
484
484
 
485
485
  // Check if mail identitiy already exists..
486
- let exists = false;
487
- let existing: PromiseResult<SES.GetEmailIdentityResponse, AWSError> | undefined = undefined;
488
486
  try {
489
- existing = await sesv2.getEmailIdentity({
487
+ const cmd = new GetEmailIdentityCommand({
490
488
  EmailIdentity: mailDomain,
491
- }).promise();
492
- exists = true;
489
+ });
490
+ const result = await client.send(cmd);
493
491
 
494
- // Check if DKIM keys are the same
495
- if (existing.VerifiedForSendingStatus === true) {
492
+ if (result.VerifiedForSendingStatus === true) {
496
493
  console.log('Cant delete AWS mail idenitiy @' + this.id + ' for ' + mailDomain + ': already validated and might be in use by other organizations');
497
494
  return;
498
495
  }
499
496
 
500
- console.log('Deleting AWS mail idenitiy @' + this.id + ' for ' + mailDomain);
497
+ console.log('Deleting AWS mail identity @' + this.id + ' for ' + mailDomain);
501
498
 
502
- await sesv2.deleteEmailIdentity({
499
+ const deleteCmd = new DeleteEmailIdentityCommand({
503
500
  EmailIdentity: mailDomain,
504
- }).promise();
501
+ });
502
+
503
+ await client.send(deleteCmd);
505
504
  console.log('Deleted AWS mail idenitiy @' + this.id + ' for ' + this.privateMeta.mailDomain);
506
505
  }
507
506
  catch (e) {
@@ -531,16 +530,18 @@ export class Organization extends QueryableModel {
531
530
  return;
532
531
  }
533
532
 
534
- const sesv2 = new SES();
533
+ const client = new SESv2Client({});
535
534
  const expectedConfigurationSetName = Formatter.slug(STAMHOOFD.platformName + '-domains');
536
535
 
537
536
  // Check if mail identitiy already exists..
538
537
  let exists = false;
539
- let existing: PromiseResult<SES.GetEmailIdentityResponse, AWSError> | undefined = undefined;
538
+ let existing: GetEmailIdentityCommandOutput | undefined = undefined;
540
539
  try {
541
- existing = await sesv2.getEmailIdentity({
540
+ const cmd = new GetEmailIdentityCommand({
542
541
  EmailIdentity: this.privateMeta.mailDomain,
543
- }).promise();
542
+ });
543
+
544
+ existing = await client.send(cmd);
544
545
  exists = true;
545
546
 
546
547
  console.log('AWS mail idenitiy exists already: just checking the verification status in AWS @' + this.id);
@@ -560,9 +561,11 @@ export class Organization extends QueryableModel {
560
561
 
561
562
  if (existing.VerifiedForSendingStatus !== true && existing.DkimAttributes?.Status === 'FAILED') {
562
563
  console.error('AWS failed to verify DKIM records. Triggering a forced recheck @' + this.id);
563
- await sesv2.deleteEmailIdentity({
564
+
565
+ const deleteCmd = new DeleteEmailIdentityCommand({
564
566
  EmailIdentity: this.privateMeta.mailDomain,
565
- }).promise();
567
+ });
568
+ await client.send(deleteCmd);
566
569
 
567
570
  // Recreate it immediately
568
571
  exists = false;
@@ -575,7 +578,7 @@ export class Organization extends QueryableModel {
575
578
  if (!exists) {
576
579
  console.log('Creating email identity in AWS SES...');
577
580
 
578
- const result = await sesv2.createEmailIdentity({
581
+ const cmd = new CreateEmailIdentityCommand({
579
582
  EmailIdentity: this.privateMeta.mailDomain,
580
583
  ConfigurationSetName: expectedConfigurationSetName,
581
584
  DkimSigningAttributes: {
@@ -592,27 +595,32 @@ export class Organization extends QueryableModel {
592
595
  Value: STAMHOOFD.environment ?? 'Unknown',
593
596
  },
594
597
  ],
598
+ });
595
599
 
596
- }).promise();
600
+ const result = await client.send(cmd);
597
601
  this.privateMeta.mailDomainActive = result.VerifiedForSendingStatus ?? false;
598
602
 
599
603
  // Disable email forwarding of bounces and complaints
600
604
  // We handle this now with the configuration set
601
- await sesv2.putEmailIdentityFeedbackAttributes({
605
+ const putFeedbackCmd = new PutEmailIdentityFeedbackAttributesCommand({
602
606
  EmailIdentity: this.privateMeta.mailDomain,
603
607
  EmailForwardingEnabled: false,
604
- }).promise();
608
+ });
609
+
610
+ await client.send(putFeedbackCmd);
605
611
  }
606
612
 
607
613
  if (this.privateMeta.mailFromDomain && (!exists || (existing && (!existing.MailFromAttributes || existing.MailFromAttributes.MailFromDomain !== this.privateMeta.mailFromDomain)))) {
608
614
  // Also set a from domain, to fix SPF
609
615
  console.log('Setting mail from domain: ' + this.privateMeta.mailFromDomain + ' for ' + this.id);
610
- const params = {
616
+
617
+ const cmd = new PutEmailIdentityMailFromAttributesCommand({
611
618
  EmailIdentity: this.privateMeta.mailDomain,
612
619
  BehaviorOnMxFailure: 'USE_DEFAULT_VALUE',
613
620
  MailFromDomain: this.privateMeta.mailFromDomain,
614
- };
615
- await sesv2.putEmailIdentityMailFromAttributes(params).promise();
621
+ });
622
+
623
+ await client.send(cmd);
616
624
  }
617
625
  }
618
626
 
@@ -161,7 +161,13 @@ export class PayconiqPayment extends QueryableModel {
161
161
  private static request(method: string, path: string, data = {}, auth: string | null = null, testMode: boolean): Promise<any> {
162
162
  return new Promise((resolve, reject) => {
163
163
  const jsonData = JSON.stringify(data);
164
- const hostname = !testMode ? 'api.payconiq.com' : 'api.ext.payconiq.com';
164
+
165
+ // Payconiq switches to Wero on 2025-09-21
166
+ const isWero = Date.now() > new Date('2025-09-21T02:00:00+02:00').getTime();
167
+ let hostname = !testMode ? 'api.payconiq.com' : 'api.ext.payconiq.com';
168
+ if (isWero) {
169
+ hostname = !testMode ? 'merchant.api.bancontact.net' : 'merchant.api.preprod.bancontact.net';
170
+ }
165
171
  const base = 'https://' + hostname;
166
172
 
167
173
  // Log all communication
@@ -89,6 +89,11 @@ export class Platform extends QueryableModel {
89
89
  }
90
90
 
91
91
  static async getShared(): Promise<Readonly<Platform> & { save: never }> {
92
+ if (this.shared) {
93
+ // Skip queue if possible (performance optimization)
94
+ return this.shared as any;
95
+ }
96
+
92
97
  return QueueHandler.schedule('Platform.getShared', async () => {
93
98
  if (this.shared) {
94
99
  return this.shared;
@@ -141,9 +146,7 @@ export class Platform extends QueryableModel {
141
146
  this.sharedStruct = null;
142
147
  this.sharedPrivateStruct = null;
143
148
  });
144
- await QueueHandler.schedule('Platform.getShared', async () => {
145
- this.shared = null;
146
- });
149
+ this.shared = null;
147
150
  }
148
151
 
149
152
  async save() {
@@ -1,5 +1,5 @@
1
1
  import { column, Database, ManyToOneRelation } from '@simonbackx/simple-database';
2
- import { EmailTemplateType, GroupPrice, PaymentMethod, PaymentMethodHelper, Recipient, RecordAnswer, RecordAnswerDecoder, RegisterItemOption, Registration as RegistrationStructure, Replacement, StockReservation } from '@stamhoofd/structures';
2
+ import { AppliedRegistrationDiscount, EmailTemplateType, GroupPrice, PaymentMethod, PaymentMethodHelper, Recipient, RecordAnswer, RecordAnswerDecoder, RegisterItemOption, Registration as RegistrationStructure, Replacement, StockReservation } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
 
@@ -130,9 +130,21 @@ export class Registration extends QueryableModel {
130
130
  /**
131
131
  * Set to null if no reservations are made, to help faster querying
132
132
  */
133
- @column({ type: 'json', decoder: new ArrayDecoder(StockReservation), nullable: true })
133
+ @column({ type: 'json', decoder: new ArrayDecoder(StockReservation) })
134
134
  stockReservations: StockReservation[] = [];
135
135
 
136
+ /**
137
+ * Cached value for calculation of discounts of other registrations (based on the single-source-of-truth stored in balance items)
138
+ *
139
+ * Discounts that were applied to this registration.
140
+ * Note that these discounts are saved in separate balance items and
141
+ * are not included in the price.
142
+ *
143
+ * Reason is that discounts can change after you've been registered
144
+ */
145
+ @column({ type: 'json', decoder: new MapDecoder(StringDecoder, AppliedRegistrationDiscount) })
146
+ discounts = new Map<string, AppliedRegistrationDiscount>();
147
+
136
148
  static group: ManyToOneRelation<'group', import('./Group').Group>;
137
149
 
138
150
  getStructure(this: Registration & { group: import('./Group').Group }) {
@@ -1,6 +1,6 @@
1
1
  import { column, Database, ManyToOneRelation } from '@simonbackx/simple-database';
2
2
  import { EmailInterfaceRecipient } from '@stamhoofd/email';
3
- import { QueryableModel, SQL, SQLJSONValue } from '@stamhoofd/sql';
3
+ import { QueryableModel, SQL, SQLJSONNull } from '@stamhoofd/sql';
4
4
  import { LoginProviderType, NewUser, Permissions, Recipient, Replacement, UserMeta, UserPermissions, User as UserStruct } from '@stamhoofd/structures';
5
5
  import argon2 from 'argon2';
6
6
  import { v4 as uuidv4 } from 'uuid';
@@ -125,7 +125,7 @@ export class User extends QueryableModel {
125
125
  .where(
126
126
  SQL.jsonValue(SQL.column('permissions'), '$.value.globalPermissions'),
127
127
  '!=',
128
- new SQLJSONValue(null),
128
+ SQLJSONNull,
129
129
  )
130
130
  .where('id', '!=', '1');
131
131