- Implemented OpeningBalanceResultStep component to display results of the import process, including success messages and error handling. - Created OpeningBalanceUploadStep component for file upload with drag-and-drop support, including validation for accepted file types. - Developed column detection logic in column-detector.ts to identify account number, name, debit, credit, and balance columns based on headers and data. - Added parser functionality in parser.ts to handle parsing of opening balance files, including validation and BAS account matching. - Created tests for column detection and parsing logic to ensure accuracy and reliability. - Defined types for detected columns and parsed rows in types.ts to improve type safety and clarity in the codebase.
391 lines
16 KiB
SQL
391 lines
16 KiB
SQL
-- =============================================================================
|
|
-- Booking Template Library
|
|
-- =============================================================================
|
|
--
|
|
-- Reusable journal entry templates (bokföringsmallar) for common scenarios
|
|
-- like EU reverse charge purchases, tax account bookings, private transfers.
|
|
--
|
|
-- Three scoping levels:
|
|
-- 1. System templates (company_id IS NULL, team_id IS NULL, is_system = TRUE)
|
|
-- Pre-seeded, visible to all authenticated users. Read-only.
|
|
-- 2. Team templates (company_id IS NULL, team_id IS NOT NULL)
|
|
-- Shared across all companies in a team. Created by team admins.
|
|
-- 3. Company templates (company_id IS NOT NULL)
|
|
-- Private to one company. Created by any non-viewer member.
|
|
--
|
|
-- Template lines use the same ratio-based pattern as categorization_templates
|
|
-- line_pattern: user enters total amount, system calculates each line.
|
|
|
|
CREATE TABLE public.booking_template_library (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
team_id UUID REFERENCES public.teams(id) ON DELETE CASCADE,
|
|
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
|
|
-- Template identity
|
|
name TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
category TEXT NOT NULL DEFAULT 'other'
|
|
CHECK (category IN (
|
|
'eu_trade', 'tax_account', 'private_transfer',
|
|
'salary', 'representation', 'year_end',
|
|
'vat', 'financial', 'other'
|
|
)),
|
|
entity_type TEXT NOT NULL DEFAULT 'all'
|
|
CHECK (entity_type IN ('all', 'enskild_firma', 'aktiebolag')),
|
|
|
|
-- Template lines (ratio-based pattern)
|
|
-- Array of: { account, label, side, type, ratio?, vat_rate? }
|
|
lines JSONB NOT NULL DEFAULT '[]',
|
|
|
|
-- Flags
|
|
is_system BOOLEAN NOT NULL DEFAULT FALSE,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Metadata
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
-- Constraints
|
|
-- System templates: no company, no team
|
|
CHECK (NOT is_system OR (company_id IS NULL AND team_id IS NULL)),
|
|
-- Team templates: no company
|
|
CHECK (team_id IS NULL OR company_id IS NULL),
|
|
-- Company or team or system — at least one scope
|
|
CHECK (company_id IS NOT NULL OR team_id IS NOT NULL OR is_system)
|
|
);
|
|
|
|
-- RLS
|
|
ALTER TABLE public.booking_template_library ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- SELECT: system templates + own company + own team templates
|
|
CREATE POLICY "btl_select" ON public.booking_template_library
|
|
FOR SELECT USING (
|
|
is_system
|
|
OR company_id IN (SELECT public.user_company_ids())
|
|
OR team_id IN (SELECT public.user_team_ids())
|
|
);
|
|
|
|
-- INSERT: company templates or team templates (viewer check enforced in API)
|
|
CREATE POLICY "btl_insert" ON public.booking_template_library
|
|
FOR INSERT WITH CHECK (
|
|
NOT is_system
|
|
AND (
|
|
company_id IN (SELECT public.user_company_ids())
|
|
OR (company_id IS NULL AND team_id IN (SELECT public.user_team_ids()))
|
|
)
|
|
);
|
|
|
|
-- UPDATE: own company or own team templates, never system
|
|
CREATE POLICY "btl_update" ON public.booking_template_library
|
|
FOR UPDATE USING (
|
|
NOT is_system
|
|
AND (
|
|
company_id IN (SELECT public.user_company_ids())
|
|
OR (company_id IS NULL AND team_id IN (SELECT public.user_team_ids()))
|
|
)
|
|
);
|
|
|
|
-- DELETE: own company or own team templates, never system
|
|
CREATE POLICY "btl_delete" ON public.booking_template_library
|
|
FOR DELETE USING (
|
|
NOT is_system
|
|
AND (
|
|
company_id IN (SELECT public.user_company_ids())
|
|
OR (company_id IS NULL AND team_id IN (SELECT public.user_team_ids()))
|
|
)
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_btl_company ON public.booking_template_library (company_id)
|
|
WHERE company_id IS NOT NULL;
|
|
CREATE INDEX idx_btl_team ON public.booking_template_library (team_id)
|
|
WHERE team_id IS NOT NULL;
|
|
CREATE INDEX idx_btl_system ON public.booking_template_library (is_system)
|
|
WHERE is_system = TRUE;
|
|
CREATE INDEX idx_btl_category ON public.booking_template_library (category);
|
|
CREATE INDEX idx_btl_active ON public.booking_template_library (is_active)
|
|
WHERE is_active = TRUE;
|
|
|
|
-- updated_at trigger
|
|
CREATE TRIGGER btl_updated_at
|
|
BEFORE UPDATE ON public.booking_template_library
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- =============================================================================
|
|
-- Seed system templates
|
|
-- =============================================================================
|
|
-- Lines format: [{ account, label, side, type, ratio?, vat_rate? }]
|
|
-- type: 'business' (main account), 'vat' (VAT line), 'settlement' (bank/cash)
|
|
-- ratio: proportion of total amount (business lines should sum to 1.0)
|
|
-- vat_rate: decimal (0.25, 0.12, 0.06) — applied via rate/(1+rate) for inclusive
|
|
|
|
-- EU TRADE
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Inköp EU-varor, omvänd moms 25%',
|
|
'Köp av varor från annat EU-land. Omvänd skattskyldighet — du redovisar både utgående och ingående moms.',
|
|
'eu_trade', 'all', TRUE,
|
|
'[
|
|
{"account": "4010", "label": "Varuinköp", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "2614", "label": "Utgående moms omvänd skattskyldighet 25%", "side": "credit", "type": "vat", "vat_rate": 0.25},
|
|
{"account": "2645", "label": "Beräknad ingående moms 25%", "side": "debit", "type": "vat", "vat_rate": 0.25},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Inköp EU-tjänster, omvänd moms 25%',
|
|
'Köp av tjänster från annat EU-land. Omvänd skattskyldighet — du redovisar både utgående och ingående moms.',
|
|
'eu_trade', 'all', TRUE,
|
|
'[
|
|
{"account": "6540", "label": "IT-tjänster", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "2614", "label": "Utgående moms omvänd skattskyldighet 25%", "side": "credit", "type": "vat", "vat_rate": 0.25},
|
|
{"account": "2645", "label": "Beräknad ingående moms 25%", "side": "debit", "type": "vat", "vat_rate": 0.25},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Försäljning EU-tjänster (B2B)',
|
|
'Tjänsteförsäljning till annat EU-land (B2B). Ingen moms — kunden redovisar omvänd skattskyldighet.',
|
|
'eu_trade', 'all', TRUE,
|
|
'[
|
|
{"account": "1510", "label": "Kundfordringar", "side": "debit", "type": "settlement", "ratio": 1.0},
|
|
{"account": "3308", "label": "Försäljning tjänster EU", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Försäljning export (utanför EU)',
|
|
'Försäljning till land utanför EU. Momsfritt.',
|
|
'eu_trade', 'all', TRUE,
|
|
'[
|
|
{"account": "1510", "label": "Kundfordringar", "side": "debit", "type": "settlement", "ratio": 1.0},
|
|
{"account": "3305", "label": "Försäljning export", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- TAX ACCOUNT
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Insättning skattekonto',
|
|
'Betalning från företagskonto till skattekontot hos Skatteverket.',
|
|
'tax_account', 'all', TRUE,
|
|
'[
|
|
{"account": "1630", "label": "Skattekonto", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Skatteåterbäring',
|
|
'Återbetalning från skattekontot till företagskonto.',
|
|
'tax_account', 'all', TRUE,
|
|
'[
|
|
{"account": "1930", "label": "Företagskonto", "side": "debit", "type": "settlement", "ratio": 1.0},
|
|
{"account": "1630", "label": "Skattekonto", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Preliminär F-skatt (EF)',
|
|
'Betalning av preliminär F-skatt från skattekontot (enskild firma).',
|
|
'tax_account', 'enskild_firma', TRUE,
|
|
'[
|
|
{"account": "2012", "label": "Egna skatter", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1630", "label": "Skattekonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Preliminär F-skatt (AB)',
|
|
'Betalning av preliminär bolagsskatt från skattekontot.',
|
|
'tax_account', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "2518", "label": "Betald F-skatt", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1630", "label": "Skattekonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Momsbetalning via skattekonto',
|
|
'Moms som dras från skattekontot efter momsdeklaration.',
|
|
'tax_account', 'all', TRUE,
|
|
'[
|
|
{"account": "2650", "label": "Redovisningskonto moms", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1630", "label": "Skattekonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Arbetsgivaravgifter via skattekonto',
|
|
'Arbetsgivaravgifter som dras från skattekontot.',
|
|
'tax_account', 'all', TRUE,
|
|
'[
|
|
{"account": "2731", "label": "Avräkning sociala avgifter", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1630", "label": "Skattekonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- PRIVATE TRANSFERS
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Eget uttag',
|
|
'Privat uttag från företagskontot (enskild firma).',
|
|
'private_transfer', 'enskild_firma', TRUE,
|
|
'[
|
|
{"account": "2013", "label": "Egna uttag", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Eget insättning',
|
|
'Privat insättning till företagskontot (enskild firma).',
|
|
'private_transfer', 'enskild_firma', TRUE,
|
|
'[
|
|
{"account": "1930", "label": "Företagskonto", "side": "debit", "type": "settlement", "ratio": 1.0},
|
|
{"account": "2018", "label": "Egna insättningar", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Aktieägarlån — insättning',
|
|
'Ägaren sätter in pengar som lån till bolaget.',
|
|
'private_transfer', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "1930", "label": "Företagskonto", "side": "debit", "type": "settlement", "ratio": 1.0},
|
|
{"account": "2893", "label": "Skuld till aktieägare", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Aktieägarlån — återbetalning',
|
|
'Bolaget betalar tillbaka lån till ägaren.',
|
|
'private_transfer', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "2893", "label": "Skuld till aktieägare", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Utdelning till aktieägare',
|
|
'Utbetalning av beslutad utdelning till aktieägare.',
|
|
'private_transfer', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "2898", "label": "Outtagen utdelning", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- SALARY
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Löneutbetalning',
|
|
'Utbetalning av nettolön till anställd.',
|
|
'salary', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "2710", "label": "Personalskatt", "side": "debit", "type": "business", "ratio": 0.3},
|
|
{"account": "2920", "label": "Upplupna semesterlöner", "side": "debit", "type": "business", "ratio": 0.12},
|
|
{"account": "7010", "label": "Löner", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Arbetsgivaravgifter',
|
|
'Bokföring av arbetsgivaravgifter (31,42% av bruttolön).',
|
|
'salary', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "7510", "label": "Arbetsgivaravgifter", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "2731", "label": "Avräkning sociala avgifter", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- REPRESENTATION
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Representation (avdragsgill, 25% moms)',
|
|
'Extern representation med avdragsgill moms. Max 300 kr/person exkl. moms.',
|
|
'representation', 'all', TRUE,
|
|
'[
|
|
{"account": "6072", "label": "Representation avdragsgill", "side": "debit", "type": "business", "ratio": 0.8},
|
|
{"account": "2641", "label": "Ingående moms", "side": "debit", "type": "vat", "vat_rate": 0.25},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- YEAR-END / FINANCIAL
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Periodiseringsfond avsättning (AB)',
|
|
'Avsättning till periodiseringsfond vid bokslut. Max 25% av överskottet.',
|
|
'year_end', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "8811", "label": "Avsättning periodiseringsfond", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "2113", "label": "Periodiseringsfond", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Periodiseringsfond återföring (AB)',
|
|
'Återföring av periodiseringsfond (senast efter 6 år).',
|
|
'year_end', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "2113", "label": "Periodiseringsfond", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "8819", "label": "Återföring periodiseringsfond", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Beräknad bolagsskatt',
|
|
'Bokföring av beräknad inkomstskatt vid bokslut.',
|
|
'year_end', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "8910", "label": "Skatt på årets resultat", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "2512", "label": "Beräknad inkomstskatt", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Överavskrivning inventarier',
|
|
'Bokföring av överavskrivning (skillnad räkenskapsenlig vs planenlig).',
|
|
'year_end', 'aktiebolag', TRUE,
|
|
'[
|
|
{"account": "8850", "label": "Förändring överavskrivning", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "2150", "label": "Ackumulerade överavskrivningar", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- VAT
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Momsredovisning (nettning)',
|
|
'Nettning av momskonton vid momsdeklaration. Justera konton och belopp efter din deklaration.',
|
|
'vat', 'all', TRUE,
|
|
'[
|
|
{"account": "2611", "label": "Utgående moms 25%", "side": "debit", "type": "business", "ratio": 0.5},
|
|
{"account": "2641", "label": "Ingående moms", "side": "credit", "type": "business", "ratio": 0.3},
|
|
{"account": "2650", "label": "Redovisningskonto moms", "side": "credit", "type": "business", "ratio": 0.2}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- FINANCIAL
|
|
INSERT INTO public.booking_template_library (name, description, category, entity_type, is_system, lines) VALUES
|
|
(
|
|
'Bankavgift',
|
|
'Månadsavgift eller transaktionsavgift från banken.',
|
|
'financial', 'all', TRUE,
|
|
'[
|
|
{"account": "6570", "label": "Bankkostnader", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Ränteintäkt',
|
|
'Ränta från sparkonto eller bank.',
|
|
'financial', 'all', TRUE,
|
|
'[
|
|
{"account": "1930", "label": "Företagskonto", "side": "debit", "type": "settlement", "ratio": 1.0},
|
|
{"account": "8311", "label": "Ränteintäkter", "side": "credit", "type": "business", "ratio": 1.0}
|
|
]'::jsonb
|
|
),
|
|
(
|
|
'Räntekostnad',
|
|
'Ränta på lån eller kredit.',
|
|
'financial', 'all', TRUE,
|
|
'[
|
|
{"account": "8410", "label": "Räntekostnader", "side": "debit", "type": "business", "ratio": 1.0},
|
|
{"account": "1930", "label": "Företagskonto", "side": "credit", "type": "settlement", "ratio": 1.0}
|
|
]'::jsonb
|
|
);
|
|
|
|
-- Schema reload for PostgREST
|
|
NOTIFY pgrst, 'reload schema';
|