findOrFail($leadId); $user = User::findOrFail($userId); $callerExtension = trim((string) ($user->voip_extension ?: '')); $providerName = Setting::where('key', 'voip_provider')->value('value') ?: 'mock'; if ($providerName === 'ami' && $callerExtension === '') { throw ValidationException::withMessages([ 'voip_extension' => ['شماره داخلی برای این کاربر ثبت نشده است.'], ]); } if (!$lead->assigned_to) { $lead->update([ 'assigned_to' => $userId, 'assigned_by' => $userId, 'is_unassigned' => false, 'pipeline_stage_id' => PipelineStage::where('slug', 'waiting_call')->value('id') ?? $lead->pipeline_stage_id, ]); \App\Models\LeadAssignment::create([ 'lead_id' => $lead->id, 'user_id' => $userId, 'assigned_by' => $userId, 'method' => 'call_claim', ]); } $phone = $this->resolvePhone($lead, $contactPhoneId); $callerId = $callerExtension !== '' ? $callerExtension : (string) $userId; $providerResult = $this->voipManager->initiateCall($phone->phone, $callerId); if (($providerResult['success'] ?? false) !== true) { throw ValidationException::withMessages([ 'voip' => [$providerResult['message'] ?? 'درخواست تماس به مرکز تلفن ارسال نشد.'], ]); } $providerCallId = $providerResult['provider_call_id'] ?? null; $recordingEnabled = Setting::where('key', 'call_recording_enabled')->value('value') === 'true'; $call = Call::create([ 'lead_id' => $leadId, 'contact_id' => $phone->contact_id, 'contact_phone_id' => $phone->id, 'user_id' => $userId, 'direction' => 'outbound', 'phone' => $phone->phone, 'result' => null, 'provider_call_id' => $providerCallId, 'recording_url' => $recordingEnabled && $providerCallId ? $this->voipManager->getRecordingUrl($providerCallId) : null, 'is_manual' => true, ]); $phone->increment('call_count'); $phone->update([ 'last_called_at' => now(), 'last_called_by' => $userId, ]); CallLog::create([ 'lead_id' => $leadId, 'contact_id' => $phone->contact_id, 'contact_phone_id' => $phone->id, 'call_id' => $call->id, 'user_id' => $userId, 'called_at' => now(), 'recording_url' => $call->recording_url, ]); $lead->update([ 'last_call_at' => now(), 'call_attempts' => $lead->call_attempts + 1, ]); ActivityLogger::log('call_initiated', "Call initiated for lead {$leadId}", $call); $payload = [ 'call_id' => $call->id, 'contact_id' => $phone->contact_id, 'contact_phone_id' => $phone->id, 'lead_name' => $lead->full_name, 'recording_enabled' => $recordingEnabled, 'recording_url' => $call->recording_url, 'provider_call_id' => $providerCallId, ]; if ($includePhone) { $payload['phone'] = $phone->phone; } return $payload; } public function registerResult(int $callId, string $result, ?string $notes = null, ?string $followUpAt = null, ?array $referral = null, array $options = []): Call { $call = Call::findOrFail($callId); return DB::transaction(function () use ($call, $callId, $result, $notes, $followUpAt, $referral, $options) { $call->update([ 'result' => $result, 'notes' => $notes, ]); $lead = $call->lead; $lead->update([ 'last_call_result' => $result, 'last_call_at' => now(), ]); $callResult = CallResult::where('name', $result)->orWhere('slug', $result)->first(); $nextAction = $callResult?->next_action ?? ($followUpAt ? 'پیگیری در زمان مشخص‌شده' : null); if ($callResult?->next_pipeline_stage_id) { $lead->update(['pipeline_stage_id' => $callResult->next_pipeline_stage_id]); } elseif ($followUpAt && $followUpStageId = PipelineStage::where('slug', 'follow_up')->value('id')) { $lead->update(['pipeline_stage_id' => $followUpStageId]); } if ($callResult?->lead_status_id) { $lead->update(['lead_status_id' => $callResult->lead_status_id]); } if ($call->contactPhone) { $success = (bool) $callResult?->is_positive; $call->contactPhone->increment($success ? 'successful_call_count' : 'failed_call_count'); $call->contactPhone->update([ 'last_call_result' => $result, 'last_called_at' => now(), 'last_called_by' => $call->user_id, 'status' => $callResult?->phone_status ?? $call->contactPhone->status, ]); } $newContact = null; if (in_array($result, ['معرفی شماره یا شخص جدید', 'معرفی شماره جدید'], true) && $referral) { $newContact = $this->createReferralContact($lead, $call, $referral, $notes); $nextAction = 'تماس با مخاطب معرفی‌شده'; $followUpAt = $referral['next_follow_up_at'] ?? $followUpAt; } if ($result === 'شماره اشتباه' && ($options['mark_phone_wrong'] ?? false) && $call->contactPhone) { $call->contactPhone->update(['status' => 'wrong']); ActivityLogger::log('contact_phone_marked_wrong', "Contact phone {$call->contactPhone->id} marked wrong from mobile call result", $call->contactPhone); } if ($result === 'عدم تمایل') { $reason = $options['disinterest_reason'] ?? null; if (($options['close_lead'] ?? false) === true) { $lead->update([ 'final_result' => 'ناموفق', 'lost_reason' => $reason, 'lead_status_id' => LeadStatus::where('slug', 'lost')->value('id') ?? $lead->lead_status_id, 'pipeline_stage_id' => PipelineStage::where('slug', 'closed')->value('id') ?? $lead->pipeline_stage_id, 'next_follow_up_at' => null, ]); $nextAction = 'لید بسته شد'; ActivityLogger::log('lead_closed_from_mobile_call_result', "Lead {$lead->id} closed after disinterest: {$reason}", $lead); } elseif ($call->contact) { $call->contact->update(['status' => 'inactive']); $nextAction = 'مخاطب غیرفعال شد'; ActivityLogger::log('contact_marked_inactive_from_mobile_call_result', "Contact {$call->contact->id} marked inactive after disinterest: {$reason}", $call->contact); } } if ($followUpAt) { if (!WorkingHours::followUpAllowed($followUpAt)) { throw \Illuminate\Validation\ValidationException::withMessages([ 'next_follow_up_at' => ['زمان پیگیری خارج از روز یا ساعت کاری مجاز است.'], ]); } $lead->update(['next_follow_up_at' => $followUpAt]); FollowUp::create([ 'lead_id' => $lead->id, 'user_id' => $call->user_id, 'call_id' => $call->id, 'scheduled_at' => $followUpAt, 'status' => 'pending', 'notes' => $newContact ? "پیگیری مخاطب معرفی‌شده: {$newContact->name}" : $notes, ]); NotificationService::notifyFollowUpReminder($call->user_id, $lead->full_name ?: ($lead->company ?? "لید {$lead->id}")); } CallLog::updateOrCreate( ['call_id' => $call->id], [ 'lead_id' => $lead->id, 'contact_id' => $call->contact_id, 'contact_phone_id' => $call->contact_phone_id, 'user_id' => $call->user_id, 'called_at' => $call->created_at ?? now(), 'result' => $result, 'notes' => $notes, 'next_action' => $nextAction, 'recording_url' => $call->recording_url, ] ); ActivityLogger::log('call_result_registered', "Call {$callId} result: {$result}", $call); return $call->fresh(['lead', 'contact', 'contactPhone']); }); } private function resolvePhone(Lead $lead, ?int $contactPhoneId): ContactPhone { if ($contactPhoneId) { return ContactPhone::whereHas('contact', fn($query) => $query->where('lead_id', $lead->id)) ->findOrFail($contactPhoneId); } $contact = $lead->contacts->firstWhere('is_primary', true) ?? $lead->contacts->first(); if ($contact) { $phone = $contact->phones->firstWhere('status', 'active') ?? $contact->phones->first(); if ($phone) { return $phone; } } $contact = Contact::create([ 'lead_id' => $lead->id, 'name' => $lead->full_name ?: ($lead->company ?? 'مخاطب اولیه'), 'role' => 'رابط', 'status' => 'active', 'is_primary' => true, 'primary_reason' => 'مخاطب اولیه لید', 'created_by' => $lead->assigned_to, ]); return ContactPhone::create([ 'contact_id' => $contact->id, 'phone' => $lead->phone, 'type' => 'mobile', 'status' => 'active', ]); } private function createReferralContact(Lead $lead, Call $call, array $referral, ?string $notes): Contact { $makePrimary = (bool) ($referral['make_primary'] ?? false); if ($makePrimary) { Contact::where('lead_id', $lead->id)->update(['is_primary' => false]); } $fromContact = $call->contact; $primaryReason = $fromContact ? "معرفی‌شده توسط {$fromContact->name}" : 'معرفی‌شده در تماس'; $contact = Contact::create([ 'lead_id' => $lead->id, 'name' => $referral['name'], 'role' => $referral['role'] ?? null, 'description' => $referral['description'] ?? $notes, 'status' => 'active', 'is_primary' => $makePrimary, 'primary_reason' => $makePrimary ? $primaryReason : null, 'created_by' => $call->user_id, ]); ContactPhone::create([ 'contact_id' => $contact->id, 'phone' => $referral['phone'], 'type' => $referral['phone_type'] ?? 'mobile', 'status' => 'active', ]); ContactRelation::create([ 'lead_id' => $lead->id, 'from_contact_id' => $call->contact_id, 'to_contact_id' => $contact->id, 'relation_type' => 'introduced', 'description' => $referral['relation_description'] ?? null, 'created_by' => $call->user_id, ]); if ($makePrimary) { ActivityLogger::log('primary_contact_changed', "Primary contact changed to {$contact->name}: {$primaryReason}", $lead); } return $contact; } }