• 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

88.89
/src/controller/vat-group-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 vat-group-controller.
23
 *
24
 * @module catalogue/vat
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 { parseRequestPagination, toResponse } from '../helpers/pagination';
33
import VatGroupService, {
34
  canSetVatGroupToDeleted,
35
  parseGetVatCalculationValuesParams,
36
  parseGetVatGroupsFilters,
37
} from '../service/vat-group-service';
38
import { UpdateVatGroupRequest, VatGroupRequest } from './request/vat-group-request';
39

40
function verifyUpdateVatGroup(vr: UpdateVatGroupRequest): boolean {
11✔
41
  return vr.name !== ''
11✔
42
    && typeof vr.deleted === 'boolean';
9✔
43
}
11✔
44

45
function verifyVatGroup(vr: VatGroupRequest): boolean {
6✔
46
  return verifyUpdateVatGroup(vr)
6✔
47
    && typeof vr.percentage === 'number'
4✔
48
    && vr.percentage >= 0;
4✔
49
}
6✔
50

51
/**
1✔
52
 * Controller for managing all routes related to the `vat group` entity.
53
 */
1✔
54
export default class VatGroupController extends BaseController {
1✔
55
  private logger: Logger = log4js.getLogger('VatGroupController');
1✔
56

57
  /**
1✔
58
   * Creates a new VAT Group controller instance.
59
   * @param options - The options passed to the base controller.
60
   */
1✔
61
  public constructor(options: BaseControllerOptions) {
1✔
62
    super(options);
2✔
63
    this.configureLogger(this.logger);
2✔
64
  }
2✔
65

66
  /**
1✔
67
   * @inheritDoc
68
   */
1✔
69
  public getPolicy(): Policy {
1✔
70
    return {
2✔
71
      '/': {
2✔
72
        GET: {
2✔
73
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'VatGroup', ['*']),
2✔
74
          handler: this.getAllVatGroups.bind(this),
2✔
75
        },
2✔
76
        POST: {
2✔
77
          policy: async (req) => this.roleManager.can(req.token.roles, 'create', 'all', 'VatGroup', ['*']),
2✔
78
          handler: this.createVatGroup.bind(this),
2✔
79
          body: { modelName: 'VatGroupRequest' },
2✔
80
        },
2✔
81
      },
2✔
82
      '/:id(\\d+)': {
2✔
83
        GET: {
2✔
84
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'VatGroup', ['*']),
2✔
85
          handler: this.getSingleVatGroup.bind(this),
2✔
86
        },
2✔
87
        PATCH: {
2✔
88
          policy: async (req) => this.roleManager.can(req.token.roles, 'update', 'all', 'VatGroup', ['*']),
2✔
89
          handler: this.updateVatGroup.bind(this),
2✔
90
        },
2✔
91
      },
2✔
92
      '/declaration': {
2✔
93
        GET: {
2✔
94
          policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'VatGroup', ['*']),
2✔
95
          handler: this.getVatDeclarationAmounts.bind(this),
2✔
96
        },
2✔
97
      },
2✔
98
    };
2✔
99
  }
2✔
100

101
  /**
1✔
102
   * GET /vatgroups
103
   * @summary Get a list of all VAT groups
104
   * @operationId getAllVatGroups
105
   * @tags vatGroups - Operations of the VAT groups controller
106
   * @security JWT
107
   * @param {integer} vatGroupId.query - ID of the VAT group
108
   * @param {string} name.query - Name of the VAT group
109
   * @param {number} percentage.query - VAT percentage
110
   * @param {boolean} deleted.query - Whether the VAT groups should be hidden if zero
111
   * @param {integer} take.query - How many transactions the endpoint should return
112
   * @param {integer} skip.query - How many transactions should be skipped (for pagination)
113
   * @return {PaginatedVatGroupResponse} 200 - A list of all VAT groups
114
   */
1✔
115
  public async getAllVatGroups(req: RequestWithToken, res: Response): Promise<void> {
1✔
116
    this.logger.trace('Get all VAT groups by user', req.token.user);
5✔
117

118
    // Parse the filters given in the query parameters. If there are any issues,
5✔
119
    // the parse method will throw an exception. We will then return a 400 error.
5✔
120
    let filters;
5✔
121
    let take;
5✔
122
    let skip;
5✔
123
    try {
5✔
124
      filters = parseGetVatGroupsFilters(req);
5✔
125
      const pagination = parseRequestPagination(req);
5✔
126
      take = pagination.take;
5✔
127
      skip = pagination.skip;
5✔
128
    } catch (e) {
5!
129
      res.status(400).json(e.message);
×
130
      return;
×
131
    }
×
132

133
    try {
5✔
134
      const [vatGroups, count] = await VatGroupService.getVatGroups(filters, { take, skip });
5✔
135
      res.status(200).json(toResponse(vatGroups.map((v) => VatGroupService.toResponse(v)), count, { take, skip }));
5✔
136
    } catch (e) {
5!
137
      res.status(500).send('Internal server error.');
×
138
      this.logger.error(e);
×
139
    }
×
140
  }
5✔
141

142
  /**
1✔
143
   * GET /vatgroups/{id}
144
   * @summary Returns the requested VAT group
145
   * @operationId getSingleVatGroup
146
   * @tags vatGroups - Operations of the VAT groups controller
147
   * @security JWT
148
   * @param {integer} id.path.required - The ID of the VAT group which should be returned
149
   * @return {VatGroupResponse} 200 - The requested VAT group entity
150
   * @return {string} 404 - Not found error
151
   * @return {string} 500 - Internal server error
152
   */
1✔
153
  public async getSingleVatGroup(req: RequestWithToken, res: Response): Promise<void> {
1✔
154
    const { id } = req.params;
2✔
155
    this.logger.trace('Get single VAT group', id, ' by user', req.token.user);
2✔
156

157
    try {
2✔
158
      const [vatGroups] = await VatGroupService.getVatGroups({
2✔
159
        vatGroupId: Number.parseInt(id, 10),
2✔
160
      });
2✔
161
      if (vatGroups.length > 0) {
2✔
162
        res.json(VatGroupService.toResponse(vatGroups[0]));
1✔
163
      } else {
1✔
164
        res.status(404).json('VAT group not found.');
1✔
165
      }
1✔
166
    } catch (error) {
2!
167
      this.logger.error('Could not return VAT group:', error);
×
168
      res.status(500).json('Internal server error.');
×
169
    }
×
170
  }
2✔
171

172
  /**
1✔
173
   * POST /vatgroups
174
   * @summary Create a new VAT group
175
   * @operationId createVatGroup
176
   * @tags vatGroups - Operations of the VAT group controller
177
   * @param {VatGroupRequest} request.body.required - The VAT group which should be created
178
   * @security JWT
179
   * @return {VatGroupResponse} 200 - The created VAT group entity
180
   * @return {string} 400 - Validation error
181
   * @return {string} 500 - Internal server error
182
   */
1✔
183
  public async createVatGroup(req: RequestWithToken, res: Response): Promise<void> {
1✔
184
    const body = req.body as VatGroupRequest;
6✔
185
    this.logger.trace('Create VAT group', body, 'by user', req.token.user);
6✔
186

187
    const validBody = verifyVatGroup(body);
6✔
188
    if (!validBody) {
6✔
189
      res.status(400).json('Invalid VAT group.');
3✔
190
      return;
3✔
191
    }
3✔
192

193
    if (body.deleted) {
6✔
194
      res.status(400).json('Don\'t already soft delete a new VAT group, that\'s stupid.');
1✔
195
      return;
1✔
196
    }
1✔
197

198
    try {
2✔
199
      const vatGroup = await VatGroupService.createVatGroup(body);
2✔
200
      res.json(VatGroupService.toResponse(vatGroup));
2✔
201
    } catch (e) {
6!
202
      res.status(500).send('Internal server error.');
×
203
      this.logger.error(e);
×
204
    }
×
205
  }
6✔
206

207
  /**
1✔
208
   * PATCH /vatgroups/{id}
209
   * @summary Create a new VAT group
210
   * @operationId updateVatGroup
211
   * @tags vatGroups - Operations of the VAT group controller
212
   * @param {integer} id.path.required - The ID of the VAT group which should be updated
213
   * @param {UpdateVatGroupRequest} request.body.required - The VAT group information
214
   * @security JWT
215
   * @return {VatGroupResponse} 200 - The created VAT group entity
216
   * @return {string} 400 - Validation error
217
   * @return {string} 404 - Not found error
218
   * @return {string} 500 - Internal server error
219
   */
1✔
220
  public async updateVatGroup(req: RequestWithToken, res: Response): Promise<void> {
1✔
221
    const body = req.body as UpdateVatGroupRequest;
5✔
222
    const id = Number.parseInt(req.params.id, 10);
5✔
223
    this.logger.trace('Update VAT group', id, 'by user', req.token.user);
5✔
224

225
    const validBody = verifyUpdateVatGroup(body);
5✔
226
    if (!validBody) {
5✔
227
      res.status(400).json('Invalid VAT group.');
1✔
228
      return;
1✔
229
    }
1✔
230

231
    try {
4✔
232
      const [vatGroups] = await VatGroupService.getVatGroups({ vatGroupId: id });
4✔
233
      if (vatGroups.length === 0) {
5✔
234
        res.status(404).json('VAT group not found.');
1✔
235
        return;
1✔
236
      }
1✔
237

238
      if (body.deleted) {
5✔
239
        const canDelete = await canSetVatGroupToDeleted(id);
2✔
240
        if (!canDelete) {
2✔
241
          res.status(400).json('Cannot set "deleted" to true, because the VAT group is still used by one or more products.');
1✔
242
          return;
1✔
243
        }
1✔
244
      }
2✔
245

246
      const vatGroup = await VatGroupService.updateVatGroup(id, body);
2✔
247
      res.status(200).json(VatGroupService.toResponse(vatGroup));
2✔
248
    } catch (error) {
5!
249
      this.logger.error('Could not update VAT group:', error);
×
250
      res.status(500).json('Internal server error.');
×
251
    }
×
252
  }
5✔
253

254
  /**
1✔
255
   * GET /vatgroups/declaration
256
   * @summary Get the VAT collections needed for VAT declarations
257
   * @operationId getVatDeclarationAmounts
258
   * @tags vatGroups - Operations of the VAT groups controller
259
   * @security JWT
260
   * @param {number} year.query.required - Calendar year for VAT declarations
261
   * @param {string} period.query.required - Period for VAT declarations
262
   * @return {PaginatedVatGroupResponse} 200 - A list of all VAT groups with declarations
263
   */
1✔
264
  public async getVatDeclarationAmounts(req: RequestWithToken, res: Response): Promise<void> {
1✔
265
    let params;
5✔
266
    try {
5✔
267
      params = parseGetVatCalculationValuesParams(req);
5✔
268
    } catch (e) {
5✔
269
      res.status(400).json(e.message);
2✔
270
      return;
2✔
271
    }
2✔
272

273
    if (params.year === undefined || params.period === undefined) {
5✔
274
      res.status(400).send('Missing year or period.');
2✔
275
      return;
2✔
276
    }
2✔
277

278
    try {
1✔
279
      const vatGroups = await VatGroupService.calculateVatDeclaration(params);
1✔
280
      res.status(200).json(vatGroups);
1✔
281
    } catch (e) {
5!
282
      res.status(500).send('Internal server error.');
×
283
      this.logger.error(e);
×
284
    }
×
285
  }
5✔
286
}
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