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).
  • FiltrFinanceFilters.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.