2026年10月13日

2026年10月13日

WooCommerceの在庫管理エラーを解決する方法

はじめに

WooCommerceの在庫管理で「在庫が減らない」「在庫切れなのに購入できる」「在庫数が注文数と合わない」問題は、注文ステータスの設定・在庫管理の有効化漏れ・同時注文によるレースコンディションが原因です。

症状・原因

  • 注文が完了しても在庫数が減らない
  • 在庫0なのにカートに追加できる・注文できる
  • 複数の注文が同時に入ると在庫がマイナスになる
  • バリエーション商品の在庫だけ正しく管理されない

解決手順

ステップ1:在庫設定の現状を確認する

# 在庫管理の全体設定を確認
wp option get woocommerce_manage_stock  # yes/no
wp option get woocommerce_stock_email_recipient
wp option get woocommerce_notify_low_stock_amount

# 特定商品の在庫状態を確認
wp eval "
\$product = wc_get_product(123);
printf(
    'Stock managed: %s, Qty: %s, Status: %s' . PHP_EOL,
    \$product->get_manage_stock() ? 'yes' : 'no',
    \$product->get_stock_quantity(),
    \$product->get_stock_status()
);
"

# 在庫が減らない注文を確認
wp eval "
\$orders = wc_get_orders(['status' => 'processing', 'limit' => 10]);
foreach (\$orders as \$order) {
    printf('Order %d: %s, stock_reduced=%s' . PHP_EOL,
        \$order->get_id(),
        \$order->get_status(),
        \$order->get_meta('_order_stock_reduced') ?: 'no'
    );
}
"

ステップ2:在庫管理を正しく設定する

// functions.php: 在庫管理の設定を確認・強制する

// ① 全商品の在庫管理を一括で有効化
add_action('woocommerce_product_set_stock', function(WC_Product $product): void {
    // 在庫変更時にログ
    WC()->logger()->info(
        sprintf('Stock updated for product %d: qty=%s status=%s',
            $product->get_id(),
            $product->get_stock_quantity(),
            $product->get_stock_status()
        ),
        ['source' => 'stock-manager']
    );
});

// ② 注文確定時に在庫を確実に減らす
add_action('woocommerce_checkout_order_processed', function(int $order_id): void {
    $order = wc_get_order($order_id);
    if (!$order) return;

    // 既に在庫が減らされているか確認
    if (!$order->get_meta('_order_stock_reduced')) {
        wc_reduce_stock_levels($order_id);
        $order->update_meta_data('_order_stock_reduced', 'yes');
        $order->save();
    }
}, 10);

// ③ 注文キャンセル時に在庫を戻す
add_action('woocommerce_order_status_cancelled', function(int $order_id): void {
    $order = wc_get_order($order_id);
    if (!$order || !$order->get_meta('_order_stock_reduced')) {
        return;
    }
    wc_restock_refunded_items($order, $order->get_items());
    $order->delete_meta_data('_order_stock_reduced');
    $order->save();
    $order->add_order_note('キャンセルにより在庫を復元しました。');
});

ステップ3:在庫のずれを修正する

// functions.php: 在庫数を手動で修正する

function fix_product_stock(int $product_id, int $correct_quantity): void {
    $product = wc_get_product($product_id);
    if (!$product) {
        return;
    }

    $current = $product->get_stock_quantity();

    // 在庫数を正しい値に設定
    wc_update_product_stock($product, $correct_quantity, 'set');

    WC()->logger()->info(
        sprintf('Stock fixed for product %d: %d → %d',
            $product_id, $current, $correct_quantity),
        ['source' => 'stock-fix']
    );
}
# WP-CLI で在庫を一括更新
wp eval "
// 全商品の在庫状態を確認・修正
\$products = wc_get_products(['limit' => -1, 'manage_stock' => true]);
foreach (\$products as \$product) {
    \$qty = \$product->get_stock_quantity();
    if (\$qty < 0) {
        echo '在庫マイナス: ' . \$product->get_name() . ' (' . \$qty . ')' . PHP_EOL;
        // wc_update_product_stock(\$product, 0, 'set'); // 0にリセットする場合
    }
}
"

# 特定商品の在庫を直接設定
wp eval "
\$product = wc_get_product(123);
wc_update_product_stock(\$product, 50, 'set');
echo '在庫を50に設定しました';
"

ステップ4:同時注文のレースコンディションを防ぐ

// functions.php: データベースロックで同時注文の在庫競合を防ぐ
add_filter('woocommerce_checkout_cart_item_quantity', function(int $qty, array $item): int {
    return $qty;
}, 10, 2);

// 在庫チェックをより厳密に行う
add_filter('woocommerce_is_purchasable', function(bool $purchasable, WC_Product $product): bool {
    if (!$purchasable) {
        return false;
    }

    if ($product->managing_stock()) {
        $stock = $product->get_stock_quantity();
        // データベースから最新の在庫を直接取得
        global $wpdb;
        $db_stock = $wpdb->get_var($wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->postmeta}
             WHERE post_id = %d AND meta_key = '_stock'",
            $product->get_id()
        ));
        if ((int) $db_stock <= 0 && !$product->backorders_allowed()) {
            return false;
        }
    }

    return $purchasable;
}, 10, 2);

// 在庫予約(注文処理中に仮押さえ)
add_action('woocommerce_checkout_order_created', function(WC_Order $order): void {
    foreach ($order->get_items() as $item) {
        $product = $item->get_product();
        if ($product && $product->managing_stock()) {
            // Transient で一時的に在庫を予約
            $key = 'stock_reserve_' . $product->get_id();
            $reserved = (int) get_transient($key);
            set_transient($key, $reserved + $item->get_quantity(), 300); // 5分間予約
        }
    }
});

ステップ5:バックオーダーと在庫切れ通知を設定する

// functions.php: 在庫切れ・低在庫の通知をカスタマイズ

// 低在庫通知のしきい値を商品ごとに設定
add_filter('woocommerce_get_low_stock_amount', function(int $amount, WC_Product $product): int {
    // カテゴリに応じてしきい値を変更
    if (has_term('popular', 'product_cat', $product->get_id())) {
        return 10; // 人気商品は10個で通知
    }
    return $amount; // デフォルト
}, 10, 2);

// バックオーダー時のメッセージをカスタマイズ
add_filter('woocommerce_get_availability', function(array $availability, WC_Product $product): array {
    if ($product->is_on_backorder()) {
        $availability['availability'] = '入荷待ち(約2週間でお届け)';
        $availability['class'] = 'available-on-backorder';
    }
    return $availability;
}, 10, 2);

// 在庫復元時に自動でバックオーダー注文を処理
add_action('woocommerce_product_set_stock', function(WC_Product $product): void {
    if ($product->get_stock_quantity() <= 0) {
        return;
    }
    // バックオーダー中の注文があれば管理者に通知
    $backorders = wc_get_orders([
        'status'     => 'on-hold',
        'meta_key'   => '_backorder_product_id',
        'meta_value' => $product->get_id(),
        'limit'      => 10,
    ]);
    if (!empty($backorders)) {
        wp_mail(
            get_option('admin_email'),
            '在庫復元:バックオーダー注文を処理してください',
            sprintf('商品「%s」の在庫が補充されました。%d件のバックオーダーがあります。',
                $product->get_name(), count($backorders))
        );
    }
});

注意事項

  • wc_reduce_stock_levels()は注文ステータスがprocessingまたはcompletedに変わったときに自動で呼ばれます。カスタムステータスを使っている場合は手動で呼び出すか、woocommerce_payment_complete_order_statusフィルターで対応してください
  • 大量注文が同時に発生する場合、MySQLのトランザクションとSELECT FOR UPDATEを使った在庫ロックが必要です。WooCommerceは内部でこれを処理しますが、カスタムコードで在庫を直接操作する場合は注意してください
  • バックオーダーを許可する場合は「設定 → 商品 → 在庫 → バックオーダー」の設定と商品個別の設定の両方を確認してください

まとめ

WooCommerce在庫エラーの解決は①wc_get_product()->get_stock_quantity()と_order_stock_reducedメタで在庫状態を確認、②manage_stock=trueとwc_reduce_stock_levels()で確実に在庫を減らす処理を実装、③wc_update_product_stock()で在庫数をデータベースから直接修正、④is_purchasableフィルターでDBから最新在庫を取得してレースコンディションを防止、⑤woocommerce_get_availabilityフィルターでバックオーダー表示と在庫復元通知をカスタマイズします。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲