Clean up inventory overview page
This commit is contained in:
@@ -61,16 +61,72 @@ BEGIN
|
||||
p.uom -- Assuming UOM logic is handled elsewhere or simple (e.g., 1=each)
|
||||
FROM public.products p
|
||||
),
|
||||
-- Stale POs: open, >90 days past expected, AND a newer PO exists for the same product.
|
||||
-- These are likely abandoned/superseded and should not consume receivings in FIFO.
|
||||
StalePOLines AS (
|
||||
SELECT po.po_id, po.pid
|
||||
FROM public.purchase_orders po
|
||||
WHERE po.status IN ('created', 'ordered', 'preordered', 'electronically_sent',
|
||||
'electronically_ready_send', 'receiving_started')
|
||||
AND po.expected_date IS NOT NULL
|
||||
AND po.expected_date < _current_date - INTERVAL '90 days'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM public.purchase_orders newer
|
||||
WHERE newer.pid = po.pid
|
||||
AND newer.status NOT IN ('canceled', 'done')
|
||||
AND COALESCE(newer.date_ordered, newer.date_created)
|
||||
> COALESCE(po.date_ordered, po.date_created)
|
||||
)
|
||||
),
|
||||
-- All non-canceled, non-stale POs in FIFO order per (pid, supplier).
|
||||
-- Includes closed ('done') POs so they consume receivings before open POs.
|
||||
POFifo AS (
|
||||
SELECT
|
||||
po.pid, po.supplier_id, po.po_id, po.ordered, po.status,
|
||||
po.po_cost_price, po.expected_date,
|
||||
SUM(po.ordered) OVER (
|
||||
PARTITION BY po.pid, po.supplier_id
|
||||
ORDER BY COALESCE(po.date_ordered, po.date_created), po.po_id
|
||||
) - po.ordered AS cumulative_before
|
||||
FROM public.purchase_orders po
|
||||
WHERE po.status != 'canceled'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM StalePOLines s
|
||||
WHERE s.po_id = po.po_id AND s.pid = po.pid
|
||||
)
|
||||
),
|
||||
-- Total received per (product, supplier) across all receivings.
|
||||
SupplierReceived AS (
|
||||
SELECT pid, supplier_id, SUM(qty_each) AS total_received
|
||||
FROM public.receivings
|
||||
WHERE status IN ('partial_received', 'full_received', 'paid')
|
||||
GROUP BY pid, supplier_id
|
||||
),
|
||||
-- FIFO allocation: receivings fill oldest POs first per (pid, supplier).
|
||||
-- Only open PO lines are reported; closed POs just absorb receivings.
|
||||
OnOrderInfo AS (
|
||||
SELECT
|
||||
pid,
|
||||
SUM(ordered) AS on_order_qty,
|
||||
SUM(ordered * po_cost_price) AS on_order_cost,
|
||||
MIN(expected_date) AS earliest_expected_date
|
||||
FROM public.purchase_orders
|
||||
WHERE status IN ('created', 'ordered', 'preordered', 'electronically_sent', 'electronically_ready_send', 'receiving_started')
|
||||
AND status NOT IN ('canceled', 'done')
|
||||
GROUP BY pid
|
||||
po.pid,
|
||||
SUM(GREATEST(0,
|
||||
po.ordered - GREATEST(0, LEAST(po.ordered,
|
||||
COALESCE(sr.total_received, 0) - po.cumulative_before
|
||||
))
|
||||
)) AS on_order_qty,
|
||||
SUM(GREATEST(0,
|
||||
po.ordered - GREATEST(0, LEAST(po.ordered,
|
||||
COALESCE(sr.total_received, 0) - po.cumulative_before
|
||||
))
|
||||
) * po.po_cost_price) AS on_order_cost,
|
||||
MIN(po.expected_date) FILTER (WHERE
|
||||
po.ordered > GREATEST(0, LEAST(po.ordered,
|
||||
COALESCE(sr.total_received, 0) - po.cumulative_before
|
||||
))
|
||||
) AS earliest_expected_date
|
||||
FROM POFifo po
|
||||
LEFT JOIN SupplierReceived sr ON sr.pid = po.pid AND sr.supplier_id = po.supplier_id
|
||||
WHERE po.status IN ('created', 'ordered', 'preordered', 'electronically_sent',
|
||||
'electronically_ready_send', 'receiving_started')
|
||||
GROUP BY po.pid
|
||||
),
|
||||
HistoricalDates AS (
|
||||
-- Note: Calculating these MIN/MAX values hourly can be slow on large tables.
|
||||
@@ -142,6 +198,17 @@ BEGIN
|
||||
FROM public.daily_product_snapshots
|
||||
GROUP BY pid
|
||||
),
|
||||
BeginningStock AS (
|
||||
-- Get stock level from 30 days ago for sell-through calculation.
|
||||
-- Uses the closest available snapshot if exact date is missing (activity-only snapshots).
|
||||
SELECT DISTINCT ON (pid)
|
||||
pid,
|
||||
eod_stock_quantity AS beginning_stock_30d
|
||||
FROM public.daily_product_snapshots
|
||||
WHERE snapshot_date <= _current_date - INTERVAL '30 days'
|
||||
AND snapshot_date >= _current_date - INTERVAL '37 days'
|
||||
ORDER BY pid, snapshot_date DESC
|
||||
),
|
||||
FirstPeriodMetrics AS (
|
||||
SELECT
|
||||
pid,
|
||||
@@ -403,10 +470,10 @@ BEGIN
|
||||
(sa.stockout_days_30d / 30.0) * 100 AS stockout_rate_30d,
|
||||
sa.gross_regular_revenue_30d - sa.gross_revenue_30d AS markdown_30d,
|
||||
((sa.gross_regular_revenue_30d - sa.gross_revenue_30d) / NULLIF(sa.gross_regular_revenue_30d, 0)) * 100 AS markdown_rate_30d,
|
||||
-- Fix sell-through rate: Industry standard is Units Sold / (Beginning Inventory + Units Received)
|
||||
-- Approximating beginning inventory as current stock + units sold - units received
|
||||
-- Sell-through rate: Industry standard is Units Sold / (Beginning Inventory + Units Received)
|
||||
-- Uses actual snapshot from 30 days ago as beginning stock, falls back to avg_stock_units_30d
|
||||
(sa.sales_30d / NULLIF(
|
||||
ci.current_stock + sa.sales_30d + sa.returns_units_30d - sa.received_qty_30d,
|
||||
COALESCE(bs.beginning_stock_30d, sa.avg_stock_units_30d::int, 0) + sa.received_qty_30d,
|
||||
0
|
||||
)) * 100 AS sell_through_30d,
|
||||
|
||||
@@ -555,6 +622,7 @@ BEGIN
|
||||
LEFT JOIN PreviousPeriodMetrics ppm ON ci.pid = ppm.pid
|
||||
LEFT JOIN DemandVariability dv ON ci.pid = dv.pid
|
||||
LEFT JOIN ServiceLevels sl ON ci.pid = sl.pid
|
||||
LEFT JOIN BeginningStock bs ON ci.pid = bs.pid
|
||||
LEFT JOIN SeasonalityAnalysis season ON ci.pid = season.pid
|
||||
WHERE s.exclude_forecast IS FALSE OR s.exclude_forecast IS NULL -- Exclude products explicitly marked
|
||||
|
||||
|
||||
Reference in New Issue
Block a user