Przejdź do głównej zawartości

Kategorie przychodów

Funkcja dzieli wszystkie przychody obiektu na jednolite kategorie sprzedaży, tak aby w zakładce Finanse można było zobaczyć nie tylko jak zapłacono (gotówka/karta/online), ale też za co zapłacono (wynajem kortu, lekcja, gastronomia itd.).

👤 Instrukcja dla pracownika​

Po co to jest​

Do tej pory Finanse pokazywały łączny przychód i podział na metody płatności. Teraz każda złotówka trafia dodatkowo do jednej z kategorii przychodu. Dzięki temu na koniec miesiąca od razu widać, ile obiekt zarobił na wynajmie kortów, ile na szkółkach dziecięcych, a ile na barze.

Lista kategorii​

  • Wynajem kortów
  • Lekcje indywidualne
  • Lekcje grupowe – Dzieci
  • Lekcje grupowe – Dorośli
  • Lekcje grupowe – Seniorzy
  • Zajęcia inne
  • Półkolonie
  • Gastronomia
  • Vouchery
  • Inne przychody

Jak to działa przy sprzedaży (najważniejsze)​

Aby raport był poprawny, kategoria musi być znana już w chwili sprzedaży. System pilnuje tego w dwóch miejscach:

  1. Sklep / bar (produkty). Zakładając lub edytując produkt w module Sklep, musisz wybrać jego kategorię przychodu: Gastronomia, Vouchery albo Inne przychody. Od tego momentu każda sprzedaż tego produktu automatycznie liczy się do właściwej kategorii – kasjer nie musi już nic wybierać przy paragonie.
  2. Zajęcia i rezerwacje. Tutaj kategoria wyliczana jest automatycznie na podstawie rodzaju zajęć oraz wieku uczestnika, więc nie trzeba jej wskazywać ręcznie (patrz tabela poniżej).

Podział lekcji grupowych po wieku​

Lekcje grupowe rozdzielane są na Dzieci / Dorosłych / Seniorów zawsze na podstawie daty urodzenia uczestnika (a nie osoby płacącej), według stanu na dzień płatności:

Wiek uczestnikaKategoria
poniżej 18 latLekcje grupowe – Dzieci
18–59 latLekcje grupowe – Dorośli
60 lat i więcejLekcje grupowe – Seniorzy

Gdzie to zobaczysz​

  • W zakładce Finanse pojawił się kafelek „Przychody wg kategorii” z paskami udziału procentowego każdej kategorii.
  • W filtrach dodano sekcję „Kategorie przychodów” – możesz zawęzić listę transakcji i podsumowania do wybranych kategorii.

🛠️ Dokumentacja techniczna​

Model danych​

  • payment.revenue_category (TEXT, nullable) – opcjonalnie zapisana jawna kategoria płatności.
  • products.revenue_category (TEXT) – wymagana w formularzu produktu; sprzedaż sklepowa dziedziczy kategorię z produktu przez sale_items → products.
  • Migracje: 0175_add_revenue_category.sql (kolumny + indeksy), 0176_backfill_revenue_category.sql (uzupełnienie danych historycznych).

Kanoniczna lista kodów oraz cała logika rozstrzygania znajdują się w lib/revenue-category.ts (RevenueCategory, resolveRevenueCategory, progi wiekowe CHILD_MAX_AGE_EXCLUSIVE = 18, SENIOR_MIN_AGE = 60).

Tabela warunków – jak liczona jest kategoria​

Rozstrzyganie odbywa się w tej kolejności (pierwszy pasujący warunek wygrywa). Wiek liczony jest z player.date_of_birth względem paid_at (lub created_at).

PriorytetWarunekKategoria (kod)
1payment.revenue_category ustawione (jawny override)wartość zapisana
2payment_type = 'court_reservation'court_rental
3payment_type = 'camp'camp
4activity_types.type = 'individual'lesson_individual
5activity_types.type = 'group' i wiek uczestnika < 18lesson_group_children
6activity_types.type = 'group' i wiek 18–59lesson_group_adults
7activity_types.type = 'group' i wiek ≥ 60lesson_group_seniors
8activity_types.type = 'group' bez daty urodzenialesson_group_adults (domyślnie)
9activity_types.type = 'booking'court_rental
10activity_types.type = 'other'activity_other
11Sprzedaż sklepowakategoria produktu (gastronomy / voucher / other_income)
12Nic z powyższych (np. trial, brak powiązanych zajęć)other_income

Sprzedaż w sklepie zawierająca produkty z różnych kategorii przypisywana jest do kategorii o najwyższej wartości pozycji w danym paragonie (dominująca kategoria).

Flow w API / Server Actions​

  • transformPaymentToTransaction (lib/actions/finances.ts) – dla każdej płatności woła resolveRevenueCategory(...), korzystając z payment_type, activity_type_type, player_date_of_birth oraz ewentualnego revenue_category. Zapytanie SQL dołącza pl.date_of_birth i p.revenue_category.
  • transformSaleToTransaction – ustawia kategorię z podzapytania wyliczającego dominującą products.revenue_category dla danego sale.id.
  • getFinancialSummary – w tej samej pętli, która agreguje metody płatności, dokłada te same kwoty do revenueCategoryBreakdown, dzięki czemu suma kategorii uzgadnia się z totalRevenue (płatności split są przybliżane pełną kwotą transakcji).
  • Filtr – FinanceFilters.revenueCategories zawęża listę transakcji po wyliczonej kategorii (parametr URL revenueCategories).

UI​

  • Widget finances.revenueCategoryBreakdown (components/widgets/finances/RevenueCategoryBreakdownWidget.tsx) – paski udziału kategorii.
  • Filtr kategorii w FinanceFilters.tsx.
  • Wymagany wybór kategorii produktu w ProductsTab.tsx (moduł Sklep).

Testy​

  • __tests__/lib/revenue-category.test.ts – logika rozstrzygania i liczenia wieku.
  • __tests__/lib/actions/finances.test.ts – agregacja revenueCategoryBreakdown w podsumowaniu.