Skip to main content

Zajęcia wakacyjne

Kompleksowy przewodnik po module zajęć wakacyjnych — pojedynczych zajęć w kalendarzu (np. „Doubles (wakacyjne)"), na które uczestnik zapisuje się termin po terminie. Obejmuje ustawienia, warunki zapisu, akceptację regulaminu, przepływy płatności, wygaśnięcia oraz zapis wszystkiego w historii.

:::note Rozróżnienie To nie to samo co moduł Półkolonie (camps). Półkolonie to wielodniowe turnusy z własnym systemem sezonów, list rezerwowych i dokumentów. Zajęcia wakacyjne opisane tutaj to zwykłe gry (game) z rodzajem zajęć w kategorii „Wakacyjne", na które klient zapisuje się pojedynczo, z dedykowaną logiką zapisu, regulaminu i płatności. :::

Przepływ w skrócie (diagram)

👤 Instrukcja dla pracownika (Administracja / Recepcja / Instruktor)

Czym są zajęcia wakacyjne

Zajęcia wakacyjne to rodzaj zajęć (Rodzaj zajęć) oznaczony kategorią „Wakacyjne". Zajęcia utworzone z takiego rodzaju trafiają na osobną zakładkę „Zajęcia wakacyjne" w portalu klienta i podlegają odrębnym regułom widoczności, zapisu i płatności.

Ustawienia (Panel administratora → Ustawienia zajęć wakacyjnych)

Dla każdego miasta konfiguruje się osobno:

  • Włącz zajęcia wakacyjne — globalny przełącznik dostępności modułu w danym mieście.
  • Natychmiastowa płatność (online) — gdy włączona, klient zapisujący się sam jest od razu kierowany do płatności; Czas na płatność online (minuty) określa, ile ma czasu, zanim zapis wygaśnie.
  • Natychmiastowa płatność (pracownik) — gdy recepcja zapisuje uczestnika, do klienta wysyłany jest link do płatności (SMS); Czas na płatność pracownik (minuty) określa okno na opłacenie.
  • Dni z wyprzedzeniem (klient) / Dni z wyprzedzeniem (pracownik) — jak daleko w przód widać dostępne terminy (osobno dla klienta i pracownika).

Zapis uczestnika

Zapis możliwy jest przez klienta (portal klienta → zakładka „Zajęcia wakacyjne") oraz przez recepcję (w imieniu uczestnika). System dopuszcza zapis tylko, gdy spełnione są wszystkie warunki:

  1. Ukończona ocena umiejętności (zajęcia próbne) — nowy uczestnik musi najpierw przejść zajęcia próbne / ocenę umiejętności, zanim zobaczy ofertę wakacyjną.

  2. Brak zaległej / oczekującej płatności wakacyjnej — uczestnik z nieopłaconą, aktywną płatnością wakacyjną nie może zapisać się na kolejny termin, dopóki jej nie ureguluje lub nie wygaśnie.

  3. Powiązanie z grupą uczestnika — uczestnik ma w profilu przypisane grupowe rodzaje zajęć (szkółki). Każde zajęcia wakacyjne wskazują z kolei jedną grupę, dla której są przeznaczone — są jej wakacyjnym odpowiednikiem. Uczestnik przypisany do dwóch grup znajdzie więc dwa zestawy terminów: odpowiedniki jednej i drugiej grupy. Wiek uczestnika nie jest tu w ogóle brany pod uwagę.

    Jeśli uczestnik nie ma przypisanego żadnego rodzaju zajęć, widzi komunikat o oczekiwaniu na przypisanie przez recepcję; jeśli ma przypisania, ale żaden termin wakacyjny nie jest z nimi powiązany, widzi „Skontaktuj się z recepcją".

  4. Wolne miejsca i okno widoczności (dni z wyprzedzeniem).

Jak powiązać zajęcia wakacyjne z grupą

Każda szkółka ma swój wakacyjny odpowiednik, więc dla grup „Skrzaty" i „Żaki" zakładasz dwa osobne rodzaje zajęć wakacyjnych. Uczestnik zapisany do obu grup zobaczy oba.

Najprościej przez skrót „Utwórz zajęcia wakacyjne na podstawie grupy" (menu przy zajęciach grupowych) — grupa, z której startujesz, jest od razu ustawiona w nowym typie zajęć.

Ręcznie, w katalogu „Rodzaje zajęć":

  1. Otwórz zajęcia wakacyjne do edycji (typ „Inne").
  2. W polu „Grupa, dla której są te zajęcia" wybierz szkółkę.
  3. Zapisz. Od tej chwili termin widzą uczestnicy przypisani do tej grupy.

Jedna grupa może mieć kilka rodzajów zajęć wakacyjnych (np. lipiec i sierpień osobno) — uczestnik zobaczy terminy ze wszystkich.

:::warning Termin bez wskazanej grupy nie trafia do nikogo Zajęcia wakacyjne bez wybranej grupy nie pokażą się żadnemu uczestnikowi. W katalogu rodzajów zajęć taki wpis jest oznaczony czerwoną etykietą „Bez grupy", a w formularzu widnieje ostrzeżenie pod polem wyboru. Po wdrożeniu tej zmiany istniejące zajęcia wakacyjne nie mają ustawionej grupy — trzeba je uzupełnić przed sezonem, inaczej oferta będzie pusta. :::

Akceptacja regulaminu (i RODO)

Zapis na zajęcia wakacyjne wymaga akceptacji regulaminu wakacyjnej szkółki oraz RODO. Akceptacja jest wymuszana na każdej ścieżce płatności:

  • podczas zapisu przez klienta (dialog zgody),
  • na stronie linku do płatności (SMS od recepcji),
  • w widoku Płatności w aplikacji, jeśli klient zamiast klikać w link zaloguje się i opłaci zajęcia z listy płatności.

Bez zaznaczenia zgody nie da się przejść do płatności. Fakt akceptacji jest zapisywany i widoczny w historii (patrz niżej).

Ostrzeżenie o braku możliwości odwołania

Jeśli termin zajęć przypada wcześniej niż wymagany czas na anulowanie (próg anulowania rodzaju zajęć), przy wyborze terminu pojawia się ostrzeżenie:

„Uwaga! Termin zajęć przypada wcześniej niż wymagany czas na jej anulowanie. Po dokonaniu zapisu nie będzie możliwości odwołania zajęć."

Dotyczy to zarówno zajęć wakacyjnych, jak i wakacyjnych zajęć próbnych.

Płatność

  • Klient (natychmiastowa płatność online) — po zapisie klient trafia do widoku Płatności (/dashboard/payments), gdzie po akceptacji regulaminu opłaca zajęcia. Zajęcia wakacyjne nie są automatycznie przekierowywane wprost na bramkę Przelewy24 — klient reguluje je z poziomu listy płatności (bramką lub portfelem).
  • Recepcja — po zapisie do klienta wychodzi link do płatności (SMS); na stronie linku klient akceptuje regulamin i płaci (Przelewy24).
  • Po zaksięgowaniu płatności klient dostaje powiadomienie „Płatność potwierdzona" (vacation_payment_success).

Wygaśnięcie płatności

Jeśli uczestnik nie opłaci w wyznaczonym czasie, płatność wygasa, a miejsce jest zwalniane automatycznie. Wygaśnięcie jest odnotowane w historii zajęć jako „Wygaśnięcie czasu na opłacenie".

Historia

Wszystkie zdarzenia związane z zajęciami wakacyjnymi trafiają do dwóch historii:

  • Historia Zajęć (podgląd/edycja pojedynczej gry) — chronologiczny log zdarzeń danej gry: dołączenie, akceptacja regulaminu, płatność, płatność z portfela, zwrot na portfel, usunięcie bez zwrotu, wygaśnięcie, odwołanie.
  • Historia aktywności (/dashboard/history) — ogólny rejestr, w którym zajęcia wakacyjne mają własne źródło „Zajęcia wakacyjne" oraz m.in. wpis „Akceptacja regulaminu".

🛠️ Dokumentacja techniczna

Model danych

  • Rodzaj zajęć wakacyjnych: activity_types.category = 'Wakacyjne' (typ at.type = 'other'). To jedyny wyróżnik odróżniający zajęcia wakacyjne od zwykłych.
  • Gra: zwykły rekord game z activity_type_id wskazującym na rodzaj „Wakacyjne".
  • Uczestnik wakacyjny: wpis w game.attendees z type = 'vacation' i paymentId.
  • Płatność: payment.payment_type = 'school', related_ids = [gameId]. Zajęcia wakacyjne rozpoznaje się jako payment_type = 'school' oraz activity_type.category = 'Wakacyjne'.
  • Log zdarzeń gry (game.event_log, JSON): m.in. joined, consent_accepted, payment_paid, paid_with_wallet, refund_to_wallet, removed_without_refund, attendee_expired, cancelled.

Ustawienia

VacationActivitySettings w lib/actions/app-settings.ts (klucze app_settings per miasto): vacation_activity_enabled, vacation_activity_immediate_payment_online, vacation_activity_immediate_payment_employee, vacation_activity_payment_time_online, vacation_activity_payment_time_employee, vacation_activity_payment_time (legacy fallback, nie edytowany już w UI), vacation_activity_days_advance_client, vacation_activity_days_advance_employee.

Formularz: components/forms/vacation-activity-settings/vacation-activity-settings-form.tsx (w UI zostały tylko dwa pola czasu płatności: online i pracownik).

Zapis i widoczność

  • enrollInVacationActivity(gameId, playerId, city, consentAccepted) — serwerowa akcja zapisu. Wymaga consentAccepted (inaczej errors.consentRequired), blokuje przy zaległościach, zapisuje uczestnika typu vacation oraz zdarzenie consent_accepted do event_log.
  • hasPendingVacationPaymentByPlayer(playerId) — blokada zapisu przy aktywnej, nieopłaconej płatności wakacyjnej (świadoma wygaśnięć).
  • getGamesByDateRangeForPlayer(...) — źródło dostępnych terminów; zwraca activity_type z category, cancellation_hours_before i innymi polami potrzebnymi UI.
  • useVacationEnrollment(...) (components/forms/vacation-enrollment/use-vacation-enrollment.ts) — hook ładujący ustawienia, dostępne terminy, status zaległości i informację o konieczności przypisania do grupy. Terminy filtrowane są przez getVacationActivityTypeIdsForGroups(...): hook przekazuje identyfikatory rodzajów zajęć przypisanych uczestnikowi (player.activity_types) i zostawia tylko te gry, których activity_type.id jest wśród zwróconych. Zapytanie o gry idzie z ignoreAge: true, więc data urodzenia nie ma znaczenia.
  • lib/actions/vacation-activity-groups.tsgetVacationActivityTypeIdsForGroups (jedno zapytanie IN (...) po kolumnie vacation_group_activity_type_id, z pominięciem zarchiwizowanych), getUnlinkedVacationActivityTypeIds (etykieta „Bez grupy" w katalogu, zawężona do kategorii Wakacyjne) oraz assertVacationGroupWritable (rola + sprawdzenie, że wskazany identyfikator to zajęcia grupowe tego samego najemcy; wołane przed zapisem, żeby odrzucenie nie zostawiło zapisanego typu zajęć).
  • Kolumna activity_types.vacation_group_activity_type_id (migracja 0185) — relacja jeden-do-wielu: termin wskazuje jedną grupę, grupa może mieć wiele terminów. Zapisywana wprost przez createActivityType / updateActivityType, więc formularz katalogu zapisuje wszystko jednym submitem.
  • Bramka zajęć próbnych (ocena umiejętności) przed ofertą wakacyjną: playerMustCompleteSkillAssessment(...).
  • UI zapisu: components/forms/vacation-enrollment/vacation-enrollment-dialog.tsx (zajęcia wakacyjne) oraz components/forms/trial-enrollment/trial-enrollment-dialog.tsx z propem vacationOnly (wakacyjne zajęcia próbne).

Płatności

  • Brak auto-redirectu na bramkę dla wakacji: po zapisie klient trafia do /dashboard/payments (a nie wprost na Przelewy24). Wakacyjne zajęcia próbne bramkowane propem vacationOnly; zwykłe próbne bez zmian.
  • Link do płatności: app/pay/[token]/page.tsx + PublicPayButton.tsx; inicjalizacja app/api/public/payment/initialize/route.ts.
  • Wykrywanie płatności wakacyjnej w widoku Płatności: pole activity_type_category wystawione z zapytań płatności (lib/actions/payment.ts), warunek payment_type === 'school' && activity_type_category === 'Wakacyjne'.

Akceptacja regulaminu jest zapisywana jako zdarzenie consent_accepted w event_log gry na każdej ścieżce płatności:

  • Widok Płatności (w aplikacji) — bramka VacationStatuteAcceptanceDialog (components/modal/vacation-statute-acceptance-dialog.tsx) wpięta w payments-list-view-user.tsx i PaymentsTableAccordion.tsx (analogicznie do bramki rezerwacji kortów); po akceptacji saveVacationStatuteAcceptance(paymentIds).
  • Link do płatnościapp/api/public/payment/initialize/route.ts wywołuje recordVacationStatuteConsent(paymentId, acceptedBy, tenantId) dla płatności typu group (updateBookingStatuteAcceptance jest no-opem dla gier — dlatego trzeba zapisać zdarzenie na grze).
  • Zapis przy zapisieenrollInVacationActivity zapisuje consent_accepted przy dołączeniu.

Rdzeń zapisu: recordVacationStatuteConsent(...) w lib/actions/payment.ts — bez zależności od sesji, best-effort, z dedupem po paymentId (nie dubluje zgody). Dialog linkuje do Regulaminu wakacyjnej szkółki oraz RODO.

Historia — renderowanie

  • Historia aktywności: builder buildGameConsentAcceptedSQL w lib/actions/activity-log.ts (typ akcji consent_accepted, źródło vacation); render w app/(dashboard)/dashboard/history/components/ActivityLogItem.tsx (badge „Akceptacja regulaminu"). Zajęcia wakacyjne mają źródło source_type = 'vacation'.
  • Historia Zajęć: app/(dashboard)/dashboard/schedule/components/GameEventsView.tsx renderuje wszystkie zdarzenia z event_log; etykiety/opisy z gameEvents.eventTypes.* i eventDescriptions.* (m.in. consent_accepted, payment_paid, paid_with_wallet, removed_without_refund, game_cancelled).

Wygaśnięcia i zwolnienie miejsca

  • Cron expirePendingPayments(env) (lib/actions/expire-payments.ts) przełącza wygasłe płatności pending → expired; logExpiriesToGameLog(...) dopisuje zdarzenie attendee_expired do event_log gry (dla płatności school na grach „Wakacyjne" oraz trial na dowolnej grze). Miejsce zwalniane jest przez leniwe filtry uczestników.

Powiadomienia

  • vacation_payment_success (lib/notification-config.ts) — SMS/e-mail po zaksięgowaniu płatności; szablon e-mail: mails/vacation-payment-success.html.

Kluczowe pliki

ObszarPlik
Ustawienialib/actions/app-settings.ts, components/forms/vacation-activity-settings/vacation-activity-settings-form.tsx
Zapislib/actions/game.ts (enrollInVacationActivity), components/forms/vacation-enrollment/*
Dostępne terminylib/actions/game.ts (getGamesByDateRangeForPlayer), use-vacation-enrollment.ts
Regulamincomponents/modal/vacation-statute-acceptance-dialog.tsx, lib/actions/payment.ts (recordVacationStatuteConsent, saveVacationStatuteAcceptance)
Płatność (link)app/pay/[token]/*, app/api/public/payment/initialize/route.ts
Płatność (widok)components/tables/payments-table/payments-list-view-user.tsx, PaymentsTableAccordion.tsx
Historialib/actions/activity-log.ts, ActivityLogItem.tsx, GameEventsView.tsx
Wygaśnięcialib/actions/expire-payments.ts
Powiadomienialib/notification-config.ts, mails/vacation-payment-success.html