データベース (ERD)
Generated by
prisma-markdown
default
ShopGroup
Properties as follows:
id:name:iconUrl:description:isPrepaidEnable:createdAt:updatedAt:
PhoneBlocklist
店舗グループ単位で管理する、予約申込をブロックする電話番号のリスト
Properties as follows:
id:shopGroupId:phoneNumber: 数字のみに正規化された電話番号(ハイフン等は除去して保存する)reason: 登録理由(任意・店舗内の管理用メモ)createdAt:updatedAt:
ShopGroupCorporateProfile
店舗グループの法人情報
Properties as follows:
id:shopGroupId:corporateNumber: 法人番号(13桁)name: 法人名address: 住所invoiceNumber: インボイス番号(T + 法人番号)createdAt:updatedAt:
Shop
Properties as follows:
id:publicId: 外部公開用の不変参照キー (21 文字 base62)。displayId はユーザーが変更できるため API キーには使えない。詳細は TYO-78。displayId:region:contract:name:description:catchCopy:iconUrl:address:schedule:paymentMethods:shopGroupId:createdAt:updatedAt:archivedAt: 閉店(論理削除)日時。null でない場合は閉店済みとして扱い、店舗管理者・メンバーからアクセス不可になる。gameMasterId:tel:contactEmail店舗への問い合わせ先として公開面やユーザー向けメールに掲載するメールアドレス TODO: 将来的にはnon-nullにして、必須項目にすることも検討する
reservationNotificationEmail: 店舗向けの予約通知メールを受け取るメールアドレス。未設定時は通知先なしとして扱うofficialUrl:businessHours:defaultReservationDeadlineDays:defaultReservationDeadlineTime:defaultCancellationDeadlineDays: デフォルトのキャンセル締切日が何日前に設定されるか。reservationCautions: 店舗レベルの「注意事項」の欄に表示される店舗からの文章needConfirmationReservationCautions: 店舗レベルの「注意事項」の欄に表示される店舗からの文章postBookingCautions: 店舗レベルの「予約確定後に表示する注意事項」の欄に表示される店舗からの文章。予約確定後にのみ表示される。未入力は nullreservationNoteInstructions: 店舗レベルの「店舗への連絡事項」の欄に表示される店舗からの指示文reservationNoteRequired: 店舗レベルの「店舗への連絡事項」への回答は必須かどうかrules:cancellationPolicy:googleAccessToken:googleRefreshToken:googleTokenExpiry:googleCalendarId:hideStaffAssignments:hideRoomInformationtrue の場合、ユーザー向け予約詳細画面で部屋情報(ShopRoom)を表示しない。 スタッフ/管理画面側では引き続き表示される。デフォルトは公開(false)。
isPluginIntegrationEnabledWordPress 予約プラグイン連携の契約フラグ。ZIP 配布表示・API キー発行可否・プラグイン API 有効/無効の単一判定軸。 API キーの有無で契約判定はしない(キーは失効・再発行されるため、契約とキーは別概念)。変更は admin(運営)のみ。
ShopApiKey
WordPress 予約プラグイン連携の店舗 API キー。 平文は発行時のレスポンスでのみ返し、DB には SHA-256 ハッシュ(hex)だけを保存する(DB 漏洩時に全店キーが漏れるのを防ぐ)。 再発行は「旧キーを即失効させて新キーを作る」1 操作。有効なキー(revokedAt IS NULL)は店舗ごとに高々 1 本。 失効済みレコードは監査(いつ発行・失効したか)のために残す。
Properties as follows:
id:shopId:keyHash: API キー平文の SHA-256 ハッシュ(hex 64 文字)。認証時はリクエストのキーをハッシュ化して照合する。createdAt:revokedAt: 失効日時。null が有効なキー。再発行・連携解除で失効させる。
ShopSocialLink
Properties as follows:
id:url:shopId:createdAt:updatedAt:
ShopGroupMember
Properties as follows:
id:shopGroupId:userId:displayName権限管理でのみ表示する氏名。人材管理用で、消費者向けサイトには一切露出しない。 User.name は本人が自由に変えられる消費者向けニックネームで人材の同定に使えないため、 グループ単位で別に持つ。ShopStaff.name(消費者に公開される GM 名)とも別物。 NULL 許容: User.name が null のユーザーが実在し、必須にすると移行時に入れる値がないため。 招待受諾時は氏名を持たない人にのみ入力を必須化する。
isGroupAdmin:order:createdAt:updatedAt:
ShopMember
Properties as follows:
id:shopId:shopGroupMemberId:shopStaffId:role:createdAt:updatedAt:
ShopStaff
Properties as follows:
id:shopId:name:iconUrl:order:createdAt:updatedAt:deletedAt:
ShopCatalog
Properties as follows:
id:publicId: 外部公開用の不変参照キー (21 文字 base62)。URL 等に貼っても安全。詳細は TYO-78。scenarioId:title:introduction:portraitImageUrl:landscapeImageUrl:shopId:minCapacityこのカタログを扱う公演で必要な最小の人数
- 未設定の場合には、募集人数固定(最大の人数Y人が必要)なカタログだと解釈される
- 設定されている場合には、募集人数可変(最小の人数X人〜最大の人数Y人が必要)なカタログだと解釈される
- scenario.minPeople とは独立に設定される。
maxCapacityこのカタログを扱う公演に参加できる最大の人数
- 最小の人数が未設定の場合には、最大の人数まで集める必要がある募集人数固定のカタログだと解釈される
- 最大の人数と最小の人数がいずれも未設定の場合には、ユーザーはこのカタログを即時予約の貸切公演・仮予約の貸切リクエスト枠に指定できないことが想定される
- scenario.maxPeople とは独立に設定される。
playMinutesこのカタログを扱う公演を開催するために確保する必要がある目安のプレイ時間
- 未設定の場合には、ユーザーはこのカタログを即時予約の貸切公演・仮予約の貸切リクエスト枠に指定できないことが想定される
- 目安のプレイ時間が即時予約の貸切公演・仮予約の貸切リクエスト枠の開始時刻から最大終了時刻までの時間を超過する場合には、ユーザーはこのカタログを指定できないことが想定される
- scenario.playMinutes とは独立に設定される。
comment:reservationCautions: カタログレベルの「注意事項」の欄に表示される店舗からの文章reservationNoteInstructions: カタログレベルの「店舗への連絡事項」の欄に表示される店舗からの指示文reservationNoteRequired:basePriceカタログの基本料金(円) - shopCatalogPriceに該当のruleがなければこの料金が適用される
- 未設定の場合には、ユーザーはこのカタログを即時予約の貸切公演・仮予約の貸切リクエスト枠に指定できないことが想定される 未設定と無料であることは区別される、無料であるならば値は未設定ではなく0にする
order:visibility:unpublishAt:createdAt:updatedAt:
ShopPricingRule
店舗が設定することができる価格ルール 例えば「平日料金」「土日祝料金」「特別価格」などのルールを定義し、 条件を満たす公演スロットに自動適用される。
Properties as follows:
id:shopId:ruleNameルール名(例: "平日料金", "土日祝料金", "10/21以降", "年末年始特別料金") ショップが自由に設定できる。
startAt: ルールの適用開始日endAt: ルールの適用終了日dayCategory曜日
- WEEKDAY: 平日(月〜金)に自動適用
- WEEKEND: 土日祝日に自動適用
priority: ルール適用の優先度、0が最優先createdAt:updatedAt:
ShopCatalogPrice
特定のルールが適用された時のカタログの価格を定義 ShopCatalogのbasePriceとは別に、ルールごとの価格を設定できる 例: カタログAの基本料金は5000円だが、「土日祝料金」ルールでは6000円
Properties as follows:
id:shopCatalogId:shopPricingRuleId:priceこのルールが適用された時の価格(円) 例: 土日祝は通常より1000円高い価格を設定
createdAt:updatedAt:
ShopStaffCatalogSkill
Properties as follows:
id:shopStaffId:shopCatalogSkillId:createdAt:updatedAt:
ShopCatalogSkill
Properties as follows:
id:skill:shopCatalogId:hidden:requiredStaffCount:createdAt:updatedAt:
ShopHeaderImageUrl
Properties as follows:
id:url:shopId:createdAt:updatedAt:
BookingSlot
店舗の予約スロット このclassの配下に PublicBookingSlot と PrivateBookingSlot がある この Slot と並列に ExternalBookingSlot がある
Properties as follows:
id:publicId: 外部公開用の不変参照キー (21 文字 base62)。URL 等に貼っても安全。詳細は TYO-78。shopId:reservationDeadlineこのスロットの予約締切日 この予約締切時間をもって、開催判断が行われる
cancellationDeadline: ユーザーの予約がキャンセル可能な締め切り日時canceledAt公開公演で、deadline までに予約が埋まらない場合、自動でキャンセルされる(canceledAtの値は入る) リクエスト貸切公演で、店側がdeclineした場合もキャンセルされる その他運営都合でキャンセルが可能。 reservation が一つも入っていない場合は、 canceledAt で削除するのではなく、物理削除で対応
cancelReason:startAt:isDraft一括公開ワークフロー維持のマスターフラグ true の間は問答無用で非公開 publishBookingSlots mutation により false 化される
listing情報開示レベル(一覧掲載と詳細閲覧権限を統合した 1 軸) UNLISTED: 一覧×・詳細はリンク保持者のみ / OCCUPIED_HINT: 一覧に「予定あり」のみ / LISTED: 一覧○・詳細○
bookingOpensAt予約開始日時 null / 過去 = 予約可、未来 = 予約受付前(ティーザー表示)
paymentMethod決済方法 これは一度でも予約を受け付けした後は変更してはならない。
linkToken: 認証用トークン。isHidden や privateBookingSlot で一人目が予約した時等に使うmemo: 公演に対して店舗がかけるメモreservationCautionsslotごとの「注意事項」の欄に表示される店舗からの文章 デフォルト値よりslot生成時に設定される。
postBookingCautionsslotごとの「予約確定後に表示する注意事項」の欄に表示される店舗からの文章。予約確定後にのみ表示される。未入力は null デフォルト値よりslot生成時に設定される。
reservationNoteInstructionsslotごとの「店舗への連絡事項」の欄に表示される店舗からの指示文 デフォルト値よりslot生成時に設定される。
reservationNoteRequiredslotごとの「店舗への連絡事項」への回答は必須かどうか デフォルト値よりslot生成時に設定される。
autoCancellationCheckedAt: reservationDeadline 時点で予約が埋まっていない場合、自動でキャンセルされるが、そのチェックが行われた時刻。googleCalendarEventId:shopRoomIdこのスロットが公演される店舗の部屋 ここには確定情報が入り、部屋A or 部屋B みたいな状態は入らない。 部屋機能を特に使わない店舗はここを空のままにしておく
createdByUserIdこの枠を作成した店舗管理の操作者(User.id)。複製で作られた枠は複製を実行した人が入る。 記録を始める前に作られた枠は null。 User への外部キーは張らない。退会で参照を空にすると、記録を始める前の枠と区別できなくなる
createdBySystemAdmin作成者が運営(システム管理者)として操作したか。 運営かどうかは後から変わるため、操作した時点の立場を保存する
lastEditedByUserId店舗管理から最後に枠を手動変更した操作者(User.id)。外部キーを張らない理由は createdByUserId と同じ。 予約の操作と自動処理(Google カレンダー連携等)では変わらない
lastEditedBySystemAdmin: 最後に手動変更した人が運営(システム管理者)として操作したかlastEditedAt店舗管理から最後に枠を手動変更した日時。 updatedAt は予約の操作や自動処理でも進むため、人の操作の日時としては使えない
createdAt:updatedAt:
PublicBookingSlot
店舗の公開募集予約スロット(オープン公演)
Properties as follows:
id:bookingSlotId:endAt:minCapacityこのオープン公演が開催されるのに必要な人数
- 未設定の場合には、受付可能な最大の人数まで集めることで開催できると解釈される
maxCapacityこのオープン公演で受付可能な最大の人数
- 無限人で開催することは現実的ではないため、この値は必須となっている
price:shopCatalogId:createdAt:updatedAt:
PrivateBookingSlot
店舗の貸切予約スロット(即時予約の貸切公演・仮予約の貸切リクエスト枠)
Properties as follows:
id:bookingSlotId:maximumEndAt: 最長の場合の終了時刻、これを超える時間のカタログは予約できないshopCatalogId[shopCatalogId] が指定されているときは、このslotは特定のカタログ専用のスロットになる。 指定されていないときは、[staffAssignments]からカタログが決定される。 また初回予約が入った瞬間に確定される。
catalogFixedByShopAt店舗が作品を事前指定した日時
- null でなければ、この枠の [shopCatalogId] は店舗による事前指定であり、 予約が全てキャンセルされても [shopCatalogId] をリセットしない(作品固定のまま再募集する)
- null であれば、[shopCatalogId] は予約時にユーザーが選んだ作品(全キャンセルでリセットされる)
shopConfirmedAt仮予約の貸切リクエスト枠が店舗によって承認された時間 (即時予約の貸切公演の場合は、この値がnullであると変なので予約時の時刻を入れることを想定している)
- これがnullであれば、仮予約の貸切リクエスト枠は店舗からの承認がないと解釈される
needShopConfirmation即時予約の貸切公演か仮予約の貸切リクエスト枠かを区別するためのフラグ
- これが true であれば、仮予約の貸切リクエスト枠であると解釈される (このとき店舗の承認が必要になり、
shopConfirmedAtがnullでなければ承認されたと解釈される)
- これが true であれば、仮予約の貸切リクエスト枠であると解釈される (このとき店舗の承認が必要になり、
shopConfirmationMessage店舗からの確認メッセージ(許可・拒否時に店舗が入力する任意メッセージ) ユーザーへの通知メールに含まれる
createdAt:updatedAt:
ShopEventSlot
店舗で公演以外のカレンダー情報を管理するモデル
Properties as follows:
id:shopId:title:startAt:endAt:createdAt:updatedAt:
ExternalBookingSlot
マダミス.jp shops を使ってない店舗の予約スロット 基本的にスクレイピングして取得したデータ
Properties as follows:
id:scenarioId:shopId:startAt:duration:price:url:currentCapacity:createdAt:updatedAt:
ShopRoom
Properties as follows:
id:publicId: 外部公開用の不変参照キー (21 文字 base62)。URL 等に貼っても安全。詳細は TYO-78。shopId:name:order:createdAt:updatedAt:
ShopCatalogRoom
カタログと部屋の紐付け(どの部屋でどのカタログが公演可能か)
Properties as follows:
id:shopCatalogId:shopRoomId:createdAt:updatedAt:
BookingSlotStaffAssignment
Properties as follows:
id:bookingSlotId:shopStaffId:
Reservation
予約情報 将来的に予約時質問等を追加することを考えると、ReservationPaymentとは別に Reservation クラスを持つのは理にかなっている。
Properties as follows:
id:bookingSlotId:customerId:sourceこの予約が ユーザーから / 店舗から 入ったかを示すフラグ 店舗から入った場合、仮に事前決済slotで決済完了していなくても、自動キャンセルされない。
reservationNotes: 「店舗への連絡事項」への回答項目usedCouponId:createdAt:updatedAt:canceledAt:confirmationEmailSentAt:campaignId: 予約ごとに一意のトークン。非会員向けメールリンクのゲストアクセス(?reservation={campaignId})や、ログイン後の予約紐付け(linkGuestReservation)に使用される。sourceUrl: 予約が発生した外部ページの URL(WP プラグイン設置ページ等)。ゲスト決済導線の「サイトに戻る」リンクに使う。書き込み時に http(s) のみ許可sessionRecordSkippedAt: この予約情報に対するプレイ記録をつけないことが意思表明された場合に値を入れる
ReservationTransaction
予約の変更履歴 予約の変更があった場合、その時点の予約状態(絶対値)を記録する transaction は未確定、確定、キャンセルの3つの状態を持つ。一つのReservationに対して、常に一つ以上存在し、また未確定は常に1つ以下存在することが想定される。 未確定または確定のtransactionが存在する場合、その [totalQuantity] の在庫は確保されなければならない。 状態遷移は 未確定 -> 確定, 未確定 -> キャンセル の2パターンである。
Properties as follows:
id:reservationId:totalQuantity: その時点の予約人数(絶対値)totalAmountこのTransactionでの支払い金額。 クーポンが考慮されている。 事前決済の場合は、基本的には Payment.amount の初期値と一緒になる。
source: この変更を行ったソース(USER: ユーザー、SHOP: 店舗スタッフ、UNKNOWN: 不明)createdAt:updatedAt:acceptPayment顧客からの支払いを受け付けるかどうか。 これが true の時は、支払いを受け付ける。
paymentCheckoutExpiresAt支払い期限 この値を過ぎていて、かつpaymentが存在しない場合は、自動でキャンセルされる。 この値がnullであれば、支払い期限がなく、自動でキャンセルされないことを意味する。 [acceptPayment] が true の時のみ、この値は意味をなす。 [paymentCheckoutExpiresAt]、[activatedAt]、[invalidatedAt]のいずれかは必ず非nullであることが想定される。(永久に在庫を確保することは許されない。)
activatedAt決済等が確定し、このtransactionが有効になった日時。 決済が不要なTransactionの場合(現地決済の予約や、店舗管理からの予約)は、最初からこの値は入っている。 店舗管理から予約を入れた場合、acceptPayment=true,payment=null,activatedAt=予約作成日時 となるパターンがあることに注意。
invalidatedAt決済失敗/タイムアウト等でこのトランザクションが無効化された日時。 事前決済のslotをユーザー側から予約した時のみ値が入りうる。
Customer
Properties as follows:
id:shopGroupId:userId:name:email:phoneNumber:memo:createdAt:updatedAt:
User
ウズアカウントを持っているユーザー
Properties as follows:
id:firebaseUid:name:iconUrl:twitterId:description:termsAcceptedAt:createdAt:updatedAt:
UserContact
ユーザーの連絡先情報
Properties as follows:
id:userId:name:email:phoneNumber:stripeCustomerId: Stripe Customer ID(カード情報を保存して次回以降の決済で再利用するために使用)fincodeCustomerId: fincode Customer ID(カード情報を保存して次回以降の決済で再利用するために使用)createdAt:updatedAt:
Scenario
Properties as follows:
id:scenarioGlobalId:title: 作品タイトルtitleFurigana: 作品タイトル(ふりがな)portraitImageUrl: タテ型作品キービジュアルURLlandscapeImageUrl: ヨコ型作品キービジュアルURLminPeople: プレイ人数(最小)maxPeople: プレイ人数(最大)manCount: プレイ人数(男性キャラクター)womanCount: プレイ人数(女性キャラクター)otherCount: プレイ人数(その他キャラクター)playMinutes: プレイ時間(分)price料金体系
具体的な価格ではない
gameMasterRequiredゲームマスターの有無
nullの場合は不明として扱う
releasedAt: 公開日releasedAtAccuracy: 公開日(年月日のいずれまで正確か)introduction: ストーリー/物語のあらすじexplanation: 作品の説明videoUrl: 動画埋め込みURLflagHideRating: 表示設定(評価の数値が表示されないようにする)flagHideReview: 表示設定(感想一覧が表示されないようにする)flagHideTags: 表示設定(シナリオタグの一覧が表示されないようにする)flagRestrictedGameMaster表示設定(管理者以外のゲームマスターを非表示にする)
trueならばシナリオ情報の管理者以外のGM情報が取得されない ただしスクレイピングしている公演情報は制限していないため、そこでは管理者以外のGMの情報が表示されることがある
制限するのは作品ページ側の表示だけ(権利者が意図しない GM を作品ページに並べさせない)。 GM が自分のプロフィールで「依頼されれば公演できる」と対応可能作品に載せることは制限しない。 GM 側の保存(rest/game_master_compose、GraphQL の createPlayableScenario)や GM 詳細ページで このフラグを見て弾かないこと(2026-10 に一度入れかけて取りやめた)
flagExcludeFromMadaanマダアン(毎月マダミスアンケート)の集計対象から除外する
主催・運営関係者の作品など、企画の趣旨上ランキングに載せない作品に立てる。 除外は集計時に効くので、既に凍結済みの MadaanResult は遡って変わらない。
isEditedByUser:createdAt:updatedAt:
ScenarioRightsHolder
Properties as follows:
id:scenarioId:userId:requestedAt:acceptedAt:rejectedReason:confirmationImageUrl:createdAt:updatedAt:
ScenarioWebLink
- シナリオの公式ページへのリンク
Properties as follows:
id:scenarioId:url:
ScenarioTagCategory
Properties as follows:
id:name:createdAt:updatedAt:
ScenarioTag
Properties as follows:
id:scenarioTagCategoryId:name:createdAt:updatedAt:
ScenarioTagRelation
Properties as follows:
id:scenarioId:scenarioTagId:createdAt:updatedAt:
ScenarioTagRelationVote
Properties as follows:
id:scenarioTagRelationId:userId:vote:createdAt:updatedAt:
Author
Properties as follows:
id:name:nameFurigana:iconUrl:description:userId:playerMypageLinkEnabled:createdAt:updatedAt:
ScenarioPosition
Properties as follows:
id:position:scenarioId:createdAt:updatedAt:
ScenarioAuthor
Properties as follows:
id:scenarioPositionId:authorId:createdAt:updatedAt:
GameMaster
Properties as follows:
id:name:iconUrl:displayId:comment:providableArea:reservationLink:email:userId:createdAt:updatedAt:
GameMasterPhoto
Properties as follows:
id:gameMasterId:photoUrl:
PlayableScenario
Properties as follows:
id:comment:fee:priority:reservationLink:scenarioId:gameMasterId:createdAt:updatedAt:
SessionRecord
プレイ記録
- プレイ未確定状態(playedConfirmedAtがnullである※プレイ予定として扱う予定だったが現状必要なくなった)
- プレイ済み状態(playedConfirmedAtがnullでない)
- レビュー済み状態(playedConfirmedAtがnullでないかつrecommendedCommentあるいはspoilerCommentがnullでない)
Properties as follows:
id:userId:scenarioId: 遊んだ作品reservationId: 紐付いている予約情報shopId: 遊んだ店舗privateSessionRecord を他人から閲覧できない状態にする。 シナリオの Rating の計算には現状利用される。
hideRatingSessionRecord の項目のうち、Rating について他人から表示されないようにする。 [private] が true であれば、この値は無視される。
hidePlayedAtSessionRecord の項目のうち、プレイ日時について他人から表示されないようにする。 これが false でも 年月までしか表示されない。 [private] が true であれば、この値は無視される。
rating評価(0〜5) ⚠️非公開にすることができる
playedAtプレイ時期として記録する日時、ユーザーが選んで入力する ⚠️プライバシーを考慮するためにXXXX年Y月という形式で表示する ⚠️非公開にすることができる
- プレイ未確定状態の場合、この値はプレイ予定の日時として表示される
- プレイ済み状態の場合、この値はプレイ時期として表示される
playedConfirmedAt: プレイ済み状態に変更された日時privateMemo自分用メモ ⚠️許可なく公開されることはない
recommendedComment: おすすめコメントspoilerComment: ネタバレコメントcreatedAt:updatedAt:
SessionRecordPerson
プレイ記録("ゲームマスター"・"プレイヤー")用に作成される人物情報 ⚠️自分が作成した人物情報は他人から参照されることがないようにしている
Properties as follows:
id:name:iconUrl:belongingUserId:userId:createdAt:updatedAt:
SessionRecordPlayer
Properties as follows:
id:sessionRecordId:sessionRecordPersonId:
SessionRecordGameMaster
Properties as follows:
id:sessionRecordId:sessionRecordPersonId:
SessionRecordFavorite
Properties as follows:
id:userId:sessionRecordId:createdAt:updatedAt:
InterestedScenario
Properties as follows:
id:userId:scenarioId:createdAt:updatedAt:
MadaanAnswer
マダアン(月刊マダミスアンケート)の 1 回答 = 1 シナリオ。 投票は対象月の翌月 1〜7 日に受け付けるため、createdAt の月は targetMonth の翌月になる。
Properties as follows:
id:xId:scenarioId:type:targetMonth遊んだ月(YYYY-MM)。投票した月ではない。 「あなたが N 月に遊んだ作品」という設問への回答なので、集計も結果表記もこの月をそのまま使う。
createdAt:updatedAt:
MadaanResult
マダアンの発表単位。発表時点の集計を凍結する。
集計自体はオンデマンドでも性能上問題ないが、公開済みの結果は告知・被リンクから 参照される確定情報なので、後から動いてはいけない。mergeScenarios による scenarioId の付け替え・除外シナリオの追加・DB migration はいずれも過去月の順位を変えうる。
Properties as follows:
id:targetMonth: 対象月(YYYY-MM)。MadaanAnswer.targetMonth と同じ概念voterCount回答者数。重複投票を除外した後のユニーク xId 数
flagExcludeFromMadaan の除外はランキングにだけ効かせ、この参加人数からは引かない (除外シナリオへの投票も回答そのものは実在するため)。現行スプレッドシートの 「回答人数」= countUNIQUE(データ!A:A) と同じ数え方。
answerCount: 回答件数。重複投票を除外した後の行数。voterCount と同じくシナリオ除外の前で数えるaggregatedAt: 集計を確定した時刻。「いつ時点の集計か」を説明可能にするpublishedAt: null = 未公開(admin プレビューのみ)createdAt:updatedAt:
MadaanResultItem
発表 1 件ぶんの行。票が入った作品を順位付きで全部持つ。
上位だけでなく全順位を保存するのは、Google Sites 時代に部門ごとの別スプレッドシート (「全票」×4)とスライド(×4)へ手作業でコピーしていた内容を、発表ページの中に そのまま出せるようにするため。
Properties as follows:
id:resultId:type:rank: 同率を潰さない順位(1,2,2,4...)voteCount:scenarioId:featured: 発表ページの上位として大きく載せるか。false でも全票一覧には出る
ScenarioPlayFormat
Properties as follows:
id:scenarioId:playFormat:
InvitationLink
Properties as follows:
id:token:expiresAt:shopGroupId:shopId:createdAt:updatedAt:
BookingSlotNotification
Properties as follows:
id:bookingSlotId:subject:body:sentAt:createdAt:updatedAt:
CustomerNotification
お客さんに対して送ったメールの履歴
Properties as follows:
id:customerId:reservationId:subject:body: htmlメールの場合は、htmlを入れるsendgridStatus: メールの送信ステータス(Event Webhookで更新)failedDetail失敗詳細(SendGrid Event Webhook 由来の構造化フィールド)。 型は src/lib/emailFailureClassification.ts の EmailFailureDetail を参照。
failedDetail->>'classification' = 'Reputation'のように構造化検索できる。sendgridMessageId: SendGridのメッセージID(x-message-id)sentAt: SendGridキューに入った日時sendgridDeliveredAt: 実際に配信された日時(Event Webhook)shopFailureNotifiedAt予約者向けメールが送信失敗(FAILED_ADDRESS_INVALID / FAILED_REJECTED)となった際に、店舗管理者へ失敗通知メールを送った日時。 SendGrid Webhook が同一イベントを重複送信した場合や、失敗後に別イベントが来た場合の二重送信を防ぐために使う。
createdAt:updatedAt:
ShopNotification
Shopに対して送ったメールの履歴
Properties as follows:
id:shopId:slotId:subject:body: htmlメールの場合は、htmlを入れるsendgridStatus: メールの送信ステータス(Event Webhookで更新)failedDetail失敗詳細(SendGrid Event Webhook 由来の構造化フィールド)。 型は src/lib/emailFailureClassification.ts の EmailFailureDetail を参照。
sendgridMessageId: SendGridのメッセージID(x-message-id)sentAt: SendGridキューに入った日時(従来のsentAtと同じ役割)sendgridDeliveredAt: 実際に配信された日時(Event Webhook)createdAt:updatedAt:
BookingSlotBilling
Properties as follows:
id:startedAt:totalGuests:totalAmount:prepaidTransferAmount:onSiteCommissionAmount:monthlyShopBillingId:createdAt:updatedAt:
MonthlyShopBilling
Properties as follows:
id:shopId:year:month:fixedCommissionAmount:plan:paymentAgencyFee:createdAt:updatedAt:
Payment
=============================== ReservationTransactionに紐ずくPayment情報を持つ。 将来的にはこの Payment に紐付いて StripePayment, GMOPayment と増やしていく想定だが、現状はStripeしか対応せず、適切に抽象化するのは困難なため、一旦Paymentに直接stripeIdを保存する。 正常系では、REFUND は行われない。 ここで CanceledAt や lastRefundedAt があっても、それは店舗等の手動でのキャンセルや返金等も考えられる。 Paymentモデルはあくまで決済状況を保持するためのモデルであり、これらのステータスが、ReservationやBookingSlotのStatusには影響出ないように独立して考える。
Properties as follows:
id:reservationTransactionId:paymentProvider決済プロバイダー(Stripe または fincode) 既存データは全て Stripe なのでデフォルトは STRIPE
externalPaymentIdマダミス.jp では外部決済サービスを必ず使う。 将来的には paymentProvider カラムを追加して、Stripe以外の決済サービスも扱えるようにする可能性がある。 その際、各決済サービスでのPaymentモデルに該当するObject の ID を保存するカラムとして利用する。 決済サービス横断のカラムなので、uniqueは保証されないが、実害が出てくるまで、uniqueをつけておく。
stripe: 管理画面でいう取引オブジェクトのid https://docs.stripe.com/api/payment_intents
stripePaymentIntentClientSecret===== Stripe 関連フィールド ===== Payment Element用のクライアントシークレット フロントエンドで決済フォームを表示するために使用
fincodeAccessId===== fincode 関連フィールド ===== fincode の Access ID 決済実行時に使用するアクセスID
amount決済金額 例えば、オーソリの金額から決済の金額が減額された場合、amountは決済時の金額に書き変わる。 一度決済されたら金額が書き変わることなく、それ以降の返金は refundedAmount に加算される。
currency: マダミス.jp の仕様上、この値は常に JPY になるcaptureMethod決済方法によって変わる。カードの場合は MANUAL、PayPayの場合は AUTOMATIC。 captureBefore が入っているかどうかで判断可能なので、captureMethod は不要。
authorizedAtオーソリ完了日時 決済実行前に、Paymentモデルは作成されるので初期状態は null。 Webhookでオーソリ成功時に設定される
capturedAt: capture 完了日時captureBeforeキャプチャの期限日時 Stripeのcapture_beforeパラメータから取得 通常はオーソリから7日間(最大30日間)
canceledAtAUTHORIZED 状態で Payment をキャンセルしたら canceledAt が入る 一度でも CAPTURED したら canceledAt 値が入ることはなくなる
refundedAmount今まで返金した金額の総額 オーソリからの減額決済はこれに含まれない。
lastRefundedAt最後に返金した日時 最終日時以外は、重要度が低いので持たない。
paymentMethodType決済方法 過去データのデフォルトはカードだが、将来的には、default値は削除したい。
createdAt:updatedAt:
Coupon
=============================== クーポンテーブル
Properties as follows:
id:code:title: クーポンのタイトル、ユーザーに表示される。internalDescription: クーポンの管理用説明、ユーザーには表示されない。discountType: 割引タイプ。UNIT=各人から円割引、TOTAL=総額から円割引、PERCENTAGE=N% 割引discountAmount: 円割引額。UNIT/TOTAL のときのみ値あり(PERCENTAGE では NULL)discountPercentage: 割引率(%、5〜100、5 の倍数)。PERCENTAGE のときのみ値ありapplyToCount: 適用人数。UNIT/PERCENTAGE で意味あり: NULL=予約全体に適用、N=N 名分のみに適用。TOTAL は人数非依存のため常に NULLvalidFrom:validUntil:issuedByUserId:maxUsageCount:perUserUsageCount:disabledAt無効化日時。非 null の場合、以降の新規クーポン適用を弾く(既存予約には影響しない)。 店舗がトグルで無効化⇄再有効化を切り替える(再有効化時は null に戻す)。物理削除ではないので 既存予約の usedCoupon リレーション・金額再計算・利用履歴は一切壊れない。
createdAt:updatedAt:
CouponScopeShop
=============================== クーポン利用範囲テーブル(店舗対象)
Properties as follows:
id:couponId:shopId:createdAt:
CouponScopeShopCatalog
=============================== クーポン利用範囲テーブル(カタログ対象)
Properties as follows:
id:couponId:shopCatalogId:createdAt:
UserSessionRecordCommentMute
プレイ記録のコメントに関するミュート設定
Properties as follows:
id:sourceUserId: このミュート設定を行ったユーザーのIDtargetUserId: このミュート設定の対象のユーザーのIDcreatedAt:
Survey
Survey の本体メタデータ statusカラムは持たず、starts_at/ends_atで有効期間を管理する
Properties as follows:
id:slug: 公開URL用途など。任意title: 表示名description: 説明startAt: 有効期間開始。NULLなら非公開endAt: 有効期間終了。NULLなら制限なしcreatedAt:updatedAt:
SurveyVersion
Survey のバージョン 質問定義のスナップショット単位。公開後の変更や履歴を安全に扱うために導入
- publishedAt IS NULL: 下書き
- publishedAt IS NOT NULL: 公開済み(最も新しい publishedAt のものがアクティブ)
Properties as follows:
id:surveyId:publishedAt: 公開時にセット。NULLなら下書き、最も新しいものがアクティブcreatedAt:updatedAt:
SurveyQuestion
Survey Version に属する質問定義
Properties as follows:
id:surveyVersionId:keyversion内で一意。API/集計で使う安定キー version間での同一性の担保にはこのkeyを使う。
title: 質問文description: 補足説明type:isRequired: 必須かどうかposition: 表示順config: 型ごとの追加設定(minLen, maxLen, min, max, maxSelections, allowedScenarioIds等)createdAt:updatedAt:
SurveyResponse
1回の回答提出(ヘッダ) レコード作成 = 提出完了
Properties as follows:
id:surveyVersionId:respondentId: ログインユーザーID。未ログインの回答ならNULLcreatedAt:updatedAt:
SurveyAnswer
Response × Question の回答(1質問=1行) scenario_single は value_scenario_id に、scenario_multi は SurveyAnswerScenario に保存
Properties as follows:
id:responseId:questionId:valueBoolean: type=BOOLEAN で使用valueString: type=STRING で使用valueInteger: type=INTEGER で使用valueScenarioId: type=SCENARIO_SINGLE で使用createdAt:updatedAt:
SurveyAnswerScenario
scenario_multi 専用。複数選択の回答値を保持
Properties as follows:
answerId:scenarioId:createdAt:
ReceiptIssueLog
=============================== 領収書発行履歴
領収書発行 (generateReceiptDownloadUrl mutation) の履歴。 同一 payment に対する発行回数のカウントと、再発行表記 (連番 + 「再発行日」) を実現するための補助テーブル。
shop (店舗管理画面) からの領収書発行導線は削除済み (お客様にマイページからセルフ発行してもらう運用)。 よってこのテーブルは「お客様による全領収書発行」のログとして機能する。
recipientName は再生成時の根拠データとして平文保存する。サニタイズ済み。
Properties as follows:
id:paymentId:issuanceCount: 同一 payment 内での発行回数 (1 から開始)recipientName: 入力された宛名 (サニタイズ済み、写し再生成の根拠データ)issuedAt:issuedByUserId: 発行操作したユーザー (お客様本人)