ImportSparePartOrdersService.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php
  2. namespace App\Services\Import;
  3. use App\Models\SparePart;
  4. use App\Models\SparePartOrder;
  5. use Exception;
  6. use Illuminate\Support\Facades\DB;
  7. use Illuminate\Support\Facades\Log;
  8. use PhpOffice\PhpSpreadsheet\IOFactory;
  9. class ImportSparePartOrdersService
  10. {
  11. private array $logs = [];
  12. private array $sparePartCache = [];
  13. public function __construct(
  14. private readonly string $filePath,
  15. private readonly int $userId,
  16. ) {}
  17. public function handle(): array
  18. {
  19. try {
  20. $this->log("Начало импорта заказов запчастей");
  21. // Загружаем кэш запчастей по артикулу
  22. $this->loadSparePartCache();
  23. DB::beginTransaction();
  24. try {
  25. $spreadsheet = IOFactory::load($this->filePath);
  26. $sheet = $spreadsheet->getActiveSheet();
  27. $highestRow = $sheet->getHighestRow();
  28. $imported = 0;
  29. $skipped = 0;
  30. $errors = 0;
  31. // Начинаем со 2-й строки (пропускаем заголовок)
  32. // Колонки: A=Артикул, B=кол-во, C=С документами?, D=Номер заказа, E=Статус
  33. for ($row = 2; $row <= $highestRow; $row++) {
  34. $article = trim($sheet->getCell('A' . $row)->getValue());
  35. $quantity = (int) $sheet->getCell('B' . $row)->getValue();
  36. $withDocsRaw = trim($sheet->getCell('C' . $row)->getValue());
  37. $orderNumber = trim($sheet->getCell('D' . $row)->getValue());
  38. $statusRaw = trim($sheet->getCell('E' . $row)->getValue());
  39. // Пропускаем пустые строки
  40. if (empty($article)) {
  41. $skipped++;
  42. continue;
  43. }
  44. // Проверяем наличие запчасти в каталоге
  45. if (!isset($this->sparePartCache[$article])) {
  46. $this->log("Строка {$row}: запчасть с артикулом '{$article}' не найдена в каталоге", 'ERROR');
  47. $errors++;
  48. continue;
  49. }
  50. $sparePartId = $this->sparePartCache[$article];
  51. // Парсим "С документами?"
  52. $withDocuments = $this->parseWithDocuments($withDocsRaw);
  53. // Парсим статус
  54. $status = $this->parseStatus($statusRaw);
  55. if ($status === null) {
  56. $this->log("Строка {$row}: неизвестный статус '{$statusRaw}'", 'ERROR');
  57. $errors++;
  58. continue;
  59. }
  60. // Создаём заказ запчасти
  61. SparePartOrder::create([
  62. 'spare_part_id' => $sparePartId,
  63. 'order_number' => $orderNumber ?: null,
  64. 'status' => $status,
  65. 'ordered_quantity' => $quantity,
  66. 'available_qty' => $quantity,
  67. 'with_documents' => $withDocuments,
  68. 'user_id' => $this->userId,
  69. ]);
  70. $imported++;
  71. }
  72. DB::commit();
  73. $this->log("=== РЕЗЮМЕ ИМПОРТА ===");
  74. $this->log("Импортировано: {$imported}");
  75. $this->log("Пропущено (пустые строки): {$skipped}");
  76. $this->log("Ошибок: {$errors}");
  77. return [
  78. 'success' => $errors === 0,
  79. 'imported' => $imported,
  80. 'skipped' => $skipped,
  81. 'errors' => $errors,
  82. 'logs' => $this->logs,
  83. ];
  84. } catch (Exception $e) {
  85. DB::rollBack();
  86. throw $e;
  87. }
  88. } catch (Exception $e) {
  89. $this->log("КРИТИЧЕСКАЯ ОШИБКА: " . $e->getMessage(), 'ERROR');
  90. Log::error("Ошибка импорта заказов запчастей: " . $e->getMessage(), [
  91. 'trace' => $e->getTraceAsString(),
  92. ]);
  93. return [
  94. 'success' => false,
  95. 'error' => $e->getMessage(),
  96. 'logs' => $this->logs,
  97. ];
  98. }
  99. }
  100. private function loadSparePartCache(): void
  101. {
  102. $this->sparePartCache = SparePart::pluck('id', 'article')->toArray();
  103. $this->log("Загружено " . count($this->sparePartCache) . " запчастей в кэш");
  104. }
  105. private function parseWithDocuments(string $value): bool
  106. {
  107. $value = mb_strtolower(trim($value));
  108. return in_array($value, ['да', 'yes', '1', 'true']);
  109. }
  110. private function parseStatus(string $value): ?string
  111. {
  112. $value = mb_strtolower(trim($value));
  113. $statusMap = [
  114. 'на складе' => SparePartOrder::STATUS_IN_STOCK,
  115. 'in_stock' => SparePartOrder::STATUS_IN_STOCK,
  116. 'in stock' => SparePartOrder::STATUS_IN_STOCK,
  117. 'заказано' => SparePartOrder::STATUS_ORDERED,
  118. 'ordered' => SparePartOrder::STATUS_ORDERED,
  119. 'отгружено' => SparePartOrder::STATUS_SHIPPED,
  120. 'shipped' => SparePartOrder::STATUS_SHIPPED,
  121. ];
  122. return $statusMap[$value] ?? null;
  123. }
  124. private function log(string $message, string $level = 'INFO'): void
  125. {
  126. $this->logs[] = '[' . date('H:i:s') . '] ' . $level . ': ' . $message;
  127. Log::info($message);
  128. }
  129. public function getLogs(): array
  130. {
  131. return $this->logs;
  132. }
  133. }