@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.
Files changed (24) hide show
  1. package/package.json +10 -10
  2. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +1 -2
  3. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +8 -0
  4. package/src/endpoints/auth/CreateAdminEndpoint.ts +4 -3
  5. package/src/endpoints/auth/PatchUserEndpoint.ts +2 -2
  6. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +12 -0
  7. package/src/endpoints/global/email/CreateEmailEndpoint.ts +2 -0
  8. package/src/endpoints/global/email/PatchEmailEndpoint.ts +6 -0
  9. package/src/endpoints/global/events/PatchEventsEndpoint.ts +1 -1
  10. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +9 -10
  11. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +46 -24
  12. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +11 -4
  13. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +2 -0
  14. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +453 -0
  15. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +19 -2
  16. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +13 -2
  17. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +1 -1
  18. package/src/excel-loaders/members.ts +87 -24
  19. package/src/helpers/AddressValidator.ts +11 -0
  20. package/src/helpers/AdminPermissionChecker.ts +14 -1
  21. package/src/helpers/Context.ts +2 -2
  22. package/src/helpers/MembershipCharger.ts +84 -2
  23. package/src/helpers/fetchToAsyncIterator.ts +3 -4
  24. 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 { ExcelExportType, Gender, LimitedFilteredRequest, MemberWithRegistrationsBlob, PaginatedResponse, Platform } from "@stamhoofd/structures";
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 PaginatedResponse({
13
- results: result.results.members,
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => {
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
105
+ getValue: ({patchedMember: object}: PlatformMember) => ({
100
106
  value: object.details.email,
101
107
  })
102
108
  },
103
- XlsxTransformerColumnHelper.createAddressColumns<MemberWithRegistrationsBlob>({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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: MemberWithRegistrationsBlob) => ({
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 = Platform.shared
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: MemberWithRegistrationsBlob) => ({
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()
@@ -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(100)
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 < 100) {
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 { EncodableObject } from "@simonbackx/simple-encoding";
2
- import { LimitedFilteredRequest, PaginatedResponse } from "@stamhoofd/structures";
1
+ import { IPaginatedResponse, LimitedFilteredRequest } from "@stamhoofd/structures";
3
2
 
4
- export function fetchToAsyncIterator<T extends EncodableObject>(
3
+ export function fetchToAsyncIterator<T>(
5
4
  initialFilter: LimitedFilteredRequest,
6
5
  loader: {
7
- fetch(request: LimitedFilteredRequest): Promise<PaginatedResponse<T, LimitedFilteredRequest>>
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
+ })