| | 1 | =Функции, процедури и тригери |
| | 2 | |
| | 3 | Во овој дел се прикажани SQL функциите, процедурите и тригер-функциите кои се користат во системот за управување со библиотека. |
| | 4 | Тие ја имплементираат главната бизнис логика на апликацијата, како што се позајмување книги, враќање книги, регистрација на членови, резервација на книги, автоматска проверка на членство и автоматско креирање казна при задоцнето враќање. |
| | 5 | |
| | 6 | |
| | 7 | |
| | 8 | == 1. Функции == |
| | 9 | |
| | 10 | === 1.1 borrow_book === |
| | 11 | |
| | 12 | {{{#!sql |
| | 13 | CREATE OR REPLACE FUNCTION public.borrow_book( |
| | 14 | p_borrowing_id bigint, |
| | 15 | p_member_id bigint, |
| | 16 | p_copy_id bigint, |
| | 17 | p_employee_id bigint |
| | 18 | ) |
| | 19 | RETURNS text |
| | 20 | LANGUAGE plpgsql |
| | 21 | AS $function$ |
| | 22 | DECLARE |
| | 23 | v_available INTEGER; |
| | 24 | v_membership_status VARCHAR; |
| | 25 | BEGIN |
| | 26 | |
| | 27 | ``` |
| | 28 | -- Проверка дали книгата е достапна |
| | 29 | SELECT available |
| | 30 | INTO v_available |
| | 31 | FROM bookcopy |
| | 32 | WHERE copy_id = p_copy_id; |
| | 33 | |
| | 34 | IF v_available = 0 THEN |
| | 35 | RETURN 'Book copy is not available'; |
| | 36 | END IF; |
| | 37 | |
| | 38 | -- Проверка дали членот е активен |
| | 39 | SELECT membership_status |
| | 40 | INTO v_membership_status |
| | 41 | FROM member |
| | 42 | WHERE member_id = p_member_id; |
| | 43 | |
| | 44 | IF v_membership_status != 'ACTIVE' THEN |
| | 45 | RETURN 'Member is not active'; |
| | 46 | END IF; |
| | 47 | |
| | 48 | -- Креирање borrowing |
| | 49 | INSERT INTO borrowing( |
| | 50 | borrowing_id, |
| | 51 | book_id, |
| | 52 | borrow_date, |
| | 53 | due_date, |
| | 54 | copy_id, |
| | 55 | membership_id, |
| | 56 | borrowed_by |
| | 57 | ) |
| | 58 | VALUES( |
| | 59 | p_borrowing_id, |
| | 60 | (SELECT book_id |
| | 61 | FROM bookcopy |
| | 62 | WHERE copy_id = p_copy_id), |
| | 63 | CURRENT_DATE, |
| | 64 | CURRENT_DATE + INTERVAL '14 days', |
| | 65 | p_copy_id, |
| | 66 | p_member_id, |
| | 67 | p_employee_id |
| | 68 | ); |
| | 69 | |
| | 70 | -- Update availability |
| | 71 | UPDATE bookcopy |
| | 72 | SET available = 0 |
| | 73 | WHERE copy_id = p_copy_id; |
| | 74 | |
| | 75 | RETURN 'Book borrowed successfully'; |
| | 76 | ``` |
| | 77 | |
| | 78 | END; |
| | 79 | $function$; |
| | 80 | }}} |
| | 81 | |
| | 82 | '''Опис:''' |
| | 83 | Функцијата `borrow_book` се користи за позајмување конкретна копија од книга. |
| | 84 | Најпрво проверува дали копијата е достапна, а потоа проверува дали членот има активен статус. |
| | 85 | Ако условите се исполнети, се внесува нов запис во табелата `borrowing` и копијата се означува како недостапна. |
| | 86 | |
| | 87 | '''Употреба:''' |
| | 88 | Оваа функција ја имплементира главната логика за позајмување книга во библиотеката. |
| | 89 | Се користи за да се спречи позајмување на недостапна книга или позајмување од член кој нема активен статус. |
| | 90 | Со тоа се обезбедува контрола врз процесот на позајмување и ажурирање на достапноста на книгите. |
| | 91 | |
| | 92 | |
| | 93 | |
| | 94 | === 1.2 register_member === |
| | 95 | |
| | 96 | {{{#!sql |
| | 97 | CREATE OR REPLACE FUNCTION public.register_member( |
| | 98 | p_member_id bigint, |
| | 99 | p_first_name character varying, |
| | 100 | p_last_name character varying, |
| | 101 | p_email character varying, |
| | 102 | p_phone character varying, |
| | 103 | p_address text |
| | 104 | ) |
| | 105 | RETURNS text |
| | 106 | LANGUAGE plpgsql |
| | 107 | AS $function$ |
| | 108 | DECLARE |
| | 109 | v_existing_email INTEGER; |
| | 110 | v_existing_member INTEGER; |
| | 111 | BEGIN |
| | 112 | |
| | 113 | ``` |
| | 114 | -- check for existing member |
| | 115 | SELECT COUNT(*) |
| | 116 | INTO v_existing_member |
| | 117 | FROM member |
| | 118 | WHERE member_id = p_member_id; |
| | 119 | |
| | 120 | IF v_existing_member > 0 THEN |
| | 121 | RETURN 'Member is already registered'; |
| | 122 | END IF; |
| | 123 | |
| | 124 | -- check for existing email |
| | 125 | SELECT COUNT(*) |
| | 126 | INTO v_existing_email |
| | 127 | FROM member |
| | 128 | WHERE email = p_email; |
| | 129 | |
| | 130 | IF v_existing_email > 0 THEN |
| | 131 | RETURN 'Email already exists'; |
| | 132 | END IF; |
| | 133 | |
| | 134 | -- create new member |
| | 135 | INSERT INTO member( |
| | 136 | member_id, |
| | 137 | first_name, |
| | 138 | last_name, |
| | 139 | email, |
| | 140 | phone, |
| | 141 | address, |
| | 142 | membership_status, |
| | 143 | membership_start_date, |
| | 144 | membership_end_date |
| | 145 | ) |
| | 146 | VALUES( |
| | 147 | p_member_id, |
| | 148 | p_first_name, |
| | 149 | p_last_name, |
| | 150 | p_email, |
| | 151 | p_phone, |
| | 152 | p_address, |
| | 153 | 'ACTIVE', |
| | 154 | CURRENT_DATE, |
| | 155 | CURRENT_DATE + INTERVAL '1 year' |
| | 156 | ); |
| | 157 | |
| | 158 | RETURN 'Member registered successfully'; |
| | 159 | ``` |
| | 160 | |
| | 161 | END; |
| | 162 | $function$; |
| | 163 | }}} |
| | 164 | |
| | 165 | '''Опис:''' |
| | 166 | Функцијата `register_member` се користи за регистрација на нов член во библиотеката. |
| | 167 | Најпрво проверува дали веќе постои член со истиот `member_id`, а потоа проверува дали внесениот email веќе се користи. |
| | 168 | Ако членот и email адресата не постојат, се креира нов член со статус `ACTIVE` и членство со важност од една година. |
| | 169 | |
| | 170 | '''Употреба:''' |
| | 171 | Оваа функција ја имплементира логиката за регистрација на корисници во библиотечниот систем. |
| | 172 | Се користи за да се спречи дуплирање на членови и користење на иста email адреса од повеќе корисници. |
| | 173 | На овој начин системот обезбедува правилна евиденција на членовите. |
| | 174 | |
| | 175 | |
| | 176 | |
| | 177 | === 1.3 return_book === |
| | 178 | |
| | 179 | {{{#!sql |
| | 180 | CREATE OR REPLACE FUNCTION public.return_book(p_borrowing_id bigint) |
| | 181 | RETURNS text |
| | 182 | LANGUAGE plpgsql |
| | 183 | AS $function$ |
| | 184 | DECLARE |
| | 185 | v_due_date DATE; |
| | 186 | v_copy_id BIGINT; |
| | 187 | v_return_date DATE; |
| | 188 | v_late_days INTEGER; |
| | 189 | v_status_id INTEGER; |
| | 190 | BEGIN |
| | 191 | |
| | 192 | ``` |
| | 193 | -- take info from borrowing |
| | 194 | SELECT due_date, copy_id, return_date |
| | 195 | INTO v_due_date, v_copy_id, v_return_date |
| | 196 | FROM borrowing |
| | 197 | WHERE borrowing_id = p_borrowing_id; |
| | 198 | |
| | 199 | -- check for existing borrow |
| | 200 | IF v_copy_id IS NULL THEN |
| | 201 | RETURN 'Borrowing record not found'; |
| | 202 | END IF; |
| | 203 | |
| | 204 | -- check if book is returned |
| | 205 | IF v_return_date IS NOT NULL THEN |
| | 206 | RETURN 'Book already returned'; |
| | 207 | END IF; |
| | 208 | |
| | 209 | -- Update return date |
| | 210 | UPDATE borrowing |
| | 211 | SET return_date = CURRENT_DATE |
| | 212 | WHERE borrowing_id = p_borrowing_id; |
| | 213 | |
| | 214 | -- bookstatus |
| | 215 | SELECT status_id |
| | 216 | INTO v_status_id |
| | 217 | FROM bookcopy |
| | 218 | WHERE copy_id = v_copy_id; |
| | 219 | |
| | 220 | -- check for dameged book |
| | 221 | IF v_status_id = 5 THEN |
| | 222 | UPDATE bookcopy |
| | 223 | SET available = 0 |
| | 224 | WHERE copy_id = v_copy_id; |
| | 225 | |
| | 226 | RETURN 'Book returned with damage'; |
| | 227 | END IF; |
| | 228 | |
| | 229 | -- book is available |
| | 230 | UPDATE bookcopy |
| | 231 | SET available = 1, |
| | 232 | status_id = 1 |
| | 233 | WHERE copy_id = v_copy_id; |
| | 234 | |
| | 235 | -- check for late retun |
| | 236 | IF v_due_date IS NOT NULL THEN |
| | 237 | v_late_days := CURRENT_DATE - v_due_date; |
| | 238 | |
| | 239 | IF v_late_days > 0 THEN |
| | 240 | RETURN 'Book returned late by ' || v_late_days || ' days'; |
| | 241 | END IF; |
| | 242 | END IF; |
| | 243 | |
| | 244 | RETURN 'Book returned successfully'; |
| | 245 | ``` |
| | 246 | |
| | 247 | END; |
| | 248 | $function$; |
| | 249 | }}} |
| | 250 | |
| | 251 | '''Опис:''' |
| | 252 | Функцијата `return_book` се користи при враќање на позајмена книга. |
| | 253 | Таа проверува дали постои запис за позајмување, дали книгата веќе е вратена и потоа го ажурира датумот на враќање. |
| | 254 | Дополнително, проверува дали книгата е оштетена и дали е вратена со задоцнување. |
| | 255 | |
| | 256 | '''Употреба:''' |
| | 257 | Оваа функција ја имплементира логиката за враќање книги во библиотеката. |
| | 258 | Ако книгата е вратена во добра состојба, копијата повторно се означува како достапна. |
| | 259 | Ако книгата е оштетена, таа останува недостапна, а ако е вратена по рокот, системот враќа информација за бројот на денови на доцнење. |
| | 260 | |
| | 261 | '''Забелешка:''' |
| | 262 | Во оваа функција оштетена книга се проверува со `status_id = 5`. |
| | 263 | Ако во базата оштетените книги се означуваат со друга вредност, на пример `status_id = 2`, тогаш условот треба да се усогласи со вистинската вредност во табелата `bookcopy`. |
| | 264 | |
| | 265 | |
| | 266 | |
| | 267 | == 2. Процедури == |
| | 268 | |
| | 269 | === 2.1 sp_borrow_book === |
| | 270 | |
| | 271 | {{{#!sql |
| | 272 | CREATE OR REPLACE PROCEDURE public.sp_borrow_book( |
| | 273 | IN p_book_id integer, |
| | 274 | IN p_membership_id integer, |
| | 275 | IN p_due_date date, |
| | 276 | IN p_borrowed_by integer |
| | 277 | ) |
| | 278 | LANGUAGE plpgsql |
| | 279 | AS $procedure$ |
| | 280 | DECLARE |
| | 281 | v_copy_id INT; |
| | 282 | BEGIN |
| | 283 | |
| | 284 | ``` |
| | 285 | -- free copy |
| | 286 | SELECT copy_id |
| | 287 | INTO v_copy_id |
| | 288 | FROM bookcopy |
| | 289 | WHERE book_id = p_book_id |
| | 290 | AND available = 1 |
| | 291 | LIMIT 1; |
| | 292 | |
| | 293 | -- doesnt have-error |
| | 294 | IF v_copy_id IS NULL THEN |
| | 295 | RAISE EXCEPTION 'No available copy for this book!'; |
| | 296 | END IF; |
| | 297 | |
| | 298 | -- borrowing row |
| | 299 | INSERT INTO borrowing( |
| | 300 | book_id, |
| | 301 | borrow_date, |
| | 302 | return_date, |
| | 303 | due_date, |
| | 304 | return_status, |
| | 305 | copy_id, |
| | 306 | membership_id, |
| | 307 | borrowed_by, |
| | 308 | returned_by, |
| | 309 | reservation_id |
| | 310 | ) |
| | 311 | VALUES ( |
| | 312 | p_book_id, |
| | 313 | CURRENT_DATE, |
| | 314 | null, |
| | 315 | p_due_date, |
| | 316 | NULL, |
| | 317 | v_copy_id, |
| | 318 | p_membership_id, |
| | 319 | p_borrowed_by, |
| | 320 | NULL, |
| | 321 | NULL |
| | 322 | ); |
| | 323 | |
| | 324 | -- not available |
| | 325 | UPDATE bookcopy |
| | 326 | SET available = 0 |
| | 327 | WHERE copy_id = v_copy_id; |
| | 328 | ``` |
| | 329 | |
| | 330 | END; |
| | 331 | $procedure$; |
| | 332 | }}} |
| | 333 | |
| | 334 | '''Опис:''' |
| | 335 | Процедурата `sp_borrow_book` се користи за позајмување книга според `book_id`. |
| | 336 | Таа најпрво бара слободна копија од дадената книга во табелата `bookcopy`. |
| | 337 | Ако нема достапна копија, се јавува грешка, а ако има, се креира запис во `borrowing` и копијата се означува како недостапна. |
| | 338 | |
| | 339 | '''Употреба:''' |
| | 340 | Оваа процедура ја имплементира логиката за автоматско избирање достапна копија при позајмување книга. |
| | 341 | Корисникот или библиотекарот не мора рачно да избира конкретен `copy_id`, туку системот сам ја наоѓа првата достапна копија. |
| | 342 | Ова ја поедноставува работата на библиотекарот и го намалува ризикот од грешки. |
| | 343 | |
| | 344 | |
| | 345 | |
| | 346 | === 2.2 sp_reserve_book === |
| | 347 | |
| | 348 | {{{#!sql |
| | 349 | CREATE OR REPLACE PROCEDURE public.sp_reserve_book( |
| | 350 | IN p_member_id integer, |
| | 351 | IN p_book_id integer |
| | 352 | ) |
| | 353 | LANGUAGE plpgsql |
| | 354 | AS $procedure$ |
| | 355 | DECLARE |
| | 356 | v_copy_id INT; |
| | 357 | BEGIN |
| | 358 | |
| | 359 | ``` |
| | 360 | -- check for free copy |
| | 361 | SELECT copy_id |
| | 362 | INTO v_copy_id |
| | 363 | FROM bookcopy |
| | 364 | WHERE book_id = p_book_id |
| | 365 | AND available = 1 |
| | 366 | LIMIT 1; |
| | 367 | |
| | 368 | --if doesnt have copy, make reservation |
| | 369 | IF v_copy_id IS NULL THEN |
| | 370 | |
| | 371 | INSERT INTO reservation( |
| | 372 | member_id, |
| | 373 | book_id, |
| | 374 | requested_date, |
| | 375 | expected_date, |
| | 376 | status, |
| | 377 | copy_id |
| | 378 | ) |
| | 379 | VALUES ( |
| | 380 | p_member_id, |
| | 381 | p_book_id, |
| | 382 | CURRENT_DATE, |
| | 383 | CURRENT_DATE + 7, |
| | 384 | 'waiting', |
| | 385 | NULL |
| | 386 | ); |
| | 387 | |
| | 388 | ELSE |
| | 389 | |
| | 390 | RAISE NOTICE 'Book is available. No reservation needed.'; |
| | 391 | |
| | 392 | END IF; |
| | 393 | ``` |
| | 394 | |
| | 395 | END; |
| | 396 | $procedure$; |
| | 397 | }}} |
| | 398 | |
| | 399 | '''Опис:''' |
| | 400 | Процедурата `sp_reserve_book` се користи за резервација на книга. |
| | 401 | Најпрво проверува дали постои достапна копија од книгата. |
| | 402 | Ако нема достапна копија, се креира запис во табелата `reservation` со статус `waiting`. |
| | 403 | |
| | 404 | '''Употреба:''' |
| | 405 | Оваа процедура ја имплементира логиката за резервација на книги кои моментално не се достапни. |
| | 406 | Ако книгата има слободна копија, резервација не е потребна и системот прикажува известување. |
| | 407 | Ако книгата нема слободна копија, членот се става на листа на чекање. |
| | 408 | |
| | 409 | |
| | 410 | |
| | 411 | == 3. Trigger функции == |
| | 412 | |
| | 413 | === 3.1 fn_create_fine_on_return === |
| | 414 | |
| | 415 | {{{#!sql |
| | 416 | CREATE OR REPLACE FUNCTION public.fn_create_fine_on_return() |
| | 417 | RETURNS trigger |
| | 418 | LANGUAGE plpgsql |
| | 419 | AS $function$ |
| | 420 | DECLARE |
| | 421 | days_late INT; |
| | 422 | total_amount INT; |
| | 423 | fine_per_day INT := 10; |
| | 424 | BEGIN |
| | 425 | |
| | 426 | ``` |
| | 427 | IF NEW.return_date IS NOT NULL |
| | 428 | AND NEW.return_date > NEW.due_date THEN |
| | 429 | |
| | 430 | days_late := NEW.return_date - NEW.due_date; |
| | 431 | total_amount := days_late * fine_per_day; |
| | 432 | |
| | 433 | INSERT INTO fine( |
| | 434 | fine_id, |
| | 435 | membership_id, |
| | 436 | borrowing_id, |
| | 437 | amount, |
| | 438 | created_date, |
| | 439 | payment_id, |
| | 440 | fee_type_id |
| | 441 | ) |
| | 442 | VALUES ( |
| | 443 | (SELECT COALESCE(MAX(fine_id),0)+1 FROM fine), |
| | 444 | NEW.membership_id, |
| | 445 | NEW.borrowing_id, |
| | 446 | total_amount, |
| | 447 | CURRENT_DATE, |
| | 448 | NULL, |
| | 449 | 1 |
| | 450 | ); |
| | 451 | |
| | 452 | END IF; |
| | 453 | |
| | 454 | RETURN NEW; |
| | 455 | ``` |
| | 456 | |
| | 457 | END; |
| | 458 | $function$; |
| | 459 | }}} |
| | 460 | |
| | 461 | '''Опис:''' |
| | 462 | Trigger функцијата `fn_create_fine_on_return` служи за автоматско креирање казна при задоцнето враќање на книга. |
| | 463 | Таа проверува дали `return_date` е поголем од `due_date`. |
| | 464 | Ако книгата е вратена по рокот, се пресметува бројот на задоцнети денови и се внесува нов запис во табелата `fine`. |
| | 465 | |
| | 466 | '''Употреба:''' |
| | 467 | Оваа trigger функција ја имплементира логиката за автоматска наплата на казни. |
| | 468 | Се користи за да не мора библиотекарот рачно да пресметува казна при секое задоцнето враќање. |
| | 469 | Висината на казната се пресметува според бројот на задоцнети денови, при што во функцијата е поставена вредност од 10 по ден. |
| | 470 | |
| | 471 | |
| | 472 | |
| | 473 | === 3.2 check_membership_status === |
| | 474 | |
| | 475 | {{{#!sql |
| | 476 | CREATE OR REPLACE FUNCTION public.check_membership_status() |
| | 477 | RETURNS trigger |
| | 478 | LANGUAGE plpgsql |
| | 479 | AS $function$ |
| | 480 | BEGIN |
| | 481 | |
| | 482 | ``` |
| | 483 | --if membership is expiredd |
| | 484 | IF NEW.membership_end_date < CURRENT_DATE THEN |
| | 485 | NEW.membership_status := 'EXPIRED'; |
| | 486 | |
| | 487 | --if doesn't |
| | 488 | ELSE |
| | 489 | NEW.membership_status := 'ACTIVE'; |
| | 490 | END IF; |
| | 491 | |
| | 492 | RETURN NEW; |
| | 493 | ``` |
| | 494 | |
| | 495 | END; |
| | 496 | $function$; |
| | 497 | }}} |
| | 498 | |
| | 499 | '''Опис:''' |
| | 500 | Trigger функцијата `check_membership_status` се користи за автоматска проверка на статусот на членството. |
| | 501 | Ако датумот `membership_end_date` е помал од тековниот датум, статусот на членот се поставува на `EXPIRED`. |
| | 502 | Во спротивно, статусот се поставува на `ACTIVE`. |
| | 503 | |
| | 504 | '''Употреба:''' |
| | 505 | Оваа trigger функција ја имплементира логиката за автоматско ажурирање на статусот на членството. |
| | 506 | Се користи за системот сам да одреди дали членот има активно или истечено членство. |
| | 507 | На овој начин се избегнува рачно ажурирање на статусот на членовите. |
| | 508 | |
| | 509 | |
| | 510 | |
| | 511 | |
| | 512 | |