Quelltext des aktuellen Standes
-- ============================================================
-- Verträge: Preis- und Laufzeitverlauf
-- ============================================================
-- Ein Vertrag trägt immer nur seinen aktuellen Stand. Diese Tabelle hält
-- fest, wie er dorthin gekommen ist: je Speichervorgang und Themenbereich
-- (Preis, Laufzeit, Status) ein Eintrag mit den tatsächlich geänderten
-- Feldern. Automatische Verlängerungen erscheinen als eigener Bereich.
--
-- Bewusst getrennt vom Audit-Log: Dort steht jede Änderung mit allen Feldern
-- und ist an die Hash-Kette gebunden; hier steht die fachliche Zeitachse, die
-- auf der Vertragsseite gelesen wird – gefiltert auf das, was den Wert und
-- die Dauer eines Vertrags ausmacht.
--
-- Statuswechsel stehen nur dort, wo jemand sie vorgenommen hat. Die täglich
-- aus den Datumsangaben abgeleiteten Wechsel (aktiv → Kündigung beachten →
-- abgelaufen) ergeben sich aus der Laufzeit und bleiben draußen.
--
-- Der Startpunkt „Vertrag angelegt" wird nicht gespeichert, sondern aus
-- `contracts.created_at` gebildet. So hat auch ein Vertrag aus der Zeit vor
-- dieser Tabelle einen vollständigen Anfang.
CREATE TABLE IF NOT EXISTS `contract_history` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`contract_id` BIGINT UNSIGNED NOT NULL,
`event_type` ENUM('price','term','status','renewal') NOT NULL,
`changes_json` TEXT NULL COMMENT 'Liste {field, old, new} in Rohwerten',
`note` VARCHAR(255) NULL,
`changed_by` BIGINT UNSIGNED NULL COMMENT 'NULL = automatisch (Zeitplan)',
`changed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_ch_contract` (`contract_id`, `changed_at`),
CONSTRAINT `fk_ch_contract` FOREIGN KEY (`contract_id`) REFERENCES `contracts`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;