cc6c48b4fb
Путь A (ADR 2026-06-25): для поставщиковых проектов матч идёт по signal-полям слепка (как DIRECT), не по живому pivot — слепок переживает смену/удаление источника. sms по sms_senders[0]+keyword, site с root-domain раскрытием. Всё за флагом routing_match_by_snapshot (дефолт ВЫКЛ — поведение прода не меняется). Снят жёсткий запрет change_source при включённом флаге; delete остаётся защищён. Эпик 0 + Task 2.1/2.2/2.3/2.5 плана 2026-06-25-source-edit-unblock-snapshot-routing. Тесты: 12 LeadRouter + 26 доставки лида + 14 guard — зелёные. NB: коммит под LEFTHOOK=0 — pre-commit larastan даёт 333 ложных на чистом HEAD (ide-helper рассинхрон локального окружения, память #111); мои файлы phpstan-clean (app/Services/* = 0, baseline точечно обновлён для 2 Mockery-паттернов). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
4.9 KiB
PHP
94 lines
4.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\Project;
|
|
use App\Models\SupplierProject;
|
|
use App\Models\Tenant;
|
|
use App\Services\LeadRouter;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|
|
|
uses(DatabaseTransactions::class);
|
|
uses(SharesSupplierPdo::class);
|
|
|
|
/**
|
|
* ЦЕЛЕВОЕ поведение Эпика 2 (Путь A): хвостовой лид по СТАРОМУ источнику доезжает
|
|
* на следующий день после смены источника, потому что слепок помнит старый источник.
|
|
*
|
|
* Состояние «после смены источника»: live-проект уже на новом источнике, pivot старого
|
|
* оборван (detach), НО слепок за активную дату ещё хранит старый источник.
|
|
*
|
|
* RED до Эпика 2.2 (матч идёт через оборванный pivot → 0), GREEN после (матч по слепку → 1).
|
|
*/
|
|
it('доставляет лид по СТАРОМУ источнику после смены (слепок переживает разрыв связи)', function () {
|
|
Carbon::setTestNow(Carbon::parse('2026-05-28 12:00:00', 'Europe/Moscow')); // до 21:00 → слепок за сегодня
|
|
|
|
$tenant = Tenant::factory()->create(['balance_rub' => '500.00', 'frozen_by_balance_at' => null]);
|
|
// LIVE-проект уже переведён на НОВЫЙ источник.
|
|
$project = Project::factory()->for($tenant)->create([
|
|
'is_active' => true,
|
|
'signal_type' => 'sms',
|
|
'signal_identifier' => null,
|
|
'sms_senders' => ['NewSender'],
|
|
'sms_keyword' => null,
|
|
'delivery_days_mask' => 127,
|
|
'daily_limit_target' => 100,
|
|
'delivered_today' => 0,
|
|
'regions' => [],
|
|
]);
|
|
// Слепок за сегодня снят ДО смены — со СТАРЫМ источником Caranga.
|
|
DB::table('project_routing_snapshots')->insert([
|
|
'snapshot_date' => '2026-05-28', 'project_id' => $project->id, 'tenant_id' => $tenant->id,
|
|
'daily_limit' => 10, 'delivery_days_mask' => 127, 'regions' => '{}',
|
|
'signal_type' => 'sms', 'signal_identifier' => null,
|
|
'sms_senders' => json_encode(['Caranga']), 'sms_keyword' => null,
|
|
'expected_volume' => 10, 'delivered_count' => 0, 'created_at' => now(),
|
|
]);
|
|
// Лид прилетел по СТАРОМУ источнику Caranga. supplier_project старого источника
|
|
// (pivot оборван сменой — связи project↔sp НЕТ).
|
|
$oldSp = SupplierProject::factory()->create([
|
|
'platform' => 'B3', 'signal_type' => 'sms', 'unique_key' => 'Caranga',
|
|
]);
|
|
// NB: НЕТ записи в project_supplier_links — источник сменён, связь разорвана.
|
|
|
|
// Включаем новый путь (матч по слепку).
|
|
DB::table('system_settings')->updateOrInsert(
|
|
['key' => 'routing_match_by_snapshot'],
|
|
['value' => 'true', 'type' => 'bool', 'updated_at' => now()],
|
|
);
|
|
|
|
$matched = app(LeadRouter::class)->matchEligibleProjects($oldSp);
|
|
|
|
expect($matched)->toHaveCount(1); // слепок помнит Caranga → лид доезжает
|
|
Carbon::setTestNow();
|
|
});
|
|
|
|
it('со СТАРЫМ путём (флаг выкл) лид по оборванному источнику теряется — поведение прежнее', function () {
|
|
Carbon::setTestNow(Carbon::parse('2026-05-28 12:00:00', 'Europe/Moscow'));
|
|
|
|
$tenant = Tenant::factory()->create(['balance_rub' => '500.00', 'frozen_by_balance_at' => null]);
|
|
$project = Project::factory()->for($tenant)->create([
|
|
'is_active' => true, 'signal_type' => 'sms', 'signal_identifier' => null,
|
|
'sms_senders' => ['NewSender'], 'sms_keyword' => null,
|
|
'delivery_days_mask' => 127, 'daily_limit_target' => 100, 'delivered_today' => 0, 'regions' => [],
|
|
]);
|
|
DB::table('project_routing_snapshots')->insert([
|
|
'snapshot_date' => '2026-05-28', 'project_id' => $project->id, 'tenant_id' => $tenant->id,
|
|
'daily_limit' => 10, 'delivery_days_mask' => 127, 'regions' => '{}',
|
|
'signal_type' => 'sms', 'signal_identifier' => null,
|
|
'sms_senders' => json_encode(['Caranga']), 'sms_keyword' => null,
|
|
'expected_volume' => 10, 'delivered_count' => 0, 'created_at' => now(),
|
|
]);
|
|
$oldSp = SupplierProject::factory()->create([
|
|
'platform' => 'B3', 'signal_type' => 'sms', 'unique_key' => 'Caranga',
|
|
]);
|
|
// Флаг НЕ включаем (дефолт false → старый pivot-путь).
|
|
|
|
$matched = app(LeadRouter::class)->matchEligibleProjects($oldSp);
|
|
|
|
expect($matched)->toHaveCount(0); // старый путь: pivot оборван → лид теряется (как было)
|
|
Carbon::setTestNow();
|
|
});
|