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
This commit is contained in:
360
backend/company-migrations/0002_activity_price_invoice_rules.sql
Normal file
360
backend/company-migrations/0002_activity_price_invoice_rules.sql
Normal file
@@ -0,0 +1,360 @@
|
||||
-- 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
|
||||
)
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user