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

GEWIS / sudosos-backend / 21253091724

22 Jan 2026 02:49PM UTC coverage: 89.623% (+0.01%) from 89.612%
21253091724

push

github

JustSamuel
chore: remove console error in test

1696 of 2043 branches covered (83.02%)

Branch coverage included in aggregate %.

8728 of 9588 relevant lines covered (91.03%)

1009.57 hits per line

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

85.99
/src/controller/inactive-administrative-cost-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 module page of the inactive-administrative-cost-controller
23
 * @module internal/controllers
24
 */
25

26
import BaseController, { BaseControllerOptions } from './base-controller';
2✔
27
import log4js, { Logger } from 'log4js';
2✔
28
import { Response } from 'express';
29
import Policy from './policy';
30
import { RequestWithToken } from '../middleware/token-middleware';
31
import { parseRequestPagination } from '../helpers/pagination';
2✔
32
import InactiveAdministrativeCostService, { parseInactiveAdministrativeCostFilterParameters, InactiveAdministrativeCostFilterParameters } from '../service/inactive-administrative-cost-service';
2✔
33
import { PaginatedInactiveAdministrativeCostResponse } from './response/inactive-administrative-cost-response';
34
import { isFail } from '../helpers/specification-validation';
2✔
35
import {
36
  CreateInactiveAdministrativeCostRequest,
37
  HandoutInactiveAdministrativeCostsRequest,
38
} from './request/inactive-administrative-cost-request';
39
import { NotImplementedError } from '../errors';
2✔
40
import verifyValidUserId from './request/validators/inactive-administrative-cost-request-spec';
2✔
41
import InactiveAdministrativeCost from '../entity/transactions/inactive-administrative-cost';
42
import User from '../entity/user/user';
2✔
43
import { In } from 'typeorm';
2✔
44
import { asBoolean, asFromAndTillDate } from '../helpers/validators';
2✔
45
import { PdfError } from '../errors';
2✔
46
import { formatTitleDate } from '../helpers/pdf';
2✔
47

48

49
export default class InactiveAdministrativeCostController extends BaseController {
2✔
50
  /**
51
   * Reference to the logger instance.
52
   */
53
  private logger: Logger = log4js.getLogger('InactiveAdministrativeCostController');
2✔
54

55
  /**
56
   * Creates a new InactiveAdministrativeCost controller instance
57
   * @param options - The options passed to the base controller.
58
   */
59
  public constructor(options: BaseControllerOptions) {
60
    super(options);
2✔
61
    this.logger.level = process.env.LOG_LEVEL;
2✔
62
  }
63

64
  /**
65
     * @inheritDoc
66
     */
67
  getPolicy(): Policy {
68
    return {
2✔
69
      '/': {
70
        GET: {
71
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
6✔
72
          handler: this.getAllInactiveAdministrativeCosts.bind(this),
73
        },
74
        POST: {
75
          body: { modelName: 'CreateInactiveAdministrativeCostRequest' },
76
          policy: async (req) => this.roleManager.can(req.token.roles, 'create', 'all', 'InactiveAdministrativeCost', ['*']),
4✔
77
          handler: this.createInactiveAdministrativeCost.bind(this),
78
        },
79
      },
80
      '/:id(\\d+)': {
81
        GET: {
82
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
4✔
83
          handler: this.getSingleInactiveAdministrativeCost.bind(this),
84
        },
85
        DELETE: {
86
          policy: async (req) => this.roleManager.can(req.token.roles, 'delete', 'all', 'InactiveAdministrativeCost', ['*']),
3✔
87
          handler: this.deleteInactiveAdministrativeCost.bind(this),
88
        },
89
      },
90
      '/eligible-users': {
91
        GET: {
92
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
2✔
93
          handler: this.checkInactiveUsers.bind(this),
94
        },
95
      },
96
      '/notify': {
97
        POST: {
98
          policy: async (req) => this.roleManager.can(req.token.roles, 'notify', 'all', 'InactiveAdministrativeCost', ['*']),
3✔
99
          handler: this.notifyInactiveUsers.bind(this),
100
          body: { modelName: 'HandoutInactiveAdministrativeCostsRequest' },
101
        },
102
      },
103
      '/handout': {
104
        POST: {
105
          policy: async (req) => this.roleManager.can(req.token.roles, 'create', 'all', 'InactiveAdministrativeCost', ['*']),
3✔
106
          handler: this.handoutInactiveAdministrativeCost.bind(this),
107
          body: { modelName: 'HandoutInactiveAdministrativeCostsRequest' },
108
        },
109
      },
110
      '/report': {
111
        GET: {
112
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
6✔
113
          handler: this.getInactiveAdministrativeCostReport.bind(this),
114
        },
115
      },
116
      '/report/pdf': {
117
        GET: {
118
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'InactiveAdministrativeCost', ['*']),
7✔
119
          handler: this.getInactiveAdministrativeCostReportPdf.bind(this),
120
        },
121
      },
122
    };
123
  }
124

125
  /**
126
   * GET /inactive-administrative-costs
127
   * @summary Returns all inactive administrative costs in the system.
128
   * @operationId getAllInactiveAdministrativeCosts
129
   * @tags inactiveAdministrativeCosts - Operations of the invoices controller
130
   * @security JWT
131
   * @param {integer} fromId.query - Filter on the id of the user
132
   * @param {integer} inactiveAdministrativeCostId.query - Filter on the id of entity
133
   * @return {PaginatedInactiveAdministrativeCostResponse} 200 - All existing inactive administrative costs
134
   * @return {string} 400 - Validation Error
135
   * @return {string} 500 - Internal server error
136
   */
137
  public async getAllInactiveAdministrativeCosts(req: RequestWithToken, res: Response): Promise<void> {
138
    const { body } = req;
5✔
139
    this.logger.trace('Get all inactive administrative costs', body, 'by user', req.token.user);
5✔
140

141
    let take;
142
    let skip;
143
    let filter: InactiveAdministrativeCostFilterParameters;
144

145
    try {
5✔
146
      const pagination = parseRequestPagination(req);
5✔
147
      filter = parseInactiveAdministrativeCostFilterParameters(req);
5✔
148
      take = pagination.take;
4✔
149
      skip = pagination.skip;
4✔
150
    } catch (e) {
151
      res.status(400).send(e.message);
1✔
152
      return;
1✔
153
    }
154

155
    // Handle request
156
    try {
4✔
157
      const inactiveAdministrativeCosts: PaginatedInactiveAdministrativeCostResponse = await new InactiveAdministrativeCostService().getPaginatedInactiveAdministrativeCosts(
4✔
158
        filter, { take, skip },
159
      );
160
      res.json(inactiveAdministrativeCosts);
4✔
161
    } catch (error) {
162
      this.logger.error('Could not return all inactive administrative costs', error);
×
163
      res.status(500).json('Internal server error.');
×
164
    }
165
  }
166

167
  /**
168
   * GET /inactive-administrative-costs/{id}
169
   * @summary Returns a single inactive administrative cost entity
170
   * @operationId getInactiveAdministrativeCosts
171
   * @param {integer} id.path.required - The id of the requested inactive administrative cost
172
   * @tags inactiveAdministrativeCosts - Operations of the invoices controller
173
   * @security JWT
174
   * @return {InactiveAdministrativeCostResponse} 200 - All existing inactive administrative cost
175
   * @return {string} 404 - InactiveAdministrativeCost not found
176
   * @return {string} 500 - Internal server error
177
   */
178
  public async getSingleInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
179
    const { id } = req.params;
3✔
180
    const inactiveAdministrativeCostId = parseInt(id, 10);
3✔
181
    this.logger.trace('Get inactive administrative costs', inactiveAdministrativeCostId, 'by user', req.token.user);
3✔
182

183
    try {
3✔
184
      const inactiveAdministrativeCosts: InactiveAdministrativeCost[] = await new InactiveAdministrativeCostService().getInactiveAdministrativeCosts(
3✔
185
        { inactiveAdministrativeCostId },
186
      );
187

188
      const inactiveAdministrativeCost = inactiveAdministrativeCosts[0];
3✔
189
      if (!inactiveAdministrativeCost) {
3✔
190
        res.status(404).json('Unknown inactive administrative cost ID.');
1✔
191
        return;
1✔
192
      }
193

194
      res.json(inactiveAdministrativeCost.toResponse());
2✔
195
    } catch (error) {
196
      this.logger.error('Could not return inactive administrative cost', error);
×
197
      res.status(500).json('Internal server error.');
×
198
    }
199

200
  }
201

202
  /**
203
   * POST /inactive-administrative-costs
204
   * @summary Adds and inactive administrative cost to the system.
205
   * @operationId createInactiveAdministrativeCosts
206
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
207
   * @security JWT
208
   * @param {CreateInactiveAdministrativeCostRequest} request.body.required -
209
   * The inactive administrative cost which should be created
210
   * @return {InactiveAdministrativeCostResponse} 200 - The created inactive administrative cost entity
211
   * @return {string} 400 - Validation error
212
   * @return {string} 500 - Internal server error
213
   */
214
  public async createInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
215
    const body   = req.body as CreateInactiveAdministrativeCostRequest;
3✔
216
    this.logger.trace('Create InactiveAdministrativeCosts', body, 'by user', req.token.user);
3✔
217

218
    // handle request
219
    try {
3✔
220
      const validation = await verifyValidUserId(body);
3✔
221
      if (isFail(validation)) {
3✔
222
        res.status(400).json(validation.fail.value);
2✔
223
        return;
2✔
224
      }
225

226
      const inactiveAdministrativeCost = await new InactiveAdministrativeCostService().createInactiveAdministrativeCost(body);
1✔
227
      res.json(inactiveAdministrativeCost.toResponse());
1✔
228
    } catch (error) {
229
      if (error instanceof NotImplementedError) {
×
230
        res.status(501).json(error.message);
×
231
        return;
×
232
      }
233
      this.logger.error('Could not create inactive administrative cost:', error);
×
234
      res.status(500).json('Internal server error.');
×
235
    }
236
  }
237

238
  /**
239
   * DELETE /inactive-administrative-costs/{id}
240
   * @summary Deletes an inactive administrative cost.
241
   * @operationId deleteInactiveAdministrativeCost
242
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
243
   * @security JWT
244
   * @param {integer} id.path.required - The id of the inactive administrative cost which should be deleted.
245
   * @return {string} 404 - Invoice not found
246
   * @return 204 - Deletion success
247
   * @return {string} 500 - Internal server error
248
   */
249
  public async deleteInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
250
    const { id } = req.params;
2✔
251
    const inactiveAdministrativeCostId = parseInt(id, 10);
2✔
252
    this.logger.trace('Delete inactive administrative costs', inactiveAdministrativeCostId, 'by user', req.token.user);
2✔
253

254
    try {
2✔
255
      // Check if entity exists before attempting deletion
256
      const existingCost = await new InactiveAdministrativeCostService().getInactiveAdministrativeCosts({ inactiveAdministrativeCostId });
2✔
257
      if (!existingCost || existingCost.length === 0) {
2✔
258
        res.status(404).json('InactiveAdministrativeCost not found.');
1✔
259
        return;
1✔
260
      }
261

262
      await new InactiveAdministrativeCostService().deleteInactiveAdministrativeCost(inactiveAdministrativeCostId);
1✔
263
      res.status(204).send();
1✔
264
    } catch (error) {
265
      this.logger.error('Could not delete InactiveAdministrativeCost:', error);
×
266
      res.status(500).json('Internal server error.');
×
267
    }
268
  }
269

270
  /**
271
   * GET /inactive-administrative-costs/eligible-users
272
   * @summary Find all users who are eligible for notification or creation of inactive administrative cost
273
   * @operationId getInactiveAdministrativeCostsEligibleUsers
274
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
275
   * @security JWT
276
   * @param {boolean} notification.query - Whether to check for notification or for fine.
277
   * @return {Array<UserToInactiveAdministrativeCostResponse>} 200 - List of eligible users
278
   * @return {string} 500 - Internal server error
279
   */
280
  public async checkInactiveUsers(req: RequestWithToken, res: Response): Promise<void> {
281
    const { body } = req;
1✔
282
    this.logger.trace('Check Inactive Users', body, 'by user', req.token.user);
1✔
283

284
    try {
1✔
285
      const notification = asBoolean(req.query.notification);
1✔
286

287
      const usersResponses = await new InactiveAdministrativeCostService().checkInactiveUsers({ notification });
1✔
288

289
      res.json(usersResponses);
1✔
290
    } catch (error) {
291
      this.logger.error('Could not check inactive users:', error);
×
292
      res.status(500).json('Internal server error.');
×
293
    }
294
  }
295

296
  /**
297
   * POST /inactive-administrative-costs/notify
298
   * @summary Notify all users which will pay administrative costs within a year
299
   * @operationId notifyInactiveAdministrativeCostsUsers
300
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
301
   * @security JWT
302
   * @param {HandoutInactiveAdministrativeCostsRequest} request.body.required -
303
   * The users that should be notified
304
   * @return 204 - Success
305
   * @return {string} 400 - Validation error
306
   * @return {string} 500 - Internal server error
307
   */
308
  public async notifyInactiveUsers(req: RequestWithToken, res: Response): Promise<void> {
309
    const body = req.body as HandoutInactiveAdministrativeCostsRequest;
2✔
310
    this.logger.trace('Notify Inactive Users', body, 'by user', req.token.user);
2✔
311

312
    try {
2✔
313
      if (!Array.isArray(body.userIds)) throw new Error('userIds is not an Array.');
2!
314
      
315
      const users = await User.find({ where: { id: In(body.userIds) } });
2✔
316
      if (users.length !== body.userIds.length) throw new Error('userIds is not a valid array of user IDs');
2✔
317
    } catch (error) {
318
      res.status(400).json(error.message);
1✔
319
      return ;
1✔
320
    }
321

322
    try {
1✔
323
      await new InactiveAdministrativeCostService().sendInactiveNotification(body);
1✔
324
      res.status(204).send();
1✔
325
    } catch (error) {
326
      this.logger.error('Could not check inactive users:', error);
×
327
      res.status(500).json('Internal server error.');
×
328
    }
329
  }
330

331
  /**
332
   * POST /inactive-administrative-costs/handout
333
   * @summary Handout inactive administrative costs to all users who are eligible.
334
   * @operationId handoutInactiveAdministrativeCostsUsers
335
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
336
   * @security JWT
337
   * @param {HandoutInactiveAdministrativeCostsRequest} request.body.required -
338
   * The users that should be fined
339
   * @return 204 - Success
340
   * @return {string} 400 - Validation error
341
   * @return {string} 500 - Internal server error
342
   */
343
  public async handoutInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
344
    const body = req.body as HandoutInactiveAdministrativeCostsRequest;
2✔
345
    this.logger.trace('Handout InactiveAdministrativeCosts', body, 'by user', req.token.user);
2✔
346

347
    try {
2✔
348
      if (!Array.isArray(body.userIds)) throw new Error('userIds is not an Array.');
2!
349

350
      const users = await User.find({ where: { id: In(body.userIds) } });
2✔
351
      if (users.length !== body.userIds.length) throw new Error('userIds is not a valid array of user IDs');
2✔
352
    } catch (error) {
353
      res.status(400).json(error.message);
1✔
354
      return ;
1✔
355
    }
356

357
    try {
1✔
358
      const inactiveAdministrativeCosts = await new InactiveAdministrativeCostService().handOutInactiveAdministrativeCost(body);
1✔
359
      const response = InactiveAdministrativeCostService.toArrayResponse(inactiveAdministrativeCosts);
1✔
360

361
      res.status(200).send(response);
1✔
362
    } catch (error) {
363
      this.logger.error('Could not check inactive users:', error);
×
364
      res.status(500).json('Internal server error.');
×
365
    }
366
  }
367

368
  /**
369
   * GET /inactive-administrative-costs/report
370
   * @summary Get a report of all inactive administrative costs
371
   * @operationId getInactiveAdministrativeCostReport
372
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
373
   * @security JWT
374
   * @param {string} fromDate.query - The start date of the report, inclusive
375
   * @param {string} toDate.query - The end date of the report, exclusive
376
   * @return {InactiveAdministrativeCostReportResponse} 200 - The requested report
377
   * @return {string} 400 - Validation error
378
   * @return {string} 500 - Internal server error
379
   */
380
  public async getInactiveAdministrativeCostReport(req: RequestWithToken, res: Response): Promise<void> {
381
    this.logger.trace('Get inactive administrative cost report by user', req.token.user);
5✔
382

383
    let fromDate, toDate;
384
    try {
5✔
385
      const filters = asFromAndTillDate(req.query.fromDate, req.query.toDate);
5✔
386
      fromDate = filters.fromDate;
2✔
387
      toDate = filters.tillDate;
2✔
388
    } catch (e) {
389
      res.status(400).json(e.message);
3✔
390
      return;
3✔
391
    }
392

393
    try {
2✔
394
      const report = await new InactiveAdministrativeCostService().getInactiveAdministrativeCostReport(fromDate, toDate);
2✔
395
      res.json(report.toResponse());
2✔
396
    } catch (error) {
397
      this.logger.error('Could not get inactive administrative cost report:', error);
×
398
      res.status(500).json('Internal server error.');
×
399
    }
400
  }
401

402
  /**
403
   * GET /inactive-administrative-costs/report/pdf
404
   * @summary Get a report of all inactive administrative costs in pdf format
405
   * @operationId getInactiveAdministrativeCostReportPdf
406
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
407
   * @security JWT
408
   * @param {string} fromDate.query.required - The start date of the report, inclusive
409
   * @param {string} toDate.query.required - The end date of the report, exclusive
410
   * @returns {string} 200 - The requested report - application/pdf
411
   * @return {string} 400 - Validation error
412
   * @return {string} 502 - PDF Generator service failed
413
   * @return {string} 500 - Internal server error
414
   */
415
  public async getInactiveAdministrativeCostReportPdf(req: RequestWithToken, res: Response): Promise<void> {
416
    this.logger.trace('Get inactive administrative cost report PDF by user', req.token.user);
6✔
417

418
    let fromDate, toDate;
419
    try {
6✔
420
      const filters = asFromAndTillDate(req.query.fromDate, req.query.toDate);
6✔
421
      fromDate = filters.fromDate;
3✔
422
      toDate = filters.tillDate;
3✔
423
    } catch (e) {
424
      res.status(400).json(e.message);
3✔
425
      return;
3✔
426
    }
427

428
    try {
3✔
429
      const report = await new InactiveAdministrativeCostService().getInactiveAdministrativeCostReport(fromDate, toDate);
3✔
430

431
      const pdf = await report.createPdf();
3✔
432
      const from = formatTitleDate(fromDate);
1✔
433
      const to = formatTitleDate(toDate);
1✔
434
      const fileName = `inactive-cost-report-${from}-${to}.pdf`;
1✔
435

436
      res.setHeader('Content-Type', 'application/pdf');
1✔
437
      res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
1✔
438
      res.status(200).send(pdf);
1✔
439
    } catch (error) {
440
      this.logger.error('Could not get inactive administrative cost report PDF:', error);
2✔
441
      if (error instanceof PdfError) {
2✔
442
        res.status(502).json('PDF Generator service failed.');
1✔
443
        return;
1✔
444
      }
445
      res.status(500).json('Internal server error.');
1✔
446
    }
447
  }
448

449

450
}
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