Changes between Version 5 and Version 6 of DatabaseCreation
- Timestamp:
- 09/16/26 06:57:25 (11 days ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
DatabaseCreation
v5 v6 1 = DatabaseCreation = 2 3 == Опис == 4 5 Релационата база на податоци е имплементирана во PostgreSQL според претходно дефинираниот ER и релационен модел. 6 7 DDL скриптата ги дефинира сите потребни табели, примарни и странски клучеви, ограничувања, проверки и default вредности. 8 9 Целосната скрипта е достапна во: 10 11 * [attachment:ddl.sql ddl.sql] 12 13 == Основни табели == 14 15 Во почетниот дел од скриптата се креираат помошните табели кои понатаму се користат од останатите ентитети. 16 17 Пример за `Role`: 18 19 {{{#!sql 20 CREATE TABLE Role ( 21 role_id SERIAL NOT NULL PRIMARY KEY, 22 role_name text NOT NULL UNIQUE, 23 description text 24 ); 25 }}} 26 27 На сличен начин се дефинирани `Permission`, `Industry`, `Project_Status`, `Technology`, `Rating_Dimension` и `Subscription_Tier`. 28 29 == Корисници == 30 31 Сите типови на корисници ги содржат заедничките податоци во табелата `"User"`: 32 33 {{{#!sql 34 CREATE TABLE "User" ( 35 user_id SERIAL NOT NULL PRIMARY KEY, 36 type text NOT NULL 37 CHECK (type IN ('client', 'vendor', 'management')), 38 first_name text NOT NULL, 39 last_name text NOT NULL, 40 email text NOT NULL UNIQUE, 41 password_hash text NOT NULL, 42 is_active bool NOT NULL DEFAULT false, 43 last_login_at timestamp, 44 created_at timestamp NOT NULL DEFAULT NOW(), 45 updated_at timestamp NOT NULL DEFAULT NOW() 46 ); 47 }}} 48 49 Полето `type` е ограничено со `CHECK`, така што може да има само една од трите дозволени вредности. 50 51 За конкретните типови на корисници се користат дополнителни табели. На пример: 52 53 {{{#!sql 54 CREATE TABLE Client_User ( 55 user_id int4 NOT NULL PRIMARY KEY, 56 client_id int4 NOT NULL, 57 58 CONSTRAINT fk_clientuser_user 59 FOREIGN KEY (user_id) REFERENCES "User" (user_id) 60 ON DELETE CASCADE 61 ON UPDATE CASCADE, 62 63 CONSTRAINT fk_clientuser_client 64 FOREIGN KEY (client_id) REFERENCES Client (client_id) 65 ON DELETE RESTRICT 66 ON UPDATE CASCADE 67 ); 68 }}} 69 70 На истиот принцип се дефинирани и `Vendor_User` и `Management_User`. 71 72 == Клиенти, продавачи и договори == 73 74 Клиентот е поврзан со индустријата преку странски клуч: 75 76 {{{#!sql 77 CREATE TABLE Client ( 78 client_id SERIAL NOT NULL PRIMARY KEY, 79 industry_id int4 NOT NULL, 80 company_name text NOT NULL, 81 contact_email text NOT NULL, 82 83 CONSTRAINT fk_client_industry 84 FOREIGN KEY (industry_id) REFERENCES Industry (industry_id) 85 ON DELETE RESTRICT 86 ON UPDATE CASCADE 87 ); 88 }}} 89 90 Врската помеѓу клиент и vendor е претставена преку договор: 91 92 {{{#!sql 93 CREATE TABLE Client_Vendor_Contract ( 94 contract_id SERIAL NOT NULL PRIMARY KEY, 95 client_id int4 NOT NULL, 96 vendor_id int4 NOT NULL, 97 contract_number text UNIQUE, 98 contract_title text NOT NULL, 99 start_date date NOT NULL DEFAULT CURRENT_DATE, 100 end_date date, 101 total_value numeric(10,2), 102 currency_code text, 103 terms_summary text, 104 is_active bool NOT NULL DEFAULT true, 105 created_at timestamp NOT NULL DEFAULT NOW(), 106 updated_at timestamp NOT NULL DEFAULT NOW(), 107 108 CONSTRAINT chk_cvc_dates 109 CHECK (end_date IS NULL OR end_date > start_date), 110 111 CONSTRAINT fk_cvc_client 112 FOREIGN KEY (client_id) REFERENCES Client (client_id), 113 114 CONSTRAINT fk_cvc_vendor 115 FOREIGN KEY (vendor_id) REFERENCES Vendor (vendor_id) 116 ); 117 }}} 118 119 Со `CHECK` ограничувањето се спречува крајниот датум на договорот да биде пред почетниот. 120 121 == Проекти == 122 123 Секој проект е поврзан со договор и со тековен статус: 124 125 {{{#!sql 126 CREATE TABLE Project ( 127 project_id SERIAL NOT NULL PRIMARY KEY, 128 contract_id int4 NOT NULL, 129 status_id int4 NOT NULL, 130 project_name text NOT NULL, 131 start_date date NOT NULL DEFAULT CURRENT_DATE, 132 end_date date, 133 budget numeric(10,2) NOT NULL, 134 created_at timestamp NOT NULL DEFAULT NOW(), 135 updated_at timestamp NOT NULL DEFAULT NOW(), 136 137 CONSTRAINT chk_project_dates 138 CHECK (end_date IS NULL OR end_date > start_date), 139 140 CONSTRAINT fk_project_contract 141 FOREIGN KEY (contract_id) 142 REFERENCES Client_Vendor_Contract (contract_id), 143 144 CONSTRAINT fk_project_status 145 FOREIGN KEY (status_id) 146 REFERENCES Project_Status (status_id) 147 ); 148 }}} 149 150 Технологиите користени на проектите се моделирани со many-to-many релација: 151 152 {{{#!sql 153 CREATE TABLE Project_Technology ( 154 project_id int4 NOT NULL, 155 technology_id int4 NOT NULL, 156 157 PRIMARY KEY (project_id, technology_id), 158 159 CONSTRAINT fk_projtec_project 160 FOREIGN KEY (project_id) REFERENCES Project (project_id) 161 ON DELETE CASCADE, 162 163 CONSTRAINT fk_projtec_technology 164 FOREIGN KEY (technology_id) REFERENCES Technology (technology_id) 165 ON DELETE RESTRICT 166 ); 167 }}} 168 169 == Историја на проектите == 170 171 За промените на буџетот се користи посебна audit табела: 172 173 {{{#!sql 174 CREATE TABLE Project_Budget_Audit ( 175 audit_id SERIAL NOT NULL PRIMARY KEY, 176 project_id int4 NOT NULL, 177 old_budget numeric(10,2) NOT NULL, 178 new_budget numeric(10,2) NOT NULL, 179 created_at timestamp NOT NULL DEFAULT NOW(), 180 updated_at timestamp NOT NULL DEFAULT NOW(), 181 182 CONSTRAINT fk_budgetaudit_project 183 FOREIGN KEY (project_id) REFERENCES Project (project_id) 184 ); 185 }}} 186 187 Промените на статусот се зачувуваат во `Project_Status_History`, каде покрај проектот и статусот се чува и корисникот кој ја направил промената. 188 189 == Reviews и оценки == 190 191 За секој проект може да постои една рецензија: 192 193 {{{#!sql 194 CREATE TABLE Review ( 195 review_id SERIAL NOT NULL PRIMARY KEY, 196 project_id int4 NOT NULL UNIQUE, 197 client_user_id int4 NOT NULL, 198 review_date date NOT NULL DEFAULT CURRENT_DATE, 199 summary_text text NOT NULL, 200 is_published bool NOT NULL DEFAULT false, 201 202 CONSTRAINT fk_review_project 203 FOREIGN KEY (project_id) REFERENCES Project (project_id), 204 205 CONSTRAINT fk_review_clientuser 206 FOREIGN KEY (client_user_id) REFERENCES Client_User (user_id) 207 ); 208 }}} 209 210 Оценките се чуваат одделно за секоја димензија: 211 212 {{{#!sql 213 CREATE TABLE Review_Score ( 214 review_id int4 NOT NULL, 215 dimension_id int4 NOT NULL, 216 score_value int4 NOT NULL, 217 218 PRIMARY KEY (review_id, dimension_id), 219 220 CONSTRAINT fk_reviewscore_review 221 FOREIGN KEY (review_id) REFERENCES Review (review_id) 222 ON DELETE CASCADE, 223 224 CONSTRAINT fk_reviewscore_dimension 225 FOREIGN KEY (dimension_id) 226 REFERENCES Rating_Dimension (dimension_id) 227 ); 228 }}} 229 230 == Dispute систем == 231 232 При оспорување на review се креира запис во `Dispute_Ticket`: 233 234 {{{#!sql 235 CREATE TABLE Dispute_Ticket ( 236 ticket_id SERIAL NOT NULL PRIMARY KEY, 237 assigned_management_user_id int4 DEFAULT NULL, 238 review_id int4 NOT NULL, 239 vendor_user_id int4 NOT NULL, 240 reason text NOT NULL, 241 is_resolved bool NOT NULL DEFAULT false, 242 filed_at date NOT NULL DEFAULT CURRENT_DATE, 243 resolved_at timestamp, 244 resolution_note text 245 ); 246 }}} 247 248 Табелата е дополнително поврзана со `Management_User`, `Review` и `Vendor_User` преку странски клучеви. 249 250 == Views == 251 252 За почесто користените прикази и аналитички податоци се дефинирани PostgreSQL views. Тие ги обединуваат податоците од повеќе поврзани табели и овозможуваат поедноставен пристап до информации за проекти, договори, буџети, оценки, спорови и претплати. 253 254 Целосната скрипта е достапна во: 255 256 * [attachment:views.sql views.sql] 257 258 === Проекти по Vendor и Client === 259 260 Погледот `vw_projects_per_vendor` ги прикажува сите проекти поврзани со одреден vendor, заедно со статусот, периодот и буџетот: 1 = Креирање на базата (!DatabaseCreation) = 2 3 Во оваа фаза се прикажани DDL скриптата за креирање на базата (2А), скриптите за генерирање и полнење на податоците (2Б) и погледите што апликацијата ги користи. 4 5 [[PageOutline(2-3,Содржина,inline)]] 6 7 == 2А: DDL скрипта == 8 9 [attachment:ddl.sql] 10 11 Скриптата ги креира 23-те табели од релациониот модел (RelationalModel) по редослед на зависност. Надворешните клучеви, проверките и составеното уникатно ограничување се именувани (`fk_*`, `chk_*`, `uq_*`), така што пораката за грешка при прекршување го именува правилото. Табелата за корисници се вика `"User"`, со наводници, бидејќи `USER` е резервиран збор. 12 13 Табелите се поделени во шест модули: 14 15 * '''Идентитет и пристап''': `"User"` е централната табела за сите корисници со `type` (`client`, `vendor` или `management`); трите подтипови `Client_User`, `Vendor_User` и `Management_User` го делат нејзиниот примарен клуч и го поврзуваат корисникот со клиентот, софтверската агенција или улогата на која припаѓа. Дозволите се доделуваат на улогите преку `Role_Permission`. 16 * '''Страни''': `Client` (клиентска компанија, припаѓа на една `Industry`) и `Vendor` (софтверска агенција). 17 * '''Комерцијален дел''': `Client_Vendor_Contract` е договорот меѓу клиент и софтверска агенција; `Vendor_Subscription` е претплатата на софтверската агенција на платформата по периоди, на ниво од `Subscription_Tier` (фиксна цена од ценовник или договорна цена). 18 * '''Испорака''': `Project` припаѓа на договор, а не директно на клиент и софтверска агенција, и има тековен статус од `Project_Status`; `Project_Technology` ги поврзува проектите со `Technology`. 19 * '''Квалитет''': `Review` е рецензијата на клиентот за завршен проект (најмногу една по проект), со оценки од 1 до 5 по секоја димензија од `Rating_Dimension` во `Review_Score`; `Dispute_Ticket` е спорот што корисник на софтверската агенција го поднесува врз рецензија, а го решава менаџмент корисник. 20 * '''Историја''': `Project_Status_History` чува по една редица за секоја промена на статус, со точно еден актер (корисник на софтверската агенција или менаџмент корисник); `Project_Budget_Audit` чува стар и нов буџет за секоја промена на буџетот. 21 22 '''Проверки (CHECK):''' 23 24 * Датуми 25 * крајниот датум е по почетниот: договор, претплата, проект 26 * спорот не се решава пред да е поднесен 27 * Износи: сите се позитивни 28 * буџет на проект, вредност на договор, цена од ценовник, договорна цена, стар и нов буџет во ревизијата 29 * Оценка: меѓу 1 и 5 30 * Формат 31 * типот на корисник е една од `client`, `vendor`, `management` 32 * е-поштата е во облик `име@домен` 33 * валутата е три големи букви 34 * Непразен текст 35 * име, презиме и лозинка на корисник 36 * наслов на договор, име на проект 37 * резиме на рецензија, причина и белешка на спор 38 * Конзистентна состојба 39 * промена на статус има точно еден актер 40 * решен спор има датум на решавање, доделен менаџмент корисник и белешка; нерешен нема датум 41 * ниво на претплата има или цена од ценовник или дозвола за договорна цена, никогаш двете 42 43 '''UNIQUE:''' 44 45 * имињата во табелите со фиксни листи: улоги, дозволи, индустрии, статуси, технологии, димензии, нивоа 46 * е-поштата на корисникот 47 * бројот на договорот 48 * една рецензија по проект 49 * името на проектот во рамки на договорот (`uq_project_contract_name`) 50 51 '''Стандардни вредности:''' 52 53 * `created_at` и `updated_at`: `NOW()` 54 * `CURRENT_DATE`: почеток на договор, претплата и проект; датум на рецензија; датум на поднесување на спор 55 * Почетна состојба 56 * нов корисник е неактивен (се активира по потврда) 57 * нов договор и нова претплата се активни 58 * нова рецензија е необјавена 59 * нов спор е нерешен и недоделен 60 61 '''Бришење:''' 62 63 * сите надворешни клучеви се `ON DELETE RESTRICT`: редица на која упатуваат други редици не може да се избрише 64 * апликацијата не брише: корисниците се деактивираат, споровите се решаваат, а договорите и претплатите истекуваат 65 66 '''Делумни уникатни индекси.''' Две правила важат само за дел од редиците и не можат да се запишат како ограничување на ниво на табела: 67 68 {{{#!sql 69 -- WHERE: уникатноста важи само за редиците што го исполнуваат условот 70 CREATE UNIQUE INDEX uq_vendorsub_one_active 71 ON Vendor_Subscription (vendor_id) 72 WHERE is_active; 73 74 CREATE UNIQUE INDEX uq_dispute_open_per_vendor_review 75 ON Dispute_Ticket (review_id, vendor_user_id) 76 WHERE is_resolved = false; 77 }}} 78 79 Софтверска агенција има најмногу една активна претплата, а корисник на агенцијата најмногу еден отворен спор за иста рецензија. 80 81 == 2Б: Податоци == 82 83 [attachment:seed_generator.py] 84 85 [attachment:load_seed.sql] 86 87 Податоците ги генерира скриптата `seed_generator.py` (Python, библиотека Faker за имиња и текст) како по една CSV датотека за секоја табела; скриптата `load_seed.sql` ги полни табелите по редослед на зависност. Податоците ги задоволуваат сите ограничувања од шемата и правилата од апликациската логика: 88 89 * Авторот на рецензија е корисник на клиентот од договорот на проектот, поднесувачот на спор е корисник на софтверската агенција од истиот договор, а актерот во историјата на статуси е корисник на софтверската агенција на проектот или менаџмент корисник. 90 * Секој проект минува низ животниот циклус Draft → Under Review → In Progress (→ On Hold) и завршува како Completed или Cancelled, или останува отворен; промените на статус се временски подредени внатре во траењето на проектот, а статусот на проектот е секогаш последната редица од историјата. 91 * Рецензии постојат само за завршени проекти (Completed или Cancelled), најмногу една по проект и со по една оценка за секоја од десетте димензии. 92 * Решените спорови имаат датум на решавање, доделен менаџмент корисник и белешка. Рецензија со отворен спор не е објавена. 93 * Промените на буџетот формираат синџир: стариот буџет на секоја промена е новиот од претходната, а последниот нов буџет е тековниот буџет на проектот. 94 * Периодите на претплата на една софтверска агенција не се преклопуваат и активен е најмногу последниот; договорна цена има само на нивоата што ја дозволуваат. 95 * Проектите лежат внатре во периодот на договорот, секој договор има барем еден проект, а секој клиент и секоја софтверска агенција има барем еден корисник. Е-поштата е во облик `име.презиме@домен-на-компанијата`. 96 97 Распределбите се избрани при генерирањето, за реалистични податоци: 98 99 * 78,5 % од проектите се Completed и 5,6 % Cancelled; останатите се отворени. 100 * Рецензијата е напишана 1 до 45 дена по крајот на проектот. Спор е поднесен за 15 % од рецензиите, а од рецензиите без отворен спор се објавени 70 %. 101 * Секоја софтверска агенција има скриен параметар на квалитет околу кој се влечат оценките на нејзините рецензии, па просечните оценки по агенција се распоредени од 1,66 до 4,61, а не сите околу 3. 102 103 '''Големина на табелите.''' Број на редици по полнењето: 104 105 ||= Табела =||= Редици =|| 106 || `Review_Score` || 10.000.000 || 107 || `Project_Status_History` || 5.946.160 || 108 || `Project_Technology` || 4.197.954 || 109 || `Project` || 1.400.000 || 110 || `Project_Budget_Audit` || 1.048.649 || 111 || `Review` || 1.000.000 || 112 || `Client_Vendor_Contract` || 250.000 || 113 || `Dispute_Ticket` || 143.894 || 114 || `"User"` || 77.000 || 115 || `Client_User` || 50.000 || 116 || `Vendor_User` || 25.000 || 117 || `Client` || 20.000 || 118 || `Vendor_Subscription` || 5.916 || 119 || `Vendor` || 5.000 || 120 || `Management_User` || 2.000 || 121 || `Role_Permission` || 40 || 122 || `Technology` || 30 || 123 || `Industry` || 20 || 124 || `Permission` || 15 || 125 || `Rating_Dimension` || 10 || 126 || `Project_Status` || 6 || 127 || `Role` || 5 || 128 || `Subscription_Tier` || 4 || 129 130 Шест табели имаат милион или повеќе редици; вкупно има 24,2 милиони редици и базата зафаќа 2,1 GB. 131 132 == Погледи == 133 134 [attachment:views.sql] 135 136 Скриптата креира 16 погледи. Првите два, `vw_contract_details` и `vw_project_details`, се помошни: апликацијата не ги чита директно, туку врз нив се градат останатите. 137 138 === Поглед 1: Детали за договор (vw_contract_details) === 139 140 Помошен поглед: по една редица за секој договор со имињата на двете страни. Поврзувањето договор–клиент–софтверска агенција е напишано еднаш, а погледите за договори и за проекти го користат. 141 142 {{{#!sql 143 CREATE OR REPLACE VIEW vw_contract_details AS 144 SELECT cvc.contract_id, 145 cvc.contract_number, 146 cvc.contract_title, 147 cvc.start_date, 148 cvc.end_date, 149 cvc.total_value, 150 cvc.currency_code, 151 cvc.is_active, 152 c.client_id, 153 c.company_name, 154 v.vendor_id, 155 v.agency_name 156 FROM Client_Vendor_Contract cvc 157 JOIN Client c ON c.client_id = cvc.client_id 158 JOIN Vendor v ON v.vendor_id = cvc.vendor_id; 159 }}} 160 161 === Поглед 2: Детали за проект (vw_project_details) === 162 163 Помошен поглед: проектот со името на статусот, договорот и двете страни. Од него читаат погледите по софтверска агенција и по клиент, погледите за оценки и погледот за промени на буџет. Оптимизаторот ја вметнува дефиницијата на помошниот поглед во прашалникот што го користи, па помошниот поглед не додава чекор во планот. 164 165 {{{#!sql 166 CREATE OR REPLACE VIEW vw_project_details AS 167 SELECT p.project_id, 168 p.project_name, 169 p.status_id, 170 ps.status_name, 171 p.start_date, 172 p.end_date, 173 p.budget, 174 cd.contract_id, 175 cd.contract_number, 176 cd.currency_code, 177 cd.client_id, 178 cd.company_name, 179 cd.vendor_id, 180 cd.agency_name 181 FROM Project p 182 -- договорот и двете страни доаѓаат од помошниот поглед 183 JOIN vw_contract_details cd ON cd.contract_id = p.contract_id 184 JOIN Project_Status ps ON ps.status_id = p.status_id; 185 }}} 186 187 === Поглед 3: Проекти по софтверска агенција (vw_projects_per_vendor) === 188 189 Сите проекти на секоја софтверска агенција со статус, датуми, буџет и валута. Апликацијата го чита филтриран по агенција на нејзината контролна табла и на нејзиниот профил, каде што клиентот го разгледува портфолиото на реални проекти пред избор на агенција. 261 190 262 191 {{{#!sql 263 192 CREATE OR REPLACE VIEW vw_projects_per_vendor AS 264 SELECT v.vendor_id, 265 v.agency_name, 266 p.project_id, 267 p.project_name, 268 ps.status_name, 269 p.start_date, 270 p.end_date, 271 p.budget, 272 cvc.currency_code 273 FROM Project p 274 JOIN Client_Vendor_Contract cvc 275 ON cvc.contract_id = p.contract_id 276 JOIN Vendor v 277 ON v.vendor_id = cvc.vendor_id 278 JOIN Project_Status ps 279 ON ps.status_id = p.status_id 280 ORDER BY v.agency_name, ps.status_name, p.project_name; 281 }}} 282 283 Соодветниот поглед за клиентите е `vw_projects_per_client`: 193 SELECT pd.vendor_id, 194 pd.agency_name, 195 pd.project_id, 196 pd.project_name, 197 pd.status_name, 198 pd.start_date, 199 pd.end_date, 200 pd.budget, 201 pd.currency_code 202 FROM vw_project_details pd 203 ORDER BY pd.agency_name, pd.status_name, pd.project_name; 204 }}} 205 206 === Поглед 4: Проекти по клиент (vw_projects_per_client) === 207 208 Сите проекти на една клиентска компанија низ сите нејзини договори и софтверски агенции. Апликацијата го чита филтриран по клиент на контролната табла на клиентот; оттука клиентот избира завршен проект за кој ќе остави рецензија. 284 209 285 210 {{{#!sql 286 211 CREATE OR REPLACE VIEW vw_projects_per_client AS 287 SELECT c.client_id, 288 c.company_name, 289 p.project_id, 290 p.project_name, 291 ps.status_name, 292 p.start_date, 293 p.end_date, 294 p.budget, 295 cvc.currency_code 296 FROM Project p 297 JOIN Client_Vendor_Contract cvc 298 ON cvc.contract_id = p.contract_id 299 JOIN Client c 300 ON c.client_id = cvc.client_id 301 JOIN Project_Status ps 302 ON ps.status_id = p.status_id 303 ORDER BY c.company_name, ps.status_name, p.project_name; 304 }}} 305 306 === Буџети по Vendor и Client === 307 308 За аналитички приказ на вкупниот број на проекти и нивниот буџет се користат `vw_budget_per_vendor` и `vw_budget_per_client`. 212 SELECT pd.client_id, 213 pd.company_name, 214 pd.project_id, 215 pd.project_name, 216 pd.status_name, 217 pd.start_date, 218 pd.end_date, 219 pd.budget, 220 pd.currency_code 221 FROM vw_project_details pd 222 ORDER BY pd.company_name, pd.status_name, pd.project_name; 223 }}} 224 225 === Поглед 5: Вкупен буџет по софтверска агенција (vw_budget_per_vendor) === 226 227 Бројот на проекти и збирот на нивните буџети за секоја софтверска агенција, по валута, за да не се собираат износи во различни валути. Финансиски преглед на обемот на работа на агенцијата: увид во сопствените перформанси и, за клиентот, показател за големината на агенцијата. 309 228 310 229 {{{#!sql 311 230 CREATE OR REPLACE VIEW vw_budget_per_vendor AS 312 SELECT v.vendor_id,313 v.agency_name,314 cvc.currency_code,315 COUNT(p.project_id) AS project_count,316 SUM(p.budget) AS total_budget317 FROM Project p318 JOIN Client_Vendor_Contract cvc 319 O N cvc.contract_id = p.contract_id320 JOIN Vendor v 321 ON v.vendor_id = cvc.vendor_id 322 GROUP BY v.vendor_id, v.agency_name, cvc.currency_code 323 ORDER BY v.agency_name, cvc.currency_code; 324 }}} 231 SELECT pd.vendor_id, 232 pd.agency_name, 233 pd.currency_code, 234 COUNT(pd.project_id) AS project_count, 235 SUM(pd.budget) AS total_budget 236 FROM vw_project_details pd 237 GROUP BY pd.vendor_id, pd.agency_name, pd.currency_code 238 ORDER BY pd.agency_name, pd.currency_code; 239 }}} 240 241 === Поглед 6: Вкупен буџет по клиент (vw_budget_per_client) === 242 243 Истата агрегација по клиент: вкупниот буџет што клиентот го ангажирал низ сите проекти и договори, по валута. Финансиски преглед на клиентот за следење на потрошениот буџет. 325 244 326 245 {{{#!sql 327 246 CREATE OR REPLACE VIEW vw_budget_per_client AS 328 SELECT c.client_id, 329 c.company_name, 330 cvc.currency_code, 331 COUNT(p.project_id) AS project_count, 332 SUM(p.budget) AS total_budget 333 FROM Project p 334 JOIN Client_Vendor_Contract cvc 335 ON cvc.contract_id = p.contract_id 336 JOIN Client c 337 ON c.client_id = cvc.client_id 338 GROUP BY c.client_id, c.company_name, cvc.currency_code 339 ORDER BY c.company_name, cvc.currency_code; 340 }}} 341 342 Групирањето се врши и според `currency_code`, со што буџетите во различни валути не се собираат во една вредност. 343 344 === Клиенти по индустрија === 345 346 `vw_clients_per_industry` овозможува приказ на клиентските компании групирани според нивната индустрија: 247 SELECT pd.client_id, 248 pd.company_name, 249 pd.currency_code, 250 COUNT(pd.project_id) AS project_count, 251 SUM(pd.budget) AS total_budget 252 FROM vw_project_details pd 253 GROUP BY pd.client_id, pd.company_name, pd.currency_code 254 ORDER BY pd.company_name, pd.currency_code; 255 }}} 256 257 === Поглед 7: Клиенти по индустрија (vw_clients_per_industry) === 258 259 Клиентите со нивната индустрија. Апликацијата го чита филтриран по индустрија, на пример кога софтверска агенција бара референци во одредена гранка; ја имплементира организацијата на клиентите по индустриска категорија од описот на проектот. 347 260 348 261 {{{#!sql 349 262 CREATE OR REPLACE VIEW vw_clients_per_industry AS 350 263 SELECT i.industry_id, 351 i.industry_name,352 c.client_id,353 c.company_name,354 c.contact_email264 i.industry_name, 265 c.client_id, 266 c.company_name, 267 c.contact_email 355 268 FROM Client c 356 JOIN Industry i 357 ON i.industry_id = c.industry_id 269 JOIN Industry i ON i.industry_id = c.industry_id 358 270 ORDER BY i.industry_name, c.company_name; 359 271 }}} 360 272 361 === П росечна оценка по Vendor===362 363 За секој vendor се пресметува просечна оценка од сите `Review_Score` записи за неговите проекти: 273 === Поглед 8: Просечна оценка по софтверска агенција (vw_avg_rating_per_vendor) === 274 275 Просечната оценка на секоја софтверска агенција преку сите димензии и бројот на рецензии, само од објавени рецензии, така што оспорена или необјавена рецензија не влијае на јавниот просек. Агенциите без објавена рецензија остануваат во резултатот со нула рецензии. Ова е јавната ранг-листа на софтверски агенции, главната функционалност на платформата; филтриран по агенција, погледот ја дава оценката прикажана на нејзиниот профил. 364 276 365 277 {{{#!sql 366 278 CREATE OR REPLACE VIEW vw_avg_rating_per_vendor AS 367 279 SELECT v.vendor_id, 368 v.agency_name, 369 COUNT(DISTINCT r.review_id) AS review_count, 370 ROUND(AVG(rs.score_value), 2) AS avg_rating 371 FROM Review_Score rs 372 JOIN Review r 373 ON r.review_id = rs.review_id 374 JOIN Project p 375 ON p.project_id = r.project_id 376 JOIN Client_Vendor_Contract cvc 377 ON cvc.contract_id = p.contract_id 378 JOIN Vendor v 379 ON v.vendor_id = cvc.vendor_id 280 v.agency_name, 281 COUNT(vr.review_id) AS review_count, 282 -- просек само од објавените рецензии: збир на збировите / збир на броевите, 283 -- што е истиот просек како AVG, но без COUNT(DISTINCT); NULLIF штити од делење со нула 284 ROUND(SUM(vr.score_sum) / NULLIF(SUM(vr.score_cnt), 0), 2) AS avg_rating 285 FROM Vendor v 286 -- LEFT JOIN: агенција без објавена рецензија останува со review_count = 0 287 -- потпрашалник: збир и број на оценки по рецензија, само објавени рецензии 288 LEFT JOIN (SELECT pd.vendor_id, 289 r.review_id, 290 SUM(rs.score_value)::numeric AS score_sum, -- ::numeric за децимален просек 291 COUNT(*) AS score_cnt 292 FROM Review r 293 JOIN Review_Score rs ON rs.review_id = r.review_id 294 JOIN vw_project_details pd ON pd.project_id = r.project_id 295 WHERE r.is_published = true 296 GROUP BY pd.vendor_id, r.review_id) vr ON vr.vendor_id = v.vendor_id 380 297 GROUP BY v.vendor_id, v.agency_name 381 298 ORDER BY avg_rating DESC NULLS LAST; 382 299 }}} 383 300 384 `COUNT(DISTINCT r.review_id)` го прикажува бројот на рецензии, додека `AVG` ја пресметува просечната вредност од сите оценети димензии. 385 386 === Нерешени Dispute Tickets === 387 388 За потребите на dispute системот е креиран `vw_unresolved_dispute_tickets`, кој ги прикажува само тикетите што сè уште не се решени: 389 390 {{{#!sql 391 CREATE OR REPLACE VIEW vw_unresolved_dispute_tickets AS 301 === Поглед 9: Нерешени спорови (vw_unresolved_dispute_tickets) === 302 303 Сите нерешени спорови со рецензијата, проектот, името на поднесувачот, името на доделениот менаџмент корисник (празно додека спорот не е доделен) и софтверската агенција во чие име е поднесен. Редот на задачи на менаџментот: се чита филтриран по проект, по агенција или по доделен корисник. 304 305 {{{#!sql 306 -- CREATE OR REPLACE не може да додаде колони во средината на листата, 307 -- па погледот се брише и се креира одново 308 DROP VIEW IF EXISTS vw_unresolved_dispute_tickets; 309 310 CREATE VIEW vw_unresolved_dispute_tickets AS 392 311 SELECT dt.ticket_id, 393 dt.filed_at, 394 dt.reason, 395 r.review_id, 396 p.project_id, 397 p.project_name, 398 vu.user_id AS filed_by_vendor_user_id, 399 vu_u.first_name || ' ' || vu_u.last_name 400 AS filed_by_vendor_user, 401 mu.user_id AS assigned_management_user_id, 402 mu_u.first_name || ' ' || mu_u.last_name 403 AS assigned_to_management_user, 404 dt.created_at, 405 dt.updated_at 312 dt.filed_at, 313 dt.reason, 314 r.review_id, 315 p.project_id, 316 p.project_name, 317 dt.vendor_user_id AS filed_by_vendor_user_id, 318 -- имињата на поднесувачот (vusr) и на доделениот корисник (musr) од две 319 -- поврзувања со "User"; musr е NULL додека спорот не е доделен (LEFT JOIN) 320 vusr.first_name || ' ' || vusr.last_name AS filed_by_vendor_user, 321 dt.assigned_management_user_id, 322 musr.first_name || ' ' || musr.last_name AS assigned_to_management_user, 323 dt.created_at, 324 dt.updated_at, 325 ven.vendor_id, 326 ven.agency_name 406 327 FROM Dispute_Ticket dt 407 JOIN Review r 408 ON r.review_id = dt.review_id 409 JOIN Project p 410 ON p.project_id = r.project_id 411 JOIN Vendor_User vu 412 ON vu.user_id = dt.vendor_user_id 413 JOIN "User" vu_u 414 ON vu_u.user_id = vu.user_id 415 LEFT JOIN Management_User mu 416 ON mu.user_id = dt.assigned_management_user_id 417 LEFT JOIN "User" mu_u 418 ON mu_u.user_id = mu.user_id 328 JOIN Review r ON r.review_id = dt.review_id 329 JOIN Project p ON p.project_id = r.project_id 330 JOIN Vendor_User vu ON vu.user_id = dt.vendor_user_id 331 JOIN Vendor ven ON ven.vendor_id = vu.vendor_id 332 JOIN "User" vusr ON vusr.user_id = vu.user_id 333 LEFT JOIN "User" musr ON musr.user_id = dt.assigned_management_user_id 419 334 WHERE dt.is_resolved = false 420 335 ORDER BY dt.filed_at; 421 336 }}} 422 337 423 За management корисникот се користи `LEFT JOIN`, бидејќи нерешен dispute ticket може сè уште да нема доделен management корисник. 424 425 === Број на проекти по статус === 426 427 За статистички приказ на состојбата на проектите се користат два погледи кои го пресметуваат бројот на проекти по статус. 428 429 За vendor: 338 === Поглед 10: Број на проекти по статус по софтверска агенција (vw_project_count_by_status_per_vendor) === 339 340 Бројот на проекти на секоја софтверска агенција по статус. Преглед на контролната табла на агенцијата (колку проекти се во тек, завршени, откажани) и, за клиентот, показател колку од проектите на агенцијата навистина се завршуваат. 430 341 431 342 {{{#!sql 432 343 CREATE OR REPLACE VIEW vw_project_count_by_status_per_vendor AS 433 SELECT v.vendor_id, 434 v.agency_name, 435 ps.status_id, 436 ps.status_name, 437 COUNT(p.project_id) AS project_count 438 FROM Project p 439 JOIN Client_Vendor_Contract cvc 440 ON cvc.contract_id = p.contract_id 441 JOIN Vendor v 442 ON v.vendor_id = cvc.vendor_id 443 JOIN Project_Status ps 444 ON ps.status_id = p.status_id 445 GROUP BY v.vendor_id, 446 v.agency_name, 447 ps.status_id, 448 ps.status_name 449 ORDER BY v.agency_name, ps.status_name; 450 }}} 451 452 За client: 344 SELECT pd.vendor_id, 345 pd.agency_name, 346 pd.status_id, 347 pd.status_name, 348 COUNT(pd.project_id) AS project_count 349 FROM vw_project_details pd 350 GROUP BY pd.vendor_id, pd.agency_name, pd.status_id, pd.status_name 351 ORDER BY pd.agency_name, pd.status_name; 352 }}} 353 354 === Поглед 11: Број на проекти по статус по клиент (vw_project_count_by_status_per_client) === 355 356 Истото по клиент: состојбата на портфолиото на една клиентска компанија низ сите нејзини софтверски агенции, на контролната табла на клиентот. 453 357 454 358 {{{#!sql 455 359 CREATE OR REPLACE VIEW vw_project_count_by_status_per_client AS 456 SELECT c.client_id, 457 c.company_name, 458 ps.status_id, 459 ps.status_name, 460 COUNT(p.project_id) AS project_count 461 FROM Project p 462 JOIN Client_Vendor_Contract cvc 463 ON cvc.contract_id = p.contract_id 464 JOIN Client c 465 ON c.client_id = cvc.client_id 466 JOIN Project_Status ps 467 ON ps.status_id = p.status_id 468 GROUP BY c.client_id, 469 c.company_name, 470 ps.status_id, 471 ps.status_name 472 ORDER BY c.company_name, ps.status_name; 473 }}} 474 475 Овие погледи овозможуваат брзо добивање на бројот на проекти во статуси како `Draft`, `In Progress`, `Completed` и останатите дефинирани статуси. 476 477 === Договори по Vendor и Client === 478 479 За приказ на договорите од двете перспективи се користат `vw_contracts_per_vendor` и `vw_contracts_per_client`. 360 SELECT pd.client_id, 361 pd.company_name, 362 pd.status_id, 363 pd.status_name, 364 COUNT(pd.project_id) AS project_count 365 FROM vw_project_details pd 366 GROUP BY pd.client_id, pd.company_name, pd.status_id, pd.status_name 367 ORDER BY pd.company_name, pd.status_name; 368 }}} 369 370 === Поглед 12: Договори по софтверска агенција (vw_contracts_per_vendor) === 371 372 Сите договори на секоја софтверска агенција со името на клиентот, периодот, вредноста и валутата, прво активните, па најновите. Преглед на активните и историските договори на агенцијата; договорот е врската преку која проектите се врзуваат за клиент и агенција. 480 373 481 374 {{{#!sql 482 375 CREATE OR REPLACE VIEW vw_contracts_per_vendor AS 483 SELECT v.vendor_id, 484 v.agency_name, 485 cvc.contract_id, 486 cvc.contract_number, 487 cvc.contract_title, 488 c.client_id, 489 c.company_name AS client_name, 490 cvc.start_date, 491 cvc.end_date, 492 cvc.total_value, 493 cvc.currency_code, 494 cvc.is_active 495 FROM Client_Vendor_Contract cvc 496 JOIN Vendor v 497 ON v.vendor_id = cvc.vendor_id 498 JOIN Client c 499 ON c.client_id = cvc.client_id 500 ORDER BY v.agency_name, 501 cvc.is_active DESC, 502 cvc.start_date DESC; 503 }}} 376 SELECT cd.vendor_id, 377 cd.agency_name, 378 cd.contract_id, 379 cd.contract_number, 380 cd.contract_title, 381 cd.client_id, 382 cd.company_name AS client_name, 383 cd.start_date, 384 cd.end_date, 385 cd.total_value, 386 cd.currency_code, 387 cd.is_active 388 FROM vw_contract_details cd 389 ORDER BY cd.agency_name, cd.is_active DESC, cd.start_date DESC; 390 }}} 391 392 === Поглед 13: Договори по клиент (vw_contracts_per_client) === 393 394 Истото од страна на клиентот: сите негови договори со името на софтверската агенција, за прегледот на активните и историските договори на клиентската компанија. 504 395 505 396 {{{#!sql 506 397 CREATE OR REPLACE VIEW vw_contracts_per_client AS 507 SELECT c.client_id, 508 c.company_name, 509 cvc.contract_id, 510 cvc.contract_number, 511 cvc.contract_title, 512 v.vendor_id, 513 v.agency_name AS vendor_name, 514 cvc.start_date, 515 cvc.end_date, 516 cvc.total_value, 517 cvc.currency_code, 518 cvc.is_active 519 FROM Client_Vendor_Contract cvc 520 JOIN Client c 521 ON c.client_id = cvc.client_id 522 JOIN Vendor v 523 ON v.vendor_id = cvc.vendor_id 524 ORDER BY c.company_name, 525 cvc.is_active DESC, 526 cvc.start_date DESC; 527 }}} 528 529 Со сортирање на `is_active DESC`, активните договори се прикажуваат пред историските договори. 530 531 === Vendor претплати === 532 533 За приказ на активните и историските претплати на агенциите е дефиниран `vw_vendor_subscriptions`: 398 SELECT cd.client_id, 399 cd.company_name, 400 cd.contract_id, 401 cd.contract_number, 402 cd.contract_title, 403 cd.vendor_id, 404 cd.agency_name AS vendor_name, 405 cd.start_date, 406 cd.end_date, 407 cd.total_value, 408 cd.currency_code, 409 cd.is_active 410 FROM vw_contract_details cd 411 ORDER BY cd.company_name, cd.is_active DESC, cd.start_date DESC; 412 }}} 413 414 === Поглед 14: Претплати на софтверски агенции (vw_vendor_subscriptions) === 415 416 Сите периоди на претплата на секоја софтверска агенција со нивото и ефективната цена: договорната цена ако постои, инаку цената од ценовникот. Преглед на активната и историските претплати на агенцијата и основа за наплатата. 534 417 535 418 {{{#!sql 536 419 CREATE OR REPLACE VIEW vw_vendor_subscriptions AS 537 420 SELECT v.vendor_id, 538 v.agency_name, 539 st.tier_id, 540 st.tier_name, 541 COALESCE( 542 vs.negotiated_price, 543 st.list_price, 544 0 545 ) AS effective_price, 546 vs.start_date, 547 vs.end_date, 548 vs.is_active 421 v.agency_name, 422 st.tier_id, 423 st.tier_name, 424 -- договорената цена ако постои, инаку цената од ценовникот, инаку 0 425 COALESCE(vs.negotiated_price, st.list_price, 0) AS effective_price, 426 vs.start_date, 427 vs.end_date, 428 vs.is_active 549 429 FROM Vendor_Subscription vs 550 JOIN Vendor v 551 ON v.vendor_id = vs.vendor_id 552 JOIN Subscription_Tier st 553 ON st.tier_id = vs.tier_id 554 ORDER BY v.agency_name, 555 vs.is_active DESC, 556 vs.start_date DESC; 557 }}} 558 559 Полето `effective_price` ја користи договорената цена доколку постои. Во спротивно се користи стандардната цена од `Subscription_Tier`. 560 561 На овој начин views обезбедуваат готови прикази за најчестите оперативни и аналитички потреби на системот, без истите `JOIN`, `GROUP BY` и агрегатни операции да се повторуваат во секој прашалник. 562 563 == Полнење со податоци == 564 565 За тестирање на базата се користи `seed_generator.py`, кој генерира реалистични податоци и CSV датотеки за табелите. 566 567 Генерирањето се извршува со: 568 569 * [attachment:seed_generator.py seed_generator.py] 570 571 Податоците се генерираат според зависностите помеѓу табелите, така што се почитуваат дефинираните foreign key ограничувања. 430 JOIN Vendor v ON v.vendor_id = vs.vendor_id 431 JOIN Subscription_Tier st ON st.tier_id = vs.tier_id 432 ORDER BY v.agency_name, vs.is_active DESC, vs.start_date DESC; 433 }}} 434 435 === Поглед 15: Оценки по димензија по софтверска агенција (vw_vendor_rating_by_dimension) === 436 437 За секоја софтверска агенција и димензија на оценување: бројот на оценки, просекот, минимумот и максимумот, само од објавени рецензии. Деталниот приказ зад просечната оценка на профилот на агенцијата (квалитет на код, комуникација, почитување на рокови и останатите димензии), односно повеќедимензионалното оценување по кое платформата се разликува од решенијата со една оценка. 438 439 {{{#!sql 440 CREATE OR REPLACE VIEW vw_vendor_rating_by_dimension AS 441 SELECT pd.vendor_id, 442 pd.agency_name, 443 rd.dimension_id, 444 rd.dimension_name, 445 COUNT(*) AS score_count, 446 ROUND(AVG(rs.score_value), 2) AS avg_score, 447 MIN(rs.score_value) AS min_score, 448 MAX(rs.score_value) AS max_score 449 FROM Review_Score rs 450 JOIN Rating_Dimension rd ON rd.dimension_id = rs.dimension_id 451 JOIN Review r ON r.review_id = rs.review_id 452 JOIN vw_project_details pd ON pd.project_id = r.project_id 453 WHERE r.is_published = true 454 GROUP BY pd.vendor_id, pd.agency_name, rd.dimension_id, rd.dimension_name; 455 }}} 456 457 === Поглед 16: Промени на буџетот по проект (vw_project_budget_changes) === 458 459 Историјата на буџетот: по една редица за секоја промена на буџетот на проект, со стариот и новиот износ, разликата и процентуалната промена. Се чита филтриран по проект на страницата на проектот или по клиент и софтверска агенција за преглед на сите промени на една страна. 460 461 {{{#!sql 462 CREATE OR REPLACE VIEW vw_project_budget_changes AS 463 SELECT pba.audit_id, 464 pd.project_id, 465 pd.project_name, 466 pd.vendor_id, 467 pd.client_id, 468 pd.currency_code, 469 pba.old_budget, 470 pba.new_budget, 471 pba.new_budget - pba.old_budget AS delta, 472 -- процентуална промена; NULLIF штити од делење со нула 473 ROUND(100.0 * (pba.new_budget - pba.old_budget) / NULLIF(pba.old_budget, 0), 1) AS pct_change, 474 pba.created_at AS changed_at 475 FROM Project_Budget_Audit pba 476 JOIN vw_project_details pd ON pd.project_id = pba.project_id; 477 }}}
