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
361 lines
13 KiB
SQL
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
|
|
)
|
|
)
|
|
);
|