Rezervace
Tato stránka slouží jako checklist pro testery pro přejmenovaný rezervační modul (větev feature/reservations-rework). Cíle: ověřit nové chování, zkontrolovat kompatibilitu s legacy stavem a odchytit regrese před merge na master.
Popis chování (referenční dokument) je v Rezervace. Zde najdete co se změnilo, jak to otestovat a jaké podmínky očekávat.
Co se v tomto kole změnilo
Section titled “Co se v tomto kole změnilo”Nové entity a tabulky
Section titled “Nové entity a tabulky”| Tabulka / Model | Stav | Účel |
|---|---|---|
pub_tables / PubTable | Nová | Stoly v podniku jako first-class entita (nahrazuje JSON capacity) |
alternate_reservations / AlternateReservation | Nová | Samostatná tabulka pro náhradníky (dříve příznak is_alternate=1 na TeamReservation) |
standing_reservations.pub_table_id | Nový sloupec | Preferovaný stůl pro autorezervaci |
team_reservations.pub_table_id | Nový sloupec | Konkrétně přiřazený stůl |
team_reservations.standing_reservation_id | Nový sloupec | Odkaz na nadřazenou autorezervaci |
team_reservations.confirmed_at | Nový sloupec | Čas potvrzení standing rezervace |
team_reservations.notification_72h_sent_at | Nový sloupec | Byl odeslán request o potvrzení |
team_reservations.notification_48h_sent_at | Nový sloupec | Došlo k auto-zrušení nepotvrzené rezervace |
standing_reservations.consecutive_unconfirmed_count | Nový sloupec | Počítač nepotvrzených rezervací (3 = trvalé zrušení) |
Nové / upravené chování
Section titled “Nové / upravené chování”- Kapacita = počet stolů, ne seats/smallseats. Nový výpočet kapacity v
Event::getCapacity()aresolveTableLayout()na základěPubTable. - Automatické přiřazení stolu při rezervaci (pokud má podnik PubTables).
- Overbooking stolu (max_capacity > optimal_capacity) vyžaduje potvrzení hráče.
- Náhradníci (
AlternateReservation) – samostatná tabulka, bez přiřazeného stolu, promuje se naTeamReservationpři uvolnění místa. - Standing reservations confirmation flow – 72 h před eventem se žádá o potvrzení, 48 h před eventem se nepotvrzené ruší, 3× nepotvrzeno = trvalé zrušení autorezervace.
- Soft deletes na všech rezervačních entitách – deaktivace stolu nemaže historická data.
- Migrace z legacy
capacityJSON – při nasazení se vygenerujíPubTablezáznamy (velké6/8, malé2/4).
Nové scheduled commandy
Section titled “Nové scheduled commandy”| Command | Frekvence | Co dělá |
|---|---|---|
reservations:backfill | hodinově (:01) | Pro každou aktivní StandingReservation zajistí právě jednu budoucí TeamReservation |
reservations:remind | denně 10:00 | Pošle remindery na zítřejší eventy (beze změny, ale ověřit) |
reservations:require-confirmation | hodinově | 72 h před eventem pošle týmům s autorezervací žádost o potvrzení |
reservations:cancel-unconfirmed | hodinově | 48 h před eventem zruší nepotvrzené standing rezervace |
Příprava testovacího prostředí
Section titled “Příprava testovacího prostředí”Testovací instance
Section titled “Testovací instance”- Web: https://beta.chytrykviz.cz
- Admin: https://admin.beta.chytrykviz.cz
- Mailhog (odchozí e-maily): http://159.89.0.170:8025/
Seed dat
Section titled “Seed dat”Seeder ReservationSeeder vytvoří:
- Klasické rezervace na dokončené eventy (podle výsledků)
- 3–6 rezervací na aktivní eventy, z toho 5+ jako
AlternateReservation - 2–3 standing reservations per aktivní blueprint + generované
TeamReservation - Rezervace na speciály (4–6, z toho 5+ jako alternate)
docker-compose exec fpm php artisan migrate:fresh --seed# Nebo jen rezervace:docker-compose exec fpm php artisan db:seed --class=ReservationSeeder1 · PubTable management
Section titled “1 · PubTable management”1.1 Přidání stolů
Section titled “1.1 Přidání stolů”- Admin → Podniky → detail → sekce Stoly.
- Klik na Přidat stoly, zadejte počet (např. 5).
- Očekávání:
- Vzniklo 5 nových stolů s
optimal_capacity = 4,max_capacity = 6,sort_orderinkrementované. - Všechny mají
is_active = true. - Stoly se zobrazí v tabulce seřazené podle
sort_order.
- Vzniklo 5 nových stolů s
1.2 Úprava stolu
Section titled “1.2 Úprava stolu”- Klik na ikonu tužky u řádku stolu.
- Upravte poznámku, optimal/max kapacity, přepněte
is_active. - Očekávání: změna se okamžitě promítne do tabulky, neaktivní stůl se přeskočí při dalším přiřazení.
1.3 Smazání stolu – blokace
Section titled “1.3 Smazání stolu – blokace”- Nastavte stůl, který má přiřazenou budoucí
TeamReservationnebo aktivníStandingReservation. - Klik na ikonu koše.
- Očekávání: notifikace „Nelze smazat stůl” a akce se zruší (
action->halt()).
1.4 Smazání stolu – povolené
Section titled “1.4 Smazání stolu – povolené”- Stůl bez budoucích rezervací/autorezervací.
- Klik na koš → potvrdit.
- Očekávání:
- Soft delete (
deleted_atnení null). - Na historických
TeamReservationzůstávápub_table_id– relationpubTable()->withTrashed()ji stále vrátí.
- Soft delete (
1.5 Migrace z legacy capacity
Section titled “1.5 Migrace z legacy capacity”- Ověřte, že po spuštění migrace
2026_03_23_182648_update_pub_tables_rename_name_to_note_and_seed_from_capacitymají podniky scapacity = [5,5,5](tří-složkový JSON) vygenerovány odpovídajícíPubTablezáznamy. - Velké (
seats) →optimal=6, max=8, malé (smallseats) →optimal=2, max=4.
2 · Výběr stolu při rezervaci
Section titled “2 · Výběr stolu při rezervaci”2.1 Happy path – ideální stůl existuje
Section titled “2.1 Happy path – ideální stůl existuje”- Podnik s PubTable
optimal=4, max=6(volný). - Web: vytvořit rezervaci se 4 hráči.
- Očekávání:
- Rezervace vytvořena,
pub_table_idvyplněno. - E-mail Potvrzení rezervace v Mailhogu.
- Kapacita eventu klesla o 1 stůl.
- Rezervace vytvořena,
2.2 Overflow – vyžaduje potvrzení
Section titled “2.2 Overflow – vyžaduje potvrzení”- Všechny volné stoly mají
optimal_capacity < počet hráčů, ale existuje stůl smax_capacity >= počet hráčů. - Submit rezervace.
- Očekávání:
- Stránka se vrátí s dialogem / zprávou
confirm_overflow(obsahuje optimal/max/players). - Po submitu s
confirmed=1se stůl přiřadí (nejbližší podle |optimal − players|).
- Stránka se vrátí s dialogem / zprávou
2.3 Žádný stůl se nevejde
Section titled “2.3 Žádný stůl se nevejde”- Všechny volné stoly mají
max_capacity < počet hráčů. - Submit rezervace.
- Očekávání: flash
dangerzpráva „K dispozici jsou jen stoly s maximální kapacitou X hráčů…“.
2.4 Kapacita vyčerpaná → náhradník
Section titled “2.4 Kapacita vyčerpaná → náhradník”- Všechny stoly obsazené (capacity = 0).
- Submit rezervace.
- Očekávání:
- Vzniká záznam v
alternate_reservations(ne vteam_reservations). - Flash: „Váš tým jsme zaregistrovali jako náhradní…”.
- E-mail ReservationAlternateConfirmation (ne Confirmation!).
pub_table_idjeNULL.
- Vzniká záznam v
2.5 Restore soft-deleted rezervace
Section titled “2.5 Restore soft-deleted rezervace”- Tým vytvoří rezervaci, zruší ji (soft delete), a poté znovu vytvoří se stejným
team_idaevent_id. - Očekávání:
- Nevznikne duplicitní záznam, původní se obnoví (
restore()). - Pokud má původní
pub_table_id, který je obsazený jiným týmem, systém vybere nejbližší volný stůl. - Odejde nová e-mailová notifikace z
restored()hooku.
- Nevznikne duplicitní záznam, původní se obnoví (
2.6 Duplicitní tým v event / v podniku
Section titled “2.6 Duplicitní tým v event / v podniku”- Anonymní: zkusit rezervaci se stejným
team_name+ stejný event. - Očekávání: flash
danger„Tento tým už má aktivní rezervaci”. - Zkusit
team_nameexistující v jiném týmu stejného podniku. - Očekávání: flash
dangers nabídkou přihlášení (HTML se strongem a ul).
2.7 Legacy podnik bez PubTable
Section titled “2.7 Legacy podnik bez PubTable”- Podnik, kde nebyla spuštěna migrace stolů (nemá
PubTable). - Očekávání:
- Kapacita se počítá ze sumy
capacityminus existující rezervace. - Rezervace se vytvoří s
pub_table_id = NULL. - Systém nevyžaduje potvrzení overflow (tento flow je jen pro PubTable).
- Kapacita se počítá ze sumy
2.8 Online rezervace vypnuté
Section titled “2.8 Online rezervace vypnuté”- Podnik s
online_reservations = false. - GET
/rezervace/{event}na blueprint event. - Očekávání:
abort(401)„Online rezervace pro tento podnik nejsou povoleny”. Speciály tímto nejsou blokovány.
3 · AlternateReservation (náhradníci)
Section titled “3 · AlternateReservation (náhradníci)”3.1 Potvrzení pozvánky – dostupná kapacita
Section titled “3.1 Potvrzení pozvánky – dostupná kapacita”- Event plný, tým je
AlternateReservation, někdo zruší rezervaci. - Očekávání:
- Job
InviteAlternateReservationsodešle e-mail ReservationInvitation všem náhradníkům na daném eventu. invited_atna alternate rezervaci se vyplní.
- Job
- Náhradník kliká na
confirmUrlz e-mailu. - Na stránce
/rezervace/confirm/{token}potvrdí. - Očekávání:
- Vzniká
TeamReservation(spub_table_idpokud je PubTable dostupný). AlternateReservationje soft-deleted.- Zpráva „Budeme se na váš těšit v…” + link na kvíz.
- Vzniká
3.2 Potvrzení pozvánky – kapacita mezitím zmizla
Section titled “3.2 Potvrzení pozvánky – kapacita mezitím zmizla”- Dva náhradníci, event s kapacitou = 1.
- První potvrdí → alokuje místo. Druhý kliká na svůj link.
- Očekávání:
- Druhý vidí zprávu „Ajajaj… Někdo byl rychlejší…”.
AlternateReservationdruhého zůstává beze změny (nepromuje se).
3.3 Zrušení alternate rezervace
Section titled “3.3 Zrušení alternate rezervace”- Tým klikne na
cancelUrlze svého confirmation e-mailu (token odAlternateReservation). - Očekávání:
AlternateReservationsoft-delete.- Pokud je event
ACTIVE: e-mail ReservationCancellation (observerdeleted). - Jinak žádný e-mail.
3.4 Migrace legacy alternate flagu
Section titled “3.4 Migrace legacy alternate flagu”- Ověřte, že migrace
2026_03_23_150010_migrate_alternate_reservations_datapřesunula všechnyTeamReservations legacy příznakemis_alternate=1doalternate_reservations. - Po migraci by v
team_reservationsneměl být žádný záznam sis_alternate=1.
4 · Standing reservation (autorezervace)
Section titled “4 · Standing reservation (autorezervace)”4.1 Založení autorezervace
Section titled “4.1 Založení autorezervace”- Přihlášený uživatel zakládá rezervaci, zaškrtne autorezervace.
- Očekávání:
- Vzniká
TeamReservationiStandingReservation(active=true). - Notifikace StandingReservationCreatedNotification manažerovi týmu.
- Token se vygeneruje v
boot::creating.
- Vzniká
4.2 Auto-backfill – budoucí event
Section titled “4.2 Auto-backfill – budoucí event”- Máte aktivní
StandingReservationpro blueprint bez existující budoucíTeamReservation. - Vytvořte budoucí event pro tento blueprint (admin).
- Spusťte
php artisan reservations:backfill(nebo počkejte na hodinovou sked). - Očekávání:
- Job
EnsureExactlyOneFutureTeamReservationJobse dispatche na queue pouze jednou per blueprint (ShouldBeUnique,uniqueFor = 60). - Vzniká
TeamReservationsstanding_reservation_idvyplněným. - Stůl se přiřadí v pořadí:
standing_reservation.pub_table_id(pokud je)- Nejbližší volný stůl podle |optimal − players|
- Job
4.3 Auto-backfill – už má budoucí rezervaci
Section titled “4.3 Auto-backfill – už má budoucí rezervaci”- Tým už má
TeamReservationna některý budoucí event blueprintu. - Spusťte
reservations:backfill --blueprint_id=X. - Očekávání: žádná nová rezervace se nevytvoří (log: „Team already has future reservation”).
4.4 Confirmation request – 72 h před eventem
Section titled “4.4 Confirmation request – 72 h před eventem”TeamReservationsestanding_reservation_id, event začíná za 72 h (rozmezí 70–74 h),confirmed_at = NULL,notification_72h_sent_at = NULL.- Spusťte
php artisan reservations:require-confirmation. - Očekávání:
- E-mail StandingReservationConfirmationRequest v Mailhogu.
notification_72h_sent_atvyplněn.- Command nepošle podruhé (WHERE
notification_72h_sent_at IS NULL).
4.5 Hráč potvrdí rezervaci
Section titled “4.5 Hráč potvrdí rezervaci”- Klik na confirm link v e-mailu →
/rezervace/confirm-standing/{token}. - Potvrdit.
- Očekávání:
TeamReservation::confirmStanding()–confirmed_atvyplněn.standing_reservation.consecutive_unconfirmed_countresetován na 0.- Redirect na
web.reservation.index.
4.6 Auto-cancel – 48 h nepotvrzeno
Section titled “4.6 Auto-cancel – 48 h nepotvrzeno”TeamReservationsestanding_reservation_id, event začíná za 48 h (rozmezí 46–50 h),confirmed_at = NULL,notification_72h_sent_at IS NOT NULL.- Spusťte
php artisan reservations:cancel-unconfirmed. - Očekávání:
TeamReservationje soft-deleted (cancelUnconfirmed()).- E-mail StandingReservationAutoCancel.
- Observer
deletedpošleReservationCancellation+ dispatcheInviteAlternateReservations. standing_reservation.consecutive_unconfirmed_countinkrementován o 1.
4.7 Trvalé zrušení po 3 × nepotvrzení
Section titled “4.7 Trvalé zrušení po 3 × nepotvrzení”StandingReservation.consecutive_unconfirmed_count = 2, proběhne cancelUnconfirmed (scénář 4.6).- Očekávání:
consecutive_unconfirmed_countdosáhne 3.StandingReservationsoft-deleted (trvalé zrušení).- E-mail StandingReservationCancelledPermanent.
4.8 Reset countru po potvrzení
Section titled “4.8 Reset countru po potvrzení”StandingReservation.consecutive_unconfirmed_count = 2.- Tým potvrdí další standing rezervaci.
- Očekávání: counter se resetuje na 0 (cyklus nepotvrzování se přeruší).
4.9 Zrušení autorezervace hráčem
Section titled “4.9 Zrušení autorezervace hráčem”- Hráč klikne na revoke link autorezervace (
StandingReservationController::revoke). - Očekávání:
StandingReservationsoft-delete.- Všechny
TeamReservations tímtoteam_idpro budoucí eventy téhož blueprintu se smažou. - Notifikace StandingReservationRevokedNotification manažerovi týmu.
- Observer
deletedodešle Cancellation e-maily za budoucí eventy (status=ACTIVE).
4.10 Preferovaný stůl na autorezervaci
Section titled “4.10 Preferovaný stůl na autorezervaci”- Moderátor v panelu → Stálé rezervace → edit → přiřaď Stůl.
- Spusťte
reservations:backfill. - Očekávání: nová
TeamReservationvygenerovaná z autorezervace má tentopub_table_id(pokud není obsazený; jinak fallback na nejbližší volný).
5 · Scheduled commandy a jobs
Section titled “5 · Scheduled commandy a jobs”5.1 Idempotence backfill jobu
Section titled “5.1 Idempotence backfill jobu”- Dispatch
EnsureExactlyOneFutureTeamReservationJob::dispatch($blueprintId)3× za sebou. - Očekávání: díky
ShouldBeUnique+uniqueFor = 60se vykoná jen 1× během 60 s. Žádné duplicitníTeamReservation.
5.2 Souběžný přístup (race condition)
Section titled “5.2 Souběžný přístup (race condition)”- Dva souběžné requesty na rezervaci stejného týmu na stejný event.
- Očekávání: díky
lockForUpdate()v job + existing-check v kontroléru nevznikne duplicita. Unique indexteam_id + event_idje poslední pojistkou.
5.3 Reminder
Section titled “5.3 Reminder”- Event zítra, rezervace s
reminder_sent_at = NULL. php artisan reservations:remind.- Očekávání: e-mail ReservationReminder,
reminder_sent_atvyplněn, podruhé se neodešle.
6 · Admin & moderátor UI
Section titled “6 · Admin & moderátor UI”6.1 Admin – detail eventu → záložka Rezervace
Section titled “6.1 Admin – detail eventu → záložka Rezervace”- Ověřte, že:
- Filament tabulka
ReservationListzobrazuje aktivníTeamReservation. - Oddělená tabulka Náhradníci (
AlternateReservationList) pro daný event. - Oddělená tabulka Autorezervace (
StandingTable) pro daný blueprint. - Sloupec Přiřazený stůl zobrazuje
Stůl {sort_order}+ poznámku, nebo—při NULL.
- Filament tabulka
6.2 Moderátor – panel → záložka Rezervace
Section titled “6.2 Moderátor – panel → záložka Rezervace”- Ověřte zobrazení
ReservationsTable+StandingReservationsTablepod sebou. - Edit standing rezervace → přiřazení stolu přes select (jen aktivní stoly podniku, formát
Stůl {n} – {note} ({opt}/{max})). - Delete standing → confirm modal „Opravdu zrušit?” + danger color + e-mail týmu.
6.3 Filtry a řazení
Section titled “6.3 Filtry a řazení”- StandingTable (admin): filtr Podle kvízu – nabídka jen blueprintů s existujícími standing rezervacemi.
- AlternateReservationList: řazení podle
invited_atpro rychlé ověření, zda už byli pozváni.
7 · E-mailové šablony – kontrola obsahu
Section titled “7 · E-mailové šablony – kontrola obsahu”Všechny šablony najdete v resources/views/emails/. Ověřte:
| Šablona | Trigger | Musí obsahovat |
|---|---|---|
reservation_confirmation | nová TeamReservation | tým, event, cancel URL |
reservation_cancellation | soft delete TeamReservation / AlternateReservation na aktivní event | tým, event |
reservation_invitation | InviteAlternateReservations job | event, confirm URL |
reservation_alternate_confirmation | nová AlternateReservation | tým, event, cancel URL |
reservation_reminder | reservations:remind | event, link |
standing_reservation_confirmation_request | reservations:require-confirmation | event, 72 h varování, confirm link |
standing_reservation_auto_cancel | reservations:cancel-unconfirmed | tým, event, důvod (48 h nepotvrzeno) |
standing_reservation_cancelled_permanent | 3× po sobě nepotvrzeno | důvod, info o trvalém zrušení |
8 · Regresní checklist
Section titled “8 · Regresní checklist”Po změnách rezervací ověřit, že nic dalšího nepraskne:
- Speciály – vytvoření rezervace, overbooking, náhradníci (
Special\ReservationsTable). - Anonymní rezervace – cookie
anon_reservation_{eventId}na 30 dní; po přihlášení se rezervace onboardne na hráčský profil. - Výpis rezervací v profilu hráče – neselže na smazaném eventu (fix z
2026-03-30). - Kapacita se počítá bez náhradníků (fix z
2026-03-11). - Po zrušení rezervace tlačítko Zpět → správný kvíz / speciál (fix).
- Po přihlášení-odhlášení-přihlášení: obnovení předchozí soft-deleted rezervace (ne chyba 500, ne unique constraint violation).
- Blueprint bez budoucího eventu → backfill se hlásí bez erroru, jen zaloguje a odejde.
- Disable event →
QuizzEventcancel → notifikaceEventCanceledvšem rezervovaným týmům.
9 · Jak rychle ověřit DB stav
Section titled “9 · Jak rychle ověřit DB stav”# Stoly v podnikudocker-compose exec fpm php artisan tinker --execute="App\Modules\Pub\Pub::find(1)->pubTables->toArray()"
# Počet aktivních standing rezervací s nenulovým counteremdocker-compose exec fpm php artisan tinker --execute="App\Models\StandingReservation::where('consecutive_unconfirmed_count', '>', 0)->count()"
# Rezervace čekající na 72h potvrzenídocker-compose exec fpm php artisan tinker --execute="App\TeamReservation::whereNotNull('standing_reservation_id')->whereNull('confirmed_at')->whereNull('notification_72h_sent_at')->count()"
# Náhradníci na konkrétním eventudocker-compose exec fpm php artisan tinker --execute="App\Models\AlternateReservation::where('event_id', 123)->get(['team_name','email','invited_at'])->toArray()"10 · Automatizované testy
Section titled “10 · Automatizované testy”Před release pustit:
docker-compose exec fpm php artisan test tests/Feature/Reservations/ --compactdocker-compose exec fpm php artisan test tests/Feature/GeneratingReservationFromStandingTest.php --compactdocker-compose exec fpm php artisan test tests/Feature/ReservationControllerTest.php --compactPokryté scénáře:
AlternateReservationTest– overbooking, promote, duplicatesPubTableTest– seed migrace, kapacita z PubTableStandingConfirmationTest– confirm / auto-cancel / permanent delete / commandyTableAssignmentTest– výběr stolu při backfill jobu