-- ============================================================= -- R__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.';