aboutsummaryrefslogtreecommitdiff
path: root/clients/screens_reports.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-20 22:00:01 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-20 22:00:01 +0200
commit10a535ace098144980d0ffd1593f6384c39cc221 (patch)
tree53a7c6c5026d479c4b6d5fd4e5b341474cd190fa /clients/screens_reports.c
parent3a5a6d3b04dc4e17a42225afb243eecb3502ead6 (diff)
downloadbokf-10a535ace098144980d0ffd1593f6384c39cc221.tar.gz
bokf-10a535ace098144980d0ffd1593f6384c39cc221.zip
tui: split bokftui into screen files
Diffstat (limited to 'clients/screens_reports.c')
-rw-r--r--clients/screens_reports.c1176
1 files changed, 1176 insertions, 0 deletions
diff --git a/clients/screens_reports.c b/clients/screens_reports.c
new file mode 100644
index 0000000..e6263a2
--- /dev/null
+++ b/clients/screens_reports.c
@@ -0,0 +1,1176 @@
+#include <dirent.h>
+#include <errno.h>
+#include <locale.h>
+#include <math.h>
+#include <ncursesw/ncurses.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "client.h"
+#include "tui.h"
+#include "formula.h"
+#include "util.h"
+#include "version.h"
+#include "yyjson.h"
+#include "ui.h"
+
+/* ------------------------------------------------------------------ */
+/* report formatting (Kapitas-like text tables) */
+/* ------------------------------------------------------------------ */
+/* prefix (8) + label (48) + amount columns */
+#define BS_RULE_W (8 + 48 + 4 * 14)
+#define IS_RULE_W (8 + 48 + 3 * 15)
+
+static void fmt_head(struct buf *t, const struct app *a, const char *title,
+ yyjson_val *res)
+{
+ yyjson_val *fy = yyjson_obj_get(res, "fiscal_year");
+ const char *start = vstr(fy, "start_date");
+ const char *end = vstr(fy, "end_date");
+ buf_line(t, MK_HEAD "%s\n%s\n\n", title, a->org_name);
+ buf_line(t, MK_DIM "Räkenskapsår: %s - %s\n", start, end);
+ buf_line(t, MK_DIM "Period: %s - %s\n\n", start, end);
+}
+/* Pad to `width` display columns without truncating (labels may be longer). */
+static void pad_min(char *buf, size_t n, int width)
+{
+ int w = tui_disp_width(buf);
+ size_t len = strlen(buf);
+ while (w < width && len + 1 < n) {
+ buf[len++] = ' ';
+ buf[len] = '\0';
+ w++;
+ }
+}
+
+/* Right-align `text` in `width` display columns (headers with å/ä/ö). */
+
+/* ---------------- balansräkning ---------------- */
+
+struct bs_sum {
+ int64_t ib, per, ub;
+};
+
+static void bs_col_head(struct buf *t, int sticky)
+{
+ buf_line(t, "%s %5s %-48s %14s %14s %14s %14s\n",
+ sticky ? MK_STICKY MK_HEAD : "", "", "", "Ing balans", "Ing saldo",
+ "Period", "Utg balans");
+ buf_rule(t, BS_RULE_W);
+}
+
+static int bs_rows(struct buf *t, yyjson_val *res, int lo, int hi,
+ struct bs_sum *sum)
+{
+ int found = 0;
+ for (int k = 0; k < 3; k++) {
+ yyjson_val *sec = yyjson_obj_get(res, BS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *row = yyjson_arr_get(arr, i);
+ const char *acc = vstr(row, "account");
+ if (!acct_in(acc, lo, hi))
+ continue;
+ int64_t ib = vint(row, "ib_ore");
+ int64_t ub = vint(row, "ub_ore");
+ int64_t per = ub - ib;
+ if (t) {
+ char nm[128], a1[48], a2[48], a3[48], a4[48];
+ tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48);
+ tui_amt_col(a1, sizeof a1, 14, ib);
+ tui_amt_col(a2, sizeof a2, 14, ib);
+ tui_amt_col(a3, sizeof a3, 14, per);
+ tui_amt_col(a4, sizeof a4, 14, ub);
+ buf_line(t, " %5s %s %s %s %s %s\n", acc, nm, a1, a2, a3, a4);
+ }
+ sum->ib += ib;
+ sum->per += per;
+ sum->ub += ub;
+ found = 1;
+ }
+ }
+ return found;
+}
+
+static void bs_sum_line(struct buf *t, const char *label,
+ const struct bs_sum *s)
+{
+ char lbl[64], a1[48], a2[48], a3[48], a4[48];
+ tui_pad_label(lbl, sizeof lbl, label, 48);
+ tui_amt_col(a1, sizeof a1, 14, s->ib);
+ tui_amt_col(a2, sizeof a2, 14, s->ib);
+ tui_amt_col(a3, sizeof a3, 14, s->per);
+ tui_amt_col(a4, sizeof a4, 14, s->ub);
+ buf_line(t, MK_DIM " %5s %s %s %s %s %s\n", "", lbl, a1, a2, a3, a4);
+}
+
+static int bs_group(struct buf *t, yyjson_val *res, const char *title,
+ const char *sumlabel, int lo, int hi, struct bs_sum *acc)
+{
+ struct bs_sum probe = { 0, 0, 0 };
+ if (!bs_rows(NULL, res, lo, hi, &probe))
+ return 0;
+ buf_line(t, MK_HEAD "%s\n", title);
+ struct bs_sum s = { 0, 0, 0 };
+ bs_rows(t, res, lo, hi, &s);
+ bs_sum_line(t, sumlabel, &s);
+ if (acc) {
+ acc->ib += s.ib;
+ acc->per += s.per;
+ acc->ub += s.ub;
+ }
+ return 1;
+}
+
+static void fmt_balans(struct buf *t, const struct app *a, yyjson_val *res)
+{
+ static const struct {
+ const char *title;
+ const char *sum;
+ int lo, hi;
+ } OMS[] = {
+ { "Varulager", "Summa varulager", 1400, 1499 },
+ { "Kortfristiga fordringar", "Summa kortfristiga fordringar", 1500,
+ 1699 },
+ { "Kortfristiga placeringar", "Summa kortfristiga placeringar", 1700,
+ 1899 },
+ { "Kassa och bank", "Summa kassa och bank", 1900, 1999 },
+ };
+ fmt_head(t, a, "Balansrapport", res);
+ struct bs_sum anl = { 0, 0, 0 }, oms = { 0, 0, 0 };
+ struct bs_sum eget = { 0, 0, 0 }, skuld = { 0, 0, 0 };
+ buf_line(t, MK_HEAD "TILLGÅNGAR\n");
+ bs_col_head(t, 1);
+ buf_line(t, "\n" MK_HEAD "Anläggningstillgångar\n");
+ bs_rows(t, res, 1000, 1399, &anl);
+ bs_sum_line(t, "Summa anläggningstillgångar", &anl);
+ buf_line(t, "\n" MK_HEAD "Omsättningstillgångar\n");
+ int printed = 0;
+ for (size_t i = 0; i < sizeof OMS / sizeof OMS[0]; i++) {
+ struct bs_sum probe = { 0, 0, 0 };
+ if (!bs_rows(NULL, res, OMS[i].lo, OMS[i].hi, &probe))
+ continue;
+ if (printed)
+ buf_line(t, "\n");
+ bs_group(t, res, OMS[i].title, OMS[i].sum, OMS[i].lo, OMS[i].hi,
+ &oms);
+ printed = 1;
+ }
+ bs_sum_line(t, "Summa omsättningstillgångar", &oms);
+ struct bs_sum as = { anl.ib + oms.ib, anl.per + oms.per, anl.ub + oms.ub };
+ buf_rule(t, BS_RULE_W);
+ bs_sum_line(t, "Summa tillgångar", &as);
+
+ buf_line(t, "\n" MK_HEAD "EGET KAPITAL OCH SKULDER\n");
+ bs_col_head(t, 0);
+ buf_line(t, "\n" MK_HEAD "Eget kapital\n");
+ bs_group(t, res, "Bundet eget kapital", "Summa bundet eget kapital", 2000,
+ 2090, &eget);
+ buf_line(t, "\n");
+ bs_group(t, res, "Fritt eget kapital", "Summa fritt eget kapital", 2091,
+ 2099, &eget);
+ bs_sum_line(t, "Summa eget kapital", &eget);
+ buf_line(t, "\n");
+ if (bs_group(t, res, "Långfristiga skulder", "Summa långfristiga skulder",
+ 2100, 2399, &skuld))
+ buf_line(t, "\n");
+ bs_group(t, res, "Kortfristiga skulder", "Summa kortfristiga skulder",
+ 2400, 2999, &skuld);
+ struct bs_sum es = { eget.ib + skuld.ib, eget.per + skuld.per,
+ eget.ub + skuld.ub };
+ buf_rule(t, BS_RULE_W);
+ bs_sum_line(t, "Summa eget kapital och skulder", &es);
+
+ yyjson_val *eq = yyjson_obj_get(res, "equity");
+ int64_t result = eq ? vint(eq, "result_ore") : 0;
+ char lbl[64], zero[48], a1[48];
+ tui_pad_label(lbl, sizeof lbl, "Beräknat resultat", 48);
+ tui_amt_col(zero, sizeof zero, 14, 0);
+ tui_amt_col(a1, sizeof a1, 14, result);
+ buf_line(t, "\n");
+ buf_rule(t, BS_RULE_W);
+ buf_line(t, MK_DIM " %5s %s %s %s %s %s\n", "", lbl, zero, zero, a1, a1);
+}
+/* ---------------- resultaträkning ---------------- */
+
+static const char *const IS_KEYS[2] = { "revenue", "expenses" };
+
+static int is_group_has(yyjson_val *res, const struct is_group *g)
+{
+ for (int k = 0; k < 2; k++) {
+ yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++)
+ if (acct_in(vstr(yyjson_arr_get(arr, i), "account"), g->lo,
+ g->hi))
+ return 1;
+ }
+ return 0;
+}
+
+static int64_t is_prev_lookup(yyjson_val *prev, const char *account,
+ int negate)
+{
+ if (!prev)
+ return 0;
+ for (int k = 0; k < 2; k++) {
+ yyjson_val *sec = yyjson_obj_get(prev, IS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *row = yyjson_arr_get(arr, i);
+ if (strcmp(vstr(row, "account"), account) == 0) {
+ int64_t a = vint(row, "amount_ore");
+ return negate ? -a : a;
+ }
+ }
+ }
+ return 0;
+}
+
+static void is_summary(struct buf *t, const char *label, int64_t cur,
+ int64_t prev, int blank)
+{
+ char lbl[64], a1[48], a2[48];
+ if (blank)
+ buf_line(t, "\n");
+ tui_pad_label(lbl, sizeof lbl, label, 48);
+ tui_amt_col(a1, sizeof a1, 15, cur);
+ tui_amt_col(a2, sizeof a2, 15, prev);
+ buf_line(t, MK_DIM " %5s %s %s %s %s\n", "", lbl, a1, a1, a2);
+}
+
+static void is_print_group(struct buf *t, yyjson_val *res, yyjson_val *prev,
+ const struct is_group *gr, int64_t *cur,
+ int64_t *pre)
+{
+ int64_t sc = 0, sp = 0;
+ for (int k = 0; k < 2; k++) {
+ yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *row = yyjson_arr_get(arr, i);
+ const char *acc = vstr(row, "account");
+ if (!acct_in(acc, gr->lo, gr->hi))
+ continue;
+ int64_t c = gr->rev ? vint(row, "amount_ore")
+ : -vint(row, "amount_ore");
+ int64_t p = is_prev_lookup(prev, acc, !gr->rev);
+ char nm[128], a1[48], a2[48];
+ tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48);
+ tui_amt_col(a1, sizeof a1, 15, c);
+ tui_amt_col(a2, sizeof a2, 15, p);
+ buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2);
+ sc += c;
+ sp += p;
+ }
+ }
+ is_summary(t, "Summa", sc, sp, 0);
+ *cur = sc;
+ *pre = sp;
+}
+
+static int is_range_has(yyjson_val *res, int lo, int hi)
+{
+ for (int k = 0; k < 2; k++) {
+ yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++)
+ if (acct_in(vstr(yyjson_arr_get(arr, i), "account"), lo, hi))
+ return 1;
+ }
+ return 0;
+}
+
+/* Rows in one account range, printed with the previous-year column; the
+ signed display sums are added to *sum / *sum_pre. */
+static void is_range_rows(struct buf *t, yyjson_val *res, yyjson_val *prev,
+ int lo, int hi, int64_t *sum, int64_t *sum_pre)
+{
+ for (int k = 0; k < 2; k++) {
+ yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *row = yyjson_arr_get(arr, i);
+ const char *acc = vstr(row, "account");
+ if (!acct_in(acc, lo, hi))
+ continue;
+ int64_t c = k == 0 ? vint(row, "amount_ore")
+ : -vint(row, "amount_ore");
+ int64_t p = is_prev_lookup(prev, acc, k != 0);
+ char nm[128], a1[48], a2[48];
+ tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48);
+ tui_amt_col(a1, sizeof a1, 15, c);
+ tui_amt_col(a2, sizeof a2, 15, p);
+ buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2);
+ *sum += c;
+ *sum_pre += p;
+ }
+ }
+}
+
+static void fmt_resultat(struct buf *t, const struct app *a, yyjson_val *res,
+ yyjson_val *prev)
+{
+ fmt_head(t, a, "Resultatrapport", res);
+ const char *sect = NULL;
+ int64_t rev = 0, rev_pre = 0, exp = 0, exp_pre = 0;
+ for (size_t g = 0; g < sizeof IS_GROUPS / sizeof IS_GROUPS[0]; g++) {
+ const struct is_group *gr = &IS_GROUPS[g];
+ if (!is_group_has(res, gr))
+ continue;
+ if (!sect || strcmp(sect, gr->section) != 0) {
+ if (sect) {
+ is_summary(t, "Summa rörelseintäkter, lagerförändringar m.m.",
+ rev, rev_pre, 0);
+ buf_line(t, "\n");
+ buf_line(t, MK_HEAD "%s\n", gr->section);
+ } else {
+ char lbl[80], h1[32], h2[32], h3[32];
+ tui_pad_label(lbl, sizeof lbl, gr->section, 48);
+ tui_pad_hdr(h1, sizeof h1, 15, "Period");
+ tui_pad_hdr(h2, sizeof h2, 15, "Ackumulerat");
+ tui_pad_hdr(h3, sizeof h3, 15, "Föreg. per.");
+ buf_line(t, MK_STICKY MK_HEAD " %5s %s %s %s %s\n", "", lbl,
+ h1, h2, h3);
+ buf_rule(t, IS_RULE_W);
+ buf_line(t, "\n");
+ buf_line(t, MK_HEAD "%s\n", gr->title);
+ }
+ sect = gr->section;
+ } else {
+ buf_line(t, "\n");
+ buf_line(t, MK_HEAD "%s\n", gr->title);
+ }
+ int64_t sc = 0, sp = 0;
+ is_print_group(t, res, prev, gr, &sc, &sp);
+ if (gr->rev) {
+ rev += sc;
+ rev_pre += sp;
+ } else {
+ exp += sc;
+ exp_pre += sp;
+ }
+ }
+ is_summary(t, "Summa rörelsekostnader", exp, exp_pre, 0);
+
+ int64_t fin = 0, fin_pre = 0, disp = 0, disp_pre = 0;
+ int64_t tax = 0, tax_pre = 0, booked = 0, booked_pre = 0;
+ int has_disp = is_range_has(res, 8800, 8899);
+ int has_tax = is_range_has(res, 8900, 8989);
+ int has_booked = is_range_has(res, 8999, 8999);
+ if (is_range_has(res, 8000, 8599)) {
+ buf_line(t, "\n" MK_HEAD "Finansiella poster\n");
+ is_range_rows(t, res, prev, 8000, 8599, &fin, &fin_pre);
+ is_summary(t, "Summa", fin, fin_pre, 0);
+ }
+ int64_t ror = rev + exp, ror_pre = rev_pre + exp_pre;
+ int64_t after = ror + fin, after_pre = ror_pre + fin_pre;
+ buf_line(t, "\n");
+ buf_rule(t, IS_RULE_W);
+ is_summary(t, "Rörelseresultat", ror, ror_pre, 0);
+ is_summary(t, "Resultat efter finansiella poster", after, after_pre, 1);
+ if (has_disp) {
+ buf_line(t, "\n" MK_HEAD "Bokslutsdispositioner\n");
+ is_range_rows(t, res, prev, 8800, 8899, &disp, &disp_pre);
+ is_summary(t, "Summa", disp, disp_pre, 0);
+ }
+ int64_t before_tax = after + disp, before_tax_pre = after_pre + disp_pre;
+ is_summary(t, "Resultat före skatt", before_tax, before_tax_pre, 1);
+ if (has_tax) {
+ buf_line(t, "\n" MK_HEAD "Skatt\n");
+ is_range_rows(t, res, prev, 8900, 8989, &tax, &tax_pre);
+ is_summary(t, "Summa", tax, tax_pre, 0);
+ }
+ int64_t ars = before_tax + tax, ars_pre = before_tax_pre + tax_pre;
+ buf_line(t, "\n");
+ buf_rule(t, IS_RULE_W);
+ is_summary(t, "Årets resultat", ars, ars_pre, 0);
+ if (has_booked) {
+ buf_line(t, "\n" MK_HEAD "Bokfört resultat\n");
+ is_range_rows(t, res, prev, 8999, 8999, &booked, &booked_pre);
+ is_summary(t, "Summa", booked, booked_pre, 0);
+ buf_line(t, "\n");
+ buf_rule(t, IS_RULE_W);
+ is_summary(t, "Ej bokfört resultat", ars + booked,
+ ars_pre + booked_pre, 0);
+ } else {
+ buf_line(t, "\n");
+ buf_rule(t, IS_RULE_W);
+ is_summary(t, "Ej bokfört resultat", ars, ars_pre, 0);
+ }
+}
+
+/* ---------------- saldobalans ---------------- */
+
+static void fmt_saldobalans(struct buf *t, const struct app *a,
+ yyjson_val *res)
+{
+ fmt_head(t, a, "Saldobalans", res);
+ char nmh[64], h1[32], h2[32], h3[32], h4[32];
+ tui_pad_label(nmh, sizeof nmh, "Benämning", 48);
+ tui_pad_hdr(h1, sizeof h1, 14, "Ingående");
+ tui_pad_hdr(h2, sizeof h2, 14, "Debet");
+ tui_pad_hdr(h3, sizeof h3, 14, "Kredit");
+ tui_pad_hdr(h4, sizeof h4, 14, "Utgående");
+ buf_line(t, MK_STICKY MK_HEAD " %5s %s %s %s %s %s\n", "Konto", nmh, h1,
+ h2, h3, h4);
+ buf_rule(t, BS_RULE_W);
+ yyjson_val *arr = yyjson_obj_get(res, "accounts");
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *row = yyjson_arr_get(arr, i);
+ char nm[128], a1[48], a2[48], a3[48], a4[48];
+ tui_pad_label(nm, sizeof nm, vstr(row, "name"), 48);
+ tui_amt_col(a1, sizeof a1, 14, vint(row, "ib_ore"));
+ tui_amt_col(a2, sizeof a2, 14, vint(row, "debit_ore"));
+ tui_amt_col(a3, sizeof a3, 14, vint(row, "credit_ore"));
+ tui_amt_col(a4, sizeof a4, 14, vint(row, "ub_ore"));
+ buf_line(t, " %5s %s %s %s %s %s\n", vstr(row, "account"), nm, a1,
+ a2, a3, a4);
+ }
+ yyjson_val *tot = yyjson_obj_get(res, "totals");
+ char lbl[64], a1[48], a2[48], a3[48], a4[48];
+ tui_pad_label(lbl, sizeof lbl, "Summa", 48);
+ tui_amt_col(a1, sizeof a1, 14, vint(tot, "ib_ore"));
+ tui_amt_col(a2, sizeof a2, 14, vint(tot, "debit_ore"));
+ tui_amt_col(a3, sizeof a3, 14, vint(tot, "credit_ore"));
+ tui_amt_col(a4, sizeof a4, 14, vint(tot, "ub_ore"));
+ buf_line(t, "\n");
+ buf_rule(t, BS_RULE_W);
+ buf_line(t, MK_DIM " %5s %s %s %s %s %s\n", "", lbl, a1, a2, a3, a4);
+}
+
+/* ---------------- momsdeklaration ---------------- */
+
+struct vat_row {
+ const char *section;
+ const char *box;
+ const char *label;
+};
+
+static const struct vat_row VAT_ROWS[] = {
+ { "A.", "05",
+ "Momspliktig försäljning som inte ingår i ruta 06, 07 eller 08" },
+ { "A.", "06", "Momspliktiga uttag" },
+ { "A.", "07", "Beskattningsunderlag vid vinstmarginalbeskattning" },
+ { "A.", "08", "Hyresinkomster vid frivillig beskattning" },
+ { "B.", "10", "Utgående moms 25%" },
+ { "B.", "11", "Utgående moms 12%" },
+ { "B.", "12", "Utgående moms 6%" },
+ { "C.", "20", "Inköp av varor från annat EU-land" },
+ { "C.", "21", "Inköp av tjänster från annat EU-land enligt huvudregeln" },
+ { "C.", "22", "Inköp av tjänster från land utanför EU" },
+ { "C.", "23", "Inköp av varor i Sverige som köparen är betalningsskyldig för" },
+ { "C.", "24", "Övriga inköp av tjänster i Sverige som köparen är betalningsskyldig för" },
+ { "D.", "30", "Utgående moms 25%" },
+ { "D.", "31", "Utgående moms 12%" },
+ { "D.", "32", "Utgående moms 6%" },
+ { "H.", "50", "Beskattningsunderlag vid import" },
+ { "I.", "60", "Utgående moms 25%" },
+ { "I.", "61", "Utgående moms 12%" },
+ { "I.", "62", "Utgående moms 6%" },
+ { "E.", "35", "Försäljning av varor till annat EU-land" },
+ { "E.", "36", "Försäljning av varor utanför EU" },
+ { "E.", "37", "Mellanmans inköp av varor vid trepartshandel" },
+ { "E.", "38", "Mellanmans försäljning av varor vid trepartshandel" },
+ { "E.", "39", "Försäljning av tjänster till beskattningsbar person i annat EU-land enligt huvudregeln" },
+ { "E.", "40", "Övrig försäljning av tjänster som tillhandahållits utomlands" },
+ { "E.", "41", "Försäljning när köparen är betalningsskyldig i Sverige" },
+ { "E.", "42", "Övrig försäljning m.m." },
+ { "F.", "48", "Ingående moms att dra av" },
+ { "G.", "49", "Moms att betala (+) eller att få tillbaka (-)" },
+};
+
+static const char *vat_section_title(const char *s)
+{
+ if (strcmp(s, "A.") == 0)
+ return "Momspliktig försäljning eller uttag exklusive moms";
+ if (strcmp(s, "B.") == 0)
+ return "Utgående moms på försäljning eller uttag i ruta 05 - 08";
+ if (strcmp(s, "C.") == 0)
+ return "Momspliktiga inköp vid omvänd skattskyldighet";
+ if (strcmp(s, "D.") == 0)
+ return "Utgående moms på inköp i ruta 20 - 24";
+ if (strcmp(s, "H.") == 0)
+ return "Import";
+ if (strcmp(s, "I.") == 0)
+ return "Utgående moms på import i ruta 50";
+ if (strcmp(s, "E.") == 0)
+ return "Försäljning m.m. som är undantagen från moms";
+ if (strcmp(s, "F.") == 0)
+ return "Ingående moms";
+ return "Moms att betala eller få tillbaka";
+}
+
+static void fmt_moms(struct buf *t, const struct app *a, yyjson_val *res)
+{
+ buf_line(t, MK_HEAD "Momsrapport\n\n%s\n\n", a->org_name);
+ buf_line(t, MK_DIM "Räkenskapsår: %s - %s\n", a->fy_start, a->fy_end);
+ buf_line(t, MK_DIM "Period: %s - %s\n", vstr(res, "from"),
+ vstr(res, "to"));
+ const char *sect = NULL;
+ yyjson_val *boxes = yyjson_obj_get(res, "boxes");
+ size_t n = yyjson_arr_size(boxes);
+ for (size_t i = 0; i < sizeof VAT_ROWS / sizeof VAT_ROWS[0]; i++) {
+ const struct vat_row *vr = &VAT_ROWS[i];
+ if (strcmp(vr->box, "49") == 0)
+ continue; /* last */
+ if (!sect || strcmp(sect, vr->section) != 0) {
+ sect = vr->section;
+ buf_line(t, "\n" MK_HEAD "%s %s\n", vr->section,
+ vat_section_title(vr->section));
+ }
+ int64_t ore = 0;
+ for (size_t j = 0; j < n; j++) {
+ yyjson_val *b = yyjson_arr_get(boxes, j);
+ if (strcmp(vstr(b, "box"), vr->box) == 0) {
+ ore = vint(b, "amount_ore");
+ break;
+ }
+ }
+ char amt[48] = "", lbl[192];
+ if (ore)
+ kr_whole(ore, amt, sizeof amt);
+ snprintf(lbl, sizeof lbl, "%s", vr->label);
+ pad_min(lbl, sizeof lbl, 66);
+ buf_line(t, "%s. %s %14s\n", vr->box, lbl, amt);
+ }
+ int64_t ore49 = 0;
+ for (size_t j = 0; j < n; j++) {
+ yyjson_val *b = yyjson_arr_get(boxes, j);
+ if (strcmp(vstr(b, "box"), "49") == 0) {
+ ore49 = vint(b, "amount_ore");
+ break;
+ }
+ }
+ buf_line(t, "\n" MK_HEAD "G. %s\n", vat_section_title("G."));
+ buf_rule(t, 87);
+ char amt49[48], lbl49[192];
+ kr_whole(ore49, amt49, sizeof amt49);
+ snprintf(lbl49, sizeof lbl49, "%s",
+ "Moms att betala (+) eller att få tillbaka (-)");
+ pad_min(lbl49, sizeof lbl49, 66);
+ buf_line(t, MK_DIM "49. %s %14s\n", lbl49, amt49);
+}
+static int64_t prev_fy_id(struct app *a)
+{
+ char *resp =
+ client_rpc(&a->conn, "fiscal_year.list", a->session, a->org, "{}");
+ if (!resp || !client_ok(resp)) {
+ free(resp);
+ return 0;
+ }
+ size_t n = jarr_size(resp, "result.items");
+ int64_t best = 0;
+ char best_end[16] = "";
+ for (size_t i = 0; i < n; i++) {
+ char path[64];
+ snprintf(path, sizeof path, "result.items.%zu.end_date", i);
+ char *e = jstr_dup(resp, path);
+ if (e && strcmp(e, a->fy_start) < 0 && strcmp(e, best_end) > 0) {
+ snprintf(path, sizeof path, "result.items.%zu.id", i);
+ int64_t id = jint_val(resp, path, 0);
+ if (id) {
+ best = id;
+ snprintf(best_end, sizeof best_end, "%s", e);
+ }
+ }
+ free(e);
+ }
+ free(resp);
+ return best;
+}
+
+/* ---------------- huvudbok ---------------- */
+
+static void fmt_huvudbok(struct buf *t, const struct app *a, yyjson_val *res)
+{
+ fmt_head(t, a, "Huvudbok", res);
+ yyjson_val *lv = yyjson_obj_get(res, "last_voucher");
+ if (lv)
+ buf_line(t, MK_DIM "Senaste ver.: %s%lld\n\n", vstr(lv, "series"),
+ (long long)vint(lv, "number"));
+ buf_line(t, MK_STICKY MK_HEAD "%-8s %-58s %14s %14s %16s\n", "Konto", "",
+ "Debet", "Kredit", "Saldo");
+ buf_rule(t, 114);
+ yyjson_val *accs = yyjson_obj_get(res, "accounts");
+ size_t n = yyjson_arr_size(accs);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *ao = yyjson_arr_get(accs, i);
+ int64_t ib = vint(ao, "ib_ore");
+ int64_t d = vint(ao, "debit_ore");
+ int64_t c = vint(ao, "credit_ore");
+ buf_line(t, "\n" MK_HEAD "%s %s\n\n", vstr(ao, "account"),
+ vstr(ao, "name"));
+ char a1[48];
+ tui_amt_col(a1, sizeof a1, 16, ib);
+ char lbl[96];
+ tui_pad_label(lbl, sizeof lbl, "Ingående balans:", 59);
+ buf_line(t, MK_DIM " %s %14s %14s %16s\n", lbl, "", "", a1);
+ tui_pad_label(lbl, sizeof lbl, "Ingående saldo:", 59);
+ buf_line(t, MK_DIM " %s %14s %14s %16s\n", lbl, "", "", a1);
+ buf_line(t, "\n");
+ yyjson_val *rows = yyjson_obj_get(ao, "rows");
+ size_t nr = yyjson_arr_size(rows);
+ for (size_t k = 0; k < nr; k++) {
+ yyjson_val *ro = yyjson_arr_get(rows, k);
+ char ver[32], nm[256], dd[48], cc[48], deb[48], kre[48], sal[48];
+ snprintf(ver, sizeof ver, "%s%lld", vstr(ro, "series"),
+ (long long)vint(ro, "number"));
+ const char *txt = vstr(ro, "row_description");
+ if (!*txt)
+ txt = vstr(ro, "description");
+ tui_pad_label(nm, sizeof nm, txt, 40);
+ tui_amt_col(dd, sizeof dd, 14, vint(ro, "debit_ore"));
+ tui_amt_col(cc, sizeof cc, 14, vint(ro, "credit_ore"));
+ snprintf(deb, sizeof deb, "%s", vint(ro, "debit_ore") ? dd : "");
+ snprintf(kre, sizeof kre, "%s", vint(ro, "credit_ore") ? cc : "");
+ tui_amt_col(sal, sizeof sal, 16, vint(ro, "saldo_ore"));
+ buf_line(t, " %-7s %-10s %s %14s %14s %16s\n", ver,
+ vstr(ro, "date"), nm, deb, kre, sal);
+ }
+ char s1[48], s2[48], s3[48];
+ tui_amt_col(s1, sizeof s1, 14, d);
+ tui_amt_col(s2, sizeof s2, 14, c);
+ tui_amt_col(s3, sizeof s3, 16, d - c);
+ tui_pad_label(lbl, sizeof lbl, "Omslutning:", 59);
+ buf_line(t, MK_DIM " %s %14s %14s %16s\n", lbl, s1, s2, s3);
+ tui_amt_col(s3, sizeof s3, 16, vint(ao, "ub_ore"));
+ tui_pad_label(lbl, sizeof lbl, "Utgående saldo:", 59);
+ buf_line(t, MK_DIM " %s %14s %14s %16s\n", lbl, s1, s2, s3);
+ }
+}
+
+/* ---------------- verifikationslista ---------------- */
+
+static void fmt_verifikationslista(struct buf *t, const struct app *a,
+ yyjson_val *res)
+{
+ fmt_head(t, a, "Verifikationslista", res);
+ yyjson_val *lv = yyjson_obj_get(res, "last_voucher");
+ if (lv)
+ buf_line(t, MK_DIM "Senaste ver.: %s%lld\n\n", vstr(lv, "series"),
+ (long long)vint(lv, "number"));
+ char nmh[64];
+ tui_pad_label(nmh, sizeof nmh, "Benämning", 48);
+ buf_line(t, MK_STICKY MK_HEAD "%-8s %-11s %s %14s %14s\n", "Ver.",
+ "Datum/Konto", nmh, "Debet", "Kredit");
+ buf_rule(t, 98);
+ yyjson_val *vs = yyjson_obj_get(res, "vouchers");
+ size_t n = yyjson_arr_size(vs);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *vo = yyjson_arr_get(vs, i);
+ char ver[32];
+ snprintf(ver, sizeof ver, "%s%lld", vstr(vo, "series"),
+ (long long)vint(vo, "number"));
+ buf_line(t, MK_HEAD "%-8s %-11s %s\n", ver, vstr(vo, "date"),
+ vstr(vo, "description"));
+ yyjson_val *rows = yyjson_obj_get(vo, "rows");
+ size_t nr = yyjson_arr_size(rows);
+ for (size_t k = 0; k < nr; k++) {
+ yyjson_val *ro = yyjson_arr_get(rows, k);
+ char nm[256], dd[48], cc[48], deb[48], kre[48];
+ tui_pad_label(nm, sizeof nm, vstr(ro, "name"), 48);
+ tui_amt_col(dd, sizeof dd, 14, vint(ro, "debit_ore"));
+ tui_amt_col(cc, sizeof cc, 14, vint(ro, "credit_ore"));
+ snprintf(deb, sizeof deb, "%s", vint(ro, "debit_ore") ? dd : "");
+ snprintf(kre, sizeof kre, "%s", vint(ro, "credit_ore") ? cc : "");
+ buf_line(t, " %-10s %s %14s %14s\n", vstr(ro, "account"),
+ nm, deb, kre);
+ }
+ }
+ yyjson_val *tot = yyjson_obj_get(res, "totals");
+ char s1[48], s2[48];
+ tui_amt_col(s1, sizeof s1, 14, vint(tot, "debit_ore"));
+ tui_amt_col(s2, sizeof s2, 14, vint(tot, "credit_ore"));
+ buf_line(t, "\n" MK_DIM "%-69s %14s %14s\n", "Omslutning:", s1, s2);
+}
+
+static void eskd_save(struct app *a, const char *from, const char *to)
+{
+ const char *home = getenv("HOME");
+ char period[8], def[512], full[512];
+ snprintf(period, sizeof period, "%.4s%.2s", to, to + 5);
+ snprintf(def, sizeof def, "%s/eskd_%s.xml", home && *home ? home : ".",
+ period);
+ char pathbuf[512]; char *path = tui_prompt_into(pathbuf, sizeof pathbuf, "Spara eSKD-fil: ", def, 0) ? pathbuf : NULL;
+ if (!path || !*path)
+ return;
+ if (path[0] == '~' && (path[1] == '/' || path[1] == '\0'))
+ snprintf(full, sizeof full, "%s%s", home ? home : "", path + 1);
+ else
+ snprintf(full, sizeof full, "%s", path);
+ char args[64];
+ snprintf(args, sizeof args, "{\"from\":\"%s\",\"to\":\"%s\"}", from, to);
+ char *resp =
+ client_rpc(&a->conn, "report.vat_eskd", a->session, a->org, args);
+ if (!resp || !client_ok(resp)) {
+ show_error("Kunde inte skapa eSKD", resp);
+ free(resp);
+ return;
+ }
+ char *b64 = jstr_dup(resp, "result.content_base64");
+ unsigned char *data = NULL;
+ size_t n = 0;
+ if (!b64 || util_b64_decode(b64, strlen(b64), &data, &n) != 0) {
+ tui_message("eSKD", "Kunde inte avkoda filen från servern.");
+ } else {
+ FILE *f = fopen(full, "wb");
+ if (!f || fwrite(data, 1, n, f) != n) {
+ tui_message("eSKD", "Kunde inte skriva %s: %s", full, strerror(errno));
+ if (f)
+ fclose(f);
+ } else {
+ fclose(f);
+ tui_message("eSKD", "Sparat: %s\n(%zu byte, ISO-8859-1)", full, n);
+ }
+ }
+ free(data);
+ free(b64);
+ free(resp);
+}
+
+/* Official fältnamn (short) for the INK2/INK2R/INK2S codes; 2025P4. */
+static const struct {
+ const char *code;
+ const char *label;
+} SRU_LABELS[] = {
+ { "7011", "Räkenskapsårets början" },
+ { "7012", "Räkenskapsårets slut" },
+ { "7104", "1.1 Överskott av näringsverksamhet" },
+ { "7114", "1.2 Underskott av näringsverksamhet" },
+ { "7131", "1.3 Kreditinstituts underlag för riskskatt" },
+ { "7132", "1.4 Underlag för särskild löneskatt på pensionskostnader" },
+ { "7133", "1.5 Negativt underlag för särskild löneskatt på pensionskostnader" },
+ { "7153", "1.6 a Försäkringsföretag m.fl. samt avsatt till pensioner 15 %" },
+ { "7154", "1.6 b Utländska pensions- försäkringar 15 %" },
+ { "7155", "1.7 a Försäkringsföretag m.fl 30 %" },
+ { "7156", "1.7 b Utländska kapitalförsäkringar 30 %" },
+ { "80", "1.8 Småhus/ ägarlägenhet" },
+ { "93", "1.9 Hyreshus: bostäder" },
+ { "84", "1.10 Småhus/ägarlägenhet: tomtmark, byggnad under uppförande" },
+ { "86", "1.11 Hyreshus: tomtmark, bostäder under uppförande" },
+ { "95", "1.12 Hyreshus: lokaler" },
+ { "96", "1.13 Industrienhet och elproduktionsenhet: värme- kraftverk" },
+ { "97", "1.14 Elproduktionsenhet, vattenkraftverk" },
+ { "98", "1.15 Elproduktionsenhet, vindkraftverk" },
+ { "90", "Övriga upplysningar på bilaga" },
+ { "1582", "1.16 Förnybar el (kilowattimmar)" },
+ { "7011", "Räkenskapsårets början" },
+ { "7012", "Räkenskapsårets slut" },
+ { "7201", "2.1 Immateriella anläggningstillgångar Koncessioner, patent, licenser, varumärken, hyresrätter, goodwill och liknande rättigheter" },
+ { "7202", "2.2 Immateriella anläggningstillgångar Förskott avseende immateriella anläggningstillgångar" },
+ { "7214", "2.3 Materiella anläggningstillgångar Byggnader och mark" },
+ { "7215", "2.4 Materiella anläggningstillgångar Maskiner, inventarier och övriga materiella anläggningstillgångar" },
+ { "7216", "2.5 Materiella anläggningstillgångar Förbättringsutgifter på annans fastighet" },
+ { "7217", "2.6 Materiella anläggningstillgångar Pågående nyanläggningar och förskott avseende materiella anläggningstillgångar" },
+ { "7230", "2.7 Finansiella anläggningstillgångar Andelar i koncernföretag" },
+ { "7231", "2.8 Finansiella anläggningstillgångar Andelar i intresseföretag och gemensamt styrda företag" },
+ { "7233", "2.9 Ägarintressen i övriga företag och andra långfristiga värdepappersinnehav" },
+ { "7232", "2.10 Finansiella anläggningstillgångar Fordringar hos koncern-, intresse- och gemensamt styrda företag" },
+ { "7234", "2.11 Finansiella anläggningstillgångar Lån till delägare eller närstående" },
+ { "7235", "2.12 Fordringar hos övriga företag som det finns ett ägarintresse i och andra långfristiga fordringar" },
+ { "7241", "2.13 Varulager m.m. Råvaror och förnödenheter" },
+ { "7242", "2.14 Varulager Varor under tillverkning" },
+ { "7243", "2.15 Varulager Färdiga varor och handelsvaror" },
+ { "7244", "2.16 Varulager Övriga lagertillgångar" },
+ { "7245", "2.17 Varulager Pågående arbeten för annans räkning" },
+ { "7246", "2.18 Varulager Förskott till leverantörer" },
+ { "7251", "2.19 Kortfristiga fordringar Kundfordringar" },
+ { "7252", "2.20 Kortfristiga fordringar Fordringar hos koncern-, intresse- och gemensamt styrda företag" },
+ { "7261", "2.21 Fordringar hos övriga företag som det finns ett ägarintresse i och övriga fordringar" },
+ { "7262", "2.22 Kortfristiga fordringar Upparbetad men ej fakturerad intäkt" },
+ { "7263", "2.23 Kortfristiga fordringar Förutbetalda kostnader och upplupna intäkter" },
+ { "7270", "2.24 Kortfristiga placeringar Andelar i koncernföretag" },
+ { "7271", "2.25 Kortfristiga placeringar Övriga kortfristiga placeringar" },
+ { "7281", "2.26 Kassa och bank Kassa, bank och redovisningsmedel" },
+ { "7301", "2.27 Eget kapital Bundet eget kapital" },
+ { "7302", "2.28 Eget kapital Fritt eget kapital" },
+ { "7321", "2.29 Obeskattade reserver Periodiseringsfonder" },
+ { "7322", "2.30 Obeskattade reserver Ackumulerade överavskrivningar" },
+ { "7323", "2.31 Obeskattade reserver Övriga obeskattade reserver" },
+ { "7331", "2.32 Avsättningar Avsättningar för pensioner och liknande förpliktelser enligt lag (1967:531)..." },
+ { "7332", "2.33 Avsättningar Övriga avsättningar för pensioner och liknande förpliktelser" },
+ { "7333", "2.34 Avsättningar Övriga avsättningar" },
+ { "7350", "2.35 Långfristiga skulder Obligationslån" },
+ { "7351", "2.36 Långfristiga skulder Checkräkningskredit" },
+ { "7352", "2.37 Långfristiga skulder Övriga skulder till kreditinstitut" },
+ { "7353", "2.38 Långfristiga skulder Skulder till koncern-, intresse- och gemensamt styrda företag" },
+ { "7354", "2.39 Skulder till övriga företag som det finns ett ägarintresse i och övriga skulder" },
+ { "7360", "2.40 Kortfristiga skulder Checkräkningskredit" },
+ { "7361", "2.41 Kortfristiga skulder Övriga skulder till kreditinstitut" },
+ { "7362", "2.42 Kortfristiga skulder Förskott från kunder" },
+ { "7363", "2.43 Kortfristiga skulder Pågående arbeten för annans räkning" },
+ { "7364", "2.44 Kortfristiga skulder Fakturerad men ej upparbetad intäkt" },
+ { "7365", "2.45 Kortfristiga skulder Leverantörsskulder" },
+ { "7366", "2.46 Kortfristiga skulder Växelskulder" },
+ { "7367", "2.47 Kortfristiga skulder Skulder till koncern-, intresse- och gemensamt styrda företag" },
+ { "7369", "2.48 Skulder till övriga företag som det finns ett ägarintresse i och övriga skulder" },
+ { "7368", "2.49 Kortfristiga skulder Skattesskulder" },
+ { "7370", "2.50 Kortfristiga skulder Upplupna kostnader och förutbetalda intäkter" },
+ { "7410", "3.1 Nettoomsättning" },
+ { "7411", "3.2 Förändring av lager av produkter i arbete, färdiga varor och pågående arbete för annans räkning. +" },
+ { "7510", "3.2 Förändring av lager av produkter i arbete, färdiga varor och pågående arbete för annans räkning. -" },
+ { "7412", "3.3 Aktiverat arbete för egen räkning" },
+ { "7413", "3.4 Övriga rörelseintäkter" },
+ { "7511", "3.5 Råvaror och förnödenheter" },
+ { "7512", "3.6 Handelsvaror" },
+ { "7513", "3.7 Övriga externa kostnader" },
+ { "7514", "3.8 Personalkostnader" },
+ { "7515", "3.9 Av- och nedskrivningar av materiella och immateriella anläggningstillgångar" },
+ { "7516", "3.10 Nedskrivningar av omsättningstillgångar utöver normala nedskrivningar" },
+ { "7517", "3.11 Övriga rörelsekostnader" },
+ { "7414", "3.12 Resultat från andelar i koncernföretag +" },
+ { "7518", "3.12 Resultat från andelar i koncernföretag -" },
+ { "7415", "3.13 Resultat från andelar i intresseföretag och gemensamt styrda företag +" },
+ { "7519", "3.13 Resultat från andelar i intresseföretag och gemensamt styrda företag -" },
+ { "7423", "3.14 Resultat från övriga företag som det finns ett ägarintresse i +" },
+ { "7530", "3.14 Resultat från övriga företag som det finns ett ägarintresse i -" },
+ { "7416", "3.15 Resultat från övriga finansiella anläggningstillgångar +" },
+ { "7520", "3.15 Resultat från övriga finansiella anläggningstillgångar -" },
+ { "7417", "3.16 Övriga ränteintäkter och liknande resultatposter" },
+ { "7521", "3.17 Nedskrivning av finansiella anläggningstillgångar och kortfristiga placeringar" },
+ { "7522", "3.18 Räntekostnader och liknande resultatposter" },
+ { "7524", "3.19 Lämnade koncernbidrag" },
+ { "7419", "3.20 Mottagna koncernbidrag" },
+ { "7420", "3.21 Återföring av periodiseringsfond" },
+ { "7525", "3.22 Avsättning till periodiseringsfond" },
+ { "7421", "3.23 Förändring av överavskrivningar +" },
+ { "7526", "3.23 Förändring av överavskrivningar -" },
+ { "7422", "3.24 Övriga bokslutsdispositioner +" },
+ { "7527", "3.24 Övriga bokslutsdispositioner -" },
+ { "7528", "3.25 Skatt på årets resultat" },
+ { "7450", "3.26 Årets resultat, vinst (flyttas till p. 4.1)" },
+ { "7550", "3.27 Årets resultat, förlust (flyttas till p. 4.2)" },
+ { "7011", "Räkenskapsårets början" },
+ { "7012", "Räkenskapsårets slut" },
+ { "7650", "4.1 Årets resultat, vinst" },
+ { "7750", "4.2 Årets resultat, förlust" },
+ { "7651", "4.3a. Bokförda kostnader som inte ska dras av: a. Skatt på årets resultat" },
+ { "7652", "b. Bokförda kostnader som inte ska dras av: b. Nedskrivning av finansiella tillgångar" },
+ { "7653", "c. Bokförda kostnader som inte ska dras av: c. Andra bokförda kostnader" },
+ { "7751", "4.4a. Kostnader som ska dras av men som inte ingår i det redovisade resultatet: a. Lämnade koncernbidrag" },
+ { "7764", "b. Kostnader som ska dras av men som inte ingår i det redovisade resultatet: b. Andra ej bokförda kostnader" },
+ { "7752", "4.5a. Bokförda intäkter som inte ska tas upp: a. Ackordsvinster" },
+ { "7753", "b. Bokförda intäkter som inte ska tas upp: b. Utdelning" },
+ { "7754", "c. Bokförda intäkter som inte ska tas upp: c. Andra bokförda intäkter" },
+ { "7654", "4.6a. Intäkter som ska tas upp men som inte ingår i det redovisade resultatet a. Beräknad schablonintäkt på periodiseringsfonder vid beskattningsårets ingång" },
+ { "7668", "b. Beräknad schablonintäkt på fondandelar ägda vid kalenderårets ingång" },
+ { "7655", "c. Intäkter som ska tas upp men som inte ingår i det redovisade resultatet: c. Mottagna koncernbidrag" },
+ { "7673", "d. Intäkter som ska tas upp men som inte ingår i det redovisade resultatet: d. Uppräknat belopp vid återföring av periodiseringsfond" },
+ { "7665", "e. Intäkter som ska tas upp men som inte ingår i det redovisade resultatet: e. Andra ej bokförda intäkter" },
+ { "7755", "4.7a. Avyttring av delägarrätter: a. Bokförd vinst" },
+ { "7656", "b. Avyttring av delägarrätter: b. Bokförd förlust" },
+ { "7756", "c. Avyttring av delägarrätter: c. Uppskov med kapitalvinst enligt blankett N4" },
+ { "7657", "d. Avyttring av delägarrätter: d. Återfört uppskov med kapitalvinst enligt blankett N4" },
+ { "7658", "e. Avyttring av delägarrätter: e. Kapitalvinst för beskattningsåret" },
+ { "7757", "f. Avyttring av delägarrätter: f. Kapitalförlust som ska dras av" },
+ { "7758", "4.8a. Andel i handelsbolag (inkl. avyttring): a. Bokförd intäkt/vinst" },
+ { "7659", "b. Andel i handelsbolag (inkl. avyttring): b. Skattemässigt överskott enligt N3B" },
+ { "7660", "c. Andel i handelsbolag (inkl. avyttring): c. Bokförd kostnad/förlust" },
+ { "7759", "d. Andel i handelsbolag (inkl. avyttring): d. Skattemässigt underskott enligt N3B" },
+ { "7666", "4.9 Skattemässig justering av bokfört resultat för avskrivningar på byggnader och annan fast egendom samt restvärdesavskrivning på maskiner och inventarier (+)" },
+ { "7765", "4.9 Skattemässig justering av bokfört resultat för avskrivningar på byggnader och annan fast egendom samt restvärdesavskrivning på maskiner och inventarier (-)" },
+ { "7661", "4.10 Skattemässig justering av bokfört resultat vid avyttring av näringsfastighet och näringsbostadsrätt" },
+ { "7760", "4.10 Skattemässig korrigering av bokfört resultat vid avyttring av näringsfastighet och näringsbostadsrätt: -" },
+ { "7761", "4.11 Skogs-/substansminskningsavdrag (specificeras på blankett N8)" },
+ { "7662", "4.12 Återföringar vid avyttring av fastighet t.ex. värdeminskningsavdrag, skogsavdrag och substansminskningsavdrag..." },
+ { "7663", "4.13 Andra skattemässiga justeringar av resultatet: +" },
+ { "7762", "4.13 Andra skattemässiga justeringar av resultatet: -" },
+ { "7763", "4.14a. Underskott: a. Outnyttjat underskott från föregående år" },
+ { "7671", "4.14b. Reduktion av outnyttjat underskott med hänsyn till beloppsspärr, ackord eller konkurs" },
+ { "7672", "4.14c. Reduktion av outnyttjat underskott med hänsyn till koncernbidragsspärr, fusionsspärr m.m. (beloppet ska också tas upp vid p. 1.2. på sid. 1)" },
+ { "7670", "4.15 Överskott (flyttas till p. 1.1 på sid. 1)" },
+ { "7770", "4.16 Underskott (flyttas till p. 1.2 på sid. 1)" },
+ { "8020", "4.17 Årets begärda och tidigare års medgivna värdeminskningsavdrag som finns vid beskattningsårets utgång avseende byggnader." },
+ { "8021", "4.18 Årets begärda och tidigare års medgivna värdeminskningsavdrag som finns vid beskattningsårets utgång avseende markanläggningar." },
+ { "8023", "4.19 Vid restvärdesavskrivning: återförda belopp för av- och nedskrivning, försäljning, utrangering" },
+ { "8026", "4.20 Lån från aktieägare (fysisk person) vid räkenskapsårets utgång" },
+ { "8022", "4.21 Pensionskostnader (som ingår i p. 3.8)" },
+ { "8028", "4.22 Koncernbidragsspärrat och fusionsspärrat underskott m.m. (frivillig uppgift)" },
+ { "8040", "Uppdragstagare (t.ex. redovisningskonsult) har biträtt vid upprättandet av årsredovisningen: Ja" },
+ { "8041", "Uppdragstagare (t.ex. redovisningskonsult) har biträtt vid upprättandet av årsredovisningen: Nej" },
+ { "8044", "Årsredovisningen har varit föremål för revision: Ja" },
+ { "8045", "Årsredovisningen har varit föremål för revision: Nej" },
+};
+
+static const char *sru_label(const char *code)
+{
+ for (size_t i = 0; i < sizeof SRU_LABELS / sizeof SRU_LABELS[0]; i++)
+ if (strcmp(SRU_LABELS[i].code, code) == 0)
+ return SRU_LABELS[i].label;
+ return code;
+}
+
+static void sru_rows(struct buf *t, yyjson_val *arr)
+{
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *row = yyjson_arr_get(arr, i);
+ char lbl[256], amt[48];
+ tui_pad_label(lbl, sizeof lbl, sru_label(vstr(row, "code")), 66);
+ tui_kr_format(vint(row, "amount") * 100, amt, sizeof amt);
+ buf_line(t, "%s %14s\n", lbl, amt);
+ }
+}
+
+static void fmt_ink2(struct buf *t, const struct app *a, yyjson_val *res)
+{
+ buf_line(t, MK_HEAD "Inkomstdeklaration 2 (INK2)\n%s\n\n", a->org_name);
+ buf_line(t, MK_DIM "Räkenskapsår: %s - %s\n", vstr(res, "from"),
+ vstr(res, "to"));
+ buf_line(t, MK_DIM "Blankett: %s\n", vstr(res, "period"));
+ buf_line(t, "\n" MK_HEAD "INK2 - huvudblankett\n");
+ buf_rule(t, 81);
+ sru_rows(t, yyjson_obj_get(res, "ink2"));
+ buf_line(t, "\n" MK_HEAD "INK2R - räkenskapsschema\n");
+ buf_rule(t, 81);
+ sru_rows(t, yyjson_obj_get(res, "ink2r"));
+ buf_line(t, "\n" MK_HEAD "INK2S - skattemässiga justeringar\n");
+ buf_rule(t, 81);
+ sru_rows(t, yyjson_obj_get(res, "ink2s"));
+ yyjson_val *un = yyjson_obj_get(res, "unmapped");
+ size_t nu = yyjson_arr_size(un);
+ if (nu) {
+ buf_line(t, "\n" MK_HEAD "Konton utan SRU-mappning (måste åtgärdas):\n");
+ for (size_t i = 0; i < nu; i++) {
+ yyjson_val *row = yyjson_arr_get(un, i);
+ char amt[48];
+ tui_kr_format(vint(row, "amount_ore"), amt, sizeof amt);
+ buf_line(t, " %s %s\n", vstr(row, "account"), amt);
+ }
+ }
+}
+
+static void sru_save(struct app *a)
+{
+ const char *home = getenv("HOME");
+ char def[512];
+ snprintf(def, sizeof def, "%s/Downloads", home && *home ? home : ".");
+ struct stat sb;
+ if (stat(def, &sb) != 0 || !S_ISDIR(sb.st_mode))
+ snprintf(def, sizeof def, "%s", home && *home ? home : ".");
+ char dirbuf[512]; char *dir = tui_prompt_into(dirbuf, sizeof dirbuf, "Spara SRU-filer i: ", def, 0) ? dirbuf : NULL;
+ if (!dir || !*dir)
+ return;
+ char full[512];
+ if (dir[0] == '~' && (dir[1] == '/' || dir[1] == '\0'))
+ snprintf(full, sizeof full, "%s%s", home ? home : "", dir + 1);
+ else
+ snprintf(full, sizeof full, "%s", dir);
+ char args[64];
+ snprintf(args, sizeof args, "{\"fiscal_year\":%lld}", (long long)a->fy);
+ char *resp = client_rpc(&a->conn, "sru.export", a->session, a->org, args);
+ if (!resp || !client_ok(resp)) {
+ show_error("Kunde inte skapa SRU-filerna", resp);
+ free(resp);
+ return;
+ }
+ const char *keys[2] = { "result.info.content_base64",
+ "result.blanketter.content_base64" };
+ const char *names[2] = { "INFO.SRU", "BLANKETTER.SRU" };
+ char saved[2][600] = { "", "" };
+ int ok = 0;
+ for (int i = 0; i < 2; i++) {
+ char *b64 = jstr_dup(resp, keys[i]);
+ unsigned char *data = NULL;
+ size_t n = 0;
+ if (!b64 || util_b64_decode(b64, strlen(b64), &data, &n) != 0) {
+ free(b64);
+ continue;
+ }
+ snprintf(saved[i], sizeof saved[i], "%s/%s", full, names[i]);
+ FILE *f = fopen(saved[i], "wb");
+ if (f && fwrite(data, 1, n, f) == n) {
+ fclose(f);
+ ok++;
+ } else {
+ if (f)
+ fclose(f);
+ tui_message("SRU", "Kunde inte skriva %s: %s", saved[i],
+ strerror(errno));
+ }
+ free(data);
+ free(b64);
+ }
+ free(resp);
+ if (ok == 2)
+ tui_message("SRU", "Sparat:\n%s\n%s\n\nLadda upp båda i Skatteverkets"
+ " filöverföring.",
+ saved[0], saved[1]);
+}
+
+void ink2_screen(struct app *a)
+{
+ char sargs[64];
+ snprintf(sargs, sizeof sargs, "{\"fiscal_year\":%lld}", (long long)a->fy);
+ for (;;) {
+ char *resp = client_rpc(&a->conn, "sru.export", a->session, a->org,
+ sargs);
+ if (!resp || !client_ok(resp)) {
+ show_error("Inkomstdeklaration", resp);
+ free(resp);
+ break;
+ }
+ yyjson_doc *sd = parse(resp);
+ yyjson_val *sres = sd ? jget(yyjson_doc_get_root(sd), "result") : NULL;
+ struct buf out;
+ buf_init(&out);
+ if (sres)
+ fmt_ink2(&out, a, sres);
+ else
+ buf_line(&out, "%s", resp);
+ buf_append(&out, "", 1);
+ int sagain = tui_pager("Rapport", (const char *)out.p, "s = spara SRU");
+ buf_free(&out);
+ yyjson_doc_free(sd);
+ free(resp);
+ if (sagain == 2)
+ sru_save(a);
+ if (!sagain)
+ break;
+ }
+}
+
+void reports_screen(struct app *a)
+{
+ static const char *const report_items[] = {
+ "Saldobalans (trial balance)",
+ "Resultaträkning",
+ "Balansräkning",
+ "Huvudbok",
+ "Verifikationslista",
+ "Momsdeklaration",
+ "Kontolista (alla konton)",
+ };
+ static int last_sel = 0;
+ for (;;) {
+ if (g_quit)
+ return;
+ int sel = tui_menu("Rapporter", report_items, 7, 0, &last_sel);
+ if (sel < 0)
+ return;
+ if (sel == 6) {
+ accounts_report(a);
+ continue;
+ }
+ char args[256];
+ char vat_from[16] = "", vat_to[16] = "";
+ const char *cmd = NULL;
+ switch (sel) {
+ case 0:
+ cmd = "report.trial_balance";
+ snprintf(args, sizeof args,
+ "{\"include_zero\":false,\"fiscal_year\":%lld}",
+ (long long)a->fy);
+ break;
+ case 1:
+ cmd = "report.income_statement";
+ snprintf(args, sizeof args, "{\"fiscal_year\":%lld}",
+ (long long)a->fy);
+ break;
+ case 2:
+ cmd = "report.balance_sheet";
+ snprintf(args, sizeof args, "{\"fiscal_year\":%lld}",
+ (long long)a->fy);
+ break;
+ case 3:
+ cmd = "report.general_ledger";
+ snprintf(args, sizeof args, "{\"fiscal_year\":%lld}",
+ (long long)a->fy);
+ break;
+ case 4:
+ cmd = "report.voucher_list";
+ snprintf(args, sizeof args, "{\"fiscal_year\":%lld}",
+ (long long)a->fy);
+ break;
+ case 5: {
+ cmd = "report.vat";
+ char frombuf[16]; char *from = tui_date_prompt_into(frombuf, sizeof frombuf, "Från (YYYY-MM-DD): ", a->fy_start) ? frombuf : NULL;
+ if (!from)
+ continue;
+ char f[16];
+ snprintf(f, sizeof f, "%s", from);
+ char tobuf[16]; char *to = tui_date_prompt_into(tobuf, sizeof tobuf, "Till (YYYY-MM-DD): ", a->fy_end) ? tobuf : NULL;
+ if (!to)
+ continue;
+ snprintf(vat_from, sizeof vat_from, "%s", f);
+ snprintf(vat_to, sizeof vat_to, "%s", to);
+ snprintf(args, sizeof args,
+ "{\"from\":\"%s\",\"to\":\"%s\"}", f, to);
+ break;
+ }
+ }
+ for (;;) {
+ char *resp = client_rpc(&a->conn, cmd, a->session, a->org, args);
+ if (!resp || !client_ok(resp)) {
+ show_error("Rapport", resp);
+ free(resp);
+ break;
+ }
+ yyjson_doc *d = parse(resp);
+ yyjson_val *res =
+ d ? jget(yyjson_doc_get_root(d), "result") : NULL;
+ struct buf out;
+ buf_init(&out);
+ if (!res) {
+ buf_line(&out, "%s", resp);
+ } else if (sel == 0) {
+ fmt_saldobalans(&out, a, res);
+ } else if (sel == 1) {
+ yyjson_doc *pd = NULL;
+ yyjson_val *prev = NULL;
+ int64_t pf = prev_fy_id(a);
+ if (pf) {
+ char pargs[64];
+ snprintf(pargs, sizeof pargs, "{\"fiscal_year\":%lld}",
+ (long long)pf);
+ char *presp = client_rpc(&a->conn,
+ "report.income_statement",
+ a->session, a->org, pargs);
+ if (presp && client_ok(presp)) {
+ pd = parse(presp);
+ prev = pd ? jget(yyjson_doc_get_root(pd), "result")
+ : NULL;
+ }
+ free(presp);
+ }
+ fmt_resultat(&out, a, res, prev);
+ yyjson_doc_free(pd);
+ } else if (sel == 2) {
+ fmt_balans(&out, a, res);
+ } else if (sel == 3) {
+ fmt_huvudbok(&out, a, res);
+ } else if (sel == 4) {
+ fmt_verifikationslista(&out, a, res);
+ } else {
+ fmt_moms(&out, a, res);
+ }
+ buf_append(&out, "", 1);
+ int again = tui_pager("Rapport", (const char *)out.p,
+ sel == 5 ? "s = spara eSKD" : NULL);
+ buf_free(&out);
+ yyjson_doc_free(d);
+ free(resp);
+ if (again == 2 && sel == 5) {
+ eskd_save(a, vat_from, vat_to);
+ break;
+ }
+ if (!again)
+ break;
+ }
+ }
+}