Schemat bazy danych
Dokumentacja techniczna schematu bazy danych aplikacji. Baza działa na Cloudflare D1 (SQLite na brzegu sieci), jest wielodostępna (multi-tenant) i liczy ~90 tabel.
👤 Dla kogo
To dokument techniczny (dla developerów). Opisuje strukturę danych: encje, relacje i konwencje. Nie jest instrukcją obsługi modułów — te znajdziesz w osobnych rozdziałach (Płatności, Półkolonie itd.).
🧱 Konwencje globalne
Te zasady obowiązują w (prawie) całej bazie:
| Konwencja | Opis |
|---|---|
tenant_id TEXT | Każda tabela domenowa ma tenant_id — izolacja danych między najemcami. Każde zapytanie filtruje po tenant_id. |
Daty jako TEXT (ISO) | Daty/czas trzymane jako ISO yyyy-MM-ddTHH:mm:ss.SSSZ (UTC). Strefa aplikacji: Europe/Warsaw — patrz lib/date.ts. |
Kolumny JSON | Część relacji trzymana w JSON: game.attendees, game.event_log, payment.related_ids, court.opening_hours. |
archived INTEGER | Miękkie usuwanie (soft-delete) — rekordy nie są kasowane, tylko archived = 1. |
id INTEGER PRIMARY KEY | Klucz główny autoinkrementowany (poza tabelami pomocniczymi z kluczem złożonym). |
⚠️ Relacje przez JSON nie są kluczami obcymi.
game.attendeesto tablica JSON odwołań do graczy, apayment.related_idsto tablica id gier. Dlatego dodaliśmy zmaterializowane tabele pomocnicze (payment_related_game,activity_log) — patrz sekcja Tabele wydajnościowe (materializacja) poniżej.
🗺️ Diagram ER (rdzeń kalendarza i płatności)
Linie pełne (
||--o{) = relacja przez kolumnę/klucz.}o--o{z dopiskiem „JSON"/„luźny" = relacja logiczna nieegzekwowana przez FK (przez tablicę JSON lub pole tekstowe).
🧩 Tabele rdzenia
game — pojedyncze zajęcia/rezerwacja w kalendarzu
| Kolumna | Typ | Opis |
|---|---|---|
id | INTEGER | PK |
activity_type_id | INTEGER | → activity_types.id |
court_id | INTEGER | → court.id |
recurring_series_id | INTEGER | → recurring_game_series.id (jeśli z serii) |
start_time, end_time | DATETIME | Zakres czasu |
instructor_id | TEXT | E-mail/sub instruktora (luźny link do employee.email) |
substitute_instructor_id | TEXT | Zastępstwo |
attendees | JSON | Tablica uczestników [{id, type, ...}] |
event_log | JSON | Tablica zdarzeń (źródło Historii → activity_log) |
custom_price | REAL | Nadpisanie ceny |
archived, cancelled_at, game_expires_at | BOOL/TEXT | Stan |
tenant_id | TEXT | Najemca |
recurring_game_series — definicja zajęć cyklicznych
| Kolumna | Typ | Opis |
|---|---|---|
id | INTEGER | PK |
activity_type_id, court_id | INTEGER | Powiązania |
instructor_id, substitute_instructor_id | TEXT | Prowadzący |
day_of_week | INTEGER | Dzień tygodnia |
start_time, end_time | TIME | Godziny |
start_date, end_date, max_occurrences | DATE/INT | Zakres generowania |
archived, tenant_id |
court — kort/lokalizacja
| Kolumna | Typ | Opis |
|---|---|---|
id | INTEGER | PK |
name, city, street | TEXT | Lokalizacja (filtrowanie po city) |
surface, description | TEXT | Opis |
opening_hours, closures | TEXT/JSON | Godziny otwarcia i wyłączenia |
tenant_id | TEXT |
activity_types — typy aktywności (słownik)
| Kolumna | Typ | Opis |
|---|---|---|
id | INTEGER | PK |
name, type, category | TEXT | Nazwa, typ (individual/group/booking/other), kategoria (np. Wakacyjne) |
price, pricing_type, pricing_rules | REAL/TEXT | Cennik |
max_participants, age_from, age_to | INTEGER | Limity |
payment_due_*, recurring_payment_due_* | Reguły płatności | |
color, make_up, check_attendance | UI / obecność | |
archived, city, tenant_id |
player — klient/uczestnik (w tym dzieci/podopieczni)
| Kolumna | Typ | Opis |
|---|---|---|
id | INTEGER | PK |
owner_email, client_id | TEXT | Właściciel konta / Auth0 |
first_name, last_name, date_of_birth, pesel | TEXT | Dane osobowe |
guardian_*, second_guardian_* | TEXT | Opiekunowie (dla dzieci) |
allergies, medications, conditions, swim_ability, pickup_persons | TEXT | Dane półkolonijne |
activity_status, archived, inactive_* | Status | |
address_*, city | TEXT | Adres |
tenant_id | TEXT |
booking — rezerwacja kortu (typ „booking")
| Kolumna | Typ | Opis |
|---|---|---|
id | INTEGER | PK |
game_id | INTEGER | → game.id (unikalny: idx_booking_game_id) |
booked_for_player_id | INTEGER | → player.id |
booking_source | TEXT | online / employee |
created_by_employee_id | TEXT | Pracownik |
custom_price, applied_pricing_rule_id, benefit_card_number | Wycena | |
cancelled_at, cancelled_by, cancellation_reason | Anulacja | |
confirmation_*_sent_at, invitation_link_sent_at | Powiadomienia | |
door_pin, statute_accepted_at, archived, tenant_id |
payment — płatność
| Kolumna | Typ | Opis |
|---|---|---|
id | INTEGER | PK |
player_id | INTEGER | → player.id |
related_ids | TEXT(JSON) | Tablica id gier, których dotyczy → zmaterializowane w payment_related_game |
amount, original_amount, currency | REAL/TEXT | Kwota |
status, payment_type, payment_method | TEXT | Stan i typ |
mixed_payment_cash/card/wallet_amount | REAL | Płatność mieszana |
funds_retained | INTEGER | Środki zatrzymane przy usunięciu uczestnika |
invoice_id, receipt_id, e_receipt_view_url | Dokumenty | |
paid_at, due_date, payment_expires_at | TEXT | Terminy |
city, street, archived, tenant_id |
wallet / employee (skrót)
wallet—id,user_email,balance,tenant_id; transakcje wwallet_transaction.employee—id,first_name,last_name,email(klucz logiczny dlainstructor_id),job,city,tenant_id.
⚡ Tabele wydajnościowe (materializacja)
Dodane, by uniknąć skanów json_each(...) po kolumnach JSON. Utrzymywane triggerami — zawsze spójne z danymi źródłowymi (ta sama transakcja).
payment_related_game — most płatność↔gra
Zastępuje korelowany EXISTS(json_each(payment.related_ids)) w kalendarzu.
| Kolumna | Typ | Opis |
|---|---|---|
payment_id | INTEGER | → payment.id (część PK) |
game_id | INTEGER | → game.id (część PK) |
player_id | INTEGER | Gracz płatności |
tenant_id | TEXT |
- Indeks:
(tenant_id, game_id, player_id) - Triggery na
payment:AFTER INSERT / UPDATE OF related_ids,player_id / DELETE - Migracja:
0163_create_payment_related_game.sql
activity_log — zmaterializowany feed Historii
Jeden wiersz na zdarzenie z game.event_log. Kolumny "key"/"value" celowo naśladują json_each, by builder Historii nie zmieniał logiki ekstrakcji.
| Kolumna | Typ | Opis |
|---|---|---|
game_id | INTEGER | → game.id (część PK) |
"key" | INTEGER | Indeks zdarzenia w event_log (część PK) |
"value" | TEXT(JSON) | Surowe zdarzenie |
event_type | TEXT | Typ zdarzenia (indeksowany) |
action_timestamp | TEXT | Czas zdarzenia |
tenant_id | TEXT |
- Indeks:
(tenant_id, event_type, action_timestamp) - Triggery na
game:AFTER INSERT / UPDATE OF event_log / DELETE - Migracja:
0164_create_activity_log.sql
📚 Pełny inwentarz tabel (90)
Pogrupowane domenowo.
| Domena | Tabele |
|---|---|
| Kalendarz / rezerwacje | game, recurring_game_series, booking, court, activity_types, reserve_list, game_substitution, individual_training_requests |
| Gracze / uczestnicy | player, player_activity_types, player_task_notes |
| Płatności / finanse | payment, payment_transaction, payment_link, linked_payment, wallet, wallet_transaction, invoice, cash_withdrawal, operating_cost, marketing_budget |
| Sklep / magazyn | products, sales, sale_items, stock_purchases, stock_adjustments |
| Półkolonie (camps) | camp_season, camp_term, camp_registration, camp_attendance, camp_waitlist, camp_season_instructor, camp_term_instructor, camp_payment_link, camp_discount_rule, camp_document, camp_feedback, camp_audit_log |
| Kursy tenisowe | tennis_course_season, tennis_course_term, tennis_course_registration, tennis_course_attendance, tennis_course_waitlist, tennis_course_term_instructor, tennis_course_payment_link, tennis_course_discount_rule, tennis_course_document, tennis_course_audit_log |
| Pracownicy / instruktorzy | employee, instructor_availability, instructor_schedule_confirmation, trainer_rating, route_access |
| Auth / system | users, accounts, sessions, local_user, verification_tokens, rate_limits, logs |
| Powiadomienia / mailing | notification_types, notification_texts, notification_preferences, notification_crons, critical_notifications, client_mail_preferences, mail_campaign, mail_campaign_recipient, mail_segment |
| CRM / wsparcie | tickets, ticket_assignees, ticket_labels, ticket_label_assignments, complaint, lead, churn_record, winback_action |
| Ustawienia / konfiguracja | app_settings, booking_settings, client_settings, client_pricing_rules, holidays, discount_code, discount_code_usage, widget_page_configs |
| Zadania / checklisty | checklist_task, checklist_completion |
| Hardware | hardware_device, hardware_sensor, hardware_event_log |
| Wydajność (materializacja) | activity_log, payment_related_game |
🔧 Migracje
Schemat budowany jest przyrostowo migracjami migrations/NNNN_*.sql, aplikowanymi w kolejności przez yarn db:update (wrangler d1 migrations apply). Zasady numerowania i kolejności — patrz CLAUDE.md (sekcja Database Migrations).