Проблема: необходимость вести раздельный учет остатков для вариаций без использования стандартных вариаций WooCommerce
Иногда стандартные вариации WooCommerce не подходят для бизнеса, где учет остатков нужно вести по уникальным параметрам, например, по цвету и размеру с дополнительными характеристиками, или когда вариации слишком сложные для стандартной модели. В таких случаях требуется настроить собственный механизм учета остатков для каждой комбинации атрибутов или пользовательских полей.
Диагностика задачи: почему стандартные вариации не подходят
- Слишком много вариаций, что замедляет админ-панель и фронтенд.
- Необходимость учета запасов по дополнительным параметрам, не поддерживаемым вариациями.
- Требования к кастомной логике списания запасов.
Если вы столкнулись с подобными задачами, решение — создать кастомный учет остатков с помощью пользовательских полей и написать свой обработчик списания товара при покупке.
Пошаговое решение: реализация кастомного учета запасов
1. Добавление пользовательских полей для хранения остатков
Создадим мета-поле для продуктов с массивом остатков по ключам атрибутов. Например, массив с ключом color-size и значением количества на складе.
function add_custom_stock_fields() {
woocommerce_wp_text_input(
array(
'id' => '_custom_stock_data',
'label' => __('Custom Stock Data (JSON)', 'woocommerce'),
'description' => __('Введите JSON с остатками, например {"red-XL":10, "blue-M":5}', 'woocommerce'),
'desc_tip' => true,
'type' => 'textarea',
'custom_attributes' => array('rows' => 5),
)
);
}
add_action('woocommerce_product_options_inventory_product_data', 'add_custom_stock_fields');
// Сохраняем данные
function save_custom_stock_fields($post_id) {
$custom_stock = isset($_POST['_custom_stock_data']) ? wp_unslash($_POST['_custom_stock_data']) : '';
update_post_meta($post_id, '_custom_stock_data', sanitize_textarea_field($custom_stock));
}
add_action('woocommerce_process_product_meta', 'save_custom_stock_fields');2. Отображение и выбор атрибутов на странице товара
Используем стандартные атрибуты (цвет, размер), но вместо вариаций скрываем стандартный выбор и выводим собственный селектор.
add_action('woocommerce_before_add_to_cart_button', 'render_custom_stock_selector');
function render_custom_stock_selector() {
global $product;
$custom_stock_json = get_post_meta($product->get_id(), '_custom_stock_data', true);
$stock_data = json_decode($custom_stock_json, true);
if (!$stock_data) return;
// Получаем уникальные значения цветов и размеров из ключей
$colors = [];
$sizes = [];
foreach (array_keys($stock_data) as $key) {
list($color, $size) = explode('-', $key);
$colors[$color] = true;
$sizes[$size] = true;
}
echo '<label>Цвет:</label><select name="custom_color" id="custom_color">';
foreach (array_keys($colors) as $color) {
echo '<option value="' . esc_attr($color) . '">' . esc_html($color) . '</option>';
}
echo '</select>';
echo '<label>Размер:</label><select name="custom_size" id="custom_size">';
foreach (array_keys($sizes) as $size) {
echo '<option value="' . esc_attr($size) . '">' . esc_html($size) . '</option>';
}
echo '</select>';
echo '<div id="custom_stock_info" style="margin-top:10px; font-weight:bold;"></div>';
// Добавим скрипт для динамического отображения остатка
?>
<script>
(function($){
var stockData = <?php echo json_encode($stock_data); ?>;
function updateStockInfo() {
var color = $('#custom_color').val();
var size = $('#custom_size').val();
var key = color + '-' + size;
var stock = stockData[key] !== undefined ? stockData[key] : 0;
$('#custom_stock_info').text('В наличии: ' + stock);
}
$('#custom_color, #custom_size').on('change', updateStockInfo);
$(document).ready(updateStockInfo);
})(jQuery);
</script>
<?php
}3. Обработка добавления в корзину с учетом пользовательского учета остатков
Перехватим добавление товара в корзину, проверим остаток и уменьшим его в мета-поле.
add_filter('woocommerce_add_to_cart_validation', 'custom_stock_add_to_cart_validation', 10, 3);
function custom_stock_add_to_cart_validation($passed, $product_id, $quantity) {
if (isset($_POST['custom_color'], $_POST['custom_size'])) {
$custom_stock_json = get_post_meta($product_id, '_custom_stock_data', true);
$stock_data = json_decode($custom_stock_json, true);
$key = sanitize_text_field($_POST['custom_color']) . '-' . sanitize_text_field($_POST['custom_size']);
if (!isset($stock_data[$key])) {
wc_add_notice(__('Выбранная комбинация отсутствует на складе.', 'woocommerce'), 'error');
return false;
}
if ($stock_data[$key] < $quantity) {
wc_add_notice(sprintf(__('На складе доступно только %d единиц выбранной комбинации.', 'woocommerce'), $stock_data[$key]), 'error');
return false;
}
}
return $passed;
}
add_action('woocommerce_add_to_cart', 'custom_stock_reduce_stock', 10, 6);
function custom_stock_reduce_stock($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
if (isset($_POST['custom_color'], $_POST['custom_size'])) {
$custom_stock_json = get_post_meta($product_id, '_custom_stock_data', true);
$stock_data = json_decode($custom_stock_json, true);
$key = sanitize_text_field($_POST['custom_color']) . '-' . sanitize_text_field($_POST['custom_size']);
if (isset($stock_data[$key])) {
$stock_data[$key] -= $quantity;
if ($stock_data[$key] < 0) $stock_data[$key] = 0;
update_post_meta($product_id, '_custom_stock_data', wp_json_encode($stock_data));
}
}
}Проверка результата после внедрения
- В админке у товара появляется поле для ввода JSON остатков.
- На странице товара пользователь видит селекторы для выбора цвета и размера и информацию об остатке.
- При добавлении в корзину проверяется наличие выбранной комбинации и выводится ошибка, если товара недостаточно.
- После успешной покупки остаток уменьшается в мета-поле.
Проверьте в админке JSON, на фронтенде корректность отображения остатков, а также попробуйте добавить в корзину количество больше доступного — должно появиться уведомление об ошибке.
Частые ошибки и как их исправить
- Неправильный формат JSON: при вводе остатков в админке используйте валидный JSON; для проверки используйте онлайн-валидаторы.
- Отсутствие проверки безопасности данных из формы: обязательно используйте
sanitize_text_fieldиwp_unslashдля обработки входящих данных. - Проблемы с кешированием: если остатки не обновляются на фронтенде, проверьте кеширование страниц и очистите кеш.
- Отсутствие обработки возврата товара: данный пример не учитывает возвраты и отмены заказов — добавьте обработчик для восстановления остатков.
Практические советы по безопасности и производительности
- Храните данные в формате JSON, но избегайте излишне больших массивов — используйте нормализацию данных.
- Для масштабных магазинов лучше вынести учет остатков в отдельную таблицу баз данных с индексами.
- Используйте nonce и проверку прав пользователя при сохранении данных из админки.
- Кэшируйте результаты выборки остатков для уменьшения нагрузки при большом трафике.
Сравнение вариантов реализации учета остатков
| Метод | Плюсы | Минусы | Когда использовать |
|---|---|---|---|
| Стандартные вариации WooCommerce | Готовое решение с учетом складов, интеграция с отчетами | Неудобно при большом количестве вариаций, высокая нагрузка | Небольшой ассортимент с классическими вариациями |
| Кастомные пользовательские поля и обработчики (как в статье) | Гибкость, возможность учет по нестандартным параметрам | Сложность реализации, необходимость дополнительного кода | Сложные товары с уникальной логикой учета |
| Использование плагинов управления складом | Расширенный функционал, поддержка отчетности | Стоимость, возможные конфликты, не всегда гибко | Средние и крупные магазины с типовыми требованиями |