Bug/baskontoplan numbers (#463)

* feat(settings): add option for company name position in invoice PDF

* feat(migrations): add backfill for VAT account labels to correct bad seed data

* feat(migrations): add backfill for VAT account labels to correct bad seed data

* fix(ui): improve accessibility for company name position toggle in PDF settings

* fix(bookkeeping): align BAS 2026 reference data with official PDF

Reconciled lib/bookkeeping/bas-data/ against the BAS 2026 v1.1 official
chart (1286 accounts). All real discrepancies fixed:

- 2089 Fond för utvecklingsutgifter: k2_excluded → true
- 8417 Räntekostnader för dold räntekompensation: k2_excluded → true
- 1250, 1260 renamed to "(Fritt konto för Inventarier, verktyg och
  installationer)" — BAS 2026 freed these slots
- Periodiseringsfond 2120-2139: added year suffixes (2120 = "...2020"
  etc.) and added 8 missing accounts (2121-2127, 2129) for years
  2019, 2021-2027. Dropped phantom 2022/2024 prior-parser garbage.
- 4075-4078: EUland → EU-land
- 8411: förlagsoch → förlags- och

Verified: 1282 of 1286 PDF accounts match exactly after edits (remaining
4 are PDF-parser artifacts, not real data). Build clean.

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

* feat(migrations): backfill BAS 2026 account labels in chart_of_accounts

Companion to the TS reference fix. Updates existing companies' rows where
they still carry seed-data typos or generic names that don't match BAS 2026:

- 4075-4078: EUland → EU-land (hyphenation)
- 8411: förlagsoch → förlags- och (hyphenation)
- 2120, 2130-2137, 2139: rename "Periodiseringsfond" (generic, no year) to
  the BAS 2026 canonical name with year suffix

Defensive: every WHERE clause matches an EXACT current value. Rows that
have been manually renamed by users — including those with a wrong year
that may reference legacy fonds from an earlier BAS numbering cycle — are
left untouched. No row is deleted.

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

* feat(migrations): fix wrong-year labels on Periodiseringsfond accounts

Follow-up to 20260513140000. The first backfill only renamed accounts whose
name was the generic "Periodiseringsfond" (no year). Many customers were
seeded from an older BAS numbering cycle where 2126 = "2016", 2127 = "2017",
etc. — BAS 2026 reuses those account numbers for years 2026/2027.

This migration aligns the year tag with the BAS 2026 meaning of each
account number across 2120-2127, 2129, 2130-2137, 2139. Only rows whose
name still starts with "Periodiseringsfond" are touched — customers who
renamed the account to something custom keep their name.

Verified on staging: all 18 accounts now carry BAS 2026 canonical names.

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

* fix(data): update account names and descriptions for clarity and consistency

* fix(migrations): backfill account names for BAS 2026 freed accounts and refine Periodiseringsfond name matching

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-05-13 11:54:06 +02:00
committed by GitHub
co-authored by Claude Opus 4.7
parent 07a7964e8d
commit 9e0b89ee7e
7 changed files with 274 additions and 62 deletions
@@ -166,10 +166,11 @@ describe('K2-excluded accounts', () => {
const k2Excluded = [
'1010', '1011', '1012', '1018', '1019',
'1370', '1518',
'2092', '2096', '2240', '2448',
'2089', '2092', '2096', '2240', '2448',
'3940', '7940',
'8290', '8291', '8295',
'8320', '8321', '8325',
'8417',
'8450', '8451', '8455',
'8480', '8940',
]
@@ -191,9 +192,9 @@ describe('K2-excluded accounts', () => {
}
})
it('total K2-excluded count matches expected (24)', () => {
it('total K2-excluded count matches expected (26)', () => {
const k2Count = BAS_REFERENCE.filter((a) => a.k2_excluded).length
expect(k2Count).toBe(24)
expect(k2Count).toBe(26)
})
})
+4 -4
View File
@@ -729,12 +729,12 @@ export const CLASS_1_ACCOUNTS: BASReferenceAccount[] = [
},
{
account_number: '1250',
account_name: 'Inventarier och verktyg',
account_name: '(Fritt konto för Inventarier, verktyg och installationer)',
account_class: 1,
account_group: '12',
account_type: 'asset',
normal_balance: 'debit',
description: 'Inventarier och verktyg',
description: 'Fritt konto för Inventarier, verktyg och installationer (BAS 2026)',
sru_code: '7215',
k2_excluded: false,
},
@@ -762,12 +762,12 @@ export const CLASS_1_ACCOUNTS: BASReferenceAccount[] = [
},
{
account_number: '1260',
account_name: 'Datorer',
account_name: '(Fritt konto för Inventarier, verktyg och installationer)',
account_class: 1,
account_group: '12',
account_type: 'asset',
normal_balance: 'debit',
description: 'Datorer',
description: 'Fritt konto för Inventarier, verktyg och installationer (BAS 2026)',
sru_code: '7215',
k2_excluded: false,
},
@@ -505,7 +505,7 @@ export const CLASS_2_ACCOUNTS: BASReferenceAccount[] = [
normal_balance: 'credit',
description: 'Fond för utvecklingsutgifter',
sru_code: '7221',
k2_excluded: false,
k2_excluded: true,
},
{
account_number: '2090',
@@ -630,133 +630,199 @@ export const CLASS_2_ACCOUNTS: BASReferenceAccount[] = [
},
{
account_number: '2120',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2020',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2020',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2022',
account_name: '2123 Periodiseringsfond',
account_class: 2,
account_group: '20',
account_type: 'equity',
normal_balance: 'credit',
description: '2123 Periodiseringsfond',
sru_code: '7221',
k2_excluded: false,
},
{
account_number: '2024',
account_name: '2125 Periodiseringsfond',
account_class: 2,
account_group: '20',
account_type: 'equity',
normal_balance: 'credit',
description: '2125 Periodiseringsfond',
sru_code: '7221',
k2_excluded: false,
},
{
account_number: '2130',
account_name: 'Periodiseringsfond',
account_number: '2121',
account_name: 'Periodiseringsfond 2021',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2021',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2122',
account_name: 'Periodiseringsfond 2022',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2022',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2123',
account_name: 'Periodiseringsfond 2023',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2023',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2124',
account_name: 'Periodiseringsfond 2024',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2024',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2125',
account_name: 'Periodiseringsfond 2025',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2025',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2126',
account_name: 'Periodiseringsfond 2026',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2026',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2127',
account_name: 'Periodiseringsfond 2027',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2027',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2129',
account_name: 'Periodiseringsfond 2019',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2019',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2130',
account_name: 'Periodiseringsfond 2020 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond avsatt taxeringsår 2020 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2131',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2021 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2021 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2132',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2022 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2022 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2133',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2023 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2023 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2134',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2024 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2024 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2135',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2025 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2025 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2136',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2026 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2026 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2137',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2027 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2027 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
{
account_number: '2139',
account_name: 'Periodiseringsfond',
account_name: 'Periodiseringsfond 2019 – nr 2',
account_class: 2,
account_group: '21',
account_type: 'untaxed_reserves',
normal_balance: 'credit',
description: 'Periodiseringsfond',
description: 'Periodiseringsfond avsatt taxeringsår 2019 – nr 2',
sru_code: '7230',
k2_excluded: false,
},
@@ -80,45 +80,45 @@ export const CLASS_4_ACCOUNTS: BASReferenceAccount[] = [
},
{
account_number: '4075',
account_name: 'Inköp av handelsvaror från annat EUland, 25 % moms',
account_name: 'Inköp av handelsvaror från annat EU-land, 25 % moms',
account_class: 4,
account_group: '40',
account_type: 'expense',
normal_balance: 'debit',
description: 'Inköp av handelsvaror från annat EUland, 25 % moms',
description: 'Inköp av handelsvaror från annat EU-land, 25 % moms',
sru_code: '7320',
k2_excluded: false,
},
{
account_number: '4076',
account_name: 'Inköp av handelsvaror från annat EUland, 12 % moms',
account_name: 'Inköp av handelsvaror från annat EU-land, 12 % moms',
account_class: 4,
account_group: '40',
account_type: 'expense',
normal_balance: 'debit',
description: 'Inköp av handelsvaror från annat EUland, 12 % moms',
description: 'Inköp av handelsvaror från annat EU-land, 12 % moms',
sru_code: '7320',
k2_excluded: false,
},
{
account_number: '4077',
account_name: 'Inköp av handelsvaror från annat EUland, 6 % moms',
account_name: 'Inköp av handelsvaror från annat EU-land, 6 % moms',
account_class: 4,
account_group: '40',
account_type: 'expense',
normal_balance: 'debit',
description: 'Inköp av handelsvaror från annat EUland, 6 % moms',
description: 'Inköp av handelsvaror från annat EU-land, 6 % moms',
sru_code: '7320',
k2_excluded: false,
},
{
account_number: '4078',
account_name: 'Inköp av handelsvaror från annat EUland, momsfri',
account_name: 'Inköp av handelsvaror från annat EU-land, momsfri',
account_class: 4,
account_group: '40',
account_type: 'expense',
normal_balance: 'debit',
description: 'Inköp av handelsvaror från annat EUland, momsfri',
description: 'Inköp av handelsvaror från annat EU-land, momsfri',
sru_code: '7320',
k2_excluded: false,
},
@@ -1070,12 +1070,12 @@ export const CLASS_8_ACCOUNTS: BASReferenceAccount[] = [
},
{
account_number: '8411',
account_name: 'Räntekostnader för obligations-, förlagsoch konvertibla lån',
account_name: 'Räntekostnader för obligations-, förlags- och konvertibla lån',
account_class: 8,
account_group: '84',
account_type: 'expense',
normal_balance: 'debit',
description: 'Räntekostnader för obligations-, förlagsoch konvertibla lån',
description: 'Räntekostnader för obligations-, förlags- och konvertibla lån',
sru_code: '7323',
k2_excluded: false,
},
@@ -1121,7 +1121,7 @@ export const CLASS_8_ACCOUNTS: BASReferenceAccount[] = [
normal_balance: 'debit',
description: 'Räntekostnader för dold räntekompensation m.m.',
sru_code: '7323',
k2_excluded: false,
k2_excluded: true,
},
{
account_number: '8418',
@@ -0,0 +1,74 @@
-- Backfill: align chart_of_accounts labels with BAS 2026 official chart.
--
-- Companion to the TS reference fix in lib/bookkeeping/bas-data/. The
-- TS file is the source of truth for the kontoplan UI and lookups, but
-- existing companies' chart_of_accounts rows were seeded with older /
-- typo'd labels that this migration cleans up.
--
-- Safety: every WHERE clause matches an EXACT bad name string. Companies
-- that have manually renamed an account are left alone. No row is deleted;
-- this migration only updates account_name.
BEGIN;
-- 1. Hyphenation fix: 4075-4078 "EUland" -> "EU-land"
UPDATE public.chart_of_accounts
SET account_name = REPLACE(account_name, 'EUland', 'EU-land'),
updated_at = now()
WHERE account_number IN ('4075', '4076', '4077', '4078')
AND account_name LIKE '%EUland%';
-- 2. Hyphenation fix: 8411 "förlagsoch" -> "förlags- och"
UPDATE public.chart_of_accounts
SET account_name = REPLACE(account_name, 'förlagsoch', 'förlags- och'),
updated_at = now()
WHERE account_number = '8411'
AND account_name LIKE '%förlagsoch%';
-- 3. BAS 2026 freed accounts 1250/1260; rename to the free-account label.
-- Exact-match guard on the old seeded name leaves any customer rename alone.
UPDATE public.chart_of_accounts
SET account_name = '(Fritt konto för Inventarier, verktyg och installationer)',
updated_at = now()
WHERE account_number = '1250'
AND account_name = 'Inventarier och verktyg';
UPDATE public.chart_of_accounts
SET account_name = '(Fritt konto för Inventarier, verktyg och installationer)',
updated_at = now()
WHERE account_number = '1260'
AND account_name = 'Datorer';
-- 4. Periodiseringsfond name backfill.
-- BAS 2026 uses deterministic year mapping for accounts 2120-2139:
-- 2120 -> 2020, 2121 -> 2021, 2122 -> 2022, 2123 -> 2023,
-- 2124 -> 2024, 2125 -> 2025, 2126 -> 2026, 2127 -> 2027,
-- 2129 -> 2019,
-- 213x -> same year as 212x with "– nr 2" suffix.
--
-- We only rename rows where the current name is the generic
-- "Periodiseringsfond" (no year suffix at all). Rows that already
-- carry a year — even a wrong one — are LEFT ALONE because they may
-- refer to a legacy fond from a previous BAS numbering cycle that the
-- user is intentionally tracking. The kontoplan UI lets users rename
-- those manually.
UPDATE public.chart_of_accounts
SET account_name = CASE account_number
WHEN '2120' THEN 'Periodiseringsfond 2020'
WHEN '2130' THEN 'Periodiseringsfond 2020 – nr 2'
WHEN '2131' THEN 'Periodiseringsfond 2021 – nr 2'
WHEN '2132' THEN 'Periodiseringsfond 2022 – nr 2'
WHEN '2133' THEN 'Periodiseringsfond 2023 – nr 2'
WHEN '2134' THEN 'Periodiseringsfond 2024 – nr 2'
WHEN '2135' THEN 'Periodiseringsfond 2025 – nr 2'
WHEN '2136' THEN 'Periodiseringsfond 2026 – nr 2'
WHEN '2137' THEN 'Periodiseringsfond 2027 – nr 2'
WHEN '2139' THEN 'Periodiseringsfond 2019 – nr 2'
END,
updated_at = now()
WHERE account_number IN ('2120','2130','2131','2132','2133','2134','2135','2136','2137','2139')
AND account_name = 'Periodiseringsfond';
COMMIT;
NOTIFY pgrst, 'reload schema';
@@ -0,0 +1,71 @@
-- Follow-up backfill: fix wrong-year labels on Periodiseringsfond accounts.
--
-- The previous migration (20260513140000) only renamed accounts whose name
-- was the generic "Periodiseringsfond" (no year). It deliberately left rows
-- with any year suffix alone, since a year tag could indicate intentional
-- legacy data. In practice it turned out that many customers were seeded
-- from an older BAS numbering cycle where 2126 = "2016", 2127 = "2017", etc.
-- BAS 2026 reuses those same account numbers for years 2026/2027.
--
-- This migration aligns the year tag with the BAS 2026 meaning of each
-- account number. To avoid clobbering customer-customised names, we only
-- touch rows whose name matches a known seeded shape:
-- "Periodiseringsfond"
-- "Periodiseringsfond YYYY"
-- "Periodiseringsfond YYYY – nr 2"
-- Anything else (e.g. "Min fond 2016", "Periodiseringsfond – avslutad") is
-- left alone.
BEGIN;
UPDATE public.chart_of_accounts
SET account_name = CASE account_number
WHEN '2120' THEN 'Periodiseringsfond 2020'
WHEN '2121' THEN 'Periodiseringsfond 2021'
WHEN '2122' THEN 'Periodiseringsfond 2022'
WHEN '2123' THEN 'Periodiseringsfond 2023'
WHEN '2124' THEN 'Periodiseringsfond 2024'
WHEN '2125' THEN 'Periodiseringsfond 2025'
WHEN '2126' THEN 'Periodiseringsfond 2026'
WHEN '2127' THEN 'Periodiseringsfond 2027'
WHEN '2129' THEN 'Periodiseringsfond 2019'
WHEN '2130' THEN 'Periodiseringsfond 2020 – nr 2'
WHEN '2131' THEN 'Periodiseringsfond 2021 – nr 2'
WHEN '2132' THEN 'Periodiseringsfond 2022 – nr 2'
WHEN '2133' THEN 'Periodiseringsfond 2023 – nr 2'
WHEN '2134' THEN 'Periodiseringsfond 2024 – nr 2'
WHEN '2135' THEN 'Periodiseringsfond 2025 – nr 2'
WHEN '2136' THEN 'Periodiseringsfond 2026 – nr 2'
WHEN '2137' THEN 'Periodiseringsfond 2027 – nr 2'
WHEN '2139' THEN 'Periodiseringsfond 2019 – nr 2'
END,
updated_at = now()
WHERE account_number IN (
'2120','2121','2122','2123','2124','2125','2126','2127','2129',
'2130','2131','2132','2133','2134','2135','2136','2137','2139'
)
AND account_name ~ '^Periodiseringsfond( \d{4}( – nr 2)?)?$'
AND account_name <> CASE account_number
WHEN '2120' THEN 'Periodiseringsfond 2020'
WHEN '2121' THEN 'Periodiseringsfond 2021'
WHEN '2122' THEN 'Periodiseringsfond 2022'
WHEN '2123' THEN 'Periodiseringsfond 2023'
WHEN '2124' THEN 'Periodiseringsfond 2024'
WHEN '2125' THEN 'Periodiseringsfond 2025'
WHEN '2126' THEN 'Periodiseringsfond 2026'
WHEN '2127' THEN 'Periodiseringsfond 2027'
WHEN '2129' THEN 'Periodiseringsfond 2019'
WHEN '2130' THEN 'Periodiseringsfond 2020 – nr 2'
WHEN '2131' THEN 'Periodiseringsfond 2021 – nr 2'
WHEN '2132' THEN 'Periodiseringsfond 2022 – nr 2'
WHEN '2133' THEN 'Periodiseringsfond 2023 – nr 2'
WHEN '2134' THEN 'Periodiseringsfond 2024 – nr 2'
WHEN '2135' THEN 'Periodiseringsfond 2025 – nr 2'
WHEN '2136' THEN 'Periodiseringsfond 2026 – nr 2'
WHEN '2137' THEN 'Periodiseringsfond 2027 – nr 2'
WHEN '2139' THEN 'Periodiseringsfond 2019 – nr 2'
END;
COMMIT;
NOTIFY pgrst, 'reload schema';