@stamhoofd/backend 2.137.0 → 2.137.2
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 +26 -26
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +0 -10
- package/src/endpoints/organization/dashboard/invoices/PatchInvoicesEndpoint.ts +18 -0
- package/src/services/InvoicePdfService.ts +48 -12
- package/src/services/InvoiceService.ts +22 -0
- package/src/services/MollieService.ts +5 -5
- package/src/sql-filters/members.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.137.
|
|
3
|
+
"version": "2.137.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -51,29 +51,29 @@
|
|
|
51
51
|
"@aws-sdk/client-sesv2": "3.1090.0",
|
|
52
52
|
"@aws-sdk/client-sqs": "3.1090.0",
|
|
53
53
|
"@aws-sdk/s3-request-presigner": "3.1090.0",
|
|
54
|
-
"@bwip-js/node": "4.11.
|
|
54
|
+
"@bwip-js/node": "4.11.2",
|
|
55
55
|
"@mollie/api-client": "4.6.0",
|
|
56
56
|
"@simonbackx/simple-database": "1.37.1",
|
|
57
57
|
"@simonbackx/simple-encoding": "2.26.11",
|
|
58
58
|
"@simonbackx/simple-endpoints": "1.21.1",
|
|
59
59
|
"@simonbackx/simple-errors": "1.5.0",
|
|
60
60
|
"@simonbackx/simple-logging": "1.0.1",
|
|
61
|
-
"@stamhoofd/backend-env": "2.137.
|
|
62
|
-
"@stamhoofd/backend-i18n": "2.137.
|
|
63
|
-
"@stamhoofd/backend-middleware": "2.137.
|
|
64
|
-
"@stamhoofd/crons": "2.137.
|
|
65
|
-
"@stamhoofd/email": "2.137.
|
|
66
|
-
"@stamhoofd/excel-writer": "2.137.
|
|
67
|
-
"@stamhoofd/logging": "2.137.
|
|
68
|
-
"@stamhoofd/models": "2.137.
|
|
69
|
-
"@stamhoofd/object-differ": "2.137.
|
|
70
|
-
"@stamhoofd/queues": "2.137.
|
|
71
|
-
"@stamhoofd/sql": "2.137.
|
|
72
|
-
"@stamhoofd/structures": "2.137.
|
|
73
|
-
"@stamhoofd/types": "2.137.
|
|
74
|
-
"@stamhoofd/utility": "2.137.
|
|
61
|
+
"@stamhoofd/backend-env": "2.137.2",
|
|
62
|
+
"@stamhoofd/backend-i18n": "2.137.2",
|
|
63
|
+
"@stamhoofd/backend-middleware": "2.137.2",
|
|
64
|
+
"@stamhoofd/crons": "2.137.2",
|
|
65
|
+
"@stamhoofd/email": "2.137.2",
|
|
66
|
+
"@stamhoofd/excel-writer": "2.137.2",
|
|
67
|
+
"@stamhoofd/logging": "2.137.2",
|
|
68
|
+
"@stamhoofd/models": "2.137.2",
|
|
69
|
+
"@stamhoofd/object-differ": "2.137.2",
|
|
70
|
+
"@stamhoofd/queues": "2.137.2",
|
|
71
|
+
"@stamhoofd/sql": "2.137.2",
|
|
72
|
+
"@stamhoofd/structures": "2.137.2",
|
|
73
|
+
"@stamhoofd/types": "2.137.2",
|
|
74
|
+
"@stamhoofd/utility": "2.137.2",
|
|
75
75
|
"archiver": "7.0.1",
|
|
76
|
-
"axios": "1.18.
|
|
76
|
+
"axios": "1.18.1",
|
|
77
77
|
"base-x": "3.0.11",
|
|
78
78
|
"chalk": "5.6.2",
|
|
79
79
|
"cookie": "0.7.2",
|
|
@@ -82,31 +82,31 @@
|
|
|
82
82
|
"jose": "5.10.0",
|
|
83
83
|
"jsonwebtoken": "9.0.3",
|
|
84
84
|
"jsvat-next": "3.0.4",
|
|
85
|
-
"libphonenumber-js": "1.13.
|
|
85
|
+
"libphonenumber-js": "1.13.9",
|
|
86
86
|
"luxon": "3.7.2",
|
|
87
|
-
"mailparser": "3.9.
|
|
87
|
+
"mailparser": "3.9.14",
|
|
88
88
|
"mockdate": "3.0.5",
|
|
89
89
|
"mysql2": "3.23.1",
|
|
90
90
|
"node-rsa": "2.0.0",
|
|
91
91
|
"openid-client": "5.7.1",
|
|
92
92
|
"stripe": "16.12.0",
|
|
93
|
-
"uuid": "14.0.
|
|
93
|
+
"uuid": "14.0.1"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
|
-
"@stamhoofd/test-utils": "2.137.
|
|
96
|
+
"@stamhoofd/test-utils": "2.137.2",
|
|
97
97
|
"@types/cookie": "0.6.0",
|
|
98
|
-
"@types/luxon": "3.7.
|
|
98
|
+
"@types/luxon": "3.7.2",
|
|
99
99
|
"@types/mailparser": "3.4.6",
|
|
100
100
|
"@types/node": "22.20.1",
|
|
101
101
|
"@types/uuid": "8.3.4",
|
|
102
|
-
"nock": "14.0.
|
|
103
|
-
"qs": "6.15.
|
|
102
|
+
"nock": "14.0.16",
|
|
103
|
+
"qs": "6.15.3",
|
|
104
104
|
"sinon": "18.0.1",
|
|
105
|
-
"vitest": "4.1.
|
|
105
|
+
"vitest": "4.1.10"
|
|
106
106
|
},
|
|
107
107
|
"license": "UNLICENCED",
|
|
108
108
|
"publishConfig": {
|
|
109
109
|
"access": "public"
|
|
110
110
|
},
|
|
111
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "e0a970fd52c4f5a57f1e5a41cf40940859277ddf"
|
|
112
112
|
}
|
|
@@ -187,16 +187,6 @@ export class GetInvoicesEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
for (const invoice of invoices) {
|
|
191
|
-
if (invoice.number && !invoice.pdf && STAMHOOFD.environment === 'development') {
|
|
192
|
-
await InvoicePdfService.generatePdf(invoice);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (invoice.number && STAMHOOFD.environment === 'development') {
|
|
196
|
-
await InvoiceXMlService.generateXml(invoice);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
190
|
return new PaginatedResponse<InvoiceStruct[], LimitedFilteredRequest>({
|
|
201
191
|
results: await AuthenticatedStructures.invoices(invoices, true),
|
|
202
192
|
next,
|
|
@@ -57,6 +57,24 @@ export class PatchInvoicesEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
57
57
|
invoices.push(model);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
for (const patch of request.body.getPatches()) {
|
|
61
|
+
const model = await Invoice.getByID(patch.id);
|
|
62
|
+
if (!model || model.organizationId !== organization.id) {
|
|
63
|
+
throw Context.auth.notFoundOrNoAccess($t('%ZcE'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!model.number) {
|
|
67
|
+
// ignore
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Create PDF
|
|
72
|
+
if (patch.pdf === null) {
|
|
73
|
+
await InvoiceService.retryInvoiceGenerationAndSending(model);
|
|
74
|
+
}
|
|
75
|
+
invoices.push(model);
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
for (const id of request.body.getDeletes()) {
|
|
61
79
|
const model = await Invoice.getByID(id);
|
|
62
80
|
if (!model || model.organizationId !== organization.id) {
|
|
@@ -258,26 +258,62 @@ export class InvoicePdfService {
|
|
|
258
258
|
const timeout = setTimeout(() => controller.abort(), 30_000);
|
|
259
259
|
|
|
260
260
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
261
|
+
if (STAMHOOFD.environment === 'test') {
|
|
262
|
+
const file = new File({
|
|
263
|
+
id: uuidv4(),
|
|
264
|
+
server: 'https://' + STAMHOOFD.SPACES_BUCKET + '.' + STAMHOOFD.SPACES_ENDPOINT,
|
|
265
|
+
path: 'test.pdf',
|
|
266
|
+
size: 0,
|
|
267
|
+
name: (invoice.number ?? invoice.id),
|
|
268
|
+
isPrivate: false,
|
|
269
|
+
contentType: 'application/pdf',
|
|
270
|
+
});
|
|
271
271
|
invoice.pdf = file;
|
|
272
272
|
await invoice.save();
|
|
273
273
|
} else {
|
|
274
|
+
// Issue with system trusted CA in development
|
|
275
|
+
const result = await fetch((STAMHOOFD.environment === 'development' ? 'http://' : 'https://') + STAMHOOFD.domains.rendererApi + '/v' + Version + '/html-to-pdf', {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
body: form,
|
|
278
|
+
signal: controller.signal,
|
|
279
|
+
});
|
|
280
|
+
if (result.status === 200) {
|
|
274
281
|
// todo
|
|
282
|
+
const buffer = Buffer.from(await result.arrayBuffer());
|
|
283
|
+
const file = await this.uploadPdf(invoice, buffer);
|
|
284
|
+
invoice.pdf = file;
|
|
285
|
+
await invoice.save();
|
|
286
|
+
} else {
|
|
287
|
+
let txt = '';
|
|
288
|
+
try {
|
|
289
|
+
txt = await result.text();
|
|
290
|
+
} catch (e) {
|
|
291
|
+
txt = '<unreadable stream>';
|
|
292
|
+
console.error('Could not read response', e);
|
|
293
|
+
}
|
|
294
|
+
console.error('Failed to generate invoice PDF. Non 200 statuscode: ', result.status, txt);
|
|
295
|
+
throw new SimpleError({
|
|
296
|
+
code: 'pdf_creation_timeout',
|
|
297
|
+
message: 'Failed to generate invoice PDF',
|
|
298
|
+
human: $t('Er ging iets mis bij het aanmaken van de factuur'),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
275
301
|
}
|
|
276
302
|
} catch (err) {
|
|
303
|
+
console.error('Failed to generate invoice PDF', err);
|
|
304
|
+
|
|
277
305
|
if (err instanceof DOMException && err.name === 'AbortError') {
|
|
278
|
-
throw new
|
|
306
|
+
throw new SimpleError({
|
|
307
|
+
code: 'pdf_creation_timeout',
|
|
308
|
+
message: 'Failed to generate invoice PDF: timeout',
|
|
309
|
+
human: $t('Er ging iets mis bij het aanmaken van de factuur'),
|
|
310
|
+
});
|
|
279
311
|
}
|
|
280
|
-
|
|
312
|
+
throw new SimpleError({
|
|
313
|
+
code: 'pdf_creation_failed',
|
|
314
|
+
message: 'Failed to generate invoice PDF',
|
|
315
|
+
human: $t('Er ging iets mis bij het aanmaken van de factuur'),
|
|
316
|
+
});
|
|
281
317
|
} finally {
|
|
282
318
|
clearTimeout(timeout);
|
|
283
319
|
}
|
|
@@ -302,6 +302,28 @@ export class InvoiceService {
|
|
|
302
302
|
return model;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
static async retryInvoiceGenerationAndSending(model: Invoice) {
|
|
306
|
+
const hadPdf = !!model.pdf;
|
|
307
|
+
if (!model.pdf) {
|
|
308
|
+
// Create PDF
|
|
309
|
+
await InvoicePdfService.generatePdf(model);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!model.xml) {
|
|
313
|
+
await InvoiceXMlService.generateXml(model);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const organization = await Organization.getByID(model.organizationId);
|
|
317
|
+
if (!organization) {
|
|
318
|
+
console.error('Unexpected missing organization for ' + model.id);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!await this.forwardInvoice(model, organization) && !model.didSendPeppol && !hadPdf) {
|
|
323
|
+
await this.sendCustomerEmail(model, organization);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
305
327
|
/**
|
|
306
328
|
* Permanently delete an invoice.
|
|
307
329
|
*
|
|
@@ -250,11 +250,11 @@ export class MollieService {
|
|
|
250
250
|
|
|
251
251
|
details: PaymentMandateDetails.create({
|
|
252
252
|
name: ('consumerName' in details ? details.consumerName : details.cardHolder) ?? undefined,
|
|
253
|
-
cardNumber: 'cardNumber' in details ? details.cardNumber : null,
|
|
254
|
-
iban: 'consumerAccount' in details ? Formatter.iban(details.consumerAccount) : null,
|
|
255
|
-
bic: ('consumerBic' in details ? details.consumerBic : undefined),
|
|
256
|
-
expiryDate: ('cardExpiryDate' in details ? DateTime.fromISO(details.cardExpiryDate, { zone: Formatter.timezone }).toJSDate() : null), // todo: parse date correctly in Brussels timezone!
|
|
257
|
-
brand: ('cardLabel' in details ? details.cardLabel : null),
|
|
253
|
+
cardNumber: 'cardNumber' in details && details.cardNumber ? details.cardNumber : null,
|
|
254
|
+
iban: 'consumerAccount' in details && details.consumerAccount ? Formatter.iban(details.consumerAccount) : null,
|
|
255
|
+
bic: ('consumerBic' in details && details.consumerBic ? details.consumerBic : undefined),
|
|
256
|
+
expiryDate: ('cardExpiryDate' in details && details.cardExpiryDate ? DateTime.fromISO(details.cardExpiryDate, { zone: Formatter.timezone }).toJSDate() : null), // todo: parse date correctly in Brussels timezone!
|
|
257
|
+
brand: ('cardLabel' in details && details.cardLabel ? details.cardLabel : null),
|
|
258
258
|
}),
|
|
259
259
|
});
|
|
260
260
|
}
|
|
@@ -522,7 +522,9 @@ export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
|
522
522
|
throw new SimpleError({
|
|
523
523
|
code: 'permission_denied',
|
|
524
524
|
message: 'No permissions to filter on record ' + key,
|
|
525
|
-
human: result.record
|
|
525
|
+
human: result.record
|
|
526
|
+
? $t(`%1Bc`, { recordName: result.record.name })
|
|
527
|
+
: $t(`%Zg6`),
|
|
526
528
|
statusCode: 400,
|
|
527
529
|
});
|
|
528
530
|
}
|