2026年9月1日

2026年9月1日

WooCommerceの在庫管理を自動化する方法|通知・定期レポート・外部在庫の同期

WooCommerceの在庫管理は、標準の在庫設定を正しく整えたうえで、woocommerce_low_stock などのフックと Action Scheduler を使えば、通知・定期レポート・外部システムとの在庫同期まで自動化できます。この記事では、標準機能で自動化できる範囲を確認し、チャットへの在庫アラート、毎朝の在庫レポート、仕入先データからの在庫同期を実装する方法と、在庫数がずれる原因になりやすい落とし穴を説明します。コードはすべて WooCommerce が提供する関数とフックを使っています。

まず標準の在庫設定を整える

自動化の前提として、WooCommerce の在庫機能そのものが正しく動いている必要があります。設定は管理画面の「WooCommerce」→「設定」→「商品」→「在庫」にあります(項目名は翻訳やバージョンで多少異なります。2026年10月現在)。

  • 在庫管理:有効にすると、注文に応じて在庫数が自動で増減します。ここが無効だと、以降のフックも意味を持ちません。
  • 在庫保留(分):支払い待ちの注文に在庫を確保しておく時間です。時間内に支払われなかった注文は自動でキャンセルされ、在庫が戻ります。標準は60分です。
  • 通知と受信者:在庫僅少・在庫切れのメール通知を有効にし、受信するアドレスを指定します。
  • 在庫僅少のしきい値/在庫切れのしきい値:この数以下になったら「僅少」「在庫切れ」と判定します。

在庫僅少のしきい値は、商品編集画面の「在庫」タブで商品ごとにも設定できます。売れ行きの速い商品は大きめに、受注生産に近い商品は小さめに、と分けておくと、通知が鳴りすぎて読まれなくなる事態を避けられます。

在庫が増減するタイミング

自動化のコードを書く前に、在庫がいつ動くかを把握しておくと混乱しません。注文のステータスが「処理中」「保留中」「完了」になるか、支払い完了の処理が走った時点で在庫が減り、注文がキャンセルされると戻ります。手動で注文を作成した場合や、ステータスを行き来させた場合は、注文メモに在庫の増減が記録されるので、ずれを調べるときはまず注文メモを確認してください。

在庫を操作する関数の使い分け

コードから在庫を変える方法は主に2つあり、使い分けを誤ると在庫数がずれる原因になります。

1つ目は、商品オブジェクトに値をセットして保存する方法です。

$product = wc_get_product( 123 );
if ( $product ) {
	$product->set_manage_stock( true );
	$product->set_stock_quantity( 50 );
	$product->save();
}

wc_get_product() で読み込んだ時点の商品データを丸ごと保存するため、読み込みから保存までの間に注文が入って在庫が減っていると、その減少分を上書きしてしまう可能性があります。商品の初期設定や、管理者が手で数を決めるような場面に向いています。

2つ目は wc_update_product_stock() です。

// 在庫数を 50 に設定
wc_update_product_stock( 123, 50, 'set' );

// 現在の在庫から 3 増やす / 2 減らす
wc_update_product_stock( 123, 3, 'increase' );
wc_update_product_stock( 123, 2, 'decrease' );

第1引数には商品IDか商品オブジェクトを渡せます。increase と decrease はデータベース上の現在値に対して加減算するため、同時に注文が入っても差分が失われにくくなります。更新後は在庫ステータス(在庫あり・在庫切れ)も数量に合わせて更新されます。入荷・返品など「いくつ増えたか」が分かっている処理では、こちらを使うのが基本です。

バリエーション商品で在庫を親商品側で管理している場合、wc_update_product_stock() は親商品の在庫を更新します。バリエーションごとに在庫を持たせたいなら、各バリエーションの在庫管理を有効にしておく必要があります。

在庫僅少・在庫切れをチャットに通知する

標準のメール通知は埋もれやすいので、チームのチャットにも流したいという要望はよくあります。WooCommerce は注文で在庫が減ってしきい値を下回ったときに woocommerce_low_stock、在庫切れになったときに woocommerce_no_stock アクションを実行し、どちらも商品オブジェクトを渡します。

ここで注意したいのは、これらのフックは購入手続きの処理中に実行される点です。フックの中で外部サービスに直接HTTPリクエストを送ると、相手の応答が遅いだけで購入完了が遅れます。そこで、フックでは送信予約だけを行い、実際の送信は Action Scheduler(WooCommerce に同梱されているバックグラウンド処理の仕組み)に任せます。

<?php
/**
 * Plugin Name: My Inventory Automation
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

add_action( 'woocommerce_low_stock', 'myinv_queue_stock_alert' );
add_action( 'woocommerce_no_stock', 'myinv_queue_stock_alert' );

function myinv_queue_stock_alert( $product ) {
	if ( ! $product instanceof WC_Product ) {
		return;
	}
	$type = ( 'woocommerce_no_stock' === current_action() ) ? 'no_stock' : 'low_stock';
	$args = array( $product->get_id(), $type );

	if ( function_exists( 'as_enqueue_async_action' ) ) {
		as_enqueue_async_action( 'myinv_send_stock_alert', $args, 'myinv' );
	} else {
		wp_schedule_single_event( time(), 'myinv_send_stock_alert', $args );
	}
}

add_action( 'myinv_send_stock_alert', 'myinv_send_stock_alert', 10, 2 );

function myinv_send_stock_alert( $product_id, $type ) {
	if ( ! defined( 'MYINV_CHAT_WEBHOOK_URL' ) ) {
		return;
	}
	$product = wc_get_product( $product_id );
	if ( ! $product ) {
		return;
	}

	$label = ( 'no_stock' === $type ) ? '在庫切れ' : '在庫僅少';
	$text  = sprintf(
		'[%s] %s(SKU: %s)残り %d 個',
		$label,
		$product->get_name(),
		$product->get_sku(),
		(int) $product->get_stock_quantity()
	);

	$response = wp_remote_post(
		MYINV_CHAT_WEBHOOK_URL,
		array(
			'timeout' => 10,
			'headers' => array( 'Content-Type' => 'application/json' ),
			'body'    => wp_json_encode( array( 'text' => $text ) ),
		)
	);

	if ( is_wp_error( $response ) ) {
		wc_get_logger()->error( '在庫通知の送信に失敗: ' . $response->get_error_message(), array( 'source' => 'myinv' ) );
	}
}

通知先のURLは、コードに直書きせず wp-config.php に定数として置きます。

define( 'MYINV_CHAT_WEBHOOK_URL', 'https://chat.example.com/hooks/your-webhook-id' );

送信する本文の形式(text というキー名など)は通知先のサービスによって異なるので、各サービスの受信用URLの仕様に合わせて変えてください。

動作確認は、テスト用の商品を在庫3個・しきい値2個に設定し、テスト注文で在庫を減らすのが簡単です。予約された処理は「WooCommerce」→「ステータス」→「予定されたアクション」で myinv_send_stock_alert を検索すると、実行済みか失敗したかを確認できます。失敗時のログは「ステータス」→「ログ」で myinv を選ぶと見られます。

なお、woocommerce_low_stock と woocommerce_no_stock は主に注文による在庫減少で実行されるフックです。管理画面や外部同期で在庫を書き換えたときにも反応させたい場合は、在庫数が変わるたびに実行される woocommerce_product_set_stock(バリエーションは woocommerce_variation_set_stock)を使います。ただし、そのフックの中で在庫を再び書き換えると処理が繰り返されるので、通知や記録だけに留めてください。

毎朝の在庫レポートを自動で送る

在庫切れを「起きてから知る」のではなく、しきい値を下回った商品を毎朝まとめて確認できると発注の判断がしやすくなります。Action Scheduler の定期実行を使います。

add_action( 'init', 'myinv_schedule_daily_report' );

function myinv_schedule_daily_report() {
	if ( ! function_exists( 'as_has_scheduled_action' ) ) {
		return;
	}
	if ( false === as_has_scheduled_action( 'myinv_daily_report' ) ) {
		$first = new DateTimeImmutable( 'tomorrow 09:00', wp_timezone() );
		as_schedule_recurring_action( $first->getTimestamp(), DAY_IN_SECONDS, 'myinv_daily_report', array(), 'myinv' );
	}
}

add_action( 'myinv_daily_report', 'myinv_send_daily_report' );

function myinv_send_daily_report() {
	$lines    = array();
	$page     = 1;
	$per_page = 200;

	do {
		$products = wc_get_products(
			array(
				'status'       => 'publish',
				'type'         => array( 'simple', 'variable', 'variation' ),
				'manage_stock' => true,
				'limit'        => $per_page,
				'page'         => $page,
			)
		);

		foreach ( $products as $product ) {
			$qty = (int) $product->get_stock_quantity();
			if ( $qty <= wc_get_low_stock_amount( $product ) ) {
				$lines[] = sprintf( '%s(SKU: %s): %d', $product->get_name(), $product->get_sku(), $qty );
			}
		}
		$page++;
	} while ( count( $products ) === $per_page );

	if ( empty( $lines ) ) {
		return;
	}

	$to = get_option( 'woocommerce_stock_email_recipient', get_option( 'admin_email' ) );
	wp_mail( $to, '【在庫レポート】しきい値以下の商品 ' . count( $lines ) . ' 件', implode( "\n", $lines ) );
}

初回の実行時刻を DateTimeImmutable と wp_timezone() で作っているのには理由があります。WordPress の内部ではPHPの標準タイムゾーンがUTCになっているため、strtotime( 'tomorrow 09:00' ) と書くと日本時間の18時に実行されてしまいます。サイトの設定タイムゾーンを使えば、日本時間の9時に揃います。

商品数が多いサイトで全件を一度に読み込むとメモリが足りなくなるため、200件ずつページを分けて取得しています。wc_get_low_stock_amount() は、商品ごとのしきい値、親商品のしきい値、全体設定の順に参照して値を返すので、設定画面と同じ基準で判定できます。

Action Scheduler の処理は、サイトへのアクセスや WP-Cron をきっかけに動きます。アクセスの少ない時間帯に実行が遅れる場合は、サーバーの cron から WP-Cron を定期的に呼び出す設定にすると安定します。

仕入先や倉庫のデータから在庫を同期する

倉庫管理システムや仕入先が在庫数をAPIやCSVで提供している場合、定期的に取り込んでWooCommerce側を合わせる処理も自動化できます。ここではSKUと数量の一覧をJSONで返すAPIを想定します。

add_action( 'myinv_sync_supplier_stock', 'myinv_sync_supplier_stock' );

function myinv_sync_supplier_stock() {
	if ( ! defined( 'MYINV_SUPPLIER_TOKEN' ) ) {
		return;
	}

	$response = wp_remote_get(
		'https://supplier.example.com/api/stock',
		array(
			'timeout' => 30,
			'headers' => array( 'Authorization' => 'Bearer ' . MYINV_SUPPLIER_TOKEN ),
		)
	);

	if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
		wc_get_logger()->error( '在庫同期: データの取得に失敗', array( 'source' => 'myinv' ) );
		return;
	}

	$rows = json_decode( wp_remote_retrieve_body( $response ), true );
	if ( ! is_array( $rows ) ) {
		return;
	}

	foreach ( $rows as $row ) {
		if ( empty( $row['sku'] ) || ! isset( $row['qty'] ) || ! is_numeric( $row['qty'] ) ) {
			continue;
		}
		$product_id = wc_get_product_id_by_sku( sanitize_text_field( $row['sku'] ) );
		$product    = $product_id ? wc_get_product( $product_id ) : false;
		if ( ! $product || ! $product->managing_stock() ) {
			continue;
		}

		$new_qty = max( 0, (int) $row['qty'] );
		if ( $new_qty !== (int) $product->get_stock_quantity() ) {
			wc_update_product_stock( $product, $new_qty, 'set' );
		}
	}
}

外部データは信用しすぎないことが大切です。SKUが見つからない行、数量が数値でない行、在庫管理が無効な商品は飛ばしています。また、値が変わらない商品は更新しないようにして、無駄な書き込みとフックの実行を減らしています。

この同期は set(上書き)なので、同期の瞬間に入った注文の減少分が、仕入先側の数値で上書きされる可能性があります。注文の少ない時間帯に実行する、あるいは仕入先側にもWooCommerceの注文を反映する仕組みを用意して、どちらを正とするかを決めておくのが現実的です。初回の実行前には、データベースのバックアップを取り、ステージング環境で数件の商品から試してください。

定期実行の登録は、先ほどのレポートと同じく as_schedule_recurring_action() で行えます。

管理画面以外から在庫をまとめて変える

コードを書かずに一括で更新したい場合は、次の方法もあります。

  • CSV:「商品」→「エクスポート」で書き出し、在庫数の列を編集して「インポート」で「既存の商品を更新」を選んで取り込みます。
  • WP-CLI:wp wc product update 123 --manage_stock=true --stock_quantity=50 --user=1 のように、管理者ユーザーを指定して実行します。シェルスクリプトと組み合わせれば、ファイルから順に更新できます。
  • REST API:/wp-json/wc/v3/products/<id> に stock_quantity を送るか、/wp-json/wc/v3/products/batch で複数件をまとめて更新します。認証には「WooCommerce」→「設定」→「高度な設定」→「REST API」で発行するキーを使い、キーは外部に漏れない場所で管理します。

どの方法でも大量の在庫を一度に書き換えることになるため、作業前のバックアップは欠かせません。

まとめ

  • 自動化の前に、在庫管理・在庫保留・しきい値などの標準設定を整え、在庫が増減するタイミングを把握する
  • 入荷や返品のような差分更新は wc_update_product_stock() の increase/decrease を使い、在庫の上書き競合を避ける
  • woocommerce_low_stock・woocommerce_no_stock で通知するときは、Action Scheduler に送信を任せて購入手続きを遅くしない
  • 定期レポートは as_schedule_recurring_action() と wc_get_products() の分割取得で作り、実行時刻は wp_timezone() で指定する
  • 外部在庫の同期は入力値を検証し、どちらの在庫数を正とするかを決めてから運用する
  • 一括更新の前にはバックアップを取り、ステージング環境で試す

お気軽にご相談ください

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