= Функции, Процедури и Тригери =
[[html(faza4.sql)]]
= Функции, Процедури и Тригери =
Во оваа фаза беа имплементирани функции, процедури и тригери кои ја поддржуваат основната бизнис логика на системот за booking на артисти и бендови. Секоја компонента е одговорна за одредена операција — од пресметување статистики, преку управување со bookings, до автоматска заштита од грешки.
== Функции ==
Функциите се користат за пресметување и враќање на вредности кои се потребни на повеќе места во апликацијата. Секоја функција прима параметри, извршува логика врз базата и враќа резултат.
=== 1. fn_artist_average_rating ===
Функцијата {{{fn_artist_average_rating}}} го пресметува просечниот рејтинг на артист или бенд врз основа на сите reviews оставени за нивните завршени bookings.
Ги поврзува табелите {{{Review}}}, {{{Booking}}} и {{{Offer}}} за да го пронајде просекот на рејтинзите за конкретниот артист. Ако артистот сè уште нема reviews, функцијата враќа {{{0}}} наместо {{{NULL}}} преку употреба на {{{COALESCE}}}.
{{{
CREATE OR REPLACE FUNCTION fn_artist_average_rating(
p_bookable_id INT
)
RETURNS NUMERIC(3,2)
AS
$$
DECLARE
v_average_rating NUMERIC(3,2);
BEGIN
SELECT
ROUND(AVG(r.rating), 2)
INTO v_average_rating
FROM Review r
JOIN Booking bk
ON r.booking_id = bk.booking_id
JOIN Offer o
ON bk.offer_id = o.offer_id
WHERE o.bookable_id = p_bookable_id;
RETURN COALESCE(v_average_rating, 0);
END;
$$
LANGUAGE plpgsql;
}}}
=== 2. fn_artist_total_earnings ===
Функцијата {{{fn_artist_total_earnings}}} ја пресметува вкупната заработка на артист или бенд земајќи ги предвид само успешно завршените плаќања со статус {{{PAID}}}.
Ги поврзува табелите {{{Payment}}}, {{{Booking}}} и {{{Offer}}} и го сумира износот на сите потврдени плаќања. Ако артистот сè уште нема реализирани плаќања, функцијата враќа {{{0}}} наместо {{{NULL}}}.
{{{
CREATE OR REPLACE FUNCTION fn_artist_total_earnings(
p_bookable_id INT
)
RETURNS NUMERIC(10,2)
AS
$$
DECLARE
v_total NUMERIC(10,2);
BEGIN
SELECT
SUM(p.amount)
INTO v_total
FROM Payment p
JOIN Booking bk
ON p.booking_id = bk.booking_id
JOIN Offer o
ON bk.offer_id = o.offer_id
WHERE o.bookable_id = p_bookable_id
AND p.payment_status = 'PAID';
RETURN COALESCE(v_total, 0);
END;
$$
LANGUAGE plpgsql;
}}}
=== 3. fn_check_artist_availability ===
Функцијата {{{fn_check_artist_availability}}} проверува дали артист или бенд има слободен термин на одреден датум и враќа {{{TRUE}}} или {{{FALSE}}}.
Пребарува во табелата {{{AvailabilitySlot}}} дали постои слот со статус {{{AVAILABLE}}} за дадениот артист на бараниот датум. Резултатот од {{{COUNT(*)}}} се споредува со {{{0}}} и се враќа соодветна булова вредност.
{{{
CREATE OR REPLACE FUNCTION fn_check_artist_availability(
p_bookable_id INT,
p_date DATE
)
RETURNS BOOLEAN
AS
$$
DECLARE
v_exists INT;
BEGIN
SELECT COUNT(*)
INTO v_exists
FROM AvailabilitySlot
WHERE bookable_id = p_bookable_id
AND DATE(start_datetime) = p_date
AND status = 'AVAILABLE';
RETURN v_exists > 0;
END;
$$
LANGUAGE plpgsql;
}}}
=== 4. fn_total_client_bookings ===
Функцијата {{{fn_total_client_bookings}}} го враќа вкупниот број на bookings направени од одреден клиент.
Ги поврзува табелите {{{BookingRequest}}}, {{{Offer}}} и {{{Booking}}} и го пресметува бројот на сите bookings поврзани со клиентот, без разлика на нивниот тековен статус.
{{{
CREATE OR REPLACE FUNCTION fn_total_client_bookings(
p_client_id INT
)
RETURNS INT
AS
$$
DECLARE
v_total INT;
BEGIN
SELECT COUNT(*)
INTO v_total
FROM BookingRequest br
JOIN Offer o
ON br.request_id = o.request_id
JOIN Booking bk
ON bk.offer_id = o.offer_id
WHERE br.client_id = p_client_id;
RETURN v_total;
END;
$$
LANGUAGE plpgsql;
}}}
== Процедури ==
Процедурите се користат за извршување на акции врз базата — за разлика од функциите, тие не враќаат вредност туку вршат промени. Секоја процедура е одговорна за еден конкретен процес во системот.
=== 1. sp_create_booking_request ===
Процедурата {{{sp_create_booking_request}}} креира нов барање за booking од страна на клиент.
Прима ги сите потребни параметри — ID на клиент, траење на настанот, тип на настан, датум и локација — и ги вметнува во табелата {{{BookingRequest}}}. Преку {{{FOREIGN KEY}}} ограничувањата во базата се обезбедува дека само валидни вредности можат да бидат вметнати.
{{{
CREATE OR REPLACE PROCEDURE sp_create_booking_request(
p_client_id INT,
p_duration_id INT,
p_event_type VARCHAR,
p_event_date DATE,
p_location_id INT
)
LANGUAGE plpgsql
AS
$$
BEGIN
INSERT INTO BookingRequest(
client_id,
duration_id,
event_type,
event_date,
location_id
)
VALUES (
p_client_id,
p_duration_id,
p_event_type,
p_event_date,
p_location_id
);
END;
$$;
}}}
=== 2. sp_cancel_booking ===
Процедурата {{{sp_cancel_booking}}} го откажува постоечки booking со менување на неговиот статус во {{{CANCELLED}}}.
Прима го ID на booking-от и врши {{{UPDATE}}} врз табелата {{{Booking}}}. Со промената на статусот системот го евидентира откажувањето, а тригерот {{{trg_booking_status_history}}} автоматски ја запишува промената во историјатот.
{{{
CREATE OR REPLACE PROCEDURE sp_cancel_booking(
p_booking_id INT
)
LANGUAGE plpgsql
AS
$$
BEGIN
UPDATE Booking
SET booking_status = 'CANCELLED'
WHERE booking_id = p_booking_id;
END;
$$;
}}}
=== 3. sp_create_payment ===
Процедурата {{{sp_create_payment}}} креира ново плаќање поврзано со одреден booking.
Прима го ID на booking-от и износот на плаќањето и ги вметнува во табелата {{{Payment}}}. Плаќањето се креира со статус {{{PAID}}}, со што се евидентира успешно извршена трансакција.
{{{
CREATE OR REPLACE PROCEDURE sp_create_payment(
p_booking_id INT,
p_amount NUMERIC(10,2)
)
LANGUAGE plpgsql
AS
$$
BEGIN
INSERT INTO Payment(
booking_id,
amount,
payment_status
)
VALUES (
p_booking_id,
p_amount,
'PAID'
);
END;
$$;
}}}
== Тригери ==
Тригерите се специјални функции кои се извршуваат автоматски при одредени настани во базата — без потреба апликацијата да ги повикува рачно. Во системот се имплементирани три тригери кои обезбедуваат конзистентност и интегритет на податоците.
=== 1. trg_update_slot_status ===
Тригерот {{{trg_update_slot_status}}} автоматски го означува слободниот термин на артистот како {{{BOOKED}}} во моментот кога се креира нов booking.
Се извршува автоматски '''по''' секој нов запис во табелата {{{Booking}}} ({{{AFTER INSERT}}}). Го пронаоѓа соодветниот {{{AvailabilitySlot}}} според артистот и датумот на настанот преку subquery-и и го менува неговиот статус — со тоа се спречува двојна резервација на ист термин.
{{{
CREATE OR REPLACE FUNCTION fn_update_slot_status()
RETURNS TRIGGER
AS
$$
BEGIN
UPDATE AvailabilitySlot s
SET status = 'BOOKED'
WHERE s.bookable_id = (
SELECT o.bookable_id
FROM Offer o
WHERE o.offer_id = NEW.offer_id
)
AND DATE(s.start_datetime) = (
SELECT br.event_date
FROM BookingRequest br
JOIN Offer o2 ON o2.request_id = br.request_id
WHERE o2.offer_id = NEW.offer_id
);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER trg_update_slot_status
AFTER INSERT ON Booking
FOR EACH ROW
EXECUTE FUNCTION fn_update_slot_status();
}}}
=== 2. trg_prevent_double_booking ===
Тригерот {{{trg_prevent_double_booking}}} спречува двоен booking — ситуација каде ист артист или бенд би бил потврден за два различни настани на ист датум.
Се извршува автоматски '''пред''' секој нов запис во табелата {{{Booking}}} ({{{BEFORE INSERT}}}). Преку subquery-и го пронаоѓа артистот и датумот на новиот booking и проверува дали веќе постои {{{CONFIRMED}}} booking за истата комбинација. Доколку постои, тригерот фрла исклучок и го спречува вметнувањето.
{{{
CREATE OR REPLACE FUNCTION fn_prevent_double_booking()
RETURNS TRIGGER
AS
$$
DECLARE
v_count INT;
BEGIN
SELECT COUNT(*)
INTO v_count
FROM Booking b
JOIN Offer o
ON b.offer_id = o.offer_id
JOIN BookingRequest br
ON br.request_id = o.request_id
WHERE o.bookable_id = (
SELECT bookable_id
FROM Offer
WHERE offer_id = NEW.offer_id
)
AND br.event_date = (
SELECT br2.event_date
FROM BookingRequest br2
JOIN Offer o2 ON o2.request_id = br2.request_id
WHERE o2.offer_id = NEW.offer_id
)
AND b.booking_status = 'CONFIRMED';
IF v_count > 0 THEN
RAISE EXCEPTION 'Artist already booked on that date!';
END IF;
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER trg_prevent_double_booking
BEFORE INSERT ON Booking
FOR EACH ROW
EXECUTE FUNCTION fn_prevent_double_booking();
}}}
=== 3. trg_booking_status_history ===
Тригерот {{{trg_booking_status_history}}} автоматски го запишува секој пат кога статусот на booking се менува, формирајќи целосен историјат на промени.
Се извршува автоматски '''по''' секој {{{UPDATE}}} на табелата {{{Booking}}}, но само кога вредноста на {{{booking_status}}} навистина се променила — ова се постигнува со условот {{{WHEN (OLD.booking_status IS DISTINCT FROM NEW.booking_status)}}}. Со тоа се избегнува непотребно запишување при update-и кои не го менуваат статусот.
{{{
CREATE OR REPLACE FUNCTION fn_booking_status_history()
RETURNS TRIGGER
AS
$$
BEGIN
INSERT INTO BookingStatusHistory(
booking_id,
new_status,
changed_at
)
VALUES (
NEW.booking_id,
NEW.booking_status,
NOW()
);
RETURN NEW;
END;
$$
LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER trg_booking_status_history
AFTER UPDATE ON Booking
FOR EACH ROW
WHEN (OLD.booking_status IS DISTINCT FROM NEW.booking_status)
EXECUTE FUNCTION fn_booking_status_history();
}}}