Skip to content

公演枠の公開設定 ​

公演枠(BookingSlot)を誰に・いつ見せ、いつから予約を受け付けるかの仕様をまとめる。前半に公開設定の仕様、後半にマスキングの仕組みと関連するソースコードを置く。

公開設定の 3 つの列 ​

公演枠の公開設定は、互いに独立した 3 つの列で決まる。

フィールド型役割
isDraftBoolean(既定値: true)一括公開ワークフローのマスターフラグ。true の間は問答無用で非公開
listingSlotListing enum情報開示レベル(一覧掲載と詳細閲覧権限を統合した 1 軸)
bookingOpensAtDateTime?予約開始日時。null/過去 = 即時予約可、未来 = 予約受付前(ティーザー)

情報開示レベル(SlotListing) ​

値一覧表示詳細閲覧予約
LISTED全情報表示誰でも閲覧可可
OCCUPIED_HINT「予定あり」(時間と部屋のみ)リンク保持者のみリンクから可
UNLISTED表示なしリンク保持者のみリンクから可

列の組み合わせ(isDraft: false のとき) ​

listingbookingOpensAt意味
LISTEDnull/過去通常公開
LISTED未来ティーザー(予約開始日時指定)
OCCUPIED_HINTnull/過去一覧に「予定あり」のみ表示
UNLISTEDnull/過去リンク共有のみ
UNLISTED未来裏側で準備中

OCCUPIED_HINT + bookingOpensAt=未来 は意味が曖昧なため、アプリケーション層で禁止する。 bookingOpensAt > startAt(公演日より後に予約受付開始)は、listing を問わず一律に禁止する。

事前決済の 180 日ルール ​

クレジットカードの authorization 期限が約 180 日のため、お客様が予約できる時点(bookingOpensAt または現在時刻)が、公演日から 180 日以内に収まる必要がある。上限は MAX_ADVANCE_PAYMENT_DAYS = 180 日(backend/src/const.ts)。

適用条件 ​

次をすべて満たすときだけ評価する。

  • paymentMethod === 'PREPAID'(事前決済)
  • needShopConfirmation !== true(リクエスト貸切は対象外)
  • isDraft === false(下書きは検査しない)
  • differenceInCalendarDays(startAt, now, { in: tz('Asia/Tokyo') }) > 180(JST のカレンダー日付の差で判定し、時刻は切り捨てる)

公開可否 ​

listingbookingOpensAt公開可否
LISTEDnullNG(画面: 通常公開)
LISTED[startAt - 180日, startAt] の範囲内OK(画面: 予約開始日時を指定して通常公開)
LISTED< startAt - 180日 または > startAtNG
OCCUPIED_HINT(任意)NG(リンク保持者がすぐ予約できるため)
UNLISTED(任意)NG(同上)

公演枠の作成 ​

createPublicBookingSlot / createPrivateBookingSlot は 180 日チェックを行わない。下書き状態で作成され、公開時(update*BookingSlot で isDraft: false)に初めて validateAdvancePaymentRule が評価される。

一括公開(publishBookingSlots) ​

publishBookingSlots は NG の枠を飛ばして、残りだけ公開する。飛ばされた枠は isDraft: true のまま残り、店舗が bookingOpensAt を適切に設定するまで個別公開も不可。

店舗管理画面の表示 ​

VisibilityField は事前決済 + 180 日超を検知すると listed / occupied_hint / unlisted の各プリセット下に「事前決済かつ公演日が 180 日以上先のため、このまま公開できません」と注釈を出す。選択肢は選べなくはせず、保存時に backend がエラーで弾く(日をまたいだり決済方法を切り替えたりしたときに、選べない状態が食い違うのを避けるため)。

listed_scheduled を選んだときの bookingOpensAt の既定値は、事前決済かつ 180 日超のときに限り subDays(startAt, 179)(15 分単位に丸める)。180 日ちょうどにすると、丸めや時刻のずれで境界を越えてしまうため、1 日の余裕を取っている。

OCCUPIED_HINT のマスキング ​

shouldMaskOccupiedHintDetails は clientType === 'mdms_jp-frontend' の場合のみ詳細(catalog / publicBookingSlot / privateBookingSlot / assignedStaffs / reservations)をマスキングする。shop / admin / grpc ではマスキングしない。clientType による出し分けの設計方針は 概要 > 設計方針 を参照。

リンク共有 URL の ?token=... が BookingSlot.linkToken と一致する場合はマスクを外す(リンク共有された人は詳細を見て予約できる必要があるため)。判定は bookingSlot(id, token) クエリの resolver が context に書き込み、子のフィールドの resolver が参照する。

カレンダーに常に出す項目 ​

OCCUPIED_HINT の枠でも公開カレンダーで作品名と貸切フラグは露出する(店舗 / 一般ユーザー双方から表示希望が多かったため)。詳細ページへのアクセスは frontend 側で抑制する(カレンダークリック無効化 + token 不一致時の非公開表示)。

フィールド意図
listingTitle: String作品名。shopCatalog 未紐付けなら null(frontend で「貸切予定枠」等にフォールバック)
isPrivate: Boolean!貸切公演かどうか(publicBookingSlot の有無で判定)

どちらも listing に依存せず常に返す。UNLISTED のスロットはそもそも公開フロント一覧に出ないため露出しても実害なし。詳細情報(料金・出演者・シナリオ詳細等)はマスクされる。

関連ソースコード ​

  • backend/src/logic/bookingSlotVisibility.ts — 組み合わせの検証(validateSlotVisibilityCombination)と 180 日ルール(validateAdvancePaymentRule)
  • backend/src/logic/shouldMaskOccupiedHintDetails.ts — OCCUPIED_HINT の詳細マスキング判定
  • backend/src/schema/booking_slot/object.ts — GraphQL リゾルバでのマスキング適用
  • backend/src/schema/booking_slot/mutation/publish_draft_booking_slots.ts — 一括公開(180 日ルールで NG の枠を飛ばす)
  • backend/src/schema/{public|private}_booking_slot/mutations/update_*_booking_slot.ts — handleUpdate*BookingSlot で 180 日ルール評価
  • shop/src/app/shops/[id]/bookingSlots/[slotId]/components/VisibilityField.tsx — 注釈の表示と、bookingOpensAt の既定値の切り替え

変更履歴 ​

日付変更PR
2026-06-03事前決済の 180 日ルールを、bookingOpensAt(無ければ現在時刻)から公演日までの日数で判定する形に変更。bookingOpensAt > startAt を禁止#21386
2026-05-28旧 visibility 列を削除#21452
2026-04-20visibility を isDraft / listing / bookingOpensAt の 3 列に分割。旧値との対応は DRAFT = isDraft: true、PUBLISHED = isDraft: false + LISTED、HIDDEN = isDraft: false + UNLISTED#20129
2025-11-05事前決済の 180 日ルールを追加#18173