Files
2026-06-02 22:18:38 +05:00

175 lines
6.8 KiB
PHP

<?php
namespace App\Services;
use App\Enums\StatusSlug;
use App\Events\RequestAssigned;
use App\Events\RequestCreated;
use App\Events\RequestStatusChanged;
use App\Models\RequestType;
use App\Models\ServiceRequest;
use App\Models\Status;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
class RequestService
{
public function __construct(private readonly SettingsService $settingsService)
{
}
public function create(array $data, User $actor): ServiceRequest
{
return DB::transaction(function () use ($data, $actor): ServiceRequest {
$payload = $this->preparePayload($data);
$payload['ticket_number'] = 'PENDING-' . uniqid('', true);
$request = ServiceRequest::query()->create($payload);
$request->ticket_number = sprintf('SRV-%04d', $request->id);
$request->saveQuietly();
$this->persistAttachments($request, Arr::get($data, 'attachments', []), $actor);
event(new RequestCreated($request, $actor));
if ($request->assigned_to) {
event(new RequestAssigned($request, $actor));
}
return $request;
});
}
public function update(ServiceRequest $request, array $data, User $actor): ServiceRequest
{
return DB::transaction(function () use ($request, $data, $actor): ServiceRequest {
$oldAssignee = $request->assigned_to;
$oldStatusId = $request->status_id;
$payload = $this->preparePayload($data, $request);
$request->fill($payload);
$request->save();
$this->persistAttachments($request, Arr::get($data, 'attachments', []), $actor);
if ($oldStatusId !== $request->status_id) {
event(new RequestStatusChanged($request->fresh('status'), $actor, $oldStatusId, $request->status_id));
}
if ($oldAssignee !== $request->assigned_to && $request->assigned_to) {
event(new RequestAssigned($request->fresh(), $actor));
}
return $request->fresh(['type', 'priority', 'status', 'client', 'assignee', 'equipment']);
});
}
public function changeStatus(ServiceRequest $request, Status $targetStatus, User $actor): ServiceRequest
{
$oldStatusId = $request->status_id;
$request->status_id = $targetStatus->id;
if ($targetStatus->slug === StatusSlug::IN_PROGRESS->value && !$request->started_at) {
$request->started_at = now();
}
if (in_array($targetStatus->slug, [StatusSlug::RESOLVED->value, StatusSlug::CLOSED->value], true)) {
$request->completed_at = now();
}
$request->save();
event(new RequestStatusChanged($request->fresh('status'), $actor, $oldStatusId, $targetStatus->id));
return $request;
}
public function allowedTransitions(ServiceRequest $request, User $user): array
{
$staffTransitions = [
StatusSlug::NEW->value => [StatusSlug::IN_PROGRESS->value, StatusSlug::WAITING->value, StatusSlug::CANCELLED->value],
StatusSlug::IN_PROGRESS->value => [StatusSlug::WAITING->value, StatusSlug::RESOLVED->value, StatusSlug::CANCELLED->value],
StatusSlug::WAITING->value => [StatusSlug::IN_PROGRESS->value, StatusSlug::RESOLVED->value, StatusSlug::CANCELLED->value],
StatusSlug::RESOLVED->value => [StatusSlug::CLOSED->value, StatusSlug::IN_PROGRESS->value],
StatusSlug::CLOSED->value => [],
StatusSlug::CANCELLED->value => [],
];
$technicianTransitions = [
StatusSlug::NEW->value => [StatusSlug::IN_PROGRESS->value, StatusSlug::WAITING->value],
StatusSlug::IN_PROGRESS->value => [StatusSlug::WAITING->value, StatusSlug::RESOLVED->value],
StatusSlug::WAITING->value => [StatusSlug::IN_PROGRESS->value, StatusSlug::RESOLVED->value],
StatusSlug::RESOLVED->value => [StatusSlug::IN_PROGRESS->value],
StatusSlug::CLOSED->value => [],
StatusSlug::CANCELLED->value => [],
];
$map = $user->isTechnician() ? $technicianTransitions : $staffTransitions;
return $map[$request->status?->slug ?? StatusSlug::NEW->value] ?? [];
}
public function generateTicketNumber(int $id): string
{
return sprintf('SRV-%04d', $id);
}
private function preparePayload(array $data, ?ServiceRequest $request = null): array
{
$typeId = Arr::get($data, 'type_id', $request?->type_id);
$type = $typeId ? RequestType::query()->find($typeId) : null;
$deadlineAt = Arr::get($data, 'deadline_at');
if (!$deadlineAt && $type) {
$baseDate = Arr::get($data, 'planned_at') ?: now();
$deadlineAt = Carbon::parse((string) $baseDate)->addHours($type->sla_hours)->toDateTimeString();
}
if (!$deadlineAt && !$type) {
$fallbackHours = $this->settingsService->defaultSlaHours();
$baseDate = Arr::get($data, 'planned_at') ?: now();
$deadlineAt = Carbon::parse((string) $baseDate)->addHours($fallbackHours)->toDateTimeString();
}
return [
'title' => Arr::get($data, 'title'),
'description' => Arr::get($data, 'description'),
'type_id' => $typeId,
'priority_id' => Arr::get($data, 'priority_id'),
'status_id' => Arr::get($data, 'status_id', $request?->status_id),
'client_id' => Arr::get($data, 'client_id', $request?->client_id),
'assigned_to' => Arr::get($data, 'assigned_to'),
'equipment_id' => Arr::get($data, 'equipment_id'),
'location' => Arr::get($data, 'location'),
'planned_at' => Arr::get($data, 'planned_at'),
'deadline_at' => $deadlineAt,
'started_at' => Arr::get($data, 'started_at', $request?->started_at),
'completed_at' => Arr::get($data, 'completed_at', $request?->completed_at),
'resolution_notes' => Arr::get($data, 'resolution_notes'),
'client_rating' => Arr::get($data, 'client_rating'),
'client_comment' => Arr::get($data, 'client_comment'),
];
}
private function persistAttachments(ServiceRequest $request, array $attachments, User $actor): void
{
/** @var UploadedFile $attachment */
foreach ($attachments as $attachment) {
$path = $attachment->store('request-attachments/'.$request->id, 'local');
$request->attachments()->create([
'user_id' => $actor->id,
'filename' => $attachment->getClientOriginalName(),
'path' => $path,
'size' => $attachment->getSize() ?: 0,
]);
}
}
}