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