trading-platform-database-v2/ddl/schemas/financial/tables/04-invoices.sql
rckrdmrd 45e77e9a9c feat: Initial commit - Database schemas and scripts
DDL schemas for Trading Platform:
- User management
- Authentication
- Payments
- Education
- ML predictions
- Trading data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 04:30:23 -06:00

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';