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

GEWIS / sudosos-backend / 25753937432

12 May 2026 09:17AM UTC coverage: 88.117% (-1.0%) from 89.089%
25753937432

push

github

web-flow
chore(deps): fix missing dependencies for running docs:dev (#911)

3925 of 4574 branches covered (85.81%)

Branch coverage included in aggregate %.

20093 of 22683 relevant lines covered (88.58%)

1125.83 hits per line

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

78.99
/src/controller/invoice-controller.ts
1
/**
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
 */
1✔
20

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

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

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

58
  /**
1✔
59
    * Creates a new Invoice controller instance.
60
    * @param options - The options passed to the base controller.
61
    */
1✔
62
  public constructor(options: BaseControllerOptions) {
1✔
63
    super(options);
2✔
64
    this.configureLogger(this.logger);
2✔
65
    globalAsyncValidatorRegistry.register('CreateInvoiceRequest', createInvoiceRequestSpec);
2✔
66
    globalAsyncValidatorRegistry.register('UpdateInvoiceRequest', updateInvoiceRequestSpec, (req) => ({
2✔
67
      ...req.body,
4✔
68
      invoiceId: parseInt(req.params.id, 10),
4✔
69
      state: asInvoiceState((req.body as UpdateInvoiceRequest).state),
4✔
70
      byId: (req.body as UpdateInvoiceRequest).byId ?? req.token.user.id,
4✔
71
    }));
4✔
72
  }
2✔
73

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

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

163
    let take;
4✔
164
    let skip;
4✔
165
    let filters: InvoiceFilterParameters;
4✔
166
    try {
4✔
167
      const pagination = parseRequestPagination(req);
4✔
168
      filters = parseInvoiceFilterParameters(req);
4✔
169
      take = pagination.take;
4✔
170
      skip = pagination.skip;
4✔
171
    } catch (e) {
4!
172
      res.status(400).send(e.message);
×
173
      return;
×
174
    }
×
175

176
    // Handle request
4✔
177
    try {
4✔
178
      const [invoices, count] = await new InvoiceService().getPaginatedInvoices(
4✔
179
        filters, { take, skip },
4✔
180
      );
181

182
      const records = filters.returnInvoiceEntries
4✔
183
        ? InvoiceService.toArrayResponse(invoices)
×
184
        : InvoiceService.toArrayWithoutEntriesResponse(invoices);
4✔
185

186
      res.json(toResponse(records, count, { take, skip }));
4✔
187
    } catch (error) {
4!
188
      this.logger.error('Could not return all invoices:', error);
×
189
      res.status(500).json('Internal server error.');
×
190
    }
×
191
  }
4✔
192

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

211
    // Handle request
6✔
212
    try {
6✔
213
      const returnInvoiceEntries = asBoolean(req.query.returnEntries) ?? true;
6✔
214

215
      const invoices: Invoice[] = await new InvoiceService().getInvoices(
6✔
216
        { invoiceId, returnInvoiceEntries },
6✔
217
      );
218

219
      const invoice = invoices[0];
6✔
220
      if (!invoice) {
6✔
221
        res.status(404).json('Unknown invoice ID.');
1✔
222
        return;
1✔
223
      }
1✔
224
      const response = returnInvoiceEntries
5✔
225
        ? InvoiceService.asInvoiceResponse(invoice)
5!
226
        : InvoiceService.asBaseInvoiceResponse(invoice);
×
227

228
      res.json(response);
6✔
229
    } catch (error) {
6!
230
      this.logger.error('Could not return invoice:', error);
×
231
      res.status(500).json('Internal server error.');
×
232
    }
×
233
  }
6✔
234

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

251
    // handle request
1✔
252
    try {
1✔
253
      const userDefinedDefaults = await new InvoiceService().getDefaultInvoiceParams(body.forId);
1✔
254

255
      // If no byId is provided we use the token user id.
1✔
256
      const params: CreateInvoiceParams = {
1✔
257
        ...userDefinedDefaults,
1✔
258
        ...body,
1✔
259
        date: body.date ? new Date(body.date) : new Date(),
1!
260
        byId: body.byId ?? req.token.user.id,
1!
261
        description: body.description ?? '',
1!
262
      };
1✔
263

264
      const invoice: Invoice = await AppDataSource.manager.transaction(async (manager) =>
1✔
265
        new InvoiceService(manager).createInvoice(params));
1✔
266
      res.json(InvoiceService.asInvoiceResponse(invoice));
1✔
267
    } catch (error) {
1!
268
      if (error instanceof NotImplementedError) {
×
269
        res.status(501).json(error.message);
×
270
        return;
×
271
      }
×
272
      this.logger.error('Could not create invoice:', error);
×
273
      res.status(500).json('Internal server error.');
×
274
    }
×
275
  }
1✔
276

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

296
    try {
1✔
297
      // Default byId to token user id.
1✔
298
      const params: UpdateInvoiceParams = {
1✔
299
        ...body,
1✔
300
        invoiceId,
1✔
301
        state: asInvoiceState(body.state),
1✔
302
        byId: body.byId ?? req.token.user.id,
1✔
303
      };
1✔
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) {
1!
310
      this.logger.error('Could not update invoice:', error);
×
311
      res.status(500).json('Internal server error.');
×
312
    }
×
313
  }
1✔
314

315
  /**
1✔
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
   */
1✔
326
  // TODO Deleting of invoices that are not of state CREATED?
1✔
327
  public async deleteInvoice(req: RequestWithToken, res: Response): Promise<void> {
1✔
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
      }
1✔
339
      res.status(204).send();
1✔
340
    } catch (error) {
2!
341
      this.logger.error('Could not delete invoice:', error);
×
342
      res.status(500).json('Internal server error.');
×
343
    }
×
344
  }
2✔
345

346
  /**
1✔
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 {PdfUrlResponse} 200 - The pdf location information.
356
   * @return {string} 500 - Internal server error
357
   */
1✔
358
  public async getInvoicePDF(req: RequestWithToken, res: Response): Promise<void> {
1✔
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
      }
1✔
369

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

372
      res.status(200).json({ pdf: pdf.downloadName } as PdfUrlResponse);
1✔
373
    } catch (error) {
2!
374
      this.logger.error('Could get invoice PDF:', error);
×
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
  }
2✔
382

383
  /**
1✔
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
   */
1✔
394
  public async deleteInvoiceUser(req: RequestWithToken, res: Response): Promise<void> {
1✔
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
      }
1✔
405

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

414
  /**
1✔
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
   */
1✔
427
  public async getSingleInvoiceUser(req: RequestWithToken, res: Response): Promise<void> {
1✔
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
      }
1✔
438

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

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

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

457
  /**
1✔
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
   */
1✔
470
  public async updateInvoiceUser(req: RequestWithToken, res: Response): Promise<void> {
1✔
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
      }
1✔
482

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

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

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

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

502
  /**
1✔
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
   */
1✔
514
  public async getEligibleTransactions(req: RequestWithToken, res: Response): Promise<void> {
1✔
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;
×
519
    try {
×
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);
×
525
      return;
×
526
    }
×
527

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

541

542
  /**
1✔
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
   */
1✔
551
  public async getDriftedInvoices(req: RequestWithToken, res: Response): Promise<void> {
1✔
552
    this.logger.trace('Get drifted invoices by user', req.token.user);
×
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);
×
558
      res.status(500).json('Internal server error.');
×
559
    }
×
560
  }
×
561

562
  /**
1✔
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
   */
1✔
569
  static async getRelation(req: RequestWithToken): Promise<string> {
1✔
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';
9✔
573
    return 'all';
6✔
574
  }
6✔
575
}
1✔
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