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
- Kliknij w zajęcia w swoim kalendarzu, aby otworzyć okno Szczegóły zajęć.
- Kliknij przycisk „Przenieś”.
- 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).
- Wybierz nową grupę, do której chcesz przenieść uczestnika.
- System natychmiast wyświetli Rozliczenie finansowe:
- 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/paymentsw 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
| Plik | Rola i odpowiedzialność |
|---|---|
lib/recurring-transfer.ts | Rdzenna logika biznesowa: pobieranie opcji przeniesienia (loadTransferOptions), walidacja miejsc, atomowa zmiana uczestnika w grach i kalkulacja finansowa (executeRecurringTransfer). |
app/api/recurring-series/transfer/route.ts | Endpoint 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.tsx | Komponent UI okna dialogowego z dynamicznym kalkulatorem nadpłat/niedopłat w czasie rzeczywistym. |
app/(dashboard)/dashboard/user-activities/[playerId]/components/UserActivities.tsx | Integracja przycisku „Przenieś” w oknie szczegółów zajęć na desktopie. |
app/(dashboard)/dashboard/user-activities/[playerId]/components/MobileActivitiesList.tsx | Integracja 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) oraztransferred_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 danymrecurring_series_id.createPaymentsForRecurringSerieszapisuje[gameId, seriesId], ale płatności utworzone przy dopisaniu uczestnika do istniejącej serii z panelu mają samo[gameId]— dopasowanie po samymrelated_idspomijał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'zfunds_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_urlpo zatrzymanych płatnościach źródłowych. Bez tego przeniesiona kwota wyglądałaby na niezafiskalizowaną sprzedaż i trafiłaby do raportulib/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.
- Płatności powiązane z serią są dopasowywane przez
wallet_transaction:- W przypadku nadpłaty tworzona jest transakcja typu
creditzwiększająca saldo portfela klienta. - W przypadku dopłaty z portfela tworzona jest transakcja typu
debit.
- W przypadku nadpłaty tworzona jest transakcja typu
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 zrelated_idsbez ID serii, dziedziczenie paragonu przez przeniesione środki oraz alokacjatakeCarriedDocument). - 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