| | 1 | = Фаза 4: Функции, процедури и тригери = |
| | 2 | |
| | 3 | Во оваа фаза се надоградуваме со функции и процедури што ќе помогнат дел од честите операции во базата — отворање записник, додавање прекршок, наплата на казна, додавање категорија на возачка — да се извршуваат преку еден повик, со сите потребни проверки веќе вградени во нив. Исто така искористивме и тригери за работите што треба да се случуваат автоматски, како пресметката на вкупниот износ во уплатата, без рачно ажурирање. |
| | 4 | |
| | 5 | |
| | 6 | [[BR]] |
| | 7 | == 1. Тригери == |
| | 8 | |
| | 9 | === Тригер: Автоматска пресметка на износот во Uplata === |
| | 10 | |
| | 11 | Секој пат кога ќе се додаде, измени или избрише ставка во `Stavka_Zapisnik`, тригерот ја пресметува вкупната сума на сите казни за тој записник и ја запишува во соодветната уплата. Така износот во `Uplata` секогаш е точен и не мора рачно да го ажурираме. |
| | 12 | |
| | 13 | {{{#!sql |
| | 14 | CREATE OR REPLACE FUNCTION azuriraj_iznos_uplata() |
| | 15 | RETURNS TRIGGER |
| | 16 | LANGUAGE plpgsql |
| | 17 | AS $$ |
| | 18 | DECLARE |
| | 19 | v_id_zapisnik int; |
| | 20 | v_nov_iznos numeric(10,2); |
| | 21 | BEGIN |
| | 22 | IF TG_OP = 'DELETE' THEN |
| | 23 | v_id_zapisnik := OLD.id_na_zapisnik; |
| | 24 | ELSE |
| | 25 | v_id_zapisnik := NEW.id_na_zapisnik; |
| | 26 | END IF; |
| | 27 | |
| | 28 | SELECT COALESCE(SUM(k.iznos_kazna), 0) |
| | 29 | INTO v_nov_iznos |
| | 30 | FROM Stavka_Zapisnik sz |
| | 31 | JOIN Prekrsok p ON sz.id_na_prekrsok = p.id_prekrsok |
| | 32 | JOIN Kazna k ON p.id_kazna = k.id_kazna |
| | 33 | WHERE sz.id_na_zapisnik = v_id_zapisnik; |
| | 34 | |
| | 35 | UPDATE Uplata |
| | 36 | SET iznos = v_nov_iznos |
| | 37 | WHERE id_zapisnik = v_id_zapisnik |
| | 38 | AND status = 'Neplateno'; |
| | 39 | |
| | 40 | IF TG_OP = 'DELETE' THEN |
| | 41 | RETURN OLD; |
| | 42 | ELSE |
| | 43 | RETURN NEW; |
| | 44 | END IF; |
| | 45 | END; |
| | 46 | $$; |
| | 47 | |
| | 48 | CREATE OR REPLACE TRIGGER trigger_azuriraj_iznos_uplata |
| | 49 | AFTER INSERT OR UPDATE OR DELETE ON Stavka_Zapisnik |
| | 50 | FOR EACH ROW |
| | 51 | EXECUTE FUNCTION azuriraj_iznos_uplata(); |
| | 52 | }}} |
| | 53 | |
| | 54 | |
| | 55 | [[BR]] |
| | 56 | == 2. Процедури == |
| | 57 | |
| | 58 | === Процедура 1: kreiraj_zapisnik_so_prekrsok === |
| | 59 | Отвора нов записник со прв прекршок и веднаш креира празна уплата со статус `Neplateno`. Износот го пополнува тригерот штом ќе се вметне првата ставка. Прво проверува дали постојат граѓанинот, полицаецот, возилото, прекршокот и случајот. |
| | 60 | |
| | 61 | {{{#!sql |
| | 62 | CREATE OR REPLACE PROCEDURE kreiraj_zapisnik_so_prekrsok( |
| | 63 | p_embg_prekrsuvac char(13), |
| | 64 | p_broj_sasija varchar(17), |
| | 65 | p_embg_policaec char(13), |
| | 66 | p_lokacija varchar(100), |
| | 67 | p_id_slucaj int, |
| | 68 | p_id_prekrsok int, |
| | 69 | p_vreme time DEFAULT CURRENT_TIME, |
| | 70 | p_datum date DEFAULT CURRENT_DATE, |
| | 71 | p_potpis boolean DEFAULT false |
| | 72 | ) |
| | 73 | LANGUAGE plpgsql |
| | 74 | AS $$ |
| | 75 | DECLARE |
| | 76 | v_id_zapisnik int; |
| | 77 | BEGIN |
| | 78 | IF NOT EXISTS (SELECT 1 FROM Gragjanin WHERE EMBG = p_embg_prekrsuvac) THEN |
| | 79 | RAISE EXCEPTION 'Gragjaninot so EMBG % ne postoi!', p_embg_prekrsuvac; |
| | 80 | END IF; |
| | 81 | IF NOT EXISTS (SELECT 1 FROM Policaec WHERE EMBG_P = p_embg_policaec) THEN |
| | 82 | RAISE EXCEPTION 'Policaecot so EMBG % ne postoi!', p_embg_policaec; |
| | 83 | END IF; |
| | 84 | IF NOT EXISTS (SELECT 1 FROM Vozilo WHERE broj_na_sasija = p_broj_sasija) THEN |
| | 85 | RAISE EXCEPTION 'Voziloto so broj na sasija % ne postoi!', p_broj_sasija; |
| | 86 | END IF; |
| | 87 | IF NOT EXISTS (SELECT 1 FROM Prekrsok WHERE id_prekrsok = p_id_prekrsok) THEN |
| | 88 | RAISE EXCEPTION 'Prekrsokot so id % ne postoi!', p_id_prekrsok; |
| | 89 | END IF; |
| | 90 | IF NOT EXISTS (SELECT 1 FROM Slucaj WHERE id_slucaj = p_id_slucaj) THEN |
| | 91 | RAISE EXCEPTION 'Slucajot so id % ne postoi!', p_id_slucaj; |
| | 92 | END IF; |
| | 93 | |
| | 94 | INSERT INTO Zapisnik (vreme, datum, lokacija, Potpis, |
| | 95 | id_slucaj, EMBG_Prekrsuvach, Vozilo_Broj_Sasija, EMBG_Policaec) |
| | 96 | VALUES (p_vreme, p_datum, p_lokacija, p_potpis, |
| | 97 | p_id_slucaj, p_embg_prekrsuvac, p_broj_sasija, p_embg_policaec) |
| | 98 | RETURNING id_na_zapisnik INTO v_id_zapisnik; |
| | 99 | |
| | 100 | INSERT INTO Uplata (iznos, status, Uplatil_Gragjanin, id_zapisnik) |
| | 101 | VALUES (0, 'Neplateno', p_embg_prekrsuvac, v_id_zapisnik); |
| | 102 | |
| | 103 | INSERT INTO Stavka_Zapisnik (reden_broj, id_na_zapisnik, id_na_prekrsok) |
| | 104 | VALUES (1, v_id_zapisnik, p_id_prekrsok); |
| | 105 | |
| | 106 | RAISE NOTICE 'Kreiran zapisnik so id % so prv prekrsok %.', v_id_zapisnik, p_id_prekrsok; |
| | 107 | END; |
| | 108 | $$; |
| | 109 | }}} |
| | 110 | |
| | 111 | Повик: |
| | 112 | {{{#!sql |
| | 113 | CALL kreiraj_zapisnik_so_prekrsok( |
| | 114 | '1508004480145', 'CE8C5F1D5AA4C0871', '1609966470237', |
| | 115 | 'Bulevar Partizanski Odredi', 1, 1 |
| | 116 | ); |
| | 117 | }}} |
| | 118 | |
| | 119 | |
| | 120 | === Процедура 2: dodadi_stavka_zapisnik === |
| | 121 | Додава нов прекршок (ставка) на веќе постоечки записник и сам го одредува следниот реден број. Не дозволува додавање ако записникот е веќе платен. |
| | 122 | |
| | 123 | {{{#!sql |
| | 124 | CREATE OR REPLACE PROCEDURE dodadi_stavka_zapisnik( |
| | 125 | p_id_zapisnik int, |
| | 126 | p_id_prekrsok int |
| | 127 | ) |
| | 128 | LANGUAGE plpgsql |
| | 129 | AS $$ |
| | 130 | DECLARE |
| | 131 | v_reden_broj int; |
| | 132 | v_status_uplata varchar(30); |
| | 133 | BEGIN |
| | 134 | IF NOT EXISTS (SELECT 1 FROM Zapisnik WHERE id_na_zapisnik = p_id_zapisnik) THEN |
| | 135 | RAISE EXCEPTION 'Zapisnikot so id % ne postoi!', p_id_zapisnik; |
| | 136 | END IF; |
| | 137 | IF NOT EXISTS (SELECT 1 FROM Prekrsok WHERE id_prekrsok = p_id_prekrsok) THEN |
| | 138 | RAISE EXCEPTION 'Prekrsokot so id % ne postoi!', p_id_prekrsok; |
| | 139 | END IF; |
| | 140 | |
| | 141 | SELECT status INTO v_status_uplata |
| | 142 | FROM Uplata WHERE id_zapisnik = p_id_zapisnik; |
| | 143 | |
| | 144 | IF v_status_uplata = 'Plateno' THEN |
| | 145 | RAISE EXCEPTION 'Zapisnik % e vekje platen, ne moze da se dodavaat novi stavki!', p_id_zapisnik; |
| | 146 | END IF; |
| | 147 | |
| | 148 | SELECT COALESCE(MAX(reden_broj), 0) + 1 INTO v_reden_broj |
| | 149 | FROM Stavka_Zapisnik WHERE id_na_zapisnik = p_id_zapisnik; |
| | 150 | |
| | 151 | INSERT INTO Stavka_Zapisnik (reden_broj, id_na_zapisnik, id_na_prekrsok) |
| | 152 | VALUES (v_reden_broj, p_id_zapisnik, p_id_prekrsok); |
| | 153 | |
| | 154 | RAISE NOTICE 'Dodadena stavka so reden_broj % vo zapisnik %.', v_reden_broj, p_id_zapisnik; |
| | 155 | END; |
| | 156 | $$; |
| | 157 | }}} |
| | 158 | |
| | 159 | Повик: |
| | 160 | {{{#!sql |
| | 161 | CALL dodadi_stavka_zapisnik(30000005, 3); |
| | 162 | }}} |
| | 163 | |
| | 164 | |
| | 165 | === Процедура 3: plati_kazna === |
| | 166 | Ја плаќа казната за даден записник — го менува статусот во `Plateno`, го запишува датумот и начинот на плаќање. Проверува дали уплатата постои, дали веќе е платена, дали има износ за наплата и дали начинот на плаќање е валиден. |
| | 167 | |
| | 168 | {{{#!sql |
| | 169 | CREATE OR REPLACE PROCEDURE plati_kazna( |
| | 170 | p_id_zapisnik int, |
| | 171 | p_nacin_plakanje varchar(30) |
| | 172 | ) |
| | 173 | LANGUAGE plpgsql |
| | 174 | AS $$ |
| | 175 | DECLARE |
| | 176 | v_status varchar(30); |
| | 177 | v_iznos numeric(10,2); |
| | 178 | BEGIN |
| | 179 | SELECT status, iznos INTO v_status, v_iznos |
| | 180 | FROM Uplata WHERE id_zapisnik = p_id_zapisnik; |
| | 181 | |
| | 182 | IF v_status IS NULL THEN |
| | 183 | RAISE EXCEPTION 'Ne postoi uplata za zapisnik %!', p_id_zapisnik; |
| | 184 | END IF; |
| | 185 | IF v_status = 'Plateno' THEN |
| | 186 | RAISE EXCEPTION 'Kaznata za zapisnik % e vekje platena!', p_id_zapisnik; |
| | 187 | END IF; |
| | 188 | IF v_iznos <= 0 THEN |
| | 189 | RAISE EXCEPTION 'Zapisnikot % nema stavki/kazni za naplata!', p_id_zapisnik; |
| | 190 | END IF; |
| | 191 | IF p_nacin_plakanje NOT IN ('E-bankarstvo', 'Platezna karticka', 'Gotovo/Uplatnica') THEN |
| | 192 | RAISE EXCEPTION 'Nepoznat nacin na plakanje: %.', p_nacin_plakanje; |
| | 193 | END IF; |
| | 194 | |
| | 195 | UPDATE Uplata |
| | 196 | SET status = 'Plateno', |
| | 197 | datum_uplata = CURRENT_DATE, |
| | 198 | nacin_plakanje = p_nacin_plakanje |
| | 199 | WHERE id_zapisnik = p_id_zapisnik; |
| | 200 | |
| | 201 | RAISE NOTICE 'Uspesno naplatena kazna od % za zapisnik % (%).', v_iznos, p_id_zapisnik, p_nacin_plakanje; |
| | 202 | END; |
| | 203 | $$; |
| | 204 | }}} |
| | 205 | |
| | 206 | Повик: |
| | 207 | {{{#!sql |
| | 208 | CALL plati_kazna(30000005, 'E-bankarstvo'); |
| | 209 | }}} |
| | 210 | |
| | 211 | |
| | 212 | === Процедура 4: dodadi_kategorija_na_vozacka === |
| | 213 | Кога возач полага дополнителна категорија (на пр. има B, полага C), ја додава на возачката. Проверува дали дозволата постои и не е истечена, дали категоријата постои и не е веќе додадена, и дали датумот на полагање е валиден. |
| | 214 | |
| | 215 | {{{#!sql |
| | 216 | CREATE OR REPLACE PROCEDURE dodadi_kategorija_na_vozacka( |
| | 217 | p_broj_dozvola varchar(30), |
| | 218 | p_id_kategorija int, |
| | 219 | p_datum_polaganje date DEFAULT CURRENT_DATE |
| | 220 | ) |
| | 221 | LANGUAGE plpgsql |
| | 222 | AS $$ |
| | 223 | DECLARE |
| | 224 | v_datum_vaznost_do date; |
| | 225 | v_datum_izdavanje date; |
| | 226 | v_kod_kategorija varchar(3); |
| | 227 | BEGIN |
| | 228 | SELECT datum_vaznost_do, datum_izdavanje |
| | 229 | INTO v_datum_vaznost_do, v_datum_izdavanje |
| | 230 | FROM Vozacka_dozvola WHERE broj_dozvola = p_broj_dozvola; |
| | 231 | |
| | 232 | IF v_datum_vaznost_do IS NULL THEN |
| | 233 | RAISE EXCEPTION 'Vozackata dozvola so broj % ne postoi!', p_broj_dozvola; |
| | 234 | END IF; |
| | 235 | IF v_datum_vaznost_do < CURRENT_DATE THEN |
| | 236 | RAISE EXCEPTION 'Vozackata dozvola % e istechena (vaznost do %)!', p_broj_dozvola, v_datum_vaznost_do; |
| | 237 | END IF; |
| | 238 | |
| | 239 | SELECT kod INTO v_kod_kategorija |
| | 240 | FROM Kategorija WHERE id_kategorija = p_id_kategorija; |
| | 241 | |
| | 242 | IF v_kod_kategorija IS NULL THEN |
| | 243 | RAISE EXCEPTION 'Kategorijata so id % ne postoi!', p_id_kategorija; |
| | 244 | END IF; |
| | 245 | |
| | 246 | IF EXISTS ( |
| | 247 | SELECT 1 FROM Kategorija_Vozacka_dozvola |
| | 248 | WHERE broj_dozvola = p_broj_dozvola AND id_kategorija = p_id_kategorija |
| | 249 | ) THEN |
| | 250 | RAISE EXCEPTION 'Kategorijata % vekje e dodadena na vozackata %!', v_kod_kategorija, p_broj_dozvola; |
| | 251 | END IF; |
| | 252 | |
| | 253 | IF p_datum_polaganje > CURRENT_DATE THEN |
| | 254 | RAISE EXCEPTION 'Datumot na polaganje % ne moze da bide vo idnina!', p_datum_polaganje; |
| | 255 | END IF; |
| | 256 | IF p_datum_polaganje < v_datum_izdavanje THEN |
| | 257 | RAISE EXCEPTION 'Datumot na polaganje % ne moze da bide pred izdavanjeto na vozackata (%).', |
| | 258 | p_datum_polaganje, v_datum_izdavanje; |
| | 259 | END IF; |
| | 260 | |
| | 261 | INSERT INTO Kategorija_Vozacka_dozvola (id_kategorija, broj_dozvola, datum_polaganje) |
| | 262 | VALUES (p_id_kategorija, p_broj_dozvola, p_datum_polaganje); |
| | 263 | |
| | 264 | RAISE NOTICE 'Uspesno dodadena kategorija % na vozackata %.', v_kod_kategorija, p_broj_dozvola; |
| | 265 | END; |
| | 266 | $$; |
| | 267 | }}} |
| | 268 | |
| | 269 | Повик: |
| | 270 | {{{#!sql |
| | 271 | CALL dodadi_kategorija_na_vozacka('L8207294', 7); |
| | 272 | CALL dodadi_kategorija_na_vozacka('L8207294', 9, '2025-11-15'); |
| | 273 | }}} |
| | 274 | |
| | 275 | |
| | 276 | [[BR]] |
| | 277 | == 3. Дополнителна функција: azuriraj_kazna_plakanja == |
| | 278 | Функција што ги обработува задоцнетите уплати наеднаш: на неплатените постари од 2 недели им го зголемува износот за 50%, а тие постари од 2 месеци ги префрла во статус `Sudska_postapka`. Враќа колку уплати вкупно се променети. |
| | 279 | |
| | 280 | Прво ја прошируваме табелата `Uplata` со новиот статус и со колона за обележување дали казната е веќе зголемена: |
| | 281 | {{{#!sql |
| | 282 | ALTER TABLE Uplata DROP CONSTRAINT IF EXISTS uplata_status_check; |
| | 283 | ALTER TABLE Uplata ADD CONSTRAINT uplata_status_check |
| | 284 | CHECK (status IN ('Plateno', 'Neplateno', 'Sudska_postapka')); |
| | 285 | |
| | 286 | ALTER TABLE Uplata ADD COLUMN IF NOT EXISTS kazna_zgolemena boolean DEFAULT false; |
| | 287 | }}} |
| | 288 | |
| | 289 | {{{#!sql |
| | 290 | CREATE OR REPLACE FUNCTION azuriraj_kazna_plakanja() |
| | 291 | RETURNS int |
| | 292 | LANGUAGE plpgsql |
| | 293 | AS $$ |
| | 294 | DECLARE |
| | 295 | v_zgolemeni int := 0; |
| | 296 | v_na_sud int := 0; |
| | 297 | BEGIN |
| | 298 | UPDATE Uplata u |
| | 299 | SET iznos = u.iznos + (u.iznos * 0.50), |
| | 300 | kazna_zgolemena = true |
| | 301 | FROM Zapisnik z |
| | 302 | WHERE u.id_zapisnik = z.id_na_zapisnik |
| | 303 | AND u.status = 'Neplateno' |
| | 304 | AND u.kazna_zgolemena = false |
| | 305 | AND z.datum < CURRENT_DATE - INTERVAL '2 weeks'; |
| | 306 | GET DIAGNOSTICS v_zgolemeni = ROW_COUNT; |
| | 307 | |
| | 308 | UPDATE Uplata u |
| | 309 | SET status = 'Sudska_postapka' |
| | 310 | FROM Zapisnik z |
| | 311 | WHERE u.id_zapisnik = z.id_na_zapisnik |
| | 312 | AND u.status = 'Neplateno' |
| | 313 | AND z.datum < CURRENT_DATE - INTERVAL '2 months'; |
| | 314 | GET DIAGNOSTICS v_na_sud = ROW_COUNT; |
| | 315 | |
| | 316 | RAISE NOTICE 'Zgolemena kazna za % uplati, prefrleni % na sudska postapka.', v_zgolemeni, v_na_sud; |
| | 317 | RETURN v_zgolemeni + v_na_sud; |
| | 318 | END; |
| | 319 | $$; |
| | 320 | }}} |
| | 321 | |
| | 322 | Повик: |
| | 323 | {{{#!sql |
| | 324 | SELECT azuriraj_kazna_plakanja(); |
| | 325 | SELECT status, COUNT(*) FROM Uplata GROUP BY status; |
| | 326 | }}} |