Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.00% covered (danger)
20.00%
7 / 35
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportFinanceDriveFile
20.00% covered (danger)
20.00%
7 / 35
40.00% covered (danger)
40.00%
2 / 5
24.43
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 middleware
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 handle
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 buildDriveService
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 failed
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Jobs;
4
5use App\Console\Commands\ImportFinanceFromDrive;
6use Google\Client as GoogleClient;
7use Google\Service\Drive as DriveService;
8use Illuminate\Bus\Queueable;
9use Illuminate\Contracts\Queue\ShouldQueue;
10use Illuminate\Foundation\Bus\Dispatchable;
11use Illuminate\Queue\InteractsWithQueue;
12use Illuminate\Queue\Middleware\RateLimited;
13use Illuminate\Queue\Middleware\WithoutOverlapping;
14use Illuminate\Queue\SerializesModels;
15use Illuminate\Support\Facades\Log;
16
17/**
18 * FIRE-1151: process one Google Drive spreadsheet at a time on its own
19 * job. Replaces the serial foreach in ImportFinanceFromDrive::handle()
20 * which previously processed files one at a time (~3 min total for 4 files).
21 *
22 * Uses the 'drive' rate-limiter bucket (separate from 'g3w') so Drive
23 * imports don't compete with G3W syncs.
24 */
25class ImportFinanceDriveFile implements ShouldQueue
26{
27    use Dispatchable;
28    use InteractsWithQueue;
29    use Queueable;
30    use SerializesModels;
31
32    public int $tries = 3;
33
34    public array $backoff = [10, 30, 60];
35
36    public int $timeout = 600;
37
38    public function __construct(
39        public readonly string $fileId,
40        public readonly string $fileName,
41        public readonly string $mimeType,
42    ) {}
43
44    public function middleware(): array
45    {
46        return [
47            (new WithoutOverlapping("drive:file:{$this->fileId}"))
48                ->expireAfter(900)
49                ->dontRelease(),
50            new RateLimited('drive'),
51        ];
52    }
53
54    public function handle(): void
55    {
56        $start = microtime(true);
57        $tempBase = tempnam(sys_get_temp_dir(), 'finance_import_');
58        $tempPath = $tempBase.'.xlsx';
59        @unlink($tempBase);
60
61        try {
62            $service = $this->buildDriveService();
63
64            $bytes = $this->mimeType === 'application/vnd.google-apps.spreadsheet'
65                ? $service->files->export($this->fileId, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ['alt' => 'media'])->getBody()->getContents()
66                : $service->files->get($this->fileId, ['alt' => 'media'])->getBody()->getContents();
67
68            file_put_contents($tempPath, $bytes);
69
70            // Delegate row processing to the existing command's processFile
71            // method (kept as the single source of truth for parsing logic).
72            $command = app(ImportFinanceFromDrive::class);
73            $imported = $command->processFile($tempPath, $this->fileName);
74
75            Log::channel('third-party')->info('ImportFinanceDriveFile finished', [
76                'file' => $this->fileName,
77                'rows' => $imported,
78                'wall_ms' => (int) round((microtime(true) - $start) * 1000),
79                'attempt' => $this->attempts(),
80            ]);
81        } finally {
82            @unlink($tempPath);
83        }
84    }
85
86    private function buildDriveService(): DriveService
87    {
88        $client = new GoogleClient;
89        $client->setClientId(env('GOOGLE_DRIVE_CLIENT_ID'));
90        $client->setClientSecret(env('GOOGLE_DRIVE_CLIENT_SECRET'));
91        $client->refreshToken(env('GOOGLE_DRIVE_REFRESH_TOKEN'));
92        $client->setScopes([DriveService::DRIVE_READONLY]);
93
94        return new DriveService($client);
95    }
96
97    public function failed(\Throwable $e): void
98    {
99        Log::channel('third-party')->error('ImportFinanceDriveFile exhausted retries', [
100            'file' => $this->fileName,
101            'error' => $e->getMessage(),
102        ]);
103    }
104}