diff --git a/app/app/Jobs/ProcessWebhookJob.php b/app/app/Jobs/ProcessWebhookJob.php index 6a9f0ae9..45f931ca 100644 --- a/app/app/Jobs/ProcessWebhookJob.php +++ b/app/app/Jobs/ProcessWebhookJob.php @@ -14,8 +14,8 @@ use App\Models\SupplierLeadCost; use App\Models\SystemSetting; use App\Models\Tenant; use App\Services\DuplicateDetector; -use App\Services\Pd\PdAuditLogger; use App\Services\NotificationService; +use App\Services\Pd\PdAuditLogger; use App\Services\SupplierResolver; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; diff --git a/app/app/Jobs/RouteSupplierLeadJob.php b/app/app/Jobs/RouteSupplierLeadJob.php index cdf74768..4de6e9fe 100644 --- a/app/app/Jobs/RouteSupplierLeadJob.php +++ b/app/app/Jobs/RouteSupplierLeadJob.php @@ -11,11 +11,11 @@ use App\Models\Project; use App\Models\SupplierLead; use App\Models\Tenant; use App\Services\Billing\LedgerService; -use App\Services\Pd\PdAuditLogger; use App\Services\DuplicateDetector; use App\Services\LeadDistributor; use App\Services\LeadRouter; use App\Services\NotificationService; +use App\Services\Pd\PdAuditLogger; use App\Services\RegionTagResolver; use App\Services\SupplierProjects\SupplierProjectResolver; use Illuminate\Bus\Queueable; diff --git a/app/app/Services/Pd/ImpersonationAuditService.php b/app/app/Services/Pd/ImpersonationAuditService.php index f56e77c2..9e425c94 100644 --- a/app/app/Services/Pd/ImpersonationAuditService.php +++ b/app/app/Services/Pd/ImpersonationAuditService.php @@ -1,4 +1,6 @@ - $t->tenant_id, 'target_tenant_id' => $t->tenant_id, 'payload_before' => null, - 'payload_after' => ['token_id' => $t->id, 'expires_at' => $t->expires_at?->toIso8601String()], + 'payload_after' => ['token_id' => $t->id, 'expires_at' => $t->expires_at->toIso8601String()], 'reason' => $t->reason, 'ip_address' => $ip ?? '127.0.0.1', 'user_agent' => null, diff --git a/app/app/Services/Pd/PdAuditLogger.php b/app/app/Services/Pd/PdAuditLogger.php index edf6f4f8..081e7227 100644 --- a/app/app/Services/Pd/PdAuditLogger.php +++ b/app/app/Services/Pd/PdAuditLogger.php @@ -1,4 +1,6 @@ -create(); - $user = User::factory()->for($tenant)->create(); + $user = User::factory()->for($tenant)->create(); DB::statement('SET app.current_tenant_id = '.$tenant->id); $log = ImportLog::create([ 'tenant_id' => $tenant->id, - 'user_id' => $user->id, - 'filename' => 'leads.csv', + 'user_id' => $user->id, + 'filename' => 'leads.csv', 'file_path' => 'imports/x.csv', - 'dry_run' => false, + 'dry_run' => false, ]); $header = "\xEF\xBB\xBF".'id,Проект,Тег проекта,Телефон,Создано,Напоминание,Комментарий,Состояние,Имя'; @@ -54,15 +56,15 @@ it('writes pd_processing_log created on historical import for each new deal', fu it('does NOT write pd_processing_log on dry_run import', function () { $tenant = Tenant::factory()->create(); - $user = User::factory()->for($tenant)->create(); + $user = User::factory()->for($tenant)->create(); DB::statement('SET app.current_tenant_id = '.$tenant->id); $log = ImportLog::create([ 'tenant_id' => $tenant->id, - 'user_id' => $user->id, - 'filename' => 'leads.csv', + 'user_id' => $user->id, + 'filename' => 'leads.csv', 'file_path' => 'imports/x.csv', - 'dry_run' => true, + 'dry_run' => true, ]); $header = "\xEF\xBB\xBF".'id,Проект,Тег проекта,Телефон,Создано,Напоминание,Комментарий,Состояние,Имя'; @@ -79,7 +81,7 @@ it('does NOT write pd_processing_log on dry_run import', function () { it('does NOT write pd_processing_log on import UPDATE (idempotent re-import)', function () { $tenant = Tenant::factory()->create(); - $user = User::factory()->for($tenant)->create(); + $user = User::factory()->for($tenant)->create(); DB::statement('SET app.current_tenant_id = '.$tenant->id); $header = "\xEF\xBB\xBF".'id,Проект,Тег проекта,Телефон,Создано,Напоминание,Комментарий,Состояние,Имя'; @@ -87,10 +89,10 @@ it('does NOT write pd_processing_log on import UPDATE (idempotent re-import)', f // First import — creates the deal $log1 = ImportLog::create([ 'tenant_id' => $tenant->id, - 'user_id' => $user->id, - 'filename' => 'leads.csv', + 'user_id' => $user->id, + 'filename' => 'leads.csv', 'file_path' => 'imports/x.csv', - 'dry_run' => false, + 'dry_run' => false, ]); $rows1 = (new CsvLeadsParser)->parse($header."\n".'9904,Окна,окна,79161000004,2023/07/10 10:00:00,,,Новые,')->rows; app(HistoricalImportService::class)->import($tenant->id, $user->id, $log1, $rows1); @@ -98,10 +100,10 @@ it('does NOT write pd_processing_log on import UPDATE (idempotent re-import)', f // Second import — updates the same deal $log2 = ImportLog::create([ 'tenant_id' => $tenant->id, - 'user_id' => $user->id, - 'filename' => 'leads2.csv', + 'user_id' => $user->id, + 'filename' => 'leads2.csv', 'file_path' => 'imports/x2.csv', - 'dry_run' => false, + 'dry_run' => false, ]); $rows2 = (new CsvLeadsParser)->parse($header."\n".'9904,Окна,окна,79161000004,2023/07/10 10:00:00,,,Оплачено,')->rows; app(HistoricalImportService::class)->import($tenant->id, $user->id, $log2, $rows2); diff --git a/app/tests/Feature/Pd/DealViewAccessLogTest.php b/app/tests/Feature/Pd/DealViewAccessLogTest.php index c0335d5b..3d1b9d3e 100644 --- a/app/tests/Feature/Pd/DealViewAccessLogTest.php +++ b/app/tests/Feature/Pd/DealViewAccessLogTest.php @@ -1,4 +1,6 @@ -tenant = Tenant::factory()->create(['balance_leads' => 100]); - $this->user = User::factory()->for($this->tenant)->create(); + $this->tenant = Tenant::factory()->create(['balance_leads' => 100]); + $this->user = User::factory()->for($this->tenant)->create(); $this->actingAs($this->user); DB::statement('SET app.current_tenant_id = '.(int) $this->tenant->id); $this->project = Project::factory()->for($this->tenant)->create(['name' => 'PD Flow Test']); @@ -37,7 +39,7 @@ it('records pd events through the whole deal lifecycle (create → view → expo // ── 1. CREATE (manual) → pd action='created', purpose='lead_create_manual' ── $created = $this->postJson('/api/deals', [ 'project_name' => $this->project->name, - 'phone' => '+7 (999) 123-45-67', + 'phone' => '+7 (999) 123-45-67', ]); $created->assertStatus(201); $dealId = (int) $created->json('deal.id'); @@ -57,18 +59,18 @@ it('records pd events through the whole deal lifecycle (create → view → expo // Mirror: ReportFileDeletePdLogTest — create a DONE ReportJob with file_path, // then DELETE /api/reports/jobs/{id}. $job = ReportJob::create([ - 'tenant_id' => $this->tenant->id, - 'user_id' => $this->user->id, - 'type' => 'deals_export', + 'tenant_id' => $this->tenant->id, + 'user_id' => $this->user->id, + 'type' => 'deals_export', 'parameters' => ['format' => 'csv', 'date_from' => '2026-01-01', 'date_to' => '2026-12-31'], - 'status' => ReportJob::STATUS_DONE, - 'file_path' => 'reports/'.(int) $this->tenant->id.'/pd_flow_test.csv', + 'status' => ReportJob::STATUS_DONE, + 'file_path' => 'reports/'.(int) $this->tenant->id.'/pd_flow_test.csv', ]); $this->deleteJson("/api/reports/jobs/{$job->id}")->assertOk(); // ── ASSERT — scoped to THIS tenant ──────────────────────────────────────── - $rows = DB::table('pd_processing_log') + $rows = DB::table('pd_processing_log') ->where('tenant_id', $this->tenant->id) ->get(); $byAction = $rows->groupBy('action'); @@ -82,7 +84,7 @@ it('records pd events through the whole deal lifecycle (create → view → expo // Correct purpose for each action. expect($rows->firstWhere('action', 'created')->purpose)->toBe('lead_create_manual'); expect($rows->firstWhere('action', 'viewed')->purpose)->toBe('lead_card_view'); - expect($rows->firstWhere('action', 'exported')->purpose)->toBe('deals_export_csv'); + expect($rows->contains(fn ($r) => $r->action === 'exported' && $r->purpose === 'deals_export_csv'))->toBeTrue(); expect($rows->firstWhere('action', 'deleted')->purpose)->toBe('report_file_'.$job->id); // 'created' and 'viewed' rows are tied to the deal we created. diff --git a/app/tests/Feature/Pd/ReportFileDeletePdLogTest.php b/app/tests/Feature/Pd/ReportFileDeletePdLogTest.php index 0ec0c40c..6211b2cb 100644 --- a/app/tests/Feature/Pd/ReportFileDeletePdLogTest.php +++ b/app/tests/Feature/Pd/ReportFileDeletePdLogTest.php @@ -1,4 +1,6 @@ -tenant = Tenant::factory()->create(); - $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); + $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); DB::statement('SET app.current_tenant_id = '.(int) $this->tenant->id); }); it('writes pd deleted when a report file is destroyed', function () { $job = ReportJob::create([ - 'tenant_id' => $this->tenant->id, - 'user_id' => $this->user->id, - 'type' => 'deals_export', + 'tenant_id' => $this->tenant->id, + 'user_id' => $this->user->id, + 'type' => 'deals_export', 'parameters' => ['format' => 'csv', 'date_from' => '2026-04-01', 'date_to' => '2026-04-30'], - 'status' => ReportJob::STATUS_DONE, - 'file_path' => 'reports/'.(int) $this->tenant->id.'/test.csv', + 'status' => ReportJob::STATUS_DONE, + 'file_path' => 'reports/'.(int) $this->tenant->id.'/test.csv', ]); $this->deleteJson("/api/reports/jobs/{$job->id}")->assertOk(); @@ -46,21 +48,21 @@ it('writes pd deleted (system actor) when cron cleanup-expired runs', function ( Storage::disk('local')->put('reports/'.(int) $this->tenant->id.'/cron2.csv', 'data'); ReportJob::create([ - 'tenant_id' => $this->tenant->id, - 'user_id' => $this->user->id, - 'type' => 'deals_export', + 'tenant_id' => $this->tenant->id, + 'user_id' => $this->user->id, + 'type' => 'deals_export', 'parameters' => ['format' => 'csv'], - 'status' => ReportJob::STATUS_DONE, - 'file_path' => 'reports/'.(int) $this->tenant->id.'/cron1.csv', + 'status' => ReportJob::STATUS_DONE, + 'file_path' => 'reports/'.(int) $this->tenant->id.'/cron1.csv', 'expires_at' => now()->subDay(), ]); ReportJob::create([ - 'tenant_id' => $this->tenant->id, - 'user_id' => $this->user->id, - 'type' => 'deals_export', + 'tenant_id' => $this->tenant->id, + 'user_id' => $this->user->id, + 'type' => 'deals_export', 'parameters' => ['format' => 'csv'], - 'status' => ReportJob::STATUS_DONE, - 'file_path' => 'reports/'.(int) $this->tenant->id.'/cron2.csv', + 'status' => ReportJob::STATUS_DONE, + 'file_path' => 'reports/'.(int) $this->tenant->id.'/cron2.csv', 'expires_at' => now()->subDay(), ]); diff --git a/app/tests/Unit/Services/Pd/ImpersonationAuditServiceTest.php b/app/tests/Unit/Services/Pd/ImpersonationAuditServiceTest.php index 3bb8a259..fe0a8e92 100644 --- a/app/tests/Unit/Services/Pd/ImpersonationAuditServiceTest.php +++ b/app/tests/Unit/Services/Pd/ImpersonationAuditServiceTest.php @@ -1,4 +1,6 @@ -id, actorTenantUserId: $user->id, actorAdminUserId: 999999, ip: null, - ))->toThrow(\Illuminate\Database\QueryException::class); + ))->toThrow(QueryException::class); });