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>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
cd96e5ec26
commit
3fa871c742
@@ -0,0 +1,17 @@
|
||||
-- =============================================================================
|
||||
-- Salary: optional vacation accrual ("none" mode)
|
||||
-- =============================================================================
|
||||
-- Adds a third value to employees.vacation_rule so a company can disable
|
||||
-- semesteravsättning entirely. Useful for sole owners (ägare = enda anställd)
|
||||
-- who don't accrue semester — booking otherwise creates a phantom 2920 liability.
|
||||
--
|
||||
-- procentregeln — 12% (or 14.4% for 30+ days) accrued to 2920 (Semesterlagen 26§)
|
||||
-- sammaloneregeln — semestertillägg only (Semesterlagen 16a§)
|
||||
-- none — no accrual; salary expense is the full cost
|
||||
|
||||
ALTER TABLE public.employees DROP CONSTRAINT IF EXISTS employees_vacation_rule_check;
|
||||
|
||||
ALTER TABLE public.employees ADD CONSTRAINT employees_vacation_rule_check
|
||||
CHECK (vacation_rule IN ('procentregeln', 'sammaloneregeln', 'none'));
|
||||
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
@@ -0,0 +1,93 @@
|
||||
-- =============================================================================
|
||||
-- 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';
|
||||
@@ -0,0 +1,32 @@
|
||||
-- Simplify company naming: remove trade_name field entirely.
|
||||
-- Add invoice_show_logo toggle for invoice PDF header.
|
||||
-- Migrate any existing trade_name into company_name so users who set a
|
||||
-- handelsnamn keep their preferred invoice display name. Existing users
|
||||
-- should be notified to verify company_name against Bolagsverket since
|
||||
-- this value is used in SIE/INK2/NE filings.
|
||||
|
||||
ALTER TABLE public.company_settings
|
||||
ADD COLUMN IF NOT EXISTS invoice_show_logo boolean DEFAULT true;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'company_settings'
|
||||
AND column_name = 'trade_name'
|
||||
) THEN
|
||||
EXECUTE $sql$
|
||||
UPDATE public.company_settings
|
||||
SET company_name = trade_name
|
||||
WHERE trade_name IS NOT NULL
|
||||
AND trade_name <> ''
|
||||
AND trade_name <> company_name
|
||||
$sql$;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE public.company_settings
|
||||
DROP COLUMN IF EXISTS trade_name;
|
||||
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
Reference in New Issue
Block a user