| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188 |
- <?php
- namespace App\Services\Import;
- use App\Models\Contract;
- use App\Models\File;
- use App\Models\MafOrder;
- use App\Models\Order;
- use App\Models\Product;
- use App\Models\ProductSKU;
- use App\Models\Reclamation;
- use App\Models\ReclamationDetail;
- use App\Models\Schedule;
- use App\Models\Ttn;
- use App\Models\User;
- use Exception;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Storage;
- use Illuminate\Support\Str;
- use PhpOffice\PhpSpreadsheet\IOFactory;
- use PhpOffice\PhpSpreadsheet\Spreadsheet;
- use ZipArchive;
- class ImportYearDataService
- {
- private string $tempDir;
- private array $manifest;
- private array $logs = [];
- // ID маппинги: old_id => new_id
- private array $productIdMapping = [];
- private array $mafOrderIdMapping = [];
- private array $orderIdMapping = [];
- private array $productSkuIdMapping = [];
- private array $reclamationIdMapping = [];
- private array $fileIdMapping = [];
- // Справочники для маппинга
- private array $districtMapping = [];
- private array $areaMapping = [];
- private array $userMapping = [];
- private array $objectTypeMapping = [];
- private array $orderStatusMapping = [];
- private array $reclamationStatusMapping = [];
- public function __construct(
- private readonly string $archivePath,
- private readonly int $year,
- private readonly int $userId,
- private readonly bool $clearExisting = false,
- ) {}
- public function handle(): bool
- {
- $this->prepareTempDirectory();
- try {
- $this->log("Начало импорта данных за {$this->year} год");
- // Распаковка архива
- $this->extractArchive();
- // Валидация манифеста
- $this->validateManifest();
- // Загрузка справочников для маппинга
- $this->loadDictionaries();
- // Очистка существующих данных (опционально)
- if ($this->clearExisting) {
- $this->log("Очистка существующих данных за {$this->year} год...");
- $this->clearExistingData();
- }
- DB::beginTransaction();
- try {
- // Импорт данных в правильном порядке
- $this->importProducts();
- $this->importMafOrders();
- $this->importOrders();
- $this->importProductsSku();
- $this->importReclamations();
- $this->importSchedules();
- $this->importContracts();
- $this->importTtn();
- // Импорт pivot таблиц (файлы заказов и рекламаций)
- $this->importPivotTables();
- DB::commit();
- $this->log("Импорт успешно завершён");
- return true;
- } catch (Exception $e) {
- DB::rollBack();
- throw $e;
- }
- } catch (Exception $e) {
- $this->log("Ошибка импорта: " . $e->getMessage(), 'ERROR');
- Log::error("ImportYearDataService error: " . $e->getMessage(), [
- 'trace' => $e->getTraceAsString()
- ]);
- return false;
- } finally {
- $this->cleanupTempDirectory();
- }
- }
- private function prepareTempDirectory(): void
- {
- $this->tempDir = storage_path('app/temp/import_year_' . $this->year . '_' . Str::random(8));
- if (!is_dir($this->tempDir)) {
- mkdir($this->tempDir, 0755, true);
- }
- }
- private function extractArchive(): void
- {
- $this->log("Распаковка архива...");
- $zip = new ZipArchive();
- if ($zip->open($this->archivePath) !== true) {
- throw new Exception("Не удалось открыть архив");
- }
- $zip->extractTo($this->tempDir);
- $zip->close();
- $this->log("Архив распакован");
- }
- private function validateManifest(): void
- {
- $manifestPath = $this->tempDir . '/manifest.json';
- if (!file_exists($manifestPath)) {
- throw new Exception("Файл manifest.json не найден в архиве");
- }
- $this->manifest = json_decode(file_get_contents($manifestPath), true);
- if (!$this->manifest) {
- throw new Exception("Некорректный формат manifest.json");
- }
- if (!isset($this->manifest['version']) || !isset($this->manifest['year'])) {
- throw new Exception("Отсутствуют обязательные поля в manifest.json");
- }
- $this->log("Манифест валиден. Версия: {$this->manifest['version']}, Год экспорта: {$this->manifest['year']}");
- // Показываем статистику из манифеста
- if (isset($this->manifest['stats'])) {
- $this->log("Статистика из архива:");
- foreach ($this->manifest['stats'] as $entity => $count) {
- $this->log(" - {$entity}: {$count}");
- }
- }
- }
- private function loadDictionaries(): void
- {
- $this->log("Загрузка справочников...");
- // Округа
- $districts = DB::table('districts')->get();
- foreach ($districts as $d) {
- $this->districtMapping[$d->shortname] = $d->id;
- }
- // Районы
- $areas = DB::table('areas')->get();
- foreach ($areas as $a) {
- $this->areaMapping[$a->name] = $a->id;
- }
- // Пользователи
- $users = User::all();
- foreach ($users as $u) {
- $this->userMapping[$u->name] = $u->id;
- }
- // Типы объектов
- $objectTypes = DB::table('object_types')->get();
- foreach ($objectTypes as $ot) {
- $this->objectTypeMapping[$ot->name] = $ot->id;
- }
- // Статусы заказов
- $orderStatuses = DB::table('order_statuses')->get();
- foreach ($orderStatuses as $os) {
- $this->orderStatusMapping[$os->name] = $os->id;
- }
- // Статусы рекламаций
- $reclamationStatuses = DB::table('reclamation_statuses')->get();
- foreach ($reclamationStatuses as $rs) {
- $this->reclamationStatusMapping[$rs->name] = $rs->id;
- }
- $this->log("Справочники загружены");
- }
- private function clearExistingData(): void
- {
- // Используем логику из ClearYearDataJob
- $orderIds = Order::withoutGlobalScopes()->withTrashed()->where('year', $this->year)->pluck('id');
- $productIds = Product::withoutGlobalScopes()->withTrashed()->where('year', $this->year)->pluck('id');
- $productSkuIds = ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $this->year)->pluck('id');
- $reclamationIds = Reclamation::whereIn('order_id', $orderIds)->pluck('id');
- // Собираем файлы до удаления связей
- $fileIds = $this->collectFileIds($orderIds, $productIds, $productSkuIds);
- // Рекламации
- DB::table('reclamation_details')->whereIn('reclamation_id', $reclamationIds)->delete();
- DB::table('reclamation_product_sku')->whereIn('reclamation_id', $reclamationIds)->delete();
- DB::table('reclamation_photo_before')->whereIn('reclamation_id', $reclamationIds)->delete();
- DB::table('reclamation_photo_after')->whereIn('reclamation_id', $reclamationIds)->delete();
- DB::table('reclamation_document')->whereIn('reclamation_id', $reclamationIds)->delete();
- DB::table('reclamation_act')->whereIn('reclamation_id', $reclamationIds)->delete();
- Reclamation::whereIn('id', $reclamationIds)->delete();
- // Связи заказов
- DB::table('order_photo')->whereIn('order_id', $orderIds)->delete();
- DB::table('order_document')->whereIn('order_id', $orderIds)->delete();
- DB::table('order_statement')->whereIn('order_id', $orderIds)->delete();
- // Расписания
- Schedule::whereIn('order_id', $orderIds)->delete();
- // SKU
- ProductSKU::withoutGlobalScopes()->withTrashed()->where('year', $this->year)->forceDelete();
- // Заказы
- Order::withoutGlobalScopes()->withTrashed()->where('year', $this->year)->forceDelete();
- // МАФ заказы
- MafOrder::withoutGlobalScopes()->withTrashed()->where('year', $this->year)->forceDelete();
- // Продукты
- Product::withoutGlobalScopes()->withTrashed()
- ->whereIn('id', $productIds)
- ->update(['certificate_id' => null]);
- Product::withoutGlobalScopes()->withTrashed()->where('year', $this->year)->forceDelete();
- // ТТН
- Ttn::where('year', $this->year)->update(['file_id' => null]);
- Ttn::where('year', $this->year)->delete();
- // Контракты
- Contract::where('year', $this->year)->delete();
- // Файлы
- $this->deleteFiles($fileIds);
- $this->log("Существующие данные очищены");
- }
- private function collectFileIds($orderIds, $productIds, $productSkuIds): \Illuminate\Support\Collection
- {
- $fileIds = collect();
- $fileIds = $fileIds->merge(DB::table('order_photo')->whereIn('order_id', $orderIds)->pluck('file_id'));
- $fileIds = $fileIds->merge(DB::table('order_document')->whereIn('order_id', $orderIds)->pluck('file_id'));
- $fileIds = $fileIds->merge(DB::table('order_statement')->whereIn('order_id', $orderIds)->pluck('file_id'));
- $reclamationIds = Reclamation::whereIn('order_id', $orderIds)->pluck('id');
- $fileIds = $fileIds->merge(DB::table('reclamation_photo_before')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id'));
- $fileIds = $fileIds->merge(DB::table('reclamation_photo_after')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id'));
- $fileIds = $fileIds->merge(DB::table('reclamation_document')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id'));
- $fileIds = $fileIds->merge(DB::table('reclamation_act')->whereIn('reclamation_id', $reclamationIds)->pluck('file_id'));
- $fileIds = $fileIds->merge(
- Product::withoutGlobalScopes()->withTrashed()
- ->whereIn('id', $productIds)
- ->whereNotNull('certificate_id')
- ->pluck('certificate_id')
- );
- $fileIds = $fileIds->merge(
- ProductSKU::withoutGlobalScopes()->withTrashed()
- ->whereIn('id', $productSkuIds)
- ->whereNotNull('passport_id')
- ->pluck('passport_id')
- );
- $fileIds = $fileIds->merge(
- Ttn::where('year', $this->year)->whereNotNull('file_id')->pluck('file_id')
- );
- return $fileIds->unique();
- }
- private function deleteFiles($fileIds): void
- {
- $files = File::whereIn('id', $fileIds)->get();
- foreach ($files as $file) {
- if ($file->path && Storage::disk('public')->exists($file->path)) {
- Storage::disk('public')->delete($file->path);
- }
- }
- File::whereIn('id', $fileIds)->delete();
- }
- /**
- * Безопасно получает значение из строки Excel
- * Возвращает null для пустых строк, или значение по умолчанию
- */
- private function getValue(array $row, array $headerMap, string $key, mixed $default = null): mixed
- {
- if (!isset($headerMap[$key])) {
- return $default;
- }
- $value = $row[$headerMap[$key]] ?? null;
- if ($value === null || $value === '') {
- return $default;
- }
- return $value;
- }
- /**
- * Получает строковое значение (пустая строка вместо null)
- */
- private function getStringValue(array $row, array $headerMap, string $key, string $default = ''): string
- {
- $value = $this->getValue($row, $headerMap, $key);
- return $value !== null ? (string) $value : $default;
- }
- /**
- * Получает числовое значение
- */
- private function getNumericValue(array $row, array $headerMap, string $key, int|float $default = 0): int|float
- {
- $value = $this->getValue($row, $headerMap, $key);
- if ($value === null) {
- return $default;
- }
- return is_numeric($value) ? $value : $default;
- }
- private function importProducts(): void
- {
- $this->log("Импорт продуктов...");
- $filePath = $this->tempDir . '/data/products.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл products.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- $oldId = $this->getValue($row, $headerMap, 'id');
- $nomenclatureNumber = $this->getStringValue($row, $headerMap, 'nomenclature_number', '');
- // Проверяем существует ли продукт
- $existing = Product::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('nomenclature_number', $nomenclatureNumber)
- ->first();
- // Импорт сертификата если есть
- $certificateId = null;
- $certificatePath = $this->getValue($row, $headerMap, 'certificate_file');
- if ($certificatePath) {
- $certificateId = $this->importFile($certificatePath);
- }
- $productData = [
- 'year' => $this->year,
- 'article' => $this->getStringValue($row, $headerMap, 'article'),
- 'name_tz' => $this->getValue($row, $headerMap, 'name_tz'),
- 'type_tz' => $this->getValue($row, $headerMap, 'type_tz'),
- 'nomenclature_number' => $nomenclatureNumber,
- 'sizes' => $this->getValue($row, $headerMap, 'sizes'),
- 'manufacturer' => $this->getValue($row, $headerMap, 'manufacturer'),
- 'unit' => $this->getStringValue($row, $headerMap, 'unit', 'шт.'),
- 'type' => $this->getStringValue($row, $headerMap, 'type', 'Товар'),
- 'product_price' => $this->getNumericValue($row, $headerMap, 'product_price', 0),
- 'installation_price' => $this->getNumericValue($row, $headerMap, 'installation_price', 0),
- 'total_price' => $this->getNumericValue($row, $headerMap, 'total_price', 0),
- 'manufacturer_name' => $this->getValue($row, $headerMap, 'manufacturer_name'),
- 'note' => $this->getValue($row, $headerMap, 'note'),
- 'passport_name' => $this->getValue($row, $headerMap, 'passport_name'),
- 'statement_name' => $this->getValue($row, $headerMap, 'statement_name'),
- 'service_life' => $this->getValue($row, $headerMap, 'service_life'),
- 'certificate_number' => $this->getValue($row, $headerMap, 'certificate_number'),
- 'certificate_date' => $this->getValue($row, $headerMap, 'certificate_date'),
- 'certificate_issuer' => $this->getValue($row, $headerMap, 'certificate_issuer'),
- 'certificate_type' => $this->getValue($row, $headerMap, 'certificate_type'),
- 'weight' => $this->getNumericValue($row, $headerMap, 'weight', 0),
- 'volume' => $this->getNumericValue($row, $headerMap, 'volume', 0),
- 'places' => $this->getNumericValue($row, $headerMap, 'places', 0),
- 'certificate_id' => $certificateId,
- ];
- if ($existing) {
- $existing->update($productData);
- $this->productIdMapping[$oldId] = $existing->id;
- } else {
- $product = Product::withoutGlobalScopes()->create($productData);
- $this->productIdMapping[$oldId] = $product->id;
- }
- $count++;
- }
- $this->log("Импортировано продуктов: {$count}");
- }
- private function importMafOrders(): void
- {
- $this->log("Импорт заказов МАФ...");
- $filePath = $this->tempDir . '/data/maf_orders.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл maf_orders.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- $oldId = $this->getValue($row, $headerMap, 'id');
- $oldProductId = $this->getValue($row, $headerMap, 'product_id');
- $orderNumber = $this->getStringValue($row, $headerMap, 'order_number', '');
- // Получаем новый product_id
- $newProductId = $this->productIdMapping[$oldProductId] ?? null;
- if (!$newProductId) {
- // Пробуем найти по номенклатуре
- $nomenclature = $this->getValue($row, $headerMap, 'product_nomenclature');
- if ($nomenclature) {
- $product = Product::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('nomenclature_number', $nomenclature)
- ->first();
- $newProductId = $product?->id;
- }
- }
- if (!$newProductId) {
- $this->log("Пропуск MafOrder {$oldId}: продукт не найден", 'WARNING');
- continue;
- }
- // Проверяем существует ли
- $existing = MafOrder::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('product_id', $newProductId)
- ->where('order_number', $orderNumber)
- ->first();
- // Получаем user_id
- $userName = $this->getValue($row, $headerMap, 'user_name');
- $userId = $this->userMapping[$userName] ?? $this->userId;
- $mafOrderData = [
- 'year' => $this->year,
- 'order_number' => $orderNumber,
- 'status' => $this->getValue($row, $headerMap, 'status'),
- 'user_id' => $userId,
- 'product_id' => $newProductId,
- 'quantity' => $this->getNumericValue($row, $headerMap, 'quantity', 0),
- 'in_stock' => $this->getNumericValue($row, $headerMap, 'in_stock', 0),
- ];
- if ($existing) {
- $existing->update($mafOrderData);
- $this->mafOrderIdMapping[$oldId] = $existing->id;
- } else {
- $mafOrder = MafOrder::withoutGlobalScopes()->create($mafOrderData);
- $this->mafOrderIdMapping[$oldId] = $mafOrder->id;
- }
- $count++;
- }
- $this->log("Импортировано заказов МАФ: {$count}");
- }
- private function importOrders(): void
- {
- $this->log("Импорт заказов (площадок)...");
- $filePath = $this->tempDir . '/data/orders.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл orders.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- $oldId = $this->getValue($row, $headerMap, 'id');
- $objectAddress = $this->getStringValue($row, $headerMap, 'object_address', '');
- // Проверяем существует ли
- $existing = Order::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('object_address', $objectAddress)
- ->first();
- // Маппинг справочников
- $districtShortname = $this->getValue($row, $headerMap, 'district_shortname');
- $districtId = $this->districtMapping[$districtShortname] ?? null;
- $areaName = $this->getValue($row, $headerMap, 'area_name');
- $areaId = $this->areaMapping[$areaName] ?? null;
- $userName = $this->getValue($row, $headerMap, 'user_name');
- $userId = $this->userMapping[$userName] ?? $this->userId;
- $objectTypeName = $this->getValue($row, $headerMap, 'object_type_name');
- $objectTypeId = $this->objectTypeMapping[$objectTypeName] ?? null;
- $brigadierName = $this->getValue($row, $headerMap, 'brigadier_name');
- $brigadierId = $this->userMapping[$brigadierName] ?? null;
- $orderStatusName = $this->getValue($row, $headerMap, 'order_status_name');
- $orderStatusId = $this->orderStatusMapping[$orderStatusName] ?? Order::STATUS_NEW;
- $orderData = [
- 'year' => $this->year,
- 'name' => $this->getValue($row, $headerMap, 'name'),
- 'user_id' => $userId,
- 'district_id' => $districtId,
- 'area_id' => $areaId,
- 'object_address' => $objectAddress,
- 'object_type_id' => $objectTypeId,
- 'comment' => $this->getValue($row, $headerMap, 'comment'),
- 'installation_date' => $this->getValue($row, $headerMap, 'installation_date'),
- 'ready_date' => $this->getValue($row, $headerMap, 'ready_date'),
- 'brigadier_id' => $brigadierId,
- 'order_status_id' => $orderStatusId,
- 'tg_group_name' => $this->getValue($row, $headerMap, 'tg_group_name'),
- 'tg_group_link' => $this->getValue($row, $headerMap, 'tg_group_link'),
- 'ready_to_mount' => $this->getValue($row, $headerMap, 'ready_to_mount'),
- 'install_days' => $this->getValue($row, $headerMap, 'install_days'),
- ];
- if ($existing) {
- $existing->update($orderData);
- $this->orderIdMapping[$oldId] = $existing->id;
- } else {
- $order = Order::withoutGlobalScopes()->create($orderData);
- $this->orderIdMapping[$oldId] = $order->id;
- }
- $count++;
- }
- $this->log("Импортировано заказов: {$count}");
- }
- private function importProductsSku(): void
- {
- $this->log("Импорт SKU продуктов...");
- $filePath = $this->tempDir . '/data/products_sku.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл products_sku.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- $oldId = $this->getValue($row, $headerMap, 'id');
- // Маппинг product_id
- $oldProductId = $this->getValue($row, $headerMap, 'product_id');
- $newProductId = $this->productIdMapping[$oldProductId] ?? null;
- if (!$newProductId) {
- // Пробуем найти по номенклатуре
- $nomenclature = $this->getValue($row, $headerMap, 'product_nomenclature');
- if ($nomenclature) {
- $product = Product::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('nomenclature_number', $nomenclature)
- ->first();
- $newProductId = $product?->id;
- }
- }
- if (!$newProductId) {
- $this->log("Пропуск SKU {$oldId}: продукт не найден", 'WARNING');
- continue;
- }
- // Маппинг order_id
- $oldOrderId = $this->getValue($row, $headerMap, 'order_id');
- $newOrderId = $this->orderIdMapping[$oldOrderId] ?? null;
- if (!$newOrderId) {
- // Пробуем найти по адресу
- $orderAddress = $this->getValue($row, $headerMap, 'order_address');
- if ($orderAddress) {
- $order = Order::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('object_address', $orderAddress)
- ->first();
- $newOrderId = $order?->id;
- }
- }
- if (!$newOrderId) {
- $this->log("Пропуск SKU {$oldId}: заказ не найден", 'WARNING');
- continue;
- }
- // Маппинг maf_order_id
- $oldMafOrderId = $this->getValue($row, $headerMap, 'maf_order_id');
- $newMafOrderId = null;
- if ($oldMafOrderId) {
- $newMafOrderId = $this->mafOrderIdMapping[$oldMafOrderId] ?? null;
- if (!$newMafOrderId) {
- // Пробуем найти по номеру заказа
- $mafOrderNumber = $this->getValue($row, $headerMap, 'maf_order_number');
- if ($mafOrderNumber) {
- $mafOrder = MafOrder::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('product_id', $newProductId)
- ->where('order_number', $mafOrderNumber)
- ->first();
- $newMafOrderId = $mafOrder?->id;
- }
- }
- }
- // Импорт паспорта если есть
- $passportId = null;
- $passportPath = $this->getValue($row, $headerMap, 'passport_file');
- if ($passportPath) {
- $passportId = $this->importFile($passportPath);
- }
- $skuData = [
- 'year' => $this->year,
- 'product_id' => $newProductId,
- 'order_id' => $newOrderId,
- 'maf_order_id' => $newMafOrderId,
- 'status' => $this->getValue($row, $headerMap, 'status'),
- 'rfid' => $this->getValue($row, $headerMap, 'rfid'),
- 'factory_number' => $this->getValue($row, $headerMap, 'factory_number'),
- 'manufacture_date' => $this->getValue($row, $headerMap, 'manufacture_date'),
- 'statement_number' => $this->getValue($row, $headerMap, 'statement_number'),
- 'statement_date' => $this->getValue($row, $headerMap, 'statement_date'),
- 'upd_number' => $this->getValue($row, $headerMap, 'upd_number'),
- 'comment' => $this->getValue($row, $headerMap, 'comment'),
- 'passport_id' => $passportId,
- ];
- $sku = ProductSKU::withoutGlobalScopes()->create($skuData);
- $this->productSkuIdMapping[$oldId] = $sku->id;
- $count++;
- }
- $this->log("Импортировано SKU: {$count}");
- }
- private function importReclamations(): void
- {
- $this->log("Импорт рекламаций...");
- $filePath = $this->tempDir . '/data/reclamations.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл reclamations.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- // Лист 1: Reclamations
- $sheet = $spreadsheet->getSheetByName('Reclamations');
- if (!$sheet) {
- $this->log("Лист Reclamations не найден", 'WARNING');
- return;
- }
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- $oldId = $this->getValue($row, $headerMap, 'id');
- // Маппинг order_id
- $oldOrderId = $this->getValue($row, $headerMap, 'order_id');
- $newOrderId = $this->orderIdMapping[$oldOrderId] ?? null;
- if (!$newOrderId) {
- // Пробуем найти по адресу
- $orderAddress = $this->getValue($row, $headerMap, 'order_address');
- if ($orderAddress) {
- $order = Order::withoutGlobalScopes()
- ->where('year', $this->year)
- ->where('object_address', $orderAddress)
- ->first();
- $newOrderId = $order?->id;
- }
- }
- if (!$newOrderId) {
- $this->log("Пропуск рекламации {$oldId}: заказ не найден", 'WARNING');
- continue;
- }
- $userName = $this->getValue($row, $headerMap, 'user_name');
- $userId = $this->userMapping[$userName] ?? $this->userId;
- $brigadierName = $this->getValue($row, $headerMap, 'brigadier_name');
- $brigadierId = $this->userMapping[$brigadierName] ?? null;
- $statusName = $this->getValue($row, $headerMap, 'status_name');
- $statusId = $this->reclamationStatusMapping[$statusName] ?? Reclamation::STATUS_NEW;
- $reclamationData = [
- 'order_id' => $newOrderId,
- 'user_id' => $userId,
- 'status_id' => $statusId,
- 'reason' => $this->getValue($row, $headerMap, 'reason'),
- 'guarantee' => $this->getValue($row, $headerMap, 'guarantee'),
- 'whats_done' => $this->getValue($row, $headerMap, 'whats_done'),
- 'create_date' => $this->getValue($row, $headerMap, 'create_date'),
- 'finish_date' => $this->getValue($row, $headerMap, 'finish_date'),
- 'start_work_date' => $this->getValue($row, $headerMap, 'start_work_date'),
- 'work_days' => $this->getValue($row, $headerMap, 'work_days'),
- 'brigadier_id' => $brigadierId,
- 'comment' => $this->getValue($row, $headerMap, 'comment'),
- ];
- $reclamation = Reclamation::create($reclamationData);
- $this->reclamationIdMapping[$oldId] = $reclamation->id;
- $count++;
- }
- $this->log("Импортировано рекламаций: {$count}");
- // Лист 2: ReclamationDetails
- $this->importReclamationDetails($spreadsheet);
- // Лист 3: ReclamationSKU
- $this->importReclamationSkuRelations($spreadsheet);
- }
- private function importReclamationDetails(Spreadsheet $spreadsheet): void
- {
- $sheet = $spreadsheet->getSheetByName('ReclamationDetails');
- if (!$sheet) {
- return;
- }
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- $oldReclamationId = $this->getValue($row, $headerMap, 'reclamation_id');
- $newReclamationId = $this->reclamationIdMapping[$oldReclamationId] ?? null;
- if (!$newReclamationId) {
- continue;
- }
- ReclamationDetail::create([
- 'reclamation_id' => $newReclamationId,
- 'name' => $this->getStringValue($row, $headerMap, 'name', ''),
- 'quantity' => $this->getNumericValue($row, $headerMap, 'quantity', 0),
- ]);
- $count++;
- }
- $this->log("Импортировано деталей рекламаций: {$count}");
- }
- private function importReclamationSkuRelations(Spreadsheet $spreadsheet): void
- {
- $sheet = $spreadsheet->getSheetByName('ReclamationSKU');
- if (!$sheet) {
- return;
- }
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- $oldReclamationId = $this->getValue($row, $headerMap, 'reclamation_id');
- $oldProductSkuId = $this->getValue($row, $headerMap, 'product_sku_id');
- if (!$oldReclamationId || !$oldProductSkuId) {
- continue;
- }
- $newReclamationId = $this->reclamationIdMapping[$oldReclamationId] ?? null;
- $newProductSkuId = $this->productSkuIdMapping[$oldProductSkuId] ?? null;
- if (!$newReclamationId || !$newProductSkuId) {
- continue;
- }
- DB::table('reclamation_product_sku')->insert([
- 'reclamation_id' => $newReclamationId,
- 'product_sku_id' => $newProductSkuId,
- ]);
- $count++;
- }
- $this->log("Импортировано связей рекламация-SKU: {$count}");
- }
- private function importSchedules(): void
- {
- $this->log("Импорт расписаний...");
- $filePath = $this->tempDir . '/data/schedules.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл schedules.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- $oldOrderId = $this->getValue($row, $headerMap, 'order_id');
- $newOrderId = $this->orderIdMapping[$oldOrderId] ?? null;
- if (!$newOrderId) {
- continue;
- }
- $districtShortname = $this->getValue($row, $headerMap, 'district_shortname');
- $districtId = $this->districtMapping[$districtShortname] ?? null;
- $areaName = $this->getValue($row, $headerMap, 'area_name');
- $areaId = $this->areaMapping[$areaName] ?? null;
- $brigadierName = $this->getValue($row, $headerMap, 'brigadier_name');
- $brigadierId = $this->userMapping[$brigadierName] ?? null;
- Schedule::create([
- 'installation_date' => $this->getValue($row, $headerMap, 'installation_date'),
- 'address_code' => $this->getValue($row, $headerMap, 'address_code'),
- 'manual' => $this->getValue($row, $headerMap, 'manual'),
- 'source' => $this->getValue($row, $headerMap, 'source'),
- 'order_id' => $newOrderId,
- 'district_id' => $districtId,
- 'area_id' => $areaId,
- 'object_address' => $this->getValue($row, $headerMap, 'object_address'),
- 'object_type' => $this->getValue($row, $headerMap, 'object_type'),
- 'mafs' => $this->getValue($row, $headerMap, 'mafs'),
- 'mafs_count' => $this->getNumericValue($row, $headerMap, 'mafs_count', 0),
- 'brigadier_id' => $brigadierId,
- 'comment' => $this->getValue($row, $headerMap, 'comment'),
- ]);
- $count++;
- }
- $this->log("Импортировано расписаний: {$count}");
- }
- private function importContracts(): void
- {
- $this->log("Импорт контрактов...");
- $filePath = $this->tempDir . '/data/contracts.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл contracts.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- Contract::create([
- 'year' => $this->year,
- 'contract_number' => $this->getStringValue($row, $headerMap, 'contract_number', ''),
- 'contract_date' => $this->getValue($row, $headerMap, 'contract_date'),
- ]);
- $count++;
- }
- $this->log("Импортировано контрактов: {$count}");
- }
- private function importTtn(): void
- {
- $this->log("Импорт ТТН...");
- $filePath = $this->tempDir . '/data/ttn.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл ttn.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- $sheet = $spreadsheet->getActiveSheet();
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- if (empty($row[$headerMap['id']])) {
- continue;
- }
- // Импорт файла ТТН если есть
- $fileId = null;
- $ttnFilePath = $this->getValue($row, $headerMap, 'file_path');
- if ($ttnFilePath) {
- $fileId = $this->importFile($ttnFilePath);
- }
- Ttn::create([
- 'year' => $this->year,
- 'ttn_number' => $this->getStringValue($row, $headerMap, 'ttn_number', ''),
- 'ttn_number_suffix' => $this->getStringValue($row, $headerMap, 'ttn_number_suffix', ''),
- 'order_number' => $this->getStringValue($row, $headerMap, 'order_number', ''),
- 'order_date' => $this->getValue($row, $headerMap, 'order_date'),
- 'order_sum' => $this->getNumericValue($row, $headerMap, 'order_sum', 0),
- 'skus' => $this->getValue($row, $headerMap, 'skus'),
- 'file_id' => $fileId,
- ]);
- $count++;
- }
- $this->log("Импортировано ТТН: {$count}");
- }
- private function importPivotTables(): void
- {
- $this->log("Импорт pivot таблиц (связей файлов)...");
- $filePath = $this->tempDir . '/data/pivot_tables.xlsx';
- if (!file_exists($filePath)) {
- $this->log("Файл pivot_tables.xlsx не найден, пропуск", 'WARNING');
- return;
- }
- $spreadsheet = IOFactory::load($filePath);
- // Order photos
- $this->importPivotSheet($spreadsheet, 'order_photo', 'order_id', $this->orderIdMapping);
- // Order documents
- $this->importPivotSheet($spreadsheet, 'order_document', 'order_id', $this->orderIdMapping);
- // Order statements
- $this->importPivotSheet($spreadsheet, 'order_statement', 'order_id', $this->orderIdMapping);
- // Reclamation photos before
- $this->importPivotSheet($spreadsheet, 'reclamation_photo_before', 'reclamation_id', $this->reclamationIdMapping);
- // Reclamation photos after
- $this->importPivotSheet($spreadsheet, 'reclamation_photo_after', 'reclamation_id', $this->reclamationIdMapping);
- // Reclamation documents
- $this->importPivotSheet($spreadsheet, 'reclamation_document', 'reclamation_id', $this->reclamationIdMapping);
- // Reclamation acts
- $this->importPivotSheet($spreadsheet, 'reclamation_act', 'reclamation_id', $this->reclamationIdMapping);
- }
- private function importPivotSheet(
- Spreadsheet $spreadsheet,
- string $sheetName,
- string $foreignKey,
- array $idMapping
- ): void {
- $sheet = $spreadsheet->getSheetByName($sheetName);
- if (!$sheet) {
- return;
- }
- $rows = $sheet->toArray();
- $headers = array_shift($rows);
- $headerMap = array_flip($headers);
- $count = 0;
- foreach ($rows as $row) {
- $oldEntityId = $row[$headerMap[$foreignKey]] ?? null;
- $archivePath = $row[$headerMap['file_archive_path']] ?? null;
- if (!$oldEntityId || !$archivePath) {
- continue;
- }
- $newEntityId = $idMapping[$oldEntityId] ?? null;
- if (!$newEntityId) {
- continue;
- }
- // Импортируем файл
- $fileId = $this->importFile($archivePath);
- if (!$fileId) {
- continue;
- }
- // Вставляем связь
- DB::table($sheetName)->insert([
- $foreignKey => $newEntityId,
- 'file_id' => $fileId,
- ]);
- $count++;
- }
- if ($count > 0) {
- $this->log("Импортировано связей {$sheetName}: {$count}");
- }
- }
- private function importFile(string $archivePath): ?int
- {
- // Проверяем кэш
- if (isset($this->fileIdMapping[$archivePath])) {
- return $this->fileIdMapping[$archivePath];
- }
- $sourcePath = $this->tempDir . '/' . $archivePath;
- if (!file_exists($sourcePath)) {
- $this->log("Файл не найден: {$archivePath}", 'WARNING');
- return null;
- }
- // Определяем путь для сохранения
- $pathParts = explode('/', $archivePath);
- array_shift($pathParts); // Убираем 'files'
- $relativePath = implode('/', $pathParts);
- $fileName = basename($archivePath);
- $targetPath = dirname($relativePath) . '/' . $fileName;
- // Сохраняем файл
- $content = file_get_contents($sourcePath);
- Storage::disk('public')->put($targetPath, $content);
- // Создаем запись в БД
- $originalName = preg_replace('/^\d+_/', '', $fileName); // Убираем ID из имени
- $mimeType = mime_content_type($sourcePath) ?: 'application/octet-stream';
- $file = File::create([
- 'user_id' => $this->userId,
- 'original_name' => $originalName,
- 'mime_type' => $mimeType,
- 'path' => $targetPath,
- 'link' => url('/storage/' . $targetPath),
- ]);
- $this->fileIdMapping[$archivePath] = $file->id;
- return $file->id;
- }
- private function cleanupTempDirectory(): void
- {
- if (is_dir($this->tempDir)) {
- $this->deleteDirectory($this->tempDir);
- }
- }
- private function deleteDirectory(string $dir): void
- {
- if (!is_dir($dir)) {
- return;
- }
- $files = array_diff(scandir($dir), ['.', '..']);
- foreach ($files as $file) {
- $path = $dir . '/' . $file;
- is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
- }
- rmdir($dir);
- }
- private function log(string $message, string $level = 'INFO'): void
- {
- $this->logs[] = [
- 'level' => $level,
- 'message' => $message,
- 'timestamp' => now()->toIso8601String(),
- ];
- if ($level === 'ERROR') {
- Log::error("ImportYearDataService: {$message}");
- } else {
- Log::info("ImportYearDataService: {$message}");
- }
- }
- public function getLogs(): array
- {
- return $this->logs;
- }
- }
|