Packages
Elixir library for Norwegian small business filings — Maskinporten auth, Altinn 3 API client, BRG XML/iXBRL generation, tax calculation (RF-1028/RF-1167), and shareholder register (RF-1086) XML generation. Ported from the Python CLI tool Wenche (https://github.com/olefredrik/Wenche).
Current section
5 Versions
Jump to
Current section
5 Versions
Compare versions
30
files changed
+5640
additions
-600
deletions
| @@ -5,6 +5,100 @@ All notable changes to this project will be documented in this file. | |
| 5 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), |
| 6 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 7 | |
| 8 | + ## [0.2.0] - unreleased |
| 9 | + |
| 10 | + ### Added |
| 11 | + |
| 12 | + - **Skattemelding XML submission to Skatteetaten.** New `Wenche.SkattemeldingXml` |
| 13 | + generates `skattemeldingUpersonlig` (v5), `naeringsspesifikasjon` (v6), and the |
| 14 | + `skattemeldingOgNaeringsspesifikasjonRequest` (v2) envelope against the official |
| 15 | + Skatteetaten XSDs (vendored in `priv/xsd/`). New `Wenche.SkdSkattemeldingClient` |
| 16 | + talks directly to the SKD REST API (pre-filled draft fetch, validation), and |
| 17 | + `Wenche.Skattemelding.valider/2` and `send_inn/2` orchestrate the full Altinn 3 |
| 18 | + submission flow including real `partsnummer` fetch and |
| 19 | + `dokumentreferanseTilGjeldendeDokument` chaining. |
| 20 | + - **MVA-melding (VAT return) submission.** New `Wenche.MvaMelding` and |
| 21 | + `Wenche.MvaMeldingXml` for bi-monthly VAT returns via Altinn 3. The MVA scope |
| 22 | + is opt-in via `Wenche.Systembruker.rights([:mva_melding])`. Marked experimental. |
| 23 | + - `Wenche.Skattemelding.beregn/2` for structured tax-return data and |
| 24 | + `parse_etter_beregning/1` for parsing SKD's beregningsrespons. |
| 25 | + - `Wenche.Skattemelding.beregn/2` honors `:permanent_forskjeller` on |
| 26 | + `SkattemeldingKonfig`, bypassing the global `eierandel_datterselskap` heuristic |
| 27 | + when an explicit list is supplied. |
| 28 | + - `:permanent_forskjeller` accepts `Decimal` `:beloep` for round-once cumulative |
| 29 | + semantics; integer is still accepted for backwards compatibility. |
| 30 | + - `:sum_override` field on all balance-sheet structs (`Anleggsmidler`, |
| 31 | + `Omloepmidler`, `Eiendeler`, `Egenkapital`, `LangsiktigGjeld`, `KortsiktigGjeld`, |
| 32 | + `EgenkapitalOgGjeld`) — lets callers that round once from decimal source data |
| 33 | + pin the grand total without mutating child line values. |
| 34 | + - Configurable systembruker scopes via `:features` option; skattemelding and |
| 35 | + mva_melding scopes are opt-in (revisor/regnskapsfører authorization required). |
| 36 | + - `Wenche.Systembruker.slett_system/3` for deleting a registered system. |
| 37 | + - Configurable `systemNavn` on `BrgXml.generer_hovedskjema`. |
| 38 | + - `:opprettet_av` option on skattemelding XML generators. |
| 39 | + - `:req_options` support on all HTTP client modules for custom `Req` config. |
| 40 | + - Aksjespesifikasjon forwarding through `send_inn/2` and `valider/2`, including |
| 41 | + `<spesifikasjonAvForholdRelevanteForBeskatning>` and |
| 42 | + `<forskjellMellomRegnskapsmessigOgSkattemessigVerdi>` emission. |
| 43 | + - Optional `<kontaktperson>` on `<virksomhet>`. |
| 44 | + - `Wenche.Maskinporten.get_skd_skattemelding_token/2` for direct SKD validation |
| 45 | + without Altinn exchange. |
| 46 | + |
| 47 | + ### Changed |
| 48 | + |
| 49 | + - `Wenche.AltinnClient` — the previously-labeled "Legacy API compatibility" |
| 50 | + block (`create_instance`, `update_data_element`, `complete_instance`, |
| 51 | + `get_status`) is now documented as the **token-based convenience API**. |
| 52 | + Both APIs are first-class: the struct-based functions |
| 53 | + (`opprett_instans`, …) drive Wenche's own `send_inn` flows; the token-based |
| 54 | + functions are the preferred surface for application callers that want to |
| 55 | + drive the Altinn 3 instance lifecycle themselves without constructing a |
| 56 | + client struct. |
| 57 | + - **3 % addback in `skattepliktigDelAvUtbytterOgUtdelinger` now floors** instead |
| 58 | + of `:half_up` rounding, per skatteloven § 2-38 (6) and the SKD veiledning |
| 59 | + convention used by reference implementations (Fiken). |
| 60 | + - Skattemelding XML rewritten against Skatteetaten XSDs (was a best-effort |
| 61 | + emission in 0.1.x). |
| 62 | + - BRG XML `underskjema` is now strict-XSD-valid; validation tests added. |
| 63 | + - `forekomst <id>` set to the kodeliste kode (was a generated index). |
| 64 | + - `aksjeklasse` is lowercased to match SKD's case-sensitive kodeliste. |
| 65 | + - Unknown `:aksjespesifikasjon :type` values now raise instead of being silently |
| 66 | + dropped. |
| 67 | + - Skattemelding Maskinporten scope now also includes Altinn instance scopes. |
| 68 | + - Reduced cyclomatic complexity and nesting depth across three modules; Credo |
| 69 | + strict cleanup. |
| 70 | + |
| 71 | + ### Fixed |
| 72 | + |
| 73 | + - BRG XML `orid` values rejected by Brønnøysundregistrene validation. |
| 74 | + - `406` from SKD `valider` by accepting XML responses; added |
| 75 | + `charset=UTF-8` to `valider` content-type. |
| 76 | + - Altinn 3 app slug for skattemelding submission. |
| 77 | + - Wrong MVA-melding resource ID in system user rights. |
| 78 | + - `EgenkapitalOgGjeld.sum/1` is now honored by `sumEgenkapitalGjeld` in BRG XML |
| 79 | + (was inlining child sums and ignoring `:sum_override`). |
| 80 | + - `AltinnClient.legg_til_data_element/6` now passes `client.req_options` to |
| 81 | + `Req.post` (was the only client function not honoring it, which blocked |
| 82 | + end-to-end stubbing of MVA-melding submission). |
| 83 | + |
| 84 | + ### Removed |
| 85 | + |
| 86 | + - **`Wenche.Skattemelding.generer/2`** — the human-readable text-summary |
| 87 | + renderer (and its ~250 lines of private formatting helpers). Application |
| 88 | + consumers should use `beregn/2`, which returns the same data as a |
| 89 | + structured map suitable for any rendering target. The original Python |
| 90 | + CLI tool emitted a fixed text report; in the Elixir library this concern |
| 91 | + belongs to the caller. |
| 92 | + - **`:dry_run` option on `Aarsregnskap.send_inn/3`, `Skattemelding.send_inn/2`, |
| 93 | + and `MvaMelding.send_inn/3`.** Previously, `dry_run: true` wrote XML |
| 94 | + documents to the host's current working directory via `File.write!` with |
| 95 | + synthesized filenames — a CLI ergonomic that web/embedded callers cannot |
| 96 | + use safely. To inspect XML without submitting, call the underlying |
| 97 | + generators directly: `Wenche.BrgXml.generer_hovedskjema/2` + |
| 98 | + `generer_underskjema/1`, `Wenche.SkattemeldingXml.generer_skattemelding_xml/3` |
| 99 | + + `generer_naeringsspesifikasjon_xml/2` + `generer_request_xml/3`, or |
| 100 | + `Wenche.MvaMeldingXml.generer_konvolutt_xml/1` + `generer_melding_xml/1`. |
| 101 | + |
| 8 102 | ## [0.1.1] - 2026-03-19 |
| 9 103 | |
| 10 104 | ### Fixed |
| @@ -18,9 +18,15 @@ Ported from the Python CLI tool [Wenche](https://github.com/olefredrik/Wenche). | |
| 18 18 | | `Wenche.Aarsregnskap` | Annual accounts submission flow (config, validation, submission) | `wenche/aarsregnskap.py` | |
| 19 19 | | `Wenche.BrgXml` | BRG annual statement XML (hovedskjema/underskjema) | `wenche/brg_xml.py` | |
| 20 20 | | `Wenche.Ixbrl` | Inline XBRL (iXBRL) HTML document generation | `wenche/xbrl.py` | |
| 21 | - | `Wenche.Skattemelding` | Tax calculation with fritaksmetoden (RF-1028/RF-1167) | `wenche/skattemelding.py` | |
| 21 | + | `Wenche.Noter` | Notes (noter) for small enterprises — structured XML + iXBRL text | `wenche/noter.py` | |
| 22 | + | `Wenche.Skattemelding` | Tax calculation, structured `beregn/2`, and Altinn 3 submission orchestration for skattemelding (RF-1028/RF-1167) | `wenche/skattemelding.py` | |
| 23 | + | `Wenche.SkattemeldingXml` | Skattemelding XML (`skattemeldingUpersonlig` v5, `naeringsspesifikasjon` v6, request envelope v2) against Skatteetaten XSDs | — | |
| 24 | + | `Wenche.SkdSkattemeldingClient` | Skatteetaten REST API client for skattemelding (pre-filled draft, valider) | — | |
| 25 | + | `Wenche.MvaMelding` | VAT return (MVA-melding) submission via Altinn 3. **Experimental.** | — | |
| 26 | + | `Wenche.MvaMeldingXml` | MVA-melding XML generation (`mvaMeldingInnsending` + `mvaMeldingDto`) | — | |
| 22 27 | | `Wenche.Aksjonaerregister` | RF-1086 shareholder register XML generation | `wenche/aksjonaerregister.py` | |
| 23 | - | `Wenche.Models` | Data structures (Selskap, Aarsregnskap, Resultatregnskap, Balanse, etc.) | `wenche/models.py` | |
| 28 | + | `Wenche.SkdClient` | Skatteetaten REST API client for RF-1086 (1086H / 1086U / bekreft) | — | |
| 29 | + | `Wenche.Models` | Data structures (Selskap, Aarsregnskap, Resultatregnskap, Balanse, SkattemeldingKonfig, etc.) | `wenche/models.py` | |
| 24 30 | |
| 25 31 | ## Installation |
| 26 32 | |
| @@ -29,7 +35,7 @@ Add `wenche` to your list of dependencies in `mix.exs`: | |
| 29 35 | ```elixir |
| 30 36 | def deps do |
| 31 37 | [ |
| 32 | - {:wenche, "~> 0.1.1"} |
| 38 | + {:wenche, "~> 0.2.0"} |
| 33 39 | ] |
| 34 40 | end |
| 35 41 | ``` |
| @@ -145,6 +151,10 @@ client = Wenche.AltinnClient.new(altinn_token, env: "prod") | |
| 145 151 | |
| 146 152 | ### Tax Calculation |
| 147 153 | |
| 154 | + `Wenche.Skattemelding.beregn/2` returns the computed tax return as a structured |
| 155 | + map (RF-1167 næringsoppgave, RF-1028 skattemelding, balance overview, equity |
| 156 | + note, sammenligning, warnings). Render it however you like. |
| 157 | + |
| 148 158 | ```elixir |
| 149 159 | alias Wenche.Models.SkattemeldingKonfig |
| 150 160 | |
| @@ -154,8 +164,17 @@ konfig = %SkattemeldingKonfig{ | |
| 154 164 | underskudd_til_fremfoering: 0 |
| 155 165 | } |
| 156 166 | |
| 157 | - rapport = Wenche.Skattemelding.generer(regnskap, konfig) |
| 158 | - IO.puts(rapport) |
| 167 | + beregning = Wenche.Skattemelding.beregn(regnskap, konfig) |
| 168 | + # %{ |
| 169 | + # selskap: %{navn: ..., org_nummer: ...}, |
| 170 | + # regnskapsaar: 2025, |
| 171 | + # rf_1167: %{driftsinntekter: %{...}, driftskostnader: %{...}, ...}, |
| 172 | + # rf_1028: %{utbytte: ..., beregnet_skatt: ..., ...}, |
| 173 | + # balanse: %{i_balanse: true, differanse: 0, ...}, |
| 174 | + # sammenligning: %{...} | nil, |
| 175 | + # egenkapitalnote: %{...} | nil, |
| 176 | + # advarsler: [...] |
| 177 | + # } |
| 159 178 | ``` |
| 160 179 | |
| 161 180 | ### Shareholder Register (RF-1086) |
| @@ -186,22 +205,34 @@ xml = Wenche.Aksjonaerregister.generer_xml(oppgave) | |
| 186 205 | |
| 187 206 | ### System User Setup (One-time) |
| 188 207 | |
| 208 | + By default, the system user requests rights for årsregnskap and aksjonærregisteroppgaven. |
| 209 | + The skattemelding scope is **not included by default** because systemic submission requires |
| 210 | + being a registered revisor or regnskapsfører. Enable it explicitly via the `:features` option |
| 211 | + if you have the appropriate authorization. |
| 212 | + |
| 189 213 | ```elixir |
| 190 214 | # Get admin token |
| 191 215 | {:ok, admin_token} = Wenche.Maskinporten.get_admin_token(config) |
| 192 216 | |
| 193 | - # Register system (once per vendor) |
| 194 | - {:ok, _} = Wenche.Systembruker.registrer_system(admin_token, vendor_orgnr, client_id) |
| 217 | + # Register system with default rights (årsregnskap + aksjonærregister) |
| 218 | + {:ok, _} = Wenche.Systembruker.registrer_system(admin_token, vendor_orgnr, client_id, |
| 219 | + name: "my_system", description: %{"nb" => "Mitt system", "nn" => "Mitt system", "en" => "My system"}) |
| 220 | + |
| 221 | + # Or include skattemelding scope (requires revisor/regnskapsfører authorization) |
| 222 | + {:ok, _} = Wenche.Systembruker.registrer_system(admin_token, vendor_orgnr, client_id, |
| 223 | + name: "my_system", description: %{"nb" => "...", "nn" => "...", "en" => "..."}, |
| 224 | + features: [:skattemelding]) |
| 195 225 | |
| 196 226 | # Create system user request for an organization |
| 197 | - {:ok, request} = Wenche.Systembruker.opprett_forespoersel(admin_token, vendor_orgnr, org_nummer) |
| 198 | - # User must approve at request["confirmUrl"] |
| 227 | + {:ok, request} = Wenche.Systembruker.opprett_forespoersel(admin_token, vendor_orgnr, org_nummer, |
| 228 | + name: "my_system") |
| 199 229 | |
| 200 230 | # Check approval status |
| 201 231 | {:ok, status} = Wenche.Systembruker.hent_forespoersel_status(admin_token, request["id"]) |
| 202 232 | |
| 203 233 | # List all approved system users |
| 204 | - {:ok, users} = Wenche.Systembruker.hent_systembrukere(admin_token, vendor_orgnr) |
| 234 | + {:ok, users} = Wenche.Systembruker.hent_systembrukere(admin_token, vendor_orgnr, |
| 235 | + name: "my_system") |
| 205 236 | ``` |
| 206 237 | |
| 207 238 | ## License |
| @@ -2,7 +2,7 @@ | |
| 2 2 | [{<<"GitHub">>,<<"https://github.com/jarls-side-projects/elixir-wenche">>}, |
| 3 3 | {<<"Original (Python)">>,<<"https://github.com/olefredrik/Wenche">>}]}. |
| 4 4 | {<<"name">>,<<"wenche">>}. |
| 5 | - {<<"version">>,<<"0.1.1">>}. |
| 5 | + {<<"version">>,<<"0.3.0">>}. |
| 6 6 | {<<"description">>, |
| 7 7 | <<69,108,105,120,105,114,32,108,105,98,114,97,114,121,32,102,111,114,32,78, |
| 8 8 | 111,114,119,101,103,105,97,110,32,115,109,97,108,108,32,98,117,115,105,110, |
| @@ -22,7 +22,12 @@ | |
| 22 22 | {<<"app">>,<<"wenche">>}. |
| 23 23 | {<<"licenses">>,[<<"MIT">>]}. |
| 24 24 | {<<"requirements">>, |
| 25 | - [[{<<"name">>,<<"joken">>}, |
| 25 | + [[{<<"name">>,<<"jason">>}, |
| 26 | + {<<"app">>,<<"jason">>}, |
| 27 | + {<<"optional">>,false}, |
| 28 | + {<<"requirement">>,<<"~> 1.4">>}, |
| 29 | + {<<"repository">>,<<"hexpm">>}], |
| 30 | + [{<<"name">>,<<"joken">>}, |
| 26 31 | {<<"app">>,<<"joken">>}, |
| 27 32 | {<<"optional">>,false}, |
| 28 33 | {<<"requirement">>,<<"~> 2.6">>}, |
| @@ -53,12 +58,28 @@ | |
| 53 58 | {<<"requirement">>,<<"~> 2.11">>}, |
| 54 59 | {<<"repository">>,<<"hexpm">>}]]}. |
| 55 60 | {<<"files">>, |
| 56 | - [<<"lib">>,<<"lib/wenche">>,<<"lib/wenche/maskinporten.ex">>, |
| 57 | - <<"lib/wenche/aksjonaerregister.ex">>,<<"lib/wenche/skattemelding.ex">>, |
| 58 | - <<"lib/wenche/ixbrl.ex">>,<<"lib/wenche/systembruker.ex">>, |
| 59 | - <<"lib/wenche/aarsregnskap.ex">>,<<"lib/wenche/skd_client.ex">>, |
| 60 | - <<"lib/wenche/noter.ex">>,<<"lib/wenche/models.ex">>, |
| 61 | - <<"lib/wenche/altinn_client.ex">>,<<"lib/wenche/brg_xml.ex">>, |
| 62 | - <<"lib/wenche.ex">>,<<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>, |
| 63 | - <<"LICENSE">>,<<"CHANGELOG.md">>]}. |
| 61 | + [<<"lib">>,<<"lib/wenche">>,<<"lib/wenche/systembruker.ex">>, |
| 62 | + <<"lib/wenche/aarsregnskap.ex">>,<<"lib/wenche/altinn_client.ex">>, |
| 63 | + <<"lib/wenche/skd_client.ex">>,<<"lib/wenche/aksjonaerregister.ex">>, |
| 64 | + <<"lib/wenche/models.ex">>,<<"lib/wenche/noter.ex">>, |
| 65 | + <<"lib/wenche/skd_skattemelding_client.ex">>, |
| 66 | + <<"lib/wenche/mva_melding_xml.ex">>,<<"lib/wenche/skattemelding_xml.ex">>, |
| 67 | + <<"lib/wenche/maskinporten.ex">>,<<"lib/wenche/skattemelding.ex">>, |
| 68 | + <<"lib/wenche/ixbrl.ex">>,<<"lib/wenche/brg_xml.ex">>, |
| 69 | + <<"lib/wenche/mva_melding.ex">>,<<"lib/wenche.ex">>,<<"priv">>, |
| 70 | + <<"priv/xsd">>,<<"priv/xsd/brreg">>,<<"priv/xsd/brreg/RR-0002">>, |
| 71 | + <<"priv/xsd/brreg/RR-0002/RR-0002_1266-51820.xsd">>, |
| 72 | + <<"priv/xsd/brreg/RR-0002/RR-0002-U_758-51980.xsd">>, |
| 73 | + <<"priv/xsd/skatteetaten">>, |
| 74 | + <<"priv/xsd/skatteetaten/skattemeldingognaeringsspesifikasjonrequest_v2.xsd">>, |
| 75 | + <<"priv/xsd/skatteetaten/skattemeldingUpersonlig_v5_ekstern.xsd">>, |
| 76 | + <<"priv/xsd/skatteetaten/naeringsspesifikasjon_v6_ekstern.xsd">>, |
| 77 | + <<"priv/xsd/skatteetaten/NOTICE">>,<<"priv/xsd/skatteetaten/mva">>, |
| 78 | + <<"priv/xsd/skatteetaten/mva/no.skatteetaten.fastsetting.avgift.mva.skattemeldingformerverdiavgift.v1.0.xsd">>, |
| 79 | + <<"priv/xsd/skatteetaten/mva/no.skatteetaten.fastsetting.avgift.mva.mvameldinginnsending.v1.0.xsd">>, |
| 80 | + <<"priv/xsd/skatteetaten/mva/no.skatteetaten.fastsetting.avgift.mva.valideringsresultat.v1.xsd">>, |
| 81 | + <<"priv/xsd/skatteetaten/mva/no.skatteetaen.fastsetting.avgift.mva.mvaMeldingInnsendingStatus.v1.xsd">>, |
| 82 | + <<"priv/xsd/skatteetaten/mva/no.skatteetaten.fastsetting.avgift.mva.skattemeldingformerverdiavgift.betalingsinformasjon.v1.0.xsd">>, |
| 83 | + <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>, |
| 84 | + <<"CHANGELOG.md">>]}. |
| 64 85 | {<<"build_tools">>,[<<"mix">>]}. |
| @@ -9,21 +9,21 @@ defmodule Wenche.Aarsregnskap do | |
| 9 9 | |
| 10 10 | alias Wenche.Models.{ |
| 11 11 | Aarsregnskap, |
| 12 | - Selskap, |
| 13 | - Resultatregnskap, |
| 12 | + Anleggsmidler, |
| 14 13 | Balanse, |
| 15 14 | Driftsinntekter, |
| 16 15 | Driftskostnader, |
| 17 | - Finansposter, |
| 18 | - Eiendeler, |
| 19 | - Anleggsmidler, |
| 20 | - Omloepmidler, |
| 21 | - EgenkapitalOgGjeld, |
| 22 16 | Egenkapital, |
| 23 | - LangsiktigGjeld, |
| 17 | + EgenkapitalOgGjeld, |
| 18 | + Eiendeler, |
| 19 | + Finansposter, |
| 24 20 | KortsiktigGjeld, |
| 21 | + LaanTilNaerstaaende, |
| 22 | + LangsiktigGjeld, |
| 25 23 | Noter, |
| 26 | - LaanTilNaerstaaende |
| 24 | + Omloepmidler, |
| 25 | + Resultatregnskap, |
| 26 | + Selskap |
| 27 27 | } |
| 28 28 | |
| 29 29 | alias Wenche.{AltinnClient, BrgXml} |
| @@ -49,15 +49,15 @@ defmodule Wenche.Aarsregnskap do | |
| 49 49 | errors = [] |
| 50 50 | |
| 51 51 | errors = |
| 52 | - if not Balanse.er_i_balanse?(regnskap.balanse) do |
| 52 | + if Balanse.er_i_balanse?(regnskap.balanse) do |
| 53 | + errors |
| 54 | + else |
| 53 55 | diff = Balanse.differanse(regnskap.balanse) |
| 54 56 | |
| 55 57 | [ |
| 56 58 | "Balansen går ikke opp: eiendeler og egenkapital+gjeld avviker med #{diff} NOK." |
| 57 59 | | errors |
| 58 60 | ] |
| 59 | - else |
| 60 | - errors |
| 61 61 | end |
| 62 62 | |
| 63 63 | org = String.replace(regnskap.selskap.org_nummer, " ", "") |
| @@ -72,7 +72,8 @@ defmodule Wenche.Aarsregnskap do | |
| 72 72 | noter = regnskap.noter |
| 73 73 | |
| 74 74 | errors = |
| 75 | - if noter.antall_ansatte == 0 and regnskap.resultatregnskap.driftskostnader.loennskostnader > 0 do |
| 75 | + if noter.antall_ansatte == 0 and |
| 76 | + regnskap.resultatregnskap.driftskostnader.loennskostnader > 0 do |
| 76 77 | ["Advarsel: Lønnskostnader > 0 men antall ansatte er 0 i noter." | errors] |
| 77 78 | else |
| 78 79 | errors |
| @@ -81,7 +82,10 @@ defmodule Wenche.Aarsregnskap do | |
| 81 82 | errors = |
| 82 83 | if regnskap.balanse.egenkapital_og_gjeld.langsiktig_gjeld.laan_fra_aksjonaer > 0 and |
| 83 84 | noter.laan_til_naerstaaende == [] do |
| 84 | - ["Advarsel: Lån fra aksjonær i balansen men ingen lån til nærstående i noter (§7-45)." | errors] |
| 85 | + [ |
| 86 | + "Advarsel: Lån fra aksjonær i balansen men ingen lån til nærstående i noter (§7-45)." |
| 87 | + | errors |
| 88 | + ] |
| 85 89 | else |
| 86 90 | errors |
| 87 91 | end |
| @@ -92,33 +96,29 @@ defmodule Wenche.Aarsregnskap do | |
| 92 96 | @doc """ |
| 93 97 | Submits the annual accounts to Brønnøysundregistrene via Altinn. |
| 94 98 | |
| 95 | - ## Options |
| 96 | - |
| 97 | - - `:dry_run` — if true, writes XML files locally without sending to Altinn (default: false) |
| 99 | + To inspect the generated XML without submitting, call |
| 100 | + `Wenche.BrgXml.generer_hovedskjema/2` and `Wenche.BrgXml.generer_underskjema/1` |
| 101 | + directly. |
| 98 102 | |
| 99 103 | Returns `{:ok, inbox_url}` or `{:error, reason}`. |
| 100 104 | """ |
| 101 105 | def send_inn(%Aarsregnskap{} = regnskap, %AltinnClient{} = client, opts \\ []) do |
| 102 | - dry_run = Keyword.get(opts, :dry_run, false) |
| 106 | + case valider(regnskap) do |
| 107 | + [] -> do_send_inn(regnskap, client, opts) |
| 108 | + errors -> {:error, {:validation_failed, errors}} |
| 109 | + end |
| 110 | + end |
| 103 111 | |
| 104 | - errors = valider(regnskap) |
| 105 | - |
| 106 | - if errors != [] do |
| 107 | - {:error, {:validation_failed, errors}} |
| 108 | - else |
| 109 | - hovedskjema = BrgXml.generer_hovedskjema(regnskap) |
| 112 | + defp do_send_inn(regnskap, client, opts) do |
| 113 | + brg_opts = Keyword.take(opts, [:system_navn]) |
| 114 | + hovedskjema = BrgXml.generer_hovedskjema(regnskap, brg_opts) |
| 110 115 | underskjema = BrgXml.generer_underskjema(regnskap) |
| 111 116 | org = regnskap.selskap.org_nummer |
| 112 | - aar = regnskap.regnskapsaar |
| 113 117 | |
| 114 | - if dry_run do |
| 115 | - hoved_fil = "aarsregnskap_#{aar}_#{org}_hovedskjema.xml" |
| 116 | - under_fil = "aarsregnskap_#{aar}_#{org}_underskjema.xml" |
| 117 | - File.write!(hoved_fil, hovedskjema) |
| 118 | - File.write!(under_fil, underskjema) |
| 118 | + submit_to_altinn(client, org, hovedskjema, underskjema) |
| 119 | + end |
| 119 120 | |
| 120 | - {:ok, {:dry_run, hoved_fil, under_fil}} |
| 121 | - else |
| 121 | + defp submit_to_altinn(client, org, hovedskjema, underskjema) do |
| 122 122 | with {:ok, instans} <- AltinnClient.opprett_instans(client, "aarsregnskap", org), |
| 123 123 | {:ok, _} <- |
| 124 124 | AltinnClient.oppdater_data_element( |
| @@ -137,11 +137,8 @@ defmodule Wenche.Aarsregnskap do | |
| 137 137 | "Underskjema", |
| 138 138 | underskjema, |
| 139 139 | "application/xml" |
| 140 | - ), |
| 141 | - {:ok, inbox_url} <- AltinnClient.fullfoor_instans(client, "aarsregnskap", instans) do |
| 142 | - {:ok, inbox_url} |
| 143 | - end |
| 144 | - end |
| 140 | + ) do |
| 141 | + AltinnClient.fullfoor_instans(client, "aarsregnskap", instans) |
| 145 142 | end |
| 146 143 | end |
| 147 144 | |
| @@ -199,71 +196,100 @@ defmodule Wenche.Aarsregnskap do | |
| 199 196 | end |
| 200 197 | |
| 201 198 | defp les_resultat(r) do |
| 202 | - di = r["driftsinntekter"] || %{} |
| 203 | - dk = r["driftskostnader"] || %{} |
| 204 | - fp = r["finansposter"] || %{} |
| 205 | - |
| 206 199 | %Resultatregnskap{ |
| 207 | - driftsinntekter: %Driftsinntekter{ |
| 200 | + driftsinntekter: parse_driftsinntekter(r["driftsinntekter"] || %{}), |
| 201 | + driftskostnader: parse_driftskostnader(r["driftskostnader"] || %{}), |
| 202 | + finansposter: parse_finansposter(r["finansposter"] || %{}) |
| 203 | + } |
| 204 | + end |
| 205 | + |
| 206 | + defp parse_driftsinntekter(di) do |
| 207 | + %Driftsinntekter{ |
| 208 208 | salgsinntekter: di["salgsinntekter"] || 0, |
| 209 209 | andre_driftsinntekter: di["andre_driftsinntekter"] || 0 |
| 210 | - }, |
| 211 | - driftskostnader: %Driftskostnader{ |
| 210 | + } |
| 211 | + end |
| 212 | + |
| 213 | + defp parse_driftskostnader(dk) do |
| 214 | + %Driftskostnader{ |
| 212 215 | loennskostnader: dk["loennskostnader"] || 0, |
| 213 216 | avskrivninger: dk["avskrivninger"] || 0, |
| 214 217 | andre_driftskostnader: dk["andre_driftskostnader"] || 0 |
| 215 | - }, |
| 216 | - finansposter: %Finansposter{ |
| 218 | + } |
| 219 | + end |
| 220 | + |
| 221 | + defp parse_finansposter(fp) do |
| 222 | + %Finansposter{ |
| 217 223 | utbytte_fra_datterselskap: fp["utbytte_fra_datterselskap"] || 0, |
| 218 224 | andre_finansinntekter: fp["andre_finansinntekter"] || 0, |
| 219 225 | rentekostnader: fp["rentekostnader"] || 0, |
| 220 226 | andre_finanskostnader: fp["andre_finanskostnader"] || 0 |
| 221 227 | } |
| 222 | - } |
| 223 228 | end |
| 224 229 | |
| 225 230 | defp les_balanse(b) do |
| 226 | - ei = b["eiendeler"] || %{} |
| 227 | - am = ei["anleggsmidler"] || %{} |
| 228 | - om = ei["omloepmidler"] || %{} |
| 229 | - eog = b["egenkapital_og_gjeld"] || %{} |
| 230 | - ek = eog["egenkapital"] || %{} |
| 231 | - lg = eog["langsiktig_gjeld"] || %{} |
| 232 | - kg = eog["kortsiktig_gjeld"] || %{} |
| 233 | - |
| 234 231 | %Balanse{ |
| 235 | - eiendeler: %Eiendeler{ |
| 236 | - anleggsmidler: %Anleggsmidler{ |
| 232 | + eiendeler: parse_eiendeler(b["eiendeler"] || %{}), |
| 233 | + egenkapital_og_gjeld: parse_egenkapital_og_gjeld(b["egenkapital_og_gjeld"] || %{}) |
| 234 | + } |
| 235 | + end |
| 236 | + |
| 237 | + defp parse_eiendeler(ei) do |
| 238 | + %Eiendeler{ |
| 239 | + anleggsmidler: parse_anleggsmidler(ei["anleggsmidler"] || %{}), |
| 240 | + omloepmidler: parse_omloepmidler(ei["omloepmidler"] || %{}) |
| 241 | + } |
| 242 | + end |
| 243 | + |
| 244 | + defp parse_anleggsmidler(am) do |
| 245 | + %Anleggsmidler{ |
| 237 246 | aksjer_i_datterselskap: am["aksjer_i_datterselskap"] || 0, |
| 238 247 | andre_aksjer: am["andre_aksjer"] || 0, |
| 239 248 | langsiktige_fordringer: am["langsiktige_fordringer"] || 0 |
| 240 | - }, |
| 241 | - omloepmidler: %Omloepmidler{ |
| 249 | + } |
| 250 | + end |
| 251 | + |
| 252 | + defp parse_omloepmidler(om) do |
| 253 | + %Omloepmidler{ |
| 242 254 | kortsiktige_fordringer: om["kortsiktige_fordringer"] || 0, |
| 243 255 | bankinnskudd: om["bankinnskudd"] || 0 |
| 244 256 | } |
| 245 | - }, |
| 246 | - egenkapital_og_gjeld: %EgenkapitalOgGjeld{ |
| 247 | - egenkapital: %Egenkapital{ |
| 257 | + end |
| 258 | + |
| 259 | + defp parse_egenkapital_og_gjeld(eog) do |
| 260 | + %EgenkapitalOgGjeld{ |
| 261 | + egenkapital: parse_egenkapital(eog["egenkapital"] || %{}), |
| 262 | + langsiktig_gjeld: parse_langsiktig_gjeld(eog["langsiktig_gjeld"] || %{}), |
| 263 | + kortsiktig_gjeld: parse_kortsiktig_gjeld(eog["kortsiktig_gjeld"] || %{}) |
| 264 | + } |
| 265 | + end |
| 266 | + |
| 267 | + defp parse_egenkapital(ek) do |
| 268 | + %Egenkapital{ |
| 248 269 | aksjekapital: ek["aksjekapital"] || 0, |
| 249 270 | overkursfond: ek["overkursfond"] || 0, |
| 250 271 | annen_egenkapital: ek["annen_egenkapital"] || 0 |
| 251 | - }, |
| 252 | - langsiktig_gjeld: %LangsiktigGjeld{ |
| 272 | + } |
| 273 | + end |
| 274 | + |
| 275 | + defp parse_langsiktig_gjeld(lg) do |
| 276 | + %LangsiktigGjeld{ |
| 253 277 | laan_fra_aksjonaer: lg["laan_fra_aksjonaer"] || 0, |
| 254 278 | andre_langsiktige_laan: lg["andre_langsiktige_laan"] || 0 |
| 255 | - }, |
| 256 | - kortsiktig_gjeld: %KortsiktigGjeld{ |
| 279 | + } |
| 280 | + end |
| 281 | + |
| 282 | + defp parse_kortsiktig_gjeld(kg) do |
| 283 | + %KortsiktigGjeld{ |
| 257 284 | leverandoergjeld: kg["leverandoergjeld"] || 0, |
| 258 285 | skyldige_offentlige_avgifter: kg["skyldige_offentlige_avgifter"] || 0, |
| 259 286 | annen_kortsiktig_gjeld: kg["annen_kortsiktig_gjeld"] || 0 |
| 260 287 | } |
| 261 | - } |
| 262 | - } |
| 263 288 | end |
| 264 289 | |
| 265 290 | defp les_noter(n) do |
| 266 | - laan = (n["laan_til_naerstaaende"] || []) |
| 291 | + laan = |
| 292 | + (n["laan_til_naerstaaende"] || []) |
| 267 293 | |> Enum.map(fn l -> |
| 268 294 | %LaanTilNaerstaaende{ |
| 269 295 | navn: l["navn"], |
| @@ -13,7 +13,7 @@ defmodule Wenche.Aksjonaerregister do | |
| 13 13 | 3. POST bekreft — confirm all sub-forms submitted |
| 14 14 | """ |
| 15 15 | |
| 16 | - alias Wenche.Models.{Aksjonaerregisteroppgave, Aksjonaer} |
| 16 | + alias Wenche.Models.{Aksjonaer, Aksjonaerregisteroppgave} |
| 17 17 | |
| 18 18 | @doc """ |
| 19 19 | Generates RF-1086 Hovedskjema XML for SKD's API. |
Loading more files…