Document.php
1 <?php 2 3 namespace App\Models; 4 5 use App\Http\Services\DocumentService; 6 use Exception; 7 use Illuminate\Database\Eloquent\Factories\HasFactory; 8 use Illuminate\Database\Eloquent\Model; 9 use Illuminate\Database\Eloquent\Relations\HasManyThrough; 10 use Illuminate\Database\Eloquent\SoftDeletes; 11 use Illuminate\Support\Facades\Auth; 12 use Illuminate\Support\Facades\Storage; 13 use Illuminate\Support\Facades\URL; 14 use Spatie\Activitylog\LogOptions; 15 use Spatie\Activitylog\Traits\LogsActivity; 16 17 class Document extends Model 18 { 19 use HasFactory, LogsActivity, SoftDeletes; 20 21 protected $guarded = []; 22 23 protected $dates = ['signed_at']; 24 25 /* 26 * Creates a document and its revision 27 */ 28 public static function create(array $data = []) 29 { 30 if (isset($data['dossier_id']) && isset($data['stage_id'])) { 31 // Check if stage_id is valid. 32 Stage::where('dossier_id', $data['dossier_id']) 33 ->where('id', $data['stage_id']) 34 ->firstOrFail(); 35 } 36 37 $number = Document::where('type_id', '=', $data['type_id'])->withTrashed()->max('number') + 1; 38 $type = DocumentType::find($data['type_id']); 39 40 $document = static::query()->create([ 41 'original_name' => array_key_exists('original_name', $data) ? $data['original_name'] : null, 42 'type_id' => $data['type_id'], 43 'number' => $number, 44 'dossier_id' => $data['dossier_id'] ?? null, 45 'stage_id' => $data['stage_id'] ?? null, 46 'name' => $type->code . '-' . str_pad($number, 4, '0', STR_PAD_LEFT), 47 'from' => $data['from'] ?? null, 48 'meet_id' => $data['meet_id'] ?? null, 49 'created_by' => $data['created_by'] ?? Auth::id(), 50 'description' => array_key_exists('description', $data) ? $data['description'] : null, 51 'folder_id' => $data['folder_id'] ?? null, 52 'template_id' => $data['template_id'] ?? null, 53 'eml_template_id' => $data['eml_template_id'] ?? null, 54 ]); 55 56 $document->save(); 57 58 return $document; 59 } 60 61 public function isExternal() 62 { 63 return $this->type->internal ? false : true; 64 } 65 66 public function isInternal() 67 { 68 return $this->type->internal == true; 69 } 70 71 public function reviews() 72 { 73 return $this->hasMany(Review::class, 'revision_id'); 74 } 75 76 public function review() 77 { 78 return $this->reviews()->where('type', 'review'); 79 } 80 81 /** 82 * @return \Illuminate\Database\Eloquent\Relations\HasMany|\LaravelIdea\Helper\App\Models\_IH_Review_QB 83 */ 84 public function approval() 85 { 86 return $this->reviews()->where('type', 'approval'); 87 } 88 89 public function lastReview() 90 { 91 return $this->hasOne(Review::class, 'revision_id') 92 ->where('type', 'review') 93 ->latest(); 94 } 95 96 public function lastApproval() 97 { 98 return $this->hasOne(Review::class, 'revision_id') 99 ->where('type', 'approval') 100 ->latest(); 101 } 102 103 public function template() 104 { 105 return $this->belongsTo(Template::class); 106 } 107 108 public function isReviewed() 109 { 110 return $this->review->first()?->has_passed ?? false; 111 } 112 113 public function isApproved() 114 { 115 return $this->approval->first()?->has_passed ?? false; 116 } 117 118 public function hasReview() 119 { 120 return $this->review->isNotEmpty(); 121 } 122 123 public function hasApproval() 124 { 125 return $this->approval->isNotEmpty(); 126 } 127 128 public function isDraft(): bool 129 { 130 return $this->head->is_draft ?? false; 131 } 132 133 public function outputs(): HasManyThrough 134 { 135 return $this->hasManyThrough( 136 OutPut::class, 137 Revision::class, 138 'document_id', // Foreign key on revisions table 139 'id', // Foreign key on outputs table 140 'id', // Local key on documents table 141 'id' // Local key on revisions table 142 ); 143 } 144 145 /* 146 * Updates a document by adding a new revision 147 */ 148 public function commit(array $data = []) 149 { 150 $explodedFilename = explode('/', $data['path']); 151 $revision = Revision::create([ 152 'path' => $data['path'], 153 'previous_id' => $this->head_id, 154 'document_id' => $this->id, 155 'name' => $data['revision_name'] ?? end($explodedFilename), 156 'description' => $data['description'] ?? '', 157 'hash' => $data['hash'] ?? md5_file(Storage::path($data['path'])), 158 'version' => array_key_exists('version', $data) && $data['version'] ? $data['version'] : count($this->revisions) + 1, 159 'size' => $data['size'] ?? Storage::size($data['path']), 160 'from' => array_key_exists('from', $data) ? $data['from'] : null, 161 'created_by' => $data['created_by'] ?? Auth::id(), 162 'approved_by' => null, 163 'template_id' => array_key_exists('template_id', $data) ? $data['template_id'] : null, 164 'is_template' => $data['is_template'] ?? false, 165 ]); 166 167 if ($data['related'] ?? null) { 168 $revision->related()->associate($data['related']); 169 $revision->save(); 170 } 171 172 $this->head_id = $revision->id; 173 $this->save(); 174 175 return $revision; 176 } 177 178 public function isJson() 179 { 180 return is_numeric($this->head->path); 181 } 182 183 public function folder() 184 { 185 return $this->belongsTo(Folder::class); 186 } 187 188 public function changeApproved(array $data = []) 189 { 190 $this->update([ 191 'approved' => $data['approved'], 192 'approved_by' => Auth::user()->id, 193 ]); 194 195 $revision = $this->head; 196 197 $revision->update([ 198 'approved' => $data['approved'], 199 'approved_by' => Auth::user()->id, 200 ]); 201 202 return $this->approved; 203 } 204 205 public function type() 206 { 207 return $this->belongsTo(DocumentType::class); 208 } 209 210 public function head() 211 { 212 return $this->belongsTo(Revision::class, 'head_id'); 213 } 214 215 public function getLatestRevision() 216 { 217 return $this->allRevisions->sortByDesc('version')->first(); 218 } 219 220 public function original() 221 { 222 return $this->belongsTo(Revision::class, 'original_id'); 223 } 224 225 public function revisions() 226 { 227 return $this->hasMany(Revision::class); 228 } 229 230 public function allRevisions() 231 { 232 return $this->hasMany(Revision::class)->withTrashed(); 233 } 234 235 public function hasDeletedRevisions() 236 { 237 return $this->revisions->count() !== $this->allRevisions->count(); 238 } 239 240 public function dossier() 241 { 242 return $this->belongsTo(Dossier::class); 243 } 244 245 public function stage() 246 { 247 return $this->belongsTo(Stage::class); 248 } 249 250 public function meet() 251 { 252 return $this->belongsTo(Meet::class); 253 } 254 255 public function documentLogs() 256 { 257 return $this->hasMany(DocumentLog::class); 258 } 259 260 public function validations() 261 { 262 return $this->hasManyThrough( 263 Revision::class, 264 Validation::class, 265 'revision_id', 266 'document_id', 267 'id', 268 'id' 269 ); 270 } 271 272 public function shouldApprove() 273 { 274 return $this->type->approve == true; 275 } 276 277 public function shouldValidate() 278 { 279 return $this->type->validate == true; 280 } 281 282 public function roles() 283 { 284 return $this->belongsToMany(Role::class, 'document_role', 'document_id', 'role_id'); 285 } 286 287 public function approveRoles() 288 { 289 return $this->roles()->where('can_approve', true); 290 } 291 292 public function reviewRoles() 293 { 294 return $this->roles()->where('can_review', true); 295 } 296 297 public function readRoles() 298 { 299 return $this->roles()->where('can_read', true); 300 } 301 302 public function writeRoles() 303 { 304 return $this->roles()->where('can_write', true); 305 } 306 307 public function roleCanRead($user) 308 { 309 $user_roles = $user->roles; 310 $document_roles = $this->roles()->wherePivot('can_read', true)->get(); 311 312 foreach ($user_roles as $user_role) { 313 if ($document_roles->contains($user_role)) { 314 return true; 315 } 316 } 317 318 return false; 319 } 320 321 public function roleCanWrite($user) 322 { 323 $user_roles = $user->roles; 324 $document_roles = $this->roles()->wherePivot('can_write', true)->get(); 325 326 foreach ($user_roles as $user_role) { 327 if ($document_roles->contains($user_role)) { 328 return true; 329 } 330 } 331 332 return false; 333 } 334 335 public function roleCanReview(User $user) 336 { 337 if (!$this->template || $this->head->is_draft) 338 return false; //we cant identify the roles 339 340 //if the user has one of the involved roles, then he can review 341 return $this->template->reviewerRoles() 342 ->whereIn('id', $user->roles->pluck('id')) 343 ->isNotEmpty(); 344 } 345 346 public function roleCanApprove($user) 347 { 348 if (!$this->template || $this->head->is_draft) 349 return false; //we cant identify the roles 350 351 //if the user has one of the involved roles, then he can approve 352 return $this->template->approverRoles() 353 ->whereIn('id', $user->roles->pluck('id')) 354 ->isNotEmpty(); 355 } 356 357 public function evidence() 358 { 359 return $this->hasOne(Evidence::class); 360 } 361 362 public function url() 363 { 364 return URL::signedRoute('documents.show', ['document' => $this->id]); 365 } 366 367 public function canBeSigned() 368 { 369 return $this->from !== "external"; 370 } 371 372 public function getActivitylogOptions(): LogOptions 373 { 374 return LogOptions::defaults() 375 ->logOnly([ 376 'type_id', 377 'type.name', 378 'number', 379 'name', 380 'original_name', 381 'description', 382 'from', 383 'original_id', 384 'head_id', 385 'dossier_id', 386 'meet_id', 387 'meet.name', 388 'created_by', 389 'stage_id', 390 'stage.name', 391 ])->logOnlyDirty() 392 ->dontSubmitEmptyLogs(); 393 } 394 395 public function createdBy() 396 { 397 return $this->belongsTo(User::class, 'created_by'); 398 } 399 400 public function actVerdict(): bool|null 401 { 402 return $this->meet->actVerdict(); 403 } 404 405 public function getDownloadNameAttribute(): string 406 { 407 return $this->name . '.' . $this->getFileExtensions(); 408 } 409 410 public function getFileExtensions() 411 { 412 return DocumentService::getExtensions($this->head->path); 413 } 414 415 public function getCanBeUpdatedByUserAttribute(): bool 416 { 417 if (empty($this->template)) { // The EMLs do not have related templates. 418 return false; 419 } 420 421 return $this->template->is_word && $this->head->is_pending; 422 } 423 424 public function scopeResolutions() 425 { 426 return $this->whereTypeId(DocumentType::whereCode('RES')->first()->id); 427 } 428 429 public function scopeReports() 430 { 431 return $this->whereTypeId(DocumentType::whereCode('INF')->first()->id); 432 } 433 434 public function scopeFilterByUser($query, ?User $authUser) 435 { 436 if (isset($authUser) and !$authUser->hasAnyRole([ 437 'root', 438 'technical_manager', 439 'quality_manager', 440 'security_manager', 441 'area_manager' 442 ])) { 443 $query->whereHas( 444 'dossier', 445 fn($query) => $query->whereHas( 446 'users', 447 fn($query) => $query->where('users.id', $authUser->id) 448 )->orWhereHas( 449 'principalCertifier', 450 fn($query) => $query->where('users.id', $authUser->id) 451 ) 452 ); 453 } 454 } 455 456 public function getFolderPath(array $skipFolderIds = []): string 457 { 458 $filename = $this->head->name; 459 if (!$this->folder) { 460 return $filename; 461 } 462 463 $folders = $this->folder->ancestors() 464 ->defaultOrder() 465 ->get() 466 ->push($this->folder); 467 468 $folders = $folders->reject(function ($folder) use ($skipFolderIds) { 469 return in_array($folder->id, $skipFolderIds); 470 }); 471 472 $path = $folders->pluck('name')->implode('/'); 473 return $path ? $path . '/' . $filename : $filename; 474 } 475 476 public function inboxfile() 477 { 478 return $this->hasOne(InboxFile::class); 479 } 480 481 public function scopeInf($query) 482 { 483 $typeId = DocumentType::where('code', 'INF')->firstOrFail()->id; 484 return $query->whereTypeId($typeId); 485 } 486 }