| 2 | | |
| 3 | | === '''fn_artist_average_rating(p_bookable_id INT)''' === |
| 4 | | Оваа функција ја пресметува просечната оцена за даден артист/бенд (bookable) така што ги зема сите рецензии (''Review'') за неговите букинзи и ја враќа средната вредност заокружена на две децимали. |
| 5 | | |
| 6 | | Со тоа се имплементира бизнис логиката за прикажување на рејтинг на артистите во апликацијата и за статистика/сортирање по оцена. |
| 7 | | |
| 8 | | === '''fn_artist_total_earnings(p_bookable_id INT)''' === |
| 9 | | Функцијата ја собира сумата на сите плаќања со статус ''PAID'' за сите букинзи поврзани со даден артист или бенд. |
| 10 | | |
| 11 | | Така се имплементира бизнис логиката за пресметка на вкупната заработка на артистот, која може да се прикаже во неговиот профил или во администраторскиот дел. |
| 12 | | |
| 13 | | === '''fn_check_artist_availability(p_bookable_id INT, p_date DATE)''' === |
| 14 | | Оваа функција проверува дали артистот има барем еден запис во ''AvailabilitySlot'' со статус ''AVAILABLE'' на дадениот датум и враќа TRUE/FALSE. |
| 15 | | |
| 16 | | Со тоа се имплементира логиката за проверка на достапност при креирање понуда или букинг – апликацијата прво проверува дали артистот е слободен на конкретниот датум. |
| 17 | | |
| 18 | | === '''fn_total_client_bookings(p_client_id INT)''' === |
| 19 | | Функцијата го брои бројот на букинзи за даден клиент преку поврзување на ''BookingRequest'', ''Offer'' и ''Booking'' и враќа цел број. |
| 20 | | |
| 21 | | Ова ја покрива бизнис логиката за статистика за клиентот (на пример колку настани има букирано), што може да се користи во неговиот профил или за аналитика. |
| | 5 | Функциите се користат за пресметување и враќање на вредности кои се потребни на повеќе места во апликацијата. Секоја функција прима параметри, извршува логика врз базата и враќа резултат. |
| | 6 | |
| | 7 | === 1. fn_artist_average_rating === |
| | 8 | Функцијата {{{fn_artist_average_rating}}} го пресметува просечниот рејтинг на артист или бенд врз основа на сите reviews оставени за нивните завршени bookings. |
| | 9 | |
| | 10 | Ги поврзува табелите {{{Review}}}, {{{Booking}}} и {{{Offer}}} за да го пронајде просекот на рејтинзите за конкретниот артист. Ако артистот сè уште нема reviews, функцијата враќа {{{0}}} наместо {{{NULL}}} преку употреба на {{{COALESCE}}}. |
| | 11 | {{{ |
| | 12 | CREATE OR REPLACE FUNCTION fn_artist_average_rating( |
| | 13 | p_bookable_id INT |
| | 14 | ) |
| | 15 | RETURNS NUMERIC(3,2) |
| | 16 | AS |
| | 17 | $$ |
| | 18 | DECLARE |
| | 19 | v_average_rating NUMERIC(3,2); |
| | 20 | BEGIN |
| | 21 | |
| | 22 | SELECT |
| | 23 | ROUND(AVG(r.rating), 2) |
| | 24 | INTO v_average_rating |
| | 25 | FROM Review r |
| | 26 | JOIN Booking bk |
| | 27 | ON r.booking_id = bk.booking_id |
| | 28 | JOIN Offer o |
| | 29 | ON bk.offer_id = o.offer_id |
| | 30 | WHERE o.bookable_id = p_bookable_id; |
| | 31 | |
| | 32 | RETURN COALESCE(v_average_rating, 0); |
| | 33 | |
| | 34 | END; |
| | 35 | $$ |
| | 36 | LANGUAGE plpgsql; |
| | 37 | }}} |
| | 38 | |
| | 39 | === 2. fn_artist_total_earnings === |
| | 40 | Функцијата {{{fn_artist_total_earnings}}} ја пресметува вкупната заработка на артист или бенд земајќи ги предвид само успешно завршените плаќања со статус {{{PAID}}}. |
| | 41 | |
| | 42 | Ги поврзува табелите {{{Payment}}}, {{{Booking}}} и {{{Offer}}} и го сумира износот на сите потврдени плаќања. Ако артистот сè уште нема реализирани плаќања, функцијата враќа {{{0}}} наместо {{{NULL}}}. |
| | 43 | {{{ |
| | 44 | CREATE OR REPLACE FUNCTION fn_artist_total_earnings( |
| | 45 | p_bookable_id INT |
| | 46 | ) |
| | 47 | RETURNS NUMERIC(10,2) |
| | 48 | AS |
| | 49 | $$ |
| | 50 | DECLARE |
| | 51 | v_total NUMERIC(10,2); |
| | 52 | BEGIN |
| | 53 | |
| | 54 | SELECT |
| | 55 | SUM(p.amount) |
| | 56 | INTO v_total |
| | 57 | FROM Payment p |
| | 58 | JOIN Booking bk |
| | 59 | ON p.booking_id = bk.booking_id |
| | 60 | JOIN Offer o |
| | 61 | ON bk.offer_id = o.offer_id |
| | 62 | WHERE o.bookable_id = p_bookable_id |
| | 63 | AND p.payment_status = 'PAID'; |
| | 64 | |
| | 65 | RETURN COALESCE(v_total, 0); |
| | 66 | |
| | 67 | END; |
| | 68 | $$ |
| | 69 | LANGUAGE plpgsql; |
| | 70 | }}} |
| | 71 | |
| | 72 | === 3. fn_check_artist_availability === |
| | 73 | Функцијата {{{fn_check_artist_availability}}} проверува дали артист или бенд има слободен термин на одреден датум и враќа {{{TRUE}}} или {{{FALSE}}}. |
| | 74 | |
| | 75 | Наместо {{{COUNT(*)}}} кој ги брои сите редици, функцијата користи {{{EXISTS}}} кој застанува веднаш при првиот пронајден слободен слот — со што е значително поефикасна. |
| | 76 | {{{ |
| | 77 | CREATE OR REPLACE FUNCTION fn_check_artist_availability( |
| | 78 | p_bookable_id INT, |
| | 79 | p_date DATE |
| | 80 | ) |
| | 81 | RETURNS BOOLEAN |
| | 82 | AS |
| | 83 | $$ |
| | 84 | DECLARE |
| | 85 | v_exists BOOLEAN; |
| | 86 | BEGIN |
| | 87 | |
| | 88 | SELECT EXISTS( |
| | 89 | SELECT 1 |
| | 90 | FROM AvailabilitySlot |
| | 91 | WHERE bookable_id = p_bookable_id |
| | 92 | AND DATE(start_datetime) = p_date |
| | 93 | AND status = 'AVAILABLE' |
| | 94 | ) |
| | 95 | INTO v_exists; |
| | 96 | |
| | 97 | RETURN v_exists; |
| | 98 | |
| | 99 | END; |
| | 100 | $$ |
| | 101 | LANGUAGE plpgsql; |
| | 102 | }}} |
| | 103 | |
| | 104 | === 4. fn_total_client_bookings === |
| | 105 | Функцијата {{{fn_total_client_bookings}}} го враќа вкупниот број на bookings направени од одреден клиент. |
| | 106 | |
| | 107 | Ги поврзува табелите {{{BookingRequest}}}, {{{Offer}}} и {{{Booking}}} за да го пресмета бројот на сите bookings поврзани со клиентот, без разлика на нивниот статус. |
| | 108 | {{{ |
| | 109 | CREATE OR REPLACE FUNCTION fn_total_client_bookings( |
| | 110 | p_client_id INT |
| | 111 | ) |
| | 112 | RETURNS INT |
| | 113 | AS |
| | 114 | $$ |
| | 115 | DECLARE |
| | 116 | v_total INT; |
| | 117 | BEGIN |
| | 118 | |
| | 119 | SELECT COUNT(*) |
| | 120 | INTO v_total |
| | 121 | FROM BookingRequest br |
| | 122 | JOIN Offer o |
| | 123 | ON br.request_id = o.request_id |
| | 124 | JOIN Booking bk |
| | 125 | ON bk.offer_id = o.offer_id |
| | 126 | WHERE br.client_id = p_client_id; |
| | 127 | |
| | 128 | RETURN v_total; |
| | 129 | |
| | 130 | END; |
| | 131 | $$ |
| | 132 | LANGUAGE plpgsql; |
| | 133 | }}} |
| 24 | | |
| 25 | | === '''sp_create_booking_request(p_client_id, p_duration_id, p_event_type, p_event_date, p_location_id)''' === |
| 26 | | Процедурата креира нов запис во ''BookingRequest'' со основните информации за настанот (клиент, траење, тип, датум, локација). |
| 27 | | |
| 28 | | На тој начин ја инкапсулира бизнис операцијата „клиентот испраќа барање за букинг“ и осигурува дека секогаш се пополнуваат потребните полиња на централизирано место. |
| 29 | | |
| 30 | | === '''sp_cancel_booking(p_booking_id INT)''' === |
| 31 | | Оваа процедура го ажурира полето ''booking_status'' на избраниот букинг во вредност ''CANCELLED''. |
| 32 | | |
| 33 | | На тој начин се имплементира логиката за откажување на букинг од страна на клиент или администратор, а преку тригерот за историја автоматски се логира и промената на статусот. |
| 34 | | |
| 35 | | === '''sp_create_payment(p_booking_id INT, p_amount NUMERIC)''' === |
| 36 | | Процедурата додава нов запис во ''Payment'' за одреден букинг, со зададена сума и статус ''PAID''. |
| 37 | | |
| 38 | | Така се имплементира делот од бизнис логиката за евиденција на успешно извршени плаќања, што потоа се користи и во функцијата за вкупна заработка на артистите. |
| | 136 | Процедурите се користат за извршување на акции врз базата — за разлика од функциите, тие не враќаат вредност туку вршат промени. Секоја процедура содржи валидација пред да изврши каква било промена, со цел да се спречат невалидни состојби во базата. |
| | 137 | |
| | 138 | === 1. sp_create_booking_request === |
| | 139 | Процедурата {{{sp_create_booking_request}}} креира нов барање за booking од страна на клиент. |
| | 140 | |
| | 141 | Пред да го изврши вметнувањето, процедурата врши три валидации: |
| | 142 | * Проверува дали клиентот постои во табелата {{{ClientProfile}}} |
| | 143 | * Проверува дали локацијата постои во табелата {{{Location}}} |
| | 144 | * Проверува дали датумот на настанот е во иднина |
| | 145 | |
| | 146 | Ако некоја од валидациите не помине, се фрла исклучок со јасна порака за грешка. |
| | 147 | {{{ |
| | 148 | CREATE OR REPLACE PROCEDURE sp_create_booking_request( |
| | 149 | p_client_id INT, |
| | 150 | p_duration_id INT, |
| | 151 | p_event_type VARCHAR, |
| | 152 | p_event_date DATE, |
| | 153 | p_location_id INT |
| | 154 | ) |
| | 155 | LANGUAGE plpgsql |
| | 156 | AS |
| | 157 | $$ |
| | 158 | BEGIN |
| | 159 | |
| | 160 | IF NOT EXISTS ( |
| | 161 | SELECT 1 FROM ClientProfile WHERE client_id = p_client_id |
| | 162 | ) THEN |
| | 163 | RAISE EXCEPTION 'Client with ID % does not exist!', p_client_id; |
| | 164 | END IF; |
| | 165 | |
| | 166 | IF NOT EXISTS ( |
| | 167 | SELECT 1 FROM Location WHERE location_id = p_location_id |
| | 168 | ) THEN |
| | 169 | RAISE EXCEPTION 'Location with ID % does not exist!', p_location_id; |
| | 170 | END IF; |
| | 171 | |
| | 172 | IF p_event_date <= CURRENT_DATE THEN |
| | 173 | RAISE EXCEPTION 'Event date must be in the future!'; |
| | 174 | END IF; |
| | 175 | |
| | 176 | INSERT INTO BookingRequest( |
| | 177 | client_id, |
| | 178 | duration_id, |
| | 179 | event_type, |
| | 180 | event_date, |
| | 181 | location_id |
| | 182 | ) |
| | 183 | VALUES ( |
| | 184 | p_client_id, |
| | 185 | p_duration_id, |
| | 186 | p_event_type, |
| | 187 | p_event_date, |
| | 188 | p_location_id |
| | 189 | ); |
| | 190 | |
| | 191 | END; |
| | 192 | $$; |
| | 193 | }}} |
| | 194 | |
| | 195 | === 2. sp_cancel_booking === |
| | 196 | Процедурата {{{sp_cancel_booking}}} го откажува постоечки booking со менување на неговиот статус во {{{CANCELLED}}}. |
| | 197 | |
| | 198 | Пред да го изврши откажувањето, процедурата врши три проверки: |
| | 199 | * Проверува дали booking со дадениот ID воопшто постои |
| | 200 | * Проверува дали booking не е веќе откажан |
| | 201 | * Проверува дали booking не е веќе завршен — завршените bookings не можат да се откажат |
| | 202 | |
| | 203 | Без овие проверки, повикот со непостоечки ID би поминал тивко без никаква промена или грешка. |
| | 204 | {{{ |
| | 205 | CREATE OR REPLACE PROCEDURE sp_cancel_booking( |
| | 206 | p_booking_id INT |
| | 207 | ) |
| | 208 | LANGUAGE plpgsql |
| | 209 | AS |
| | 210 | $$ |
| | 211 | DECLARE |
| | 212 | v_current_status VARCHAR(30); |
| | 213 | BEGIN |
| | 214 | |
| | 215 | SELECT booking_status |
| | 216 | INTO v_current_status |
| | 217 | FROM Booking |
| | 218 | WHERE booking_id = p_booking_id; |
| | 219 | |
| | 220 | IF NOT FOUND THEN |
| | 221 | RAISE EXCEPTION 'Booking with ID % does not exist!', p_booking_id; |
| | 222 | END IF; |
| | 223 | |
| | 224 | IF v_current_status = 'CANCELLED' THEN |
| | 225 | RAISE EXCEPTION 'Booking % is already cancelled!', p_booking_id; |
| | 226 | END IF; |
| | 227 | |
| | 228 | IF v_current_status = 'COMPLETED' THEN |
| | 229 | RAISE EXCEPTION 'Booking % is already completed and cannot be cancelled!', p_booking_id; |
| | 230 | END IF; |
| | 231 | |
| | 232 | UPDATE Booking |
| | 233 | SET booking_status = 'CANCELLED' |
| | 234 | WHERE booking_id = p_booking_id; |
| | 235 | |
| | 236 | END; |
| | 237 | $$; |
| | 238 | }}} |
| | 239 | |
| | 240 | === 3. sp_create_payment === |
| | 241 | Процедурата {{{sp_create_payment}}} креира ново плаќање поврзано со одреден booking. |
| | 242 | |
| | 243 | Клучна поправка во однос на оригиналната верзија е дека плаќањето се креира со статус {{{PENDING}}} наместо веднаш {{{PAID}}}. Ова одговара на реалниот тек — плаќањето прво се иницира, па дури потоа се потврдува. |
| | 244 | |
| | 245 | Процедурата врши три валидации: |
| | 246 | * Проверува дали booking постои |
| | 247 | * Проверува дали износот е позитивен број |
| | 248 | * Проверува дали веќе не постои плаќање за истиот booking |
| | 249 | {{{ |
| | 250 | CREATE OR REPLACE PROCEDURE sp_create_payment( |
| | 251 | p_booking_id INT, |
| | 252 | p_amount NUMERIC(10,2) |
| | 253 | ) |
| | 254 | LANGUAGE plpgsql |
| | 255 | AS |
| | 256 | $$ |
| | 257 | BEGIN |
| | 258 | |
| | 259 | IF NOT EXISTS ( |
| | 260 | SELECT 1 FROM Booking WHERE booking_id = p_booking_id |
| | 261 | ) THEN |
| | 262 | RAISE EXCEPTION 'Booking with ID % does not exist!', p_booking_id; |
| | 263 | END IF; |
| | 264 | |
| | 265 | IF p_amount <= 0 THEN |
| | 266 | RAISE EXCEPTION 'Payment amount must be greater than 0!'; |
| | 267 | END IF; |
| | 268 | |
| | 269 | IF EXISTS ( |
| | 270 | SELECT 1 FROM Payment WHERE booking_id = p_booking_id |
| | 271 | ) THEN |
| | 272 | RAISE EXCEPTION 'Payment for booking % already exists!', p_booking_id; |
| | 273 | END IF; |
| | 274 | |
| | 275 | INSERT INTO Payment( |
| | 276 | booking_id, |
| | 277 | amount, |
| | 278 | payment_status |
| | 279 | ) |
| | 280 | VALUES ( |
| | 281 | p_booking_id, |
| | 282 | p_amount, |
| | 283 | 'PENDING' |
| | 284 | ); |
| | 285 | |
| | 286 | END; |
| | 287 | $$; |
| | 288 | }}} |
| 41 | | |
| 42 | | === '''fn_update_slot_status() / trg_update_slot_status''' === |
| 43 | | Овој тригер се активира по вметнување (AFTER INSERT) на нов букинг во ''Booking'' и преку функцијата ''fn_update_slot_status'' ги ажурира сите ''AvailabilitySlot'' записи за соодветниот артист на датумот на настанот во статус ''BOOKED''. |
| 44 | | |
| 45 | | Со тоа се имплементира бизнис правилото дека кога ќе се потврди букинг за одреден датум, слободните термини за тој датум повеќе не се достапни во календарот на артистот. |
| 46 | | |
| 47 | | === '''fn_prevent_double_booking() / trg_prevent_double_booking''' === |
| 48 | | Тригерот се активира пред вметнување (BEFORE INSERT) на нов букинг и во функцијата ''fn_prevent_double_booking'' проверува дали веќе постои букинг со статус ''CONFIRMED'' за истиот артист (bookable_id) и истиот датум на настан (event_date). |
| 49 | | |
| 50 | | Ако постои, се крева грешка „Artist already booked on that date!“ и се спречува внесот, со што директно се имплементира бизнис правилото дека артист може да има најмногу еден потврден настап во еден ден. |
| 51 | | |
| 52 | | === '''fn_booking_status_history() / trg_booking_status_history''' === |
| 53 | | Овој тригер се активира по ажурирање (AFTER UPDATE) на ''Booking'' кога статусот се менува и преку функцијата ''fn_booking_status_history'' внесува ред во ''BookingStatusHistory'' со новиот статус и времето на промена. |
| 54 | | |
| 55 | | Така се имплементира бизнис логиката за водење историја (audit trail) на статусите на букинзите, што е корисно за следење на целиот животен циклус на букинг (''CREATED → CONFIRMED → COMPLETED/CANCELLED''). |
| | 291 | Тригерите се специјални функции кои се извршуваат автоматски при одредени настани во базата — без потреба апликацијата да ги повикува рачно. Во системот се имплементирани три тригери кои обезбедуваат конзистентност и интегритет на податоците. |
| | 292 | |
| | 293 | === 1. trg_prevent_double_booking === |
| | 294 | Тригерот {{{trg_prevent_double_booking}}} спречува двоен booking — ситуација каде ист артист или бенд би бил потврден за два различни настани на ист датум. |
| | 295 | |
| | 296 | Се извршува автоматски '''пред''' секој нов запис во табелата {{{Booking}}} ({{{BEFORE INSERT}}}). Доколку за истиот артист веќе постои {{{CONFIRMED}}} booking на истиот датум, тригерот фрла исклучок и го спречува вметнувањето. |
| | 297 | {{{ |
| | 298 | CREATE OR REPLACE FUNCTION fn_prevent_double_booking() |
| | 299 | RETURNS TRIGGER |
| | 300 | AS |
| | 301 | $$ |
| | 302 | DECLARE |
| | 303 | v_count INT; |
| | 304 | v_bookable INT; |
| | 305 | v_event_date DATE; |
| | 306 | BEGIN |
| | 307 | |
| | 308 | SELECT o.bookable_id, br.event_date |
| | 309 | INTO v_bookable, v_event_date |
| | 310 | FROM Offer o |
| | 311 | JOIN BookingRequest br ON br.request_id = o.request_id |
| | 312 | WHERE o.offer_id = NEW.offer_id; |
| | 313 | |
| | 314 | SELECT COUNT(*) |
| | 315 | INTO v_count |
| | 316 | FROM Booking b |
| | 317 | JOIN Offer o |
| | 318 | ON b.offer_id = o.offer_id |
| | 319 | JOIN BookingRequest br |
| | 320 | ON br.request_id = o.request_id |
| | 321 | WHERE o.bookable_id = v_bookable |
| | 322 | AND br.event_date = v_event_date |
| | 323 | AND b.booking_status = 'CONFIRMED'; |
| | 324 | |
| | 325 | IF v_count > 0 THEN |
| | 326 | RAISE EXCEPTION 'Artist/Band with ID % is already booked on %!', |
| | 327 | v_bookable, v_event_date; |
| | 328 | END IF; |
| | 329 | |
| | 330 | RETURN NEW; |
| | 331 | |
| | 332 | END; |
| | 333 | $$ |
| | 334 | LANGUAGE plpgsql; |
| | 335 | |
| | 336 | CREATE OR REPLACE TRIGGER trg_prevent_double_booking |
| | 337 | BEFORE INSERT ON Booking |
| | 338 | FOR EACH ROW |
| | 339 | EXECUTE FUNCTION fn_prevent_double_booking(); |
| | 340 | }}} |
| | 341 | |
| | 342 | === 2. trg_update_slot_status === |
| | 343 | Тригерот {{{trg_update_slot_status}}} автоматски го означува слободниот термин на артистот како {{{BOOKED}}} во моментот кога се креира нов booking. |
| | 344 | |
| | 345 | Се извршува автоматски '''по''' секој нов запис во табелата {{{Booking}}} ({{{AFTER INSERT}}}). Го пронаоѓа соодветниот {{{AvailabilitySlot}}} според артистот и датумот на настанот и го менува неговиот статус — со тоа се спречува двојна резервација на ист термин. |
| | 346 | {{{ |
| | 347 | CREATE OR REPLACE FUNCTION fn_update_slot_status() |
| | 348 | RETURNS TRIGGER |
| | 349 | AS |
| | 350 | $$ |
| | 351 | DECLARE |
| | 352 | v_bookable INT; |
| | 353 | v_event_date DATE; |
| | 354 | BEGIN |
| | 355 | |
| | 356 | SELECT o.bookable_id, br.event_date |
| | 357 | INTO v_bookable, v_event_date |
| | 358 | FROM Offer o |
| | 359 | JOIN BookingRequest br ON br.request_id = o.request_id |
| | 360 | WHERE o.offer_id = NEW.offer_id; |
| | 361 | |
| | 362 | UPDATE AvailabilitySlot |
| | 363 | SET status = 'BOOKED' |
| | 364 | WHERE bookable_id = v_bookable |
| | 365 | AND DATE(start_datetime) = v_event_date |
| | 366 | AND status = 'AVAILABLE'; |
| | 367 | |
| | 368 | RETURN NEW; |
| | 369 | |
| | 370 | END; |
| | 371 | $$ |
| | 372 | LANGUAGE plpgsql; |
| | 373 | |
| | 374 | CREATE OR REPLACE TRIGGER trg_update_slot_status |
| | 375 | AFTER INSERT ON Booking |
| | 376 | FOR EACH ROW |
| | 377 | EXECUTE FUNCTION fn_update_slot_status(); |
| | 378 | }}} |
| | 379 | |
| | 380 | === 3. trg_booking_status_history === |
| | 381 | Тригерот {{{trg_booking_status_history}}} автоматски го запишува секој пат кога статусот на booking се менува, формирајќи целосен историјат на промени. |
| | 382 | |
| | 383 | Се извршува автоматски '''по''' секој {{{UPDATE}}} на табелата {{{Booking}}}, но само кога вредноста на {{{booking_status}}} навистина се променила — ова се постигнува со условот {{{WHEN (OLD.booking_status IS DISTINCT FROM NEW.booking_status)}}}. Со тоа се избегнува непотребно запишување при update-и кои не го менуваат статусот. |
| | 384 | {{{ |
| | 385 | CREATE OR REPLACE FUNCTION fn_booking_status_history() |
| | 386 | RETURNS TRIGGER |
| | 387 | AS |
| | 388 | $$ |
| | 389 | BEGIN |
| | 390 | |
| | 391 | INSERT INTO BookingStatusHistory( |
| | 392 | booking_id, |
| | 393 | new_status, |
| | 394 | changed_at |
| | 395 | ) |
| | 396 | VALUES ( |
| | 397 | NEW.booking_id, |
| | 398 | NEW.booking_status, |
| | 399 | NOW() |
| | 400 | ); |
| | 401 | |
| | 402 | RETURN NEW; |
| | 403 | |
| | 404 | END; |
| | 405 | $$ |
| | 406 | LANGUAGE plpgsql; |
| | 407 | |
| | 408 | CREATE OR REPLACE TRIGGER trg_booking_status_history |
| | 409 | AFTER UPDATE ON Booking |
| | 410 | FOR EACH ROW |
| | 411 | WHEN (OLD.booking_status IS DISTINCT FROM NEW.booking_status) |
| | 412 | EXECUTE FUNCTION fn_booking_status_history(); |
| | 413 | }}} |