Skip to main content

Przeniesienie cyklu zajęć (Zmiana grupy stałej)

Funkcjonalność „Przenieś” umożliwia klientom oraz recepcji przeniesienie całego zapisu na zajęcia stałe (cykl / recurring_game_series) do innej dostępnej grupy na tym samym poziomie zaawansowania.

System w pełni automatycznie i transparentnie rozlicza różnice w cenach zajęć (nadpłaty oraz niedopłaty) dla bieżącego opłaconego okresu, nie wymagając ręcznych korekt księgowych.


👤 Instrukcja dla użytkownika i recepcji

1. Gdzie znajduje się opcja „Przenieś”

Przycisk „Przenieś” znajduje się w oknie szczegółów zajęć (Szczegóły zajęć), które otwiera się po kliknięciu w dowolne przyszłe zajęcia w kalendarzu uczestnika (/dashboard/user-activities/[playerId]).

[!NOTE] Przycisk pojawia się wyłącznie dla zajęć wchodzących w skład cyklu stałego (recurring_game_series). Dla pojedynczych rezerwacji, zajęć próbnych lub zajęć z przeszłości opcja jest niedostępna.

+-------------------------------------------------------------+
| Szczegóły zajęć X |
| Danielllll Kochanek |
| |
| [ Kort 1 ] [ sobota, 15 sierpnia ] [ 21:30-22:30 ]|
| [ AKTYWNOŚĆ ] [ INSTRUKTOR ] [ STATUS ] |
| |
| [ Anuluj ] [ Przenieś ] [ Odwołaj zajęcia]|
+-------------------------------------------------------------+

2. Krok po kroku: Jak przenieść cykl

  1. Kliknij w zajęcia w swoim kalendarzu, aby otworzyć okno Szczegóły zajęć.
  2. Kliknij przycisk „Przenieś”.
  3. Otworzy się okno „Przenieś cykl zajęć”, w którym widzisz:
    • Podsumowanie obecnego cyklu: nazwę grupy, dzień i godzinę, kort, liczbę pozostałych zajęć w cyklu oraz wartość już opłaconych zajęć.
    • Listę dostępnych nowych grup: grupy w Twoim przypisanym poziomie z wolnymi miejscami (dzień tygodnia, godziny, trener, kort, liczba wolnych miejsc i cena).
  4. Wybierz nową grupę, do której chcesz przenieść uczestnika.
  5. System natychmiast wyświetli Rozliczenie finansowe:
  1. Kliknij „Potwierdź i przenieś” (lub „Przenieś i opłać”, jeśli wymagana jest dopłata online). System automatycznie zaktualizuje Twój kalendarz i rozliczenia, a przy dopłacie online od razu przekieruje Cię do zakładki Płatności.

3. Zasady rozliczania finansowego (Nadpłata i Niedopłata)

Rozliczenie w momencie przenoszenia dotyczy wyłącznie bieżącego, opłaconego okresu (miesiąca):

A. Nowa grupa jest tańsza (Nadpłata)

  • Jeśli za pozostałe zajęcia w bieżącym miesiącu zapłacono więcej niż wynosi koszt nowej grupy, powstaje nadpłata.
  • Nadwyżka środków zostaje automatycznie zwrócona do Portfela klienta w aplikacji (wallet).
  • Środki z portfela można wykorzystać na przyszłe opłaty, rezerwacje kortów lub inne usługi.
  • Wszystkie zajęcia w nowej grupie w bieżącym miesiącu zostają oznaczone jako w pełni opłacone.

B. Nowa grupa jest droższa (Niedopłata / Wymagana dopłata)

  • Jeśli nowa grupa ma wyższą stawkę za zajęcia w bieżącym miesiącu, powstaje różnica do dopłaty.
  • Opcja 1 (Płatność z Portfela): Jeśli klient posiada wystarczające środki w portfelu, może włączyć przełącznik „Opłać dopłatę ze środków w portfelu” — kwota zostanie natychmiast pobrana, a zajęcia oznaczone jako opłacone (przycisk ma treść „Potwierdź i przenieś”).
  • Opcja 2 (Płatność online — Przenieś i opłać): Jeśli środki w portfelu są niewystarczające lub przełącznik jest wyłączony, przycisk zmienia treść na „Przenieś i opłać”. Po zatwierdzeniu cykl zostaje przeniesiony, a klient zostaje natychmiast przekierowany do widoku /dashboard/payments w celu szybkiego uregulowania różnicy online (BLIK/P24).

C. Co z kolejnymi miesiącami cyklu?

  • Przyszłe, jeszcze nieopłacone miesiące ze starej grupy zostają automatycznie anulowane.
  • W ich miejsce generowane są nowe płatności ze stawką nowej grupy.
  • Klient opłaca je standardowo co miesiąc w zakładce Płatności, zgodnie z terminem płatności.

D. Paragon (lub faktura) po przeniesieniu

  • Przeniesienie nie jest nową sprzedażą — te same pieniądze zmieniają jedynie zajęcia, na które są zaksięgowane. Klub nie wystawia więc drugiego paragonu.
  • Dokument wystawiony przy pierwotnej wpłacie wędruje razem z pieniędzmi: nowe zajęcia w zakładce Płatności pokazują ten sam numer paragonu i ten sam link do e-Paragonu, co zajęcia w starej grupie.
  • Kwota, której przeniesione środki nie pokrywają (dopłata przy droższej grupie, kolejne miesiące), zostaje jako do zapłaty i otrzyma własny dokument dopiero w chwili faktycznej wpłaty.

🛠️ Dokumentacja techniczna dla programistów

1. Architektura i diagram przepływu


2. Moduły i pliki źródłowe

PlikRola i odpowiedzialność
lib/recurring-transfer.tsRdzenna logika biznesowa: pobieranie opcji przeniesienia (loadTransferOptions), walidacja miejsc, atomowa zmiana uczestnika w grach i kalkulacja finansowa (executeRecurringTransfer).
app/api/recurring-series/transfer/route.tsEndpoint HTTP GET oraz POST z obsługą rate limitera (20 req/min), weryfikacji sesji Better Auth i tokenu CSRF.
components/forms/recurring-series/transfer-cycle-dialog.tsxKomponent UI okna dialogowego z dynamicznym kalkulatorem nadpłat/niedopłat w czasie rzeczywistym.
app/(dashboard)/dashboard/user-activities/[playerId]/components/UserActivities.tsxIntegracja przycisku „Przenieś” w oknie szczegółów zajęć na desktopie.
app/(dashboard)/dashboard/user-activities/[playerId]/components/MobileActivitiesList.tsxIntegracja przycisku „Przenieś” w oknie szczegółów zajęć w widoku mobilnym.

3. Modele danych i powiązania

  • game.attendees: Tablica JSON [{ id: playerId }]. Podczas transferu uczestnik jest usuwany z gier starej serii i dodawany do gier nowej serii.
  • game.event_log: Rejestrowane są wpisy zdarzeń transferred_out (ze wskazaniem ID nowej serii) oraz transferred_in (ze wskazaniem ID starej serii).
  • payment:
    • Płatności powiązane z serią są dopasowywane przez related_ids[0] (ID zajęć) do gier o danym recurring_series_id. createPaymentsForRecurringSeries zapisuje [gameId, seriesId], ale płatności utworzone przy dopisaniu uczestnika do istniejącej serii z panelu mają samo [gameId] — dopasowanie po samym related_ids pomijało je, przez co opłacone zajęcia nie były zwracane, a klient płacił drugi raz w nowej grupie.
    • Płatności za przyszłe miesiące posiadają flagę monthly_only = 1.
    • Stare opłacone płatności przechodzą w stan status = 'refunded' z funds_retained = 1, co odzwierciedla przetransferowanie środków do nowego cyklu.
    • Nowe płatności opłacone z przeniesionych środków dziedziczą invoice_id / invoice_number / receipt_id / receipt_number / e_receipt_view_url po zatrzymanych płatnościach źródłowych. Bez tego przeniesiona kwota wyglądałaby na niezafiskalizowaną sprzedaż i trafiłaby do raportu lib/missing-documents-report.ts, a wystawienie z tego raportu zdublowałoby paragon.
    • Przydziałem dokumentu zajmuje się takeCarriedDocument(): zatrzymane środki tworzą kolejkę konsumowaną w kolejności wstawiania nowych płatności. Płatność dziedziczy dokument tylko gdy przeniesione środki pokrywają ją w całości i bierze referencję źródła, które pokryło jej największą część. Reszta (np. część finansowana z portfela przy dopłacie) zostaje bez dokumentu.
  • wallet_transaction:
    • W przypadku nadpłaty tworzona jest transakcja typu credit zwiększająca saldo portfela klienta.
    • W przypadku dopłaty z portfela tworzona jest transakcja typu debit.

4. Testy automatyczne

Kompletny zestaw testów jednostkowych, integracyjnych i komponentowych:

  • Logika biznesowa: __tests__/lib/recurring-transfer.test.ts (19 testów: autoryzacja, blokada przepełnionych grup, rozliczenie nadpłat do portfela, rozliczenie dopłat, obsługa 1:1, statusy płatności, płatności z related_ids bez ID serii, dziedziczenie paragonu przez przeniesione środki oraz alokacja takeCarriedDocument).
  • Endpoint API: __tests__/api/recurring-transfer-route.test.ts (7 testów: CSRF, autoryzacja, walidacja parametrów GET/POST, propagacja błędów).
  • Komponent UI: __tests__/components/forms/transfer-cycle-dialog.test.tsx (4 testy: renderowanie grup, dynamiczne alerty nadpłaty/niedopłaty, przełącznik portfela, wysyłka formularza).

Uruchomienie testów:

yarn test __tests__/lib/recurring-transfer.test.ts __tests__/api/recurring-transfer-route.test.ts __tests__/components/forms/transfer-cycle-dialog.test.tsx