DDL schemas for Trading Platform: - User management - Authentication - Payments - Education - ML predictions - Trading data Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
121 lines
4.7 KiB
SQL
121 lines
4.7 KiB
SQL
-- =====================================================
|
|
-- ORBIQUANT IA - INVOICES TABLE
|
|
-- =====================================================
|
|
-- Description: Invoice records for subscriptions and one-time charges
|
|
-- Schema: financial
|
|
-- =====================================================
|
|
|
|
CREATE TABLE financial.invoices (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Referencias
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE RESTRICT,
|
|
subscription_id UUID REFERENCES financial.subscriptions(id) ON DELETE SET NULL,
|
|
|
|
-- Stripe integration
|
|
stripe_invoice_id VARCHAR(255) UNIQUE,
|
|
stripe_customer_id VARCHAR(255),
|
|
|
|
-- Invoice details
|
|
invoice_number VARCHAR(100) UNIQUE, -- Número de factura interno
|
|
invoice_type financial.invoice_type NOT NULL,
|
|
status financial.invoice_status NOT NULL DEFAULT 'draft',
|
|
|
|
-- Amounts
|
|
subtotal DECIMAL(15,2) NOT NULL DEFAULT 0,
|
|
tax DECIMAL(15,2) DEFAULT 0,
|
|
total DECIMAL(15,2) NOT NULL,
|
|
amount_paid DECIMAL(15,2) DEFAULT 0,
|
|
amount_due DECIMAL(15,2) GENERATED ALWAYS AS (total - amount_paid) STORED,
|
|
|
|
currency financial.currency_code NOT NULL DEFAULT 'USD',
|
|
|
|
-- Dates
|
|
invoice_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
due_date TIMESTAMPTZ,
|
|
period_start TIMESTAMPTZ,
|
|
period_end TIMESTAMPTZ,
|
|
|
|
-- Payment
|
|
paid BOOLEAN DEFAULT false,
|
|
paid_at TIMESTAMPTZ,
|
|
attempted BOOLEAN DEFAULT false,
|
|
attempt_count INTEGER DEFAULT 0,
|
|
next_payment_attempt TIMESTAMPTZ,
|
|
|
|
-- URLs
|
|
hosted_invoice_url TEXT, -- Stripe hosted invoice URL
|
|
invoice_pdf_url TEXT,
|
|
|
|
-- Billing details
|
|
billing_email VARCHAR(255),
|
|
billing_name VARCHAR(255),
|
|
billing_address JSONB,
|
|
|
|
-- Line items (si se quiere detalle simple)
|
|
line_items JSONB DEFAULT '[]',
|
|
|
|
-- Metadata
|
|
description TEXT,
|
|
notes TEXT,
|
|
metadata JSONB DEFAULT '{}',
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
finalized_at TIMESTAMPTZ,
|
|
voided_at TIMESTAMPTZ,
|
|
|
|
-- Constraints
|
|
CONSTRAINT positive_subtotal CHECK (subtotal >= 0),
|
|
CONSTRAINT positive_tax CHECK (tax >= 0),
|
|
CONSTRAINT positive_total CHECK (total >= 0),
|
|
CONSTRAINT positive_paid CHECK (amount_paid >= 0),
|
|
CONSTRAINT paid_lte_total CHECK (amount_paid <= total),
|
|
CONSTRAINT total_equals_subtotal_plus_tax CHECK (total = subtotal + tax),
|
|
CONSTRAINT paid_has_timestamp CHECK (
|
|
(paid = false) OR
|
|
(paid = true AND paid_at IS NOT NULL)
|
|
),
|
|
CONSTRAINT finalized_for_open_paid CHECK (
|
|
(status IN ('open', 'paid') AND finalized_at IS NOT NULL) OR
|
|
(status NOT IN ('open', 'paid'))
|
|
),
|
|
CONSTRAINT void_has_timestamp CHECK (
|
|
(status = 'void' AND voided_at IS NOT NULL) OR
|
|
(status != 'void')
|
|
),
|
|
CONSTRAINT due_date_after_invoice CHECK (
|
|
due_date IS NULL OR due_date >= invoice_date
|
|
),
|
|
CONSTRAINT period_dates_order CHECK (
|
|
(period_start IS NULL AND period_end IS NULL) OR
|
|
(period_start IS NOT NULL AND period_end IS NOT NULL AND period_start < period_end)
|
|
)
|
|
);
|
|
|
|
-- Sequence para invoice numbers
|
|
CREATE SEQUENCE financial.invoice_number_seq START 1000;
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_invoices_user_id ON financial.invoices(user_id);
|
|
CREATE INDEX idx_invoices_subscription_id ON financial.invoices(subscription_id) WHERE subscription_id IS NOT NULL;
|
|
CREATE INDEX idx_invoices_status ON financial.invoices(status);
|
|
CREATE INDEX idx_invoices_stripe_id ON financial.invoices(stripe_invoice_id) WHERE stripe_invoice_id IS NOT NULL;
|
|
CREATE INDEX idx_invoices_invoice_number ON financial.invoices(invoice_number);
|
|
CREATE INDEX idx_invoices_due_date ON financial.invoices(due_date) WHERE due_date IS NOT NULL AND status = 'open';
|
|
CREATE INDEX idx_invoices_invoice_date ON financial.invoices(invoice_date DESC);
|
|
CREATE INDEX idx_invoices_user_date ON financial.invoices(user_id, invoice_date DESC);
|
|
CREATE INDEX idx_invoices_unpaid ON financial.invoices(user_id, status) WHERE paid = false AND status = 'open';
|
|
CREATE INDEX idx_invoices_period ON financial.invoices(period_start, period_end) WHERE period_start IS NOT NULL;
|
|
|
|
-- Comments
|
|
COMMENT ON TABLE financial.invoices IS 'Invoice records for subscriptions and one-time charges';
|
|
COMMENT ON COLUMN financial.invoices.invoice_number IS 'Internal unique invoice number (auto-generated)';
|
|
COMMENT ON COLUMN financial.invoices.invoice_type IS 'Type: subscription, one_time, or usage-based';
|
|
COMMENT ON COLUMN financial.invoices.amount_due IS 'Computed: total - amount_paid';
|
|
COMMENT ON COLUMN financial.invoices.line_items IS 'JSON array of invoice line items with description, amount, quantity';
|
|
COMMENT ON COLUMN financial.invoices.billing_address IS 'JSON object with billing address details';
|
|
COMMENT ON COLUMN financial.invoices.hosted_invoice_url IS 'URL to Stripe-hosted invoice page';
|
|
COMMENT ON COLUMN financial.invoices.attempt_count IS 'Number of payment attempts made';
|