preparePayload($data); $payload['ticket_number'] = $this->generateTicketNumber(); $request = ServiceRequest::query()->create($payload); $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(): string { $maxId = ServiceRequest::withTrashed()->max('id') ?? 0; return sprintf('SRV-%04d', $maxId + 1); } 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, ]); } } }