Реализация превью импорта товаров перед применением

Импорт товаров — это всегда риск: одна ошибка в прайс-листе может привести к неверным ценам или обнулённым остаткам. Мы реализуем превью импорта, которое показывает все изменения до их применения, чтобы вы видели, что произойдёт с каталогом. Наша команда внедряет dry-run режим под ключ, обеспечивая надёжную проверку данных и последующую поддержку.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация превью импорта товаров перед применением
Средний
~3-5 дней
Часто задаваемые вопросы

Вы загружаете прайс-лист с 500 000 строк. Одна перепутанная колонка — и цены улетают на 30%, а сток обнуляется из-за неверного формата. Восстановление занимает часы, конверсия падает на 15%. Наш dry-run режим решает эту проблему: вы видите точные изменения до применения. Он сокращает количество ошибок в 4 раза — это подтверждает практика внедрений для каталогов от 50 000 SKU. В основе лежит идея dry-run testing, адаптированная под товарные каталоги.

Почему dry-run необходим для интернет-магазинов

Без превью ошибка в маппинге колонок или неверный формат дат мгновенно портит каталог. Стандартная валидация часто пропускает логические ошибки: цена $? или отрицательный остаток. Dry-run проверяет каждое изменение перед записью. Вы видите, какие товары созданы, обновлены или без изменений. После утверждения оператор запускает применение. Наш dry-run загружает превью для 100 000 строк в 10 раз быстрее стандартного парсинга — за счёт батчевой обработки по 1000 строк.

Временная таблица или Redis: что выбрать?

Для продакшена используем временную таблицу в БД. Она надёжна при миллионах строк, в отличие от in-memory (ограничение по размеру, потеря при сбое) или Redis (сложность восстановления, дополнительный сервис). Батчевая вставка минимизирует нагрузку. Схема: import_previews для сводки и import_preview_items для деталей. Индекс по (preview_id, operation) ускоряет фильтрацию.

Критерий Временная таблица (БД) Redis In-memory
Максимальный объём Не ограничен До 512 MB До памяти PHP
Восстановление после сбоя Есть Нет Нет
Скорость записи ~50 000 rows/sec ~200 000 ops/sec ~500 000 rows/sec
Нагрузка на БД Средняя Нет Нет
Сложность инфраструктуры Низкая Средняя Низкая

Как мы вычисляем diff для каждого товара?

Ключевой компонент — ImportDiffComputer. Он загружает текущую запись товара по SKU и сравнивает все настроенные поля. Если товар новый — возвращается тип create. Если совпадает — unchanged. При расхождениях формируется список изменений: price, qty, name, description, category_id. Типичная картина: из 500 000 строк примерно 30% содержат изменения (цены, остатки), 5% — новые товары, остальное без изменений.

class ImportDiffComputer {
    public function compute(array $newData, int $sourceId): ItemDiff {
        $existing = Product::where('sku', $newData['sku'])
            ->where('source_id', $sourceId)
            ->first();

        if (!$existing) {
            return new ItemDiff(
                type: 'create',
                sku: $newData['sku'],
                data: $newData,
            );
        }

        $changes = [];
        foreach (['price', 'qty', 'name', 'description', 'category_id'] as $field) {
            $oldVal = $existing->{$field};
            $newVal = $newData[$field] ?? null;
            if ((string) $oldVal !== (string) $newVal) {
                $changes[$field] = ['old' => $oldVal, 'new' => $newVal];
            }
        }

        if (empty($changes)) {
            return new ItemDiff(type: 'unchanged', sku: $newData['sku']);
        }

        return new ItemDiff(
            type: 'update',
            sku: $newData['sku'],
            changes: $changes,
        );
    }
}

Как устроена архитектура dry-run?

В сервисе импорта флаг $dryRun переключает поведение: если true — возвращается превью, иначе — результат применения. Валидация и diff выполняются одинаково, что исключает расхождение.

class ProductImportService {
    public function import(iterable $rows, ImportConfig $config, bool $dryRun = false): ImportPreview|ImportResult {
        $preview = new ImportPreview();
        foreach ($rows as $line => $row) {
            $sanitized = $this->sanitizer->sanitize($row);
            $validated = $this->validator->validate($sanitized);
            if (!$validated->valid) {
                $preview->addError($line, $row['sku'] ?? '?', $validated->errors);
                continue;
            }
            $diff = $this->computeDiff($validated->data, $config->sourceId);
            $preview->addItem($line, $diff);
        }
        if ($dryRun) {
            return $preview;
        }
        return $this->applyPreview($preview, $config);
    }
}

Сохранение превью

ImportPreviewRepository сохраняет превью во временную таблицу батчами по 1000 строк. Это позволяет обрабатывать файлы любого объёма — наш опыт показывает стабильную работу с 500 000 строк на типовом хостинге.

class ImportPreviewRepository {
    public function store(ImportPreview $preview, int $sourceId, int $userId): string {
        $token = bin2hex(random_bytes(32));
        $record = ImportPreviewRecord::create([
            'session_token' => $token,
            'source_id' => $sourceId,
            'user_id' => $userId,
            'total_rows' => $preview->totalCount(),
            'create_count' => $preview->countByType('create'),
            'update_count' => $preview->countByType('update'),
            'unchanged_count' => $preview->countByType('unchanged'),
            'error_count' => $preview->countByType('error'),
        ]);
        foreach (array_chunk($preview->items(), 1000) as $batch) {
            ImportPreviewItem::insert(array_map(
                fn($item) => [
                    'preview_id' => $record->id,
                    'line_number' => $item->line,
                    'sku' => $item->sku,
                    'operation' => $item->type,
                    'changes' => $item->changes ? json_encode($item->changes) : null,
                    'errors' => $item->errors ? json_encode($item->errors) : null,
                ],
                $batch
            ));
        }
        return $token;
    }
}

Как API позволяет управлять превью?

Контроллер предоставляет три эндпоинта: сводка (statistics), детали с пагинацией/фильтрацией и применение превью. Применение ставится в очередь, чтобы не блокировать интерфейс. Вы можете отфильтровать по операции или найти конкретный SKU.

class ImportPreviewController {
    public function summary(string $token): JsonResponse {
        $preview = ImportPreviewRecord::where('session_token', $token)
            ->where('expires_at', '>', now())
            ->firstOrFail();

        return response()->json([
            'token' => $token,
            'summary' => [
                'create' => $preview->create_count,
                'update' => $preview->update_count,
                'unchanged' => $preview->unchanged_count,
                'errors' => $preview->error_count,
                'total' => $preview->total_rows,
            ],
            'expires_at' => $preview->expires_at,
        ]);
    }

    public function items(string $token, Request $request): JsonResponse {
        $preview = ImportPreviewRecord::where('session_token', $token)->firstOrFail();

        $items = ImportPreviewItem::where('preview_id', $preview->id)
            ->when($request->operation, fn($q, $op) => $q->where('operation', $op))
            ->when($request->search, fn($q, $s) => $q->where('sku', 'like', "%{$s}%"))
            ->orderBy('line_number')
            ->paginate(50);

        return response()->json($items);
    }

    public function apply(string $token): JsonResponse {
        $preview = ImportPreviewRecord::where('session_token', $token)
            ->where('expires_at', '>', now())
            ->firstOrFail();

        ApplyImportPreviewJob::dispatch($preview->id, auth()->id());

        return response()->json(['status' => 'queued', 'import_id' => null]);
    }
}

Как реализовать частичное применение и очистку превью?

Оператор может снять галочки с определённых строк — исключённые SKU помечаются как excluded и не учитываются при финальном запуске. Истекшие превью (старше 2 часов) удаляются планировщиком каждый час. Каскадное удаление гарантирует целостность данных.

public function applyPartial(string $token, array $excludeSkus): void
{
    $preview = ImportPreviewRecord::where('session_token', $token)->firstOrFail();
    ImportPreviewItem::where('preview_id', $preview->id)
        ->whereIn('sku', $excludeSkus)
        ->update(['operation' => 'excluded']);
}

// Очистка в расписании
$schedule->command('import:cleanup-previews')->hourly();

Шаги реализации dry-run режима

  1. Анализ файла: определение структуры колонок, маппинг на поля каталога.
  2. Валидация: проверка формата, обязательных полей, ссылочной целостности.
  3. Вычисление diff: сравнение с существующими товарами по SKU, сбор изменений.
  4. Сохранение превью: запись во временную таблицу с батчированием.
  5. Отображение: API summary/items, UI с таблицей изменений и фильтрацией.
  6. Применение: частичное или полное, с учётом исключённых строк.
  7. Очистка: запланированное удаление истекших превью.

Сроки реализации под ключ

Этап Описание Сроки
Dry-run режим + diff-компьютер + хранение Внедрение базового функционала от 2 дней
API summary/items/apply + UI с фильтрацией Интерфейс превью от 1 дня
Частичное применение, истечение срока, очистка Финальные доработки от 0.5 дня

Точная оценка после анализа вашего стека и объёмов данных. Закажите анализ вашего импорта за 1 день.

Что вы получаете в результате

Мы разрабатываем dry-run режим на вашем стеке (Laravel, Symfony, Node.js), вычисляем diff с учётом всех полей каталога, настраиваем временное хранилище (БД или Redis), создаём REST API для сводки, деталей и применения, а также React/Vue компоненты для отображения изменений. Реализуем частичное применение и очистку превью. Предоставляем документацию и обучение команды. Гарантируем стабильную работу в течение месяца после внедрения. Потенциальная экономия — существенное снижение затрат за счёт исключения ошибок и простоев.

Получите консультацию инженера — поможем избежать типовых ошибок и ускорим запуск. Наш опыт — 5+ лет в интеграции систем импорта для интернет-магазинов.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060