ExamesImportService.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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 = Código do exame
  36. * Coluna 1 = Nome do exame
  37. * Coluna 2 = Preço
  38. */
  39. $codeExams = $cells->get(0, '');
  40. $name = $cells->get(1, '');
  41. $price = $cells->get(2, '');
  42. /*
  43. * Ignora linhas sem nome.
  44. */
  45. if ($name === '') {
  46. continue;
  47. }
  48. /*
  49. * Trata cabeçalhos do Excel.
  50. */
  51. $codeHeader = mb_strtoupper($codeExams);
  52. $nameHeader = mb_strtoupper($name);
  53. $priceHeader = mb_strtoupper($price);
  54. $isHeader =
  55. str_contains($codeHeader, 'CÓDIGO') ||
  56. str_contains($codeHeader, 'CODIGO') ||
  57. $nameHeader === 'EXAME' ||
  58. $nameHeader === 'NOME' ||
  59. str_contains($nameHeader, 'EXAME') &&
  60. (
  61. str_contains($priceHeader, 'VALOR') ||
  62. str_contains($priceHeader, 'PREÇO') ||
  63. str_contains($priceHeader, 'PRECO')
  64. );
  65. if ($isHeader) {
  66. continue;
  67. }
  68. /*
  69. * Converte o preço para o formato numérico.
  70. */
  71. $associatePrice = $this->parsePrice($price);
  72. /*
  73. * Procura um exame já cadastrado para esse parceiro.
  74. *
  75. * Primeiro tenta localizar pelo código quando
  76. * o código foi informado.
  77. */
  78. $service = null;
  79. if ($codeExams !== '') {
  80. $service = PartnerAgreementService::withTrashed()
  81. ->where('partner_agreement_id', $partnerAgreementId)
  82. ->where('code_exams', $codeExams)
  83. ->first();
  84. }
  85. /*
  86. * Caso não exista código ou não tenha encontrado pelo código,
  87. * procura pelo nome.
  88. */
  89. if (!$service) {
  90. $service = PartnerAgreementService::withTrashed()
  91. ->where('partner_agreement_id', $partnerAgreementId)
  92. ->whereRaw(
  93. 'LOWER(TRIM(name)) = LOWER(TRIM(?))',
  94. [$name]
  95. )
  96. ->first();
  97. }
  98. /*
  99. * EXAME JÁ EXISTE
  100. */
  101. if ($service) {
  102. /*
  103. * Se estava excluído logicamente, restaura.
  104. */
  105. if ($service->trashed()) {
  106. $service->restore();
  107. }
  108. $changed = false;
  109. /*
  110. * Atualiza o código quando informado.
  111. */
  112. if (
  113. $codeExams !== '' &&
  114. $service->code_exams !== $codeExams
  115. ) {
  116. $service->code_exams = $codeExams;
  117. $changed = true;
  118. }
  119. /*
  120. * Atualiza o preço somente quando
  121. * o Excel trouxe um preço válido.
  122. */
  123. if (
  124. $associatePrice !== null &&
  125. (float) $service->associate_price !== (float) $associatePrice
  126. ) {
  127. $service->associate_price = $associatePrice;
  128. $changed = true;
  129. }
  130. /*
  131. * Garante que o serviço seja do tipo EXAME.
  132. */
  133. if (
  134. $service->type !== PartnerAgreementServiceTypeEnum::EXAME
  135. ) {
  136. $service->type = PartnerAgreementServiceTypeEnum::EXAME;
  137. $changed = true;
  138. }
  139. /*
  140. * Garante que o exame fique ativo.
  141. */
  142. if (
  143. $service->status !== PartnerAgreementServiceStatusEnum::ACTIVE
  144. ) {
  145. $service->status = PartnerAgreementServiceStatusEnum::ACTIVE;
  146. $changed = true;
  147. }
  148. if ($changed) {
  149. $service->save();
  150. $updated++;
  151. }
  152. continue;
  153. }
  154. /*
  155. * EXAME NÃO EXISTE
  156. *
  157. * Cria um novo serviço vinculado
  158. * ao parceiro informado.
  159. */
  160. PartnerAgreementService::create([
  161. 'partner_agreement_id' => $partnerAgreementId,
  162. 'code_exams' => $codeExams !== '' ? $codeExams : null,
  163. 'name' => $name,
  164. 'associate_price' => $associatePrice,
  165. 'type' => PartnerAgreementServiceTypeEnum::EXAME,
  166. 'status' => PartnerAgreementServiceStatusEnum::ACTIVE,
  167. ]);
  168. $created++;
  169. }
  170. return [
  171. 'total' => $rows->count(),
  172. 'created' => $created,
  173. 'updated' => $updated,
  174. ];
  175. }
  176. /**
  177. * Converte diferentes formatos de preço
  178. * para float.
  179. */
  180. private function parsePrice(mixed $value): ?float
  181. {
  182. if ($value === null || $value === '') {
  183. return null;
  184. }
  185. $value = trim((string) $value);
  186. /*
  187. * Remove moeda e espaços.
  188. *
  189. * Exemplos:
  190. * R$ 50,00 -> 50,00
  191. * R$50,00 -> 50,00
  192. */
  193. $value = str_replace(['R$', ' '], '', $value);
  194. /*
  195. * Formato brasileiro:
  196. *
  197. * 50,00
  198. * 1.250,50
  199. */
  200. if (str_contains($value, ',')) {
  201. $value = str_replace('.', '', $value);
  202. $value = str_replace(',', '.', $value);
  203. }
  204. /*
  205. * Formato decimal:
  206. *
  207. * 50
  208. * 50.00
  209. * 1250.50
  210. */
  211. return is_numeric($value)
  212. ? (float) $value
  213. : null;
  214. }
  215. }