@stamhoofd/backend 2.17.4 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/crons/setup-steps.ts +9 -0
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +49 -0
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +16 -14
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +66 -4
- package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +6 -7
- package/src/endpoints/organization/dashboard/billing/GetDetailedBillingStatusEndpoint.ts +77 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +4 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +6 -2
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +78 -0
- package/src/excel-loaders/payments.ts +36 -4
- package/src/helpers/AuthenticatedStructures.ts +6 -7
- package/src/helpers/MembershipCharger.ts +126 -0
- package/src/helpers/SetupStepsUpdater.ts +210 -0
- package/src/seeds/1724076679-setup-steps.ts +16 -0
- package/src/endpoints/admin/invoices/GetInvoicesCountEndpoint.ts +0 -47
- package/src/endpoints/admin/invoices/GetInvoicesEndpoint.ts +0 -185
- package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +0 -424
- package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +0 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"@stamhoofd/backend-i18n": "^2.17.0",
|
|
40
40
|
"@stamhoofd/backend-middleware": "^2.17.0",
|
|
41
41
|
"@stamhoofd/email": "^2.17.0",
|
|
42
|
-
"@stamhoofd/models": "^2.
|
|
42
|
+
"@stamhoofd/models": "^2.19.0",
|
|
43
43
|
"@stamhoofd/queues": "^2.17.3",
|
|
44
|
-
"@stamhoofd/sql": "^2.
|
|
45
|
-
"@stamhoofd/structures": "^2.
|
|
44
|
+
"@stamhoofd/sql": "^2.18.0",
|
|
45
|
+
"@stamhoofd/structures": "^2.19.0",
|
|
46
46
|
"@stamhoofd/utility": "^2.17.0",
|
|
47
47
|
"archiver": "^7.0.1",
|
|
48
48
|
"aws-sdk": "^2.885.0",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"postmark": "4.0.2",
|
|
61
61
|
"stripe": "^16.6.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "876e7b976122e1c2f67d4cceebe92ec2af2bc35a"
|
|
64
64
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
3
|
+
import { sleep } from '@stamhoofd/utility';
|
|
4
|
+
import { Context } from '../../../helpers/Context';
|
|
5
|
+
import { MembershipCharger } from '../../../helpers/MembershipCharger';
|
|
6
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
type Params = Record<string, never>;
|
|
10
|
+
type Query = Record<string, never>;
|
|
11
|
+
type Body = undefined;
|
|
12
|
+
type ResponseBody = undefined;
|
|
13
|
+
|
|
14
|
+
export class ChargeMembershipsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
16
|
+
if (request.method != "POST") {
|
|
17
|
+
return [false];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const params = Endpoint.parseParameters(request.url, "/admin/charge-memberships", {});
|
|
21
|
+
|
|
22
|
+
if (params) {
|
|
23
|
+
return [true, params as Params];
|
|
24
|
+
}
|
|
25
|
+
return [false];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
29
|
+
await Context.authenticate()
|
|
30
|
+
|
|
31
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
32
|
+
throw Context.auth.error()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (QueueHandler.isRunning('charge-memberships')) {
|
|
36
|
+
throw new SimpleError({
|
|
37
|
+
code: 'charge_pending',
|
|
38
|
+
message: 'Charge already pending',
|
|
39
|
+
human: 'Er is al een aanrekening bezig, even geduld.'
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
QueueHandler.schedule('charge-memberships', async () => {
|
|
44
|
+
await MembershipCharger.charge()
|
|
45
|
+
}).catch(console.error);
|
|
46
|
+
|
|
47
|
+
return new Response(undefined);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -2,6 +2,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
2
2
|
import { SQL, SQLAlias, SQLCount, SQLDistinct, SQLSelectAs, SQLSum } from '@stamhoofd/sql';
|
|
3
3
|
import { ChargeMembershipsSummary, ChargeMembershipsTypeSummary } from '@stamhoofd/structures';
|
|
4
4
|
import { Context } from '../../../helpers/Context';
|
|
5
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
type Params = Record<string, never>;
|
|
@@ -29,6 +30,14 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
|
|
|
29
30
|
if (!Context.auth.hasPlatformFullAccess()) {
|
|
30
31
|
throw Context.auth.error()
|
|
31
32
|
}
|
|
33
|
+
|
|
34
|
+
if (QueueHandler.isRunning('charge-memberships')) {
|
|
35
|
+
return new Response(
|
|
36
|
+
ChargeMembershipsSummary.create({
|
|
37
|
+
running: true
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
}
|
|
32
41
|
|
|
33
42
|
const query = SQL
|
|
34
43
|
.select(
|
|
@@ -63,12 +72,8 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
|
|
|
63
72
|
new SQLAlias('data__price')
|
|
64
73
|
)
|
|
65
74
|
)
|
|
66
|
-
.from(
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
.where(SQL.column('invoiceId'), null)
|
|
70
|
-
.andWhere(SQL.column('invoiceItemDetailId'), null);
|
|
71
|
-
|
|
75
|
+
.from('member_platform_memberships')
|
|
76
|
+
.where('balanceItemId', null);
|
|
72
77
|
|
|
73
78
|
const result = await query.fetch();
|
|
74
79
|
const members = result[0]['data']['members'] as number;
|
|
@@ -78,6 +83,7 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
|
|
|
78
83
|
|
|
79
84
|
return new Response(
|
|
80
85
|
ChargeMembershipsSummary.create({
|
|
86
|
+
running: false,
|
|
81
87
|
memberships: memberships ?? 0,
|
|
82
88
|
members: members ?? 0,
|
|
83
89
|
price: price ?? 0,
|
|
@@ -122,17 +128,13 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
|
|
|
122
128
|
new SQLAlias('data__price')
|
|
123
129
|
)
|
|
124
130
|
)
|
|
125
|
-
.from(
|
|
126
|
-
|
|
131
|
+
.from('member_platform_memberships')
|
|
132
|
+
.where('balanceItemId', null)
|
|
133
|
+
.groupBy(
|
|
134
|
+
SQL.column('member_platform_memberships', 'membershipTypeId')
|
|
127
135
|
);
|
|
128
|
-
query.where(SQL.column('invoiceId'), null)
|
|
129
|
-
query.andWhere(SQL.column('invoiceItemDetailId'), null)
|
|
130
|
-
query.groupBy(SQL.column('member_platform_memberships', 'membershipTypeId'));
|
|
131
|
-
|
|
132
136
|
|
|
133
137
|
const result = await query.fetch();
|
|
134
|
-
console.log(result);
|
|
135
|
-
|
|
136
138
|
const membershipsPerType = new Map<string, ChargeMembershipsTypeSummary>();
|
|
137
139
|
|
|
138
140
|
for (const row of result) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, patchObject } from "@simonbackx/simple-encoding";
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
-
import { Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
4
|
-
import { Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
3
|
+
import { Organization, Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
4
|
+
import { PlatformPremiseType, Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
5
5
|
|
|
6
|
-
import { Context } from "../../../helpers/Context";
|
|
7
6
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
7
|
+
import { Context } from "../../../helpers/Context";
|
|
8
|
+
import { SetupStepUpdater } from "../../../helpers/SetupStepsUpdater";
|
|
8
9
|
|
|
9
10
|
type Params = Record<string, never>;
|
|
10
11
|
type Query = undefined;
|
|
@@ -64,7 +65,18 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// Update config
|
|
67
|
-
|
|
68
|
+
if(request.body.config.premiseTypes) {
|
|
69
|
+
const oldConfig = platform.config.clone();
|
|
70
|
+
platform.config = patchObject(platform.config, request.body.config);
|
|
71
|
+
const newPremiseTypes = platform.config.premiseTypes;
|
|
72
|
+
|
|
73
|
+
// update setup step premise types
|
|
74
|
+
if(this.shouldUpdateSetupStepPremise(newPremiseTypes, oldConfig.premiseTypes)) {
|
|
75
|
+
await SetupStepUpdater.updateSetupStepsForAllOrganizationsInCurrentPeriod({premiseTypes: newPremiseTypes});
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
platform.config = patchObject(platform.config, request.body.config)
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
if (request.body.period && request.body.period.id !== platform.periodId) {
|
|
@@ -78,7 +90,57 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
78
90
|
platform.periodId = period.id
|
|
79
91
|
}
|
|
80
92
|
|
|
93
|
+
if (request.body.membershipOrganizationId !== undefined) {
|
|
94
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
95
|
+
throw Context.auth.error()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (request.body.membershipOrganizationId) {
|
|
99
|
+
const organization = await Organization.getByID(request.body.membershipOrganizationId)
|
|
100
|
+
if (!organization) {
|
|
101
|
+
throw new SimpleError({
|
|
102
|
+
code: "invalid_organization",
|
|
103
|
+
message: "Invalid organization"
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
platform.membershipOrganizationId = organization.id
|
|
107
|
+
} else {
|
|
108
|
+
platform.membershipOrganizationId = null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
81
112
|
await platform.save()
|
|
82
113
|
return new Response(await Platform.getSharedPrivateStruct());
|
|
83
114
|
}
|
|
115
|
+
|
|
116
|
+
private shouldUpdateSetupStepPremise(newPremiseTypes: PlatformPremiseType[], oldPremiseTypes: PlatformPremiseType[]) {
|
|
117
|
+
for(const premiseType of newPremiseTypes) {
|
|
118
|
+
const id = premiseType.id;
|
|
119
|
+
const oldVersion = oldPremiseTypes.find(x => x.id === id);
|
|
120
|
+
|
|
121
|
+
// if premise type is not new
|
|
122
|
+
if(oldVersion) {
|
|
123
|
+
if(oldVersion.min !== premiseType.min || oldVersion.max !== premiseType.max) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// if premise type is new
|
|
130
|
+
if(premiseType.min || premiseType.max) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for(const oldPremiseType of oldPremiseTypes) {
|
|
136
|
+
const id = oldPremiseType.id;
|
|
137
|
+
|
|
138
|
+
// if premise type is removed
|
|
139
|
+
if(!newPremiseTypes.some(x => x.id === id)) {
|
|
140
|
+
if(oldPremiseType.min || oldPremiseType.max) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
84
146
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
-
import {
|
|
3
|
-
import { STInvoice } from "@stamhoofd/models";
|
|
4
|
-
import { Token } from "@stamhoofd/models";
|
|
5
|
-
import { STBillingStatus } from "@stamhoofd/structures";
|
|
2
|
+
import { OrganizationBillingStatus } from "@stamhoofd/structures";
|
|
6
3
|
|
|
7
4
|
import { Context } from "../../../../helpers/Context";
|
|
8
5
|
|
|
9
6
|
type Params = Record<string, never>;
|
|
10
7
|
type Query = undefined;
|
|
11
|
-
type ResponseBody =
|
|
8
|
+
type ResponseBody = OrganizationBillingStatus;
|
|
12
9
|
type Body = undefined;
|
|
13
10
|
|
|
14
11
|
export class GetBillingStatusEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
@@ -32,8 +29,10 @@ export class GetBillingStatusEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
32
29
|
// If the user has permission, we'll also search if he has access to the organization's key
|
|
33
30
|
if (!await Context.auth.canManageFinances(organization.id)) {
|
|
34
31
|
throw Context.auth.error()
|
|
35
|
-
}
|
|
32
|
+
}
|
|
36
33
|
|
|
37
|
-
return new Response(
|
|
34
|
+
return new Response(
|
|
35
|
+
OrganizationBillingStatus.create({})
|
|
36
|
+
)
|
|
38
37
|
}
|
|
39
38
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { OrganizationDetailedBillingStatus, OrganizationDetailedBillingStatusItem, PaymentMethod, PaymentStatus } from "@stamhoofd/structures";
|
|
3
|
+
|
|
4
|
+
import { BalanceItem, Organization, Payment } from "@stamhoofd/models";
|
|
5
|
+
import { SQL } from "@stamhoofd/sql";
|
|
6
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
|
|
8
|
+
import { Context } from "../../../../helpers/Context";
|
|
9
|
+
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
type ResponseBody = OrganizationDetailedBillingStatus;
|
|
13
|
+
type Body = undefined;
|
|
14
|
+
|
|
15
|
+
export class GetDetailedBillingStatusEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method != "GET") {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, "/billing/status/detailed", {});
|
|
22
|
+
|
|
23
|
+
if (params) {
|
|
24
|
+
return [true, params as Params];
|
|
25
|
+
}
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
30
|
+
const organization = await Context.setOrganizationScope();
|
|
31
|
+
await Context.authenticate()
|
|
32
|
+
|
|
33
|
+
// If the user has permission, we'll also search if he has access to the organization's key
|
|
34
|
+
if (!await Context.auth.canManageFinances(organization.id)) {
|
|
35
|
+
throw Context.auth.error()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const balanceItemModels = await BalanceItem.balanceItemsForOrganization(organization.id);
|
|
39
|
+
|
|
40
|
+
// Hide pending online payments
|
|
41
|
+
const paymentModels = await Payment.select()
|
|
42
|
+
.where('payingOrganizationId', organization.id)
|
|
43
|
+
.andWhere(
|
|
44
|
+
SQL.whereNot('status', PaymentStatus.Failed)
|
|
45
|
+
)
|
|
46
|
+
.fetch()
|
|
47
|
+
|
|
48
|
+
const organizationIds = Formatter.uniqueArray([
|
|
49
|
+
...balanceItemModels.map(b => b.organizationId),
|
|
50
|
+
...paymentModels.map(p => p.organizationId).filter(p => p !== null)
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
// Group by organization you'll have to pay to
|
|
54
|
+
if (organizationIds.length === 0) {
|
|
55
|
+
return new Response(
|
|
56
|
+
OrganizationDetailedBillingStatus.create({})
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const balanceItems = await BalanceItem.getStructureWithPayments(balanceItemModels)
|
|
61
|
+
const organizationModels = await Organization.getByIDs(...organizationIds)
|
|
62
|
+
const organizations = await AuthenticatedStructures.organizations(organizationModels)
|
|
63
|
+
const payments = await AuthenticatedStructures.paymentsGeneral(paymentModels, false)
|
|
64
|
+
|
|
65
|
+
return new Response(
|
|
66
|
+
OrganizationDetailedBillingStatus.create({
|
|
67
|
+
organizations: organizations.map(o => {
|
|
68
|
+
return OrganizationDetailedBillingStatusItem.create({
|
|
69
|
+
organization: o,
|
|
70
|
+
balanceItems: balanceItems.filter(b => b.organizationId == o.id),
|
|
71
|
+
payments: payments.filter(p => p.organizationId === o.id)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -8,6 +8,7 @@ import { Formatter } from '@stamhoofd/utility';
|
|
|
8
8
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
9
9
|
import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
|
|
10
10
|
import { Context } from '../../../../helpers/Context';
|
|
11
|
+
import { SetupStepUpdater } from '../../../../helpers/SetupStepsUpdater';
|
|
11
12
|
import { ViesHelper } from '../../../../helpers/ViesHelper';
|
|
12
13
|
|
|
13
14
|
type Params = Record<string, never>;
|
|
@@ -100,6 +101,9 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
|
|
|
100
101
|
if (request.body.privateMeta && request.body.privateMeta.isPatch()) {
|
|
101
102
|
organization.privateMeta.emails = request.body.privateMeta.emails.applyTo(organization.privateMeta.emails)
|
|
102
103
|
organization.privateMeta.premises = patchObject(organization.privateMeta.premises, request.body.privateMeta.premises);
|
|
104
|
+
if(request.body.privateMeta.premises) {
|
|
105
|
+
await SetupStepUpdater.updateForOrganization(organization);
|
|
106
|
+
}
|
|
103
107
|
organization.privateMeta.roles = request.body.privateMeta.roles.applyTo(organization.privateMeta.roles)
|
|
104
108
|
organization.privateMeta.responsibilities = request.body.privateMeta.responsibilities.applyTo(organization.privateMeta.responsibilities)
|
|
105
109
|
organization.privateMeta.inheritedResponsibilityRoles = request.body.privateMeta.inheritedResponsibilityRoles.applyTo(organization.privateMeta.inheritedResponsibilityRoles)
|
|
@@ -4,7 +4,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
|
|
|
4
4
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
5
|
import { Payment } from '@stamhoofd/models';
|
|
6
6
|
import { SQL, compileToSQLFilter, compileToSQLSorter } from "@stamhoofd/sql";
|
|
7
|
-
import { CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, PaymentGeneral, StamhoofdFilter, getSortFilter } from '@stamhoofd/structures';
|
|
7
|
+
import { CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, PaymentGeneral, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
|
|
8
8
|
|
|
9
9
|
import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
|
|
10
10
|
import { Context } from '../../../../helpers/Context';
|
|
@@ -134,7 +134,11 @@ export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
134
134
|
query.where(compileToSQLFilter(q.pageFilter, filterCompilers))
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
query.orderBy(compileToSQLSorter(q.sort,
|
|
137
|
+
query.orderBy(compileToSQLSorter(assertSort(q.sort, [
|
|
138
|
+
{
|
|
139
|
+
key: 'id'
|
|
140
|
+
}
|
|
141
|
+
]), sorters))
|
|
138
142
|
query.limit(q.limit)
|
|
139
143
|
}
|
|
140
144
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, SetupStepType } from "@stamhoofd/structures";
|
|
3
|
+
|
|
4
|
+
import { AutoEncoder, BooleanDecoder, Decoder, EnumDecoder, field } from "@simonbackx/simple-encoding";
|
|
5
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
6
|
+
import { OrganizationRegistrationPeriod } from "@stamhoofd/models";
|
|
7
|
+
import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
|
|
8
|
+
import { Context } from "../../../../helpers/Context";
|
|
9
|
+
|
|
10
|
+
type Params = { readonly id: string };
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
|
|
13
|
+
class Body extends AutoEncoder {
|
|
14
|
+
@field({decoder: new EnumDecoder(SetupStepType)})
|
|
15
|
+
type: SetupStepType
|
|
16
|
+
|
|
17
|
+
@field({decoder: BooleanDecoder})
|
|
18
|
+
isReviewed: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ResponseBody = OrganizationRegistrationPeriodStruct
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Endpoint to mark a setup step as reviewed
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export class SetupStepReviewEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
28
|
+
bodyDecoder = Body as Decoder<Body>;
|
|
29
|
+
|
|
30
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
31
|
+
if (request.method != "POST") {
|
|
32
|
+
return [false];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const params = Endpoint.parseParameters(request.url, "/organization/registration-period/@id/setup-steps/review", {id: String});
|
|
36
|
+
|
|
37
|
+
if (params) {
|
|
38
|
+
return [true, params as Params];
|
|
39
|
+
}
|
|
40
|
+
return [false];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
44
|
+
const organization = await Context.setOrganizationScope();
|
|
45
|
+
await Context.authenticate()
|
|
46
|
+
|
|
47
|
+
if (!await Context.auth.hasFullAccess(organization.id)) {
|
|
48
|
+
throw Context.auth.error()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const periodId = request.params.id;
|
|
52
|
+
const stepType = request.body.type;
|
|
53
|
+
const isReviewed = request.body.isReviewed;
|
|
54
|
+
|
|
55
|
+
const organizationPeriod = await OrganizationRegistrationPeriod.getByID(periodId);
|
|
56
|
+
if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
|
|
57
|
+
throw new SimpleError({
|
|
58
|
+
code: "not_found",
|
|
59
|
+
message: "Period not found",
|
|
60
|
+
statusCode: 404
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const setupSteps = organizationPeriod.setupSteps;
|
|
65
|
+
|
|
66
|
+
if(isReviewed) {
|
|
67
|
+
setupSteps.markReviewed(stepType);
|
|
68
|
+
} else {
|
|
69
|
+
setupSteps.resetReviewed(stepType);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await organizationPeriod.save();
|
|
73
|
+
|
|
74
|
+
return new Response(
|
|
75
|
+
await AuthenticatedStructures.organizationRegistrationPeriod(organizationPeriod),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { XlsxBuiltInNumberFormat, XlsxTransformerColumn } from "@stamhoofd/excel-writer";
|
|
1
|
+
import { XlsxBuiltInNumberFormat, XlsxTransformerColumn, XlsxTransformerConcreteColumn } from "@stamhoofd/excel-writer";
|
|
2
2
|
import { StripeAccount as StripeAccountStruct, BalanceItemPaymentDetailed, BalanceItemRelationType, CountryHelper, ExcelExportType, getBalanceItemRelationTypeName, getBalanceItemTypeName, PaymentGeneral, PaymentMethodHelper, PaymentStatusHelper, PaginatedResponse, PaymentProvider } from "@stamhoofd/structures";
|
|
3
3
|
import { ExportToExcelEndpoint } from "../endpoints/global/files/ExportToExcelEndpoint";
|
|
4
4
|
import { GetPaymentsEndpoint } from "../endpoints/organization/dashboard/payments/GetPaymentsEndpoint";
|
|
@@ -52,12 +52,44 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Payments, {
|
|
|
52
52
|
{
|
|
53
53
|
id: 'balanceItemPayments',
|
|
54
54
|
name: 'Betaallijnen',
|
|
55
|
-
transform: (data: PaymentGeneral) => data.balanceItemPayments.map(p => ({
|
|
55
|
+
transform: (data: PaymentGeneral): PaymentWithItem[] => data.balanceItemPayments.map(p => ({
|
|
56
56
|
payment: data,
|
|
57
57
|
balanceItemPayment: p
|
|
58
58
|
})),
|
|
59
59
|
columns: [
|
|
60
|
-
...getBalanceItemColumns()
|
|
60
|
+
...getBalanceItemColumns(),
|
|
61
|
+
|
|
62
|
+
// Repeating columns need to de-transform again
|
|
63
|
+
...[
|
|
64
|
+
...getGeneralColumns(),
|
|
65
|
+
...getInvoiceColumns(),
|
|
66
|
+
].map(c => {
|
|
67
|
+
if ('match' in c) {
|
|
68
|
+
return {
|
|
69
|
+
...c,
|
|
70
|
+
match: (id: string) => {
|
|
71
|
+
const result = c.match(id)
|
|
72
|
+
if (!result) {
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result.map(cc => ({
|
|
77
|
+
...cc,
|
|
78
|
+
getValue: (object: PaymentWithItem) => {
|
|
79
|
+
return cc.getValue(object.payment)
|
|
80
|
+
},
|
|
81
|
+
}))
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
...c,
|
|
88
|
+
getValue: (object: PaymentWithItem) => {
|
|
89
|
+
return c.getValue(object.payment)
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
})
|
|
61
93
|
]
|
|
62
94
|
}
|
|
63
95
|
]
|
|
@@ -164,7 +196,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<PaymentWithItem>[] {
|
|
|
164
196
|
}
|
|
165
197
|
|
|
166
198
|
|
|
167
|
-
function getGeneralColumns():
|
|
199
|
+
function getGeneralColumns(): XlsxTransformerConcreteColumn<PaymentGeneral>[] {
|
|
168
200
|
return [
|
|
169
201
|
{
|
|
170
202
|
id: 'id',
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
2
2
|
import { Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, User, Webshop } from "@stamhoofd/models";
|
|
3
|
-
import { Event as EventStruct,
|
|
4
|
-
import { OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType } from '@stamhoofd/structures';
|
|
3
|
+
import { Event as EventStruct, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, PrivateWebshop, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
5
4
|
|
|
6
|
-
import { Context } from "./Context";
|
|
7
5
|
import { Formatter } from "@stamhoofd/utility";
|
|
6
|
+
import { Context } from "./Context";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Builds authenticated structures for the current user
|
|
@@ -55,7 +54,7 @@ export class AuthenticatedStructures {
|
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
static async groups(groups: Group[]) {
|
|
58
|
-
const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null)
|
|
57
|
+
const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null))
|
|
59
58
|
const waitingLists = waitingListIds.length > 0 ? await Group.getByIDs(...waitingListIds) : []
|
|
60
59
|
|
|
61
60
|
const structs: GroupStruct[] = []
|
|
@@ -194,7 +193,7 @@ export class AuthenticatedStructures {
|
|
|
194
193
|
*/
|
|
195
194
|
static async usersWithMembers(users: User[]): Promise<UserWithMembers[]> {
|
|
196
195
|
const structs: UserWithMembers[] = [];
|
|
197
|
-
const memberIds = Formatter.uniqueArray(users.map(u => u.memberId).filter(id => id !== null)
|
|
196
|
+
const memberIds = Formatter.uniqueArray(users.map(u => u.memberId).filter(id => id !== null))
|
|
198
197
|
const members = memberIds.length > 0 ? await Member.getBlobByIds(...memberIds) : []
|
|
199
198
|
|
|
200
199
|
for (const user of users) {
|
|
@@ -251,7 +250,7 @@ export class AuthenticatedStructures {
|
|
|
251
250
|
const platformMemberships = members.length > 0 ? await MemberPlatformMembership.where({ deletedAt: null, memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
|
|
252
251
|
|
|
253
252
|
// Load missing organizations
|
|
254
|
-
const organizationIds = Formatter.uniqueArray(responsibilities.map(r => r.organizationId).filter(id => id !== null)
|
|
253
|
+
const organizationIds = Formatter.uniqueArray(responsibilities.map(r => r.organizationId).filter(id => id !== null))
|
|
255
254
|
for (const id of organizationIds) {
|
|
256
255
|
if (includeContextOrganization || id !== Context.auth.organization?.id) {
|
|
257
256
|
const found = organizations.get(id);
|
|
@@ -275,7 +274,7 @@ export class AuthenticatedStructures {
|
|
|
275
274
|
|
|
276
275
|
static async events(events: Event[]): Promise<EventStruct[]> {
|
|
277
276
|
// Load groups
|
|
278
|
-
const groupIds = events.map(e => e.groupId).filter(id => id !== null)
|
|
277
|
+
const groupIds = events.map(e => e.groupId).filter(id => id !== null)
|
|
279
278
|
const groups = groupIds.length > 0 ? await Group.getByIDs(...groupIds) : []
|
|
280
279
|
const groupStructs = await this.groups(groups)
|
|
281
280
|
|