Skip to main content

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:

KonwencjaOpis
tenant_id TEXTKaż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 JSONCzęść relacji trzymana w JSON: game.attendees, game.event_log, payment.related_ids, court.opening_hours.
archived INTEGERMiękkie usuwanie (soft-delete) — rekordy nie są kasowane, tylko archived = 1.
id INTEGER PRIMARY KEYKlucz główny autoinkrementowany (poza tabelami pomocniczymi z kluczem złożonym).

⚠️ Relacje przez JSON nie są kluczami obcymi. game.attendees to tablica JSON odwołań do graczy, a payment.related_ids to 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

KolumnaTypOpis
idINTEGERPK
activity_type_idINTEGERactivity_types.id
court_idINTEGERcourt.id
recurring_series_idINTEGERrecurring_game_series.id (jeśli z serii)
start_time, end_timeDATETIMEZakres czasu
instructor_idTEXTE-mail/sub instruktora (luźny link do employee.email)
substitute_instructor_idTEXTZastępstwo
attendeesJSONTablica uczestników [{id, type, ...}]
event_logJSONTablica zdarzeń (źródło Historii → activity_log)
custom_priceREALNadpisanie ceny
archived, cancelled_at, game_expires_atBOOL/TEXTStan
tenant_idTEXTNajemca

recurring_game_series — definicja zajęć cyklicznych

KolumnaTypOpis
idINTEGERPK
activity_type_id, court_idINTEGERPowiązania
instructor_id, substitute_instructor_idTEXTProwadzący
day_of_weekINTEGERDzień tygodnia
start_time, end_timeTIMEGodziny
start_date, end_date, max_occurrencesDATE/INTZakres generowania
archived, tenant_id

court — kort/lokalizacja

KolumnaTypOpis
idINTEGERPK
name, city, streetTEXTLokalizacja (filtrowanie po city)
surface, descriptionTEXTOpis
opening_hours, closuresTEXT/JSONGodziny otwarcia i wyłączenia
tenant_idTEXT

activity_types — typy aktywności (słownik)

KolumnaTypOpis
idINTEGERPK
name, type, categoryTEXTNazwa, typ (individual/group/booking/other), kategoria (np. Wakacyjne)
price, pricing_type, pricing_rulesREAL/TEXTCennik
max_participants, age_from, age_toINTEGERLimity
payment_due_*, recurring_payment_due_*Reguły płatności
color, make_up, check_attendanceUI / obecność
archived, city, tenant_id

player — klient/uczestnik (w tym dzieci/podopieczni)

KolumnaTypOpis
idINTEGERPK
owner_email, client_idTEXTWłaściciel konta / Auth0
first_name, last_name, date_of_birth, peselTEXTDane osobowe
guardian_*, second_guardian_*TEXTOpiekunowie (dla dzieci)
allergies, medications, conditions, swim_ability, pickup_personsTEXTDane półkolonijne
activity_status, archived, inactive_*Status
address_*, cityTEXTAdres
tenant_idTEXT

booking — rezerwacja kortu (typ „booking")

KolumnaTypOpis
idINTEGERPK
game_idINTEGERgame.id (unikalny: idx_booking_game_id)
booked_for_player_idINTEGERplayer.id
booking_sourceTEXTonline / employee
created_by_employee_idTEXTPracownik
custom_price, applied_pricing_rule_id, benefit_card_numberWycena
cancelled_at, cancelled_by, cancellation_reasonAnulacja
confirmation_*_sent_at, invitation_link_sent_atPowiadomienia
door_pin, statute_accepted_at, archived, tenant_id

payment — płatność

KolumnaTypOpis
idINTEGERPK
player_idINTEGERplayer.id
related_idsTEXT(JSON)Tablica id gier, których dotyczy → zmaterializowane w payment_related_game
amount, original_amount, currencyREAL/TEXTKwota
status, payment_type, payment_methodTEXTStan i typ
mixed_payment_cash/card/wallet_amountREALPłatność mieszana
funds_retainedINTEGERŚrodki zatrzymane przy usunięciu uczestnika
invoice_id, receipt_id, e_receipt_view_urlDokumenty
paid_at, due_date, payment_expires_atTEXTTerminy
city, street, archived, tenant_id

wallet / employee (skrót)

  • walletid, user_email, balance, tenant_id; transakcje w wallet_transaction.
  • employeeid, first_name, last_name, email (klucz logiczny dla instructor_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).

Zastępuje korelowany EXISTS(json_each(payment.related_ids)) w kalendarzu.

KolumnaTypOpis
payment_idINTEGERpayment.id (część PK)
game_idINTEGERgame.id (część PK)
player_idINTEGERGracz płatności
tenant_idTEXT
  • 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.

KolumnaTypOpis
game_idINTEGERgame.id (część PK)
"key"INTEGERIndeks zdarzenia w event_log (część PK)
"value"TEXT(JSON)Surowe zdarzenie
event_typeTEXTTyp zdarzenia (indeksowany)
action_timestampTEXTCzas zdarzenia
tenant_idTEXT
  • 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.

DomenaTabele
Kalendarz / rezerwacjegame, recurring_game_series, booking, court, activity_types, reserve_list, game_substitution, individual_training_requests
Gracze / uczestnicyplayer, player_activity_types, player_task_notes
Płatności / finansepayment, payment_transaction, payment_link, linked_payment, wallet, wallet_transaction, invoice, cash_withdrawal, operating_cost, marketing_budget
Sklep / magazynproducts, 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 tenisowetennis_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 / instruktorzyemployee, instructor_availability, instructor_schedule_confirmation, trainer_rating, route_access
Auth / systemusers, accounts, sessions, local_user, verification_tokens, rate_limits, logs
Powiadomienia / mailingnotification_types, notification_texts, notification_preferences, notification_crons, critical_notifications, client_mail_preferences, mail_campaign, mail_campaign_recipient, mail_segment
CRM / wsparcietickets, ticket_assignees, ticket_labels, ticket_label_assignments, complaint, lead, churn_record, winback_action
Ustawienia / konfiguracjaapp_settings, booking_settings, client_settings, client_pricing_rules, holidays, discount_code, discount_code_usage, widget_page_configs
Zadania / checklistychecklist_task, checklist_completion
Hardwarehardware_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).