43012c982e
Кнопка «Удалить» на карточках «На актуализацию» рядом с Добавить/Заменить. Отклоняет ТОЛЬКО находку (новый сайт/адрес): фирма поля остаётся, находка → архив, её новые ключи запоминаются у фирмы поля (новая колонка dismissed_actualize_keys jsonb). Классификатор вычитает отклонённые ключи → та же находка больше не всплывает; реально другой новый сайт даст новый ключ → покажется. Не глушим по имени, конкурента не убираем. Бэк: миграция + модель-каст + ProposalClassifier (вычитание) + эндпоинт dismissActualize + роут. Фронт: api + store + кнопка «Удалить». Тесты: классификатор 8/8, ветка 185/185, фронт 30/30. Canon-sync schema.sql (v8.62) — follow-up (миграция idempotent-guarded). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
150 lines
7.8 KiB
PHP
150 lines
7.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\AutopodborCompetitor;
|
|
use App\Models\AutopodborRun;
|
|
use App\Models\AutopodborSource;
|
|
use App\Models\Project;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|
|
|
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
|
|
|
|
/**
|
|
* Фирма в поле с одним сайт-источником (изучена — study_run_id есть).
|
|
*
|
|
* @return array{0: Tenant, 1: User, 2: AutopodborRun, 3: AutopodborCompetitor, 4: AutopodborSource}
|
|
*/
|
|
function actualizeSetup(): array
|
|
{
|
|
$tenant = Tenant::factory()->create();
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
|
DB::statement('SET app.current_tenant_id = '.$tenant->id);
|
|
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
|
|
$field = AutopodborCompetitor::create([
|
|
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(),
|
|
'name' => 'Яричъ', 'box' => 'field', 'site_url' => 'yarich.ru', 'dedup_key' => 'site:yarich.ru',
|
|
]);
|
|
$oldSrc = AutopodborSource::create([
|
|
'tenant_id' => $tenant->id, 'competitor_id' => $field->id, 'study_run_id' => $run->id,
|
|
'signal_type' => 'site', 'identifier' => 'yarich.ru', 'box' => 'field', 'dedup_key' => 'site:yarich.ru',
|
|
]);
|
|
|
|
return [$tenant, $user, $run, $field, $oldSrc];
|
|
}
|
|
|
|
it('actualize add — новый сайт заводится источником, старый цел', function () {
|
|
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
|
|
|
|
$resp = $this->actingAs($user)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
|
|
'action' => 'add', 'new_site' => 'https://www.Yarich-New.ru/contacts',
|
|
])->assertOk();
|
|
|
|
$new = AutopodborSource::find($resp->json('source_id'));
|
|
expect($new)->not->toBeNull()
|
|
->and($new->signal_type)->toBe('site')
|
|
->and($new->identifier)->toBe('yarich-new.ru') // нормализован
|
|
->and($new->box)->toBe('field');
|
|
// старый сайт-источник не тронут (оба сайта живут)
|
|
expect($oldSrc->fresh()->box)->toBe('field');
|
|
});
|
|
|
|
it('actualize replace без активного проекта — старый сайт в архив, новый заводится', function () {
|
|
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
|
|
|
|
$resp = $this->actingAs($user)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
|
|
'action' => 'replace', 'new_site' => 'yarich-new.ru',
|
|
])->assertOk();
|
|
|
|
expect($oldSrc->fresh()->box)->toBe('archived'); // старый умер — в архив
|
|
$new = AutopodborSource::find($resp->json('source_id'));
|
|
expect($new->identifier)->toBe('yarich-new.ru')->and($new->box)->toBe('field');
|
|
});
|
|
|
|
it('actualize replace при активном проекте по старому сайту — 409 manage_via_project', function () {
|
|
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
|
|
$project = Project::factory()->create(['tenant_id' => $tenant->id, 'is_active' => true, 'preflight_blocked_at' => null]);
|
|
$oldSrc->update(['created_project_id' => $project->id]);
|
|
|
|
$this->actingAs($user)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
|
|
'action' => 'replace', 'new_site' => 'yarich-new.ru',
|
|
])->assertStatus(409)->assertJsonPath('error', 'manage_via_project');
|
|
|
|
// ничего не тронуто — управлять надо через проект
|
|
expect($oldSrc->fresh()->box)->toBe('field');
|
|
});
|
|
|
|
it('actualize чужой тенант — 404', function () {
|
|
[$tenant, $user, $run, $field, $oldSrc] = actualizeSetup();
|
|
$other = User::factory()->create(['tenant_id' => Tenant::factory()->create()->id]);
|
|
|
|
$this->actingAs($other)->postJson("/api/autopodbor/competitors/{$field->id}/actualize", [
|
|
'action' => 'add', 'new_site' => 'x.ru',
|
|
])->assertStatus(404);
|
|
});
|
|
|
|
it('GET /proposals — actualize-карточка несёт matched с текущими сайтами фирмы', function () {
|
|
$tenant = Tenant::factory()->create();
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
|
DB::statement('SET app.current_tenant_id = '.$tenant->id);
|
|
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
|
|
|
|
// в поле: Яричъ, сайт yarich.ru, карточка 2ГИС 111
|
|
$field = AutopodborCompetitor::create([
|
|
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'study_run_id' => $run->id, 'studied_at' => now(),
|
|
'name' => 'Яричъ', 'box' => 'field', 'site_url' => 'yarich.ru',
|
|
'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:yarich.ru',
|
|
]);
|
|
// предложение: та же карточка 2ГИС 111, но новый сайт → на актуализацию
|
|
AutopodborCompetitor::create([
|
|
'tenant_id' => $tenant->id, 'search_run_id' => $run->id,
|
|
'name' => 'Яричъ (нашли заново)', 'box' => 'proposal', 'site_url' => 'yarich-new.ru',
|
|
'directory_urls' => ['https://2gis.ru/y/firm/111'], 'dedup_key' => 'site:yarich-new.ru',
|
|
]);
|
|
|
|
$resp = $this->actingAs($user)->getJson('/api/autopodbor/proposals')->assertOk();
|
|
|
|
$resp->assertJsonPath('groups.actualize.0.matched_id', $field->id)
|
|
->assertJsonPath('groups.actualize.0.matched.name', 'Яричъ');
|
|
expect(collect($resp->json('groups.actualize.0.matched.sites')))->toContain('yarich.ru');
|
|
});
|
|
|
|
it('dismiss-actualize — находка в архив, ключ запомнен, та же находка больше не всплывает', function () {
|
|
$tenant = Tenant::factory()->create();
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
|
DB::statement('SET app.current_tenant_id = '.$tenant->id);
|
|
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
|
|
|
|
// Фирма поля: сайт old.ru + карточка 2ГИС firm/111.
|
|
$field = AutopodborCompetitor::create([
|
|
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'X', 'box' => 'field',
|
|
'site_url' => 'old.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:old.ru',
|
|
]);
|
|
// Находка: та же карточка, но новый сайт new.ru → actualize.
|
|
$find = AutopodborCompetitor::create([
|
|
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'X', 'box' => 'proposal',
|
|
'site_url' => 'new.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:new.ru',
|
|
]);
|
|
|
|
$before = $this->actingAs($user)->getJson('/api/autopodbor/proposals')->assertOk()->json('groups.actualize');
|
|
expect(collect($before)->pluck('id'))->toContain($find->id);
|
|
|
|
$this->actingAs($user)->postJson("/api/autopodbor/competitors/{$find->id}/dismiss-actualize")
|
|
->assertOk()->assertJson(['dismissed' => true]);
|
|
|
|
expect($find->fresh()->box)->toBe('archived')
|
|
->and($field->fresh()->dismissed_actualize_keys)->toContain('s:new.ru');
|
|
|
|
// Новая такая же находка (new.ru) больше НЕ попадает в actualize.
|
|
$find2 = AutopodborCompetitor::create([
|
|
'tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'X', 'box' => 'proposal',
|
|
'site_url' => 'new.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111'], 'dedup_key' => 'site:new.ru-2',
|
|
]);
|
|
$after = $this->actingAs($user)->getJson('/api/autopodbor/proposals')->assertOk()->json('groups.actualize');
|
|
expect(collect($after)->pluck('id'))->not->toContain($find2->id);
|
|
});
|