candidatePhone); $cleanName = Str::slug($dto->candidateName); $submissionUniqueId = $cleanName.'_'.$cleanPhone.'_'.time(); $scheduledAt = $dto->scheduledAt ? Carbon::parse($dto->scheduledAt) : now(); $expiresAt = $scheduledAt->copy()->addHours($dto->validHours); $resumePath = null; if ($dto->resume) { $resumePath = $dto->resume->store('interview_resumes', 'public'); } elseif ($dto->resumePath) { $resumePath = $dto->resumePath; } $interview = DB::transaction(function () use ($dto, $tempPassword, $submissionUniqueId, $scheduledAt, $expiresAt, $creatorId, $resumePath) { return Interview::create([ 'candidate_name' => $dto->candidateName, 'candidate_email' => $dto->candidateEmail, 'candidate_phone' => $dto->candidatePhone, 'job_title' => $dto->jobTitle, 'round' => $dto->round, 'description' => $dto->description, 'resume_path' => $resumePath, 'temp_password' => $tempPassword, 'scheduled_at' => $scheduledAt, 'expires_at' => $expiresAt, 'language' => $dto->language, 'status' => 'scheduled', 'assigned_interviewers' => $dto->assignedInterviewers, 'proctor_logs' => [], 'submission_unique_id' => $submissionUniqueId, 'created_by' => $creatorId, ]); }); // Trigger iCalendar generation & queued emails $this->sendInvitationsAction->execute($interview); return $interview; } }