= Релационен модел == ЕР дијаграм [[Image(RelationalModel-drive-net.svg,1400px)]] == Архитектура и главни концепти === Користачи и нивни улоги Основна е поделбата меѓу `users` (сите во системот), `drivers` (возачи) и `passengers` (патници). Табелата `users` е како "мајка" — содржи основни информации (име, телефон, емајл). Потоа `drivers` и `passengers` додаваат своите специфични полиња. Една иста личност може да биде и возач и патник — нема никаква забрана за тоа. === Возила и производители Возилата имаат три нивоа организирање. Прво `manufacturers` (Volkswagen, Mercedes, итн.), потоа `vehicle_models` (Golf, C-Class), потоа самите `vehicles` (твоја лична кола). Вакво расчленување спречува да пишувале "Volkswagen" стоди пати во базата. Возачот има много возила, возило мора да припаѓа на возач. За тоа го имаме `vehicle_ownership` — табела која чува каде возачот имал кола. Кога возачот купи ново возило, едноставно ј додаваме нов ред во `vehicle_ownership`. Старото возило останува во историјата, не го бришеме. Вака видиме точно кој возач кога имал кола. === Рути и возења `Routes` е шаблон — замисли рута "Скопје → Куманово → Пријепа" дефинирана веќе во системот. `Rides` е конкретно возење: „Во понеделник во 08:00 возачот Марко ја возеше оваа рута со Volkswagen Golf". Застанувањата на рутата се во `route_stops` со редоследни броеви (1, 2, 3...). Рутата може да има 2 застанувања или 10 застанувања. Исто така, на иста рута можеш да направиш и круг (почеток и крај иста локација). === Сегменти — како се делат возењата Возењето од Скопје до Пријепа преку Куманово не е "целина" во пресметките. Поделено е на сегменти: - Скопје → Куманово - Куманово → Пријепа Патниците не мора да патуваат целата рута. Еден патник можеш да влезе во Куманово и да излезе во Пријепа. За тоа `route_segments` дели рутата на делови меѓу два стопа, а `passenger_segments` чува колку патници беа на секој сегмент. Вака точно можеме да изразунаме колку требало да плати секој патник според каде вле и каде слезе. === Патарини Не можеме едноставно да кажеме "возење чини X денари". Некои возења минуваат наплатни рампи. За тоа имаме: - `toll_points` — каде се наплатните рампи и колку чинат по тип возило - `ride_tolls` — евидентира дека возењето Х минало низ рампата Y - `toll_passenger_split` — раздела на патарината меѓу патниците кои беа присутни === Готовина, не картички Апликацијата е готовина-само. Нема онлајн плаќања, нема дигитални портфели. Возачот собира готовина од патниците. Табелите `fare_splits` и `booking_final_fare` служат само да покажат возачу колку собира од кого — ништо повеќе. === Оценувања Патниците оценуваат возачи, возачи оценуваат патници. Табелата `ratings` чува сите оценувања. Има CHECK constraint кој вели: "Човек не може да го оцени самиот себеси". За просечната оцена не пишуваме `avg_rating` поле директно. Наместо тоа, правиме view-и `driver_ratings` и `passenger_ratings` кои пресметуваат просек на лету. Вака ако додадеш нова оценка, просекот се ажурира веднаш без дополнителна логика. === История и следливост Кога возење промени статус (scheduled → in_progress → completed), промената оди во `ride_status_history` со временска марка. Исто и резервациите во `booking_status_history`. Вака администраторот има целосна историја — кога точно се случи што. === Бришење на податоци Претпоставка: администраторот избрише користачки профил. Но возачот има 50 завршени возења! Ние не ги бришеме поврзаните возења — користиме `ON DELETE RESTRICT`. За статистика, користиме резервиран корисник со `id = 0` наречен "избришан корисник". Вака можеме да видиме "некој возач" направил возење, дури и кога профилот е избришан. == Прегледи (Views) Сите овие табели се напалени со JOIN-ови. Наместо апликацијата да пишува `SELECT ... FROM rides JOIN drivers JOIN vehicles ...` низ целиот код, ми направивме 9 views. Секој view е спремена SQL "забелешка" која делува како табела. === v_available_rides — Возења отворени за резервирање Ова е она што патникот вижи кога пребарува возење. Показува: - Кога поаѓа возењето - Каде почнува, каде завршува - Име на возач - Марка и модел на возило - Проценета цена - Просечна оцена на возачот VIEW вика JOIN меѓу `rides`, `drivers`, `vehicles`, `vehicle_models`, `manufacturers`, `routes`, `route_stops`, `ratings` и пресметува просечна оцена на лету. Филтрира само возења со статус `scheduled`. Апликацијата го користи вака: `SELECT * FROM v_available_rides WHERE origin_city = 'Скопје' AND departure_time >= NOW()`. Едноставно! === v_driver_profile — Профилот на возачот Возачот кога си ажурира профилот, вижи ова. Исто и патникот пред да резервира место ако сака да види со кого ќе возеше. Содржи: - Лични податоци (име, телефон) - Информации за возачка дозвола (број, датум на издавање) - Примарното возило на возачот (ако има повеќе, земаме оно со најмал ID) - Статистика: вкупно возења, завршени, откажани - Просечна оцена VIEW прави JOIN меѓу `drivers`, `users`, `driver_licenses`, `vehicles`, `vehicle_ownership`, `rides` и `ratings`. Пресметува број на возења по статус и просек на оценувања. === v_passenger_trip_history — Моја патна историја Патникот кога кликне на "Мои патувања" вижи: - Кога беше возењето - Каде почнало, каде завршило - Име на возач - Потврда дека је качен (pickup_confirmed_at) - Потврда дека је слезен (dropoff_confirmed_at) - Колку платил - Неговата оценка за возачот (ако ја дал) Секоја резервација е редок во view-ot. Апликацијата може лесно да филтрира по статус (`completed`, `canceled`) или да го пагинира. === v_ride_manifest — Список на патници за возење Возачот одлучи да поаѓа во 08:00. Пред да ја запали машината, отворува ја оваа листа за да вижи: - Кои патници се качуваат - Имиња и телефонски броеви - Каде секој вле (punkt_A_stop) - Каде секој слезе (punkt_B_stop) - Колку собира од секој Прикажува само резервации со статус `confirmed`, `picked_up` или `completed`. Откажаните резервации не се прикажуваат — нема смисла да ги вижи. === v_booking_details — Целосен детај на резервација Патникот кликна на една резервација. Вижи "сё на едно место": - Кој е возачот (име, телефон, оцена) - Какво возило (марка, модел, регистарски табички) - Точна локација за качување (Централна станица, Скопје) - Точна локација за слегување (Куманово центар) - Точен час на поаѓање - Финална цена - Статус на резервација Содржи толку многу JOIN-ови што апликацијата СЕКОГАШ го вика со филтер: `WHERE booking_id = 123`. Никогаш не го вика без филтер — би ја спалил базата од JOIN-ови! === v_unread_notifications — Мои нови нотификации Патникот или возачот отворува го "звончето". Вижи само непрочитаните нотификации: - Каде е написано нотификацијата (текст) - Од кого (некој возач, администратор, итн.) - Кога (точна време или "пред 5 минути") VIEW-ot пресметува "старост во минути" за да покаже "пред 5 мин" без дополнителна логика во апликацијата. Апликацијата го вика со `WHERE user_id = 42 AND read_at IS NULL`. === v_driver_earnings — Мои приходи во месец Возачот кликне "Финансиски извештај". Вижи: - За секој месец колку возења направил - Колку резервации наплатил (можеби некој отказал во последниот момент) - Вкупен приход - Просечен приход по резервација - Максимален приход - Минимален приход Администраторот го користи и за анализа — ако возачот нема приход три месеца, веројатно е неактивен. Пресметува на основа на `bookings` со статус `completed` (само завршени) и нивните финални суми. === v_route_popularity — Кои рути се најпопуларни Апликацијата на почетниот екран прикажува популарни рути за да го вдахновува патникот. Ова view-ot ги рангира: - По број на вкупни резервации - Просечна цена по резервација - Просечна цена по километар Служи и како сигнал за возачите. Ако рутата Скопје↔Куманово има 50 резервации месечно, но возачот направил 10 возења и не продал ништо — сигнал е дека нешто не е окај. Можеби е превиско скапо, или нема добро рекламирано возење. === v_incident_summary — Безбедносни инциденти Само администраторот видува ова. Прикажува: - Тип на инцидент (невежливост, несреќа, кршење на правила) - Опис на инцидентот - Кога е пријавен - Лични податоци на пријавувачот (патник или возач) - Лични податоци на другата страна - Возачка дозвола број - Рутата на возењето Инцидентите могат да будат пријавени дури и после завршено возење, така да view-ot не филтрира по статус на возење. == Важно: NULL вредности со логика Неколку полиња се NULL базирано на логика: * `rides.recurrence_days` и `rides.recurrence_end_date` — се пополнуваат САМО ако `is_recurring = TRUE`. За еднократни возења се NULL. * `bookings.pickup_stop_id` и `bookings.dropoff_stop_id` — ако патникот резервира целата рута, стоповите се почеток и крај. Ако резервира дел, се полнат конкретни стопови. Апликацијата треба да проверува ова пред да користи вредностите.