DocumentService.php
1 <?php 2 3 namespace App\Http\Services; 4 5 use App\Helpers\DocGen\DocumentDownloader; 6 use App\Models\Review; 7 use Exception; 8 use Illuminate\Support\Str; 9 use Throwable; 10 use App\Models\Dossier; 11 use App\Models\Document; 12 use App\Models\Revision; 13 use App\Models\Template; 14 use App\Enums\UserTypeEnum; 15 use App\Events\DocumentGenerated; 16 use App\Helpers\DocxToPdfConverter; 17 use App\Models\DocumentType; 18 use App\Models\Folder; 19 use App\Models\Notification; 20 use App\Models\Role; 21 use App\Models\User; 22 use App\Models\Validation; 23 use App\Notifications\DocumentForApproval; 24 use App\Notifications\DocumentForReview; 25 use Illuminate\Http\Response; 26 use Illuminate\Support\Facades\DB; 27 use Illuminate\Support\Facades\Log; 28 use Illuminate\Support\Facades\Auth; 29 use Illuminate\Database\Eloquent\Model; 30 use Illuminate\Support\Facades\Storage; 31 use Illuminate\Database\Eloquent\Builder; 32 use Illuminate\Http\Request; 33 use Illuminate\Support\Collection; 34 use InvalidArgumentException; 35 use Livewire\TemporaryUploadedFile; 36 37 class DocumentService 38 { 39 public static function generateFile( 40 DocumentType $type, 41 Revision $revision, 42 ?int $notificatedReportId = null, 43 ?int $certificateId = null, 44 ?bool $convertToPdf = true, 45 array $excludeTokens = [] 46 ) { 47 $docGenName = $revision->template->getClassDocGen(); 48 $document = $revision->document; 49 50 if ($type->code === 'INF') { 51 if ($document->original->related_type == Validation::class) { 52 $document = $document->revisions()->where('related_type', Validation::class)->latest()->first()?->related; 53 } 54 } 55 56 if ($type->code === 'NOT') { 57 $document = Notification::create([ 58 'dossier_id' => $revision->document->dossier->id, 59 'revision_id' => $revision->id, 60 'notificated_report_id' => $notificatedReportId 61 ]); 62 $revision->update(['notification_id' => $notificatedReportId]); 63 } 64 65 $docGen = new $docGenName($document, $certificateId); 66 return $docGen->save($revision->path, $convertToPdf, excludeTokens: $excludeTokens); 67 } 68 69 private static function getGenerationData(bool $new, ?Document $document = null, ?Template $template = null, ?Dossier $dossier = null, ?DocumentType $type = null, ?bool $convertToPdf = true): array 70 { 71 $explodedTemplateName = !$new 72 ? explode('.', $document->head->template->filename) 73 : explode('.', $template->filename); 74 75 $explodedFilename = explode('-v', $explodedTemplateName[0]); 76 77 $version = !$new ? $document->head->version + 1 : 1; 78 $name = !$new ? $explodedFilename[0] . '-v' . $version : $template->name; 79 $filename = !$new ? $name . '.' . $explodedTemplateName[1] : $template->filename; 80 81 if (!$document) { 82 $number = Document::where('type_id', $type->id)->withTrashed()->max('number') + 1; 83 } 84 85 $path = 'tmp/' . uniqid() . '/' . $filename; 86 if (Str::endsWith($path, '.docx') && $convertToPdf) { 87 //we are forcing pdf conversion 88 $path = substr($path, 0, -4) . 'pdf'; 89 } 90 91 return [ 92 'version' => $version, 93 'name' => $name . '.' . DocumentService::getExtensions($path), 94 'path' => $path 95 ]; 96 } 97 98 public static function generateRevision(Document $document, array $data, int $templateId, ?bool $is_draft = null): Revision 99 { 100 $revision = new Revision(); 101 102 $revision->document_id = $document->id; 103 $revision->template_id = $templateId; 104 $revision->created_by = Auth::id() ?? 1; 105 $revision->path = $data['path']; 106 $revision->name = $data['name']; 107 $revision->version = $data['version']; 108 $revision->description = $data['description']; 109 $revision->is_draft = $is_draft ?? false; 110 111 $revision->save(); 112 113 $document->update([ 114 'head_id' => $revision->id 115 ]); 116 117 return $revision; 118 } 119 120 private static function getNewRevisionPath(Revision $revision): string 121 { 122 $extension = pathinfo($revision->path, PATHINFO_EXTENSION); 123 $templateName = $revision->template->name; 124 $newPath = 'tmp/' . uniqid() . '/' . $templateName . '.' . $extension; 125 return $newPath; 126 } 127 128 public static function createRevision(Document $document): Revision 129 { 130 $version = upgrade_version($document->head->version); 131 $revision = $document->revisions()->create([ 132 'document_id' => $document->id, 133 'created_by' => Auth::id(), 134 'template_id' => $document->head->template_id, 135 'from' => 'internal', 136 'name' => $document->head->name, 137 'path' => self::getNewRevisionPath($document->head), 138 'hash' => '', 139 'size' => 0, 140 'version' => $version, 141 'previous_id' => $document->head->id, 142 ]); 143 144 if ($document->head->related) { 145 $revision->related()->associate($document->head->related); 146 $revision->save(); 147 } 148 149 $document->update([ 150 'head_id' => $revision->id 151 ]); 152 153 $document->fresh(); 154 155 return $revision; 156 } 157 158 public static function makeReviewed(Document $document): Revision 159 { 160 $revision = self::createRevision($document); 161 $revision->makeReviewed(); 162 self::generateFile($document->type, $revision); 163 164 $revision->update([ 165 'hash' => md5_file(Storage::path($revision->path)), 166 'size' => Storage::fileSize($revision->path), 167 'is_pending' => false, 168 ]); 169 $revision->moveToFilesystem(); 170 171 return $revision; 172 } 173 174 public static function makeApproved(Document $document): Revision 175 { 176 $newRevision = self::createRevision($document); 177 $newRevision->makeApproved(); 178 $document->refresh(); 179 180 self::generateFile($document->type, $newRevision, convertToPdf: true); 181 182 $newRevision->update([ 183 'hash' => md5_file(Storage::path($newRevision->path)), 184 'size' => Storage::fileSize($newRevision->path), 185 'is_pending' => false, 186 ]); 187 $newRevision->moveToFilesystem(); 188 $document->refresh(); 189 190 $revision = self::sign($document); 191 $revision->makeApproved(); 192 193 return $revision; 194 } 195 196 public static function removeDraft(Document $document) 197 { 198 $revision = self::createRevision($document); 199 self::generateFile($document->type, $revision); 200 201 202 $revision->update([ 203 'hash' => md5_file(Storage::path($revision->path)), 204 'size' => Storage::fileSize($revision->path), 205 'is_draft' => false, 206 'is_pending' => false, 207 ]); 208 $document->refresh(); 209 event(new DocumentGenerated($document)); 210 $revision->moveToFilesystem(); 211 return $revision; 212 } 213 214 public static function sign(Document $document): Revision 215 { 216 $gpgService = new GpgService(); 217 218 $toSign = $document->head; 219 220 $revision = self::createRevision($document); 221 $user = auth()->user(); 222 $keyPassPhrase = $user->activeKey->key_passphrase; 223 224 Storage::makeDirectory(dirname($revision->path)); 225 $path = $gpgService->signFile($user, $keyPassPhrase, $toSign, $revision->path); 226 227 $revision->update([ 228 'path' => $path, 229 'name' => basename($path), 230 'hash' => md5_file(Storage::path($path)), 231 'size' => Storage::fileSize($path), 232 'is_pending' => false, 233 ]); 234 235 $document->update([ 236 'head_id' => $revision->id 237 ]); 238 239 $revision->moveToFilesystem(); 240 241 return $revision; 242 } 243 244 /** 245 * @throws Exception 246 */ 247 public static function addNewRevision(int $documentId, ?string $description = '', bool $isDraft = false) 248 { 249 $document = Document::findOrFail($documentId); 250 $type = $document->type; 251 252 if ($type->code === 'EXT' || $type->code === 'EML') { 253 throw new Exception(__('documents.notifications.update.error.message')); 254 } 255 256 $data = self::getGenerationData(false, $document, convertToPdf: false); 257 258 try { 259 DB::beginTransaction(); 260 261 $data['description'] = $description; 262 263 $revision = self::generateRevision($document, $data, $document->head->template_id, $isDraft); 264 $path = $revision->path; 265 266 $notificatedReportId = $document->head->notifications->first() 267 ? $document->head->notifications->first()->notificated_report_id 268 : null; 269 270 $document->update([ 271 'head_id' => $revision->id 272 ]); 273 274 self::generateFile($type, $revision, $notificatedReportId, convertToPdf: false); 275 276 $revision->update([ 277 'hash' => md5_file(Storage::path($path)), 278 'size' => Storage::size($path), 279 ]); 280 281 DB::commit(); 282 return true; 283 } catch (Exception $e) { 284 DB::rollBack(); 285 Storage::delete($data['path']); 286 log_exception($e); 287 288 throw $e; 289 } 290 } 291 292 public static function createWithTemplate( 293 array $data, 294 ?int $userId = null, 295 ?int $notificatedReportId = null, 296 ?int $certificateId = null, 297 ?bool $convertToPdf = true, 298 array $excludeTokens = [] 299 ): Document { 300 $type = DocumentType::findOrFail($data['type_id']); 301 $template = Template::findOrFail($data['template_id']); 302 $dossier = Dossier::findOrFail($data['dossier_id']); 303 304 if ($type->code === 'EXT' || $type->code === 'EML') { 305 throw new Exception(__('documents.notifications.create.error.message')); 306 } 307 308 $revisionData = self::getGenerationData(true, null, $template, $dossier, $type, $convertToPdf); 309 310 try { 311 DB::beginTransaction(); 312 313 $document = Document::create([ 314 'type_id' => $type->id, 315 'dossier_id' => $dossier->id, 316 'created_by' => $userId ?? Auth::id(), 317 'from' => 'internal', 318 'meet_id' => $data['meet_id'], 319 'template_id' => $template->id, 320 ]); 321 322 $revisionData['description'] = $data['description'] ?? ''; 323 324 $revision = self::generateRevision($document, $revisionData, $template->id, $data['is_draft'] ?? false); 325 326 $document->original_id = $revision->id; 327 $document->save(); 328 329 self::generateFile($type, $revision, $notificatedReportId, $certificateId, $convertToPdf, $excludeTokens); 330 $revision->update([ 331 'hash' => md5_file(Storage::path($revision->path)), 332 'size' => Storage::size($revision->path) 333 ]); 334 335 DB::commit(); 336 return $document; 337 } catch (Exception $e) { 338 DB::rollBack(); 339 Storage::delete($revisionData['path']); 340 341 throw $e; 342 } 343 } 344 345 public static function searchRevisionInDocument(int $documentId, string $searchString, string $sortField, string $sortDirection) 346 { 347 $documentQuery = Revision::where('document_id', $documentId) 348 ->where('name', 'like', '%' . $searchString . '%'); 349 350 return $sortField && $sortDirection 351 ? $documentQuery->orderBy($sortField, $sortDirection) 352 : $documentQuery; 353 } 354 355 public static function searchInDossier(int $dossierId, string $searchString, string $sortField, string $sortDirection, string $folderId = null) 356 { 357 $documentQuery = Document::where('dossier_id', $dossierId) 358 ->where('name', 'like', '%' . $searchString . '%') 359 ->when($folderId, function ($query) use ($folderId) { 360 return $query->where('folder_id', $folderId); 361 }); 362 363 return $sortField && $sortDirection 364 ? $documentQuery->orderBy($sortField, $sortDirection) 365 : $documentQuery; 366 } 367 368 public static function deleteRevision(int $id) 369 { 370 return Revision::findOrFail($id)->delete(); 371 } 372 373 /** 374 * Display a listing of the resource. 375 */ 376 public static function find(int $id): Document|Builder|Model 377 { 378 return Document::where('id', $id)->first(); 379 } 380 381 /** 382 * Search resources 383 */ 384 public static function search(bool $isInternal, string $searchString, string $sortField, string $sortDirection, array|null $filters, ?User $authUser = null): Document|Builder 385 { 386 $query = Document::filterByUser($authUser); 387 388 $query->whereHas('type', function ($q) use ($isInternal) { 389 $q->where('internal', $isInternal); 390 }); 391 392 $query->when($filters && $filters['withTrashed'], fn($query) => $query->withTrashed()); 393 394 $query 395 ->where(function ($query) use ($searchString) { 396 $query 397 ->where('name', 'like', '%' . $searchString . '%') 398 ->orWhere('number', 'like', '%' . $searchString . '%') 399 ->orWhere('description', 'like', '%' . $searchString . '%') 400 ->orWhere('original_name', 'like', '%' . $searchString . '%') 401 ->orWhereHas('head', function ($query) use ($searchString) { 402 $query->where('name', 'like', '%' . $searchString . '%'); 403 }); 404 }) 405 ->when($filters && !empty($filters['types']), fn($query) => $query->whereIn('type_id', $filters['types'])) 406 ->when($filters && $filters['created_at_since'], fn($query) => $query->where('created_at', '>=', $filters['created_at_since'])) 407 ->when($filters && $filters['created_at_to'], fn($query) => $query->where('created_at', '<=', $filters['created_at_to'])) 408 ->when($filters && $filters['user_by'], function ($query) use ($filters) { 409 /**Search by created_by, reviewed_by or approved_by */ 410 $query->where(function ($query) use ($filters) { 411 $query->whereHas('createdBy', function ($query) use ($filters) { 412 $query->where('name', 'like', '%' . $filters['user_by'] . '%'); 413 })->orWhereHas('head', function ($query) use ($filters) { 414 $query->whereHas('reviewedBy', function ($query) use ($filters) { 415 $query->where('name', 'like', '%' . $filters['user_by'] . '%'); 416 })->orWhereHas('approvedBy', function ($query) use ($filters) { 417 $query->where('name', 'like', '%' . $filters['user_by'] . '%'); 418 }); 419 }); 420 }); 421 }) 422 ->when($filters && $filters['reviewed_at_since'], function ($query) use ($filters) { 423 $query->whereHas('head', function ($query) use ($filters) { 424 $query->where('reviewed_at', '>=', $filters['reviewed_at_since']); 425 }); 426 }) 427 ->when($filters && $filters['reviewed_at_to'], function ($query) use ($filters) { 428 $query->whereHas('head', function ($query) use ($filters) { 429 $query->where('reviewed_at', '<=', $filters['reviewed_at_to']); 430 }); 431 }) 432 ->when($filters && $filters['approved_at_since'], function ($query) use ($filters) { 433 $query->whereHas('head', function ($query) use ($filters) { 434 $query->where('approved_at', '>=', $filters['approved_at_since']); 435 }); 436 }) 437 ->when($filters && $filters['approved_at_to'], function ($query) use ($filters) { 438 $query->whereHas('head', function ($query) use ($filters) { 439 $query->where('approved_at', '<=', $filters['approved_at_to']); 440 }); 441 }); 442 443 444 if ($sortField && $sortDirection) { 445 $query->orderBy($sortField, $sortDirection); 446 } 447 448 return $query; 449 } 450 451 public static function getRevisions(array|null $filters, Document $document) 452 { 453 return $document->revisions()->latest() 454 ->when($filters, fn($query) => $query->where(function ($query) use ($filters) { 455 $query->where('name', 'like', '%' . $filters['search'] . '%') 456 ->orWhere('version', 'like', '%' . $filters['search'] . '%') 457 ->orWhere('description', 'like', '%' . $filters['search'] . '%'); 458 }) 459 ->when($filters['created_at_since'], fn($query) => $query->where('created_at', '>=', $filters['created_at_since'])) 460 ->when($filters['created_at_to'], fn($query) => $query->where('created_at', '<=', $filters['created_at_to'])) 461 ->when($filters['user_by'], fn($query) => $query->where(function ($query) use ($filters) { 462 $query->whereHas('createdBy', function ($query) use ($filters) { 463 $query->where('name', 'like', '%' . $filters['user_by'] . '%'); 464 })->orWhereHas('reviewedBy', function ($query) use ($filters) { 465 $query->where('name', 'like', '%' . $filters['user_by'] . '%'); 466 })->orWhereHas('approvedBy', function ($query) use ($filters) { 467 $query->where('name', 'like', '%' . $filters['user_by'] . '%'); 468 }); 469 })) 470 ->when($filters['reviewed_at_since'], function ($query) use ($filters) { 471 $query->where('reviewed_at', '>=', $filters['reviewed_at_since']); 472 }) 473 ->when($filters['reviewed_at_to'], function ($query) use ($filters) { 474 $query->where('reviewed_at', '<=', $filters['reviewed_at_to']); 475 }) 476 ->when($filters['approved_at_since'], function ($query) use ($filters) { 477 $query->where('approved_at', '>=', $filters['approved_at_since']); 478 }) 479 ->when($filters['approved_at_to'], function ($query) use ($filters) { 480 $query->where('approved_at', '<=', $filters['approved_at_to']); 481 }) 482 ->when($filters['withTrashed'], fn($query) => $query->withTrashed())) 483 ->orderBy('id', 'desc'); 484 } 485 486 /** 487 * Create a resource 488 * 489 * @throws Throwable 490 */ 491 public static function create(array $data): Document|Exception 492 { 493 try { 494 DB::beginTransaction(); 495 496 if ($data['isInternal']) { 497 $data['from'] = 'internal'; 498 } else { 499 $data['from'] = 'external'; 500 } 501 502 $doc = Document::create($data); 503 $doc->commit($data); 504 $doc->original_id = $doc->head_id; 505 $doc->save(); 506 507 DB::commit(); 508 509 return $doc; 510 } catch (Exception $e) { 511 DB::rollBack(); 512 throw new Exception($e); 513 } 514 } 515 516 public static function GenerateCopyAsSignedPdf(Document $doc): Document 517 { 518 $path = DocumentDownloader::docxToSignedPdf($doc->head->path); 519 $name = basename($path); 520 521 return self::createOrVersionate([ 522 'name' => $name, 523 'revision_name' => $name, 524 'hash' => md5_file(Storage::path($path)), 525 'size' => Storage::size($path), 526 'isInternal' => $doc->isInternal(), 527 'dossier_id' => $doc->dossier_id, 528 'folder_id' => $doc->folder_id, 529 'type_id' => $doc->type_id, 530 'created_by' => Auth::id(), 531 'from' => $doc->isInternal() ? 'internal' : 'external', 532 'path' => $path, 533 'active' => true, 534 'description' => '', 535 'is_template' => $doc->head->is_template 536 ]); 537 } 538 539 public static function createOrVersionate(array $data): Document 540 { 541 $doc = self::findByDataArray($data); 542 if ($doc) { 543 self::addRevision($doc->id, $data['path']); 544 } else { 545 $doc = self::create($data); 546 } 547 548 return $doc->refresh(); 549 } 550 551 /** 552 * Find a document from the data array used for creation 553 * @param $data 554 * @return Document|null 555 */ 556 public static function findByDataArray($data) 557 { 558 return Document::where('dossier_id', $data['dossier_id']) 559 ->where('folder_id', $data['folder_id']) 560 ->where('name', $data['name']) 561 ->first(); 562 } 563 564 public static function addRevision($document_id, $data, $version = null): Revision 565 { 566 try { 567 DB::beginTransaction(); 568 $doc = Document::findOrFail($document_id); 569 $revision = $doc->commit([ 570 'path' => $data['path'], 571 'revision_name' => $data['name'], 572 'version' => $version, 573 'from' => $doc->isExternal() ? 'external' : 'internal', 574 'related' => $data['related'] ?? $doc->head->related, 575 'description' => $data['description'] ?? '', 576 'is_template' => $data['is_template'] ?? false, 577 ]); 578 $revision->moveToFilesystem(); 579 DB::commit(); 580 581 return $revision; 582 } catch (Exception $e) { 583 DB::rollBack(); 584 log_exception($e); 585 throw new Exception(__('documents.notifications.update.error.message')); 586 } 587 } 588 589 /** 590 * Update a document description 591 * 592 * @throws Throwable 593 */ 594 public static function update(int $id, string $description): void 595 { 596 DB::beginTransaction(); 597 598 try { 599 Document::findOrFail($id)?->head?->update([ 600 'description' => $description, 601 ]); 602 603 DB::commit(); 604 } catch (Exception $e) { 605 DB::rollBack(); 606 throw new Exception(__('documents.notifications.update.error.message')); 607 } 608 } 609 610 /** 611 * Update a revision name and description 612 * 613 * @throws Throwable 614 */ 615 public static function updateRevision(int $id, string $name, ?string $description): void 616 { 617 DB::beginTransaction(); 618 619 try { 620 $revision = Revision::findOrFail($id); 621 $revision->update([ 622 'name' => $name, 623 'description' => $description, 624 ]); 625 626 DB::commit(); 627 } catch (Exception $e) { 628 DB::rollBack(); 629 throw new Exception(__('documents.notifications.update.error.message')); 630 } 631 } 632 633 /** 634 * Destroy a resource 635 * 636 * @return true 637 * 638 * @throws Throwable 639 */ 640 public static function remove(int $id): bool 641 { 642 try { 643 DB::beginTransaction(); 644 Document::destroy($id); 645 DB::commit(); 646 647 return true; 648 } catch (Exception $e) { 649 DB::rollBack(); 650 throw new Exception(__('documents.notifications.remove.error.message')); 651 } 652 } 653 654 public static function removeRevision(int $id): bool 655 { 656 try { 657 $revision = Revision::findOrFail($id); 658 659 if ($revision->document->revisions->count() === 1) { 660 $documentId = $revision->document->id; 661 $dossier = $revision->document?->dossier; 662 Document::destroy($documentId); 663 //Revision::destroy($id); 664 665 auth()->user()->hasPermissionTo('can_read_documents') ? redirect()->route('documents.index') : redirect()->route('dossiers.dossier.view', ['dossier' => $dossier]); 666 return true; 667 } 668 669 //Comprobamos que la versión que borramos sea la ultima 670 // para actualizar y mostrar correctamente los documentos en el index 671 if ($revision->document->head_id === $id && !is_null($revision->previous_id)) { 672 $revision->document()->update(['head_id' => $revision->previous_id]); 673 } 674 675 Revision::destroy($id); 676 return true; 677 } catch (Exception $e) { 678 logger()->error($e); 679 throw new Exception(__('documents.notifications.remove.error.message')); 680 } 681 } 682 683 public static function hardDeleteRevision(int $id): void 684 { 685 try { 686 DB::beginTransaction(); 687 688 $revision = Revision::withTrashed()->find($id); 689 690 Storage::delete($revision->path); 691 $revision->forceDelete(); 692 693 DB::commit(); 694 } catch (Exception $e) { 695 DB::rollBack(); 696 logger()->error($e); 697 throw new Exception(__('documents.notifications.remove.error.message')); 698 } 699 } 700 701 /** 702 * Restore a resource 703 * 704 * @return true 705 * 706 * @throws Throwable 707 */ 708 public static function restore(int $id): bool 709 { 710 try { 711 DB::beginTransaction(); 712 Document::withTrashed()->find($id)->restore(); 713 //Revision::withTrashed()->where('document_id', $id)->restore(); 714 DB::commit(); 715 716 return true; 717 } catch (Exception $e) { 718 DB::rollBack(); 719 throw new Exception(__('documents.notifications.remove.error.message')); 720 } 721 } 722 723 public static function moveToFolder(int $documentId, int $folderId) 724 { 725 try { 726 DB::beginTransaction(); 727 $document = Document::findOrFail($documentId); 728 $document->folder_id = $folderId; 729 $document->save(); 730 DB::commit(); 731 return true; 732 } catch (Exception $e) { 733 DB::rollBack(); 734 throw new Exception(__('documents.notifications.update.error.message')); 735 } 736 } 737 738 public static function uploadToFolder(TemporaryUploadedFile $file, Folder $folder, string $description, int $typeId): int 739 { 740 $name = $file->getClientOriginalName(); 741 $dossier = $folder->dossier; 742 743 $path = $file->storeAs('tmp/' . uniqid() . '/' . $name); 744 $document = DocumentService::create([ 745 'type_id' => $typeId ? $typeId : DocumentType::where('code', 'EXT')->first()->id, 746 'file' => $file, 747 'revision_name' => $name, 748 'isInternal' => false, 749 'description' => $description, 750 'path' => $path, 751 'dossier_id' => $dossier->id ?? null, 752 'created_by' => Auth::id(), 753 'folder_id' => $folder->id, 754 'hash' => '', 755 'size' => 0 756 ]); 757 758 $fixedPath = $document->head->moveToFilesystem(); 759 760 $document->head->update([ 761 'hash' => md5_file(Storage::path($fixedPath)), 762 'size' => Storage::size($fixedPath) 763 ]); 764 return $document->id; 765 } 766 767 private static function createTaskForRoleNeeded(Document $document, ?int $roleId, string $taskTitleFunction) 768 { 769 if (!in_array($document->type->code, ['INF', 'NOT', 'ACT', 'RES'])) { 770 return; 771 } 772 773 TaskService::$taskTitleFunction($roleId, $document->dossier->stage->id, $document->head->name, $document->head?->id); 774 } 775 776 777 public static function createApprovalTask(Document $document) 778 { 779 self::createTaskForRoleNeeded($document, $document->template->approver_role_id, 'createApprovalTask'); 780 } 781 782 /** 783 * Search templates 784 * @param string $searchString 785 * @param string $sortField 786 * @param string $sortDirection 787 * @param array|null $filters 788 * @return Collection<array-key, mixed> 789 * @throws InvalidArgumentException 790 */ 791 public static function searchTemplates(string $searchString, string $sortField, string $sortDirection, array|null $filters) 792 { 793 $codes = DocumentType::whereIn('code', ['INF', 'NOT', 'ACT'])->pluck('id'); 794 795 $templatesQuery = Template::query(); 796 797 $templatesQuery 798 ->where(function ($query) use ($searchString) { 799 $query->where('name', 'like', '%' . $searchString . '%'); 800 }); 801 802 if ($filters && $filters['reviewer_roles']) { 803 $templatesQuery->whereIn('reviewer_role_id', $filters['reviewer_roles']); 804 } 805 806 if ($filters && $filters['approver_roles']) { 807 $templatesQuery->whereIn('approver_role_id', $filters['approver_roles']); 808 } 809 810 if ($sortField && $sortDirection) { 811 $templatesQuery->orderBy($sortField, $sortDirection); 812 } 813 814 $templates = $templatesQuery->get()->filter(function ($template) { 815 return Storage::exists($template->path); //Ciertas plantillas no existen 816 }); 817 818 return $templates->map(function ($template) use ($codes) { 819 if ($codes->contains($template->document_type_id)) { 820 $template->isEdit = true; 821 } 822 return $template; 823 }); 824 } 825 826 public static function getDossierDocumentsFilter(int $dossierId, Request $request): Collection 827 { 828 $documents = Document::where('dossier_id', $dossierId) 829 ->with('head', fn($query) => $query->select(['id', 'name', 'description'])) 830 ->orderBy('name') 831 ->select(['id', 'name', 'head_id']) 832 ->when( 833 $request->exists('search'), 834 fn($query) => $query 835 ->where(function ($query) use ($request) { 836 $query 837 ->where('name', 'like', "%{$request->search}%") 838 ->orWhereHas( 839 'head', 840 fn($query) => $query->where('name', 'like', "%{$request->search}%") 841 ); 842 }) 843 )->when( 844 $request->exists('selected'), 845 fn($query) => $query->whereIn('id', $request->selected) 846 )->get() ?? collect(); 847 848 return $documents->map(function ($document) use ($request) { 849 $description = '<strong>' . $document->head->name . '</strong><br>' . $document->head->description; 850 return [ 851 'id' => $request->exists('is_revision') && $request->input('is_revision') ? $document->head_id : $document->id, 852 'name' => $document->name, 853 'description' => $description, 854 ]; 855 }); 856 } 857 858 public static function createTemplate($data) 859 { 860 try { 861 DB::beginTransaction(); 862 863 Template::create($data); 864 865 DB::commit(); 866 } catch (Exception $e) { 867 DB::rollBack(); 868 Storage::delete($data['path']); 869 log_exception($e); 870 throw new Exception(__('documents.templates.notifications.create.error.message')); 871 } 872 } 873 874 public static function uploadDocx(Document $document, $data) 875 { 876 try { 877 DB::beginTransaction(); 878 $version = upgrade_version($document->head->version); 879 $parts = explode('v', $document->head->name); 880 unset($parts[count($parts) - 1]); 881 $parts = implode('v', $parts); 882 $revisionName = $parts . 'v' . $version . '.' . DocumentService::getExtensions($data['file']->getClientOriginalName()); 883 884 $path = 'tmp/' . uniqid() . '/' . $revisionName; 885 Storage::putFileAs($path, $data['file'], $revisionName); 886 887 $path .= '/' . $revisionName; 888 889 $revision = $document->commit( 890 [ 891 'path' => $path, 892 'name' => $revisionName, 893 'revision_name' => $revisionName, 894 'description' => $data['description'], 895 'from' => $document->head->from, 896 'created_by' => Auth::id(), 897 'template_id' => $document->head->template_id, 898 'is_template' => true, 899 'version' => $version, 900 ] 901 ); 902 903 //Actualizamos el is_peding a false 904 $revision->update([ 905 'is_pending' => false, 906 'is_draft' => $document->head->is_draft 907 ]); 908 909 $document->refresh(); 910 $revision->moveToFilesystem(); 911 912 DB::commit(); 913 } catch (Exception $e) { 914 DB::rollBack(); 915 Storage::delete($path); 916 log_exception($e); 917 throw new Exception(__('documents.notifications.upload.error.message')); 918 } 919 } 920 921 public static function createPDF(Document $document) 922 { 923 try { 924 DB::beginTransaction(); 925 926 // Prepare new version data 927 $path = 'tmp/' . uniqid() . '/' . $document->name . '.pdf'; 928 $filename = explode('.', $document->head->name)[0] . '.pdf'; 929 930 // En el caso en el que haya tareas asociadas a la revisión, deberemos asociarlas a la revisión que se va a generar. Esto hará que el flujo de creación y cerrado automático de tareas no se rompa. 931 $tasks = $document->head->task; 932 933 // Creamos la revision antes de generar el pdf! 934 $revision = $document->commit( 935 [ 936 'path' => $path, 937 'name' => $filename, 938 'revision_name' => $filename, 939 'description' => $document->head?->description, 940 'from' => $document->head->from ?? null, 941 'created_by' => Auth::id(), 942 'template_id' => $document->head->template_id ?? null, 943 'is_template' => false, 944 'version' => upgrade_version($document->getLatestRevision()->version), 945 'hash' => "", 946 'size' => 0, 947 ] 948 ); 949 950 $revision->update([ 951 'is_pending' => false, 952 'is_draft' => $document->head->is_draft 953 ]); 954 955 $revision->task()->saveMany($tasks); 956 957 $document->refresh(); 958 959 // Converting to pdf 960 if ($document->head->template) { 961 $path = self::generateFile($document->type, $document->head, convertToPdf: true); 962 } else { 963 $path = DocxToPdfConverter::convert($document->head->previous->path, $document->head->path); 964 } 965 966 $revision->update([ 967 'hash' => md5_file(Storage::path($path)), 968 'size' => Storage::fileSize($path), 969 ]); 970 971 $revision->moveToFilesystem(); 972 DB::commit(); 973 } catch (Exception $e) { 974 DB::rollBack(); 975 Storage::delete($path); 976 log_exception($e); 977 throw $e; 978 throw new Exception(__('documents.notifications.create_pdf.error.message')); 979 } 980 } 981 982 public static function getExtensions(string $path) 983 { 984 $parts = explode('.', basename($path)); 985 array_shift($parts); 986 $extensions = implode('.', $parts); 987 988 return $extensions; 989 } 990 991 public static function getRevisionCorrectPath(Revision $revision) 992 { 993 $dossier = $revision->document->dossier; 994 $root = $dossier 995 ? 'dossiers/' . $dossier->code 996 : 'common'; 997 998 $documentCode = $revision->document->name; // has zerofill 999 $version = $revision->version; 1000 1001 $extensions = self::getExtensions($revision->path); 1002 $filename = $revision->document->name . '.' . $extensions; 1003 1004 return "$root/$documentCode/$version/$filename"; 1005 } 1006 }