| 31 | | }}} |
| 32 | | |
| 33 | | Функцијата го пресметува бројот на слободни места за даден настан... |
| | 32 | |
| | 33 | SELECT 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 | {{{ |
| | 44 | CREATE OR REPLACE FUNCTION fn_prihod_po_nastan(p_eventid BIGINT) |
| | 45 | RETURNS INT AS $$ |
| | 46 | DECLARE |
| | 47 | v_prihod INT; |
| | 48 | BEGIN |
| | 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; |
| | 57 | END; |
| | 58 | $$ LANGUAGE plpgsql; |
| | 59 | |
| | 60 | SELECT 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 | {{{ |
| | 71 | CREATE OR REPLACE FUNCTION fn_moze_review( |
| | 72 | p_userid BIGINT, |
| | 73 | p_eventid BIGINT |
| | 74 | ) |
| | 75 | RETURNS BOOLEAN AS $$ |
| | 76 | BEGIN |
| | 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; |
| | 95 | END; |
| | 96 | $$ LANGUAGE plpgsql; |
| | 97 | |
| | 98 | SELECT 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 | {{{ |
| | 109 | CREATE OR REPLACE FUNCTION fn_statistika_recenzii(p_eventid BIGINT) |
| | 110 | RETURNS 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 $$ |
| | 119 | BEGIN |
| | 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; |
| | 131 | END; |
| | 132 | $$ LANGUAGE plpgsql; |
| | 133 | |
| | 134 | SELECT * 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 | {{{ |
| | 145 | CREATE OR REPLACE FUNCTION fn_pozicija_vo_waitlist( |
| | 146 | p_userid BIGINT, |
| | 147 | p_eventid BIGINT |
| | 148 | ) |
| | 149 | RETURNS INT AS $$ |
| | 150 | DECLARE |
| | 151 | v_pozicija INT; |
| | 152 | BEGIN |
| | 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; |
| | 170 | END; |
| | 171 | $$ LANGUAGE plpgsql; |
| | 172 | |
| | 173 | SELECT fn_pozicija_vo_waitlist(2, 1) AS pozicija; |
| | 174 | -- Резултат: 1 |
| | 175 | }}} |
| | 176 | |
| | 177 | Функцијата го враќа редниот број на корисникот во листата на чекање за даден настан, подреден по времето на пријавување. Ја имплементира бизнис логиката за информирање на корисникот за неговата позиција во редот при пополнет настан. |
| | 178 | |
| | 179 | ---- |
| | 180 | |
| | 181 | === fn_verifikacija_tiket(p_ticketid) |
| | 182 | |
| | 183 | {{{ |
| | 184 | CREATE OR REPLACE FUNCTION fn_verifikacija_tiket(p_ticketid BIGINT) |
| | 185 | RETURNS BOOLEAN AS $$ |
| | 186 | DECLARE |
| | 187 | v_status VARCHAR(20); |
| | 188 | v_start_date DATE; |
| | 189 | v_end_date DATE; |
| | 190 | BEGIN |
| | 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; |
| | 210 | END; |
| | 211 | $$ LANGUAGE plpgsql; |
| | 212 | |
| | 213 | SELECT 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 | {{{ |
| | 226 | CREATE OR REPLACE PROCEDURE proc_najavi_korisnik( |
| | 227 | p_email VARCHAR(50), |
| | 228 | p_password VARCHAR(255) |
| | 229 | ) LANGUAGE plpgsql AS $$ |
| | 230 | DECLARE |
| | 231 | v_userid BIGINT; |
| | 232 | v_role_name VARCHAR(20); |
| | 233 | BEGIN |
| | 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; |
| | 251 | END; |
| | 252 | $$; |
| | 253 | |
| | 254 | CALL 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 | {{{ |
| | 265 | CREATE 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 $$ |
| | 273 | DECLARE |
| | 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); |
| | 280 | BEGIN |
| | 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; |
| | 318 | END; |
| | 319 | $$; |
| | 320 | |
| | 321 | CALL 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 | {{{ |
| | 332 | CREATE OR REPLACE PROCEDURE proc_plati_narachka( |
| | 333 | p_orderid BIGINT, |
| | 334 | p_payment_methodid BIGINT |
| | 335 | ) LANGUAGE plpgsql AS $$ |
| | 336 | DECLARE |
| | 337 | v_amount INT; |
| | 338 | v_status_name VARCHAR(20); |
| | 339 | v_paid_id BIGINT; |
| | 340 | BEGIN |
| | 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; |
| | 372 | END; |
| | 373 | $$; |
| | 374 | |
| | 375 | CALL 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 | {{{ |
| | 386 | CREATE OR REPLACE PROCEDURE proc_otkazi_narachka(p_orderid BIGINT) |
| | 387 | LANGUAGE plpgsql AS $$ |
| | 388 | DECLARE |
| | 389 | v_paymentid BIGINT; |
| | 390 | v_amount INT; |
| | 391 | v_status_id BIGINT; |
| | 392 | BEGIN |
| | 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; |
| | 417 | END; |
| | 418 | $$; |
| | 419 | |
| | 420 | CALL proc_otkazi_narachka(12500001); |
| | 421 | }}} |
| | 422 | |
| | 423 | Процедурата го обработува откажувањето на нарачка — ги откажува нарачката и сите нејзини тикети и доколку нарачката е претходно платена, автоматски креира барање за рефундирање. Ја имплементира бизнис логиката за откажување и рефундирање во апликацијата. |
| | 424 | |
| | 425 | ---- |
| | 426 | |
| | 427 | === proc_dodaj_vo_waitlist(p_userid, p_eventid) |
| | 428 | |
| | 429 | {{{ |
| | 430 | CREATE OR REPLACE PROCEDURE proc_dodaj_vo_waitlist( |
| | 431 | p_userid BIGINT, |
| | 432 | p_eventid BIGINT |
| | 433 | ) LANGUAGE plpgsql AS $$ |
| | 434 | DECLARE |
| | 435 | v_waitlist_id BIGINT; |
| | 436 | BEGIN |
| | 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; |
| | 470 | END; |
| | 471 | $$; |
| | 472 | |
| | 473 | CALL 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 | {{{ |
| | 483 | CREATE 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 $$ |
| | 489 | BEGIN |
| | 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; |
| | 510 | END; |
| | 511 | $$; |
| | 512 | |
| | 513 | CALL 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 | {{{ |
| | 526 | CREATE OR REPLACE FUNCTION fn_trig_waitlist() |
| | 527 | RETURNS TRIGGER AS $$ |
| | 528 | DECLARE |
| | 529 | v_waitlist_id BIGINT; |
| | 530 | BEGIN |
| | 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; |
| | 547 | END; |
| | 548 | $$ LANGUAGE plpgsql; |
| | 549 | |
| | 550 | CREATE TRIGGER trig_waitlist |
| | 551 | AFTER UPDATE ON ticket |
| | 552 | FOR EACH ROW |
| | 553 | EXECUTE FUNCTION fn_trig_waitlist(); |
| | 554 | |
| | 555 | -- Test |
| | 556 | UPDATE 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 | {{{ |
| | 570 | CREATE OR REPLACE FUNCTION fn_trig_refund() |
| | 571 | RETURNS TRIGGER AS $$ |
| | 572 | BEGIN |
| | 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; |
| | 582 | END; |
| | 583 | $$ LANGUAGE plpgsql; |
| | 584 | |
| | 585 | CREATE TRIGGER trig_refund |
| | 586 | BEFORE INSERT ON refund |
| | 587 | FOR EACH ROW |
| | 588 | EXECUTE FUNCTION fn_trig_refund(); |
| | 589 | |
| | 590 | -- Test |
| | 591 | INSERT INTO refund (amount, reason, status, paymentid) |
| | 592 | VALUES (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 | {{{ |
| | 603 | CREATE OR REPLACE FUNCTION fn_trig_quantity() |
| | 604 | RETURNS TRIGGER AS $$ |
| | 605 | BEGIN |
| | 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; |
| | 626 | END; |
| | 627 | $$ LANGUAGE plpgsql; |
| | 628 | |
| | 629 | CREATE TRIGGER trig_quantity |
| | 630 | AFTER INSERT OR UPDATE ON ticket |
| | 631 | FOR EACH ROW |
| | 632 | EXECUTE FUNCTION fn_trig_quantity(); |
| | 633 | |
| | 634 | -- Pred insert: quantity_available = 692 |
| | 635 | INSERT INTO ticket (code, status, ticket_typeid, user_orderid, seatid, app_userid, eventid, hallid) |
| | 636 | VALUES ('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 | |
| | 642 | UPDATE ticket SET status = 'CANCELLED' WHERE code = 'TKT-TEST-TRIG'; |
| | 643 | -- Po update: quantity_available = 692 |
| | 644 | }}} |
| | 645 | |
| | 646 | Тригерот се активира по секое вметнување или ажурирање на тикет и автоматски го ажурира бројот на достапни тикети во табелата event_ticket_type — го намалува при продажба и го зголемува при откажување. Ја имплементира бизнис логиката за следење на достапноста на тикети во реално време. |