• 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

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

26
import BaseController, { BaseControllerOptions } from './base-controller';
27
import log4js, { Logger } from 'log4js';
28
import { Response } from 'express';
29
import Policy from './policy';
30
import { RequestWithToken } from '../middleware/token-middleware';
31
import { parseRequestPagination, toResponse } from '../helpers/pagination';
32
import InactiveAdministrativeCostService, { parseInactiveAdministrativeCostFilterParameters, InactiveAdministrativeCostFilterParameters } from '../service/inactive-administrative-cost-service';
33
import {
34
  CreateInactiveAdministrativeCostRequest,
35
  HandoutInactiveAdministrativeCostsRequest,
36
} from './request/inactive-administrative-cost-request';
37
import { NotImplementedError } from '../errors';
38
import {
39
  createInactiveAdministrativeCostRequestSpec,
40
  handoutInactiveAdministrativeCostsRequestSpec,
41
} from './request/validators/inactive-administrative-cost-request-spec';
42
import { globalAsyncValidatorRegistry } from '../middleware/async-validator-registry';
43
import InactiveAdministrativeCost from '../entity/transactions/inactive-administrative-cost';
44
import { asBoolean, asFromAndTillDate } from '../helpers/validators';
45
import { PdfError } from '../errors';
46
import { formatTitleDate } from '../helpers/pdf';
47

48

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

55
  /**
1✔
56
   * Creates a new InactiveAdministrativeCost controller instance
57
   * @param options - The options passed to the base controller.
58
   */
1✔
59
  public constructor(options: BaseControllerOptions) {
1✔
60
    super(options);
2✔
61
    this.configureLogger(this.logger);
2✔
62
    globalAsyncValidatorRegistry.register('CreateInactiveAdministrativeCostRequest', createInactiveAdministrativeCostRequestSpec);
2✔
63
    globalAsyncValidatorRegistry.register('HandoutInactiveAdministrativeCostsRequest', handoutInactiveAdministrativeCostsRequestSpec);
2✔
64
  }
2✔
65

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

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

143
    let take;
5✔
144
    let skip;
5✔
145
    let filter: InactiveAdministrativeCostFilterParameters;
5✔
146

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

157
    // Handle request
4✔
158
    try {
4✔
159
      const [costs, count] = await new InactiveAdministrativeCostService().getPaginatedInactiveAdministrativeCosts(
4✔
160
        filter, { take, skip },
4✔
161
      );
162
      const records = InactiveAdministrativeCostService.toArrayResponse(costs);
4✔
163
      res.json(toResponse(records, count, { take, skip }));
4✔
164
    } catch (error) {
5!
165
      this.logger.error('Could not return all inactive administrative costs', error);
×
166
      res.status(500).json('Internal server error.');
×
167
    }
×
168
  }
5✔
169

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

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

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

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

203
  }
3✔
204

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

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

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

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

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

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

281
    try {
1✔
282
      const notification = asBoolean(req.query.notification);
1✔
283

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

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

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

309
    try {
1✔
310
      await new InactiveAdministrativeCostService().sendInactiveNotification(body);
1✔
311
      res.status(204).send();
1✔
312
    } catch (error) {
1!
313
      this.logger.error('Could not check inactive users:', error);
×
314
      res.status(500).json('Internal server error.');
×
315
    }
×
316
  }
1✔
317

318
  /**
1✔
319
   * POST /inactive-administrative-costs/handout
320
   * @summary Handout inactive administrative costs to all users who are eligible.
321
   * @operationId handoutInactiveAdministrativeCostsUsers
322
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
323
   * @security JWT
324
   * @param {HandoutInactiveAdministrativeCostsRequest} request.body.required -
325
   * The users that should be fined
326
   * @return {InactiveAdministrativeCostResponse[]} 200 - Success
327
   * @return {ValidationResponse} 400 - Validation error
328
   * @return {string} 500 - Internal server error
329
   */
1✔
330
  public async handoutInactiveAdministrativeCost(req: RequestWithToken, res: Response): Promise<void> {
1✔
331
    const body = req.body as HandoutInactiveAdministrativeCostsRequest;
1✔
332
    this.logger.trace('Handout InactiveAdministrativeCosts', body, 'by user', req.token.user);
1✔
333

334
    try {
1✔
335
      const inactiveAdministrativeCosts = await new InactiveAdministrativeCostService().handOutInactiveAdministrativeCost(body);
1✔
336
      const response = InactiveAdministrativeCostService.toArrayResponse(inactiveAdministrativeCosts);
1✔
337

338
      res.status(200).send(response);
1✔
339
    } catch (error) {
1!
340
      this.logger.error('Could not check inactive users:', error);
×
341
      res.status(500).json('Internal server error.');
×
342
    }
×
343
  }
1✔
344

345
  /**
1✔
346
   * GET /inactive-administrative-costs/report
347
   * @summary Get a report of all inactive administrative costs
348
   * @operationId getInactiveAdministrativeCostReport
349
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
350
   * @security JWT
351
   * @param {string} fromDate.query - The start date of the report, inclusive
352
   * @param {string} toDate.query - The end date of the report, exclusive
353
   * @return {InactiveAdministrativeCostReportResponse} 200 - The requested report
354
   * @return {string} 400 - Validation error
355
   * @return {string} 500 - Internal server error
356
   */
1✔
357
  public async getInactiveAdministrativeCostReport(req: RequestWithToken, res: Response): Promise<void> {
1✔
358
    this.logger.trace('Get inactive administrative cost report by user', req.token.user);
5✔
359

360
    let fromDate, toDate;
5✔
361
    try {
5✔
362
      const filters = asFromAndTillDate(req.query.fromDate, req.query.toDate);
5✔
363
      fromDate = filters.fromDate;
5✔
364
      toDate = filters.tillDate;
5✔
365
    } catch (e) {
5✔
366
      res.status(400).json(e.message);
3✔
367
      return;
3✔
368
    }
3✔
369

370
    try {
2✔
371
      const report = await new InactiveAdministrativeCostService().getInactiveAdministrativeCostReport(fromDate, toDate);
2✔
372
      res.json(report.toResponse());
2✔
373
    } catch (error) {
5!
374
      this.logger.error('Could not get inactive administrative cost report:', error);
×
375
      res.status(500).json('Internal server error.');
×
376
    }
×
377
  }
5✔
378

379
  /**
1✔
380
   * GET /inactive-administrative-costs/report/pdf
381
   * @summary Get a report of all inactive administrative costs in pdf format
382
   * @operationId getInactiveAdministrativeCostReportPdf
383
   * @tags inactiveAdministrativeCosts - Operations of the inactive administrative cost controller
384
   * @security JWT
385
   * @param {string} fromDate.query.required - The start date of the report, inclusive
386
   * @param {string} toDate.query.required - The end date of the report, exclusive
387
   * @returns {string} 200 - The requested report - application/pdf
388
   * @return {string} 400 - Validation error
389
   * @return {string} 502 - PDF Generator service failed
390
   * @return {string} 500 - Internal server error
391
   */
1✔
392
  public async getInactiveAdministrativeCostReportPdf(req: RequestWithToken, res: Response): Promise<void> {
1✔
393
    this.logger.trace('Get inactive administrative cost report PDF by user', req.token.user);
6✔
394

395
    let fromDate, toDate;
6✔
396
    try {
6✔
397
      const filters = asFromAndTillDate(req.query.fromDate, req.query.toDate);
6✔
398
      fromDate = filters.fromDate;
6✔
399
      toDate = filters.tillDate;
6✔
400
    } catch (e) {
6✔
401
      res.status(400).json(e.message);
3✔
402
      return;
3✔
403
    }
3✔
404

405
    try {
3✔
406
      const report = await new InactiveAdministrativeCostService().getInactiveAdministrativeCostReport(fromDate, toDate);
3✔
407

408
      const pdf = await report.createPdf();
3✔
409
      const from = formatTitleDate(fromDate);
1✔
410
      const to = formatTitleDate(toDate);
1✔
411
      const fileName = `inactive-cost-report-${from}-${to}.pdf`;
1✔
412

413
      res.setHeader('Content-Type', 'application/pdf');
1✔
414
      res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
1✔
415
      res.status(200).send(pdf);
1✔
416
    } catch (error) {
6✔
417
      this.logger.error('Could not get inactive administrative cost report PDF:', error);
2✔
418
      if (error instanceof PdfError) {
2✔
419
        res.status(502).json('PDF Generator service failed.');
1✔
420
        return;
1✔
421
      }
1✔
422
      res.status(500).json('Internal server error.');
1✔
423
    }
1✔
424
  }
6✔
425

426

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