Files
accounted/supabase/migrations/20260512200100_employee_benefits.sql
Mattsson 3fa871c742 Bug/accounting suggestion (#456)
* feat: add bike benefit handling and optional vacation accrual

- Introduced bike benefit (cykelförmån) with calculations for annual market value and monthly taxable value.
- Updated schemas to include new benefit types and validation rules.
- Implemented API routes for creating, updating, and deleting employee benefits.
- Enhanced salary calculation logic to accommodate new vacation rule options, including a 'none' option for no accrual.
- Added UI components for managing employee benefits, including input for bike benefit specifics.
- Created database migrations for employee benefits and updated salary line items to support new benefit types.

* chore: remove Langfuse env var checks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: enhance OAuth callback URL handling and update default scopes for Visma integration

* feat: remove trade_name field and simplify company naming in invoices

* refactor: destructure canWrite from useCanWrite for consistency across components

* feat: enhance PATCH endpoint to validate existing benefits and handle bike benefit updates

* feat: add missing label for bike benefit in salary line item types

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:26:04 +02:00

94 lines
4.2 KiB
SQL

-- =============================================================================
-- Employee benefits (förmånshantering)
-- =============================================================================
-- Per-employee benefit assignments that drive auto-generated line items on
-- every salary run. Today the engine has helpers for car/meal/wellness benefits
-- but no link from a specific employee to a specific benefit instance — meaning
-- users had to add a benefit_* line item manually each month. This table closes
-- that gap.
--
-- Includes bike benefit (cykelförmån, skattefri schablon 3 000 kr/år from 2022,
-- Skatteverket).
CREATE TABLE public.employee_benefits (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
employee_id uuid NOT NULL REFERENCES public.employees(id) ON DELETE CASCADE,
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
benefit_type text NOT NULL
CHECK (benefit_type IN ('bike', 'car', 'meals', 'housing', 'wellness', 'other')),
description text NOT NULL,
-- Monthly taxable förmånsvärde (SEK). For bike, computed as
-- max(0, annual_market_value - 3000) / 12 per Skatteverket schablon.
monthly_value numeric NOT NULL CHECK (monthly_value >= 0),
valid_from date NOT NULL,
valid_to date,
-- Free-form context: bike → { annual_market_value, tax_free_allowance }
-- car → { nybilspris, environmental_type, ... }
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (valid_to IS NULL OR valid_to >= valid_from)
);
ALTER TABLE public.employee_benefits ENABLE ROW LEVEL SECURITY;
CREATE POLICY "employee_benefits_select" ON public.employee_benefits
FOR SELECT USING (company_id IN (SELECT public.user_company_ids()));
CREATE POLICY "employee_benefits_insert" ON public.employee_benefits
FOR INSERT WITH CHECK (company_id IN (SELECT public.user_company_ids()));
CREATE POLICY "employee_benefits_update" ON public.employee_benefits
FOR UPDATE USING (company_id IN (SELECT public.user_company_ids()));
CREATE POLICY "employee_benefits_delete" ON public.employee_benefits
FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
CREATE INDEX idx_employee_benefits_employee ON public.employee_benefits (employee_id);
CREATE INDEX idx_employee_benefits_company ON public.employee_benefits (company_id);
CREATE INDEX idx_employee_benefits_active
ON public.employee_benefits (employee_id, valid_from, valid_to)
WHERE is_active = true;
CREATE TRIGGER employee_benefits_updated_at
BEFORE UPDATE ON public.employee_benefits
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
-- Link a salary_line_items row back to the employee_benefits row that produced
-- it. Used by the calculate flow to delete-then-recreate benefit line items
-- without disturbing manually-added line items.
ALTER TABLE public.salary_line_items
ADD COLUMN source_benefit_id uuid REFERENCES public.employee_benefits(id) ON DELETE SET NULL;
CREATE INDEX idx_salary_line_items_source_benefit
ON public.salary_line_items (source_benefit_id)
WHERE source_benefit_id IS NOT NULL;
-- Extend salary_line_items to accept a benefit_bike type for cykelförmån.
ALTER TABLE public.salary_line_items DROP CONSTRAINT IF EXISTS salary_line_items_item_type_check;
ALTER TABLE public.salary_line_items ADD CONSTRAINT salary_line_items_item_type_check
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_bike', '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'
));
NOTIFY pgrst, 'reload schema';