| | 143 | |
| | 144 | == Functions/Procedures/Triggers: == |
| | 145 | |
| | 146 | 1.is_zone_overloaded (Функција) |
| | 147 | CREATE OR REPLACE FUNCTION is_zone_overloaded(p_zone_id INT) |
| | 148 | RETURNS BOOLEAN AS $$ |
| | 149 | DECLARE |
| | 150 | available_drivers_count INT; |
| | 151 | BEGIN |
| | 152 | -- Броиме колку возачи во таа зона денес имаат помалку од 10 нарачки |
| | 153 | SELECT COUNT(DISTINCT driver_id) INTO available_drivers_count |
| | 154 | FROM "Order" |
| | 155 | WHERE delivery_zone_id = p_zone_id |
| | 156 | AND order_date = CURRENT_DATE |
| | 157 | GROUP BY driver_id |
| | 158 | HAVING COUNT(order_id) < 10; |
| | 159 | |
| | 160 | -- Ако има помалку од 3 слободни возачи, зоната е преоптоварена |
| | 161 | RETURN COALESCE(available_drivers_count, 0) < 3; |
| | 162 | END; |
| | 163 | Ја проверува оптовареноста на одредена зона во реално време. Бизнис логиката ја користи за да изброи колку активни возачи имаат помалку од 10 нарачки во денот, па доколку таа бројка е под 3, системот сигнализира дека зоната е преоптоварена и апликацијата може да го зголеми времето на чекање или цената на доставата. |
| | 164 | |
| | 165 | 2.trg_prevent_delivered_update & lock_delivered_orders (Тригер) |
| | 166 | CREATE OR REPLACE FUNCTION trg_prevent_delivered_update() |
| | 167 | RETURNS TRIGGER AS $$ |
| | 168 | BEGIN |
| | 169 | -- Ако старата вредност на статусот била 'delivered', прекини ја операцијата |
| | 170 | IF OLD.status = 'delivered' THEN |
| | 171 | RAISE EXCEPTION 'Не може да се менува нарачка која е веќе доставена!'; |
| | 172 | END IF; |
| | 173 | |
| | 174 | RETURN NEW; |
| | 175 | END; |
| | 176 | $$ LANGUAGE plpgsql; |
| | 177 | |
| | 178 | CREATE TRIGGER lock_delivered_orders |
| | 179 | BEFORE UPDATE ON "Order" |
| | 180 | FOR EACH ROW |
| | 181 | EXECUTE FUNCTION trg_prevent_delivered_update(); |
| | 182 | |
| | 183 | CREATE OR REPLACE PROCEDURE cancel_stale_orders(p_days_old INT) |
| | 184 | AS $$ |
| | 185 | BEGIN |
| | 186 | -- Ги наоѓаме сите pending нарачки постари од X дена |
| | 187 | UPDATE "Order" |
| | 188 | SET status = 'canceled' |
| | 189 | WHERE status = 'pending' |
| | 190 | AND order_date < (CURRENT_DATE - p_days_old); |
| | 191 | |
| | 192 | RAISE NOTICE 'Старите нарачки се успешно откажани.'; |
| | 193 | END; |
| | 194 | $$ LANGUAGE plpgsql; |
| | 195 | Обезбедува строг интегритет на податоците во базата со тоа што спречува какви било последователни промени на веќе доставена нарачка (status = 'delivered'). Ова е клучен безбедносен механизам што оневозможува манипулација со старите финансиски и архивски записи од страна на корисници или администратори. |
| | 196 | |
| | 197 | 3. cancel_stale_orders (Процедура) |
| | 198 | CREATE OR REPLACE PROCEDURE cancel_stale_orders(p_days_old INT) |
| | 199 | AS $$ |
| | 200 | BEGIN |
| | 201 | -- Ги наоѓаме сите pending нарачки постари од X дена |
| | 202 | UPDATE "Order" |
| | 203 | SET status = 'canceled' |
| | 204 | WHERE status = 'pending' |
| | 205 | AND order_date < (CURRENT_DATE - p_days_old); |
| | 206 | |
| | 207 | RAISE NOTICE 'Старите нарачки се успешно откажани.'; |
| | 208 | END; |
| | 209 | Автоматски системски чистач на „заглавени“ нарачки. Бизнис логиката ја извршува оваа процедура во позадина (на пример, на крајот од денот) за автоматски да ги откаже (canceled) сите нарачки кои со денови останале со статус pending, со цел да се исчисти оперативната меморија на базата. |
| | 210 | |
| | 211 | 4. trg_limit_driver_daily_orders & check_driver_limit (Тригер) |
| | 212 | CREATE OR REPLACE FUNCTION trg_limit_driver_daily_orders() |
| | 213 | RETURNS TRIGGER AS $$ |
| | 214 | DECLARE |
| | 215 | daily_count INT; |
| | 216 | BEGIN |
| | 217 | -- Броиме колку нарачки има возачот денес |
| | 218 | SELECT COUNT(*) INTO daily_count |
| | 219 | FROM "Order" |
| | 220 | WHERE driver_id = NEW.driver_id |
| | 221 | AND order_date = CURRENT_DATE; |
| | 222 | |
| | 223 | IF daily_count >= 20 THEN |
| | 224 | RAISE EXCEPTION 'Возачот го достигна дневниот лимит од 20 нарачки!'; |
| | 225 | END IF; |
| | 226 | |
| | 227 | RETURN NEW; |
| | 228 | END; |
| | 229 | $$ LANGUAGE plpgsql; |
| | 230 | |
| | 231 | |
| | 232 | |
| | 233 | CREATE TRIGGER check_driver_limit |
| | 234 | BEFORE INSERT ON "Order" |
| | 235 | FOR EACH ROW |
| | 236 | WHEN (NEW.driver_id IS NOT NULL) |
| | 237 | EXECUTE FUNCTION trg_limit_driver_daily_orders(); |
| | 238 | Го контролира дневниот капацитет и безбедноста на возачите на ниво на база. Пред да се додели нова нарачка на возач, тригерот проверува дали тој веќе ја достигнал границата од 20 достави во денот, и доколку е така, фрла грешка и спречува преоптоварување со работа. |
| | 239 | |
| | 240 | 5.reassign_driver_orders (Процедура) |
| | 241 | CREATE OR REPLACE PROCEDURE reassign_driver_orders(old_driver_id INT, new_driver_id INT) |
| | 242 | AS $$ |
| | 243 | BEGIN |
| | 244 | UPDATE "Order" |
| | 245 | SET driver_id = new_driver_id |
| | 246 | WHERE driver_id = old_driver_id |
| | 247 | AND status = 'pending'; |
| | 248 | RAISE NOTICE 'Нарачките се успешно префрлени на новиот возач.'; |
| | 249 | END; |
| | 250 | Служи за итна реакција при непредвидени ситуации на терен (дефект на возило, сообраќајна несреќа). Администраторот или диспечерот ја повикува оваа процедура за инстантно и безбедно да ги префрли сите активни (pending) нарачки од еден возач кај друг слободен колега. |
| | 251 | |
| | 252 | 6. trg_check_driver_performance & after_order_delivered_performance (Тригер) |
| | 253 | CREATE OR REPLACE FUNCTION trg_check_driver_performance() |
| | 254 | RETURNS TRIGGER AS $$ |
| | 255 | DECLARE |
| | 256 | cancel_count INT; |
| | 257 | BEGIN |
| | 258 | -- Броиме колку откажани нарачки има возачот во последните 30 дена |
| | 259 | SELECT COUNT(*) INTO cancel_count |
| | 260 | FROM "Order" |
| | 261 | WHERE driver_id = NEW.driver_id |
| | 262 | AND status = 'canceled' |
| | 263 | AND order_date > CURRENT_DATE - INTERVAL '30 days'; |
| | 264 | |
| | 265 | -- Ако има над 5 откажани, се логира предупредување (или се врши акција) |
| | 266 | IF cancel_count > 5 THEN |
| | 267 | RAISE NOTICE 'Возачот % има висок степен на откажани нарачки (%)!', NEW.driver_id, cancel_count; |
| | 268 | -- Тука може да додадеш UPDATE Driver SET rating = rating - 1 ... |
| | 269 | END IF; |
| | 270 | |
| | 271 | RETURN NEW; |
| | 272 | END; |
| | 273 | $$ LANGUAGE plpgsql; |
| | 274 | |
| | 275 | CREATE TRIGGER after_order_delivered_performance |
| | 276 | AFTER UPDATE OF status ON "Order" |
| | 277 | FOR EACH ROW |
| | 278 | WHEN (NEW.status = 'delivered') |
| | 279 | EXECUTE FUNCTION trg_check_driver_performance(); |
| | 280 | Ова е автоматизиран систем за контрола на квалитетот кој се активира по секоја успешна достава. Ја пресметува доверливоста на возачот со тоа што брои колку пати откажал нарачка во последните 30 дена, и доколку тој број е критичен (над 5), веднаш испраќа системско предупредување до менаџментот. |
| | 281 | |
| | 282 | 7. suspend_inactive_drivers (Процедура) |
| | 283 | CREATE OR REPLACE PROCEDURE suspend_inactive_drivers() |
| | 284 | AS $$ |
| | 285 | BEGIN |
| | 286 | UPDATE Driver |
| | 287 | SET status = 'suspended' |
| | 288 | WHERE driver_id NOT IN ( |
| | 289 | SELECT DISTINCT driver_id |
| | 290 | FROM "Order" |
| | 291 | WHERE status = 'delivered' |
| | 292 | AND order_date > CURRENT_DATE - INTERVAL '90 days' |
| | 293 | ); |
| | 294 | |
| | 295 | RAISE NOTICE 'Неактивните возачи се успешно суспендирани.'; |
| | 296 | END; |
| | 297 | Служи за филтрирање и одржување на листата на вработени. Процедурата ја пребарува базата и автоматски ги суспендира (suspended) сите возачи кои биле комплетно неактивни (не доставиле ниту една нарачка) во изминатите 90 дена, со што се спречува „зашумување“ на податоците. |
| | 298 | |
| | 299 | 8. trg_check_minimum_order_amount & check_min_amount (Тригер) |
| | 300 | CREATE OR REPLACE FUNCTION trg_check_minimum_order_amount() |
| | 301 | RETURNS TRIGGER AS $$ |
| | 302 | BEGIN |
| | 303 | -- Проверуваме дали вкупната сума е помала од 300 (на пример) |
| | 304 | IF NEW.total_price < 300 THEN |
| | 305 | RAISE EXCEPTION 'Нарачката мора да биде најмалку 300 денари за да биде прифатена.'; |
| | 306 | END IF; |
| | 307 | |
| | 308 | RETURN NEW; |
| | 309 | END; |
| | 310 | $$ LANGUAGE plpgsql; |
| | 311 | |
| | 312 | CREATE TRIGGER check_min_amount |
| | 313 | BEFORE INSERT ON "Order" |
| | 314 | FOR EACH ROW |
| | 315 | EXECUTE FUNCTION trg_check_minimum_order_amount(); |
| | 316 | Ја спроведува бизнис политиката за рентабилност директно во базата пред да се запише нарачката. Ако корисникот се обиде да направи нарачка чија вкупна вредност е помала од 300 денари, тригерот ја прекинува трансакцијата и враќа порака дека сумата е под дозволениот минимум. |
| | 317 | |
| | 318 | 9. update_store_prices_percent (Процедура) |
| | 319 | CREATE OR REPLACE PROCEDURE update_store_prices_percent(p_store_id INT, p_percentage DECIMAL) |
| | 320 | AS $$ |
| | 321 | BEGIN |
| | 322 | -- Ги ажурираме сите продукти кои припаѓаат на таа продавница |
| | 323 | UPDATE Product |
| | 324 | SET price = ROUND(price * (1 + p_percentage / 100)) |
| | 325 | WHERE store_id = p_store_id; |
| | 326 | |
| | 327 | RAISE NOTICE 'Цените за продавницата % се зголемени за % проценти.', p_store_id, p_percentage; |
| | 328 | |
| | 329 | COMMIT; |
| | 330 | END; |
| | 331 | Овозможува масовно менаџирање со цените во услови на инфлација или промотивни периоди. Со едноставен повик, процедурата ги ажурира и ги заокружува цените на сите продукти кои припаѓаат на една специфична продавница за одреден процент, заштедувајќи време и ресурси. |
| | 332 | |
| | 333 | 10. register_new_customer (Процедура) |
| | 334 | CREATE OR REPLACE PROCEDURE register_new_customer( |
| | 335 | p_username VARCHAR(255), |
| | 336 | p_email VARCHAR(255), |
| | 337 | p_password_plain VARCHAR(255), -- Ова доаѓа од фронтендот (React) |
| | 338 | p_phone VARCHAR(255) |
| | 339 | ) |
| | 340 | AS $$ |
| | 341 | DECLARE |
| | 342 | v_email_exists INT; |
| | 343 | v_password_hash VARCHAR(255); |
| | 344 | BEGIN |
| | 345 | -- 1. Проверка дали е-маилот е веќе зафатен |
| | 346 | SELECT COUNT(*) INTO v_email_exists |
| | 347 | FROM "User" -- Името на табелата прилагоди го ако ти е Customer или AppUser |
| | 348 | WHERE email = p_email; |
| | 349 | |
| | 350 | IF v_email_exists > 0 THEN |
| | 351 | RAISE EXCEPTION 'Корисник со е-маил адресата % веќе постои во системот!', p_email; |
| | 352 | END IF; |
| | 353 | |
| | 354 | -- 2. Симулација на хаширање на лозинката за безбедност |
| | 355 | -- Во реален Postgres би користеле crypt(), тука правиме едноставен MD5 за факултет |
| | 356 | v_password_hash := md5(p_password_plain); |
| | 357 | |
| | 358 | -- 3. Вметнување на новиот корисник |
| | 359 | INSERT INTO "User" (username, email, password, phone, created_at) |
| | 360 | VALUES (p_username, p_email, v_password_hash, p_phone, CURRENT_TIMESTAMP); |
| | 361 | |
| | 362 | RAISE NOTICE 'Корисникот % е успешно регистриран со хаширана лозинка.', p_username; |
| | 363 | |
| | 364 | COMMIT; |
| | 365 | END; |
| | 366 | Ја презема одговорноста за безбедно и правилно креирање на кориснички профил. Бизнис логиката ја користи за да провери дали е-маилот е веќе зафатен, да ја трансформира чистата лозинка во безбеден md5 хаш запис и да го внесе корисникот во системот како една сигурна целина. |
| | 367 | |
| | 368 | 11. create_new_order (Процедура) |
| | 369 | CREATE OR REPLACE PROCEDURE create_new_order( |
| | 370 | p_user_id INT, |
| | 371 | p_store_id INT, |
| | 372 | p_address_id INT, |
| | 373 | p_zone_id INT, |
| | 374 | p_delivery_fee INT |
| | 375 | ) |
| | 376 | AS $$ |
| | 377 | DECLARE |
| | 378 | v_order_id INT; |
| | 379 | BEGIN |
| | 380 | -- 1. Валидација: Проверка дали адресата на достава е во точната зона |
| | 381 | -- (Претпоставуваме дека во StoreInstance или Address чуваш zone_id) |
| | 382 | IF p_zone_id IS NULL THEN |
| | 383 | RAISE EXCEPTION 'Невалидна зона за достава!'; |
| | 384 | END IF; |
| | 385 | |
| | 386 | -- 2. Вметнување на главниот запис за нарачката |
| | 387 | -- Почетниот статус секогаш е 'pending', а total_price почнува од 0 |
| | 388 | -- (подоцна се ажурира кога ќе се додадат продуктите во OrderItem) |
| | 389 | INSERT INTO "Order" ( |
| | 390 | user_id, |
| | 391 | store_id, |
| | 392 | address_id, |
| | 393 | zone_id, |
| | 394 | delivery_fee, |
| | 395 | total_price, |
| | 396 | status, |
| | 397 | order_date |
| | 398 | ) |
| | 399 | VALUES ( |
| | 400 | p_user_id, |
| | 401 | p_store_id, |
| | 402 | p_address_id, |
| | 403 | p_zone_id, |
| | 404 | p_delivery_fee, |
| | 405 | 0, -- почетна цена |
| | 406 | 'pending', |
| | 407 | CURRENT_DATE |
| | 408 | ) |
| | 409 | RETURNING order_id INTO v_order_id; |
| | 410 | |
| | 411 | -- 3. Логирање на успешно креирана празна нарачка во конзола |
| | 412 | RAISE NOTICE 'Успешно креирана нарачка со ID: %. Сега апликацијата може да додава продукти во OrderItem.', v_order_id; |
| | 413 | |
| | 414 | COMMIT; |
| | 415 | END; |
| | 416 | Го отвора процесот на купување (Checkout) во системот. Процедурата ја валидира зоната на достава, креира нов запис со почетен статус pending, ја зачувува цената за достава и на крајот го враќа новото order_id назад до апликацијата за таа да може да почне да ги полни ставките од кошничката. |
| | 417 | |
| | 418 | 12. cancel_order (Процедура) |
| | 419 | CREATE OR REPLACE PROCEDURE cancel_order(p_order_id INT) |
| | 420 | AS $$ |
| | 421 | DECLARE |
| | 422 | current_order_status VARCHAR(50); |
| | 423 | BEGIN |
| | 424 | -- 1. Го земаме моменталниот статус на нарачката |
| | 425 | SELECT status INTO current_order_status |
| | 426 | FROM "Order" |
| | 427 | WHERE order_id = p_order_id; |
| | 428 | |
| | 429 | -- 2. Проверка дали нарачката воопшто постои |
| | 430 | IF current_order_status IS NULL THEN |
| | 431 | RAISE EXCEPTION 'Нарачката со ID % не постои во системот!', p_order_id; |
| | 432 | END IF; |
| | 433 | |
| | 434 | -- 3. Проверка дали статусот е 'pending' |
| | 435 | -- Ако е веќе 'delivered' или 'canceled', не дозволуваме промена |
| | 436 | IF current_order_status != 'pending' THEN |
| | 437 | RAISE EXCEPTION 'Не може да се откаже нарачката бидејќи нејзиниот моментален статус е "%".', current_order_status; |
| | 438 | END IF; |
| | 439 | |
| | 440 | -- 4. Ја правиме промената во 'canceled' |
| | 441 | UPDATE "Order" |
| | 442 | SET status = 'canceled' |
| | 443 | WHERE order_id = p_order_id; |
| | 444 | |
| | 445 | RAISE NOTICE 'Нарачката со ID % е успешно откажана.', p_order_id; |
| | 446 | |
| | 447 | COMMIT; |
| | 448 | END; |
| | 449 | Ја содржи целата бизнис логика за откажување на нарачка од страна на купувачот. Овозможува трансакцијата безбедно да се стопира и да се префрли во статус canceled, но само под услов нарачката сè уште да е во почетна фаза (pending) и да не е веќе процесирана или доставена. |