From df47b7fd1a982143c2571acfbba0f3f1aad8cd06 Mon Sep 17 00:00:00 2001 From: cos Date: Sun, 1 May 2022 17:04:54 +0200 Subject: Initial commit --- README.md | 37 +++++ bin/combine-ne.pl | 9 ++ bin/deklarera.sh | 411 ++++++++++++++++++++++++++++++++++++++++++++++++++ bin/ink1-ifyllning.pl | 51 +++++++ bin/k4-ifyllning.pl | 65 ++++++++ bin/moms-ifyllning.pl | 55 +++++++ bin/ne-ifyllning.pl | 116 ++++++++++++++ 7 files changed, 744 insertions(+) create mode 100644 README.md create mode 100644 bin/combine-ne.pl create mode 100755 bin/deklarera.sh create mode 100755 bin/ink1-ifyllning.pl create mode 100755 bin/k4-ifyllning.pl create mode 100755 bin/moms-ifyllning.pl create mode 100755 bin/ne-ifyllning.pl diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef5f8f1 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +Skatteverket in .se requires taxes to be declared every year. While they do +provide "e-tjänster" to do this online, those are essentially worthless¹ and +filling in paper forms are practically still the easiest way to get this stuff +done. + +Manually filling in papers is boring and error prone. Thus the existance of +these scripts. They are only written to work for me and no warranties are +given or implied. + +Seriously. Please hold no expectations that these scripts could be of any use +for you. It's low quality scripting, run at most once per year. I publish it on +the odd chance that possibly someone might find inspiration on how to deal with +instutitions still depending on actual paper forms for things. + +The idea is that running `bin/deklarera.sh` should interactively go through the +steps necessary to file taxes. It is basically just a checklist. Some auxiliary +scripts prepares svg files with numbers placed in the right boxes. Fragile and +only implemented for forms and fields that I have encountered. + +Consider it all to be in the public domain. Patches are welcome. + +1. Skatteverket's e-tjänster have arbitrary unavailability planned for parts of + each and every day, and require the use of e-legitimation which practically + means BankID. While BankID has legitimate use cases, that authentication + solution is unsuitable for filing taxes. As with any task required to be + performed on a tight schedule, it is best done without potentially involving + the requirement to run out and buy a new MacBook and/or spending a few days + messing with a bank's customer service. In addition, I assume all the usual + disadvantages of modern websites exist. All too quick automatic logout on + percieved inactivity and user interfaces that change between every time? It + is likely is about as buggy as one can expect. + In case anyone at Skatteverket with sufficient power ever happens to read + this; If you'd wish to process less paper forms, could you please launch + initiatives to incent some e-legitimation to again be usable by open source + software? If you've noticed a bump in the trend, remember that BankID used + to work with fribid.se before those monopolists started to actively block + auditable and secure software for no good reason. diff --git a/bin/combine-ne.pl b/bin/combine-ne.pl new file mode 100644 index 0000000..4c657a5 --- /dev/null +++ b/bin/combine-ne.pl @@ -0,0 +1,9 @@ +use strict; +use PDF::Reuse; + +prFile("ne.pdf"); + +prDoc('ne-framsida.pdf'); +prDoc('ne-baksida.pdf'); + +prEnd(); diff --git a/bin/deklarera.sh b/bin/deklarera.sh new file mode 100755 index 0000000..e17672f --- /dev/null +++ b/bin/deklarera.sh @@ -0,0 +1,411 @@ +#!/bin/bash -eu +# +# Require bash due to readline version of `read`. Also used for lower casing. +# +# Debian dependencies: (and likely others) +# datefudge evince inkscape libpdf-reuse-perl libgd-svg-perl mupdf pdf2svg + +YEAR="$(datefudge '6 months ago' date +%Y)" +SKIP=0 +CLEAR='no' + +usage() { + echo "${0} [--help] [--skip=]" + exit 1 +} + +show_help() { + echo '(y)es Run the command. Advances to the next on success.' + echo '(n)o Skip the command.' + echo '(c)lear Clear the screen.' + echo '(r)epeat Run the command. Asks again, never advances.' + echo '(h)elp Show this help' + echo '(q)uit Exit script.' +} + +handle_arg() { + local arg="${1}" + local param="${arg%=*}" value="${arg#*=}" + local ret=0 + + if [ "${arg}" = "${param}" ]; then + value="${2}" + ret=1 + fi + + case "${param}" in + '--help' | '-h') + usage + exit 0 + ;; + '--skip' | '-s') + SKIP="${value}" + return "${ret}" + ;; + *) + echo "Unknown parameter: ${param}" >&2 + exit 1 + ;; + esac + + return 0 +} + +SHIFT_NEXT='' +for ARG in "${@}"; do + shift + [ ! "${SHIFT_NEXT}" ] || { SHIFT_NEXT=''; continue; } + + if [ "${ARG#-}" != "${ARG}" ]; then + handle_arg "${ARG}" "${1}" || SHIFT_NEXT='y' + continue + fi + set -- "$@" "${ARG}" +done + +do_cmd() { + local start end duration pipe tee_pid + + if eval "${CMD}"; then + DONE='yes' + else + FAILED='yes' + fi + if [ "${FAILED}" != 'no' ]; then + echo 'Command failed.' >&2 + fi + [ "${CLEAR}" = 'no' ] || clear +} + +input() { + local file="${1}" var="${2}" prompt="${3}" value=${4:-} + local exists input tmp_file=$( mktemp ) + + if grep -q "^${var}" "${file}" 2>/dev/null; then + exists='y' + else + exists='' + fi + + if [ "${exists}" ]; then + if [ -e "${file}" ]; then + value=$( sed -n "s/^${var}: //p" <"${file}" ) + else + value='' + fi + fi + read -p "${prompt}" -i "${value}" -e input + + if [ "${exists}" ]; then + sed "s/^${var}: .*/${var}: ${input}/" <"${file}" >"${tmp_file}" + elif [ -e "${file}" ]; then + ( cat "${file}" 2>/dev/null; echo "${var}: ${input}" ) >>"${tmp_file}" + elif [ "${value}" ]; then + echo "${var}: ${input}" >>"${tmp_file}" + fi + mv "${tmp_file}" "${file}" +} + +############################################################################### + +copy_template() { + # Skatteverket used to publish 215003.pdf, a regular editable pdf. Now they + # seem to have fully gone for xfa forms. :( + # (https://unix.stackexchange.com/questions/265845/xfa-forms) + cp --recursive 'mall/0001-forenklad-arsbokslut/' "${YEAR}" +} + +mata_in_arsbokslut() { + local values="${YEAR}/0001-forenklad-arsbokslut/values.txt" + local b9 b10 b14 r1 r5 r11 + + input "${values}" 'B9' 'B9 Kassa och bank: ' + input "${values}" 'B10' 'B10 Eget kapital: ' + input "${values}" 'B14' 'B14 Skatteskulder: ' + input "${values}" 'R1' 'R1 Försäljning och utfört arbete…: ' + input "${values}" 'R5' 'R5 Varor, material och tjänster: ' + input "${values}" 'R11' 'R11 Bokfört resultat: ' + echo "${values}" + echo "${values}" | sed 's/./-/g' + cat "${values}" + + b9=$( sed -n 's/^B9: //p' <"${values}" ) + b10=$( sed -n 's/^B10: //p' <"${values}" ) + b14=$( sed -n 's/^B14: //p' <"${values}" ) + r1=$( sed -n 's/^R1: //p' <"${values}" ) + r5=$( sed -n 's/^R5: //p' <"${values}" ) + r11=$( sed -n 's/^R11: //p' <"${values}" ) + + if [ "$(( b10 + b14 ))" = "${b9}" ]; then + echo 'Balance seems to be ok.' + else + echo "Invalid numbers: B10 + B14 != B9. ($(( b10 + b14 )))" + return 1 + fi + if [ "$(( r1 - r5 ))" = "${r11}" ]; then + echo 'Result seems to be ok.' + else + echo "Invalid numbers: R1 - R5 != R11. ($(( r1 + r5 )))" + return 1 + fi +} + +mata_in_ne() { + local values="${YEAR}/0003-ne-enskild_näringsverksamhet/values.txt" + + # FIXME Get value från R43 of previous year. + input "${values}" 'R40' 'R40 Medgivna avdrag för egenavgifter…: ' + input "${values}" 'R41' 'R41 Påförda egenavgifter och…: ' + # FIXME Get these too from previous year. + input "${values}" 'Personnummer' 'Personnummer: ' + input "${values}" 'SNI-kategori' 'SNI-kategori: ' + echo 'https://www.verksamt.se/driva/skatter-och-avgifter/enskild-naringsverksamhet/preliminar-skatt-och-egenavgifter' + input "${values}" 'egenavgifter' 'egenavgifter: ' + + echo "${values}" + echo "${values}" | sed 's/./-/g' + cat "${values}" +} + +mata_in_k4() { + local values="${YEAR}/0004-k4/values.txt" + + input "${values}" 'Antal' 'Antal: ' + input "${values}" 'Beteckning' 'Beteckning: ' + input "${values}" 'Forsaljningspris' 'Försäljninspris: ' + input "${values}" 'Omkostnadsbelopp' 'Omkostnadsbelopp: ' + input "${values}" 'Vinst' 'Vinst: ' + + echo "${values}" + echo "${values}" | sed 's/./-/g' + cat "${values}" +} + +overfor_till_ink1() { + local ne="${1}" + local k4="${2}" + local values="${YEAR}/0005-ink1/values.txt" + local p7_4 p10_1 + + p7_4=$( sed -n 's/^Vinst: //p' <"${k4}" ) + p10_1=$( sed -n 's/^R47: //p' <"${ne}" ) + + input "${values}" 'p7_4' '7.4: ' "${p7_4}" + input "${values}" 'p10_1' '10.1: ' "${p10_1}" + input "${values}" 'email' 'E-postadress: ' + input "${values}" 'phone' 'Telefonnummer: ' + + echo "${values}" + echo "${values}" | sed 's/./-/g' + cat "${values}" +} + +overfor_till_moms() { + local bokslut="${1}" + local ink1="${2}" + local values="${YEAR}/0006-moms/values.txt" + local a05 g49 email phone + + a05=$( sed -n 's/^R1: //p' <"${bokslut}" ) + g49=$( sed -n 's/^B14: //p' <"${bokslut}" ) + email=$( sed -n 's/^email: //p' <"${ink1}" ) + phone=$( sed -n 's/^phone: //p' <"${ink1}" ) + + input "${values}" 'A05' 'A05 Momspliktig försäljning…: ' "${a05}" + input "${values}" 'B10' 'B10 Utgående moms 25%: ' + input "${values}" 'F48' 'F48 Ingående moms: ' + input "${values}" 'G49' 'G49 Moms att betala eller få tillbaka: ' + input "${values}" 'email' 'E-postadress: ' "${email}" + input "${values}" 'phone' 'Telefonnummer: ' "${phone}" + + echo "${values}" + echo "${values}" | sed 's/./-/g' + cat "${values}" +} + +ladda_ner_neblankett() { + echo "Please use your web browser to download SKV 2161" + echo 'https://skatteverket.se/funktioner/sok/sok.4.64a656d113f4c7597011b3.html?query=ne' + echo "Must be the 'Icke ifyllnadsbar pdf för utskrift' one." +} + +ladda_ner_k4blankett() { + echo "Please use your web browser to download SKV 2104" + echo 'https://skatteverket.se/funktioner/sok/sok.4.64a656d113f4c7597011b3.html?query=k4' + echo "Must be the 'Icke ifyllnadsbar pdf för utskrift' one." +} + +gor_neblankett_redigerbar() { + cd "${YEAR}/0003-ne-enskild_näringsverksamhet/" + pdf2svg 2161_*_20??-01-01?20??-12-31.pdf ne-%d.svg all + cd ../../ +} + +gor_k4blankett_redigerbar() { + cd "${YEAR}/0004-k4/" + pdf2svg 2104_*_web_20??*.pdf k4-%d.svg all + cd ../../ +} + +skriv_under() { +cat <&2 ;; + esac + done +done diff --git a/bin/ink1-ifyllning.pl b/bin/ink1-ifyllning.pl new file mode 100755 index 0000000..a5f7fb0 --- /dev/null +++ b/bin/ink1-ifyllning.pl @@ -0,0 +1,51 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use GD::SVG; + +open FILE, $ARGV[0] or die $!; +my %values; +my ($key, $value); +while (my $line = ) { + chomp($line); + ($key, $value) = $line =~ /^(.*): *(.*)/g; + $values{$key} = $value; +} +close FILE; + +my $ink1_1 = GD::SVG::Image->new(792, 1122); +my $ink1_2 = GD::SVG::Image->new(792, 1122); + +my $ink1_1_white = $ink1_1->colorAllocate(255,255,255); +my $ink1_1_black = $ink1_1->colorAllocate(0,0,0); +my $ink1_2_white = $ink1_2->colorAllocate(255,255,255); +my $ink1_2_black = $ink1_2->colorAllocate(0,0,0); +my $font = gdLargeFont; + +$ink1_1->startGroup('ink1-1'); +$ink1_1->rectangle(0,0,792,1122, $ink1_1_white); + +$ink1_1->string($font, 688, 432, $values{'p7_4'}, $ink1_1_black); + +$ink1_1->endGroup; + +$ink1_2->startGroup('ink1-2'); +$ink1_2->rectangle(0,0,792,1122, $ink1_2_white); + +print "Before\n"; +$ink1_2->string($font, 212, 124, $values{'p10_1'}, $ink1_2_black); +print "After\n"; + +# 266 is too far left, 280 is too far right. +my $contact_column = 273; +$ink1_2->string($font, $contact_column, 984, $values{'email'}, $ink1_2_black); +$ink1_2->string($font, $contact_column+356, 984, $values{'phone'}, $ink1_2_black); + +$ink1_1->endGroup; + +open(SVGFILE, ">ink1_1-overlay.svg"); +print SVGFILE $ink1_1->svg; +open(SVGFILE, ">ink1_2-overlay.svg"); +print SVGFILE $ink1_2->svg; diff --git a/bin/k4-ifyllning.pl b/bin/k4-ifyllning.pl new file mode 100755 index 0000000..c5a9a00 --- /dev/null +++ b/bin/k4-ifyllning.pl @@ -0,0 +1,65 @@ +#!/usr/bin/perl +# FIXME Rewrite to handle multiple entries. + +use strict; +use warnings; + +use GD::SVG; +use User::pwent; + +my ($full_name) = (User::pwent::getpwnam(getlogin)->gecos)[0] =~ /([^,]*),+$/; + +open FILE, $ARGV[0] or die $!; +my %values; +my ($key, $value); +while (my $line = ) { + chomp($line); + ($key, $value) = $line =~ /^(.*): *(.*)/g; + $values{$key} = $value; +} +close FILE; + +my $k4_1 = GD::SVG::Image->new(792, 1122); +my $k4_2 = GD::SVG::Image->new(792, 1122); + +my $k4_1_white = $k4_1->colorAllocate(255,255,255); +my $k4_1_black = $k4_1->colorAllocate(0,0,0); +my $k4_2_white = $k4_2->colorAllocate(255,255,255); +my $k4_2_black = $k4_2->colorAllocate(0,0,0); +my $font = gdLargeFont; + +$k4_1->startGroup('k4-1'); +$k4_1->rectangle(0,0,792,1122, $k4_1_white); + +$k4_1->string($font, 580, 112, `date +%Y-%m-%d`, $k4_1_black); + +$k4_1->string($font, 60, 225, $full_name, $k4_1_black); +$k4_1->string($font, 580, 225, $values{'Personnummer'}, $k4_1_black); + +# Antal +$k4_1->string($font, 80, 321, $values{'Antal'}, $k4_1_black); +# Beteckning +$k4_1->string($font, 165, 321, $values{'Beteckning'}, $k4_1_black); +# Forsaljningspris +$k4_1->string($font, 290, 321, $values{'Forsaljningspris'}, $k4_1_black); +# Omkostnadsbelopp +$k4_1->string($font, 405, 321, $values{'Omkostnadsbelopp'}, $k4_1_black); +# Vinst +$k4_1->string($font, 520, 321, $values{'Vinst'}, $k4_1_black); +# Forlust +#$k4_1->string($font, 635, 321, "0", $k4_1_black); + +# Summa +# Forsaljningspris +$k4_1->string($font, 290, 615, $values{'Forsaljningspris'}, $k4_1_black); +# Omkostnadsbelopp +$k4_1->string($font, 405, 615, $values{'Omkostnadsbelopp'}, $k4_1_black); +# Vinst +$k4_1->string($font, 520, 615, $values{'Vinst'}, $k4_1_black); +# Forlust +#$k4_1->string($font, 635, 615, "0", $k4_1_black); + +$k4_1->endGroup; + +open(SVGFILE, ">k4_1-overlay.svg"); +print SVGFILE $k4_1->svg; diff --git a/bin/moms-ifyllning.pl b/bin/moms-ifyllning.pl new file mode 100755 index 0000000..8f8d46b --- /dev/null +++ b/bin/moms-ifyllning.pl @@ -0,0 +1,55 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use GD::SVG; +use User::pwent; + +my ($full_name) = (User::pwent::getpwnam(getlogin)->gecos)[0] =~ /([^,]*),+$/; + +open FILE, $ARGV[0] or die $!; +my %values; +my ($key, $value); +while (my $line = ) { + chomp($line); + ($key, $value) = $line =~ /^(.*): *(.*)/g; + $values{$key} = $value; +} +close FILE; + +if (int($values{'A05'}*0.25+0.5) ne $values{'B10'}) { + print STDERR "Utgående moms stämmer icke! (A05, B10)\n"; + exit 1; +} + +if ($values{'B10'}-$values{'F48'} ne $values{'G49'}) { + print STDERR "Momsberäkning balanserar icke! (B10-F48 != G49)\n"; + print STDERR "B10 - F48 = $values{'B10'} - $values{'F48'} == " + .($values{'B10'}-$values{'F48'})."\n"; + print STDERR "G49 = $values{'G49'} == ".($values{'G49'})."\n"; + exit 1; +} + +my $moms = GD::SVG::Image->new(792, 1122); + +my $moms_white = $moms->colorAllocate(255,255,255); +my $moms_black = $moms->colorAllocate(0,0,0); +my $font = gdLargeFont; + +$moms->startGroup('ne-1'); +$moms->rectangle(0,0,792,1122, $moms_white); + +$moms->string($font, 219, 297, $values{'A05'}, $moms_black); +$moms->string($font, 563, 297, $values{'B10'}, $moms_black); +$moms->string($font, 563, 786, $values{'F48'}, $moms_black); +$moms->string($font, 563, 851, $values{'G49'}, $moms_black); + +$moms->string($font, 427, 945, $full_name, $moms_black); +$moms->string($font, 427, 977, $full_name, $moms_black); +$moms->string($font, 427, 1008, $values{'phone'}, $moms_black); + +$moms->endGroup; + +open(SVGFILE, ">moms-overlay.svg"); +print SVGFILE $moms->svg; diff --git a/bin/ne-ifyllning.pl b/bin/ne-ifyllning.pl new file mode 100755 index 0000000..27c0459 --- /dev/null +++ b/bin/ne-ifyllning.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use GD::SVG; +use User::pwent; + +my ($full_name) = (User::pwent::getpwnam(getlogin)->gecos)[0] =~ /([^,]*),+$/; + +open FILE, $ARGV[0] or die $!; +my %values; +my ($key, $value); +while (my $line = ) { + chomp($line); + ($key, $value) = $line =~ /^(.*): *(.*)/g; + $values{$key} = $value; +} +close FILE; + +if ($values{'B9'}-$values{'B14'} ne $values{'B10'}) { + print STDERR "Balanserar icke (B1-B12)!\n"; + exit 1; +} + +if ($values{'R1'}-$values{'R5'} ne $values{'R11'}) { + print STDERR "Balanserar icke! (R1-R11)\n"; + exit 1; +} + +# Bokfört resultat (förs över från B11) +$values{'R12'} = $values{'R11'}; +# Sammanlagt resultat av verksamheten +$values{'R17'} = $values{'R12'}; +# Överskott (+) / Underskott (-) +$values{'R29'} = $values{'R12'}; +# Överskott (+) / Underskott (-), periodiseringsfond +$values{'R33'} = $values{'R29'}; +# Överskott (+) / Underskott (-), expansionsfond +$values{'R35'} = $values{'R33'}; +# Överskott (+) / Underskott (-), före egenavgifter och löneskatt +$values{'R42'} = $values{'R35'}+$values{'R40'}-$values{'R41'}; +# Årets beräknade avdrag för egenavgifter och särskild löneskatt +my $R43_max_deduct = int(($values{'R42'} * 0.075) + 0.5); +if ($R43_max_deduct > 15000) { + $R43_max_deduct = 15000; +} +#my $covid_100k = 100000; +#my $R43 = int(($covid_100k * 0.1021) + (($R42 - $covid_100k) * 0.2897) + 0.5) - $R43_max_deduct; +$values{'R43'} = int(($values{'R42'} * $values{'egenavgifter'}) + 0.5) - $R43_max_deduct; +# Överskott (+) +$values{'R47'} = $values{'R42'}-$values{'R43'}; + +my $ne1 = GD::SVG::Image->new(792, 1122); +my $ne2 = GD::SVG::Image->new(792, 1122); + +my $ne1_white = $ne1->colorAllocate(255,255,255); +my $ne1_black = $ne1->colorAllocate(0,0,0); +my $ne2_white = $ne2->colorAllocate(255,255,255); +my $ne2_black = $ne2->colorAllocate(0,0,0); +my $font = gdLargeFont; + +$ne1->startGroup('ne-1'); +$ne1->rectangle(0,0,792,1122, $ne1_white); + +$ne1->string($font, 420, 152, `date +%Y-%m-%d`, $ne1_black); +$ne1->string($font, 60, 200, $full_name, $ne1_black); +$ne1->string($font, 600, 200, $values{'Personnummer'}, $ne1_black); +$ne1->string($font, 60, 250, $values{'SNI-kategori'}, $ne1_black); +$ne1->string($font, 600, 296, $values{'Personnummer'}, $ne1_black); + +$ne1->string($font, 300, 704, $values{'B9'}, $ne1_black); +$ne1->string($font, 645, 400, $values{'B10'}, $ne1_black); +$ne1->string($font, 645, 641, $values{'B14'}, $ne1_black); + +$ne1->string($font, 300, 785, $values{'R1'}, $ne1_black); +$ne1->string($font, 300, 945, $values{'R5'}, $ne1_black); +$ne1->string($font, 645, 898, $values{'R11'}, $ne1_black); + +# Uppdragstagare har biträtt vid upprättandet +$ne1->string(gdGiantFont, 583, 980, 'x', $ne1_black); + +$ne1->endGroup; + +my $ne2_left_col = 320; +my $ne2_right_col = 675; +# my $ne2_line_height = 35.78; # Lines differ in height... + +$ne2->startGroup('ne-2'); +$ne2->rectangle(0, 0, 792, 1122, $ne2_white); + +$ne2->string($font, 600, 50, $values{'Personnummer'}, $ne2_black); + +$ne2->string($font, $ne2_left_col, 112, $values{'R12'}, $ne2_black); +$ne2->string($font, $ne2_left_col, 271, $values{'R17'}, $ne2_black); +$ne2->string($font, $ne2_right_col, 206, $values{'R33'}, $ne2_black); +$ne2->string($font, $ne2_right_col, 271, $values{'R35'}, $ne2_black); +$ne2->string($font, $ne2_right_col, 451, $values{'R40'}, $ne2_black); +$ne2->string($font, $ne2_right_col, 485, $values{'R41'}, $ne2_black); +$ne2->string($font, $ne2_right_col, 519, $values{'R42'}, $ne2_black); +$ne2->string($font, $ne2_right_col, 553, $values{'R43'}, $ne2_black); +$ne2->string($font, $ne2_right_col, 703, $values{'R47'}, $ne2_black); +$ne2->string($font, $ne2_left_col, 735, $values{'R29'}, $ne2_black); + +$ne2->endGroup; + +open(SVGFILE, ">ne1-overlay.svg"); +print SVGFILE $ne1->svg; +open(SVGFILE, ">ne2-overlay.svg"); +print SVGFILE $ne2->svg; + +open(FILE, '>', $ARGV[0]."-out"); + for (keys %values ) { + print FILE $_.": ".$values{$_}."\n"; + } +close FILE; -- cgit v1.2.3