a4c9200261
Раздел «Сделки»: колонка «Источник» разбита на две — «Конкурент» (ссылка на экран конкурента) и «Источник» (ссылка на «Настройки проекта»). В модалку настроек добавлена кнопка пуск/стоп (срабатывает не закрывая окно). - бэк DealController::index: батч-резолв конкурента+источника по deal.project_id → autopodbor_sources.created_project_id → competitor (без N+1) - контракт ApiDeal/MockDeal + маппер: competitor_*/source_* поля - DealsTable: 1 колонка → 2 (Конкурент/Источник), router-link, fallback без автоподбора - AutopodborView: deep-link ?competitor=&project= открывает экран конкурента + модалку - FieldCompetitorScreen: кнопка ⏸/▶ в модалке настроек + авто-открытие по ссылке Тесты: бэк DealIndexTest +4 (31 ✅), фронт +новые (66 ✅). Дизайн не менялся. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
496 lines
21 KiB
PHP
496 lines
21 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Models\AutopodborCompetitor;
|
||
use App\Models\AutopodborRun;
|
||
use App\Models\AutopodborSource;
|
||
use App\Models\Deal;
|
||
use App\Models\LeadCharge;
|
||
use App\Models\Project;
|
||
use App\Models\Tenant;
|
||
use App\Models\User;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Str;
|
||
|
||
/**
|
||
* Тесты GET /api/deals — list-endpoint для DealsView/KanbanView (замена MOCK_DEALS).
|
||
*
|
||
* Покрывает: фильтры (status_in, project_id, manager_id, search), сортировку
|
||
* по received_at DESC, RLS-изоляцию между tenant'ами, относительные поля
|
||
* (project_name, manager_name/initials), 401/404, пагинацию (limit/offset).
|
||
*/
|
||
uses(DatabaseTransactions::class);
|
||
|
||
beforeEach(function () {
|
||
$this->tenant = Tenant::factory()->create();
|
||
$this->otherTenant = Tenant::factory()->create();
|
||
|
||
$this->user = User::factory()->for($this->tenant)->create();
|
||
$this->actingAs($this->user);
|
||
|
||
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
|
||
$this->project = Project::factory()->for($this->tenant)->create(['name' => 'Окна Москва']);
|
||
$this->project2 = Project::factory()->for($this->tenant)->create(['name' => 'Натяжные потолки']);
|
||
|
||
$this->manager = User::factory()->for($this->tenant)->create([
|
||
'first_name' => 'Иван',
|
||
'last_name' => 'Петров',
|
||
'email' => 'ivan@example.test',
|
||
]);
|
||
});
|
||
|
||
test('GET /api/deals возвращает 401 без auth', function () {
|
||
auth()->logout();
|
||
$this->getJson('/api/deals')->assertStatus(401);
|
||
});
|
||
|
||
test('GET /api/deals возвращает пустой список для tenant без сделок', function () {
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
$r->assertStatus(200)
|
||
->assertJson(['deals' => [], 'total' => 0, 'limit' => 100, 'offset' => 0]);
|
||
});
|
||
|
||
test('GET /api/deals возвращает сделки tenant\'а с проектом и менеджером', function () {
|
||
$deal = Deal::factory()
|
||
->for($this->tenant)
|
||
->for($this->project)
|
||
->create([
|
||
'phone' => '+7 (999) 111-11-11',
|
||
'contact_name' => 'Анна С.',
|
||
'status' => 'new',
|
||
'manager_id' => $this->manager->id,
|
||
]);
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
$r->assertStatus(200);
|
||
expect($r->json('total'))->toBe(1);
|
||
expect($r->json('deals.0.id'))->toBe($deal->id);
|
||
expect($r->json('deals.0.phone'))->toBe('+7 (999) 111-11-11');
|
||
expect($r->json('deals.0.project_name'))->toBe('Окна Москва');
|
||
expect($r->json('deals.0.manager_name'))->toBe('Иван П.');
|
||
expect($r->json('deals.0.manager_initials'))->toBe('ИП');
|
||
expect($r->json('deals.0.contact_name'))->toBe('Анна С.');
|
||
expect($r->json('deals.0.received_at'))->toBeString();
|
||
});
|
||
|
||
test('GET /api/deals не возвращает сделки чужого tenant\'а (RLS)', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
|
||
|
||
DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id);
|
||
$foreignProject = Project::factory()->for($this->otherTenant)->create();
|
||
Deal::factory()->for($this->otherTenant)->for($foreignProject)->create(['status' => 'new']);
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
expect($r->json('total'))->toBe(1);
|
||
expect($r->json('deals.0.tenant_id'))->toBe($this->tenant->id);
|
||
});
|
||
|
||
test('GET /api/deals сортирует по received_at DESC', function () {
|
||
$oldest = Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'received_at' => now()->subHours(3),
|
||
]);
|
||
$newest = Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'received_at' => now()->subMinutes(1),
|
||
]);
|
||
$middle = Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'received_at' => now()->subHours(1),
|
||
]);
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
expect($r->json('deals.0.id'))->toBe($newest->id);
|
||
expect($r->json('deals.1.id'))->toBe($middle->id);
|
||
expect($r->json('deals.2.id'))->toBe($oldest->id);
|
||
});
|
||
|
||
test('GET /api/deals фильтрует по status_in[]', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'won']);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'lost']);
|
||
|
||
$r = $this->getJson('/api/deals?status_in[]=new&status_in[]=won');
|
||
|
||
expect($r->json('total'))->toBe(2);
|
||
$statuses = collect($r->json('deals'))->pluck('status')->sort()->values()->all();
|
||
expect($statuses)->toBe(['new', 'won']);
|
||
});
|
||
|
||
test('GET /api/deals фильтрует по project_id', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
Deal::factory()->for($this->tenant)->for($this->project2)->create();
|
||
|
||
$r = $this->getJson('/api/deals?project_id='.$this->project2->id);
|
||
|
||
expect($r->json('total'))->toBe(1);
|
||
expect($r->json('deals.0.project_name'))->toBe('Натяжные потолки');
|
||
});
|
||
|
||
test('GET /api/deals фильтрует по manager_id', function () {
|
||
$other = User::factory()->for($this->tenant)->create();
|
||
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['manager_id' => $this->manager->id]);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['manager_id' => $other->id]);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['manager_id' => null]);
|
||
|
||
$r = $this->getJson('/api/deals?manager_id='.$this->manager->id);
|
||
|
||
expect($r->json('total'))->toBe(1);
|
||
expect($r->json('deals.0.manager_id'))->toBe($this->manager->id);
|
||
});
|
||
|
||
test('GET /api/deals фильтрует по search (phone + contact_name, ILIKE)', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'phone' => '+7 (999) 111-11-11',
|
||
'contact_name' => 'Анна Соколова',
|
||
]);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'phone' => '+7 (903) 222-22-22',
|
||
'contact_name' => 'Дмитрий Петров',
|
||
]);
|
||
|
||
expect($this->getJson('/api/deals?search=Соколова')
|
||
->json('total'))->toBe(1);
|
||
|
||
expect($this->getJson('/api/deals?search=903')
|
||
->json('total'))->toBe(1);
|
||
|
||
expect($this->getJson('/api/deals?search=сокол') // case-insensitive ILIKE
|
||
->json('total'))->toBe(1);
|
||
});
|
||
|
||
test('GET /api/deals поддерживает limit + offset', function () {
|
||
foreach (range(1, 5) as $i) {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'received_at' => now()->subMinutes($i),
|
||
]);
|
||
}
|
||
|
||
$r = $this->getJson('/api/deals?limit=2&offset=1');
|
||
|
||
expect($r->json('total'))->toBe(5);
|
||
expect($r->json('limit'))->toBe(2);
|
||
expect($r->json('offset'))->toBe(1);
|
||
expect(count($r->json('deals')))->toBe(2);
|
||
});
|
||
|
||
test('GET /api/deals?only_deleted=true возвращает только soft-deleted', function () {
|
||
$alive = Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
$deleted1 = Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
$deleted2 = Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
$deleted1->delete();
|
||
$deleted2->delete();
|
||
|
||
$r = $this->getJson('/api/deals?only_deleted=true');
|
||
|
||
expect($r->json('total'))->toBe(2);
|
||
$ids = collect($r->json('deals'))->pluck('id')->all();
|
||
expect($ids)->not->toContain($alive->id);
|
||
expect($ids)->toContain($deleted1->id);
|
||
expect($ids)->toContain($deleted2->id);
|
||
});
|
||
|
||
test('GET /api/deals (без only_deleted) НЕ возвращает soft-deleted (default)', function () {
|
||
$alive = Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
$deleted = Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
$deleted->delete();
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
expect($r->json('total'))->toBe(1);
|
||
expect($r->json('deals.0.id'))->toBe($alive->id);
|
||
});
|
||
|
||
test('GET /api/deals?only_deleted=true изолирует чужие удалённые сделки', function () {
|
||
DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id);
|
||
$foreignProject = Project::factory()->for($this->otherTenant)->create();
|
||
$foreign = Deal::factory()->for($this->otherTenant)->for($foreignProject)->create();
|
||
$foreign->delete();
|
||
|
||
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
|
||
$own = Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
$own->delete();
|
||
|
||
$r = $this->getJson('/api/deals?only_deleted=true');
|
||
|
||
expect($r->json('total'))->toBe(1);
|
||
expect($r->json('deals.0.id'))->toBe($own->id);
|
||
});
|
||
|
||
test('GET /api/deals возвращает manager_name/initials = null если manager_id null', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['manager_id' => null]);
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
expect($r->json('deals.0.manager_id'))->toBeNull();
|
||
expect($r->json('deals.0.manager_name'))->toBeNull();
|
||
expect($r->json('deals.0.manager_initials'))->toBeNull();
|
||
});
|
||
|
||
// Sprint 4 Phase A (audit O-perf-04): keyset pagination через cursor.
|
||
|
||
test('GET /api/deals с cursor возвращает следующую страницу через keyset', function () {
|
||
// 5 сделок с received_at через 1 минуту (фиксируем порядок).
|
||
$base = now()->subHours(5);
|
||
$ids = [];
|
||
for ($i = 0; $i < 5; $i++) {
|
||
$ids[] = Deal::factory()
|
||
->for($this->tenant)
|
||
->for($this->project)
|
||
->create([
|
||
'status' => 'new',
|
||
'received_at' => $base->copy()->addMinutes($i),
|
||
])->id;
|
||
}
|
||
|
||
// Первая страница без cursor: limit=2 → последние 2 (по received_at DESC).
|
||
$r1 = $this->getJson('/api/deals?limit=2');
|
||
$r1->assertStatus(200);
|
||
expect($r1->json('deals'))->toHaveLength(2);
|
||
expect($r1->json('deals.0.id'))->toBe($ids[4]);
|
||
expect($r1->json('deals.1.id'))->toBe($ids[3]);
|
||
|
||
// Cursor для следующей страницы — base64 от {r:received_at,i:id} последнего элемента.
|
||
$cursor = base64_encode((string) json_encode([
|
||
'r' => $r1->json('deals.1.received_at'),
|
||
'i' => $r1->json('deals.1.id'),
|
||
]));
|
||
|
||
$r2 = $this->getJson('/api/deals?limit=2&cursor='.$cursor);
|
||
$r2->assertStatus(200);
|
||
expect($r2->json('deals'))->toHaveLength(2);
|
||
expect($r2->json('deals.0.id'))->toBe($ids[2]);
|
||
expect($r2->json('deals.1.id'))->toBe($ids[1]);
|
||
});
|
||
|
||
test('GET /api/deals с невалидным cursor возвращает 422', function () {
|
||
$r = $this->getJson('/api/deals?cursor=not-base64-json');
|
||
$r->assertStatus(422);
|
||
expect($r->json('message'))->toBeString();
|
||
});
|
||
|
||
test('GET /api/deals возвращает next_cursor когда есть ещё страницы', function () {
|
||
$base = now()->subHours(3);
|
||
for ($i = 0; $i < 3; $i++) {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'status' => 'new',
|
||
'received_at' => $base->copy()->addMinutes($i),
|
||
]);
|
||
}
|
||
|
||
$r = $this->getJson('/api/deals?limit=2');
|
||
$r->assertStatus(200);
|
||
expect($r->json('next_cursor'))->toBeString();
|
||
expect($r->json('next_cursor'))->not->toBeEmpty();
|
||
|
||
// Последняя страница: next_cursor = null.
|
||
$cursor = $r->json('next_cursor');
|
||
$r2 = $this->getJson('/api/deals?limit=2&cursor='.$cursor);
|
||
$r2->assertStatus(200);
|
||
expect($r2->json('next_cursor'))->toBeNull();
|
||
});
|
||
|
||
test('GET /api/deals?count_only=1 возвращает только total без массива deals', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'won']);
|
||
|
||
$r = $this->getJson('/api/deals?count_only=1');
|
||
|
||
$r->assertStatus(200);
|
||
expect($r->json('total'))->toBe(2);
|
||
expect($r->json('deals'))->toBeNull();
|
||
});
|
||
|
||
test('GET /api/deals?count_only=1 учитывает фильтры (status_in)', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'won']);
|
||
|
||
expect($this->getJson('/api/deals?count_only=1&status_in[]=new')->json('total'))->toBe(2);
|
||
});
|
||
|
||
test('GET /api/deals?count_only=1 изолирует чужой tenant (RLS)', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['status' => 'new']);
|
||
|
||
DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id);
|
||
$foreignProject = Project::factory()->for($this->otherTenant)->create();
|
||
Deal::factory()->for($this->otherTenant)->for($foreignProject)->create(['status' => 'new']);
|
||
|
||
expect($this->getJson('/api/deals?count_only=1')->json('total'))->toBe(1);
|
||
});
|
||
|
||
test('GET /api/deals фильтрует по received_from/received_to', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['received_at' => '2026-05-10 12:00:00']);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['received_at' => '2026-05-15 12:00:00']);
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['received_at' => '2026-05-20 12:00:00']);
|
||
|
||
$r = $this->getJson('/api/deals?received_from=2026-05-12&received_to=2026-05-16');
|
||
|
||
expect($r->json('total'))->toBe(1);
|
||
});
|
||
|
||
test('GET /api/deals received_to включает весь день (конец дня)', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create(['received_at' => '2026-05-16 23:30:00']);
|
||
|
||
expect($this->getJson('/api/deals?received_to=2026-05-16')->json('total'))->toBe(1);
|
||
});
|
||
|
||
test('GET /api/deals возвращает comment/city/project_signal_type', function () {
|
||
$this->project->update(['signal_type' => 'call', 'signal_identifier' => '79990001122']);
|
||
$deal = Deal::factory()->for($this->tenant)->for($this->project)->create([
|
||
'comment' => 'перезвонить',
|
||
'city' => 'Казань',
|
||
]);
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
expect($r->json('deals.0.comment'))->toBe('перезвонить');
|
||
expect($r->json('deals.0.city'))->toBe('Казань');
|
||
expect($r->json('deals.0.project_signal_type'))->toBe('call');
|
||
});
|
||
|
||
test('GET /api/deals возвращает 422 на невалидную received_from', function () {
|
||
$this->getJson('/api/deals?received_from=не-дата')->assertStatus(422);
|
||
});
|
||
|
||
test('GET /api/deals возвращает 422 на невалидную received_to', function () {
|
||
$this->getJson('/api/deals?received_to=garbage')->assertStatus(422);
|
||
});
|
||
|
||
/* ----------------------------------------------------------------------
|
||
* U4 (UX-аудит 25.06): цена лида должна быть видна и в листинге, не только
|
||
* в детальном show — иначе панель и канбан показывают «—» при реальном списании.
|
||
* -------------------------------------------------------------------- */
|
||
|
||
test('GET /api/deals отдаёт cost_kopecks из lead_charges для rub-списания', function () {
|
||
$deal = Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
|
||
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
|
||
LeadCharge::create([
|
||
'tenant_id' => $this->tenant->id,
|
||
'deal_id' => $deal->id,
|
||
'deal_received_at' => $deal->received_at,
|
||
'tier_no' => 1,
|
||
'charge_source' => 'rub',
|
||
'price_per_lead_kopecks' => 7000,
|
||
'charged_at' => now(),
|
||
'created_at' => now(),
|
||
]);
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
$r->assertStatus(200);
|
||
expect($r->json('deals.0.cost_kopecks'))->toBe(7000);
|
||
});
|
||
|
||
test('GET /api/deals cost_kopecks = null если списания нет', function () {
|
||
Deal::factory()->for($this->tenant)->for($this->project)->create();
|
||
|
||
$r = $this->getJson('/api/deals');
|
||
|
||
$r->assertStatus(200);
|
||
expect($r->json('deals.0.cost_kopecks'))->toBeNull();
|
||
});
|
||
|
||
/* ----------------------------------------------------------------------
|
||
* Автоподбор (2026-07-08): конкурент + источник в списке сделок.
|
||
* Связь deal.project_id → autopodbor_sources.created_project_id → competitor.
|
||
* -------------------------------------------------------------------- */
|
||
|
||
/**
|
||
* Создаёт конкурента автоподбора + его источники (box=field), привязанные к проекту.
|
||
* $sources — массив пар [signal_type, identifier].
|
||
*/
|
||
function makeAutopodborField(int $tenantId, int $projectId, string $name, array $sources): AutopodborCompetitor
|
||
{
|
||
$run = AutopodborRun::create(['tenant_id' => $tenantId, 'kind' => 'study']);
|
||
$competitor = AutopodborCompetitor::create([
|
||
'tenant_id' => $tenantId,
|
||
'name' => $name,
|
||
'description' => 'микрокредитная компания',
|
||
'relevance_pct' => 58,
|
||
'dedup_key' => 'dk-'.Str::random(10),
|
||
'box' => 'field',
|
||
]);
|
||
foreach ($sources as [$signalType, $ident]) {
|
||
AutopodborSource::create([
|
||
'tenant_id' => $tenantId,
|
||
'competitor_id' => $competitor->id,
|
||
'study_run_id' => $run->id,
|
||
'signal_type' => $signalType,
|
||
'identifier' => $ident,
|
||
'dedup_key' => 'sk-'.Str::random(10),
|
||
'created_project_id' => $projectId,
|
||
'box' => 'field',
|
||
]);
|
||
}
|
||
|
||
return $competitor;
|
||
}
|
||
|
||
test('GET /api/deals отдаёт competitor_* и source_* для автоподбор-сделки', function () {
|
||
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
|
||
|
||
$competitor = makeAutopodborField($this->tenant->id, $this->project->id, 'AutoMoney', [
|
||
['call', '79135396546'],
|
||
]);
|
||
Deal::factory()->for($this->tenant)->create([
|
||
'project_id' => $this->project->id,
|
||
'phone' => '79000000001',
|
||
'received_at' => now(),
|
||
]);
|
||
|
||
$res = $this->getJson('/api/deals')->assertOk()->json('deals.0');
|
||
|
||
expect($res['competitor_id'])->toBe($competitor->id);
|
||
expect($res['competitor_name'])->toBe('AutoMoney');
|
||
expect($res['source_identifier'])->toBe('79135396546');
|
||
expect($res['source_signal_type'])->toBe('call');
|
||
expect($res['source_project_id'])->toBe($this->project->id);
|
||
expect($res['source_is_active'])->toBeTrue();
|
||
expect($res['source_extra_count'])->toBe(0);
|
||
});
|
||
|
||
test('GET /api/deals: merge источников на проект → один конкурент, source_extra_count>0', function () {
|
||
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
|
||
|
||
makeAutopodborField($this->tenant->id, $this->project->id, 'Сфера займа24', [
|
||
['call', '79029826282'],
|
||
['site', 'sfera.ru'],
|
||
]);
|
||
Deal::factory()->for($this->tenant)->create(['project_id' => $this->project->id, 'received_at' => now()]);
|
||
|
||
$res = $this->getJson('/api/deals')->assertOk()->json('deals.0');
|
||
expect($res['competitor_name'])->toBe('Сфера займа24');
|
||
expect($res['source_extra_count'])->toBe(1);
|
||
});
|
||
|
||
test('GET /api/deals: сделка без автоподбор-источника → competitor/source = null', function () {
|
||
Deal::factory()->for($this->tenant)->create(['project_id' => $this->project2->id, 'received_at' => now()]);
|
||
|
||
$res = $this->getJson('/api/deals')->assertOk()->json('deals.0');
|
||
expect($res['competitor_id'])->toBeNull();
|
||
expect($res['competitor_name'])->toBeNull();
|
||
expect($res['source_identifier'])->toBeNull();
|
||
expect($res['source_project_id'])->toBeNull();
|
||
expect($res['source_is_active'])->toBeNull();
|
||
expect($res['source_extra_count'])->toBe(0);
|
||
});
|
||
|
||
test('GET /api/deals: источник чужого tenant не подтягивается к сделке', function () {
|
||
// Источник чужого tenant с created_project_id нашего проекта — where(tenant_id)+RLS отсекают.
|
||
DB::statement('SET app.current_tenant_id = '.$this->otherTenant->id);
|
||
makeAutopodborField($this->otherTenant->id, $this->project->id, 'Чужой', [['call', '70000000000']]);
|
||
|
||
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
|
||
Deal::factory()->for($this->tenant)->create(['project_id' => $this->project->id, 'received_at' => now()]);
|
||
|
||
$res = $this->getJson('/api/deals')->assertOk()->json('deals.0');
|
||
expect($res['competitor_name'])->toBeNull();
|
||
});
|