diff --git a/app/app/Console/Commands/SnapshotBackfillCommand.php b/app/app/Console/Commands/SnapshotBackfillCommand.php new file mode 100644 index 00000000..c92e510a --- /dev/null +++ b/app/app/Console/Commands/SnapshotBackfillCommand.php @@ -0,0 +1,61 @@ +option('date') ?? Carbon::today('Europe/Moscow')->toDateString()); + $date = Carbon::parse($dateStr, 'Europe/Moscow'); + $weekdayBit = 1 << ($date->isoWeekday() - 1); + + $count = DB::connection('pgsql_supplier')->transaction(function () use ($dateStr, $weekdayBit) { + return DB::connection('pgsql_supplier')->insert(<< 0 + AND p.preflight_blocked_at IS NULL + AND t.frozen_by_balance_at IS NULL + AND t.deleted_at IS NULL + ON CONFLICT (snapshot_date, project_id) DO NOTHING + SQL, [$dateStr, $weekdayBit]); + }); + + $this->info("Snapshot backfilled for {$dateStr}: {$count} rows."); + Log::info('snapshot.backfill', ['date' => $dateStr, 'rows' => $count]); + + return self::SUCCESS; + } +} diff --git a/app/tests/Feature/Console/SnapshotBackfillCommandTest.php b/app/tests/Feature/Console/SnapshotBackfillCommandTest.php new file mode 100644 index 00000000..f214cb75 --- /dev/null +++ b/app/tests/Feature/Console/SnapshotBackfillCommandTest.php @@ -0,0 +1,39 @@ +create(['frozen_by_balance_at' => null]); + Project::factory()->for($tenant)->asCallSignal('79161234567')->create([ + 'is_active' => true, + 'delivery_days_mask' => 127, + 'daily_limit_target' => 10, + ]); + + $this->artisan('snapshot:backfill', ['--date' => '2026-05-27']) + ->assertSuccessful(); + + expect(\DB::table('project_routing_snapshots')->where('snapshot_date', '2026-05-27')->count())->toBe(1); + Carbon::setTestNow(); +}); + +it('is idempotent — does not duplicate on re-run', function () { + Carbon::setTestNow('2026-05-27 14:00:00', 'Europe/Moscow'); + $tenant = Tenant::factory()->create(['frozen_by_balance_at' => null]); + Project::factory()->for($tenant)->asCallSignal('79161234567')->create([ + 'is_active' => true, + 'delivery_days_mask' => 127, + 'daily_limit_target' => 10, + ]); + + $this->artisan('snapshot:backfill', ['--date' => '2026-05-27'])->assertSuccessful(); + $this->artisan('snapshot:backfill', ['--date' => '2026-05-27'])->assertSuccessful(); + + expect(\DB::table('project_routing_snapshots')->count())->toBe(1); + Carbon::setTestNow(); +});