332 lines
16 KiB
SQL
332 lines
16 KiB
SQL
-- Migration 005: Fahrzeugverwaltung (Vehicle Fleet Management)
|
||
-- Depends on: 001_create_users_table.sql (uuid-ossp extension, users table)
|
||
|
||
-- ============================================================
|
||
-- TABLE: fahrzeuge
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS fahrzeuge (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
bezeichnung VARCHAR(100) NOT NULL, -- e.g. "LF 20/16", "HLF 10"
|
||
kurzname VARCHAR(20), -- e.g. "LF1", "HLF2"
|
||
amtliches_kennzeichen VARCHAR(20) UNIQUE, -- e.g. "WN-FW 1"
|
||
fahrgestellnummer VARCHAR(50), -- VIN
|
||
baujahr INTEGER CHECK (baujahr >= 1950 AND baujahr <= 2100),
|
||
hersteller VARCHAR(100), -- e.g. "MAN", "Mercedes-Benz", "Rosenbauer"
|
||
typ_schluessel VARCHAR(30), -- DIN 14502 code, e.g. "LF 20/16"
|
||
besatzung_soll VARCHAR(10), -- crew config e.g. "1/8", "1/5"
|
||
status VARCHAR(40) NOT NULL DEFAULT 'einsatzbereit'
|
||
CHECK (status IN (
|
||
'einsatzbereit',
|
||
'ausser_dienst_wartung',
|
||
'ausser_dienst_schaden',
|
||
'in_lehrgang'
|
||
)),
|
||
status_bemerkung TEXT,
|
||
standort VARCHAR(100) NOT NULL DEFAULT 'Feuerwehrhaus',
|
||
bild_url VARCHAR(500),
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_fahrzeuge_status ON fahrzeuge(status);
|
||
CREATE INDEX IF NOT EXISTS idx_fahrzeuge_kennzeichen ON fahrzeuge(amtliches_kennzeichen);
|
||
|
||
-- Auto-update updated_at (reuses function from migration 001)
|
||
CREATE TRIGGER update_fahrzeuge_updated_at
|
||
BEFORE UPDATE ON fahrzeuge
|
||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||
|
||
|
||
-- ============================================================
|
||
-- TABLE: fahrzeug_pruefungen
|
||
-- Stores both upcoming scheduled inspections AND completed ones.
|
||
-- A row with durchgefuehrt_am = NULL is an open/scheduled inspection.
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS fahrzeug_pruefungen (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
fahrzeug_id UUID NOT NULL REFERENCES fahrzeuge(id) ON DELETE CASCADE,
|
||
pruefung_art VARCHAR(30) NOT NULL
|
||
CHECK (pruefung_art IN (
|
||
'HU', -- Hauptuntersuchung (TÜV), 24-month interval
|
||
'AU', -- Abgasuntersuchung, 12-month interval
|
||
'UVV', -- Unfallverhütungsvorschrift BGV D29, 12-month
|
||
'Leiter', -- Leiternprüfung (DLK), 12-month
|
||
'Kran', -- Kranprüfung, 12-month
|
||
'Seilwinde', -- Seilwindenprüfung, 12-month
|
||
'Sonstiges'
|
||
)),
|
||
-- faellig_am: the deadline by which this inspection must be completed
|
||
faellig_am DATE NOT NULL,
|
||
-- durchgefuehrt_am: NULL = not yet done; set when inspection is completed
|
||
durchgefuehrt_am DATE,
|
||
ergebnis VARCHAR(30)
|
||
CHECK (ergebnis IS NULL OR ergebnis IN (
|
||
'bestanden',
|
||
'bestanden_mit_maengeln',
|
||
'nicht_bestanden',
|
||
'ausstehend'
|
||
)),
|
||
-- naechste_faelligkeit: auto-calculated next due date after completion
|
||
naechste_faelligkeit DATE,
|
||
pruefende_stelle VARCHAR(150), -- e.g. "TÜV Süd Stuttgart", "DEKRA"
|
||
kosten DECIMAL(8,2),
|
||
dokument_url VARCHAR(500),
|
||
bemerkung TEXT,
|
||
erfasst_von UUID REFERENCES users(id) ON DELETE SET NULL,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_pruefungen_fahrzeug_id ON fahrzeug_pruefungen(fahrzeug_id);
|
||
CREATE INDEX IF NOT EXISTS idx_pruefungen_faellig_am ON fahrzeug_pruefungen(faellig_am);
|
||
CREATE INDEX IF NOT EXISTS idx_pruefungen_art ON fahrzeug_pruefungen(pruefung_art);
|
||
-- Composite index for the "latest per type" query pattern
|
||
CREATE INDEX IF NOT EXISTS idx_pruefungen_fahrzeug_art_faellig
|
||
ON fahrzeug_pruefungen(fahrzeug_id, pruefung_art, faellig_am DESC);
|
||
|
||
|
||
-- ============================================================
|
||
-- TABLE: fahrzeug_wartungslog
|
||
-- Service/maintenance log entries (fuel, repairs, tyres, etc.)
|
||
-- ============================================================
|
||
CREATE TABLE IF NOT EXISTS fahrzeug_wartungslog (
|
||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
fahrzeug_id UUID NOT NULL REFERENCES fahrzeuge(id) ON DELETE CASCADE,
|
||
datum DATE NOT NULL,
|
||
art VARCHAR(30)
|
||
CHECK (art IS NULL OR art IN (
|
||
'Inspektion',
|
||
'Reparatur',
|
||
'Kraftstoff',
|
||
'Reifenwechsel',
|
||
'Hauptuntersuchung',
|
||
'Reinigung',
|
||
'Sonstiges'
|
||
)),
|
||
beschreibung TEXT NOT NULL,
|
||
km_stand INTEGER CHECK (km_stand >= 0),
|
||
kraftstoff_liter DECIMAL(6,2) CHECK (kraftstoff_liter >= 0),
|
||
kosten DECIMAL(8,2) CHECK (kosten >= 0),
|
||
externe_werkstatt VARCHAR(150),
|
||
erfasst_von UUID REFERENCES users(id) ON DELETE SET NULL,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_wartungslog_fahrzeug_id ON fahrzeug_wartungslog(fahrzeug_id);
|
||
CREATE INDEX IF NOT EXISTS idx_wartungslog_datum ON fahrzeug_wartungslog(datum DESC);
|
||
|
||
|
||
-- ============================================================
|
||
-- VIEW: fahrzeuge_mit_pruefstatus
|
||
-- For each vehicle, joins its LATEST scheduled pruefung per art
|
||
-- and computes tage_bis_faelligkeit (negative = overdue).
|
||
-- The dashboard alert panel and fleet overview query this view.
|
||
-- ============================================================
|
||
CREATE OR REPLACE VIEW fahrzeuge_mit_pruefstatus AS
|
||
WITH latest_pruefungen AS (
|
||
-- For each (fahrzeug, pruefung_art) pair, pick the row with the
|
||
-- latest faellig_am that is NOT yet completed (durchgefuehrt_am IS NULL),
|
||
-- OR if all are completed, the one with the highest naechste_faelligkeit.
|
||
SELECT DISTINCT ON (fahrzeug_id, pruefung_art)
|
||
fahrzeug_id,
|
||
pruefung_art,
|
||
id AS pruefung_id,
|
||
faellig_am,
|
||
durchgefuehrt_am,
|
||
ergebnis,
|
||
naechste_faelligkeit,
|
||
pruefende_stelle,
|
||
CURRENT_DATE - faellig_am::date AS tage_ueberfaellig,
|
||
faellig_am::date - CURRENT_DATE AS tage_bis_faelligkeit
|
||
FROM fahrzeug_pruefungen
|
||
ORDER BY
|
||
fahrzeug_id,
|
||
pruefung_art,
|
||
-- Open inspections (nicht durchgeführt) first, then most recent
|
||
(durchgefuehrt_am IS NULL) DESC,
|
||
faellig_am DESC
|
||
)
|
||
SELECT
|
||
f.id,
|
||
f.bezeichnung,
|
||
f.kurzname,
|
||
f.amtliches_kennzeichen,
|
||
f.fahrgestellnummer,
|
||
f.baujahr,
|
||
f.hersteller,
|
||
f.typ_schluessel,
|
||
f.besatzung_soll,
|
||
f.status,
|
||
f.status_bemerkung,
|
||
f.standort,
|
||
f.bild_url,
|
||
f.created_at,
|
||
f.updated_at,
|
||
-- HU
|
||
hu.pruefung_id AS hu_pruefung_id,
|
||
hu.faellig_am AS hu_faellig_am,
|
||
hu.tage_bis_faelligkeit AS hu_tage_bis_faelligkeit,
|
||
hu.ergebnis AS hu_ergebnis,
|
||
-- AU
|
||
au.pruefung_id AS au_pruefung_id,
|
||
au.faellig_am AS au_faellig_am,
|
||
au.tage_bis_faelligkeit AS au_tage_bis_faelligkeit,
|
||
au.ergebnis AS au_ergebnis,
|
||
-- UVV
|
||
uvv.pruefung_id AS uvv_pruefung_id,
|
||
uvv.faellig_am AS uvv_faellig_am,
|
||
uvv.tage_bis_faelligkeit AS uvv_tage_bis_faelligkeit,
|
||
uvv.ergebnis AS uvv_ergebnis,
|
||
-- Leiter (DLK only)
|
||
leiter.pruefung_id AS leiter_pruefung_id,
|
||
leiter.faellig_am AS leiter_faellig_am,
|
||
leiter.tage_bis_faelligkeit AS leiter_tage_bis_faelligkeit,
|
||
leiter.ergebnis AS leiter_ergebnis,
|
||
-- Overall worst tage_bis_faelligkeit across all active inspections
|
||
LEAST(
|
||
hu.tage_bis_faelligkeit,
|
||
au.tage_bis_faelligkeit,
|
||
uvv.tage_bis_faelligkeit,
|
||
leiter.tage_bis_faelligkeit
|
||
) AS naechste_pruefung_tage
|
||
FROM
|
||
fahrzeuge f
|
||
LEFT JOIN latest_pruefungen hu ON hu.fahrzeug_id = f.id AND hu.pruefung_art = 'HU'
|
||
LEFT JOIN latest_pruefungen au ON au.fahrzeug_id = f.id AND au.pruefung_art = 'AU'
|
||
LEFT JOIN latest_pruefungen uvv ON uvv.fahrzeug_id = f.id AND uvv.pruefung_art = 'UVV'
|
||
LEFT JOIN latest_pruefungen leiter ON leiter.fahrzeug_id = f.id AND leiter.pruefung_art = 'Leiter';
|
||
|
||
|
||
-- ============================================================
|
||
-- SEED DATA: 3 typical German Feuerwehr vehicles
|
||
-- ============================================================
|
||
DO $$
|
||
DECLARE
|
||
v_lf10_id UUID := uuid_generate_v4();
|
||
v_hlf20_id UUID := uuid_generate_v4();
|
||
v_mtf_id UUID := uuid_generate_v4();
|
||
BEGIN
|
||
-- Only insert if no vehicles exist yet (idempotent seed)
|
||
IF (SELECT COUNT(*) FROM fahrzeuge) = 0 THEN
|
||
|
||
-- 1) LF 10 – Standard Löschgruppenfahrzeug
|
||
INSERT INTO fahrzeuge (
|
||
id, bezeichnung, kurzname, amtliches_kennzeichen,
|
||
fahrgestellnummer, baujahr, hersteller, typ_schluessel,
|
||
besatzung_soll, status, standort
|
||
) VALUES (
|
||
v_lf10_id,
|
||
'LF 10',
|
||
'LF 1',
|
||
'WN-FW 1',
|
||
'WDB9634031L123456',
|
||
2018,
|
||
'Mercedes-Benz Atego',
|
||
'LF 10',
|
||
'1/8',
|
||
'einsatzbereit',
|
||
'Feuerwehrhaus'
|
||
);
|
||
|
||
-- LF 10 inspections
|
||
INSERT INTO fahrzeug_pruefungen (fahrzeug_id, pruefung_art, faellig_am, durchgefuehrt_am, ergebnis, naechste_faelligkeit, pruefende_stelle) VALUES
|
||
(v_lf10_id, 'HU', '2026-03-15', NULL, 'ausstehend', NULL, 'TÜV Süd Stuttgart'),
|
||
(v_lf10_id, 'AU', '2026-04-01', NULL, 'ausstehend', NULL, 'TÜV Süd Stuttgart'),
|
||
(v_lf10_id, 'UVV', '2025-11-30', '2025-11-28', 'bestanden', '2026-11-28', 'DGUV Prüfer Rems');
|
||
|
||
-- LF 10 maintenance log
|
||
INSERT INTO fahrzeug_wartungslog (fahrzeug_id, datum, art, beschreibung, km_stand, kraftstoff_liter, kosten) VALUES
|
||
(v_lf10_id, '2025-11-28', 'Inspektion', 'Jahresinspektion nach Herstellervorgabe, Öl- und Filterwechsel, Bremsenprüfung', 48320, NULL, 420.00),
|
||
(v_lf10_id, '2025-10-15', 'Kraftstoff', 'Betankung nach Einsatz Feuerwehr Rems', 48150, 85.4, 145.18),
|
||
(v_lf10_id, '2025-09-01', 'Reifenwechsel','Sommerreifen auf Winterreifen gewechselt, alle 4 Reifen erneuert (Continental)', 47800, NULL, 980.00);
|
||
|
||
-- 2) HLF 20/16 – Hilfeleistungslöschgruppenfahrzeug (flagship)
|
||
INSERT INTO fahrzeuge (
|
||
id, bezeichnung, kurzname, amtliches_kennzeichen,
|
||
fahrgestellnummer, baujahr, hersteller, typ_schluessel,
|
||
besatzung_soll, status, standort
|
||
) VALUES (
|
||
v_hlf20_id,
|
||
'HLF 20/16',
|
||
'HLF 1',
|
||
'WN-FW 2',
|
||
'WMAN29ZZ3LM654321',
|
||
2020,
|
||
'MAN TGM / Rosenbauer',
|
||
'HLF 20/16',
|
||
'1/8',
|
||
'einsatzbereit',
|
||
'Feuerwehrhaus'
|
||
);
|
||
|
||
-- HLF 20 inspections — all current
|
||
INSERT INTO fahrzeug_pruefungen (fahrzeug_id, pruefung_art, faellig_am, durchgefuehrt_am, ergebnis, naechste_faelligkeit, pruefende_stelle) VALUES
|
||
(v_hlf20_id, 'HU', '2026-08-20', NULL, 'ausstehend', NULL, 'DEKRA Esslingen'),
|
||
(v_hlf20_id, 'AU', '2026-02-01', '2026-01-28', 'bestanden', '2027-01-28', 'DEKRA Esslingen'),
|
||
(v_hlf20_id, 'UVV', '2026-01-15', '2026-01-14', 'bestanden', '2027-01-14', 'DGUV Prüfer Rems');
|
||
|
||
-- HLF 20 maintenance log
|
||
INSERT INTO fahrzeug_wartungslog (fahrzeug_id, datum, art, beschreibung, km_stand, kraftstoff_liter, kosten) VALUES
|
||
(v_hlf20_id, '2026-01-28', 'Hauptuntersuchung', 'AU bestanden ohne Mängel bei DEKRA Esslingen', 22450, NULL, 185.00),
|
||
(v_hlf20_id, '2026-01-14', 'Inspektion', 'UVV-Prüfung bestanden, Licht und Bremsen geprüft', 22430, NULL, 0.00),
|
||
(v_hlf20_id, '2025-12-10', 'Kraftstoff', 'Betankung nach Übung', 22300, 120.0, 204.00),
|
||
(v_hlf20_id, '2025-10-05', 'Reparatur', 'Hydraulikpumpe für Rettungssatz getauscht', 21980, NULL, 2340.00);
|
||
|
||
-- 3) MTF – Mannschaftstransportfahrzeug
|
||
INSERT INTO fahrzeuge (
|
||
id, bezeichnung, kurzname, amtliches_kennzeichen,
|
||
fahrgestellnummer, baujahr, hersteller, typ_schluessel,
|
||
besatzung_soll, status, status_bemerkung, standort
|
||
) VALUES (
|
||
v_mtf_id,
|
||
'MTF',
|
||
'MTF 1',
|
||
'WN-FW 5',
|
||
'WDB9066371S789012',
|
||
2015,
|
||
'Mercedes-Benz Sprinter',
|
||
'MTF',
|
||
'1/8',
|
||
'ausser_dienst_wartung',
|
||
'Geplante Inspektion: Zahnriemenwechsel 60.000 km fällig',
|
||
'Feuerwehrhaus'
|
||
);
|
||
|
||
-- MTF inspections — HU overdue (safety-critical test data)
|
||
INSERT INTO fahrzeug_pruefungen (fahrzeug_id, pruefung_art, faellig_am, durchgefuehrt_am, ergebnis, naechste_faelligkeit, pruefende_stelle) VALUES
|
||
(v_mtf_id, 'HU', '2026-01-31', NULL, 'ausstehend', NULL, 'TÜV Süd Stuttgart'),
|
||
(v_mtf_id, 'AU', '2025-06-15', '2025-06-12', 'bestanden', '2026-06-12', 'TÜV Süd Stuttgart'),
|
||
(v_mtf_id, 'UVV', '2025-12-01', '2025-11-30', 'bestanden_mit_maengeln', '2026-11-30', 'DGUV Prüfer Rems');
|
||
|
||
-- MTF maintenance log
|
||
INSERT INTO fahrzeug_wartungslog (fahrzeug_id, datum, art, beschreibung, km_stand, kraftstoff_liter, kosten) VALUES
|
||
(v_mtf_id, '2025-11-30', 'Inspektion', 'UVV-Prüfung: kleiner Mangel Innenbeleuchtung, nachgebessert', 58920, NULL, 0.00),
|
||
(v_mtf_id, '2025-11-01', 'Kraftstoff', 'Betankung regulär', 58700, 65.0, 110.50),
|
||
(v_mtf_id, '2025-09-20', 'Reparatur', 'Heckleuchte links defekt, Glühbirne getauscht', 58400, NULL, 12.50);
|
||
|
||
END IF;
|
||
END
|
||
$$;
|
||
|
||
-- ---------------------------------------------------------------------------
|
||
-- Cross-migration FK: add fahrzeug_id FK to einsatz_fahrzeuge (created in 004)
|
||
-- This runs only if einsatz_fahrzeuge exists but the FK is not yet present.
|
||
-- Handles the case where 004 ran before 005 and deferred the FK creation.
|
||
-- ---------------------------------------------------------------------------
|
||
DO $$
|
||
BEGIN
|
||
IF EXISTS (
|
||
SELECT 1 FROM information_schema.tables
|
||
WHERE table_schema = 'public' AND table_name = 'einsatz_fahrzeuge'
|
||
)
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM information_schema.table_constraints
|
||
WHERE table_name = 'einsatz_fahrzeuge'
|
||
AND constraint_name = 'fk_ef_fahrzeug'
|
||
) THEN
|
||
ALTER TABLE einsatz_fahrzeuge
|
||
ADD CONSTRAINT fk_ef_fahrzeug
|
||
FOREIGN KEY (fahrzeug_id) REFERENCES fahrzeuge(id) ON DELETE CASCADE;
|
||
RAISE NOTICE 'Added fk_ef_fahrzeug FK on einsatz_fahrzeuge';
|
||
END IF;
|
||
END $$;
|