'boolean', 'edited' => 'boolean', ]; } /** * "role" and "author_id" are read-only virtual attributes derived from * authorLink (see below) — neither is a real column anymore, so this * keeps every existing `$message->role`/`$message->author_id` read * working unchanged. Write via attachAuthor() instead. */ public function getAttribute($key) { if ($key === 'role') { return $this->authorLink?->role?->key ?? 'system'; } if ($key === 'author_id') { return $this->authorLink?->user_id; } return parent::getAttribute($key); } public function ticket(): BelongsTo { return $this->belongsTo(Ticket::class); } public function apiClient(): BelongsTo { return $this->belongsTo(ApiClient::class); } /** * Who wrote this message and in what role — replaces the old flat * author_id/role columns. A message with no row here is a * system-generated entry (no author, no role). */ public function authorLink(): HasOne { return $this->hasOne(TicketMessageAuthor::class); } public function author(): HasOneThrough { return $this->hasOneThrough(User::class, TicketMessageAuthor::class, 'ticket_message_id', 'id', 'id', 'user_id'); } /** * Links this message to the given author + role (or does nothing for a * system message, i.e. $roleKey === null) — the write-side counterpart * to the role/author_id reads above, since neither is a plain column * anymore. */ public function attachAuthor(?int $userId, ?string $roleKey): void { if ($roleKey === null) { return; } $this->authorLink()->create([ 'user_id' => $userId, 'role_id' => Role::query()->where('key', $roleKey)->value('id'), ]); } public function attachments(): HasMany { return $this->hasMany(TicketAttachment::class, 'message_id'); } }