fix(bookkeeping): drop non-standard account 2012, book EF F-skatt on 2013 (#1409) (#1489)

Primary-source check against bas.se (BAS 2026 v2): the official kontoplan
has no account 2012; the enskild firma equity block is 2010, 2011, 2013,
2017, 2018, 2019. 2012 'Avräkning för skatter och avgifter' is a program
convention (Visma, Bokio, Björn Lundén), not standard BAS, and a
non-standard account in BAS_REFERENCE leaks via the backfill into charts,
SIE export and SRU filing.

- remove 2012 from class-2-equity-liabilities.ts, with a tombstone comment
- migration retargets 'Preliminär F-skatt (EF)' lines 2012 -> 2013
  (system row plus any clones still carrying the seeded shape)
- pin 2012's absence in bas-ef-equity-accounts.test.ts (2113 precedent)
- correct the swedish-year-end-closing references that motivated #1388,
  regenerate atom seed migration

Companies whose charts already got 2012 backfilled keep it: existing
history stays valid; only future template use books 2013.

Closes #1409

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-10 14:04:08 +02:00
committed by GitHub
parent 3ff12faa77
commit fe1ff8649b
11 changed files with 17631 additions and 46 deletions
@@ -152,5 +152,5 @@ All P&L accounts (classes 3-8) net to **8999** (Årets resultat).
### Enskild firma equity flow
1. Year-end: Debit 8999 / Credit **2019** (Årets resultat, delägare 1)
2. New year start: Zero all sub-accounts (**2011** egna varuuttag, **2012** avräkning skatter, **2013** övriga egna uttag, **2017** årets kapitaltillskott, **2018** övriga egna insättningar, **2019** årets resultat) → net into **2010** (Eget kapital)
2. New year start: Zero all sub-accounts (**2011** egna varuuttag, **2013** övriga egna uttag inklusive ägarens egna skatter, **2017** årets kapitaltillskott, **2018** övriga egna insättningar, **2019** årets resultat) → net into **2010** (Eget kapital). Note: some programs (Visma, Bokio) add a non-standard **2012** "Avräkning för skatter och avgifter" for owner taxes; official BAS has no 2012, and Accounted books owner taxes on 2013.
3. No bolagsstämma required
@@ -117,8 +117,8 @@ Zero all of 2011-2019 into 2010:
```
Debit 2010 Eget kapital (for net credits from sub-accounts)
Credit 2011 Egna varuuttag
Credit 2012 Avräkning skatter och avgifter
Credit 2013 Övriga egna uttag
Credit 2013 Övriga egna uttag (incl. ägarens egna skatter; some programs use a
non-standard 2012 for these, official BAS has no 2012)
Debit 2017 Årets kapitaltillskott (reverse into 2010)
Debit 2018 Övriga egna insättningar (reverse into 2010)
Debit 2019 Årets resultat (reverse into 2010)
+1
View File
@@ -852,3 +852,4 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-09] Regeluppdat + docs-freshness scans (#1417) built as local loop skills with due-date self-gating, not cloud crons: cloud routines were retired 2026-07-20, and session crons die at 7 days, so weekly/monthly cadence is achieved by loop-ignite running each loop when its run marker says it is due. loop-regeluppdat files tickets only (no auto-fix PRs): regulatory changes touch money math and compliance logic, which .claude/loops.md forbids loops from changing. Docs check diffs the live .md mirror routes against repo-built markdown (exact, canonicalised both sides) instead of diffing the gnubok-website checkout, so it also catches deployed-but-stale and route-404 states.
[2026-08-10] RC VAT template fix scoped to template-library.ts (applyTemplate + deriveTemplateLinesFromBooking): counterparty/SIE learned patterns already strip fiktiv-moms legs and regenerate them via generateReverseChargeLines with the gross base, so mapping-engine/counterparty paths were already correct and stay untouched.
[2026-08-10] Receipt hunt cron keeps searchMail=false: the mailbox leg stays manual until its time budget is proven. A sweep of one 172-message mailbox took over 600s while the route's maxDuration is 300, so enabling it nightly would time out mid-run. Flip both this flag and RECEIPT_HUNT_COMPANY_IDS together once the per-company budget is measured.
[2026-08-10] Account 2012 reverted from BAS_REFERENCE and the EF F-skatt template moved to 2013 (#1409): the primary source (bas.se BAS 2026 v2 PDF) has no 2012 anywhere; the EF equity block is 2010/2011/2013/2017/2018/2019. 2012 "Avräkning för skatter och avgifter" is a Visma/Bokio/BL program convention that the swedish-year-end-closing skill had absorbed as if standard. Kept per-company charts that already have 2012 untouched (their history is legal and the account is a valid free slot); only the reference, the seeded template and the skill guidance change.
@@ -2,37 +2,40 @@ import { describe, it, expect } from 'vitest'
import { getBASReference } from '@/lib/bookkeeping/bas-reference'
/**
* The enskild firma equity block must be complete.
* The enskild firma equity block must match the official BAS kontoplan.
*
* 2012 was missing from the reference, which is not cosmetic:
* `lib/bookkeeping/account-backfill.ts` only seeds accounts that appear in
* BAS_REFERENCE, so an account absent from it can never be added to a company's
* chart on demand. Any entry touching 2012 failed with AccountsNotInChartError,
* which is exactly what the "Preliminär F-skatt (EF)" template did.
*
* The gap was found by the pack validator asserting that every account a
* template references exists in BAS 2026.
* 2012 "Avräkning för skatter och avgifter" was added in #1388 on the strength
* of the swedish-year-end-closing skill alone. The primary-source check
* (#1409, bas.se BAS 2026 v2) shows the official chart has no 2012: the block
* runs 2010, 2011, 2013, 2017, 2018, 2019, and the official "Avräkning för
* skatter och avgifter (skattekonto)" accounts are 1630/1640 (asset) and 2850
* (liability). 2012 is a program convention (Visma, Bokio, Björn Lundén), not
* standard BAS. A non-standard account in BAS_REFERENCE leaks into every chart
* the backfill seeds, and from there into SIE export and SRU filing, so the
* reference must not carry it. Owner taxes paid by the firm are an eget uttag
* on 2013, which is what the "Preliminär F-skatt (EF)" template books since
* migration 20260810120000.
*/
describe('enskild firma equity accounts (20xx)', () => {
it('has no hole in the 2010-2013 run', () => {
for (const account of ['2010', '2011', '2012', '2013']) {
it('carries exactly the official BAS block for delägare 1', () => {
for (const account of ['2010', '2011', '2013', '2017', '2018', '2019']) {
expect(getBASReference(account), `${account} missing from BAS reference`).toBeDefined()
}
})
it('2012 is the owner-tax equity account, distinct from the 1630 skattekonto asset', () => {
const equity = getBASReference('2012')
const skattekonto = getBASReference('1630')
expect(equity?.account_type).toBe('equity')
expect(equity?.normal_balance).toBe('debit')
// Both are called "avräkning", which is precisely why they get confused.
expect(skattekonto?.account_type).toBe('asset')
expect(equity?.account_number).not.toBe(skattekonto?.account_number)
it('does not carry 2012: not in official BAS, verified against bas.se in #1409', () => {
// Pinned like 2113 below: prevents a well-meaning "fix" that re-adds the
// program-convention account instead of keeping the reference official.
expect(getBASReference('2012')).toBeUndefined()
})
it('shares the equity SRU code with its siblings, since they all net into 2010', () => {
const siblings = ['2011', '2012', '2013', '2018'].map((a) => getBASReference(a)?.sru_code)
it('keeps the skattekonto asset account the template settles against', () => {
const skattekonto = getBASReference('1630')
expect(skattekonto?.account_type).toBe('asset')
})
it('shares the equity SRU code across the sub-accounts, since they all net into 2010', () => {
const siblings = ['2011', '2013', '2018'].map((a) => getBASReference(a)?.sru_code)
expect(new Set(siblings).size).toBe(1)
expect(siblings[0]).toBe(getBASReference('2010')?.sru_code)
})
@@ -23,21 +23,11 @@ export const CLASS_2_ACCOUNTS: BASReferenceAccount[] = [
sru_code: '7221',
k2_excluded: false,
},
{
account_number: '2012',
account_name: 'Avräkning för skatter och avgifter',
account_class: 2,
account_group: '20',
account_type: 'equity',
normal_balance: 'debit',
description:
'Enskild firma: ägarens egna skatter och avgifter (t.ex. preliminär F-skatt) som betalas ' +
'av företaget. Ett eget uttag, inte en företagskostnad. Nollas mot 2010 Eget kapital vid ' +
'nytt räkenskapsår. Ska inte förväxlas med 1630 Avräkning för skatter och avgifter ' +
'(skattekonto), som är tillgångssidans saldo mot Skatteverket.',
sru_code: '7221',
k2_excluded: false,
},
// 2012 is deliberately absent: the official BAS kontoplan (bas.se, BAS 2026 v2)
// has no 2012; the enskild firma equity block runs 2010, 2011, 2013, 2017,
// 2018, 2019. "Avräkning för skatter och avgifter" as 2012 is a program
// convention (Visma, Bokio, Björn Lundén), not standard BAS; owner taxes are
// booked as eget uttag on 2013. Verified against the primary source in #1409.
{
account_number: '2013',
account_name: 'Övriga egna uttag',
@@ -54,6 +54,15 @@ const INTENTIONAL_DIVERGENCES: Record<string, Divergence> = {
seededName: 'Periodiseringsfond återföring (AB)',
reason: 'Same 2113 fix as periodiseringsfond-avsattning-ab.',
},
'preliminar-f-skatt-ef': {
seededName: 'Preliminär F-skatt (EF)',
reason:
'Seeded version debited 2012 "Avräkning för skatter och avgifter". The primary-source ' +
'check in #1409 (bas.se BAS 2026 v2) shows official BAS has no 2012: the EF equity block ' +
'is 2010/2011/2013/2017/2018/2019, and 2012 is a Visma/Bokio/BL program convention. Owner ' +
'taxes paid by the firm are an eget uttag, so the template now debits 2013 Övriga egna ' +
'uttag; migration 20260810120000 retargets the seeded rows the same way.',
},
'representation-avdragsgill-25-moms': {
seededName: 'Representation (avdragsgill, 25% moms)',
reason:
+1 -1
View File
@@ -9,7 +9,7 @@ meta:
description: >-
Betalning av preliminär F-skatt från skattekontot (enskild firma).
lines:
- account: '2012'
- account: '2013'
label: 'Egna skatter'
side: debit
type: business
+4 -4
View File
@@ -244,12 +244,12 @@
"version": 3
},
"horizontal/swedish-year-end-closing/closing-process": {
"hash": "bce8e129860dd26c04e5e406dc9312e2d42dea6aa8c0139f168de43529abf939",
"version": 2
"hash": "2d23c29bcf8a7f5d6ce72e677abe518efdf8300ed11360269377a342f51945b5",
"version": 3
},
"horizontal/swedish-year-end-closing/journal-entries": {
"hash": "acd171ade0305bcbb4142e0f9817482f845dc6e1b3620a62eb36b6e8eef6110a",
"version": 2
"hash": "b4891b88b6cf409f00c76c70b7acb9dbf20e13ac05814886f978b80b878ab0a9",
"version": 3
},
"horizontal/swedish-year-end-closing/k2-vs-k3": {
"hash": "e3fa066d0e1fe4284661df669736241daf05e08599f893b40f5a6f94722d0bce",
+5 -1
View File
@@ -127,6 +127,9 @@ export function buildMigrationSql(atoms: DiscoveredAtom[], versions: Record<stri
-- in-app agent read skill content from the DB (works on Vercel, Docker, self-hosted).
-- Idempotent: ON CONFLICT refreshes content fields but leaves is_active and
-- mcp_exposed under manual control (they take column defaults on first insert).
-- The WHERE guard skips rows whose registry version is newer than this seed's:
-- two branches can each carry a full seed, and the one that applies last can
-- no longer downgrade atoms the other already bumped.
`
const values = atoms.map((a) => buildValuesRow(a, versions[a.id])).join(',\n')
@@ -147,7 +150,8 @@ ON CONFLICT (id) DO UPDATE SET
parent_atom_id = EXCLUDED.parent_atom_id,
version = EXCLUDED.version,
schema_version = EXCLUDED.schema_version,
updated_at = now();
updated_at = now()
WHERE public.agent_atom_registry.version <= EXCLUDED.version;
`
return `${header}\n${insert}\nNOTIFY pgrst, 'reload schema';\n`
@@ -0,0 +1,29 @@
-- "Preliminär F-skatt (EF)" booked Debit 2012 / Credit 1630. Account 2012
-- "Avräkning för skatter och avgifter" was added to BAS_REFERENCE in #1388 on
-- the strength of the swedish-year-end-closing skill alone; the primary-source
-- check (#1409, bas.se BAS 2026 v2) shows the official kontoplan has no 2012:
-- the enskild firma equity block is 2010, 2011, 2013, 2017, 2018, 2019, and
-- "Avräkning för skatter och avgifter (skattekonto)" is 1630/1640 (asset) and
-- 2850 (liability). 2012 is a program convention (Visma, Bokio, Björn Lundén),
-- not standard BAS, so the reference drops it and the template moves to 2013
-- "Övriga egna uttag": owner taxes paid by an enskild firma are an eget uttag.
--
-- Scope: every enskild_firma template row still carrying a 2012 line, i.e. the
-- seeded system row plus any clone of it. Charts that already had 2012
-- backfilled keep the account and its history; only future template use moves
-- to 2013. account-backfill seeds 2013 on demand (it is standard BAS), so the
-- template works for charts that lack it.
UPDATE public.booking_template_library
SET lines = (
SELECT jsonb_agg(
CASE
WHEN line->>'account' = '2012' THEN jsonb_set(line, '{account}', '"2013"')
ELSE line
END
ORDER BY ord
)
FROM jsonb_array_elements(lines) WITH ORDINALITY AS t(line, ord)
)
WHERE entity_type = 'enskild_firma'
AND lines @> '[{"account": "2012"}]';
File diff suppressed because it is too large Load Diff