-- Migration 1043: Idempotent accounting follow-up when prepaid cards reach 80% and 100% usage. -- Existing card balances and transactions are left untouched. CREATE TABLE IF NOT EXISTS tticket_prepaid_card_alerts ( id SERIAL PRIMARY KEY, -- Keep the audit trail if an old card is later deleted. card_id INTEGER NOT NULL, alert_type VARCHAR(24) NOT NULL CHECK (alert_type IN ('usage_80_percent', 'usage_100_percent')), case_id INTEGER REFERENCES sag_sager(id) ON DELETE SET NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (card_id, alert_type) ); CREATE INDEX IF NOT EXISTS idx_prepaid_card_alerts_case ON tticket_prepaid_card_alerts(case_id) WHERE case_id IS NOT NULL; CREATE OR REPLACE FUNCTION notify_prepaid_card_usage_threshold() RETURNS TRIGGER AS $$ DECLARE v_alert_id INTEGER; v_case_id INTEGER; v_group_id INTEGER; v_customer_name TEXT; BEGIN IF COALESCE(NEW.purchased_hours, 0) <= 0 THEN RETURN NEW; END IF; IF NEW.used_hours >= NEW.purchased_hours * 0.80 THEN INSERT INTO tticket_prepaid_card_alerts (card_id, alert_type) VALUES (NEW.id, 'usage_80_percent') ON CONFLICT (card_id, alert_type) DO NOTHING RETURNING id INTO v_alert_id; IF v_alert_id IS NOT NULL THEN SELECT id INTO v_group_id FROM groups WHERE LOWER(TRIM(name)) IN ('bogholderi', 'økonomi', 'okonomi', 'accounting') ORDER BY CASE WHEN LOWER(TRIM(name)) = 'bogholderi' THEN 0 ELSE 1 END, id LIMIT 1; SELECT name INTO v_customer_name FROM customers WHERE id = NEW.customer_id; INSERT INTO sag_sager ( titel, beskrivelse, type, template_key, status, customer_id, assigned_group_id, created_by_user_id ) VALUES ( 'Klippekort ' || NEW.card_number || ' har nået 80 %', format('Klippekort %s for %s har brugt %.2f af %.2f timer (%.1f%%). Vurder om kunden skal tilbydes et nyt klippekort. Der er ikke oprettet en ordre eller et nyt klippekort automatisk.', NEW.card_number, COALESCE(v_customer_name, 'kunden'), NEW.used_hours, NEW.purchased_hours, NEW.used_hours / NEW.purchased_hours * 100), 'bogholderi', 'bogholderi', 'åben', NEW.customer_id, v_group_id, (SELECT user_id FROM users ORDER BY user_id LIMIT 1) ) RETURNING id INTO v_case_id; UPDATE tticket_prepaid_card_alerts SET case_id = v_case_id WHERE id = v_alert_id; END IF; END IF; IF NEW.used_hours >= NEW.purchased_hours THEN SELECT case_id INTO v_case_id FROM tticket_prepaid_card_alerts WHERE card_id = NEW.id AND alert_type = 'usage_80_percent'; IF v_case_id IS NULL THEN -- Covers cards that were already above 80% when this migration was installed. INSERT INTO tticket_prepaid_card_alerts (card_id, alert_type) VALUES (NEW.id, 'usage_80_percent') ON CONFLICT (card_id, alert_type) DO NOTHING RETURNING id INTO v_alert_id; IF v_alert_id IS NOT NULL THEN SELECT id INTO v_group_id FROM groups WHERE LOWER(TRIM(name)) IN ('bogholderi', 'økonomi', 'okonomi', 'accounting') ORDER BY CASE WHEN LOWER(TRIM(name)) = 'bogholderi' THEN 0 ELSE 1 END, id LIMIT 1; SELECT name INTO v_customer_name FROM customers WHERE id = NEW.customer_id; INSERT INTO sag_sager ( titel, beskrivelse, type, template_key, status, customer_id, assigned_group_id, created_by_user_id ) VALUES ( 'Klippekort ' || NEW.card_number || ' har nået 80 %', format('Klippekort %s for %s har nået fuldt forbrug. Vurder eventuelt nyt klippekort.', NEW.card_number, COALESCE(v_customer_name, 'kunden')), 'bogholderi', 'bogholderi', 'åben', NEW.customer_id, v_group_id, (SELECT user_id FROM users ORDER BY user_id LIMIT 1) ) RETURNING id INTO v_case_id; UPDATE tticket_prepaid_card_alerts SET case_id = v_case_id WHERE id = v_alert_id; END IF; END IF; INSERT INTO tticket_prepaid_card_alerts (card_id, alert_type, case_id) VALUES (NEW.id, 'usage_100_percent', v_case_id) ON CONFLICT (card_id, alert_type) DO NOTHING RETURNING id INTO v_alert_id; IF v_alert_id IS NOT NULL AND v_case_id IS NOT NULL THEN INSERT INTO sag_kommentarer (sag_id, forfatter, indhold, er_system_besked) VALUES (v_case_id, 'System', format('Klippekort %s er nu fuldt brugt og er lukket for nyt forbrug.', NEW.card_number), TRUE); END IF; UPDATE tticket_prepaid_cards SET status = 'depleted', updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id AND status = 'active'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trg_prepaid_card_usage_thresholds ON tticket_prepaid_cards; CREATE TRIGGER trg_prepaid_card_usage_thresholds AFTER UPDATE OF used_hours ON tticket_prepaid_cards FOR EACH ROW WHEN (NEW.used_hours IS DISTINCT FROM OLD.used_hours) EXECUTE FUNCTION notify_prepaid_card_usage_threshold(); COMMENT ON TABLE tticket_prepaid_card_alerts IS 'One-time bookkeeping follow-up case references for prepaid card usage thresholds.';