Initial Commit
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final class Attachment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_attachments';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'comment_id',
|
||||
'user_id',
|
||||
'disk',
|
||||
'path',
|
||||
'original_name',
|
||||
'mime_type',
|
||||
'size',
|
||||
'extension',
|
||||
'category',
|
||||
'checksum_sha256',
|
||||
'download_count',
|
||||
'last_downloaded_at',
|
||||
'deleted_at',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'size' => 'integer',
|
||||
'download_count' => 'integer',
|
||||
'last_downloaded_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<Ticket, Attachment> */
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Comment, Attachment> */
|
||||
public function comment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Comment::class, 'comment_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, Attachment> */
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an attribute only when it was actually selected from the database.
|
||||
*
|
||||
* Laravel 13 can throw when accessing missing attributes. The Helpdesk
|
||||
* attachment metadata columns are introduced by an optional forward
|
||||
* migration, so model helpers must remain safe before that migration has
|
||||
* been applied.
|
||||
*/
|
||||
private function loadedAttribute(string $key): mixed
|
||||
{
|
||||
$attributes = $this->getAttributes();
|
||||
|
||||
return array_key_exists($key, $attributes) ? $this->getAttributeValue($key) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the original client filename with compatibility fallbacks.
|
||||
*/
|
||||
public function displayName(): string
|
||||
{
|
||||
$name = $this->loadedAttribute('original_name')
|
||||
?? $this->loadedAttribute('original_filename')
|
||||
?? $this->loadedAttribute('filename')
|
||||
?? $this->loadedAttribute('file_name');
|
||||
|
||||
if (is_string($name) && $name !== '') {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return basename((string) ($this->loadedAttribute('path') ?? $this->loadedAttribute('file_path') ?? 'attachment'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stored byte size, falling back to legacy names.
|
||||
*/
|
||||
public function byteSize(): int
|
||||
{
|
||||
return (int) ($this->loadedAttribute('size') ?? $this->loadedAttribute('size_bytes') ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the safe download count when the column exists.
|
||||
*/
|
||||
public function downloadCount(): int
|
||||
{
|
||||
return (int) ($this->loadedAttribute('download_count') ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the attachment has been soft deleted.
|
||||
*/
|
||||
public function isDeletedAttachment(): bool
|
||||
{
|
||||
return filled($this->loadedAttribute('deleted_at'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lower-case file extension from stored metadata or filename.
|
||||
*/
|
||||
public function extension(): string
|
||||
{
|
||||
$extension = $this->loadedAttribute('extension');
|
||||
|
||||
if (! is_string($extension) || $extension === '') {
|
||||
$extension = pathinfo($this->displayName(), PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
return strtolower(ltrim((string) $extension, '.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the display category used by Helpdesk attachment cards.
|
||||
*/
|
||||
public function category(): string
|
||||
{
|
||||
$category = $this->loadedAttribute('category');
|
||||
|
||||
if (is_string($category) && $category !== '') {
|
||||
return $category;
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
$this->isImage() => 'image',
|
||||
$this->isPdf() => 'pdf',
|
||||
$this->isArchive() => 'archive',
|
||||
$this->isSourceCode() => 'source',
|
||||
default => 'file',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the attachment is a browser-previewable image.
|
||||
*/
|
||||
public function isImage(): bool
|
||||
{
|
||||
return in_array($this->extension(), ['png', 'jpg', 'jpeg', 'webp', 'gif'], true)
|
||||
|| Str::startsWith((string) $this->loadedAttribute('mime_type'), 'image/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the attachment is a PDF document.
|
||||
*/
|
||||
public function isPdf(): bool
|
||||
{
|
||||
return $this->extension() === 'pdf' || $this->loadedAttribute('mime_type') === 'application/pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the attachment is an archive.
|
||||
*/
|
||||
public function isArchive(): bool
|
||||
{
|
||||
return in_array($this->extension(), ['zip', 'tar', 'gz', '7z', 'rar'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the attachment is source/configuration text safe for the read-only viewer.
|
||||
*/
|
||||
public function isSourceCode(): bool
|
||||
{
|
||||
return in_array($this->extension(), [
|
||||
'php', 'phtml', 'phps', 'blade.php', 'js', 'jsx', 'ts', 'tsx', 'vue', 'css', 'scss', 'sass', 'less',
|
||||
'html', 'htm', 'xml', 'json', 'yaml', 'yml', 'sql', 'sh', 'bash', 'zsh', 'ps1', 'bat', 'cmd',
|
||||
'py', 'rb', 'go', 'rs', 'java', 'kt', 'kts', 'cs', 'cpp', 'c', 'h', 'hpp', 'swift', 'dart',
|
||||
'rpg', 'rpgle', 'cl', 'clle', 'cbl', 'cob', 'copybook', 'inc', 'env', 'ini', 'conf', 'config', 'txt', 'log', 'md',
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the attachment can be streamed inline by the preview route.
|
||||
*/
|
||||
public function isPreviewableBinary(): bool
|
||||
{
|
||||
return $this->isImage() || $this->isPdf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the Helpdesk UI can show an inline preview or viewer link.
|
||||
*/
|
||||
public function canPreview(): bool
|
||||
{
|
||||
return $this->isSourceCode() || $this->isPreviewableBinary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Font Awesome icon for the attachment type.
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return match ($this->category()) {
|
||||
'image' => 'fa-regular fa-file-image',
|
||||
'pdf' => 'fa-regular fa-file-pdf',
|
||||
'source' => 'fa-solid fa-code',
|
||||
'archive' => 'fa-regular fa-file-zipper',
|
||||
default => 'fa-regular fa-file-lines',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the syntax language key used by the source viewer.
|
||||
*/
|
||||
public function language(): string
|
||||
{
|
||||
return match ($this->extension()) {
|
||||
'php', 'phtml', 'phps', 'blade.php' => 'php',
|
||||
'js', 'jsx' => 'javascript',
|
||||
'ts', 'tsx' => 'typescript',
|
||||
'vue' => 'vue',
|
||||
'css', 'scss', 'sass', 'less' => 'css',
|
||||
'html', 'htm' => 'html',
|
||||
'xml' => 'xml',
|
||||
'json' => 'json',
|
||||
'yaml', 'yml' => 'yaml',
|
||||
'sql' => 'sql',
|
||||
'sh', 'bash', 'zsh' => 'shell',
|
||||
'ps1' => 'powershell',
|
||||
'py' => 'python',
|
||||
'java', 'kt', 'kts' => 'java',
|
||||
'c', 'h', 'cpp', 'hpp', 'cs' => 'c-like',
|
||||
'rpg', 'rpgle' => 'rpgle',
|
||||
'cl', 'clle' => 'clle',
|
||||
'cbl', 'cob', 'copybook' => 'cobol',
|
||||
default => 'text',
|
||||
};
|
||||
}
|
||||
|
||||
public function url(): string
|
||||
{
|
||||
return Storage::disk((string) $this->loadedAttribute('disk'))->url((string) $this->loadedAttribute('path'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class AutomationRule extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_automation_rules';
|
||||
|
||||
protected $fillable = ['name', 'slug', 'match_sender', 'match_subject', 'match_mailbox', 'match_category_id', 'set_priority_id', 'set_category_id', 'set_team_id', 'set_project_id', 'set_status_id', 'set_agent_id', 'is_active', 'sort_order'];
|
||||
|
||||
protected $casts = ['match_category_id' => 'integer', 'set_priority_id' => 'integer', 'set_category_id' => 'integer', 'set_team_id' => 'integer', 'set_project_id' => 'integer', 'set_status_id' => 'integer', 'set_agent_id' => 'integer', 'is_active' => 'boolean', 'sort_order' => 'integer'];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class CannedResponse extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_canned_responses';
|
||||
|
||||
protected $fillable = ['category_id', 'team_id', 'name', 'slug', 'visibility', 'subject', 'body', 'is_active', 'sort_order'];
|
||||
|
||||
protected $casts = ['category_id' => 'integer', 'team_id' => 'integer', 'is_active' => 'boolean', 'sort_order' => 'integer'];
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class, 'category_id');
|
||||
}
|
||||
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class, 'team_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class Category extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_categories';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['name', 'slug', 'color', 'description', 'is_default'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = ['is_default' => 'boolean'];
|
||||
|
||||
/** @return HasMany<Ticket> */
|
||||
public function tickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'category_id');
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<User> */
|
||||
public function agents(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'helpdesk_category_user', 'category_id', 'user_id')->withTimestamps();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class ChecklistItem extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_checklist_items';
|
||||
|
||||
protected $fillable = ['ticket_id', 'template_id', 'title', 'description', 'is_completed', 'completed_by', 'completed_at', 'sort_order'];
|
||||
|
||||
protected $casts = ['ticket_id' => 'integer', 'template_id' => 'integer', 'is_completed' => 'boolean', 'completed_by' => 'integer', 'completed_at' => 'datetime', 'sort_order' => 'integer'];
|
||||
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class ChecklistTemplate extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_checklist_templates';
|
||||
|
||||
protected $fillable = ['category_id', 'project_id', 'name', 'slug', 'items', 'is_active'];
|
||||
|
||||
protected $casts = ['category_id' => 'integer', 'project_id' => 'integer', 'items' => 'array', 'is_active' => 'boolean'];
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
final class Comment extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_comments';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'parent_id',
|
||||
'user_id',
|
||||
'subject',
|
||||
'body',
|
||||
'body_text',
|
||||
'is_internal',
|
||||
'guid',
|
||||
'unid',
|
||||
'replica_id',
|
||||
'parent_unid',
|
||||
'root_unid',
|
||||
'originator_id',
|
||||
'sync_sequence',
|
||||
'sync_hash',
|
||||
'source_system',
|
||||
'source_record_id',
|
||||
'replicated_at',
|
||||
'synced_at',
|
||||
'tombstoned_at',
|
||||
'deleted_by',
|
||||
'delete_reason',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'is_internal' => 'boolean',
|
||||
'sync_sequence' => 'integer',
|
||||
'replicated_at' => 'datetime',
|
||||
'synced_at' => 'datetime',
|
||||
'tombstoned_at' => 'datetime',
|
||||
'deleted_by' => 'integer',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<Ticket, Comment> */
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Comment, Comment> */
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Comment> */
|
||||
public function replies(): HasMany
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id')
|
||||
->active()
|
||||
->oldest();
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, Comment> */
|
||||
public function deleter(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'deleted_by');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, Comment> */
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Attachment> */
|
||||
public function attachments(): HasMany
|
||||
{
|
||||
return $this->hasMany(Attachment::class, 'comment_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the query to non-tombstoned comments when the legacy column exists.
|
||||
*
|
||||
* Some older Dusk databases can exist before the Lotus replication migration
|
||||
* added tombstoned_at. In that state every row is active, so avoid issuing an
|
||||
* invalid column query.
|
||||
*
|
||||
* @param Builder<Comment> $query
|
||||
* @return Builder<Comment>
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
if (! Schema::hasColumn($this->getTable(), 'tombstoned_at')) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->whereNull('tombstoned_at');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class GitCommit extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_git_commits';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'repository_id', 'ticket_id', 'hash', 'short_hash', 'branch', 'author_name', 'author_email', 'message', 'url', 'committed_at',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = ['repository_id' => 'integer', 'ticket_id' => 'integer', 'committed_at' => 'datetime'];
|
||||
|
||||
/** @return BelongsTo<GitRepository, GitCommit> */
|
||||
public function repository(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GitRepository::class, 'repository_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Ticket, GitCommit> */
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class GitRepository extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_git_repositories';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['project_id', 'name', 'provider', 'remote_url', 'default_branch', 'webhook_secret', 'is_active'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = ['project_id' => 'integer', 'is_active' => 'boolean'];
|
||||
|
||||
/** @return BelongsTo<Project, GitRepository> */
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class, 'project_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<GitCommit> */
|
||||
public function commits(): HasMany
|
||||
{
|
||||
return $this->hasMany(GitCommit::class, 'repository_id')->latest('committed_at');
|
||||
}
|
||||
|
||||
/** @return HasMany<GitWebhookLog> */
|
||||
public function webhookLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(GitWebhookLog::class, 'repository_id')->latest('received_at');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class GitWebhookLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_git_webhook_logs';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'repository_id',
|
||||
'provider',
|
||||
'delivery_id',
|
||||
'event',
|
||||
'status',
|
||||
'message',
|
||||
'commit_count',
|
||||
'linked_count',
|
||||
'ignored_count',
|
||||
'payload',
|
||||
'received_at',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'repository_id' => 'integer',
|
||||
'commit_count' => 'integer',
|
||||
'linked_count' => 'integer',
|
||||
'ignored_count' => 'integer',
|
||||
'payload' => 'array',
|
||||
'received_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository that received the provider webhook.
|
||||
*
|
||||
* @return BelongsTo<GitRepository, GitWebhookLog>
|
||||
*/
|
||||
public function repository(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GitRepository::class, 'repository_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Configurable Helpdesk Kanban board.
|
||||
*/
|
||||
final class KanbanBoard extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_kanban_boards';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'owner_id',
|
||||
'is_default',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'owner_id' => 'integer',
|
||||
'is_default' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<User, KanbanBoard> */
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Ticket> */
|
||||
public function tickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'board_id');
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Status> */
|
||||
public function statuses(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Status::class, 'helpdesk_kanban_board_statuses', 'board_id', 'status_id')
|
||||
->withPivot(['sort_order'])
|
||||
->orderBy('helpdesk_kanban_board_statuses.sort_order')
|
||||
->orderBy('helpdesk_statuses.sort_order')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class Priority extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_priorities';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['name', 'slug', 'color', 'sort_order', 'is_default'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = ['sort_order' => 'integer', 'is_default' => 'boolean'];
|
||||
|
||||
/** @return HasMany<Ticket> */
|
||||
public function tickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'priority_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class Product extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_products';
|
||||
|
||||
protected $fillable = ['name', 'slug', 'type', 'version', 'release_channel', 'description', 'is_active'];
|
||||
|
||||
protected $casts = ['is_active' => 'boolean'];
|
||||
|
||||
public function tickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'product_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
final class Project extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'helpdesk_projects';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['team_id', 'title', 'slug', 'color', 'description', 'archived_at', 'is_default'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = ['team_id' => 'integer', 'archived_at' => 'datetime', 'is_default' => 'boolean'];
|
||||
|
||||
/** @return BelongsTo<Team, Project> */
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class, 'team_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Ticket> */
|
||||
public function tickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'project_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class SlaPolicy extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_sla_policies';
|
||||
|
||||
protected $fillable = ['priority_id', 'category_id', 'name', 'slug', 'first_response_minutes', 'resolution_minutes', 'business_hours_only', 'is_default', 'is_active'];
|
||||
|
||||
protected $casts = ['priority_id' => 'integer', 'category_id' => 'integer', 'first_response_minutes' => 'integer', 'resolution_minutes' => 'integer', 'business_hours_only' => 'boolean', 'is_default' => 'boolean', 'is_active' => 'boolean'];
|
||||
|
||||
public function priority(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Priority::class, 'priority_id');
|
||||
}
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class, 'category_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string $color
|
||||
* @property bool $is_closed
|
||||
*/
|
||||
final class Status extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_statuses';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['name', 'slug', 'color', 'sort_order', 'is_closed', 'is_default'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = ['is_closed' => 'boolean', 'is_default' => 'boolean', 'sort_order' => 'integer'];
|
||||
|
||||
/** @return HasMany<Ticket> */
|
||||
public function tickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'status_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
final class Tag extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_tags';
|
||||
|
||||
protected $fillable = ['name', 'slug', 'color'];
|
||||
|
||||
public function tickets(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Ticket::class, 'helpdesk_ticket_tag', 'tag_id', 'ticket_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
final class Team extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'helpdesk_teams';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['name', 'slug', 'color', 'description', 'archived_at', 'is_default'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'archived_at' => 'datetime',
|
||||
'is_default' => 'boolean',
|
||||
];
|
||||
|
||||
/** @return HasMany<Project> */
|
||||
public function projects(): HasMany
|
||||
{
|
||||
return $this->hasMany(Project::class, 'team_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Ticket> */
|
||||
public function tickets(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'team_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Modules\Helpdesk\Database\Factories\TicketFactory;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $reference
|
||||
* @property string $subject
|
||||
* @property string $body
|
||||
*/
|
||||
final class Ticket extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'helpdesk_tickets';
|
||||
|
||||
protected $with = ['user'];
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'reference', 'user_id', 'agent_id', 'status_id', 'board_id', 'priority_id', 'category_id', 'project_id', 'team_id',
|
||||
'subject', 'body', 'due_at', 'completed_at', 'last_activity_at', 'archived_at',
|
||||
'sla_policy_id', 'product_id', 'first_response_at', 'sla_first_response_due_at', 'sla_resolution_due_at',
|
||||
'sla_first_response_breached_at', 'sla_resolution_breached_at', 'public_replies_count', 'time_spent_minutes',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'team_id' => 'integer',
|
||||
'board_id' => 'integer',
|
||||
'due_at' => 'datetime',
|
||||
'completed_at' => 'datetime',
|
||||
'last_activity_at' => 'datetime',
|
||||
'archived_at' => 'datetime',
|
||||
'sla_policy_id' => 'integer',
|
||||
'product_id' => 'integer',
|
||||
'first_response_at' => 'datetime',
|
||||
'sla_first_response_due_at' => 'datetime',
|
||||
'sla_resolution_due_at' => 'datetime',
|
||||
'sla_first_response_breached_at' => 'datetime',
|
||||
'sla_resolution_breached_at' => 'datetime',
|
||||
'public_replies_count' => 'integer',
|
||||
'time_spent_minutes' => 'integer',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<User, Ticket> */
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function tickets(): Ticket|HasMany
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'ticket_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, Ticket> */
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, Ticket> */
|
||||
public function agent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'agent_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Status, Ticket> */
|
||||
public function status(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Status::class, 'status_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<KanbanBoard, Ticket> */
|
||||
public function board(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(KanbanBoard::class, 'board_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Priority, Ticket> */
|
||||
public function priority(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Priority::class, 'priority_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Category, Ticket> */
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class, 'category_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Project, Ticket> */
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class, 'project_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Team, Ticket> */
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class, 'team_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Comment> */
|
||||
public function comments(): HasMany
|
||||
{
|
||||
return $this->hasMany(Comment::class, 'ticket_id')
|
||||
->whereNull('parent_id')
|
||||
->oldest();
|
||||
}
|
||||
|
||||
/** @return BelongsTo<SlaPolicy, Ticket> */
|
||||
public function slaPolicy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SlaPolicy::class, 'sla_policy_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Product, Ticket> */
|
||||
public function product(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class, 'product_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<TicketSlaEvent> */
|
||||
public function slaEvents(): HasMany
|
||||
{
|
||||
return $this->hasMany(TicketSlaEvent::class, 'ticket_id')->latest();
|
||||
}
|
||||
|
||||
/** @return HasMany<TicketRating> */
|
||||
public function ratings(): HasMany
|
||||
{
|
||||
return $this->hasMany(TicketRating::class, 'ticket_id')->latest();
|
||||
}
|
||||
|
||||
/** @return HasMany<ChecklistItem> */
|
||||
public function checklistItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(ChecklistItem::class, 'ticket_id')->orderBy('sort_order')->orderBy('id');
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Tag> */
|
||||
public function tags(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Tag::class, 'helpdesk_ticket_tag', 'ticket_id', 'tag_id');
|
||||
}
|
||||
|
||||
public function getSlaFirstResponseBreachedAttribute(): bool
|
||||
{
|
||||
return $this->first_response_at === null
|
||||
&& $this->sla_first_response_due_at !== null
|
||||
&& now()->greaterThan($this->sla_first_response_due_at);
|
||||
}
|
||||
|
||||
public function getSlaResolutionBreachedAttribute(): bool
|
||||
{
|
||||
return $this->completed_at === null
|
||||
&& $this->sla_resolution_due_at !== null
|
||||
&& now()->greaterThan($this->sla_resolution_due_at);
|
||||
}
|
||||
|
||||
/** @return HasMany<GitCommit> */
|
||||
public function gitCommits(): HasMany
|
||||
{
|
||||
return $this->hasMany(GitCommit::class, 'ticket_id')->latest('committed_at');
|
||||
}
|
||||
|
||||
/** @return HasMany<Attachment> */
|
||||
public function attachments(): HasMany
|
||||
{
|
||||
return $this->hasMany(Attachment::class, 'ticket_id')->latest();
|
||||
}
|
||||
|
||||
/** @param Builder<Ticket> $query @return Builder<Ticket> */
|
||||
public function scopeOpen(Builder $query): Builder
|
||||
{
|
||||
return $query->whereNull('completed_at');
|
||||
}
|
||||
|
||||
/** @param Builder<Ticket> $query @return Builder<Ticket> */
|
||||
public function scopeVisibleTo(Builder $query, User $user): Builder
|
||||
{
|
||||
if ($user->isAdmin() || (bool) ($user->helpdesk_admin ?? false)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
if ((bool) ($user->helpdesk_agent ?? false)) {
|
||||
return $query->where(static function (Builder $subQuery) use ($user): void {
|
||||
$subQuery->where('agent_id', $user->id)->orWhere('user_id', $user->id);
|
||||
});
|
||||
}
|
||||
|
||||
return $query->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new factory instance for the model.
|
||||
*/
|
||||
protected static function newFactory(): TicketFactory
|
||||
{
|
||||
return TicketFactory::new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class TicketRating extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_ticket_ratings';
|
||||
|
||||
protected $fillable = ['ticket_id', 'agent_id', 'team_id', 'category_id', 'rating', 'token', 'comment', 'rated_at'];
|
||||
|
||||
protected $casts = ['ticket_id' => 'integer', 'agent_id' => 'integer', 'team_id' => 'integer', 'category_id' => 'integer', 'rating' => 'integer', 'rated_at' => 'datetime'];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class TicketSlaEvent extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_ticket_sla_events';
|
||||
|
||||
protected $fillable = ['ticket_id', 'sla_policy_id', 'event', 'due_at', 'occurred_at', 'breached', 'meta'];
|
||||
|
||||
protected $casts = ['ticket_id' => 'integer', 'sla_policy_id' => 'integer', 'due_at' => 'datetime', 'occurred_at' => 'datetime', 'breached' => 'boolean', 'meta' => 'array'];
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class TicketTimeLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_ticket_time_logs';
|
||||
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'user_id',
|
||||
'minutes',
|
||||
'seconds',
|
||||
'description',
|
||||
'started_at',
|
||||
'ended_at',
|
||||
'logged_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'ticket_id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
'minutes' => 'integer',
|
||||
'seconds' => 'integer',
|
||||
'started_at' => 'datetime',
|
||||
'ended_at' => 'datetime',
|
||||
'logged_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ticket this time log belongs to.
|
||||
*/
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who logged the time.
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class TimeLog extends Model
|
||||
{
|
||||
protected $table = 'helpdesk_time_logs';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'user_id',
|
||||
'minutes',
|
||||
'description',
|
||||
'logged_at',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'minutes' => 'integer',
|
||||
'logged_at' => 'datetime',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<Ticket, TimeLog> */
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<User, TimeLog> */
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class WorkflowLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_workflow_logs';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['template_id', 'ticket_id', 'event', 'recipient', 'subject', 'status', 'tracking_token', 'first_opened_at', 'last_opened_at', 'opened_count', 'error'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = [
|
||||
'first_opened_at' => 'datetime',
|
||||
'last_opened_at' => 'datetime',
|
||||
'opened_count' => 'integer',
|
||||
];
|
||||
|
||||
/** @return BelongsTo<WorkflowTemplate, WorkflowLog> */
|
||||
public function template(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(WorkflowTemplate::class, 'template_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<Ticket, WorkflowLog> */
|
||||
public function ticket(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Helpdesk\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class WorkflowTemplate extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'helpdesk_workflow_templates';
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = ['name', 'event', 'subject', 'html_body', 'text_body', 'to_role', 'cc', 'bcc', 'is_active'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $casts = ['is_active' => 'boolean'];
|
||||
|
||||
/** @return HasMany<WorkflowLog> */
|
||||
public function logs(): HasMany
|
||||
{
|
||||
return $this->hasMany(WorkflowLog::class, 'template_id');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user