Files
accounted/components/settings/AccountingFrameworkForm.tsx
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

221 lines
8.5 KiB
TypeScript

'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { useToast } from '@/components/ui/use-toast'
import {
SettingsRow,
SettingsRowNote,
SettingsSelect,
} from '@/components/settings/SettingsRows'
import type { AccountingFramework } from '@/types'
import { Loader2 } from 'lucide-react'
interface AccountingFrameworkFormProps {
/** Current framework on the company row. */
current: AccountingFramework
/** Bubble up after a successful save so parent state can refresh. */
onSaved?: (next: AccountingFramework) => void
}
/**
* K2/K3 selector row for AB. Lives in the Grunder group on the bookkeeping
* settings page. Renders nothing for non-AB entities: the parent gates this
* component by entity_type.
*
* UX rules (regulatory area: kept in Swedish):
* - Default is K2 (matches the column default and BFNAR 2016:10 baseline).
* - Switching in either direction fires a confirmation dialog. K2 → K3
* names what the system then does (K3-mallen for the årsredovisning,
* komponentuppdelning in the asset register) and the one obligation the
* choice itself carries: komponentavskrivning is mandatory under K3 where
* component useful lives differ materially (punkt 17.4,
* .claude/skills/swedish-year-end-closing/references/k2-vs-k3.md:5).
* Kassaflödesanalys is NOT a consequence of K3: it follows from being a
* större företag (references/reporting-and-filing.md:10), so the copy
* says the product includes one, it does not blame the regelverk.
* The recommendation per BFN is that the choice is permanent once made,
* surfaced as a warning, not a block.
* K3 → K2 warns about what the system does NOT do: uppskjuten skatt
* (2240/8940) balances and komponentavskrivningar are not unwound
* automatically, and the K3 årsredovisning content stops applying.
* - The save is its own request (PATCH /api/company/current): separate
* from /api/settings because the column lives on companies, not on
* company_settings.
*/
export function AccountingFrameworkForm({ current, onSaved }: AccountingFrameworkFormProps) {
const { toast } = useToast()
const [selected, setSelected] = useState<AccountingFramework>(current)
const [pending, setPending] = useState<AccountingFramework | null>(null)
const [saving, setSaving] = useState(false)
async function persist(next: AccountingFramework) {
setSaving(true)
try {
const res = await fetch('/api/company/current', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accounting_framework: next }),
})
const body = await res.json()
if (!res.ok) {
toast({
title: 'Kunde inte spara',
description: body?.error ?? 'Försök igen.',
variant: 'destructive',
})
setSelected(current)
return
}
toast({
title: 'Sparat',
description:
next === 'k3'
? 'Bolaget redovisar nu enligt K3 (BFNAR 2012:1).'
: 'Bolaget redovisar nu enligt K2 (BFNAR 2016:10).',
})
onSaved?.(next)
} catch {
toast({
title: 'Kunde inte spara',
description: 'Försök igen.',
variant: 'destructive',
})
setSelected(current)
} finally {
setSaving(false)
setPending(null)
}
}
function handleChange(next: string) {
const value = next as AccountingFramework
if (value === selected) return
// Both directions are consequential: K2 → K3 adds obligations, K3 → K2
// leaves K3-only balances behind. Confirm before persisting either way.
setPending(value)
}
return (
<>
<SettingsRow
label="Regelverk"
htmlFor="accounting_framework"
help={
<>
K2 är standard för mindre bolag och innebär förenklade regler. Större företag
ska tillämpa K3 och upprätta kassaflödesanalys: dit räknas bland annat bolag
som överskrider mer än ett av tre tröskelvärden (nettoomsättning &gt; 80 MSEK,
tillgångar &gt; 40 MSEK, fler än 50 anställda) under vart och ett av de två
senaste räkenskapsåren, och bolag med noterade värdepapper. För räkenskapsår
som börjar efter 2025-12-31 är K2 dessutom stängt för bolag med utländsk
filial, kryptotillgångar eller aktierelaterade ersättningar, och för bolag där
byggnader ger minst 75 % av nettoomsättningen. Med K3
valt bygger Accounted årsredovisningen enligt K3-mallen: kassaflödesanalys,
förändring av eget kapital som egen räkning och utökade noter.
Anläggningsregistret tar emot komponentuppdelning först med K3, som kräver
komponentavskrivning när komponenterna har väsentligt olika nyttjandeperioder.
Obeskattade reserver redovisas brutto i juridisk person enligt K3 punkt 29.37.
</>
}
>
<SettingsSelect
id="accounting_framework"
value={selected}
onChange={(e) => handleChange(e.target.value)}
// Saves via its own PATCH; the row sits inside the page's
// SettingsFormWrapper form, so keep the wrapper's dirty tracking
// (onInput on the form) from reacting to this select. No `name`
// either, so the wrapper's FormData never picks it up.
onInput={(e) => e.stopPropagation()}
disabled={saving}
>
<option value="k2">K2 (BFNAR 2016:10): mindre företag</option>
<option value="k3">K3 (BFNAR 2012:1): större företag</option>
</SettingsSelect>
{saving && <SettingsRowNote>Sparar…</SettingsRowNote>}
</SettingsRow>
<Dialog
open={pending !== null}
onOpenChange={(open) => {
if (!open) setPending(null)
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>{pending === 'k2' ? 'Byta till K2?' : 'Byta till K3?'}</DialogTitle>
<DialogDescription className="space-y-2 pt-2">
{pending === 'k2' ? (
<>
<span className="block">
Bokförda saldon för uppskjuten skatt (konto 2240 / 8940) och gjorda
komponentavskrivningar återförs inte automatiskt: de måste hanteras
manuellt.
</span>
<span className="block">
Årsredovisningens K3-innehåll (kassaflödesanalys, K3-noter och
uppskjuten skatt) gäller inte längre. Fortsätt?
</span>
</>
) : (
<>
<span className="block">
Årsredovisningen byggs då enligt K3-mallen: kassaflödesanalys,
förändring av eget kapital som egen räkning och utökade noter.
Kassaflödesanalys är i sig ett krav för större företag, inte en följd
av regelverksvalet.
</span>
<span className="block">
Komponentavskrivning blir obligatorisk för tillgångar vars komponenter
har väsentligt olika nyttjandeperioder (K3 punkt 17.4).
Anläggningsregistret tar emot komponentuppdelning först när K3 är valt.
</span>
<span className="block">
Bytet är permanent enligt rekommendation. Fortsätt?
</span>
</>
)}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setPending(null)}
disabled={saving}
>
Avbryt
</Button>
<Button
onClick={() => {
if (!pending) return
setSelected(pending)
void persist(pending)
}}
disabled={saving}
>
{saving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Sparar…
</>
) : pending === 'k2' ? (
'Byt till K2'
) : (
'Byt till K3'
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
}