0aecf5b41c
Remove influencer-specific features (campaigns, TikTok, gifts, shadow ledger, contracts, briefings) and consolidate into a clean ERP foundation with core bookkeeping, invoicing, receipts, tax reporting, and calendar functionality. Reorganize database migrations into a clean numbered sequence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
404 lines
16 KiB
PL/PgSQL
404 lines
16 KiB
PL/PgSQL
-- Migration 1: Core Schema
|
|
-- Profiles, company_settings, bank_connections, customers, invoices,
|
|
-- invoice_items, transactions, salary_payments, tax_rates
|
|
|
|
-- =============================================================================
|
|
-- Extensions
|
|
-- =============================================================================
|
|
create extension if not exists "uuid-ossp" with schema extensions;
|
|
|
|
-- =============================================================================
|
|
-- Trigger function: update_updated_at_column (reused across all migrations)
|
|
-- =============================================================================
|
|
create or replace function public.update_updated_at_column()
|
|
returns trigger as $$
|
|
begin
|
|
new.updated_at = now();
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
-- =============================================================================
|
|
-- 1. profiles
|
|
-- =============================================================================
|
|
create table public.profiles (
|
|
id uuid primary key references auth.users on delete cascade,
|
|
email text,
|
|
full_name text,
|
|
avatar_url text,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.profiles enable row level security;
|
|
|
|
create policy "profiles_select" on public.profiles
|
|
for select using (auth.uid() = id);
|
|
create policy "profiles_insert" on public.profiles
|
|
for insert with check (auth.uid() = id);
|
|
create policy "profiles_update" on public.profiles
|
|
for update using (auth.uid() = id);
|
|
|
|
create trigger profiles_updated_at
|
|
before update on public.profiles
|
|
for each row execute function public.update_updated_at_column();
|
|
|
|
-- Auto-create profile on new user signup
|
|
create or replace function public.handle_new_user()
|
|
returns trigger as $$
|
|
begin
|
|
insert into public.profiles (id, email, full_name)
|
|
values (new.id, new.email, new.raw_user_meta_data->>'full_name');
|
|
return new;
|
|
end;
|
|
$$ language plpgsql security definer;
|
|
|
|
create trigger on_auth_user_created
|
|
after insert on auth.users
|
|
for each row execute function public.handle_new_user();
|
|
|
|
-- =============================================================================
|
|
-- 2. company_settings
|
|
-- =============================================================================
|
|
create table public.company_settings (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
user_id uuid references auth.users on delete cascade unique not null,
|
|
entity_type text check (entity_type in ('enskild_firma', 'aktiebolag')),
|
|
company_name text,
|
|
org_number text,
|
|
vat_number text,
|
|
address_line1 text,
|
|
address_line2 text,
|
|
postal_code text,
|
|
city text,
|
|
country text default 'SE',
|
|
phone text,
|
|
email text,
|
|
website text,
|
|
bankgiro text,
|
|
plusgiro text,
|
|
iban text,
|
|
bic text,
|
|
f_skatt boolean default true,
|
|
vat_registered boolean default false,
|
|
moms_period text check (moms_period in ('monthly', 'quarterly', 'yearly')),
|
|
fiscal_year_start_month integer default 1,
|
|
invoice_prefix text,
|
|
next_invoice_number integer default 1,
|
|
invoice_default_days integer default 30,
|
|
logo_url text,
|
|
onboarding_complete boolean default false,
|
|
schablon_mileage_rate numeric default 2.50,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.company_settings enable row level security;
|
|
|
|
create policy "company_settings_select" on public.company_settings
|
|
for select using (auth.uid() = user_id);
|
|
create policy "company_settings_insert" on public.company_settings
|
|
for insert with check (auth.uid() = user_id);
|
|
create policy "company_settings_update" on public.company_settings
|
|
for update using (auth.uid() = user_id);
|
|
create policy "company_settings_delete" on public.company_settings
|
|
for delete using (auth.uid() = user_id);
|
|
|
|
create index idx_company_settings_user_id on public.company_settings (user_id);
|
|
|
|
create trigger company_settings_updated_at
|
|
before update on public.company_settings
|
|
for each row execute function public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 3. bank_connections
|
|
-- =============================================================================
|
|
create table public.bank_connections (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
user_id uuid references auth.users on delete cascade not null,
|
|
provider text not null default 'enablebanking',
|
|
bank_name text,
|
|
session_id text,
|
|
status text default 'pending'
|
|
check (status in ('pending', 'active', 'expired', 'error', 'revoked')),
|
|
consent_expires timestamptz,
|
|
accounts_data jsonb,
|
|
error_message text,
|
|
last_synced_at timestamptz,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.bank_connections enable row level security;
|
|
|
|
create policy "bank_connections_select" on public.bank_connections
|
|
for select using (auth.uid() = user_id);
|
|
create policy "bank_connections_insert" on public.bank_connections
|
|
for insert with check (auth.uid() = user_id);
|
|
create policy "bank_connections_update" on public.bank_connections
|
|
for update using (auth.uid() = user_id);
|
|
create policy "bank_connections_delete" on public.bank_connections
|
|
for delete using (auth.uid() = user_id);
|
|
|
|
create index idx_bank_connections_user_id on public.bank_connections (user_id);
|
|
|
|
create trigger bank_connections_updated_at
|
|
before update on public.bank_connections
|
|
for each row execute function public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 4. customers
|
|
-- =============================================================================
|
|
create table public.customers (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
user_id uuid references auth.users on delete cascade not null,
|
|
name text not null,
|
|
org_number text,
|
|
vat_number text,
|
|
email text,
|
|
phone text,
|
|
address_line1 text,
|
|
postal_code text,
|
|
city text,
|
|
country text default 'SE',
|
|
is_international boolean default false,
|
|
notes text,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.customers enable row level security;
|
|
|
|
create policy "customers_select" on public.customers
|
|
for select using (auth.uid() = user_id);
|
|
create policy "customers_insert" on public.customers
|
|
for insert with check (auth.uid() = user_id);
|
|
create policy "customers_update" on public.customers
|
|
for update using (auth.uid() = user_id);
|
|
create policy "customers_delete" on public.customers
|
|
for delete using (auth.uid() = user_id);
|
|
|
|
create index idx_customers_user_id on public.customers (user_id);
|
|
|
|
create trigger customers_updated_at
|
|
before update on public.customers
|
|
for each row execute function public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 5. invoices
|
|
-- =============================================================================
|
|
create table public.invoices (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
user_id uuid references auth.users on delete cascade not null,
|
|
customer_id uuid references public.customers (id) on delete set null,
|
|
invoice_number text not null,
|
|
invoice_date date not null,
|
|
due_date date not null,
|
|
status text default 'draft'
|
|
check (status in ('draft', 'sent', 'paid', 'overdue', 'cancelled', 'credited')),
|
|
currency text default 'SEK',
|
|
exchange_rate numeric,
|
|
exchange_rate_date date,
|
|
subtotal numeric default 0,
|
|
subtotal_sek numeric default 0,
|
|
vat_amount numeric default 0,
|
|
vat_amount_sek numeric default 0,
|
|
total numeric default 0,
|
|
total_sek numeric default 0,
|
|
vat_treatment text default 'standard_25',
|
|
vat_rate numeric default 25,
|
|
moms_ruta text,
|
|
your_reference text,
|
|
our_reference text,
|
|
notes text,
|
|
reverse_charge_text text,
|
|
credited_invoice_id uuid references public.invoices (id) on delete set null,
|
|
paid_at timestamptz,
|
|
paid_amount numeric,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
|
|
unique (user_id, invoice_number)
|
|
);
|
|
|
|
alter table public.invoices enable row level security;
|
|
|
|
create policy "invoices_select" on public.invoices
|
|
for select using (auth.uid() = user_id);
|
|
create policy "invoices_insert" on public.invoices
|
|
for insert with check (auth.uid() = user_id);
|
|
create policy "invoices_update" on public.invoices
|
|
for update using (auth.uid() = user_id);
|
|
create policy "invoices_delete" on public.invoices
|
|
for delete using (auth.uid() = user_id);
|
|
|
|
create index idx_invoices_user_id on public.invoices (user_id);
|
|
create index idx_invoices_status on public.invoices (status);
|
|
create index idx_invoices_due_date on public.invoices (due_date);
|
|
|
|
create trigger invoices_updated_at
|
|
before update on public.invoices
|
|
for each row execute function public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 6. invoice_items
|
|
-- =============================================================================
|
|
create table public.invoice_items (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
invoice_id uuid references public.invoices (id) on delete cascade not null,
|
|
sort_order integer default 0,
|
|
description text not null,
|
|
quantity numeric default 1,
|
|
unit text default 'st',
|
|
unit_price numeric default 0,
|
|
line_total numeric default 0,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.invoice_items enable row level security;
|
|
|
|
create policy "invoice_items_select" on public.invoice_items
|
|
for select using (
|
|
exists (
|
|
select 1 from public.invoices
|
|
where invoices.id = invoice_items.invoice_id
|
|
and invoices.user_id = auth.uid()
|
|
)
|
|
);
|
|
create policy "invoice_items_insert" on public.invoice_items
|
|
for insert with check (
|
|
exists (
|
|
select 1 from public.invoices
|
|
where invoices.id = invoice_items.invoice_id
|
|
and invoices.user_id = auth.uid()
|
|
)
|
|
);
|
|
create policy "invoice_items_update" on public.invoice_items
|
|
for update using (
|
|
exists (
|
|
select 1 from public.invoices
|
|
where invoices.id = invoice_items.invoice_id
|
|
and invoices.user_id = auth.uid()
|
|
)
|
|
);
|
|
create policy "invoice_items_delete" on public.invoice_items
|
|
for delete using (
|
|
exists (
|
|
select 1 from public.invoices
|
|
where invoices.id = invoice_items.invoice_id
|
|
and invoices.user_id = auth.uid()
|
|
)
|
|
);
|
|
|
|
create index idx_invoice_items_invoice_id on public.invoice_items (invoice_id);
|
|
|
|
-- =============================================================================
|
|
-- 7. transactions
|
|
-- =============================================================================
|
|
create table public.transactions (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
user_id uuid references auth.users on delete cascade not null,
|
|
bank_connection_id uuid references public.bank_connections (id) on delete set null,
|
|
external_id text,
|
|
date date not null,
|
|
description text not null,
|
|
amount numeric not null,
|
|
currency text default 'SEK',
|
|
amount_sek numeric,
|
|
exchange_rate numeric,
|
|
exchange_rate_date date,
|
|
category text default 'uncategorized',
|
|
is_business boolean,
|
|
invoice_id uuid references public.invoices (id) on delete set null,
|
|
potential_invoice_id uuid references public.invoices (id) on delete set null,
|
|
journal_entry_id uuid,
|
|
mcc_code integer,
|
|
merchant_name text,
|
|
receipt_id uuid,
|
|
notes text,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
|
|
unique (user_id, external_id)
|
|
);
|
|
|
|
alter table public.transactions enable row level security;
|
|
|
|
create policy "transactions_select" on public.transactions
|
|
for select using (auth.uid() = user_id);
|
|
create policy "transactions_insert" on public.transactions
|
|
for insert with check (auth.uid() = user_id);
|
|
create policy "transactions_update" on public.transactions
|
|
for update using (auth.uid() = user_id);
|
|
create policy "transactions_delete" on public.transactions
|
|
for delete using (auth.uid() = user_id);
|
|
|
|
create index idx_transactions_user_id on public.transactions (user_id);
|
|
create index idx_transactions_date on public.transactions (date);
|
|
create index idx_transactions_external_id on public.transactions (external_id);
|
|
create index idx_transactions_category on public.transactions (category);
|
|
|
|
create trigger transactions_updated_at
|
|
before update on public.transactions
|
|
for each row execute function public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 8. salary_payments
|
|
-- =============================================================================
|
|
create table public.salary_payments (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
user_id uuid references auth.users on delete cascade not null,
|
|
gross_amount numeric not null,
|
|
net_amount numeric not null,
|
|
employer_tax numeric not null,
|
|
preliminary_tax numeric not null,
|
|
payment_date date not null,
|
|
period_start date not null,
|
|
period_end date not null,
|
|
status text default 'planned'
|
|
check (status in ('planned', 'paid', 'reported')),
|
|
notes text,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.salary_payments enable row level security;
|
|
|
|
create policy "salary_payments_select" on public.salary_payments
|
|
for select using (auth.uid() = user_id);
|
|
create policy "salary_payments_insert" on public.salary_payments
|
|
for insert with check (auth.uid() = user_id);
|
|
create policy "salary_payments_update" on public.salary_payments
|
|
for update using (auth.uid() = user_id);
|
|
create policy "salary_payments_delete" on public.salary_payments
|
|
for delete using (auth.uid() = user_id);
|
|
|
|
create index idx_salary_payments_user_id on public.salary_payments (user_id);
|
|
|
|
create trigger salary_payments_updated_at
|
|
before update on public.salary_payments
|
|
for each row execute function public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 9. tax_rates (read-only reference table)
|
|
-- =============================================================================
|
|
create table public.tax_rates (
|
|
id uuid primary key default uuid_generate_v4(),
|
|
rate_type text not null,
|
|
rate numeric not null,
|
|
valid_from date not null,
|
|
valid_to date,
|
|
description text
|
|
);
|
|
|
|
alter table public.tax_rates enable row level security;
|
|
|
|
create policy "tax_rates_select" on public.tax_rates
|
|
for select to authenticated using (true);
|
|
|
|
-- Seed current Swedish tax rates
|
|
insert into public.tax_rates (rate_type, rate, valid_from, description) values
|
|
('egenavgifter', 28.97, '2024-01-01', 'Egenavgifter for enskild firma'),
|
|
('bolagsskatt', 20.6, '2019-01-01', 'Bolagsskatt (corporate tax)'),
|
|
('arbetsgivaravgifter', 31.42, '2024-01-01', 'Arbetsgivaravgifter (employer social contributions)');
|