251 خطوط
8.7 KiB
PHP
251 خطوط
8.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Contact;
|
|
use App\Models\ContactPhone;
|
|
use App\Models\ContactRelation;
|
|
use App\Models\FollowUp;
|
|
use App\Models\Lead;
|
|
use App\Models\LeadStatus;
|
|
use App\Models\PipelineStage;
|
|
use App\Models\Setting;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Spatie\Permission\Models\Role;
|
|
use Tests\TestCase;
|
|
|
|
class AgentMobileTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_agent_can_open_mobile_dashboard_and_queue(): void
|
|
{
|
|
$agent = $this->makeUserWithRole('agent');
|
|
$lead = $this->makeLead($agent, [
|
|
'priority' => 9,
|
|
'next_follow_up_at' => now()->subHour(),
|
|
]);
|
|
$this->makePrimaryContact($lead, '09120001122');
|
|
FollowUp::create([
|
|
'lead_id' => $lead->id,
|
|
'user_id' => $agent->id,
|
|
'scheduled_at' => now()->subHour(),
|
|
'status' => 'pending',
|
|
]);
|
|
|
|
$response = $this->actingAs($agent)
|
|
->getJson('/api/agent/mobile/dashboard')
|
|
->assertOk()
|
|
->assertJsonPath('data.stats.leads_needing_action', 1)
|
|
->assertJsonPath('data.suggested_call.id', $lead->id);
|
|
$this->assertStringContainsString('no-store', $response->headers->get('Cache-Control'));
|
|
|
|
$this->actingAs($agent)
|
|
->getJson('/api/agent/mobile/call-queue')
|
|
->assertOk()
|
|
->assertJsonPath('data.0.id', $lead->id)
|
|
->assertJsonMissing(['phone' => '09120001122']);
|
|
}
|
|
|
|
public function test_mobile_leads_are_scoped_to_logged_in_agent(): void
|
|
{
|
|
$agent = $this->makeUserWithRole('agent');
|
|
$otherAgent = $this->makeUserWithRole('agent');
|
|
$ownLead = $this->makeLead($agent, ['first_name' => 'Own']);
|
|
$otherLead = $this->makeLead($otherAgent, ['first_name' => 'Other']);
|
|
|
|
$this->actingAs($agent)
|
|
->getJson('/api/agent/mobile/leads')
|
|
->assertOk()
|
|
->assertJsonFragment(['id' => $ownLead->id])
|
|
->assertJsonMissing(['id' => $otherLead->id]);
|
|
|
|
$this->actingAs($agent)
|
|
->getJson("/api/agent/mobile/leads/{$otherLead->id}")
|
|
->assertForbidden();
|
|
}
|
|
|
|
public function test_non_agent_cannot_access_mobile_agent_endpoints(): void
|
|
{
|
|
$supervisor = $this->makeUserWithRole('supervisor');
|
|
|
|
$this->actingAs($supervisor)
|
|
->getJson('/api/agent/mobile/dashboard')
|
|
->assertForbidden();
|
|
}
|
|
|
|
public function test_mobile_lead_detail_does_not_expose_phone_numbers(): void
|
|
{
|
|
$agent = $this->makeUserWithRole('agent');
|
|
$lead = $this->makeLead($agent, [
|
|
'phone' => '09123334455',
|
|
'phone_secondary' => '02199887766',
|
|
]);
|
|
$this->makePrimaryContact($lead, '09123334455');
|
|
|
|
$response = $this->actingAs($agent)
|
|
->getJson("/api/agent/mobile/leads/{$lead->id}")
|
|
->assertOk();
|
|
|
|
$content = $response->getContent();
|
|
|
|
$this->assertStringNotContainsString('09123334455', $content);
|
|
$this->assertStringNotContainsString('02199887766', $content);
|
|
}
|
|
|
|
public function test_mobile_start_call_uses_secure_endpoint_without_exposing_phone(): void
|
|
{
|
|
$agent = $this->makeUserWithRole('agent');
|
|
$lead = $this->makeLead($agent);
|
|
$contact = $this->makePrimaryContact($lead, '09124445566');
|
|
$phone = $contact->phones()->first();
|
|
|
|
$response = $this->actingAs($agent)
|
|
->postJson('/api/agent/mobile/calls', [
|
|
'lead_id' => $lead->id,
|
|
'contact_phone_id' => $phone->id,
|
|
])
|
|
->assertCreated()
|
|
->assertJsonPath('data.contact_phone_id', $phone->id);
|
|
|
|
$this->assertStringNotContainsString('09124445566', $response->getContent());
|
|
$response->assertJsonMissingPath('data.recording_url');
|
|
$response->assertJsonMissingPath('data.provider_call_id');
|
|
}
|
|
|
|
public function test_ami_call_requires_agent_extension(): void
|
|
{
|
|
Setting::updateOrCreate(['key' => 'voip_provider'], ['value' => 'ami', 'group' => 'voip', 'type' => 'string']);
|
|
|
|
$agent = $this->makeUserWithRole('agent');
|
|
$lead = $this->makeLead($agent);
|
|
$contact = $this->makePrimaryContact($lead, '09124445566');
|
|
$phone = $contact->phones()->first();
|
|
|
|
$this->actingAs($agent)
|
|
->postJson('/api/agent/mobile/calls', [
|
|
'lead_id' => $lead->id,
|
|
'contact_phone_id' => $phone->id,
|
|
])
|
|
->assertUnprocessable()
|
|
->assertJsonPath('message', 'شماره داخلی برای این کاربر ثبت نشده است.');
|
|
|
|
$this->assertDatabaseMissing('calls', [
|
|
'lead_id' => $lead->id,
|
|
'user_id' => $agent->id,
|
|
]);
|
|
}
|
|
|
|
public function test_mobile_call_result_creates_follow_up_new_contact_and_primary_change(): void
|
|
{
|
|
$agent = $this->makeUserWithRole('agent');
|
|
$lead = $this->makeLead($agent);
|
|
$contact = $this->makePrimaryContact($lead, '09127778899');
|
|
$phone = $contact->phones()->first();
|
|
|
|
$callId = $this->actingAs($agent)
|
|
->postJson('/api/agent/mobile/calls', [
|
|
'lead_id' => $lead->id,
|
|
'contact_phone_id' => $phone->id,
|
|
])
|
|
->json('data.call_id');
|
|
|
|
$followUpAt = now()->addDay()->setSecond(0)->toIso8601String();
|
|
|
|
$this->actingAs($agent)
|
|
->postJson('/api/agent/mobile/call-results', [
|
|
'call_id' => $callId,
|
|
'result' => 'معرفی شماره جدید',
|
|
'notes' => 'با مخاطب جدید هماهنگ شود',
|
|
'referral' => [
|
|
'name' => 'خانم محمدی',
|
|
'phone' => '09121110000',
|
|
'role' => 'مدیر خرید',
|
|
'relation_description' => 'همکار تصمیمگیرنده',
|
|
'make_primary' => true,
|
|
'next_follow_up_at' => $followUpAt,
|
|
],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('message', 'نتیجه تماس ثبت شد');
|
|
|
|
$this->assertDatabaseHas('calls', [
|
|
'id' => $callId,
|
|
'result' => 'معرفی شماره جدید',
|
|
'user_id' => $agent->id,
|
|
]);
|
|
$this->assertDatabaseHas('follow_ups', [
|
|
'lead_id' => $lead->id,
|
|
'user_id' => $agent->id,
|
|
'call_id' => $callId,
|
|
'status' => 'pending',
|
|
]);
|
|
$this->assertDatabaseHas('contacts', [
|
|
'lead_id' => $lead->id,
|
|
'name' => 'خانم محمدی',
|
|
'is_primary' => true,
|
|
]);
|
|
$this->assertDatabaseHas('contact_phones', [
|
|
'phone' => '09121110000',
|
|
'status' => 'active',
|
|
]);
|
|
$this->assertSame(1, ContactRelation::where('lead_id', $lead->id)->where('from_contact_id', $contact->id)->count());
|
|
$this->assertFalse($contact->fresh()->is_primary);
|
|
}
|
|
|
|
private function makeUserWithRole(string $roleName): User
|
|
{
|
|
$role = Role::firstOrCreate(['name' => $roleName, 'guard_name' => 'web']);
|
|
$user = User::factory()->create(['is_active' => true]);
|
|
$user->assignRole($role);
|
|
|
|
return $user;
|
|
}
|
|
|
|
private function makeLead(User $agent, array $attributes = []): Lead
|
|
{
|
|
$status = LeadStatus::firstOrCreate(
|
|
['name' => 'در جریان'],
|
|
['color' => '#2563EB', 'sort_order' => 0, 'is_default' => true]
|
|
);
|
|
$stage = PipelineStage::firstOrCreate(
|
|
['name' => 'در انتظار تماس'],
|
|
['color' => '#6366F1', 'sort_order' => 1, 'is_default' => false]
|
|
);
|
|
|
|
return Lead::create(array_merge([
|
|
'company' => 'Acme',
|
|
'first_name' => 'Ali',
|
|
'last_name' => 'Karimi',
|
|
'phone' => '02111112222',
|
|
'assigned_to' => $agent->id,
|
|
'is_unassigned' => false,
|
|
'lead_status_id' => $status->id,
|
|
'pipeline_stage_id' => $stage->id,
|
|
], $attributes));
|
|
}
|
|
|
|
private function makePrimaryContact(Lead $lead, string $phone): Contact
|
|
{
|
|
$contact = Contact::create([
|
|
'lead_id' => $lead->id,
|
|
'name' => 'آقای رضایی',
|
|
'role' => 'مسئول خرید',
|
|
'status' => 'active',
|
|
'is_primary' => true,
|
|
'primary_reason' => 'مخاطب اصلی',
|
|
'created_by' => $lead->assigned_to,
|
|
]);
|
|
|
|
ContactPhone::create([
|
|
'contact_id' => $contact->id,
|
|
'phone' => $phone,
|
|
'type' => 'mobile',
|
|
'status' => 'active',
|
|
]);
|
|
|
|
return $contact;
|
|
}
|
|
}
|