/ app / Http / Services / DossierService.php
DossierService.php
  1  <?php
  2  
  3  namespace App\Http\Services;
  4  
  5  use App\Data\Enisa\CertificateData;
  6  use App\Data\Enisa\ContactInfoData;
  7  use App\Data\Enisa\InformationData;
  8  use App\Data\Enisa\ProductData;
  9  use App\Data\Enisa\ProductTypeData;
 10  use App\Enums\EntityTypeEnum;
 11  use App\Enums\FolderEnum;
 12  use App\Enums\RoleEnum;
 13  use App\Enums\StatusEnum;
 14  use App\Events\UserAssignmentEvent;
 15  use App\Models\CertificateInfo;
 16  use App\Models\Document;
 17  use App\Models\Dossier;
 18  use App\Models\DossierStatus;
 19  use App\Models\DossierTechnicalDomain;
 20  use App\Models\DossierType;
 21  use App\Models\Entity;
 22  use App\Models\Folder;
 23  use App\Models\InboxFile;
 24  use App\Models\Role;
 25  use App\Models\Stage;
 26  use App\Models\Taxonomy;
 27  use App\Models\Task;
 28  use App\Models\User;
 29  use App\Notifications\TaskAssigned;
 30  use Illuminate\Support\Facades\DB;
 31  use Illuminate\Support\Facades\Log;
 32  use Illuminate\Support\Facades\Notification;
 33  use Illuminate\Support\Facades\Storage as FacadesStorage;
 34  use Illuminate\Support\Str;
 35  
 36  use Exception;
 37  
 38  class DossierService
 39  {
 40      /**
 41       * Search resources
 42       *
 43       * @return \Illuminate\Http\Response
 44       */
 45      public static function search(string $searchString, string $sortField, string $sortDirection, array|null $filters)
 46      {
 47          $user = auth()->user();
 48          $dossiersQuery = Dossier::query();
 49  
 50          //Improved Code pendant of review
 51          $dossiersQuery = Dossier::with(['toe', 'toe.company:id,name', 'laboratory:id,name', 'principalCertifier:id,name,lastname', 'status'])->withCount([
 52              'tasks',
 53              'tasks as tasks_completed_count' => fn($query) => $query->where('task_status_id', StatusEnum::finished()->value)
 54          ]);
 55  
 56          $dossiersQuery
 57              ->when($filters && $filters['withTrashed'], fn($query) => $query->withTrashed())
 58              ->when($filters && $filters['active'], fn($query) => $query->active())
 59              ->when($filters && $filters['hide_legacy'], fn($query) => $query->where('is_legacy', false))
 60              ->when($filters && $filters['withoutTOEAssigned'], fn($query) => $query->doesntHave('toe'))
 61              ->when($filters && $filters['technical_domain_id'], fn($query) => $query->where('technical_domain_id', $filters['technical_domain_id']))
 62              ->when($filters && $filters['created_at_since'], fn($query) => $query->where('created_at', '>=', $filters['created_at_since']))
 63              ->when($filters && $filters['created_at_to'], fn($query) => $query->where('created_at', '<=', $filters['created_at_to']))
 64              ->when($filters && $filters['updated_at_since'], fn($query) => $query->where('updated_at', '>=', $filters['updated_at_since']))
 65              ->when($filters && $filters['updated_at_to'], fn($query) => $query->where('updated_at', '<=', $filters['updated_at_to']))
 66              ->when($filters && $filters['code_year'], fn($query) => $query->where('code_year', 'like', '%' . $filters['code_year'] . '%'))
 67              ->when($filters && $filters['code'], fn($query) => $query->whereIn('id', $filters['code']))
 68              ->when(
 69                  $filters && $filters['type'],
 70                  fn($query) => $query->whereHas(
 71                      'dossierType',
 72                      fn($q) => $q->whereHas(
 73                          'father',
 74                          fn($q) => $q->whereHas(
 75                              'father',
 76                              fn($q) => $q->where('id', $filters['type'])
 77                          )
 78                      )
 79                  )
 80              )
 81              ->when(
 82                  $filters && $filters['mode'],
 83                  fn($query) => $query->whereHas(
 84                      'dossierType',
 85                      fn($q) => $q->whereHas(
 86                          'father',
 87                          fn($q) => $q->where('id', $filters['mode'])
 88                      )
 89                  )
 90              )
 91              ->when($filters && $filters['subtype'], fn($query) => $query->where('dossier_type_id', $filters['subtype']))
 92              ->when($filters && $filters['applicant'], fn($query) => $query->whereIn('applicant_id', $filters['applicant']))
 93              ->when($filters && $filters['sponsor'], fn($query) => $query->whereIn('sponsor_id', $filters['sponsor']))
 94              ->when($filters && $filters['manufacturer'], fn($query) => $query->whereIn('manufacturer_id', $filters['manufacturer']))
 95              ->when($filters && $filters['toe'], function ($query) use ($filters) {
 96                  $query->whereHas('toe', function ($query) use ($filters) {
 97                      $query->where('name', 'like', '%' . $filters['toe'] . '%');
 98                  });
 99              })
100              ->when($filters && $filters['dossier_status'], fn($query) => $query->whereIn('dossier_status_id', $filters['dossier_status']))
101              ->when($filters && $filters['laboratory'], fn($query) => $query->whereIn('laboratory_id', $filters['laboratory']))
102              ->when($filters && $filters['norm'], fn($query) => $query->whereIn('norm_id', $filters['norm']))
103              ->when($filters && $filters['scope'], function ($query) use ($filters) {
104                  $query->whereHas('normScope', function ($query) use ($filters) {
105                      $query->where('name', 'like', '%' . $filters['scope'] . '%');
106                  });
107              })
108              ->when($filters && $filters['stage'], function ($query) use ($filters) {
109                  $query->whereHas('stage', function ($query) use ($filters) {
110                      $query->where('name', 'like', '%' . $filters['stage'] . '%');
111                  });
112              })
113              ->when($filters && $filters['principal_certifier'], fn($query) => $query->whereIn('principal_certifier_id', $filters['principal_certifier']))
114              ->when($filters && $filters['secondary_certifier'], function ($query) use ($filters) {
115                  $query->whereHas('users', function ($query) use ($filters) {
116                      $query->whereIn('users.id', function ($query) use ($filters) {
117                          $query->select('entity_user.user_id')
118                              ->from('entity_user')
119                              ->where('entity_user.role_id', '=', Role::where('name', 'secondary_certifier')->first()->id)
120                              ->whereIn('entity_user.user_id', $filters['secondary_certifier']);
121                      });
122                  });
123              })
124              ->when($filters && $filters['informed_staff'], function ($query) use ($filters) {
125                  $query->whereHas('users', function ($query) use ($filters) {
126                      $query->whereIn('users.id', function ($query) use ($filters) {
127                          $query->select('entity_user.user_id')
128                              ->from('entity_user')
129                              ->where('entity_user.role_id', '=', Role::where('name', 'informed_staff')->first()->id)
130                              ->whereIn('entity_user.user_id', $filters['informed_staff']);
131                      });
132                  });
133              })
134              ->when($filters && $filters['expert'], function ($query) use ($filters) {
135                  $query->whereHas('users', function ($query) use ($filters) {
136                      $query->whereIn('users.id', function ($query) use ($filters) {
137                          $query->select('entity_user.user_id')
138                              ->from('entity_user')
139                              ->where('entity_user.role_id', '=', Role::where('name', 'external_expert')->first()->id)
140                              ->orWhere('entity_user.role_id', '=', Role::where('name', 'internal_expert')->first()->id)
141                              ->whereIn('entity_user.user_id', $filters['expert']);
142                      });
143                  });
144              })
145              ->when($filters && $filters['external_certifier'], function ($query) use ($filters) {
146                  $query->whereHas('users', function ($query) use ($filters) {
147                      $query->whereIn('users.id', function ($query) use ($filters) {
148                          $query->select('entity_user.user_id')
149                              ->from('entity_user')
150                              ->where('entity_user.role_id', '=', Role::where('name', 'external_certifier')->first()->id)
151                              ->whereIn('entity_user.user_id', $filters['external_certifier']);
152                      });
153                  });
154              })
155              ->when(!$user->hasAnyRole([
156                  'root',
157                  'technical_manager',
158                  'quality_manager',
159                  'security_manager',
160                  'area_manager'
161              ]), function ($query) use ($user) {
162                  $query->where('principal_certifier_id', $user->id)
163                      ->orwhereHas('users', function ($query) use ($user) {
164                          $query->where('users.id', $user->id);
165                      });
166              });
167  
168  
169          if ($sortField && $sortDirection) {
170              $dossiersQuery->orderBy($sortField, $sortDirection);
171          }
172  
173          return $dossiersQuery;
174      }
175  
176      /**
177       * Display a listing of the resource.
178       *
179       * @return Dossier[]|\Illuminate\Pagination\LengthAwarePaginator|\LaravelIdea\Helper\App\Models\_IH_Dossier_C
180       */
181      public static function index()
182      {
183          return Dossier::orderBy('name', 'asc')->paginate(10);
184      }
185  
186      public static function create(array $data, int $userId = null): Dossier
187      {
188          try {
189              DB::beginTransaction();
190  
191              $dossier = Dossier::create([
192                  'code_year' => $data['code_year'] ?? date('Y'),
193                  'code_seq' => $data['code_seq'] ?? self::getNextCodeByYear(),
194                  'toe_id' => $data['toe_id'],
195                  'dossier_type_id' => $data['dossier_type_id'] ?? $data['subtype'],
196                  'applicant_id' => $data['applicant_id'],
197                  'sponsor_id' => $data['sponsor_id'],
198                  'manufacturer_id' => $data['manufacturer_id'],
199                  'laboratory_id' => $data['laboratory_id'],
200                  'principal_certifier_id' => $data['principal_certifier_id'],
201                  'norm_id' => $data['norm_id'],
202                  'norm_scope_id' => $data['norm_scope_id'],
203              ]);
204  
205  
206              collect($data['evaluators'])->each(function ($userId) use ($dossier) {
207                  $dossier->users()->attach($userId, ['role_id' => RoleEnum::evaluator()->value]);
208              });
209  
210              collect($data['secondary_certifiers'])->each(function ($userId) use ($dossier) {
211                  $dossier->users()->attach($userId, ['role_id' => RoleEnum::secondaryCertifier()->value]);
212              });
213  
214              collect($data['external_certifiers'])->each(function ($userId) use ($dossier) {
215                  $dossier->users()->attach($userId, ['role_id' => RoleEnum::externalCertifier()->value]);
216              });
217  
218              collect($data['experts'])->each(function ($userId) use ($dossier) {
219                  $user = User::findOrFail($userId);
220                  $dossier->users()->attach($userId, [
221                      'role_id' => !is_null($user->internal_identification_number)
222                          ? RoleEnum::internalExpert()->value
223                          : RoleEnum::externalExpert()->value
224                  ]);
225              });
226  
227              collect($data['informed_staff'])->each(function ($userId) use ($dossier) {
228                  $dossier->users()->attach($userId, ['role_id' => RoleEnum::informedStaff()->value]);
229              });
230  
231              if (isset($data['applicant_poc_ids'])) {
232                  $dossier->pocs()->sync($data['applicant_poc_ids']);
233              }
234  
235              DossierService::createFolders($dossier->id, $userId ?? auth()->user()->id);
236  
237              $dossier->workbooks()->sync($data['related_workbooks']);
238  
239              DB::commit();
240  
241              return $dossier;
242          } catch (Exception $e) {
243              DB::rollBack();
244              throw $e;
245          }
246      }
247  
248      /**
249       * Update a resource
250       *
251       * @return \Illuminate\Http\Response
252       */
253      public static function update(array $data, int $entityId)
254      {
255          try {
256              DB::beginTransaction();
257  
258              $dossier = Dossier::findOrFail($entityId);
259  
260              $dossier->toe_id = $data['toe_id'];
261              $dossier->dossier_type_id = $data['subtype'];
262              $dossier->applicant_id = $data['applicant_id'];
263              $dossier->sponsor_id = $data['sponsor_id'];
264              $dossier->manufacturer_id = $data['manufacturer_id'];
265              $dossier->laboratory_id = $data['laboratory_id'];
266              $dossier->norm_id = $data['norm_id'];
267              $dossier->norm_scope_id = $data['norm_scope_id'];
268              $dossier->dossier_status_id = $data['dossier_status_id'] ?? DossierStatus::in_process;
269              $dossier->current_stage_id = $data['dossier_current_stage'];
270              $dossier->monitoring_level = $data['monitoring_level'];
271              $dossier->language = $data['language'];
272              $dossier->evaluation_end_date = $data['evaluation_end_date'];
273              $dossier->certification_end_date = $data['certification_end_date'];
274              $dossier->active = DossierStatus::isOpen($data['dossier_status_id']);
275              $dossier->etr_name = $data['etr_name']; //Token
276              $dossier->st_name = $data['st_name']; //Token
277              $dossier->code_report_lab = $data['code_report_lab']; //Token
278              $dossier->iar_document_id = $data['iar_document_id']; //Token
279              $dossier->original_dossier_id = $data['original_dossier_id']; //Token
280              $dossier->technical_domain_id = $data['technical_domain_id'];
281  
282              $dossier->save();
283  
284              $dossier->users()->detach();
285              $dossier->workbooks()->sync($data['related_workbooks']);
286              $dossier->PPs()->sync($data['pps']);
287  
288              self::assignCertifiers($dossier, $data);
289  
290              collect($data['evaluators'])->each(function ($userId) use ($dossier) {
291                  $dossier->users()->attach($userId, ['role_id' => RoleEnum::evaluator()->value]);
292              });
293  
294              collect($data['experts'])->each(function ($userId) use ($dossier) {
295                  $user = User::findOrFail($userId);
296                  $dossier->users()->attach($userId, [
297                      'role_id' => !is_null($user->internal_identification_number)
298                          ? RoleEnum::internalExpert()->value
299                          : RoleEnum::externalExpert()->value
300                  ]);
301              });
302  
303              collect($data['informed_staff'])->each(function ($userId) use ($dossier) {
304                  $dossier->users()->attach($userId, ['role_id' => RoleEnum::informedStaff()->value]);
305              });
306  
307  
308              if (isset($data['applicant_poc_ids']))
309                  $dossier->pocs()->sync($data['applicant_poc_ids']);
310  
311              if (in_array($dossier->dossier_status_id, [DossierStatus::finished, DossierStatus::dismissed, DossierStatus::desisted])) {
312                  DossierService::setDossierToStage12($dossier);
313              }
314  
315              DB::commit();
316  
317              return true;
318          } catch (Exception $e) {
319              DB::rollBack();
320              //throw new Exception(__('dossiers.notifications.update.error.message'));
321              throw new Exception($e->getMessage());
322          }
323      }
324  
325      public static function setDossierToStage12(Dossier $dossier)
326      {
327          $stage = $dossier->stages()->where('name', __('stages.default.12'))->first();
328          if ($stage) {
329              $stage->stage_status_id = StatusEnum::finished()->value;
330              $stage->save();
331          }
332      }
333  
334      public static function createTechnicalDomain(string $name): int
335      {
336          return DossierTechnicalDomain::create(['name' => $name])->id;
337      }
338  
339      public static function assignCertifiers(Dossier $dossier, array $data)
340      {
341          $dossier->update(['principal_certifier_id' => $data['principal_certifier_id']]);
342          self::syncCertifiers($dossier, $data['secondary_certifiers'], RoleEnum::secondaryCertifier(), 'secondaryCertifiers');
343          self::syncCertifiers($dossier, $data['external_certifiers'], RoleEnum::externalCertifier(), 'externalCertifiers');
344      }
345  
346      private static function syncCertifiers(Dossier $dossier, array $ids, RoleEnum $rol, string $type)
347      {
348          $detachIds = $dossier->$type()->pluck('id');
349          $dossier->users()->detach($detachIds);
350  
351          $syncData = [];
352          foreach ($ids as $userId) {
353              $syncData[$userId] = ['role_id' => $rol->value];
354          }
355          $dossier->users()->attach($syncData);
356      }
357  
358      public static function changeDossierTasks(User $from, User $to, Dossier $dossier)
359      {
360          $tasks = $from->tasks()->whereHas('stage', function ($query) use (&$dossier) {
361              $query->where('dossier_id', $dossier->id);
362          });
363          $tasks->update(['user_id' => $to->id]);
364          foreach ($tasks as $task) {
365              Notification::send($to, new TaskAssigned($task));
366          }
367      }
368  
369      /**
370       * Destroy a resource
371       *
372       * @return \Illuminate\Http\Response
373       */
374      public static function remove(int $entityId)
375      {
376          try {
377              DB::beginTransaction();
378  
379              Dossier::destroy($entityId);
380              InboxFile::where('dossier_id', $entityId)->delete();
381              Document::where('dossier_id', $entityId)->delete();
382              $stageIds = Stage::where('dossier_id', $entityId)->pluck('id');
383              Task::whereIn('stage_id', $stageIds)->delete();
384  
385              DB::commit();
386  
387              return true;
388          } catch (Exception $e) {
389              DB::rollBack();
390              throw new Exception(__('dossiers.notifications.remove.error.message'));
391          }
392      }
393  
394      /**
395       * Restore a resource
396       *
397       * @return \Illuminate\Http\Response
398       */
399      public static function restore(int $entityId)
400      {
401          try {
402              DB::beginTransaction();
403  
404              Dossier::withTrashed()->find($entityId)->restore();
405              InboxFile::withTrashed()->where('dossier_id', $entityId)->restore();
406              Document::withTrashed()->where('dossier_id', $entityId)->restore();
407              Stage::withTrashed()->where('dossier_id', $entityId)->restore();
408              $stageIds = Stage::withTrashed()->where('dossier_id', $entityId)->pluck('id');
409              Task::withTrashed()->whereIn('stage_id', $stageIds)->restore();
410  
411              DB::commit();
412  
413              return true;
414          } catch (Exception $e) {
415              DB::rollBack();
416              throw new Exception(__('dossiers.notifications.remove.error.message'));
417          }
418      }
419  
420      public static function getNextCodeByYear(?string $year = null): string
421      {
422          $dossiersByYear = Dossier::withTrashed()->where('code_year', $year ?? date('Y'))->get()->count();
423  
424          return str_pad($dossiersByYear + 1, 3, '0', STR_PAD_LEFT);
425      }
426  
427      public static function addTaxonomy(Dossier $dossier, Taxonomy $taxonomy): Dossier
428      {
429          $stages = $taxonomy->taxonomyStages ?? [];
430          foreach ($stages as $stage) {
431              $dossierStage = Stage::create([
432                  'dossier_id' => $dossier->id,
433                  'name' => $stage->name,
434                  'order' => $stage->order,
435              ]);
436  
437              $tasks = $stage->tasks ?? [];
438              foreach ($tasks as $task) {
439                  if ($task->is_milestone) {
440                      TaskService::createMilestone($dossierStage, $task->title, type: $task->type);
441                  } else {
442                      Task::create([
443                          'title' => $task->title,
444                          'description' => $task->description,
445                          'stage_id' => $dossierStage->id,
446                          'is_milestone' => $task->is_milestone,
447                      ]);
448                  }
449              }
450          }
451  
452          $dossier->current_stage_id = $dossier->stages->first()->id;
453          $dossier->save();
454  
455          // self::createMilestones($dossier);
456  
457          return $dossier;
458      }
459  
460      public static function buildFromTaxonomy($taxonomy, $data = null, $codeYear = null, $codeSeq = null): ?Dossier
461      {
462          try {
463              DB::beginTransaction();
464  
465              $dossier = Dossier::create([
466                  'code_year' => $codeYear ?? date('Y'),
467                  'code_seq' => $codeSeq ?? self::getNextCodeByYear(),
468                  'dossier_type_id' => $taxonomy->dossier_type_id,
469                  'toe_id' => $data['toe_id'] ?? null,
470                  'laboratory_id' => $data['laboratory_id'] ?? null,
471              ]);
472  
473              self::addTaxonomy($dossier, $taxonomy);
474              DossierService::createFolders($dossier->id, auth()->id());
475  
476              DB::commit();
477  
478              return $dossier;
479          } catch (Exception $e) {
480              Log::error($e->getMessage());
481              DB::rollBack();
482  
483              return null;
484          }
485      }
486  
487      public function assignUser(Dossier $dossier, User $user): void
488      {
489          $dossier->users->attach($user->id);
490  
491          UserAssignmentEvent::dispatch($user, $dossier, true);
492      }
493  
494      public function deassignUser(Dossier $dossier, User $user): void
495      {
496          $dossier->users->detach($user->id);
497  
498          UserAssignmentEvent::dispatch($user, $dossier, false);
499      }
500  
501      public static function searchDossierInsideNotification(string $notificationId)
502      {
503          $dossiers = Dossier::all();
504          foreach ($dossiers as $dossier) {
505              if (Str::contains(auth()->user()->notifications->find($notificationId)->data['message'], $dossier->code_year . '-' . $dossier->code_seq))
506                  return $dossier->id;
507          }
508      }
509  
510      public static function getDossierTypes()
511      {
512          return DossierType::query()
513              ->select('id', 'name')
514              ->whereNull('parent_id')
515              ->orderBy('name', 'asc')
516              ->get();
517      }
518  
519      public static function getDossierModes(?int $parent_id)
520      {
521          return DossierType::query()
522              ->select('id', 'name')
523              ->whereNotNull('parent_id')
524              ->where('parent_id', '=', $parent_id)
525              ->orderBy('name', 'asc')
526              ->get();
527      }
528  
529      public static function getDossierSubtypes(?int $parent_id)
530      {
531          return DossierType::query()
532              ->select('id', 'name')
533              ->where('parent_id', '=', $parent_id)
534              ->whereNotNull('parent_id')
535              ->orderBy('name', 'asc')
536              ->get();
537      }
538      public static function createFolders(int $dossierId, int $userId)
539      {
540          $dossier = Dossier::findOrFail($dossierId);
541  
542          // Buscar o crear la carpeta principal del dossier
543          $dossierFolder = self::firstOrCreateFolder($dossier->folders(), [
544              'name' => "{$dossier->code_year}-{$dossier->code_seq}",
545              'created_by' => $userId,
546              'parent_id' => FolderEnum::DOSSIERS,
547          ]);
548  
549          // Subcarpetas principales
550          $subfolders = [
551              'resolution',
552              'internal',
553              'external',
554              'resolution_signed',
555              'resolution_final'
556          ];
557  
558          foreach ($subfolders as $subfolder) {
559              self::firstOrCreateFolder($dossierFolder->children(), [
560                  'name' => __("documents.folders.$subfolder"),
561                  'created_by' => $userId,
562                  'dossier_id' => $dossierId,
563              ]);
564          }
565  
566          // Subcarpetas dentro de "external"
567          $externalFolder = $dossierFolder->children()->where('name', __('documents.folders.external'))->first();
568          if ($externalFolder) {
569              foreach (
570                  [
571                      'certification_requests',
572                      'evaluation_requests',
573                      'partial_reports',
574                      'jsons',
575                      'ETRs',
576                      'ORs',
577                      'others'
578                  ] as $name
579              ) {
580                  self::firstOrCreateFolder($externalFolder->children(), [
581                      'name' => __("documents.folders.$name"),
582                      'created_by' => $userId,
583                      'dossier_id' => $dossierId,
584                  ]);
585              }
586          }
587  
588          Folder::fixTree();
589      }
590  
591      /**
592       * Retorna una carpeta existente o la crea si no existe.
593       */
594      private static function firstOrCreateFolder($query, array $attributes)
595      {
596          return $query->firstOrCreate(['name' => $attributes['name']], $attributes);
597      }
598  
599  
600      public static function createCertificateXml(int $dossierId, CertificateInfo $certificateInfo)
601      {
602          $dossier = Dossier::find($dossierId);
603          $toe = $dossier->toe;
604          $cab = Entity::where('entity_type_id', EntityTypeEnum::cab())->first();
605          $lab = $dossier->laboratory;
606          $ncca = Entity::where('entity_type_id', EntityTypeEnum::ncca())->first();
607          $product = new ProductData(
608              $toe->name,
609              $toe->evaluation_target,
610              $toe->evaluation_scope,
611              $toe->st->url(),
612              $toe->commercial_name,
613              new ProductTypeData(
614                  $toe->sectorial_dimensions,
615                  $toe->usage_dimension,
616                  $toe->technological_dimensions,
617              ),
618              $toe->version,
619              $toe->company->legal_name,
620              new ContactInfoData(
621                  $toe->company->contactInfo->address,
622                  $toe->company->contactInfo->city,
623                  $toe->company->contactInfo->postal_code,
624                  $toe->company->contactInfo->country->name,
625                  $toe->company->contactInfo->phone,
626                  $toe->company->contactInfo->email
627              ),
628              $toe->company->legal_name,
629              $toe->cyber_security_info_url,
630              $toe->guidance_url,
631              $toe->period_security_support,
632              $toe->provider_contact_vulnerability_url,
633              $toe->vulnerability_notifications,
634              $toe->publicly_disclosed_url
635          );
636          $information = new InformationData(
637              $cab->name,
638              new ContactInfoData(
639                  $cab->contactInfo->address,
640                  $cab->contactInfo->city,
641                  $cab->contactInfo->postal_code,
642                  $cab->contactInfo->country->name,
643                  $cab->contactInfo->phone,
644                  $cab->contactInfo->email
645              ),
646              $cab->notification_number,
647              $cab->authorization_number,
648              $lab->name,
649              new ContactInfoData(
650                  $lab->contactInfo->address,
651                  $lab->contactInfo->city,
652                  $lab->contactInfo->postal_code,
653                  $lab->contactInfo->country->name,
654                  $lab->contactInfo->phone,
655                  $lab->contactInfo->email
656              ),
657              $lab->notification_number,
658              $lab->authorization_number,
659              $ncca->legal_name,
660              'Common Criteria based European candidate cybersecurity certification scheme',
661              'EUCC',
662              'https://www.enisa.europa.eu/publications/cybersecurity-certification-eucc-candidate-scheme-v1-1.1/',
663              //TODO create CRUD for certification Scheme
664              $dossier->certificationReport->url(),
665              $dossier->certificationReport->url(),
666              $dossier->assuranceLevel ?? 'Basic',
667              $dossier->norm->name,
668              $dossier->norm->normGroup->description,
669              $dossier->norm->version,
670              $dossier->norm->normGroup->url,
671              $dossier->normScope->name,
672              //TODO This is weird, we should have a relation to the protection profile
673              $dossier->protectionProfiles->first()->name,
674              $dossier->protectionProfiles->first()->name,
675              $dossier->protectionProfiles->first()->head->version,
676              $dossier->protectionProfiles->first()->original_name ?? "",
677  
678              $certificateInfo->issued_at,
679              $certificateInfo->validation_period,
680              $certificateInfo->expired_at,
681  
682              $certificateInfo->status,
683              $certificateInfo->version,
684              $certificateInfo->version_history,
685              $certificateInfo->previous_certificate_url,
686              $certificateInfo->version_changelog,
687              'Domain Certification' //TODO not supplied by ENISA
688          );
689  
690          $certificate = new CertificateData(
691              EnisaService::generateCertificateId($dossier),
692              $product,
693              $information
694          );
695          $xml = xml_encode($certificate->toArray(), 'certificate', 'camel', "1.1");
696          $filename = $dossier->code_year . '-' . $dossier->code_seq . '.xml';
697          FacadesStorage::disk('local')->put("certificates/xml/$filename", $xml);
698          $path = FacadesStorage::disk('local')->path("certificates/xml/$filename");
699  
700          return
701              $dossier->certificates()->create([
702                  'cuid' => $certificate->cuid,
703                  'file_name' => $certificate->cuid . '.xml',
704                  'xml_path' => $path,
705                  'file_hash' => hash_file('sha256', $path),
706                  'country' => $dossier->toe->company->contactInfo->country->code,
707                  'ncca' => $ncca->legal_name,
708                  'cab' => $cab->legal_name,
709                  'certificate_info_id' => $certificateInfo->id,
710              ]);
711      }
712  
713      public function checkCertificateRequirements($dossierId): bool
714      {
715          $dossier = Dossier::findOrfail($dossierId);
716          if (!$dossier->toe) {
717              throw new Exception(__('dossiers.exceptions.toe'));
718          }
719          if (!$dossier->toe->st) {
720              throw new Exception(__('dossiers.exceptions.st'));
721          }
722          if (!$dossier->certificationReport) {
723              throw new Exception(__('dossiers.exceptions.certificationReport'));
724          }
725          if (!$dossier->protectionProfiles->first()) {
726              throw new Exception(__('dossiers.exceptions.protectionProfile'));
727          }
728          if (!$dossier->norm) {
729              throw new Exception(__('dossiers.exceptions.norm'));
730          }
731          $cab = Entity::where('entity_type_id', EntityTypeEnum::cab())->first();
732          if (!$cab) {
733              throw new Exception(__('dossiers.exceptions.cab'));
734          }
735          throw_if(!$cab->contactInfo->country, new Exception(__('dossiers.exceptions.cab_country')));
736          if (!$ncca = Entity::where('entity_type_id', EntityTypeEnum::ncca())->first()) {
737              throw new Exception(__('dossiers.exceptions.ncca'));
738          }
739          if (!$cab->notification_number) {
740              throw new Exception(__('dossiers.exceptions.cab_notification_number'));
741          }
742          if (!$cab->authorization_number) {
743              throw new Exception(__('dossiers.exceptions.cab_authorization_number'));
744          }
745          if (!$dossier->laboratory) {
746              throw new Exception(__('dossiers.exceptions.laboratory'));
747          }
748          throw_if(!$dossier->laboratory->contactInfo->country, new Exception(__('dossiers.exceptions.lab_country')));
749          if (!$dossier->laboratory->notification_number) {
750              throw new Exception(__('dossiers.exceptions.lab_notification_number'));
751          }
752          if (!$dossier->laboratory->authorization_number) {
753              throw new Exception(__('dossiers.exceptions.lab_authorization_number'));
754          }
755  
756          return true;
757      }
758  
759      public static function findByCode(string $code): Dossier|null
760      {
761          $code = explode('-', $code);
762          if (count($code) !== 2) {
763              return null;
764          }
765  
766          $code_seq_padded = str_pad($code[1], 3, '0', STR_PAD_LEFT);
767  
768          $dossier = Dossier
769              ::where('code_year', $code[0])
770              ->where('code_seq', $code_seq_padded)
771              ->first();
772  
773          return $dossier;
774      }
775  
776      public static function getDossierProgress(Dossier $dossier)
777      {
778          $tasksCount = $dossier->tasks_count;
779          $tasksCompletedCount = $dossier->tasks_completed_count;
780          return $dossier->setAttribute('percent', $percent = $tasksCount ? number_format(($tasksCompletedCount / $tasksCount) * 100, 2) : 0)
781              ->setAttribute('percent_formatted', intval($percent) . '%')
782              ->setAttribute('task_formatted', ($tasksCount - $tasksCompletedCount) . " / {$tasksCount}");
783      }
784  
785  
786      public static function calculateDossierStatus(Dossier $dossier)
787      {
788          $percent = self::getDossierProgress($dossier)->percent;
789  
790          return match ($percent) {
791              0, "0.00" => DossierStatus::created,
792              100, "100.00" => DossierStatus::finished,
793              default => DossierStatus::in_process
794          };
795      }
796  
797      public static function getDossiersByStage(string $stageName, int $statusId = null)
798      {
799          return Dossier::whereHas('stages', function ($query) use ($stageName, $statusId) {
800              $query->where('name', $stageName)->where('stage_status_id', $statusId);
801          })->get();
802      }
803  
804      public static function setStatus(Dossier $dossier, int $status)
805      {
806          try {
807              $dossier->dossier_status_id = $status;
808              $dossier->save();
809          } catch (Exception $e) {
810              throw $e;
811          }
812      }
813  
814      public static function setNewCurrentStage(Dossier $dossier, int $stageId): void
815      {
816          try {
817              $dossier->current_stage_id = $stageId;
818              $dossier->save();
819          } catch (Exception $e) {
820              logger($e);
821          }
822      }
823  
824  
825      private static function createMilestones(Dossier $dossier)
826      {
827          self::createStage1Milestones($dossier);
828          self::createStage2Milestones($dossier);
829          self::createStage4Milestones($dossier);
830          self::createStage5Milestones($dossier);
831          self::createStage6Milestones($dossier);
832          self::createStage12Milestones($dossier);
833      }
834  
835      private static function createStage1Milestones(Dossier $dossier)
836      {
837          $stage = $dossier->getStage(1);
838          TaskService::createMilestone($stage, __('tasks.milestones.certification-request-received'));
839          TaskService::createMilestone($stage, __('tasks.milestones.certifiers-assigned'));
840          TaskService::createMilestone($stage, __('tasks.milestones.start-notification'));
841  
842          // This milestone can be completed inmediately as the dossier has been
843          // created from a certification request
844          TaskService::closeMilestone($dossier, __('tasks.milestones.certification-request-received'));
845      }
846  
847      private static function createStage2Milestones(Dossier $dossier)
848      {
849          $stage = $dossier->getStage(2);
850          TaskService::createMilestone($stage, __('tasks.milestones.evaluation-request-received'));
851          TaskService::createMilestone($stage, __('tasks.milestones.evaluation-meet-finished'));
852          TaskService::createMilestone($stage, __('tasks.milestones.evaluation-meet-minutes-sent'));
853      }
854  
855      private static function createStage4Milestones(Dossier $dossier)
856      {
857          $stage = $dossier->getStage(4);
858          TaskService::createMilestone($stage, __('tasks.milestones.pre-hearing-meeting-convened'));
859          TaskService::createMilestone($stage, __('tasks.milestones.pre-hearing-meeting-minutes-sent'));
860      }
861  
862  
863      private static function createStage5Milestones(Dossier $dossier)
864      {
865          $stage = $dossier->getStage(5);
866          TaskService::createMilestone($stage, __('tasks.milestones.resolution-processed'));
867          TaskService::createMilestone($stage, __('tasks.milestones.resolution-signed'));
868          TaskService::createMilestone($stage, __('tasks.milestones.resolution-sent-to-boe'));
869          TaskService::createMilestone($stage, __('tasks.milestones.resolution-published-in-boe'));
870          TaskService::createMilestone($stage, __('tasks.milestones.resolution-certificate-sent'));
871      }
872  
873      private static function createStage6Milestones(Dossier $dossier)
874      {
875          $stage = $dossier->getStage(6);
876          TaskService::createMilestone($stage, __('tasks.milestones.certified-list-updated'));
877          TaskService::createMilestone($stage, __('tasks.milestones.certified-product-web-published'));
878      }
879  
880      private static function createStage12Milestones(Dossier $dossier)
881      {
882          $stage = $dossier->getStage(12);
883          TaskService::createMilestone($stage, __('tasks.milestones.dossier-closed'));
884      }
885  }