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 }