| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192 |
- <?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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->withTrashed()->where('year', $this->year)->pluck('id');
- $productIds = Product::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->withTrashed()->where('year', $this->year)->pluck('id');
- $productSkuIds = ProductSKU::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->withTrashed()->where('year', $this->year)->forceDelete();
- // Заказы
- Order::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->withTrashed()->where('year', $this->year)->forceDelete();
- // МАФ заказы
- MafOrder::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->withTrashed()->where('year', $this->year)->forceDelete();
- // Продукты
- Product::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->withTrashed()
- ->whereIn('id', $productIds)
- ->update(['certificate_id' => null]);
- Product::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->withTrashed()
- ->whereIn('id', $productIds)
- ->whereNotNull('certificate_id')
- ->pluck('certificate_id')
- );
- $fileIds = $fileIds->merge(
- ProductSKU::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->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 (is_string($value)) {
- $value = trim($value);
- }
- 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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->where('year', $this->year)
- ->where('nomenclature_number', $nomenclature)
- ->first();
- $newProductId = $product?->id;
- }
- }
- if (!$newProductId) {
- $this->log("Пропуск MafOrder {$oldId}: продукт не найден", 'WARNING');
- continue;
- }
- // Проверяем существует ли
- $existing = MafOrder::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)->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::withoutGlobalScope(\App\Models\Scopes\YearScope::class)
- ->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'),
- 'departure_date' => $this->getValue($row, $headerMap, 'departure_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;
- }
- }
|