Автоматическое отслеживание изменений остатков товаров в WooCommerce

Почему важно отслеживать изменения остатков в WooCommerce

Для интернет-магазинов на WooCommerce контроль остатков товара — ключевой элемент управления запасами. Быстрая реакция на изменение запасов помогает избежать ситуаций с переизбытком или дефицитом товара, а также дает возможность оперативно информировать клиентов и складских сотрудников.

Диагностика проблемы: как понять, что автоматическое отслеживание не настроено

По умолчанию WooCommerce не ведет историю изменений запасов товаров. Это значит, что если вы хотите знать, когда и кем был изменён остаток, а также видеть динамику, нужно внедрять кастомные решения или использовать плагины.

Проверить отсутствие отслеживания можно так:

  • Измените остаток товара в админке и проверьте, появляется ли лог с изменениями;
  • Проверьте базу данных на наличие таблиц или записей, отвечающих за историю остатков;
  • Оцените, есть ли уведомления о снижении остатков.

Пошаговое решение: реализация автоматического логирования изменений запасов

1. Создаем таблицу для хранения логов изменений запасов

function create_stock_log_table() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'stock_change_log';
    $charset_collate = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE IF NOT EXISTS $table_name (
        id BIGINT(20) NOT NULL AUTO_INCREMENT,
        product_id BIGINT(20) NOT NULL,
        old_stock INT NOT NULL,
        new_stock INT NOT NULL,
        changed_by BIGINT(20) DEFAULT NULL,
        changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY  (id)
    ) $charset_collate;";

    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}
register_activation_hook(__FILE__, 'create_stock_log_table');

2. Отслеживаем изменения запасов при сохранении товара

Используем хук woocommerce_update_product для фиксации изменений:

add_action('woocommerce_update_product', 'log_stock_changes', 10, 1);
function log_stock_changes($product_id) {
    $product = wc_get_product($product_id);
    if (!$product) return;

    global $wpdb;
    $table_name = $wpdb->prefix . 'stock_change_log';

    // Получаем старый остаток из мета, если есть
    $old_stock = get_post_meta($product_id, '_previous_stock', true);
    $new_stock = $product->get_stock_quantity();

    // Если старый остаток не установлен, считаем его равным новому
    if ($old_stock === '') {
        update_post_meta($product_id, '_previous_stock', $new_stock);
        return;
    }

    if (intval($old_stock) !== intval($new_stock)) {
        $user_id = get_current_user_id() ?: null;

        $wpdb->insert(
            $table_name,
            [
                'product_id' => $product_id,
                'old_stock' => intval($old_stock),
                'new_stock' => intval($new_stock),
                'changed_by' => $user_id,
                'changed_at' => current_time('mysql', 1)
            ],
            ['%d', '%d', '%d', '%d', '%s']
        );

        // Обновляем мета для следующего сравнения
        update_post_meta($product_id, '_previous_stock', $new_stock);
    }
}

3. Выводим историю изменений в админке товара

Для удобства добавим метабокс с логами:

add_action('add_meta_boxes', function() {
    add_meta_box('stock_change_log_box', 'История изменений остатков', 'render_stock_change_log_box', 'product', 'normal', 'default');
});

function render_stock_change_log_box($post) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'stock_change_log';
    $product_id = $post->ID;

    $logs = $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM $table_name WHERE product_id = %d ORDER BY changed_at DESC LIMIT 10",
        $product_id
    ));

    if (!$logs) {
        echo '<p>История изменений отсутствует.</p>';
        return;
    }

    echo '<table style="width:100%; border-collapse: collapse;">';
    echo '<thead><tr><th style="border:1px solid #ccc; padding:5px;">Дата и время</th><th style="border:1px solid #ccc; padding:5px;">Старый остаток</th><th style="border:1px solid #ccc; padding:5px;">Новый остаток</th><th style="border:1px solid #ccc; padding:5px;">Изменил</th></tr></thead><tbody>';
    foreach ($logs as $log) {
        $user_info = $log->changed_by ? get_userdata($log->changed_by) : null;
        $user_name = $user_info ? esc_html($user_info->user_login) : 'Система';

        echo '<tr>' .
             '<td style="border:1px solid #ccc; padding:5px;">' . esc_html($log->changed_at) . '</td>' .
             '<td style="border:1px solid #ccc; padding:5px;">' . intval($log->old_stock) . '</td>' .
             '<td style="border:1px solid #ccc; padding:5px;">' . intval($log->new_stock) . '</td>' .
             '<td style="border:1px solid #ccc; padding:5px;">' . $user_name . '</td>' .
             '</tr>';
    }
    echo '</tbody></table>';
}

Проверка результата после внедрения

  • Отредактируйте запас товара в админке WooCommerce;
  • Обновите страницу редактирования товара и найдите метабокс «История изменений остатков»;
  • Проверьте, что добавилась новая запись с правильными значениями старого и нового остатка, а также указано имя пользователя;
  • Повторите изменения несколько раз, чтобы убедиться в корректности логирования.

Частые ошибки и как их исправить

  • Отсутствие таблицы в базе данных: проверьте, что функция создания таблицы вызвана при активации плагина. Можно вручную вызвать create_stock_log_table() для теста.
  • Нет записей в логе: убедитесь, что мета _previous_stock корректно обновляется и что хук woocommerce_update_product срабатывает. Для проверки можно добавить error_log() внутри функции.
  • Неправильное отображение пользователя: проверьте, что ID пользователя существует и что get_userdata() возвращает объект. Для автоматических изменений (например, через импорт) может быть null.

Практические советы по безопасности и производительности

  • Добавьте проверку прав пользователя перед записью лога, чтобы избежать ненужных операций при автоматических процессах.
  • Регулярно очищайте устаревшие записи в таблице логов, чтобы не перегружать базу (например, оставлять данные за последние 6 месяцев).
  • Для больших магазинов используйте асинхронное логирование через WP-Cron или внешние сервисы, чтобы не замедлять сохранение товара.

Таблица сравнения вариантов реализации

МетодПреимуществаНедостаткиКомпромисс
Кастомный код (как в статье) Полный контроль, без плагинов, легкая кастомизация Требует времени на разработку и тестирование Подходит для разработчиков, которые хотят гибкость
Плагин стороннего разработчика Быстрое развертывание, поддержка и обновления Может содержать лишний функционал, нагрузка на сайт Подходит при нехватке времени и ресурсов
Встроенные отчеты WooCommerce (ограниченные) Нет дополнительной нагрузки, простота Нет детальной истории изменений запасов Подходит для базового мониторинга
Как создать расписание задач в WordPress с помощью WP-Cron и реального cron
30.03.2026
Как создать собственный тип записи (Custom Post Type) в WordPress с примерами кода
03.01.2026
Как создать динамические поля в WordPress без плагинов
26.03.2026
Автоматический импорт продуктов в WordPress: настройка и примеры
17.02.2026
Как избежать проблем с хуками в WordPress: практические решения
17.03.2026