Add support for training courses

This commit is contained in:
Matteo Gheza 2024-01-09 00:37:18 +01:00
parent 7be73d06ba
commit 37b7951b64
18 changed files with 443 additions and 27 deletions

View File

@ -44,6 +44,56 @@ class DocumentsController extends Controller
return response()->file(storage_path('app/public/' . $document->file_path));
}
function addTrainingCourse(Request $request)
{
$request->validate([
'user' => ['required', 'integer', 'exists:users,id'],
'type' => ['required', 'integer', 'exists:training_course_types,id'],
'date' => ['required', 'date'],
'doc_number' => ['required', 'string', 'max:255'],
'file' => [
File::types('application/pdf')
->max(50 * 1024)
]
]);
if($request->user()->id != $request->input('user') && !$request->user()->hasPermission("users-add-training-course")) abort(401);
if($request->user()->id == $request->input('user') && !$request->user()->hasPermission("user-add-training-course")) abort(401);
$document = new Document();
$document->type = 'training_course';
$document->doc_type = $request->input('type');
$document->doc_number = $request->input('doc_number');
$document->user = $request->input('user');
$document->added_by = auth()->user()->id;
if($request->hasFile('file')) {
$fileName = time() . '_' . $request->file->getClientOriginalName();
$filePath = $request->file('file')->storeAs('training_courses', $fileName, 'public');
$documentFile = new DocumentFile();
$documentFile->uuid = Str::uuid()->toString();
$documentFile->type = 'training_course';
$documentFile->file_path = $filePath;
$documentFile->uploadedBy()->associate(auth()->user());
$documentFile->save();
$document->documentFile()->associate($documentFile);
}
$document->date = $request->input('date');
$document->save();
return response()->json([
"id" => $document->id
]);
}
function serveTrainingCourse($uuid)
{
$document = DocumentFile::where('uuid', $uuid)->firstOrFail();
return response()->file(storage_path('app/public/' . $document->file_path));
}
function addMedicalExamination(Request $request)
{
$request->validate([

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers;
use App\Models\TrainingCourseType;
use App\Models\User;
use Illuminate\Http\Request;
use App\Utils\Logger;
class TrainingCourseTypeController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
if(!$request->user()->hasPermission("users-read") && !$request->user()->hasPermission("user-read")) abort(401);
User::where('id', $request->user()->id)->update(['last_access' => now()]);
return response()->json(
TrainingCourseType::get()
);
}
/**
* Add a new TrainingCourseType.
*/
public function create(Request $request)
{
if(!$request->user()->hasPermission("users-add-training-course") && !$request->user()->hasPermission("user-add-training-course")) abort(401);
$trainingCourseType = new TrainingCourseType();
$trainingCourseType->name = $request->name;
$trainingCourseType->save();
Logger::log("Aggiunto tipo di corso di formazione ($trainingCourseType->name)");
return response()->json(
$trainingCourseType
);
}
}

View File

@ -70,6 +70,27 @@ class UserController extends Controller
$user->driving_license = $dl_tmp[0];
}
$tc_tmp = Document::where('documents.user', $user->id)
->where('documents.type', 'training_course')
->leftJoin('document_files', 'document_files.id', '=', 'documents.document_file_id')
->leftJoin('training_course_types', 'training_course_types.id', '=', 'documents.doc_type')
->select('documents.doc_number as doc_number', 'documents.date', 'document_files.uuid as doc_uuid', 'training_course_types.name as type')
->get();
if($tc_tmp->count() > 0) {
$user->training_courses = $tc_tmp;
foreach($user->training_courses as $tc) {
if(!is_null($tc->doc_uuid)) {
$tc->doc_url = URL::temporarySignedRoute(
'training_course_serve', now()->addHours(1), ['uuid' => $tc->doc_uuid]
);
unset($tc->doc_uuid);
}
}
} else {
$user->training_courses = [];
}
$me_tmp = Document::where('documents.user', $user->id)
->where('documents.type', 'medical_examination')
->leftJoin('document_files', 'document_files.id', '=', 'documents.document_file_id')

View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TrainingCourseType extends Model
{
use HasFactory;
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name'
];
}

View File

@ -13,21 +13,21 @@ return [
'roles_structure' => [
'superadmin' => [
'users' => 'c,r,u,d,i,b,h,sc,sd,ame',
'users' => 'c,r,u,d,i,b,h,sc,sd,atc,ame',
'user' => 'u,h,sc,sd',
'services' => 'c,r,u,d',
'trainings' => 'c,r,u,d',
'alerts' => 'c,r,u',
],
'admin' => [
'users' => 'c,r,u,d,i,b,h,sc,sd,ame',
'users' => 'c,r,u,d,i,b,h,sc,sd,atc,ame',
'user' => 'u,h,sc,sd',
'services' => 'c,r,u,d',
'trainings' => 'c,r,u,d',
'alerts' => 'c,r,u',
],
'chief' => [
'users' => 'r,u,sc,sd,ame',
'users' => 'r,u,sc,sd,atc,ame',
'user' => 'u',
'services' => 'c,r,u,d',
'trainings' => 'c,r,u,d',
@ -53,6 +53,7 @@ return [
'h' => 'hide',
'sc' => 'set-chief',
'sd' => 'set-driver',
'atc' => 'add-training-course',
'ame' => 'add-medical-examination'
]
];

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('training_course_types', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('training_course_types');
}
};

View File

@ -13,6 +13,7 @@ use App\Http\Controllers\ServiceController;
use App\Http\Controllers\StatsController;
use App\Http\Controllers\PlacesController;
use App\Http\Controllers\ServiceTypeController;
use App\Http\Controllers\TrainingCourseTypeController;
use App\Http\Controllers\TrainingController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
@ -50,8 +51,12 @@ Route::middleware('auth:sanctum')->group( function () {
Route::put('/users/{user}', [UserController::class, 'update']);
Route::post('/documents/driving_license', [DocumentsController::class, 'uploadDrivingLicenseScan']);
Route::post('/documents/training_course', [DocumentsController::class, 'addTrainingCourse']);
Route::post('/documents/medical_examination', [DocumentsController::class, 'addMedicalExamination']);
Route::get('/training_course_types', [TrainingCourseTypeController::class, 'index']);
Route::post('/training_course_types', [TrainingCourseTypeController::class, 'create']);
Route::get('/schedules', [ScheduleSlotsController::class, 'index']);
Route::post('/schedules', [ScheduleSlotsController::class, 'store']);
@ -91,6 +96,7 @@ Route::middleware('auth:sanctum')->group( function () {
Route::middleware('signed')->group( function () {
Route::get('/documents/driving_license/{uuid}', [DocumentsController::class, 'serveDrivingLicenseScan'])->name('driving_license_scan_serve');
Route::get('/documents/training_course/{uuid}', [DocumentsController::class, 'serveTrainingCourse'])->name('training_course_serve');
Route::get('/documents/medical_examination/{uuid}', [DocumentsController::class, 'serveMedicalExamination'])->name('medical_examination_serve');
});

View File

@ -24,8 +24,8 @@
<input formControlName="certifier" type="text" class="form-control" id="me_certifier">
</div>
<div class="col-12">
<label for="dl_scan" class="form-label">{{ 'upload_medical_examination_certificate'|translate|ftitlecase }} (<u>{{ 'optional'|translate|uppercase }}</u>, <i>.pdf</i>)</label>
<input class="form-control" type="file" id="dl_scan" (change)="onMedicalExaminationCertificateSelected($event)">
<label for="me_cert" class="form-label">{{ 'upload_medical_examination_certificate'|translate|ftitlecase }} (<u>{{ 'optional'|translate|uppercase }}</u>, <i>.pdf</i>)</label>
<input class="form-control" type="file" id="me_cert" (change)="onMedicalExaminationCertificateSelected($event)">
</div>
</form>
</div>

View File

@ -0,0 +1,53 @@
<div class="modal-header">
<h4 class="modal-title pull-left" translate>training_course_modal.title</h4>
<button
type="button"
class="btn-close close pull-right"
[attr.aria-label]="'close' | translate | ftitlecase"
(click)="bsModalRef.hide()"
>
<span aria-hidden="true" class="visually-hidden">&times;</span>
</button>
</div>
<div class="modal-body">
<form [formGroup]="form" (submit)="formSubmit()" class="row g-3">
<div class="col-12">
<label for="tc_type">{{ 'type'|translate|ftitlecase }}</label>
<div class="input-group has-validation">
<select formControlName="type" class="form-control mr-2" id="tc_type">
<option selected disabled translate>select_type</option>
<option *ngFor="let type of types" value="{{ type.id }}">{{ type.name }}</option>
</select>
<button class="btn btn-outline-secondary" type="button" tabindex="-1" (click)="addingType = true">
{{ 'add'|translate|ftitlecase }}
</button>
</div>
</div>
<div class="input-group mb-2 mt-2" *ngIf="addingType">
<input type="text" class="form-control" [placeholder]="'type'|translate|titlecase" [(ngModel)]="newType"
[ngModelOptions]="{standalone: true}">
<button class="btn btn-secondary" type="button" (click)="addType()">{{ 'submit'|translate|ftitlecase }}</button>
</div>
<div class="col-12">
<label for="tc_date" class="form-label">{{ 'date'|translate|ftitlecase }}</label>
<input formControlName="date" type="text" class="form-control" [placeholder]="'press_to_select_a_date'|translate|ftitlecase" id="tc_date" bsDatepicker [bsConfig]="{ adaptivePosition: true, dateInputFormat: 'DD/MM/YYYY' }" [maxDate]="dateMaxDate">
</div>
<div class="col-12">
<label for="tc_doc_number">{{ 'training_course_modal.doc_number'|translate|ftitlecase }}</label>
<input formControlName="doc_number" type="text" class="form-control" id="tc_doc_number">
</div>
<div class="col-12">
<label for="tc_scan" class="form-label">{{ 'upload_training_course_doc'|translate|ftitlecase }} (<u>{{ 'optional'|translate|uppercase }}</u>, <i>.pdf</i>)</label>
<input class="form-control" type="file" id="tc_scan" (change)="onTrainingCourseDocumentSelected($event)">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="formSubmit()">
{{ "add" | translate | ftitlecase }}
</button>
<button type="button" class="btn btn-secondary" (click)="bsModalRef.hide()">
{{ "close" | translate }}
</button>
</div>

View File

@ -0,0 +1,132 @@
import { Component, OnInit, EventEmitter } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { BsModalRef } from 'ngx-bootstrap/modal';
import { ApiClientService } from 'src/app/_services/api-client.service';
import { AuthService } from 'src/app/_services/auth.service';
import { TranslateService } from '@ngx-translate/core';
import Swal from 'sweetalert2';
@Component({
selector: 'modal-add-training-course',
templateUrl: './modal-add-training-course.component.html',
styleUrls: ['./modal-add-training-course.component.scss']
})
export class ModalAddTrainingCourseComponent implements OnInit {
userId: number = 0;
form: FormGroup = this.formBuilder.group({
type: [0, [Validators.required]],
date: [null, [Validators.required]],
doc_number: [null, [Validators.required]],
file: [null]
});
dateMaxDate = new Date();
expirationDateMinDate = new Date(new Date().setDate(new Date().getDate() + 1)); //Tomorrow
allowedImageTypes = ["application/pdf"];
maxImageSize = 1024 * 1024 * 50; //50MB
addingType = false;
newType = "";
types: any[] = [];
submitEvents: EventEmitter<any> = new EventEmitter();
constructor(
public bsModalRef: BsModalRef,
private formBuilder: FormBuilder,
private api: ApiClientService,
public auth: AuthService,
private translateService: TranslateService
) { }
ngOnInit() {
this.api.get("training_course_types").then((response) => {
this.types = response;
}).catch((err) => {
console.log(err);
});
}
addType() {
this.addingType = false;
this.api.post("training_course_types", { name: this.newType }).then((response) => {
this.types.push(response);
this.form.patchValue({
type: response.id
});
}).catch((err) => {
Swal.fire({
title: this.translateService.instant("error_title"),
text: err.error.message,
icon: 'error',
confirmButtonText: 'Ok'
});
console.log(err);
});
}
onTrainingCourseDocumentSelected(event: any) {
const file: File = event.target.files[0];
if (file) {
if(!this.allowedImageTypes.includes(file.type)) {
event.target.value = null;
Swal.fire({
title: this.translateService.instant("error_title"),
text: this.translateService.instant("validation.document_format_not_supported"),
icon: 'error',
confirmButtonText: 'Ok'
});
return;
}
if(file.size > this.maxImageSize) {
event.target.value = null;
Swal.fire({
title: this.translateService.instant("error_title"),
text: this.translateService.instant("validation.file_too_big"),
icon: 'error',
confirmButtonText: 'Ok'
});
return;
}
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (_event) => {
this.form.patchValue({
file
});
this.form.get('file')?.updateValueAndValidity();
}
}
}
formSubmit() {
const formValues = this.form.value;
formValues.date = formValues.date ? new Date(formValues.date).toISOString() : null;
console.log(formValues);
const formData = new FormData();
formData.append('user', this.userId.toString());
formData.append('type', formValues.type);
formData.append('date', formValues.date);
formData.append('doc_number', formValues.doc_number);
if(formValues.file) formData.append('file', formValues.file, formValues.file.name);
this.api.post("documents/training_course", formData).then((response) => {
console.log(response);
this.bsModalRef.hide();
this.submitEvents.emit();
}).catch((err) => {
Swal.fire({
title: this.translateService.instant("error_title"),
text: err.error.message,
icon: 'error',
confirmButtonText: 'Ok'
});
console.log(err);
});
}
}

View File

@ -0,0 +1,27 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslationModule } from '../../translation.module';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { FirstLetterUppercasePipe } from 'src/app/_pipes/first-letter-uppercase.pipe';
import { ModalAddTrainingCourseComponent } from './modal-add-training-course.component';
@NgModule({
declarations: [
ModalAddTrainingCourseComponent
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslationModule,
BsDatepickerModule.forRoot(),
FirstLetterUppercasePipe
],
exports: [
ModalAddTrainingCourseComponent
]
})
export class ModalAddTrainingCourseModule { }

View File

@ -78,7 +78,7 @@
<br>
<div class="input-group has-validation">
<select formControlName="type" [class.is-invalid]="!isFieldValid('type')" class="form-control mr-2">
<option selected disabled translate>edit_service.select_type</option>
<option selected disabled translate>select_type</option>
<option *ngFor="let service_type of types" value="{{ service_type.id }}">{{ service_type.name }}</option>
</select>
<button class="btn btn-outline-secondary" type="button" tabindex="-1" (click)="addingType = true">
@ -89,7 +89,7 @@
</div>
</div>
<div class="input-group mb-2 mt-2" *ngIf="addingType">
<input type="text" class="form-control" [placeholder]="'type'|translate" [(ngModel)]="newType"
<input type="text" class="form-control" [placeholder]="'type'|translate|titlecase" [(ngModel)]="newType"
[ngModelOptions]="{standalone: true}">
<button class="btn btn-secondary" type="button" (click)="addType()">{{ 'submit'|translate|ftitlecase }}</button>
</div>

View File

@ -147,40 +147,39 @@
</div>
</form>
<ng-container *ngIf="false">
<hr>
<div class="text-center">
<h3>Corsi di formazione</h3>
<button disabled class="btn btn-sm btn-outline-info m-2">
<i class="fas fa-download"></i> LIFM
<h3>{{ 'training_courses'|translate|uppercase }}</h3>
<button *ngIf="!hideTCAddBtn" class="btn btn-sm btn-outline-success m-2" (click)="openModalAddTrainingCourse()">
<i class="fas fa-plus"></i> {{ 'add'|translate|ftitlecase }}
</button>
<table class="mx-auto table table-striped table-bordered table-responsive">
<thead>
<tr>
<th>TIPOLOGIA</th>
<th>NOME</th>
<th>DATA</th>
<th>SCADENZA</th>
<th>CERT.</th>
<th>{{ 'type'|translate|uppercase }}</th>
<th>{{ 'date'|translate|uppercase }}</th>
<th>{{ 'training_course_modal.doc_number'|translate|uppercase }}</th>
</tr>
</thead>
<tbody>
<tr *ngIf="false">
<td>TIPOCORSO</td>
<td>Nome</td>
<td>31/12/2024</td>
<td>31/12/2027</td>
<tr *ngFor="let row of user.training_courses">
<td>{{ row.type }}</td>
<td>{{ row.date | date:'dd/MM/YYYY' }}</td>
<td>
<a href=''><i class='fas fa-graduation-cap'></i></a>
<ng-container *ngIf="row.doc_url">
<a [href]='row.doc_url' target="_blank">{{ row.doc_number }}</a>
</ng-container>
<ng-container *ngIf="!row.doc_url">
{{ row.doc_number }}
</ng-container>
</td>
</tr>
</tbody>
</table>
<br>
</div>
</ng-container>
<hr>
@ -204,8 +203,8 @@
<td>{{ row.date | date:'dd/MM/YYYY' }}</td>
<td>{{ row.certifier }}</td>
<td>{{ row.expiration_date | date:'dd/MM/YYYY' }}</td>
<td *ngIf="!hideMECertCol && row.cert_url">
<a [href]='row.cert_url' target="_blank"><i class='fas fa-clipboard-list'></i></a>
<td *ngIf="!hideMECertCol">
<a *ngIf="row.cert_url" [href]='row.cert_url' target="_blank"><i class='fas fa-clipboard-list'></i></a>
</td>
</tr>
</tbody>

View File

@ -4,6 +4,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ApiClientService } from 'src/app/_services/api-client.service';
import { AuthService } from 'src/app/_services/auth.service';
import { TranslateService } from '@ngx-translate/core';
import { ModalAddTrainingCourseComponent } from 'src/app/_components/modal-add-traning-course/modal-add-training-course.component';
import { ModalAddMedicalExaminationComponent } from 'src/app/_components/modal-add-medical-examination/modal-add-medical-examination.component';
import { BsModalService } from 'ngx-bootstrap/modal';
import Swal from 'sweetalert2';
@ -44,6 +45,7 @@ export class EditUserComponent implements OnInit {
boot_size: ['']
});
hideTCAddBtn = true;
hideMECertCol = true;
hideMEAddBtn = true;
birthdayMaxDate = new Date(new Date().setFullYear(new Date().getFullYear() - 18)); //18 years ago
@ -141,6 +143,9 @@ export class EditUserComponent implements OnInit {
let canHide = this.id == this.auth.profile.id ?
this.auth.profile.can('user-hide') :
this.auth.profile.can('users-hide');
let canAddTrainingCourse = this.id == this.auth.profile.id ?
this.auth.profile.can('user-add-training-course') :
this.auth.profile.can('users-add-training-course');
let canAddMedicalExamination = this.id == this.auth.profile.id ?
this.auth.profile.can('user-add-medical-examination') :
this.auth.profile.can('users-add-medical-examination');
@ -149,6 +154,7 @@ export class EditUserComponent implements OnInit {
if(!canSetDriver) this.profileForm.get('driver')?.disable();
if(!canBan) this.profileForm.get('banned')?.disable();
if(!canHide) this.profileForm.get('hidden')?.disable();
this.hideTCAddBtn = !canAddTrainingCourse;
this.hideMEAddBtn = !canAddMedicalExamination;
}
@ -237,6 +243,23 @@ export class EditUserComponent implements OnInit {
}
}
openModalAddTrainingCourse() {
const modalReference = this.modalService.show(ModalAddTrainingCourseComponent, {
initialState: {
userId: this.id
}
});
modalReference.content?.submitEvents.subscribe(() => {
//Refresh user data after modal is closed
this.api.get(`users/${this.id}`).then((response) => {
this.user = response;
console.log(response);
}).catch((err) => {
console.log(err);
});
});
}
openModalAddMedicalExamination() {
const modalReference = this.modalService.show(ModalAddMedicalExaminationComponent, {
initialState: {

View File

@ -4,6 +4,7 @@ import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { BackBtnModule } from '../../_components/back-btn/back-btn.module';
import { ModalAddTrainingCourseModule } from 'src/app/_components/modal-add-traning-course/modal-add-training-course.module';
import { ModalAddMedicalExaminationModule } from 'src/app/_components/modal-add-medical-examination/modal-add-medical-examination.module';
import { TranslationModule } from '../../translation.module';
import { FirstLetterUppercasePipe } from 'src/app/_pipes/first-letter-uppercase.pipe';
@ -22,6 +23,7 @@ import { EditUserComponent } from './edit-user.component';
ReactiveFormsModule,
BsDatepickerModule.forRoot(),
BackBtnModule,
ModalAddTrainingCourseModule,
ModalAddMedicalExaminationModule,
TranslationModule,
FirstLetterUppercasePipe

View File

@ -75,7 +75,6 @@
"select_end_datetime": "Select end date and time for the service",
"insert_code": "Insert service code",
"other_crew_members": "Other crew members",
"select_type": "Select a type",
"select_service_type": "Select a service type",
"type_added_successfully": "Type added successfully",
"service_added_successfully": "Service added successfully",
@ -110,6 +109,10 @@
"user_info_modal": {
"title": "User info"
},
"training_course_modal": {
"title": "Add training course",
"doc_number": "document number"
},
"medical_examination_modal": {
"title": "Add medical examination"
},
@ -126,6 +129,7 @@
"warning": "warning",
"press_for_more_info": "press here for more info",
"update_availability_schedule": "Update availability schedule",
"select_type": "Select a type",
"save_changes": "Save changes",
"close": "Close",
"monday": "Monday",
@ -168,10 +172,12 @@
"driving_license_scan": "driving license scan",
"upload_scan": "upload scan",
"upload_medical_examination_certificate": "upload medical examination certificate",
"upload_training_course_doc": "upload training course document",
"clothings": "clothings",
"suit_size": "suit size",
"boot_size": "boot size",
"medical_examinations": "medical examinations",
"training_courses": "training courses",
"date": "date",
"expiration_date": "expiration date",
"certifier": "certifier",

View File

@ -75,7 +75,6 @@
"select_end_datetime": "Seleziona data e ora di fine dell'intervento",
"insert_code": "Inserisci il progressivo dell'intervento",
"other_crew_members": "Altri membri della squadra",
"select_type": "Seleziona una tipologia",
"select_service_type": "Seleziona una tipologia di intervento",
"type_already_exists": "La tipologia è già presente",
"type_added_successfully": "Tipologia aggiunta con successo",
@ -111,6 +110,10 @@
"user_info_modal": {
"title": "Scheda utente"
},
"training_course_modal": {
"title": "Aggiungi corso di formazione",
"doc_number": "numero Ordine del Giorno"
},
"medical_examination_modal": {
"title": "Aggiungi visita medica"
},
@ -125,6 +128,7 @@
"warning": "attenzione",
"press_for_more_info": "premi qui per informazioni",
"update_availability_schedule": "Aggiorna programmazione disponibilità",
"select_type": "Seleziona una tipologia",
"save_changes": "Salva modifiche",
"close": "Chiudi",
"monday": "Lunedì",
@ -167,10 +171,12 @@
"driving_license_scan": "scansione patente",
"upload_scan": "carica scansione",
"upload_medical_examination_certificate": "carica certificato visita medica",
"upload_training_course_doc": "carica Ordine del Giorno",
"clothings": "indumenti",
"suit_size": "taglia tuta",
"boot_size": "taglia scarponi",
"medical_examinations": "visite mediche",
"training_courses": "corsi di formazione",
"date": "data",
"expiration_date": "data di scadenza",
"certifier": "ente/certificatore",