From 1de698403532e4fbf4e05232a4404e7275509376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 28 Jun 2026 13:46:42 +0300 Subject: [PATCH] =?UTF-8?q?feat(=D0=B0=D0=B2=D1=82=D0=BE=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=B1=D0=BE=D1=80):=20=D0=B4=D0=B6=D0=BE=D0=B1=D0=B0=20=D1=88?= =?UTF-8?q?=D0=B0=D0=B3=D0=B0=201=20(=D0=BF=D0=BE=D0=B4=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D0=BA=D1=83=D1=80=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Autopodbor/RunAutopodborSearchJob.php | 88 +++++++++++++++++++ app/tests/Doubles/EmptyCompetitorAgent.php | 29 ++++++ .../Autopodbor/RunAutopodborSearchJobTest.php | 61 +++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php create mode 100644 app/tests/Doubles/EmptyCompetitorAgent.php create mode 100644 app/tests/Feature/Autopodbor/RunAutopodborSearchJobTest.php diff --git a/app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php b/app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php new file mode 100644 index 00000000..4aa0783a --- /dev/null +++ b/app/app/Jobs/Autopodbor/RunAutopodborSearchJob.php @@ -0,0 +1,88 @@ +runId); + + // Выставляем tenant-контекст сессионно — все запросы (включая вложенные транзакции charge) видят GUC + DB::statement('SET app.current_tenant_id = '.$run->tenant_id); + + $run->update(['status' => 'running', 'started_at' => now()]); + + try { + $p = $run->params; + $max = (int) (SystemSettings::get('autopodbor_max_competitors') ?? 15); + + $res = $agent->findCompetitors(new FindCompetitorsRequest( + regionCode: (int) $run->region_code, + examples: $p['examples'] ?? [], + aboutSelf: $p['about_self'] ?? [], + includeFederal: (bool) ($p['include_federal'] ?? false), + maxCompetitors: $max, + )); + + $unique = $dedup->dedupCompetitors($res->competitors); + + if ($unique === []) { + $run->update(['status' => 'empty', 'finished_at' => now()]); + return; + } + + foreach (array_slice($unique, 0, $max) as $c) { + AutopodborCompetitor::create([ + 'tenant_id' => $run->tenant_id, + 'search_run_id' => $run->id, + 'name' => $c['name'], + 'description' => $c['description'] ?? null, + 'is_federal' => (bool) ($c['is_federal'] ?? false), + 'relevance_pct' => $c['relevance_pct'] ?? null, + 'origin' => 'auto', + 'site_url' => $c['site_url'] ?? null, + 'directory_urls' => $c['directory_urls'] ?? [], + 'provenance' => $c['provenance'] ?? [], + 'dedup_key' => $c['dedup_key'], + ]); + } + + $price = (string) (SystemSettings::get('autopodbor_price_search_rub') ?? '0'); + $charge->chargeForRun($run, $price); + + $run->update(['status' => 'done', 'finished_at' => now()]); + } catch (\Throwable $e) { + $run->update([ + 'status' => 'failed', + 'error_code' => substr($e->getMessage(), 0, 64), + 'finished_at' => now(), + ]); + throw $e; + } + } +} diff --git a/app/tests/Doubles/EmptyCompetitorAgent.php b/app/tests/Doubles/EmptyCompetitorAgent.php new file mode 100644 index 00000000..279ccfca --- /dev/null +++ b/app/tests/Doubles/EmptyCompetitorAgent.php @@ -0,0 +1,29 @@ +call([new RunAutopodborSearchJob($runId), 'handle']); +} + +it('успешный подбор: сохраняет конкурентов, списывает, status=done', function () { + $tenant = Tenant::factory()->create(['balance_rub' => '100000.00']); + DB::statement("SET app.current_tenant_id = ".$tenant->id); + SystemSetting::updateOrCreate(['key' => 'autopodbor_price_search_rub'], ['value' => '500', 'type' => 'decimal']); + SystemSetting::updateOrCreate(['key' => 'autopodbor_max_competitors'], ['value' => '15', 'type' => 'int']); + $run = AutopodborRun::create([ + 'tenant_id' => $tenant->id, + 'kind' => 'search', + 'status' => 'queued', + 'region_code' => 16, + 'params' => ['examples' => ['okna.ru'], 'about_self' => [], 'include_federal' => true], + ]); + + runSearchJob($run->id); + + expect($run->fresh()->status)->toBe('done') + ->and($run->fresh()->price_rub_charged)->toBe('500.00') + ->and(AutopodborCompetitor::where('search_run_id', $run->id)->count())->toBeGreaterThan(0) + ->and((string) $tenant->fresh()->balance_rub)->toBe('99500.00'); +}); + +it('пустой результат: status=empty, без списания', function () { + app()->bind(CompetitorAgent::class, \Tests\Doubles\EmptyCompetitorAgent::class); + $tenant = Tenant::factory()->create(['balance_rub' => '100000.00']); + DB::statement("SET app.current_tenant_id = ".$tenant->id); + SystemSetting::updateOrCreate(['key' => 'autopodbor_price_search_rub'], ['value' => '500', 'type' => 'decimal']); + $run = AutopodborRun::create([ + 'tenant_id' => $tenant->id, + 'kind' => 'search', + 'status' => 'queued', + 'region_code' => 16, + 'params' => ['examples' => [], 'about_self' => [], 'include_federal' => false], + ]); + + runSearchJob($run->id); + + expect($run->fresh()->status)->toBe('empty') + ->and($run->fresh()->price_rub_charged)->toBeNull() + ->and((string) $tenant->fresh()->balance_rub)->toBe('100000.00'); +});