@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
package/src/helpers/TagHelper.ts
CHANGED
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
import { Organization, Platform } from '@stamhoofd/models';
|
|
2
2
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
3
|
-
import { OrganizationTag, TagHelper as SharedTagHelper } from '@stamhoofd/structures';
|
|
3
|
+
import { AuditLogSource, OrganizationTag, TagHelper as SharedTagHelper } from '@stamhoofd/structures';
|
|
4
4
|
import { ModelHelper } from './ModelHelper';
|
|
5
|
+
import { AuditLogService } from '../services/AuditLogService';
|
|
5
6
|
|
|
6
7
|
export class TagHelper extends SharedTagHelper {
|
|
7
8
|
static async updateOrganizations() {
|
|
8
9
|
const queueId = 'update-tags-on-organizations';
|
|
9
10
|
QueueHandler.cancel(queueId);
|
|
10
11
|
|
|
11
|
-
await
|
|
12
|
-
|
|
12
|
+
await AuditLogService.setContext({ source: AuditLogSource.System }, async () => {
|
|
13
|
+
await QueueHandler.schedule(queueId, async () => {
|
|
14
|
+
let platform = await Platform.getShared();
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const tagCounts = new Map<string, number>();
|
|
17
|
+
await this.loopOrganizations(async (organizations) => {
|
|
18
|
+
for (const organization of organizations) {
|
|
19
|
+
organization.meta.tags = this.getAllTagsFromHierarchy(organization.meta.tags, platform.config.tags);
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
for (const tag of organization.meta.tags) {
|
|
22
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
|
-
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
await Promise.all(organizations.map(organization => organization.save()));
|
|
27
|
+
});
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
// Reload platform to avoid race conditions
|
|
30
|
+
platform = await Platform.getShared();
|
|
31
|
+
for (const [tag, count] of tagCounts.entries()) {
|
|
32
|
+
const tagObject = platform.config.tags.find(t => t.id === tag);
|
|
33
|
+
if (tagObject) {
|
|
34
|
+
tagObject.organizationCount = count;
|
|
35
|
+
}
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
await platform.save();
|
|
38
|
+
});
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
-
import {
|
|
2
|
+
import { PlatformMembershipService } from '../services/PlatformMembershipService';
|
|
3
3
|
|
|
4
4
|
export default new Migration(async () => {
|
|
5
5
|
if (STAMHOOFD.environment == 'test') {
|
|
@@ -13,5 +13,5 @@ export default new Migration(async () => {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
process.stdout.write('\n');
|
|
16
|
-
await
|
|
16
|
+
await PlatformMembershipService.updateAll();
|
|
17
17
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Migration } from '@simonbackx/simple-database';
|
|
2
2
|
import { Registration } from '@stamhoofd/models';
|
|
3
|
+
import { RegistrationService } from '../services/RegistrationService';
|
|
3
4
|
|
|
4
5
|
export default new Migration(async () => {
|
|
5
6
|
if (STAMHOOFD.environment == 'test') {
|
|
@@ -27,7 +28,7 @@ export default new Migration(async () => {
|
|
|
27
28
|
const registrations = await Registration.getByIDs(...rawRegistrations.map(g => g.id));
|
|
28
29
|
|
|
29
30
|
for (const registration of registrations) {
|
|
30
|
-
|
|
31
|
+
RegistrationService.scheduleStockUpdate(registration.id);
|
|
31
32
|
|
|
32
33
|
c++;
|
|
33
34
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Migration } from '@simonbackx/simple-database';
|
|
2
|
+
import { logger } from '@simonbackx/simple-logging';
|
|
3
|
+
import { BalanceItem, Registration } from '@stamhoofd/models';
|
|
4
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
5
|
+
|
|
6
|
+
export default new Migration(async () => {
|
|
7
|
+
if (STAMHOOFD.environment == 'test') {
|
|
8
|
+
console.log('skipped in tests');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process.stdout.write('\n');
|
|
13
|
+
let c = 0;
|
|
14
|
+
let id: string = '';
|
|
15
|
+
|
|
16
|
+
await logger.setContext({ tags: ['seed'] }, async () => {
|
|
17
|
+
while (true) {
|
|
18
|
+
const items = await BalanceItem.where({
|
|
19
|
+
id: {
|
|
20
|
+
value: id,
|
|
21
|
+
sign: '>',
|
|
22
|
+
},
|
|
23
|
+
registrationId: {
|
|
24
|
+
value: null,
|
|
25
|
+
sign: '!=',
|
|
26
|
+
},
|
|
27
|
+
payingOrganizationId: {
|
|
28
|
+
value: null,
|
|
29
|
+
sign: '!=',
|
|
30
|
+
},
|
|
31
|
+
}, { limit: 1000, sort: ['id'] });
|
|
32
|
+
|
|
33
|
+
if (items.length === 0) {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.stdout.write('.');
|
|
38
|
+
|
|
39
|
+
const registrationIds = Formatter.uniqueArray(items.map(i => i.registrationId!));
|
|
40
|
+
const registrations = await Registration.getByIDs(...registrationIds);
|
|
41
|
+
|
|
42
|
+
for (const item of items) {
|
|
43
|
+
if (!item.registrationId || !item.payingOrganizationId) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const registration = registrations.find(r => r.id === item.registrationId);
|
|
47
|
+
if (!registration) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
registration.payingOrganizationId = item.payingOrganizationId;
|
|
52
|
+
if (await registration.save()) {
|
|
53
|
+
c++;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (items.length < 1000) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
id = items[items.length - 1].id;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log('Updated ' + c + ' registrations');
|
|
65
|
+
|
|
66
|
+
// Do something here
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
});
|
|
@@ -1,317 +1,102 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { AuditLogReplacement, AuditLogReplacementType, AuditLogType, GroupType, MemberDetails, OrganizationMetaData, OrganizationPrivateMetaData, PlatformConfig, PlatformPrivateConfig } from '@stamhoofd/structures';
|
|
4
|
-
import { Context } from '../helpers/Context';
|
|
5
|
-
import { explainPatch } from './explainPatch';
|
|
1
|
+
import { Model, ModelEvent } from '@simonbackx/simple-database';
|
|
2
|
+
import { AuditLogSource } from '@stamhoofd/structures';
|
|
6
3
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
4
|
+
import { DocumentTemplateLogger } from '../audit-logs/DocumentTemplateLogger';
|
|
5
|
+
import { EventLogger } from '../audit-logs/EventLogger';
|
|
6
|
+
import { GroupLogger } from '../audit-logs/GroupLogger';
|
|
7
|
+
import { MemberLogger } from '../audit-logs/MemberLogger';
|
|
8
|
+
import { MemberPlatformMembershipLogger } from '../audit-logs/MemberPlatformMembershipLogger';
|
|
9
|
+
import { MemberResponsibilityRecordLogger } from '../audit-logs/MemberResponsibilityRecordLogger';
|
|
10
|
+
import { OrderLogger } from '../audit-logs/OrderLogger';
|
|
11
|
+
import { OrganizationLogger } from '../audit-logs/OrganizationLogger';
|
|
12
|
+
import { OrganizationRegistrationPeriodLogger } from '../audit-logs/OrganizationRegistrationPeriodLogger';
|
|
13
|
+
import { PaymentLogger } from '../audit-logs/PaymentLogger';
|
|
14
|
+
import { PlatformLogger } from '../audit-logs/PlatformLogger';
|
|
15
|
+
import { RegistrationLogger } from '../audit-logs/RegistrationLogger';
|
|
16
|
+
import { RegistrationPeriodLogger } from '../audit-logs/RegistrationPeriodLogger';
|
|
17
|
+
import { StripeAccountLogger } from '../audit-logs/StripeAccountLogger';
|
|
18
|
+
import { WebshopLogger } from '../audit-logs/WebshopLogger';
|
|
19
|
+
|
|
20
|
+
export type AuditLogContextSettings = {
|
|
21
|
+
disable?: boolean;
|
|
22
|
+
source?: AuditLogSource;
|
|
23
|
+
userId?: string | null;
|
|
24
|
+
|
|
25
|
+
// If no userId is known, fallback to this userId
|
|
26
|
+
// this is useful e.g. for side effects of webhooks where the webhook calls but we don't have the userid in the request, still the action is tied to a user
|
|
27
|
+
fallbackUserId?: string | null;
|
|
28
|
+
fallbackOrganizationId?: string | null;
|
|
25
29
|
};
|
|
26
30
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
|
|
31
|
-
};
|
|
31
|
+
export class AuditLogService {
|
|
32
|
+
private constructor() { }
|
|
33
|
+
static disableLocalStore = new AsyncLocalStorage<AuditLogContextSettings>();
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
oldData?: AutoEncoder;
|
|
37
|
-
patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
|
|
38
|
-
};
|
|
35
|
+
static disable<T>(run: () => T): T {
|
|
36
|
+
return this.setContext({ disable: true }, run);
|
|
37
|
+
}
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
oldData?: AutoEncoder;
|
|
44
|
-
patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type GroupAuditOptions = {
|
|
48
|
-
type: AuditLogType.GroupAdded | AuditLogType.GroupEdited | AuditLogType.GroupDeleted;
|
|
49
|
-
group: Group;
|
|
50
|
-
oldData?: AutoEncoder;
|
|
51
|
-
patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export type PeriodAuditOptions = {
|
|
55
|
-
type: AuditLogType.RegistrationPeriodAdded | AuditLogType.RegistrationPeriodEdited | AuditLogType.RegistrationPeriodDeleted;
|
|
56
|
-
period: RegistrationPeriod;
|
|
57
|
-
oldData?: AutoEncoder;
|
|
58
|
-
patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export type StripeAccountAuditOptions = {
|
|
62
|
-
type: AuditLogType.StripeAccountAdded | AuditLogType.StripeAccountEdited | AuditLogType.StripeAccountDeleted;
|
|
63
|
-
stripeAccount: StripeAccount;
|
|
64
|
-
oldData?: AutoEncoder;
|
|
65
|
-
patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export type AuditLogOptions = StripeAccountAuditOptions | PeriodAuditOptions | GroupAuditOptions | EventAuditOptions | MemberAddedAuditOptions | MemberEditedAuditOptions | MemberRegisteredAuditOptions | PlatformConfigChangeAuditOptions | OrganizationConfigChangeAuditOptions;
|
|
69
|
-
|
|
70
|
-
export const AuditLogService = {
|
|
71
|
-
disableLocalStore: new AsyncLocalStorage<boolean>(),
|
|
72
|
-
|
|
73
|
-
disable<T extends Promise<void> | void>(run: () => T): T {
|
|
74
|
-
return this.disableLocalStore.run(true, () => {
|
|
39
|
+
static setContext<T>(context: AuditLogContextSettings, run: () => T): T {
|
|
40
|
+
const currentContext = this.getContext() ?? {};
|
|
41
|
+
return this.disableLocalStore.run({ ...currentContext, ...context }, () => {
|
|
75
42
|
return run();
|
|
76
43
|
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
isDisabled(): boolean {
|
|
80
|
-
const c = this.disableLocalStore.getStore();
|
|
81
|
-
|
|
82
|
-
if (!c) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return true;
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
async log(options: AuditLogOptions) {
|
|
90
|
-
try {
|
|
91
|
-
if (this.isDisabled()) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const userId = Context.optionalAuth?.user?.id ?? null;
|
|
95
|
-
const organizationId = Context.organization?.id ?? null;
|
|
96
|
-
|
|
97
|
-
const model = new AuditLog();
|
|
98
|
-
|
|
99
|
-
model.type = options.type;
|
|
100
|
-
model.userId = userId;
|
|
101
|
-
model.organizationId = organizationId;
|
|
102
|
-
|
|
103
|
-
if (options.type === AuditLogType.MemberRegistered) {
|
|
104
|
-
this.fillForMemberRegistered(model, options);
|
|
105
|
-
}
|
|
106
|
-
else if (options.type === AuditLogType.MemberUnregistered) {
|
|
107
|
-
this.fillForMemberRegistered(model, options);
|
|
108
|
-
}
|
|
109
|
-
else if (options.type === AuditLogType.MemberEdited) {
|
|
110
|
-
this.fillForMemberEdited(model, options);
|
|
111
|
-
}
|
|
112
|
-
else if (options.type === AuditLogType.MemberAdded) {
|
|
113
|
-
this.fillForMemberAdded(model, options);
|
|
114
|
-
}
|
|
115
|
-
else if (options.type === AuditLogType.PlatformSettingsChanged) {
|
|
116
|
-
this.fillForPlatformConfig(model, options);
|
|
117
|
-
}
|
|
118
|
-
else if (options.type === AuditLogType.OrganizationSettingsChanged) {
|
|
119
|
-
this.fillForOrganizationConfig(model, options);
|
|
120
|
-
}
|
|
121
|
-
else if (options.type === AuditLogType.EventAdded || options.type === AuditLogType.EventEdited || options.type === AuditLogType.EventDeleted) {
|
|
122
|
-
this.fillForEvent(model, options);
|
|
123
|
-
}
|
|
124
|
-
else if (options.type === AuditLogType.GroupAdded || options.type === AuditLogType.GroupEdited || options.type === AuditLogType.GroupDeleted) {
|
|
125
|
-
this.fillForGroup(model, options);
|
|
126
|
-
}
|
|
127
|
-
else if (options.type === AuditLogType.RegistrationPeriodAdded || options.type === AuditLogType.RegistrationPeriodEdited || options.type === AuditLogType.RegistrationPeriodDeleted) {
|
|
128
|
-
this.fillForPeriod(model, options);
|
|
129
|
-
}
|
|
130
|
-
else if (options.type === AuditLogType.StripeAccountAdded || options.type === AuditLogType.StripeAccountEdited || options.type === AuditLogType.StripeAccountDeleted) {
|
|
131
|
-
if (this.fillForStripeAccount(model, options) === false) {
|
|
132
|
-
// do not save
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// In the future we might group these saves together in one query to improve performance
|
|
138
|
-
await model.save();
|
|
139
|
-
}
|
|
140
|
-
catch (e) {
|
|
141
|
-
console.error('Failed to save log', options, e);
|
|
142
|
-
}
|
|
143
|
-
},
|
|
44
|
+
}
|
|
144
45
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
model.replacements = new Map([
|
|
148
|
-
['m', AuditLogReplacement.create({
|
|
149
|
-
id: options.member.id,
|
|
150
|
-
value: options.member.details.name,
|
|
151
|
-
type: AuditLogReplacementType.Member,
|
|
152
|
-
})],
|
|
153
|
-
['g', AuditLogReplacement.create({
|
|
154
|
-
id: options.group.id,
|
|
155
|
-
value: options.group.settings.name,
|
|
156
|
-
type: AuditLogReplacementType.Group,
|
|
157
|
-
})],
|
|
158
|
-
]);
|
|
46
|
+
static isDisabled(): boolean {
|
|
47
|
+
const c = this.getContext();
|
|
159
48
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
model.description = registrationStructure.description;
|
|
49
|
+
if (c && c.disable === true) {
|
|
50
|
+
return true;
|
|
163
51
|
}
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
fillForMemberEdited(model: AuditLog, options: MemberEditedAuditOptions) {
|
|
167
|
-
model.objectId = options.member.id;
|
|
168
|
-
|
|
169
|
-
model.replacements = new Map([
|
|
170
|
-
['m', AuditLogReplacement.create({
|
|
171
|
-
id: options.member.id,
|
|
172
|
-
value: options.member.details.name,
|
|
173
|
-
type: AuditLogReplacementType.Member,
|
|
174
|
-
})],
|
|
175
|
-
]);
|
|
176
|
-
|
|
177
|
-
// Generate changes list
|
|
178
|
-
model.patchList = explainPatch(options.oldMemberDetails, options.memberDetailsPatch);
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
fillForMemberAdded(model: AuditLog, options: MemberAddedAuditOptions) {
|
|
182
|
-
model.objectId = options.member.id;
|
|
183
|
-
|
|
184
|
-
model.replacements = new Map([
|
|
185
|
-
['m', AuditLogReplacement.create({
|
|
186
|
-
id: options.member.id,
|
|
187
|
-
value: options.member.details.name,
|
|
188
|
-
type: AuditLogReplacementType.Member,
|
|
189
|
-
})],
|
|
190
|
-
]);
|
|
191
52
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
},
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
195
55
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (options.patch) {
|
|
201
|
-
// Generate changes list
|
|
202
|
-
model.patchList = explainPatch(options.oldData ?? null, options.patch);
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
fillForOrganizationConfig(model: AuditLog, options: OrganizationConfigChangeAuditOptions) {
|
|
207
|
-
model.objectId = options.organization.id;
|
|
208
|
-
model.organizationId = options.organization.id;
|
|
209
|
-
|
|
210
|
-
model.replacements = new Map([
|
|
211
|
-
['o', AuditLogReplacement.create({
|
|
212
|
-
id: options.organization.id,
|
|
213
|
-
value: options.organization.name,
|
|
214
|
-
type: AuditLogReplacementType.Organization,
|
|
215
|
-
})],
|
|
216
|
-
]);
|
|
217
|
-
|
|
218
|
-
// Generate changes list
|
|
219
|
-
if (options.patch) {
|
|
220
|
-
// Generate changes list
|
|
221
|
-
model.patchList = explainPatch(options.oldData ?? null, options.patch);
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
fillForEvent(model: AuditLog, options: EventAuditOptions) {
|
|
226
|
-
model.objectId = options.event.id;
|
|
227
|
-
|
|
228
|
-
if (options.patch) {
|
|
229
|
-
// Generate changes list
|
|
230
|
-
model.patchList = explainPatch(options.oldData ?? null, options.patch);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
model.replacements = new Map([
|
|
234
|
-
['e', AuditLogReplacement.create({
|
|
235
|
-
id: options.event.id,
|
|
236
|
-
value: options.event.name,
|
|
237
|
-
type: AuditLogReplacementType.Event,
|
|
238
|
-
})],
|
|
239
|
-
]);
|
|
240
|
-
},
|
|
56
|
+
static getContext(): AuditLogContextSettings | null {
|
|
57
|
+
const c = this.disableLocalStore.getStore();
|
|
58
|
+
return c ?? null;
|
|
59
|
+
}
|
|
241
60
|
|
|
242
|
-
|
|
243
|
-
model.objectId = options.group.id;
|
|
61
|
+
static listening = false;
|
|
244
62
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
63
|
+
static listen() {
|
|
64
|
+
if (this.listening) {
|
|
65
|
+
return;
|
|
248
66
|
}
|
|
67
|
+
this.listening = true;
|
|
68
|
+
Model.modelEventBus.addListener(this, async (event) => {
|
|
69
|
+
const modelType = event.model.static as typeof Model;
|
|
70
|
+
const definition = modelLogDefinitions.get(modelType);
|
|
249
71
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
switch (options.type) {
|
|
253
|
-
case AuditLogType.GroupAdded:
|
|
254
|
-
model.type = AuditLogType.WaitingListAdded;
|
|
255
|
-
break;
|
|
256
|
-
case AuditLogType.GroupEdited:
|
|
257
|
-
model.type = AuditLogType.WaitingListEdited;
|
|
258
|
-
break;
|
|
259
|
-
case AuditLogType.GroupDeleted:
|
|
260
|
-
model.type = AuditLogType.WaitingListDeleted;
|
|
261
|
-
break;
|
|
72
|
+
if (!definition) {
|
|
73
|
+
return;
|
|
262
74
|
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
model.replacements = new Map([
|
|
266
|
-
['g', AuditLogReplacement.create({
|
|
267
|
-
id: options.group.id,
|
|
268
|
-
value: options.group.settings.name,
|
|
269
|
-
type: AuditLogReplacementType.Group,
|
|
270
|
-
})],
|
|
271
|
-
]);
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
fillForPeriod(model: AuditLog, options: PeriodAuditOptions) {
|
|
275
|
-
model.objectId = options.period.id;
|
|
276
|
-
|
|
277
|
-
if (options.patch) {
|
|
278
|
-
// Generate changes list
|
|
279
|
-
model.patchList = explainPatch(options.oldData ?? null, options.patch);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
model.replacements = new Map([
|
|
283
|
-
['p', AuditLogReplacement.create({
|
|
284
|
-
id: options.period.id,
|
|
285
|
-
value: options.period.getStructure().nameShort,
|
|
286
|
-
type: AuditLogReplacementType.RegistrationPeriod,
|
|
287
|
-
})],
|
|
288
|
-
]);
|
|
289
|
-
},
|
|
290
75
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (options.patch) {
|
|
295
|
-
// Generate changes list
|
|
296
|
-
model.patchList = explainPatch(options.oldData ?? null, options.patch);
|
|
297
|
-
|
|
298
|
-
if (model.patchList.length === 0) {
|
|
299
|
-
// No changes, ignore (only for stripe)
|
|
300
|
-
return false;
|
|
76
|
+
if (this.isDisabled()) {
|
|
77
|
+
console.log('Audit log disabled');
|
|
78
|
+
return;
|
|
301
79
|
}
|
|
302
|
-
}
|
|
303
80
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
model.replacements = new Map([
|
|
310
|
-
['a', AuditLogReplacement.create({
|
|
311
|
-
id: options.stripeAccount.id,
|
|
312
|
-
value: options.stripeAccount.accountId,
|
|
313
|
-
type: AuditLogReplacementType.StripeAccount,
|
|
314
|
-
})],
|
|
315
|
-
]);
|
|
316
|
-
},
|
|
81
|
+
await definition.logEvent(event);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
317
84
|
};
|
|
85
|
+
|
|
86
|
+
const modelLogDefinitions = new Map<typeof Model, { logEvent: (event: ModelEvent) => Promise<any> }>();
|
|
87
|
+
|
|
88
|
+
modelLogDefinitions.set(RegistrationLogger.model, RegistrationLogger);
|
|
89
|
+
modelLogDefinitions.set(GroupLogger.model, GroupLogger);
|
|
90
|
+
modelLogDefinitions.set(OrganizationLogger.model, OrganizationLogger);
|
|
91
|
+
modelLogDefinitions.set(PlatformLogger.model, PlatformLogger);
|
|
92
|
+
modelLogDefinitions.set(EventLogger.model, EventLogger);
|
|
93
|
+
modelLogDefinitions.set(RegistrationPeriodLogger.model, RegistrationPeriodLogger);
|
|
94
|
+
modelLogDefinitions.set(OrganizationRegistrationPeriodLogger.model, OrganizationRegistrationPeriodLogger);
|
|
95
|
+
modelLogDefinitions.set(StripeAccountLogger.model, StripeAccountLogger);
|
|
96
|
+
modelLogDefinitions.set(MemberLogger.model, MemberLogger);
|
|
97
|
+
modelLogDefinitions.set(WebshopLogger.model, WebshopLogger);
|
|
98
|
+
modelLogDefinitions.set(OrderLogger.model, OrderLogger);
|
|
99
|
+
modelLogDefinitions.set(PaymentLogger.model, PaymentLogger);
|
|
100
|
+
modelLogDefinitions.set(MemberPlatformMembershipLogger.model, MemberPlatformMembershipLogger);
|
|
101
|
+
modelLogDefinitions.set(MemberResponsibilityRecordLogger.model, MemberResponsibilityRecordLogger);
|
|
102
|
+
modelLogDefinitions.set(DocumentTemplateLogger.model, DocumentTemplateLogger);
|
|
@@ -16,7 +16,7 @@ export const BalanceItemPaymentService = {
|
|
|
16
16
|
await balanceItemPayment.balanceItem.save();
|
|
17
17
|
|
|
18
18
|
// Do logic of balance item
|
|
19
|
-
if (balanceItemPayment.balanceItem.status === BalanceItemStatus.Paid && old !== BalanceItemStatus.Paid) {
|
|
19
|
+
if (balanceItemPayment.balanceItem.status === BalanceItemStatus.Paid && old !== BalanceItemStatus.Paid && balanceItemPayment.price >= 0) {
|
|
20
20
|
// Only call markPaid once (if it wasn't (partially) paid before)
|
|
21
21
|
await BalanceItemService.markPaid(balanceItemPayment.balanceItem, balanceItemPayment.payment, organization);
|
|
22
22
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BalanceItem, Order, Organization, Payment, Webshop } from '@stamhoofd/models';
|
|
2
|
-
import { BalanceItemStatus, OrderStatus } from '@stamhoofd/structures';
|
|
2
|
+
import { AuditLogSource, BalanceItemStatus, OrderStatus } from '@stamhoofd/structures';
|
|
3
3
|
import { RegistrationService } from './RegistrationService';
|
|
4
|
+
import { AuditLogService } from './AuditLogService';
|
|
4
5
|
|
|
5
6
|
export const BalanceItemService = {
|
|
6
7
|
async markPaid(balanceItem: BalanceItem, payment: Payment | null, organization: Organization) {
|
|
@@ -16,6 +17,10 @@ export const BalanceItemService = {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
// It is possible this balance item was earlier paid
|
|
21
|
+
// and later the regigstration / order has been canceled and it became a negative balance item - which as some point has been reembursed and marked as 'paid'
|
|
22
|
+
// in that case, we should be careful not to mark the registration as valid again
|
|
23
|
+
|
|
19
24
|
// If registration
|
|
20
25
|
if (balanceItem.registrationId) {
|
|
21
26
|
await RegistrationService.markValid(balanceItem.registrationId);
|
|
@@ -43,10 +48,14 @@ export const BalanceItemService = {
|
|
|
43
48
|
async markUpdated(balanceItem: BalanceItem, payment: Payment, organization: Organization) {
|
|
44
49
|
// For orders: mark order as changed (so they are refetched in front ends)
|
|
45
50
|
if (balanceItem.orderId) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
await AuditLogService.setContext({ source: AuditLogSource.Payment }, async () => {
|
|
52
|
+
if (balanceItem.orderId) {
|
|
53
|
+
const order = await Order.getByID(balanceItem.orderId);
|
|
54
|
+
if (order) {
|
|
55
|
+
await order.paymentChanged(payment, organization);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
50
59
|
}
|
|
51
60
|
},
|
|
52
61
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Model } from '@simonbackx/simple-database';
|
|
2
|
+
import { PlainObject } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { Document, Group } from '@stamhoofd/models';
|
|
4
|
+
|
|
5
|
+
function getGroupFieldsAffectingDocuments(group: Group): PlainObject {
|
|
6
|
+
return {
|
|
7
|
+
type: group.type,
|
|
8
|
+
startDate: group.settings.startDate.getTime(),
|
|
9
|
+
endDate: group.settings.endDate.getTime(),
|
|
10
|
+
name: group.settings.name,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class DocumentService {
|
|
15
|
+
static listening = false;
|
|
16
|
+
|
|
17
|
+
static listen() {
|
|
18
|
+
if (this.listening) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.listening = true;
|
|
22
|
+
Model.modelEventBus.addListener(this, async (event) => {
|
|
23
|
+
try {
|
|
24
|
+
if (event.model instanceof Group && event.type === 'updated' && event.changedFields.settings && !event.model.deletedAt) {
|
|
25
|
+
const oldModel = event.getOldModel() as Group;
|
|
26
|
+
const oldFields = getGroupFieldsAffectingDocuments(oldModel);
|
|
27
|
+
const newFields = getGroupFieldsAffectingDocuments(event.model);
|
|
28
|
+
|
|
29
|
+
if (JSON.stringify(oldFields) === JSON.stringify(newFields)) {
|
|
30
|
+
console.log('Group changes cannot affect documents');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// If a group is changed - update the documents for it
|
|
35
|
+
await Document.updateForGroup(event.model.id, event.model.organizationId);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.error('Failed to update documents after group change', e);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|