@stamhoofd/models 2.19.0 → 2.21.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 (69) hide show
  1. package/dist/src/helpers/EmailBuilder.d.ts +12 -3
  2. package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
  3. package/dist/src/helpers/EmailBuilder.js +97 -6
  4. package/dist/src/helpers/EmailBuilder.js.map +1 -1
  5. package/dist/src/models/Member.js.map +1 -1
  6. package/dist/src/models/MemberResponsibilityRecord.d.ts +5 -0
  7. package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -1
  8. package/dist/src/models/MemberResponsibilityRecord.js +16 -1
  9. package/dist/src/models/MemberResponsibilityRecord.js.map +1 -1
  10. package/dist/src/models/Order.d.ts.map +1 -1
  11. package/dist/src/models/Order.js +2 -10
  12. package/dist/src/models/Order.js.map +1 -1
  13. package/dist/src/models/Organization.d.ts +2 -0
  14. package/dist/src/models/Organization.d.ts.map +1 -1
  15. package/dist/src/models/Organization.js +19 -15
  16. package/dist/src/models/Organization.js.map +1 -1
  17. package/dist/src/models/Platform.d.ts +3 -1
  18. package/dist/src/models/Platform.d.ts.map +1 -1
  19. package/dist/src/models/Platform.js.map +1 -1
  20. package/dist/src/models/RegisterCode.d.ts +0 -10
  21. package/dist/src/models/RegisterCode.d.ts.map +1 -1
  22. package/dist/src/models/RegisterCode.js +0 -86
  23. package/dist/src/models/RegisterCode.js.map +1 -1
  24. package/dist/src/models/Registration.d.ts.map +1 -1
  25. package/dist/src/models/Registration.js +16 -27
  26. package/dist/src/models/Registration.js.map +1 -1
  27. package/dist/src/models/STCredit.d.ts +0 -7
  28. package/dist/src/models/STCredit.d.ts.map +1 -1
  29. package/dist/src/models/STCredit.js +0 -69
  30. package/dist/src/models/STCredit.js.map +1 -1
  31. package/dist/src/models/STInvoice.d.ts +2 -20
  32. package/dist/src/models/STInvoice.d.ts.map +1 -1
  33. package/dist/src/models/STInvoice.js +0 -364
  34. package/dist/src/models/STInvoice.js.map +1 -1
  35. package/dist/src/models/STPackage.d.ts.map +1 -1
  36. package/dist/src/models/STPackage.js +2 -12
  37. package/dist/src/models/STPackage.js.map +1 -1
  38. package/dist/src/models/STPendingInvoice.d.ts +1 -21
  39. package/dist/src/models/STPendingInvoice.d.ts.map +1 -1
  40. package/dist/src/models/STPendingInvoice.js +0 -213
  41. package/dist/src/models/STPendingInvoice.js.map +1 -1
  42. package/dist/src/models/UsedRegisterCode.d.ts +0 -5
  43. package/dist/src/models/UsedRegisterCode.d.ts.map +1 -1
  44. package/dist/src/models/UsedRegisterCode.js +0 -101
  45. package/dist/src/models/UsedRegisterCode.js.map +1 -1
  46. package/package.json +2 -2
  47. package/src/helpers/EmailBuilder.ts +112 -11
  48. package/src/models/Member.ts +5 -5
  49. package/src/models/MemberResponsibilityRecord.ts +21 -2
  50. package/src/models/Order.ts +3 -11
  51. package/src/models/Organization.ts +23 -15
  52. package/src/models/Platform.ts +4 -4
  53. package/src/models/RegisterCode.ts +0 -95
  54. package/src/models/Registration.ts +17 -30
  55. package/src/models/STCredit.ts +0 -82
  56. package/src/models/STInvoice.ts +2 -433
  57. package/src/models/STPackage.ts +3 -14
  58. package/src/models/STPendingInvoice.ts +2 -247
  59. package/src/models/UsedRegisterCode.ts +0 -115
  60. package/dist/src/helpers/InvoiceBuilder.d.ts +0 -29
  61. package/dist/src/helpers/InvoiceBuilder.d.ts.map +0 -1
  62. package/dist/src/helpers/InvoiceBuilder.js +0 -407
  63. package/dist/src/helpers/InvoiceBuilder.js.map +0 -1
  64. package/dist/src/helpers/InvoiceBuilder.test.d.ts +0 -2
  65. package/dist/src/helpers/InvoiceBuilder.test.d.ts.map +0 -1
  66. package/dist/src/helpers/InvoiceBuilder.test.js +0 -52
  67. package/dist/src/helpers/InvoiceBuilder.test.js.map +0 -1
  68. package/src/helpers/InvoiceBuilder.test.ts +0 -57
  69. package/src/helpers/InvoiceBuilder.ts +0 -501
@@ -1,14 +1,14 @@
1
- import { EmailAddress, EmailBuilder } from "@stamhoofd/email";
2
- import { EmailTemplateType, Recipient, Replacement } from "@stamhoofd/structures";
1
+ import { Email, EmailAddress, EmailBuilder } from "@stamhoofd/email";
2
+ import { EmailTemplateType, OrganizationEmail, Recipient, Replacement } from "@stamhoofd/structures";
3
3
  import { Formatter } from "@stamhoofd/utility";
4
4
 
5
5
  import { SimpleError } from "@simonbackx/simple-errors";
6
- import { EmailTemplate, Organization, Platform, User } from "../models";
6
+ import { EmailTemplate, Group, Organization, Platform, User, Webshop } from "../models";
7
7
 
8
8
  export type EmailTemplateOptions = {
9
9
  type: EmailTemplateType,
10
- webshopId?: string | null,
11
- groupId?: string | null,
10
+ webshop?: Webshop | null,
11
+ group?: Group | null,
12
12
  organizationId?: string | null
13
13
  }
14
14
 
@@ -17,22 +17,22 @@ export async function getEmailTemplate(data: EmailTemplateOptions) {
17
17
  const q = EmailTemplate.select()
18
18
  .where('type', data.type)
19
19
 
20
- if (data.groupId) {
21
- q.where('groupId', data.groupId)
20
+ if (data.group) {
21
+ q.where('groupId', data.group.id)
22
22
  }
23
23
 
24
24
  if (data.organizationId) {
25
25
  q.where('organizationId', data.organizationId)
26
26
  }
27
27
 
28
- if (data.webshopId) {
29
- q.where('webshopId', data.webshopId)
28
+ if (data.webshop) {
29
+ q.where('webshopId', data.webshop.id)
30
30
  }
31
31
 
32
32
  let templates = await q.limit(1).fetch()
33
33
 
34
34
  // Specific for organization
35
- if (templates.length == 0 && (data.groupId || data.webshopId) && data.organizationId) {
35
+ if (templates.length == 0 && (data.group?.id || data.webshop?.id) && data.organizationId) {
36
36
  templates = await EmailTemplate.select()
37
37
  .where('type', data.type)
38
38
  .where('organizationId', data.organizationId)
@@ -43,7 +43,7 @@ export async function getEmailTemplate(data: EmailTemplateOptions) {
43
43
  }
44
44
 
45
45
  // Default for platform
46
- if (templates.length == 0 && (data.groupId || data.webshopId || data.organizationId)) {
46
+ if (templates.length == 0 && (data.group?.id || data.webshop?.id || data.organizationId)) {
47
47
  templates = await EmailTemplate.select()
48
48
  .where('type', data.type)
49
49
  .where('organizationId', null)
@@ -61,6 +61,105 @@ export async function getEmailTemplate(data: EmailTemplateOptions) {
61
61
  return templates[0]
62
62
  }
63
63
 
64
+ export async function getDefaultEmailFrom(organization: Organization|null, options: Pick<EmailBuilderOptions, "type"> & { template: Omit<EmailTemplateOptions, "organizationId"> }) {
65
+ // When choosing sending domain, prefer using the one with the highest reputation
66
+ const preferStrong = options.type === 'transactional'
67
+
68
+ let preferEmailId: string | null = null;
69
+
70
+ if (options.template.group) {
71
+ preferEmailId = options.template.group.privateSettings.defaultEmailId
72
+ }
73
+
74
+ if (options.template.webshop) {
75
+ preferEmailId = options.template.webshop.privateMeta.defaultEmailId
76
+ }
77
+
78
+ if (organization) {
79
+ // Send confirmation e-mail
80
+ let from = preferStrong ? organization.getStrongEmail(organization.i18n, false) : organization.uri+"@stamhoofd.email";
81
+ const sender: OrganizationEmail | undefined = (preferEmailId ? organization.privateMeta.emails.find(e => e.id === preferEmailId) : null) ?? organization.privateMeta.emails.find(e => e.default) ?? organization.privateMeta.emails[0];
82
+ let replyTo: string | undefined = undefined
83
+
84
+ if (sender) {
85
+ replyTo = sender.email
86
+
87
+ // Can we send from this e-mail or reply-to?
88
+ if (replyTo && organization.privateMeta.mailDomain && organization.privateMeta.mailDomainActive && sender.email.endsWith("@"+organization.privateMeta.mailDomain)) {
89
+ from = sender.email
90
+ replyTo = undefined
91
+ }
92
+
93
+ // Include name in form field
94
+ if (sender.name) {
95
+ from = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
96
+ } else {
97
+ from = '"'+organization.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
98
+ }
99
+
100
+ if (replyTo) {
101
+ if (sender.name) {
102
+ replyTo = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
103
+ } else {
104
+ replyTo = '"'+organization.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
105
+ }
106
+ }
107
+ } else {
108
+ from = '"'+organization.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
109
+ }
110
+
111
+ return {
112
+ from, replyTo
113
+ }
114
+ }
115
+
116
+ const platform = await Platform.getSharedPrivateStruct()
117
+
118
+ // Platform
119
+ // TODO: read from config
120
+ let from = 'hallo@stamhoofd.be'
121
+ const sender: OrganizationEmail | undefined = (preferEmailId ? platform.privateConfig.emails.find(e => e.id === preferEmailId) : null) ?? platform.privateConfig.emails.find(e => e.default) ?? platform.privateConfig.emails[0];
122
+ let replyTo: string | undefined = undefined
123
+
124
+ if (sender) {
125
+ replyTo = sender.email
126
+
127
+ // Include name in form field
128
+ if (sender.name) {
129
+ from = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
130
+ } else {
131
+ from = '"'+platform.config.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
132
+ }
133
+
134
+ if (replyTo) {
135
+ if (sender.name) {
136
+ replyTo = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
137
+ } else {
138
+ replyTo = '"'+platform.config.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
139
+ }
140
+ }
141
+ } else {
142
+ from = '"'+platform.config.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
143
+ }
144
+
145
+ return {
146
+ from, replyTo
147
+ }
148
+ }
149
+
150
+
151
+ export async function sendEmailTemplate(organization: Organization|null, options: Omit<EmailBuilderOptions, "subject" | "html" | "from" | "replyTo"> & { template: Omit<EmailTemplateOptions, "organizationId"> }) {
152
+ if (options.template.webshop) {
153
+ options.defaultReplacements = [...(options.defaultReplacements ?? []), ...options.template.webshop.meta.getEmailReplacements()]
154
+ }
155
+ const builder = await getEmailBuilderForTemplate(organization, {
156
+ ...options,
157
+ ...(await getDefaultEmailFrom(organization, options))
158
+ });
159
+ if (builder) {
160
+ Email.schedule(builder)
161
+ }
162
+ }
64
163
 
65
164
  export async function getEmailBuilderForTemplate(organization: Organization|null, options: Omit<EmailBuilderOptions, "subject" | "html"> & { template: Omit<EmailTemplateOptions, "organizationId"> }) {
66
165
  const template = await getEmailTemplate({
@@ -162,6 +261,8 @@ export async function getEmailBuilder(organization: Organization|null, email: Em
162
261
  recipient.replacements.push(...email.defaultReplacements)
163
262
  }
164
263
 
264
+ recipient.replacements.push(...recipient.getDefaultReplacements())
265
+
165
266
  if (organization) {
166
267
  const extra = organization.meta.getEmailReplacements()
167
268
  recipient.replacements.push(...extra)
@@ -359,11 +359,11 @@ export class Member extends Model {
359
359
  .from(Member.table)
360
360
  .join(
361
361
  SQL
362
- .leftJoin('_members_users')
363
- .where(
364
- SQL.column('_members_users', 'membersId'),
365
- SQL.column(Member.table, 'id'),
366
- )
362
+ .leftJoin('_members_users')
363
+ .where(
364
+ SQL.column('_members_users', 'membersId'),
365
+ SQL.column(Member.table, 'id'),
366
+ )
367
367
  ).where(
368
368
  SQL.column('_members_users', 'usersId'),
369
369
  user.id,
@@ -1,6 +1,7 @@
1
- import { column, Model } from '@simonbackx/simple-database';
2
- import { v4 as uuidv4 } from "uuid";
1
+ import { column, Model, SQLResultNamespacedRow } from '@simonbackx/simple-database';
2
+ import { SQL, SQLSelect } from '@stamhoofd/sql';
3
3
  import { MemberResponsibilityRecord as MemberResponsibilityRecordStruct } from '@stamhoofd/structures';
4
+ import { v4 as uuidv4 } from "uuid";
4
5
 
5
6
  export class MemberResponsibilityRecord extends Model {
6
7
  static table = "member_responsibility_records"
@@ -43,4 +44,22 @@ export class MemberResponsibilityRecord extends Model {
43
44
  getStructure() {
44
45
  return MemberResponsibilityRecordStruct.create(this)
45
46
  }
47
+
48
+ /**
49
+ * Experimental: needs to move to library
50
+ */
51
+ static select() {
52
+ const transformer = (row: SQLResultNamespacedRow): MemberResponsibilityRecord => {
53
+ const d = (this as typeof MemberResponsibilityRecord & typeof Model).fromRow(row[this.table] as any) as MemberResponsibilityRecord|undefined
54
+
55
+ if (!d) {
56
+ throw new Error("MemberResponsibilityRecord not found")
57
+ }
58
+
59
+ return d;
60
+ }
61
+
62
+ const select = new SQLSelect(transformer, SQL.wildcard())
63
+ return select.from(SQL.table(this.table))
64
+ }
46
65
  }
@@ -6,7 +6,7 @@ import { BalanceItemPaymentWithPayment, BalanceItemPaymentWithPrivatePayment, Ba
6
6
  import { Formatter } from "@stamhoofd/utility";
7
7
  import { v4 as uuidv4 } from "uuid";
8
8
 
9
- import { getEmailBuilderForTemplate } from "../helpers/EmailBuilder";
9
+ import { getEmailBuilderForTemplate, sendEmailTemplate } from "../helpers/EmailBuilder";
10
10
  import { WebshopCounter } from '../helpers/WebshopCounter';
11
11
  import { BalanceItem, Organization, Payment, Ticket, Webshop, WebshopDiscountCode } from './';
12
12
 
@@ -875,22 +875,14 @@ export class Order extends Model {
875
875
  }
876
876
 
877
877
  // Create e-mail builder
878
- const builder = await getEmailBuilderForTemplate(this.webshop.organization, {
878
+ await sendEmailTemplate(this.webshop.organization, {
879
879
  recipients: [recipient],
880
880
  template: {
881
881
  type: data.type,
882
- webshopId: this.id
882
+ webshop: this.webshop
883
883
  },
884
- // text: template.text,
885
- from: data.from,
886
- replyTo: data.replyTo,
887
884
  type: 'transactional',
888
- defaultReplacements: this.webshop.meta.getEmailReplacements()
889
885
  })
890
-
891
- if (builder) {
892
- Email.schedule(builder)
893
- }
894
886
  }
895
887
 
896
888
  /**
@@ -220,6 +220,14 @@ export class Organization extends Model {
220
220
  message: "No organization known for host " + host,
221
221
  });
222
222
  }
223
+
224
+ if (!organization.active) {
225
+ throw new SimpleError({
226
+ code: "archived",
227
+ message: "This organization is archived",
228
+ human: 'Deze groep is gearchiveerd'
229
+ });
230
+ }
223
231
  return organization;
224
232
  }
225
233
 
@@ -264,7 +272,13 @@ export class Organization extends Model {
264
272
  return this.id+"." + defaultDomain;
265
273
  }
266
274
 
275
+ private _cachedPeriod?: OrganizationRegistrationPeriod
276
+
267
277
  async getPeriod() {
278
+ if (this._cachedPeriod) {
279
+ return this._cachedPeriod;
280
+ }
281
+
268
282
  const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: this.periodId, organizationId: this.id }, {limit: 1})
269
283
 
270
284
  let oPeriod: OrganizationRegistrationPeriod;
@@ -289,12 +303,14 @@ export class Organization extends Model {
289
303
  oPeriod = oPeriods[0];
290
304
  }
291
305
 
306
+ this._cachedPeriod = oPeriod
292
307
  return oPeriod
293
308
  }
294
309
 
295
310
  getBaseStructure(): OrganizationStruct {
296
311
  return OrganizationStruct.create({
297
312
  id: this.id,
313
+ active: this.active,
298
314
  name: this.name,
299
315
  meta: this.meta,
300
316
  address: this.address,
@@ -792,13 +808,17 @@ export class Organization extends Model {
792
808
  return await this.getAdminToEmails()
793
809
  }
794
810
 
811
+ async getAdmins() {
812
+ // Circular reference fix
813
+ const User = (await import('./User')).User;
814
+ return await User.getAdmins([this.id], {verified: true});
815
+ }
816
+
795
817
  /**
796
818
  * These email addresess are private
797
819
  */
798
820
  async getFullAdmins() {
799
- // Circular reference fix
800
- const User = (await import('./User')).User;
801
- const admins = await User.getAdmins([this.id], {verified: true})
821
+ const admins = await this.getAdmins();
802
822
 
803
823
  // Only full access
804
824
  return admins.filter(a => a.permissions && a.permissions.forOrganization(this)?.hasFullAccess())
@@ -842,18 +862,6 @@ export class Organization extends Model {
842
862
  lastName: f.lastName,
843
863
  email: f.email,
844
864
  replacements: [
845
- Replacement.create({
846
- token: "firstName",
847
- value: f.firstName ?? ""
848
- }),
849
- Replacement.create({
850
- token: "lastName",
851
- value: f.lastName ?? ""
852
- }),
853
- Replacement.create({
854
- token: "email",
855
- value: f.email
856
- }),
857
865
  Replacement.create({
858
866
  token: "organizationName",
859
867
  value: this.name
@@ -30,7 +30,7 @@ export class Platform extends Model {
30
30
  static sharedStruct: PlatformStruct | null = null;
31
31
 
32
32
  static async getSharedStruct(): Promise<PlatformStruct> {
33
- const struct = await this.getSharedPrivateStruct();
33
+ const struct: PlatformStruct = await this.getSharedPrivateStruct();
34
34
  const clone = struct.clone();
35
35
  clone.privateConfig = null;
36
36
  clone.setShared();
@@ -38,9 +38,9 @@ export class Platform extends Model {
38
38
  return clone;
39
39
  }
40
40
 
41
- static async getSharedPrivateStruct(): Promise<PlatformStruct> {
41
+ static async getSharedPrivateStruct(): Promise<PlatformStruct & {privateConfig: PlatformPrivateConfig}> {
42
42
  if (this.sharedStruct && this.sharedStruct.privateConfig) {
43
- return this.sharedStruct;
43
+ return this.sharedStruct as any;
44
44
  }
45
45
 
46
46
  return await QueueHandler.schedule('Platform.getSharedStruct', async () => {
@@ -52,7 +52,7 @@ export class Platform extends Model {
52
52
  });
53
53
  this.sharedStruct = struct;
54
54
 
55
- return struct;
55
+ return struct as any;
56
56
  });
57
57
  }
58
58
 
@@ -1,12 +1,7 @@
1
1
  import { column, Model } from "@simonbackx/simple-database";
2
- import { SimpleError } from "@simonbackx/simple-errors";
3
- import { EmailInterfaceBase } from "@stamhoofd/email";
4
2
  import basex from "base-x";
5
3
  import crypto from "crypto";
6
4
 
7
- import { Organization } from "./Organization";
8
- import { STCredit } from "./STCredit";
9
- import { UsedRegisterCode } from "./UsedRegisterCode";
10
5
  const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
11
6
  const bs58 = basex(ALPHABET)
12
7
 
@@ -71,94 +66,4 @@ export class RegisterCode extends Model {
71
66
  async generateCode() {
72
67
  this.code = bs58.encode(await randomBytes(8)).toUpperCase();
73
68
  }
74
-
75
- /**
76
- * Prepares a register code for usage.
77
- * Returns the models to save and the emails to send when succesful
78
- */
79
- static async applyRegisterCode(organization: Organization, codeString: string): Promise<{models: Model[], emails: EmailInterfaceBase[]}> {
80
- const alreadyUsed = await UsedRegisterCode.getFor(organization.id)
81
- if (alreadyUsed) {
82
- throw new SimpleError({
83
- code: "invalid_field",
84
- message: "Invalid register code",
85
- human: "Je hebt al een doorverwijzingscode gebruikt",
86
- field: "registerCode",
87
- });
88
- }
89
-
90
- const code = await RegisterCode.getByID(codeString)
91
- if (!code) {
92
- throw new SimpleError({
93
- code: "invalid_field",
94
- message: "Invalid register code",
95
- human: "De doorverwijzingscode die je hebt opgegeven is niet langer geldig",
96
- field: "registerCode",
97
- });
98
- }
99
-
100
- const otherOrganization = code.organizationId ? await Organization.getByID(code.organizationId) : undefined
101
-
102
- const delayEmails: EmailInterfaceBase[] = []
103
- let credit: STCredit | undefined = undefined
104
- let usedCode: UsedRegisterCode | undefined = undefined
105
-
106
- if (code.value > 0) {
107
- // Create initial credit
108
- credit = new STCredit()
109
- credit.organizationId = organization.id
110
- credit.change = code.value
111
- credit.description = otherOrganization ? ("Tegoed gekregen van "+otherOrganization.name) : code.description
112
-
113
- // Expire in one year (will get extended for every purchase or activation)
114
- credit.expireAt = new Date()
115
- credit.expireAt.setFullYear(credit.expireAt.getFullYear() + 1)
116
- credit.expireAt.setMilliseconds(0)
117
-
118
- // Save later
119
- }
120
-
121
- if (otherOrganization) {
122
- const admins = await otherOrganization.getAdminToEmails()
123
- if (admins) {
124
- if (code.invoiceValue) {
125
- // Delay email until everything is validated and saved
126
- delayEmails.push({
127
- to: admins,
128
- bcc: "simon@stamhoofd.be",
129
- subject: organization.name+" heeft jullie doorverwijzingslink gebruikt 🥳",
130
- type: "transactional",
131
- text: "Dag "+otherOrganization.name+",\n\nGoed nieuws! "+organization.name+" heeft jullie doorverwijzingslink gebruikt om zich op Stamhoofd te registreren.\n\n— Stamhoofd"
132
- })
133
- } else {
134
- // Delay email until everything is validated and saved
135
- delayEmails.push({
136
- to: admins,
137
- bcc: "simon@stamhoofd.be",
138
- subject: organization.name+" heeft jullie doorverwijzingslink gebruikt 🥳",
139
- type: "transactional",
140
- text: "Dag "+otherOrganization.name+",\n\nGoed nieuws! "+organization.name+" heeft jullie doorverwijzingslink gebruikt om zich op Stamhoofd te registreren. Als zij minstens 1 euro op Stamhoofd uitgeven ontvangen jullie een tegoed dat kan oplopen tot 100 euro per vereniging (zie daarvoor Stamhoofd > Instellingen). Lees zeker onze tips na om nog een groter bedrag te verzamelen 😉\n\n— Stamhoofd"
141
- })
142
- }
143
- }
144
- } else {
145
- delayEmails.push({
146
- to: 'hallo@stamhoofd.be',
147
- subject: organization.name+" heeft jullie doorverwijzingslink gebruikt 🥳",
148
- type: "transactional",
149
- text: "Dag Stamhoofd,\n\nGoed nieuws! "+organization.name+" heeft jullie doorverwijzingslink "+code.code+" gebruikt om zich op Stamhoofd te registreren. \n\n— Stamhoofd"
150
- })
151
- }
152
-
153
- // Save that we used this code (so we can reward the other organization)
154
- usedCode = new UsedRegisterCode()
155
- usedCode.organizationId = organization.id
156
- usedCode.code = code.code
157
-
158
- // Save later
159
- return {
160
- models: credit ? [credit, usedCode] : [usedCode],
161
- emails: delayEmails
162
- }
163
- }
164
69
  }
@@ -6,7 +6,7 @@ import { v4 as uuidv4 } from "uuid";
6
6
 
7
7
  import { ArrayDecoder } from '@simonbackx/simple-encoding';
8
8
  import { QueueHandler } from '@stamhoofd/queues';
9
- import { getEmailBuilderForTemplate } from '../helpers/EmailBuilder';
9
+ import { sendEmailTemplate } from '../helpers/EmailBuilder';
10
10
  import { Document, Group, Organization, User } from './';
11
11
 
12
12
  export class Registration extends Model {
@@ -251,6 +251,14 @@ export class Registration extends Model {
251
251
  token: "lastName",
252
252
  value: member.details.lastName,
253
253
  }),
254
+ Replacement.create({
255
+ token: "firstNameMember",
256
+ value: member.details.firstName,
257
+ }),
258
+ Replacement.create({
259
+ token: "lastNameMember",
260
+ value: member.details.lastName,
261
+ }),
254
262
  Replacement.create({
255
263
  token: "email",
256
264
  value: user.email
@@ -293,29 +301,18 @@ export class Registration extends Model {
293
301
 
294
302
  const recipients = await this.getRecipients(organization, group)
295
303
 
296
- const {from, replyTo} = organization.getGroupEmail(group)
297
-
298
304
  // Create e-mail builder
299
- const builder = await getEmailBuilderForTemplate(organization, {
305
+ await sendEmailTemplate(organization, {
300
306
  template: {
301
307
  type: data.type,
302
- groupId: this.groupId
308
+ group
303
309
  },
304
310
  recipients,
305
- from,
306
311
  type: "transactional",
307
- replyTo
308
312
  })
309
-
310
- if (builder) {
311
- Email.schedule(builder)
312
- }
313
313
  }
314
314
 
315
315
  static async sendTransferEmail(user: User, organization: Organization, payment: import('./').Payment) {
316
- const data = {
317
- type: EmailTemplateType.RegistrationTransferDetails
318
- };
319
316
  const paymentGeneral = await payment.getGeneralStructure();
320
317
  const groupIds = paymentGeneral.groupIds;
321
318
 
@@ -386,31 +383,21 @@ export class Registration extends Model {
386
383
  })
387
384
  ];
388
385
 
389
- let {from, replyTo} = organization.getDefaultEmail()
386
+ let group: Group|undefined|null = null;
390
387
 
391
388
  if (groupIds.length == 1) {
392
389
  const Group = (await import('./')).Group
393
- const group = await Group.getByID(groupIds[0]);
394
- if (group) {
395
- const groupEmail = organization.getGroupEmail(group)
396
- from = groupEmail.from
397
- replyTo = groupEmail.replyTo
398
- }
390
+ group = await Group.getByID(groupIds[0]);
399
391
  }
400
392
 
401
393
  // Create e-mail builder
402
- const builder = await getEmailBuilderForTemplate(organization, {
394
+ await sendEmailTemplate(organization, {
403
395
  template: {
404
- type: EmailTemplateType.RegistrationTransferDetails
396
+ type: EmailTemplateType.RegistrationTransferDetails,
397
+ group
405
398
  },
406
- recipients,
407
- from,
408
- replyTo
399
+ recipients
409
400
  })
410
-
411
- if (builder) {
412
- Email.schedule(builder)
413
- }
414
401
  }
415
402
 
416
403
  shouldIncludeStock() {
@@ -1,8 +1,6 @@
1
1
  import { column, Model } from "@simonbackx/simple-database";
2
- import { STInvoiceItem } from "@stamhoofd/structures";
3
2
  import { v4 as uuidv4 } from "uuid";
4
3
 
5
- import { STInvoice } from "./";
6
4
 
7
5
  export class STCredit extends Model {
8
6
  static table = "stamhoofd_credits";
@@ -51,84 +49,4 @@ export class STCredit extends Model {
51
49
  skipUpdate: true
52
50
  })
53
51
  updatedAt: Date
54
-
55
- static async getForOrganization(organizationId: string) {
56
- return await STCredit.where({ organizationId }, {
57
- sort: [{
58
- column: "createdAt",
59
- direction: "DESC"
60
- }]
61
- })
62
- }
63
-
64
- static async getBalance(organizationId: string) {
65
- const now = new Date()
66
- const credits = await this.getForOrganization(organizationId)
67
- credits.reverse()
68
- let balance = 0
69
- let balanceTransactions = 0
70
-
71
- for (const credit of credits) {
72
- if (credit.expireAt !== null && credit.expireAt <= now) {
73
- continue
74
- }
75
- // TODO: we can expire credits here
76
- balance += credit.change
77
- if (balance < 0) {
78
- // This is needed to make deleting credit and expiring credit work.
79
- // At no point in time, the credits can get negative.
80
- // E.g. Getting credits, using them, and later expiring 'getting the credits' won't have impact on future credits
81
- balance = 0
82
- }
83
-
84
- if (credit.allowTransactions || credit.change < 0) {
85
- balanceTransactions += credit.change
86
-
87
- // No point in time we can have more balance for transactions
88
- balanceTransactions = Math.min(Math.max(balanceTransactions, 0), balance)
89
- }
90
- }
91
-
92
- return {balance: balance - balanceTransactions, balanceTransactions}
93
- }
94
-
95
- static async applyCredits(organizationId: string, invoice: STInvoice, dryRun: boolean) {
96
- // Apply credits
97
- const {balance, balanceTransactions} = await STCredit.getBalance(organizationId)
98
- if (balance > 0 || balanceTransactions > 0) {
99
- // Loop all items where you can use credits for
100
- const maxCredits = invoice.meta.items.filter(i => i.canUseCredits).reduce((price, item) => price + item.price, 0)
101
- let applyValue = Math.min(maxCredits, balance)
102
-
103
- if (balanceTransactions > 0) {
104
- // Can apply to all items
105
- const maxTransactionsCredits = invoice.meta.items.reduce((price, item) => price + item.price, 0) - applyValue
106
- applyValue += Math.min(maxTransactionsCredits, balanceTransactions)
107
- }
108
-
109
- if (applyValue > 0) {
110
- invoice.meta.items.push(STInvoiceItem.create({
111
- name: "Gebruikt tegoed",
112
- unitPrice: -applyValue,
113
- amount: 1,
114
- date: new Date()
115
- }))
116
-
117
- if (!dryRun) {
118
- const credit = new STCredit()
119
- credit.organizationId = organizationId
120
- credit.change = -applyValue
121
- credit.description = "Tijdelijk vrijgehouden voor lopende betaling"
122
-
123
- // Reserve for one week (direct debit can take a while)
124
- credit.expireAt = new Date()
125
- credit.expireAt.setDate(credit.expireAt.getDate() + 7)
126
- credit.expireAt.setMilliseconds(0)
127
-
128
- await credit.save()
129
- invoice.creditId = credit.id
130
- }
131
- }
132
- }
133
- }
134
52
  }