ImportSparePartsService.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <?php
  2. namespace App\Services\Import;
  3. use App\Models\PricingCode;
  4. use App\Models\SparePart;
  5. use Exception;
  6. use Illuminate\Support\Facades\DB;
  7. use Illuminate\Support\Facades\Log;
  8. use PhpOffice\PhpSpreadsheet\IOFactory;
  9. class ImportSparePartsService
  10. {
  11. private array $logs = [];
  12. public function __construct(
  13. private readonly string $filePath,
  14. private readonly int $userId,
  15. ) {}
  16. public function handle(): array
  17. {
  18. try {
  19. $this->log("Начало импорта каталога запчастей и справочника расшифровок");
  20. DB::beginTransaction();
  21. try {
  22. // Загружаем файл Excel
  23. $spreadsheet = IOFactory::load($this->filePath);
  24. // Импорт каталога запчастей (первая вкладка)
  25. $this->importSpareParts($spreadsheet->getSheet(0));
  26. // Импорт справочника расшифровок (вторая вкладка, если есть)
  27. if ($spreadsheet->getSheetCount() > 1) {
  28. $this->importPricingCodes($spreadsheet->getSheet(1));
  29. }
  30. DB::commit();
  31. $this->log("Импорт успешно завершён");
  32. return [
  33. 'success' => true,
  34. 'logs' => $this->logs,
  35. ];
  36. } catch (Exception $e) {
  37. DB::rollBack();
  38. throw $e;
  39. }
  40. } catch (Exception $e) {
  41. $this->log("ОШИБКА: " . $e->getMessage());
  42. Log::error("Ошибка импорта запчастей: " . $e->getMessage(), [
  43. 'trace' => $e->getTraceAsString(),
  44. ]);
  45. return [
  46. 'success' => false,
  47. 'error' => $e->getMessage(),
  48. 'logs' => $this->logs,
  49. ];
  50. }
  51. }
  52. private function importSpareParts($sheet): void
  53. {
  54. $this->log("Импорт каталога запчастей...");
  55. $highestRow = $sheet->getHighestRow();
  56. $imported = 0;
  57. $updated = 0;
  58. $skipped = 0;
  59. // Начинаем со 2-й строки (пропускаем заголовок)
  60. for ($row = 2; $row <= $highestRow; $row++) {
  61. $article = trim($sheet->getCell('B' . $row)->getValue());
  62. // Пропускаем пустые строки
  63. if (empty($article)) {
  64. $skipped++;
  65. continue;
  66. }
  67. $data = [
  68. 'article' => $article,
  69. 'used_in_maf' => $sheet->getCell('C' . $row)->getValue(),
  70. 'note' => $sheet->getCell('G' . $row)->getValue(),
  71. 'purchase_price' => $this->parsePrice($sheet->getCell('H' . $row)->getValue()),
  72. 'customer_price' => $this->parsePrice($sheet->getCell('I' . $row)->getValue()),
  73. 'expertise_price' => $this->parsePrice($sheet->getCell('J' . $row)->getValue()),
  74. 'tsn_number' => $sheet->getCell('K' . $row)->getValue(),
  75. 'pricing_code' => $sheet->getCell('L' . $row)->getValue(),
  76. 'min_stock' => (int)$sheet->getCell('M' . $row)->getValue() ?: 0,
  77. ];
  78. // Создаём или обновляем запчасть (включая удалённые)
  79. $sparePart = SparePart::withTrashed()->where('article', $article)->first();
  80. if ($sparePart) {
  81. // Восстанавливаем, если была удалена
  82. if ($sparePart->trashed()) {
  83. $sparePart->restore();
  84. }
  85. $sparePart->update($data);
  86. $updated++;
  87. } else {
  88. SparePart::create($data);
  89. $imported++;
  90. }
  91. }
  92. $this->log("Каталог запчастей: импортировано {$imported}, обновлено {$updated}, пропущено {$skipped}");
  93. }
  94. private function importPricingCodes($sheet): void
  95. {
  96. $this->log("Импорт справочника расшифровок...");
  97. $highestRow = $sheet->getHighestRow();
  98. $imported = 0;
  99. $updated = 0;
  100. $skipped = 0;
  101. // Начинаем со 2-й строки (пропускаем заголовок)
  102. for ($row = 2; $row <= $highestRow; $row++) {
  103. $typeText = trim($sheet->getCell('B' . $row)->getValue());
  104. $code = trim($sheet->getCell('C' . $row)->getValue());
  105. $description = trim($sheet->getCell('D' . $row)->getValue());
  106. // Пропускаем пустые строки
  107. if (empty($code)) {
  108. $skipped++;
  109. continue;
  110. }
  111. // Определяем тип
  112. $type = null;
  113. if (strpos($typeText, 'ТСН') !== false) {
  114. $type = PricingCode::TYPE_TSN_NUMBER;
  115. } elseif (strpos($typeText, 'Шифр') !== false || strpos($typeText, 'расценк') !== false) {
  116. $type = PricingCode::TYPE_PRICING_CODE;
  117. }
  118. if (!$type) {
  119. $this->log("Предупреждение: неизвестный тип '{$typeText}' для кода '{$code}' в строке {$row}");
  120. $skipped++;
  121. continue;
  122. }
  123. // Создаём или обновляем запись
  124. $pricingCode = PricingCode::where('type', $type)->where('code', $code)->first();
  125. if ($pricingCode) {
  126. $pricingCode->update(['description' => $description]);
  127. $updated++;
  128. } else {
  129. PricingCode::create([
  130. 'type' => $type,
  131. 'code' => $code,
  132. 'description' => $description,
  133. ]);
  134. $imported++;
  135. }
  136. }
  137. $this->log("Справочник расшифровок: импортировано {$imported}, обновлено {$updated}, пропущено {$skipped}");
  138. }
  139. /**
  140. * Парсинг цены
  141. * Если цена уже в копейках (целое число > 1000), оставляем как есть
  142. * Если цена в рублях (дробное или < 1000), конвертируем в копейки
  143. */
  144. private function parsePrice($value): ?int
  145. {
  146. if (empty($value)) {
  147. return null;
  148. }
  149. $price = (float)$value;
  150. // Если число целое и большое (скорее всего уже в копейках)
  151. if ($price == (int)$price && $price >= 1000) {
  152. return (int)$price;
  153. }
  154. // Иначе конвертируем из рублей в копейки
  155. return round($price * 100);
  156. }
  157. private function log(string $message): void
  158. {
  159. $this->logs[] = '[' . date('H:i:s') . '] ' . $message;
  160. Log::info($message);
  161. }
  162. public function getLogs(): array
  163. {
  164. return $this->logs;
  165. }
  166. }