@stamhoofd/backend 2.58.0 → 2.60.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/index.ts +10 -1
- package/package.json +12 -12
- package/src/audit-logs/DocumentTemplateLogger.ts +22 -0
- package/src/audit-logs/EventLogger.ts +30 -0
- package/src/audit-logs/GroupLogger.ts +95 -0
- package/src/audit-logs/MemberLogger.ts +24 -0
- package/src/audit-logs/MemberPlatformMembershipLogger.ts +60 -0
- package/src/audit-logs/MemberResponsibilityRecordLogger.ts +69 -0
- package/src/audit-logs/ModelLogger.ts +219 -0
- package/src/audit-logs/OrderLogger.ts +57 -0
- package/src/audit-logs/OrganizationLogger.ts +16 -0
- package/src/audit-logs/OrganizationRegistrationPeriodLogger.ts +77 -0
- package/src/audit-logs/PaymentLogger.ts +43 -0
- package/src/audit-logs/PlatformLogger.ts +13 -0
- package/src/audit-logs/RegistrationLogger.ts +53 -0
- package/src/audit-logs/RegistrationPeriodLogger.ts +21 -0
- package/src/audit-logs/StripeAccountLogger.ts +47 -0
- package/src/audit-logs/WebshopLogger.ts +35 -0
- package/src/crons/updateSetupSteps.ts +1 -1
- package/src/crons.ts +2 -1
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +12 -24
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +7 -21
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +5 -13
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +5 -12
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +0 -15
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +43 -27
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +0 -19
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +3 -13
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +20 -43
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +5 -14
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +7 -4
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +8 -2
- package/src/helpers/AuthenticatedStructures.ts +16 -1
- package/src/helpers/Context.ts +8 -2
- package/src/helpers/MemberUserSyncer.ts +45 -40
- package/src/helpers/PeriodHelper.ts +5 -5
- package/src/helpers/SetupStepUpdater.ts +503 -0
- package/src/helpers/TagHelper.ts +23 -20
- package/src/seeds/1722344162-update-membership.ts +2 -2
- package/src/seeds/1726572303-schedule-stock-updates.ts +2 -1
- package/src/seeds/1726847064-setup-steps.ts +1 -1
- package/src/seeds/1733319079-fill-paying-organization-ids.ts +68 -0
- package/src/services/AuditLogService.ts +81 -296
- package/src/services/BalanceItemPaymentService.ts +1 -1
- package/src/services/BalanceItemService.ts +14 -5
- package/src/services/DocumentService.ts +43 -0
- package/src/services/MemberNumberService.ts +120 -0
- package/src/services/PaymentService.ts +199 -193
- package/src/services/PlatformMembershipService.ts +284 -0
- package/src/services/RegistrationService.ts +78 -27
- package/src/services/diff.ts +512 -0
- package/src/sql-filters/events.ts +13 -1
- package/src/helpers/MembershipHelper.ts +0 -54
- package/src/services/explainPatch.ts +0 -782
|
@@ -209,6 +209,18 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// Who is going to pay?
|
|
213
|
+
let whoWillPayNow: 'member' | 'organization' | 'nobody' = 'member'; // if this is set to 'organization', there will also be created separate balance items so the member can pay back the paying organization
|
|
214
|
+
|
|
215
|
+
if (request.body.asOrganizationId && request.body.asOrganizationId === organization.id) {
|
|
216
|
+
// Will get added to the outstanding amount of the member / already paying organization
|
|
217
|
+
whoWillPayNow = 'nobody';
|
|
218
|
+
}
|
|
219
|
+
else if (request.body.asOrganizationId && request.body.asOrganizationId !== organization.id) {
|
|
220
|
+
// The organization will pay to the organizing organization, and it will get added to the outstanding amount of the member towards the paying organization
|
|
221
|
+
whoWillPayNow = 'organization';
|
|
222
|
+
}
|
|
223
|
+
|
|
212
224
|
const registrationMemberRelation = new ManyToOneRelation(Member, 'member');
|
|
213
225
|
registrationMemberRelation.foreignKey = 'memberId';
|
|
214
226
|
|
|
@@ -264,6 +276,19 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
264
276
|
registration.options = item.options;
|
|
265
277
|
registration.recordAnswers = item.recordAnswers;
|
|
266
278
|
|
|
279
|
+
if (whoWillPayNow === 'organization' && request.body.asOrganizationId) {
|
|
280
|
+
registration.payingOrganizationId = request.body.asOrganizationId;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (whoWillPayNow === 'nobody' && item.replaceRegistrations.length > 0) {
|
|
284
|
+
// If the replace registration was paid by an organization
|
|
285
|
+
// Make sure this updated registration will also be paid by the organization, not the member
|
|
286
|
+
const paidAsOrganization = item.replaceRegistrations[0].payingOrganizationId;
|
|
287
|
+
if (paidAsOrganization) {
|
|
288
|
+
registration.payingOrganizationId = paidAsOrganization;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
267
292
|
payRegistrations.push({
|
|
268
293
|
registration,
|
|
269
294
|
item,
|
|
@@ -272,18 +297,6 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
272
297
|
registrations.push(registration);
|
|
273
298
|
}
|
|
274
299
|
|
|
275
|
-
// Who is going to pay?
|
|
276
|
-
let whoWillPayNow: 'member' | 'organization' | 'nobody' = 'member'; // if this is set to 'organization', there will also be created separate balance items so the member can pay back the paying organization
|
|
277
|
-
|
|
278
|
-
if (request.body.asOrganizationId && request.body.asOrganizationId === organization.id) {
|
|
279
|
-
// Will get added to the outstanding amount of the member
|
|
280
|
-
whoWillPayNow = 'nobody';
|
|
281
|
-
}
|
|
282
|
-
else if (request.body.asOrganizationId && request.body.asOrganizationId !== organization.id) {
|
|
283
|
-
// The organization will pay to the organizing organization, and it will get added to the outstanding amount of the member towards the paying organization
|
|
284
|
-
whoWillPayNow = 'organization';
|
|
285
|
-
}
|
|
286
|
-
|
|
287
300
|
// Validate payment method
|
|
288
301
|
if (totalPrice > 0 && whoWillPayNow !== 'nobody') {
|
|
289
302
|
const allowedPaymentMethods = organization.meta.registrationPaymentConfiguration.getAvailablePaymentMethods({
|
|
@@ -369,8 +382,13 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
369
382
|
deactivatedRegistrationGroupIds.push(existingRegistration.groupId);
|
|
370
383
|
}
|
|
371
384
|
|
|
372
|
-
async function createBalanceItem({ registration, amount, unitPrice, description, type, relations }: { amount?: number; registration: RegistrationWithMemberAndGroup; unitPrice: number; description: string; relations: Map<BalanceItemRelationType, BalanceItemRelation>; type: BalanceItemType }) {
|
|
385
|
+
async function createBalanceItem({ registration, skipZero, amount, unitPrice, description, type, relations }: { amount?: number; skipZero?: boolean; registration: RegistrationWithMemberAndGroup; unitPrice: number; description: string; relations: Map<BalanceItemRelationType, BalanceItemRelation>; type: BalanceItemType }) {
|
|
373
386
|
// NOTE: We also need to save zero-price balance items because for online payments, we need to know which registrations to activate after payment
|
|
387
|
+
if (skipZero === true) {
|
|
388
|
+
if (unitPrice === 0 || amount === 0) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
374
392
|
|
|
375
393
|
// Create balance item
|
|
376
394
|
const balanceItem = new BalanceItem();
|
|
@@ -386,11 +404,11 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
386
404
|
|
|
387
405
|
// Who is responsible for payment?
|
|
388
406
|
let balanceItem2: BalanceItem | null = null;
|
|
389
|
-
if (
|
|
407
|
+
if (registration.payingOrganizationId) {
|
|
390
408
|
// Create a separate balance item for this meber to pay back the paying organization
|
|
391
409
|
// this is not yet associated with a payment but will be added to the outstanding balance of the member
|
|
392
410
|
|
|
393
|
-
balanceItem.payingOrganizationId =
|
|
411
|
+
balanceItem.payingOrganizationId = registration.payingOrganizationId;
|
|
394
412
|
|
|
395
413
|
balanceItem2 = new BalanceItem();
|
|
396
414
|
|
|
@@ -405,7 +423,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
405
423
|
balanceItem2.type = type;
|
|
406
424
|
|
|
407
425
|
// Who needs to receive this money?
|
|
408
|
-
balanceItem2.organizationId =
|
|
426
|
+
balanceItem2.organizationId = registration.payingOrganizationId;
|
|
409
427
|
|
|
410
428
|
// Who is responsible for payment?
|
|
411
429
|
balanceItem2.memberId = registration.memberId;
|
|
@@ -437,26 +455,19 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
437
455
|
const { item, registration } = bundle;
|
|
438
456
|
registration.reservedUntil = null;
|
|
439
457
|
|
|
440
|
-
/* if (shouldMarkValid) {
|
|
441
|
-
await registration.markValid({ skipEmail: bundle.item.replaceRegistrations.length > 0 });
|
|
442
|
-
}
|
|
443
|
-
else { */
|
|
444
458
|
// Reserve registration for 30 minutes (if needed)
|
|
445
459
|
const group = groups.find(g => g.id === registration.groupId);
|
|
446
460
|
|
|
447
461
|
if (group && group.settings.maxMembers !== null) {
|
|
448
462
|
registration.reservedUntil = new Date(new Date().getTime() + 1000 * 60 * 30);
|
|
449
463
|
}
|
|
464
|
+
|
|
465
|
+
// Only now save the registration
|
|
450
466
|
await registration.save();
|
|
451
|
-
// }
|
|
452
467
|
|
|
453
468
|
// Note: we should always create the balance items: even when the price is zero
|
|
454
469
|
// Otherwise we don't know which registrations to activate after payment
|
|
455
470
|
|
|
456
|
-
if (shouldMarkValid && item.calculatedPrice === 0) {
|
|
457
|
-
// continue;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
471
|
// Create balance items
|
|
461
472
|
const sharedRelations: [BalanceItemRelationType, BalanceItemRelation][] = [
|
|
462
473
|
[
|
|
@@ -490,6 +501,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
490
501
|
registration,
|
|
491
502
|
unitPrice: item.groupPrice.price.forMember(item.member),
|
|
492
503
|
type: BalanceItemType.Registration,
|
|
504
|
+
skipZero: false, // Always create at least one balance item for each registration - even when the price is zero
|
|
493
505
|
description: `${item.member.patchedMember.name} bij ${item.group.settings.name}`,
|
|
494
506
|
relations: new Map([
|
|
495
507
|
...sharedRelations,
|
|
@@ -502,6 +514,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
502
514
|
registration,
|
|
503
515
|
amount: option.amount,
|
|
504
516
|
unitPrice: option.option.price.forMember(item.member),
|
|
517
|
+
skipZero: true, // Do not create for zero option prices
|
|
505
518
|
type: BalanceItemType.Registration,
|
|
506
519
|
description: `${option.optionMenu.name}: ${option.option.name}`,
|
|
507
520
|
relations: new Map([
|
|
@@ -583,10 +596,13 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
583
596
|
let paymentUrl: string | null = null;
|
|
584
597
|
let payment: Payment | null = null;
|
|
585
598
|
|
|
586
|
-
//
|
|
599
|
+
// Delay marking as valid as late as possible so any errors will prevent creating valid balance items
|
|
600
|
+
// Keep a copy because createdBalanceItems will be altered - and we don't want to mark added items as valid
|
|
601
|
+
const markValidList = [...createdBalanceItems, ...unrelatedCreatedBalanceItems];
|
|
602
|
+
|
|
587
603
|
async function markValidIfNeeded() {
|
|
588
604
|
if (shouldMarkValid) {
|
|
589
|
-
for (const balanceItem of
|
|
605
|
+
for (const balanceItem of markValidList) {
|
|
590
606
|
// Mark valid
|
|
591
607
|
await BalanceItemService.markPaid(balanceItem, payment, organization);
|
|
592
608
|
}
|
|
@@ -63,11 +63,6 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
63
63
|
|
|
64
64
|
await period.save();
|
|
65
65
|
periods.push(period);
|
|
66
|
-
|
|
67
|
-
await AuditLogService.log({
|
|
68
|
-
type: AuditLogType.RegistrationPeriodAdded,
|
|
69
|
-
period,
|
|
70
|
-
});
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
for (const patch of request.body.getPatches()) {
|
|
@@ -80,8 +75,6 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
80
75
|
message: 'Registration period not found',
|
|
81
76
|
});
|
|
82
77
|
}
|
|
83
|
-
const initialStructure = model.getStructure().clone();
|
|
84
|
-
|
|
85
78
|
if (patch.startDate !== undefined) {
|
|
86
79
|
model.startDate = patch.startDate;
|
|
87
80
|
}
|
|
@@ -102,13 +95,6 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
102
95
|
|
|
103
96
|
// Schedule patch of all groups in this period
|
|
104
97
|
PeriodHelper.updateGroupsInPeriod(model).catch(console.error);
|
|
105
|
-
|
|
106
|
-
await AuditLogService.log({
|
|
107
|
-
type: AuditLogType.RegistrationPeriodEdited,
|
|
108
|
-
period: model,
|
|
109
|
-
patch,
|
|
110
|
-
oldData: initialStructure,
|
|
111
|
-
});
|
|
112
98
|
}
|
|
113
99
|
|
|
114
100
|
for (const id of request.body.getDeletes()) {
|
|
@@ -123,11 +109,6 @@ export class PatchRegistrationPeriodsEndpoint extends Endpoint<Params, Query, Bo
|
|
|
123
109
|
}
|
|
124
110
|
|
|
125
111
|
await model.delete();
|
|
126
|
-
|
|
127
|
-
await AuditLogService.log({
|
|
128
|
-
type: AuditLogType.RegistrationPeriodDeleted,
|
|
129
|
-
period: model,
|
|
130
|
-
});
|
|
131
112
|
}
|
|
132
113
|
|
|
133
114
|
// Clear platform cache
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, cloneObject, Decoder, isPatchableArray, ObjectData, PatchableArrayAutoEncoder, patchObject } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
3
|
import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
4
|
-
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod,
|
|
5
|
-
import {
|
|
4
|
+
import { Organization, OrganizationRegistrationPeriod, PayconiqPayment, Platform, RegistrationPeriod, StripeAccount, Webshop } from '@stamhoofd/models';
|
|
5
|
+
import { BuckarooSettings, Company, OrganizationMetaData, OrganizationPatch, Organization as OrganizationStruct, PayconiqAccount, PaymentMethod, PaymentMethodHelper, PermissionLevel, PlatformConfig } from '@stamhoofd/structures';
|
|
6
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
7
7
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
@@ -10,7 +10,7 @@ import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
|
|
|
10
10
|
import { Context } from '../../../../helpers/Context';
|
|
11
11
|
import { TagHelper } from '../../../../helpers/TagHelper';
|
|
12
12
|
import { ViesHelper } from '../../../../helpers/ViesHelper';
|
|
13
|
-
import {
|
|
13
|
+
import { SetupStepUpdater } from '../../../../helpers/SetupStepUpdater';
|
|
14
14
|
|
|
15
15
|
type Params = Record<string, never>;
|
|
16
16
|
type Query = undefined;
|
|
@@ -63,8 +63,6 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const initialStruct = (await AuthenticatedStructures.organization(organization)).clone();
|
|
67
|
-
|
|
68
66
|
const errors = new SimpleErrors();
|
|
69
67
|
let shouldUpdateSetupSteps = false;
|
|
70
68
|
let updateTags = false;
|
|
@@ -387,14 +385,6 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
387
385
|
await TagHelper.updateOrganizations();
|
|
388
386
|
}
|
|
389
387
|
const struct = await AuthenticatedStructures.organization(organization);
|
|
390
|
-
|
|
391
|
-
await AuditLogService.log({
|
|
392
|
-
type: AuditLogType.OrganizationSettingsChanged,
|
|
393
|
-
organization,
|
|
394
|
-
oldData: initialStruct,
|
|
395
|
-
patch: struct,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
388
|
return new Response(struct);
|
|
399
389
|
}
|
|
400
390
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import {
|
|
2
|
+
import { GroupPrivateSettings, Group as GroupStruct, GroupType, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, PermissionLevel, PermissionsResourceType, ResourcePermissions, Version } from '@stamhoofd/structures';
|
|
3
3
|
|
|
4
4
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
5
5
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
6
|
-
import { Group,
|
|
6
|
+
import { Group, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
7
7
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
8
8
|
import { Context } from '../../../../helpers/Context';
|
|
9
|
-
import {
|
|
9
|
+
import { SetupStepUpdater } from '../../../../helpers/SetupStepUpdater';
|
|
10
10
|
|
|
11
11
|
type Params = Record<string, never>;
|
|
12
12
|
type Query = undefined;
|
|
@@ -81,6 +81,20 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
81
81
|
|
|
82
82
|
let deleteUnreachable = false;
|
|
83
83
|
const allowedIds: string[] = [];
|
|
84
|
+
let wasInvalid = false;
|
|
85
|
+
|
|
86
|
+
// Check if have an initial invalid state
|
|
87
|
+
if (patch.settings) {
|
|
88
|
+
// Already clean up the categories (not yet delete the groups)
|
|
89
|
+
const groups = await Group.getAll(organization.id, organizationPeriod.periodId);
|
|
90
|
+
await organizationPeriod.cleanCategories(groups);
|
|
91
|
+
|
|
92
|
+
for (const category of organizationPeriod.settings.categories) {
|
|
93
|
+
if (category.groupIds.length && category.categoryIds.length) {
|
|
94
|
+
wasInvalid = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
84
98
|
|
|
85
99
|
// #region prevent patch category lock if no full platform access
|
|
86
100
|
const originalCategories = organizationPeriod.settings.categories;
|
|
@@ -90,7 +104,7 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
90
104
|
if (patch.settings.categories) {
|
|
91
105
|
deleteUnreachable = true;
|
|
92
106
|
}
|
|
93
|
-
organizationPeriod.settings.
|
|
107
|
+
organizationPeriod.settings = organizationPeriod.settings.patch(patch.settings);
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
else {
|
|
@@ -123,7 +137,7 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
// Check if we have a category with groups and categories combined
|
|
126
|
-
if (patch.settings) {
|
|
140
|
+
if (patch.settings && !wasInvalid) {
|
|
127
141
|
for (const category of organizationPeriod.settings.categories) {
|
|
128
142
|
if (category.groupIds.length && category.categoryIds.length) {
|
|
129
143
|
throw new SimpleError({
|
|
@@ -281,12 +295,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
281
295
|
|
|
282
296
|
model.deletedAt = new Date();
|
|
283
297
|
await model.save();
|
|
284
|
-
Member.updateMembershipsForGroupId(id);
|
|
285
|
-
|
|
286
|
-
await AuditLogService.log({
|
|
287
|
-
type: AuditLogType.GroupDeleted,
|
|
288
|
-
group: model,
|
|
289
|
-
});
|
|
290
298
|
}
|
|
291
299
|
|
|
292
300
|
static async patchGroup(struct: AutoEncoderPatchType<GroupStruct>, period?: RegistrationPeriod | null) {
|
|
@@ -295,12 +303,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
295
303
|
if (!model || !await Context.auth.canAccessGroup(model, PermissionLevel.Full)) {
|
|
296
304
|
throw Context.auth.error('Je hebt geen toegangsrechten om deze groep te wijzigen');
|
|
297
305
|
}
|
|
298
|
-
const originalStruct = (await AuthenticatedStructures.group(model)).clone(); // Clone is required for deep changes
|
|
299
|
-
|
|
300
|
-
const previousProperties = {
|
|
301
|
-
deletedAt: model.deletedAt,
|
|
302
|
-
defaultAgeGroupId: model.defaultAgeGroupId,
|
|
303
|
-
};
|
|
304
306
|
|
|
305
307
|
if (struct.settings) {
|
|
306
308
|
struct.settings.period = undefined; // Not allowed to patch manually
|
|
@@ -416,26 +418,8 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
416
418
|
}
|
|
417
419
|
}
|
|
418
420
|
|
|
419
|
-
await model.updateOccupancy(
|
|
420
|
-
previousProperties,
|
|
421
|
-
});
|
|
421
|
+
await model.updateOccupancy();
|
|
422
422
|
await model.save();
|
|
423
|
-
|
|
424
|
-
if (struct.deletedAt !== undefined || struct.defaultAgeGroupId !== undefined) {
|
|
425
|
-
Member.updateMembershipsForGroupId(model.id);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (Object.keys(struct).length === 1 && struct.id) {
|
|
429
|
-
// Nothing changed
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
await AuditLogService.log({
|
|
434
|
-
type: AuditLogType.GroupEdited,
|
|
435
|
-
group: model,
|
|
436
|
-
oldData: originalStruct,
|
|
437
|
-
patch,
|
|
438
|
-
});
|
|
439
423
|
}
|
|
440
424
|
|
|
441
425
|
static async createGroup(struct: GroupStruct, organizationId: string, period: RegistrationPeriod, options?: { allowedIds?: string[] }): Promise<Group> {
|
|
@@ -515,13 +499,6 @@ export class PatchOrganizationRegistrationPeriodsEndpoint extends Endpoint<Param
|
|
|
515
499
|
}
|
|
516
500
|
|
|
517
501
|
await model.save();
|
|
518
|
-
await model.updateOccupancy({ isNew: true }); // Force update steps
|
|
519
|
-
|
|
520
|
-
await AuditLogService.log({
|
|
521
|
-
type: AuditLogType.GroupAdded,
|
|
522
|
-
group: model,
|
|
523
|
-
});
|
|
524
|
-
|
|
525
502
|
return model;
|
|
526
503
|
}
|
|
527
504
|
}
|
|
@@ -92,12 +92,6 @@ export class ConnectMollieEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
92
92
|
model.setMetaFromStripeAccount(account);
|
|
93
93
|
await model.save();
|
|
94
94
|
|
|
95
|
-
// Track audit log
|
|
96
|
-
await AuditLogService.log({
|
|
97
|
-
type: AuditLogType.StripeAccountAdded,
|
|
98
|
-
stripeAccount: model,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
95
|
// Return information about the Stripe Account
|
|
102
96
|
|
|
103
97
|
return new Response(StripeAccountStruct.create(model));
|
|
@@ -64,12 +64,6 @@ export class DeleteStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
|
|
|
64
64
|
model.status = 'deleted';
|
|
65
65
|
await model.save();
|
|
66
66
|
|
|
67
|
-
// Track audit log
|
|
68
|
-
await AuditLogService.log({
|
|
69
|
-
type: AuditLogType.StripeAccountDeleted,
|
|
70
|
-
stripeAccount: model,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
67
|
return new Response(undefined);
|
|
74
68
|
}
|
|
75
69
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { StripeAccount } from '@stamhoofd/models';
|
|
3
|
-
import {
|
|
3
|
+
import { AuditLogSource, PermissionLevel, StripeAccount as StripeAccountStruct } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Context } from '../../../../helpers/Context';
|
|
6
6
|
import { StripeHelper } from '../../../../helpers/StripeHelper';
|
|
7
7
|
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
8
|
-
import { Model } from '@simonbackx/simple-database';
|
|
9
8
|
|
|
10
9
|
type Params = { id: string };
|
|
11
10
|
type Body = undefined;
|
|
@@ -45,19 +44,11 @@ export class UpdateStripeAccountEndpoint extends Endpoint<Params, Query, Body, R
|
|
|
45
44
|
// Get account
|
|
46
45
|
const stripe = StripeHelper.getInstance();
|
|
47
46
|
const account = await stripe.accounts.retrieve(model.accountId);
|
|
48
|
-
const beforeMeta = model.meta;
|
|
49
|
-
model.setMetaFromStripeAccount(account);
|
|
50
|
-
|
|
51
|
-
if (await model.save()) {
|
|
52
|
-
// Track audit log
|
|
53
|
-
await AuditLogService.log({
|
|
54
|
-
type: AuditLogType.StripeAccountEdited,
|
|
55
|
-
stripeAccount: model,
|
|
56
|
-
oldData: beforeMeta,
|
|
57
|
-
patch: model.meta,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
47
|
|
|
48
|
+
await AuditLogService.setContext({ userId: null, source: AuditLogSource.System }, async () => {
|
|
49
|
+
model.setMetaFromStripeAccount(account);
|
|
50
|
+
await model.save();
|
|
51
|
+
});
|
|
61
52
|
return new Response(StripeAccountStruct.create(model));
|
|
62
53
|
}
|
|
63
54
|
}
|
|
@@ -3,9 +3,10 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
4
|
import { BalanceItem, BalanceItemPayment, Order, Payment, Token, Webshop } from '@stamhoofd/models';
|
|
5
5
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
6
|
-
import { BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, PrivatePayment, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
6
|
+
import { AuditLogSource, BalanceItemRelation, BalanceItemRelationType, BalanceItemStatus, BalanceItemType, OrderStatus, PaymentMethod, PaymentStatus, PermissionLevel, PrivateOrder, PrivatePayment, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
7
7
|
|
|
8
8
|
import { Context } from '../../../../helpers/Context';
|
|
9
|
+
import { AuditLogService } from '../../../../services/AuditLogService';
|
|
9
10
|
|
|
10
11
|
type Params = { id: string };
|
|
11
12
|
type Query = undefined;
|
|
@@ -131,7 +132,7 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
131
132
|
order.data.validate(webshopGetter.struct, organization.meta, request.i18n, true);
|
|
132
133
|
|
|
133
134
|
try {
|
|
134
|
-
await order.updateStock();
|
|
135
|
+
await order.updateStock(null, true);
|
|
135
136
|
const totalPrice = order.data.totalPrice;
|
|
136
137
|
|
|
137
138
|
if (totalPrice == 0) {
|
|
@@ -286,9 +287,11 @@ export class PatchWebshopOrdersEndpoint extends Endpoint<Params, Query, Body, Re
|
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
|
|
290
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
291
|
+
await model.setRelation(Order.webshop, webshop).updateStock(previousData, true);
|
|
292
|
+
await model.setRelation(Order.webshop, webshop).updateTickets();
|
|
293
|
+
});
|
|
289
294
|
await model.save();
|
|
290
|
-
await model.setRelation(Order.webshop, webshop).updateStock(previousData);
|
|
291
|
-
await model.setRelation(Order.webshop, webshop).updateTickets();
|
|
292
295
|
}
|
|
293
296
|
|
|
294
297
|
const mapped = orders.map(order => order.setRelation(Order.webshop, webshop));
|
|
@@ -6,12 +6,13 @@ import { I18n } from '@stamhoofd/backend-i18n';
|
|
|
6
6
|
import { Email } from '@stamhoofd/email';
|
|
7
7
|
import { BalanceItem, BalanceItemPayment, MolliePayment, MollieToken, Order, PayconiqPayment, Payment, RateLimiter, Webshop, WebshopDiscountCode } from '@stamhoofd/models';
|
|
8
8
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
9
|
-
import { BalanceItemStatus, Order as OrderStruct, OrderData, OrderResponse, Payment as PaymentStruct, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Version, Webshop as WebshopStruct, WebshopAuthType, BalanceItemType, BalanceItemRelationType, BalanceItemRelation } from '@stamhoofd/structures';
|
|
9
|
+
import { BalanceItemStatus, Order as OrderStruct, OrderData, OrderResponse, Payment as PaymentStruct, PaymentMethod, PaymentMethodHelper, PaymentProvider, PaymentStatus, Version, Webshop as WebshopStruct, WebshopAuthType, BalanceItemType, BalanceItemRelationType, BalanceItemRelation, AuditLogSource } from '@stamhoofd/structures';
|
|
10
10
|
import { Formatter } from '@stamhoofd/utility';
|
|
11
11
|
|
|
12
12
|
import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
|
|
13
13
|
import { Context } from '../../../helpers/Context';
|
|
14
14
|
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
15
|
+
import { AuditLogService } from '../../../services/AuditLogService';
|
|
15
16
|
|
|
16
17
|
type Params = { id: string };
|
|
17
18
|
type Query = undefined;
|
|
@@ -140,7 +141,12 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
140
141
|
order.userId = Context.user?.id ?? null;
|
|
141
142
|
|
|
142
143
|
// Always reserve the stock
|
|
143
|
-
await
|
|
144
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
145
|
+
await order.updateStock(null, true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
await order.save();
|
|
149
|
+
|
|
144
150
|
return { webshop, order, organization };
|
|
145
151
|
});
|
|
146
152
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
2
|
import { AuditLog, BalanceItem, CachedBalance, Document, Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Order, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, Ticket, User, Webshop } from '@stamhoofd/models';
|
|
3
|
-
import { AuditLog as AuditLogStruct, Document as DocumentStruct, Event as EventStruct, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogReplacement, AuditLogReplacementType, AuditLog as AuditLogStruct, Document as DocumentStruct, Event as EventStruct, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, NamedObject, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, Platform, PrivateOrder, PrivateWebshop, ReceivableBalanceObject, ReceivableBalanceObjectContact, ReceivableBalance as ReceivableBalanceStruct, ReceivableBalanceType, TicketPrivate, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
4
|
|
|
5
5
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
6
|
import { Context } from './Context';
|
|
@@ -645,6 +645,8 @@ export class AuthenticatedStructures {
|
|
|
645
645
|
|
|
646
646
|
const userIds = Formatter.uniqueArray(logs.map(l => l.userId).filter(id => id !== null));
|
|
647
647
|
const users = await User.getByIDs(...userIds);
|
|
648
|
+
const organizationsIds = Formatter.uniqueArray(logs.map(l => l.organizationId).filter(id => id !== null));
|
|
649
|
+
const organizations = await Organization.getByIDs(...organizationsIds);
|
|
648
650
|
|
|
649
651
|
for (const log of logs) {
|
|
650
652
|
const user = log.userId ? (users.find(u => u.id === log.userId) ?? null) : null;
|
|
@@ -673,9 +675,22 @@ export class AuthenticatedStructures {
|
|
|
673
675
|
}
|
|
674
676
|
}
|
|
675
677
|
|
|
678
|
+
let replacements = log.replacements;
|
|
679
|
+
|
|
680
|
+
if (log.organizationId && log.organizationId !== Context.organization?.id) {
|
|
681
|
+
replacements = new Map(log.replacements);
|
|
682
|
+
const org = organizations.find(o => o.id === log.organizationId);
|
|
683
|
+
replacements.set('org', AuditLogReplacement.create({
|
|
684
|
+
id: log.organizationId,
|
|
685
|
+
value: org?.name ?? 'verwijderde vereniging',
|
|
686
|
+
type: AuditLogReplacementType.Organization,
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
|
|
676
690
|
structs.push(
|
|
677
691
|
AuditLogStruct.create({
|
|
678
692
|
...log,
|
|
693
|
+
replacements,
|
|
679
694
|
user: userStruct,
|
|
680
695
|
}),
|
|
681
696
|
);
|
package/src/helpers/Context.ts
CHANGED
|
@@ -46,9 +46,15 @@ export class ContextInstance {
|
|
|
46
46
|
|
|
47
47
|
static asyncLocalStorage = new AsyncLocalStorage<ContextInstance>();
|
|
48
48
|
|
|
49
|
-
static get
|
|
49
|
+
static get optional(): ContextInstance | null {
|
|
50
50
|
const c = this.asyncLocalStorage.getStore();
|
|
51
51
|
|
|
52
|
+
return c ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static get current(): ContextInstance {
|
|
56
|
+
const c = this.optional;
|
|
57
|
+
|
|
52
58
|
if (!c) {
|
|
53
59
|
throw new SimpleError({
|
|
54
60
|
code: 'no_context',
|
|
@@ -220,7 +226,7 @@ export class ContextInstance {
|
|
|
220
226
|
export const Context = new Proxy(ContextInstance, {
|
|
221
227
|
get(target, prop, receiver) {
|
|
222
228
|
const c = target.current[prop];
|
|
223
|
-
if (c && typeof c
|
|
229
|
+
if (c && typeof c === 'function') {
|
|
224
230
|
return c.bind(target.current);
|
|
225
231
|
}
|
|
226
232
|
return c;
|