Rezerwacje kortów – instrukcja dla recepcji
Krótki przewodnik po codziennej obsłudze rezerwacji kortów: tworzenie, edycja, rozliczanie, odwoływanie i szukanie rezerwacji.
👤 Instrukcja dla pracownika
Gdzie pracujesz
Podstawowym narzędziem jest Kalendarz (menu boczne → Kalendarz, nagłówek strony „Harmonogram”). Widzisz w nim siatkę: kolumny to korty, wiersze to godziny.
- Miasto/lokalizację wybierasz w selektorze na górze panelu – filtruje on całą aplikację, nie tylko kalendarz.
- Widok przełączasz między dniem, tygodniem i miesiącem paskiem nad siatką.
- Zielone/puste pola to terminy wolne, kafelki to zajęte terminy.
Tworzenie rezerwacji
- Kliknij wolne pole w siatce, na korcie i o godzinie, o którą prosi klient.
- Otworzy się okno z dwiema zakładkami:
- Rezerwacja – zwykły wynajem kortu (domyślna, tej używasz najczęściej),
- Aktywność – zajęcia z instruktorem, treningi, zajęcia grupowe.
- Uzupełnij formularz:
- Kort – możesz zmienić, jeśli kliknąłeś nie w ten, co trzeba.
- Czas rezerwacji – godzina startu i końca. Długość zmienia się skokowo, zgodnie z ustawieniami klubu (np. co 30 min), i nie wyjdzie poza godziny otwarcia kortu.
- Cena – wyliczana automatycznie z cennika (uwzględnia reguły godzinowe i indywidualne zniżki klienta).
- Cena niestandardowa – wpisz, tylko jeśli chcesz nadpisać cennik.
- Notatki – widoczne wyłącznie dla pracowników, nie dla klienta.
- Uczestnicy → Dodaj gracza – zacznij wpisywać imię, nazwisko lub telefon. Jeśli klienta nie ma w bazie, z tej samej listy założysz nowego (może być wymagany numer telefonu).
- Kliknij Utwórz rezerwację.
:::tip Bez powiadomienia Strzałka ▲ obok przycisku daje opcję Dodaj aktywność bez powiadomienia – rezerwacja powstanie, ale klient nie dostanie potwierdzenia (mail/SMS/push). Używaj, gdy poprawiasz coś „na już” i nie chcesz zasypywać klienta wiadomościami. :::
Bez uczestnika nie utworzysz rezerwacji – przycisk pozostanie nieaktywny do momentu dodania co najmniej jednej osoby.
Jeśli klub ma włączoną natychmiastową płatność, zobaczysz żółte ostrzeżenie: rezerwacja wygaśnie, jeśli nie zostanie opłacona w podanym czasie (np. 10 minut).
:::tip Rezerwacja za 0 zł Gdy wpiszesz cenę 0 (rezerwacja gratisowa), ostrzeżenie znika. Nie ma czego opłacić, więc kort nie jest blokowany na 10 minut, klient nie dostaje SMS-a z linkiem do płatności, a rezerwacja jest potwierdzona od razu – tak samo jak w klubie bez natychmiastowej płatności. :::
Rezerwacje cykliczne (stali klienci)
W sekcji Zajęcia cykliczne zaznacz powtarzalność (co tydzień / codziennie / co miesiąc) oraz zakończenie – po liczbie powtórzeń albo do konkretnej daty. Pod spodem zobaczysz, ile terminów zostanie utworzonych – po sprawdzeniu konfliktów licznik pokazuje realną liczbę, razem z informacją, ile terminów wypadło na dni wolne lub na zamknięcie kortu.
System sam sprawdzi konflikty z istniejącymi rezerwacjami. Jeśli któreś terminy są zajęte, wybierz Utwórz N rezerwacji (pomiń konflikty) – powstaną tylko wolne terminy, a zajęte zostaną pominięte. Dni wolne, święta oraz okresy zamknięcia kortu są pomijane zawsze, także przy pomijaniu konfliktów – seria przechodząca przez remont kortu powstanie, ale bez terminów z tego okresu.
Terminy są sprawdzane ponownie w chwili zapisu. Jeśli w międzyczasie ktoś zajął którykolwiek z nich, cała seria zostanie odrzucona z komunikatem o zajętym terminie – nic nie powstanie połowicznie. Odśwież konflikty i zapisz ponownie.
Cena niestandardowa wpisana przy serii obowiązuje na każdym terminie – tak samo jak przy rezerwacji jednorazowej.
Klient dostaje potwierdzenie rezerwacji cyklicznej: kort, adres, dzień tygodnia i godzina, zakres dat, liczba terminów, cena za termin i PIN do drzwi. Nie jest to już wiadomość o zapisie na zajęcia – nie ma w niej instruktora ani nazwy grupy.
:::warning Seria z natychmiastową płatnością Jeśli klub ma włączoną natychmiastową płatność, seria jest tylko zablokowana do czasu opłacenia – zobaczysz żółte ostrzeżenie z czasem (np. 10 minut). Klient dostaje jeden link, do pierwszego terminu. Opłacenie go potwierdza całą serię; pozostałe terminy rozliczasz normalnie przy ladzie. Jeśli nikt nie zapłaci w tym czasie, wszystkie terminy serii zostają zwolnione i korty wracają do sprzedaży. Wysłanie serii „bez powiadomienia" nie wyśle też linku – przy natychmiastowej płatności używaj zwykłego przycisku. :::
Podgląd i edycja rezerwacji
Kliknij kafelek rezerwacji w kalendarzu – otworzy się Szczegóły rezerwacji: typ (jednorazowa/cykliczna), kort, termin, cena, dane klienta i notatka.
- Kliknięcie w imię i nazwisko przenosi do profilu klienta.
- Kliknięcie w telefon lub e-mail kopiuje je do schowka.
Na dole są trzy przyciski: Odwołaj, Edytuj, Rozlicz (ten ostatni znika, gdy rezerwacja jest już opłacona).
Edytuj pozwala zmienić kort, datę, godziny, cenę, notatki i listę uczestników. Zapisujesz przyciskiem Zaktualizuj (lub „bez powiadomienia”). W oknie edycji jest też Podziel – dzieli jedną długą rezerwację na kilka segmentów z osobno wyliczoną ceną (np. gdy część godzin idzie po innej stawce).
Rozliczenie płatności
Przy ladzie: Szczegóły rezerwacji → Rozlicz.
- Sprawdź kwotę do zapłaty (pole z imieniem i nazwiskiem możesz poprawić, np. na potrzeby paragonu).
- Jeśli klient płaci kartą benefitową – kliknij Multisport / Medicover / FitProfit / PZU Sport tyle razy, ile kart wnosi. Każde kliknięcie obniża kwotę o 15 zł (do zapłaty zostaje minimum 1 zł). Minus w rogu kafelka odejmuje kartę, Resetuj czyści wszystkie.
- Zaznacz Drukuj paragon, jeśli klient chce paragon.
- Wybierz formę płatności:
- Gotówka / Karta – rozliczenie w klubie,
- Portfel – pobiera środki z wirtualnego portfela klienta (w nawiasie widzisz jego saldo; przy niewystarczających środkach pobierze ile się da, a resztę trzeba dopłacić),
- Zaległość – klient wychodzi bez płacenia, kwota trafia na jego zaległości,
- Więcej opcji – płatność mieszana (np. część gotówką, część kartą).
Jeśli klient prosił o fakturę, nad kwotą pojawi się niebieska ramka z danymi do faktury – to sygnał, żeby nie wystawiać zwykłego paragonu.
Odwoływanie rezerwacji
Szczegóły rezerwacji → Odwołaj. W oknie potwierdzenia:
- przy rezerwacji opłaconej zobaczysz zaznaczony checkbox „Zwróć X zł na portfel klienta” – zostaw go zaznaczonego przy zwykłym odwołaniu; odznacz tylko wtedy, gdy zwrot się nie należy,
- Odwołaj rezerwację – klient dostaje powiadomienie o odwołaniu,
- strzałka ▼ → Odwołaj bez powiadomienia,
- przy rezerwacji cyklicznej dostępne jest Odwołaj całą serię – zobaczysz listę wszystkich przyszłych terminów, zanim potwierdzisz.
:::warning Zwrot to portfel, nie przelew Zwrócone pieniądze trafiają do wirtualnego portfela klienta i pokrywają kolejne rezerwacje. Nie jest to przelew na konto ani zwrot na kartę. :::
Szukanie rezerwacji
Gdy klient dzwoni i nie wiesz, kiedy ma kort: menu → Historia → filtr Przegląd rezerwacji. Masz tam wyszukiwarkę (nazwisko, telefon) oraz statusy (aktywna / odwołana / wygasła) i źródło rezerwacji (online albo „rezerwacja telefoniczna”, czyli założona przez pracownika). Kliknięcie pozycji otwiera to samo okno Szczegółów rezerwacji, z którego odwołasz i rozliczysz.
PIN do drzwi
Każdy uczestnik ma jeden stały 4-cyfrowy PIN, który otwiera drzwi na wszystkich jego rezerwacjach. Nie generuje się nowego kodu do każdej rezerwacji.
Aby go podejrzeć lub zmienić: menu → Uczestnicy → ⋮ przy wierszu → PIN do drzwi. Klient widzi swój PIN w aplikacji w sekcji Moi gracze.
Najczęstsze sytuacje
| Sytuacja | Co zrobić |
|---|---|
| Klient chce przesunąć godzinę | Szczegóły → Edytuj → zmień godziny → Zaktualizuj |
| Klient chce inny kort | Szczegóły → Edytuj → zmień Kort |
| Klient nie przyszedł i nie zapłacił | Rozlicz → Zaległość |
| Klient płaci połowę gotówką, połowę kartą | Rozlicz → Więcej opcji |
| Rezerwacja cykliczna kończy się wcześniej | Odwołaj → Odwołaj całą serię (odwoła przyszłe terminy) |
| Nie widać wolnego terminu, choć kort pusty | Sprawdź miasto/lokalizację na górze i godziny otwarcia kortu (przycisk Godziny otwarcia) |
🛠️ Dokumentacja techniczna
Ścieżki i komponenty
| Element | Plik |
|---|---|
| Kalendarz recepcji | app/(dashboard)/dashboard/schedule (BookingClient.tsx) |
| Formularz nowej rezerwacji | app/(dashboard)/dashboard/schedule/components/ReservationForm.tsx |
| Przełącznik Rezerwacja/Aktywność | app/(dashboard)/dashboard/schedule/components/GameFormContainer.tsx |
| Edycja rezerwacji + podział | EditReservationForm.tsx, SplitReservationDialog.tsx |
| Szczegóły rezerwacji | components/schedule/reservation-details-dialog.tsx |
| Rozliczenie / płatność mieszana | reservation-settle-dialog.tsx, reservation-advanced-payment-dialog.tsx |
| Odwołanie | components/schedule/reservation-cancel-dialog.tsx |
| Lista rezerwacji dla pracownika | app/(dashboard)/dashboard/history (/reservations-overview przekierowuje tutaj) |
Logika serwerowa
createAdminReservation(lib/actions/booking.ts) – tworzy grę, booking, opcjonalną płatność i wysyła powiadomienia. Gdyimmediate_payment_employee(lub indywidualnerequire_online_paymentklienta) jest włączone, rezerwacja i płatność dostająexpires_at= teraz +employee_payment_timeminut, a gra powstaje bez płatności (createGameWithoutPayment).- Cena liczona jest przez
calculateActivityPricez uwzględnieniem reguł cenowych i nadpisań klienta (applyClientPricingOverride);custom_pricez formularza nadpisuje wynik. Cena jest ustalana przed decyzją o holdzie, bo to ona o niej współdecyduje. - Rezerwacja za 0 zł nigdy nie dostaje holdu –
requiresImmediateCourtPayment(lib/utils/immediate-payment.ts) zwracafalsedla ceny<= 0, niezależnie od ustawień lokalizacji, nadpisania klienta i zaległości.createPaymentrozlicza zerową kwotę już przyINSERT(statuspaid), więc żadna późniejsza zmiana statusu nie nadejdzie, aclearGameExpiresAt/clearBookingExpiresAt– jedyne miejsca zdejmujące blokadę – nigdy by się nie uruchomiły: kort stałby za terminem, którego nic nie potrafi wyczyścić, i po cichu przepadł. Zamiast tego gra powstaje przezcreateGame, uczestnik nie jestdraft,booking_expires_atzostaje puste, a klient dostaje zwykłe potwierdzenie zamiast SMS-a z linkiem do płatności. Ta sama reguła obowiązuje w rezerwacji klienckiej i w serii. Formularze (ReservationForm,court-booking-dialog) czytają ten sam predykat i podają mu kwotę, która faktycznie zostanie policzona, więc żółte ostrzeżenie o czasie na płatność nie pokazuje się dla ceny 0. - Obniżenie ceny do 0 na już istniejącej rezerwacji z holdem domyka się w
updateReservation:handlePriceChangeForPaymentstylko przepisuje kwotę i zostawia płatność jakopending, a linku na 0 zł nie da się opłacić (/api/public/payment/initializeodrzuca kwoty<= 0), więc żadna zmiana statusu by nie nadeszła i hold wygasłby po cichu. Dlatego akcja sama rozlicza taką płatność (status = 'paid',payment_expires_at = NULL) i zdejmuje blokadę przezclearGameExpiresAt+clearBookingExpiresAt– ten sam chokepoint, co przy potwierdzeniu wpłaty. Płatność już opłaconą zostawia w spokoju (obniżka idzie wtedy zwykłą ścieżką zwrotu do portfela). generateRecurringReservations(lib/actions/booking.ts) – odpowiednik dla serii. Sprawdza zaległości, wyrównanie do siatki oraz zamknięcia kortu dla każdego terminu z osobna (godziny otwarcia mogą się zmienić w trakcie serii), a przed pierwszym zapisem ponownie weryfikuje dostępność wszystkich terminów i przerywa całą serię błędemCOURT_SCHEDULE_OVERLAP.custom_price(tylko dlaADMIN/BACKOFFICE) trafia do kolumnycustom_pricekażdej gry, do wierszabookingi do kwoty płatności.- Hold serii: gdy
requiresImmediateCourtPaymentzwrócitrue,generateRecurringReservationszapisuje termin blokady wgame_expires_atkażdej gry,booking_expires_atkażdego wierszabooking, w polachdraft/paymentStartedAt/paymentExpiresAtuczestnika oraz wpayment_expires_atkażdej płatności. Link do płatności idzie tylko dla pierwszego terminu (sendReservationNotifications), a potwierdzenie tej płatności zwalnia całą serię przezreleaseRecurringCourtSeriesHold(lib/series-reservation-hold-sql.ts) wpięte wclearGameExpiresAt– ten sam chokepoint, przez który przechodzą wszystkie potwierdzenia płatności. Zwolnienie omija terminy, których slot ktoś zajął w czasie, gdy blokada była wygasła – wskrzeszenie ich oznaczałoby podwójną rezerwację; taki termin zostaje wygaszony, a jego płatność wygaszaexpirePendingPayments, więc klient nie płaci za kort, który przepadł. Gdy seria nie wygenerowała płatności za pierwszy termin (typ aktywności, której cena nie trafiła na płatność), akcja zakłada ją sama – bez płatności nie byłoby linku, a blokada wygasłaby po cichu. Seria wyceniona na 0 zł w ogóle nie dostaje holdu (patrz reguła zerowej ceny wyżej). Brak wpłaty =expirePendingPaymentswygasza płatności, a leniwe filtry nagame_expires_atzwalniają korty. Kształt skopiowany zaddPlayerToRecurringSeries(zapis na zajęcia stałe). generateRecurringGames(lib/actions/game.ts) zenforceCourtAvailability: truewstawia każde wystąpienie zapytaniemINSERT ... WHERE NOT EXISTS, tak jakcreateGame. Przegrany wyścig o termin cofa wszystkie wystąpienia zapisane w tym wywołaniu razem z ich płatnościami, a formularz kasuje pustą serię.- Powiadomienie o serii:
player_added_to_recurring_reservation(lib/notification-config.ts, szablonmails/recurring-reservation-confirmation.html, migracja0242). Wysyła jegenerateRecurringReservationspo utworzeniu bookingów – dopiero wtedy znany jest PIN serii.generateRecurringGamesdostajesendNotification: falsez tej ścieżki, żeby nie poszła wiadomość o zapisie na zajęcia (player_added_to_recurring_series), która mówiła o instruktorze. Klient z kontem dostaje ją kanałami ze swoich ustawień, a klient założony przy ladzie (kontolocal, adres bez skrzynki) – SMS-em, tak samo jak przy jednorazowej. Przy aktywnym holdzie zamiast potwierdzenia idzie SMS z linkiem, a po zapłaciebooking_payment_success. - Zamknięcia kortu (
court.closures) sprawdzafindClosureForInstant(lib/utils/opening-hours.ts) – godzinę i dzień czyta w strefieEurope/Warsaw, więc Worker w UTC widzi to samo, co kalendarz. Serwer odrzuca zamknięty termin błędemCOURT_CLOSEDrównież przy rezerwacji jednorazowej. - Terminy serii generuje
generateRecurringGameOccurrencesShared(lib/utils/recurring-occurrences.ts) – pomija dni wolne i zamknięcia kortu, raportuje je wskippedDates(kind: 'holiday' | 'closure') i liczy godziny w strefieEurope/Warsaw. Ten sam wynik zasila sprawdzanie konfliktów (useRecurringConflicts) i zapis, także przy „pomiń konflikty”. cancelReservationprzyjmuje flagirefundToWalletiskipNotification; odwołanie serii idzie przezdeleteRecurringGameSeries.- Rozliczenie:
settlePaymentInClub(gotówka/karta/portfel, karty benefitowe jako lista{type, amount, count}, 15 zł za sztukę) orazmarkPaymentAsArrearsdla przycisku Zaległość. - Po utworzeniu i po zmianie rezerwacji wywoływane jest
syncBookingToHardware, które przekazuje termin i PIN do kontroli dostępu.
Uprawnienia
Formularz pokazuje pola Cena niestandardowa, Notatki i sekcję
Uczestnicy tylko rolom ADMIN i BACKOFFICE (useRole). Klient w tym samym
oknie widzi wyłącznie kort, termin i cenę.