242 lines
7.5 KiB
SQL
242 lines
7.5 KiB
SQL
-- Database Schema Draft for Meldestelle (Offline-First)
|
|
-- Dialect: SQLite (compatible with SQLDelight)
|
|
-- Status: Draft / Proposal
|
|
-- Based on: OEPS Legacy Spec V2.4 & Domain Analysis
|
|
|
|
-- ==================================================================
|
|
-- 1. CORE INFRASTRUCTURE (Sync & Audit)
|
|
-- ==================================================================
|
|
-- Every table should ideally have these fields, but for brevity
|
|
-- they are implied or added where critical.
|
|
-- id: TEXT NOT NULL PRIMARY KEY (UUID)
|
|
-- created_at: INTEGER NOT NULL (Epoch Millis)
|
|
-- updated_at: INTEGER NOT NULL (Epoch Millis)
|
|
-- version: INTEGER NOT NULL (Optimistic Locking / Sync Counter)
|
|
-- is_deleted: INTEGER NOT NULL DEFAULT 0 (Soft Delete)
|
|
|
|
-- ==================================================================
|
|
-- 2. MASTER DATA (Stammdaten)
|
|
-- ==================================================================
|
|
|
|
-- Akteure: Personen und Organisationen
|
|
-- Covers: Reiter, Richter, Besitzer, Vereine
|
|
CREATE TABLE actor (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
type TEXT NOT NULL, -- 'PERSON', 'ORGANIZATION'
|
|
|
|
-- Display Data
|
|
first_name TEXT, -- NULL for Organizations
|
|
last_name TEXT NOT NULL, -- Name or Org-Name
|
|
|
|
-- OEPS Specifics (Legacy Spec)
|
|
oeps_id TEXT, -- 'Satznummer' (6 digits for Person, 4 for Club)
|
|
oeps_category TEXT, -- 'Verein', 'Reiter', 'Richter'
|
|
|
|
-- Licenses & Status
|
|
license_code TEXT, -- e.g. 'R1', 'RD3'
|
|
has_start_card INTEGER NOT NULL DEFAULT 0, -- Boolean: Paid annual fee?
|
|
is_locked INTEGER NOT NULL DEFAULT 0, -- Boolean: Sperrliste?
|
|
|
|
-- Contact & Meta
|
|
nationality TEXT NOT NULL DEFAULT 'AUT', -- ISO 3-Letter
|
|
contact_json TEXT, -- Address, Phone, Email
|
|
|
|
-- Sync Meta
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
version INTEGER NOT NULL DEFAULT 1
|
|
);
|
|
|
|
CREATE INDEX idx_actor_oeps_id ON actor(oeps_id);
|
|
CREATE INDEX idx_actor_name ON actor(last_name, first_name);
|
|
|
|
|
|
-- Pferde
|
|
CREATE TABLE horse (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
|
|
-- Identification
|
|
oeps_id TEXT, -- 'Satznummer' (10 digits) - CRITICAL for Export
|
|
head_number_permanent TEXT, -- 'Kopfnummer' (e.g. A123)
|
|
life_number TEXT, -- 'Lebensnummer' (Zucht)
|
|
fei_id TEXT,
|
|
|
|
-- Details
|
|
birth_year INTEGER,
|
|
gender TEXT, -- 'M', 'W', 'G' (Gelding/Wallach)
|
|
color TEXT,
|
|
sire_name TEXT, -- Vater (Denormalized for search)
|
|
dam_name TEXT, -- Mutter
|
|
|
|
-- Owner Link
|
|
owner_id TEXT, -- FK to actor.id
|
|
|
|
-- Status
|
|
is_locked INTEGER NOT NULL DEFAULT 0, -- Sperrliste
|
|
|
|
-- Sync Meta
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
version INTEGER NOT NULL DEFAULT 1
|
|
);
|
|
|
|
CREATE INDEX idx_horse_oeps_id ON horse(oeps_id);
|
|
CREATE INDEX idx_horse_head_num ON horse(head_number_permanent);
|
|
CREATE INDEX idx_horse_name ON horse(name);
|
|
|
|
-- ==================================================================
|
|
-- 3. EVENT STRUCTURE
|
|
-- ==================================================================
|
|
|
|
CREATE TABLE event (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
start_date INTEGER NOT NULL, -- Epoch Day
|
|
end_date INTEGER NOT NULL,
|
|
location TEXT,
|
|
organizer_id TEXT NOT NULL, -- FK to actor.id
|
|
|
|
status TEXT NOT NULL DEFAULT 'PLANNING' -- PLANNING, ACTIVE, ARCHIVED
|
|
);
|
|
|
|
CREATE TABLE tournament (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
event_id TEXT NOT NULL REFERENCES event(id),
|
|
|
|
-- OEPS Spec
|
|
oeps_number TEXT NOT NULL, -- 5 digits (e.g. 21001)
|
|
category TEXT, -- e.g. 'CSN-A'
|
|
ruleset TEXT NOT NULL DEFAULT 'OETO', -- 'OETO', 'FEI'
|
|
|
|
-- Sync Meta
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
-- Bewerbe (Competitions)
|
|
-- Note: If a competition is split into 2 departments (Abteilungen),
|
|
-- we create 2 rows here to match the OEPS 'B-Satz' logic.
|
|
CREATE TABLE competition (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
tournament_id TEXT NOT NULL REFERENCES tournament(id),
|
|
|
|
-- Identification
|
|
code_internal TEXT NOT NULL, -- '01', '02' (2 digits)
|
|
code_official TEXT, -- '001' (3 digits, optional)
|
|
division_id INTEGER NOT NULL DEFAULT 0, -- 'Abteilung' (0=None, 1=1st, 2=2nd)
|
|
|
|
-- Description
|
|
title TEXT NOT NULL,
|
|
category TEXT, -- e.g. 'LM', 'S*'
|
|
discipline TEXT NOT NULL, -- 'D', 'S', 'C' (Dressage, Jumping, Combined)
|
|
|
|
-- Rules & Scoring
|
|
scoring_method TEXT NOT NULL, -- 'A0', 'C', 'DRESSAGE_PERCENT'
|
|
start_fee INTEGER NOT NULL DEFAULT 0, -- In Cents
|
|
|
|
-- State
|
|
status TEXT NOT NULL DEFAULT 'OPEN', -- OPEN, CLOSED_FOR_ENTRIES, RUNNING, FINISHED, SIGNED_OFF
|
|
|
|
-- Sync Meta
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE INDEX idx_comp_tournament ON competition(tournament_id);
|
|
|
|
-- ==================================================================
|
|
-- 4. SPORT & PROCESS
|
|
-- ==================================================================
|
|
|
|
-- Nennungen (Entries)
|
|
-- Represents the intent to start.
|
|
CREATE TABLE entry (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
competition_id TEXT NOT NULL REFERENCES competition(id),
|
|
|
|
-- The Pair
|
|
horse_id TEXT NOT NULL REFERENCES horse(id),
|
|
rider_id TEXT NOT NULL REFERENCES actor(id),
|
|
|
|
-- Financials
|
|
responsible_person_id TEXT REFERENCES actor(id), -- Who pays?
|
|
fee_agreed INTEGER NOT NULL, -- In Cents (Snapshot of price at entry time)
|
|
payment_status TEXT NOT NULL DEFAULT 'PENDING', -- PENDING, PAID, WAIVED
|
|
|
|
-- Validation Override (The "Human Factor")
|
|
validation_status TEXT NOT NULL DEFAULT 'OK', -- OK, WARNING, BLOCKED
|
|
override_comment TEXT, -- Why was this allowed despite warning?
|
|
|
|
-- Sync Meta
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE INDEX idx_entry_comp ON entry(competition_id);
|
|
CREATE INDEX idx_entry_rider ON entry(rider_id);
|
|
|
|
-- Startliste (Start Order)
|
|
-- Subset of entries that actually start.
|
|
CREATE TABLE start_list_entry (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
entry_id TEXT NOT NULL REFERENCES entry(id),
|
|
|
|
-- Ordering
|
|
start_order INTEGER, -- 1, 2, 3...
|
|
start_time_planned INTEGER, -- Epoch Millis (optional)
|
|
|
|
-- Tournament Specifics
|
|
head_number_event TEXT, -- Startnummer am Turnier (kann von A123 abweichen)
|
|
|
|
-- Status
|
|
status TEXT NOT NULL DEFAULT 'READY', -- READY, STARTED, DNS (Did Not Start)
|
|
|
|
UNIQUE(entry_id)
|
|
);
|
|
|
|
-- Ergebnisse (Results)
|
|
CREATE TABLE result (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
start_list_entry_id TEXT NOT NULL REFERENCES start_list_entry(id),
|
|
|
|
-- The Outcome
|
|
rank INTEGER, -- 1, 2, 3... (NULL if eliminated)
|
|
|
|
-- Scoring Details (Polymorphic based on Competition Type)
|
|
points_jump_faults DECIMAL(5,2), -- Springfehler
|
|
time_taken_ms INTEGER, -- Zeit in Millisekunden
|
|
score_dressage_percent DECIMAL(5,3), -- 72.500
|
|
score_dressage_total DECIMAL(6,2), -- Summe Punkte
|
|
|
|
-- Status Flags
|
|
classification TEXT NOT NULL DEFAULT 'OK', -- OK, EL (Elim), RET (Retired), DIS (Disq)
|
|
|
|
-- Detailed Marks (JSON)
|
|
-- e.g. { "judge_C": 7.5, "judge_H": 7.2, "obstacles": [...] }
|
|
details_json TEXT,
|
|
|
|
-- Money
|
|
prize_money INTEGER DEFAULT 0, -- In Cents
|
|
|
|
-- Audit
|
|
updated_by_user_id TEXT,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE INDEX idx_result_starter ON result(start_list_entry_id);
|
|
|
|
-- ==================================================================
|
|
-- 5. AUDIT LOG (NFR-07)
|
|
-- ==================================================================
|
|
|
|
CREATE TABLE audit_log (
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
entity_type TEXT NOT NULL, -- 'RESULT', 'ENTRY'
|
|
entity_id TEXT NOT NULL,
|
|
action TEXT NOT NULL, -- 'CREATE', 'UPDATE', 'DELETE'
|
|
|
|
user_id TEXT,
|
|
timestamp INTEGER NOT NULL,
|
|
|
|
changes_json TEXT -- { "score_old": 7.0, "score_new": 7.5 }
|
|
);
|