ExamesImportService.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <?php
  2. namespace App\Services;
  3. use App\Enums\PartnerAgreementServiceStatusEnum;
  4. use App\Enums\PartnerAgreementServiceTypeEnum;
  5. use App\Imports\ExamesImport;
  6. use App\Models\PartnerAgreementService;
  7. use Maatwebsite\Excel\Facades\Excel;
  8. class ExamesImportService
  9. {
  10. public function syncFromExcel(
  11. string $filePath,
  12. int $partnerAgreementId
  13. ): array {
  14. $import = new ExamesImport();
  15. Excel::import($import, $filePath);
  16. $rows = $import->rows ?? collect();
  17. $created = 0;
  18. $updated = 0;
  19. foreach ($rows as $row) {
  20. /*
  21. * Normaliza as células da linha.
  22. */
  23. $cells = collect($row)
  24. ->map(fn ($value) => trim((string) $value))
  25. ->values();
  26. /*
  27. * Ignora linha completamente vazia.
  28. */
  29. if ($cells->filter(fn ($value) => $value !== '')->isEmpty()) {
  30. continue;
  31. }
  32. /*
  33. * Estrutura esperada do Excel:
  34. *
  35. * Coluna 0 = Nome do exame
  36. * Coluna 1 = Preço
  37. */
  38. $name = $cells->get(0, '');
  39. $price = $cells->get(1, '');
  40. /*
  41. * Ignora linhas sem nome.
  42. */
  43. if ($name === '') {
  44. continue;
  45. }
  46. /*
  47. * Trata cabeçalhos do Excel.
  48. *
  49. * Exemplos:
  50. * EXAME | VALOR
  51. * NOME | PREÇO
  52. * NOME DO EXAME | PREÇO
  53. */
  54. $nameHeader = mb_strtoupper($name);
  55. $priceHeader = mb_strtoupper($price);
  56. $isHeader =
  57. $nameHeader === 'EXAME' ||
  58. $nameHeader === 'NOME' ||
  59. (
  60. str_contains($nameHeader, 'EXAME') &&
  61. (
  62. str_contains($priceHeader, 'VALOR') ||
  63. str_contains($priceHeader, 'PREÇO') ||
  64. str_contains($priceHeader, 'PRECO')
  65. )
  66. );
  67. if ($isHeader) {
  68. continue;
  69. }
  70. /*
  71. * Converte o preço para o formato numérico.
  72. */
  73. $associatePrice = $this->parsePrice($price);
  74. /*
  75. * Procura um exame já cadastrado para esse parceiro.
  76. *
  77. * O nome é comparado ignorando:
  78. * - maiúsculas/minúsculas
  79. * - espaços no início/fim
  80. */
  81. $service = PartnerAgreementService::withTrashed()
  82. ->where('partner_agreement_id', $partnerAgreementId)
  83. ->whereRaw(
  84. 'LOWER(TRIM(name)) = LOWER(TRIM(?))',
  85. [$name]
  86. )
  87. ->first();
  88. /*
  89. * EXAME JÁ EXISTE
  90. */
  91. if ($service) {
  92. /*
  93. * Se estava excluído logicamente, restaura.
  94. */
  95. if ($service->trashed()) {
  96. $service->restore();
  97. }
  98. $changed = false;
  99. /*
  100. * Atualiza o preço somente quando
  101. * o Excel trouxe um preço válido.
  102. */
  103. if (
  104. $associatePrice !== null &&
  105. (float) $service->associate_price !== (float) $associatePrice
  106. ) {
  107. $service->associate_price = $associatePrice;
  108. $changed = true;
  109. }
  110. /*
  111. * Garante que o serviço seja do tipo EXAME.
  112. */
  113. if (
  114. $service->type !== PartnerAgreementServiceTypeEnum::EXAME
  115. ) {
  116. $service->type = PartnerAgreementServiceTypeEnum::EXAME;
  117. $changed = true;
  118. }
  119. /*
  120. * Garante que o exame fique ativo.
  121. */
  122. if (
  123. $service->status !== PartnerAgreementServiceStatusEnum::ACTIVE
  124. ) {
  125. $service->status = PartnerAgreementServiceStatusEnum::ACTIVE;
  126. $changed = true;
  127. }
  128. if ($changed) {
  129. $service->save();
  130. $updated++;
  131. }
  132. continue;
  133. }
  134. /*
  135. * EXAME NÃO EXISTE
  136. *
  137. * Cria um novo serviço vinculado
  138. * ao parceiro informado.
  139. */
  140. PartnerAgreementService::create([
  141. 'partner_agreement_id' => $partnerAgreementId,
  142. 'name' => $name,
  143. 'associate_price' => $associatePrice,
  144. 'type' => PartnerAgreementServiceTypeEnum::EXAME,
  145. 'status' => PartnerAgreementServiceStatusEnum::ACTIVE,
  146. ]);
  147. $created++;
  148. }
  149. return [
  150. 'total' => $rows->count(),
  151. 'created' => $created,
  152. 'updated' => $updated,
  153. ];
  154. }
  155. /**
  156. * Converte diferentes formatos de preço
  157. * para float.
  158. */
  159. private function parsePrice(mixed $value): ?float
  160. {
  161. if ($value === null || $value === '') {
  162. return null;
  163. }
  164. $value = trim((string) $value);
  165. /*
  166. * Remove moeda e espaços.
  167. *
  168. * Exemplos:
  169. * R$ 50,00 -> 50,00
  170. * R$50,00 -> 50,00
  171. */
  172. $value = str_replace(['R$', ' '], '', $value);
  173. /*
  174. * Formato brasileiro:
  175. *
  176. * 50,00
  177. * 1.250,50
  178. */
  179. if (str_contains($value, ',')) {
  180. $value = str_replace('.', '', $value);
  181. $value = str_replace(',', '.', $value);
  182. }
  183. /*
  184. * Formato decimal:
  185. *
  186. * 50
  187. * 50.00
  188. * 1250.50
  189. */
  190. return is_numeric($value)
  191. ? (float) $value
  192. : null;
  193. }
  194. }