Files
company-tool/backend/company-migrations/0002_activity_price_invoice_rules.sql
Torsten Schulz (local) 0e539710c0 feat: Add password reset functionality with request and reset forms
feat: Implement price list import feature with preview and apply options

feat: Create price rules management page with CRUD operations

feat: Develop quotes management page with itemized quotes and status tracking

feat: Introduce organization registration page for new users

feat: Build suppliers management page with detailed supplier information

feat: Create users management page for inviting and managing roles

chore: Add TypeScript configuration for improved type checking

chore: Set up Vite configuration for development server and API proxy

chore: Add Vite environment type definitions for better TypeScript support
2026-06-02 15:28:38 +02:00

361 lines
13 KiB
SQL

-- Template migration for each organization schema.
-- Replace {schema} with the real schema name, e.g. company_<organization_id>.
create table if not exists {schema}.customers (
id uuid primary key,
customer_number text unique,
name_ciphertext bytea not null,
name_nonce bytea not null,
name_key_id text not null,
status text not null default 'active',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint customers_status_valid check (status in ('active', 'inactive', 'blocked'))
);
create table if not exists {schema}.suppliers (
id uuid primary key,
supplier_number text unique,
name_ciphertext bytea not null,
name_nonce bytea not null,
name_key_id text not null,
status text not null default 'active',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint suppliers_status_valid check (status in ('active', 'inactive', 'blocked'))
);
create table if not exists {schema}.items (
id uuid primary key,
item_number text not null unique,
name_ciphertext bytea not null,
name_nonce bytea not null,
name_key_id text not null,
unit text not null default 'Stk',
tax_rate numeric(7, 4) not null default 19.0,
default_purchase_price numeric(14, 4),
default_sales_price numeric(14, 4),
status text not null default 'active',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint items_status_valid check (status in ('active', 'inactive', 'blocked')),
constraint items_tax_rate_non_negative check (tax_rate >= 0),
constraint items_default_purchase_price_non_negative check (
default_purchase_price is null or default_purchase_price >= 0
),
constraint items_default_sales_price_non_negative check (
default_sales_price is null or default_sales_price >= 0
)
);
create table if not exists {schema}.cash_discount_terms (
id uuid primary key,
code text not null unique,
name text not null,
discount_percent numeric(7, 4) not null,
discount_days integer not null,
net_days integer,
valid_from date,
valid_until date,
is_default_customer_term boolean not null default false,
is_default_supplier_term boolean not null default false,
is_active boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint cash_discount_terms_percent_valid check (
discount_percent >= 0 and discount_percent <= 100
),
constraint cash_discount_terms_days_valid check (
discount_days >= 0 and (net_days is null or net_days >= discount_days)
),
constraint cash_discount_terms_valid_range check (
valid_until is null or valid_from is null or valid_until >= valid_from
)
);
create unique index if not exists idx_cash_discount_terms_default_customer
on {schema}.cash_discount_terms (is_default_customer_term)
where is_default_customer_term;
create unique index if not exists idx_cash_discount_terms_default_supplier
on {schema}.cash_discount_terms (is_default_supplier_term)
where is_default_supplier_term;
create table if not exists {schema}.customer_price_terms (
id uuid primary key,
customer_id uuid not null references {schema}.customers(id) on delete cascade,
standard_discount_percent numeric(7, 4) not null default 0,
cash_discount_term_id uuid references {schema}.cash_discount_terms(id),
valid_from date,
valid_until date,
is_active boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint customer_price_terms_discount_valid check (
standard_discount_percent >= 0 and standard_discount_percent <= 100
),
constraint customer_price_terms_valid_range check (
valid_until is null or valid_from is null or valid_until >= valid_from
)
);
create index if not exists idx_customer_price_terms_customer_active
on {schema}.customer_price_terms (customer_id, is_active, valid_from, valid_until);
create table if not exists {schema}.supplier_price_terms (
id uuid primary key,
supplier_id uuid not null references {schema}.suppliers(id) on delete cascade,
standard_discount_percent numeric(7, 4) not null default 0,
cash_discount_term_id uuid references {schema}.cash_discount_terms(id),
payment_days integer,
valid_from date,
valid_until date,
is_active boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint supplier_price_terms_discount_valid check (
standard_discount_percent >= 0 and standard_discount_percent <= 100
),
constraint supplier_price_terms_payment_days_valid check (
payment_days is null or payment_days >= 0
),
constraint supplier_price_terms_valid_range check (
valid_until is null or valid_from is null or valid_until >= valid_from
)
);
create index if not exists idx_supplier_price_terms_supplier_active
on {schema}.supplier_price_terms (supplier_id, is_active, valid_from, valid_until);
create table if not exists {schema}.activities (
id uuid primary key,
activity_type text not null,
title_ciphertext bytea not null,
title_nonce bytea not null,
title_key_id text not null,
body_ciphertext bytea,
body_nonce bytea,
body_key_id text,
status text not null default 'open',
priority text not null default 'normal',
due_at timestamptz,
starts_at timestamptz,
ends_at timestamptz,
assigned_to_user_id uuid,
created_by_user_id uuid,
completed_by_user_id uuid,
completed_at timestamptz,
visibility text not null default 'internal',
system_source text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint activities_type_valid check (
activity_type in (
'email_note',
'phone_note',
'internal_note',
'task',
'follow_up',
'calendar_event',
'system_event',
'work_step'
)
),
constraint activities_status_valid check (
status in ('open', 'in_progress', 'done', 'cancelled')
),
constraint activities_priority_valid check (
priority in ('low', 'normal', 'high', 'critical')
),
constraint activities_visibility_valid check (
visibility in ('internal', 'organization')
),
constraint activities_body_encryption_complete check (
(
body_ciphertext is null
and body_nonce is null
and body_key_id is null
)
or (
body_ciphertext is not null
and body_nonce is not null
and body_key_id is not null
)
),
constraint activities_time_range_valid check (
ends_at is null or starts_at is null or ends_at >= starts_at
)
);
create index if not exists idx_activities_status_due
on {schema}.activities (status, due_at);
create index if not exists idx_activities_assigned_status
on {schema}.activities (assigned_to_user_id, status);
create table if not exists {schema}.activity_links (
activity_id uuid not null references {schema}.activities(id) on delete cascade,
entity_type text not null,
entity_id uuid not null,
created_at timestamptz not null default now(),
primary key (activity_id, entity_type, entity_id),
constraint activity_links_entity_type_valid check (
entity_type in (
'customer',
'supplier',
'contact',
'quote',
'outgoing_invoice',
'incoming_invoice',
'item',
'document',
'import'
)
)
);
create index if not exists idx_activity_links_entity
on {schema}.activity_links (entity_type, entity_id);
create table if not exists {schema}.outgoing_invoices (
id uuid primary key,
invoice_number text unique,
customer_id uuid not null references {schema}.customers(id),
status text not null default 'draft',
cash_discount_term_id uuid references {schema}.cash_discount_terms(id),
issued_at date,
due_at date,
created_by_user_id uuid,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
finalized_at timestamptz,
constraint outgoing_invoices_status_valid check (
status in ('draft', 'finalized', 'sent', 'paid', 'cancelled', 'overdue')
)
);
create index if not exists idx_outgoing_invoices_customer_status
on {schema}.outgoing_invoices (customer_id, status);
create table if not exists {schema}.outgoing_invoice_items (
id uuid primary key,
invoice_id uuid not null references {schema}.outgoing_invoices(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,
price_override_reason_ciphertext bytea,
price_override_reason_nonce bytea,
price_override_reason_key_id text,
price_overridden_by_user_id uuid,
price_overridden_at timestamptz,
tax_rate numeric(7, 4) not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (invoice_id, line_number),
constraint outgoing_invoice_items_quantity_positive check (quantity > 0),
constraint outgoing_invoice_items_unit_price_non_negative check (unit_price >= 0),
constraint outgoing_invoice_items_original_price_non_negative check (
original_unit_price is null or original_unit_price >= 0
),
constraint outgoing_invoice_items_discount_valid check (
discount_percent >= 0 and discount_percent <= 100
),
constraint outgoing_invoice_items_tax_rate_non_negative check (tax_rate >= 0),
constraint outgoing_invoice_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
)
),
constraint outgoing_invoice_items_override_reason_encryption_complete check (
(
price_override_reason_ciphertext is null
and price_override_reason_nonce is null
and price_override_reason_key_id is null
)
or (
price_override_reason_ciphertext is not null
and price_override_reason_nonce is not null
and price_override_reason_key_id is not null
)
),
constraint outgoing_invoice_items_override_complete check (
(
price_overridden = false
and price_overridden_by_user_id is null
and price_overridden_at is null
)
or (
price_overridden = true
and price_overridden_by_user_id is not null
and price_overridden_at is not null
)
)
);
create index if not exists idx_outgoing_invoice_items_item
on {schema}.outgoing_invoice_items (item_id);
create table if not exists {schema}.incoming_invoices (
id uuid primary key,
invoice_number text,
supplier_id uuid not null references {schema}.suppliers(id),
status text not null default 'draft',
cash_discount_term_id uuid references {schema}.cash_discount_terms(id),
invoice_date date,
due_at date,
created_by_user_id uuid,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint incoming_invoices_status_valid check (
status in ('draft', 'received', 'approved', 'paid', 'cancelled', 'overdue')
)
);
create index if not exists idx_incoming_invoices_supplier_status
on {schema}.incoming_invoices (supplier_id, status);
create table if not exists {schema}.incoming_invoice_items (
id uuid primary key,
invoice_id uuid not null references {schema}.incoming_invoices(id) on delete cascade,
line_number integer not null,
item_id uuid 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,
tax_rate numeric(7, 4) not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (invoice_id, line_number),
constraint incoming_invoice_items_quantity_positive check (quantity > 0),
constraint incoming_invoice_items_unit_price_non_negative check (unit_price >= 0),
constraint incoming_invoice_items_tax_rate_non_negative check (tax_rate >= 0),
constraint incoming_invoice_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
)
)
);