-- Template migration for each organization schema. -- Replace {schema} with the real schema name, e.g. company_. create table if not exists {schema}.quotes ( id uuid primary key, quote_number text not null unique, customer_id uuid not null references {schema}.customers(id), status text not null default 'draft', valid_until date, cash_discount_term_id uuid references {schema}.cash_discount_terms(id), customer_discount_percent numeric(7, 4) not null default 0, notes_ciphertext bytea, notes_nonce bytea, notes_key_id text, created_by_user_id uuid, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint quotes_status_valid check ( status in ('draft', 'sent', 'accepted', 'rejected', 'expired', 'cancelled') ), constraint quotes_customer_discount_valid check ( customer_discount_percent >= 0 and customer_discount_percent <= 100 ), constraint quotes_notes_encryption_complete check ( ( notes_ciphertext is null and notes_nonce is null and notes_key_id is null ) or ( notes_ciphertext is not null and notes_nonce is not null and notes_key_id is not null ) ) ); create index if not exists idx_quotes_customer_status on {schema}.quotes (customer_id, status); create table if not exists {schema}.quote_items ( id uuid primary key, quote_id uuid not null references {schema}.quotes(id) on delete cascade, line_number integer not null, item_id uuid not null references {schema}.items(id), description_ciphertext bytea, description_nonce bytea, description_key_id text, quantity numeric(14, 4) not null, unit_price numeric(14, 4) not null, original_unit_price numeric(14, 4), discount_percent numeric(7, 4) not null default 0, price_overridden boolean not null default false, tax_rate numeric(7, 4) not null, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (quote_id, line_number), constraint quote_items_quantity_positive check (quantity > 0), constraint quote_items_unit_price_non_negative check (unit_price >= 0), constraint quote_items_original_price_non_negative check ( original_unit_price is null or original_unit_price >= 0 ), constraint quote_items_discount_valid check ( discount_percent >= 0 and discount_percent <= 100 ), constraint quote_items_tax_rate_non_negative check (tax_rate >= 0), constraint quote_items_description_encryption_complete check ( ( description_ciphertext is null and description_nonce is null and description_key_id is null ) or ( description_ciphertext is not null and description_nonce is not null and description_key_id is not null ) ) ); create index if not exists idx_quote_items_item on {schema}.quote_items (item_id);