fix repeatable migrations

This commit is contained in:
Dusan Vojacek
2026-04-19 20:15:46 +02:00
parent 0c93f493a4
commit 22bca9cd9e
73 changed files with 22 additions and 15 deletions

View File

@@ -0,0 +1,168 @@
-- =============================================================
-- R__044_fn_set_mode.sql
-- EMS Platform přepínání provozních režimů
-- Repeatable migration
-- =============================================================
CREATE OR REPLACE FUNCTION ems.fn_set_mode(
p_site_id INT,
p_mode_code TEXT,
p_activated_by TEXT DEFAULT 'system',
p_valid_until TIMESTAMPTZ DEFAULT NULL,
p_notes TEXT DEFAULT NULL
)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
v_current_mode TEXT;
v_mode_exists BOOLEAN;
BEGIN
-- Ověřit že režim existuje
SELECT EXISTS(SELECT 1 FROM ems.operating_mode_def WHERE code = p_mode_code)
INTO v_mode_exists;
IF NOT v_mode_exists THEN
RAISE EXCEPTION 'Neznámý provozní režim: %', p_mode_code;
END IF;
-- Zjistit aktuální režim (pro log a previous_mode)
SELECT mode_code INTO v_current_mode
FROM ems.site_operating_mode
WHERE site_id = p_site_id;
-- Pokud se režim nemění, nic nedělat
IF v_current_mode = p_mode_code THEN
RETURN p_mode_code;
END IF;
-- Uzavřít předchozí záznam v logu
UPDATE ems.site_operating_mode_log
SET deactivated_at = now()
WHERE site_id = p_site_id
AND deactivated_at IS NULL;
-- Upsert aktivního režimu
INSERT INTO ems.site_operating_mode
(site_id, mode_code, activated_at, activated_by, valid_until, previous_mode, notes)
VALUES
(p_site_id, p_mode_code, now(), p_activated_by, p_valid_until, v_current_mode, p_notes)
ON CONFLICT (site_id) DO UPDATE SET
mode_code = EXCLUDED.mode_code,
activated_at = EXCLUDED.activated_at,
activated_by = EXCLUDED.activated_by,
valid_until = EXCLUDED.valid_until,
previous_mode = EXCLUDED.previous_mode,
notes = EXCLUDED.notes;
-- Přidat záznam do logu
INSERT INTO ems.site_operating_mode_log
(site_id, mode_code, activated_at, activated_by, notes)
VALUES
(p_site_id, p_mode_code, now(), p_activated_by, p_notes);
RETURN p_mode_code;
END;
$$;
COMMENT ON FUNCTION ems.fn_set_mode(INT, TEXT, TEXT, TIMESTAMPTZ, TEXT) IS
'Přepne provozní režim lokality. Atomicky aktualizuje site_operating_mode a zapíše do audit logu.
Ignoruje přepnutí na stejný režim. Vyhodí výjimku pro neznámý kód režimu.
Příklad: SELECT ems.fn_set_mode(1, ''SELF_SUSTAIN'', ''user:jan'', NULL, ''Odjezd na dovolenou'');';
-- ============================================================
CREATE OR REPLACE FUNCTION ems.fn_restore_previous_mode(
p_site_id INT,
p_activated_by TEXT DEFAULT 'system'
)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
v_previous TEXT;
BEGIN
SELECT previous_mode INTO v_previous
FROM ems.site_operating_mode
WHERE site_id = p_site_id;
IF v_previous IS NULL THEN
-- Fallback na AUTO pokud není předchozí režim
v_previous := 'AUTO';
END IF;
RETURN ems.fn_set_mode(p_site_id, v_previous, p_activated_by, NULL, 'Obnova předchozího režimu');
END;
$$;
COMMENT ON FUNCTION ems.fn_restore_previous_mode(INT, TEXT) IS
'Přepne lokalitu zpět na předchozí provozní režim (uložený v previous_mode).
Pokud předchozí režim neexistuje, přepne na AUTO. Používat po skončení dočasného přepisu.';
-- ============================================================
drop function if exists ems.fn_expire_modes();
CREATE OR REPLACE FUNCTION ems.fn_expire_modes()
RETURNS TABLE(site_id INT, site_code TEXT, old_mode TEXT, new_mode TEXT)
LANGUAGE plpgsql
AS $$
DECLARE
v_rec RECORD;
v_new_mode TEXT;
BEGIN
FOR v_rec IN
SELECT som.site_id,
s.code AS site_code,
som.mode_code AS old_mode,
som.previous_mode
FROM ems.site_operating_mode som
JOIN ems.site s ON s.id = som.site_id
WHERE som.valid_until IS NOT NULL
AND som.valid_until <= now()
AND som.mode_code <> 'AUTO'
LOOP
v_new_mode := COALESCE(v_rec.previous_mode, 'AUTO');
PERFORM ems.fn_set_mode(
v_rec.site_id,
v_new_mode,
'system:expiry',
NULL,
'Automatické vypršení dočasného režimu'
);
site_id := v_rec.site_id;
site_code := v_rec.site_code;
old_mode := v_rec.old_mode;
new_mode := v_new_mode;
RETURN NEXT;
END LOOP;
END;
$$;
COMMENT ON FUNCTION ems.fn_expire_modes() IS
'Zkontroluje všechny lokality s dočasným režimem (valid_until IS NOT NULL) a přepne zpět ty s prosahlým časem.
Volat každou minutu jako scheduled task. Vrátí řádky (site_id, site_code, old_mode, new_mode) pro každé provedené přepnutí — backend z toho pošle Discord notifikace.';
-- ============================================================
CREATE OR REPLACE FUNCTION ems.fn_update_heartbeat(
p_site_id INT,
p_status TEXT DEFAULT 'ok',
p_ems_version TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE sql
AS $$
INSERT INTO ems.site_heartbeat (site_id, last_seen, status, ems_version)
VALUES (p_site_id, now(), p_status, p_ems_version)
ON CONFLICT (site_id) DO UPDATE SET
last_seen = now(),
status = EXCLUDED.status,
ems_version = COALESCE(EXCLUDED.ems_version, ems.site_heartbeat.ems_version);
$$;
COMMENT ON FUNCTION ems.fn_update_heartbeat(INT, TEXT, TEXT) IS
'Aktualizuje informační heartbeat záznam EMS pro danou lokalitu.
Volat každou minutu z backend service po úspěšném odeslání pulzu do Loxone.
Slouží pouze pro EMS dashboard Loxone watchdog nezávisí na této tabulce,
sleduje HTTP pulzy přímo a nezávisle na dostupnosti DB.';