Przejdź do głównej zawartości

Zawieszanie konta klienta

Mechanizm automatycznego zawieszania klientów z zaległościami w płatnościach. Zawieszony klient jest oznaczany, traci część benefitów i nie może dokonywać nowych zapisów/rezerwacji — dopóki nie ureguluje zaległości. Uregulowanie może nastąpić od razu przy rezerwacji (spłata zaległości razem z nową rezerwacją w jednej płatności online).

Zawieszenie jest wyliczane w locie na podstawie stanu zaległości i ustawień miasta — nie jest zapisywane jako trwały stan, dzięki czemu automatycznie znika po spłacie.

👤 Instrukcja dla pracownika (Administrator)

Ścieżka: Dashboard ➔ Ustawienia ➔ Ograniczenia dla klientów

Włączenie zawieszania

Przełącznik „Blokuj klientów z zaległościami" jest głównym włącznikiem funkcji. Gdy jest wyłączony, żaden klient nie jest zawieszany, niezależnie od zaległości.

Kryteria zawieszenia

Po włączeniu głównego przełącznika można wybrać jedno lub kilka kryteriów. Spełnienie któregokolwiek z włączonych kryteriów zawiesza konto (logika „LUB"):

  • Według liczby dni zwłoki – zawieszenie, gdy najstarsza zaległość przekroczy ustawioną liczbę dni (np. 30).
  • Według kwoty zaległości – zawieszenie, gdy suma zaległości osiągnie ustawioną kwotę (zł).
  • Według liczby zaległych płatności – zawieszenie, gdy liczba zaległych płatności osiągnie ustawioną wartość.

Kryteria można łączyć dowolnie. Przykład: „30 dni lub 500 zł" oznacza, że klient zostanie zawieszony, gdy którakolwiek zaległość ma ponad 30 dni albo suma zaległości sięga 500 zł.

Ustawienia są definiowane per miasto — każde miasto może mieć własne progi.

Pozostałe ograniczenia

  • Blokuj odwoływanie zajęć – zawieszony klient nie odwoła zaplanowanych zajęć.
  • Blokuj odrabianie zajęć – zawieszony klient nie zapisze się na odrabianie.

Administrator i backoffice nie podlegają blokadom (mogą działać w imieniu klienta).

👤 Instrukcja dla klienta

Gdy konto jest zawieszone:

  • Na ekranie Płatności pojawia się nakładka informująca o zawieszeniu i łącznej kwocie zaległości z przyciskiem szybkiej płatności.
  • Zapisy na zajęcia, treningi indywidualne oraz standardowe rezerwacje są zablokowane.
  • Rezerwacja kortu jest możliwa pod warunkiem jednoczesnej spłaty zaległości. Przy rezerwacji wyświetlany jest komunikat o zaległościach; po potwierdzeniu klient jest przekierowany do jednej płatności online obejmującej rezerwację + wszystkie zaległości. Po zaksięgowaniu wpłaty konto przestaje być zawieszone.
  • W trakcie zawieszenia klient traci benefity: indywidualne (negocjowane) ceny nie są stosowane (obowiązuje cena podstawowa), a kody rabatowe są niedostępne. Karty benefit (np. Multisport) pozostają aktywne.

👨‍🏫 Widok trenera

Zawieszony uczestnik pozostaje zapisany na zajęcia — nie jest usuwany z grafiku ani listy obecności, więc:

  • cena zajęć i rozliczenia nie ulegają zmianie,
  • trener widzi uczestnika z czerwoną etykietą „Zawieszony" oraz przekreślonym nazwiskiem,
  • obecność można zaznaczać normalnie.

Dzięki temu zawieszenie jest sygnałem informacyjnym dla trenera, a nie operacją wypisania z zajęć.


🛠️ Dokumentacja techniczna

Model danych

Zawieszenie nie ma własnej kolumny w bazie — jest polem wyliczanym (is_suspended, suspension_reason) doklejanym do obiektu Player w czasie odczytu.

Ustawienia przechowywane są w tabeli app_settings (klucz/wartość, per city + tenant_id). Klucze funkcji:

KluczTypDomyślnieZnaczenie
block_clients_with_overduebool0Główny włącznik funkcji
overdue_by_days_enabledbool1Kryterium: dni zwłoki
overdue_threshold_daysint30Próg dni
overdue_by_amount_enabledbool0Kryterium: kwota
overdue_threshold_amountnumber0Próg kwoty (zł)
overdue_by_count_enabledbool0Kryterium: liczba płatności
overdue_threshold_countint0Próg liczby zaległych płatności

Brak migracji schematu — nowe klucze są odczytywane z domyślnymi wartościami, co zachowuje zgodność wsteczną (istniejące konfiguracje działają jak dotąd — tryb „tylko dni").

Centralna logika (lib/actions/payment.ts)

Cała ewaluacja zaległości przechodzi przez jeden zestaw funkcji, aby uniknąć rozjazdu między miejscami użycia:

  • getOwnerOverdueStats(ownerEmail){ totalArrears, overdueCount, maxOverdueDays, paymentIds } — jedno zapytanie agregujące zaległe płatności właściciela (status pending/overdue, due_date w przeszłości, z pominięciem wygasłych linków płatności).
  • evaluateSuspensionCriteria(settings, stats)boolean — czysta funkcja reguł w lib/utils/suspension.ts (poza plikiem 'use server', bo jest synchroniczna). Zwraca true, gdy główny włącznik jest aktywny, istnieją zaległości i spełnione jest którekolwiek z włączonych kryteriów (LUB). Gdy nie włączono żadnego kryterium, stosuje próg dni (fallback zgodności wstecznej).
  • isOwnerSuspendedBySettings(ownerEmail, settings)Promise<boolean> — połączenie powyższych.
  • getCurrentUserOverdueInfo(){ isSuspended, totalArrears, paymentIds } — dla zalogowanego klienta (na podstawie jego owner_email).

Miejsca korzystające z tej logiki: appendSuspensionStatus/appendSuspensionStatusBatch (lib/actions/players.ts), blokady zapisów (lib/actions/game.ts), blokady odwołań/odrabiania (lib/actions/substitution.ts), blokada rezerwacji (lib/actions/booking.ts), formularze klienta (grafik, treningi indywidualne).

Utrata benefitów

  • Ceny indywidualne — funkcja applyClientPricingOverride (lib/utils/client-pricing.ts) przyjmuje opcję { skipForSuspension }. Gdy true, zwraca bazowy typ aktywności bez nadpisań cenowych. Włączane w ścieżce rezerwacji klienta (createCourtBooking) na podstawie overdueInfo.blocked.
  • Kody rabatowevalidateDiscountCode (lib/actions/discount-codes.ts) na wstępie sprawdza zawieszenie zalogowanego klienta (getCurrentUserOverdueInfo) i zwraca reason: 'account_suspended', blokując zastosowanie kodu.

Spłata zaległości przy rezerwacji

Trasa POST /api/payments/initialize przyjmuje tablicę paymentIds (do 50) i tworzy jedną transakcję Przelewy24. Dialog rezerwacji (components/schedule/court-booking-dialog.tsx) po utworzeniu rezerwacji, jeśli istnieją overduePaymentIds, wywołuje processPayments([reservationPaymentId, ...overduePaymentIds]) i przekierowuje do bramki płatności. Po opłaceniu zaległości znikają, więc zawieszenie ustaje automatycznie.

Widok trenera

Etykieta „Zawieszony" i przekreślenie nazwiska renderowane są w app/(dashboard)/dashboard/schedule/components/AttendanceChecker.tsx na podstawie pola is_suspended. Uczestnik nie jest usuwany z zajęć, więc rozliczenia i obecności pozostają nienaruszone.

Testy

  • __tests__/lib/actions/payment-overdue.test.ts — agregacja getOwnerOverdueStats oraz reguły evaluateSuspensionCriteria (dni / kwota / liczba / kombinacja / fallback).
  • __tests__/lib/actions/players.suspension.test.ts — doklejanie statusu zawieszenia do gracza.