# Manual SQL Migrations

Diese Datei sammelt SQL-Aenderungen, die auf Live/Test manuell eingespielt werden muessen, wenn das Produktions-Setup keine automatische Schema-Migration ausfuehrt.

## Pflege-Regel

Ab jetzt gilt fuer jede Schemaaenderung:

1. SQL fuer `test` und `live` direkt hier eintragen.
2. Datum, betroffene Tabelle/Felder und optionalen Backfill dokumentieren.
3. Erst danach den Frontend-/Backend-Teil als abgeschlossen markieren.

Ergaenzend:

- Die produktive Anwendung soll sich nicht auf `sync({ alter: true })` verlassen.
- Manuelle Migrationsschritte gehoeren in diese Datei, nicht nur in Chat-Verlaeufe oder Commit-Messages.
- Wenn eine Aenderung rueckwaertskompatibel ist, soll das hier explizit vermerkt werden.

## 2026-07-22

### `member.user_id`

Sichere optionale Zuordnung eines Login-Kontos zu einem Vereinsmitglied für
persönliche Mitgliederfunktionen:

```sql
ALTER TABLE `member`
  ADD COLUMN `user_id` INT NULL,
  ADD UNIQUE KEY `member_user_id_unique` (`user_id`),
  ADD CONSTRAINT `member_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL;
```

Bestehende Mitglieder bleiben zunächst unverknüpft. Die Zuordnung erfolgt
bewusst über die Verwaltungs-API und nicht über einen automatischen E-Mail-Abgleich.

### `club_communication_threads.reply_to`

Optionale Antwortadresse je Kommunikationsvorgang:

```sql
ALTER TABLE club_communication_threads
  ADD COLUMN reply_to VARCHAR(320) NULL AFTER subject;
```

Rueckwaertskompatibilitaet:

- Bestehende Vorgange behalten eine leere Antwortadresse und verwenden weiterhin die globale Absenderadresse.

## 2026-07-23

### Persönlicher Mitgliederbereich: Änderungsanfragen und Terminrückmeldungen

```sql
CREATE TABLE member_profile_change_requests (
  id INT AUTO_INCREMENT PRIMARY KEY,
  club_id INT NOT NULL,
  member_id INT NOT NULL,
  requested_by_user_id INT NOT NULL,
  requested_changes JSON NOT NULL,
  status ENUM('open', 'approved', 'rejected') NOT NULL DEFAULT 'open',
  review_note TEXT NULL,
  reviewed_by_user_id INT NULL,
  reviewed_at DATETIME NULL,
  created_at DATETIME NOT NULL,
  updated_at DATETIME NOT NULL,
  INDEX member_profile_change_requests_club_status (club_id, status),
  INDEX member_profile_change_requests_member_status (member_id, status)
);

CREATE TABLE member_event_responses (
  id INT AUTO_INCREMENT PRIMARY KEY,
  club_id INT NOT NULL,
  event_id INT NOT NULL,
  member_id INT NOT NULL,
  status ENUM('attending', 'declined') NOT NULL,
  created_at DATETIME NOT NULL,
  updated_at DATETIME NOT NULL,
  UNIQUE KEY member_event_responses_event_member (event_id, member_id),
  INDEX member_event_responses_club_event (club_id, event_id)
);
```

Die Daten enthalten keine Zahlungsdaten. Bestehende Vereins- und
Mitgliedsdaten bleiben unverändert; die neuen Tabellen werden erst durch die
persönlichen Rückmeldungen bzw. Änderungsanfragen befüllt.

## 2026-07-24

### `club_communication_recipients.read_at`

Persönlicher Gelesen-Status für Nachrichten. Er wird ausschließlich für das
jeweilige Empfänger-Mitglied erfasst; die Kommunikationsverwaltung kann daraus
keine Kontakt- oder Zahlungsdaten ableiten.

```sql
ALTER TABLE club_communication_recipients
  ADD COLUMN read_at DATETIME NULL AFTER delivered_at,
  ADD INDEX club_communication_recipients_member_read (club_id, member_id, read_at);
```

### Bestehende `calendar_events`-Tabelle an das aktuelle Modell anpassen

Wenn die Tabelle vor der Kalendererweiterung angelegt wurde, fehlen die neuen
Metadaten-Spalten. Die Migration ist additiv und lässt bestehende Termine
unverändert:

```sql
ALTER TABLE `calendar_events`
  ADD COLUMN IF NOT EXISTS `event_type` VARCHAR(32) NOT NULL DEFAULT 'club_event' AFTER `club_id`,
  ADD COLUMN IF NOT EXISTS `status` VARCHAR(32) NOT NULL DEFAULT 'planning' AFTER `event_type`,
  ADD COLUMN IF NOT EXISTS `description` TEXT NULL AFTER `title`,
  ADD COLUMN IF NOT EXISTS `location` VARCHAR(255) NULL AFTER `description`,
  ADD COLUMN IF NOT EXISTS `registration_deadline` DATE NULL AFTER `end_date`,
  ADD COLUMN IF NOT EXISTS `organizer_user_id` INT NULL AFTER `registration_deadline`,
  ADD COLUMN IF NOT EXISTS `category` VARCHAR(64) NULL AFTER `organizer_user_id`,
  ADD COLUMN IF NOT EXISTS `notes` TEXT NULL AFTER `category`;
```

## 2026-03-17

### `predefined_activities.exclude_from_stats`

```sql
ALTER TABLE predefined_activities
  ADD COLUMN exclude_from_stats TINYINT(1) NOT NULL DEFAULT 0;
```

Optionale Initialmarkierung der bekannten Strukturaktivitaeten:

```sql
UPDATE predefined_activities
SET exclude_from_stats = 1
WHERE name IN (
  'Begruessung',
  'Aktivierung',
  'Aufbauen',
  'Turnier',
  'Abbauen',
  'Abschlussgespraech',
  'Einspielen',
  'Einspielen (VH/RH)'
);
```

### `participants.attendance_status`

```sql
ALTER TABLE participants
  ADD COLUMN attendance_status VARCHAR(32) NOT NULL DEFAULT 'present';
```

Optionaler Backfill:

```sql
UPDATE participants
SET attendance_status = 'present'
WHERE attendance_status IS NULL OR attendance_status = '';
```

Optionaler Schutz gegen doppelte Teilnehmerzeilen pro Trainingstag:

```sql
ALTER TABLE participants
  ADD UNIQUE KEY participants_diary_date_member_unique (diary_date_id, member_id);
```

Rueckwaertskompatibilitaet:

- Bestehende Datensaetze bleiben durch den Default `present` gueltig.

## 2026-03-24

### `member_orders`

```sql
CREATE TABLE IF NOT EXISTS member_orders (
  id INT NOT NULL AUTO_INCREMENT,
  member_id INT NOT NULL,
  club_id INT NOT NULL,
  item VARCHAR(255) NOT NULL,
  status ENUM('requested', 'ordered', 'arrived', 'handed_over') NOT NULL DEFAULT 'requested',
  order_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  status_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  cost DECIMAL(10,2) NOT NULL DEFAULT 0.00,
  paid_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_member_orders_member_id (member_id),
  KEY idx_member_orders_club_id (club_id),
  KEY idx_member_orders_status (status)
);
```

### `member_order_history`

```sql
CREATE TABLE IF NOT EXISTS member_order_history (
  id INT NOT NULL AUTO_INCREMENT,
  member_order_id INT NOT NULL,
  member_id INT NOT NULL,
  club_id INT NOT NULL,
  item VARCHAR(255) NOT NULL,
  status ENUM('requested', 'ordered', 'arrived', 'handed_over') NOT NULL,
  changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  cost DECIMAL(10,2) NOT NULL DEFAULT 0.00,
  paid_amount DECIMAL(10,2) NOT NULL DEFAULT 0.00,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_member_order_history_order_id (member_order_id),
  KEY idx_member_order_history_member_id (member_id),
  KEY idx_member_order_history_club_id (club_id),
  KEY idx_member_order_history_changed_at (changed_at)
);
```

Rueckwaertskompatibilitaet:

- Das Feature ist additiv. Bestehende Mitglieder- und Vereinstabellen werden nicht veraendert.
- Das Frontend funktioniert auch ohne Bestelldaten; die neuen Tabellen werden nur fuer das Feature genutzt.
