-- Drop tables in reverse order of dependency DROP TABLE IF EXISTS public.product_metrics CASCADE; DROP TABLE IF EXISTS public.daily_product_snapshots CASCADE; -- Table Definition: daily_product_snapshots CREATE TABLE public.daily_product_snapshots ( snapshot_date DATE NOT NULL, pid INT8 NOT NULL, sku VARCHAR, -- Copied for convenience -- Inventory Metrics (End of Day / Last Snapshot of Day) eod_stock_quantity INT NOT NULL DEFAULT 0, eod_stock_cost NUMERIC(14, 4) NOT NULL DEFAULT 0.00, -- Increased precision eod_stock_retail NUMERIC(14, 4) NOT NULL DEFAULT 0.00, eod_stock_gross NUMERIC(14, 4) NOT NULL DEFAULT 0.00, stockout_flag BOOLEAN NOT NULL DEFAULT FALSE, -- Sales Metrics (Aggregated for the snapshot_date) units_sold INT NOT NULL DEFAULT 0, units_returned INT NOT NULL DEFAULT 0, gross_revenue NUMERIC(14, 4) NOT NULL DEFAULT 0.00, discounts NUMERIC(14, 4) NOT NULL DEFAULT 0.00, returns_revenue NUMERIC(14, 4) NOT NULL DEFAULT 0.00, net_revenue NUMERIC(14, 4) NOT NULL DEFAULT 0.00, -- gross_revenue - discounts cogs NUMERIC(14, 4) NOT NULL DEFAULT 0.00, gross_regular_revenue NUMERIC(14, 4) NOT NULL DEFAULT 0.00, profit NUMERIC(14, 4) NOT NULL DEFAULT 0.00, -- net_revenue - cogs -- Receiving Metrics (Aggregated for the snapshot_date) units_received INT NOT NULL DEFAULT 0, cost_received NUMERIC(14, 4) NOT NULL DEFAULT 0.00, calculation_timestamp TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (snapshot_date, pid) -- Composite primary key -- CONSTRAINT fk_daily_snapshot_pid FOREIGN KEY (pid) REFERENCES public.products(pid) ON DELETE CASCADE ON UPDATE CASCADE -- FK Optional on snapshot table ); -- Add Indexes for daily_product_snapshots CREATE INDEX idx_daily_snapshot_pid_date ON public.daily_product_snapshots(pid, snapshot_date); -- Useful for product-specific time series -- Table Definition: product_metrics CREATE TABLE public.product_metrics ( pid INT8 PRIMARY KEY, last_calculated TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Product Info (Copied for convenience/performance) sku VARCHAR, title VARCHAR, brand VARCHAR, vendor VARCHAR, image_url VARCHAR, -- (e.g., products.image_175) is_visible BOOLEAN, is_replenishable BOOLEAN, -- Current Status (Refreshed Hourly) current_price NUMERIC(10, 2), current_regular_price NUMERIC(10, 2), current_cost_price NUMERIC(10, 4), -- Increased precision for cost current_landing_cost_price NUMERIC(10, 4), -- Increased precision for cost current_stock INT NOT NULL DEFAULT 0, current_stock_cost NUMERIC(14, 4) NOT NULL DEFAULT 0.00, current_stock_retail NUMERIC(14, 4) NOT NULL DEFAULT 0.00, current_stock_gross NUMERIC(14, 4) NOT NULL DEFAULT 0.00, on_order_qty INT NOT NULL DEFAULT 0, on_order_cost NUMERIC(14, 4) NOT NULL DEFAULT 0.00, on_order_retail NUMERIC(14, 4) NOT NULL DEFAULT 0.00, earliest_expected_date DATE, -- total_received_lifetime INT NOT NULL DEFAULT 0, -- Can calc if needed -- Historical Dates (Calculated Once/Periodically) date_created DATE, date_first_received DATE, date_last_received DATE, date_first_sold DATE, date_last_sold DATE, age_days INT, -- Calculated based on LEAST(date_created, date_first_sold) -- Rolling Period Metrics (Refreshed Hourly from daily_product_snapshots) sales_7d INT, revenue_7d NUMERIC(14, 4), sales_14d INT, revenue_14d NUMERIC(14, 4), sales_30d INT, revenue_30d NUMERIC(14, 4), cogs_30d NUMERIC(14, 4), profit_30d NUMERIC(14, 4), returns_units_30d INT, returns_revenue_30d NUMERIC(14, 4), discounts_30d NUMERIC(14, 4), gross_revenue_30d NUMERIC(14, 4), gross_regular_revenue_30d NUMERIC(14, 4), stockout_days_30d INT, sales_365d INT, revenue_365d NUMERIC(14, 4), avg_stock_units_30d NUMERIC(10, 2), avg_stock_cost_30d NUMERIC(14, 4), avg_stock_retail_30d NUMERIC(14, 4), avg_stock_gross_30d NUMERIC(14, 4), received_qty_30d INT, received_cost_30d NUMERIC(14, 4), -- Lifetime Metrics (Recalculated Hourly/Daily from daily_product_snapshots) lifetime_sales INT, lifetime_revenue NUMERIC(16, 4), -- First Period Metrics (Calculated Once/Periodically from daily_product_snapshots) first_7_days_sales INT, first_7_days_revenue NUMERIC(14, 4), first_30_days_sales INT, first_30_days_revenue NUMERIC(14, 4), first_60_days_sales INT, first_60_days_revenue NUMERIC(14, 4), first_90_days_sales INT, first_90_days_revenue NUMERIC(14, 4), -- Calculated KPIs (Refreshed Hourly based on rolling metrics) asp_30d NUMERIC(10, 2), -- revenue_30d / sales_30d acp_30d NUMERIC(10, 4), -- cogs_30d / sales_30d avg_ros_30d NUMERIC(10, 4), -- profit_30d / sales_30d avg_sales_per_day_30d NUMERIC(10, 2), -- sales_30d / 30.0 avg_sales_per_month_30d NUMERIC(10, 2), -- sales_30d (assuming 30d = 1 month for this metric) margin_30d NUMERIC(8, 2), -- (profit_30d / revenue_30d) * 100 markup_30d NUMERIC(8, 2), -- (profit_30d / cogs_30d) * 100 gmroi_30d NUMERIC(10, 2), -- profit_30d / avg_stock_cost_30d stockturn_30d NUMERIC(10, 2), -- sales_30d / avg_stock_units_30d return_rate_30d NUMERIC(8, 2), -- returns_units_30d / (sales_30d + returns_units_30d) * 100 discount_rate_30d NUMERIC(8, 2), -- discounts_30d / gross_revenue_30d * 100 stockout_rate_30d NUMERIC(8, 2), -- stockout_days_30d / 30.0 * 100 markdown_30d NUMERIC(14, 4), -- gross_regular_revenue_30d - gross_revenue_30d markdown_rate_30d NUMERIC(8, 2), -- markdown_30d / gross_regular_revenue_30d * 100 sell_through_30d NUMERIC(8, 2), -- sales_30d / (current_stock + sales_30d) * 100 avg_lead_time_days INT, -- Calculated Periodically from purchase_orders -- Forecasting & Replenishment (Refreshed Hourly) abc_class CHAR(1), -- Updated Periodically (e.g., Weekly) sales_velocity_daily NUMERIC(10, 4), -- sales_30d / (30.0 - stockout_days_30d) config_lead_time INT, -- From settings tables config_days_of_stock INT, -- From settings tables config_safety_stock INT, -- From settings_product planning_period_days INT, -- config_lead_time + config_days_of_stock lead_time_forecast_units NUMERIC(10, 2), -- sales_velocity_daily * config_lead_time days_of_stock_forecast_units NUMERIC(10, 2), -- sales_velocity_daily * config_days_of_stock planning_period_forecast_units NUMERIC(10, 2), -- lead_time_forecast_units + days_of_stock_forecast_units lead_time_closing_stock NUMERIC(10, 2), -- current_stock + on_order_qty - lead_time_forecast_units days_of_stock_closing_stock NUMERIC(10, 2), -- lead_time_closing_stock - days_of_stock_forecast_units replenishment_needed_raw NUMERIC(10, 2), -- planning_period_forecast_units + config_safety_stock - current_stock - on_order_qty replenishment_units INT, -- CEILING(GREATEST(0, replenishment_needed_raw)) replenishment_cost NUMERIC(14, 4), -- replenishment_units * COALESCE(current_landing_cost_price, current_cost_price) replenishment_retail NUMERIC(14, 4), -- replenishment_units * current_price replenishment_profit NUMERIC(14, 4), -- replenishment_units * (current_price - COALESCE(current_landing_cost_price, current_cost_price)) to_order_units INT, -- Apply MOQ/UOM logic to replenishment_units forecast_lost_sales_units NUMERIC(10, 2), -- GREATEST(0, -lead_time_closing_stock) forecast_lost_revenue NUMERIC(14, 4), -- forecast_lost_sales_units * current_price stock_cover_in_days NUMERIC(10, 1), -- current_stock / sales_velocity_daily po_cover_in_days NUMERIC(10, 1), -- on_order_qty / sales_velocity_daily sells_out_in_days NUMERIC(10, 1), -- (current_stock + on_order_qty) / sales_velocity_daily replenish_date DATE, -- Calc based on when stock hits safety stock minus lead time overstocked_units INT, -- GREATEST(0, current_stock - config_safety_stock - planning_period_forecast_units) overstocked_cost NUMERIC(14, 4), -- overstocked_units * COALESCE(current_landing_cost_price, current_cost_price) overstocked_retail NUMERIC(14, 4), -- overstocked_units * current_price is_old_stock BOOLEAN, -- Based on age, last sold, last received, on_order status -- Yesterday's Metrics (Refreshed Hourly from daily_product_snapshots) yesterday_sales INT, CONSTRAINT fk_product_metrics_pid FOREIGN KEY (pid) REFERENCES public.products(pid) ON DELETE CASCADE ON UPDATE CASCADE ); -- Add Indexes for product_metrics (adjust based on common filtering/sorting in frontend) CREATE INDEX idx_product_metrics_brand ON public.product_metrics(brand); CREATE INDEX idx_product_metrics_vendor ON public.product_metrics(vendor); CREATE INDEX idx_product_metrics_sku ON public.product_metrics(sku); CREATE INDEX idx_product_metrics_abc_class ON public.product_metrics(abc_class); CREATE INDEX idx_product_metrics_revenue_30d ON public.product_metrics(revenue_30d DESC NULLS LAST); -- Example sorting index CREATE INDEX idx_product_metrics_sales_30d ON public.product_metrics(sales_30d DESC NULLS LAST); -- Example sorting index CREATE INDEX idx_product_metrics_current_stock ON public.product_metrics(current_stock); CREATE INDEX idx_product_metrics_sells_out_in_days ON public.product_metrics(sells_out_in_days ASC NULLS LAST); -- Example sorting index