9a9164fee2
Секция «На актуализацию»: дельта-карточка показывает текущие сайты и карточки фирмы поля + новый сайт кликабельными ссылками, кнопки Добавить и Заменить ведут через подтверждение «Создать проект?» в существующую форму CreateScreen с подставленным новым источником. Имя фирмы берётся из поля, не перезатирается.
Бэкенд: endpoint POST /competitors/{id}/actualize action=add|replace new_site. add заводит сайт-источник рядом со старым, replace архивирует старые сайт-источники и заводит новый, при активном проекте по старому сайту возвращает 409 manage_via_project. /proposals обогащён полем matched с текущими сайтами и карточками совпавшей фирмы.
По TDD. Бэкенд автоподбора 254/254, фронт автоподбора 64/64, ESLint чисто, Pint чисто.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
115 lines
5.7 KiB
PHP
115 lines
5.7 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');
|
|
});
|