@stamhoofd/models 2.115.0 → 2.116.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/MemberFactory.d.ts +3 -3
- package/dist/src/factories/MemberFactory.d.ts.map +1 -1
- package/dist/src/factories/MemberFactory.js.map +1 -1
- package/dist/src/migrations/1770917406-create-cached-balances-global-index.sql +2 -0
- package/dist/src/migrations/1770917407-create-member-name-ordering-index.sql +2 -0
- package/dist/src/migrations/1770917408-create-member-name-ordering-index-desc.sql +2 -0
- package/dist/src/migrations/1770917409-create-member-age-ordering-index.sql +2 -0
- package/dist/src/migrations/1770917410-create-member-age-ordering-index-desc.sql +2 -0
- package/dist/src/migrations/1770917411-drop-duplicate-member-indexes.sql +3 -0
- package/dist/src/models/Document.d.ts +3 -3
- package/dist/src/models/Document.d.ts.map +1 -1
- package/dist/src/models/Document.js +11 -7
- package/dist/src/models/Document.js.map +1 -1
- package/dist/src/models/DocumentTemplate.d.ts +4 -0
- package/dist/src/models/DocumentTemplate.d.ts.map +1 -1
- package/dist/src/models/DocumentTemplate.js +33 -12
- package/dist/src/models/DocumentTemplate.js.map +1 -1
- package/dist/src/models/Group.d.ts +1 -5
- package/dist/src/models/Group.d.ts.map +1 -1
- package/dist/src/models/Group.js +1 -55
- package/dist/src/models/Group.js.map +1 -1
- package/dist/src/models/Member.d.ts +51 -10
- package/dist/src/models/Member.d.ts.map +1 -1
- package/dist/src/models/Member.js +209 -126
- package/dist/src/models/Member.js.map +1 -1
- package/dist/src/models/Organization.d.ts.map +1 -1
- package/dist/src/models/Organization.js +1 -0
- package/dist/src/models/Organization.js.map +1 -1
- package/dist/src/models/Platform.test.js +3 -3
- package/dist/src/models/Platform.test.js.map +1 -1
- package/dist/src/models/Registration.js +1 -1
- package/dist/src/models/Registration.js.map +1 -1
- package/dist/src/models/User.d.ts.map +1 -1
- package/dist/src/models/User.js +1 -0
- package/dist/src/models/User.js.map +1 -1
- package/package.json +2 -2
- package/src/factories/MemberFactory.ts +3 -3
- package/src/migrations/1770917406-create-cached-balances-global-index.sql +2 -0
- package/src/migrations/1770917407-create-member-name-ordering-index.sql +2 -0
- package/src/migrations/1770917408-create-member-name-ordering-index-desc.sql +2 -0
- package/src/migrations/1770917409-create-member-age-ordering-index.sql +2 -0
- package/src/migrations/1770917410-create-member-age-ordering-index-desc.sql +2 -0
- package/src/migrations/1770917411-drop-duplicate-member-indexes.sql +3 -0
- package/src/models/Document.ts +13 -10
- package/src/models/DocumentTemplate.ts +37 -13
- package/src/models/Group.ts +4 -71
- package/src/models/Member.ts +262 -156
- package/src/models/Organization.ts +2 -1
- package/src/models/Platform.test.ts +3 -3
- package/src/models/Registration.ts +1 -1
- package/src/models/User.ts +2 -1
package/src/models/Group.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { column, Database, ManyToOneRelation
|
|
1
|
+
import { column, Database, ManyToOneRelation } from '@simonbackx/simple-database';
|
|
2
2
|
import { GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType, StockReservation } from '@stamhoofd/structures';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
4
|
|
|
5
5
|
import { ArrayDecoder } from '@simonbackx/simple-encoding';
|
|
6
6
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
7
|
-
import { QueryableModel } from '@stamhoofd/sql';
|
|
8
|
-
import {
|
|
9
|
-
import { Member, MemberWithRegistrations, OrganizationRegistrationPeriod, Payment, Registration, User } from './index.js';
|
|
7
|
+
import { ModelCache, QueryableModel } from '@stamhoofd/sql';
|
|
8
|
+
import { Member, OrganizationRegistrationPeriod, Payment, Registration, User } from './index.js';
|
|
10
9
|
|
|
11
10
|
if (Member === undefined) {
|
|
12
11
|
throw new Error('Import Member is undefined');
|
|
@@ -23,6 +22,7 @@ if (Registration === undefined) {
|
|
|
23
22
|
|
|
24
23
|
export class Group extends QueryableModel {
|
|
25
24
|
static table = 'groups';
|
|
25
|
+
// static cache = new ModelCache<Group>();
|
|
26
26
|
|
|
27
27
|
@column({
|
|
28
28
|
primary: true, type: 'string', beforeSave(value) {
|
|
@@ -135,73 +135,6 @@ export class Group extends QueryableModel {
|
|
|
135
135
|
return [...map.values()];
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
/**
|
|
139
|
-
* Fetch all members with their corresponding (valid) registrations, users
|
|
140
|
-
*/
|
|
141
|
-
async getMembersWithRegistration(waitingList = false, cycleOffset = 0): Promise<MemberWithRegistrations[]> {
|
|
142
|
-
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()}, ${User.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
143
|
-
|
|
144
|
-
query += `JOIN \`${Registration.table}\` ON \`${Registration.table}\`.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND (\`${Registration.table}\`.\`registeredAt\` is not null OR \`${Registration.table}\`.\`waitingList\` = 1)\n`;
|
|
145
|
-
|
|
146
|
-
if (waitingList) {
|
|
147
|
-
query += `JOIN \`${Registration.table}\` as reg_filter ON reg_filter.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND reg_filter.\`waitingList\` = 1\n`;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
query += `JOIN \`${Registration.table}\` as reg_filter ON reg_filter.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND reg_filter.\`waitingList\` = 0 AND reg_filter.\`registeredAt\` is not null\n`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
query += Member.users.joinQuery(Member.table, User.table) + '\n';
|
|
154
|
-
|
|
155
|
-
// We do an extra join because we also need to get the other registrations of each member (only one regitration has to match the query)
|
|
156
|
-
query += `where reg_filter.\`groupId\` = ? AND reg_filter.\`cycle\` = ?`;
|
|
157
|
-
|
|
158
|
-
const [results] = await Database.select(query, [this.id, this.cycle - cycleOffset]);
|
|
159
|
-
const members: MemberWithRegistrations[] = [];
|
|
160
|
-
|
|
161
|
-
const groupIds = results.map(r => r[Registration.table]?.groupId).filter(id => id) as string[];
|
|
162
|
-
const groups = await Group.getByIDs(...Formatter.uniqueArray(groupIds));
|
|
163
|
-
|
|
164
|
-
for (const row of results) {
|
|
165
|
-
const foundMember = Member.fromRow(row[Member.table]);
|
|
166
|
-
if (!foundMember) {
|
|
167
|
-
throw new Error('Expected member in every row');
|
|
168
|
-
}
|
|
169
|
-
const _f = foundMember.setManyRelation(Member.registrations as unknown as OneToManyRelation<'registrations', Member, Registration & { group: Group }>, []).setManyRelation(Member.users, []);
|
|
170
|
-
|
|
171
|
-
// Seach if we already got this member?
|
|
172
|
-
const existingMember = members.find(m => m.id == _f.id);
|
|
173
|
-
|
|
174
|
-
const member: MemberWithRegistrations = (existingMember ?? _f);
|
|
175
|
-
if (!existingMember) {
|
|
176
|
-
members.push(member);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Check if we have a registration with a payment
|
|
180
|
-
const registration = Registration.fromRow(row[Registration.table]);
|
|
181
|
-
if (registration) {
|
|
182
|
-
// Check if we already have this registration
|
|
183
|
-
if (!member.registrations.find(r => r.id == registration.id)) {
|
|
184
|
-
const group = groups.find(g => g.id == registration.groupId);
|
|
185
|
-
if (!group) {
|
|
186
|
-
throw new Error('Expected group');
|
|
187
|
-
}
|
|
188
|
-
member.registrations.push(registration.setRelation(Registration.group, group));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Check if we have a user
|
|
193
|
-
const user = User.fromRow(row[User.table]);
|
|
194
|
-
if (user) {
|
|
195
|
-
// Check if we already have this registration
|
|
196
|
-
if (!member.users.find(r => r.id == user.id)) {
|
|
197
|
-
member.users.push(user);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return members;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
138
|
/**
|
|
206
139
|
* @deprecated
|
|
207
140
|
*/
|
package/src/models/Member.ts
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
import { column, Database, ManyToManyRelation, ManyToOneRelation, OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
|
-
import { QueryableModel, SQL } from '@stamhoofd/sql';
|
|
2
|
+
import { ModelCache, QueryableModel, SQL } from '@stamhoofd/sql';
|
|
3
3
|
import { MemberDetails, NationalRegisterNumberOptOut, RegistrationWithTinyMember, TinyMember } from '@stamhoofd/structures';
|
|
4
4
|
import { Formatter } from '@stamhoofd/utility';
|
|
5
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
6
6
|
|
|
7
|
-
import { Group, MemberResponsibilityRecord, Payment, Registration, User } from './index.js';
|
|
7
|
+
import { Group, MemberResponsibilityRecord, MemberUser, Payment, Registration, User } from './index.js';
|
|
8
8
|
export type MemberWithUsers = Member & {
|
|
9
9
|
users: User[];
|
|
10
10
|
};
|
|
11
|
-
export type MemberWithRegistrations =
|
|
12
|
-
registrations: (
|
|
11
|
+
export type MemberWithRegistrations<R extends Registration = Registration> = Member & {
|
|
12
|
+
registrations: (R)[];
|
|
13
13
|
};
|
|
14
|
+
export type RegistrationWithGroup = Registration & { group: Group };
|
|
15
|
+
export type MemberWithRegistrationsAndGroups = MemberWithRegistrations<RegistrationWithGroup>;
|
|
16
|
+
export type MemberWithUsersAndRegistrations<R extends Registration = Registration> = MemberWithUsers & MemberWithRegistrations<R>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated
|
|
20
|
+
* For performance reasons, avoid loading the groups of registrations when not required. Use MemberWithRegistrations instead.
|
|
21
|
+
*/
|
|
22
|
+
export type MemberWithUsersRegistrationsAndGroups = MemberWithUsers & MemberWithUsersAndRegistrations<RegistrationWithGroup>;
|
|
14
23
|
|
|
15
24
|
// Defined here to prevent cycles
|
|
16
25
|
export type RegistrationWithMember = Registration & { member: Member };
|
|
17
26
|
|
|
18
27
|
export class Member extends QueryableModel {
|
|
19
28
|
static table = 'members';
|
|
29
|
+
// static cache = new ModelCache<Member>();
|
|
20
30
|
|
|
21
31
|
// #region Columns
|
|
22
32
|
@column({
|
|
@@ -102,10 +112,51 @@ export class Member extends QueryableModel {
|
|
|
102
112
|
/**
|
|
103
113
|
* Fetch all members with their corresponding (valid) registration
|
|
104
114
|
*/
|
|
105
|
-
static async getWithRegistrations(id: string): Promise<
|
|
115
|
+
static async getWithRegistrations(id: string): Promise<MemberWithUsersRegistrationsAndGroups | null> {
|
|
106
116
|
return (await this.getBlobByIds(id))[0] ?? null;
|
|
107
117
|
}
|
|
108
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Fetch all members with their corresponding (valid) registration
|
|
121
|
+
*/
|
|
122
|
+
static async getByIdWithRegistrationsAndGroups(id: string): Promise<MemberWithRegistrationsAndGroups | null> {
|
|
123
|
+
const member = await this.getByID(id);
|
|
124
|
+
if (!member) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
await this.loadRegistrations([member], true);
|
|
128
|
+
return member as MemberWithRegistrationsAndGroups;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fetch all members with their corresponding (valid) registration
|
|
133
|
+
*/
|
|
134
|
+
static async getByIdWithUsers(id: string): Promise<MemberWithUsers | null> {
|
|
135
|
+
const member = await this.getByID(id);
|
|
136
|
+
if (!member) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
await this.loadUsers([member]);
|
|
140
|
+
return member as MemberWithUsers;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Fetch all members with their corresponding (valid) registration
|
|
145
|
+
*/
|
|
146
|
+
static async getByIdWithUsersAndRegistrations(id: string): Promise<MemberWithUsersAndRegistrations | null> {
|
|
147
|
+
const member = await this.getByID(id);
|
|
148
|
+
if (!member) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
await this.loadRegistrationsAndUsers([member]);
|
|
152
|
+
return member as MemberWithUsersAndRegistrations;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static async getByIdsWithUsers(...ids: string[]): Promise<MemberWithUsers[]> {
|
|
156
|
+
const members = await Member.getByIDs(...ids);
|
|
157
|
+
return await Member.loadUsers(members);
|
|
158
|
+
}
|
|
159
|
+
|
|
109
160
|
/**
|
|
110
161
|
* Fetch all registrations with members with their corresponding (valid) registrations
|
|
111
162
|
*/
|
|
@@ -113,195 +164,243 @@ export class Member extends QueryableModel {
|
|
|
113
164
|
if (ids.length === 0) {
|
|
114
165
|
return [];
|
|
115
166
|
}
|
|
116
|
-
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const [results] = await Database.select(query, [ids]);
|
|
124
|
-
const registrations: RegistrationWithMember[] = [];
|
|
125
|
-
|
|
126
|
-
// In the future we might add a 'reverse' method on manytoone relation, instead of defining the new relation. But then we need to store 2 model types in the many to one relation.
|
|
127
|
-
const registrationMemberRelation = new ManyToOneRelation(Member, 'member');
|
|
128
|
-
registrationMemberRelation.foreignKey = Member.registrations.foreignKey;
|
|
168
|
+
const registrations = await Registration.getByIDs(...ids);
|
|
169
|
+
await this.loadMembersForRegistrations(registrations);
|
|
170
|
+
return registrations as RegistrationWithMember[];
|
|
171
|
+
}
|
|
129
172
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Fetch all registrations with members with their corresponding (valid) registrations
|
|
175
|
+
*/
|
|
176
|
+
static async loadMembersForRegistrations(registrations: Registration[]): Promise<RegistrationWithMember[]> {
|
|
177
|
+
if (registrations.length === 0) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
135
180
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
const memberIds = Formatter.uniqueArray(registrations.map(r => r.memberId));
|
|
182
|
+
if (memberIds.length) {
|
|
183
|
+
// In the future we might add a 'reverse' method on manytoone relation, instead of defining the new relation. But then we need to store 2 model types in the many to one relation.
|
|
184
|
+
const registrationMemberRelation = new ManyToOneRelation(Member, 'member');
|
|
185
|
+
registrationMemberRelation.foreignKey = Member.registrations.foreignKey;
|
|
186
|
+
|
|
187
|
+
const members = await Member.getByIDs(...memberIds);
|
|
188
|
+
for (const registration of registrations) {
|
|
189
|
+
const member = members.find(m => m.id === registration.memberId);
|
|
190
|
+
if (member) {
|
|
191
|
+
registration.setRelation(registrationMemberRelation, member);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
throw new Error('Unexpected missing member for registration ' + registration.id);
|
|
195
|
+
}
|
|
139
196
|
}
|
|
140
|
-
|
|
141
|
-
const _f = registration.setRelation(registrationMemberRelation, foundMember);
|
|
142
|
-
registrations.push(_f);
|
|
143
197
|
}
|
|
144
198
|
|
|
145
|
-
return registrations;
|
|
199
|
+
return registrations as RegistrationWithMember[];
|
|
146
200
|
}
|
|
147
201
|
|
|
148
202
|
/**
|
|
149
203
|
* Fetch all registrations with members with their corresponding (valid) registrations
|
|
150
204
|
*/
|
|
151
205
|
static async getRegistrationWithMembersForGroup(groupId: string): Promise<RegistrationWithMember[]> {
|
|
152
|
-
|
|
206
|
+
const registrations = await Registration.select()
|
|
207
|
+
.where('groupId', groupId)
|
|
208
|
+
.where('registeredAt', '!=', null)
|
|
209
|
+
.where('deactivatedAt', null)
|
|
210
|
+
.fetch();
|
|
153
211
|
|
|
154
|
-
|
|
212
|
+
return this.loadMembersForRegistrations(registrations);
|
|
213
|
+
}
|
|
155
214
|
|
|
156
|
-
|
|
157
|
-
|
|
215
|
+
static async loadRegistrations(members: Member[], withGroups?: false): Promise<MemberWithRegistrations[]>;
|
|
216
|
+
static async loadRegistrations(members: Member[], withGroups: true): Promise<MemberWithRegistrationsAndGroups[]>;
|
|
217
|
+
static async loadRegistrations(members: Member[], withGroups: boolean | undefined = true): Promise<MemberWithRegistrations[] | MemberWithRegistrationsAndGroups[]> {
|
|
218
|
+
if (members.length === 0) {
|
|
219
|
+
return members as MemberWithRegistrations[];
|
|
220
|
+
}
|
|
158
221
|
|
|
159
|
-
|
|
160
|
-
|
|
222
|
+
// Load relations
|
|
223
|
+
// Load registrations of these members
|
|
224
|
+
const loadAllFor: string[] = [];
|
|
225
|
+
const alreadyLoadedRegistrations: (Registration | undefined)[] = [];
|
|
161
226
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
227
|
+
for (const member of members) {
|
|
228
|
+
if ('registrations' in member && Array.isArray(member.registrations)) {
|
|
229
|
+
if (member.registrations.length === 0) {
|
|
230
|
+
// Nothing to do
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
165
233
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (!registration) {
|
|
169
|
-
throw new Error('Expected registration in every row');
|
|
234
|
+
alreadyLoadedRegistrations.push(...member.registrations as Registration[]);
|
|
235
|
+
continue;
|
|
170
236
|
}
|
|
237
|
+
loadAllFor.push(member.id);
|
|
238
|
+
}
|
|
171
239
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
240
|
+
const ids = Formatter.uniqueArray(loadAllFor);
|
|
241
|
+
if (ids.length) {
|
|
242
|
+
const registrations = await Registration.select()
|
|
243
|
+
.where('memberId', ids)
|
|
244
|
+
.where(
|
|
245
|
+
SQL.where('registeredAt', '!=', null),
|
|
246
|
+
)
|
|
247
|
+
.fetch() as (Registration | undefined)[];
|
|
248
|
+
alreadyLoadedRegistrations.push(...registrations);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
if (!withGroups) {
|
|
252
|
+
return members as MemberWithRegistrations[];
|
|
175
253
|
}
|
|
176
|
-
|
|
177
|
-
const _f = registration.setRelation(registrationMemberRelation, foundMember);
|
|
178
|
-
registrations.push(_f);
|
|
179
254
|
}
|
|
180
255
|
|
|
181
|
-
|
|
182
|
-
|
|
256
|
+
if (withGroups) {
|
|
257
|
+
const groupIds = Formatter.uniqueArray(alreadyLoadedRegistrations.map(r => r && 'group' in r ? undefined : r?.groupId));
|
|
258
|
+
if (groupIds.length) {
|
|
259
|
+
const groups = await Group.getByIDs(...groupIds);
|
|
183
260
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
261
|
+
for (const [index, registration] of alreadyLoadedRegistrations.entries()) {
|
|
262
|
+
if ('group' in registration!) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const group = groups.find(g => g.id === registration!.groupId);
|
|
266
|
+
if (group && !group.deletedAt) {
|
|
267
|
+
registration!.setRelation(Registration.group, group);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Remove registration from list
|
|
271
|
+
alreadyLoadedRegistrations[index] = undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
if (ids.length === 0) {
|
|
277
|
+
// Nothing loaded
|
|
278
|
+
return members as MemberWithRegistrations[];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
191
282
|
|
|
192
|
-
|
|
283
|
+
for (const member of members) {
|
|
284
|
+
// Add registrations
|
|
285
|
+
const memberRegistrations = alreadyLoadedRegistrations.filter(r => !!r && r.memberId === member.id) as Registration[];
|
|
286
|
+
member.setManyRelation(Member.registrations, memberRegistrations);
|
|
287
|
+
}
|
|
193
288
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
query += `JOIN \`${Payment.table}\` ON \`${Payment.table}\`.\`${Payment.primary.name}\` = \`${BalanceItemPayment.table}\`.\`${BalanceItemPayment.payment.foreignKey}\`\n`;
|
|
289
|
+
return members as MemberWithRegistrations[];
|
|
290
|
+
}
|
|
197
291
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
292
|
+
static async loadUsers(members: Member[]): Promise<MemberWithUsers[]> {
|
|
293
|
+
// Load relations
|
|
294
|
+
// Load registrations of these members
|
|
295
|
+
const ids = Formatter.uniqueArray(members.map(m => ('users' in m) ? undefined : m.id));
|
|
296
|
+
if (ids.length === 0) {
|
|
297
|
+
return members as MemberWithUsers[];
|
|
298
|
+
}
|
|
201
299
|
|
|
202
|
-
const
|
|
203
|
-
|
|
300
|
+
const users = await User.select(
|
|
301
|
+
SQL.wildcard(User.table),
|
|
302
|
+
SQL.column(MemberUser.table, 'membersId'),
|
|
303
|
+
)
|
|
304
|
+
.join(
|
|
305
|
+
SQL.join(MemberUser.table)
|
|
306
|
+
.where(
|
|
307
|
+
SQL.parentColumn('id'),
|
|
308
|
+
SQL.column('usersId'),
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
.where(SQL.column(MemberUser.table, 'membersId'), ids)
|
|
312
|
+
.fetch();
|
|
313
|
+
|
|
314
|
+
for (const member of members) {
|
|
315
|
+
if ('users' in member) {
|
|
316
|
+
// Was already loaded
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// Add registrations
|
|
320
|
+
// Add users
|
|
321
|
+
const memberUsers = users.filter((u) => {
|
|
322
|
+
const memberId = u.rawSelectedRow?.[MemberUser.table]?.['membersId'];
|
|
323
|
+
if (memberId) {
|
|
324
|
+
return memberId === member.id;
|
|
325
|
+
}
|
|
326
|
+
return false;
|
|
327
|
+
});
|
|
328
|
+
member.setManyRelation(Member.users, memberUsers);
|
|
329
|
+
}
|
|
204
330
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
registrationMemberRelation.foreignKey = Member.registrations.foreignKey;
|
|
331
|
+
return members as MemberWithUsers[];
|
|
332
|
+
}
|
|
208
333
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (
|
|
212
|
-
|
|
334
|
+
static unloadUsers(members: Member[]) {
|
|
335
|
+
for (const member of members) {
|
|
336
|
+
if ('users' in member) {
|
|
337
|
+
delete member.users;
|
|
213
338
|
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
214
341
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
342
|
+
static unloadRegistrations(members: Member[]) {
|
|
343
|
+
for (const member of members) {
|
|
344
|
+
if ('registrations' in member) {
|
|
345
|
+
delete member.registrations;
|
|
218
346
|
}
|
|
219
|
-
|
|
220
|
-
const _f = registration.setRelation(registrationMemberRelation, foundMember);
|
|
221
|
-
registrations.push(_f);
|
|
222
347
|
}
|
|
348
|
+
}
|
|
223
349
|
|
|
224
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Fetch all members with their corresponding (valid) registrations, users
|
|
352
|
+
*/
|
|
353
|
+
static async loadRegistrationsAndUsers(members: Member[], withGroups?: false): Promise<MemberWithUsersAndRegistrations[]>;
|
|
354
|
+
static async loadRegistrationsAndUsers(members: Member[], withGroups: true): Promise<MemberWithUsersRegistrationsAndGroups[]>;
|
|
355
|
+
static async loadRegistrationsAndUsers(members: Member[], withGroups: boolean | undefined = true): Promise<MemberWithUsersAndRegistrations[] | MemberWithUsersRegistrationsAndGroups[]> {
|
|
356
|
+
await this.loadRegistrations(members, withGroups as true);
|
|
357
|
+
await this.loadUsers(members);
|
|
358
|
+
return members as MemberWithUsersAndRegistrations[];
|
|
225
359
|
}
|
|
226
360
|
|
|
227
361
|
/**
|
|
228
362
|
* Fetch all members with their corresponding (valid) registrations, users
|
|
229
363
|
*/
|
|
230
|
-
static async getBlobByIds(...ids: string[]): Promise<
|
|
364
|
+
static async getBlobByIds(...ids: string[]): Promise<MemberWithUsersRegistrationsAndGroups[]> {
|
|
231
365
|
if (ids.length == 0) {
|
|
232
366
|
return [];
|
|
233
367
|
}
|
|
234
|
-
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()}, ${User.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
235
|
-
query += `LEFT JOIN \`${Registration.table}\` ON \`${Registration.table}\`.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND (\`${Registration.table}\`.\`registeredAt\` is not null OR \`${Registration.table}\`.\`canRegister\` = 1)\n`;
|
|
236
|
-
query += Member.users.joinQuery(Member.table, User.table) + '\n';
|
|
237
|
-
|
|
238
|
-
// We do an extra join because we also need to get the other registrations of each member (only one regitration has to match the query)
|
|
239
|
-
query += `where \`${Member.table}\`.\`${Member.primary.name}\` IN (?)`;
|
|
240
|
-
|
|
241
|
-
const [results] = await Database.select(query, [ids]);
|
|
242
|
-
const members: MemberWithRegistrations[] = [];
|
|
243
|
-
|
|
244
|
-
// Load groups
|
|
245
|
-
const groupIds = results.map(r => r[Registration.table]?.groupId).filter(id => id) as string[];
|
|
246
|
-
const groups = await Group.getByIDs(...Formatter.uniqueArray(groupIds));
|
|
247
|
-
|
|
248
|
-
for (const row of results) {
|
|
249
|
-
const foundMember = Member.fromRow(row[Member.table]);
|
|
250
|
-
if (!foundMember) {
|
|
251
|
-
throw new Error('Expected member in every row');
|
|
252
|
-
}
|
|
253
|
-
const _f = foundMember
|
|
254
|
-
.setManyRelation(Member.registrations as unknown as OneToManyRelation<'registrations', Member, Registration & { group: Group }>, [])
|
|
255
|
-
.setManyRelation(Member.users, []);
|
|
256
368
|
|
|
257
|
-
|
|
258
|
-
const existingMember = members.find(m => m.id == _f.id);
|
|
369
|
+
const baseMembers = await this.getByIDs(...ids);
|
|
259
370
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
members.push(member);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Check if we have a registration with a payment
|
|
266
|
-
const registration = Registration.fromRow(row[Registration.table]);
|
|
267
|
-
if (registration) {
|
|
268
|
-
// Check if we already have this registration
|
|
269
|
-
if (!member.registrations.find(r => r.id == registration.id)) {
|
|
270
|
-
const g = groups.find(g => g.id == registration.groupId);
|
|
271
|
-
if (!g) {
|
|
272
|
-
throw new Error('Group not found');
|
|
273
|
-
}
|
|
274
|
-
if (g.deletedAt === null) {
|
|
275
|
-
member.registrations.push(registration.setRelation(Registration.group, g));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Check if we have a user
|
|
281
|
-
const user = User.fromRow(row[User.table]);
|
|
282
|
-
if (user) {
|
|
283
|
-
// Check if we already have this registration
|
|
284
|
-
if (!member.users.find(r => r.id == user.id)) {
|
|
285
|
-
member.users.push(user);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
371
|
+
if (baseMembers.length === 0) {
|
|
372
|
+
return [];
|
|
288
373
|
}
|
|
289
374
|
|
|
290
|
-
|
|
375
|
+
await this.loadRegistrationsAndUsers(baseMembers, true);
|
|
376
|
+
return baseMembers as MemberWithUsersRegistrationsAndGroups[];
|
|
291
377
|
}
|
|
292
378
|
|
|
293
379
|
/**
|
|
294
380
|
* Fetch all members with their corresponding (valid) registrations and payment
|
|
295
381
|
*/
|
|
296
|
-
static async
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
382
|
+
static async getFamily(id: string): Promise<Member[]> {
|
|
383
|
+
const results = await SQL.select(
|
|
384
|
+
SQL.column('l2', 'membersId'),
|
|
385
|
+
)
|
|
386
|
+
.from('_members_users', 'l1')
|
|
387
|
+
.join(
|
|
388
|
+
SQL.join('_members_users', 'l2')
|
|
389
|
+
.where(
|
|
390
|
+
SQL.column('l2', 'usersId'),
|
|
391
|
+
SQL.column('l1', 'usersId'),
|
|
392
|
+
),
|
|
393
|
+
)
|
|
394
|
+
.where(
|
|
395
|
+
SQL.column('l1', 'membersId'),
|
|
396
|
+
id,
|
|
397
|
+
)
|
|
398
|
+
.groupBy(SQL.column('l2', 'membersId'))
|
|
399
|
+
.fetch();
|
|
300
400
|
|
|
301
|
-
const [results] = await Database.select(query, [id]);
|
|
302
401
|
const ids: string[] = [];
|
|
303
402
|
for (const row of results) {
|
|
304
|
-
ids.push(row['l2']['
|
|
403
|
+
ids.push(row['l2']['membersId'] as string);
|
|
305
404
|
}
|
|
306
405
|
|
|
307
406
|
if (!ids.includes(id)) {
|
|
@@ -309,7 +408,16 @@ export class Member extends QueryableModel {
|
|
|
309
408
|
ids.push(id);
|
|
310
409
|
}
|
|
311
410
|
|
|
312
|
-
return await this.
|
|
411
|
+
return await this.getByIDs(...ids);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @deprecated Please avoid implicit relation loading and only load the rel
|
|
416
|
+
* Fetch all members with their corresponding (valid) registrations and payment
|
|
417
|
+
*/
|
|
418
|
+
static async getFamilyWithRegistrations(id: string): Promise<MemberWithUsersRegistrationsAndGroups[]> {
|
|
419
|
+
const members = await this.getFamily(id);
|
|
420
|
+
return await this.loadRegistrationsAndUsers(members, true);
|
|
313
421
|
}
|
|
314
422
|
|
|
315
423
|
/**
|
|
@@ -317,28 +425,26 @@ export class Member extends QueryableModel {
|
|
|
317
425
|
*/
|
|
318
426
|
static async getMemberIdsForUser(user: User): Promise<string[]> {
|
|
319
427
|
const query = SQL
|
|
320
|
-
.select('
|
|
321
|
-
.from(
|
|
322
|
-
.
|
|
323
|
-
SQL
|
|
324
|
-
.leftJoin('_members_users')
|
|
325
|
-
.where(
|
|
326
|
-
SQL.column('_members_users', 'membersId'),
|
|
327
|
-
SQL.column(Member.table, 'id'),
|
|
328
|
-
),
|
|
329
|
-
).where(
|
|
330
|
-
SQL.column('_members_users', 'usersId'),
|
|
331
|
-
user.id,
|
|
332
|
-
);
|
|
428
|
+
.select('membersId')
|
|
429
|
+
.from('_members_users')
|
|
430
|
+
.where('usersId', user.id);
|
|
333
431
|
|
|
334
432
|
const data = await query.fetch();
|
|
335
|
-
return Formatter.uniqueArray(data.map(r => r.
|
|
433
|
+
return Formatter.uniqueArray(data.map(r => r._members_users.membersId as string));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Fetch all members with their corresponding (valid) registrations or waiting lists and payments
|
|
438
|
+
*/
|
|
439
|
+
static async getMembersForUser(user: User): Promise<Member[]> {
|
|
440
|
+
return this.getByIDs(...(await this.getMemberIdsForUser(user)));
|
|
336
441
|
}
|
|
337
442
|
|
|
338
443
|
/**
|
|
444
|
+
* @deprecated Use getMembersForUser and load relations as needed
|
|
339
445
|
* Fetch all members with their corresponding (valid) registrations or waiting lists and payments
|
|
340
446
|
*/
|
|
341
|
-
static async getMembersWithRegistrationForUser(user: User): Promise<
|
|
447
|
+
static async getMembersWithRegistrationForUser(user: User): Promise<MemberWithUsersRegistrationsAndGroups[]> {
|
|
342
448
|
return this.getBlobByIds(...(await this.getMemberIdsForUser(user)));
|
|
343
449
|
}
|
|
344
450
|
|
|
@@ -2,7 +2,7 @@ import { column, Database } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { I18n } from '@stamhoofd/backend-i18n';
|
|
4
4
|
import { EmailInterfaceRecipient } from '@stamhoofd/email';
|
|
5
|
-
import { QueryableModel } from '@stamhoofd/sql';
|
|
5
|
+
import { ModelCache, QueryableModel } from '@stamhoofd/sql';
|
|
6
6
|
import { Address, appToUri, Country, DNSRecordStatus, EmailTemplateType, Language, OrganizationEmail, OrganizationMetaData, OrganizationPrivateMetaData, Organization as OrganizationStruct, PaymentMethod, PaymentProvider, PrivatePaymentConfiguration, Recipient, Replacement, STPackageType, TransferSettings } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
8
|
import { CreateEmailIdentityCommand, DeleteEmailIdentityCommand, GetEmailIdentityCommand, GetEmailIdentityCommandOutput, PutEmailIdentityFeedbackAttributesCommand, PutEmailIdentityMailFromAttributesCommand, SESv2Client } from '@aws-sdk/client-sesv2';
|
|
@@ -18,6 +18,7 @@ import { OrganizationRegistrationPeriod, StripeAccount } from './index.js';
|
|
|
18
18
|
|
|
19
19
|
export class Organization extends QueryableModel {
|
|
20
20
|
static table = 'organizations';
|
|
21
|
+
// static cache = new ModelCache<Organization>();
|
|
21
22
|
|
|
22
23
|
@column({
|
|
23
24
|
primary: true, type: 'string', beforeSave(value) {
|