/
DATKAI
/
ITCUP
Обзор
Документация
Войти
/
DATKAI
/
ITCUP
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
app/Models/Ticket.php
204 строки
6 KB
DATKAI
feat(account): полноценный ЛК сотрудника — дашборд и уведомления
03 июл 2026, 07:46
03 июл 2026, 07:46
004a052
Код
Авторство
О чём код?
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; class Ticket extends Model { protected $fillable = [ 'number', 'title', 'description', 'status', 'priority', 'ticket_category_id', 'created_by', 'assigned_to', 'asset_id', 'location_id', 'due_date', 'first_responded_at', 'resolved_at', 'closed_at', ]; protected $casts = [ 'due_date' => 'datetime', 'first_responded_at' => 'datetime', 'resolved_at' => 'datetime', 'closed_at' => 'datetime', ]; public const STATUSES = [ 'new' => 'Новая', 'in_progress' => 'В работе', 'waiting' => 'Ожидает ответа', 'resolved' => 'Решена', 'closed' => 'Закрыта', ]; public const STATUS_COLORS = [ 'new' => 'gray', 'in_progress' => 'info', 'waiting' => 'warning', 'resolved' => 'success', 'closed' => 'primary', ]; public const PRIORITIES = [ 'low' => 'Низкий', 'medium' => 'Средний', 'high' => 'Высокий', 'critical' => 'Критичный', ]; public const PRIORITY_COLORS = [ 'low' => 'gray', 'medium' => 'info', 'high' => 'warning', 'critical' => 'danger', ]; public const PRIORITY_ICONS = [ 'low' => 'heroicon-o-chevron-down', 'medium' => 'heroicon-o-minus', 'high' => 'heroicon-o-chevron-up', 'critical' => 'heroicon-o-fire', ]; public const OPEN_STATUSES = ['new', 'in_progress', 'waiting']; protected static function booted(): void { static::creating(function (Ticket $ticket) { if (empty($ticket->number)) { $ticket->number = self::generateNumber(); } }); static::saving(function (Ticket $ticket) { // автоматические даты при смене статуса if ($ticket->isDirty('status')) { $newStatus = $ticket->status; $oldStatus = $ticket->getOriginal('status'); if ($newStatus === 'resolved' && !$ticket->resolved_at) { $ticket->resolved_at = now(); } if ($newStatus === 'closed' && !$ticket->closed_at) { $ticket->closed_at = now(); if (!$ticket->resolved_at) { $ticket->resolved_at = now(); } } // При переоткрытии — очищаем даты if (in_array($oldStatus, ['resolved', 'closed']) && in_array($newStatus, self::OPEN_STATUSES)) { if ($newStatus !== 'waiting') { $ticket->resolved_at = null; $ticket->closed_at = null; } } } }); // Уведомление админам о новой заявке static::created(function (Ticket $ticket) { $admins = User::whereHas('roles', fn ($q) => $q->where('name', 'admin')) ->where('id', '!=', $ticket->created_by) ->get(); foreach ($admins as $admin) { \Filament\Notifications\Notification::make() ->title("Новая заявка {$ticket->number}") ->body($ticket->title) ->icon('heroicon-o-ticket') ->color('info') ->sendToDatabase($admin); } }); // Уведомление автору заявки о смене статуса (если менял не он сам) static::updated(function (Ticket $ticket) { if (!$ticket->wasChanged('status') || !$ticket->creator) { return; } if (auth()->id() === $ticket->created_by) { return; } \Filament\Notifications\Notification::make() ->title("Заявка {$ticket->number}: " . (self::STATUSES[$ticket->status] ?? $ticket->status)) ->body($ticket->title) ->icon('heroicon-o-ticket') ->color(self::STATUS_COLORS[$ticket->status] ?? 'info') ->sendToDatabase($ticket->creator); }); } public static function generateNumber(): string { $year = now()->year; $lastNumber = self::where('number', 'like', "HD-{$year}-%") ->orderByDesc('id') ->value('number'); $seq = 1; if ($lastNumber && preg_match('/HD-\d{4}-(\d+)/', $lastNumber, $m)) { $seq = (int) $m[1] + 1; } return sprintf('HD-%d-%04d', $year, $seq); } public function category(): BelongsTo { return $this->belongsTo(TicketCategory::class, 'ticket_category_id'); } public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } public function assignedUser(): BelongsTo { return $this->belongsTo(User::class, 'assigned_to'); } public function asset(): BelongsTo { return $this->belongsTo(Asset::class); } public function location(): BelongsTo { return $this->belongsTo(Location::class); } public function comments(): HasMany { return $this->hasMany(TicketComment::class)->latest(); } public function publicComments(): HasMany { return $this->hasMany(TicketComment::class)->where('is_internal', false)->latest(); } public function isOpen(): bool { return in_array($this->status, self::OPEN_STATUSES); } public function isOverdue(): bool { return $this->due_date && $this->due_date->isPast() && $this->isOpen(); } }