04dbb31d7e
* feat: implement salary module with personnummer encryption, salary entries, tax tables, and AGI tracking - Added personnummer encryption and decryption functions for secure storage. - Created salary entries handling for journal entries including gross salary, tax withholding, and employer contributions. - Implemented tax table lookup functionality for calculating tax amounts based on monthly income. - Developed SQL migration for salary module including tables for payroll configuration, tax rates, employees, salary runs, salary run employees, salary line items, and AGI declarations. - Established row-level security policies for all new tables to ensure company-scoped access. * feat: add salary calculation modules for 2026 - Implemented engångsskatt calculation for one-time payments with tax brackets. - Added löneväxling functionality for salary sacrifice to pension, including employer savings and warnings. - Created pain.001 generator for salary batch payments in compliance with Swedish banking standards. - Developed PDF template for payslips, including detailed breakdowns and employer costs. - Generated seed data for Swedish tax tables for 2026, including SQL insert statements. - Implemented traktamente calculations for per diem and mileage allowances, adhering to Skatteverket regulations. - Added seed script for populating tax tables in the database. * feat: Update meal reduction percentages in traktamente calculation fix: Remove obsolete seed script for 2026 tax tables feat: Extend SalaryRunStatus type to include 'corrected' status feat: Implement KU10 XML generation endpoint for annual employee income statements feat: Add endpoint for creating corrections to booked salary runs feat: Implement endpoint for sending payslip PDFs to employees feat: Create KU10 XML generator for annual reporting feat: Add salary transaction matcher for auto-linking bank transactions to salary entries chore: Add database migration for salary correction support * feat: replace select elements with custom Select component for employment and salary types * feat: enhance salary calculations with pension entry and avgifter category support
539 lines
21 KiB
PL/PgSQL
539 lines
21 KiB
PL/PgSQL
-- =============================================================================
|
|
-- Salary Module (Lönehantering)
|
|
-- =============================================================================
|
|
--
|
|
-- Comprehensive payroll module for Swedish AB companies:
|
|
-- - Employee register with personnummer encryption
|
|
-- - Salary runs with multi-step workflow
|
|
-- - Per-employee calculation results and line items
|
|
-- - Tax table reference data
|
|
-- - Annual payroll configuration (statutory rates)
|
|
-- - AGI declaration tracking
|
|
--
|
|
-- All tables are company-scoped with RLS via user_company_ids().
|
|
-- Soft-delete only for employees (BFL 7 kap, 7-year retention).
|
|
|
|
-- =============================================================================
|
|
-- 1. salary_payroll_config — Year-specific statutory rates (system table)
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE public.salary_payroll_config (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
config_year integer NOT NULL UNIQUE,
|
|
|
|
-- Arbetsgivaravgifter breakdown
|
|
avgifter_total numeric NOT NULL,
|
|
avgifter_alderspension numeric NOT NULL,
|
|
avgifter_sjukforsakring numeric NOT NULL,
|
|
avgifter_foraldraforsakring numeric NOT NULL,
|
|
avgifter_efterlevandepension numeric NOT NULL,
|
|
avgifter_arbetsmarknad numeric NOT NULL,
|
|
avgifter_arbetsskada numeric NOT NULL,
|
|
avgifter_allman_loneavgift numeric NOT NULL,
|
|
avgifter_reduced_65plus numeric NOT NULL,
|
|
avgifter_youth_rate numeric,
|
|
avgifter_youth_salary_cap numeric,
|
|
avgifter_vaxa_stod_rate numeric,
|
|
avgifter_vaxa_stod_cap numeric,
|
|
avgifter_minimum_annual numeric NOT NULL,
|
|
|
|
-- Egenavgifter for EF
|
|
egenavgifter_total numeric NOT NULL,
|
|
|
|
-- SLP on pensions
|
|
slp_rate numeric NOT NULL,
|
|
|
|
-- Thresholds
|
|
prisbasbelopp numeric NOT NULL,
|
|
inkomstbasbelopp numeric NOT NULL,
|
|
max_pgi numeric NOT NULL,
|
|
sgi_ceiling numeric NOT NULL,
|
|
statlig_skatt_brytpunkt numeric NOT NULL,
|
|
|
|
-- Traktamente
|
|
traktamente_heldag numeric NOT NULL,
|
|
traktamente_halvdag numeric NOT NULL,
|
|
traktamente_natt numeric NOT NULL,
|
|
|
|
-- Milersättning
|
|
milersattning_egen_bil numeric NOT NULL,
|
|
milersattning_formansbil_fossil numeric NOT NULL,
|
|
milersattning_formansbil_el numeric NOT NULL,
|
|
|
|
-- Benefits
|
|
kostforman_heldag numeric NOT NULL,
|
|
kostforman_lunch numeric NOT NULL,
|
|
kostforman_frukost numeric NOT NULL,
|
|
friskvard_cap numeric NOT NULL,
|
|
bilforman_slr numeric NOT NULL,
|
|
|
|
-- Sjuklön
|
|
sjuklon_rate numeric NOT NULL DEFAULT 0.80,
|
|
karensavdrag_factor numeric NOT NULL DEFAULT 0.20,
|
|
max_karensavdrag_per_year integer NOT NULL DEFAULT 10,
|
|
|
|
-- Age thresholds
|
|
reduced_avgift_age integer NOT NULL,
|
|
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
ALTER TABLE public.salary_payroll_config ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "payroll_config_select" ON public.salary_payroll_config
|
|
FOR SELECT USING (auth.role() = 'authenticated');
|
|
|
|
-- Seed 2026 values
|
|
INSERT INTO public.salary_payroll_config (
|
|
config_year,
|
|
avgifter_total, avgifter_alderspension, avgifter_sjukforsakring,
|
|
avgifter_foraldraforsakring, avgifter_efterlevandepension,
|
|
avgifter_arbetsmarknad, avgifter_arbetsskada, avgifter_allman_loneavgift,
|
|
avgifter_reduced_65plus, avgifter_youth_rate, avgifter_youth_salary_cap,
|
|
avgifter_vaxa_stod_rate, avgifter_vaxa_stod_cap, avgifter_minimum_annual,
|
|
egenavgifter_total, slp_rate,
|
|
prisbasbelopp, inkomstbasbelopp, max_pgi, sgi_ceiling, statlig_skatt_brytpunkt,
|
|
traktamente_heldag, traktamente_halvdag, traktamente_natt,
|
|
milersattning_egen_bil, milersattning_formansbil_fossil, milersattning_formansbil_el,
|
|
kostforman_heldag, kostforman_lunch, kostforman_frukost,
|
|
friskvard_cap, bilforman_slr,
|
|
sjuklon_rate, karensavdrag_factor, max_karensavdrag_per_year,
|
|
reduced_avgift_age
|
|
) VALUES (
|
|
2026,
|
|
0.3142, 0.1021, 0.0355,
|
|
0.0200, 0.0030,
|
|
0.0264, 0.0010, 0.1262,
|
|
0.1021, 0.2081, 25000,
|
|
0.1021, 35000, 1000,
|
|
0.2897, 0.2426,
|
|
59200, 83400, 625500, 592000, 660400,
|
|
300, 150, 150,
|
|
25, 12, 9.50,
|
|
310, 124, 62,
|
|
5000, 0.0255,
|
|
0.80, 0.20, 10,
|
|
67
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 2. tax_table_rates — Skatteverket annual tax tables (system table)
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE public.tax_table_rates (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
table_year integer NOT NULL,
|
|
table_number integer NOT NULL,
|
|
column_number integer NOT NULL,
|
|
income_from numeric NOT NULL,
|
|
income_to numeric NOT NULL,
|
|
tax_amount numeric NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
UNIQUE (table_year, table_number, column_number, income_from)
|
|
);
|
|
|
|
ALTER TABLE public.tax_table_rates ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY "tax_tables_select" ON public.tax_table_rates
|
|
FOR SELECT USING (auth.role() = 'authenticated');
|
|
|
|
CREATE INDEX idx_tax_table_rates_lookup
|
|
ON public.tax_table_rates (table_year, table_number, column_number, income_from);
|
|
|
|
-- =============================================================================
|
|
-- 3. employees — Company-scoped employee register
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE public.employees (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
|
|
-- Identity
|
|
first_name text NOT NULL,
|
|
last_name text NOT NULL,
|
|
personnummer text NOT NULL,
|
|
personnummer_last4 text NOT NULL,
|
|
|
|
-- Employment
|
|
employment_type text NOT NULL DEFAULT 'employee'
|
|
CHECK (employment_type IN ('employee', 'company_owner', 'board_member')),
|
|
employment_start date NOT NULL,
|
|
employment_end date,
|
|
employment_degree numeric NOT NULL DEFAULT 100
|
|
CHECK (employment_degree > 0 AND employment_degree <= 100),
|
|
|
|
-- Salary
|
|
salary_type text NOT NULL DEFAULT 'monthly'
|
|
CHECK (salary_type IN ('monthly', 'hourly')),
|
|
monthly_salary numeric,
|
|
hourly_rate numeric,
|
|
|
|
-- Tax
|
|
tax_table_number integer,
|
|
tax_column integer DEFAULT 1
|
|
CHECK (tax_column BETWEEN 1 AND 6),
|
|
tax_municipality text,
|
|
jamkning_percentage numeric,
|
|
jamkning_valid_from date,
|
|
jamkning_valid_to date,
|
|
is_sidoinkomst boolean NOT NULL DEFAULT false,
|
|
|
|
-- F-skatt
|
|
f_skatt_status text DEFAULT 'a_skatt'
|
|
CHECK (f_skatt_status IN ('a_skatt', 'f_skatt', 'fa_skatt', 'not_verified')),
|
|
f_skatt_verified_at date,
|
|
|
|
-- Bank
|
|
clearing_number text,
|
|
bank_account_number text,
|
|
|
|
-- Vacation
|
|
vacation_rule text NOT NULL DEFAULT 'procentregeln'
|
|
CHECK (vacation_rule IN ('procentregeln', 'sammaloneregeln')),
|
|
vacation_days_per_year integer NOT NULL DEFAULT 25,
|
|
vacation_days_saved integer NOT NULL DEFAULT 0,
|
|
semestertillagg_rate numeric NOT NULL DEFAULT 0.0043,
|
|
|
|
-- Contact
|
|
email text,
|
|
phone text,
|
|
address_line1 text,
|
|
postal_code text,
|
|
city text,
|
|
|
|
-- AGI
|
|
specification_number integer,
|
|
|
|
-- Växa-stöd
|
|
vaxa_stod_eligible boolean NOT NULL DEFAULT false,
|
|
vaxa_stod_start date,
|
|
vaxa_stod_end date,
|
|
|
|
is_active boolean NOT NULL DEFAULT true,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
UNIQUE (company_id, personnummer)
|
|
);
|
|
|
|
ALTER TABLE public.employees ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "employees_select" ON public.employees
|
|
FOR SELECT USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "employees_insert" ON public.employees
|
|
FOR INSERT WITH CHECK (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "employees_update" ON public.employees
|
|
FOR UPDATE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "employees_delete" ON public.employees
|
|
FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE INDEX idx_employees_company ON public.employees (company_id);
|
|
CREATE INDEX idx_employees_active ON public.employees (company_id, is_active) WHERE is_active = true;
|
|
|
|
CREATE TRIGGER employees_updated_at
|
|
BEFORE UPDATE ON public.employees
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 4. salary_runs — Monthly salary batch container
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE public.salary_runs (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
|
|
period_year integer NOT NULL,
|
|
period_month integer NOT NULL CHECK (period_month BETWEEN 1 AND 12),
|
|
payment_date date NOT NULL,
|
|
|
|
status text NOT NULL DEFAULT 'draft'
|
|
CHECK (status IN ('draft', 'review', 'approved', 'paid', 'booked')),
|
|
|
|
voucher_series text NOT NULL DEFAULT 'A'
|
|
CHECK (voucher_series ~ '^[A-Z]$'),
|
|
|
|
-- Denormalized totals
|
|
total_gross numeric NOT NULL DEFAULT 0,
|
|
total_tax numeric NOT NULL DEFAULT 0,
|
|
total_net numeric NOT NULL DEFAULT 0,
|
|
total_avgifter numeric NOT NULL DEFAULT 0,
|
|
total_vacation_accrual numeric NOT NULL DEFAULT 0,
|
|
total_employer_cost numeric NOT NULL DEFAULT 0,
|
|
|
|
-- Journal entry refs
|
|
salary_entry_id uuid REFERENCES public.journal_entries(id),
|
|
avgifter_entry_id uuid REFERENCES public.journal_entries(id),
|
|
vacation_entry_id uuid REFERENCES public.journal_entries(id),
|
|
|
|
-- AGI tracking
|
|
agi_generated_at timestamptz,
|
|
agi_submitted_at timestamptz,
|
|
|
|
-- Calculation parameters snapshot
|
|
calculation_params jsonb,
|
|
|
|
-- Audit
|
|
approved_by uuid REFERENCES auth.users(id),
|
|
approved_at timestamptz,
|
|
paid_at timestamptz,
|
|
booked_at timestamptz,
|
|
booked_by uuid REFERENCES auth.users(id),
|
|
notes text,
|
|
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
UNIQUE (company_id, period_year, period_month)
|
|
);
|
|
|
|
ALTER TABLE public.salary_runs ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "salary_runs_select" ON public.salary_runs
|
|
FOR SELECT USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "salary_runs_insert" ON public.salary_runs
|
|
FOR INSERT WITH CHECK (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "salary_runs_update" ON public.salary_runs
|
|
FOR UPDATE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "salary_runs_delete" ON public.salary_runs
|
|
FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE INDEX idx_salary_runs_company ON public.salary_runs (company_id);
|
|
CREATE INDEX idx_salary_runs_period ON public.salary_runs (company_id, period_year, period_month);
|
|
CREATE INDEX idx_salary_runs_status ON public.salary_runs (status);
|
|
|
|
CREATE TRIGGER salary_runs_updated_at
|
|
BEFORE UPDATE ON public.salary_runs
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 5. salary_run_employees — Per-employee calculation results
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE public.salary_run_employees (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
salary_run_id uuid NOT NULL REFERENCES public.salary_runs(id) ON DELETE CASCADE,
|
|
employee_id uuid NOT NULL REFERENCES public.employees(id) ON DELETE RESTRICT,
|
|
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
|
|
-- Snapshots
|
|
employment_degree numeric NOT NULL,
|
|
monthly_salary numeric NOT NULL,
|
|
salary_type text NOT NULL,
|
|
hours_worked numeric,
|
|
|
|
-- Calculation results
|
|
gross_salary numeric NOT NULL DEFAULT 0,
|
|
gross_deductions numeric NOT NULL DEFAULT 0,
|
|
benefit_values numeric NOT NULL DEFAULT 0,
|
|
taxable_income numeric NOT NULL DEFAULT 0,
|
|
tax_withheld numeric NOT NULL DEFAULT 0,
|
|
net_deductions numeric NOT NULL DEFAULT 0,
|
|
net_salary numeric NOT NULL DEFAULT 0,
|
|
|
|
-- Employer contributions
|
|
avgifter_rate numeric NOT NULL DEFAULT 0.3142,
|
|
avgifter_amount numeric NOT NULL DEFAULT 0,
|
|
avgifter_basis numeric NOT NULL DEFAULT 0,
|
|
|
|
-- Vacation
|
|
vacation_accrual numeric NOT NULL DEFAULT 0,
|
|
vacation_accrual_avgifter numeric NOT NULL DEFAULT 0,
|
|
|
|
-- Tax snapshot
|
|
tax_table_number integer,
|
|
tax_column integer,
|
|
tax_table_year integer,
|
|
|
|
-- Absence summary
|
|
sick_days numeric NOT NULL DEFAULT 0,
|
|
vab_days numeric NOT NULL DEFAULT 0,
|
|
parental_days numeric NOT NULL DEFAULT 0,
|
|
vacation_days_taken numeric NOT NULL DEFAULT 0,
|
|
|
|
-- Calculation breakdown
|
|
calculation_breakdown jsonb,
|
|
|
|
-- YTD tracking
|
|
ytd_gross numeric NOT NULL DEFAULT 0,
|
|
ytd_tax numeric NOT NULL DEFAULT 0,
|
|
ytd_net numeric NOT NULL DEFAULT 0,
|
|
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
UNIQUE (salary_run_id, employee_id)
|
|
);
|
|
|
|
ALTER TABLE public.salary_run_employees ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "sre_select" ON public.salary_run_employees
|
|
FOR SELECT USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "sre_insert" ON public.salary_run_employees
|
|
FOR INSERT WITH CHECK (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "sre_update" ON public.salary_run_employees
|
|
FOR UPDATE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "sre_delete" ON public.salary_run_employees
|
|
FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE INDEX idx_sre_salary_run ON public.salary_run_employees (salary_run_id);
|
|
CREATE INDEX idx_sre_employee ON public.salary_run_employees (employee_id);
|
|
CREATE INDEX idx_sre_company ON public.salary_run_employees (company_id);
|
|
|
|
CREATE TRIGGER sre_updated_at
|
|
BEFORE UPDATE ON public.salary_run_employees
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 6. salary_line_items — Individual pay slip line items
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE public.salary_line_items (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
salary_run_employee_id uuid NOT NULL REFERENCES public.salary_run_employees(id) ON DELETE CASCADE,
|
|
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
|
|
item_type text NOT NULL
|
|
CHECK (item_type IN (
|
|
'monthly_salary', 'hourly_salary', 'overtime', 'bonus', 'commission',
|
|
'gross_deduction_pension', 'gross_deduction_other',
|
|
'benefit_car', 'benefit_housing', 'benefit_meals', 'benefit_wellness', 'benefit_other',
|
|
'sick_karens', 'sick_day2_14', 'sick_day15_plus',
|
|
'vab', 'parental_leave', 'vacation',
|
|
'traktamente_taxfree', 'traktamente_taxable',
|
|
'mileage_taxfree', 'mileage_taxable',
|
|
'net_deduction_advance', 'net_deduction_union', 'net_deduction_benefit_payment',
|
|
'net_deduction_other',
|
|
'correction', 'other'
|
|
)),
|
|
|
|
description text NOT NULL,
|
|
quantity numeric,
|
|
unit_price numeric,
|
|
amount numeric NOT NULL,
|
|
|
|
-- Compliance flags
|
|
is_taxable boolean NOT NULL DEFAULT true,
|
|
is_avgift_basis boolean NOT NULL DEFAULT true,
|
|
is_vacation_basis boolean NOT NULL DEFAULT true,
|
|
is_gross_deduction boolean NOT NULL DEFAULT false,
|
|
is_net_deduction boolean NOT NULL DEFAULT false,
|
|
|
|
-- Account mapping
|
|
account_number text,
|
|
sort_order integer NOT NULL DEFAULT 0,
|
|
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
ALTER TABLE public.salary_line_items ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "sli_select" ON public.salary_line_items
|
|
FOR SELECT USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "sli_insert" ON public.salary_line_items
|
|
FOR INSERT WITH CHECK (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "sli_update" ON public.salary_line_items
|
|
FOR UPDATE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "sli_delete" ON public.salary_line_items
|
|
FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE INDEX idx_sli_sre ON public.salary_line_items (salary_run_employee_id);
|
|
CREATE INDEX idx_sli_company ON public.salary_line_items (company_id);
|
|
|
|
CREATE TRIGGER sli_updated_at
|
|
BEFORE UPDATE ON public.salary_line_items
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- 7. agi_declarations — AGI filing tracking
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE public.agi_declarations (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
salary_run_id uuid REFERENCES public.salary_runs(id),
|
|
|
|
period_year integer NOT NULL,
|
|
period_month integer NOT NULL,
|
|
xml_content text NOT NULL,
|
|
status text NOT NULL DEFAULT 'generated'
|
|
CHECK (status IN ('generated', 'exported', 'submitted', 'accepted', 'rejected')),
|
|
|
|
individuppgifter jsonb NOT NULL,
|
|
|
|
total_gross numeric NOT NULL DEFAULT 0,
|
|
total_tax numeric NOT NULL DEFAULT 0,
|
|
total_avgifter_basis numeric NOT NULL DEFAULT 0,
|
|
total_avgifter numeric NOT NULL DEFAULT 0,
|
|
employee_count integer NOT NULL DEFAULT 0,
|
|
|
|
kvittensnummer text,
|
|
submitted_at timestamptz,
|
|
submitted_by uuid REFERENCES auth.users(id),
|
|
response_data jsonb,
|
|
|
|
is_correction boolean NOT NULL DEFAULT false,
|
|
corrects_agi_id uuid REFERENCES public.agi_declarations(id),
|
|
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
UNIQUE (company_id, period_year, period_month)
|
|
);
|
|
|
|
ALTER TABLE public.agi_declarations ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY "agi_select" ON public.agi_declarations
|
|
FOR SELECT USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "agi_insert" ON public.agi_declarations
|
|
FOR INSERT WITH CHECK (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE POLICY "agi_update" ON public.agi_declarations
|
|
FOR UPDATE USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
CREATE INDEX idx_agi_company ON public.agi_declarations (company_id);
|
|
CREATE INDEX idx_agi_period ON public.agi_declarations (company_id, period_year, period_month);
|
|
|
|
CREATE TRIGGER agi_updated_at
|
|
BEFORE UPDATE ON public.agi_declarations
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- Auto-assign specification_number for AGI FK570
|
|
-- =============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION public.assign_specification_number()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.specification_number IS NULL THEN
|
|
SELECT COALESCE(MAX(specification_number), 0) + 1
|
|
INTO NEW.specification_number
|
|
FROM public.employees
|
|
WHERE company_id = NEW.company_id;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER employees_assign_spec_number
|
|
BEFORE INSERT ON public.employees
|
|
FOR EACH ROW EXECUTE FUNCTION public.assign_specification_number();
|
|
|
|
-- Schema reload for PostgREST
|
|
NOTIFY pgrst, 'reload schema';
|