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

GEWIS / sudosos-backend / 23807134840

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

Pull #829

github

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

1809 of 2218 branches covered (81.56%)

Branch coverage included in aggregate %.

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

23 existing lines in 1 file now uncovered.

9286 of 10239 relevant lines covered (90.69%)

1004.31 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: {
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: {
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) {
166
      res.status(400).send(e.message);
×
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) {
182
      this.logger.error('Could not return all invoices:', error);
×
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) {
224
      this.logger.error('Could not return invoice:', error);
×
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) {
262
      if (error instanceof NotImplementedError) {
×
263
        res.status(501).json(error.message);
×
264
        return;
×
265
      }
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
      const validation = await verifyUpdateInvoiceRequest(params);
4✔
300
      if (isFail(validation)) {
4✔
301
        res.status(400).json({ valid: false, errors: [String(validation.fail.value)] });
3✔
302
        return;
3✔
303
      }
304

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

493
      invoiceUser = await InvoiceUser.save(invoiceUser);
3✔
494

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

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

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

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

541

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

562
  /**
563
   * Function to determine which credentials are needed to get invoice
564
   * all if user is not connected to invoice
565
   * own if user is connected to invoice
566
   * @param req
567
   * @return whether invoice is connected to used token
568
   */
569
  static async getRelation(req: RequestWithToken): Promise<string> {
570
    const invoice: Invoice = await Invoice.findOne({ where: { id: parseInt(req.params.id, 10) }, relations: ['to'] });
9✔
571
    if (!invoice) return 'all';
9✔
572
    if (invoice.to.id === req.token.user.id) return 'own';
7✔
573
    return 'all';
6✔
574
  }
575
}
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