diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-18 14:32:23 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-18 14:32:23 +0200 |
| commit | a5d3ddf6d512bc459cfa450c6b449c2e7a245373 (patch) | |
| tree | e14c2b28e3cd6d7b744f301b5bf498f5feb8f03e /clients | |
| parent | 07b5e5890fedbf71a1503f984dfbc2baa872223c (diff) | |
| download | bokf-a5d3ddf6d512bc459cfa450c6b449c2e7a245373.tar.gz bokf-a5d3ddf6d512bc459cfa450c6b449c2e7a245373.zip | |
reports: Kapitas-style TUI tables, corrected moms rules (schema v3)v0.1.26
The report views dumped JSON; they now render Saldobalans,
Resultatrapport (previous-year column, 89xx bokfört/ej bokfört),
Balansrapport (Ing balans/Ing saldo/Period/Utg balans, Beräknat
resultat) and Momsrapport ruta för ruta, with Swedish amount
formatting (1 234,56).
The moms starter rules missed 33xx sales, sent reverse-charge VAT
2614 to box 10 instead of 30 and had box 48 positive. Rules may now
share a box and report.vat sums them; box 49 is the sum of the moms
boxes only. Schema v3 replaces the rules for existing orgs. Verified
on a copy of the live DB: 05=703 200, 10=175 800, 20=1 453, 30=851,
48=-1 030, 49=175 621, matching the Kapitas 2027 export.
Ctrl+R reload passes --socket and auto-login no longer rewrites
tui.conf; pty tests now run through scripts/tui-sandbox.sh so they
cannot touch the real config, cache or bw session.
Diffstat (limited to 'clients')
| -rw-r--r-- | clients/bokftui.c | 643 |
1 files changed, 633 insertions, 10 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c index 1662c87..9ea6eb6 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -832,11 +832,21 @@ static int parse_kr(const char *s, int64_t *out) static void kr_format(int64_t ore, char *buf, size_t n) { long long v = (long long)ore; - if (v < 0) { - snprintf(buf, n, "-%lld.%02lld", -v / 100, -v % 100); - } else { - snprintf(buf, n, "%lld.%02lld", v / 100, v % 100); + int neg = v < 0; + if (neg) + v = -v; + char digits[32]; + snprintf(digits, sizeof digits, "%lld", v / 100); + char out[28]; + size_t o = 0; + size_t len = strlen(digits); + for (size_t i = 0; i < len && o + 2 < sizeof out; i++) { + if (i > 0 && (len - i) % 3 == 0) + out[o++] = ' '; + out[o++] = digits[i]; } + out[o] = '\0'; + snprintf(buf, n, "%s%s,%02lld", neg ? "-" : "", out, v % 100); } static int parse_x_double(const char *s, double *out) @@ -2512,6 +2522,589 @@ static void accounts_report(struct app *a) } } +/* ------------------------------------------------------------------ */ +/* report formatting (Kapitas-like text tables) */ +/* ------------------------------------------------------------------ */ + +static const char *vstr(yyjson_val *o, const char *k) +{ + yyjson_val *v = o ? yyjson_obj_get(o, k) : NULL; + return v && yyjson_is_str(v) ? yyjson_get_str(v) : ""; +} + +static int64_t vint(yyjson_val *o, const char *k) +{ + yyjson_val *v = o ? yyjson_obj_get(o, k) : NULL; + return v && yyjson_is_int(v) ? (int64_t)yyjson_get_int(v) : 0; +} + +static void buf_line(struct buf *b, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +static void buf_line(struct buf *b, const char *fmt, ...) +{ + char line[1024]; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(line, sizeof line, fmt, ap); + va_end(ap); + if (n < 0) + return; + buf_append(b, line, (size_t)n < sizeof line ? (size_t)n : sizeof line - 1); +} + +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, "%s\n%s\n\n", title, a->org_name); + buf_line(t, "Räkenskapsår: %s - %s\n", start, end); + buf_line(t, "Period: %s - %s\n\n", start, end); +} + +static int acct_in(const char *number, int lo, int hi) +{ + int n = atoi(number); + return n >= lo && n <= hi; +} + +static void pad_label(char *buf, size_t n, const char *text, int width) +{ + snprintf(buf, n, "%s", text); + pad_field(buf, n, width); +} + +static void amt_col(char *buf, size_t n, int width, int64_t ore) +{ + char a[48]; + kr_format(ore, a, sizeof a); + snprintf(buf, n, "%*s", width, a); +} + +/* ---------------- balansräkning ---------------- */ + +static const char *const BS_KEYS[3] = { "assets", "equity", "liabilities" }; + +struct bs_sum { + int64_t ib, per, ub; +}; + +static void bs_col_head(struct buf *t) +{ + buf_line(t, " %5s %-48s %14s %14s %14s %14s\n", "", "", "Ing balans", + "Ing saldo", "Period", "Utg balans"); +} + +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]; + pad_label(nm, sizeof nm, vstr(row, "name"), 48); + amt_col(a1, sizeof a1, 14, ib); + amt_col(a2, sizeof a2, 14, ib); + amt_col(a3, sizeof a3, 14, per); + amt_col(a4, sizeof a4, 14, ub); + buf_line(t, "%-6s %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]; + pad_label(lbl, sizeof lbl, label, 48); + amt_col(a1, sizeof a1, 14, s->ib); + amt_col(a2, sizeof a2, 14, s->ib); + amt_col(a3, sizeof a3, 14, s->per); + amt_col(a4, sizeof a4, 14, s->ub); + buf_line(t, " %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, "%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, "TILLGÅNGAR\n"); + buf_line(t, "Anläggningstillgångar\n"); + bs_rows(t, res, 1000, 1399, &anl); + bs_sum_line(t, "Summa anläggningstillgångar", &anl); + buf_line(t, "\nOmsättningstillgångar\n"); + for (size_t i = 0; i < sizeof OMS / sizeof OMS[0]; i++) { + if (i) + buf_line(t, "\n"); + bs_group(t, res, OMS[i].title, OMS[i].sum, OMS[i].lo, OMS[i].hi, + &oms); + } + 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 }; + bs_sum_line(t, "Summa tillgångar", &as); + + buf_line(t, "\nEGET KAPITAL OCH SKULDER\n"); + bs_col_head(t); + buf_line(t, "\nEget 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 }; + 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]; + pad_label(lbl, sizeof lbl, "Beräknat resultat", 36); + amt_col(zero, sizeof zero, 14, 0); + amt_col(a1, sizeof a1, 14, result); + buf_line(t, "\n\n%-6s %s %s %s %s %s\n", "", lbl, zero, zero, a1, a1); +} + +/* ---------------- resultaträkning ---------------- */ + +struct is_group { + const char *section; + const char *title; + int lo, hi; + int rev; +}; + +static const struct is_group IS_GROUPS[] = { + { "Rörelseintäkter, lagerförändringar m.m.", "Nettoomsättning", 3000, 3799, + 1 }, + { "Rörelseintäkter, lagerförändringar m.m.", "Övriga rörelseintäkter", + 3800, 3999, 1 }, + { "Rörelsekostnader", "Råvaror och förnödenheter", 4000, 4999, 0 }, + { "Rörelsekostnader", "Övriga externa kostnader", 5000, 6999, 0 }, + { "Rörelsekostnader", "Personalkostnader", 7000, 7699, 0 }, + { "Rörelsekostnader", "Avskrivningar och nedskrivningar", 7700, 7899, 0 }, + { "Rörelsekostnader", "Övriga rörelsekostnader", 7900, 7999, 0 }, +}; + +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"); + pad_label(lbl, sizeof lbl, label, 48); + amt_col(a1, sizeof a1, 15, cur); + amt_col(a2, sizeof a2, 15, prev); + buf_line(t, " %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]; + pad_label(nm, sizeof nm, vstr(row, "name"), 48); + amt_col(a1, sizeof a1, 15, c); + 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 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, "%s\n", gr->section); + } else { + buf_line(t, " %5s %-48s %15s %15s %15s\n", "", + gr->section, "Period", "Ackumulerat", + "Föreg. per."); + buf_line(t, "\n"); + buf_line(t, "%s\n", gr->title); + } + sect = gr->section; + } else { + buf_line(t, "%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); + + /* finansiella poster */ + int64_t fin = 0, fin_pre = 0, booked = 0, booked_pre = 0; + int has_fin = 0, has_booked = 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, 8000, 8899)) + has_fin = 1; + else if (acct_in(acc, 8900, 8999)) + has_booked = 1; + } + } + if (has_fin) { + buf_line(t, "\nFinansiella poster\n"); + 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, 8000, 8899)) + 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]; + pad_label(nm, sizeof nm, vstr(row, "name"), 48); + amt_col(a1, sizeof a1, 15, c); + amt_col(a2, sizeof a2, 15, p); + buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2); + fin += c; + fin_pre += p; + } + } + 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; + is_summary(t, "Rörelseresultat", ror, ror_pre, 1); + is_summary(t, "Resultat efter finansiella poster", after, after_pre, 1); + is_summary(t, "Resultat före skatt", after, after_pre, 1); + is_summary(t, "Årets resultat", after, after_pre, 1); + if (has_booked) { + buf_line(t, "Bokfört resultat\n"); + 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, 8900, 8999)) + continue; + int64_t c = -vint(row, "amount_ore"); + int64_t p = is_prev_lookup(prev, acc, 1); + char nm[128], a1[48], a2[48]; + pad_label(nm, sizeof nm, vstr(row, "name"), 48); + amt_col(a1, sizeof a1, 15, c); + amt_col(a2, sizeof a2, 15, p); + buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2); + booked += c; + booked_pre += p; + } + } + is_summary(t, "Summa", booked, booked_pre, 0); + is_summary(t, "Ej bokfört resultat", after + booked, + after_pre + booked_pre, 2); + } else { + is_summary(t, "Ej bokfört resultat", after, after_pre, 2); + } +} + +/* ---------------- saldobalans ---------------- */ + +static void fmt_saldobalans(struct buf *t, const struct app *a, + yyjson_val *res) +{ + fmt_head(t, a, "Saldobalans", res); + buf_line(t, " %5s %-48s %14s %14s %14s %14s\n", "Konto", "Benämning", + "Ingående", "Debet", "Kredit", "Utgående"); + 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]; + pad_label(nm, sizeof nm, vstr(row, "name"), 48); + amt_col(a1, sizeof a1, 14, vint(row, "ib_ore")); + amt_col(a2, sizeof a2, 14, vint(row, "debit_ore")); + amt_col(a3, sizeof a3, 14, vint(row, "credit_ore")); + 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]; + pad_label(lbl, sizeof lbl, "Summa", 48); + amt_col(a1, sizeof a1, 14, vint(tot, "ib_ore")); + amt_col(a2, sizeof a2, 14, vint(tot, "debit_ore")); + amt_col(a3, sizeof a3, 14, vint(tot, "credit_ore")); + amt_col(a4, sizeof a4, 14, vint(tot, "ub_ore")); + buf_line(t, "\n %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, "Momsrapport\n\n%s\n\n", a->org_name); + buf_line(t, "Räkenskapsår: %s - %s\n", a->fy_start, a->fy_end); + buf_line(t, "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%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[32] = ""; + if (ore) + snprintf(amt, sizeof amt, "%lld", + (long long)(ore / 100)); /* Kapitas truncates öre */ + buf_line(t, "%s. %-66s %14s\n", vr->box, vr->label, 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, "\nG. %s\n", vat_section_title("G.")); + buf_line(t, "49. %-66s %14lld\n", + "Moms att betala (+) eller att få tillbaka (-)", + (long long)(ore49 / 100)); +} + +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; +} + static void reports_screen(struct app *a) { static const char *const report_items[] = { @@ -2575,10 +3168,40 @@ static void reports_screen(struct app *a) yyjson_doc *d = parse(resp); yyjson_val *res = d ? jget(yyjson_doc_get_root(d), "result") : NULL; - char *pretty = - res ? yyjson_val_write(res, YYJSON_WRITE_PRETTY, NULL) : NULL; - int again = text_view("Rapport", pretty ? pretty : resp); - free(pretty); + 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 { + fmt_moms(&out, a, res); + } + buf_append(&out, "", 1); + int again = text_view("Rapport", (const char *)out.p); + buf_free(&out); yyjson_doc_free(d); free(resp); if (!again) @@ -3998,6 +4621,8 @@ static void do_reload(struct app *a, const char *self) snprintf(orgbuf, sizeof orgbuf, "%lld", (long long)a->org); snprintf(fybuf, sizeof fybuf, "%lld", (long long)a->fy); argv[n++] = (char *)(self && *self ? self : "bokftui"); + argv[n++] = "--socket"; + argv[n++] = a->socket; argv[n++] = "--org"; argv[n++] = orgbuf; if (a->fy > 0) { @@ -4517,7 +5142,6 @@ int main(int argc, char **argv) char *err = NULL; int rc = try_login(&app, NULL, NULL, token, &err); if (rc == 0) { - config_save(&app); tui_log("auto-login: token ok"); break; } @@ -4536,7 +5160,6 @@ int main(int argc, char **argv) if (rc == 0) { snprintf(app.password, sizeof app.password, "%s", env_pass); - config_save(&app); tui_log("auto-login: password ok"); break; } |
