diff --git a/src/Router.ts b/src/Router.ts index 6ab9c74..47978b7 100644 --- a/src/Router.ts +++ b/src/Router.ts @@ -299,6 +299,8 @@ router.use( routerGroup((r: Router) => { r.post("/", SoloAdminController.createSolo); r.patch("/", SoloAdminController.updateSolo); + + r.post("/extend", SoloAdminController.extendSolo); }) ); diff --git a/src/controllers/solo/SoloAdminController.ts b/src/controllers/solo/SoloAdminController.ts index 189bfc2..24bcbe3 100644 --- a/src/controllers/solo/SoloAdminController.ts +++ b/src/controllers/solo/SoloAdminController.ts @@ -5,6 +5,7 @@ import dayjs from "dayjs"; import { HttpStatusCode } from "axios"; import { User } from "../../models/User"; import { EndorsementGroupsBelongsToUsers } from "../../models/through/EndorsementGroupsBelongsToUsers"; +import { TrainingSession } from "../../models/TrainingSession"; type CreateSoloRequestBody = { solo_duration: string; @@ -15,6 +16,12 @@ type CreateSoloRequestBody = { type UpdateSoloRequestBody = Omit; +/** + * Create a new Solo + * @param request + * @param response + * @param next + */ async function createSolo(request: Request, response: Response, next: NextFunction) { try { const user: User = request.body.user; @@ -45,7 +52,13 @@ async function createSolo(request: Request, response: Response, next: NextFuncti where: { id: body.trainee_id, }, - include: [User.associations.user_solo, User.associations.endorsement_groups], + include: [ + { + association: User.associations.user_solo, + include: [UserSolo.associations.solo_creator], + }, + User.associations.endorsement_groups, + ], }); response.status(HttpStatusCode.Created).send(returnUser); @@ -54,9 +67,14 @@ async function createSolo(request: Request, response: Response, next: NextFuncti } } +/** + * Updates the solo (potentially using the contingent) + * @param request + * @param response + * @param next + */ async function updateSolo(request: Request, response: Response, next: NextFunction) { try { - const user: User = request.body.user; const body = request.body as UpdateSoloRequestBody; _SoloAdminValidator.validateUpdateRequest(body); @@ -72,11 +90,97 @@ async function updateSolo(request: Request, response: Response, next: NextFuncti } const newDuration = currentSolo.solo_used + Number(body.solo_duration); - await currentSolo.update({ - solo_used: newDuration, - current_solo_end: dayjs.utc(currentSolo.current_solo_start).add(newDuration, "days").toDate(), + + // If solo_start == NULL, then the solo is still active + if (body.solo_start == null) { + await currentSolo.update({ + created_by: request.body.user.id, + solo_used: newDuration, + current_solo_end: dayjs.utc(currentSolo.current_solo_start).add(newDuration, "days").toDate(), + }); + } else { + // If solo_start != NULL, then the solo is inactive and the new days have to be calculated (newDuration, for example, isn't correct! It's start_date + Number(body.solo_duration) + // Else we'll add the entire solo duration to the length again :). + await currentSolo.update({ + created_by: request.body.user.id, + solo_used: newDuration, + current_solo_start: dayjs.utc(body.solo_start).toDate(), + current_solo_end: dayjs.utc(body.solo_start).add(Number(body.solo_duration), "days").toDate(), + }); + } + + response.sendStatus(HttpStatusCode.Ok); + } catch (e) { + next(e); + } +} + +/** + * Validates and extends solo + * @param request + * @param response + * @param next + */ +async function extendSolo(request: Request, response: Response, next: NextFunction) { + try { + const body = request.body as { trainee_id: string }; + _SoloAdminValidator.validateExtensionRequest(body); + + // Check the user has had a training in the last 20 days. + const user = await User.findOne({ + where: { + id: body.trainee_id, + }, + include: [ + { + association: User.associations.training_sessions, + include: [TrainingSession.associations.training_type], + }, + ], + }); + + if (user == null || user.training_sessions == null) { + response.sendStatus(HttpStatusCode.NotFound); + return; + } + + let cpt_planned = false; + let training_last_20_days = false; + for (const trainingSession of user.training_sessions) { + if ( + trainingSession.date != null && + trainingSession.date > dayjs.utc().subtract(20, "days").startOf("day").toDate() && + trainingSession.training_type?.type != "cpt" && + trainingSession.TrainingSessionBelongsToUsers?.passed + ) { + training_last_20_days = true; + } + + if (trainingSession.training_type?.type == "cpt") { + cpt_planned = true; + } + } + + if (!cpt_planned || !training_last_20_days) { + response.status(HttpStatusCode.BadRequest).send({ + cpt_planned: cpt_planned, + training_last_20_days: training_last_20_days, + }); + return; + } + + // Here, both cases are valid, we can extend the solo no problem! + const solo = await UserSolo.findOne({ + where: { + user_id: user.id, + }, }); + if (solo == null) { + response.sendStatus(HttpStatusCode.InternalServerError); + return; + } + response.sendStatus(HttpStatusCode.Ok); } catch (e) { next(e); @@ -86,4 +190,5 @@ async function updateSolo(request: Request, response: Response, next: NextFuncti export default { createSolo, updateSolo, + extendSolo, }; diff --git a/src/controllers/solo/_SoloAdmin.validator.ts b/src/controllers/solo/_SoloAdmin.validator.ts index 30e57c1..c87c1d5 100644 --- a/src/controllers/solo/_SoloAdmin.validator.ts +++ b/src/controllers/solo/_SoloAdmin.validator.ts @@ -54,7 +54,22 @@ function validateUpdateRequest(data: any) { } } +function validateExtensionRequest(data: any) { + const validation = ValidationHelper.validate([ + { + name: "trainee_id", + validationObject: data.trainee_id, + toValidate: [{ val: ValidationOptions.NON_NULL }, { val: ValidationOptions.NUMBER }], + }, + ]); + + if (validation.invalid) { + throw new ValidationException(validation); + } +} + export default { validateCreateRequest, validateUpdateRequest, + validateExtensionRequest, }; diff --git a/src/controllers/training-station/TrainingStationAdminController.ts b/src/controllers/training-station/TrainingStationAdminController.ts index ed63a20..d2d08cd 100644 --- a/src/controllers/training-station/TrainingStationAdminController.ts +++ b/src/controllers/training-station/TrainingStationAdminController.ts @@ -47,7 +47,7 @@ type DataHubStations = { frequency: string; abbreviation: string; description: string; -} +}; async function syncStations(request: Request, response: Response, next: NextFunction) { try { @@ -57,18 +57,18 @@ async function syncStations(request: Request, response: Response, next: NextFunc for (const station of stations) { if (station.logon.length === 0 || station.frequency.length === 0) continue; - const dbStation = await TrainingStation.findOne({where: {callsign: station.logon}}); + const dbStation = await TrainingStation.findOne({ where: { callsign: station.logon } }); if (dbStation == null) { await TrainingStation.create({ callsign: station.logon, - frequency: Number(station.frequency) + frequency: Number(station.frequency), }); continue; } await dbStation.update({ callsign: station.logon, - frequency: Number(station.frequency) + frequency: Number(station.frequency), }); } diff --git a/src/controllers/training-type/TrainingTypeAdminController.ts b/src/controllers/training-type/TrainingTypeAdminController.ts index deaf641..4862b3e 100644 --- a/src/controllers/training-type/TrainingTypeAdminController.ts +++ b/src/controllers/training-type/TrainingTypeAdminController.ts @@ -94,7 +94,7 @@ async function create(request: Request, response: Response) { const trainingType = await TrainingType.create({ name: body.name, type: body.type, - log_template_id: !isNaN(log_template_id) || log_template_id == -1 ? null : log_template_id, + log_template_id: isNaN(log_template_id) || log_template_id == -1 ? null : log_template_id, }); response.status(HttpStatusCode.Created).send({ id: trainingType.id }); diff --git a/src/controllers/user/UserInformationAdminController.ts b/src/controllers/user/UserInformationAdminController.ts index 2453428..e6c715a 100644 --- a/src/controllers/user/UserInformationAdminController.ts +++ b/src/controllers/user/UserInformationAdminController.ts @@ -2,6 +2,7 @@ import { Request, Response } from "express"; import { User } from "../../models/User"; import PermissionHelper from "../../utility/helper/PermissionHelper"; import { UserSolo } from "../../models/UserSolo"; +import { EndorsementGroup } from "../../models/EndorsementGroup"; /** * Returns the user data for a user with id request.query.user_id diff --git a/src/models/TrainingSession.ts b/src/models/TrainingSession.ts index 9c8ab2d..284942b 100644 --- a/src/models/TrainingSession.ts +++ b/src/models/TrainingSession.ts @@ -40,6 +40,7 @@ export class TrainingSession extends Model, Inf declare training_station?: NonAttribute; declare course?: NonAttribute; declare training_session_belongs_to_users?: NonAttribute; + declare TrainingSessionBelongsToUsers?: NonAttribute; declare static associations: { users: Association;