Răsfoiți Sursa

feat: :sparkles: feat (queue-jobs import) adicionado queue e jobs nos imports

foram implementados queue e jobs nos imports para conseguir importar grandes quantidades de dados

fase:dev | origin:escopo
Gustavo Zanatta 1 săptămână în urmă
părinte
comite
3634674614

+ 14 - 5
app/Http/Controllers/AssociadoImportController.php

@@ -2,22 +2,31 @@
 
 namespace App\Http\Controllers;
 
-use App\Services\AssociadoImportService;
+use App\Jobs\SyncAssociadosJob;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Str;
 
 class AssociadoImportController extends Controller
 {
-    public function __construct(protected AssociadoImportService $service) {}
-
     public function importAndSync(Request $request): JsonResponse
     {
         $request->validate([
             'file' => 'required|file|mimes:xlsx|max:10240',
         ]);
 
-        $stats = $this->service->syncFromExcel($request->file('file'));
+        $importId = Str::uuid()->toString();
+        $filePath = Storage::putFileAs('imports', $request->file('file'), $importId . '.xlsx');
+
+        Cache::put($importId, ['status' => 'pending'], now()->addDay());
+
+        SyncAssociadosJob::dispatch($filePath, $importId)->onQueue('imports');
 
-        return $this->successResponse(payload: $stats, message: __('messages.updated'));
+        return $this->successResponse(
+            payload: ['import_id' => $importId],
+            code: 202,
+        );
     }
 }

+ 20 - 0
app/Http/Controllers/ImportStatusController.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Cache;
+
+class ImportStatusController extends Controller
+{
+    public function status(string $importId): JsonResponse
+    {
+        $entry = Cache::get($importId);
+
+        if ($entry === null) {
+            return $this->errorResponse('Import not found or expired.', 404);
+        }
+
+        return $this->successResponse(payload: $entry);
+    }
+}

+ 25 - 11
app/Http/Controllers/PartnerImportController.php

@@ -2,27 +2,33 @@
 
 namespace App\Http\Controllers;
 
-use App\Services\ConveniosMedicosImportService;
-use App\Services\ParceirosImportService;
+use App\Jobs\SyncConveniosMedicosJob;
+use App\Jobs\SyncParceirosJob;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Str;
 
 class PartnerImportController extends Controller
 {
-    public function __construct(
-        protected ParceirosImportService       $parceirosService,
-        protected ConveniosMedicosImportService $conveniosService,
-    ) {}
-
     public function importParceiros(Request $request): JsonResponse
     {
         $request->validate([
             'file' => 'required|file|mimes:xlsx|max:10240',
         ]);
 
-        $stats = $this->parceirosService->syncFromExcel($request->file('file'));
+        $importId = Str::uuid()->toString();
+        $filePath = Storage::putFileAs('imports', $request->file('file'), $importId . '.xlsx');
+
+        Cache::put($importId, ['status' => 'pending'], now()->addDay());
+
+        SyncParceirosJob::dispatch($filePath, $importId)->onQueue('imports');
 
-        return $this->successResponse(payload: $stats, message: __('messages.updated'));
+        return $this->successResponse(
+            payload: ['import_id' => $importId],
+            code: 202,
+        );
     }
 
     public function importConveniosMedicos(Request $request): JsonResponse
@@ -31,8 +37,16 @@ class PartnerImportController extends Controller
             'file' => 'required|file|mimes:xlsx|max:10240',
         ]);
 
-        $stats = $this->conveniosService->syncFromExcel($request->file('file'));
+        $importId = Str::uuid()->toString();
+        $filePath = Storage::putFileAs('imports', $request->file('file'), $importId . '.xlsx');
+
+        Cache::put($importId, ['status' => 'pending'], now()->addDay());
+
+        SyncConveniosMedicosJob::dispatch($filePath, $importId)->onQueue('imports');
 
-        return $this->successResponse(payload: $stats, message: __('messages.updated'));
+        return $this->successResponse(
+            payload: ['import_id' => $importId],
+            code: 202,
+        );
     }
 }

+ 59 - 0
app/Jobs/BaseImportJob.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Jobs;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Storage;
+use Throwable;
+
+abstract class BaseImportJob implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    public int $tries = 1;
+
+    public int $timeout = 600;
+
+    public function __construct(
+        protected string $filePath,
+        protected string $importId,
+    ) {}
+
+    public function handle(): void
+    {
+        Cache::put($this->importId, ['status' => 'processing'], now()->addDay());
+
+        try {
+            $stats = $this->runSync($this->filePath);
+
+            Cache::put($this->importId, [
+                'status' => 'completed',
+                'stats'  => $stats,
+            ], now()->addDay());
+        } finally {
+            $this->cleanupFile();
+        }
+    }
+
+    public function failed(Throwable $e): void
+    {
+        Cache::put($this->importId, [
+            'status'  => 'failed',
+            'message' => $e->getMessage(),
+        ], now()->addDay());
+
+        $this->cleanupFile();
+    }
+
+    abstract protected function runSync(string $filePath): array;
+
+    private function cleanupFile(): void
+    {
+        Storage::delete($this->filePath);
+    }
+}

+ 13 - 0
app/Jobs/SyncAssociadosJob.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Services\AssociadoImportService;
+
+class SyncAssociadosJob extends BaseImportJob
+{
+    protected function runSync(string $filePath): array
+    {
+        return app(AssociadoImportService::class)->syncFromExcel($filePath);
+    }
+}

+ 13 - 0
app/Jobs/SyncConveniosMedicosJob.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Services\ConveniosMedicosImportService;
+
+class SyncConveniosMedicosJob extends BaseImportJob
+{
+    protected function runSync(string $filePath): array
+    {
+        return app(ConveniosMedicosImportService::class)->syncFromExcel($filePath);
+    }
+}

+ 13 - 0
app/Jobs/SyncParceirosJob.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Services\ParceirosImportService;
+
+class SyncParceirosJob extends BaseImportJob
+{
+    protected function runSync(string $filePath): array
+    {
+        return app(ParceirosImportService::class)->syncFromExcel($filePath);
+    }
+}

+ 8 - 3
app/Services/AssociadoImportService.php

@@ -7,17 +7,16 @@ use App\Enums\UserTypeEnum;
 use App\Imports\AssociadoImport;
 use App\Models\User;
 use Carbon\Carbon;
-use Illuminate\Http\UploadedFile;
 use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Str;
 use Maatwebsite\Excel\Facades\Excel;
 
 class AssociadoImportService
 {
-    public function syncFromExcel(UploadedFile $file): array
+    public function syncFromExcel(string $filePath): array
     {
         $import = new AssociadoImport();
-        Excel::import($import, $file);
+        Excel::import($import, $filePath);
         $rows = $import->rows ?? collect();
 
         $today = Carbon::today()->toDateString();
@@ -28,6 +27,7 @@ class AssociadoImportService
             ->keyBy('registration');
 
         $importedRegistrations = [];
+        $seenRegistrations     = [];
         $created    = 0;
         $updated    = 0;
 
@@ -35,6 +35,11 @@ class AssociadoImportService
             $registration = $row['registration'];
             $name         = $row['name'];
 
+            if (isset($seenRegistrations[$registration])) {
+                continue;
+            }
+            $seenRegistrations[$registration] = true;
+
             $importedRegistrations[] = $registration;
 
             if ($existing->has($registration)) {

+ 2 - 3
app/Services/ConveniosMedicosImportService.php

@@ -9,7 +9,6 @@ use App\Models\Category;
 use App\Models\PartnerAgreement;
 use App\Models\PartnerAgreementService;
 use App\Traits\ImportsPartners;
-use Illuminate\Http\UploadedFile;
 use Maatwebsite\Excel\Facades\Excel;
 
 class ConveniosMedicosImportService
@@ -19,10 +18,10 @@ class ConveniosMedicosImportService
     private const MODE_CONSULTAS   = 'consultas';
     private const MODE_LABORATORIO = 'laboratorio';
 
-    public function syncFromExcel(UploadedFile $file): array
+    public function syncFromExcel(string $filePath): array
     {
         $import = new ParceirosImport();
-        Excel::import($import, $file);
+        Excel::import($import, $filePath);
         $rows = $import->rows ?? collect();
 
         $mode         = null;

+ 2 - 3
app/Services/ParceirosImportService.php

@@ -7,17 +7,16 @@ use App\Imports\ParceirosImport;
 use App\Models\Category;
 use App\Models\PartnerAgreement;
 use App\Traits\ImportsPartners;
-use Illuminate\Http\UploadedFile;
 use Maatwebsite\Excel\Facades\Excel;
 
 class ParceirosImportService
 {
     use ImportsPartners;
 
-    public function syncFromExcel(UploadedFile $file): array
+    public function syncFromExcel(string $filePath): array
     {
         $import = new ParceirosImport();
-        Excel::import($import, $file);
+        Excel::import($import, $filePath);
         $rows = $import->rows ?? collect();
 
         $created             = 0;

+ 1 - 1
app/Services/UserService.php

@@ -24,7 +24,7 @@ class UserService
 
     public function getAllPaginated(array $filters = [], int $perPage = 10): \Illuminate\Pagination\LengthAwarePaginator
     {
-        $query = User::with(['position', 'sector'])->orderBy('created_at', 'desc');
+        $query = User::with(['position', 'sector'])->orderBy('name', 'asc');
 
         if (!empty($filters['type'])) {
             $query->where('type', $filters['type']);

+ 5 - 0
app/Traits/ImportsPartners.php

@@ -77,4 +77,9 @@ trait ImportsPartners
     {
         return trim(preg_replace('/\s+/', ' ', (string) ($value ?? '')));
     }
+
+    private function firstLine(mixed $value): string
+    {
+        return trim(explode("\n", (string) ($value ?? ''))[0]);
+    }
 }

+ 24 - 0
database/migrations/2026_06_03_095127_expand_phone_columns_in_partner_agreements.php

@@ -0,0 +1,24 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('partner_agreements', function (Blueprint $table) {
+            $table->string('phone', 100)->nullable()->change();
+            $table->string('whatsapp', 100)->nullable()->change();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('partner_agreements', function (Blueprint $table) {
+            $table->string('phone', 20)->nullable()->change();
+            $table->string('whatsapp', 20)->nullable()->change();
+        });
+    }
+};

+ 6 - 0
routes/authRoutes/import_status.php

@@ -0,0 +1,6 @@
+<?php
+
+use App\Http\Controllers\ImportStatusController;
+use Illuminate\Support\Facades\Route;
+
+Route::get('/import-status/{importId}', [ImportStatusController::class, 'status']);