Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
BulkSyncG3WRun
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 4
30
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 / 27
0.00% covered (danger)
0.00%
0 / 1
6
 failed
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Jobs;
4
5use App\Services\PresupuestosService;
6use Illuminate\Bus\Queueable;
7use Illuminate\Contracts\Queue\ShouldQueue;
8use Illuminate\Foundation\Bus\Dispatchable;
9use Illuminate\Queue\InteractsWithQueue;
10use Illuminate\Queue\Middleware\RateLimited;
11use Illuminate\Queue\Middleware\WithoutOverlapping;
12use Illuminate\Queue\SerializesModels;
13use Illuminate\Support\Facades\Log;
14
15/**
16 * FIRE-1170: per-region worker for the "Sincronización masiva" admin tool.
17 * One job per region per run — the controller fans out N jobs (one per
18 * selected region) and they share the `g3w` rate-limiter with the regular
19 * cron sync. The `WithoutOverlapping` lock keys on (region, runId) so two
20 * different runs can target the same region concurrently without colliding
21 * but the same run can't double-process a region if it gets re-dispatched.
22 *
23 * Mirrors the SyncG3WRegion pattern from FIRE-1151.
24 */
25class BulkSyncG3WRun implements ShouldQueue
26{
27    use Dispatchable;
28    use InteractsWithQueue;
29    use Queueable;
30    use SerializesModels;
31
32    public int $tries = 3;
33
34    /** @var array<int,int> seconds between retries */
35    public array $backoff = [30, 90, 300];
36
37    /** Hard kill at 1 hour — bulk runs can be longer than the daily cron. */
38    public int $timeout = 3600;
39
40    /**
41     * @param  array<int,string>  $fields  UI checkbox names (see PresupuestosService::BULK_SYNC_FIELD_MAP)
42     */
43    public function __construct(
44        public readonly int $runId,
45        public readonly string $region,
46        public readonly string $dateFrom,
47        public readonly string $dateTo,
48        public readonly array $fields,
49        public readonly bool $suppressOps,
50        public readonly bool $suppressFin,
51    ) {}
52
53    public function middleware(): array
54    {
55        return [
56            (new WithoutOverlapping("g3w:bulk:{$this->region}:{$this->runId}"))
57                ->expireAfter(7200)
58                ->dontRelease(),
59            new RateLimited('g3w'),
60        ];
61    }
62
63    public function handle(PresupuestosService $service): void
64    {
65        // The job can take 20+ minutes for large date ranges. Disable the
66        // PHP execution timeout — this matters when the job runs via
67        // `dispatchAfterResponse` on a web worker (which inherits the
68        // request's max_execution_time). A real queue worker doesn't have
69        // this limit.
70        @set_time_limit(0);
71
72        $start = microtime(true);
73
74        try {
75            $service->bulkSyncByFilters(
76                $this->runId,
77                $this->region,
78                $this->dateFrom,
79                $this->dateTo,
80                $this->fields,
81                $this->suppressOps,
82                $this->suppressFin,
83            );
84            $status = 'ok';
85        } catch (\Throwable $e) {
86            $status = 'failed';
87            Log::channel('g3w')->error('BulkSyncG3WRun failed', [
88                'run_id' => $this->runId,
89                'region' => $this->region,
90                'error' => $e->getMessage(),
91            ]);
92            throw $e;
93        } finally {
94            Log::channel('g3w')->info('BulkSyncG3WRun finished', [
95                'run_id' => $this->runId,
96                'region' => $this->region,
97                'wall_ms' => (int) round((microtime(true) - $start) * 1000),
98                'status' => $status ?? 'unknown',
99                'attempt' => $this->attempts(),
100            ]);
101        }
102    }
103
104    public function failed(\Throwable $e): void
105    {
106        Log::channel('g3w')->error('BulkSyncG3WRun exhausted retries', [
107            'run_id' => $this->runId,
108            'region' => $this->region,
109            'error' => $e->getMessage(),
110        ]);
111    }
112}