Реалізація прев'ю імпорту товарів перед застосуванням

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

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

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1361
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1253
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    958
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1190
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    931
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    949

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

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

Без прев'ю помилка в маппінгу колонок або невірний формат дат миттєво псує каталог. Стандартна валідація часто пропускає логічні помилки: ціна 0 рублів або від'ємний залишок. 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+ років в інтеграції систем імпорту для інтернет-магазинів.

Розробка інтернет-магазинів

Ми знаємо: інтернет-магазин — це не просто «сайт з кошиком». Це розподілена система управління товарами, інвентарем, замовленнями, платежами, доставкою, поверненнями та комунікацією з клієнтами. Кожен блок має нетривіальну реалізацію, і більшість проблем в e-commerce виникає на стику цих підсистем. Наш досвід — понад 50 реалізованих проектів — показує, що правильна архітектура на старті економить до 40% бюджету на доробках.

Чому продуктивність каталогу деградує при зростанні SKU?

Найчастіша технічна проблема e-commerce — деградація сторінок категорій при збільшенні асортименту. Сторінка працює добре на 500 товарах і починає гальмувати на 10 000. Причини майже завжди одні й ті ж.

N+1 на атрибутах. Завантажуєте список товарів — 50 елементів. Для кожного потрібні категорія, головне фото, ціна з урахуванням знижки, наявність на складі, рейтинг. Без правильного eager loading це 250+ запитів на сторінку. В Laravel вирішується через with(['category', 'mainImage', 'currentPrice', 'stockStatus']) та withAvg('reviews', 'rating'). Але варто з'явитися персональним цінам (b2b) або складським залишкам по регіонах — і одного with() недостатньо. Потрібні Query Object або виділений ReadModel.

Фасетна фільтрація без індексів. Фільтр за кольором + розміром + брендом + діапазоном цін на таблиці в 500 000 записів без складених індексів — це seq scan при кожному запиті. PostgreSQL з правильними індексами тримає фасетну фільтрацію до кількох мільйонів товарів. Для великих каталогів — Elasticsearch або OpenSearch з агрегаціями: вони рахують кількість товарів на фільтр (facet counts) значно швидше.

Пагінація через OFFSET. LIMIT 50 OFFSET 10000 на великій таблиці — погана ідея: PostgreSQL все одно читає перші 10 050 рядків. Keyset pagination (cursor-based) через WHERE id > $last_id ORDER BY id LIMIT 50 працює за постійний час незалежно від сторінки. Як зазначено в документації PostgreSQL, cursor-based pagination гарантує O(log n) при будь-якому зміщенні, що особливо важливо для каталогів із сотнями тисяч товарів.

Конкретний кейс: каталог будівельних матеріалів, 180 000 SKU, фасетна фільтрація по 12 атрибутах. Після переходу з OFFSET-пагінації на курсорну та додавання partial index по (category_id, is_active, price) час відповіді сторінки каталогу знизився з 4,2 с до 280 мс. Економія на серверних ресурсах склала близько 30 000 ₽ на місяць. В іншому проекті (ювелірний маркетплейс) впровадження агрегацій через Elasticsearch скоротило час фільтрації з 8 до 200 мс та заощадило 50 000 ₽ на місяць на інфраструктурі — ще один приклад, як правильна архітектура знижує TCO.

Що таке race condition у кошику та як його уникнути?

Checkout — місце, де гроші або потрапляють на рахунок, або ні. Технічні проблеми тут коштують дорого.

Race condition при резервуванні товару. Два покупці одночасно додають останній екземпляр у кошик і обоє натискають «Оплатити». Без песимістичного блокування або атомарного UPDATE з перевіркою залишку обидва замовлення проходять, інвентар іде в мінус. В PostgreSQL:

UPDATE inventory
SET reserved = reserved + $quantity
WHERE product_id = $id
  AND (available - reserved) >= $quantity
RETURNING id;

Якщо RETURNING повернув 0 рядків — товару немає, показуємо помилку до списання грошей.

Ідемпотентність платіжних вебхуків. payment.succeeded від Stripe або ЮКассы може прийти двічі через мережеві збої або retry-логіку на стороні шлюзу. Без перевірки WHERE NOT EXISTS (SELECT 1 FROM processed_events WHERE event_id = $id) — дублювання замовлення або подвійне зарахування. Webhook idempotency — обов'язковий патерн для будь-якого платіжного інтегратора. Ми включаємо тест на ідемпотентність у стандартний чек-лист кожного проекту.

Checkout у кілька кроків. Multi-step checkout (адреса → доставка → оплата → підтвердження) vs single-page checkout. Дослідження показують, що single-page з прогрес-індикатором конвертує на 15–20% краще на мобільних. Стан між кроками — або localStorage + server-side сесія, або повністю server-side з проміжним збереженням. Ми гарантуємо, що кожне замовлення проходить аудит на ідемпотентність та блокування — це входить у стандартний чек-лист тестування.

Чому варто уникати CommerceML для великих каталогів CommerceML через HTTP — класична інтеграція 1С з сайтом. 1С вивантажує XML за розкладом, сайт імпортує. Для невеликих каталогів (до 5 000 SKU) це прийнятно, але при зростанні до 50 000+ SKU виникають проблеми: файл вивантаження 200 МБ кожні 30 хвилин, парсинг блокує чергу, імпорт займає 10–15 хвилин, в цей час на сайті старі ціни. Рішення — інкрементальне вивантаження (тільки зміни) та фонова обробка через Laravel Queue з кількома workers. Для високонавантажених систем ми рекомендуємо REST API або проміжну шину (RabbitMQ).

Як інтегрувати 1С з інтернет-магазином?

1С — окрема глава. Три поширених способи інтеграції:

  1. CommerceML через HTTP — 1С вивантажує XML за розкладом, сайт імпортує. Працює для невеликих каталогів, є затримка синхронізації.
  2. REST API / OData від 1С — двостороння синхронізація в реальному часі. Вимагає налаштування на стороні 1С, примхлива до версій конфігурацій.
  3. Проміжна шина (RabbitMQ / Kafka) — 1С публікує події, сайт підписується. Найнадійніший підхід для високонавантажених систем, але найдорожчий у розробці.

Служби доставки — СДЕК, Boxberry, Пошта Росії, DHL: всі надають REST API для розрахунку вартості та створення накладних. Агрегатори (Shiptor, Shipnow) дозволяють працювати з кількома службами через єдиний API.

Платіжні шлюзи

Шлюз Особливості інтеграції
Stripe Webhook-based, відмінна документація, Stripe Elements для PCI DSS
ЮКасса Популярний в РФ, підтримка ФЗ-54 (фіскалізація)
ЄРИП Білоруська система, SOAP API, специфічна документація
Tinkoff Acquiring REST API, 3D Secure 2.0, webhook-повідомлення

Для кожного шлюзу обов'язкова перевірка підпису вебхука — без цього будь-хто може відправити фейковий payment.succeeded.

CMS vs власна розробка

WooCommerce — виправданий для магазинів до ~5 000 SKU з типовою бізнес-логікою. Швидкий старт, величезна екосистема плагінів. Проблеми починаються при нестандартних цінових правилах, складних варіантах товарів або навантаженні від 10 000+ замовлень на місяць. Економія на ліцензії WooCommerce (безкоштовно) обертається витратами на плагіни та хостинг; для каталогу 50 000 SKU місячна вартість підтримки може перевищити 100 000 ₽.

OpenCart, Prestashop — аналогічна історія. Хороші для старту, обмежені при зростанні.

Власна розробка на Laravel — для:

  • Нестандартної бізнес-логіки (підписки, оренда, b2b-прайси, конфігуратор);
  • Високих вимог до продуктивності;
  • Складних інтеграцій (кілька складів, ERP, маркетплейси);
  • Унікального UX checkout.

Як ми розробляємо інтернет-магазин: покроковий процес

  1. Аналітика та проектування. Збираємо вимоги, уточнюємо бізнес-процеси, моделюємо доменну логіку. На виході — технічне завдання та архітектурна схема.
  2. Backend та API. Реалізуємо ядро (товари, кошик, замовлення), інтеграції з 1С/складами/платіжками. Використовуємо Laravel 11 з Repository pattern, чергами для асинхронних операцій.
  3. Frontend та checkout. Налаштовуємо React 18 / Next.js 14 з оптимізованим рендерингом (SSR/SSG для каталогу), єдиний single-page checkout.
  4. Тестування. Перевіряємо race condition, ідемпотентність вебхуків, навантажувальне тестування (k6), security-аудит.
  5. Деплой та моніторинг. Розгортаємо на Vercel / Docker / виділеному сервері, підключаємо Sentry та Uptime.

SEO для e-commerce

Canonical та дублювання. Фасетна фільтрація генерує тисячі URL (?color=red&size=M&sort=price). Без canonical або noindex на фільтрованих сторінках краулінговий бюджет витрачається на дублі, а основні сторінки індексуються гірше.

Structured data. Product schema з offers, aggregateRating, availability — це rich snippets у видачі: зірочки рейтингу, ціна, наявність. Впливає на CTR.

Core Web Vitals на сторінках товарів. Hero image товару — це LCP element. fetchpriority="high" на першому зображенні, правильні srcset з WebP, width та height атрибути для запобігання CLS.

Що входить у результат роботи

Після завершення проекту ви отримуєте:

  • Вихідний код та повну документацію (API, архітектура, інфраструктура);
  • Доступи до репозиторію, хостингу, моніторингу (Sentry, Uptime);
  • Навчання команди роботі з адмін-панеллю та кастомізаціями;
  • Гарантійну підтримку 3 місяці (виправлення помилок, консультації);
  • Детальний звіт по навантажувальному тестуванню та оптимізації.

Орієнтири за термінами

Тип магазину Термін
Малий (до 1 000 SKU, типова логіка) 8–12 тижнів
Середній (до 50 000 SKU, інтеграція 1С) 14–20 тижнів
Великий (100 000+ SKU, ERP, маркетплейси) 24–40 тижнів

Вартість розраховується після аналізу вимог: кількість інтеграцій, складність ціноутворення, обсяг каталогу та унікальність UX — основні фактори. Оцінимо ваш проект безкоштовно — замовте консультацію.

Чек-лист перед запуском

  • Race condition при оплаті останнього товару — покритий тестом
  • Ідемпотентність вебхуків платіжного шлюзу
  • Rate limiting на ендпоїнтах кошика та checkout
  • Canonical на фільтрованих сторінках каталогу
  • Фіскалізація чеків (ФЗ-54 для РФ або аналог)
  • Стрес-тест checkout під навантаженням (k6 або Locust)
  • Моніторинг помилок (Sentry) та алерти на payment errors
  • Backup бази даних з перевіреним restore-процесом

Гарантуємо — кожен проект проходить цей чек-лист перед релізом. Зв'яжіться з нами — підберемо оптимальну архітектуру під ваш бюджет та терміни.