Forráskód Böngészése

feat: :sparkles: feat (importacoes) criada importacoes do sistema

foram criadas as importacoes de associados, parceiros e convenios medicos

fase:dev | origin:escopo
Gustavo Zanatta 1 hete
szülő
commit
b2eab4f330

+ 23 - 0
app/Http/Controllers/AssociadoImportController.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\AssociadoImportService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+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'));
+
+        return $this->successResponse(payload: $stats, message: __('messages.updated'));
+    }
+}

+ 38 - 0
app/Http/Controllers/PartnerImportController.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Services\ConveniosMedicosImportService;
+use App\Services\ParceirosImportService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+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'));
+
+        return $this->successResponse(payload: $stats, message: __('messages.updated'));
+    }
+
+    public function importConveniosMedicos(Request $request): JsonResponse
+    {
+        $request->validate([
+            'file' => 'required|file|mimes:xlsx|max:10240',
+        ]);
+
+        $stats = $this->conveniosService->syncFromExcel($request->file('file'));
+
+        return $this->successResponse(payload: $stats, message: __('messages.updated'));
+    }
+}

+ 24 - 0
app/Imports/AssociadoImport.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Imports;
+
+use Illuminate\Support\Collection;
+use Maatwebsite\Excel\Concerns\ToCollection;
+
+class AssociadoImport implements ToCollection
+{
+    public Collection $rows;
+
+    public function collection(Collection $rows): void
+    {
+        $this->rows = $rows->skip(1)->values()->filter(function ($row) {
+            return !empty(trim((string) ($row[0] ?? '')))
+                && !empty(trim((string) ($row[1] ?? '')));
+        })->map(function ($row) {
+            return [
+                'registration' => trim((string) $row[0]),
+                'name'         => trim((string) $row[1]),
+            ];
+        })->values();
+    }
+}

+ 16 - 0
app/Imports/ParceirosImport.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Imports;
+
+use Illuminate\Support\Collection;
+use Maatwebsite\Excel\Concerns\ToCollection;
+
+class ParceirosImport implements ToCollection
+{
+    public Collection $rows;
+
+    public function collection(Collection $rows): void
+    {
+        $this->rows = $rows;
+    }
+}

+ 99 - 0
app/Services/AssociadoImportService.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace App\Services;
+
+use App\Enums\UserStatusEnum;
+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
+    {
+        $import = new AssociadoImport();
+        Excel::import($import, $file);
+        $rows = $import->rows ?? collect();
+
+        $today = Carbon::today()->toDateString();
+
+        $existing = User::where('type', UserTypeEnum::ASSOCIADO)
+            ->whereNotNull('registration')
+            ->get()
+            ->keyBy('registration');
+
+        $importedRegistrations = [];
+        $created    = 0;
+        $updated    = 0;
+
+        foreach ($rows as $row) {
+            $registration = $row['registration'];
+            $name         = $row['name'];
+
+            $importedRegistrations[] = $registration;
+
+            if ($existing->has($registration)) {
+                $user = $existing->get($registration);
+                $changed = false;
+
+                if ($user->name !== $name) {
+                    $user->name = $name;
+                    $changed = true;
+                }
+
+                if ($user->status !== UserStatusEnum::ACTIVE) {
+                    $user->status      = UserStatusEnum::ACTIVE;
+                    $user->excluded_at = null;
+                    $changed = true;
+                }
+
+                if ($changed) {
+                    $user->save();
+                    $updated++;
+                }
+            } else {
+                $firstName = $this->extractFirstName($name);
+                $email     = "{$firstName}_{$registration}@serprati.com";
+                $password  = "{$firstName}2026";
+
+                User::create([
+                    'name'           => $name,
+                    'email'          => $email,
+                    'password'       => Hash::make($password),
+                    'type'           => UserTypeEnum::ASSOCIADO,
+                    'status'         => UserStatusEnum::ACTIVE,
+                    'registration'   => $registration,
+                    'admission_date' => $today,
+                ]);
+
+                $created++;
+            }
+        }
+
+        $inactivated = User::where('type', UserTypeEnum::ASSOCIADO)
+            ->where('status', UserStatusEnum::ACTIVE)
+            ->whereNotNull('registration')
+            ->whereNotIn('registration', $importedRegistrations)
+            ->update([
+                'status'      => UserStatusEnum::INACTIVE,
+                'excluded_at' => now(),
+            ]);
+
+        return [
+            'created'     => $created,
+            'updated'     => $updated,
+            'inactivated' => $inactivated,
+        ];
+    }
+
+    private function extractFirstName(string $fullName): string
+    {
+        $firstName = explode(' ', trim($fullName))[0];
+        return strtolower(Str::ascii($firstName));
+    }
+}

+ 212 - 0
app/Services/ConveniosMedicosImportService.php

@@ -0,0 +1,212 @@
+<?php
+
+namespace App\Services;
+
+use App\Enums\PartnerAgreementServiceStatusEnum;
+use App\Enums\PartnerAgreementStatusEnum;
+use App\Imports\ParceirosImport;
+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
+{
+    use ImportsPartners;
+
+    private const MODE_CONSULTAS   = 'consultas';
+    private const MODE_LABORATORIO = 'laboratorio';
+
+    public function syncFromExcel(UploadedFile $file): array
+    {
+        $import = new ParceirosImport();
+        Excel::import($import, $file);
+        $rows = $import->rows ?? collect();
+
+        $mode         = null;
+        $clinicGroups = [];
+        $labRows      = [];
+
+        foreach ($rows as $row) {
+            $col0 = $this->cell($row[0] ?? '');
+            $col1 = $this->cell($row[1] ?? '');
+            $col2 = $this->cell($row[2] ?? '');
+
+            if ($col0 === '' && $col1 === '' && $col2 === '') {
+                continue;
+            }
+
+            $upper0 = mb_strtoupper($col0);
+            $upper1 = mb_strtoupper($col1);
+
+            if ($upper0 === 'ESPECIALIDADE') {
+                $mode = self::MODE_CONSULTAS;
+                continue;
+            }
+
+            if (str_starts_with($upper0, 'LABORAT') && $upper1 === 'TELEFONE') {
+                $mode = self::MODE_LABORATORIO;
+                continue;
+            }
+
+            if ($mode === null) {
+                continue;
+            }
+
+            if ($mode === self::MODE_CONSULTAS) {
+                $specialty  = $col0;
+                $clinicName = $col1;
+                $doctor     = $col2;
+                $phone      = $this->cell($row[3] ?? '');
+                $address    = $this->cell($row[4] ?? '');
+                $priceRaw   = $this->cell($row[5] ?? '');
+
+                if ($clinicName === '' || $specialty === '') {
+                    continue;
+                }
+
+                if (!isset($clinicGroups[$clinicName])) {
+                    $clinicGroups[$clinicName] = [
+                        'phone'    => null,
+                        'address'  => null,
+                        'services' => [],
+                    ];
+                }
+
+                if ($phone && !$clinicGroups[$clinicName]['phone']) {
+                    $clinicGroups[$clinicName]['phone'] = $phone;
+                }
+                if ($address && !$clinicGroups[$clinicName]['address']) {
+                    $clinicGroups[$clinicName]['address'] = $address;
+                }
+
+                $serviceName = $doctor
+                    ? "{$specialty} - {$doctor}"
+                    : $specialty;
+
+                $clinicGroups[$clinicName]['services'][] = [
+                    'name'  => $serviceName,
+                    'price' => $this->parsePrice($priceRaw),
+                ];
+            } elseif ($mode === self::MODE_LABORATORIO) {
+                if ($col0 === '') {
+                    continue;
+                }
+
+                $labRows[] = [
+                    'name'    => $col0,
+                    'phone'   => $col1 ?: null,
+                    'address' => $col2 ?: null,
+                ];
+            }
+        }
+
+        $convMedicaCategory = Category::firstOrCreate(
+            ['name' => 'Convênios Médicos', 'type' => 'partner'],
+            ['active' => true]
+        );
+
+        $labCategory = Category::firstOrCreate(
+            ['name' => 'Laboratório', 'type' => 'partner'],
+            ['active' => true]
+        );
+
+        $stats = [
+            'partners_created'    => 0,
+            'partners_updated'    => 0,
+            'partners_inactivated'=> 0,
+            'services_created'    => 0,
+            'services_updated'    => 0,
+            'services_inactivated'=> 0,
+        ];
+
+        $importedPartnerIds = [];
+
+        foreach ($clinicGroups as $clinicName => $clinicData) {
+            [$partner, $isNew] = $this->upsertPartner($clinicName, [
+                'phone'       => $clinicData['phone'],
+                'address'     => $clinicData['address'],
+                'category_id' => $convMedicaCategory->id,
+            ]);
+
+            $importedPartnerIds[] = $partner->id;
+            $isNew ? $stats['partners_created']++ : $stats['partners_updated']++;
+
+            $processedServiceIds = [];
+
+            foreach ($clinicData['services'] as $svcData) {
+                $service = PartnerAgreementService::withTrashed()
+                    ->where('partner_agreement_id', $partner->id)
+                    ->whereRaw('LOWER(TRIM(name)) = LOWER(TRIM(?))', [$svcData['name']])
+                    ->first();
+
+                if ($service) {
+                    if ($service->trashed()) {
+                        $service->restore();
+                    }
+
+                    $changed = false;
+
+                    if ($svcData['price'] !== null && (float) $service->associate_price !== (float) $svcData['price']) {
+                        $service->associate_price = $svcData['price'];
+                        $changed = true;
+                    }
+
+                    if ($service->status !== PartnerAgreementServiceStatusEnum::ACTIVE) {
+                        $service->status = PartnerAgreementServiceStatusEnum::ACTIVE;
+                        $changed = true;
+                    }
+
+                    if ($changed) {
+                        $service->save();
+                        $stats['services_updated']++;
+                    }
+
+                    $processedServiceIds[] = $service->id;
+                } else {
+                    $created = PartnerAgreementService::create([
+                        'partner_agreement_id' => $partner->id,
+                        'name'                 => $svcData['name'],
+                        'associate_price'      => $svcData['price'],
+                        'status'               => PartnerAgreementServiceStatusEnum::ACTIVE,
+                    ]);
+
+                    $processedServiceIds[] = $created->id;
+                    $stats['services_created']++;
+                }
+            }
+
+            if (!empty($processedServiceIds)) {
+                $stats['services_inactivated'] += PartnerAgreementService::where('partner_agreement_id', $partner->id)
+                    ->where('status', PartnerAgreementServiceStatusEnum::ACTIVE)
+                    ->whereNotIn('id', $processedServiceIds)
+                    ->update(['status' => PartnerAgreementServiceStatusEnum::INACTIVE]);
+            }
+        }
+
+        foreach ($labRows as $labData) {
+            [$partner, $isNew] = $this->upsertPartner($labData['name'], [
+                'phone'       => $labData['phone'],
+                'address'     => $labData['address'],
+                'category_id' => $labCategory->id,
+            ]);
+
+            $importedPartnerIds[] = $partner->id;
+            $isNew ? $stats['partners_created']++ : $stats['partners_updated']++;
+        }
+
+        if (!empty($importedPartnerIds)) {
+            $stats['partners_inactivated'] = PartnerAgreement::whereIn('category_id', [
+                $convMedicaCategory->id,
+                $labCategory->id,
+            ])
+                ->where('status', PartnerAgreementStatusEnum::ACTIVE)
+                ->whereNotIn('id', $importedPartnerIds)
+                ->update(['status' => PartnerAgreementStatusEnum::INACTIVE]);
+        }
+
+        return $stats;
+    }
+}

+ 99 - 0
app/Services/ParceirosImportService.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace App\Services;
+
+use App\Enums\PartnerAgreementStatusEnum;
+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
+    {
+        $import = new ParceirosImport();
+        Excel::import($import, $file);
+        $rows = $import->rows ?? collect();
+
+        $created             = 0;
+        $updated             = 0;
+        $importedPartnerIds  = [];
+        $processedCategoryIds = [];
+        $currentCategoryId   = null;
+
+        foreach ($rows as $row) {
+            $col0 = $this->cell($row[0] ?? '');
+            $col1 = $this->cell($row[1] ?? '');
+            $col2 = $this->cell($row[2] ?? '');
+
+            if ($col0 === '' && $col1 === '' && $col2 === '') {
+                continue;
+            }
+
+            $upper0 = mb_strtoupper($col0);
+
+            if (str_contains($upper0, 'LISTA DE PARCEIROS')) {
+                continue;
+            }
+
+            if ($upper0 === 'EMPRESA') {
+                if ($col1 === '') {
+                    continue;
+                }
+
+                $category = $this->findOrCreateCategory($col1);
+                $currentCategoryId = $category->id;
+                $processedCategoryIds[$currentCategoryId] = true;
+                continue;
+            }
+
+            if ($currentCategoryId === null || $col0 === '') {
+                continue;
+            }
+
+            [$partner, $isNew] = $this->upsertPartner($col0, [
+                'description' => $col1 ?: null,
+                'phone'       => $col2 ?: null,
+                'category_id' => $currentCategoryId,
+            ]);
+
+            $importedPartnerIds[] = $partner->id;
+            $isNew ? $created++ : $updated++;
+        }
+
+        $inactivated = 0;
+        if (!empty($processedCategoryIds) && !empty($importedPartnerIds)) {
+            $inactivated = PartnerAgreement::whereIn('category_id', array_keys($processedCategoryIds))
+                ->where('status', PartnerAgreementStatusEnum::ACTIVE)
+                ->whereNotIn('id', $importedPartnerIds)
+                ->update(['status' => PartnerAgreementStatusEnum::INACTIVE]);
+        }
+
+        return [
+            'created'     => $created,
+            'updated'     => $updated,
+            'inactivated' => $inactivated,
+        ];
+    }
+
+    private function findOrCreateCategory(string $rawName): Category
+    {
+        $name = mb_convert_case(mb_strtolower($rawName), MB_CASE_TITLE, 'UTF-8');
+
+        $category = Category::whereRaw(
+            'LOWER(TRIM(name)) = LOWER(TRIM(?)) AND type = ?',
+            [$name, 'partner']
+        )->first();
+
+        return $category ?? Category::create([
+            'name'   => $name,
+            'type'   => 'partner',
+            'active' => true,
+        ]);
+    }
+}

+ 80 - 0
app/Traits/ImportsPartners.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Traits;
+
+use App\Enums\PartnerAgreementStatusEnum;
+use App\Enums\UserStatusEnum;
+use App\Enums\UserTypeEnum;
+use App\Models\PartnerAgreement;
+use App\Models\User;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Str;
+
+trait ImportsPartners
+{
+    private function upsertPartner(string $companyName, array $data): array
+    {
+        $partner = PartnerAgreement::withTrashed()
+            ->whereRaw('LOWER(TRIM(company_name)) = LOWER(TRIM(?))', [$companyName])
+            ->first();
+
+        if ($partner) {
+            if ($partner->trashed()) {
+                $partner->restore();
+            }
+
+            $partner->update(array_merge($data, [
+                'status' => PartnerAgreementStatusEnum::ACTIVE,
+            ]));
+
+            return [$partner->fresh(), false];
+        }
+
+        $user = $this->findOrCreateUser($companyName);
+
+        $partner = PartnerAgreement::create(array_merge($data, [
+            'company_name' => $companyName,
+            'user_id'      => $user->id,
+            'status'       => PartnerAgreementStatusEnum::ACTIVE,
+        ]));
+
+        return [$partner, true];
+    }
+
+    private function findOrCreateUser(string $companyName): User
+    {
+        $slug    = Str::slug($companyName);
+        $email   = "{$slug}@serprati.com";
+        $counter = 2;
+
+        while (User::where('email', $email)->exists()) {
+            $email = "{$slug}{$counter}@serprati.com";
+            $counter++;
+        }
+
+        return User::create([
+            'name'     => $companyName,
+            'email'    => $email,
+            'password' => Hash::make('Serprati2026'),
+            'type'     => UserTypeEnum::PARCEIRO,
+            'status'   => UserStatusEnum::ACTIVE,
+        ]);
+    }
+
+    private function parsePrice(?string $value): ?float
+    {
+        if (empty($value)) {
+            return null;
+        }
+
+        $cleaned = preg_replace('/[^\d,]/', '', (string) $value);
+        $cleaned = str_replace(',', '.', $cleaned);
+
+        return is_numeric($cleaned) ? (float) $cleaned : null;
+    }
+
+    private function cell(mixed $value): string
+    {
+        return trim(preg_replace('/\s+/', ' ', (string) ($value ?? '')));
+    }
+}

+ 8 - 0
routes/authRoutes/associado_import.php

@@ -0,0 +1,8 @@
+<?php
+
+use App\Http\Controllers\AssociadoImportController;
+use Illuminate\Support\Facades\Route;
+
+Route::controller(AssociadoImportController::class)->prefix('user')->group(function () {
+    Route::post('/import/associados', 'importAndSync')->middleware('permission:config.user,add');
+});

+ 12 - 0
routes/authRoutes/parceiro_import.php

@@ -0,0 +1,12 @@
+<?php
+
+use App\Http\Controllers\PartnerImportController;
+use Illuminate\Support\Facades\Route;
+
+Route::controller(PartnerImportController::class)->prefix('partner-agreement')->group(function () {
+    Route::post('/import/parceiros', 'importParceiros')
+        ->middleware('permission:parceiro.convenio,add');
+
+    Route::post('/import/convenios-medicos', 'importConveniosMedicos')
+        ->middleware('permission:parceiro.convenio,add');
+});