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