$tasks * @property-read Collection $members * * @method Builder visibleByEmployee(User $user) * @method static ProjectFactory factory() */ class Project extends Model implements AuditableContract { use ComputedAttributes; use CustomAuditable; /** @use HasFactory */ use HasFactory; use HasUuids; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'name' => 'string', 'color' => 'string', 'archived_at' => 'datetime', 'estimated_time' => 'integer', 'spent_time' => 'integer', ]; /** * Set default values for attributes. * * @var array */ protected $attributes = [ 'is_billable' => false, ]; /** * The attributes that are computed. (f.e. for performance reasons) * These attributes can be regenerated at any time. * * @var string[] */ protected array $computed = [ 'spent_time', ]; /** * Attributes to exclude from the Audit. * * @var array */ protected array $auditExclude = [ 'spent_time', ]; public function getSpentTimeComputed(): ?int { if ($this->hasAttribute('spent_time_computed')) { return $this->attributes['spent_time_computed'] === null ? 0 : (int) $this->attributes['spent_time_computed']; } else { /** @var object{ spent_time: string } $result */ $result = $this->timeEntries() ->whereNotNull('end') ->selectRaw('sum(extract(epoch from ("end" - start))) as spent_time') ->first(); return (int) $result->spent_time; } } /** * This scope will be applied during the computed property generation with artisan computed-attributes:generate. * * @param Builder $builder * @param array $attributes Attributes that will be generated. * @return Builder */ public function scopeComputedAttributesGenerate(Builder $builder, array $attributes): Builder { if (in_array('spent_time', $attributes, true)) { $builder->withAggregate('timeEntries as spent_time_computed', DB::raw('extract(epoch from ("end" - start))'), 'sum'); } return $builder; } /** * This scope will be applied during the computed property validation with artisan computed-attributes:validate. * * @param Builder $builder * @param array $attributes Attributes that will be validated. * @return Builder */ public function scopeComputedAttributesValidate(Builder $builder, array $attributes): Builder { return $this->scopeComputedAttributesGenerate($builder, $attributes); } /** * @return BelongsTo */ public function organization(): BelongsTo { return $this->belongsTo(Organization::class, 'organization_id'); } /** * @return BelongsTo */ public function client(): BelongsTo { return $this->belongsTo(Client::class, 'client_id'); } /** * @return HasMany */ public function members(): HasMany { return $this->hasMany(ProjectMember::class, 'project_id'); } /** * @return HasMany */ public function tasks(): HasMany { return $this->hasMany(Task::class); } /** * @return HasMany */ public function timeEntries(): HasMany { return $this->hasMany(TimeEntry::class, 'project_id'); } /** * @param Builder $builder */ public function scopeVisibleByEmployee(Builder $builder, User $user): void { $builder->where(function (Builder $builder) use ($user): Builder { return $builder->where('is_public', '=', true) ->orWhereHas('members', function (Builder $builder) use ($user): Builder { return $builder->whereBelongsTo($user, 'user'); }); }); } /** * @return Attribute */ protected function isArchived(): Attribute { return Attribute::make( get: fn (mixed $value, array $attributes) => isset($attributes['archived_at']), ); } }