- Added database migrations for tickets, statuses, roles, users, and related associations. - Introduced models and enums for ticket status, permissions, and notifications. - Implemented `TicketService` for ticket management and notification handling. - Created Livewire form and UI for raising tickets with file attachments and routing options. - Added tests covering ticket creation, validation, notifications, and file uploads.
160 lines
4.7 KiB
PHP
160 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Data\Ticket\CreateTicketRequest;
|
|
use App\Data\Ui\Ticket\TicketData;
|
|
use App\Enums\TicketStatusEnum;
|
|
use App\Models\{Ticket, TicketStatus, User};
|
|
use App\Notifications\TicketRaisedNotification;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use InvalidArgumentException;
|
|
use Throwable;
|
|
|
|
final class TicketService
|
|
{
|
|
public function __construct(
|
|
private readonly TicketAttachmentService $attachmentService
|
|
) {}
|
|
|
|
/**
|
|
* Create a new support ticket.
|
|
*
|
|
* @throws Throwable
|
|
*/
|
|
public function create(CreateTicketRequest $request): TicketData
|
|
{
|
|
return DB::transaction(function () use ($request): TicketData {
|
|
$defaultStatus = TicketStatus::query()
|
|
->where('slug', TicketStatusEnum::Open->value)
|
|
->firstOrFail();
|
|
|
|
/** @var Ticket $ticket */
|
|
$ticket = Ticket::query()->create([
|
|
'user_id' => $request->userId,
|
|
'connected_app_id' => $request->connectedAppId,
|
|
'issue_category' => $request->issueCategory,
|
|
'description' => $request->description,
|
|
'status_id' => $defaultStatus->id,
|
|
]);
|
|
|
|
if (! empty($request->notifiedRoleIds)) {
|
|
$ticket->notifiedRoles()->sync($request->notifiedRoleIds);
|
|
}
|
|
|
|
if (! empty($request->notifiedUserIds)) {
|
|
$ticket->notifiedUsers()->sync($request->notifiedUserIds);
|
|
}
|
|
|
|
if ($request->attachment instanceof UploadedFile) {
|
|
$this->attachmentService->store($ticket->id, $request->attachment);
|
|
}
|
|
|
|
// Load relations to build complete DTO and trigger notifications
|
|
$ticket->load(['user', 'connectedApp', 'status', 'notifiedRoles', 'notifiedUsers', 'attachments']);
|
|
|
|
$this->sendNotifications($ticket);
|
|
|
|
return TicketData::fromModel($ticket);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get all tickets raised by a specific user.
|
|
*
|
|
* @return Collection<int, TicketData>
|
|
*/
|
|
public function getAllForUser(int $userId): Collection
|
|
{
|
|
if ($userId <= 0) {
|
|
throw new InvalidArgumentException('Invalid user ID');
|
|
}
|
|
|
|
$tickets = Ticket::with(['user', 'connectedApp', 'status', 'notifiedRoles', 'notifiedUsers', 'attachments'])
|
|
->where('user_id', $userId)
|
|
->latest()
|
|
->get();
|
|
|
|
return TicketData::collect($tickets);
|
|
}
|
|
|
|
/**
|
|
* Get all tickets in the system (for admins/support).
|
|
*
|
|
* @return Collection<int, TicketData>
|
|
*/
|
|
public function getAll(): Collection
|
|
{
|
|
$tickets = Ticket::with(['user', 'connectedApp', 'status', 'notifiedRoles', 'notifiedUsers', 'attachments'])
|
|
->latest()
|
|
->get();
|
|
|
|
return TicketData::collect($tickets);
|
|
}
|
|
|
|
/**
|
|
* Get a specific ticket.
|
|
*/
|
|
public function getTicket(int $id): TicketData
|
|
{
|
|
if ($id <= 0) {
|
|
throw new InvalidArgumentException('Invalid ticket ID');
|
|
}
|
|
|
|
$ticket = Ticket::with(['user', 'connectedApp', 'status', 'notifiedRoles', 'notifiedUsers', 'attachments'])
|
|
->findOrFail($id);
|
|
|
|
return TicketData::fromModel($ticket);
|
|
}
|
|
|
|
/**
|
|
* Update the status of a ticket.
|
|
*
|
|
* @throws Throwable
|
|
*/
|
|
public function updateStatus(int $ticketId, string $statusSlug): void
|
|
{
|
|
if ($ticketId <= 0) {
|
|
throw new InvalidArgumentException('Invalid ticket ID');
|
|
}
|
|
|
|
DB::transaction(function () use ($ticketId, $statusSlug): void {
|
|
$ticket = Ticket::findOrFail($ticketId);
|
|
$status = TicketStatus::where('slug', $statusSlug)->firstOrFail();
|
|
|
|
$ticket->update(['status_id' => $status->id]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Send notifications to all routed recipients.
|
|
*/
|
|
private function sendNotifications(Ticket $ticket): void
|
|
{
|
|
$notification = new TicketRaisedNotification($ticket);
|
|
$notifiedUserIds = [];
|
|
|
|
// Notify specific users
|
|
foreach ($ticket->notifiedUsers as $user) {
|
|
$user->notify($notification);
|
|
$notifiedUserIds[] = $user->id;
|
|
}
|
|
|
|
// Notify role users
|
|
foreach ($ticket->notifiedRoles as $role) {
|
|
$users = User::role($role->name)->get();
|
|
foreach ($users as $user) {
|
|
// Avoid double notifications
|
|
if (! in_array($user->id, $notifiedUserIds, true)) {
|
|
$user->notify($notification);
|
|
$notifiedUserIds[] = $user->id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|