Anta en verden med veldig enkle skattytere og enkel skatteberegning. Hver skattyter kan bare ha følgende skatteobjekter:
- lønnsinntekt
- renteinntekt
- renteutgift
- fagforeningskontingent
Alminnelig inntekt
beregnes som
lønnsinntekt
+ renteinntekt
- renteutgift
- fagforeningskontingent
Og skatten er 33% av Alminnelig inntekt
Anta videre en skattyter med følgende skatteobjekter:
Skatteobjekt | Beløp |
---|---|
lønnsinntekt | kr 78_100 |
renteinntekt | kr 270 |
renteutgift | kr 3_800 |
fagforeningskontingent | kr 3_400 |
Gitt at følgende avhengighet finnes i pom.xml
(bytt til siste/aktuelle versjon)
<dependencies>
<dependency>
<groupId>ske.fastsetting.skatt</groupId>
<artifactId>uttrykk-kjerne</artifactId>
<version>1.25</version>
</dependency>
</dependencies>
Da kan følgende kode beregne skatten til skattyteren:
import ske.fastsetting.skatt.domene.Belop;
import ske.fastsetting.skatt.uttrykk.belop.BelopUttrykk;
import ske.fastsetting.skatt.uttrykk.tall.TallUttrykk;
import static ske.fastsetting.skatt.uttrykk.belop.KroneUttrykk.kr;
import static ske.fastsetting.skatt.uttrykk.tall.ProsentUttrykk.prosent;
public class Skatteberegning {
static final TallUttrykk FELLESSKATT_SATS = prosent(33);
static final BelopUttrykk lonnsinntekt = kr(78_100);
static final BelopUttrykk renteinntekt = kr(270);
static final BelopUttrykk renteutgift = kr(3_800);
static final BelopUttrykk fagforeningskontingent = kr(3_400);
static final BelopUttrykk alminneligInntekt = lonnsinntekt
.pluss(renteinntekt)
.minus(renteutgift)
.minus(fagforeningskontingent);
static final BelopUttrykk fellesskatt =
alminneligInntekt.multiplisertMed(FELLESSKATT_SATS);
public static void main(String[] args) {
SkattyterKontekst kontekst = SkattyterKontekst.ny();
System.out.println(kontekst.verdiAv(fellesskatt));
System.out.println(kontekst.verdiAv(alminneligInntekt));
System.out.println(kontekst.verdiAv(fagforeningskontingent));
}
}
Kjører du main
-metoden, så bør du få følgende opp i konsollet:
kr 23 486
kr 71 170
kr 3 400
Det første å legge merke til er at alle verdier og beregninger er uttrykk. Et uttrykk representerer en kalkulasjon,
som så kan kjedes sammen til større uttrykkstrær avhengig av hvilke operasjoner en uttrykkstype støtter.
Koden over viser to uttrykkstyper, TallUttrykk
og BelopUttrykk
, som representerer kalkulasjoner med henholdsvis Belop og Tall.
Det er først når et uttrykk evalueres at verdien kalkuleres. Det skjer i main
-metoden over når verdiAv
-metoden på SkattyterKontekst
kalles med et av uttrykkene:
...
SkattyterKontekst kontekst = SkattyterKontekst.ny();
System.out.println(kontekst.verdiAv(fellesskatt));
...
Eksempelet over virker bare for denne ene skattyteren, og i virkelighetens verden ønsker vi å hente skattyterenes verdier fra en ekstern datakilde, og kjøre skatteberegningene for alle.
Vi begynner med å anta at vi har et uttrykk som representer verdien for et skatteobjekt angitt med en ID. Altså i stedet for å si
...
static final BelopUttrykk lonnsinntekt = kr(78_100);
static final BelopUttrykk renteinntekt = kr(270);
static final BelopUttrykk renteutgift = kr(3_800);
static final BelopUttrykk fagforeningskontingent = kr(3_400);
...
så kan vi si
...
static final BelopUttrykk lonnsinntekt = skatteobjekt("lønnsinntekt");
static final BelopUttrykk renteinntekt = skatteobjekt("renteinntekt");
static final BelopUttrykk renteutgift = skatteobjekt("renteutgift");
static final BelopUttrykk fagforeningskontingent = skatteobjekt("fagforeningskontingent");
...
skatteobjekt(...)
impliserer en factory method
som returner et BelopUttrykk, så vi lager en klasse som oppfyller det
public class BelopSkatteobjektUttrykk implements BelopUttrykk {
public static BelopSkatteobjektUttrykk skatteobjekt(String skatteobjekttype) {
return new BelopSkatteobjektUttrykk(skatteobjekttype)
}
private final String skatteobjekttype;
private BelopSkatteobjektUttrykk(String skatteobjekttype) {
this.skatteobjekttype = skatteobjekttype;
}
}
BelopSkatteobjektUttrykk
er et BelopUttrykk
, så skatteobjekt(...)
kan returnere en ny instans av klassen.
Konstruktøren initialiser instansen med skatteobjektstypen som blir sendt inn.
Imidlertid vil ikke dette kompilere fordi BelopUttrykk
(egentlig Uttrykk
) krever at følgende metoder blir implementert:
V eval(UttrykkContext ctx);
String beskriv(UttrykkContext ctx);
String id();
String navn();
Set<String> tags();
@Deprecated List<Regel> regler();
List<Hjemmel> hjemler();
Heldigvis finnes det en klasse som gir en god implementasjon av de fleste av disse metodene - AbstractUttrykk
-
og som i tillegg gir noen andre nyttige egenskaper, som vi skal se senere. AbstractUttrykk
har to typeparametre. Den første
angir hvilken type som eval skal returnere. Her er det Belop
siden evaluering av BelopUttrykk
gir Belop
. Den andre typeparameteren
er klassen selv. De eneste metodene AbstractUttrykk
ikke implementerer, er:
V eval(UttrykkContext ctx);
String beskriv(UttrykkContext ctx);
Skjelettet av implementasjonen blir da før eval
og beskriv
implementeres:
public class BelopSkatteobjektUttrykk extends AbstractUttrykk<Belop,BelopSkatteobjektUttrykk> implements BelopUttrykk {
public static BelopSkatteobjektUttrykk skatteobjekt(String skatteobjekttype) {
return new BelopSkatteobjektUttrykk(skatteobjekttype)
}
private final String skatteobjekttype;
private BelopSkatteobjektUttrykk(String skatteobjekttype) {
this.skatteobjekttype = skatteobjekttype;
}
public Belop eval(UttrykkContext uc) {
}
public String beskriv(UttrykkContext uc) {
}
}
Beskrivelsen kan være det vi vil, og her er det naturlig at uttrykket sier noe om at den representer et skatteobjekt av den skatteobjektstypen den er blitt initalisert med
public class BelopSkatteobjektUttrykk extends AbstractUttrykk<Belop,BelopSkatteobjektUttrykk> implements BelopUttrykk {
private final String skatteobjekttype;
...
public String beskriv(UttrykkContext uc) {
return "Skatteobjekt "+skatteobjekttype;
}
}
Da gjenstår eval
-metoden, som skal gi verdien når uttrykket blir evaluert. Verdien skal vi få fra datakilden vår.
La oss si at vår eksterne datakilde gir oss skattyterdata som et Skattegrunnlag
,
som vi antar er et enkelt Map
-aktig interface som lar oss hente ut beløp for angitte skatteobjekt
Skattegrunnlag skattegrunnlag = ... // hent skattyters data fra ekstern kilde
Belop lonnsinntektSY1 = skattegrunnlag.skatteobjekt("lønnsinntekt")
Belop renteinntektSY1 = skattegrunnlag.skatteobjekt("renteinntekt")
Da har vi nesten det vi trenger for å implementere eval
-metoden også:
public class BelopSkatteobjektUttrykk extends AbstractUttrykk<Belop,BelopSkatteobjektUttrykk> implements BelopUttrykk {
private final String skatteobjekttype;
...
public Belop eval(UttrykkContext uc) {
// hent skattyters data fra ekstern kilde
Skattegrunnlag skattegrunnlag = ...
return skattegrunnlag.skatteobjekt(this.skatteobjekttype)
}
}
eval
-metoden kunne gått rett mot den eksterne kilden, men det kan være en komplisert affære, og vi ønsker å holde uttrykkene enkle.
I stedet lar vi uttrykket rett og slett forlange at noen har gitt Skattegrunnlag
'et som en input-verdi. UttrykkContext
'en som sendes inn til eval
-metoden, gir nettopp tilgang til input-verdier
Da har vi det vi trenger for å implementere eval
-metoden også:
public class BelopSkatteobjektUttrykk extends AbstractUttrykk<Belop,BelopSkatteobjektUttrykk> implements BelopUttrykk {
private final String skatteobjekttype;
...
public Belop eval(UttrykkContext uc) {
// anta at noen har sendt med riktig input - feile hardt hvis ikke
Skattegrunnlag skattegrunnlag = uc.input(Skattegrunnlag.class)
return skattegrunnlag.skatteobjekt(this.skatteobjekttype)
}
}
Hvis noen mot formodning har glemt å sette et Skattegrunnlag
som input, så vil evaluering av uttrykket feile med en exception.
En mer forsiktig tilnærming kan være å sjekke om input'en finnes:
public class BelopSkatteobjektUttrykk extends AbstractUttrykk<Belop,BelopSkatteobjektUttrykk> implements BelopUttrykk {
private final String skatteobjekttype;
...
public Belop eval(UttrykkContext uc) {
if(uc.harInput(Skattegrunnlag.class)) {
// vi vet vi har input nå
Skattegrunnlag skattegrunnlag = uc.input(Skattegrunnlag.class)
return skattegrunnlag.skatteobjekt(this.skatteobjekttype)
} else {
return Belop.kr0(); // returner en default-verdi
}
}
}
Vi går likevel for den agressive varianten, og den komplette uttrykksklassen ser da slik ut:
public class BelopSkatteobjektUttrykk extends AbstractUttrykk<Belop,BelopSkatteobjektUttrykk> implements BelopUttrykk {
public static BelopSkatteobjektUttrykk skatteobjekt(String skatteobjekttype) {
return new BelopSkatteobjektUttrykk(skatteobjekttype)
}
private final String skatteobjekttype;
private BelopSkatteobjektUttrykk(String skatteobjekttype) {
this.skatteobjekttype = skatteobjekttype;
}
public Belop eval(UttrykkContext uc) {
Skattegrunnlag skattegrunnlag = uc.input(Skattegrunnlag.class)
return skattegrunnlag.skatteobjekt(this.skatteobjekttype)
}
public String beskriv(UttrykkContext uc) {
return "Skatteobjekt "+skatteobjekttype;
}
}
Det eneste som gjenstår er da å få send inn input-verdien, og det gjør vi når vi oppretter SkattyterKontekst
, som er en implementasjon av UttrykkContext
public static void main(String[] args) {
// hent skattyters data fra ekstern kilde
Skattegrunnlag skattegrunnlag = ...
// opprett SkattyterKontekst med input
SkattyterKontekst kontekst = SkattyterKontekst.ny(skattegrunnlag);
System.out.println(kontekst.verdiAv(fellesskatt));
...
}
Den komplette beregningskoden ser da slik ut:
import ske.fastsetting.skatt.domene.Belop;
import ske.fastsetting.skatt.uttrykk.belop.BelopUttrykk;
import ske.fastsetting.skatt.uttrykk.tall.TallUttrykk;
import static ske.fastsetting.skatt.uttrykk.belop.KroneUttrykk.kr;
import static ske.fastsetting.skatt.uttrykk.tall.ProsentUttrykk.prosent;
import static ske.fastsetting.skatt.uttrykk.skattegrunnlag.BelopSkatteobjektUttrykk.skatteobjekt;
public class Skatteberegning {
static final TallUttrykk FELLESSKATT_SATS = prosent(33);
static final BelopUttrykk lonnsinntekt = skatteobjekt("lønnsinntekt");
static final BelopUttrykk renteinntekt = skatteobjekt("renteinntekt");
static final BelopUttrykk renteutgift = skatteobjekt("renteutgift");
static final BelopUttrykk fagforeningskontingent = skatteobjekt("fagforeningskontingent");
static final BelopUttrykk alminneligInntekt = lonnsinntekt
.pluss(renteinntekt)
.minus(renteutgift)
.minus(fagforeningskontingent);
static final BelopUttrykk fellesskatt =
alminneligInntekt.multiplisertMed(FELLESSKATT_SATS);
public static void main(String[] args) {
// hent skattyters data fra ekstern kilde
Skattegrunnlag skattegrunnlag = ...
/ opprett SkattyterKontekst med input
SkattyterKontekst kontekst = SkattyterKontekst.ny(skattegrunnlag);
System.out.println(kontekst.verdiAv(fellesskatt));
System.out.println(kontekst.verdiAv(alminneligInntekt));
System.out.println(kontekst.verdiAv(fagforeningskontingent));
}
}
- Opprett en klasse som arver fra
AbstractUttrykk
og implementer aktuelt interface - Implementer metodene
eval
ogbeskriv
- Legg gjerne til en (eller flere) factory methods som lar utvikleren lag en instans av klassen på en kompakt måte ved hjelp av static imports
La oss nå skille forretningslogikken fra koden som setter opp og kjører logikken. Først flytter vi main
-metoden til en egen klasse.
public class App {
public static void main(String[] args) {
// hent skattyters data fra ekstern kilde
Skattegrunnlag skattegrunnlag = ...
// opprett SkattyterKontekst med input
SkattyterKontekst kontekst = SkattyterKontekst.ny(skattegrunnlag);
System.out.println(kontekst.verdiAv(fellesskatt));
System.out.println(kontekst.verdiAv(alminneligInntekt));
System.out.println(kontekst.verdiAv(fagforeningskontingent));
}
}
Forretningslogikken fordeler vi på to klasser, Grunnlag
og Skatteberegning
.
public class Grunnlag {
static final BelopUttrykk lonnsinntekt = skatteobjekt("lønnsinntekt");
static final BelopUttrykk renteinntekt = skatteobjekt("renteinntekt");
static final BelopUttrykk renteutgift = skatteobjekt("renteutgift");
static final BelopUttrykk fagforeningskontingent = skatteobjekt("fagforeningskontingent");
}
public class Skatteberegning {
static final TallUttrykk FELLESSKATT_SATS = prosent(33);
static final BelopUttrykk alminneligInntekt = lonnsinntekt
.pluss(renteinntekt)
.minus(renteutgift)
.minus(fagforeningskontingent);
static final BelopUttrykk fellesskatt =
alminneligInntekt.multiplisertMed(FELLESSKATT_SATS);
}
Nå ønsker vi å teste fellesskatt-uttrykket i Skatteberegning
og oppretter en testklasse
public class SkatteberegningTest {
@Test
public void testFellesskatt() {
// Fyll på med testkode
}
}
Utfordringen vi har nå er at fellesskatt
er et uttrykk på toppen av et uttrykkstre. I vårt tilfelle er det ikke et veldig komplisert tre,
men det er uansett en utfordring at løvnodene i treet er BelopSkatteobjektUttrykk
, som er avhengig av en input.
En metode som vil fungere er å lage input'en i testen, og hvis vi oppretter en klasse som implementerer Skattegrunnlag
, så kan vi få til det.
public class SkatteberegningTest {
@Test
public void testFellesskatt() {
TestSkattegrunnlag skattegrunnlag = new TestSkattegrunnlag();
skattegrunnlag.leggTil("lønnsinntekt",Belop.kr(100));
skattegrunnlag.leggTil("renteinntekt",Belop.kr(50));
// opprett SkattyterKontekst med input
SkattyterKontekst kontekst = SkattyterKontekst.ny(skattegrunnlag);
assertEquals(Belop.kr(50),kontekst.verdiAv(fellesskatt).rundAvTilHeleKroner());
}
}
Her implementerer TestSkattegrunnlag
inteface't Skattegrunnlag
og tilbyr metoden leggTil
for å sette verdier på skatteobjektene.
Legg merke til at vi i assertEquals
runder av beløpet vi får fra beregningen, siden det ikke nødvendigvis er nøyaktig hele kroner (det er kr 49,50).
Denne fremgangsmåten er omstendig, men kanskje til å leve med i dette tilfellet, og vi kan se for oss å gjenbruke TestSkattegrunnlag
i mange tilfeller.
Imidlertid var det vi ønsket å teste i dette tilfellet at fellesskatt ble beregnet riktig, og den avhenger kun av alminneligInntekt og skattesatsen på 33%.
Når uttrykkstreet vokser i kompleksitet og dybde, så blir det stadig vanskeligere å gi input som sørger for at vi kan teste interessante kombinasjoner av uttrykk nær toppen.
Heldigvis finnes det annen tilnærmingen som lar oss kortslutte uttrykkstreet der vi ønsker.
public class SkatteberegningTest {
@Test
public void testFellesskatt() {
// opprett SkattyterKontekst UTEN input
SkattyterKontekst kontekst = SkattyterKontekst.ny();
kontekst.overstyrVerdi(Skatteberegning.alminneligInntekt, Belop.kr(150))
assertEquals(Belop.kr(50),kontekst.verdiAv(fellesskatt).rundAvTilHeleKroner());
}
}
overstyrVerdi
lar oss sette verdien som skal benyttes under evalueringen av et gitt uttrykk. Verdien bør overstyres før noe uttrykk evalueres fordi verdien
av uttrykkene caches på konteksten etter hvert som de blir evaluert. Merk at vi ikke lenger trenger å sende input på kontektsten
fordi alle uttrykk som benytter input, ikke lenger blir evaluert.
Vi kan legge til en ny test, som eksponerer en feil i koden vår
@Test
public void testNegativAlimnneligInntekt() {
SkattyterKontekst kontekst = SkattyterKontekst.ny();
// Negativ alminnelig inntekt ...
kontekst.overstyrVerdi(Skatteberegning.alminneligInntekt, Belop.kr(-100))
// ... bør gi 0 i skatt
assertEquals(Belop.kr0(),kontekst.verdiAv(fellesskatt));
}
Testen over feiler fordi skatten beregnes til kr -33, som er en åpenbar feil fordi skatten ikke kan være negativ. Feilen ligger i beregningen av fellesskatt.
static final BelopUttrykk fellesskatt =
alminneligInntekt.multiplisertMed(FELLESSKATT_SATS);
Her burde det vært en regel som sa at fellesskatten ikke kan være negativ. Det er flere måter å gjøre det med uttrykk. Første måte er med hvis-uttrykk
static final BelopUttrykk fellesskatt =
hvis(alminneligInntekt.erStorreEnn(kr0()))
.brukDa(alminneligInntekt.multiplisertMed(FELLESSKATT_SATS))
.ellersBruk(kr0());
En annen måte er med begrens-uttrykk
static final BelopUttrykk fellesskatt =
begrens(
alminneligInntekt.multiplisertMed(FELLESSKATT_SATS))
.nedad(kr0());
Å begrense nedad til kr 0 er så vanlig at det finnes en kortform for det - nedre0(...)
static final BelopUttrykk fellesskatt =
nedre0(alminneligInntekt.multiplisertMed(FELLESSKATT_SATS));
Vi forestiller oss at lovgiverne har bestemt at det skal innføres toppskatt. Den har to nivåer:
- ingen toppskatt på den delen av alminnelig inntekt som er under kr 550 000
- 5% på den delen av alminnelig inntekt som er over kr 550 000 og under kr 875 000
- 8% på den delen av alminnelig inntekt som er over kr 875 000
Dette finnes et eget uttrykk for å håndtere disse tilfellene.
static final BelopUttrykk toppskatt =
multisatsFunksjonAv(alminneligInntekt)
.medSats(prosent(0),kr(550_000))
.medSats(prosent(5),kr(875_000))
.medSats(prosent(8));
Lovgiverne innfører boligsparing for ungdom (BSU). Skattyteren kan spare inntil kr 25 000 til boligformål og få inntil 20% av beløpet i skattefradraget, altså maks kr 5 000. Loven sier at hvis en av ektefellene ikke får utnyttet hele fradraget, så kan den ubenyttede delen overføres til ektefellen. Skattyteren får ikke utnyttet fradraget hvis maks fradrag er større enn utliknet skatt.
Vi antar at det finnes et skatteobjekt i skattegrunnlaget, sparebeløpBSU, som inneholder sparebeløpet. Maks BSU-fradrag er gitt ved:
static final BelopUttrykk maksSparebelopBSU =
begrens(skatteobjekt("sparebeløpBSU"))
.oppad(kr(25_000))
static final BelopUttrykk maksFradragBSU =
maksSparebelopBSU.multiplisertMed(prosent(20));
Fradraget kan ikke være større enn utliknet skatt:
static final BelopUttrykk utliknetSkatt =
fellesskatt
.pluss(toppskatt)
static final BelopUttrykk begrensetFradragBSU =
begrens(maksfradrag).oppad(utliknetSkatt);
Vi lager et uttrykk som angir hva som evt skal overføres til ektefelle
static final BelopUttrykk fradragBSUTilOverforing =
begrens(
maksfradrag.minus(utliknetSkatt)
).nedad(kr0);
Og da har vi det vi trenger for å beregne det endelige fradraget
static final BelopUttrykk fradragBSU =
begrens(
begrensetFradragBSU
.pluss(ektefelles(fradragBSUTilOverforing))
).oppad(utliknetSkatt);
Når vi skal beregne BSU-fradraget til en skattyter, så må vi også sende med informasjon om ektefellen.
public class App {
public static void main(String[] args) {
// hent skattyters og ektefelles data fra ekstern kilde
Skattegrunnlag skattegrunnlag = ...
Skattegrunnlag skattegrunnlagEktefelle =
// opprett SkattyterKontekst med input
SkattyterKontekst kontekst = SkattyterKontekst
.ny(skattegrunnlag)
.medEktefelle(skattgunnlagEktefelle)
System.out.println(kontekst.verdiAv(fradragBSU));
}
}
Eller vi kan gjøre det i en test:
public class FradragBSUTest {
@Test
public void testFellesskatt() {
SkattyterKontekst kontekst = SkattyterKontekst
.ny()
.medEktefelle();
kontekst.overstyrVerdi(alminneligInntekt, Belop.kr(500_000))
kontekst.overstyrVerdi(maksSparebelopBSU, Belop.kr(25_000))
kontekst.overstyrVerdiEktefelle(alminneligInntekt, Belop.kr(0))
kontekst.overstyrVerdiEktefelle(maksSparebelopBSU, Belop.kr(25_000))
assertEquals(Belop.kr(10_000),kontekst.verdiAv(fradragBSU));
}
}
GOTCHA Merk at vi overstyrer maksSparebelopBSU
og ikke skatteobjekt("sparebeløpBSU")
. Det skyldes at skatteobjekt(...)
returnerer
en ny instans av BelopSkatteobjektUttrykk
hver gang metoden kalles, og konteksten cacher hver instans. Dermed ville
kontekst.overstyrVerdi(skatteobjekt("sparebeløpBSU"), Belop.kr(30_000))
overstyrt en annen verdi enn den som brukes i
static final BelopUttrykk maksSparebelopBSU =
begrens(skatteobjekt("sparebeløpBSU"))
.oppad(kr(25_000))
Det kan også løses ved å sikre at at kall til skatteobjekt(...)
returner samme instans for en id:
public class BelopSkatteobjektUttrykk extends AbstractUttrykk<Belop,BelopSkatteobjektUttrykk> implements BelopUttrykk {
// Bruk ConcurrentHashMap for å være trådsikker i alle tilfellers skyld
private static final Map<String,BelopUttrykk> skatteobjekter = new ConcurrentHashMap<>();
// Vi returner BelopUttrykk, som er immutable, i stedet for BelopSkatteobjektUttrykk
public static BelopUttrykk skatteobjekt(String skatteobjekttype) {
return skatteobjekter.computeIfAbsent(skatteobjekttype,sot->new BelopSkatteobjektUttrykk(skatteobjekttype));
}
...
}
Da kan testen bli mer intuitiv:
public class FradragBSUTest {
@Test
public void testFellesskatt() {
SkattyterKontekst kontekst = SkattyterKontekst
.ny()
.medEktefelle();
kontekst.overstyrVerdi(alminneligInntekt, Belop.kr(500_000))
kontekst.overstyrVerdi(skatteobjekt("sparebeløpBSU"), Belop.kr(25_000))
kontekst.overstyrVerdiEktefelle(alminneligInntekt, Belop.kr(0))
kontekst.overstyrVerdiEktefelle(skatteobjekt("sparebeløpBSU"), Belop.kr(25_000))
assertEquals(Belop.kr(10_000),kontekst.verdiAv(fradragBSU));
}
}
Den enkle staten vår blir stadig mer kompleks. Politikerne bestemmer at det skal innføres kommuneskatt. Lønnsinntekt og renteinntekter tilfaller alltid hjemstedskommunen, mens skatten på næringsinntekt tilfaller kommunen der den oppstår. Fordelingsfradrag som renteutgifter og fagforeningskontingent fordeles på kommune forholdsmessig etter inntekten. Satsen for kummeneskatt settes til 8% og fellesskatt-satsen reduseres tilsvarende til 25%.
Vi ser for oss en skattyter bosatt i Asker, som driver med jordbruk i Tønsberg. Hun har følgende skatteobjekter:
Skatteobjekt | Beløp | Sted |
---|---|---|
lønnsinntekt | kr 78 100 | (Hjemsted) |
renteinntekt | kr 270 | (Hjemsted) |
næringsinntekt | kr 56 000 | Tønsberg |
renteutgift | kr 3 800 | (Fordeles) |
fagforeningskontingent | kr 3 400 | (Fordeles) |
Da kan følgende kode beregne skatten
import ske.fastsetting.skatt.uttrykk.belop.BelopUttrykk;
import ske.fastsetting.skatt.uttrykk.multibelop.MultiBelopUttrykk;
import ske.fastsetting.skatt.uttrykk.tall.TallUttrykk;
import static ske.fastsetting.skatt.uttrykk.multibelop.MultiKroneUttrykk.kr;
import static ske.fastsetting.skatt.uttrykk.belop.KroneUttrykk.kr;
import static ske.fastsetting.skatt.uttrykk.tall.ProsentUttrykk.prosent;
public class Skatteberegning {
static final TallUttrykk FELLESSKATT_SATS = prosent(25);
static final TallUttrykk KOMMUNESKATT_SATS = prosent(8);
static final StedbundetBelopUttrykk<String> lonnsinntekt = kr(78_100, "Asker");
static final StedbundetBelopUttrykk<String> renteinntekt = kr(270, "Asker");
static final StedbundetBelopUttrykk<String> naeringsinntekt = kr(56_000, "Tønsberg");
static final BelopUttrykk renteutgift = kr(3_800);
static final BelopUttrykk fagforeningskontingent = kr(3_400);
static final StedbundetBelopUttrykk<String> inntekt =
lonnsinntekt
.pluss(renteinntekt)
.pluss(naeringsinntekt);
static final BelopUttrykk fordelingsfradrag =
renteutgift
.pluss(fagforeningskontingent);
static final StedbundetBelopUttrykk<String> alminneligInntekt =
inntekt.minusProporsjonalt(fordelingsfradrag);
static final BelopUttrykk fellesskatt =
alminneligInntekt.steduavhengig().multiplisertMed(FELLESSKATT_SATS);
static final StedbundetBelopUttrykk<String> kommuneskatt =
alminneligInntekt.multiplisertMed(KOMMUNESKATT_SATS);
public static void main(String[] args) {
SkattyterKontekst kontekst = SkattyterKontekst.ny();
System.out.println(kontekst.verdiAv(fellesskatt));
System.out.println(kontekst.verdiAv(kommuneskatt));
System.out.println(kontekst.verdiAv(kommuneskatt).get("Asker"));
}
}
Kjører du main
-metoden, så bør du få følgende opp i konsollet:
kr 31 793
{Tønsberg=kr 4 240, Asker=kr 5 934}
kr 5 934
Kode-eksempelet viser at du kan konvertere et StedbundetBelopUttrykk
til et BelopUttrykk
ved å kalle steduavhengig()
på uttrykket.
Den forholdsmessige fordelingen av fordelingsfradrag mellom kommuner skjer i kallet
static final StedbundetBelopUttrykk<String> alminneligInntekt =
inntekt.minusProporsjonalt(fordelingsfradrag);
Politikerne innfører forskuddstrekk. Foreløpig er det snakk om to forskuddsformer - Frikort og Prosentkort. Frikort får du hvis alminnelig inntekt er under kr 30 000
static final TekstUttrykk FORSKUDSSFORM_PROSENTKORT = tekst("Prosentkort");
static final TekstUttrykk FORSKUDSSFORM_FRIKORT = tekst("Frikort");
static final TekstUttrykk forskuddsform =
hvis(alminneligInntekt.steduavhengig().erMindreEnn(kr(30_000)))
.brukDa(FORSKUDSSFORM_FRIKORT)
.ellersBruk(FORSKUDSSFORM_PROSENTKORT)
Hvis skattyter har fått prosentkort, så skal trekkprosenten være lik utliknet skatt dividert med alminnelig inntekt (i prosent), men aldri mindre enn 5%. Ellers blir trekkprosenten 0%.
static final TallUttrykk ujusterTrekkprosent = utliknetSkatt.dividertMedTilProsent(alminneligInntekt);
static final BolskUttrykk harProsentkort = forskuddsform.erLik(FORSKUDSSFORM_PROSENTKORT);
static final BolskUttrykk erUnder5Prosent = ujustertTrekkprosent.erMindreEnn(prosent(5));
static final TallUttrykk trekkprosent =
hvis(harProsentkort.og(erUnder5Prosent))).brukDa(prosent(5))
.ellersHvis(harProsentkort).brukDa(ujustertTrekkprosent)
.ellersBruk(prosent(0))
For å komme pendlerne i møte, så innføres det et reisefradrag ifm reise til og fra jobb. Det gis kr 1,50/km for reise under 50 000 km, og deretter 0,70 kr/km opp til 75 000 km. Sakttyter trekkes en egenandel på kr 16 000, og fradraget er begrenset oppad til kr 92 500 før egenandelen blir trukket.
Hvis vi antar at en skattyter reiser 56 000 km pr år, så kan reisefradraget beregnes slik:
DistanseUttrykk reiseKm = km(56_000);
final BelopPerKvantitetUttrykk<Distanse> SATS_REISE_HOY = kr(1.50).per(km());
final BelopPerKvantitetUttrykk<Distanse> SATS_REISE_LAV = kr(0.70).per(km());
final BelopUttrykk MAKS_REISEUTGIFTER = kr(92_500);
final BelopUttrykk EGENANDEL_REISEUTGIFTER = kr(16_000);
final DistanseUttrykk OEVRE_GRENSE_SATS_REISE_HØY_KM = km(50_000);
final DistanseUttrykk OEVRE_GRENSE_SATS_REISE_LAV_KM = km(75_000);
BelopUttrykk bruttoReise = multisats(reiseKm)
.medSats(SATS_REISE_HOY, OEVRE_GRENSE_SATS_REISE_HØY_KM)
.medSats(SATS_REISE_LAV, OEVRE_GRENSE_SATS_REISE_LAV_KM);
BelopUttrykk begrensetBrutto = begrens(bruttoReise).oppad(MAKS_REISEUTGIFTER);
BelopUttrykk reisefradrag = begrens(begrensetBrutto.minus(EGENANDEL_REISEUTGIFTER)).nedad(kr(0));
Skattytere bosatt lengst nord i landet skal kompenseres med et eget skattefradrag. Det gjøres enkelt sagt ved at utliknet skatt beregnes med to forskjellige satser, og differansen mellom de to blir finnmarksfradraget som skal trekkes fra. Et forenklet eksempel på bruk er som følger:
static final TallUttrykk FELLESSKATT_SATS =
hvis(skattyterErBosattIFinnmark().og(erFinnmarksberegning()))
.brukDa(prosent(18))
.ellersBruk(prosent(25))
static final TallUttrykk KOMMUNESKATT_SATS =
hvis(skattyterErBosattIFinnmark().og(erFinnmarksberegning()))
.brukDa(prosent(5))
.ellersBruk(prosent(8))
...
static final BelopUttrykk fellesskatt =
alminneligInntekt.steduavhengig().multiplisertMed(FELLESSKATT_SATS);
static final StedbundetBelopUttrykk<String> kommuneskatt =
alminneligInntekt.multiplisertMed(KOMMUNESKATT_SATS);
static final BelopUttrykk utliknetSkattFoerFradrag =
fellesskatt
.pluss(kommuneskatt.steduavhgengig);
static final BelopUttrykk finnmarksfradrag =
utliknetSkatt
.minus(finnmarksberegningAv(utliknetSkatt));
static final BelopUttrykk utliknetSkattEtterfradrag =
utliknetSkatt
.minus(finnmarksfradrag);
Uttrykkene finnmarksberegningAv(...)
og erFinnmarksberegning()
henger sammen. Den første setter opp en uavhengig beregningskontekst som er isolert fra annen beregning.
Det andre uttrykket sjekker hvilken beregningskontekst som gjelder, og gjør det mulig f.eks å velge andre satser utifra det.
Beregning av skatteklasse 2 fungerer likt, men da benyttes hhv uttrykkene skatteklasse2BeregningAv(...)
og erSkatteklasse2Beregning()
.
For eksempel finnes skatteklassen med denne kodesnutten:
private static TekstUttrykk skatteklasserForEktefeller = TekstHvisUttrykk
.hvis(skatteklasse2BeregningAv(fellesUtliknetSkatt).pluss(ektefelles(skatteklasse2BeregningAv(fellesUtliknetSkatt)))
.erMindreEnn(fellesUtliknetSkatt.pluss(ektefelles(fellesUtliknetSkatt))))
.brukDa(SKATTEKLASSE_2F)
.ellersBruk(SKATTEKLASSE_1E)
.navn("skatteklasse for ektefeller");
og i hierarkiet av beregningsuttrykk som gir fellesUtlinketSkatt
benyttes uttrykk som:
public static final StedbundetBelopUttrykk<Kommune> grunnlagInntektsskatt =
hvis(erSkatteklasse2Beregning())
.brukDa(grunnlagInntektsskattEnslig.pluss(ektefelles(grunnlagInntektsskattEnslig)))
.ellersBruk(grunnlagInntektsskattEnslig)
GOTCHA Merk at det implisitt settes opp ny kontekst, så det er foreløpig ikke mulig å overstyre verdier innenfor finnmarksberegning og skatteklasse-2-beregning ifm testing.
Det kan genereres dokumentasjon fra ett eller flere uttrykkstrær. I tillegg til beskriv
-metoden, som har ansvar for å beskrive uttrykket,
så kan også et uttrykk ha navn og tags. Uttrykk som arver fra AbstractUttrykk
har metoder for å sette navn og tags:
BelopUttrykk inntektUttrykk = kr(45_000).navn("lønnsinntekt").tags("beregningsgrunnlag");
SkattyterKontekst kontekst = SkattyterKontekst.ny();
UttrykkResultat<Belop> resultat = kontekst.dokumenter(inntektUttrykk)
ConsoleUttrykkBeskriver.print(resultat);
SkattyterKontekst kontekst = SkattyterKontekst.ny();
UttrykkResultat<Belop> resultat = kontekst.dokumenter(inntektUttrykk)
// Bør konvertere resultatet slik at det blir tilpasset wiki
UttrykkResultat<Belop> confluenceResultat = ConfluenceResultatKonverterer.konverterResultat(resultat);
ConfluenceUttrykkBeskriver beskriver = new ConfluenceUttrykkBeskriver("Hovedside");
final Map<String, ConfluenceUttrykkBeskriver.ConfluenceSide> sider = beskriver.beskriv(confluenceResultat);
sider.forEach((tittel, side) -> {
System.out.println("### " + tittel + " ###");
System.out.println(side);
});
SkattyterKontekst kontekst = SkattyterKontekst.ny();
UttrykkResultat<Belop> resultat = kontekst.dokumenter(inntektUttrykk)
// Bør konvertere resultatet slik at det blir tilpasset excel
UttrykkResultat<Belop> excelResultat = ExcelEnsligUttrykkResultatKonverterer.konverterResultat(resultat)
// Bruk evt ektefellekonverter
// ExcelEktefelleUttrykkResultatKonverterer ektefellekonverterer = new ExcelEktefelleUttrykkResultatKonverterer();
// UttrykkResultat<Belop> excelResultat = ektefellekonverter.konverterResultat(resultat)
ExcelUttrykkBeskriver beskriver = new ExcelUttrykkBeskriver();
final Workbook wb = beskriver.beskriv(excelResultat);
FileOutputStream out = new FileOutputStream("workbook.xlsx");
wb.write(out);
out.close();