@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.
- package/dist/src/helpers/EmailBuilder.d.ts +12 -3
- package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
- package/dist/src/helpers/EmailBuilder.js +97 -6
- package/dist/src/helpers/EmailBuilder.js.map +1 -1
- package/dist/src/models/Member.js.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.d.ts +5 -0
- package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.js +16 -1
- package/dist/src/models/MemberResponsibilityRecord.js.map +1 -1
- package/dist/src/models/Order.d.ts.map +1 -1
- package/dist/src/models/Order.js +2 -10
- package/dist/src/models/Order.js.map +1 -1
- package/dist/src/models/Organization.d.ts +2 -0
- package/dist/src/models/Organization.d.ts.map +1 -1
- package/dist/src/models/Organization.js +19 -15
- package/dist/src/models/Organization.js.map +1 -1
- package/dist/src/models/Platform.d.ts +3 -1
- package/dist/src/models/Platform.d.ts.map +1 -1
- package/dist/src/models/Platform.js.map +1 -1
- package/dist/src/models/RegisterCode.d.ts +0 -10
- package/dist/src/models/RegisterCode.d.ts.map +1 -1
- package/dist/src/models/RegisterCode.js +0 -86
- package/dist/src/models/RegisterCode.js.map +1 -1
- package/dist/src/models/Registration.d.ts.map +1 -1
- package/dist/src/models/Registration.js +16 -27
- package/dist/src/models/Registration.js.map +1 -1
- package/dist/src/models/STCredit.d.ts +0 -7
- package/dist/src/models/STCredit.d.ts.map +1 -1
- package/dist/src/models/STCredit.js +0 -69
- package/dist/src/models/STCredit.js.map +1 -1
- package/dist/src/models/STInvoice.d.ts +2 -20
- package/dist/src/models/STInvoice.d.ts.map +1 -1
- package/dist/src/models/STInvoice.js +0 -364
- package/dist/src/models/STInvoice.js.map +1 -1
- package/dist/src/models/STPackage.d.ts.map +1 -1
- package/dist/src/models/STPackage.js +2 -12
- package/dist/src/models/STPackage.js.map +1 -1
- package/dist/src/models/STPendingInvoice.d.ts +1 -21
- package/dist/src/models/STPendingInvoice.d.ts.map +1 -1
- package/dist/src/models/STPendingInvoice.js +0 -213
- package/dist/src/models/STPendingInvoice.js.map +1 -1
- package/dist/src/models/UsedRegisterCode.d.ts +0 -5
- package/dist/src/models/UsedRegisterCode.d.ts.map +1 -1
- package/dist/src/models/UsedRegisterCode.js +0 -101
- package/dist/src/models/UsedRegisterCode.js.map +1 -1
- package/package.json +2 -2
- package/src/helpers/EmailBuilder.ts +112 -11
- package/src/models/Member.ts +5 -5
- package/src/models/MemberResponsibilityRecord.ts +21 -2
- package/src/models/Order.ts +3 -11
- package/src/models/Organization.ts +23 -15
- package/src/models/Platform.ts +4 -4
- package/src/models/RegisterCode.ts +0 -95
- package/src/models/Registration.ts +17 -30
- package/src/models/STCredit.ts +0 -82
- package/src/models/STInvoice.ts +2 -433
- package/src/models/STPackage.ts +3 -14
- package/src/models/STPendingInvoice.ts +2 -247
- package/src/models/UsedRegisterCode.ts +0 -115
- package/dist/src/helpers/InvoiceBuilder.d.ts +0 -29
- package/dist/src/helpers/InvoiceBuilder.d.ts.map +0 -1
- package/dist/src/helpers/InvoiceBuilder.js +0 -407
- package/dist/src/helpers/InvoiceBuilder.js.map +0 -1
- package/dist/src/helpers/InvoiceBuilder.test.d.ts +0 -2
- package/dist/src/helpers/InvoiceBuilder.test.d.ts.map +0 -1
- package/dist/src/helpers/InvoiceBuilder.test.js +0 -52
- package/dist/src/helpers/InvoiceBuilder.test.js.map +0 -1
- package/src/helpers/InvoiceBuilder.test.ts +0 -57
- 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
|
-
|
|
11
|
-
|
|
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.
|
|
21
|
-
q.where('groupId', data.
|
|
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.
|
|
29
|
-
q.where('webshopId', data.
|
|
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.
|
|
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.
|
|
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)
|
package/src/models/Member.ts
CHANGED
|
@@ -359,11 +359,11 @@ export class Member extends Model {
|
|
|
359
359
|
.from(Member.table)
|
|
360
360
|
.join(
|
|
361
361
|
SQL
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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 {
|
|
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
|
}
|
package/src/models/Order.ts
CHANGED
|
@@ -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
|
-
|
|
878
|
+
await sendEmailTemplate(this.webshop.organization, {
|
|
879
879
|
recipients: [recipient],
|
|
880
880
|
template: {
|
|
881
881
|
type: data.type,
|
|
882
|
-
|
|
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
|
-
|
|
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
|
package/src/models/Platform.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
305
|
+
await sendEmailTemplate(organization, {
|
|
300
306
|
template: {
|
|
301
307
|
type: data.type,
|
|
302
|
-
|
|
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
|
|
386
|
+
let group: Group|undefined|null = null;
|
|
390
387
|
|
|
391
388
|
if (groupIds.length == 1) {
|
|
392
389
|
const Group = (await import('./')).Group
|
|
393
|
-
|
|
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
|
-
|
|
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() {
|
package/src/models/STCredit.ts
CHANGED
|
@@ -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
|
}
|