Реалізація автоматичного оновлення залишків товарів від постачальників

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

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація автоматичного оновлення залишків товарів від постачальників
Середня
~3-5 робочих днів
Часті питання

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

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

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

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

Реалізація автоматичного оновлення залишків товарів від постачальників

Залишки — найбільш волатильні дані в інтернет-магазині. Покупець оформляє замовлення, а товару немає на складі. Або навпаки: товар є, але він приховано з нульовим залишком у застарілому фіді. Автоматичне оновлення stock-даних від постачальників розв'язує цю проблему системно, а не латками.

Що саме потрібно синхронізувати

Залишок — це не тільки кількість. Повна картина включає:

  • qty — кількість одиниць на складі постачальника
  • warehouse — на якому складі (особливо важливо при регіональних складах)
  • available_date — дата очікуваного прибуття, якщо зараз 0
  • reserved — зарезервовано під чужі замовлення
  • status — знято з продажу, під замовлення, тільки гуртом

Мінімальний набір для більшості магазинів: sku, qty, warehouse_id.

Джерела та формати залишків

CSV/Excel по розписанню

Найпоширеніший варіант — постачальник кладе оновлений файл на FTP раз на годину:

class FtpStockSource implements StockSourceInterface
{
    public function fetch(): array
    {
        $ftp = ftp_connect($this->host);
        ftp_login($ftp, $this->user, $this->pass);
        $tmpFile = tempnam(sys_get_temp_dir(), 'stock_');
        ftp_get($ftp, $tmpFile, $this->remotePath, FTP_BINARY);
        ftp_close($ftp);

        $reader = \PhpOffice\PhpSpreadsheet\IOFactory::load($tmpFile);
        $rows   = $reader->getActiveSheet()->toArray();
        unlink($tmpFile);

        $stocks = [];
        foreach (array_slice($rows, 1) as $row) { // пропустити заголовок
            $stocks[] = [
                'sku' => (string) $row[0],
                'qty' => (int)    $row[2],
            ];
        }
        return $stocks;
    }
}

REST API з delta-оновленнями

Сучасні постачальники надають endpoint для інкрементальних змін — тільки ті SKU, залишки яких змінилися з останнього запиту:

$response = $client->get('/stocks/delta', [
    'query' => ['since' => $this->lastSyncAt->toIso8601String()],
    'headers' => ['X-API-Key' => $this->apiKey],
]);
// Повертає тільки змінені позиції — економить трафік і час обробки

Webhook від постачальника

Якщо постачальник умить пушити зміни:

// routes/api.php
Route::post('/webhooks/stock/{source}', StockWebhookController::class)
    ->middleware('webhook.signature');
class StockWebhookController
{
    public function __invoke(Request $request, string $source): JsonResponse
    {
        $payload = $request->validated();
        ProcessStockWebhookJob::dispatch($source, $payload);
        return response()->json(['status' => 'queued']);
    }
}

Webhook-ендпоінт повинен відповідати за < 200 мс та відразу ставити завдання в чергу.

Логіка застосування залишків

class StockUpdater
{
    public function apply(array $stocks, int $sourceId): StockUpdateResult
    {
        $updated = $skipped = 0;

        // Bulk upsert через один запит замість N окремих UPDATE
        $chunks = array_chunk($stocks, 500);
        foreach ($chunks as $chunk) {
            $rows = [];
            foreach ($chunk as $item) {
                $productId = $this->skuMap[$item['sku']] ?? null;
                if (!$productId) { $skipped++; continue; }

                $rows[] = [
                    'product_id' => $productId,
                    'source_id'  => $sourceId,
                    'qty'        => max(0, $item['qty']),
                    'updated_at' => now(),
                ];
                $updated++;
            }

            if ($rows) {
                DB::table('product_stocks')->upsert(
                    $rows,
                    ['product_id', 'source_id'],
                    ['qty', 'updated_at']
                );
            }
        }

        return new StockUpdateResult($updated, $skipped);
    }
}

Метод upsert у Laravel підтримується від версії 8 та працює через INSERT ... ON CONFLICT DO UPDATE у PostgreSQL.

Агрегація залишків з кількох складів

Якщо постачальник веде кілька складів, підсумковий залишок на сайті — сума по всім або вибірково:

-- Уявлення для вітрини: сумарний доступний залишок
CREATE VIEW product_available_stock AS
SELECT
    product_id,
    SUM(qty) AS total_qty,
    MAX(updated_at) AS last_synced_at
FROM product_stocks
WHERE source_active = true
GROUP BY product_id;

Якщо один склад «Москва» вважається пріоритетним — можна зберігати warehouse_priority та брати максимальний пріоритет при qty > 0.

Автоматичне управління видимістю

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

class StockVisibilityObserver
{
    public function updated(ProductStock $stock): void
    {
        $totalQty = ProductStock::where('product_id', $stock->product_id)->sum('qty');

        Product::where('id', $stock->product_id)->update([
            'in_stock'    => $totalQty > 0,
            'stock_count' => $totalQty,
        ]);
    }
}

Замість Observer можна використовувати database trigger — швидше, але складніше тестувати.

Частота оновлень та навантаження

Тип магазину Рекомендована частота Метод
До 5 000 SKU, 1 постачальник Кожні 30 хв CSV/FTP по розписанню
5 000–50 000 SKU Кожні 15 хв API з delta
Більш ніж 50 000 SKU Real-time Webhook + чергу
Маркетплейс Постійно Чергу з дедублікацією

При частому оновленні важливо не перегрузити БД. Bulk upsert по 500 рядків за один запит — оптимальний розмір чанку для PostgreSQL.

Тривалість реалізації

  • Одне джерело (CSV/FTP), scheduler, bulk upsert, пересчет видимості — 2 дні
  • Кілька джерел + агрегація по складам — +1–2 дні
  • Webhook-прийом + панель моніторингу синхронізації — +2 дні

Обробка помилок синхронізації

Якщо постачальник не відповів або повернув невалідний файл — не обнулюємо залишки. Використовуємо TTL: якщо дані від джерела старше max_age (наприклад, 4 годин), помічаємо товари з цього джерела як «дані застаріли» та показуємо попередження в админці, але не трогаємо qty на вітрині.