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 | }}} |