|
@@ -0,0 +1,224 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Services;
|
|
|
|
|
+
|
|
|
|
|
+use App\Enums\PartnerAgreementServiceStatusEnum;
|
|
|
|
|
+use App\Enums\PartnerAgreementServiceTypeEnum;
|
|
|
|
|
+use App\Imports\ExamesImport;
|
|
|
|
|
+use App\Models\PartnerAgreementService;
|
|
|
|
|
+use Maatwebsite\Excel\Facades\Excel;
|
|
|
|
|
+
|
|
|
|
|
+class ExamesImportService
|
|
|
|
|
+{
|
|
|
|
|
+ public function syncFromExcel(
|
|
|
|
|
+ string $filePath,
|
|
|
|
|
+ int $partnerAgreementId
|
|
|
|
|
+ ): array {
|
|
|
|
|
+ $import = new ExamesImport();
|
|
|
|
|
+
|
|
|
|
|
+ Excel::import($import, $filePath);
|
|
|
|
|
+
|
|
|
|
|
+ $rows = $import->rows ?? collect();
|
|
|
|
|
+
|
|
|
|
|
+ $created = 0;
|
|
|
|
|
+ $updated = 0;
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($rows as $row) {
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Normaliza as células da linha.
|
|
|
|
|
+ */
|
|
|
|
|
+ $cells = collect($row)
|
|
|
|
|
+ ->map(fn ($value) => trim((string) $value))
|
|
|
|
|
+ ->values();
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Ignora linha completamente vazia.
|
|
|
|
|
+ */
|
|
|
|
|
+ if ($cells->filter(fn ($value) => $value !== '')->isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Estrutura esperada do Excel:
|
|
|
|
|
+ *
|
|
|
|
|
+ * Coluna 0 = Nome do exame
|
|
|
|
|
+ * Coluna 1 = Preço
|
|
|
|
|
+ */
|
|
|
|
|
+ $name = $cells->get(0, '');
|
|
|
|
|
+ $price = $cells->get(1, '');
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Ignora linhas sem nome.
|
|
|
|
|
+ */
|
|
|
|
|
+ if ($name === '') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Trata cabeçalhos do Excel.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Exemplos:
|
|
|
|
|
+ * EXAME | VALOR
|
|
|
|
|
+ * NOME | PREÇO
|
|
|
|
|
+ * NOME DO EXAME | PREÇO
|
|
|
|
|
+ */
|
|
|
|
|
+ $nameHeader = mb_strtoupper($name);
|
|
|
|
|
+ $priceHeader = mb_strtoupper($price);
|
|
|
|
|
+
|
|
|
|
|
+ $isHeader =
|
|
|
|
|
+ $nameHeader === 'EXAME' ||
|
|
|
|
|
+ $nameHeader === 'NOME' ||
|
|
|
|
|
+ (
|
|
|
|
|
+ str_contains($nameHeader, 'EXAME') &&
|
|
|
|
|
+ (
|
|
|
|
|
+ str_contains($priceHeader, 'VALOR') ||
|
|
|
|
|
+ str_contains($priceHeader, 'PREÇO') ||
|
|
|
|
|
+ str_contains($priceHeader, 'PRECO')
|
|
|
|
|
+ )
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if ($isHeader) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Converte o preço para o formato numérico.
|
|
|
|
|
+ */
|
|
|
|
|
+ $associatePrice = $this->parsePrice($price);
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Procura um exame já cadastrado para esse parceiro.
|
|
|
|
|
+ *
|
|
|
|
|
+ * O nome é comparado ignorando:
|
|
|
|
|
+ * - maiúsculas/minúsculas
|
|
|
|
|
+ * - espaços no início/fim
|
|
|
|
|
+ */
|
|
|
|
|
+ $service = PartnerAgreementService::withTrashed()
|
|
|
|
|
+ ->where('partner_agreement_id', $partnerAgreementId)
|
|
|
|
|
+ ->whereRaw(
|
|
|
|
|
+ 'LOWER(TRIM(name)) = LOWER(TRIM(?))',
|
|
|
|
|
+ [$name]
|
|
|
|
|
+ )
|
|
|
|
|
+ ->first();
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * EXAME JÁ EXISTE
|
|
|
|
|
+ */
|
|
|
|
|
+ if ($service) {
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Se estava excluído logicamente, restaura.
|
|
|
|
|
+ */
|
|
|
|
|
+ if ($service->trashed()) {
|
|
|
|
|
+ $service->restore();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $changed = false;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Atualiza o preço somente quando
|
|
|
|
|
+ * o Excel trouxe um preço válido.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (
|
|
|
|
|
+ $associatePrice !== null &&
|
|
|
|
|
+ (float) $service->associate_price !== (float) $associatePrice
|
|
|
|
|
+ ) {
|
|
|
|
|
+ $service->associate_price = $associatePrice;
|
|
|
|
|
+ $changed = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Garante que o serviço seja do tipo EXAME.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (
|
|
|
|
|
+ $service->type !== PartnerAgreementServiceTypeEnum::EXAME
|
|
|
|
|
+ ) {
|
|
|
|
|
+ $service->type = PartnerAgreementServiceTypeEnum::EXAME;
|
|
|
|
|
+ $changed = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Garante que o exame fique ativo.
|
|
|
|
|
+ */
|
|
|
|
|
+ if (
|
|
|
|
|
+ $service->status !== PartnerAgreementServiceStatusEnum::ACTIVE
|
|
|
|
|
+ ) {
|
|
|
|
|
+ $service->status = PartnerAgreementServiceStatusEnum::ACTIVE;
|
|
|
|
|
+ $changed = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($changed) {
|
|
|
|
|
+ $service->save();
|
|
|
|
|
+ $updated++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * EXAME NÃO EXISTE
|
|
|
|
|
+ *
|
|
|
|
|
+ * Cria um novo serviço vinculado
|
|
|
|
|
+ * ao parceiro informado.
|
|
|
|
|
+ */
|
|
|
|
|
+ PartnerAgreementService::create([
|
|
|
|
|
+ 'partner_agreement_id' => $partnerAgreementId,
|
|
|
|
|
+ 'name' => $name,
|
|
|
|
|
+ 'associate_price' => $associatePrice,
|
|
|
|
|
+ 'type' => PartnerAgreementServiceTypeEnum::EXAME,
|
|
|
|
|
+ 'status' => PartnerAgreementServiceStatusEnum::ACTIVE,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $created++;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'total' => $rows->count(),
|
|
|
|
|
+ 'created' => $created,
|
|
|
|
|
+ 'updated' => $updated,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Converte diferentes formatos de preço
|
|
|
|
|
+ * para float.
|
|
|
|
|
+ */
|
|
|
|
|
+ private function parsePrice(mixed $value): ?float
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($value === null || $value === '') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $value = trim((string) $value);
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Remove moeda e espaços.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Exemplos:
|
|
|
|
|
+ * R$ 50,00 -> 50,00
|
|
|
|
|
+ * R$50,00 -> 50,00
|
|
|
|
|
+ */
|
|
|
|
|
+ $value = str_replace(['R$', ' '], '', $value);
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Formato brasileiro:
|
|
|
|
|
+ *
|
|
|
|
|
+ * 50,00
|
|
|
|
|
+ * 1.250,50
|
|
|
|
|
+ */
|
|
|
|
|
+ if (str_contains($value, ',')) {
|
|
|
|
|
+ $value = str_replace('.', '', $value);
|
|
|
|
|
+ $value = str_replace(',', '.', $value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Formato decimal:
|
|
|
|
|
+ *
|
|
|
|
|
+ * 50
|
|
|
|
|
+ * 50.00
|
|
|
|
|
+ * 1250.50
|
|
|
|
|
+ */
|
|
|
|
|
+ return is_numeric($value)
|
|
|
|
|
+ ? (float) $value
|
|
|
|
|
+ : null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|