[SAAS-019] feat: Add Portfolio module DDL
- Schema creation and grants - Enums: product_type, product_status, price_type, attribute_type - Tables: categories, products, variants, prices - RLS policies for tenant isolation - Performance indexes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8915b7ce71
commit
a3f354528a
19
ddl/schemas/portfolio/00-schema.sql
Normal file
19
ddl/schemas/portfolio/00-schema.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- TEMPLATE-SAAS: Portfolio Schema
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- Module: SAAS-019
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- Create schema
|
||||||
|
CREATE SCHEMA IF NOT EXISTS portfolio;
|
||||||
|
|
||||||
|
-- Grant permissions
|
||||||
|
GRANT USAGE ON SCHEMA portfolio TO template_saas_app;
|
||||||
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA portfolio TO template_saas_app;
|
||||||
|
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA portfolio TO template_saas_app;
|
||||||
|
|
||||||
|
-- Default privileges for future tables
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA portfolio
|
||||||
|
GRANT ALL PRIVILEGES ON TABLES TO template_saas_app;
|
||||||
|
ALTER DEFAULT PRIVILEGES IN SCHEMA portfolio
|
||||||
|
GRANT ALL PRIVILEGES ON SEQUENCES TO template_saas_app;
|
||||||
41
ddl/schemas/portfolio/01-enums.sql
Normal file
41
ddl/schemas/portfolio/01-enums.sql
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- TEMPLATE-SAAS: Portfolio Enums
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- Module: SAAS-019
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- Product type enum
|
||||||
|
CREATE TYPE portfolio.product_type AS ENUM (
|
||||||
|
'physical',
|
||||||
|
'digital',
|
||||||
|
'service',
|
||||||
|
'subscription',
|
||||||
|
'bundle'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Product status enum
|
||||||
|
CREATE TYPE portfolio.product_status AS ENUM (
|
||||||
|
'draft',
|
||||||
|
'active',
|
||||||
|
'inactive',
|
||||||
|
'discontinued',
|
||||||
|
'out_of_stock'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Price type enum
|
||||||
|
CREATE TYPE portfolio.price_type AS ENUM (
|
||||||
|
'one_time',
|
||||||
|
'recurring',
|
||||||
|
'usage_based',
|
||||||
|
'tiered'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Variant attribute type enum
|
||||||
|
CREATE TYPE portfolio.attribute_type AS ENUM (
|
||||||
|
'color',
|
||||||
|
'size',
|
||||||
|
'material',
|
||||||
|
'style',
|
||||||
|
'capacity',
|
||||||
|
'custom'
|
||||||
|
);
|
||||||
262
ddl/schemas/portfolio/02-tables.sql
Normal file
262
ddl/schemas/portfolio/02-tables.sql
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- TEMPLATE-SAAS: Portfolio Tables
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- Module: SAAS-019
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Categories (hierarchical product categories)
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE portfolio.categories (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Hierarchy
|
||||||
|
parent_id UUID REFERENCES portfolio.categories(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Category info
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
slug VARCHAR(120) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
|
||||||
|
-- Display
|
||||||
|
position INT NOT NULL DEFAULT 0,
|
||||||
|
image_url VARCHAR(500),
|
||||||
|
color VARCHAR(7) DEFAULT '#3B82F6',
|
||||||
|
icon VARCHAR(50),
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- SEO
|
||||||
|
meta_title VARCHAR(200),
|
||||||
|
meta_description TEXT,
|
||||||
|
|
||||||
|
-- Custom fields
|
||||||
|
custom_fields JSONB DEFAULT '{}'::jsonb,
|
||||||
|
|
||||||
|
-- Audit
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
created_by UUID REFERENCES users.users(id) ON DELETE SET NULL,
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Constraints
|
||||||
|
CONSTRAINT unique_category_slug UNIQUE (tenant_id, slug),
|
||||||
|
CONSTRAINT unique_category_position UNIQUE (tenant_id, parent_id, position)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE portfolio.categories IS 'Hierarchical product categories per tenant';
|
||||||
|
COMMENT ON COLUMN portfolio.categories.parent_id IS 'Parent category for hierarchical structure';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Products (main product catalog)
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE portfolio.products (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Category reference
|
||||||
|
category_id UUID REFERENCES portfolio.categories(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Basic info
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
slug VARCHAR(280) NOT NULL,
|
||||||
|
sku VARCHAR(100),
|
||||||
|
barcode VARCHAR(100),
|
||||||
|
description TEXT,
|
||||||
|
short_description VARCHAR(500),
|
||||||
|
|
||||||
|
-- Product type
|
||||||
|
product_type portfolio.product_type DEFAULT 'physical' NOT NULL,
|
||||||
|
status portfolio.product_status DEFAULT 'draft' NOT NULL,
|
||||||
|
|
||||||
|
-- Pricing (base price, can be overridden by variants/prices)
|
||||||
|
base_price DECIMAL(15, 2) DEFAULT 0,
|
||||||
|
cost_price DECIMAL(15, 2),
|
||||||
|
compare_at_price DECIMAL(15, 2),
|
||||||
|
currency VARCHAR(3) DEFAULT 'USD',
|
||||||
|
|
||||||
|
-- Inventory
|
||||||
|
track_inventory BOOLEAN DEFAULT TRUE,
|
||||||
|
stock_quantity INT DEFAULT 0,
|
||||||
|
low_stock_threshold INT DEFAULT 5,
|
||||||
|
allow_backorder BOOLEAN DEFAULT FALSE,
|
||||||
|
|
||||||
|
-- Physical properties
|
||||||
|
weight DECIMAL(10, 3),
|
||||||
|
weight_unit VARCHAR(10) DEFAULT 'kg',
|
||||||
|
length DECIMAL(10, 2),
|
||||||
|
width DECIMAL(10, 2),
|
||||||
|
height DECIMAL(10, 2),
|
||||||
|
dimension_unit VARCHAR(10) DEFAULT 'cm',
|
||||||
|
|
||||||
|
-- Media
|
||||||
|
images JSONB DEFAULT '[]'::jsonb,
|
||||||
|
featured_image_url VARCHAR(500),
|
||||||
|
|
||||||
|
-- SEO
|
||||||
|
meta_title VARCHAR(200),
|
||||||
|
meta_description TEXT,
|
||||||
|
tags JSONB DEFAULT '[]'::jsonb,
|
||||||
|
|
||||||
|
-- Visibility
|
||||||
|
is_visible BOOLEAN DEFAULT TRUE,
|
||||||
|
is_featured BOOLEAN DEFAULT FALSE,
|
||||||
|
|
||||||
|
-- Attributes for variants
|
||||||
|
has_variants BOOLEAN DEFAULT FALSE,
|
||||||
|
variant_attributes JSONB DEFAULT '[]'::jsonb,
|
||||||
|
|
||||||
|
-- Custom fields
|
||||||
|
custom_fields JSONB DEFAULT '{}'::jsonb,
|
||||||
|
|
||||||
|
-- Audit
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
created_by UUID REFERENCES users.users(id) ON DELETE SET NULL,
|
||||||
|
published_at TIMESTAMPTZ,
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Constraints
|
||||||
|
CONSTRAINT unique_product_slug UNIQUE (tenant_id, slug),
|
||||||
|
CONSTRAINT unique_product_sku UNIQUE (tenant_id, sku),
|
||||||
|
CONSTRAINT check_prices CHECK (base_price >= 0 AND (cost_price IS NULL OR cost_price >= 0))
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE portfolio.products IS 'Product catalog with multi-tenant support';
|
||||||
|
COMMENT ON COLUMN portfolio.products.images IS 'JSON array of image URLs';
|
||||||
|
COMMENT ON COLUMN portfolio.products.variant_attributes IS 'JSON array of attribute names used for variants';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Variants (product variants: size, color, etc.)
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE portfolio.variants (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
||||||
|
product_id UUID NOT NULL REFERENCES portfolio.products(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Variant identification
|
||||||
|
sku VARCHAR(100),
|
||||||
|
barcode VARCHAR(100),
|
||||||
|
name VARCHAR(255),
|
||||||
|
|
||||||
|
-- Attributes (e.g., {"color": "red", "size": "L"})
|
||||||
|
attributes JSONB DEFAULT '{}'::jsonb NOT NULL,
|
||||||
|
|
||||||
|
-- Pricing
|
||||||
|
price DECIMAL(15, 2),
|
||||||
|
cost_price DECIMAL(15, 2),
|
||||||
|
compare_at_price DECIMAL(15, 2),
|
||||||
|
|
||||||
|
-- Inventory
|
||||||
|
stock_quantity INT DEFAULT 0,
|
||||||
|
low_stock_threshold INT,
|
||||||
|
|
||||||
|
-- Physical properties (can override product)
|
||||||
|
weight DECIMAL(10, 3),
|
||||||
|
|
||||||
|
-- Media
|
||||||
|
image_url VARCHAR(500),
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
position INT DEFAULT 0,
|
||||||
|
|
||||||
|
-- Audit
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Constraints
|
||||||
|
CONSTRAINT unique_variant_sku UNIQUE (tenant_id, sku),
|
||||||
|
CONSTRAINT check_variant_prices CHECK (price IS NULL OR price >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE portfolio.variants IS 'Product variants (size, color, etc.)';
|
||||||
|
COMMENT ON COLUMN portfolio.variants.attributes IS 'JSON object with attribute key-value pairs';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Prices (multi-currency pricing)
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE portfolio.prices (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Reference (either product or variant)
|
||||||
|
product_id UUID REFERENCES portfolio.products(id) ON DELETE CASCADE,
|
||||||
|
variant_id UUID REFERENCES portfolio.variants(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- Pricing
|
||||||
|
price_type portfolio.price_type DEFAULT 'one_time' NOT NULL,
|
||||||
|
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
|
||||||
|
amount DECIMAL(15, 2) NOT NULL,
|
||||||
|
compare_at_amount DECIMAL(15, 2),
|
||||||
|
|
||||||
|
-- Recurring pricing (for subscriptions)
|
||||||
|
billing_period VARCHAR(20),
|
||||||
|
billing_interval INT,
|
||||||
|
|
||||||
|
-- Tiered pricing
|
||||||
|
min_quantity INT DEFAULT 1,
|
||||||
|
max_quantity INT,
|
||||||
|
|
||||||
|
-- Validity
|
||||||
|
valid_from TIMESTAMPTZ,
|
||||||
|
valid_until TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Priority (for overlapping prices)
|
||||||
|
priority INT DEFAULT 0,
|
||||||
|
|
||||||
|
-- Status
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- Audit
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Constraints
|
||||||
|
CONSTRAINT check_price_reference CHECK (
|
||||||
|
(product_id IS NOT NULL AND variant_id IS NULL) OR
|
||||||
|
(product_id IS NULL AND variant_id IS NOT NULL)
|
||||||
|
),
|
||||||
|
CONSTRAINT check_amount CHECK (amount >= 0),
|
||||||
|
CONSTRAINT check_quantity_range CHECK (
|
||||||
|
min_quantity > 0 AND (max_quantity IS NULL OR max_quantity >= min_quantity)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMENT ON TABLE portfolio.prices IS 'Multi-currency pricing for products and variants';
|
||||||
|
COMMENT ON COLUMN portfolio.prices.billing_period IS 'day, week, month, year for recurring prices';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Triggers for updated_at
|
||||||
|
-- ============================================
|
||||||
|
CREATE OR REPLACE FUNCTION portfolio.update_updated_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_categories_updated_at
|
||||||
|
BEFORE UPDATE ON portfolio.categories
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_updated_at();
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_products_updated_at
|
||||||
|
BEFORE UPDATE ON portfolio.products
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_updated_at();
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_variants_updated_at
|
||||||
|
BEFORE UPDATE ON portfolio.variants
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_updated_at();
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_prices_updated_at
|
||||||
|
BEFORE UPDATE ON portfolio.prices
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION portfolio.update_updated_at();
|
||||||
85
ddl/schemas/portfolio/04-rls.sql
Normal file
85
ddl/schemas/portfolio/04-rls.sql
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- TEMPLATE-SAAS: Portfolio Row Level Security
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- Module: SAAS-019
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Enable RLS on all tables
|
||||||
|
-- ============================================
|
||||||
|
ALTER TABLE portfolio.categories ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE portfolio.products ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE portfolio.variants ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE portfolio.prices ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Categories Policies
|
||||||
|
-- ============================================
|
||||||
|
CREATE POLICY categories_tenant_isolation ON portfolio.categories
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY categories_insert ON portfolio.categories
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY categories_update ON portfolio.categories
|
||||||
|
FOR UPDATE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY categories_delete ON portfolio.categories
|
||||||
|
FOR DELETE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Products Policies
|
||||||
|
-- ============================================
|
||||||
|
CREATE POLICY products_tenant_isolation ON portfolio.products
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY products_insert ON portfolio.products
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY products_update ON portfolio.products
|
||||||
|
FOR UPDATE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY products_delete ON portfolio.products
|
||||||
|
FOR DELETE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Variants Policies
|
||||||
|
-- ============================================
|
||||||
|
CREATE POLICY variants_tenant_isolation ON portfolio.variants
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY variants_insert ON portfolio.variants
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY variants_update ON portfolio.variants
|
||||||
|
FOR UPDATE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY variants_delete ON portfolio.variants
|
||||||
|
FOR DELETE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Prices Policies
|
||||||
|
-- ============================================
|
||||||
|
CREATE POLICY prices_tenant_isolation ON portfolio.prices
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY prices_insert ON portfolio.prices
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY prices_update ON portfolio.prices
|
||||||
|
FOR UPDATE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
|
|
||||||
|
CREATE POLICY prices_delete ON portfolio.prices
|
||||||
|
FOR DELETE
|
||||||
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
||||||
119
ddl/schemas/portfolio/05-indexes.sql
Normal file
119
ddl/schemas/portfolio/05-indexes.sql
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- TEMPLATE-SAAS: Portfolio Indexes
|
||||||
|
-- Version: 1.0.0
|
||||||
|
-- Module: SAAS-019
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Categories Indexes
|
||||||
|
-- ============================================
|
||||||
|
CREATE INDEX idx_categories_tenant ON portfolio.categories(tenant_id);
|
||||||
|
CREATE INDEX idx_categories_parent ON portfolio.categories(tenant_id, parent_id);
|
||||||
|
CREATE INDEX idx_categories_slug ON portfolio.categories(tenant_id, slug);
|
||||||
|
CREATE INDEX idx_categories_position ON portfolio.categories(tenant_id, parent_id, position);
|
||||||
|
CREATE INDEX idx_categories_active ON portfolio.categories(tenant_id, is_active) WHERE is_active = TRUE;
|
||||||
|
|
||||||
|
-- Soft delete filter
|
||||||
|
CREATE INDEX idx_categories_not_deleted ON portfolio.categories(tenant_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Products Indexes
|
||||||
|
-- ============================================
|
||||||
|
-- Primary indexes
|
||||||
|
CREATE INDEX idx_products_tenant ON portfolio.products(tenant_id);
|
||||||
|
CREATE INDEX idx_products_category ON portfolio.products(tenant_id, category_id) WHERE category_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_products_slug ON portfolio.products(tenant_id, slug);
|
||||||
|
CREATE INDEX idx_products_sku ON portfolio.products(tenant_id, sku) WHERE sku IS NOT NULL;
|
||||||
|
CREATE INDEX idx_products_status ON portfolio.products(tenant_id, status);
|
||||||
|
CREATE INDEX idx_products_type ON portfolio.products(tenant_id, product_type);
|
||||||
|
|
||||||
|
-- Search indexes
|
||||||
|
CREATE INDEX idx_products_name_search ON portfolio.products
|
||||||
|
USING gin(to_tsvector('simple', name));
|
||||||
|
|
||||||
|
-- Filtering indexes
|
||||||
|
CREATE INDEX idx_products_visible ON portfolio.products(tenant_id, is_visible, status)
|
||||||
|
WHERE is_visible = TRUE AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_products_featured ON portfolio.products(tenant_id, is_featured)
|
||||||
|
WHERE is_featured = TRUE AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Price range queries
|
||||||
|
CREATE INDEX idx_products_price ON portfolio.products(tenant_id, base_price);
|
||||||
|
|
||||||
|
-- Inventory queries
|
||||||
|
CREATE INDEX idx_products_low_stock ON portfolio.products(tenant_id, stock_quantity)
|
||||||
|
WHERE track_inventory = TRUE AND deleted_at IS NULL;
|
||||||
|
CREATE INDEX idx_products_out_of_stock ON portfolio.products(tenant_id)
|
||||||
|
WHERE track_inventory = TRUE AND stock_quantity <= 0 AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Soft delete filter
|
||||||
|
CREATE INDEX idx_products_active ON portfolio.products(tenant_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Catalog view (common query pattern)
|
||||||
|
CREATE INDEX idx_products_catalog ON portfolio.products(tenant_id, category_id, status, base_price)
|
||||||
|
WHERE deleted_at IS NULL AND is_visible = TRUE;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Variants Indexes
|
||||||
|
-- ============================================
|
||||||
|
CREATE INDEX idx_variants_tenant ON portfolio.variants(tenant_id);
|
||||||
|
CREATE INDEX idx_variants_product ON portfolio.variants(product_id);
|
||||||
|
CREATE INDEX idx_variants_sku ON portfolio.variants(tenant_id, sku) WHERE sku IS NOT NULL;
|
||||||
|
CREATE INDEX idx_variants_position ON portfolio.variants(product_id, position);
|
||||||
|
|
||||||
|
-- Inventory queries
|
||||||
|
CREATE INDEX idx_variants_stock ON portfolio.variants(tenant_id, stock_quantity);
|
||||||
|
|
||||||
|
-- Active variants
|
||||||
|
CREATE INDEX idx_variants_active ON portfolio.variants(product_id, is_active)
|
||||||
|
WHERE is_active = TRUE AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Soft delete filter
|
||||||
|
CREATE INDEX idx_variants_not_deleted ON portfolio.variants(tenant_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Prices Indexes
|
||||||
|
-- ============================================
|
||||||
|
CREATE INDEX idx_prices_tenant ON portfolio.prices(tenant_id);
|
||||||
|
CREATE INDEX idx_prices_product ON portfolio.prices(product_id) WHERE product_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_prices_variant ON portfolio.prices(variant_id) WHERE variant_id IS NOT NULL;
|
||||||
|
CREATE INDEX idx_prices_currency ON portfolio.prices(tenant_id, currency);
|
||||||
|
CREATE INDEX idx_prices_type ON portfolio.prices(tenant_id, price_type);
|
||||||
|
|
||||||
|
-- Active prices
|
||||||
|
CREATE INDEX idx_prices_active ON portfolio.prices(tenant_id, is_active)
|
||||||
|
WHERE is_active = TRUE AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Time-based price queries
|
||||||
|
CREATE INDEX idx_prices_validity ON portfolio.prices(tenant_id, valid_from, valid_until)
|
||||||
|
WHERE is_active = TRUE AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Priority for price selection
|
||||||
|
CREATE INDEX idx_prices_priority ON portfolio.prices(product_id, currency, priority DESC)
|
||||||
|
WHERE is_active = TRUE AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Soft delete filter
|
||||||
|
CREATE INDEX idx_prices_not_deleted ON portfolio.prices(tenant_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- Composite indexes for common queries
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- Product listing with category
|
||||||
|
CREATE INDEX idx_products_category_listing ON portfolio.products(tenant_id, category_id, status, position)
|
||||||
|
WHERE deleted_at IS NULL AND is_visible = TRUE;
|
||||||
|
|
||||||
|
-- Product search by tags
|
||||||
|
CREATE INDEX idx_products_tags ON portfolio.products USING gin(tags);
|
||||||
|
|
||||||
|
-- Variant lookup for product
|
||||||
|
CREATE INDEX idx_variants_product_active ON portfolio.variants(product_id, position)
|
||||||
|
WHERE is_active = TRUE AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- Price lookup for product + currency
|
||||||
|
CREATE INDEX idx_prices_product_currency ON portfolio.prices(product_id, currency, priority DESC, valid_from)
|
||||||
|
WHERE is_active = TRUE AND deleted_at IS NULL AND product_id IS NOT NULL;
|
||||||
Loading…
Reference in New Issue
Block a user