@stamhoofd/backend 2.36.2 → 2.38.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/package.json +10 -10
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -2
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +8 -0
- package/src/endpoints/auth/CreateAdminEndpoint.ts +4 -3
- package/src/endpoints/auth/PatchUserEndpoint.ts +2 -2
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +12 -0
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -0
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +6 -0
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +1 -1
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -10
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +46 -24
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +11 -4
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +2 -0
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +453 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +19 -2
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +13 -2
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
- package/src/excel-loaders/members.ts +87 -24
- package/src/helpers/AddressValidator.ts +11 -0
- package/src/helpers/AdminPermissionChecker.ts +14 -1
- package/src/helpers/Context.ts +2 -2
- package/src/helpers/MembershipCharger.ts +84 -2
- package/src/helpers/fetchToAsyncIterator.ts +3 -4
- package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +40 -0
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { XlsxBuiltInNumberFormat } from "@stamhoofd/excel-writer";
|
|
2
|
-
import {
|
|
2
|
+
import { Platform } from "@stamhoofd/models";
|
|
3
|
+
import { ExcelExportType, Gender, GroupType, LimitedFilteredRequest, MemberWithRegistrationsBlob, PlatformFamily, PlatformMember, UnencodeablePaginatedResponse, Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
3
4
|
import { ExportToExcelEndpoint } from "../endpoints/global/files/ExportToExcelEndpoint";
|
|
4
5
|
import { GetMembersEndpoint } from "../endpoints/global/members/GetMembersEndpoint";
|
|
5
6
|
import { Context } from "../helpers/Context";
|
|
6
7
|
import { XlsxTransformerColumnHelper } from "../helpers/xlsxAddressTransformerColumnFactory";
|
|
8
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
9
|
+
import { AuthenticatedStructures } from "../helpers/AuthenticatedStructures";
|
|
7
10
|
|
|
8
11
|
ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
9
12
|
fetch: async (query: LimitedFilteredRequest) => {
|
|
10
13
|
const result = await GetMembersEndpoint.buildData(query)
|
|
11
14
|
|
|
12
|
-
return new
|
|
13
|
-
results: result.results
|
|
15
|
+
return new UnencodeablePaginatedResponse({
|
|
16
|
+
results: PlatformFamily.createSingles(result.results, {
|
|
17
|
+
contextOrganization: Context.organization ? (await AuthenticatedStructures.organization(Context.organization)) : null,
|
|
18
|
+
platform: await Platform.getSharedStruct()
|
|
19
|
+
}),
|
|
14
20
|
next: result.next
|
|
15
21
|
});
|
|
16
22
|
},
|
|
@@ -23,7 +29,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
23
29
|
id: 'id',
|
|
24
30
|
name: 'ID',
|
|
25
31
|
width: 20,
|
|
26
|
-
getValue: (object:
|
|
32
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
27
33
|
value: object.id
|
|
28
34
|
})
|
|
29
35
|
},
|
|
@@ -31,7 +37,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
31
37
|
id: 'memberNumber',
|
|
32
38
|
name: 'Nummer',
|
|
33
39
|
width: 20,
|
|
34
|
-
getValue: (object:
|
|
40
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
35
41
|
value: object.details.memberNumber
|
|
36
42
|
})
|
|
37
43
|
},
|
|
@@ -39,7 +45,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
39
45
|
id: 'firstName',
|
|
40
46
|
name: 'Voornaam',
|
|
41
47
|
width: 20,
|
|
42
|
-
getValue: (object:
|
|
48
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
43
49
|
value: object.details.firstName
|
|
44
50
|
})
|
|
45
51
|
},
|
|
@@ -47,7 +53,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
47
53
|
id: 'lastName',
|
|
48
54
|
name: 'Achternaam',
|
|
49
55
|
width: 20,
|
|
50
|
-
getValue: (object:
|
|
56
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
51
57
|
value: object.details.lastName
|
|
52
58
|
})
|
|
53
59
|
},
|
|
@@ -55,7 +61,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
55
61
|
id: 'birthDay',
|
|
56
62
|
name: 'Geboortedatum',
|
|
57
63
|
width: 20,
|
|
58
|
-
getValue: (object:
|
|
64
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
59
65
|
value: object.details.birthDay,
|
|
60
66
|
style: {
|
|
61
67
|
numberFormat: {
|
|
@@ -68,7 +74,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
68
74
|
id: 'age',
|
|
69
75
|
name: 'Leeftijd',
|
|
70
76
|
width: 20,
|
|
71
|
-
getValue: (object:
|
|
77
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
72
78
|
value: object.details.age,
|
|
73
79
|
})
|
|
74
80
|
},
|
|
@@ -76,7 +82,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
76
82
|
id: 'gender',
|
|
77
83
|
name: 'Geslacht',
|
|
78
84
|
width: 20,
|
|
79
|
-
getValue: (object:
|
|
85
|
+
getValue: ({patchedMember: object}: PlatformMember) => {
|
|
80
86
|
const gender = object.details.gender;
|
|
81
87
|
|
|
82
88
|
return ({
|
|
@@ -88,7 +94,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
88
94
|
id: 'phone',
|
|
89
95
|
name: 'Telefoonnummer',
|
|
90
96
|
width: 20,
|
|
91
|
-
getValue: (object:
|
|
97
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
92
98
|
value: object.details.phone,
|
|
93
99
|
})
|
|
94
100
|
},
|
|
@@ -96,14 +102,14 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
96
102
|
id: 'email',
|
|
97
103
|
name: 'E-mailadres',
|
|
98
104
|
width: 20,
|
|
99
|
-
getValue: (object:
|
|
105
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
100
106
|
value: object.details.email,
|
|
101
107
|
})
|
|
102
108
|
},
|
|
103
|
-
XlsxTransformerColumnHelper.createAddressColumns<
|
|
109
|
+
XlsxTransformerColumnHelper.createAddressColumns<PlatformMember>({
|
|
104
110
|
matchId: 'address',
|
|
105
111
|
identifier: 'Adres',
|
|
106
|
-
getAddress: (object) => {
|
|
112
|
+
getAddress: ({patchedMember: object}: PlatformMember) => {
|
|
107
113
|
// get member address if exists
|
|
108
114
|
const memberAddress = object.details.address;
|
|
109
115
|
if(memberAddress) {
|
|
@@ -124,7 +130,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
124
130
|
id: 'securityCode',
|
|
125
131
|
name: 'Beveiligingscode',
|
|
126
132
|
width: 20,
|
|
127
|
-
getValue: (object:
|
|
133
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
128
134
|
value: object.details.securityCode,
|
|
129
135
|
})
|
|
130
136
|
},
|
|
@@ -132,7 +138,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
132
138
|
id: 'uitpasNumber',
|
|
133
139
|
name: 'UiTPAS-nummer',
|
|
134
140
|
width: 20,
|
|
135
|
-
getValue: (object:
|
|
141
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
136
142
|
value: object.details.uitpasNumber,
|
|
137
143
|
})
|
|
138
144
|
},
|
|
@@ -141,7 +147,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
141
147
|
// todo: use correct term
|
|
142
148
|
name: 'Financiële ondersteuning',
|
|
143
149
|
width: 20,
|
|
144
|
-
getValue: (object:
|
|
150
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
145
151
|
value: XlsxTransformerColumnHelper.formatBoolean(object.details.requiresFinancialSupport?.value),
|
|
146
152
|
})
|
|
147
153
|
},
|
|
@@ -149,11 +155,69 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
149
155
|
id: 'notes',
|
|
150
156
|
name: 'Notities',
|
|
151
157
|
width: 20,
|
|
152
|
-
getValue: (object:
|
|
158
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
153
159
|
value: object.details.notes,
|
|
154
160
|
})
|
|
155
161
|
},
|
|
156
162
|
|
|
163
|
+
{
|
|
164
|
+
id: 'organization',
|
|
165
|
+
name: 'Groep',
|
|
166
|
+
width: 40,
|
|
167
|
+
getValue: (member: PlatformMember) => {
|
|
168
|
+
const organizations = member.filterOrganizations({currentPeriod: true, types: [GroupType.Membership]})
|
|
169
|
+
const str = Formatter.joinLast(organizations.map(o => o.name).sort(), ', ', ' en ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f')
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
value: str
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
id: 'uri',
|
|
179
|
+
name: 'Groepsnummer',
|
|
180
|
+
width: 30,
|
|
181
|
+
getValue: (member: PlatformMember) => {
|
|
182
|
+
const organizations = member.filterOrganizations({currentPeriod: true, types: [GroupType.Membership]})
|
|
183
|
+
const str = Formatter.joinLast(organizations.map(o => o.uri).sort(), ', ', ' en ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f')
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
value: str
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
{
|
|
192
|
+
id: 'group',
|
|
193
|
+
name: 'Leeftijdsgroep',
|
|
194
|
+
width: 40,
|
|
195
|
+
getValue: (member: PlatformMember) => {
|
|
196
|
+
const groups = member.filterRegistrations({currentPeriod: true, types: [GroupType.Membership], organizationId: Context.organization?.id})
|
|
197
|
+
const str = Formatter.joinLast(Formatter.uniqueArray(groups.map(o => o.group.settings.name)).sort(), ', ', ' en ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f')
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
value: str
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
{
|
|
206
|
+
id: 'defaultAgeGroup',
|
|
207
|
+
name: 'Standaard leeftijdsgroep',
|
|
208
|
+
width: 40,
|
|
209
|
+
getValue: (member: PlatformMember) => {
|
|
210
|
+
const groups = member.filterRegistrations({currentPeriod: true, types: [GroupType.Membership], organizationId: Context.organization?.id})
|
|
211
|
+
const defaultAgeGroupIds = Formatter.uniqueArray(groups.filter(o => o.group.defaultAgeGroupId))
|
|
212
|
+
const defaultAgeGroups = defaultAgeGroupIds.map(o => PlatformStruct.shared.config.defaultAgeGroups.find(g => g.id === o.group.defaultAgeGroupId)?.name ?? 'verwijderde leeftijdsgroep')
|
|
213
|
+
const str = Formatter.joinLast(Formatter.uniqueArray(defaultAgeGroups).sort(), ', ', ' en ') || Context.i18n.$t('1a16a32a-7ee4-455d-af3d-6073821efa8f')
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
value: str
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
157
221
|
...XlsxTransformerColumnHelper.creatColumnsForParents(),
|
|
158
222
|
|
|
159
223
|
// unverified data
|
|
@@ -161,7 +225,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
161
225
|
id: 'unverifiedPhones',
|
|
162
226
|
name: 'Niet-geverifieerde telefoonnummers',
|
|
163
227
|
width: 20,
|
|
164
|
-
getValue: (object:
|
|
228
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
165
229
|
value: object.details.unverifiedPhones.join(', '),
|
|
166
230
|
})
|
|
167
231
|
},
|
|
@@ -169,7 +233,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
169
233
|
id: 'unverifiedEmails',
|
|
170
234
|
name: 'Niet-geverifieerde e-mailadressen',
|
|
171
235
|
width: 20,
|
|
172
|
-
getValue: (object:
|
|
236
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
173
237
|
value: object.details.unverifiedEmails.join(', '),
|
|
174
238
|
})
|
|
175
239
|
},
|
|
@@ -183,7 +247,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
183
247
|
id: 'unverifiedAddresses',
|
|
184
248
|
name: 'Niet-geverifieerde adressen',
|
|
185
249
|
width: 20,
|
|
186
|
-
getValue: (object:
|
|
250
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
187
251
|
value: object.details.unverifiedAddresses.map(a => a.toString()).join('; '),
|
|
188
252
|
})
|
|
189
253
|
},
|
|
@@ -191,9 +255,8 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
191
255
|
// Dynamic records
|
|
192
256
|
{
|
|
193
257
|
match(id) {
|
|
194
|
-
console.log('match', id)
|
|
195
258
|
if (id.startsWith('recordAnswers.')) {
|
|
196
|
-
const platform =
|
|
259
|
+
const platform = PlatformStruct.shared
|
|
197
260
|
const organization = Context.organization
|
|
198
261
|
|
|
199
262
|
const recordSettings = [
|
|
@@ -219,7 +282,7 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Members, {
|
|
|
219
282
|
id: `recordAnswers.${recordSettingId}.${index}`,
|
|
220
283
|
name: columnName,
|
|
221
284
|
width: 20,
|
|
222
|
-
getValue: (object:
|
|
285
|
+
getValue: ({patchedMember: object}: PlatformMember) => ({
|
|
223
286
|
value: object.details.recordAnswers.get(recordSettingId)?.excelValues[index]?.value ?? ''
|
|
224
287
|
})
|
|
225
288
|
}
|
|
@@ -55,6 +55,17 @@ export class AddressValidatorStatic {
|
|
|
55
55
|
})
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
if (address.country !== Country.Belgium && address.country !== Country.Netherlands) {
|
|
60
|
+
// No validation for other countries
|
|
61
|
+
return ValidatedAddress.create(Object.assign({ ... address }, {
|
|
62
|
+
postalCode: address.postalCode,
|
|
63
|
+
city: address.city,
|
|
64
|
+
cityId: 'unknown',
|
|
65
|
+
parentCityId: null,
|
|
66
|
+
provinceId: 'unknown',
|
|
67
|
+
}))
|
|
68
|
+
}
|
|
58
69
|
|
|
59
70
|
const city = await PostalCode.getCity(postalCode, address.city, address.country)
|
|
60
71
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, PatchMap } from "@simonbackx/simple-encoding"
|
|
2
2
|
import { SimpleError } from "@simonbackx/simple-errors"
|
|
3
|
-
import { BalanceItem, Document, DocumentTemplate, EmailTemplate, Event, Group, Member, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
3
|
+
import { BalanceItem, CachedOutstandingBalance, Document, DocumentTemplate, EmailTemplate, Event, Group, Member, MemberPlatformMembership, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, Registration, User, Webshop } from "@stamhoofd/models"
|
|
4
4
|
import { AccessRight, FinancialSupportSettings, GroupCategory, GroupStatus, MemberWithRegistrationsBlob, PermissionLevel, PermissionsResourceType, Platform as PlatformStruct, RecordCategory } from "@stamhoofd/structures"
|
|
5
5
|
import { Formatter } from "@stamhoofd/utility"
|
|
6
6
|
|
|
@@ -250,7 +250,20 @@ export class AdminPermissionChecker {
|
|
|
250
250
|
* Only full admins can delete members permanently
|
|
251
251
|
*/
|
|
252
252
|
async canDeleteMember(member: MemberWithRegistrations) {
|
|
253
|
+
if (member.registrations.length === 0 && this.isUserManager(member)) {
|
|
254
|
+
const platformMemberships = await MemberPlatformMembership.where({ memberId: member.id })
|
|
255
|
+
if (platformMemberships.length === 0) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const cachedBalance = await CachedOutstandingBalance.getForObjects([member.id])
|
|
260
|
+
if (cachedBalance.length === 0 || (cachedBalance[0].amount === 0 && cachedBalance[0].amountPending === 0)) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
253
265
|
if (member.organizationId) {
|
|
266
|
+
// Not a platform
|
|
254
267
|
return await this.hasFullAccess(member.organizationId)
|
|
255
268
|
}
|
|
256
269
|
return this.hasPlatformFullAccess()
|
package/src/helpers/Context.ts
CHANGED
|
@@ -133,8 +133,8 @@ export class ContextInstance {
|
|
|
133
133
|
return await this.setOrganizationScope()
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
async setOrganizationScope() {
|
|
137
|
-
const organization = await Organization.fromApiHost(this.request.host);
|
|
136
|
+
async setOrganizationScope(options?: {allowInactive?: boolean}) {
|
|
137
|
+
const organization = await Organization.fromApiHost(this.request.host, options);
|
|
138
138
|
|
|
139
139
|
this.organization = organization
|
|
140
140
|
this.i18n.switchToLocale({ country: organization.address.country })
|
|
@@ -27,6 +27,7 @@ export const MembershipCharger = {
|
|
|
27
27
|
|
|
28
28
|
let createdCount = 0;
|
|
29
29
|
let createdPrice = 0;
|
|
30
|
+
const chunkSize = 100;
|
|
30
31
|
|
|
31
32
|
// eslint-disable-next-line no-constant-condition
|
|
32
33
|
while (true) {
|
|
@@ -35,7 +36,7 @@ export const MembershipCharger = {
|
|
|
35
36
|
.where('balanceItemId', null)
|
|
36
37
|
.where('deletedAt', null)
|
|
37
38
|
.whereNot('organizationId', chargeVia)
|
|
38
|
-
.limit(
|
|
39
|
+
.limit(chunkSize)
|
|
39
40
|
.orderBy(
|
|
40
41
|
new SQLOrderBy({
|
|
41
42
|
column: SQL.column('id'),
|
|
@@ -57,6 +58,7 @@ export const MembershipCharger = {
|
|
|
57
58
|
if (membership.balanceItemId) {
|
|
58
59
|
continue;
|
|
59
60
|
}
|
|
61
|
+
|
|
60
62
|
const type = getType(membership.membershipTypeId);
|
|
61
63
|
if (!type) {
|
|
62
64
|
console.error('Unknown membership type id ', membership.membershipTypeId)
|
|
@@ -74,6 +76,14 @@ export const MembershipCharger = {
|
|
|
74
76
|
continue;
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
// Force price update (required because could have changed - especially for free memberships in combination with deletes)
|
|
80
|
+
try {
|
|
81
|
+
await membership.calculatePrice(member)
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error('Failed to update price for membership. Not charged.', membership.id, e)
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
const balanceItem = new BalanceItem();
|
|
78
88
|
balanceItem.unitPrice = membership.price
|
|
79
89
|
balanceItem.amount = 1
|
|
@@ -101,6 +111,7 @@ export const MembershipCharger = {
|
|
|
101
111
|
|
|
102
112
|
await balanceItem.save();
|
|
103
113
|
membership.balanceItemId = balanceItem.id;
|
|
114
|
+
membership.maximumFreeAmount = membership.freeAmount;
|
|
104
115
|
await membership.save()
|
|
105
116
|
|
|
106
117
|
createdBalanceItems.push(balanceItem)
|
|
@@ -111,7 +122,7 @@ export const MembershipCharger = {
|
|
|
111
122
|
|
|
112
123
|
await BalanceItem.updateOutstanding(createdBalanceItems)
|
|
113
124
|
|
|
114
|
-
if (memberships.length <
|
|
125
|
+
if (memberships.length < chunkSize) {
|
|
115
126
|
break;
|
|
116
127
|
}
|
|
117
128
|
|
|
@@ -124,5 +135,76 @@ export const MembershipCharger = {
|
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
console.log('Charged ' + Formatter.integer(createdCount) +' memberships, for a total value of ' + Formatter.price(createdPrice))
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
async updatePrices(organizationId?: string) {
|
|
141
|
+
console.log('Update prices...')
|
|
142
|
+
|
|
143
|
+
// Loop all
|
|
144
|
+
let lastId = "";
|
|
145
|
+
let createdCount = 0;
|
|
146
|
+
const chunkSize = 100;
|
|
147
|
+
|
|
148
|
+
// eslint-disable-next-line no-constant-condition
|
|
149
|
+
while (true) {
|
|
150
|
+
const q = MemberPlatformMembership.select()
|
|
151
|
+
.where('id', SQLWhereSign.Greater, lastId)
|
|
152
|
+
.where('balanceItemId', null)
|
|
153
|
+
.where('deletedAt', null);
|
|
154
|
+
|
|
155
|
+
if (organizationId) {
|
|
156
|
+
q.where('organizationId', organizationId)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const memberships = await q
|
|
160
|
+
.limit(chunkSize)
|
|
161
|
+
.orderBy(
|
|
162
|
+
new SQLOrderBy({
|
|
163
|
+
column: SQL.column('id'),
|
|
164
|
+
direction: 'ASC'
|
|
165
|
+
})
|
|
166
|
+
)
|
|
167
|
+
.fetch();
|
|
168
|
+
|
|
169
|
+
if (memberships.length === 0) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const memberIds = Formatter.uniqueArray(memberships.map(m => m.memberId))
|
|
174
|
+
const members = await Member.getByIDs(...memberIds)
|
|
175
|
+
|
|
176
|
+
for (const membership of memberships) {
|
|
177
|
+
const member = members.find(m => m.id === membership.memberId)
|
|
178
|
+
|
|
179
|
+
if (!member) {
|
|
180
|
+
console.error('Unexpected missing member id ', membership.memberId, 'for membership', membership.id)
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Force price update (required because could have changed - especially for free memberships in combination with deletes)
|
|
185
|
+
try {
|
|
186
|
+
await membership.calculatePrice(member)
|
|
187
|
+
await membership.save()
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.error('Failed to update price for membership', membership.id, e)
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
console.log('Updated price for membership', membership.id, membership.price)
|
|
193
|
+
createdCount += 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (memberships.length < chunkSize) {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const z = lastId;
|
|
201
|
+
lastId = memberships[memberships.length - 1].id;
|
|
202
|
+
|
|
203
|
+
if (lastId === z) {
|
|
204
|
+
throw new Error('Unexpected infinite loop found in MembershipCharger')
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log('Updated prices of ' + Formatter.integer(createdCount) +' memberships')
|
|
127
209
|
}
|
|
128
210
|
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LimitedFilteredRequest, PaginatedResponse } from "@stamhoofd/structures";
|
|
1
|
+
import { IPaginatedResponse, LimitedFilteredRequest } from "@stamhoofd/structures";
|
|
3
2
|
|
|
4
|
-
export function fetchToAsyncIterator<T
|
|
3
|
+
export function fetchToAsyncIterator<T>(
|
|
5
4
|
initialFilter: LimitedFilteredRequest,
|
|
6
5
|
loader: {
|
|
7
|
-
fetch(request: LimitedFilteredRequest): Promise<
|
|
6
|
+
fetch(request: LimitedFilteredRequest): Promise<IPaginatedResponse<T, LimitedFilteredRequest>>
|
|
8
7
|
}
|
|
9
8
|
): AsyncIterable<T> {
|
|
10
9
|
return {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { logger } from '@simonbackx/simple-logging';
|
|
3
|
+
import { BalanceItem } from '@stamhoofd/models';
|
|
4
|
+
|
|
5
|
+
export default new Migration(async () => {
|
|
6
|
+
if (STAMHOOFD.environment == "test") {
|
|
7
|
+
console.log("skipped in tests")
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
process.stdout.write('\n');
|
|
12
|
+
let c = 0;
|
|
13
|
+
let id: string = '';
|
|
14
|
+
|
|
15
|
+
await logger.setContext({tags: ['silent-seed', 'seed']}, async () => {
|
|
16
|
+
while(true) {
|
|
17
|
+
const items = await BalanceItem.where({
|
|
18
|
+
id: {
|
|
19
|
+
value: id,
|
|
20
|
+
sign: '>'
|
|
21
|
+
}
|
|
22
|
+
}, {limit: 1000, sort: ['id']});
|
|
23
|
+
|
|
24
|
+
await BalanceItem.updateOutstanding(items)
|
|
25
|
+
|
|
26
|
+
c += items.length;
|
|
27
|
+
process.stdout.write('.');
|
|
28
|
+
|
|
29
|
+
if (items.length < 1000) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
id = items[items.length - 1].id;
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
console.log("Updated outstanding balance for " + c + " items")
|
|
37
|
+
|
|
38
|
+
// Do something here
|
|
39
|
+
return Promise.resolve()
|
|
40
|
+
})
|