| | 1 | = Прогноза за потрошувачка на состојки врз основа на продажби = |
| | 2 | |
| | 3 | == Опис == |
| | 4 | |
| | 5 | Извештајот предвидува колку од секоја состојка ќе биде потрошена во следните 30 дена, врз основа на просечната дневна потрошувачка во последните 90 дена. Користи три нивоа на вгнездување: (1) продажби по производ по ден, (2) агрегација по состојка преку рецепт, (3) споредба со моменталната залиха за да се најдат состојки што ќе треба наскоро да се нарачаат. |
| | 6 | |
| | 7 | == SQL решение == |
| | 8 | |
| | 9 | {{{ |
| | 10 | CREATE OR REPLACE FUNCTION project.get_ingredient_consumption_forecast() |
| | 11 | RETURNS TABLE ( |
| | 12 | ingredient_id INT, |
| | 13 | ingredient_name TEXT, |
| | 14 | avg_daily_usage NUMERIC, |
| | 15 | forecast_30d NUMERIC, |
| | 16 | current_stock NUMERIC, |
| | 17 | days_until_empty NUMERIC, |
| | 18 | action TEXT |
| | 19 | ) |
| | 20 | LANGUAGE sql |
| | 21 | AS $$ |
| | 22 | WITH daily_product_sales AS ( |
| | 23 | SELECT |
| | 24 | oi.product_id, |
| | 25 | DATE(o.created_at) AS sale_date, |
| | 26 | SUM(oi.quantity) AS qty_sold |
| | 27 | FROM project.order_item oi |
| | 28 | JOIN project.orders o ON o.order_id = oi.order_id |
| | 29 | WHERE o.status = 'ПЛАТЕНА' |
| | 30 | AND o.created_at >= NOW() - INTERVAL '90 days' |
| | 31 | GROUP BY oi.product_id, DATE(o.created_at) |
| | 32 | ), |
| | 33 | ingredient_usage AS ( |
| | 34 | SELECT |
| | 35 | ri.ingredient_id AS ing_id, |
| | 36 | SUM(dps.qty_sold * ri.quantity_needed)::numeric AS total_used |
| | 37 | FROM daily_product_sales dps |
| | 38 | JOIN project.recipe r ON r.product_id = dps.product_id |
| | 39 | JOIN project.recipe_item ri ON ri.recipe_id = r.recipe_id |
| | 40 | GROUP BY ri.ingredient_id |
| | 41 | ), |
| | 42 | stock AS ( |
| | 43 | SELECT |
| | 44 | ii.ingredient_id AS ing_id, |
| | 45 | COALESCE(SUM(ii.quantity_change), 0)::numeric AS current_stock |
| | 46 | FROM project.ingredient_inventory ii |
| | 47 | GROUP BY ii.ingredient_id |
| | 48 | ) |
| | 49 | SELECT |
| | 50 | i.ingredient_id, |
| | 51 | i.name::TEXT, |
| | 52 | ROUND(iu.total_used / 90.0, 3), |
| | 53 | ROUND((iu.total_used / 90.0) * 30, 2), |
| | 54 | COALESCE(s.current_stock, 0), |
| | 55 | CASE |
| | 56 | WHEN (iu.total_used / 90.0) > 0 |
| | 57 | THEN ROUND(COALESCE(s.current_stock, 0) / (iu.total_used / 90.0), 1) |
| | 58 | ELSE NULL |
| | 59 | END, |
| | 60 | CASE |
| | 61 | WHEN COALESCE(s.current_stock, 0) < (iu.total_used / 90.0) * 30 |
| | 62 | AND i.min_stock > COALESCE(s.current_stock, 0) THEN 'ИТНО НАРАЧАЈ' |
| | 63 | WHEN COALESCE(s.current_stock, 0) < (iu.total_used / 90.0) * 30 THEN 'НАРАЧАЈ' |
| | 64 | ELSE 'ОК' |
| | 65 | END::TEXT |
| | 66 | FROM project.ingredient i |
| | 67 | JOIN ingredient_usage iu ON iu.ing_id = i.ingredient_id |
| | 68 | LEFT JOIN stock s ON s.ing_id = i.ingredient_id |
| | 69 | WHERE i.active = TRUE |
| | 70 | ORDER BY 6 ASC NULLS LAST; |
| | 71 | $$; |
| | 72 | }}} |
| | 73 | |
| | 74 | == Релациона алгебра == |
| | 75 | |
| | 76 | {{{ |
| | 77 | OI(order_id, product_id, quantity, unit_price) |
| | 78 | O(order_id, status, created_at) |
| | 79 | R(recipe_id, product_id) |
| | 80 | RI(recipe_id, ingredient_id, quantity_needed) |
| | 81 | I(ingredient_id, name, min_stock, active) |
| | 82 | II(id, ingredient_id, quantity_change) |
| | 83 | |
| | 84 | J1 ← OI ⨝ OI.order_id = O.order_id O |
| | 85 | F1 ← σ status='ПЛАТЕНА' ∧ created_at ≥ NOW() - INTERVAL '90 days' (J1) |
| | 86 | |
| | 87 | G1 ← γ product_id, sale_date; SUM(quantity) → qty_sold (F1) |
| | 88 | |
| | 89 | J2 ← G1 ⨝ G1.product_id = R.product_id R |
| | 90 | J3 ← J2 ⨝ R.recipe_id = RI.recipe_id RI |
| | 91 | G2 ← γ ingredient_id; SUM(qty_sold * quantity_needed) → total_used (J3) |
| | 92 | |
| | 93 | G3 ← γ ingredient_id; COALESCE(SUM(quantity_change), 0) → current_stock (II) |
| | 94 | |
| | 95 | J4 ← I ⨝ G2 ⨝ G3 |
| | 96 | F2 ← σ active=TRUE (J4) |
| | 97 | |
| | 98 | R ← π ingredient_id, ingredient_name, avg_daily_usage, forecast_30d, |
| | 99 | current_stock, days_until_empty, action (F2) |
| | 100 | |
| | 101 | R_final ← τ days_until_empty ASC (R) |
| | 102 | }}} |