whisper-money/app/Console/Commands/BackfillAccountIbans.php

115 lines
3.6 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Contracts\BankingProviderInterface;
use App\Enums\BankingProvider;
use App\Models\Account;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Http\Client\RequestException;
use Throwable;
class BackfillAccountIbans extends Command
{
protected $signature = 'banking:backfill-ibans
{--user= : Filter by user email address}
{--connection= : Filter by banking connection ID}
{--dry-run : Preview what would be updated without making changes}';
protected $description = 'Backfill missing IBAN values for Enable Banking accounts by fetching them from the API';
public function handle(BankingProviderInterface $provider): int
{
$isDryRun = $this->option('dry-run');
$userEmail = $this->option('user');
$connectionId = $this->option('connection');
if ($isDryRun) {
$this->warn('DRY RUN — no changes will be saved.');
}
$query = Account::query()
->whereNull('iban')
->whereNotNull('external_account_id')
->whereNotNull('banking_connection_id')
->whereHas('bankingConnection', fn ($q) => $q->where('provider', BankingProvider::EnableBanking));
if ($connectionId) {
$query->where('banking_connection_id', $connectionId);
}
if ($userEmail) {
$user = User::query()->where('email', $userEmail)->first();
if (! $user) {
$this->error("User with email '{$userEmail}' not found.");
return Command::FAILURE;
}
$query->where('user_id', $user->id);
}
$accounts = $query->get();
if ($accounts->isEmpty()) {
$this->info('No accounts found with missing IBAN.');
return Command::SUCCESS;
}
$this->info("Found {$accounts->count()} account(s) with missing IBAN.");
$bar = $this->output->createProgressBar($accounts->count());
$bar->start();
$updated = 0;
$skipped = 0;
$expiredSessions = 0;
$failed = 0;
foreach ($accounts as $account) {
try {
$data = $provider->getAccount($account->external_account_id);
$iban = $data['account_id']['iban'] ?? null;
if (! $iban) {
$skipped++;
$bar->advance();
continue;
}
if (! $isDryRun) {
$account->update(['iban' => $iban]);
}
$updated++;
} catch (RequestException $e) {
if ($e->response->status() === 404) {
$expiredSessions++;
} else {
$this->newLine();
$this->warn("Failed for account {$account->id} ({$account->external_account_id}): {$e->getMessage()}");
$failed++;
}
} catch (Throwable $e) {
$this->newLine();
$this->warn("Failed for account {$account->id} ({$account->external_account_id}): {$e->getMessage()}");
$failed++;
}
$bar->advance();
}
$bar->finish();
$this->newLine();
$verb = $isDryRun ? 'would be updated' : 'updated';
$this->info("IBAN {$verb} for {$updated} account(s). Skipped (no IBAN in API response): {$skipped}. Skipped (expired/revoked session): {$expiredSessions}. Failed: {$failed}.");
return $failed > 0 ? Command::FAILURE : Command::SUCCESS;
}
}