@stamhoofd/backend 2.120.3 → 2.120.5
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 +12 -12
- package/src/crons/balance-emails.ts +4 -7
- package/src/endpoints/admin/organizations/PatchPackagesEndpoint.test.ts +124 -0
- package/src/endpoints/admin/organizations/PatchPackagesEndpoint.ts +94 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +2 -1
- package/src/helpers/outstandingBalanceJoin.ts +7 -14
- package/src/services/STPackageService.ts +9 -0
- package/src/sql-filters/orders.ts +74 -3
- package/src/sql-filters/payments.ts +6 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.120.
|
|
3
|
+
"version": "2.120.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -62,17 +62,17 @@
|
|
|
62
62
|
"@bwip-js/node": "^4.5.1",
|
|
63
63
|
"@mollie/api-client": "4.3.3",
|
|
64
64
|
"@simonbackx/simple-database": "1.36.12",
|
|
65
|
-
"@simonbackx/simple-encoding": "2.26.
|
|
66
|
-
"@simonbackx/simple-endpoints": "1.21.
|
|
65
|
+
"@simonbackx/simple-encoding": "2.26.6",
|
|
66
|
+
"@simonbackx/simple-endpoints": "1.21.1",
|
|
67
67
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
68
|
-
"@stamhoofd/backend-i18n": "2.120.
|
|
69
|
-
"@stamhoofd/backend-middleware": "2.120.
|
|
70
|
-
"@stamhoofd/email": "2.120.
|
|
71
|
-
"@stamhoofd/models": "2.120.
|
|
72
|
-
"@stamhoofd/queues": "2.120.
|
|
73
|
-
"@stamhoofd/sql": "2.120.
|
|
74
|
-
"@stamhoofd/structures": "2.120.
|
|
75
|
-
"@stamhoofd/utility": "2.120.
|
|
68
|
+
"@stamhoofd/backend-i18n": "2.120.5",
|
|
69
|
+
"@stamhoofd/backend-middleware": "2.120.5",
|
|
70
|
+
"@stamhoofd/email": "2.120.5",
|
|
71
|
+
"@stamhoofd/models": "2.120.5",
|
|
72
|
+
"@stamhoofd/queues": "2.120.5",
|
|
73
|
+
"@stamhoofd/sql": "2.120.5",
|
|
74
|
+
"@stamhoofd/structures": "2.120.5",
|
|
75
|
+
"@stamhoofd/utility": "2.120.5",
|
|
76
76
|
"archiver": "^7.0.1",
|
|
77
77
|
"axios": "^1.13.2",
|
|
78
78
|
"cookie": "^0.7.0",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"publishConfig": {
|
|
91
91
|
"access": "public"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "5c4a1e159033bd2bfb2f6f3c60c9b03bd89c9291"
|
|
94
94
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { registerCron } from '@stamhoofd/crons';
|
|
2
2
|
import { CachedBalance, Email, EmailRecipient, Organization, User } from '@stamhoofd/models';
|
|
3
|
-
import type { IterableSQLSelect} from '@stamhoofd/sql';
|
|
4
|
-
import { readDynamicSQLExpression, SQL
|
|
3
|
+
import type { IterableSQLSelect } from '@stamhoofd/sql';
|
|
4
|
+
import { readDynamicSQLExpression, SQL } from '@stamhoofd/sql';
|
|
5
5
|
import type { OrganizationEmail, StamhoofdFilter } from '@stamhoofd/structures';
|
|
6
6
|
import { EmailRecipientFilter, EmailRecipientFilterType, EmailRecipientSubfilter, EmailTemplateType, ReceivableBalanceType } from '@stamhoofd/structures';
|
|
7
7
|
import { ContextInstance } from '../helpers/Context.js';
|
|
@@ -220,11 +220,8 @@ async function sendTemplate({
|
|
|
220
220
|
.set('lastReminderAmountOpen', SQL.column('amountOpen'))
|
|
221
221
|
.set(
|
|
222
222
|
'reminderEmailCount',
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
new SQLPlusSign(),
|
|
226
|
-
readDynamicSQLExpression(1),
|
|
227
|
-
),
|
|
223
|
+
SQL.calculation(SQL.column('reminderEmailCount'))
|
|
224
|
+
.add(readDynamicSQLExpression(1))
|
|
228
225
|
)
|
|
229
226
|
.where('id', balanceItemIds)
|
|
230
227
|
.where('organizationId', organization.id)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import { OrganizationFactory, STPackageFactory } from '@stamhoofd/models';
|
|
3
|
+
import { AccessRight, OrganizationPackagesStatus, STPackage, STPackageBundle, STPackageBundleHelper } from '@stamhoofd/structures';
|
|
4
|
+
import { STExpect, TestUtils } from '@stamhoofd/test-utils';
|
|
5
|
+
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
6
|
+
import { initAdmin, initPlatformAdmin } from '../../../../tests/init/index.js';
|
|
7
|
+
import { PatchPackagesEndpoint } from './PatchPackagesEndpoint.js';
|
|
8
|
+
import { PatchableArray } from '@simonbackx/simple-encoding';
|
|
9
|
+
|
|
10
|
+
const baseUrl = `/organization/packages`;
|
|
11
|
+
const endpoint = new PatchPackagesEndpoint();
|
|
12
|
+
|
|
13
|
+
describe('Endpoint.PatchPackagesEndpoint', () => {
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
TestUtils.setEnvironment('userMode', 'organization');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('Permission checking', () => {
|
|
19
|
+
test('Cannot patch organization packages as finance director', async () => {
|
|
20
|
+
const organization = await new OrganizationFactory({}).create();
|
|
21
|
+
|
|
22
|
+
const package1 = await new STPackageFactory({
|
|
23
|
+
organization,
|
|
24
|
+
bundle: STPackageBundle.Members,
|
|
25
|
+
}).create();
|
|
26
|
+
|
|
27
|
+
const package2 = await new STPackageFactory({
|
|
28
|
+
organization,
|
|
29
|
+
bundle: STPackageBundle.TrialWebshops,
|
|
30
|
+
}).create();
|
|
31
|
+
|
|
32
|
+
const { adminToken } = await initAdmin({
|
|
33
|
+
organization,
|
|
34
|
+
accessRights: [AccessRight.OrganizationFinanceDirector],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Try to request all members at organization
|
|
38
|
+
const request = Request.patch({
|
|
39
|
+
path: baseUrl,
|
|
40
|
+
host: organization.getApiHost(),
|
|
41
|
+
body: OrganizationPackagesStatus.patch({
|
|
42
|
+
|
|
43
|
+
}),
|
|
44
|
+
headers: {
|
|
45
|
+
authorization: 'Bearer ' + adminToken.accessToken,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(STExpect.errorWithCode('permission_denied'));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('Can create a new package', async () => {
|
|
54
|
+
const organization = await new OrganizationFactory({}).create();
|
|
55
|
+
|
|
56
|
+
const { adminToken } = await initPlatformAdmin();
|
|
57
|
+
|
|
58
|
+
const patch = OrganizationPackagesStatus.patch({});
|
|
59
|
+
|
|
60
|
+
const pack = STPackageBundleHelper.getCurrentPackage(STPackageBundle.Webshops, new Date())
|
|
61
|
+
pack.validAt = new Date(); // ignored by backend for now
|
|
62
|
+
patch.packages.addPut(pack);
|
|
63
|
+
|
|
64
|
+
// Try to request all members at organization
|
|
65
|
+
const request = Request.patch({
|
|
66
|
+
path: baseUrl,
|
|
67
|
+
host: organization.getApiHost(),
|
|
68
|
+
body: patch,
|
|
69
|
+
headers: {
|
|
70
|
+
authorization: 'Bearer ' + adminToken.accessToken,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const response = await testServer.test(endpoint, request);
|
|
75
|
+
expect(response.status).toBe(200);
|
|
76
|
+
expect(response.body.packages).toHaveLength(1);
|
|
77
|
+
|
|
78
|
+
const reference = response.body.packages[0];
|
|
79
|
+
|
|
80
|
+
// Ignore created at
|
|
81
|
+
pack.createdAt = reference.createdAt;
|
|
82
|
+
pack.updatedAt = reference.updatedAt;
|
|
83
|
+
pack.validAt = reference.validAt;
|
|
84
|
+
expect(reference).toEqual(pack);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('Can edit a package', async () => {
|
|
88
|
+
const organization = await new OrganizationFactory({}).create();
|
|
89
|
+
|
|
90
|
+
const { adminToken } = await initPlatformAdmin();
|
|
91
|
+
|
|
92
|
+
const package1 = await new STPackageFactory({
|
|
93
|
+
organization,
|
|
94
|
+
bundle: STPackageBundle.Members,
|
|
95
|
+
}).create();
|
|
96
|
+
|
|
97
|
+
const patch = OrganizationPackagesStatus.patch({});
|
|
98
|
+
const removeAt = new Date();
|
|
99
|
+
removeAt.setMilliseconds(0)
|
|
100
|
+
patch.packages.addPatch(STPackage.patch({
|
|
101
|
+
id: package1.id,
|
|
102
|
+
removeAt
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
// Try to request all members at organization
|
|
106
|
+
const request = Request.patch({
|
|
107
|
+
path: baseUrl,
|
|
108
|
+
host: organization.getApiHost(),
|
|
109
|
+
body: patch,
|
|
110
|
+
headers: {
|
|
111
|
+
authorization: 'Bearer ' + adminToken.accessToken,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const response = await testServer.test(endpoint, request);
|
|
116
|
+
expect(response.status).toBe(200);
|
|
117
|
+
expect(response.body.packages).toHaveLength(1);
|
|
118
|
+
|
|
119
|
+
const reference = response.body.packages[0];
|
|
120
|
+
|
|
121
|
+
// Ignore created at
|
|
122
|
+
expect(reference.removeAt).toEqual(removeAt);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { AutoEncoderPatchType, Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { STPackage } from '@stamhoofd/models';
|
|
6
|
+
import { OrganizationPackagesStatus, STPackage as STPackageStruct } from '@stamhoofd/structures';
|
|
7
|
+
import { Context } from '../../../helpers/Context.js';
|
|
8
|
+
import { STPackageService } from '../../../services/STPackageService.js';
|
|
9
|
+
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
type ResponseBody = OrganizationPackagesStatus;
|
|
13
|
+
type Body = AutoEncoderPatchType<OrganizationPackagesStatus>;
|
|
14
|
+
|
|
15
|
+
export class PatchPackagesEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
16
|
+
bodyDecoder = OrganizationPackagesStatus.patchType() as Decoder<AutoEncoderPatchType<OrganizationPackagesStatus>>
|
|
17
|
+
|
|
18
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
19
|
+
if (request.method !== 'PATCH') {
|
|
20
|
+
return [false];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const params = Endpoint.parseParameters(request.url, '/organization/packages', {});
|
|
24
|
+
|
|
25
|
+
if (params) {
|
|
26
|
+
return [true, params as Params];
|
|
27
|
+
}
|
|
28
|
+
return [false];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
32
|
+
const organization = await Context.setOrganizationScope();
|
|
33
|
+
await Context.authenticate();
|
|
34
|
+
|
|
35
|
+
// If the user has permission, we'll also search if he has access to the organization's key
|
|
36
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
37
|
+
throw Context.auth.error();
|
|
38
|
+
}
|
|
39
|
+
let updatePackages = false
|
|
40
|
+
const packages = await STPackageService.getValidPackagesWithExpired(organization.id);
|
|
41
|
+
|
|
42
|
+
// Do patches
|
|
43
|
+
if (request.body.packages) {
|
|
44
|
+
for (const patch of request.body.packages.getPatches()) {
|
|
45
|
+
const pack = packages.find(p => p.id === patch.id)
|
|
46
|
+
if (!pack) {
|
|
47
|
+
throw new SimpleError({
|
|
48
|
+
code: 'not_found',
|
|
49
|
+
message: 'Package not found with id '+patch.id
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (patch.meta !== undefined) {
|
|
54
|
+
pack.meta.patchOrPut(patch.meta)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (patch.validUntil !== undefined) {
|
|
58
|
+
pack.validUntil = patch.validUntil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (patch.removeAt !== undefined) {
|
|
62
|
+
pack.removeAt = patch.removeAt
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await pack.save()
|
|
66
|
+
updatePackages = true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const put of request.body.packages.getPuts()) {
|
|
70
|
+
const pack = new STPackage()
|
|
71
|
+
pack.id = put.put.id;
|
|
72
|
+
pack.validAt = new Date()
|
|
73
|
+
pack.organizationId = organization.id
|
|
74
|
+
|
|
75
|
+
pack.meta = put.put.meta
|
|
76
|
+
pack.validUntil = put.put.validUntil
|
|
77
|
+
pack.removeAt = put.put.removeAt
|
|
78
|
+
|
|
79
|
+
await pack.save()
|
|
80
|
+
updatePackages = true
|
|
81
|
+
|
|
82
|
+
packages.push(pack);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (updatePackages) {
|
|
87
|
+
await STPackageService.updateOrganizationPackages(organization.id)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return new Response(OrganizationPackagesStatus.create({
|
|
91
|
+
packages: packages.map(p => STPackageStruct.create(p)),
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -32,7 +32,7 @@ export class GetPackagesEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
32
32
|
throw Context.auth.error();
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const packages = await STPackageService.getActivePackages(organization.id);
|
|
35
|
+
const packages = Context.auth.hasPlatformFullAccess() ? await STPackageService.getValidPackagesWithExpired(organization.id) : await STPackageService.getActivePackages(organization.id);
|
|
36
36
|
|
|
37
37
|
return new Response(OrganizationPackagesStatus.create({
|
|
38
38
|
packages: packages.map(p => STPackageStruct.create(p)),
|
|
@@ -5,6 +5,7 @@ import { SimpleError, SimpleErrors } from '@simonbackx/simple-errors';
|
|
|
5
5
|
import { Webshop } from '@stamhoofd/models';
|
|
6
6
|
import { PermissionLevel, PermissionsResourceType, PrivateWebshop, ResourcePermissions, Version, WebshopPrivateMetaData } from '@stamhoofd/structures';
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
8
9
|
|
|
9
10
|
import { Context } from '../../../../helpers/Context.js';
|
|
10
11
|
|
|
@@ -46,7 +47,7 @@ export class CreateWebshopEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
46
47
|
|
|
47
48
|
const webshop = new Webshop();
|
|
48
49
|
|
|
49
|
-
webshop.id =
|
|
50
|
+
webshop.id = uuidv4()
|
|
50
51
|
webshop.meta = request.body.meta;
|
|
51
52
|
webshop.meta.domainActive = false;
|
|
52
53
|
webshop.privateMeta = request.body.privateMeta;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CachedBalance, Registration } from '@stamhoofd/models';
|
|
2
|
-
import type { SQLNamedExpression} from '@stamhoofd/sql';
|
|
3
|
-
import { SQL, SQLAlias,
|
|
2
|
+
import type { SQLNamedExpression } from '@stamhoofd/sql';
|
|
3
|
+
import { SQL, SQLAlias, SQLSelectAs, SQLSum } from '@stamhoofd/sql';
|
|
4
4
|
|
|
5
5
|
export const memberCachedBalanceForOrganizationJoin = SQL.leftJoin(
|
|
6
6
|
SQL.select('objectId', 'organizationId',
|
|
@@ -24,23 +24,16 @@ export const registrationCachedBalanceJoin = SQL.leftJoin(
|
|
|
24
24
|
SQL.select('objectId', 'organizationId',
|
|
25
25
|
new SQLSelectAs(
|
|
26
26
|
new SQLSum(
|
|
27
|
-
|
|
28
|
-
SQL.column('
|
|
29
|
-
new SQLPlusSign(),
|
|
30
|
-
SQL.column('amountPending'),
|
|
31
|
-
),
|
|
27
|
+
SQL.calculation(SQL.column('amountOpen'))
|
|
28
|
+
.add(SQL.column('amountPending'))
|
|
32
29
|
),
|
|
33
30
|
new SQLAlias('toPay'),
|
|
34
31
|
),
|
|
35
32
|
new SQLSelectAs(
|
|
36
33
|
new SQLSum(
|
|
37
|
-
|
|
38
|
-
SQL.column('
|
|
39
|
-
|
|
40
|
-
SQL.column('amountPaid'),
|
|
41
|
-
new SQLPlusSign(),
|
|
42
|
-
SQL.column('amountPending'),
|
|
43
|
-
),
|
|
34
|
+
SQL.calculation(SQL.column('amountOpen'))
|
|
35
|
+
.add(SQL.column('amountPaid'))
|
|
36
|
+
.add(SQL.column('amountPending'))
|
|
44
37
|
),
|
|
45
38
|
new SQLAlias('price'),
|
|
46
39
|
),
|
|
@@ -16,6 +16,15 @@ export class STPackageService {
|
|
|
16
16
|
SQL.where('removeAt', null)
|
|
17
17
|
.or('removeAt', '>', new Date()),
|
|
18
18
|
)
|
|
19
|
+
.orderBy('validAt', 'DESC')
|
|
20
|
+
.fetch();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async getValidPackagesWithExpired(organizationId: string) {
|
|
24
|
+
return await STPackage.select()
|
|
25
|
+
.where('organizationId', organizationId)
|
|
26
|
+
.andWhere('validAt', '!=', null)
|
|
27
|
+
.orderBy('validAt', 'DESC')
|
|
19
28
|
.fetch();
|
|
20
29
|
}
|
|
21
30
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { Payment } from '@stamhoofd/models';
|
|
2
|
+
import type { SQLExpression, SQLFilterDefinitions } from '@stamhoofd/sql';
|
|
3
|
+
import { baseSQLFilterCompilers, createColumnFilter, createExistsFilter, createWildcardColumnFilter, SQL, SQLCast, SQLConcat, SQLJsonExtract, SQLJsonUnquote, SQLScalar, SQLSum, SQLValueType } from '@stamhoofd/sql';
|
|
4
|
+
import { PaymentStatus } from '@stamhoofd/structures';
|
|
5
|
+
import { paymentFilterCompilers } from './payments.js';
|
|
3
6
|
|
|
4
7
|
export const orderFilterCompilers: SQLFilterDefinitions = {
|
|
5
8
|
...baseSQLFilterCompilers,
|
|
@@ -56,6 +59,11 @@ export const orderFilterCompilers: SQLFilterDefinitions = {
|
|
|
56
59
|
type: SQLValueType.JSONString,
|
|
57
60
|
nullable: false,
|
|
58
61
|
}),
|
|
62
|
+
checkoutMethodId: createColumnFilter({
|
|
63
|
+
expression: SQL.jsonExtract(SQL.column('data'), '$.value.checkoutMethod.id'),
|
|
64
|
+
type: SQLValueType.JSONString,
|
|
65
|
+
nullable: true,
|
|
66
|
+
}),
|
|
59
67
|
checkoutMethod: createColumnFilter({
|
|
60
68
|
expression: SQL.jsonExtract(SQL.column('data'), '$.value.checkoutMethod.type'),
|
|
61
69
|
type: SQLValueType.JSONString,
|
|
@@ -107,7 +115,14 @@ export const orderFilterCompilers: SQLFilterDefinitions = {
|
|
|
107
115
|
type: SQLValueType.Datetime,
|
|
108
116
|
nullable: true,
|
|
109
117
|
}),
|
|
110
|
-
|
|
118
|
+
discountCodes: {
|
|
119
|
+
...baseSQLFilterCompilers,
|
|
120
|
+
code: createColumnFilter({
|
|
121
|
+
expression: SQL.jsonExtract(SQL.column('data'), '$.value.discountCodes[*].code'),
|
|
122
|
+
type: SQLValueType.JSONArray,
|
|
123
|
+
nullable: true,
|
|
124
|
+
})
|
|
125
|
+
},
|
|
111
126
|
items: createExistsFilter(
|
|
112
127
|
/**
|
|
113
128
|
* There is a bug in MySQL 8 that is fixed in 9.3
|
|
@@ -203,4 +218,60 @@ export const orderFilterCompilers: SQLFilterDefinitions = {
|
|
|
203
218
|
}),
|
|
204
219
|
}),
|
|
205
220
|
),
|
|
221
|
+
payments: createExistsFilter(
|
|
222
|
+
// should equal payments on structure
|
|
223
|
+
SQL.select()
|
|
224
|
+
.from(
|
|
225
|
+
SQL.table('balance_items'),
|
|
226
|
+
)
|
|
227
|
+
.join(
|
|
228
|
+
SQL.join(
|
|
229
|
+
SQL.table('balance_item_payments'),
|
|
230
|
+
).where(
|
|
231
|
+
SQL.column('balance_items', 'id'),
|
|
232
|
+
SQL.column('balance_item_payments', 'balanceItemId'),
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
.join(
|
|
236
|
+
SQL.join(
|
|
237
|
+
SQL.table('payments'),
|
|
238
|
+
).where(
|
|
239
|
+
SQL.column('payments', 'id'),
|
|
240
|
+
SQL.column('balance_item_payments', 'paymentId')
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
.where(
|
|
244
|
+
SQL.column('balance_items', 'orderId'),
|
|
245
|
+
SQL.column('webshop_orders', 'id'),
|
|
246
|
+
)
|
|
247
|
+
// payments on structure filter away failed payments -> also filter out failed payments in backend
|
|
248
|
+
.whereNot(
|
|
249
|
+
SQL.column('payments', 'status'),
|
|
250
|
+
PaymentStatus.Failed
|
|
251
|
+
),
|
|
252
|
+
{
|
|
253
|
+
...baseSQLFilterCompilers,
|
|
254
|
+
paidAt: createColumnFilter({
|
|
255
|
+
expression: SQL.column(Payment.table, 'paidAt'),
|
|
256
|
+
type: SQLValueType.Datetime,
|
|
257
|
+
nullable: false,
|
|
258
|
+
}),
|
|
259
|
+
method: paymentFilterCompilers.method,
|
|
260
|
+
status: paymentFilterCompilers.status,
|
|
261
|
+
price: paymentFilterCompilers.price,
|
|
262
|
+
transferDescription: paymentFilterCompilers.transferDescription,
|
|
263
|
+
},
|
|
264
|
+
),
|
|
265
|
+
// not same as open balance (balance can be negative)
|
|
266
|
+
amountToPay: createColumnFilter({
|
|
267
|
+
expression: SQL.calculation(SQL.column('totalPrice')).subtract(getPricePaidSubQuery()),
|
|
268
|
+
type: SQLValueType.Number,
|
|
269
|
+
nullable: false,
|
|
270
|
+
}),
|
|
206
271
|
};
|
|
272
|
+
|
|
273
|
+
function getPricePaidSubQuery(): SQLExpression {
|
|
274
|
+
return SQL.subQuery(SQL.select(new SQLSum(SQL.column('balance_items', 'pricePaid')))
|
|
275
|
+
.from(SQL.table('balance_items'))
|
|
276
|
+
.where(SQL.column('balance_items', 'orderId'), SQL.column('webshop_orders', 'id')));
|
|
277
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Payment } from '@stamhoofd/models';
|
|
2
|
+
import type { SQLFilterDefinitions } from '@stamhoofd/sql';
|
|
2
3
|
import { baseSQLFilterCompilers, createColumnFilter, createExistsFilter, SQL, SQLCast, SQLConcat, SQLJsonUnquote, SQLScalar, SQLValueType } from '@stamhoofd/sql';
|
|
3
4
|
import { balanceItemPaymentsCompilers } from './balance-item-payments.js';
|
|
4
5
|
|
|
@@ -13,7 +14,7 @@ export const paymentFilterCompilers: SQLFilterDefinitions = {
|
|
|
13
14
|
nullable: false,
|
|
14
15
|
}),
|
|
15
16
|
method: createColumnFilter({
|
|
16
|
-
expression: SQL.column('method'),
|
|
17
|
+
expression: SQL.column(Payment.table, 'method'),
|
|
17
18
|
type: SQLValueType.String,
|
|
18
19
|
nullable: false,
|
|
19
20
|
}),
|
|
@@ -23,7 +24,7 @@ export const paymentFilterCompilers: SQLFilterDefinitions = {
|
|
|
23
24
|
nullable: false,
|
|
24
25
|
}),
|
|
25
26
|
status: createColumnFilter({
|
|
26
|
-
expression: SQL.column('status'),
|
|
27
|
+
expression: SQL.column(Payment.table, 'status'),
|
|
27
28
|
type: SQLValueType.String,
|
|
28
29
|
nullable: false,
|
|
29
30
|
}),
|
|
@@ -53,7 +54,7 @@ export const paymentFilterCompilers: SQLFilterDefinitions = {
|
|
|
53
54
|
nullable: true,
|
|
54
55
|
}),
|
|
55
56
|
price: createColumnFilter({
|
|
56
|
-
expression: SQL.column('price'),
|
|
57
|
+
expression: SQL.column(Payment.table, 'price'),
|
|
57
58
|
type: SQLValueType.Number,
|
|
58
59
|
nullable: false,
|
|
59
60
|
}),
|
|
@@ -63,7 +64,7 @@ export const paymentFilterCompilers: SQLFilterDefinitions = {
|
|
|
63
64
|
nullable: true,
|
|
64
65
|
}),
|
|
65
66
|
transferDescription: createColumnFilter({
|
|
66
|
-
expression: SQL.column('transferDescription'),
|
|
67
|
+
expression: SQL.column(Payment.table, 'transferDescription'),
|
|
67
68
|
type: SQLValueType.String,
|
|
68
69
|
nullable: true,
|
|
69
70
|
}),
|