2026年9月23日

2026年9月23日

WooCommerce Appointmentsのエラーを解決する方法

はじめに

WooCommerce Appointmentsで予約ページを開いても「利用可能な時間帯がありません」と表示され予約スロットが一切表示されない・同じ時間帯に複数の予約が入ってしまう重複予約が発生する・予約完了後に確認メールが届かない・Googleカレンダーとの同期が機能しないといった問題は、予約可能時間の設定・在庫管理・メール設定が原因です。

症状・原因

  • 商品の「予約可能日時」を設定したのにカレンダーに選択できるスロットが表示されない
  • 同じ時間帯に定員1名の設定でも複数人が予約を完了できてしまう
  • 予約確認後に「ご予約を承りました」メールがユーザーに送られない
  • WordPress管理画面でGoogleカレンダー連携を設定したが予約がGoogleカレンダーに反映されない

解決手順

ステップ1:WooCommerce Appointmentsの状態を確認する

# WooCommerce Appointmentsの設定確認
wp eval "
if (defined('APPOINTMENTS_VERSION')) {
    echo 'WC Appointments version: ' . APPOINTMENTS_VERSION . PHP_EOL;
}

// 予約可能商品を確認
\$products = get_posts([
    'post_type'      => 'product',
    'posts_per_page' => 10,
    'tax_query'      => [[
        'taxonomy' => 'product_type',
        'field'    => 'slug',
        'terms'    => 'appointment',
    ]],
]);

echo 'Appointment products: ' . count(\$products) . PHP_EOL;

foreach (\$products as \$product_post) {
    \$product = wc_get_product(\$product_post->ID);
    if (!\$product) continue;

    printf('  #%d: %s | Duration: %s %s | Capacity: %d | Staff: %d' . PHP_EOL,
        \$product->get_id(),
        \$product->get_name(),
        \$product->get_duration(),
        \$product->get_duration_unit(),
        \$product->get_qty(),
        count(\$product->get_staff_ids())
    );
}

// 最近の予約を確認
\$appointments = WC_Appointment_Data_Store::get_appointment_counts_by_status();
foreach (\$appointments as \$status => \$count) {
    echo 'Status ' . \$status . ': ' . \$count . PHP_EOL;
}
"

ステップ2:予約スロットの可用性をカスタマイズする

// functions.php: 予約スロット設定

// ① スロットの可用性チェックをカスタマイズ
add_filter('wc_appointment_is_slot_available', function(bool $available, string $slot_start, int $qty, int $product_id): bool {
    // 過去のスロットは予約不可
    if (strtotime($slot_start) < time()) {
        return false;
    }

    // 特定の日(例:日曜日)は予約不可
    $day_of_week = (int) date('w', strtotime($slot_start));
    if ($day_of_week === 0) {
        return false;
    }

    return $available;
}, 10, 4);

// ② 祝日を予約不可日として設定
add_filter('wc_appointments_get_unavailable_dates', function(array $dates, int $product_id): array {
    // 祝日リスト
    $holidays = [
        '2026-01-01', // 元日
        '2026-01-12', // 成人の日
        '2026-02-11', // 建国記念の日
        '2026-03-20', // 春分の日
    ];
    return array_merge($dates, $holidays);
}, 10, 2);

// ③ スタッフの可用性を確認
add_filter('wc_appointment_staff_is_available', function(bool $available, int $staff_id, string $slot_start, string $slot_end): bool {
    // スタッフのメタで休暇日を確認
    $vacation_days = get_user_meta($staff_id, '_appointment_vacation', true);
    if (!is_array($vacation_days)) return $available;

    $slot_date = date('Y-m-d', strtotime($slot_start));
    if (in_array($slot_date, $vacation_days, true)) {
        return false;
    }
    return $available;
}, 10, 4);

ステップ3:重複予約を防止する

// functions.php: 重複予約防止

// ① 予約前に同時間帯の予約数を確認
add_filter('woocommerce_add_to_cart_validation', function(bool $passed, int $product_id): bool {
    $product = wc_get_product($product_id);
    if (!$product || $product->get_type() !== 'appointment') return $passed;

    $start = isset($_POST['wc_appointments_field_start_date'])
        ? sanitize_text_field($_POST['wc_appointments_field_start_date'])
        : '';
    if (empty($start)) return $passed;

    // 同時間帯の確定予約数を確認
    $existing = WC_Appointment_Data_Store::get_appointments_for_product(
        $product_id,
        ['paid', 'confirmed'],
        strtotime($start),
        strtotime($start) + $product->get_duration() * 60
    );

    if (count($existing) >= $product->get_qty()) {
        wc_add_notice('この時間帯は満席です。別の時間帯をお選びください。', 'error');
        return false;
    }

    return $passed;
}, 10, 2);

// ② 予約確定時に在庫を即座に減算
add_action('woocommerce_appointment_confirmed', function(int $appointment_id): void {
    $appointment = get_wc_appointment($appointment_id);
    if (!$appointment) return;

    // トランジェントをクリアしてキャッシュされた可用性をリセット
    $product_id = $appointment->get_product_id();
    delete_transient('wc_appts_product_' . $product_id);

    error_log(sprintf('[WCA] Appointment #%d confirmed for product #%d at %s',
        $appointment_id, $product_id, $appointment->get_start()));
});

ステップ4:予約メールを設定する

# 予約メール設定の確認
wp eval "
// WooCommerceメール設定を確認
\$mailer = WC()->mailer();
\$emails = \$mailer->get_emails();

echo 'WC email classes: ' . count(\$emails) . PHP_EOL;

// 予約関連メールを確認
\$appointment_emails = array_filter(\$emails, fn(\$e) =>
    str_contains(get_class(\$e), 'Appointment')
);

foreach (\$appointment_emails as \$email) {
    printf('  %s | Enabled: %s | To: %s' . PHP_EOL,
        \$email->title,
        \$email->is_enabled() ? 'yes' : 'no',
        \$email->get_recipient()
    );
}

// サンプル予約でメールを強制送信(テスト)
\$appointments = WC_Appointment_Data_Store::get_appointments(['status' => 'confirmed', 'limit' => 1]);
if (!empty(\$appointments)) {
    \$appt = reset(\$appointments);
    echo 'Latest confirmed appointment: #' . \$appt->get_id() . PHP_EOL;
    echo 'Customer email: ' . \$appt->get_customer_id() . PHP_EOL;
}
"

ステップ5:Googleカレンダー同期を修正する

// functions.php: Googleカレンダー同期

// ① 予約確定時にGoogleカレンダーにイベントを追加
add_action('woocommerce_appointment_confirmed', function(int $appointment_id): void {
    if (!class_exists('WC_Appointments_Google_Calendar_Connection')) return;

    $appointment = get_wc_appointment($appointment_id);
    if (!$appointment) return;

    // Googleカレンダー同期を強制実行
    $gcal = new WC_Appointments_Google_Calendar_Connection($appointment);
    try {
        $gcal->sync_to_gcal();
        error_log(sprintf('[WCA] Synced appointment #%d to Google Calendar', $appointment_id));
    } catch (Exception $e) {
        error_log(sprintf('[WCA] Google Calendar sync failed for #%d: %s',
            $appointment_id, $e->getMessage()));
    }
});

// ② 予約キャンセル時にGoogleカレンダーからイベントを削除
add_action('woocommerce_appointment_cancelled', function(int $appointment_id): void {
    $gcal_event_id = get_post_meta($appointment_id, '_wc_appointments_gcal_event_id', true);
    if (empty($gcal_event_id)) return;

    error_log(sprintf('[WCA] Appointment #%d cancelled, gcal_event_id=%s',
        $appointment_id, $gcal_event_id));
});

// ③ 予約リマインダーメールを送信
add_action('wc_appointment_reminder', function(int $appointment_id): void {
    $appointment = get_wc_appointment($appointment_id);
    if (!$appointment) return;

    $order = $appointment->get_order();
    if (!$order) return;

    wp_mail(
        $order->get_billing_email(),
        sprintf('[%s] 明日の予約のご確認', get_bloginfo('name')),
        sprintf('明日 %s のご予約をお忘れなく。', $appointment->get_start_date())
    );
});

注意事項

  • 予約スロットが表示されない最多原因は「予約可能時間」設定のタイムゾーンのずれです。WordPressの設定タイムゾーンとサーバーのタイムゾーンが異なる場合にスロットの時刻計算が狂います。「設定 → 一般 → タイムゾーン」を必ず「東京」に設定してください
  • WooCommerce Appointmentsの重複予約問題はトランジェントキャッシュが原因のことが多いです。「WooCommerce → ステータス → ツール → トランジェントをクリア」を実行するとスロットの可用性が再計算されます
  • Googleカレンダー同期にはGoogle Cloud ConsoleでOAuth2認証の設定が必要です。認証トークンの有効期限が切れると同期が止まります。定期的にトークンを更新する設定を確認してください

まとめ

WooCommerce Appointments修復は①WC_Appointment_Data_Store::get_appointment_counts_by_status()で予約状態・商品の定員・スタッフ数を確認、②wc_appointment_is_slot_availableフィルターで過去・日曜日を除外・wc_appointments_get_unavailable_datesフィルターで祝日を設定、③woocommerce_add_to_cart_validationフィルターで同時間帯の既存予約数を確認・定員超過時にwc_add_notice()でエラー、④WooCommerceメール設定で予約関連メールの有効/無効・宛先を確認、⑤woocommerce_appointment_confirmedフックでGoogleカレンダー同期・キャンセル時の削除・リマインダーメール送信する手順で解決します。

お気軽にご相談ください

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