wiki:Advanced Application Development

Report Tables

Main Restaurant Report Summary Table

CREATE TABLE restaurant_weekly_report (
    report_id SERIAL PRIMARY KEY,
    restaurant_id INT NOT NULL,
    report_week DATE NOT NULL,              -- Monday of the week
    week_number INT NOT NULL,               -- Week 1, 2, or 3 (most recent)

    -- Order Statistics
    total_orders INTEGER DEFAULT 0,
    completed_orders INTEGER DEFAULT 0,
    cancelled_orders INTEGER DEFAULT 0,

    -- Financial Metrics
    gross_revenue DECIMAL(12,2) DEFAULT 0.00,
    net_revenue DECIMAL(12,2) DEFAULT 0.00, -- After platform commission
    average_order_value DECIMAL(10,2) DEFAULT 0.00,
    total_items_sold INTEGER DEFAULT 0,

    -- Operational Metrics
    avg_preparation_time_minutes DECIMAL(8,2) DEFAULT 0.00,
    peak_hour INTEGER,                      -- Hour with most orders
    busiest_day INTEGER,                    -- Day of week (0=Sunday, 6=Saturday)

    -- Performance Indicators
    completion_rate DECIMAL(5,2) DEFAULT 0.00,       -- % of orders completed
    customer_satisfaction DECIMAL(3,2) DEFAULT 0.00, -- Placeholder for ratings
    orders_per_hour DECIMAL(8,2) DEFAULT 0.00,

    -- Timestamps
    generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_weekly_report_restaurant 
        FOREIGN KEY (restaurant_id) REFERENCES restaurant(restaurant_id),
    CONSTRAINT uq_restaurant_week 
        UNIQUE (restaurant_id, report_week)
);


Popular items report

CREATE TABLE restaurant_popular_items (
    report_id SERIAL PRIMARY KEY,
    restaurant_id INT NOT NULL,
    report_week DATE NOT NULL,
    item_id INT NOT NULL,
    item_name VARCHAR(100),
    times_ordered INTEGER DEFAULT 0,
    total_quantity INTEGER DEFAULT 0,
    total_revenue DECIMAL(10,2) DEFAULT 0.00,
    avg_price DECIMAL(8,2) DEFAULT 0.00,
    popularity_rank INTEGER,
    generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_popular_items_restaurant 
        FOREIGN KEY (restaurant_id) REFERENCES restaurant(restaurant_id),
    CONSTRAINT fk_popular_items_item 
        FOREIGN KEY (item_id) REFERENCES item(item_id)
);


Daily breakdown

CREATE TABLE restaurant_daily_breakdown (
    breakdown_id SERIAL PRIMARY KEY,
    restaurant_id INT NOT NULL,
    report_date DATE NOT NULL,
    day_of_week INTEGER,                     -- 0=Sunday, 6=Saturday
    daily_orders INTEGER DEFAULT 0,
    daily_revenue DECIMAL(10,2) DEFAULT 0.00,
    daily_items_sold INTEGER DEFAULT 0,
    avg_order_value DECIMAL(8,2) DEFAULT 0.00,

    -- Hourly breakdown (JSON format for flexibility)
    hourly_orders JSONB,
    generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_daily_breakdown_restaurant 
        FOREIGN KEY (restaurant_id) REFERENCES restaurant(restaurant_id),
    CONSTRAINT uq_restaurant_daily 
        UNIQUE (restaurant_id, report_date)
);


Comparative analysis table (week over week)

CREATE TABLE restaurant_trend_analysis (
    trend_id SERIAL PRIMARY KEY,
    restaurant_id INT NOT NULL,
    analysis_date DATE DEFAULT CURRENT_DATE,

    -- 3-week trends
    week1_revenue DECIMAL(10,2),             -- Most recent week
    week2_revenue DECIMAL(10,2),
    week3_revenue DECIMAL(10,2),
    week1_orders INTEGER,
    week2_orders INTEGER,
    week3_orders INTEGER,

    -- Trend indicators
    revenue_trend VARCHAR(20),               -- 'INCREASING', 'DECREASING', 'STABLE'
    order_trend VARCHAR(20),

    -- Growth rates
    revenue_growth_rate DECIMAL(8,2),        -- Week over week %
    order_growth_rate DECIMAL(8,2),

    -- Performance alerts
    performance_alerts JSONB,
    recommendations JSONB,
    generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    CONSTRAINT fk_trend_analysis_restaurant 
        FOREIGN KEY (restaurant_id) REFERENCES restaurant(restaurant_id)
);


Performance metrics table

CREATE TABLE performance_metrics (
    metric_id SERIAL PRIMARY KEY,
    restaurant_id INT NOT NULL,
    metric_type VARCHAR(50),
    metric_value DECIMAL(8,2),
    recorded_at TIMESTAMP,

    CONSTRAINT fk_performance_metrics_restaurant 
        FOREIGN KEY (restaurant_id) REFERENCES restaurant(restaurant_id)
);


Audit table for orders

CREATE TABLE audit_orders (
    audit_id SERIAL PRIMARY KEY,
    order_id INT,
    operation VARCHAR(50),
    new_status VARCHAR(50),
    changed_by VARCHAR(50),
    change_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);


Utility Functions

Function to get the Monday of a given week

CREATE OR REPLACE FUNCTION get_week_start(input_date DATE)
RETURNS DATE AS $$
BEGIN
    RETURN input_date - INTERVAL '1 day' * EXTRACT(DOW FROM input_date);
END;
$$ LANGUAGE plpgsql;


Function to get week number (1, 2, or 3 for last 3 weeks)

CREATE OR REPLACE FUNCTION get_week_number(input_date DATE)
RETURNS INTEGER AS $$
DECLARE
    week_start DATE;
    current_week_start DATE;
BEGIN
    week_start := get_week_start(input_date);
    current_week_start := get_week_start(CURRENT_DATE);

    RETURN CASE
        WHEN week_start = current_week_start THEN 1
        WHEN week_start = current_week_start - INTERVAL '7 days' THEN 2
        WHEN week_start = current_week_start - INTERVAL '14 days' THEN 3
        ELSE 0 -- Outside 3-week window
    END;
END;
$$ LANGUAGE plpgsql;


Main report generation function

CREATE OR REPLACE FUNCTION generate_restaurant_weekly_report(
    p_restaurant_id INT,
    p_target_date DATE DEFAULT CURRENT_DATE
)
RETURNS void AS $$
DECLARE
    week_start DATE;
    week_end DATE;
    week_num INTEGER;

    -- Variables for calculations
    v_total_orders INTEGER;
    v_completed_orders INTEGER;
    v_cancelled_orders INTEGER;
    v_gross_revenue DECIMAL(12,2);
    v_net_revenue DECIMAL(12,2);
    v_avg_order_value DECIMAL(10,2);
    v_total_items_sold INTEGER;
    v_avg_prep_time DECIMAL(8,2);
    v_peak_hour INTEGER;
    v_busiest_day INTEGER;
    v_completion_rate DECIMAL(5,2);
    v_orders_per_hour DECIMAL(8,2);

    commission_rate DECIMAL(5,4) := 0.15; -- 15% platform fee
BEGIN
    -- Calculate week boundaries
    week_start := get_week_start(p_target_date);
    week_end := week_start + INTERVAL '6 days';
    week_num := get_week_number(p_target_date);

    -- Skip if outside 3-week window
    IF week_num = 0 THEN
        RETURN;
    END IF;

    -- Calculate order statistics
    SELECT
        COUNT(*),
        COUNT(*) FILTER (WHERE order_status = 'delivered'),
        COUNT(*) FILTER (WHERE order_status = 'cancelled')
    INTO v_total_orders, v_completed_orders, v_cancelled_orders
    FROM orders
    WHERE restaurant_id = p_restaurant_id
      AND order_date::date BETWEEN week_start AND week_end;

    -- Calculate financial metrics
    SELECT
        COALESCE(SUM(total_amount), 0),
        COALESCE(SUM(total_amount * (1 - commission_rate)), 0),
        COALESCE(AVG(total_amount), 0)
    INTO v_gross_revenue, v_net_revenue, v_avg_order_value
    FROM orders
    WHERE restaurant_id = p_restaurant_id
      AND order_date::date BETWEEN week_start AND week_end
      AND order_status = 'delivered';

    -- Calculate total items sold
    SELECT COALESCE(SUM(oi.quantity), 0)
    INTO v_total_items_sold
    FROM order_items oi
    JOIN orders o ON oi.order_id = o.order_id
    WHERE o.restaurant_id = p_restaurant_id
      AND o.order_date::date BETWEEN week_start AND week_end
      AND o.order_status = 'delivered';

    -- Calculate average preparation time (if available)
    SELECT COALESCE(AVG(metric_value), 0)
    INTO v_avg_prep_time
    FROM performance_metrics pm
    WHERE pm.restaurant_id = p_restaurant_id
      AND pm.metric_type = 'prep_time_minutes'
      AND pm.recorded_at::date BETWEEN week_start AND week_end;

    -- Find peak hour
    SELECT EXTRACT(hour FROM order_date)
    INTO v_peak_hour
    FROM orders
    WHERE restaurant_id = p_restaurant_id
      AND order_date::date BETWEEN week_start AND week_end
    GROUP BY EXTRACT(hour FROM order_date)
    ORDER BY COUNT(*) DESC
    LIMIT 1;

    -- Find busiest day
    SELECT EXTRACT(dow FROM order_date)
    INTO v_busiest_day
    FROM orders
    WHERE restaurant_id = p_restaurant_id
      AND order_date::date BETWEEN week_start AND week_end
    GROUP BY EXTRACT(dow FROM order_date)
    ORDER BY COUNT(*) DESC
    LIMIT 1;

    -- Completion rate
    v_completion_rate := CASE
        WHEN v_total_orders > 0 THEN
            ROUND((v_completed_orders::DECIMAL / v_total_orders::DECIMAL) * 100, 2)
        ELSE 0
    END;

    -- Orders per hour (during operating hours)
    WITH operating_hours AS (
        SELECT EXTRACT(EPOCH FROM (closing_time - opening_time)) / 3600 * 7 AS total_hours_week
        FROM restaurant
        WHERE restaurant_id = p_restaurant_id
    )
    SELECT CASE
               WHEN oh.total_hours_week > 0 
               THEN v_total_orders::DECIMAL / oh.total_hours_week
               ELSE 0
           END
    INTO v_orders_per_hour
    FROM operating_hours oh;

    -- Insert or update weekly report
    INSERT INTO restaurant_weekly_report (
        restaurant_id, report_week, week_number,
        total_orders, completed_orders, cancelled_orders,
        gross_revenue, net_revenue, average_order_value, total_items_sold,
        avg_preparation_time_minutes, peak_hour, busiest_day,
        completion_rate, orders_per_hour, last_updated
    ) VALUES (
        p_restaurant_id, week_start, week_num,
        v_total_orders, v_completed_orders, v_cancelled_orders,
        v_gross_revenue, v_net_revenue, v_avg_order_value, v_total_items_sold,
        v_avg_prep_time, v_peak_hour, v_busiest_day,
        v_completion_rate, v_orders_per_hour, CURRENT_TIMESTAMP
    )
    ON CONFLICT (restaurant_id, report_week)
    DO UPDATE SET
        total_orders = EXCLUDED.total_orders,
        completed_orders = EXCLUDED.completed_orders,
        cancelled_orders = EXCLUDED.cancelled_orders,
        gross_revenue = EXCLUDED.gross_revenue,
        net_revenue = EXCLUDED.net_revenue,
        average_order_value = EXCLUDED.average_order_value,
        total_items_sold = EXCLUDED.total_items_sold,
        avg_preparation_time_minutes = EXCLUDED.avg_preparation_time_minutes,
        peak_hour = EXCLUDED.peak_hour,
        busiest_day = EXCLUDED.busiest_day,
        completion_rate = EXCLUDED.completion_rate,
        orders_per_hour = EXCLUDED.orders_per_hour,
        last_updated = CURRENT_TIMESTAMP;

    -- Generate supporting reports
    PERFORM generate_popular_items_report(p_restaurant_id, week_start);
    PERFORM generate_daily_breakdown_report(p_restaurant_id, week_start, week_end);
END;
$$ LANGUAGE plpgsql;


Popular items report function

CREATE OR REPLACE FUNCTION generate_popular_items_report(
    p_restaurant_id INT,
    p_week_start DATE
)
RETURNS void AS $$
BEGIN
    -- Clear existing data for this week
    DELETE FROM restaurant_popular_items
    WHERE restaurant_id = p_restaurant_id
      AND report_week = p_week_start;

    -- Insert top 10 popular items
    INSERT INTO restaurant_popular_items (
        restaurant_id, report_week, item_id, item_name,
        times_ordered, total_quantity, total_revenue, avg_price, popularity_rank
    )
    WITH popular_items AS (
        SELECT
            i.item_id,
            i.name AS item_name,
            COUNT(DISTINCT o.order_id) AS times_ordered,
            SUM(oi.quantity) AS total_quantity,
            SUM(oi.total_price) AS total_revenue,
            AVG(oi.total_price / oi.quantity) AS avg_price,
            ROW_NUMBER() OVER (ORDER BY COUNT(DISTINCT o.order_id) DESC) AS popularity_rank
        FROM orders o
        JOIN order_items oi ON o.order_id = oi.order_id
        JOIN item i ON oi.item_id = i.item_id
        WHERE o.restaurant_id = p_restaurant_id
          AND o.order_date::date BETWEEN p_week_start AND p_week_start + INTERVAL '6 days'
          AND o.order_status = 'delivered'
        GROUP BY i.item_id, i.name
        ORDER BY times_ordered DESC
        LIMIT 10
    )
    SELECT
        p_restaurant_id, p_week_start, item_id, item_name,
        times_ordered, total_quantity, total_revenue, avg_price, popularity_rank
    FROM popular_items;
END;
$$ LANGUAGE plpgsql;


Trend analysis function

CREATE OR REPLACE FUNCTION generate_trend_analysis(p_restaurant_id INT)
RETURNS void AS $$
DECLARE
    week1_start DATE := get_week_start(CURRENT_DATE);
    week2_start DATE := week1_start - INTERVAL '7 days';
    week3_start DATE := week1_start - INTERVAL '14 days';

    w1_revenue DECIMAL(10,2);
    w2_revenue DECIMAL(10,2);
    w3_revenue DECIMAL(10,2);
    w1_orders INTEGER;
    w2_orders INTEGER;
    w3_orders INTEGER;

    revenue_trend_val VARCHAR(20);
    order_trend_val VARCHAR(20);
    revenue_growth DECIMAL(8,2);
    order_growth DECIMAL(8,2);

    alerts JSONB := '[]'::jsonb;
    recommendations JSONB := '[]'::jsonb;
BEGIN
    -- Get weekly data
    SELECT gross_revenue, total_orders
    INTO w1_revenue, w1_orders
    FROM restaurant_weekly_report
    WHERE restaurant_id = p_restaurant_id AND report_week = week1_start;

    SELECT gross_revenue, total_orders
    INTO w2_revenue, w2_orders
    FROM restaurant_weekly_report
    WHERE restaurant_id = p_restaurant_id AND report_week = week2_start;

    SELECT gross_revenue, total_orders
    INTO w3_revenue, w3_orders
    FROM restaurant_weekly_report
    WHERE restaurant_id = p_restaurant_id AND report_week = week3_start;

    -- Growth calculations
    IF w2_revenue > 0 THEN
        revenue_growth := ROUND(((w1_revenue - w2_revenue) / w2_revenue) * 100, 2);
    ELSE
        revenue_growth := 0;
    END IF;

    IF w2_orders > 0 THEN
        order_growth := ROUND(((w1_orders - w2_orders)::DECIMAL / w2_orders::DECIMAL) * 100, 2);
    ELSE
        order_growth := 0;
    END IF;

    -- Trend direction
    revenue_trend_val := CASE
        WHEN revenue_growth > 5 THEN 'INCREASING'
        WHEN revenue_growth < -5 THEN 'DECREASING'
        ELSE 'STABLE'
    END;

    order_trend_val := CASE
        WHEN order_growth > 5 THEN 'INCREASING'
        WHEN order_growth < -5 THEN 'DECREASING'
        ELSE 'STABLE'
    END;

    -- Alerts
    IF revenue_growth < -20 THEN
        alerts := alerts || '{"type":"revenue_decline","severity":"high","message":"Revenue declined by more than 20% this week"}'::jsonb;
    END IF;

    IF order_growth < -15 THEN
        alerts := alerts || '{"type":"order_decline","severity":"medium","message":"Order volume declined by more than 15% this week"}'::jsonb;
    END IF;

    -- Recommendations
    IF revenue_trend_val = 'DECREASING' THEN
        recommendations := recommendations || '{"type":"marketing","action":"Consider promotional campaigns or menu updates"}'::jsonb;
    END IF;

    IF order_trend_val = 'DECREASING' THEN
        recommendations := recommendations || '{"type":"operations","action":"Review menu pricing and delivery times"}'::jsonb;
    END IF;

    -- Insert or update
    INSERT INTO restaurant_trend_analysis (
        restaurant_id, week1_revenue, week2_revenue, week3_revenue,
        week1_orders, week2_orders, week3_orders,
        revenue_trend, order_trend, revenue_growth_rate, order_growth_rate,
        performance_alerts, recommendations
    ) VALUES (
        p_restaurant_id, w1_revenue, w2_revenue, w3_revenue,
        w1_orders, w2_orders, w3_orders,
        revenue_trend_val, order_trend_val, revenue_growth, order_growth,
        alerts, recommendations
    )
    ON CONFLICT (restaurant_id)
    DO UPDATE SET
        week1_revenue = EXCLUDED.week1_revenue,
        week2_revenue = EXCLUDED.week2_revenue,
        week3_revenue = EXCLUDED.week3_revenue,
        week1_orders = EXCLUDED.week1_orders,
        week2_orders = EXCLUDED.week2_orders,
        week3_orders = EXCLUDED.week3_orders,
        revenue_trend = EXCLUDED.revenue_trend,
        order_trend = EXCLUDED.order_trend,
        revenue_growth_rate = EXCLUDED.revenue_growth_rate,
        order_growth_rate = EXCLUDED.order_growth_rate,
        performance_alerts = EXCLUDED.performance_alerts,
        recommendations = EXCLUDED.recommendations,
        generated_at = CURRENT_TIMESTAMP;
END;
$$ LANGUAGE plpgsql;


Trigger Functions

Trigger function to update reports when orders change

CREATE OR REPLACE FUNCTION update_restaurant_reports()
RETURNS TRIGGER AS $$
DECLARE
    affected_restaurant_id INT;
    order_date_to_check DATE;
BEGIN
    -- Determine which restaurant and date to update
    affected_restaurant_id := COALESCE(NEW.restaurant_id, OLD.restaurant_id);
    order_date_to_check := COALESCE(NEW.order_date::date, OLD.order_date::date);

    -- Only update if the order is within the last 3 weeks
    IF order_date_to_check >= CURRENT_DATE - INTERVAL '21 days' THEN
        -- Update reports for the affected week
        PERFORM generate_restaurant_weekly_report(affected_restaurant_id, order_date_to_check);
        -- Update trend analysis
        PERFORM generate_trend_analysis(affected_restaurant_id);

        -- Log the report update
        INSERT INTO audit_orders (order_id, operation, new_status, changed_by)
        VALUES (
            COALESCE(NEW.order_id, OLD.order_id),
            'REPORT_UPDATE',
            'Restaurant report updated',
            'system'
        );
    END IF;

    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;


Trigger function for order items changes

CREATE OR REPLACE FUNCTION update_reports_on_items_change()
RETURNS TRIGGER AS $$
DECLARE
    affected_restaurant_id INT;
    order_date_to_check DATE;
BEGIN
    -- Get restaurant and date from the order
    SELECT restaurant_id, order_date::date 
    INTO affected_restaurant_id, order_date_to_check
    FROM orders 
    WHERE order_id = COALESCE(NEW.order_id, OLD.order_id);

    -- Only update if within the last 3 weeks
    IF order_date_to_check >= CURRENT_DATE - INTERVAL '21 days' THEN
        PERFORM generate_restaurant_weekly_report(affected_restaurant_id, order_date_to_check);
        PERFORM generate_trend_analysis(affected_restaurant_id);
    END IF;

    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;


Create triggers

-- Trigger on orders table
CREATE TRIGGER trg_update_restaurant_reports
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
EXECUTE FUNCTION update_restaurant_reports();

-- Trigger on order_items table
CREATE TRIGGER trg_update_reports_items
AFTER INSERT OR UPDATE OR DELETE ON order_items
FOR EACH ROW
EXECUTE FUNCTION update_reports_on_items_change();


Report Views

-- Comprehensive 3-week restaurant report view
CREATE VIEW restaurant_3week_summary AS
SELECT
    r.name as restaurant_name,
    rwr.restaurant_id,

    -- Current week (Week 1)
    MAX(CASE WHEN rwr.week_number = 1 THEN rwr.total_orders END) as current_week_orders,
    MAX(CASE WHEN rwr.week_number = 1 THEN rwr.gross_revenue END) as current_week_revenue,
    MAX(CASE WHEN rwr.week_number = 1 THEN rwr.completion_rate END) as current_week_completion_rate,

    -- Previous week (Week 2)
    MAX(CASE WHEN rwr.week_number = 2 THEN rwr.total_orders END) as prev_week_orders,
    MAX(CASE WHEN rwr.week_number = 2 THEN rwr.gross_revenue END) as prev_week_revenue,

    -- 3 weeks ago (Week 3)
    MAX(CASE WHEN rwr.week_number = 3 THEN rwr.total_orders END) as week3_orders,
    MAX(CASE WHEN rwr.week_number = 3 THEN rwr.gross_revenue END) as week3_revenue,

    -- Totals
    SUM(rwr.total_orders) as total_orders_3weeks,
    SUM(rwr.gross_revenue) as total_revenue_3weeks,
    AVG(rwr.average_order_value) as avg_order_value_3weeks,
    AVG(rwr.completion_rate) as avg_completion_rate,

    -- Operational insights
    MAX(CASE WHEN rwr.week_number = 1 THEN rwr.peak_hour END) as current_peak_hour,
    MAX(CASE WHEN rwr.week_number = 1 THEN rwr.busiest_day END) as current_busiest_day,
    AVG(rwr.avg_preparation_time_minutes) as avg_prep_time,
    MAX(rwr.last_updated) as last_updated
FROM restaurant_weekly_report rwr
JOIN restaurant r ON rwr.restaurant_id = r.restaurant_id
WHERE rwr.report_week >= CURRENT_DATE - INTERVAL '21 days'
GROUP BY r.name, rwr.restaurant_id;
-- Top performing restaurants view
CREATE VIEW top_restaurants_3weeks AS
SELECT
    r.name as restaurant_name,
    SUM(rwr.gross_revenue) as total_revenue,
    SUM(rwr.total_orders) as total_orders,
    AVG(rwr.completion_rate) as avg_completion_rate,
    AVG(rwr.average_order_value) as avg_order_value,
    rta.revenue_trend,
    rta.revenue_growth_rate
FROM restaurant_weekly_report rwr
JOIN restaurant r ON rwr.restaurant_id = r.restaurant_id
LEFT JOIN restaurant_trend_analysis rta ON rwr.restaurant_id = rta.restaurant_id
WHERE rwr.report_week >= CURRENT_DATE - INTERVAL '21 days'
GROUP BY r.name, rta.revenue_trend, rta.revenue_growth_rate
ORDER BY total_revenue DESC;

Daily reports function

CREATE OR REPLACE FUNCTION generate_daily_breakdown_report(
    p_restaurant_id INT,
    p_week_start DATE,
    p_week_end DATE
) RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
    v_day DATE := p_week_start;
    v_daily_orders INTEGER := 0;
    v_daily_revenue NUMERIC(12,2) := 0;
    v_daily_items_sold INTEGER := 0;
    v_avg_order_value NUMERIC(12,2) := 0;
    v_hourly_orders JSONB := '{}'::jsonb;
BEGIN
    IF p_week_end < p_week_start THEN
        RAISE EXCEPTION 'p_week_end (%) must be >= p_week_start (%)', p_week_end, p_week_start;
    END IF;

    -- Clear previous data
    DELETE FROM restaurant_daily_breakdown
    WHERE restaurant_id = p_restaurant_id
      AND report_date BETWEEN p_week_start AND p_week_end;

    -- Loop through days
    WHILE v_day <= p_week_end LOOP
        -- Daily metrics
        SELECT
            COUNT(*),
            COALESCE(SUM(total_amount), 0)::NUMERIC(12,2),
            COALESCE(AVG(total_amount), 0)::NUMERIC(12,2)
        INTO v_daily_orders, v_daily_revenue, v_avg_order_value
        FROM orders
        WHERE restaurant_id = p_restaurant_id
          AND order_date::date = v_day
          AND order_status = 'delivered';

        -- Daily items sold
        SELECT COALESCE(SUM(oi.quantity), 0)
        INTO v_daily_items_sold
        FROM order_items oi
        JOIN orders o ON oi.order_id = o.order_id
        WHERE o.restaurant_id = p_restaurant_id
          AND o.order_date::date = v_day
          AND o.order_status = 'delivered';

        -- Hourly order breakdown (as JSONB)
        SELECT COALESCE(jsonb_object_agg(order_hour::text, order_count), '{}'::jsonb)
        INTO v_hourly_orders
        FROM (
            SELECT
                EXTRACT(hour FROM order_date) AS order_hour,
                COUNT(*) AS order_count
            FROM orders
            WHERE restaurant_id = p_restaurant_id
              AND order_date::date = v_day
              AND order_status = 'delivered'
            GROUP BY 1
        ) AS hourly_counts;

        -- Insert daily breakdown
        INSERT INTO restaurant_daily_breakdown (
            restaurant_id,
            report_date,
            day_of_week,
            daily_orders,
            daily_revenue,
            daily_items_sold,
            avg_order_value,
            hourly_orders
        ) VALUES (
            p_restaurant_id,
            v_day,
            EXTRACT(DOW FROM v_day),
            v_daily_orders,
            v_daily_revenue,
            v_daily_items_sold,
            v_avg_order_value,
            v_hourly_orders
        );

        -- Next day
        v_day := v_day + 1;
    END LOOP;
END;
$$;
ALTER TABLE restaurant_trend_analysis
ADD CONSTRAINT uq_restaurant_id UNIQUE (restaurant_id);
Last modified 4 weeks ago Last modified on 09/18/25 16:11:07
Note: See TracWiki for help on using the wiki.