165 lines
9.2 KiB
SQL
165 lines
9.2 KiB
SQL
-- 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(5, 2), -- (profit_30d / revenue_30d) * 100
|
|
markup_30d NUMERIC(5, 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(5, 2), -- returns_units_30d / (sales_30d + returns_units_30d) * 100
|
|
discount_rate_30d NUMERIC(5, 2), -- discounts_30d / gross_revenue_30d * 100
|
|
stockout_rate_30d NUMERIC(5, 2), -- stockout_days_30d / 30.0 * 100
|
|
markdown_30d NUMERIC(14, 4), -- gross_regular_revenue_30d - gross_revenue_30d
|
|
markdown_rate_30d NUMERIC(5, 2), -- markdown_30d / gross_regular_revenue_30d * 100
|
|
sell_through_30d NUMERIC(5, 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 |