• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GEWIS / sudosos-backend / 23807427344

31 Mar 2026 04:10PM UTC coverage: 89.066% (-0.02%) from 89.088%
23807427344

Pull #829

github

web-flow
Merge baaecb902 into b26417fa9
Pull Request #829: Lift invoice validators into AsyncValidatorMiddleware

1809 of 2218 branches covered (81.56%)

Branch coverage included in aggregate %.

14 of 14 new or added lines in 2 files covered. (100.0%)

36 existing lines in 2 files now uncovered.

9286 of 10239 relevant lines covered (90.69%)

1004.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.3
/src/controller/invoice-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2026 Study association GEWIS
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as published
7
 *  by the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 *
18
 *  @license
19
 */
20

21
/**
22
 * This is the page of invoice controller.
23
 *
24
 * @module invoicing
25
 */
26

27
import log4js, { Logger } from 'log4js';
2✔
28
import { Response } from 'express';
29
import BaseController, { BaseControllerOptions } from './base-controller';
2✔
30
import Policy from './policy';
31
import { RequestWithToken } from '../middleware/token-middleware';
32
import InvoiceService, { InvoiceFilterParameters, parseInvoiceFilterParameters } from '../service/invoice-service';
2✔
33
import { parseRequestPagination, toResponse } from '../helpers/pagination';
2✔
34
import {
35
  CreateInvoiceParams,
36
  CreateInvoiceRequest,
37
  UpdateInvoiceParams,
38
  UpdateInvoiceRequest,
39
} from './request/invoice-request';
40
import { createInvoiceRequestSpec, verifyUpdateInvoiceRequest } from './request/validators/invoice-request-spec';
2✔
41
import { isFail } from '../helpers/specification-validation';
2✔
42
import { globalAsyncValidatorRegistry } from '../middleware/async-validator-registry';
2✔
43
import { asBoolean, asDate, asInvoiceState, asNumber } from '../helpers/validators';
2✔
44
import Invoice from '../entity/invoices/invoice';
2✔
45
import User, { UserType } from '../entity/user/user';
2✔
46
import { UpdateInvoiceUserRequest } from './request/user-request';
47
import InvoiceUser from '../entity/user/invoice-user';
2✔
48
import { parseInvoiceUserToResponse } from '../helpers/revision-to-response';
2✔
49
import { AppDataSource } from '../database/database';
2✔
50
import { NotImplementedError, PdfError } from '../errors';
2✔
51

52
/**
53
 * The Invoice controller.
54
 */
55
export default class InvoiceController extends BaseController {
2✔
56
  private logger: Logger = log4js.getLogger('InvoiceController');
2✔
57

58
  /**
59
    * Creates a new Invoice controller instance.
60
    * @param options - The options passed to the base controller.
61
    */
62
  public constructor(options: BaseControllerOptions) {
63
    super(options);
2✔
64
    this.logger.level = process.env.LOG_LEVEL;
2✔
65
    globalAsyncValidatorRegistry.register('CreateInvoiceRequest', createInvoiceRequestSpec);
2✔
66
  }
67

68
  /**
69
    * @inhertidoc
70
    */
71
  getPolicy(): Policy {
72
    return {
2✔
73
      '/': {
74
        GET: {
75
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'Invoice', ['*']),
5✔
76
          handler: this.getAllInvoices.bind(this),
77
        },
78
        POST: {
79
          body: { modelName: 'CreateInvoiceRequest' },
80
          policy: async (req) => this.roleManager.can(req.token.roles, 'create', 'all', 'Invoice', ['*']),
7✔
81
          handler: this.createInvoice.bind(this),
82
        },
83
      },
84
      '/users/:id(\\d+)': {
85
        GET: {
86
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'Invoice', ['*']),
5✔
87
          handler: this.getSingleInvoiceUser.bind(this),
88
        },
89
        PUT : {
90
          body: { modelName: 'UpdateInvoiceUserRequest' },
91
          policy: async (req) => this.roleManager.can(req.token.roles, 'update', 'all', 'Invoice', ['*']),
6✔
92
          handler: this.updateInvoiceUser.bind(this),
93
        },
94
        DELETE: {
95
          policy: async (req) => this.roleManager.can(req.token.roles, 'delete', 'all', 'Invoice', ['*']),
3✔
96
          handler: this.deleteInvoiceUser.bind(this),
97
        },
98
      },
99
      '/:id(\\d+)': {
100
        GET: {
101
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', await InvoiceController.getRelation(req), 'Invoice', ['*']),
6✔
102
          handler: this.getSingleInvoice.bind(this),
103
        },
104
        PATCH: {
105
          body: { modelName: 'UpdateInvoiceRequest' },
106
          policy: async (req) => this.roleManager.can(req.token.roles, 'update', 'all', 'Invoice', ['*']),
5✔
107
          handler: this.updateInvoice.bind(this),
108
        },
109
        DELETE: {
110
          policy: async (req) => this.roleManager.can(req.token.roles, 'delete', 'all', 'Invoice', ['*']),
2✔
111
          handler: this.deleteInvoice.bind(this),
112
        },
113
      },
114
      '/:id(\\d+)/pdf': {
115
        GET: {
116
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', await InvoiceController.getRelation(req), 'Invoice', ['*']),
3✔
117
          handler: this.getInvoicePDF.bind(this),
118
        },
119
      },
120
      '/eligible-transactions': {
121
        GET: {
UNCOV
122
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'Invoice', ['*']),
×
123
          handler: this.getEligibleTransactions.bind(this),
124
        },
125
      },
126
      '/drift': {
127
        GET: {
UNCOV
128
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'Invoice', ['*']),
×
129
          handler: this.getDriftedInvoices.bind(this),
130
        },
131
      },
132
    };
133
  }
134

135
  /**
136
   * GET /invoices
137
   * @summary Returns all invoices in the system.
138
   * @operationId getAllInvoices
139
   * @tags invoices - Operations of the invoices controller
140
   * @security JWT
141
   * @param {integer} toId.query - Filter on Id of the debtor
142
   * @param {number} invoiceId.query - Filter on invoice ID
143
   * @param {Array<string|number>} currentState.query enum:CREATED,SENT,PAID,DELETED - Filter based on Invoice State.
144
   * @param {boolean} returnEntries.query - Boolean if invoice entries should be returned
145
   * @param {string} fromDate.query - Start date for selected invoices (inclusive)
146
   * @param {string} tillDate.query - End date for selected invoices (exclusive)
147
   * @param {string} description.query - Filter invoices by description (partial match)
148
   * @param {integer} take.query - How many entries the endpoint should return
149
   * @param {integer} skip.query - How many entries should be skipped (for pagination)
150
   * @return {PaginatedInvoiceResponse} 200 - All existing invoices
151
   * @return {string} 500 - Internal server error
152
   */
153
  public async getAllInvoices(req: RequestWithToken, res: Response): Promise<void> {
154
    const { body } = req;
4✔
155
    this.logger.trace('Get all invoices', body, 'by user', req.token.user);
4✔
156

157
    let take;
158
    let skip;
159
    let filters: InvoiceFilterParameters;
160
    try {
4✔
161
      const pagination = parseRequestPagination(req);
4✔
162
      filters = parseInvoiceFilterParameters(req);
4✔
163
      take = pagination.take;
4✔
164
      skip = pagination.skip;
4✔
165
    } catch (e) {
UNCOV
166
      res.status(400).send(e.message);
×
UNCOV
167
      return;
×
168
    }
169

170
    // Handle request
171
    try {
4✔
172
      const [invoices, count] = await new InvoiceService().getPaginatedInvoices(
4✔
173
        filters, { take, skip },
174
      );
175

176
      const records = filters.returnInvoiceEntries
4!
177
        ? InvoiceService.toArrayResponse(invoices)
178
        : InvoiceService.toArrayWithoutEntriesResponse(invoices);
179

180
      res.json(toResponse(records, count, { take, skip }));
4✔
181
    } catch (error) {
UNCOV
182
      this.logger.error('Could not return all invoices:', error);
×
UNCOV
183
      res.status(500).json('Internal server error.');
×
184
    }
185
  }
186

187
  /**
188
   * GET /invoices/{id}
189
   * @summary Returns a single invoice in the system.
190
   * @operationId getSingleInvoice
191
   * @param {integer} id.path.required - The id of the requested invoice
192
   * @tags invoices - Operations of the invoices controller
193
   * @security JWT
194
   * @param {boolean} returnEntries.query -
195
   * Boolean if invoice entries should be returned, defaults to true.
196
   * @return {InvoiceResponse} 200 - All existing invoices
197
   * @return {string} 404 - Invoice not found
198
   * @return {string} 500 - Internal server error
199
   */
200
  public async getSingleInvoice(req: RequestWithToken, res: Response): Promise<void> {
201
    const { id } = req.params;
6✔
202
    const invoiceId = parseInt(id, 10);
6✔
203
    this.logger.trace('Get invoice', invoiceId, 'by user', req.token.user);
6✔
204

205
    // Handle request
206
    try {
6✔
207
      const returnInvoiceEntries = asBoolean(req.query.returnEntries) ?? true;
6✔
208

209
      const invoices: Invoice[] = await new InvoiceService().getInvoices(
6✔
210
        { invoiceId, returnInvoiceEntries },
211
      );
212

213
      const invoice = invoices[0];
6✔
214
      if (!invoice) {
6✔
215
        res.status(404).json('Unknown invoice ID.');
1✔
216
        return;
1✔
217
      }
218
      const response = returnInvoiceEntries
5!
219
        ? InvoiceService.asInvoiceResponse(invoice)
220
        : InvoiceService.asBaseInvoiceResponse(invoice);
221

222
      res.json(response);
5✔
223
    } catch (error) {
UNCOV
224
      this.logger.error('Could not return invoice:', error);
×
UNCOV
225
      res.status(500).json('Internal server error.');
×
226
    }
227
  }
228

229
  /**
230
   * POST /invoices
231
   * @summary Adds an invoice to the system.
232
   * @operationId createInvoice
233
   * @tags invoices - Operations of the invoices controller
234
   * @security JWT
235
   * @param {CreateInvoiceRequest} request.body.required -
236
   * The invoice which should be created
237
   * @return {InvoiceResponse} 200 - The created invoice entity
238
   * @return {ValidationResponse} 400 - Validation error
239
   * @return {string} 500 - Internal server error
240
   */
241
  public async createInvoice(req: RequestWithToken, res: Response): Promise<void> {
242
    const body = req.body as CreateInvoiceRequest;
1✔
243
    this.logger.trace('Create Invoice', body, 'by user', req.token.user);
1✔
244

245
    // handle request
246
    try {
1✔
247
      const userDefinedDefaults = await new InvoiceService().getDefaultInvoiceParams(body.forId);
1✔
248

249
      // If no byId is provided we use the token user id.
250
      const params: CreateInvoiceParams = {
1✔
251
        ...userDefinedDefaults,
252
        ...body,
253
        date: body.date ? new Date(body.date) : new Date(),
1!
254
        byId: body.byId ?? req.token.user.id,
1!
255
        description: body.description ?? '',
1!
256
      };
257

258
      const invoice: Invoice = await AppDataSource.manager.transaction(async (manager) =>
1✔
259
        new InvoiceService(manager).createInvoice(params));
1✔
260
      res.json(InvoiceService.asInvoiceResponse(invoice));
1✔
261
    } catch (error) {
UNCOV
262
      if (error instanceof NotImplementedError) {
×
UNCOV
263
        res.status(501).json(error.message);
×
UNCOV
264
        return;
×
265
      }
UNCOV
266
      this.logger.error('Could not create invoice:', error);
×
267
      res.status(500).json('Internal server error.');
×
268
    }
269
  }
270

271
  /**
272
   * PATCH /invoices/{id}
273
   * @summary Adds an invoice to the system.
274
   * @operationId updateInvoice
275
   * @tags invoices - Operations of the invoices controller
276
   * @security JWT
277
   * @param {integer} id.path.required - The id of the invoice which should be updated
278
   * @param {UpdateInvoiceRequest} request.body.required -
279
   * The invoice update to process
280
   * @return {BaseInvoiceResponse} 200 - The updated invoice entity
281
   * @return {ValidationResponse} 400 - Validation error
282
   * @return {string} 500 - Internal server error
283
   */
284
  public async updateInvoice(req: RequestWithToken, res: Response): Promise<void> {
285
    const body = req.body as UpdateInvoiceRequest;
4✔
286
    const { id } = req.params;
4✔
287
    const invoiceId = parseInt(id, 10);
4✔
288
    this.logger.trace('Update Invoice', body, 'by user', req.token.user);
4✔
289

290
    try {
4✔
291
      // Default byId to token user id.
292
      const params: UpdateInvoiceParams = {
4✔
293
        ...body,
294
        invoiceId,
295
        state: asInvoiceState(body.state),
296
        byId: body.byId ?? req.token.user.id,
8✔
297
      };
298

299
      // Validated inline because the spec needs invoiceId from route params,
300
      // which the async validator middleware cannot provide yet.
301
      const validation = await verifyUpdateInvoiceRequest(params);
4✔
302
      if (isFail(validation)) {
4✔
303
        res.status(400).json({ valid: false, errors: [String(validation.fail.value)] });
3✔
304
        return;
3✔
305
      }
306

307
      const invoice: Invoice = await AppDataSource.manager.transaction(async (manager) =>
1✔
308
        new InvoiceService(manager).updateInvoice(params));
1✔
309

310
      res.json(InvoiceService.asBaseInvoiceResponse(invoice));
1✔
311
    } catch (error) {
UNCOV
312
      this.logger.error('Could not update invoice:', error);
×
UNCOV
313
      res.status(500).json('Internal server error.');
×
314
    }
315
  }
316

317
  /**
318
   * DELETE /invoices/{id}
319
   * @summary Deletes an invoice.
320
   * @operationId deleteInvoice
321
   * @tags invoices - Operations of the invoices controller
322
   * @security JWT
323
   * @param {integer} id.path.required - The id of the invoice which should be deleted
324
   * @return {string} 404 - Invoice not found
325
   * @return 204 - Deletion success
326
   * @return {string} 500 - Internal server error
327
   */
328
  // TODO Deleting of invoices that are not of state CREATED?
329
  public async deleteInvoice(req: RequestWithToken, res: Response): Promise<void> {
330
    const { id } = req.params;
2✔
331
    const invoiceId = parseInt(id, 10);
2✔
332
    this.logger.trace('Delete Invoice', id, 'by user', req.token.user);
2✔
333

334
    try {
2✔
335
      const invoice = await AppDataSource.manager.transaction(async (manager) =>
2✔
336
        new InvoiceService(manager).deleteInvoice(invoiceId, req.token.user.id));
2✔
337
      if (!invoice) {
2✔
338
        res.status(404).json('Invoice not found.');
1✔
339
        return;
1✔
340
      }
341
      res.status(204).send();
1✔
342
    } catch (error) {
UNCOV
343
      this.logger.error('Could not delete invoice:', error);
×
UNCOV
344
      res.status(500).json('Internal server error.');
×
345
    }
346
  }
347

348
  /**
349
   * GET /invoices/{id}/pdf
350
   * @summary Get an invoice pdf.
351
   * @operationId getInvoicePdf
352
   * @tags invoices - Operations of the invoices controller
353
   * @security JWT
354
   * @param {integer} id.path.required - The id of the invoice to return
355
   * @param {boolean} force.query - Force creation of pdf
356
   * @return {string} 404 - Invoice not found
357
   * @return {string} 200 - The pdf location information.
358
   * @return {string} 500 - Internal server error
359
   */
360
  public async getInvoicePDF(req: RequestWithToken, res: Response): Promise<void> {
361
    const { id } = req.params;
2✔
362
    const invoiceId = parseInt(id, 10);
2✔
363
    this.logger.trace('Get Invoice PDF', id, 'by user', req.token.user);
2✔
364

365
    try {
2✔
366
      const invoice = await Invoice.findOne(InvoiceService.getOptions({ invoiceId, returnInvoiceEntries: true }) );
2✔
367
      if (!invoice) {
2✔
368
        res.status(404).json('Invoice not found.');
1✔
369
        return;
1✔
370
      }
371

372
      const pdf = await invoice.getOrCreatePdf(req.query.force === 'true');
1✔
373

374
      res.status(200).json({ pdf: pdf.downloadName });
1✔
375
    } catch (error) {
376
      this.logger.error('Could get invoice PDF:', error);
×
UNCOV
377
      if (error instanceof PdfError) {
×
378
        res.status(502).json('PDF Generator service failed.');
×
UNCOV
379
        return;
×
380
      }
UNCOV
381
      res.status(500).json('Internal server error.');
×
382
    }
383
  }
384

385
  /**
386
   * DELETE /invoices/users/{id}
387
   * @summary Delete invoice user defaults.
388
   * @operationId deleteInvoiceUser
389
   * @tags invoices - Operations of the invoices controller
390
   * @security JWT
391
   * @param {integer} id.path.required - The id of the invoice user to delete.
392
   * @return {string} 404 - Invoice User not found
393
   * @return 204 - Success
394
   * @return {string} 500 - Internal server error
395
   */
396
  public async deleteInvoiceUser(req: RequestWithToken, res: Response): Promise<void> {
397
    const { id } = req.params;
2✔
398
    const userId = parseInt(id, 10);
2✔
399
    this.logger.trace('Delete Invoice User', id, 'by user', req.token.user);
2✔
400

401
    try {
2✔
402
      const invoiceUser = await InvoiceUser.findOne({ where: { userId } });
2✔
403
      if (!invoiceUser) {
2✔
404
        res.status(404).json('Invoice User not found.');
1✔
405
        return;
1✔
406
      }
407

408
      await InvoiceUser.delete(userId);
1✔
409
      res.status(204).json();
1✔
410
    } catch (error) {
UNCOV
411
      this.logger.error('Could not get invoice user:', error);
×
UNCOV
412
      res.status(500).json('Internal server error.');
×
413
    }
414
  }
415

416
  /**
417
   * GET /invoices/users/{id}
418
   * @summary Get invoice user defaults.
419
   * @operationId getSingleInvoiceUser
420
   * @tags invoices - Operations of the invoices controller
421
   * @security JWT
422
   * @param {integer} id.path.required - The id of the invoice user to return.
423
   * @return {string} 404 - Invoice User not found
424
   * @return {string} 404 - User not found
425
   * @return {string} 400 - User is not of type INVOICE
426
   * @return {InvoiceUserResponse} 200 - The requested Invoice User
427
   * @return {string} 500 - Internal server error
428
   */
429
  public async getSingleInvoiceUser(req: RequestWithToken, res: Response): Promise<void> {
430
    const { id } = req.params;
4✔
431
    const userId = parseInt(id, 10);
4✔
432
    this.logger.trace('Get Invoice User', id, 'by user', req.token.user);
4✔
433

434
    try {
4✔
435
      const user = await User.findOne({ where: { id: userId, deleted: false } });
4✔
436
      if (!user) {
4✔
437
        res.status(404).json('User not found.');
1✔
438
        return;
1✔
439
      }
440

441
      if (user.type !== UserType.INVOICE) {
3✔
442
        res.status(400).json(`User is of type ${UserType[user.type]} and not of type INVOICE.`);
1✔
443
        return;
1✔
444
      }
445

446
      const invoiceUser = await InvoiceUser.findOne({ where: { userId }, relations: ['user'] });
2✔
447
      if (!invoiceUser) {
2✔
448
        res.status(404).json('Invoice User not found.');
1✔
449
        return;
1✔
450
      }
451

452
      res.status(200).json(parseInvoiceUserToResponse(invoiceUser));
1✔
453
    } catch (error) {
UNCOV
454
      this.logger.error('Could not get invoice user:', error);
×
UNCOV
455
      res.status(500).json('Internal server error.');
×
456
    }
457
  }
458

459
  /**
460
   * PUT /invoices/users/{id}
461
   * @summary Update or create invoice user defaults.
462
   * @operationId putInvoiceUser
463
   * @tags invoices - Operations of the invoices controller
464
   * @security JWT
465
   * @param {integer} id.path.required - The id of the user to update
466
   * @param {UpdateInvoiceUserRequest} request.body.required - The invoice user which should be updated
467
   * @return {string} 404 - User not found
468
   * @return {string} 400 - User is not of type INVOICE
469
   * @return {InvoiceUserResponse} 200 - The updated / created Invoice User
470
   * @return {string} 500 - Internal server error
471
   */
472
  public async updateInvoiceUser(req: RequestWithToken, res: Response): Promise<void> {
473
    const { id } = req.params;
5✔
474
    const body = req.body as UpdateInvoiceUserRequest;
5✔
475
    const userId = parseInt(id, 10);
5✔
476
    this.logger.trace('Update Invoice User', id, 'by user', req.token.user);
5✔
477

478
    try {
5✔
479
      const user = await User.findOne({ where: { id: userId, deleted: false } });
5✔
480
      if (!user) {
5✔
481
        res.status(404).json('User not found.');
1✔
482
        return;
1✔
483
      }
484

485
      if (!([UserType.INVOICE, UserType.ORGAN].includes(user.type))) {
4✔
486
        res.status(400).json(`User is of type ${UserType[user.type]} and not of type INVOICE or ORGAN.`);
1✔
487
        return;
1✔
488
      }
489

490
      let invoiceUser = Object.assign(new InvoiceUser(), {
3✔
491
        ...body,
492
        user,
493
      }) as InvoiceUser;
494

495
      invoiceUser = await InvoiceUser.save(invoiceUser);
3✔
496

497
      res.status(200).json(parseInvoiceUserToResponse(invoiceUser));
3✔
498
    } catch (error) {
UNCOV
499
      this.logger.error('Could not update invoice user:', error);
×
UNCOV
500
      res.status(500).json('Internal server error.');
×
501
    }
502
  }
503

504
  /**
505
   * GET /invoices/eligible-transactions
506
   * @summary Get eligible transactions for invoice creation.
507
   * @operationId getEligibleTransactions
508
   * @tags invoices - Operations of the invoices controller
509
   * @security JWT
510
   * @param {integer} forId.query.required - Filter on Id of the debtor
511
   * @param {string} fromDate.query.required - Start date for selected transactions (inclusive)
512
   * @param {string} tillDate.query - End date for selected transactions (exclusive)
513
   * @return {TransactionResponse} 200 - The eligible transactions
514
   * @return {string} 500 - Internal server error
515
   */
516
  public async getEligibleTransactions(req: RequestWithToken, res: Response): Promise<void> {
UNCOV
517
    this.logger.trace('Get eligible transactions for invoice creation', req.query, 'by user', req.token.user);
×
518

519
    let fromDate, tillDate;
520
    let forId;
521
    try {
×
UNCOV
522
      forId = asNumber(req.query.forId);
×
523
      fromDate = asDate(req.query.fromDate);
×
524
      tillDate = req.query.tillDate ? asDate(req.query.tillDate) : undefined;
×
525
    } catch (e) {
UNCOV
526
      res.status(400).send(e.message);
×
527
      return;
×
528
    }
529

UNCOV
530
    try {
×
UNCOV
531
      const transactions = await new InvoiceService().getEligibleTransactions({
×
532
        forId,
533
        fromDate,
534
        tillDate,
535
      });
536
      res.json(transactions);
×
537
    } catch (error) {
UNCOV
538
      this.logger.error('Could not get eligible transactions:', error);
×
UNCOV
539
      res.status(500).json('Internal server error.');
×
540
    }
541
  }
542

543

544
  /**
545
   * GET /invoices/drift
546
   * @summary Returns all invoices with transfer amount drift: for active invoices transfer != row sum; for deleted invoices transfer != credit transfer.
547
   * @operationId getInvoiceDrift
548
   * @tags invoices - Operations of the invoices controller
549
   * @security JWT
550
   * @return {Array<InvoiceDriftResponse>} 200 - All invoices with transfer amount drift
551
   * @return {string} 500 - Internal server error
552
   */
553
  public async getDriftedInvoices(req: RequestWithToken, res: Response): Promise<void> {
554
    this.logger.trace('Get drifted invoices by user', req.token.user);
×
UNCOV
555
    try {
×
556
      const results = await new InvoiceService().findDriftedInvoices();
×
557
      res.json(results.map(InvoiceService.asInvoiceDriftResponse));
×
558
    } catch (error) {
UNCOV
559
      this.logger.error('Could not return drifted invoices:', error);
×
UNCOV
560
      res.status(500).json('Internal server error.');
×
561
    }
562
  }
563

564
  /**
565
   * Function to determine which credentials are needed to get invoice
566
   * all if user is not connected to invoice
567
   * own if user is connected to invoice
568
   * @param req
569
   * @return whether invoice is connected to used token
570
   */
571
  static async getRelation(req: RequestWithToken): Promise<string> {
572
    const invoice: Invoice = await Invoice.findOne({ where: { id: parseInt(req.params.id, 10) }, relations: ['to'] });
9✔
573
    if (!invoice) return 'all';
9✔
574
    if (invoice.to.id === req.token.user.id) return 'own';
7✔
575
    return 'all';
6✔
576
  }
577
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc