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:
- 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.
- 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 uczestnika | Kategoria |
|---|---|
| poniżej 18 lat | Lekcje grupowe – Dzieci |
| 18–59 lat | Lekcje grupowe – Dorośli |
| 60 lat i więcej | Lekcje 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 przezsale_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).
| Priorytet | Warunek | Kategoria (kod) |
|---|---|---|
| 1 | payment.revenue_category ustawione (jawny override) | wartość zapisana |
| 2 | payment_type = 'court_reservation' | court_rental |
| 3 | payment_type = 'camp' | camp |
| 4 | activity_types.type = 'individual' | lesson_individual |
| 5 | activity_types.type = 'group' i wiek uczestnika < 18 | lesson_group_children |
| 6 | activity_types.type = 'group' i wiek 18–59 | lesson_group_adults |
| 7 | activity_types.type = 'group' i wiek ≥ 60 | lesson_group_seniors |
| 8 | activity_types.type = 'group' bez daty urodzenia | lesson_group_adults (domyślnie) |
| 9 | activity_types.type = 'booking' | court_rental |
| 10 | activity_types.type = 'other' | activity_other |
| 11 | Sprzedaż sklepowa | kategoria produktu (gastronomy / voucher / other_income) |
| 12 | Nic 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łaresolveRevenueCategory(...), korzystając zpayment_type,activity_type_type,player_date_of_birthoraz ewentualnegorevenue_category. Zapytanie SQL dołączapl.date_of_birthip.revenue_category.transformSaleToTransaction– ustawia kategorię z podzapytania wyliczającego dominującąproducts.revenue_categorydla danegosale.id.getFinancialSummary– w tej samej pętli, która agreguje metody płatności, dokłada te same kwoty dorevenueCategoryBreakdown, dzięki czemu suma kategorii uzgadnia się ztotalRevenue(płatności split są przybliżane pełną kwotą transakcji).- Filtr –
FinanceFilters.revenueCategorieszawęża listę transakcji po wyliczonej kategorii (parametr URLrevenueCategories).
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– agregacjarevenueCategoryBreakdownw podsumowaniu.