PMS のセット販売用「仮想部屋」の予約が『閉室・原因不明』に見える罠と直し方(Beds24 の room dependencies)
セット部屋(仮想部屋)の予約で構成室が numAvail=0 になり集計が『閉室・原因不明』と誤計上する仕組みと、3 回の GET での診断、予約を構成室へ展開する共通純関数モジュールで全ツールを直す手順
約2万トークンの節約 (API料金換算で約30円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「PMS のセット販売用「仮想部屋」の予約が『閉室・原因不明』に見える罠と直し方(Beds24 の room dependencies)」は、業務自動化カテゴリのAI指示書(MDファイル)です。セット部屋(仮想部屋)の予約で構成室が numAvail=0 になり集計が『閉室・原因不明』と誤計上する仕組みと、3 回の GET での診断、予約を構成室へ展開する共通純関数モジュールで全ツールを直す手順この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約2万トークン(API料金換算で約30円)・80%のトークンを節約できます。
- カテゴリ
- 業務自動化
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約2.5万トークン
- この巻物使用時
- 約5,000トークン
- 節約量
- 約2万トークン (約30円)
- 更新日
- 2026-09-20
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/pms-beds24-room-dependencies/raw を読み込んで、この指示書どおりに実装して"
中身
PMS のセット販売用「仮想部屋」の予約が『閉室・原因不明』に見える罠と直し方(Beds24 の room dependencies)
最初に守ること
numAvail=0は「開け忘れ」の証拠ではない。実部屋の予約、override、セット部屋の予約を照合するまで、在庫を開ける提案や書き込みをしない。この手順は GET による診断と集計の修正を対象とする。
1. なぜ予約が消えたように見えるのか
セット部屋は、複数の実部屋をまとめて販売する仮想部屋である。そこに予約が入ると room dependencies によって構成室が占有され、構成室の numAvail は 0 になる。
一方、予約レコードの roomId はセット部屋の ID のまま残る。実部屋の ID だけを通す集計では、この予約が除外される。その結果、構成室が「予約が無いのに売れない」と分類される。
この閉じ方では override が none のままになる。blackout の検査だけでは原因を見つけられない。「blackout がない」と「予約がない」は別の判定である。
典型的な誤報告は、夜間の提案ループが複数の構成室について「原因不明の閉室があるので管理画面で開ける」と勧めるものだ。実体はセット部屋への OTA 予約であり、開ければ同じ実部屋を重ねて販売してしまう。
2. 3種類の GET で診断する
① 実部屋と仮想部屋を一覧にする
/properties?id=<propertyId>&includeAllRooms=true を読み、応答内の roomTypes を全部取得する。実部屋だけにフィルタしてから調査を始めない。
実部屋の ID・名称、仮想部屋の ID・名称、構成室の候補を表にする。ID は文字列へ正規化する。名前から読み取れる構成は候補なので、既存設定や room dependencies と照合して確定する。
② セット部屋を含む予約を取得する
/bookings?propertyId=<propertyId>&arrivalFrom=<検索開始>&arrivalTo=<検索終了> を使い、roomId、予約 ID、到着日、出発日、状態を取得する。セット部屋の ID を除外してはいけない。
到着日の検索範囲を調査窓と同じにすると、窓より前に到着した滞在を落とす。検索開始を十分前へ広げるか、利用可能な滞在期間の検索条件を併用する。取得後は「到着日 < 窓の終了、かつ出発日 > 窓の開始」で重なりを判定する。宿泊日は到着日を含み、出発日を含まない。
キャンセル等を除外する規則は既存集計と一致させる。ページングや取得上限も確認し、必要なら期間分割して予約 ID で重複を除く。完全取得を確認できなければ「予約なし」とは判定しない。
③ 構成室のカレンダーを読む
/inventory/rooms/calendar?roomId=<roomId>&includeNumAvail=true&includeOverride=true に調査期間の条件を加え、構成室ごとの numAvail と override を取得する。
「対象日に有効なセット予約がある」「その部屋が構成室である」「構成室の numAvail が 0」「override が none」がそろえば、実予約による占有として説明できる。override が同時に存在する場合も、予約による占有を消してはいけない。
ここでいう3回は3種類の確認を指す。部屋数やページングにより実際の HTTP リクエストは増える。確認の回数を減らすために予約を取りこぼさない。
3. 修正は共通の純関数へ集める
<リポ>/tools/set-rooms.mjs のような共通モジュールを作り、空室抽出、閉室理由、稼働率、提案ループの入力生成から import する。すでに同等の処理がある場合は、更新時刻と呼び出し箇所を確認して重複実装を避ける。
setRoomMembers(roomTypes, realRooms) は仮想部屋名を ,、&& で分割し、各要素を実部屋名に解決する。「全室」系の名称は全実部屋へ対応させる。ただし命名規則を確認済みの対象に限る。未知の名前は unknown に積み、推測で構成室を作らない。
expandBookingsToRealRooms(bookings, members, realRooms) は実部屋予約をそのまま残し、セット予約を構成室ごとの浅いコピーへ展開する。コピーの roomId を構成室へ差し替え、viaSetRoom:{id, bookingId} に由来を残す。その他の部屋は展開結果に含めない。入力オブジェクトは変更しない。
未知の仮想部屋に予約がある場合、関数がそれを落とした結果だけを使って在庫を安全と判定してはいけない。呼び出し側で unknown と予約を突き合わせ、影響範囲は判定不能にする。実部屋名の重複や空の対応表も先に検出する。
4. 共通モジュールとテストの骨格
以下の「全室」判定は保守的な例である。部分一致を広げる前に、実際の命名規則と依存設定を確認する。末尾のテスト部分は別のテストファイルへ置く。
export function setRoomMembers(roomTypes, realRooms) {
const members = Object.create(null);
const unknown = [];
if (!Array.isArray(roomTypes)) return { members, unknown };
const realIds = Object.keys(realRooms);
const names = new Set(realIds.map(id => realRooms[id]));
for (const rt of roomTypes) {
const id = String(rt.id);
if (Object.hasOwn(realRooms, id)) continue;
const name = rt.name;
const parts = typeof name === 'string'
? name.split(/[,、&&]/).map(part => part.trim()) : [];
let memberIds;
if (parts.length && parts.every(part => part && names.has(part))) {
const selected = new Set(parts);
memberIds = realIds.filter(id => selected.has(realRooms[id]));
} else if (typeof name === 'string' && /^(全室|ALL)$/i.test(name.trim())) {
memberIds = [...realIds];
} else {
unknown.push({ id, name });
continue;
}
members[id] = memberIds;
}
return { members, unknown };
}
export function expandBookingsToRealRooms(bookings, members, realRooms) {
const result = [];
for (const booking of bookings) {
const id = String(booking.roomId);
if (Object.hasOwn(realRooms, id)) {
result.push(booking);
} else if (Object.hasOwn(members, id)) {
for (const memberId of members[id]) {
result.push({
...booking,
roomId: String(memberId),
viaSetRoom: { id, bookingId: booking.id }
});
}
}
}
return result;
}
// 以下は別のテストファイルへ移す。
import assert from 'node:assert/strict';
import { fixture } from './fixtures/set-rooms.mjs';
const before = structuredClone(fixture.bookings);
const { members } = setRoomMembers(fixture.roomTypes, fixture.realRooms);
assert.deepEqual(expandBookingsToRealRooms(before, members, fixture.realRooms), fixture.expected);
assert.deepEqual(before, fixture.bookings);
fixture は実際の roomTypes と予約応答を保存して作る。公開時には ID・名称・日付等を一貫した一般名や合成値へ置換し、依存関係を保つ。上記は骨格なので、実際のテストファイルでは対象関数を import する。
5. 集計と表示を直す
展開後の予約で booked を数える。予約レコード数ではなく、実部屋 ID と宿泊日の組で重複を除く。直接予約とセット経由予約が同じ実部屋・同じ日に現れたら、一度だけ数え、競合の警告も残す。
既存の出力キーは変更しない。セット経由の内訳として setRoomBookedNights を追加する。これは booked の内数であり、再加算しない。直接予約と重なる場合の優先順位を固定し、内訳の合計が booked を超えないようにする。
閉室理由には「セット部屋の予約(構成室を占有・閉室ではない)」の節を追加する。blocked は予約で説明できない売止めを表し、override の有無と未確認状態を別に表示する。従来の blocked の意味が異なる場合は、その契約を保ちつつ内訳を明示する。
6. 修正後の読み戻し
比較には同じ窓、同じ実部屋一覧、同じ予約スナップショットを使う。修正後に再取得した API 応答でも予約・在庫の状態を確認する。途中で予約が変わった場合は、その変化と修正効果を分ける。
- 全仮想部屋と構成室を確認し、unknown の影響を評価した。
- 窓より前の到着、出発日境界、キャンセル、取得上限をテストした。
- 実部屋予約、セット予約、全室セット、未知の名前を fixture で検証した。
- 元の予約を変更せず、重複占有を二重計上していない。
- 誤分類されていたセット室泊ぶん booked が増え、blocked が同じだけ減った。
- 修正後の実 API と原因表示を突き合わせた。
- 提案ループの入力にも共通の展開処理が適用されている。
増減の一致は、もともと blocked に誤分類されていた室泊について確認する。ほかの売止めまで消えることを成功条件にしない。
運用への組み込み 「予約が無いのに売れない」は、実部屋の予約、override、セット部屋の予約の順で調べる。ただし途中で原因が見つかっても予約照合は完了させる。「在庫を開ける」という打ち手を生成する前に展開と完全性確認を通し、未確認の室泊は人が確認できる状態で止める。
よくある質問
+「PMS のセット販売用「仮想部屋」の予約が『閉室・原因不明』に見える罠と直し方(Beds24 の room dependencies)」とは何ですか?
セット部屋(仮想部屋)の予約で構成室が numAvail=0 になり集計が『閉室・原因不明』と誤計上する仕組みと、3 回の GET での診断、予約を構成室へ展開する共通純関数モジュールで全ツールを直す手順
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約2.5万トークンかかりますが、この巻物を使えば約5,000トークンで済みます。差し引き約2万トークン(API料金換算で約30円)・80%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
Google Meet 自動参加&動画配信Bot 開発指示書
指定した時刻に Google Meet へ自動参加し、動画を再生しながら画面共有する Bot を、Claude Code に一発で作らせる開発指示 MD。朝会の定例動画配信・ウェビナーの自動放送に。
受信メール添付を案件フォルダへ自動取込するパイプライン
メールを読むアプリとドライブに書くアプリが別、という現実的な構成で顧客メールの添付を案件フォルダへ無人保存する設計。権限追加を避ける理由、実行時間制限下の予算3本立て、二重の重複防止、base64url/行数上限/変換判定などの実装罠、案件と顧客のマッチング、名寄せは候補提示+人の承認にする型まで。
Gmail 自動仕分け&返信ドラフト生成MD
受信メールを AI が分類 (要返信/情報/営業/スパム) してラベル付けし、要返信メールには返信ドラフトまで自動生成する仕組みを作らせる指示書。DWD (ドメイン全体委任) 設定手順込み。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア