@stamhoofd/models 2.4.0 → 2.6.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/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/migrations/1722845608-registration-stock-reservations.sql +2 -0
- package/dist/src/migrations/1722845609-group-stock-reservations.sql +2 -0
- package/dist/src/migrations/1722852362-stripe-intents-account-id.sql +2 -0
- package/dist/src/migrations/1722852363-stripe-checkout-sessions-account-id.sql +2 -0
- package/dist/src/models/BalanceItem.d.ts +8 -0
- package/dist/src/models/BalanceItem.d.ts.map +1 -1
- package/dist/src/models/BalanceItem.js +70 -44
- package/dist/src/models/BalanceItem.js.map +1 -1
- package/dist/src/models/DocumentTemplate.js +1 -1
- package/dist/src/models/DocumentTemplate.js.map +1 -1
- package/dist/src/models/Event.d.ts +7 -0
- package/dist/src/models/Event.d.ts.map +1 -1
- package/dist/src/models/Event.js +28 -0
- package/dist/src/models/Event.js.map +1 -1
- package/dist/src/models/Group.d.ts +13 -3
- package/dist/src/models/Group.d.ts.map +1 -1
- package/dist/src/models/Group.js +45 -6
- package/dist/src/models/Group.js.map +1 -1
- package/dist/src/models/Member.d.ts +1 -1
- package/dist/src/models/Member.d.ts.map +1 -1
- package/dist/src/models/Member.js +12 -9
- package/dist/src/models/Member.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 -11
- package/dist/src/models/Organization.d.ts.map +1 -1
- package/dist/src/models/Organization.js +4 -28
- package/dist/src/models/Organization.js.map +1 -1
- package/dist/src/models/Payment.d.ts +5 -7
- package/dist/src/models/Payment.d.ts.map +1 -1
- package/dist/src/models/Payment.js +8 -13
- package/dist/src/models/Payment.js.map +1 -1
- package/dist/src/models/Registration.d.ts +17 -2
- package/dist/src/models/Registration.d.ts.map +1 -1
- package/dist/src/models/Registration.js +59 -7
- package/dist/src/models/Registration.js.map +1 -1
- package/dist/src/models/StripeCheckoutSession.d.ts +4 -0
- package/dist/src/models/StripeCheckoutSession.d.ts.map +1 -1
- package/dist/src/models/StripeCheckoutSession.js +7 -0
- package/dist/src/models/StripeCheckoutSession.js.map +1 -1
- package/dist/src/models/StripePaymentIntent.d.ts +4 -0
- package/dist/src/models/StripePaymentIntent.d.ts.map +1 -1
- package/dist/src/models/StripePaymentIntent.js +7 -0
- package/dist/src/models/StripePaymentIntent.js.map +1 -1
- package/package.json +2 -2
- 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/migrations/1722845608-registration-stock-reservations.sql +2 -0
- package/src/migrations/1722845609-group-stock-reservations.sql +2 -0
- package/src/migrations/1722852362-stripe-intents-account-id.sql +2 -0
- package/src/migrations/1722852363-stripe-checkout-sessions-account-id.sql +2 -0
- package/src/models/BalanceItem.ts +78 -47
- package/src/models/DocumentTemplate.ts +1 -1
- package/src/models/Event.ts +31 -0
- package/src/models/Group.ts +53 -14
- package/src/models/Member.ts +13 -10
- package/src/models/Order.ts +2 -2
- package/src/models/Organization.ts +5 -34
- package/src/models/Payment.ts +10 -16
- package/src/models/Registration.ts +71 -11
- package/src/models/StripeAccount.ts +1 -1
- package/src/models/StripeCheckoutSession.ts +6 -0
- package/src/models/StripePaymentIntent.ts +6 -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
|
@@ -8,6 +8,10 @@ class StripePaymentIntent extends simple_database_1.Model {
|
|
|
8
8
|
constructor() {
|
|
9
9
|
super(...arguments);
|
|
10
10
|
this.organizationId = null;
|
|
11
|
+
/**
|
|
12
|
+
* For direct charges, this should be set
|
|
13
|
+
*/
|
|
14
|
+
this.accountId = null;
|
|
11
15
|
}
|
|
12
16
|
}
|
|
13
17
|
exports.StripePaymentIntent = StripePaymentIntent;
|
|
@@ -28,4 +32,7 @@ tslib_1.__decorate([
|
|
|
28
32
|
tslib_1.__decorate([
|
|
29
33
|
(0, simple_database_1.column)({ type: "string", nullable: true })
|
|
30
34
|
], StripePaymentIntent.prototype, "organizationId", void 0);
|
|
35
|
+
tslib_1.__decorate([
|
|
36
|
+
(0, simple_database_1.column)({ type: "string", nullable: true })
|
|
37
|
+
], StripePaymentIntent.prototype, "accountId", void 0);
|
|
31
38
|
//# sourceMappingURL=StripePaymentIntent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StripePaymentIntent.js","sourceRoot":"","sources":["../../../src/models/StripePaymentIntent.ts"],"names":[],"mappings":";;;;AAAA,iEAA2D;AAC3D,+BAAoC;AAEpC,MAAa,mBAAoB,SAAQ,uBAAK;IAA9C;;QAiBI,mBAAc,GAAkB,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"StripePaymentIntent.js","sourceRoot":"","sources":["../../../src/models/StripePaymentIntent.ts"],"names":[],"mappings":";;;;AAAA,iEAA2D;AAC3D,+BAAoC;AAEpC,MAAa,mBAAoB,SAAQ,uBAAK;IAA9C;;QAiBI,mBAAc,GAAkB,IAAI,CAAC;QAErC;;WAEG;QAEH,cAAS,GAAgB,IAAI,CAAA;IACjC,CAAC;;AAxBD,kDAwBC;AAvBU,yBAAK,GAAG,wBAAwB,AAA3B,CAA4B;AAOxC;IALC,IAAA,wBAAM,EAAC;QACJ,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,KAAK;YAC3C,OAAO,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,IAAA,SAAM,GAAE,CAAC;QAC7B,CAAC;KACJ,CAAC;+CACU;AAGZ;IADC,IAAA,wBAAM,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;sDACT;AAGlB;IADC,IAAA,wBAAM,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;2DACJ;AAGvB;IADC,IAAA,wBAAM,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;2DACN;AAMrC;IADC,IAAA,wBAAM,EAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sDACd"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/models",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"main": "./dist/src/index.js",
|
|
5
5
|
"types": "./dist/src/index.d.ts",
|
|
6
6
|
"license": "UNLICENCED",
|
|
@@ -27,5 +27,5 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@simonbackx/simple-database": "1.24.0"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "7a3f9f6c08058dc8b671befbfad73184afdc6d7c"
|
|
31
31
|
}
|
|
@@ -18,15 +18,22 @@ export class BalanceItem extends Model {
|
|
|
18
18
|
})
|
|
19
19
|
id!: string;
|
|
20
20
|
|
|
21
|
+
// Receiving organization
|
|
22
|
+
|
|
21
23
|
@column({ type: "string" })
|
|
22
24
|
organizationId: string
|
|
23
25
|
|
|
26
|
+
// Payer: memberId, userId or payingOrganizationId
|
|
27
|
+
|
|
24
28
|
@column({ type: "string", nullable: true })
|
|
25
29
|
memberId: string | null = null;
|
|
26
30
|
|
|
27
31
|
@column({ type: "string", nullable: true })
|
|
28
32
|
userId: string | null = null;
|
|
29
33
|
|
|
34
|
+
@column({ type: "string", nullable: true })
|
|
35
|
+
payingOrganizationId: string | null = null;
|
|
36
|
+
|
|
30
37
|
/**
|
|
31
38
|
* The registration ID that is linked to this balance item
|
|
32
39
|
*/
|
|
@@ -39,6 +46,14 @@ export class BalanceItem extends Model {
|
|
|
39
46
|
@column({ type: "string", nullable: true })
|
|
40
47
|
orderId: string | null = null;
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The depending balance item ID that is linked to this balance item
|
|
51
|
+
* -> as soon as this balance item is paid, we'll mark this balance item as pending if it is still hidden
|
|
52
|
+
* -> allows for a pay back system where one user needs to pay back a different user
|
|
53
|
+
*/
|
|
54
|
+
@column({ type: "string", nullable: true })
|
|
55
|
+
dependingBalanceItemId: string | null = null;
|
|
56
|
+
|
|
42
57
|
@column({ type: "string" })
|
|
43
58
|
description = "";
|
|
44
59
|
|
|
@@ -92,6 +107,12 @@ export class BalanceItem extends Model {
|
|
|
92
107
|
|
|
93
108
|
async markPaid(payment: Payment, organization: Organization) {
|
|
94
109
|
// status and pricePaid changes are handled inside balanceitempayment
|
|
110
|
+
if (this.dependingBalanceItemId) {
|
|
111
|
+
const depending = await BalanceItem.getByID(this.dependingBalanceItemId)
|
|
112
|
+
if (depending && depending.status === BalanceItemStatus.Hidden) {
|
|
113
|
+
await BalanceItem.reactivateItems([depending])
|
|
114
|
+
}
|
|
115
|
+
}
|
|
95
116
|
|
|
96
117
|
// If registration
|
|
97
118
|
if (this.registrationId) {
|
|
@@ -100,7 +121,7 @@ export class BalanceItem extends Model {
|
|
|
100
121
|
|
|
101
122
|
if (registration) {
|
|
102
123
|
// 1. Mark registration as being valid
|
|
103
|
-
if (registration.registeredAt === null) {
|
|
124
|
+
if (registration.registeredAt === null || registration.deactivatedAt) {
|
|
104
125
|
await registration.markValid()
|
|
105
126
|
|
|
106
127
|
const {Group} = await import("./Group");
|
|
@@ -137,6 +158,28 @@ export class BalanceItem extends Model {
|
|
|
137
158
|
}
|
|
138
159
|
}
|
|
139
160
|
}
|
|
161
|
+
|
|
162
|
+
// Do we have a different connected balance item?
|
|
163
|
+
// Make it visible if this one is paid
|
|
164
|
+
if (this.dependingBalanceItemId) {
|
|
165
|
+
const depending = await BalanceItem.getByID(this.dependingBalanceItemId)
|
|
166
|
+
if (depending) {
|
|
167
|
+
if (this.status === BalanceItemStatus.Hidden) {
|
|
168
|
+
depending.status = BalanceItemStatus.Pending
|
|
169
|
+
await depending.save()
|
|
170
|
+
|
|
171
|
+
if (depending.memberId) {
|
|
172
|
+
const {Member} = await import("./Member");
|
|
173
|
+
await Member.updateOutstandingBalance([depending.memberId])
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (depending.registrationId) {
|
|
177
|
+
const {Registration} = await import("./Registration");
|
|
178
|
+
await Registration.updateOutstandingBalance([depending.registrationId], depending.organizationId)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
140
183
|
}
|
|
141
184
|
|
|
142
185
|
async undoPaid(payment: Payment, organization: Organization) {
|
|
@@ -178,76 +221,52 @@ export class BalanceItem extends Model {
|
|
|
178
221
|
}
|
|
179
222
|
|
|
180
223
|
updateStatus() {
|
|
181
|
-
this.status = this.pricePaid >= this.price ? BalanceItemStatus.Paid : BalanceItemStatus.Pending;
|
|
224
|
+
this.status = this.pricePaid >= this.price ? BalanceItemStatus.Paid : (this.pricePaid > 0 ? BalanceItemStatus.Pending : (this.status === BalanceItemStatus.Hidden ? BalanceItemStatus.Hidden : BalanceItemStatus.Pending));
|
|
182
225
|
}
|
|
183
226
|
|
|
184
227
|
static async deleteItems(items: BalanceItem[]) {
|
|
185
|
-
const {
|
|
228
|
+
const {balanceItemPayments} = await BalanceItem.loadPayments(items)
|
|
186
229
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (payment.status === PaymentStatus.Succeeded) {
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
if (!(payment.method === PaymentMethod.PointOfSale || payment.method === PaymentMethod.Transfer || payment.method === PaymentMethod.Unknown)) {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
const bip = allBalanceItemPayments.filter(p => p.paymentId == payment.id)
|
|
197
|
-
const bis = balanceItems.filter(b => b.status !== BalanceItemStatus.Hidden && bip.find(p => p.balanceItemId == b.id))
|
|
198
|
-
|
|
199
|
-
const remainingAfterDelete = bis.filter(b => !items.find(i => i.id == b.id))
|
|
200
|
-
if (remainingAfterDelete.length == 0) {
|
|
201
|
-
// Delete payment
|
|
202
|
-
payment.status = PaymentStatus.Failed
|
|
203
|
-
payment._forceUpdatedAt = new Date(1900, 0, 1)
|
|
204
|
-
await payment.save()
|
|
205
|
-
}
|
|
206
|
-
}
|
|
230
|
+
// todo: in the future we could automatically delete payments that are not needed anymore and weren't paid yet -> to prevent leaving ghost payments
|
|
231
|
+
// for now, an admin can manually cancel those payments
|
|
232
|
+
let needsUpdate = false
|
|
207
233
|
|
|
208
234
|
// Set other items to zero (the balance item payments keep the real price)
|
|
209
235
|
for (const item of items) {
|
|
236
|
+
needsUpdate = needsUpdate || (item.price > 0 && item.status !== BalanceItemStatus.Hidden)
|
|
237
|
+
|
|
210
238
|
// Don't change status of items that are already paid or are partially paid
|
|
211
239
|
// Not using item.paidPrice, since this is cached
|
|
212
|
-
const bip =
|
|
213
|
-
const relatedPayments = payments.filter(p => bip.find(b => b.paymentId == p.id))
|
|
240
|
+
const bip = balanceItemPayments.filter(p => p.balanceItemId == item.id)
|
|
214
241
|
|
|
215
|
-
if (
|
|
216
|
-
// No
|
|
242
|
+
if (bip.length === 0) {
|
|
243
|
+
// No payments associated with this item
|
|
217
244
|
item.status = BalanceItemStatus.Hidden
|
|
245
|
+
item.price = 0
|
|
246
|
+
await item.save()
|
|
247
|
+
} else {
|
|
248
|
+
item.price = 0
|
|
218
249
|
await item.save()
|
|
219
250
|
}
|
|
220
251
|
}
|
|
252
|
+
|
|
253
|
+
if (needsUpdate) {
|
|
254
|
+
await this.updateOutstanding(items)
|
|
255
|
+
}
|
|
221
256
|
}
|
|
222
257
|
|
|
223
258
|
static async reactivateItems(items: BalanceItem[]) {
|
|
224
|
-
|
|
259
|
+
let needsUpdate = false
|
|
225
260
|
for (const item of items) {
|
|
226
261
|
if (item.status === BalanceItemStatus.Hidden) {
|
|
227
262
|
item.status = BalanceItemStatus.Pending
|
|
263
|
+
needsUpdate = needsUpdate || item.price > 0
|
|
228
264
|
await item.save()
|
|
229
265
|
}
|
|
230
266
|
}
|
|
231
267
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// Load all balance items
|
|
235
|
-
const {balanceItems, balanceItemPayments: allBalanceItemPayments} = await Payment.loadBalanceItems(payments)
|
|
236
|
-
for (const payment of payments) {
|
|
237
|
-
if (payment.status !== PaymentStatus.Failed) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (!(payment.method === PaymentMethod.PointOfSale || payment.method === PaymentMethod.Transfer || payment.method === PaymentMethod.Unknown)) {
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
const bip = allBalanceItemPayments.filter(p => p.paymentId == payment.id)
|
|
244
|
-
const bis = balanceItems.filter(b => b.status !== BalanceItemStatus.Hidden && bip.find(p => p.balanceItemId == b.id))
|
|
245
|
-
|
|
246
|
-
if (bis.length > 0) {
|
|
247
|
-
// Undo failed
|
|
248
|
-
payment.status = PaymentStatus.Created
|
|
249
|
-
await payment.save()
|
|
250
|
-
}
|
|
268
|
+
if (needsUpdate) {
|
|
269
|
+
await this.updateOutstanding(items)
|
|
251
270
|
}
|
|
252
271
|
}
|
|
253
272
|
|
|
@@ -285,6 +304,18 @@ export class BalanceItem extends Model {
|
|
|
285
304
|
}
|
|
286
305
|
}
|
|
287
306
|
|
|
307
|
+
static async updateOutstanding(items: BalanceItem[], organizationId?: string) {
|
|
308
|
+
const Member = (await import('./Member')).Member;
|
|
309
|
+
|
|
310
|
+
// Update outstanding amount of related members and registrations
|
|
311
|
+
const memberIds: string[] = Formatter.uniqueArray(items.map(p => p.memberId).filter(id => id !== null)) as any
|
|
312
|
+
await Member.updateOutstandingBalance(memberIds)
|
|
313
|
+
|
|
314
|
+
const {Registration} = await import('./Registration');
|
|
315
|
+
const registrationIds: string[] = Formatter.uniqueArray(items.map(p => p.registrationId).filter(id => id !== null)) as any
|
|
316
|
+
await Registration.updateOutstandingBalance(registrationIds, organizationId)
|
|
317
|
+
}
|
|
318
|
+
|
|
288
319
|
static async loadPayments(items: BalanceItem[]) {
|
|
289
320
|
if (items.length == 0) {
|
|
290
321
|
return {balanceItemPayments: [], payments: []}
|
|
@@ -426,7 +426,7 @@ export class DocumentTemplate extends Model {
|
|
|
426
426
|
|
|
427
427
|
for (const groupDefinition of this.privateSettings.groups) {
|
|
428
428
|
// Get the registrations for this group with this cycle
|
|
429
|
-
const registrations = await Member.getRegistrationWithMembersForGroup(groupDefinition.groupId
|
|
429
|
+
const registrations = await Member.getRegistrationWithMembersForGroup(groupDefinition.groupId)
|
|
430
430
|
|
|
431
431
|
for (const registration of registrations) {
|
|
432
432
|
const document = await this.generateForRegistration(registration)
|
package/src/models/Event.ts
CHANGED
|
@@ -55,6 +55,9 @@ export class Event extends Model {
|
|
|
55
55
|
})
|
|
56
56
|
updatedAt: Date
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated
|
|
60
|
+
*/
|
|
58
61
|
getStructure(group?: Group|null) {
|
|
59
62
|
return EventStruct.create({
|
|
60
63
|
...this,
|
|
@@ -62,10 +65,38 @@ export class Event extends Model {
|
|
|
62
65
|
})
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @deprecated
|
|
70
|
+
*/
|
|
65
71
|
getPrivateStructure(group?: Group|null) {
|
|
66
72
|
return EventStruct.create({
|
|
67
73
|
...this,
|
|
68
74
|
group: group ? group.getPrivateStructure() : null
|
|
69
75
|
})
|
|
70
76
|
}
|
|
77
|
+
|
|
78
|
+
async syncGroupRequirements(group: Group|null) {
|
|
79
|
+
if (!group) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
group.settings.requireDefaultAgeGroupIds = this.meta.defaultAgeGroupIds ?? []
|
|
84
|
+
group.settings.requireGroupIds = this.meta.groupIds ?? []
|
|
85
|
+
|
|
86
|
+
if (this.organizationId) {
|
|
87
|
+
// This is a not-national event, so require the organization
|
|
88
|
+
group.settings.requireOrganizationIds = this.meta.organizationTagIds ?? []
|
|
89
|
+
group.settings.requireOrganizationTags = []
|
|
90
|
+
group.settings.requirePlatformMembershipOn = null
|
|
91
|
+
} else {
|
|
92
|
+
group.settings.requireOrganizationTags = this.meta.organizationTagIds ?? []
|
|
93
|
+
|
|
94
|
+
// Everyone can register
|
|
95
|
+
group.settings.requireOrganizationIds = []
|
|
96
|
+
|
|
97
|
+
// But they need a valid platform membership
|
|
98
|
+
group.settings.requirePlatformMembershipOn = this.endDate
|
|
99
|
+
}
|
|
100
|
+
await group.save()
|
|
101
|
+
}
|
|
71
102
|
}
|
package/src/models/Group.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { column, Database, ManyToOneRelation, Model, OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
|
-
import { GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType } from '@stamhoofd/structures';
|
|
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 { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { Member, MemberWithRegistrations, OrganizationRegistrationPeriod, Payment, Registration, User } from './';
|
|
7
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
8
|
+
import { ArrayDecoder } from '@simonbackx/simple-encoding';
|
|
7
9
|
|
|
8
10
|
if (Member === undefined) {
|
|
9
11
|
throw new Error("Import Member is undefined")
|
|
@@ -42,6 +44,9 @@ export class Group extends Model {
|
|
|
42
44
|
@column({ type: "string" })
|
|
43
45
|
organizationId: string;
|
|
44
46
|
|
|
47
|
+
@column({ type: "string", nullable: true })
|
|
48
|
+
waitingListId: string | null = null
|
|
49
|
+
|
|
45
50
|
@column({ type: "string" })
|
|
46
51
|
periodId: string;
|
|
47
52
|
|
|
@@ -82,17 +87,20 @@ export class Group extends Model {
|
|
|
82
87
|
})
|
|
83
88
|
deletedAt: Date | null = null
|
|
84
89
|
|
|
85
|
-
/**
|
|
86
|
-
* Every time a new registration period starts, this number increases. This is used to mark all older registrations as 'out of date' automatically
|
|
87
|
-
*/
|
|
88
90
|
@column({ type: "string" })
|
|
89
91
|
status = GroupStatus.Open;
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Editing this field is only allowed when running inside the QueueHandler
|
|
95
|
+
*/
|
|
96
|
+
@column({ type: "json", decoder: new ArrayDecoder(StockReservation) })
|
|
97
|
+
stockReservations: StockReservation[] = []
|
|
98
|
+
|
|
91
99
|
static async getAll(organizationId: string, periodId: string|null, active = true) {
|
|
92
100
|
const w: any = periodId ? {periodId} : {}
|
|
93
101
|
if (active) {
|
|
94
102
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
95
|
-
return await Group.where({ organizationId, deletedAt: null, ...w
|
|
103
|
+
return await Group.where({ organizationId, deletedAt: null, ...w })
|
|
96
104
|
}
|
|
97
105
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
98
106
|
return await Group.where({ organizationId, ...w })
|
|
@@ -186,10 +194,16 @@ export class Group extends Model {
|
|
|
186
194
|
|
|
187
195
|
}
|
|
188
196
|
|
|
197
|
+
/**
|
|
198
|
+
* @deprecated
|
|
199
|
+
*/
|
|
189
200
|
getStructure() {
|
|
190
201
|
return GroupStruct.create({ ...this, privateSettings: null })
|
|
191
202
|
}
|
|
192
203
|
|
|
204
|
+
/**
|
|
205
|
+
* @deprecated
|
|
206
|
+
*/
|
|
193
207
|
getPrivateStructure() {
|
|
194
208
|
return GroupStruct.create(this)
|
|
195
209
|
}
|
|
@@ -207,18 +221,13 @@ export class Group extends Model {
|
|
|
207
221
|
|
|
208
222
|
async updateOccupancy() {
|
|
209
223
|
this.settings.registeredMembers = await Group.getCount(
|
|
210
|
-
"groupId = ? and
|
|
211
|
-
[this.id
|
|
224
|
+
"groupId = ? and registeredAt is not null AND deactivatedAt is null",
|
|
225
|
+
[this.id]
|
|
212
226
|
)
|
|
213
227
|
|
|
214
228
|
this.settings.reservedMembers = await Group.getCount(
|
|
215
|
-
"groupId = ? and
|
|
216
|
-
[this.id,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
this.settings.waitingListSize = await Group.getCount(
|
|
220
|
-
"groupId = ? and cycle = ? and waitingList = 1",
|
|
221
|
-
[this.id, this.cycle, new Date()]
|
|
229
|
+
"groupId = ? and registeredAt is null AND (canRegister = 1 OR reservedUntil >= ?)",
|
|
230
|
+
[this.id, new Date()]
|
|
222
231
|
)
|
|
223
232
|
}
|
|
224
233
|
|
|
@@ -253,6 +262,14 @@ export class Group extends Model {
|
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
for (const group of allGroups) {
|
|
265
|
+
if (group.periodId !== period.periodId) {
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (group.type !== GroupType.Membership) {
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
|
|
256
273
|
if (!reachable.get(group.id) && group.deletedAt === null) {
|
|
257
274
|
console.log("Deleting unreachable group "+group.id+" from organization "+organizationId + " org period "+period.id)
|
|
258
275
|
group.deletedAt = new Date()
|
|
@@ -263,6 +280,28 @@ export class Group extends Model {
|
|
|
263
280
|
}
|
|
264
281
|
}
|
|
265
282
|
|
|
283
|
+
static async applyStockReservations(groupId: string, addStockReservations: StockReservation[], free = false) {
|
|
284
|
+
await QueueHandler.schedule('group-stock-update-'+groupId, async () => {
|
|
285
|
+
const updatedGroup = await Group.getByID(groupId)
|
|
286
|
+
if (!updatedGroup) {
|
|
287
|
+
throw new Error("Expected group")
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!free) {
|
|
291
|
+
updatedGroup.stockReservations = StockReservation.added(updatedGroup.stockReservations, addStockReservations)
|
|
292
|
+
} else {
|
|
293
|
+
updatedGroup.stockReservations = StockReservation.removed(updatedGroup.stockReservations, addStockReservations)
|
|
294
|
+
}
|
|
295
|
+
await updatedGroup.save()
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
static async freeStockReservations(groupId: string, reservations: StockReservation[]) {
|
|
301
|
+
return await this.applyStockReservations(groupId, reservations, true)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
266
305
|
}
|
|
267
306
|
|
|
268
307
|
Registration.group = new ManyToOneRelation(Group, "group")
|
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, GroupStatus } from '@stamhoofd/structures';
|
|
3
|
+
import { Member as MemberStruct, MemberDetails, MemberWithRegistrationsBlob, RegistrationWithMember as RegistrationWithMemberStruct, User as UserStruct, GroupStatus, TinyMember } from '@stamhoofd/structures';
|
|
4
4
|
import { Formatter, Sorter } from '@stamhoofd/utility';
|
|
5
5
|
import { v4 as uuidv4 } from "uuid";
|
|
6
6
|
|
|
@@ -144,7 +144,7 @@ export class Member extends Model {
|
|
|
144
144
|
}
|
|
145
145
|
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
146
146
|
|
|
147
|
-
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}\`.\`
|
|
147
|
+
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}\`.\`canRegister\` = 1)\n`
|
|
148
148
|
|
|
149
149
|
// 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)
|
|
150
150
|
query += `where \`${Registration.table}\`.\`${Registration.primary.name}\` IN (?)`
|
|
@@ -177,15 +177,15 @@ export class Member extends Model {
|
|
|
177
177
|
/**
|
|
178
178
|
* Fetch all registrations with members with their corresponding (valid) registrations
|
|
179
179
|
*/
|
|
180
|
-
static async getRegistrationWithMembersForGroup(groupId: string
|
|
180
|
+
static async getRegistrationWithMembersForGroup(groupId: string): Promise<RegistrationWithMember[]> {
|
|
181
181
|
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
182
182
|
|
|
183
|
-
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}\`.\`
|
|
183
|
+
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}\`.\`canRegister\` = 1)\n`
|
|
184
184
|
|
|
185
185
|
// 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)
|
|
186
|
-
query += `where \`${Registration.table}\`.\`groupId\` =
|
|
186
|
+
query += `where \`${Registration.table}\`.\`groupId\` = ?`
|
|
187
187
|
|
|
188
|
-
const [results] = await Database.select(query, [groupId
|
|
188
|
+
const [results] = await Database.select(query, [groupId])
|
|
189
189
|
const registrations: RegistrationWithMember[] = []
|
|
190
190
|
|
|
191
191
|
// 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.
|
|
@@ -261,7 +261,7 @@ export class Member extends Model {
|
|
|
261
261
|
return []
|
|
262
262
|
}
|
|
263
263
|
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()}, ${User.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
264
|
-
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}\`.\`
|
|
264
|
+
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`
|
|
265
265
|
query += Member.users.joinQuery(Member.table, User.table)+"\n"
|
|
266
266
|
|
|
267
267
|
// 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)
|
|
@@ -380,7 +380,11 @@ export class Member extends Model {
|
|
|
380
380
|
return RegistrationWithMemberStruct.create({
|
|
381
381
|
...registration.getStructure(),
|
|
382
382
|
cycle: registration.cycle,
|
|
383
|
-
member:
|
|
383
|
+
member: TinyMember.create({
|
|
384
|
+
id: registration.member.id,
|
|
385
|
+
firstName: registration.member.firstName,
|
|
386
|
+
lastName: registration.member.lastName,
|
|
387
|
+
}),
|
|
384
388
|
})
|
|
385
389
|
}
|
|
386
390
|
|
|
@@ -415,10 +419,9 @@ export class Member extends Model {
|
|
|
415
419
|
}
|
|
416
420
|
|
|
417
421
|
async updateMemberships(this: MemberWithRegistrations) {
|
|
418
|
-
console.log('Updating memberships for member: ' + this.id)
|
|
419
422
|
return await QueueHandler.schedule('updateMemberships-' + this.id, async () => {
|
|
420
423
|
const platform = await Platform.getShared()
|
|
421
|
-
const registrations = this.registrations.filter(r => r.group.periodId == platform.periodId &&
|
|
424
|
+
const registrations = this.registrations.filter(r => r.group.periodId == platform.periodId && r.registeredAt && !r.deactivatedAt)
|
|
422
425
|
|
|
423
426
|
const defaultMemberships = registrations.flatMap(r => {
|
|
424
427
|
if (!r.group.defaultAgeGroupId) {
|
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
|
+
}
|
|
@@ -264,14 +264,9 @@ export class Organization extends Model {
|
|
|
264
264
|
return this.id+"." + defaultDomain;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
async getPeriod(
|
|
267
|
+
async getPeriod() {
|
|
268
268
|
const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: this.periodId, organizationId: this.id }, {limit: 1})
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (!period) {
|
|
272
|
-
throw new Error("Period not found")
|
|
273
|
-
}
|
|
274
|
-
|
|
269
|
+
|
|
275
270
|
let oPeriod: OrganizationRegistrationPeriod;
|
|
276
271
|
if (oPeriods.length == 0) {
|
|
277
272
|
// Automatically create a period
|
|
@@ -293,19 +288,12 @@ export class Organization extends Model {
|
|
|
293
288
|
} else {
|
|
294
289
|
oPeriod = oPeriods[0];
|
|
295
290
|
}
|
|
296
|
-
const groups = emptyGroups ? [] : (await (await import("./Group")).Group.getAll(this.id, this.periodId))
|
|
297
291
|
|
|
298
|
-
return
|
|
299
|
-
organizationPeriod: oPeriod,
|
|
300
|
-
period,
|
|
301
|
-
groups
|
|
302
|
-
}
|
|
292
|
+
return oPeriod
|
|
303
293
|
}
|
|
304
294
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const struct = OrganizationStruct.create({
|
|
295
|
+
getBaseStructure(): OrganizationStruct {
|
|
296
|
+
return OrganizationStruct.create({
|
|
309
297
|
id: this.id,
|
|
310
298
|
name: this.name,
|
|
311
299
|
meta: this.meta,
|
|
@@ -313,25 +301,8 @@ export class Organization extends Model {
|
|
|
313
301
|
registerDomain: this.registerDomain,
|
|
314
302
|
uri: this.uri,
|
|
315
303
|
website: this.website,
|
|
316
|
-
groups: groups.map(g => g.getStructure()),
|
|
317
304
|
createdAt: this.createdAt,
|
|
318
|
-
period: organizationPeriod.getStructure(period, groups)
|
|
319
305
|
})
|
|
320
|
-
|
|
321
|
-
if (this.meta.modules.disableActivities) {
|
|
322
|
-
// Only show groups that are in a given category
|
|
323
|
-
struct.groups = struct.categoryTree.categories[0]?.groups ?? []
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (emptyGroups) {
|
|
327
|
-
// Reduce data
|
|
328
|
-
struct.meta = struct.meta.clone()
|
|
329
|
-
struct.meta.categories = []
|
|
330
|
-
struct.meta.recordsConfiguration = OrganizationRecordsConfiguration.create({})
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return struct
|
|
335
306
|
}
|
|
336
307
|
|
|
337
308
|
async updateDNSRecords() {
|