singleloginsystem/app/Services/TicketService.php
= cac5f4905b WIP: implement ticketing system with support for notifications and attachments
- 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.
2026-05-25 13:04:43 +00:00

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;
}
}
}
}
}