| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- <?php
- namespace App\Services\Import;
- use App\Models\PricingCode;
- use App\Models\SparePart;
- use Exception;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use PhpOffice\PhpSpreadsheet\IOFactory;
- class ImportSparePartsService
- {
- private array $logs = [];
- public function __construct(
- private readonly string $filePath,
- private readonly int $userId,
- ) {}
- public function handle(): array
- {
- try {
- $this->log("Начало импорта каталога запчастей и справочника расшифровок");
- DB::beginTransaction();
- try {
- // Загружаем файл Excel
- $spreadsheet = IOFactory::load($this->filePath);
- // Импорт каталога запчастей (первая вкладка)
- $this->importSpareParts($spreadsheet->getSheet(0));
- // Импорт справочника расшифровок (вторая вкладка, если есть)
- if ($spreadsheet->getSheetCount() > 1) {
- $this->importPricingCodes($spreadsheet->getSheet(1));
- }
- DB::commit();
- $this->log("Импорт успешно завершён");
- return [
- 'success' => true,
- 'logs' => $this->logs,
- ];
- } catch (Exception $e) {
- DB::rollBack();
- throw $e;
- }
- } catch (Exception $e) {
- $this->log("ОШИБКА: " . $e->getMessage());
- Log::error("Ошибка импорта запчастей: " . $e->getMessage(), [
- 'trace' => $e->getTraceAsString(),
- ]);
- return [
- 'success' => false,
- 'error' => $e->getMessage(),
- 'logs' => $this->logs,
- ];
- }
- }
- private function importSpareParts($sheet): void
- {
- $this->log("Импорт каталога запчастей...");
- $highestRow = $sheet->getHighestRow();
- $imported = 0;
- $updated = 0;
- $skipped = 0;
- // Начинаем со 2-й строки (пропускаем заголовок)
- for ($row = 2; $row <= $highestRow; $row++) {
- $article = trim($sheet->getCell('B' . $row)->getValue());
- // Пропускаем пустые строки
- if (empty($article)) {
- $skipped++;
- continue;
- }
- $data = [
- 'article' => $article,
- 'used_in_maf' => $sheet->getCell('C' . $row)->getValue(),
- 'note' => $sheet->getCell('G' . $row)->getValue(),
- 'purchase_price' => $this->parsePrice($sheet->getCell('H' . $row)->getValue()),
- 'customer_price' => $this->parsePrice($sheet->getCell('I' . $row)->getValue()),
- 'expertise_price' => $this->parsePrice($sheet->getCell('J' . $row)->getValue()),
- 'tsn_number' => $sheet->getCell('K' . $row)->getValue(),
- 'pricing_code' => $sheet->getCell('L' . $row)->getValue(),
- 'min_stock' => (int)$sheet->getCell('M' . $row)->getValue() ?: 0,
- ];
- // Создаём или обновляем запчасть (включая удалённые)
- $sparePart = SparePart::withTrashed()->where('article', $article)->first();
- if ($sparePart) {
- // Восстанавливаем, если была удалена
- if ($sparePart->trashed()) {
- $sparePart->restore();
- }
- $sparePart->update($data);
- $updated++;
- } else {
- SparePart::create($data);
- $imported++;
- }
- }
- $this->log("Каталог запчастей: импортировано {$imported}, обновлено {$updated}, пропущено {$skipped}");
- }
- private function importPricingCodes($sheet): void
- {
- $this->log("Импорт справочника расшифровок...");
- $highestRow = $sheet->getHighestRow();
- $imported = 0;
- $updated = 0;
- $skipped = 0;
- // Начинаем со 2-й строки (пропускаем заголовок)
- for ($row = 2; $row <= $highestRow; $row++) {
- $typeText = trim($sheet->getCell('B' . $row)->getValue());
- $code = trim($sheet->getCell('C' . $row)->getValue());
- $description = trim($sheet->getCell('D' . $row)->getValue());
- // Пропускаем пустые строки
- if (empty($code)) {
- $skipped++;
- continue;
- }
- // Определяем тип
- $type = null;
- if (strpos($typeText, 'ТСН') !== false) {
- $type = PricingCode::TYPE_TSN_NUMBER;
- } elseif (strpos($typeText, 'Шифр') !== false || strpos($typeText, 'расценк') !== false) {
- $type = PricingCode::TYPE_PRICING_CODE;
- }
- if (!$type) {
- $this->log("Предупреждение: неизвестный тип '{$typeText}' для кода '{$code}' в строке {$row}");
- $skipped++;
- continue;
- }
- // Создаём или обновляем запись
- $pricingCode = PricingCode::where('type', $type)->where('code', $code)->first();
- if ($pricingCode) {
- $pricingCode->update(['description' => $description]);
- $updated++;
- } else {
- PricingCode::create([
- 'type' => $type,
- 'code' => $code,
- 'description' => $description,
- ]);
- $imported++;
- }
- }
- $this->log("Справочник расшифровок: импортировано {$imported}, обновлено {$updated}, пропущено {$skipped}");
- }
- /**
- * Парсинг цены
- * Если цена уже в копейках (целое число > 1000), оставляем как есть
- * Если цена в рублях (дробное или < 1000), конвертируем в копейки
- */
- private function parsePrice($value): ?int
- {
- if (empty($value)) {
- return null;
- }
- $price = (float)$value;
- // Если число целое и большое (скорее всего уже в копейках)
- if ($price == (int)$price && $price >= 1000) {
- return (int)$price;
- }
- // Иначе конвертируем из рублей в копейки
- return round($price * 100);
- }
- private function log(string $message): void
- {
- $this->logs[] = '[' . date('H:i:s') . '] ' . $message;
- Log::info($message);
- }
- public function getLogs(): array
- {
- return $this->logs;
- }
- }
|