/ app / Models / Document.php
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  }