wiki:AdvancedReport9

Version 1 (modified by 201178, 5 days ago) ( diff )

--

Прогноза за потрошувачка на состојки врз основа на продажби

Опис

Извештајот предвидува колку од секоја состојка ќе биде потрошена во следните 30 дена, врз основа на просечната дневна потрошувачка во последните 90 дена. Користи три нивоа на вгнездување: (1) продажби по производ по ден, (2) агрегација по состојка преку рецепт, (3) споредба со моменталната залиха за да се најдат состојки што ќе треба наскоро да се нарачаат.

SQL решение

CREATE OR REPLACE FUNCTION project.get_ingredient_consumption_forecast()
RETURNS TABLE (
    ingredient_id INT,
    ingredient_name TEXT,
    avg_daily_usage NUMERIC,
    forecast_30d NUMERIC,
    current_stock NUMERIC,
    days_until_empty NUMERIC,
    action TEXT
)
LANGUAGE sql
AS $$
    WITH daily_product_sales AS (
        SELECT 
            oi.product_id,
            DATE(o.created_at) AS sale_date,
            SUM(oi.quantity) AS qty_sold
        FROM project.order_item oi
        JOIN project.orders o ON o.order_id = oi.order_id
        WHERE o.status = 'ПЛАТЕНА'
          AND o.created_at >= NOW() - INTERVAL '90 days'
        GROUP BY oi.product_id, DATE(o.created_at)
    ),
    ingredient_usage AS (
        SELECT 
            ri.ingredient_id AS ing_id,
            SUM(dps.qty_sold * ri.quantity_needed)::numeric AS total_used
        FROM daily_product_sales dps
        JOIN project.recipe r ON r.product_id = dps.product_id
        JOIN project.recipe_item ri ON ri.recipe_id = r.recipe_id
        GROUP BY ri.ingredient_id
    ),
    stock AS (
        SELECT 
            ii.ingredient_id AS ing_id,
            COALESCE(SUM(ii.quantity_change), 0)::numeric AS current_stock
        FROM project.ingredient_inventory ii
        GROUP BY ii.ingredient_id
    )
    SELECT 
        i.ingredient_id,
        i.name::TEXT,
        ROUND(iu.total_used / 90.0, 3),
        ROUND((iu.total_used / 90.0) * 30, 2),
        COALESCE(s.current_stock, 0),
        CASE 
            WHEN (iu.total_used / 90.0) > 0 
            THEN ROUND(COALESCE(s.current_stock, 0) / (iu.total_used / 90.0), 1)
            ELSE NULL
        END,
        CASE 
            WHEN COALESCE(s.current_stock, 0) < (iu.total_used / 90.0) * 30 
                 AND i.min_stock > COALESCE(s.current_stock, 0) THEN 'ИТНО НАРАЧАЈ'
            WHEN COALESCE(s.current_stock, 0) < (iu.total_used / 90.0) * 30 THEN 'НАРАЧАЈ'
            ELSE 'ОК'
        END::TEXT
    FROM project.ingredient i
    JOIN ingredient_usage iu ON iu.ing_id = i.ingredient_id
    LEFT JOIN stock s ON s.ing_id = i.ingredient_id
    WHERE i.active = TRUE
    ORDER BY 6 ASC NULLS LAST;
$$;

Релациона алгебра

OI(order_id, product_id, quantity, unit_price)
O(order_id, status, created_at)
R(recipe_id, product_id)
RI(recipe_id, ingredient_id, quantity_needed)
I(ingredient_id, name, min_stock, active)
II(id, ingredient_id, quantity_change)

J1 ← OI ⨝ OI.order_id = O.order_id O
F1 ← σ status='ПЛАТЕНА' ∧ created_at ≥ NOW() - INTERVAL '90 days' (J1)

G1 ← γ product_id, sale_date; SUM(quantity) → qty_sold (F1)

J2 ← G1 ⨝ G1.product_id = R.product_id R
J3 ← J2 ⨝ R.recipe_id = RI.recipe_id RI
G2 ← γ ingredient_id; SUM(qty_sold * quantity_needed) → total_used (J3)

G3 ← γ ingredient_id; COALESCE(SUM(quantity_change), 0) → current_stock (II)

J4 ← I ⨝ G2 ⨝ G3
F2 ← σ active=TRUE (J4)

R ← π ingredient_id, ingredient_name, avg_daily_usage, forecast_30d,
      current_stock, days_until_empty, action (F2)

R_final ← τ days_until_empty ASC (R)
Note: See TracWiki for help on using the wiki.