Changes between Version 4 and Version 5 of DatabaseProgramming


Ignore:
Timestamp:
05/26/26 16:12:09 (10 hours ago)
Author:
231070
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • DatabaseProgramming

    v4 v5  
    33== Извештај ==
    44Извештајот за фаза 4 е достапен овде: [attachment:FPT.pdf Извештај]
     5
    56
    67== Функции
     
    2930END;
    3031$$ LANGUAGE plpgsql;
    31 }}}
    32 
    33 Функцијата го пресметува бројот на слободни места за даден настан...
     32
     33SELECT fn_slobodni_mesta(100) AS slobodni_mesta;
     34-- Резултат: 736
     35}}}
     36
     37Функцијата го пресметува бројот на слободни места за даден настан така што од вкупниот капацитет на сите сали го одзема бројот на активни тикети. Ја имплементира бизнис логиката за проверка на достапност пред купување на тикет и се користи во процедурата proc_kupi_tiket и proc_dodaj_vo_waitlist.
     38
     39----
     40
     41=== fn_prihod_po_nastan(p_eventid)
     42
     43{{{
     44CREATE OR REPLACE FUNCTION fn_prihod_po_nastan(p_eventid BIGINT)
     45RETURNS INT AS $$
     46DECLARE
     47    v_prihod INT;
     48BEGIN
     49    SELECT SUM(p.amount)
     50    INTO v_prihod
     51    FROM payment p
     52    JOIN user_order uo ON uo.id = p.user_orderid
     53    JOIN ticket t ON t.user_orderid = uo.id
     54    WHERE t.eventid = p_eventid AND p.status = 'COMPLETED';
     55
     56    RETURN v_prihod;
     57END;
     58$$ LANGUAGE plpgsql;
     59
     60SELECT fn_prihod_po_nastan(1) AS vkupen_prihod;
     61-- Резултат: 370300
     62}}}
     63
     64Функцијата го пресметува вкупниот приход за даден настан преку сумирање на сите завршени плаќања поврзани со тикетите за тој настан. Ја имплементира бизнис логиката за финансиски извештај и се користи при генерирање на статистики за организаторите на настани.
     65
     66----
     67
     68=== fn_moze_review(p_userid, p_eventid)
     69
     70{{{
     71CREATE OR REPLACE FUNCTION fn_moze_review(
     72    p_userid  BIGINT,
     73    p_eventid BIGINT
     74)
     75RETURNS BOOLEAN AS $$
     76BEGIN
     77    IF NOT EXISTS (
     78        SELECT 1 FROM ticket
     79        WHERE app_userid = p_userid
     80          AND eventid    = p_eventid
     81          AND status IN ('USED')
     82    ) THEN
     83        RETURN FALSE;
     84    END IF;
     85
     86    IF EXISTS (
     87        SELECT 1 FROM review
     88        WHERE app_userid = p_userid
     89          AND eventid    = p_eventid
     90    ) THEN
     91        RETURN FALSE;
     92    END IF;
     93
     94    RETURN TRUE;
     95END;
     96$$ LANGUAGE plpgsql;
     97
     98SELECT fn_moze_review(28547, 35) AS moze_review;
     99-- Резултат: true
     100}}}
     101
     102Функцијата проверува дали корисникот смее да остави рецензија за даден настан — враќа TRUE само ако корисникот има искористен тикет за тој настан и сè уште нема оставено рецензија. Ја имплементира бизнис логиката за заштита од лажни рецензии и се користи во процедурата proc_ostavi_review и тригерот trig_review.
     103
     104----
     105
     106=== fn_statistika_recenzii(p_eventid)
     107
     108{{{
     109CREATE OR REPLACE FUNCTION fn_statistika_recenzii(p_eventid BIGINT)
     110RETURNS TABLE (
     111    vkupno_recenzii  BIGINT,
     112    prosecen_rejting NUMERIC,
     113    pet_zvezdi       BIGINT,
     114    cetiri_zvezdi    BIGINT,
     115    tri_zvezdi       BIGINT,
     116    dve_zvezdi       BIGINT,
     117    edna_zvezda      BIGINT
     118) AS $$
     119BEGIN
     120    RETURN QUERY
     121    SELECT
     122        COUNT(*)                                   AS vkupno_recenzii,
     123        ROUND(AVG(rating), 2)                      AS prosecen_rejting,
     124        COUNT(*) FILTER (WHERE rating = 5)         AS pet_zvezdi,
     125        COUNT(*) FILTER (WHERE rating = 4)         AS cetiri_zvezdi,
     126        COUNT(*) FILTER (WHERE rating = 3)         AS tri_zvezdi,
     127        COUNT(*) FILTER (WHERE rating = 2)         AS dve_zvezdi,
     128        COUNT(*) FILTER (WHERE rating = 1)         AS edna_zvezda
     129    FROM review
     130    WHERE eventid = p_eventid;
     131END;
     132$$ LANGUAGE plpgsql;
     133
     134SELECT * FROM fn_statistika_recenzii(33);
     135-- Резултат: 94  3.83  33  29  19  9  4
     136}}}
     137
     138Функцијата враќа детална статистика за рецензиите на даден настан — вкупен број, просечен рејтинг и распределба по број на ѕвезди. Ја имплементира бизнис логиката за приказ на статистики на страницата на настанот и им овозможува на корисниците да донесат информирана одлука пред купување на тикет.
     139
     140----
     141
     142=== fn_pozicija_vo_waitlist(p_userid, p_eventid)
     143
     144{{{
     145CREATE OR REPLACE FUNCTION fn_pozicija_vo_waitlist(
     146    p_userid  BIGINT,
     147    p_eventid BIGINT
     148)
     149RETURNS INT AS $$
     150DECLARE
     151    v_pozicija INT;
     152BEGIN
     153    SELECT pozicija INTO v_pozicija
     154    FROM (
     155        SELECT
     156            uw.app_userid,
     157            ROW_NUMBER() OVER (ORDER BY w.created_at ASC) AS pozicija
     158        FROM waitlist w
     159        JOIN user_waitlist uw ON uw.waitlistid = w.id
     160        WHERE w.eventid = p_eventid
     161          AND w.status  = 'PENDING'
     162    ) sub
     163    WHERE app_userid = p_userid;
     164
     165    IF v_pozicija IS NULL THEN
     166        RAISE EXCEPTION 'Korisnikot % ne e vo waitlist za event %', p_userid, p_eventid;
     167    END IF;
     168
     169    RETURN v_pozicija;
     170END;
     171$$ LANGUAGE plpgsql;
     172
     173SELECT fn_pozicija_vo_waitlist(2, 1) AS pozicija;
     174-- Резултат: 1
     175}}}
     176
     177Функцијата го враќа редниот број на корисникот во листата на чекање за даден настан, подреден по времето на пријавување. Ја имплементира бизнис логиката за информирање на корисникот за неговата позиција во редот при пополнет настан.
     178
     179----
     180
     181=== fn_verifikacija_tiket(p_ticketid)
     182
     183{{{
     184CREATE OR REPLACE FUNCTION fn_verifikacija_tiket(p_ticketid BIGINT)
     185RETURNS BOOLEAN AS $$
     186DECLARE
     187    v_status     VARCHAR(20);
     188    v_start_date DATE;
     189    v_end_date   DATE;
     190BEGIN
     191    SELECT t.status, e.start_date, e.end_date
     192    INTO v_status, v_start_date, v_end_date
     193    FROM ticket t
     194    JOIN event e ON e.id = t.eventid
     195    WHERE t.id = p_ticketid;
     196
     197    IF v_status IS NULL THEN
     198        RAISE EXCEPTION 'Tiketot % ne postoi', p_ticketid;
     199    END IF;
     200
     201    IF v_status != 'ACTIVE' THEN
     202        RETURN FALSE;
     203    END IF;
     204
     205    IF CURRENT_DATE > v_end_date THEN
     206        RETURN FALSE;
     207    END IF;
     208
     209    RETURN TRUE;
     210END;
     211$$ LANGUAGE plpgsql;
     212
     213SELECT fn_verifikacija_tiket(1) AS e_validen;
     214-- Резултат: false
     215}}}
     216
     217Функцијата проверува дали тикетот е валиден за скенирање при влез на настан — враќа TRUE само ако тикетот е со статус ACTIVE и настанот сè уште не е завршен. Ја имплементира бизнис логиката за контрола на влез на настани.
     218
     219----
     220
     221== Процедури
     222
     223=== proc_najavi_korisnik(p_email, p_password)
     224
     225{{{
     226CREATE OR REPLACE PROCEDURE proc_najavi_korisnik(
     227    p_email    VARCHAR(50),
     228    p_password VARCHAR(255)
     229) LANGUAGE plpgsql AS $$
     230DECLARE
     231    v_userid    BIGINT;
     232    v_role_name VARCHAR(20);
     233BEGIN
     234    SELECT id INTO v_userid
     235    FROM app_user
     236    WHERE email = p_email
     237      AND user_password = p_password
     238      AND id != 1;
     239
     240    IF v_userid IS NULL THEN
     241        RAISE EXCEPTION 'Pogreshen email ili lozinka';
     242    END IF;
     243
     244    SELECT r.role_name INTO v_role_name
     245    FROM user_roles ur
     246    JOIN roles r ON r.id = ur.rolesid
     247    WHERE ur.app_userid = v_userid
     248    LIMIT 1;
     249
     250    RAISE NOTICE 'Korisnikot % se prijavi kako %', p_email, v_role_name;
     251END;
     252$$;
     253
     254CALL proc_najavi_korisnik('Gertie.Gold0@gmail.com', 'GertieGold');
     255-- Резултат: Korisnikot Gertie.Gold0@gmail.com se prijavi kako USER
     256}}}
     257
     258Процедурата ја верификува најавата на корисникот преку проверка на email и лозинка и ја враќа неговата улога во системот. Ја имплементира бизнис логиката за автентикација на корисници. Напомена: ова е поедноставена верзија — во продукциски систем лозинката би се споредувала со bcrypt хаш.
     259
     260----
     261
     262=== proc_kupi_tiket(p_userid, p_eventid, p_hallid, p_typeid, p_seatid, p_promo_codeid)
     263
     264{{{
     265CREATE OR REPLACE PROCEDURE proc_kupi_tiket(
     266    p_userid       BIGINT,
     267    p_eventid      BIGINT,
     268    p_hallid       BIGINT,
     269    p_typeid       BIGINT,
     270    p_seatid       BIGINT,
     271    p_promo_codeid BIGINT DEFAULT NULL
     272) LANGUAGE plpgsql AS $$
     273DECLARE
     274    v_orderid     BIGINT;
     275    v_price       INT;
     276    v_discount    INT := 0;
     277    v_final_price INT;
     278    v_status_id   BIGINT;
     279    v_ticket_code VARCHAR(50);
     280BEGIN
     281    SELECT price INTO v_price
     282    FROM event_ticket_type
     283    WHERE eventid = p_eventid AND ticket_typeid = p_typeid;
     284
     285    IF v_price IS NULL THEN
     286        RAISE EXCEPTION 'Ne postoi ticket type % za event %', p_typeid, p_eventid;
     287    END IF;
     288
     289    IF fn_slobodni_mesta(p_eventid) <= 0 THEN
     290        RAISE EXCEPTION 'Nema slobodni mesta za event %', p_eventid;
     291    END IF;
     292
     293    IF p_promo_codeid IS NOT NULL THEN
     294        SELECT discount_percent INTO v_discount
     295        FROM promo_code
     296        WHERE id = p_promo_codeid AND expiration_date >= CURRENT_DATE;
     297
     298        IF v_discount IS NULL THEN
     299            RAISE EXCEPTION 'Promo kodot ne e validen ili e istecen';
     300        END IF;
     301    END IF;
     302
     303    v_final_price := v_price - (v_price * v_discount / 100);
     304
     305    SELECT id INTO v_status_id
     306    FROM status WHERE status_name = 'CREATED';
     307
     308    INSERT INTO user_order (order_date, total_amount, app_userid, statusid, promo_codeid)
     309    VALUES (CURRENT_DATE, v_final_price, p_userid, v_status_id, p_promo_codeid)
     310    RETURNING id INTO v_orderid;
     311
     312    v_ticket_code := 'TKT-' || v_orderid || '-' || extract(epoch FROM now())::BIGINT;
     313
     314    INSERT INTO ticket (code, status, ticket_typeid, user_orderid, seatid, app_userid, eventid, hallid)
     315    VALUES (v_ticket_code, 'ACTIVE', p_typeid, v_orderid, p_seatid, p_userid, p_eventid, p_hallid);
     316
     317    RAISE NOTICE 'Tiketot e kreiran so kod: %', v_ticket_code;
     318END;
     319$$;
     320
     321CALL proc_kupi_tiket(2, 1, 1, 1, 5, NULL);
     322-- Резултат: Tiketot e kreiran so kod: TKT-12500002-1778661805
     323}}}
     324
     325Процедурата го имплементира целиот процес на купување тикет — проверува дали постои типот на тикет за тој настан, дали има слободни места, дали промо кодот е валиден, ја пресметува конечната цена со попуст и креира нарачка и тикет. Ја имплементира централната бизнис логика за продажба на тикети во апликацијата.
     326
     327----
     328
     329=== proc_plati_narachka(p_orderid, p_payment_methodid)
     330
     331{{{
     332CREATE OR REPLACE PROCEDURE proc_plati_narachka(
     333    p_orderid          BIGINT,
     334    p_payment_methodid BIGINT
     335) LANGUAGE plpgsql AS $$
     336DECLARE
     337    v_amount      INT;
     338    v_status_name VARCHAR(20);
     339    v_paid_id     BIGINT;
     340BEGIN
     341    IF NOT EXISTS (SELECT 1 FROM user_order WHERE id = p_orderid) THEN
     342        RAISE EXCEPTION 'Order % ne postoi', p_orderid;
     343    END IF;
     344
     345    SELECT s.status_name INTO v_status_name
     346    FROM user_order uo
     347    JOIN status s ON s.id = uo.statusid
     348    WHERE uo.id = p_orderid;
     349
     350    IF v_status_name = 'PAID' THEN
     351        RAISE EXCEPTION 'Order % e vekje platen', p_orderid;
     352    END IF;
     353
     354    IF v_status_name = 'CANCELLED' THEN
     355        RAISE EXCEPTION 'Order % e otkazan i ne moze da se plati', p_orderid;
     356    END IF;
     357
     358    SELECT total_amount INTO v_amount
     359    FROM user_order WHERE id = p_orderid;
     360
     361    INSERT INTO payment (amount, status, payment_date, user_orderid, payment_methodid)
     362    VALUES (v_amount, 'COMPLETED', CURRENT_DATE, p_orderid, p_payment_methodid);
     363
     364    SELECT id INTO v_paid_id FROM status WHERE status_name = 'PAID';
     365    UPDATE user_order SET statusid = v_paid_id WHERE id = p_orderid;
     366
     367    INSERT INTO notification (message, created_at, app_userid)
     368    SELECT 'Narachkata e uspeshno platena', CURRENT_DATE, app_userid
     369    FROM user_order WHERE id = p_orderid;
     370
     371    RAISE NOTICE 'Order % e platen so iznos %', p_orderid, v_amount;
     372END;
     373$$;
     374
     375CALL proc_plati_narachka(4, 1);
     376-- Резултат: Order 4 e platen so iznos 3000
     377}}}
     378
     379Процедурата го обработува плаќањето на нарачка — проверува дали нарачката постои и дали не е веќе платена или откажана, креира запис за плаќање, го ажурира статусот на нарачката во PAID и праќа нотификација до корисникот. Ја имплементира бизнис логиката за финансиски трансакции во апликацијата.
     380
     381----
     382
     383=== proc_otkazi_narachka(p_orderid)
     384
     385{{{
     386CREATE OR REPLACE PROCEDURE proc_otkazi_narachka(p_orderid BIGINT)
     387LANGUAGE plpgsql AS $$
     388DECLARE
     389    v_paymentid  BIGINT;
     390    v_amount     INT;
     391    v_status_id  BIGINT;
     392BEGIN
     393    IF NOT EXISTS (SELECT 1 FROM user_order WHERE id = p_orderid) THEN
     394        RAISE EXCEPTION 'Order % ne postoi', p_orderid;
     395    END IF;
     396
     397    SELECT id INTO v_status_id
     398    FROM status WHERE status_name = 'CANCELLED';
     399
     400    UPDATE user_order SET statusid = v_status_id WHERE id = p_orderid;
     401
     402    UPDATE ticket SET status = 'CANCELLED' WHERE user_orderid = p_orderid;
     403
     404    SELECT id, amount INTO v_paymentid, v_amount
     405    FROM payment
     406    WHERE user_orderid = p_orderid AND status = 'COMPLETED'
     407    LIMIT 1;
     408
     409    IF v_paymentid IS NOT NULL THEN
     410        INSERT INTO refund (amount, reason, status, paymentid)
     411        VALUES (v_amount, 'Customer request', 'APPROVED', v_paymentid);
     412
     413        RAISE NOTICE 'Refund kreiran za order %', p_orderid;
     414    END IF;
     415
     416    RAISE NOTICE 'Narachkata % e otkazana', p_orderid;
     417END;
     418$$;
     419
     420CALL proc_otkazi_narachka(12500001);
     421}}}
     422
     423Процедурата го обработува откажувањето на нарачка — ги откажува нарачката и сите нејзини тикети и доколку нарачката е претходно платена, автоматски креира барање за рефундирање. Ја имплементира бизнис логиката за откажување и рефундирање во апликацијата.
     424
     425----
     426
     427=== proc_dodaj_vo_waitlist(p_userid, p_eventid)
     428
     429{{{
     430CREATE OR REPLACE PROCEDURE proc_dodaj_vo_waitlist(
     431    p_userid  BIGINT,
     432    p_eventid BIGINT
     433) LANGUAGE plpgsql AS $$
     434DECLARE
     435    v_waitlist_id BIGINT;
     436BEGIN
     437    IF NOT EXISTS (SELECT 1 FROM event WHERE id = p_eventid) THEN
     438        RAISE EXCEPTION 'Nastanot % ne postoi', p_eventid;
     439    END IF;
     440
     441    IF NOT EXISTS (SELECT 1 FROM app_user WHERE id = p_userid AND id != 1) THEN
     442        RAISE EXCEPTION 'Korisnikot % ne postoi', p_userid;
     443    END IF;
     444
     445    IF fn_slobodni_mesta(p_eventid) > 0 THEN
     446        RAISE EXCEPTION 'Ima slobodni mesta za event %, kupi tiket namesto waitlist', p_eventid;
     447    END IF;
     448
     449    IF EXISTS (
     450        SELECT 1 FROM user_waitlist uw
     451        JOIN waitlist w ON w.id = uw.waitlistid
     452        WHERE uw.app_userid = p_userid
     453          AND w.eventid = p_eventid
     454          AND w.status = 'PENDING'
     455    ) THEN
     456        RAISE EXCEPTION 'Korisnikot % vekje e vo waitlist za event %', p_userid, p_eventid;
     457    END IF;
     458
     459    INSERT INTO waitlist (created_at, status, eventid)
     460    VALUES (CURRENT_DATE, 'PENDING', p_eventid)
     461    RETURNING id INTO v_waitlist_id;
     462
     463    INSERT INTO user_waitlist (app_userid, waitlistid)
     464    VALUES (p_userid, v_waitlist_id);
     465
     466    INSERT INTO notification (message, created_at, app_userid)
     467    VALUES ('You have been added to the waitlist.', CURRENT_DATE, p_userid);
     468
     469    RAISE NOTICE 'Korisnikot % e dodaden vo waitlist za event %', p_userid, p_eventid;
     470END;
     471$$;
     472
     473CALL proc_dodaj_vo_waitlist(2, 1);
     474}}}
     475
     476Процедурата го додава корисникот на листата на чекање за пополнет настан — проверува дали настанот и корисникот постојат, дали навистина нема слободни места, дали корисникот не е веќе на листата и по успешно додавање праќа нотификација. Ја имплементира бизнис логиката за управување со листата на чекање при пополнети настани.
     477
     478----
     479
     480=== proc_ostavi_review(p_userid, p_eventid, p_rating, p_comment)
     481
     482{{{
     483CREATE OR REPLACE PROCEDURE proc_ostavi_review(
     484    p_userid   BIGINT,
     485    p_eventid  BIGINT,
     486    p_rating   INT,
     487    p_comment  VARCHAR(255) DEFAULT NULL
     488) LANGUAGE plpgsql AS $$
     489BEGIN
     490    IF NOT EXISTS (SELECT 1 FROM event WHERE id = p_eventid) THEN
     491        RAISE EXCEPTION 'Nastanot % ne postoi', p_eventid;
     492    END IF;
     493
     494    IF NOT EXISTS (SELECT 1 FROM event WHERE id = p_eventid AND end_date < CURRENT_DATE) THEN
     495        RAISE EXCEPTION 'Ne moze da se ostavi review za nastan koj ne e zavrshen';
     496    END IF;
     497
     498    IF NOT fn_moze_review(p_userid, p_eventid) THEN
     499        RAISE EXCEPTION 'Korisnikot % ne smee da ostavi review za event %', p_userid, p_eventid;
     500    END IF;
     501
     502    IF p_rating NOT BETWEEN 1 AND 5 THEN
     503        RAISE EXCEPTION 'Ratingot mora da bide pomegu 1 i 5';
     504    END IF;
     505
     506    INSERT INTO review (rating, review_comment, app_userid, eventid)
     507    VALUES (p_rating, p_comment, p_userid, p_eventid);
     508
     509    RAISE NOTICE 'Review e ostaveno za event % od korisnik %', p_eventid, p_userid;
     510END;
     511$$;
     512
     513CALL proc_ostavi_review(21615, 49952, 5, 'Odlichno iskustvo!');
     514-- Резултат: Review e ostaveno za event 49952 od korisnik 21615
     515}}}
     516
     517Процедурата го обработува оставањето рецензија за минат настан — проверува дали настанот постои и е завршен, го користи fn_moze_review за да провери дали корисникот смее да остави рецензија и дали рејтингот е во валиден опсег. Ја имплементира бизнис логиката за систем на рецензии во апликацијата.
     518
     519----
     520
     521== Тригери
     522
     523=== trig_waitlist
     524
     525{{{
     526CREATE OR REPLACE FUNCTION fn_trig_waitlist()
     527RETURNS TRIGGER AS $$
     528DECLARE
     529    v_waitlist_id BIGINT;
     530BEGIN
     531    IF NEW.status = 'CANCELLED' AND OLD.status != 'CANCELLED' THEN
     532        SELECT w.id INTO v_waitlist_id
     533        FROM waitlist w
     534        WHERE w.eventid = NEW.eventid AND w.status = 'PENDING'
     535        ORDER BY w.created_at
     536        LIMIT 1;
     537
     538        IF v_waitlist_id IS NOT NULL THEN
     539            UPDATE waitlist SET status = 'CONFIRMED'
     540            WHERE id = v_waitlist_id;
     541
     542            RAISE NOTICE 'Waitlist % e potvrden za event %', v_waitlist_id, NEW.eventid;
     543        END IF;
     544    END IF;
     545
     546    RETURN NEW;
     547END;
     548$$ LANGUAGE plpgsql;
     549
     550CREATE TRIGGER trig_waitlist
     551AFTER UPDATE ON ticket
     552FOR EACH ROW
     553EXECUTE FUNCTION fn_trig_waitlist();
     554
     555-- Test
     556UPDATE ticket SET status = 'CANCELLED' WHERE id = (
     557    SELECT t.id FROM ticket t
     558    JOIN waitlist w ON w.eventid = t.eventid
     559    WHERE w.status = 'PENDING' LIMIT 1
     560);
     561}}}
     562
     563Тригерот се активира автоматски по секое ажурирање на статусот на тикет во CANCELLED и го потврдува првиот корисник во редот на чекање за тој настан. Ја имплементира бизнис логиката за автоматско управување со листата на чекање при ослободување на место.
     564
     565----
     566
     567=== trig_refund
     568
     569{{{
     570CREATE OR REPLACE FUNCTION fn_trig_refund()
     571RETURNS TRIGGER AS $$
     572BEGIN
     573    IF EXISTS (
     574        SELECT 1 FROM refund
     575        WHERE paymentid = NEW.paymentid
     576          AND status IN ('REQUESTED', 'APPROVED')
     577    ) THEN
     578        RAISE EXCEPTION 'Vekje postoi refund za payment %', NEW.paymentid;
     579    END IF;
     580
     581    RETURN NEW;
     582END;
     583$$ LANGUAGE plpgsql;
     584
     585CREATE TRIGGER trig_refund
     586BEFORE INSERT ON refund
     587FOR EACH ROW
     588EXECUTE FUNCTION fn_trig_refund();
     589
     590-- Test
     591INSERT INTO refund (amount, reason, status, paymentid)
     592VALUES (100, 'Test duplikat', 'REQUESTED', 1);
     593-- Резултат: ERROR: Vekje postoi refund za payment 1
     594}}}
     595
     596Тригерот се активира пред секое вметнување во табелата REFUND и проверува дали за тоа плаќање веќе постои активно барање за рефундирање. Ја имплементира бизнис логиката за заштита од двојно рефундирање на исто плаќање.
     597
     598----
     599
     600=== trig_quantity
     601
     602{{{
     603CREATE OR REPLACE FUNCTION fn_trig_quantity()
     604RETURNS TRIGGER AS $$
     605BEGIN
     606    IF TG_OP = 'INSERT' AND NEW.status = 'ACTIVE' THEN
     607        UPDATE event_ticket_type
     608        SET quantity_available = quantity_available - 1
     609        WHERE eventid = NEW.eventid AND ticket_typeid = NEW.ticket_typeid;
     610
     611        IF NOT FOUND THEN
     612            RAISE EXCEPTION 'Ne postoi ticket type % za event %',
     613                NEW.ticket_typeid, NEW.eventid;
     614        END IF;
     615
     616    ELSIF TG_OP = 'UPDATE'
     617        AND NEW.status = 'CANCELLED'
     618        AND OLD.status != 'CANCELLED' THEN
     619
     620        UPDATE event_ticket_type
     621        SET quantity_available = quantity_available + 1
     622        WHERE eventid = NEW.eventid AND ticket_typeid = NEW.ticket_typeid;
     623    END IF;
     624
     625    RETURN NEW;
     626END;
     627$$ LANGUAGE plpgsql;
     628
     629CREATE TRIGGER trig_quantity
     630AFTER INSERT OR UPDATE ON ticket
     631FOR EACH ROW
     632EXECUTE FUNCTION fn_trig_quantity();
     633
     634-- Pred insert: quantity_available = 692
     635INSERT INTO ticket (code, status, ticket_typeid, user_orderid, seatid, app_userid, eventid, hallid)
     636VALUES ('TKT-TEST-TRIG', 'ACTIVE', 1,
     637        (SELECT MIN(id) FROM user_order), NULL,
     638        (SELECT MIN(id) FROM app_user WHERE id != 1), 33,
     639        (SELECT MIN(hallid) FROM event_hall WHERE eventid = 33));
     640-- Po insert: quantity_available = 691
     641
     642UPDATE ticket SET status = 'CANCELLED' WHERE code = 'TKT-TEST-TRIG';
     643-- Po update: quantity_available = 692
     644}}}
     645
     646Тригерот се активира по секое вметнување или ажурирање на тикет и автоматски го ажурира бројот на достапни тикети во табелата event_ticket_type — го намалува при продажба и го зголемува при откажување. Ја имплементира бизнис логиката за следење на достапноста на тикети во реално време.