@stamhoofd/models 2.3.0 → 2.5.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/factories/GroupFactory.d.ts.map +1 -1
- package/dist/src/factories/GroupFactory.js +5 -5
- package/dist/src/factories/GroupFactory.js.map +1 -1
- package/dist/src/helpers/EmailBuilder.d.ts +3 -2
- package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
- package/dist/src/helpers/EmailBuilder.js +29 -16
- package/dist/src/helpers/EmailBuilder.js.map +1 -1
- package/dist/src/helpers/GroupBuilder.d.ts.map +1 -1
- package/dist/src/helpers/GroupBuilder.js +0 -23
- package/dist/src/helpers/GroupBuilder.js.map +1 -1
- package/dist/src/migrations/1721050380-email-table.sql +24 -0
- package/dist/src/migrations/1721050381-email-recipients-table.sql +18 -0
- package/dist/src/migrations/1721342679-responsibility-groupId.sql +2 -0
- package/dist/src/migrations/1721342680-responsibility-groupId-foreign-key.sql +1 -0
- package/dist/src/migrations/1721400546-users-memberId.sql +3 -0
- package/dist/src/migrations/1721639159-membership-deleted-at.sql +2 -0
- package/dist/src/migrations/1721639160-membership-generated.sql +2 -0
- package/dist/src/migrations/1721841819-group-type.sql +2 -0
- package/dist/src/migrations/1722090482-events.sql +18 -0
- package/dist/src/migrations/1722269236-group-waitinglist-id.sql +4 -0
- package/dist/src/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
- package/dist/src/migrations/1722525787-depending-balance-item.sql +2 -0
- package/dist/src/models/BalanceItem.d.ts +7 -0
- package/dist/src/models/BalanceItem.d.ts.map +1 -1
- package/dist/src/models/BalanceItem.js +34 -1
- package/dist/src/models/BalanceItem.js.map +1 -1
- package/dist/src/models/DocumentTemplate.js +14 -14
- package/dist/src/models/DocumentTemplate.js.map +1 -1
- package/dist/src/models/Email.d.ts +41 -0
- package/dist/src/models/Email.d.ts.map +1 -0
- package/dist/src/models/Email.js +490 -0
- package/dist/src/models/Email.js.map +1 -0
- package/dist/src/models/EmailRecipient.d.ts +20 -0
- package/dist/src/models/EmailRecipient.d.ts.map +1 -0
- package/dist/src/models/EmailRecipient.js +95 -0
- package/dist/src/models/EmailRecipient.js.map +1 -0
- package/dist/src/models/EmailTemplate.d.ts +2 -1
- package/dist/src/models/EmailTemplate.d.ts.map +1 -1
- package/dist/src/models/EmailTemplate.js +4 -0
- package/dist/src/models/EmailTemplate.js.map +1 -1
- package/dist/src/models/Event.d.ts +19 -0
- package/dist/src/models/Event.d.ts.map +1 -0
- package/dist/src/models/Event.js +78 -0
- package/dist/src/models/Event.js.map +1 -0
- package/dist/src/models/Group.d.ts +9 -1
- package/dist/src/models/Group.d.ts.map +1 -1
- package/dist/src/models/Group.js +25 -22
- package/dist/src/models/Group.js.map +1 -1
- package/dist/src/models/Member.d.ts +1 -0
- package/dist/src/models/Member.d.ts.map +1 -1
- package/dist/src/models/Member.js +42 -11
- package/dist/src/models/Member.js.map +1 -1
- package/dist/src/models/MemberPlatformMembership.d.ts +7 -0
- package/dist/src/models/MemberPlatformMembership.d.ts.map +1 -1
- package/dist/src/models/MemberPlatformMembership.js +22 -0
- package/dist/src/models/MemberPlatformMembership.js.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.d.ts +3 -0
- package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.js +8 -0
- package/dist/src/models/MemberResponsibilityRecord.js.map +1 -1
- package/dist/src/models/Order.js +1 -1
- package/dist/src/models/Order.js.map +1 -1
- package/dist/src/models/Organization.d.ts +3 -4
- package/dist/src/models/Organization.d.ts.map +1 -1
- package/dist/src/models/Organization.js +28 -24
- package/dist/src/models/Organization.js.map +1 -1
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts +1 -0
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts.map +1 -1
- package/dist/src/models/OrganizationRegistrationPeriod.js +7 -0
- package/dist/src/models/OrganizationRegistrationPeriod.js.map +1 -1
- package/dist/src/models/RegistrationPeriod.d.ts +1 -0
- package/dist/src/models/RegistrationPeriod.d.ts.map +1 -1
- package/dist/src/models/RegistrationPeriod.js +12 -0
- package/dist/src/models/RegistrationPeriod.js.map +1 -1
- package/dist/src/models/User.d.ts +2 -1
- package/dist/src/models/User.d.ts.map +1 -1
- package/dist/src/models/User.js +13 -3
- package/dist/src/models/User.js.map +1 -1
- package/dist/src/models/index.d.ts +3 -0
- package/dist/src/models/index.d.ts.map +1 -1
- package/dist/src/models/index.js +3 -0
- package/dist/src/models/index.js.map +1 -1
- package/package.json +2 -2
- package/src/factories/GroupFactory.ts +6 -6
- package/src/helpers/EmailBuilder.ts +33 -18
- package/src/helpers/GroupBuilder.ts +0 -23
- package/src/migrations/1721050380-email-table.sql +24 -0
- package/src/migrations/1721050381-email-recipients-table.sql +18 -0
- package/src/migrations/1721342679-responsibility-groupId.sql +2 -0
- package/src/migrations/1721342680-responsibility-groupId-foreign-key.sql +1 -0
- package/src/migrations/1721400546-users-memberId.sql +3 -0
- package/src/migrations/1721639159-membership-deleted-at.sql +2 -0
- package/src/migrations/1721639160-membership-generated.sql +2 -0
- package/src/migrations/1721841819-group-type.sql +2 -0
- package/src/migrations/1722090482-events.sql +18 -0
- package/src/migrations/1722269236-group-waitinglist-id.sql +4 -0
- package/src/migrations/1722525785-balance-item-paying-organization-id.sql +2 -0
- package/src/migrations/1722525787-depending-balance-item.sql +2 -0
- package/src/models/BalanceItem.ts +38 -1
- package/src/models/DocumentTemplate.ts +2 -2
- package/src/models/Email.ts +556 -0
- package/src/models/EmailRecipient.ts +81 -0
- package/src/models/EmailTemplate.ts +5 -1
- package/src/models/Event.ts +71 -0
- package/src/models/Group.ts +27 -37
- package/src/models/Member.ts +60 -12
- package/src/models/MemberPlatformMembership.ts +21 -0
- package/src/models/MemberResponsibilityRecord.ts +7 -0
- package/src/models/Order.ts +2 -2
- package/src/models/Organization.ts +32 -28
- package/src/models/OrganizationRegistrationPeriod.ts +8 -0
- package/src/models/RegistrationPeriod.ts +14 -0
- package/src/models/User.ts +13 -3
- package/src/models/index.ts +3 -0
- package/dist/src/assets/assets/Metropolis-Black.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-BlackItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Bold.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-BoldItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraBold.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraBoldItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraLight.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraLightItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Light.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-LightItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Medium.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-MediumItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Regular.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-RegularItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-SemiBold.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-SemiBoldItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Thin.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ThinItalic.woff2 +0 -0
- package/dist/src/assets/assets/logo.png +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { column, Model } from '@simonbackx/simple-database';
|
|
2
|
+
import { EmailRecipient as EmailRecipientStruct, Replacement } from '@stamhoofd/structures';
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
|
|
5
|
+
import { ArrayDecoder } from '@simonbackx/simple-encoding';
|
|
6
|
+
|
|
7
|
+
export class EmailRecipient extends Model {
|
|
8
|
+
static table = "email_recipients";
|
|
9
|
+
|
|
10
|
+
@column({
|
|
11
|
+
primary: true, type: "string", beforeSave(value) {
|
|
12
|
+
return value ?? uuidv4();
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
id!: string;
|
|
16
|
+
|
|
17
|
+
@column({ type: "string" })
|
|
18
|
+
emailId: string
|
|
19
|
+
|
|
20
|
+
@column({ type: "string", nullable: true })
|
|
21
|
+
firstName: string | null = null;
|
|
22
|
+
|
|
23
|
+
@column({ type: "string", nullable: true})
|
|
24
|
+
lastName: string | null = null;
|
|
25
|
+
|
|
26
|
+
@column({ type: "string" })
|
|
27
|
+
email: string;
|
|
28
|
+
|
|
29
|
+
@column({ type: "json", decoder: new ArrayDecoder(Replacement) })
|
|
30
|
+
replacements: Replacement[] = []
|
|
31
|
+
|
|
32
|
+
@column({ type: "string", nullable: true})
|
|
33
|
+
failErrorMessage: string|null = null
|
|
34
|
+
|
|
35
|
+
@column({ type: "integer" })
|
|
36
|
+
failCount = 0
|
|
37
|
+
|
|
38
|
+
@column({
|
|
39
|
+
type: "datetime",
|
|
40
|
+
nullable: true
|
|
41
|
+
})
|
|
42
|
+
firstFailedAt: Date|null = null
|
|
43
|
+
|
|
44
|
+
@column({
|
|
45
|
+
type: "datetime",
|
|
46
|
+
nullable: true
|
|
47
|
+
})
|
|
48
|
+
lastFailedAt: Date|null = null
|
|
49
|
+
|
|
50
|
+
@column({
|
|
51
|
+
type: "datetime",
|
|
52
|
+
nullable: true
|
|
53
|
+
})
|
|
54
|
+
sentAt: Date|null = null
|
|
55
|
+
|
|
56
|
+
@column({
|
|
57
|
+
type: "datetime", beforeSave(old?: any) {
|
|
58
|
+
if (old !== undefined) {
|
|
59
|
+
return old;
|
|
60
|
+
}
|
|
61
|
+
const date = new Date()
|
|
62
|
+
date.setMilliseconds(0)
|
|
63
|
+
return date
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
createdAt: Date
|
|
67
|
+
|
|
68
|
+
@column({
|
|
69
|
+
type: "datetime", beforeSave() {
|
|
70
|
+
const date = new Date()
|
|
71
|
+
date.setMilliseconds(0)
|
|
72
|
+
return date
|
|
73
|
+
},
|
|
74
|
+
skipUpdate: true
|
|
75
|
+
})
|
|
76
|
+
updatedAt: Date
|
|
77
|
+
|
|
78
|
+
getStructure() {
|
|
79
|
+
return EmailRecipientStruct.create(this)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { column, Model } from "@simonbackx/simple-database";
|
|
2
2
|
import { AnyDecoder } from "@simonbackx/simple-encoding";
|
|
3
|
-
import { EmailTemplateType } from "@stamhoofd/structures";
|
|
3
|
+
import { EmailRecipientFilterType, EmailTemplate as EmailTemplateStruct, EmailTemplateType } from "@stamhoofd/structures";
|
|
4
4
|
import { v4 as uuidv4 } from "uuid";
|
|
5
5
|
|
|
6
6
|
|
|
@@ -61,4 +61,8 @@ export class EmailTemplate extends Model {
|
|
|
61
61
|
skipUpdate: true
|
|
62
62
|
})
|
|
63
63
|
updatedAt: Date
|
|
64
|
+
|
|
65
|
+
getStructure() {
|
|
66
|
+
return EmailTemplateStruct.create(this)
|
|
67
|
+
}
|
|
64
68
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
import { column, Model } from "@simonbackx/simple-database";
|
|
3
|
+
import { EventMeta, Event as EventStruct } from '@stamhoofd/structures';
|
|
4
|
+
import { v4 as uuidv4 } from "uuid";
|
|
5
|
+
import { Group } from "./Group";
|
|
6
|
+
|
|
7
|
+
export class Event extends Model {
|
|
8
|
+
static table = "events";
|
|
9
|
+
|
|
10
|
+
@column({ primary: true, type: "string", beforeSave(value) {
|
|
11
|
+
return value ?? uuidv4();
|
|
12
|
+
} })
|
|
13
|
+
id!: string;
|
|
14
|
+
|
|
15
|
+
@column({ type: "string" })
|
|
16
|
+
name: string
|
|
17
|
+
|
|
18
|
+
@column({ type: "string" })
|
|
19
|
+
typeId: string
|
|
20
|
+
|
|
21
|
+
@column({ type: "string", nullable: true })
|
|
22
|
+
organizationId: string|null = null
|
|
23
|
+
|
|
24
|
+
@column({ type: "string", nullable: true })
|
|
25
|
+
groupId: string|null = null
|
|
26
|
+
|
|
27
|
+
@column({ type: "datetime" })
|
|
28
|
+
startDate: Date
|
|
29
|
+
|
|
30
|
+
@column({ type: "datetime" })
|
|
31
|
+
endDate: Date
|
|
32
|
+
|
|
33
|
+
@column({ type: "json", decoder: EventMeta })
|
|
34
|
+
meta = EventMeta.create({})
|
|
35
|
+
|
|
36
|
+
@column({
|
|
37
|
+
type: "datetime", beforeSave(old?: any) {
|
|
38
|
+
if (old !== undefined) {
|
|
39
|
+
return old;
|
|
40
|
+
}
|
|
41
|
+
const date = new Date()
|
|
42
|
+
date.setMilliseconds(0)
|
|
43
|
+
return date
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
createdAt: Date
|
|
47
|
+
|
|
48
|
+
@column({
|
|
49
|
+
type: "datetime", beforeSave() {
|
|
50
|
+
const date = new Date()
|
|
51
|
+
date.setMilliseconds(0)
|
|
52
|
+
return date
|
|
53
|
+
},
|
|
54
|
+
skipUpdate: true
|
|
55
|
+
})
|
|
56
|
+
updatedAt: Date
|
|
57
|
+
|
|
58
|
+
getStructure(group?: Group|null) {
|
|
59
|
+
return EventStruct.create({
|
|
60
|
+
...this,
|
|
61
|
+
group: group ? group.getStructure() : null
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getPrivateStructure(group?: Group|null) {
|
|
66
|
+
return EventStruct.create({
|
|
67
|
+
...this,
|
|
68
|
+
group: group ? group.getPrivateStructure() : null
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/models/Group.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { column, Database, ManyToOneRelation, Model, OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
|
-
import {
|
|
2
|
+
import { GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType } from '@stamhoofd/structures';
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
4
|
|
|
5
|
-
import { Member, MemberWithRegistrations, OrganizationRegistrationPeriod, Payment, Registration, User } from './';
|
|
6
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
|
+
import { Member, MemberWithRegistrations, OrganizationRegistrationPeriod, Payment, Registration, User } from './';
|
|
7
7
|
|
|
8
8
|
if (Member === undefined) {
|
|
9
9
|
throw new Error("Import Member is undefined")
|
|
@@ -28,6 +28,9 @@ export class Group extends Model {
|
|
|
28
28
|
})
|
|
29
29
|
id!: string;
|
|
30
30
|
|
|
31
|
+
@column({ type: "string" })
|
|
32
|
+
type = GroupType.Membership;
|
|
33
|
+
|
|
31
34
|
@column({ type: "json", decoder: GroupSettings })
|
|
32
35
|
settings: GroupSettings;
|
|
33
36
|
|
|
@@ -39,6 +42,9 @@ export class Group extends Model {
|
|
|
39
42
|
@column({ type: "string" })
|
|
40
43
|
organizationId: string;
|
|
41
44
|
|
|
45
|
+
@column({ type: "string", nullable: true })
|
|
46
|
+
waitingListId: string | null = null
|
|
47
|
+
|
|
42
48
|
@column({ type: "string" })
|
|
43
49
|
periodId: string;
|
|
44
50
|
|
|
@@ -183,10 +189,16 @@ export class Group extends Model {
|
|
|
183
189
|
|
|
184
190
|
}
|
|
185
191
|
|
|
192
|
+
/**
|
|
193
|
+
* @deprecated
|
|
194
|
+
*/
|
|
186
195
|
getStructure() {
|
|
187
196
|
return GroupStruct.create({ ...this, privateSettings: null })
|
|
188
197
|
}
|
|
189
198
|
|
|
199
|
+
/**
|
|
200
|
+
* @deprecated
|
|
201
|
+
*/
|
|
190
202
|
getPrivateStructure() {
|
|
191
203
|
return GroupStruct.create(this)
|
|
192
204
|
}
|
|
@@ -207,7 +219,6 @@ export class Group extends Model {
|
|
|
207
219
|
"groupId = ? and cycle = ? and waitingList = 0 and registeredAt is not null",
|
|
208
220
|
[this.id, this.cycle]
|
|
209
221
|
)
|
|
210
|
-
//const query = `select count(*) as c from \`${Registration.table}\` where groupId = ? and cycle = ? and (((registeredAt is not null or reservedUntil >= ?) and waitingList = 0) OR (waitingList = 1 AND canRegister = 1))`
|
|
211
222
|
|
|
212
223
|
this.settings.reservedMembers = await Group.getCount(
|
|
213
224
|
"groupId = ? and cycle = ? and ((waitingList = 0 and registeredAt is null AND reservedUntil >= ?) OR (waitingList = 1 and canRegister = 1))",
|
|
@@ -218,37 +229,6 @@ export class Group extends Model {
|
|
|
218
229
|
"groupId = ? and cycle = ? and waitingList = 1",
|
|
219
230
|
[this.id, this.cycle, new Date()]
|
|
220
231
|
)
|
|
221
|
-
|
|
222
|
-
// Loop cycle -1 until current (excluding current)
|
|
223
|
-
for (let cycle = -1; cycle < this.cycle; cycle++) {
|
|
224
|
-
if (!this.settings.cycleSettings.has(cycle)) {
|
|
225
|
-
this.settings.cycleSettings.set(cycle, CycleInformation.create({
|
|
226
|
-
registeredMembers: 0,
|
|
227
|
-
reservedMembers: 0,
|
|
228
|
-
waitingListSize: 0
|
|
229
|
-
}))
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Older cycles
|
|
234
|
-
// todo: optimize this a bit
|
|
235
|
-
for (const [cycle, info] of this.settings.cycleSettings) {
|
|
236
|
-
|
|
237
|
-
info.registeredMembers = await Group.getCount(
|
|
238
|
-
"groupId = ? and cycle = ? and waitingList = 0 and registeredAt is not null",
|
|
239
|
-
[this.id, cycle]
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
info.reservedMembers = await Group.getCount(
|
|
243
|
-
"groupId = ? and cycle = ? and ((waitingList = 0 and registeredAt is null AND reservedUntil >= ?) OR (waitingList = 1 and canRegister = 1))",
|
|
244
|
-
[this.id, cycle, new Date()]
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
info.waitingListSize = await Group.getCount(
|
|
248
|
-
"groupId = ? and cycle = ? and waitingList = 1",
|
|
249
|
-
[this.id, cycle, new Date()]
|
|
250
|
-
)
|
|
251
|
-
}
|
|
252
232
|
}
|
|
253
233
|
|
|
254
234
|
static async deleteUnreachable(organizationId: string, period: OrganizationRegistrationPeriod, allGroups: Group[]) {
|
|
@@ -282,10 +262,20 @@ export class Group extends Model {
|
|
|
282
262
|
}
|
|
283
263
|
|
|
284
264
|
for (const group of allGroups) {
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
265
|
+
if (group.periodId !== period.periodId) {
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (group.type !== GroupType.Membership) {
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!reachable.get(group.id) && group.deletedAt === null) {
|
|
274
|
+
console.log("Deleting unreachable group "+group.id+" from organization "+organizationId + " org period "+period.id)
|
|
275
|
+
group.deletedAt = new Date()
|
|
288
276
|
await group.save()
|
|
277
|
+
|
|
278
|
+
Member.updateMembershipsForGroupId(group.id)
|
|
289
279
|
}
|
|
290
280
|
}
|
|
291
281
|
}
|
package/src/models/Member.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { column, Database, ManyToManyRelation, ManyToOneRelation, Model, OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
2
|
import { SQL } from "@stamhoofd/sql";
|
|
3
|
-
import { Member as MemberStruct, MemberDetails, MemberWithRegistrationsBlob, RegistrationWithMember as RegistrationWithMemberStruct, User as UserStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { Member as MemberStruct, MemberDetails, MemberWithRegistrationsBlob, RegistrationWithMember as RegistrationWithMemberStruct, User as UserStruct, GroupStatus } from '@stamhoofd/structures';
|
|
4
4
|
import { Formatter, Sorter } from '@stamhoofd/utility';
|
|
5
5
|
import { v4 as uuidv4 } from "uuid";
|
|
6
6
|
|
|
@@ -300,7 +300,9 @@ export class Member extends Model {
|
|
|
300
300
|
if (!g) {
|
|
301
301
|
throw new Error("Group not found")
|
|
302
302
|
}
|
|
303
|
-
|
|
303
|
+
if (g.deletedAt === null) {
|
|
304
|
+
member.registrations.push(registration.setRelation(Registration.group, g))
|
|
305
|
+
}
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
308
|
|
|
@@ -370,10 +372,7 @@ export class Member extends Model {
|
|
|
370
372
|
...this,
|
|
371
373
|
registrations: this.registrations.map(r => r.getStructure()),
|
|
372
374
|
details: this.details,
|
|
373
|
-
users: this.users.map(u =>
|
|
374
|
-
...u,
|
|
375
|
-
hasAccount: u.hasAccount()
|
|
376
|
-
})),
|
|
375
|
+
users: this.users.map(u => u.getStructure()),
|
|
377
376
|
})
|
|
378
377
|
}
|
|
379
378
|
|
|
@@ -385,6 +384,36 @@ export class Member extends Model {
|
|
|
385
384
|
})
|
|
386
385
|
}
|
|
387
386
|
|
|
387
|
+
static updateMembershipsForGroupId(id: string) {
|
|
388
|
+
QueueHandler.schedule('bulk-update-memberships', async () => {
|
|
389
|
+
console.log('Bulk updating memberships for group id ', id)
|
|
390
|
+
|
|
391
|
+
// Get all members that are registered in this group
|
|
392
|
+
const memberIds = (await SQL.select(
|
|
393
|
+
SQL.column('members', 'id')
|
|
394
|
+
)
|
|
395
|
+
.from(SQL.table(Member.table))
|
|
396
|
+
.join(
|
|
397
|
+
SQL.leftJoin(
|
|
398
|
+
SQL.table(Registration.table)
|
|
399
|
+
).where(
|
|
400
|
+
SQL.column(Registration.table, 'memberId'),
|
|
401
|
+
SQL.column(Member.table, 'id')
|
|
402
|
+
)
|
|
403
|
+
).where(
|
|
404
|
+
SQL.column(Registration.table, 'groupId'),
|
|
405
|
+
id
|
|
406
|
+
).fetch()).flatMap(r => (r.members && (typeof r.members.id) === 'string') ? [r.members.id as string] : [])
|
|
407
|
+
|
|
408
|
+
for (const id of memberIds) {
|
|
409
|
+
const member = await Member.getWithRegistrations(id)
|
|
410
|
+
await member?.updateMemberships()
|
|
411
|
+
}
|
|
412
|
+
}).catch((e) => {
|
|
413
|
+
console.error('Failed to update memberships for group id ', id), e
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
388
417
|
async updateMemberships(this: MemberWithRegistrations) {
|
|
389
418
|
console.log('Updating memberships for member: ' + this.id)
|
|
390
419
|
return await QueueHandler.schedule('updateMemberships-' + this.id, async () => {
|
|
@@ -410,17 +439,19 @@ export class Member extends Model {
|
|
|
410
439
|
membership: defaultMembership,
|
|
411
440
|
}]
|
|
412
441
|
})
|
|
413
|
-
// Get active memberships for this member
|
|
442
|
+
// Get active memberships for this member that
|
|
414
443
|
const memberships = await MemberPlatformMembership.where({memberId: this.id, periodId: platform.periodId })
|
|
415
444
|
const now = new Date()
|
|
416
|
-
const activeMemberships = memberships.filter(m => m.startDate <= now && m.endDate >= now)
|
|
445
|
+
const activeMemberships = memberships.filter(m => m.startDate <= now && m.endDate >= now && m.deletedAt === null)
|
|
446
|
+
const activeMembershipsUndeletable = activeMemberships.filter(m => !m.canDelete() || !m.generated)
|
|
417
447
|
|
|
418
448
|
if (defaultMemberships.length == 0) {
|
|
419
|
-
// Stop all active memberships
|
|
449
|
+
// Stop all active memberships taht were added automatically
|
|
420
450
|
for (const membership of activeMemberships) {
|
|
421
|
-
if (
|
|
451
|
+
if (membership.canDelete() && membership.generated) {
|
|
422
452
|
console.log('Removing membership because no longer registered member and not yet invoiced for: ' + this.id + ' - membership ' + membership.id)
|
|
423
|
-
|
|
453
|
+
membership.deletedAt = new Date()
|
|
454
|
+
await membership.save()
|
|
424
455
|
}
|
|
425
456
|
}
|
|
426
457
|
|
|
@@ -429,7 +460,7 @@ export class Member extends Model {
|
|
|
429
460
|
}
|
|
430
461
|
|
|
431
462
|
|
|
432
|
-
if (
|
|
463
|
+
if (activeMembershipsUndeletable.length) {
|
|
433
464
|
// Skip automatic additions
|
|
434
465
|
console.log('Skipping automatic membership for: ' + this.id, ' - already has active memberships')
|
|
435
466
|
return
|
|
@@ -447,11 +478,18 @@ export class Member extends Model {
|
|
|
447
478
|
throw new Error("No membership found")
|
|
448
479
|
}
|
|
449
480
|
|
|
481
|
+
// Check if already have the same membership
|
|
482
|
+
if (activeMemberships.find(m => m.membershipTypeId == cheapestMembership.membership.id)) {
|
|
483
|
+
console.log('Skipping automatic membership for: ' + this.id, ' - already has this membership')
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
450
487
|
const periodConfig = cheapestMembership.membership.periods.get(platform.periodId)
|
|
451
488
|
if (!periodConfig) {
|
|
452
489
|
throw new Error("Period config not found")
|
|
453
490
|
}
|
|
454
491
|
|
|
492
|
+
// Can we revive an earlier deleted membership?
|
|
455
493
|
console.log('Creating automatic membership for: ' + this.id + ' - membership type ' + cheapestMembership.membership.id)
|
|
456
494
|
const membership = new MemberPlatformMembership();
|
|
457
495
|
membership.memberId = this.id
|
|
@@ -462,9 +500,19 @@ export class Member extends Model {
|
|
|
462
500
|
membership.startDate = periodConfig.startDate
|
|
463
501
|
membership.endDate = periodConfig.endDate
|
|
464
502
|
membership.expireDate = periodConfig.expireDate
|
|
503
|
+
membership.generated = true;
|
|
465
504
|
|
|
466
505
|
await membership.calculatePrice()
|
|
467
506
|
await membership.save()
|
|
507
|
+
|
|
508
|
+
// This reasoning allows us to replace an existing membership with a cheaper one (not date based ones, but type based ones)
|
|
509
|
+
for (const toDelete of activeMemberships) {
|
|
510
|
+
if (toDelete.canDelete() && toDelete.generated) {
|
|
511
|
+
console.log('Removing membership because cheaper membership found for: ' + this.id + ' - membership ' + toDelete.id)
|
|
512
|
+
toDelete.deletedAt = new Date()
|
|
513
|
+
await toDelete.save()
|
|
514
|
+
}
|
|
515
|
+
}
|
|
468
516
|
});
|
|
469
517
|
}
|
|
470
518
|
}
|
|
@@ -47,6 +47,17 @@ export class MemberPlatformMembership extends Model {
|
|
|
47
47
|
@column({ type: "integer" })
|
|
48
48
|
price = 0;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Whether this was added automatically by the system
|
|
52
|
+
*/
|
|
53
|
+
@column({ type: "boolean" })
|
|
54
|
+
generated = false
|
|
55
|
+
|
|
56
|
+
@column({
|
|
57
|
+
type: "datetime", nullable: true
|
|
58
|
+
})
|
|
59
|
+
deletedAt: Date|null = null
|
|
60
|
+
|
|
50
61
|
@column({
|
|
51
62
|
type: "datetime", beforeSave(old?: any) {
|
|
52
63
|
if (old !== undefined) {
|
|
@@ -69,6 +80,16 @@ export class MemberPlatformMembership extends Model {
|
|
|
69
80
|
})
|
|
70
81
|
updatedAt: Date
|
|
71
82
|
|
|
83
|
+
canDelete() {
|
|
84
|
+
if (this.invoiceId || this.invoiceItemDetailId) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
delete(): Promise<void> {
|
|
91
|
+
throw new Error('Cannot delete a membership. Use the deletedAt column.');
|
|
92
|
+
}
|
|
72
93
|
|
|
73
94
|
async calculatePrice() {
|
|
74
95
|
if (this.invoiceId || this.invoiceItemDetailId) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { column, Model } from '@simonbackx/simple-database';
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { MemberResponsibilityRecord as MemberResponsibilityRecordStruct } from '@stamhoofd/structures';
|
|
3
4
|
|
|
4
5
|
export class MemberResponsibilityRecord extends Model {
|
|
5
6
|
static table = "member_responsibility_records"
|
|
@@ -15,6 +16,9 @@ export class MemberResponsibilityRecord extends Model {
|
|
|
15
16
|
@column({ type: "string", nullable: true })
|
|
16
17
|
organizationId: string|null = null;
|
|
17
18
|
|
|
19
|
+
@column({ type: "string", nullable: true })
|
|
20
|
+
groupId: string|null = null;
|
|
21
|
+
|
|
18
22
|
@column({ type: "string" })
|
|
19
23
|
memberId: string
|
|
20
24
|
|
|
@@ -36,4 +40,7 @@ export class MemberResponsibilityRecord extends Model {
|
|
|
36
40
|
@column({ type: "datetime", nullable: true })
|
|
37
41
|
endDate: Date | null = null
|
|
38
42
|
|
|
43
|
+
getStructure() {
|
|
44
|
+
return MemberResponsibilityRecordStruct.create(this)
|
|
45
|
+
}
|
|
39
46
|
}
|
package/src/models/Order.ts
CHANGED
|
@@ -875,7 +875,7 @@ export class Order extends Model {
|
|
|
875
875
|
const template = templates[0]
|
|
876
876
|
|
|
877
877
|
let recipient = (await this.getStructure()).getRecipient(
|
|
878
|
-
|
|
878
|
+
this.webshop.organization.getBaseStructure(),
|
|
879
879
|
WebshopPreview.create(this.webshop)
|
|
880
880
|
)
|
|
881
881
|
|
|
@@ -1027,4 +1027,4 @@ export class Order extends Model {
|
|
|
1027
1027
|
}
|
|
1028
1028
|
}
|
|
1029
1029
|
}
|
|
1030
|
-
}
|
|
1030
|
+
}
|
|
@@ -3,7 +3,7 @@ import { DecodedRequest } from '@simonbackx/simple-endpoints';
|
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { I18n } from "@stamhoofd/backend-i18n";
|
|
5
5
|
import { Email, EmailInterfaceRecipient } from "@stamhoofd/email";
|
|
6
|
-
import { Address, Country, DNSRecordStatus, EmailTemplateType, OrganizationEmail, OrganizationMetaData, OrganizationPrivateMetaData, OrganizationRecordsConfiguration, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentMethod, PaymentProvider, PrivatePaymentConfiguration, Recipient, Replacement, STPackageType, TransferSettings } from "@stamhoofd/structures";
|
|
6
|
+
import { AccessRight, Address, Country, DNSRecordStatus, EmailTemplateType, OrganizationEmail, OrganizationMetaData, OrganizationPrivateMetaData, OrganizationRecordsConfiguration, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentMethod, PaymentProvider, PrivatePaymentConfiguration, Recipient, Replacement, STPackageType, TransferSettings } from "@stamhoofd/structures";
|
|
7
7
|
import { AWSError } from 'aws-sdk';
|
|
8
8
|
import SES from 'aws-sdk/clients/sesv2';
|
|
9
9
|
import { PromiseResult } from 'aws-sdk/lib/request';
|
|
@@ -13,6 +13,7 @@ import { validateDNSRecords } from "../helpers/DNSValidator";
|
|
|
13
13
|
import { getEmailBuilder } from "../helpers/EmailBuilder";
|
|
14
14
|
import { OrganizationServerMetaData } from '../structures/OrganizationServerMetaData';
|
|
15
15
|
import { EmailTemplate, Group, OrganizationRegistrationPeriod, RegistrationPeriod, StripeAccount } from "./";
|
|
16
|
+
import { QueueHandler } from "@stamhoofd/queues";
|
|
16
17
|
|
|
17
18
|
export class Organization extends Model {
|
|
18
19
|
static table = "organizations";
|
|
@@ -263,13 +264,36 @@ export class Organization extends Model {
|
|
|
263
264
|
return this.id+"." + defaultDomain;
|
|
264
265
|
}
|
|
265
266
|
|
|
266
|
-
async
|
|
267
|
-
const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: this.periodId }, {limit: 1})
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
async getPeriod() {
|
|
268
|
+
const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: this.periodId, organizationId: this.id }, {limit: 1})
|
|
269
|
+
|
|
270
|
+
let oPeriod: OrganizationRegistrationPeriod;
|
|
271
|
+
if (oPeriods.length == 0) {
|
|
272
|
+
// Automatically create a period
|
|
273
|
+
oPeriod = await QueueHandler.schedule('create-missing-organization-period', async () => {
|
|
274
|
+
// Race condition check
|
|
275
|
+
const updatedPeriods = await OrganizationRegistrationPeriod.where({ periodId: this.periodId, organizationId: this.id }, {limit: 1})
|
|
276
|
+
|
|
277
|
+
if (updatedPeriods.length) {
|
|
278
|
+
return updatedPeriods[0]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log('Automatically creating new organization registration period for organization ' + this.id + ' and period ' + this.periodId + ' - organization period is missing')
|
|
282
|
+
const created = new OrganizationRegistrationPeriod()
|
|
283
|
+
created.organizationId = this.id
|
|
284
|
+
created.periodId = this.periodId
|
|
285
|
+
await created.save()
|
|
286
|
+
return created
|
|
287
|
+
})
|
|
288
|
+
} else {
|
|
289
|
+
oPeriod = oPeriods[0];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return oPeriod
|
|
293
|
+
}
|
|
271
294
|
|
|
272
|
-
|
|
295
|
+
getBaseStructure(): OrganizationStruct {
|
|
296
|
+
return OrganizationStruct.create({
|
|
273
297
|
id: this.id,
|
|
274
298
|
name: this.name,
|
|
275
299
|
meta: this.meta,
|
|
@@ -277,28 +301,8 @@ export class Organization extends Model {
|
|
|
277
301
|
registerDomain: this.registerDomain,
|
|
278
302
|
uri: this.uri,
|
|
279
303
|
website: this.website,
|
|
280
|
-
groups: groups.map(g => g.getStructure()),
|
|
281
304
|
createdAt: this.createdAt,
|
|
282
|
-
period: OrganizationRegistrationPeriodStruct.create({
|
|
283
|
-
...oPeriod,
|
|
284
|
-
period: period!.getStructure()
|
|
285
|
-
})
|
|
286
305
|
})
|
|
287
|
-
|
|
288
|
-
if (this.meta.modules.disableActivities) {
|
|
289
|
-
// Only show groups that are in a given category
|
|
290
|
-
struct.groups = struct.categoryTree.categories[0]?.groups ?? []
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (emptyGroups) {
|
|
294
|
-
// Reduce data
|
|
295
|
-
struct.meta = struct.meta.clone()
|
|
296
|
-
struct.meta.categories = []
|
|
297
|
-
struct.meta.recordsConfiguration = OrganizationRecordsConfiguration.create({})
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return struct
|
|
302
306
|
}
|
|
303
307
|
|
|
304
308
|
async updateDNSRecords() {
|
|
@@ -879,7 +883,7 @@ export class Organization extends Model {
|
|
|
879
883
|
// Circular reference fix
|
|
880
884
|
const User = (await import('./User')).User;
|
|
881
885
|
const admins = await User.where({ organizationId: this.id, permissions: { sign: "!=", value: null }})
|
|
882
|
-
const filtered = admins.filter(a => a.
|
|
886
|
+
const filtered = admins.filter(a => a.permissions?.forOrganization(this)?.hasAccessRight(AccessRight.OrganizationFinanceDirector))
|
|
883
887
|
|
|
884
888
|
if (filtered.length > 0) {
|
|
885
889
|
return filtered.map(f => f.getEmailTo() ).join(", ")
|
|
@@ -53,6 +53,14 @@ export class OrganizationRegistrationPeriod extends Model {
|
|
|
53
53
|
})
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
getPrivateStructure(this: OrganizationRegistrationPeriod, period: RegistrationPeriod, groups: Group[]) {
|
|
57
|
+
return OrganizationRegistrationPeriodStruct.create({
|
|
58
|
+
...this,
|
|
59
|
+
period: period.getStructure(),
|
|
60
|
+
groups: groups.map(g => g.getPrivateStructure()).sort(GroupStruct.defaultSort)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
async cleanCategories(groups: {id: string}[]) {
|
|
57
65
|
const reachable = new Map<string, boolean>()
|
|
58
66
|
const queue = [this.settings.rootCategoryId]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { column, Model } from '@simonbackx/simple-database';
|
|
2
|
+
import { SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
2
3
|
import { RegistrationPeriodSettings, RegistrationPeriod as RegistrationPeriodStruct } from '@stamhoofd/structures';
|
|
3
4
|
import { v4 as uuidv4 } from "uuid";
|
|
4
5
|
|
|
@@ -52,4 +53,17 @@ export class RegistrationPeriod extends Model {
|
|
|
52
53
|
getStructure() {
|
|
53
54
|
return RegistrationPeriodStruct.create(this)
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
static async getByDate(date: Date): Promise<RegistrationPeriod|null> {
|
|
58
|
+
const result = await SQL.select().from(SQL.table(this.table))
|
|
59
|
+
.where(SQL.column('startDate'), SQLWhereSign.LessEqual, date)
|
|
60
|
+
.where(SQL.column('endDate'), SQLWhereSign.GreaterEqual, date)
|
|
61
|
+
.first(false);
|
|
62
|
+
|
|
63
|
+
if (result === null || !result[this.table]) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return RegistrationPeriod.fromRow(result[this.table]) ?? null
|
|
68
|
+
}
|
|
55
69
|
}
|