Przejdź do głównej zawartości

Płatności nierozliczone

Zestawienie wszystkich nieopłaconych pozycji w wybranym okresie, pogrupowane po uczestniku — odpowiednik zakładki „Nierozliczone" znanej z panelu kluby.org.

👤 Instrukcja dla pracownika (Recepcja / Administracja)

Ścieżka: Dashboard ➔ Płatności ➔ Nierozliczone

Zakładka odpowiada na jedno pytanie: kto i ile jest nam winien w danym miesiącu. Domyślnie pokazuje bieżący miesiąc i lokalizację wybraną w górnym pasku (miasto + ulica).

1. Lista uczestników

KolumnaZnaczenie
UczestnikOsoba, której dotyczą zajęcia / rezerwacje
Opiekun / kontoKonto klienta, na którym wisi płatność (rodzic, opiekun prawny)
PozycjeLiczba nieopłaconych pozycji w zakresie dat
Do zapłatySuma tych pozycji; kliknięcie otwiera szczegóły

Na dole listy widoczna jest suma Łącznie dla całego zestawienia.

Wiersz to para uczestnik + konto. Jeśli płatności jednego uczestnika wiszą na dwóch adresach (tak zostaje po zmianie e-maila konta), zobaczysz dwa wiersze — każdy rozliczasz osobno, z portfelem i limitem zaległości tego właśnie konta.

Przejście do szczegółów zabiera ze sobą wszystkie aktywne filtry (zakres dat, typ produktu, lokalizacja, archiwalne, konto), więc kwota w szczegółach zgadza się z kwotą w wierszu, a „Rozlicz" nie sięga po pozycje, których lista nie liczyła.

2. Wyszukiwarka (filtry)

Przycisk Wyszukiwarka rozwija panel filtrów:

  • Od / Do — zakres dat rozliczeniowych. Datą rozliczeniową jest termin zajęć, a gdy go nie ma — termin płatności, a w ostateczności data utworzenia pozycji.
  • Szukaj — imię, nazwisko uczestnika lub e-mail konta.
  • Typ produktu — Rezerwacje, Szkółka, Zajęcia indywidualne, Zajęcia próbne, Inne zajęcia, Półkolonie, Weekend z tenisem. Domyślnie zaznaczone wszystkie. Doładowań portfela tu nie ma: ich rozliczenie musi jednocześnie zasilić portfel, co robi wyłącznie okno doładowania przy ladzie.
  • Uwzględnij ➔ Archiwalne — dokłada pozycje z profili archiwalnych.

Miasto i ulica pochodzą z globalnego selektora lokalizacji na górze ekranu, tak samo jak na pozostałych stronach finansowych.

3. Rozliczenie zbiorcze

Zaznacz uczestników i naciśnij przycisk pod listą. To, co dostajesz, zależy od tego, czy zaznaczenie mieści się w jednym koncie opiekuna.

Jedno konto → przycisk Rozlicz otwiera to samo okno rozliczenia, co reszta aplikacji (PaymentMethodDialog): karty benefitowe, zapłata z portfela z widocznym saldem, a dalej Gotówka / Karta / Płatność mieszana / Zaległość, komentarz i „Drukuj paragon". Jeśli któryś z zaznaczonych klientów prosi o fakturę, okno o tym ostrzega, zanim wydrukujesz paragon.

Kilka kont → zostaje Rozlicz jako zaległość z potwierdzeniem, a pod listą pojawia się wyjaśnienie. Gotówka, karta i płatność mieszana to jedna transakcja przy kasie: jedna osoba podaje jedną kwotę, a płatność mieszana rozdziela część gotówkową i kartową po wszystkich zaznaczonych pozycjach — w poprzek rodzin nie miałoby to pokrycia w tym, kto ile realnie zapłacił. Zaległość nie dotyka kasy ani dokumentów, więc działa dla wielu kont naraz.

Kiedy widać „Zaległość"

Zaległość pojawia się tylko dla klientów, którzy mogą ją mieć — czyli z client_settings.allow_arrears = 1. Reguły:

SytuacjaCo widzi pracownik
Jedno konto, ma zgodęPrzycisk Zaległość z podpisem „Pozostały limit zaległości: X zł"
Jedno konto, ma zgodę bez limituPrzycisk Zaległość z podpisem „Bez limitu zaległości"
Jedno konto, kwota przekracza pozostały limitPrzycisk widoczny, ale nieaktywny, z informacją o przekroczeniu
Jedno konto, brak zgodyPrzycisku nie ma w oknie w ogóle
Kilka kont, część ma zgodę„N z M kont nie ma zgody na zaległości — te pozycje zostaną pominięte"; rozliczane są wyłącznie konta ze zgodą
Kilka kont, żadne nie ma zgodyPrzycisk nieaktywny + „Żadne z zaznaczonych kont nie ma zgody na zaległości"

Limit liczony jest tak samo jak w oknie rozliczenia rezerwacji: available = saldo portfela + limit, gdzie limit to client_settings.max_arrears_amount, a gdy go nie ma — max_arrears_amount_default z ustawień ograniczeń klienta dla miasta konta.

Komentarz wpisany w oknie zapisuje się w payment_comment na każdej rozliczanej pozycji — także przy rozliczeniu wielu pozycji naraz i przy zaległości.

W obu przypadkach system zbiera wszystkie nierozliczone pozycje zaznaczonych uczestników z aktualnie ustawionego zakresu i filtrów, a po zakończeniu pokazuje podsumowanie — ile pozycji rozliczono, a ile odrzucono (np. gdy klient nie ma zgody na zaległości albo przekroczyłby limit).

Dokumenty przy rozliczeniu zbiorczym

Zaznaczenie kilku uczestników nie tworzy jednego wspólnego paragonu. processPaymentStatusChange grupuje pozycje kluczem player-{player_id}-{payment_type} i wystawia dokument osobno dla każdego uczestnika i typu produktu. Zaległość nie generuje dokumentu w ogóle — kończy się statusem paid_wallet, a generator dokumentów bierze pod uwagę tylko paid, paid_cash, paid_card, paid_online i paid_mixed.

4. Szczegóły uczestnika

Kliknięcie nazwiska lub kwoty otwiera widok pojedynczego uczestnika:

  • Lista pozycji pogrupowana po typie produktu, każda z własnym checkboxem (domyślnie wszystkie zaznaczone).
  • Podsumowanie — kwoty w rozbiciu na typy produktu i łączna suma.
  • Formularz opłaty — kwota do zapłaty za zaznaczone pozycje i przycisk Rozlicz.

Ponieważ tu jest jedno konto i jeden portfel, okno rozliczenia startuje od kroku „Rabaty i kwota":

  1. Karty benefitowe (Multisport, Medicover, PZU Sport, Fit Profit) i Zapłać z portfela z widocznym saldem klienta.
  2. Metoda: Gotówka, Karta, Płatność mieszana albo Zaległość, plus komentarz i „Drukuj paragon".

Po rozliczeniu następuje powrót do listy.

🛠️ Dokumentacja techniczna

Trasy

ŚcieżkaWariantyRole
/dashboard/unsettled-paymentsadminADMIN, BACKOFFICE
/dashboard/unsettled-payments/[playerId]adminADMIN, BACKOFFICE

Wpisy w route_access dodaje migracja 0266_add_unsettled_payments_route.sql; bez nich pozycja menu byłaby widoczna wyłącznie dla ADMIN-a (strony bez wiersza w route_access widzi w menu tylko ADMIN).

Pliki

PlikRola
lib/unsettled-payments.tsTypy, lista typów produktu, parseProductTypes
lib/actions/unsettled-payments.tsZapytania i akcja rozliczenia (server actions)
app/(dashboard)/dashboard/unsettled-payments/page.tsxLista uczestników
app/(dashboard)/dashboard/unsettled-payments/[playerId]/page.tsxSzczegóły uczestnika
components/tables/unsettled-payments-table/*Filtry, tabela, widok szczegółów
components/tables/unsettled-payments-table/settle-selected.tsWspólna ścieżka rozliczenia dla listy i szczegółów
lib/invoice-notice.tsPobranie preferencji fakturowych dla ostrzeżenia o paragonie

getOwnersArrearsAllowance w lib/actions/unsettled-payments.ts odpowiada na pytanie „które z tych kont mogą mieć zaległość" jednym zapytaniem na tabelę (client_settings, wallet, user.preferredCity) zamiast pary zapytań na klienta — inaczej „Zaznacz wszystkie" na pełnej liście odpalałoby kilkadziesiąt zapytań naraz. Miasto do domyślnego limitu czytane jest z konta (tak samo jak getUserCity, które sprawdza limit przy samym obciążeniu portfela), a nie z player.city — inaczej podpowiedź w oknie mówiłaby o innym limicie niż ten realnie egzekwowany.

Definicja „nierozliczonej" pozycji

p.status IN ('pending', 'overdue')
AND p.archived = 0 -- chyba że zaznaczono "Archiwalne"
AND NOT (wygasła rezerwacja online) -- payment_expires_at w przeszłości
AND NOT EXISTS (zajęcia odwołane lub zarchiwizowane)
AND date(<data rozliczeniowa PL>) BETWEEN :dateFrom AND :dateTo
AND <typ produktu> IN (:productTypes)

Data rozliczeniowa to COALESCE(g.start_time, p.due_date, p.created_at) przeliczone na czas Polski przez sqlPolandLocal — ta sama reguła, według której miesiąc wyznacza tabela płatności.

Typ produktu ≠ payment_type

Szkółka, zajęcia indywidualne i zajęcia „inne" mają ten sam payment_type = 'school' i różnią się dopiero typem rodzaju zajęć (activity_types.type). Filtr liczy więc PRODUCT_KIND_SQL:

Typ produktu w filtrzeWarunek
Rezerwacjepayment_type = 'court_reservation'
Zajęcia próbnepayment_type = 'trial'
Półkoloniepayment_type = 'camp'
Weekend z tenisempayment_type = 'tennis_course'
Zajęcia indywidualneactivity_types.type = 'individual'
Inne zajęciaactivity_types.type = 'other'
Szkółkapozostałe (w tym school bez dopiętych zajęć)

Płatność typu school, do której nie udało się dopiąć zajęć, trafia do „Szkółki" — inaczej zniknęłaby ze wszystkich filtrów naraz.

Doładowania portfela (payment_type = 'wallet_topup') są odcięte osobnym warunkiem SQL, a nie tylko brakiem pozycji w filtrze. Zamknięcie doładowania musi też uznać portfel — robi to jedynie settleWalletTopupAtDesk, które przy nieudanym uznaniu cofa płatność do pending. Rozliczone stąd doładowanie zabrałoby pieniądze bez podniesienia salda, a „Zaległość" obciążyłaby portfel kwotą, którą klient chciał go zasilić.

Przepływ rozliczenia zbiorczego

Kolejność w settleSelectedPayments nie jest przypadkowa: portfel obciążamy przed oznaczeniem czegokolwiek jako opłacone i sprawdzamy, czy nie pokrył całej kwoty. Bez tego dopłata gotówką na wierzchu pobrałaby pieniądze drugi raz.

Rozliczenie idzie pozycja po pozycji, bo markPaymentAsArrears „zajmuje" płatność warunkowym UPDATE-em przed obciążeniem portfela — to właśnie ten warunek chroni przed podwójnym obciążeniem, gdy dwie osoby na recepcji klikną rozliczenie równocześnie.

Rabat, karty benefitowe i kwota z portfela wpisane w pierwszym kroku okna działają tak samo dla „Zaległości", jak dla gotówki: wspólne applyPreApply obniża kwoty płatności, zanim cokolwiek zostanie zamknięte, więc na konto klienta jako dług idzie kwota po odliczeniach, a nie pierwotna.

Limit jednego rozliczenia

Jednorazowo rozliczanych jest maksymalnie 50 pozycji (MAX_BULK_SETTLE_PAYMENTS). Każda zaległość to kilkanaście zapytań do D1, a cała paczka wykonuje się w jednym wywołaniu Workera — większe zaznaczenie zostałoby przerwane w połowie i nikt nie wiedziałby, które portfele zdążyły zostać obciążone. Limit sprawdza zarówno przeglądarka (komunikat „Za dużo pozycji naraz"), jak i sama akcja serwerowa, która w takim wypadku nie rozlicza niczego.

Lista, która zmieniła się w międzyczasie

Identyfikatory płatności są odczytywane dopiero przy zatwierdzeniu, razem z ich sumą. Jeśli suma różni się od tej, którą pokazywała strona (ktoś opłacił pozycję online, druga recepcja rozliczyła tego samego klienta), rozliczenie jest przerywane, lista się odświeża, a recepcja zaznacza pozycje jeszcze raz — inaczej podział na gotówkę i kartę wpisany dla starej kwoty rozłożyłby się na mniejszy zbiór pozycji i kasa pokazałaby więcej, niż wynosiły płatności.

Limity D1

Zapytania po liście identyfikatorów (uczestnicy, płatności) są dzielone na paczki poniżej limitu 100 parametrów D1, tak samo jak w lib/actions/payment.ts.