From e2fb20ef05730bb8aa7058f4f3cf2e8a786e0fc1 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: Thu, 21 May 2026 07:35:11 +0300 Subject: [PATCH] feat(projects): source+name dedup on update --- app/app/Services/Project/ProjectService.php | 12 ++++++++ .../Project/ProjectCreateDedupTest.php | 2 ++ .../Project/ProjectUpdateDedupTest.php | 28 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 app/tests/Feature/Project/ProjectUpdateDedupTest.php diff --git a/app/app/Services/Project/ProjectService.php b/app/app/Services/Project/ProjectService.php index 24317d58..9c2c2d56 100644 --- a/app/app/Services/Project/ProjectService.php +++ b/app/app/Services/Project/ProjectService.php @@ -41,6 +41,18 @@ class ProjectService || array_key_exists('daily_limit_target', $data) || array_key_exists('delivery_days_mask', $data); + if (array_key_exists('signal_identifier', $data) || array_key_exists('sms_senders', $data) || array_key_exists('sms_keyword', $data)) { + $this->assertSourceUnique($project->tenant_id, array_merge([ + 'signal_type' => $project->signal_type, + 'signal_identifier' => $project->signal_identifier, + 'sms_senders' => $project->sms_senders, + 'sms_keyword' => $project->sms_keyword, + ], $data), exceptId: $project->id); + } + if (array_key_exists('name', $data)) { + $this->assertNameUnique($project->tenant_id, (string) $data['name'], exceptId: $project->id); + } + $project->update($data); if ($needsResync) { diff --git a/app/tests/Feature/Project/ProjectCreateDedupTest.php b/app/tests/Feature/Project/ProjectCreateDedupTest.php index 909dbfe9..93a0e8d6 100644 --- a/app/tests/Feature/Project/ProjectCreateDedupTest.php +++ b/app/tests/Feature/Project/ProjectCreateDedupTest.php @@ -6,8 +6,10 @@ use App\Models\Project; use App\Models\Tenant; use App\Services\Project\ProjectService; use Illuminate\Http\Exceptions\HttpResponseException; +use Illuminate\Support\Facades\Queue; beforeEach(function () { + Queue::fake(); $this->tenant = Tenant::factory()->create(['balance_leads' => 100]); }); diff --git a/app/tests/Feature/Project/ProjectUpdateDedupTest.php b/app/tests/Feature/Project/ProjectUpdateDedupTest.php new file mode 100644 index 00000000..017b252a --- /dev/null +++ b/app/tests/Feature/Project/ProjectUpdateDedupTest.php @@ -0,0 +1,28 @@ + Queue::fake()); + +it('blocks update that collides source with another project of same tenant', function () { + $tenant = Tenant::factory()->create(['balance_leads' => 100]); + $svc = app(ProjectService::class); + $a = $svc->create($tenant, ['name' => 'A', 'signal_type' => 'call', 'signal_identifier' => '79991110000', 'daily_limit_target' => 5, 'regions' => [], 'delivery_days_mask' => 31]); + $b = $svc->create($tenant, ['name' => 'B', 'signal_type' => 'call', 'signal_identifier' => '79992220000', 'daily_limit_target' => 5, 'regions' => [], 'delivery_days_mask' => 31]); + + expect(fn () => $svc->update($b, ['signal_identifier' => '79991110000'])) + ->toThrow(HttpResponseException::class); +}); + +it('allows update keeping same source on the same project', function () { + $tenant = Tenant::factory()->create(['balance_leads' => 100]); + $svc = app(ProjectService::class); + $a = $svc->create($tenant, ['name' => 'A', 'signal_type' => 'call', 'signal_identifier' => '79991110000', 'daily_limit_target' => 5, 'regions' => [], 'delivery_days_mask' => 31]); + $updated = $svc->update($a, ['signal_identifier' => '79991110000', 'daily_limit_target' => 7]); + expect($updated->daily_limit_target)->toBe(7); +});