$tags * @property string $user_id * @property string $member_id * @property bool $is_imported * @property Carbon|null $still_active_email_sent_at * @property Carbon|null $created_at * @property Carbon|null $updated_at * @property-read User $user * @property-read Member $member * @property string $organization_id * @property-read Organization $organization * @property string|null $project_id * @property-read Project|null $project * @property string|null $client_id * @property-read Client|null $client * @property string|null $task_id * @property-read Task|null $task * @property-read Collection $tagsRelation * * @method Builder hasTag(Tag $tag) * @method static TimeEntryFactory factory() */ class TimeEntry extends Model implements AuditableContract { use ComputedAttributes; use CustomAuditable; /** @use HasFactory */ use HasFactory; use HasJsonRelationships; use HasUuids; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'description' => 'string', 'start' => 'datetime', 'end' => 'datetime', 'billable' => 'bool', 'tags' => 'array', 'billable_rate' => 'int', 'is_imported' => 'bool', 'still_active_email_sent_at' => 'datetime', ]; public const array SELECT_COLUMNS = [ 'id', 'description', 'start', 'end', 'billable_rate', 'billable', 'user_id', 'organization_id', 'project_id', 'task_id', 'tags', 'created_at', 'updated_at', 'member_id', 'client_id', 'is_imported', 'still_active_email_sent_at', ]; /** * The attributes that are computed. (f.e. for performance reasons) * These attributes can be regenerated at any time. * * @var string[] */ protected array $computed = [ 'billable_rate', 'client_id', ]; /** * Attributes to exclude from the Audit. * * @var array */ protected array $auditExclude = [ 'billable_rate', ]; public function getBillableRateComputed(): ?int { return app(BillableRateService::class)->getBillableRateForTimeEntry($this); } public function getClientIdComputed(): ?string { return $this->project_id === null || $this->project === null ? null : $this->project->client_id; } /** * 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('client_id', $attributes, true)) { $builder->with([ 'project' => function (Relation $builder): void { /** @var Builder $builder */ $builder->select('id', 'client_id'); }, ]); } 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); } public function getDuration(): ?CarbonInterval { return $this->end === null ? null : $this->start->diffAsCarbonInterval($this->end); } /** * @param Builder $builder */ public function scopeHasTag(Builder $builder, Tag $tag): void { $builder->whereJsonContains('tags', $tag->getKey()); } /** * @return BelongsTo */ public function user(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } /** * @return BelongsTo */ public function member(): BelongsTo { return $this->belongsTo(Member::class, 'member_id'); } /** * @return BelongsTo */ public function organization(): BelongsTo { return $this->belongsTo(Organization::class, 'organization_id'); } /** * @return BelongsTo */ public function project(): BelongsTo { return $this->belongsTo(Project::class, 'project_id'); } /** * @return BelongsTo */ public function task(): BelongsTo { return $this->belongsTo(Task::class, 'task_id'); } /** * This relation can be reconstructed via the task relation. It is only here for performance reasons. * * @return BelongsTo */ public function client(): BelongsTo { return $this->belongsTo(Client::class, 'client_id'); } /** * Warning: This relation based on a JSON column. Please make sure that there are no performance issues, before using it. * * @return BelongsToJson */ public function tagsRelation(): BelongsToJson { return $this->belongsToJson(Tag::class, 'tags'); } }