== **Тригери** == // Приказ на дефинирани тригери, со нивната улога, кога се активираат и зошто се корисни. // == 1) Тригер за залиха (inventory_brandedmedicine) – сетирање датум и забрана за негативни количини\\ \\ **Што прави:** Автоматски поставува last_changed на денешен датум и спречува quantity < 0.\\ **Кога се активира:** Пред INSERT или UPDATE на табелата inventory_brandedmedicine.\\ **Зошто е корисен:** Осигурува конзистентност на залихата и забрана за нелогични вредности.\\ {{{ CREATE OR REPLACE FUNCTION synergymed.fn_inventory_bm_before() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF NEW.quantity IS NULL OR NEW.quantity < 0 THEN RAISE EXCEPTION 'Количината не смее да биде негативна'; END IF; NEW.last_changed := CURRENT_DATE; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_inventory_bm_before_ins ON synergymed.inventory_brandedmedicine; CREATE TRIGGER trg_inventory_bm_before_ins BEFORE INSERT ON synergymed.inventory_brandedmedicine FOR EACH ROW EXECUTE FUNCTION synergymed.fn_inventory_bm_before(); DROP TRIGGER IF EXISTS trg_inventory_bm_before_upd ON synergymed.inventory_brandedmedicine; CREATE TRIGGER trg_inventory_bm_before_upd BEFORE UPDATE ON synergymed.inventory_brandedmedicine FOR EACH ROW EXECUTE FUNCTION synergymed.fn_inventory_bm_before(); }}} ---- == 2) Тригер за лојалност (payment) – поени на клуб-картичка при успешно плаќање \\ \\ **Што прави:** Додава поени на clubcard ако payment.status = 'завршено'. \\ **Кога се активира:** По INSERT или UPDATE на табелата payment. \\ **Зошто е корисен:** Автоматска интеграција на лојалност со финансиски трансакции. \\ {{{ CREATE OR REPLACE FUNCTION synergymed.fn_payment_loyalty_after() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_inc INT; BEGIN IF NEW.status = 'завршено' THEN IF EXISTS (SELECT 1 FROM synergymed.clubcard WHERE id = NEW.client_id) THEN v_inc := GREATEST(0, FLOOR(COALESCE(NEW.amount,0) / 10)); UPDATE synergymed.clubcard SET points = points + v_inc WHERE id = NEW.client_id; END IF; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_payment_loyalty ON synergymed.payment; CREATE TRIGGER trg_payment_loyalty AFTER INSERT OR UPDATE OF status, amount ON synergymed.payment FOR EACH ROW EXECUTE FUNCTION synergymed.fn_payment_loyalty_after(); }}} ---- == 3) Тригер за верификација на клиент (sensitiveclientdata) – автоматско одразување во client.is_verified \\ \\ **Што прави:** Ако verification_status = 'верифицирано', тогаш client.is_verified = TRUE; ако е 'одбиено' → FALSE. \\ **Кога се активира:** По INSERT или UPDATE на табелата sensitiveclientdata. \\ **Зошто е корисен:** Автоматски ја усогласува регулаторната состојба на клиентот со flag-от во Client. \\ {{{ CREATE OR REPLACE FUNCTION synergymed.fn_sensitiveclientdata_affect_client_after() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN IF NEW.verification_status = 'верифицирано' THEN UPDATE synergymed.client SET is_verified = TRUE WHERE id = NEW.client_id; ELSIF NEW.verification_status = 'одбиено' THEN UPDATE synergymed.client SET is_verified = FALSE WHERE id = NEW.client_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_sensitiveclientdata_affect_client ON synergymed.sensitiveclientdata; CREATE TRIGGER trg_sensitiveclientdata_affect_client AFTER INSERT OR UPDATE OF verification_status ON synergymed.sensitiveclientdata FOR EACH ROW EXECUTE FUNCTION synergymed.fn_sensitiveclientdata_affect_client_after(); }}} ---- == 4) Тригер за интеракции на лекови (medicineinteraction) – симетрија и анти-рефлексивност \\ \\ **Што прави:** Спречува интеракција лек-со-себе, и редоследува пар (id1, id2) така што id1 < id2. \\ **Кога се активира:** Пред INSERT или UPDATE на табелата medicineinteraction. \\ **Зошто е корисен:** Гарантира уникатни и чисти парови лекови. \\ {{{ CREATE OR REPLACE FUNCTION synergymed.fn_medicineinteraction_order_before() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE a INT; b INT; BEGIN IF NEW.medicine_id_1 = NEW.medicine_id_2 THEN RAISE EXCEPTION 'Лек не може да биде во интеракција сам со себе'; END IF; a := LEAST(NEW.medicine_id_1, NEW.medicine_id_2); b := GREATEST(NEW.medicine_id_1, NEW.medicine_id_2); NEW.medicine_id_1 := a; NEW.medicine_id_2 := b; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_mi_order_before_ins ON synergymed.medicineinteraction; CREATE TRIGGER trg_mi_order_before_ins BEFORE INSERT ON synergymed.medicineinteraction FOR EACH ROW EXECUTE FUNCTION synergymed.fn_medicineinteraction_order_before(); DROP TRIGGER IF EXISTS trg_mi_order_before_upd ON synergymed.medicineinteraction; CREATE TRIGGER trg_mi_order_before_upd BEFORE UPDATE ON synergymed.medicineinteraction FOR EACH ROW EXECUTE FUNCTION synergymed.fn_medicineinteraction_order_before(); CREATE UNIQUE INDEX IF NOT EXISTS ux_mi_pair ON synergymed.medicineinteraction (medicine_id_1, medicine_id_2); }}} ---- == 5) Тригер за авто-ажурирање на каталогот на аптеки \\ \\ **Што прави:** Кога се менува inventory_brandedmedicine, автоматски додава записи во pharmacy_catalog за да гарантира дека секој производ што е во залиха е и во каталогот на аптеката. \\ **Кога се активира:** По INSERT или UPDATE на табелата inventory_brandedmedicine. \\ **Зошто е корисен:** Ја одржува усогласеноста меѓу залихи и каталог, без рачна интервенција. \\ {{{ CREATE OR REPLACE FUNCTION synergymed.fn_inventory_autocatalog_after() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_pharmacy_id INT; BEGIN SELECT ph.company_id INTO v_pharmacy_id FROM synergymed.inventory i JOIN synergymed.facility f ON f.id = i.facility_id JOIN synergymed.pharmacy ph ON ph.company_id = f.company_id WHERE i.id = COALESCE(NEW.inventory_id, OLD.inventory_id) LIMIT 1; IF v_pharmacy_id IS NOT NULL THEN INSERT INTO synergymed.pharmacy_catalog(pharmacy_id, branded_medicine_id) VALUES (v_pharmacy_id, COALESCE(NEW.branded_medicine_id, OLD.branded_medicine_id)) ON CONFLICT DO NOTHING; END IF; RETURN COALESCE(NEW, OLD); END; $$; DROP TRIGGER IF EXISTS trg_inventory_autocatalog_ins ON synergymed.inventory_brandedmedicine; CREATE TRIGGER trg_inventory_autocatalog_ins AFTER INSERT ON synergymed.inventory_brandedmedicine FOR EACH ROW EXECUTE FUNCTION synergymed.fn_inventory_autocatalog_after(); DROP TRIGGER IF EXISTS trg_inventory_autocatalog_upd ON synergymed.inventory_brandedmedicine; CREATE TRIGGER trg_inventory_autocatalog_upd AFTER UPDATE OF quantity ON synergymed.inventory_brandedmedicine FOR EACH ROW EXECUTE FUNCTION synergymed.fn_inventory_autocatalog_after(); }}} ---- == 6) Тригер за блокирање на клиент со повеќе неуспешни плаќања \\ \\ **Што прави:** Кога клиент има 3 или повеќе неуспешни плаќања во последните 10 дена, автоматски го поставува is_verified = FALSE во табелата Client. \\ **Кога се активира:** По INSERT или UPDATE на табелата payment кога статусот е „неуспешно“. \\ **Зошто е корисен:** Спречува понатамошни обиди за нарачки и злоупотреба од клиенти со повеќекратни неуспешни трансакции, со што ја зголемува безбедноста и стабилноста на системот. \\ {{{ CREATE OR REPLACE FUNCTION synergymed.fn_block_client_after_failed_payment() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE v_failures INT; BEGIN SELECT COUNT(*) INTO v_failures FROM synergymed.payment p WHERE p.client_id = NEW.client_id AND p.status = 'неуспешно' AND p.payment_date >= (CURRENT_DATE - INTERVAL '10 days'); IF v_failures >= 3 THEN UPDATE synergymed.client SET is_verified = FALSE WHERE user_id = NEW.client_id; END IF; RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_block_client_after_failed_payment ON synergymed.payment; CREATE TRIGGER trg_block_client_after_failed_payment AFTER INSERT OR UPDATE OF status ON synergymed.payment FOR EACH ROW WHEN (NEW.status = 'неуспешно') EXECUTE FUNCTION synergymed.fn_block_client_after_failed_payment(); }}}