@stamhoofd/backend 2.114.0 → 2.115.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.
@@ -0,0 +1,620 @@
1
+ import { BalanceItem, BalanceItemPayment, Email, Member, MemberResponsibilityRecord, Order, Organization, Payment, RecipientLoader, User, Webshop } from '@stamhoofd/models';
2
+ import { compileToSQLFilter, SQL } from '@stamhoofd/sql';
3
+ import { BalanceItemRelationType, BalanceItemType, CountFilteredRequest, EmailRecipient, EmailRecipientFilterType, LimitedFilteredRequest, PaginatedResponse, PaymentGeneral, PaymentMethod, PaymentMethodHelper, Replacement, StamhoofdFilter, Webshop as WebshopStruct } from '@stamhoofd/structures';
4
+ import { Formatter } from '@stamhoofd/utility';
5
+ import { GetPaymentsEndpoint } from '../endpoints/organization/dashboard/payments/GetPaymentsEndpoint.js';
6
+ import { createOrderDataHTMLTable, createPaymentDataHTMLTable } from '../helpers/email-html-helpers.js';
7
+ import { memberResponsibilityRecordFilterCompilers } from '../sql-filters/member-responsibility-records.js';
8
+
9
+ type ReplacementsOptions = {
10
+ shouldAddReplacementsForOrder: boolean;
11
+ shouldAddReplacementsForTransfers: boolean;
12
+ orderMap: Map<string, Order>;
13
+ webshopMap: Map<string, Webshop>;
14
+ organizationMap: Map<string, Organization>;
15
+ };
16
+
17
+ async function getRecipients(result: PaginatedResponse<PaymentGeneral[], LimitedFilteredRequest>, type: EmailRecipientFilterType.Payment | EmailRecipientFilterType.PaymentOrganization, subFilter: StamhoofdFilter | null, beforeFetchAllResult: BeforeFetchAllResult | undefined) {
18
+ const recipients: EmailRecipient[] = [];
19
+ const userIds: { userId: string; payment: PaymentGeneral }[] = [];
20
+ const organizationIds: { organizationId: string; payment: PaymentGeneral }[] = [];
21
+ const memberIds: { memberId: string; payment: PaymentGeneral }[] = [];
22
+ const orderIds: { orderId: string; payment: PaymentGeneral }[] = [];
23
+
24
+ for (const payment of result.results) {
25
+ if (payment.payingOrganizationId) {
26
+ organizationIds.push({ organizationId: payment.payingOrganizationId, payment });
27
+ continue;
28
+ }
29
+
30
+ if (payment.payingUserId) {
31
+ userIds.push({ userId: payment.payingUserId, payment });
32
+ continue;
33
+ }
34
+
35
+ const balanceItemOrganizationIds = new Set<string>();
36
+ const balanceItemUserIds = new Set<string>();
37
+ const balanceItemMemberIds = new Set<string>();
38
+ const balanceItemOrderIds = new Set<string>();
39
+
40
+ for (const balanceItemPayment of payment.balanceItemPayments) {
41
+ const balanceItem = balanceItemPayment.balanceItem;
42
+
43
+ if (balanceItem.payingOrganizationId) {
44
+ balanceItemOrganizationIds.add(balanceItem.payingOrganizationId);
45
+ continue;
46
+ }
47
+
48
+ if (balanceItem.userId) {
49
+ balanceItemUserIds.add(balanceItem.userId);
50
+ continue;
51
+ }
52
+
53
+ if (balanceItem.memberId) {
54
+ balanceItemMemberIds.add(balanceItem.memberId);
55
+ continue;
56
+ }
57
+
58
+ if (balanceItem.orderId) {
59
+ balanceItemOrderIds.add(balanceItem.orderId);
60
+ continue;
61
+ }
62
+ }
63
+
64
+ const totalRecipientsForPayment = balanceItemOrganizationIds.size + balanceItemUserIds.size + balanceItemMemberIds.size + balanceItemOrderIds.size;
65
+ if (totalRecipientsForPayment > 1) {
66
+ console.warn('Multiple recipients found for payment: ', payment.id);
67
+ }
68
+
69
+ if (balanceItemOrganizationIds.size > 0) {
70
+ organizationIds.push(...Array.from(balanceItemOrganizationIds).map(organizationId => ({ organizationId, payment })));
71
+ }
72
+ if (balanceItemUserIds.size > 0) {
73
+ userIds.push(...Array.from(balanceItemUserIds).map(userId => ({ userId, payment })));
74
+ }
75
+ if (balanceItemMemberIds.size > 0) {
76
+ memberIds.push(...Array.from(balanceItemMemberIds).map(memberId => ({ memberId, payment })));
77
+ }
78
+ if (balanceItemOrderIds.size > 0) {
79
+ orderIds.push(...Array.from(balanceItemOrderIds).map(orderId => ({ orderId, payment })));
80
+ }
81
+ }
82
+
83
+ // get all orders linked to the payments
84
+ const allOrderIdsSet = new Set<string>();
85
+ const allWebshopIdsSet = new Set<string>();
86
+ const organizationIdsForOrdersSet = new Set<string>();
87
+
88
+ for (const payment of result.results) {
89
+ payment.webshopIds.forEach(id => allWebshopIdsSet.add(id));
90
+
91
+ for (const balanceItemPayment of payment.balanceItemPayments) {
92
+ const balanceItem = balanceItemPayment.balanceItem;
93
+ if (balanceItem.orderId) {
94
+ allOrderIdsSet.add(balanceItem.orderId);
95
+
96
+ // only important if balance item has order
97
+ organizationIdsForOrdersSet.add(balanceItem.organizationId);
98
+ }
99
+ }
100
+ }
101
+
102
+ // get all orders (for replacements later)
103
+ const orders = await Order.getByIDs(...allOrderIdsSet);
104
+ const orderMap = new Map<string, Order>(orders.map(o => [o.id, o] as [string, Order]));
105
+
106
+ // get all webshops (for replacements later)
107
+ const webshops = await Webshop.getByIDs(...allWebshopIdsSet);
108
+ const webshopMap = new Map<string, Webshop>(webshops.map(w => [w.id, w] as [string, Webshop]));
109
+
110
+ // get all organizations (for replacements later)
111
+ const organizations = await Organization.getByIDs(...organizationIdsForOrdersSet);
112
+ const organizationMap = new Map<string, Organization>(organizations.map(o => [o.id, o] as [string, Organization]));
113
+
114
+ const replacementOptions: ReplacementsOptions = {
115
+ shouldAddReplacementsForOrder: beforeFetchAllResult ? !beforeFetchAllResult.doesIncludePaymentWithoutOrders : false,
116
+ shouldAddReplacementsForTransfers: beforeFetchAllResult ? beforeFetchAllResult.areAllPaymentsTransfers : false,
117
+ orderMap,
118
+ webshopMap,
119
+ organizationMap,
120
+ };
121
+
122
+ if (type === EmailRecipientFilterType.Payment) {
123
+ recipients.push(...await getUserRecipients(userIds, replacementOptions));
124
+ recipients.push(...await getMemberRecipients(memberIds, replacementOptions));
125
+ recipients.push(...await getOrderRecipients(orderIds, replacementOptions));
126
+ }
127
+ else {
128
+ recipients.push(...await getOrganizationRecipients(organizationIds, replacementOptions, subFilter));
129
+ }
130
+
131
+ return recipients;
132
+ }
133
+
134
+ async function getUserRecipients(ids: { userId: string; payment: PaymentGeneral }[], replacementOptions: ReplacementsOptions): Promise<EmailRecipient[]> {
135
+ if (ids.length === 0) {
136
+ return [];
137
+ }
138
+
139
+ const allUserIds = Formatter.uniqueArray(ids.map(i => i.userId));
140
+ const users = await User.getByIDs(...allUserIds);
141
+
142
+ const results: EmailRecipient[] = [];
143
+
144
+ for (const { userId, payment } of ids) {
145
+ const user = users.find(u => u.id === userId);
146
+
147
+ if (user) {
148
+ results.push(EmailRecipient.create({
149
+ objectId: payment.id,
150
+ userId,
151
+ firstName: user.firstName,
152
+ lastName: user.lastName,
153
+ email: user.email,
154
+ replacements: getEmailReplacementsForPayment(payment, replacementOptions),
155
+ }));
156
+ }
157
+ }
158
+
159
+ return results;
160
+ }
161
+
162
+ async function getMembersForOrganizations(organizationIds: string[], filter: StamhoofdFilter | null): Promise<Map<string, Member[]>> {
163
+ const query = MemberResponsibilityRecord.select()
164
+ .where('organizationId', organizationIds)
165
+ .where('endDate', null)
166
+ .where(await compileToSQLFilter(filter,
167
+ memberResponsibilityRecordFilterCompilers));
168
+
169
+ const responsibilites = await query
170
+ .fetch();
171
+
172
+ const allMemberIds = Formatter.uniqueArray(responsibilites.map(r => r.memberId));
173
+ const members = await Member.getByIDs(...allMemberIds);
174
+
175
+ const result = new Map<string, Member[]>();
176
+
177
+ for (const responsibility of responsibilites) {
178
+ const organizationId = responsibility.organizationId;
179
+
180
+ if (!organizationId) {
181
+ continue;
182
+ }
183
+
184
+ const member = members.find(m => m.id === responsibility.memberId);
185
+
186
+ if (!member) {
187
+ continue;
188
+ }
189
+
190
+ const membersForOrganization = result.get(organizationId);
191
+ if (membersForOrganization) {
192
+ membersForOrganization.push(member);
193
+ }
194
+ else {
195
+ result.set(organizationId, [member]);
196
+ }
197
+ }
198
+
199
+ return result;
200
+ }
201
+
202
+ async function getOrganizationRecipients(ids: { organizationId: string; payment: PaymentGeneral }[], replacementOptions: ReplacementsOptions, subFilter: StamhoofdFilter | null): Promise<EmailRecipient[]> {
203
+ if (ids.length === 0 || subFilter === null) {
204
+ return [];
205
+ }
206
+
207
+ const allOrganizationIds = Formatter.uniqueArray(ids.map(i => i.organizationId));
208
+ const organizationMap = replacementOptions.organizationMap;
209
+
210
+ // fetch organizations that are not in map yet
211
+ const fetchedOrganizations = await Organization.getByIDs(...allOrganizationIds.filter(id => !organizationMap.has(id)));
212
+
213
+ for (const organization of fetchedOrganizations) {
214
+ organizationMap.set(organization.id, organization);
215
+ }
216
+
217
+ const membersForOrganizations = await getMembersForOrganizations(allOrganizationIds, subFilter);
218
+
219
+ const results: EmailRecipient[] = [];
220
+
221
+ for (const { organizationId, payment } of ids) {
222
+ const organization = organizationMap.get(organizationId);
223
+
224
+ if (organization) {
225
+ const members = membersForOrganizations.get(organizationId);
226
+ if (!members) {
227
+ continue;
228
+ }
229
+
230
+ const replacements = getEmailReplacementsForPayment(payment, replacementOptions);
231
+
232
+ for (const member of members) {
233
+ for (const email of member.details.getMemberEmails()) {
234
+ results.push(EmailRecipient.create({
235
+ objectId: payment.id,
236
+ name: organization.name,
237
+ memberId: member.id,
238
+ firstName: member.details.firstName,
239
+ lastName: member.details.lastName,
240
+ email,
241
+ replacements,
242
+ }));
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ return results;
249
+ }
250
+
251
+ async function getMemberRecipients(ids: { memberId: string; payment: PaymentGeneral }[], replacementOptions: ReplacementsOptions): Promise<EmailRecipient[]> {
252
+ if (ids.length === 0) {
253
+ return [];
254
+ }
255
+
256
+ const allMemberIds = Formatter.uniqueArray(ids.map(i => i.memberId));
257
+ const members = await Member.getBlobByIds(...allMemberIds);
258
+
259
+ const results: EmailRecipient[] = [];
260
+
261
+ for (const { memberId, payment } of ids) {
262
+ const member = members.find(m => m.id === memberId);
263
+
264
+ if (member) {
265
+ const emails = member.details.getNotificationEmails();
266
+
267
+ for (const user of member.users) {
268
+ if (!emails.includes(user.email.toLocaleLowerCase())) {
269
+ continue;
270
+ }
271
+
272
+ const recipient = EmailRecipient.create({
273
+ objectId: payment.id,
274
+ userId: user.id,
275
+ memberId,
276
+ firstName: user.firstName,
277
+ lastName: user.lastName,
278
+ email: user.email,
279
+ replacements: getEmailReplacementsForPayment(payment, replacementOptions),
280
+ });
281
+ results.push(recipient);
282
+ }
283
+ }
284
+ }
285
+
286
+ return results;
287
+ }
288
+
289
+ async function getOrderRecipients(ids: { orderId: string; payment: PaymentGeneral }[], replacementOptions: ReplacementsOptions): Promise<EmailRecipient[]> {
290
+ if (ids.length === 0) {
291
+ return [];
292
+ }
293
+
294
+ const orderMap = replacementOptions.orderMap;
295
+ const results: EmailRecipient[] = [];
296
+
297
+ for (const { orderId, payment } of ids) {
298
+ const order = orderMap.get(orderId);
299
+
300
+ if (order) {
301
+ const { firstName, lastName, email } = order.data.customer;
302
+
303
+ results.push(EmailRecipient.create({
304
+ objectId: payment.id,
305
+ userId: order.userId,
306
+ firstName,
307
+ lastName,
308
+ email,
309
+ replacements: getEmailReplacementsForPayment(payment, replacementOptions),
310
+ }));
311
+ }
312
+ }
313
+
314
+ return results;
315
+ }
316
+
317
+ function getEmailReplacementsForPayment(payment: PaymentGeneral, options: ReplacementsOptions): Replacement[] {
318
+ const { orderMap, webshopMap, organizationMap, shouldAddReplacementsForOrder, shouldAddReplacementsForTransfers } = options;
319
+ const orderIds = new Set<string>();
320
+
321
+ for (const balanceItemPayment of payment.balanceItemPayments) {
322
+ const orderId = balanceItemPayment.balanceItem.orderId;
323
+ if (orderId) {
324
+ orderIds.add(orderId);
325
+ }
326
+ }
327
+
328
+ // will be set if only 1 order is linked
329
+ let singleOrder: Order | null = null;
330
+
331
+ if (orderIds.size === 1) {
332
+ const singleOrderId = [...orderIds][0];
333
+ if (singleOrderId) {
334
+ const order = orderMap.get(singleOrderId);
335
+
336
+ if (order) {
337
+ singleOrder = order;
338
+ }
339
+ }
340
+ }
341
+
342
+ let orderUrlReplacement: Replacement | null = null;
343
+
344
+ // add replacement for order url if only 1 order is linked
345
+ if (singleOrder && shouldAddReplacementsForOrder) {
346
+ const webshop = webshopMap.get(singleOrder.webshopId);
347
+ const organization = organizationMap.get(singleOrder.organizationId);
348
+
349
+ if (webshop && organization) {
350
+ const webshopStruct = WebshopStruct.create(webshop);
351
+
352
+ orderUrlReplacement = Replacement.create({
353
+ token: 'orderUrl',
354
+ value: 'https://' + webshopStruct.getUrl(organization) + '/order/' + (singleOrder.id),
355
+ });
356
+ }
357
+ }
358
+
359
+ const createPaymentDataHtml = () => {
360
+ if (singleOrder) {
361
+ const webshop = webshopMap.get(singleOrder.webshopId);
362
+ if (webshop) {
363
+ return createOrderDataHTMLTable(singleOrder, webshop);
364
+ }
365
+ }
366
+
367
+ return createPaymentDataHTMLTable(payment);
368
+ };
369
+
370
+ const paymentDataHtml = createPaymentDataHtml();
371
+ const paymentDataReplacement = paymentDataHtml
372
+ ? Replacement.create({
373
+ token: 'paymentData',
374
+ value: '',
375
+ html: paymentDataHtml,
376
+ })
377
+ : null;
378
+
379
+ return ([
380
+ Replacement.create({
381
+ token: 'paymentPrice',
382
+ value: Formatter.price(payment.price),
383
+ }),
384
+ Replacement.create({
385
+ token: 'paymentMethod',
386
+ value: PaymentMethodHelper.getName(payment.method ?? PaymentMethod.Unknown),
387
+ }),
388
+ ...(shouldAddReplacementsForTransfers
389
+ ? [
390
+ Replacement.create({
391
+ token: 'transferDescription',
392
+ value: (payment.transferDescription ?? ''),
393
+ }),
394
+ Replacement.create({
395
+ token: 'transferBankAccount',
396
+ value: payment.transferSettings?.iban ?? '',
397
+ }),
398
+ Replacement.create({
399
+ token: 'transferBankCreditor',
400
+ // todo?
401
+ value: payment.transferSettings?.creditor ?? (payment.organizationId ? organizationMap.get(payment.organizationId)?.name : ''),
402
+ }),
403
+ ]
404
+ : []),
405
+
406
+ Replacement.create({
407
+ token: 'balanceItemPaymentsTable',
408
+ value: '',
409
+ // todo: unbox orders?
410
+ html: payment.getBalanceItemPaymentsHtmlTable(),
411
+ }),
412
+ Replacement.create({
413
+ token: 'paymentTable',
414
+ value: '',
415
+ html: payment.getHTMLTable(),
416
+ }),
417
+ Replacement.create({
418
+ token: 'overviewContext',
419
+ value: getPaymentContext(payment, options),
420
+ }),
421
+ orderUrlReplacement,
422
+ paymentDataReplacement,
423
+ ]).filter(replacementOrNull => replacementOrNull !== null);
424
+ }
425
+
426
+ function getPaymentContext(payment: PaymentGeneral, { orderMap, webshopMap }: ReplacementsOptions) {
427
+ const overviewContext = new Set<string>();
428
+ const registrationMemberNames = new Set<string>();
429
+
430
+ // only add to context if type is order or registration
431
+ for (const balanceItemPayment of payment.balanceItemPayments) {
432
+ const balanceItem = balanceItemPayment.balanceItem;
433
+ const type = balanceItem.type;
434
+
435
+ switch (type) {
436
+ case BalanceItemType.Order: {
437
+ if (balanceItem.orderId) {
438
+ const order = orderMap.get(balanceItem.orderId);
439
+
440
+ if (order) {
441
+ const webshop = webshopMap.get(order.webshopId);
442
+ if (webshop) {
443
+ overviewContext.add($t('{webshop} (bestelling {orderNumber})', {
444
+ webshop: webshop.meta.name,
445
+ orderNumber: order.number ?? '',
446
+ }));
447
+ }
448
+ else {
449
+ overviewContext.add($t('Bestelling {orderNumber}', {
450
+ orderNumber: order.number ?? '',
451
+ }));
452
+ }
453
+ }
454
+ }
455
+ break;
456
+ }
457
+ case BalanceItemType.Registration: {
458
+ const memberName = balanceItem.relations.get(BalanceItemRelationType.Member)?.name.toString();
459
+ if (memberName) {
460
+ registrationMemberNames.add(memberName);
461
+ }
462
+ else {
463
+ overviewContext.add(balanceItem.itemTitle);
464
+ }
465
+
466
+ break;
467
+ }
468
+ default: {
469
+ break;
470
+ }
471
+ }
472
+ }
473
+
474
+ if (registrationMemberNames.size > 0) {
475
+ const memberNames = Formatter.joinLast([...registrationMemberNames], ', ', ' ' + $t(`6a156458-b396-4d0f-b562-adb3e38fc51b`) + ' ');
476
+ overviewContext.add($t(`01d5fd7e-2960-4eb4-ab3a-2ac6dcb2e39c`) + ' ' + memberNames);
477
+ }
478
+
479
+ if (overviewContext.size === 0) {
480
+ // add item title if no balance items with type order or registration
481
+ if (payment.balanceItemPayments.length === 1) {
482
+ const balanceItem = payment.balanceItemPayments[0].balanceItem;
483
+ return balanceItem.itemTitle;
484
+ }
485
+
486
+ if (payment.balanceItemPayments.length > 1) {
487
+ // return title if all balance items have the same title
488
+ const firstTitle = payment.balanceItemPayments[0].balanceItem.itemTitle;
489
+ const haveAllSameTitle = payment.balanceItemPayments.every(p => p.balanceItem.itemTitle === firstTitle);
490
+
491
+ if (haveAllSameTitle) {
492
+ return `${firstTitle} (${payment.balanceItemPayments.length}x)`;
493
+ }
494
+
495
+ // else return default text for multiple items
496
+ return $t('Betaling voor {count} items', { count: payment.balanceItemPayments.length });
497
+ }
498
+
499
+ // else return default text for single item
500
+ return $t('Betaling voor 1 item');
501
+ }
502
+
503
+ // join texts for balance items with type order or registration
504
+ return [...overviewContext].join(', ');
505
+ }
506
+
507
+ type BeforeFetchAllResult = {
508
+ doesIncludePaymentWithoutOrders: boolean;
509
+ areAllPaymentsTransfers: boolean;
510
+ };
511
+
512
+ async function fetchPaymentRecipients(query: LimitedFilteredRequest, beforeFetchAllResult?: BeforeFetchAllResult) {
513
+ const result = await GetPaymentsEndpoint.buildData(query);
514
+
515
+ return new PaginatedResponse({
516
+ results: await getRecipients(result, EmailRecipientFilterType.Payment, null, beforeFetchAllResult),
517
+ next: result.next,
518
+ });
519
+ }
520
+
521
+ const paymentRecipientLoader: RecipientLoader<BeforeFetchAllResult> = {
522
+ beforeFetchAll: async (query: LimitedFilteredRequest) => {
523
+ const doesIncludePaymentWithoutOrders = await doesQueryIncludePaymentsWithoutOrder(query);
524
+ const areAllPaymentsTransfers = await doesQueryIncludePaymentsOtherThanTransfers(query);
525
+
526
+ return {
527
+ doesIncludePaymentWithoutOrders,
528
+ areAllPaymentsTransfers,
529
+ };
530
+ },
531
+ fetch: async (query: LimitedFilteredRequest, _subfilter, beforeFetchAllResult) => fetchPaymentRecipients(query, beforeFetchAllResult),
532
+ // For now: only count the number of payments - not the amount of emails
533
+ count: async (query: LimitedFilteredRequest, _subfilter) => {
534
+ const q = await GetPaymentsEndpoint.buildQuery(query);
535
+ const base = await q.count();
536
+
537
+ if (base < 1000) {
538
+ // Do full scan
539
+ query.limit = 1000;
540
+ const result = await fetchPaymentRecipients(query);
541
+ return result.results.length;
542
+ }
543
+
544
+ return base;
545
+ },
546
+ };
547
+
548
+ Email.recipientLoaders.set(EmailRecipientFilterType.Payment, paymentRecipientLoader);
549
+
550
+ async function doesQueryIncludePaymentsWithoutOrder(filterRequest: LimitedFilteredRequest) {
551
+ // create count request (without limit and page filter)
552
+ const countRequest = new CountFilteredRequest({
553
+ filter: filterRequest.filter,
554
+ search: filterRequest.search,
555
+ });
556
+
557
+ const baseQuery = await GetPaymentsEndpoint.buildQuery(countRequest);
558
+
559
+ const balanceItemPaymentsJoin = SQL.innerJoin(BalanceItemPayment.table)
560
+ .where(
561
+ SQL.column(BalanceItemPayment.table, 'paymentId'),
562
+ SQL.column(Payment.table, 'id'),
563
+ );
564
+
565
+ const balanceItemJoin = SQL.innerJoin(BalanceItem.table)
566
+ .where(
567
+ SQL.column(BalanceItem.table, 'id'),
568
+ SQL.column(BalanceItemPayment.table, 'balanceItemId'),
569
+ );
570
+
571
+ // check if 1 payment without order
572
+ const results = await baseQuery
573
+ .join(balanceItemPaymentsJoin)
574
+ .join(balanceItemJoin)
575
+ // where no order
576
+ .where(SQL.column(BalanceItem.table, 'orderId'), null)
577
+ .limit(1)
578
+ .count();
579
+
580
+ return results > 0;
581
+ }
582
+
583
+ async function doesQueryIncludePaymentsOtherThanTransfers(filterRequest: LimitedFilteredRequest) {
584
+ // create count request (without limit and page filter)
585
+ const countRequest = new CountFilteredRequest({
586
+ filter: filterRequest.filter,
587
+ search: filterRequest.search,
588
+ });
589
+
590
+ const baseQuery = await GetPaymentsEndpoint.buildQuery(countRequest);
591
+
592
+ // check if 1 payment with other method than transfer
593
+ const results = await baseQuery
594
+ // where method is not transfer
595
+ .whereNot(SQL.column(Payment.table, 'method'), PaymentMethod.Transfer)
596
+ .limit(1)
597
+ .count();
598
+
599
+ return results === 0;
600
+ }
601
+
602
+ async function fetchPaymentOrganizationRecipients(query: LimitedFilteredRequest, subfilter: StamhoofdFilter | null, beforeFetchAllResult?: BeforeFetchAllResult) {
603
+ const result = await GetPaymentsEndpoint.buildData(query);
604
+
605
+ return new PaginatedResponse({
606
+ results: await getRecipients(result, EmailRecipientFilterType.PaymentOrganization, subfilter, beforeFetchAllResult),
607
+ next: result.next,
608
+ });
609
+ }
610
+
611
+ const paymentOrganizationRecipientLoader: RecipientLoader<BeforeFetchAllResult> = {
612
+ fetch: async (query: LimitedFilteredRequest, subfilter: StamhoofdFilter | null, beforeFetchAllResult) => fetchPaymentOrganizationRecipients(query, subfilter, beforeFetchAllResult),
613
+ // For now: only count the number of payments - not the amount of emails
614
+ count: async (query: LimitedFilteredRequest, subfilter: StamhoofdFilter | null) => {
615
+ const q = await GetPaymentsEndpoint.buildQuery(query);
616
+ return await q.count();
617
+ },
618
+ };
619
+
620
+ Email.recipientLoaders.set(EmailRecipientFilterType.PaymentOrganization, paymentOrganizationRecipientLoader);
@@ -1,7 +1,7 @@
1
1
  import { CachedBalance, Email } from '@stamhoofd/models';
2
2
  import { BalanceItem as BalanceItemStruct, compileToInMemoryFilter, EmailRecipient, EmailRecipientFilterType, LimitedFilteredRequest, PaginatedResponse, receivableBalanceObjectContactInMemoryFilterCompilers, ReceivableBalanceType, Replacement, StamhoofdFilter } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
- import { GetReceivableBalancesEndpoint } from '../endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint';
4
+ import { GetReceivableBalancesEndpoint } from '../endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.js';
5
5
 
6
6
  async function fetch(query: LimitedFilteredRequest, subfilter: StamhoofdFilter | null) {
7
7
  const result = await GetReceivableBalancesEndpoint.buildData(query);
@@ -73,15 +73,6 @@ Email.recipientLoaders.set(EmailRecipientFilterType.ReceivableBalances, {
73
73
  // For now: only count the number of organizations - not the amount of emails
74
74
  count: async (query: LimitedFilteredRequest, subfilter: StamhoofdFilter | null) => {
75
75
  const q = await GetReceivableBalancesEndpoint.buildQuery(query);
76
- const base = await q.count();
77
-
78
- if (base < 1000) {
79
- // Do full scan
80
- query.limit = 1000;
81
- const result = await fetch(query, subfilter);
82
- return result.results.length;
83
- }
84
-
85
- return base;
76
+ return await q.count();
86
77
  },
87
78
  });
@@ -127,7 +127,7 @@ export class CreateEmailEndpoint extends Endpoint<Params, Query, Body, ResponseB
127
127
  }
128
128
 
129
129
  await model.save();
130
- await model.buildExampleRecipient();
130
+ await model.buildExampleRecipient(true);
131
131
  await model.updateCount();
132
132
 
133
133
  if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent || request.body.status === EmailStatus.Queued) {
@@ -210,7 +210,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
210
210
  }
211
211
 
212
212
  // Update documents
213
- await Document.updateForMember(member.id);
213
+ await Document.updateForMember(member);
214
214
 
215
215
  // Update responsibilities
216
216
  for (const patchResponsibility of patch.responsibilities.getPatches()) {
@@ -871,7 +871,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
871
871
  await MemberUserSyncer.onChangeMember(m);
872
872
 
873
873
  // Update documents
874
- await Document.updateForMember(m.id);
874
+ await Document.updateForMember(m);
875
875
  }
876
876
  }
877
877
  }
@@ -149,7 +149,7 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
149
149
  await MemberUserSyncer.onChangeMember(member);
150
150
 
151
151
  // Update documents
152
- await Document.updateForMember(member.id);
152
+ await Document.updateForMember(member);
153
153
  }
154
154
 
155
155
  // Modify members
@@ -173,7 +173,7 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
173
173
  await MemberUserSyncer.linkUser(user.email, updatedMember, true);
174
174
  }
175
175
 
176
- await Document.updateForMember(updatedMember.id);
176
+ await Document.updateForMember(updatedMember);
177
177
  }
178
178
  }
179
179