ImportSparePartOrdersService.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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=Статус, F=Примечание
  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. $note = trim((string) $sheet->getCell('F' . $row)->getValue());
  40. // Пропускаем пустые строки
  41. if (empty($article)) {
  42. $skipped++;
  43. continue;
  44. }
  45. // Проверяем наличие запчасти в каталоге
  46. if (!isset($this->sparePartCache[$article])) {
  47. $this->log("Строка {$row}: запчасть с артикулом '{$article}' не найдена в каталоге", 'ERROR');
  48. $errors++;
  49. continue;
  50. }
  51. $sparePartId = $this->sparePartCache[$article];
  52. // Парсим "С документами?"
  53. $withDocuments = $this->parseWithDocuments($withDocsRaw);
  54. // Парсим статус
  55. $status = $this->parseStatus($statusRaw);
  56. if ($status === null) {
  57. $this->log("Строка {$row}: неизвестный статус '{$statusRaw}'", 'ERROR');
  58. $errors++;
  59. continue;
  60. }
  61. // Создаём заказ запчасти
  62. SparePartOrder::create([
  63. 'spare_part_id' => $sparePartId,
  64. 'order_number' => $orderNumber ?: null,
  65. 'status' => $status,
  66. 'ordered_quantity' => $quantity,
  67. 'available_qty' => $quantity,
  68. 'with_documents' => $withDocuments,
  69. 'note' => $note !== '' ? $note : null,
  70. 'user_id' => $this->userId,
  71. ]);
  72. $imported++;
  73. }
  74. DB::commit();
  75. $this->log("=== РЕЗЮМЕ ИМПОРТА ===");
  76. $this->log("Импортировано: {$imported}");
  77. $this->log("Пропущено (пустые строки): {$skipped}");
  78. $this->log("Ошибок: {$errors}");
  79. return [
  80. 'success' => $errors === 0,
  81. 'imported' => $imported,
  82. 'skipped' => $skipped,
  83. 'errors' => $errors,
  84. 'logs' => $this->logs,
  85. ];
  86. } catch (Exception $e) {
  87. DB::rollBack();
  88. throw $e;
  89. }
  90. } catch (Exception $e) {
  91. $this->log("КРИТИЧЕСКАЯ ОШИБКА: " . $e->getMessage(), 'ERROR');
  92. Log::error("Ошибка импорта заказов запчастей: " . $e->getMessage(), [
  93. 'trace' => $e->getTraceAsString(),
  94. ]);
  95. return [
  96. 'success' => false,
  97. 'error' => $e->getMessage(),
  98. 'logs' => $this->logs,
  99. ];
  100. }
  101. }
  102. private function loadSparePartCache(): void
  103. {
  104. $this->sparePartCache = SparePart::pluck('id', 'article')->toArray();
  105. $this->log("Загружено " . count($this->sparePartCache) . " запчастей в кэш");
  106. }
  107. private function parseWithDocuments(string $value): bool
  108. {
  109. $value = mb_strtolower(trim($value));
  110. return in_array($value, ['да', 'yes', '1', 'true']);
  111. }
  112. private function parseStatus(string $value): ?string
  113. {
  114. $value = mb_strtolower(trim($value));
  115. $statusMap = [
  116. 'на складе' => SparePartOrder::STATUS_IN_STOCK,
  117. 'in_stock' => SparePartOrder::STATUS_IN_STOCK,
  118. 'in stock' => SparePartOrder::STATUS_IN_STOCK,
  119. 'заказано' => SparePartOrder::STATUS_ORDERED,
  120. 'ordered' => SparePartOrder::STATUS_ORDERED,
  121. 'отгружено' => SparePartOrder::STATUS_SHIPPED,
  122. 'shipped' => SparePartOrder::STATUS_SHIPPED,
  123. ];
  124. return $statusMap[$value] ?? null;
  125. }
  126. private function log(string $message, string $level = 'INFO'): void
  127. {
  128. $this->logs[] = '[' . date('H:i:s') . '] ' . $level . ': ' . $message;
  129. Log::info($message);
  130. }
  131. public function getLogs(): array
  132. {
  133. return $this->logs;
  134. }
  135. }