| 1 | | Развојот на овој дел од апликацијата може да се види на следниот линк: |
| 2 | | |
| 3 | | Back-end: https://github.com/borisdz/DistributorApp-Web/tree/dev-advanced |
| 4 | | |
| 5 | | Front-end: https://github.com/borisdz/DistributorApp-Frontend/tree/dev |
| | 1 | ===== Развојот на овој дел од апликацијата може да се види на следниот линк: |
| | 2 | |
| | 3 | '''Back-end''': https://github.com/borisdz/DistributorApp-Web/tree/dev-advanced |
| | 4 | |
| | 5 | '''Front-end''': https://github.com/borisdz/DistributorApp-Frontend/tree/dev |
| | 6 | |
| | 7 | [[PageOutline(2-4, Содржина, inline)]] |
| | 8 | |
| | 9 | == Индекси |
| | 10 | |
| | 11 | Заради забрзување на пристап и пребарување низ базата, креирани се индекси на секој надворешен клуч во секој ентитет. |
| | 12 | |
| | 13 | {{{#!sql |
| | 14 | create index idx_article_manufacturerId on article(man_id); |
| | 15 | create index idx_article_categoryId on article(art_id); |
| | 16 | create index idx_articleUnit_warehouseId on article_unit(wh_id); |
| | 17 | create index idx_articleUnit_orderId on article_unit(ord_id); |
| | 18 | create index idx_city_regionId on city(region_id); |
| | 19 | create index idx_warehouse_cityId on warehouse(city_id); |
| | 20 | create index idx_vehicle_warehouseId on vehicle(wh_id); |
| | 21 | create index idx_user_cityId on users(city_id); |
| | 22 | create index idx_user_userEmail on users(user_email); |
| | 23 | create index idx_manager_userId on manager(user_id); |
| | 24 | create index idx_manager_warehouseId on manager(wh_id); |
| | 25 | create index idx_delivery_dStatusId on delivery(d_status_id); |
| | 26 | create index idx_delivery_vehicleId on delivery(veh_id); |
| | 27 | create index idx_driver_userId on driver(user_id); |
| | 28 | create index idx_driver_vehicleId on driver(veh_id); |
| | 29 | create index idx_imageStore_entityType on image_store(img_ent_type); |
| | 30 | create index idx_imageStore_entityId on image_store(img_ent_id); |
| | 31 | create index idx_price_articleId on price(art_id); |
| | 32 | create index idx_proForma_pfStatusId on pro_forma(pf_status_id); |
| | 33 | create index idx_orders_oStatusId on orders(o_status_id); |
| | 34 | create index idx_orders_deliveryId on orders(del_id); |
| | 35 | create index idx_orders_pfId on orders(pf_id); |
| | 36 | create index idx_unitPrice_unitId on unit_price(unit_id); |
| | 37 | create index idx_unitPrice_priceId on unit_price(price_id); |
| | 38 | create index idx_customerWeekday_customerId on customer_weekday(cust_id); |
| | 39 | create index idx_customerWeekday_dayId on customer_weekday(day_id); |
| | 40 | create index idx_token_userId on token_(t_user); |
| | 41 | }}} |
| | 42 | |
| | 43 | == Процедури |
| | 44 | |
| | 45 | === Промена на број на продажби на секој артикл при промена на цена |
| | 46 | |
| | 47 | {{{#!sql |
| | 48 | create procedure ArticleSalesChangeOnPriceChange() |
| | 49 | language plpgsql |
| | 50 | as $$ |
| | 51 | begin |
| | 52 | with PriceChanges as ( |
| | 53 | select |
| | 54 | p.art_id, |
| | 55 | p.price, |
| | 56 | p.price_eff_date, |
| | 57 | lag(p.price) over (partition by p.art_id order by p.price_eff_date) as prevPrice |
| | 58 | from price p |
| | 59 | ), |
| | 60 | FilteredPrice as ( |
| | 61 | select |
| | 62 | art_id, |
| | 63 | price, |
| | 64 | price_eff_date |
| | 65 | from PriceChanges |
| | 66 | where prevPrice is null or abs(price - prevPrice) >= 1 |
| | 67 | ), |
| | 68 | SalesByPrice as ( |
| | 69 | select |
| | 70 | p.art_id, |
| | 71 | fp.price, |
| | 72 | fp.price_eff_date, |
| | 73 | count(au.unit_id) as units_sold, |
| | 74 | count(au.unit_id) * fp.price as revenue |
| | 75 | from article_unit au |
| | 76 | join orders o on au.ord_id = o.ord_id |
| | 77 | join unit_price up on up.unit_id = au.unit_id |
| | 78 | join price p on up.price_id = p.price_id |
| | 79 | join FilteredPrice fp on p.art_id = fp.art_id and p.price_eff_date = fp.price_eff_date |
| | 80 | where o.ord_date >= fp.price_eff_date and o.ord_date < coalesce( |
| | 81 | (select min(fp2.price_eff_date) |
| | 82 | from FilteredPrice fp2 |
| | 83 | where fp2.art_id = fp.art_id and fp2.price_eff_date > fp.price_eff_date), |
| | 84 | '9999-12-31' |
| | 85 | ) |
| | 86 | group by p.art_id, fp.price, fp.price_eff_date |
| | 87 | ) |
| | 88 | select |
| | 89 | a.art_name as articleName, |
| | 90 | sp.price as unitPrice, |
| | 91 | sp.price_eff_date as priceEffectiveDate, |
| | 92 | sp.units_sold, |
| | 93 | sp.revenue |
| | 94 | from SalesByPrice sp |
| | 95 | join article a on sp.art_id = a.art_id |
| | 96 | order by a.art_name, sp.price_eff_date; |
| | 97 | end; |
| | 98 | $$; |
| | 99 | }}} |
| | 100 | |
| | 101 | === Споредба на потрошувачка по категорија по региони изразено во количина и приход |
| | 102 | |
| | 103 | {{{#!sql |
| | 104 | create procedure SalesComparisonByCategoryAndRegionInQuantityAndIncome() |
| | 105 | language plpgsql |
| | 106 | as $$ |
| | 107 | begin |
| | 108 | with ArticleSales as ( |
| | 109 | select a.art_id, |
| | 110 | a.ctg_id, |
| | 111 | w.wh_id, |
| | 112 | c.city_id, |
| | 113 | r.region_id, |
| | 114 | p.price as unitPrice, |
| | 115 | count(au.unit_id) as unitsSold, |
| | 116 | count(au.unit_id)*p.price as income |
| | 117 | from article_unit au |
| | 118 | join unit_price up on up.unit_id = au.unit_id |
| | 119 | join price p on up.price_id = p.price_id |
| | 120 | join article a on p.art_id = a.art_id |
| | 121 | join warehouse w on au.wh_id = w.wh_id |
| | 122 | join city c on w.city_id = c.city_id |
| | 123 | join region r on c.region_id = r.region_id |
| | 124 | join orders o on au.ord_id = o.ord_id |
| | 125 | where o.ord_date>=p.price_eff_date |
| | 126 | group by a.art_id, a.ctg_id, w.wh_id, c.city_id, r.region_id, p.price, p.price |
| | 127 | ) |
| | 128 | select r.region_name as region, |
| | 129 | cat.ctg_name as category, |
| | 130 | sum(asales.unitsSold) as totalUnitsSold, |
| | 131 | sum(asales.income) as totalIncome |
| | 132 | from ArticleSales asales |
| | 133 | join category cat on asales.ctg_id=cat.ctg_id |
| | 134 | join region r on asales.region_id=r.region_id |
| | 135 | group by r.region_name, cat.ctg_name |
| | 136 | order by r.region_name, totalIncome desc; |
| | 137 | end; |
| | 138 | $$; |
| | 139 | }}} |
| | 140 | |
| | 141 | === Прогноза на потрошувачка на артикли од магацинот според продажба во претходните недели, за подобра претпоставка за набавка на нови артикли |
| | 142 | |
| | 143 | {{{#!sql |
| | 144 | create procedure SalesPrognosingOnArticlesAccToSalesInPreviousWeeks() |
| | 145 | language plpgsql |
| | 146 | as $$ |
| | 147 | begin |
| | 148 | with WeeklySales as ( |
| | 149 | select a.art_id, |
| | 150 | date_trunc('week', o.ord_date) as week_start, |
| | 151 | count(*) as units_sold |
| | 152 | from orders o |
| | 153 | join article_unit au on o.ord_id = au.ord_id |
| | 154 | join unit_price up on au.unit_id = up.unit_id |
| | 155 | join price p on up.price_id = p.price_id |
| | 156 | join article a on p.art_id = a.art_id |
| | 157 | where au.wh_id = 1 |
| | 158 | and o.ord_date between current_date - interval '10 weeks' |
| | 159 | and current_date - interval '2 week' |
| | 160 | group by a.art_id, date_trunc('week',o.ord_date) |
| | 161 | ), |
| | 162 | AvgSales as ( |
| | 163 | select art_id, |
| | 164 | avg(units_sold) as avg_units_sold |
| | 165 | from WeeklySales |
| | 166 | group by art_id |
| | 167 | ), |
| | 168 | Inventory as ( |
| | 169 | select a.art_id, count(distinct au.unit_id) as currentStock |
| | 170 | from article_unit au |
| | 171 | join warehouse w on au.wh_id = w.wh_id |
| | 172 | join unit_price up on au.unit_id = up.unit_id |
| | 173 | join price p on up.price_id = p.price_id |
| | 174 | join article a on p.art_id = a.art_id |
| | 175 | where w.wh_id = 1 |
| | 176 | group by a.art_id |
| | 177 | order by currentStock |
| | 178 | ) |
| | 179 | select i.art_id, |
| | 180 | a.art_name, |
| | 181 | i.currentStock, |
| | 182 | avs.avg_units_sold, |
| | 183 | (i.currentStock - avs.avg_units_sold) as remaining_after_forecast |
| | 184 | from Inventory i |
| | 185 | join article a on i.art_id = a.art_id |
| | 186 | join AvgSales avs on i.art_id = avs.art_id; |
| | 187 | end; |
| | 188 | $$; |
| | 189 | }}} |
| | 190 | |
| | 191 | === Продажба на артикли во одреден магацин по квартали |
| | 192 | |
| | 193 | {{{#!sql |
| | 194 | create procedure ArticleSalesInWarehouseByQuarters() |
| | 195 | language plpgsql |
| | 196 | as $$ |
| | 197 | begin |
| | 198 | select w.wh_id as warehouseId, |
| | 199 | w.wh_adr as warehouseAddress, |
| | 200 | c.city_name as warehouseCity, |
| | 201 | r.region_name as warehouseRegion, |
| | 202 | extract(year from o.ord_date) as "year", |
| | 203 | extract(quarter from o.ord_date) as "quarter", |
| | 204 | count(au.unit_id) as totalArticlesSold, |
| | 205 | sum(o.ord_sum) as totalSales |
| | 206 | from warehouse w |
| | 207 | join city c on w.city_id = c.city_id |
| | 208 | join region r on c.region_id = r.region_id |
| | 209 | join article_unit au on w.wh_id = au.wh_id |
| | 210 | join orders o on au.ord_id = o.ord_id |
| | 211 | group by w.wh_id, w.wh_adr, c.city_name, r.region_name, extract(year from o.ord_date), extract(quarter from o.ord_date) |
| | 212 | order by w.wh_id,"year","quarter"; |
| | 213 | end; |
| | 214 | $$; |
| | 215 | }}} |
| | 216 | |
| | 217 | === Годишен приход, профит и трошок по магацини |
| | 218 | |
| | 219 | {{{#!sql |
| | 220 | create procedure YearlyIncomeProfitAndExpensesByWarehouses() |
| | 221 | language plpgsql |
| | 222 | as $$ |
| | 223 | begin |
| | 224 | select w.wh_id as warehouseId, |
| | 225 | c.city_name as warehouseCity, |
| | 226 | r.region_name as warehouseRegion, |
| | 227 | extract(year from o.ord_date) as "year", |
| | 228 | sum(o.ord_sum) as totalIncome, |
| | 229 | sum(ord_sum)-sum(au.unit_cost_price) as total_profit |
| | 230 | from warehouse w |
| | 231 | join city c on w.city_id = c.city_id |
| | 232 | join region r on c.region_id = r.region_id |
| | 233 | join article_unit au on w.wh_id = au.wh_id |
| | 234 | join orders o on au.ord_id = o.ord_id |
| | 235 | group by w.wh_id, c.city_name, r.region_name, o.ord_date |
| | 236 | order by w.wh_id,"year"; |
| | 237 | end; |
| | 238 | $$; |
| | 239 | }}} |
| | 240 | |
| | 241 | === Аналитика на продукти по приход, потрошувачка, профит на определен период |
| | 242 | |
| | 243 | {{{#!sql |
| | 244 | create procedure AnalyticsOfProductsByIncomeExpensesAndProfit() |
| | 245 | language plpgsql |
| | 246 | as $$ |
| | 247 | begin |
| | 248 | with ProductSales as (select a.art_id, |
| | 249 | a.art_name, |
| | 250 | cat.ctg_name as category, |
| | 251 | sum(p.price) as totalIncome, |
| | 252 | sum(au.unit_cost_price) as totalCost, |
| | 253 | sum(p.price) - sum(au.unit_cost_price) as totalProfit, |
| | 254 | count(au.unit_id) as totalUnitsSold |
| | 255 | from article_unit au |
| | 256 | join unit_price up on up.unit_id = au.unit_id |
| | 257 | join price p on up.price_id = p.price_id |
| | 258 | join article a on p.art_id = a.art_id |
| | 259 | join category cat on a.ctg_id = cat.ctg_id |
| | 260 | join orders o on au.ord_id = o.ord_id |
| | 261 | where o.ord_date between '2024-01-01' and '2024-12-31' |
| | 262 | and o.ord_date >= p.price_eff_date |
| | 263 | group by a.art_id, a.art_name, cat.ctg_name) |
| | 264 | select art_id, |
| | 265 | art_name, |
| | 266 | category, |
| | 267 | totalUnitsSold, |
| | 268 | totalIncome, |
| | 269 | totalCost, |
| | 270 | totalProfit |
| | 271 | from ProductSales |
| | 272 | order by totalProfit desc; |
| | 273 | end; |
| | 274 | $$; |
| | 275 | }}} |
| | 276 | |
| | 277 | == Погледи |
| | 278 | |
| | 279 | === Чинење на единка артикл во моментот |
| | 280 | |
| | 281 | {{{#!sql |
| | 282 | create view article_unit_current_cost as |
| | 283 | select au.unit_id, uch.cost_price |
| | 284 | from article_unit au |
| | 285 | left join ( |
| | 286 | select unit_id, |
| | 287 | cost_price, |
| | 288 | row_number() over ( |
| | 289 | partition by unit_id order by effective_date desc |
| | 290 | ) as rn |
| | 291 | from unit_cost_history |
| | 292 | ) uch on au.unit_id = uch.unit_id and uch.rn=1; |
| | 293 | }}} |
| | 294 | |
| | 295 | === Последен сервис на возило |
| | 296 | |
| | 297 | {{{#!sql |
| | 298 | create view vehicle_latest_service as |
| | 299 | select v.veh_id, |
| | 300 | vs.service_date as last_service_date, |
| | 301 | vs.service_km as last_service_km, |
| | 302 | vs.service_next_date, |
| | 303 | vs.service_next_km |
| | 304 | from vehicle v |
| | 305 | left join ( |
| | 306 | select veh_id, |
| | 307 | service_date, |
| | 308 | service_km, |
| | 309 | servicie_next_date, |
| | 310 | service_next_km, |
| | 311 | row_number() over (partition by veh_id order by service_date desc) as rn |
| | 312 | from vehicle_service |
| | 313 | ) vs on v.veh_id = vs.veh_id and vs.rn=1; |
| | 314 | }}} |
| | 315 | |
| | 316 | == Функции и Тригери |
| | 317 | |
| | 318 | === `Update order sum` функција |
| | 319 | |
| | 320 | {{{#!sql |
| | 321 | create or replace function update_order_sum() |
| | 322 | returns trigger as $$ |
| | 323 | begin |
| | 324 | update orders |
| | 325 | set ord_sum = ( |
| | 326 | select coalesce(sum(total_price),0) |
| | 327 | from order_item |
| | 328 | where ord_id = new.ord_id) |
| | 329 | where ord_id = new.ord_id; |
| | 330 | return new; |
| | 331 | end; |
| | 332 | $$ language plpgsql; |
| | 333 | }}} |
| | 334 | |
| | 335 | ==== `Order item after change` тригер |
| | 336 | |
| | 337 | {{{#!sql |
| | 338 | create trigger order_item_after_change |
| | 339 | after insert or update or delete on order_item |
| | 340 | for each row execute function update_order_sum(); |
| | 341 | }}} |