diff options
Diffstat (limited to 'clients')
| -rw-r--r-- | clients/bokftui.c | 1252 |
1 files changed, 832 insertions, 420 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c index 69fe3a3..ade7948 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -5221,7 +5221,7 @@ static void ib_screen(struct app *a) struct company_field { const char *key; const char *label; - int numeric; + int numeric; /* 0 = text, 1 = month (1-12), 2 = unsigned integer */ }; static const struct company_field COMPANY_FIELDS[] = { @@ -5234,6 +5234,8 @@ static const struct company_field COMPANY_FIELDS[] = { { "country", "Land", 0 }, { "email", "E-post", 0 }, { "phone", "Telefon", 0 }, + { "description", "Verksamhetsbeskrivning", 0 }, + { "shares", "Antal aktier", 2 }, { "moms_period", "Momsperiod (month/quarter/year)", 0 }, { "framework", "Regelverk (K2/K3)", 0 }, { "fiscal_year_start_month", "Startmånad räkenskapsår (1-12)", 1 }, @@ -5244,7 +5246,9 @@ static int company_field_valid(const struct company_field *f, const char *v) if (f->numeric) { char *end = NULL; long n = strtol(v, &end, 10); - return end && *end == '\0' && n >= 1 && n <= 12; + if (!end || *end != '\0') + return 0; + return f->numeric == 2 ? n >= 0 : (n >= 1 && n <= 12); } if (strcmp(f->key, "moms_period") == 0) return strcmp(v, "month") == 0 || strcmp(v, "quarter") == 0 || @@ -5254,6 +5258,272 @@ static int company_field_valid(const struct company_field *f, const char *v) return 1; } +/* Board members shown in the årsredovisning (name + title). */ +static void board_screen(struct app *a) +{ + int owner = a->role[0] && strcmp(a->role, "owner") == 0; + int cursor = 0; + for (;;) { + if (g_quit) + return; + char *resp = client_rpc(&a->conn, "board.list", a->session, a->org, + "{}"); + if (!resp || !client_ok(resp)) { + show_error("Styrelseledamöter", resp); + free(resp); + return; + } + size_t n = jarr_size(resp, "result.items"); + int rows = (int)n + (owner ? 1 : 0); + char **lines = xcalloc(rows ? rows : 1, sizeof(char *)); + char **names = xcalloc(n + 1, sizeof(char *)); + char **titles = xcalloc(n + 1, sizeof(char *)); + int64_t *ids = xcalloc(n + 1, sizeof(int64_t)); + for (size_t i = 0; i < n; i++) { + char path[64], line[512]; + snprintf(path, sizeof path, "result.items.%zu.id", i); + ids[i] = jint_val(resp, path, 0); + snprintf(path, sizeof path, "result.items.%zu.name", i); + names[i] = jstr_dup(resp, path); + snprintf(path, sizeof path, "result.items.%zu.title", i); + titles[i] = jstr_dup(resp, path); + snprintf(line, sizeof line, "%s %s", + titles[i] ? titles[i] : "", names[i] ? names[i] : ""); + lines[i] = xstrdup(line); + } + if (owner) + lines[n] = xstrdup("+ Ny ledamot (^N)"); + int s = select_list("Styrelseledamöter", lines, rows, cursor, 1, + &cursor, owner ? 1 : 0, + owner && n > 0 ? "ta bort" : NULL, 0); + if (s == -6 && cursor >= 0 && (size_t)cursor < n) { + char q[320]; + snprintf(q, sizeof q, "Ta bort '%s'? (j/n): ", lines[cursor]); + char *ans = prompt(q, "n", 0); + if (ans && (ans[0] == 'j' || ans[0] == 'J')) { + char args[64]; + snprintf(args, sizeof args, "{\"id\":%lld}", + (long long)ids[cursor]); + char *r = client_rpc(&a->conn, "board.remove", a->session, + a->org, args); + if (r && client_ok(r)) { + message("Styrelseledamöter", "Borttagen."); + } else { + show_error("Kunde inte ta bort", r); + } + free(r); + } + } else if (s == -4 || (s >= 0 && owner && (size_t)s == n)) { + char namebuf[256] = ""; + char *name = prompt("Namn: ", "", 0); + if (name && *name) + snprintf(namebuf, sizeof namebuf, "%s", name); + char *title = namebuf[0] + ? prompt("Titel: ", "Styrelseledamot", 0) + : NULL; + if (namebuf[0] && title && *title) { + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_strcpy(d, o, "name", namebuf); + yyjson_mut_obj_add_strcpy(d, o, "title", title); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *r = client_rpc(&a->conn, "board.add", a->session, + a->org, args); + free(args); + if (r && client_ok(r)) + message("Styrelseledamöter", "Tillagd."); + else + show_error("Kunde inte lägga till", r); + free(r); + } + } else if (s >= 0 && (size_t)s < n) { + if (!owner) { + message("Styrelseledamöter", + "Endast ägare kan ändra styrelseledamöterna."); + } else { + char namebuf[256] = ""; + char *name = prompt("Namn: ", names[s] ? names[s] : "", 0); + if (name && *name) + snprintf(namebuf, sizeof namebuf, "%s", name); + char *title = namebuf[0] + ? prompt("Titel: ", + titles[s] ? titles[s] : "", 0) + : NULL; + if (namebuf[0] && title && *title) { + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_int(d, o, "id", ids[s]); + yyjson_mut_obj_add_strcpy(d, o, "name", namebuf); + yyjson_mut_obj_add_strcpy(d, o, "title", title); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *r = client_rpc(&a->conn, "board.update", a->session, + a->org, args); + free(args); + if (r && client_ok(r)) + message("Styrelseledamöter", "Sparad."); + else + show_error("Kunde inte spara", r); + free(r); + } + } + } + int back = s == -1; + for (int i = 0; i < rows; i++) + free(lines[i]); + free(lines); + for (size_t i = 0; i < n; i++) { + free(names[i]); + free(titles[i]); + } + free(names); + free(titles); + free(ids); + free(resp); + if (back) + return; + } +} + +/* "Information om året": the per-year årsredovisning details. New years + inherit the stable fields, so only what changed needs editing. */ +static void yearinfo_screen(struct app *a) +{ + static const char *const labels[6] = { + "Väsentliga händelser", + "Årsstämma (YYYY-MM-DD)", + "Föreslagen utdelning (kr)", + "Utdelningens datum (YYYY-MM-DD)", + "Medelantal anställda", + "Övriga noter (t.ex. revisor)", + }; + static const char *const keys[6] = { "events", "agm_date", "dividend_ore", + "dividend_date", "employees", + "notes" }; + const int nf = 6; + int can_edit = strcmp(a->role, "owner") == 0 || + strcmp(a->role, "bookkeeper") == 0; + for (;;) { + if (g_quit) + return; + char args[48]; + snprintf(args, sizeof args, "{\"id\":%lld}", (long long)a->fy); + char *resp = client_rpc(&a->conn, "fiscal_year.get", a->session, + a->org, args); + if (!resp || !client_ok(resp)) { + show_error("Information om året", resp); + free(resp); + return; + } + char *vals[6]; + vals[0] = jstr_dup(resp, "result.events"); + vals[1] = jstr_dup(resp, "result.agm_date"); + char divbuf[48]; + kr_format(jint_val(resp, "result.dividend_ore", 0), divbuf, + sizeof divbuf); + vals[2] = xstrdup(divbuf); + vals[3] = jstr_dup(resp, "result.dividend_date"); + vals[4] = jstr_dup(resp, "result.employees"); + vals[5] = jstr_dup(resp, "result.notes"); + for (int i = 0; i < nf; i++) + if (!vals[i]) + vals[i] = xstrdup(""); + free(resp); + + int sel = 0; + int back = 0; + for (;;) { + frame("Information om året"); + attron(A_BOLD); + mvaddstr(3, 2, "Uppgift"); + mvaddstr(3, 34, "Värde"); + attroff(A_BOLD); + for (int i = 0; i < nf; i++) { + char lbl[128], line[640]; + snprintf(lbl, sizeof lbl, "%s", labels[i]); + pad_field(lbl, sizeof lbl, 31); + snprintf(line, sizeof line, "%s %s", lbl, vals[i]); + if (i == sel) { + attron(A_REVERSE); + mvaddnstr(5 + i, 2, line, COLS - 4); + attroff(A_REVERSE); + } else { + if (!can_edit) + attron(A_DIM); + mvaddnstr(5 + i, 2, line, COLS - 4); + if (!can_edit) + attroff(A_DIM); + } + } + hints(can_edit + ? "upp/ned Enter = ändra F5 = uppdatera" + " Esc/q = tillbaka ^C = avsluta" + : "upp/ned F5 = uppdatera Esc/q = tillbaka" + " ^C = avsluta (endast ägare/redovisare)"); + refresh(); + int ch = ui_getch(); + if (ch == KEY_UP && sel > 0) + sel--; + else if (ch == KEY_DOWN && sel < nf - 1) + sel++; + else if (ch == KEY_F(5)) + break; /* reload */ + else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { + if (!can_edit) { + message("Information om året", + "Endast ägare eller redovisare kan ändra."); + continue; + } + char label[160]; + snprintf(label, sizeof label, "%s: ", labels[sel]); + char *v = prompt(label, vals[sel], 0); + if (!v) + continue; + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + yyjson_mut_obj_add_int(d, o, "id", a->fy); + if (sel == 2) { + int64_t ore = 0; + if (parse_kr(v, &ore) != 0) { + message("Ogiltigt värde", "%s", labels[sel]); + yyjson_mut_doc_free(d); + continue; + } + yyjson_mut_obj_add_int(d, o, keys[sel], ore); + } else { + if ((sel == 1 || sel == 3) && *v && + !util_parse_iso_date(v)) { + message("Ogiltigt datum", "%s", v); + yyjson_mut_doc_free(d); + continue; + } + yyjson_mut_obj_add_strcpy(d, o, keys[sel], v); + } + char *uargs = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *r = client_rpc(&a->conn, "fiscal_year.update", + a->session, a->org, uargs); + free(uargs); + if (!r || !client_ok(r)) + show_error("Kunde inte spara", r); + free(r); + break; /* reload from the server */ + } else if (ch == 27 || ch == 'q') { + back = 1; + break; + } + } + for (int i = 0; i < nf; i++) + free(vals[i]); + if (back) + return; + } +} + static void company_screen(struct app *a) { const int nf = (int)(sizeof COMPANY_FIELDS / sizeof COMPANY_FIELDS[0]); @@ -5291,20 +5561,26 @@ static void company_screen(struct app *a) mvaddstr(3, 2, "Uppgift"); mvaddstr(3, 34, "Värde"); attroff(A_BOLD); - for (int i = 0; i < nf; i++) { + for (int i = 0; i <= nf; i++) { char lbl[128], line[512]; - snprintf(lbl, sizeof lbl, "%s", COMPANY_FIELDS[i].label); - pad_field(lbl, sizeof lbl, 31); - snprintf(line, sizeof line, "%s %s", lbl, vals[i]); + if (i < nf) { + snprintf(lbl, sizeof lbl, "%s", COMPANY_FIELDS[i].label); + pad_field(lbl, sizeof lbl, 31); + snprintf(line, sizeof line, "%s %s", lbl, vals[i]); + } else { + snprintf(lbl, sizeof lbl, "Styrelseledamöter"); + pad_field(lbl, sizeof lbl, 31); + snprintf(line, sizeof line, "%s (lista)", lbl); + } if (i == sel) { attron(A_REVERSE); mvaddnstr(5 + i, 2, line, COLS - 4); attroff(A_REVERSE); } else { - if (!owner) + if (!owner && i < nf) attron(A_DIM); mvaddnstr(5 + i, 2, line, COLS - 4); - if (!owner) + if (!owner && i < nf) attroff(A_DIM); } } @@ -5317,11 +5593,21 @@ static void company_screen(struct app *a) int ch = ui_getch(); if (ch == KEY_UP && sel > 0) sel--; - else if (ch == KEY_DOWN && sel < nf - 1) + else if (ch == KEY_DOWN && sel < nf) sel++; else if (ch == KEY_F(5)) break; /* reload */ else if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) { + if (sel == nf) { + if (!owner) { + message("Företagsuppgifter", + "Endast ägare kan ändra" + " styrelseledamöterna."); + continue; + } + board_screen(a); + break; /* reload */ + } if (!owner) { message("Företagsuppgifter", "Endast ägare kan ändra företagsuppgifterna."); @@ -5739,81 +6025,46 @@ static void bokslut_screen(struct app *a) /* K2 årsredovisning (draft text) */ /* ------------------------------------------------------------------ */ -static void ar_amounts(struct buf *t, const char *acct, const char *label, - int64_t cur, int64_t prev) -{ - char nm[160], a1[48], a2[48]; - pad_label(nm, sizeof nm, label, 48); - amt_col(a1, sizeof a1, 15, cur); - amt_col(a2, sizeof a2, 15, prev); - buf_line(t, " %-5s %s %s %s\n", acct ? acct : "", nm, a1, a2); -} - -static int64_t ar_is_sum(yyjson_val *res, int lo, int hi) +/* Whole kronor with space thousands; rounded half away from zero. */ +static void kr0_format(int64_t ore, char *buf, size_t n) { - int64_t s = 0; - if (!res) - return 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); - if (!acct_in(vstr(row, "account"), lo, hi)) - continue; - int64_t a = vint(row, "amount_ore"); - s += k == 0 ? a : -a; - } + long long v = (long long)(ore >= 0 ? (ore + 50) / 100 + : (ore - 50) / 100); + int neg = v < 0; + if (neg) + v = -v; + char digits[32], out[40]; + snprintf(digits, sizeof digits, "%lld", v); + size_t o = 0, 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]; } - return s; + out[o] = '\0'; + snprintf(buf, n, "%s%s", neg ? "-" : "", out); } -static void ar_is_rows(struct buf *t, yyjson_val *res, yyjson_val *prev, - int lo, int hi, int64_t *cur, int64_t *pre) +/* Thousands of kronor for the flerårsöversikt. */ +static long long ar_tkr(int64_t ore) { - 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); - ar_amounts(t, acc, vstr(row, "name"), c, p); - *cur += c; - *pre += p; - } - } + return (long long)(ore >= 0 ? (ore + 50000) / 100000 + : (ore - 50000) / 100000); } -static void ar_is_group(struct buf *t, yyjson_val *res, yyjson_val *prev, - const struct is_group *gr, int64_t *cur, - int64_t *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, 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); - ar_amounts(t, acc, vstr(row, "name"), c, p); - *cur += c; - *pre += p; - } - } -} +#define AR_YEARS 5 +#define AR_GROUPS 7 +struct ar_year { + char label[8]; + char from[16], to[16]; + int64_t netto, ror, res_fin, skatt, res, disp; + int64_t fin_in, fin_out; + int64_t groups[AR_GROUPS]; +}; + +/* Balance-sheet amounts are shown the way an annual report reads them: + credit balances (equity, liabilities) as positive numbers. */ static int64_t ar_bs_ib_sum(yyjson_val *res, int lo, int hi) { int64_t s = 0; @@ -5834,8 +6085,6 @@ static int64_t ar_bs_ib_sum(yyjson_val *res, int lo, int hi) return s; } -/* Balance-sheet amounts are shown the way an annual report reads them: - credit balances (equity, liabilities) as positive numbers. */ static int64_t ar_bs_sum(yyjson_val *res, int lo, int hi) { int64_t s = 0; @@ -5856,298 +6105,423 @@ static int64_t ar_bs_sum(yyjson_val *res, int lo, int hi) return s; } -static int ar_bs_rows(struct buf *t, yyjson_val *res, int lo, int hi, - int64_t *cur, int64_t *pre) +/* One year's income statement from its general ledger. Vouchers whose + description starts with "Stäng" are the source system's closings in + imported history years and are skipped, so those years show their real + figures instead of a zeroed P&L. */ +static void ar_load_year(struct ar_year *y, yyjson_val *gl) { - 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)) + memset(y, 0, sizeof *y); + yyjson_val *fy = yyjson_obj_get(gl, "fiscal_year"); + snprintf(y->label, sizeof y->label, "%.4s", vstr(fy, "start_date")); + snprintf(y->from, sizeof y->from, "%s", vstr(fy, "start_date")); + snprintf(y->to, sizeof y->to, "%s", vstr(fy, "end_date")); + yyjson_val *accts = yyjson_obj_get(gl, "accounts"); + size_t na = yyjson_arr_size(accts); + for (size_t i = 0; i < na; i++) { + yyjson_val *acct = yyjson_arr_get(accts, i); + const char *num = vstr(acct, "account"); + int n = atoi(num); + if (n < 3000 || n > 8999) + continue; + int64_t net = 0; + yyjson_val *rows = yyjson_obj_get(acct, "rows"); + size_t nr = yyjson_arr_size(rows); + for (size_t k = 0; k < nr; k++) { + yyjson_val *row = yyjson_arr_get(rows, k); + if (strncmp(vstr(row, "description"), "Stäng", 7) == 0) continue; - int64_t ub = vint(row, "ub_ore"), ib = vint(row, "ib_ore"); - if (lo >= 2000) { - ub = -ub; - ib = -ib; + net += vint(row, "debit_ore") - vint(row, "credit_ore"); + } + int64_t disp = -net; + int done = 0; + for (size_t g = 0; g < AR_GROUPS; g++) { + if (acct_in(num, IS_GROUPS[g].lo, IS_GROUPS[g].hi)) { + y->groups[g] += disp; + done = 1; + break; } - if (t) - ar_amounts(t, acc, vstr(row, "name"), ub, ib); - if (cur) - *cur += ub; - if (pre) - *pre += ib; - found = 1; } + if (done) + continue; + if (n >= 8000 && n <= 8399) + y->fin_in += disp; + else if (n >= 8400 && n <= 8599) + y->fin_out += disp; + else if (n >= 8800 && n <= 8899) + y->disp += disp; + else if (n >= 8900 && n <= 8989) + y->skatt += disp; } - return found; + int64_t rev = 0, cost = 0; + for (size_t g = 0; g < AR_GROUPS; g++) + if (IS_GROUPS[g].rev) + rev += y->groups[g]; + else + cost += y->groups[g]; + y->netto = y->groups[0]; + y->ror = rev + cost; + y->res_fin = y->ror + y->fin_in + y->fin_out; + y->res = y->res_fin + y->disp + y->skatt; } -static int ar_bs_group(struct buf *t, yyjson_val *res, const char *title, - const char *sumlabel, int lo, int hi, int64_t *cur, - int64_t *pre) +static void ar_amt_col(char *buf, size_t n, int width, int64_t ore) { - int64_t c = 0, p = 0; - if (!ar_bs_rows(NULL, res, lo, hi, &c, &p)) - return 0; - buf_line(t, "%s\n", title); - ar_bs_rows(t, res, lo, hi, NULL, NULL); - ar_amounts(t, "", sumlabel, c, p); - if (cur) - *cur += c; - if (pre) - *pre += p; - return 1; + char tmp[40]; + kr0_format(ore, tmp, sizeof tmp); + snprintf(buf, n, "%*s", width, tmp); } -static int64_t ar_is_result(yyjson_val *is) +static void ar_row(struct buf *t, const char *label, int64_t cur, int64_t prev, + int indent) { - return ar_is_sum(is, 3000, 3999) + ar_is_sum(is, 4000, 8989); + char nm[160], padded[200], a1[40], a2[40]; + snprintf(nm, sizeof nm, "%s%s", indent ? " " : "", label); + pad_label(padded, sizeof padded, nm, 48); + ar_amt_col(a1, sizeof a1, 16, cur); + ar_amt_col(a2, sizeof a2, 16, prev); + buf_line(t, "%s %s %s\n", padded, a1, a2); } -static int64_t ar_resultat(struct buf *t, yyjson_val *is, yyjson_val *prev, - const char *cur_lbl, const char *prev_lbl) +/* Equity table: four amount columns. */ +static void ar_eq_row(struct buf *t, const char *label, int64_t ak, + int64_t br, int64_t ar, int indent) { - buf_line(t, "\nResultaträkning\n\n"); - char h1[32], h2[32], blank[64]; - pad_label(blank, sizeof blank, "", 48); - pad_hdr(h1, sizeof h1, 15, cur_lbl); - pad_hdr(h2, sizeof h2, 15, prev_lbl); - buf_line(t, " %5s %s %s %s\n", "", blank, h1, h2); - buf_rule(t, 88); - const char *sect = NULL; - int64_t rev = 0, rev_pre = 0, exp = 0, exp_pre = 0; - int rev_groups = 0, exp_groups = 0, rev_summed = 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(is, gr)) - continue; - if (!sect || strcmp(sect, gr->section) != 0) { - if (sect && rev_groups && !rev_summed) { - ar_amounts(t, "", "Summa rörelseintäkter," - " lagerförändringar m.m.", rev, rev_pre); - rev_summed = 1; - buf_line(t, "\n"); - } - buf_line(t, "%s\n", gr->section); - sect = gr->section; - } - buf_line(t, "%s\n", gr->title); - int64_t sc = 0, sp = 0; - ar_is_group(t, is, prev, gr, &sc, &sp); - if (gr->rev) { - rev += sc; - rev_pre += sp; - rev_groups++; - } else { - exp += sc; - exp_pre += sp; - exp_groups++; - } - } - if (rev_groups && !rev_summed) - ar_amounts(t, "", "Summa rörelseintäkter, lagerförändringar m.m.", - rev, rev_pre); - if (exp_groups) - ar_amounts(t, "", "Summa rörelsekostnader", exp, exp_pre); - int64_t ror = rev + exp, ror_pre = rev_pre + exp_pre; - int64_t fin = 0, fin_pre = 0, disp = 0, disp_pre = 0; - int64_t tax = 0, tax_pre = 0; - if (is_range_has(is, 8000, 8599)) { - buf_line(t, "\nFinansiella poster\n"); - ar_is_rows(t, is, prev, 8000, 8599, &fin, &fin_pre); - ar_amounts(t, "", "Summa", fin, fin_pre); - } - int64_t after = ror + fin, after_pre = ror_pre + fin_pre; - buf_line(t, "\n"); - buf_rule(t, 88); - ar_amounts(t, "", "Rörelseresultat", ror, ror_pre); - ar_amounts(t, "", "Resultat efter finansiella poster", after, after_pre); - if (is_range_has(is, 8800, 8899)) { - buf_line(t, "\nBokslutsdispositioner\n"); - ar_is_rows(t, is, prev, 8800, 8899, &disp, &disp_pre); - ar_amounts(t, "", "Summa", disp, disp_pre); - } - int64_t btax = after + disp, btax_pre = after_pre + disp_pre; - ar_amounts(t, "", "Resultat före skatt", btax, btax_pre); - if (is_range_has(is, 8900, 8989)) { - buf_line(t, "\nSkatt på årets resultat\n"); - ar_is_rows(t, is, prev, 8900, 8989, &tax, &tax_pre); - } - int64_t ars = btax + tax, ars_pre = btax_pre + tax_pre; - buf_line(t, "\n"); - buf_rule(t, 88); - ar_amounts(t, "", "Årets resultat", ars, ars_pre); - return ars; + char nm[160], padded[200], c1[32], c2[32], c3[32], c4[32]; + snprintf(nm, sizeof nm, "%s%s", indent ? " " : "", label); + pad_label(padded, sizeof padded, nm, 34); + ar_amt_col(c1, sizeof c1, 12, ak); + ar_amt_col(c2, sizeof c2, 14, br); + ar_amt_col(c3, sizeof c3, 14, ar); + ar_amt_col(c4, sizeof c4, 14, ak + br + ar); + buf_line(t, "%s %s %s %s %s\n", padded, c1, c2, c3, c4); } -static int64_t ar_equity(yyjson_val *bs, int64_t result) +static void ar_wrap(struct buf *t, const char *text) { - return ar_bs_sum(bs, 2000, 2090) + ar_bs_sum(bs, 2091, 2098) + result; + char line[128] = ""; + const char *p = text; + while (*p) { + while (*p == ' ' || *p == '\n' || *p == '\t') + p++; + if (!*p) + break; + size_t n = strcspn(p, " \n\t"); + size_t ll = strlen(line); + if (ll + (ll ? 1 : 0) + n > 76) { + buf_line(t, "%s\n", line); + ll = 0; + line[0] = '\0'; + } + if (ll) { + line[ll++] = ' '; + line[ll] = '\0'; + } + if (n > sizeof line - 1 - ll) + n = sizeof line - 1 - ll; + memcpy(line + ll, p, n); + line[ll + n] = '\0'; + p += n; + } + if (line[0]) + buf_line(t, "%s\n", line); } -static void ar_balans(struct buf *t, yyjson_val *bs, const char *cur_lbl, - const char *prev_lbl, int64_t ars, int64_t ars_pre) +/* "2026-06-04" -> "4 juni 2026"; anything else becomes a placeholder. */ +static void ar_sv_date(const char *iso, char *out, size_t n) { - buf_line(t, "\nBalansräkning\n\n"); - char h1[32], h2[32], blank[64]; - pad_label(blank, sizeof blank, "", 48); - pad_hdr(h1, sizeof h1, 15, cur_lbl); - pad_hdr(h2, sizeof h2, 15, prev_lbl); - buf_line(t, " %5s %s %s %s\n", "", blank, h1, h2); - buf_rule(t, 88); - buf_line(t, "TILLGÅNGAR\n"); - int64_t anl = 0, anl_p = 0, oms = 0, oms_p = 0; - ar_bs_group(t, bs, "Anläggningstillgångar", - "Summa anläggningstillgångar", 1000, 1399, &anl, &anl_p); - buf_line(t, "\nOmsättningstillgångar\n"); - 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 }, + static const char *const months[12] = { + "januari", "februari", "mars", "april", "maj", "juni", "juli", + "augusti", "september", "oktober", "november", "december" }; - for (size_t i = 0; i < sizeof OMS / sizeof OMS[0]; i++) - ar_bs_group(t, bs, OMS[i].title, OMS[i].sum, OMS[i].lo, OMS[i].hi, - &oms, &oms_p); - ar_amounts(t, "", "Summa omsättningstillgångar", oms, oms_p); - int64_t tok = anl + oms, tok_p = anl_p + oms_p; - buf_line(t, "\n"); - buf_rule(t, 88); - ar_amounts(t, "", "SUMMA TILLGÅNGAR", tok, tok_p); - buf_line(t, "\nEGET KAPITAL OCH SKULDER\n"); - int64_t eget = 0, eget_p = 0; - ar_bs_group(t, bs, "Bundet eget kapital", "Summa bundet eget kapital", - 2000, 2090, &eget, &eget_p); - buf_line(t, "\n"); - int64_t free_c = 0, free_p = 0; - ar_bs_rows(NULL, bs, 2091, 2098, &free_c, &free_p); - buf_line(t, "Fritt eget kapital\n"); - ar_bs_rows(t, bs, 2091, 2098, NULL, NULL); - ar_amounts(t, "", "Årets resultat", ars, ars_pre); - free_c += ars; - free_p += ars_pre; - ar_amounts(t, "", "Summa fritt eget kapital", free_c, free_p); - eget += free_c; - eget_p += free_p; - buf_line(t, "\n"); - ar_amounts(t, "", "Summa eget kapital", eget, eget_p); - int64_t skuld = 0, skuld_p = 0; - buf_line(t, "\n"); - if (ar_bs_group(t, bs, "Obeskattade reserver", - "Summa obeskattade reserver", 2100, 2199, &skuld, - &skuld_p)) - buf_line(t, "\n"); - if (ar_bs_group(t, bs, "Avsättningar", "Summa avsättningar", 2200, 2299, - &skuld, &skuld_p)) - buf_line(t, "\n"); - if (ar_bs_group(t, bs, "Långfristiga skulder", - "Summa långfristiga skulder", 2300, 2399, &skuld, - &skuld_p)) - buf_line(t, "\n"); - ar_bs_group(t, bs, "Kortfristiga skulder", "Summa kortfristiga skulder", - 2400, 2999, &skuld, &skuld_p); - buf_line(t, "\n"); - buf_rule(t, 88); - ar_amounts(t, "", "SUMMA EGET KAPITAL OCH SKULDER", eget + skuld, - eget_p + skuld_p); + int y = 0, m = 0, d = 0; + if (!iso || strlen(iso) != 10 || sscanf(iso, "%d-%d-%d", &y, &m, &d) != 3 || + m < 1 || m > 12 || d < 1 || d > 31) { + snprintf(out, n, "[datum]"); + return; + } + snprintf(out, n, "%d %s %d", d, months[m - 1], y); } static void fmt_arsredovisning(struct buf *t, const struct app *a, const char *org_nr, const char *city, - yyjson_val *is, yyjson_val *prev_is, - yyjson_val *bs, int64_t dividend) + const char *description, int64_t shares, + yyjson_val *fyj, const char *board_text, + const char *first_board, yyjson_val *bs, + struct ar_year *years, int nyears) { - const char *cur_lbl = vstr(yyjson_obj_get(is, "fiscal_year"), "label"); - const char *prev_lbl = - prev_is ? vstr(yyjson_obj_get(prev_is, "fiscal_year"), "label") : ""; - int64_t ars = ar_is_result(is); - int64_t prev_ars = ar_is_result(prev_is); - buf_line(t, "ÅRSREDOVISNING\n\n%s\nOrganisationsnummer %s\n", - a->org_name, org_nr); - buf_line(t, "\nRäkenskapsåret %s - %s\n", a->fy_start, a->fy_end); - buf_line(t, "\nFörvaltningsberättelse\n\nVerksamhet\n"); - buf_line(t, "Bolaget bedriver [kort beskrivning av verksamheten]." - " Verksamheten bedrivs från %s.\n", city); + struct ar_year *cur = &years[nyears - 1]; + struct ar_year *prev = nyears > 1 ? &years[nyears - 2] : NULL; + int64_t ars = cur->res; + int64_t prev_ars = prev ? prev->res : 0; + const char *events = vstr(fyj, "events"); + const char *employees = vstr(fyj, "employees"); + const char *notes = vstr(fyj, "notes"); + int64_t dividend = vint(fyj, "dividend_ore"); + char agm_sv[64], pay_sv[64]; + ar_sv_date(vstr(fyj, "agm_date"), agm_sv, sizeof agm_sv); + ar_sv_date(vstr(fyj, "dividend_date"), pay_sv, sizeof pay_sv); + + buf_line(t, "ÅRSREDOVISNING\n\n%s\nOrg nr %s\n\n", a->org_name, org_nr); + buf_line(t, "Årsredovisning för räkenskapsåret %s - %s\n\n", a->fy_start, + a->fy_end); + buf_line(t, "Styrelsen avger följande årsredovisning.\n\n"); + buf_line(t, "Om inte annat särskilt anges, redovisas alla belopp i hela" + " kronor.\n"); + + buf_line(t, "\nFastställelseintyg\n\n"); + buf_line(t, "Undertecknad styrelseledamot i %s intygar, dels att denna" + " kopia av\nårsredovisningen stämmer överens med originalet," + " dels att resultaträkningen\noch balansräkningen har" + " fastställts på årsstämma den %s. Årsstämman\nbeslutade" + " att godkänna styrelsens förslag till hur vinsten ska" + " fördelas.\n\n", a->org_name, agm_sv); + buf_line(t, "%s %s\n\nNamnteckning:" + " ___________________________\n\nNamnförtydligande: %s\n", + city, agm_sv, first_board); + + buf_line(t, "\nFörvaltningsberättelse\n\nVerksamheten\n\nAllmänt om" + " verksamheten\n"); + if (description && *description) + ar_wrap(t, description); + else + buf_line(t, "[Kort beskrivning av verksamheten saknas — fyll i under" + " Företagsuppgifter.]\n"); + buf_line(t, "\nBolaget har sitt säte i %s.\n", city); buf_line(t, "\nVäsentliga händelser under räkenskapsåret\n"); - buf_line(t, "[Beskriv väsentliga händelser, eller skriv att inga" - " väsentliga händelser har inträffat.]\n"); - buf_line(t, "\nEkonomisk översikt\n\n"); - buf_line(t, " %-40s %15s %15s\n", "", cur_lbl, prev_lbl); - buf_rule(t, 72); - ar_amounts(t, "", "Nettoomsättning", ar_is_sum(is, 3000, 3799), - prev_is ? ar_is_sum(prev_is, 3000, 3799) : 0); - int64_t ror = ar_is_sum(is, 3000, 3999) + ar_is_sum(is, 4000, 7999); - int64_t ror_p = prev_is ? ar_is_sum(prev_is, 3000, 3999) + - ar_is_sum(prev_is, 4000, 7999) - : 0; - ar_amounts(t, "", "Rörelseresultat", ror, ror_p); - ar_amounts(t, "", "Årets resultat", ars, prev_ars); - ar_amounts(t, "", "Balansomslutning", - vint(yyjson_obj_get(bs, "assets"), "total_ore"), - ar_bs_ib_sum(bs, 1000, 1999)); - ar_amounts(t, "", "Eget kapital", ar_equity(bs, ars), - ar_bs_ib_sum(bs, 2000, 2090) + ar_bs_ib_sum(bs, 2091, 2098) + - prev_ars); - if (prev_is && prev_ars == 0 && ar_is_sum(prev_is, 3000, 3799) == 0) - buf_line(t, "\n[Jämförelsetalen för %s är inte kompletta i den" - " löpande bokföringen\n(historikåret importerades med" - " källsystemets stängningsverifikat). Hämta dem från\n" - "föregående års årsredovisning.]\n", prev_lbl); - int64_t disponibelt = ar_bs_sum(bs, 2091, 2098) + ars; - buf_line(t, "\nFörslag till disposition av bolagets resultat\n\n"); - ar_amounts(t, "", "Balanserade vinstmedel", ar_bs_sum(bs, 2091, 2098), - 0); - ar_amounts(t, "", "Årets resultat", ars, 0); - ar_amounts(t, "", "Summa", disponibelt, 0); + if (events && *events) + ar_wrap(t, events); + else + buf_line(t, "[Beskriv väsentliga händelser.]\n"); + + if (nyears > 1) { + char row[512]; + int off = snprintf(row, sizeof row, "%-38s", "Flerårsöversikt"); + for (int i = 0; i < nyears; i++) + off += snprintf(row + off, sizeof row - (size_t)off, "%9s", + years[i].label); + buf_line(t, "\n%s\n\n", row); + off = snprintf(row, sizeof row, "%-38s", "Nettoomsättning, tkr"); + for (int i = 0; i < nyears; i++) + off += snprintf(row + off, sizeof row - (size_t)off, "%9lld", + ar_tkr(years[i].netto)); + buf_line(t, "%s\n", row); + off = snprintf(row, sizeof row, "%-38s", + "Resultat efter finansiella poster, tkr"); + for (int i = 0; i < nyears; i++) + off += snprintf(row + off, sizeof row - (size_t)off, "%9lld", + ar_tkr(years[i].res_fin)); + buf_line(t, "%s\n", row); + } + + int64_t ak_ib = ar_bs_ib_sum(bs, 2081, 2089); + int64_t ak_ub = ar_bs_sum(bs, 2081, 2089); + int64_t br_ib = ar_bs_ib_sum(bs, 2091, 2098); + int64_t br_ub = ar_bs_sum(bs, 2091, 2098); + int64_t movement = br_ub - br_ib; + int64_t paid_div = prev_ars - movement; + if (paid_div < 0) + paid_div = 0; + int64_t balanced = prev_ars - paid_div; + if (prev) { + char row[512]; + int off = snprintf(row, sizeof row, "%-34s", "Förändringar i eget" + " kapital"); + off += snprintf(row + off, sizeof row - (size_t)off, "%12s", + "Aktiekapital"); + off += snprintf(row + off, sizeof row - (size_t)off, "%14s", + "Balanserat"); + off += snprintf(row + off, sizeof row - (size_t)off, "%14s", + "Årets"); + off += snprintf(row + off, sizeof row - (size_t)off, "%14s", + "Totalt"); + buf_line(t, "\n%s\n", row); + buf_line(t, "%s\n", " resultat" + " resultat"); + int has_disp = movement != 0; + int64_t ar_ib = has_disp ? prev_ars : 0; + ar_eq_row(t, "Belopp vid årets ingång", ak_ib, br_ib, ar_ib, 0); + if (has_disp) { + buf_line(t, "Resultatdisposition enligt årsstämman\n"); + ar_eq_row(t, "Utdelning", 0, -paid_div, 0, 1); + ar_eq_row(t, "Balanseras i ny räkning", 0, balanced, -prev_ars, 1); + } + ar_eq_row(t, "Årets resultat", 0, 0, ars, 0); + ar_eq_row(t, "Belopp vid årets utgång", ak_ub, br_ub, ars, 0); + } + + int64_t dispon = br_ub + ars; + buf_line(t, "\nResultatdisposition\n\nTill årsstämmans förfogande står" + " följande vinstmedel:\n"); + ar_row(t, "Balanserat resultat", br_ub, 0, 1); + ar_row(t, "Årets resultat", ars, 0, 1); + ar_row(t, "Totalt", dispon, 0, 1); + buf_line(t, "\nStyrelsen föreslår att vinstmedlen disponeras enligt" + " följande:\n"); + ar_row(t, "Utdelning till ägarna", dividend, 0, 1); + ar_row(t, "Balanseras i ny räkning", dispon - dividend, 0, 1); + ar_row(t, "Totalt", dispon, 0, 1); + + if (dividend > 0) { + buf_line(t, "\nStyrelsens yttrande över den föreslagna" + " vinstutdelningen\n\n"); + if (shares > 0) { + char per[48]; + kr_format((dividend + shares / 2) / shares, per, sizeof per); + buf_line(t, "Utdelning per aktie: %s kr. Datum för utdelning är" + " %s.\n\n", per, pay_sv); + } else { + buf_line(t, "Datum för utdelning är %s.\n\n", pay_sv); + } + buf_line(t, "Likviditeten i bolaget bedöms kunna upprätthållas på en" + " likaledes betryggande nivå.\n\n"); + buf_line(t, "Styrelsens uppfattning är att den föreslagna utdelningen" + " ej hindrar bolaget\nfrån att fullgöra sina förpliktelser" + " på kort och lång sikt, ej heller att fullgöra\nerforderliga" + " investeringar. Den föreslagna utdelningen kan därmed" + " försvaras med\nhänsyn till vad som anförs i ABL 17 kap." + " 3 § 2 st. (försiktighetsregeln).\n"); + } + + buf_line(t, "\nResultaträkning\nSamtliga belopp anges i svenska" + " kronor.\n\n"); { - char tmpd[48], tmpr[48]; - kr_format(dividend, tmpd, sizeof tmpd); - kr_format(disponibelt - dividend, tmpr, sizeof tmpr); - if (dividend > 0) - buf_line(t, "\nStyrelsen föreslår att vinstmedlen disponeras så" - " att\n till aktieägarna utdelas %s kr och\n" - " i ny räkning överförs %s kr.\n", - tmpd, tmpr); + char h1[40], h2[40], row[512]; + snprintf(h1, sizeof h1, "%s - %s", cur->from, cur->to); + if (prev) + snprintf(h2, sizeof h2, "%s - %s", prev->from, prev->to); else - buf_line(t, "\nStyrelsen föreslår att vinstmedlen disponeras så" - " att i ny räkning överförs %s kr.\n", tmpr); + h2[0] = '\0'; + int off = snprintf(row, sizeof row, "%-48s", ""); + off += snprintf(row + off, sizeof row - (size_t)off, "%16s", h1); + if (h2[0]) + off += snprintf(row + off, sizeof row - (size_t)off, "%17s", h2); + buf_line(t, "%s\n", row); } - ar_resultat(t, is, prev_is, cur_lbl, prev_lbl); - ar_balans(t, bs, cur_lbl, prev_lbl, ars, prev_ars); - buf_line(t, "\nNoter\n\nNot 1 Redovisningsprinciper\n"); - buf_line(t, "Årsredovisningen har upprättats enligt" - " årsredovisningslagen och BFNAR 2016:10 Årsredovisning i" - " mindre företag (K2). Tillgångar och skulder värderas till" - " anskaffningsvärde.\n"); + buf_line(t, "Rörelseintäkter, lagerförändringar m.m.\n"); + ar_row(t, "Nettoomsättning", cur->groups[0], + prev ? prev->groups[0] : 0, 1); + int64_t rev_cur = cur->groups[0] + cur->groups[1]; + int64_t rev_pre = prev ? prev->groups[0] + prev->groups[1] : 0; + ar_row(t, "Summa Rörelseintäkter, lagerförändringar m.m.", rev_cur, + rev_pre, 1); + buf_line(t, "\nRörelsekostnader\n"); + int64_t cost_cur = 0, cost_pre = 0; + for (size_t g = 2; g < AR_GROUPS; g++) { + int64_t c = cur->groups[g], p = prev ? prev->groups[g] : 0; + if (c || p) + ar_row(t, IS_GROUPS[g].title, c, p, 1); + cost_cur += c; + cost_pre += p; + } + ar_row(t, "Summa Rörelsens kostnader", cost_cur, cost_pre, 1); + buf_line(t, "\n"); + ar_row(t, "Rörelseresultat", cur->ror, prev ? prev->ror : 0, 0); + if (cur->fin_in || (prev && prev->fin_in)) + ar_row(t, "Ränteintäkter", cur->fin_in, prev ? prev->fin_in : 0, 0); + if (cur->fin_out || (prev && prev->fin_out)) + ar_row(t, "Räntekostnader", cur->fin_out, prev ? prev->fin_out : 0, 0); + ar_row(t, "Resultat efter finansiella poster", cur->res_fin, + prev ? prev->res_fin : 0, 0); + int64_t btax = cur->res_fin + cur->disp; + int64_t btax_pre = prev ? prev->res_fin + prev->disp : 0; + if (cur->disp || (prev && prev->disp)) { + buf_line(t, "\nBokslutsdispositioner\n"); + ar_row(t, "Summa", cur->disp, prev ? prev->disp : 0, 1); + } + ar_row(t, "Resultat före skatt", btax, btax_pre, 0); + if (cur->skatt || (prev && prev->skatt)) + ar_row(t, "Skatt på årets resultat", cur->skatt, + prev ? prev->skatt : 0, 0); + buf_line(t, "\n"); + ar_row(t, "Årets resultat", ars, prev_ars, 0); + + buf_line(t, "\nBalansräkning\n\n"); + { + char h1[40], h2[40], row[256]; + snprintf(h1, sizeof h1, "%s", cur->to); + if (prev) + snprintf(h2, sizeof h2, "%s", prev->to); + else + h2[0] = '\0'; + int off = snprintf(row, sizeof row, "%-48s", ""); + off += snprintf(row + off, sizeof row - (size_t)off, "%16s", h1); + if (h2[0]) + off += snprintf(row + off, sizeof row - (size_t)off, "%17s", h2); + buf_line(t, "%s\n", row); + } + buf_line(t, "Tillgångar\nAnläggningstillgångar\n"); + ar_row(t, "Summa anläggningstillgångar", ar_bs_sum(bs, 1000, 1399), + ar_bs_ib_sum(bs, 1000, 1399), 1); + buf_line(t, "Omsättningstillgångar\n"); + int64_t v = ar_bs_sum(bs, 1400, 1499), vp = ar_bs_ib_sum(bs, 1400, 1499); + if (v || vp) + ar_row(t, "Varulager", v, vp, 0); + ar_row(t, "Kortfristiga fordringar", ar_bs_sum(bs, 1500, 1699), + ar_bs_ib_sum(bs, 1500, 1699), 0); + v = ar_bs_sum(bs, 1700, 1899); + vp = ar_bs_ib_sum(bs, 1700, 1899); + if (v || vp) + ar_row(t, "Kortfristiga placeringar", v, vp, 0); + ar_row(t, "Kassa och bank", ar_bs_sum(bs, 1900, 1999), + ar_bs_ib_sum(bs, 1900, 1999), 0); + ar_row(t, "Summa tillgångar", ar_bs_sum(bs, 1000, 1999), + ar_bs_ib_sum(bs, 1000, 1999), 0); + buf_line(t, "\nEget kapital och skulder\nBundet eget kapital\n"); + ar_row(t, "Aktiekapital", ak_ub, ak_ib, 1); + ar_row(t, "Summa bundet eget kapital", ar_bs_sum(bs, 2000, 2090), + ar_bs_ib_sum(bs, 2000, 2090), 1); + buf_line(t, "Fritt eget kapital\n"); + ar_row(t, "Balanserat resultat", br_ub, br_ib, 1); + ar_row(t, "Årets resultat", ars, prev_ars, 1); + ar_row(t, "Summa fritt eget kapital", ar_bs_sum(bs, 2091, 2098) + ars, + ar_bs_ib_sum(bs, 2091, 2098) + prev_ars, 1); + ar_row(t, "Summa eget kapital", + ar_bs_sum(bs, 2000, 2090) + ar_bs_sum(bs, 2091, 2098) + ars, + ar_bs_ib_sum(bs, 2000, 2090) + ar_bs_ib_sum(bs, 2091, 2098) + + prev_ars, + 0); + buf_line(t, "\n"); + ar_row(t, "Obeskattade reserver", ar_bs_sum(bs, 2100, 2199), + ar_bs_ib_sum(bs, 2100, 2199), 0); + ar_row(t, "Avsättningar", ar_bs_sum(bs, 2200, 2299), + ar_bs_ib_sum(bs, 2200, 2299), 0); + buf_line(t, "Kortfristiga skulder\n"); + ar_row(t, "Skatteskulder", ar_bs_sum(bs, 2500, 2599), + ar_bs_ib_sum(bs, 2500, 2599), 1); + ar_row(t, "Mervärdesskatt", ar_bs_sum(bs, 2600, 2699), + ar_bs_ib_sum(bs, 2600, 2699), 1); + ar_row(t, "Personalskatter och sociala avgifter", + ar_bs_sum(bs, 2700, 2799), ar_bs_ib_sum(bs, 2700, 2799), 1); + ar_row(t, "Övriga kortfristiga skulder", ar_bs_sum(bs, 2800, 2999) + + ar_bs_sum(bs, 2400, 2499), + ar_bs_ib_sum(bs, 2800, 2999) + ar_bs_ib_sum(bs, 2400, 2499), 1); + int64_t skuld = ar_bs_sum(bs, 2400, 2999); + int64_t skuld_p = ar_bs_ib_sum(bs, 2400, 2999); + ar_row(t, "Summa kortfristiga skulder", skuld, skuld_p, 1); + ar_row(t, "Summa eget kapital, avsättningar och skulder", + ar_bs_sum(bs, 2000, 2999) + ars, + ar_bs_ib_sum(bs, 2000, 2999) + prev_ars, 0); + + buf_line(t, "\nNoter\n\nNot 1 Redovisnings- och värderingsprinciper\n"); + buf_line(t, "Årsredovisningen är upprättad i enlighet med" + " årsredovisningslagen och\nBFNAR 2016:10 Årsredovisning i" + " mindre företag.\n"); buf_line(t, "\nNot 2 Medelantal anställda\n"); buf_line(t, "Medelantalet anställda under räkenskapsåret har uppgått" - " till [antal].\n"); - int64_t fond_avs = -(ar_is_sum(is, 8811, 8811)); - if (fond_avs != 0 || ar_bs_sum(bs, 2110, 2199) != 0) { - buf_line(t, "\nNot 3 Periodiseringsfond\n"); - buf_line(t, "Årets avsättning till periodiseringsfond uppgår till" - " %lld kr. Utgående balans uppgår till %lld kr.\n", - (long long)(fond_avs / 100), - (long long)(ar_bs_sum(bs, 2110, 2199) / 100)); + " till %s.\n", employees && *employees ? employees + : "[antal]"); + if (notes && *notes) { + buf_line(t, "\nNot 3 Övriga upplysningar\n"); + ar_wrap(t, notes); } - buf_line(t, "\nUnderskrifter\n\n%s den [datum]\n\n", city); - buf_line(t, "____________________________ " - "____________________________\n"); - buf_line(t, "[Namn], styrelseledamot " - "[Namn], styrelseledamot\n"); - buf_line(t, "\nRevisionsberättelse\n"); - buf_line(t, "[Bolaget har inte någon revisor; någon revisionsberättelse" - " har därför inte lämnats.]\n"); + + buf_line(t, "\nUnderskrifter\n\n%s %s\n\n", city, agm_sv); + if (board_text && *board_text) + buf_line(t, "%s", board_text); + else + buf_line(t, "[Lägg till styrelseledamöter under" + " Företagsuppgifter.]\n"); } static void arsredovisning_save(struct app *a, const char *text) @@ -6169,8 +6543,13 @@ static void arsredovisning_save(struct app *a, const char *text) size_t fl = strlen(full); if (fl && full[fl - 1] != '/') snprintf(full + fl, sizeof full - fl, "/"); + char name[80]; + snprintf(name, sizeof name, "%s", a->fy_label); + for (char *p = name; *p; p++) + if (*p == '/') + *p = '-'; snprintf(full + strlen(full), sizeof full - strlen(full), - "Årsredovisning %.4s-%.4s.txt", a->fy_start, a->fy_end); + "Årsredovisning %s.txt", *name ? name : "utkast"); FILE *f = fopen(full, "wb"); size_t n = strlen(text); if (!f || fwrite(text, 1, n, f) != n) { @@ -6187,88 +6566,115 @@ static void arsredovisning_save(struct app *a, const char *text) static void arsredovisning_screen(struct app *a) { - int have_div = 0; - int64_t div_ore = 0; for (;;) { - char args[64]; - snprintf(args, sizeof args, "{\"fiscal_year\":%lld}", + char *fresp = client_rpc(&a->conn, "fiscal_year.list", a->session, + a->org, "{}"); + if (!fresp || !client_ok(fresp)) { + show_error("Årsredovisning", fresp); + free(fresp); + return; + } + struct ar_year years[AR_YEARS]; + int nyears = 0; + { + yyjson_doc *fd = parse(fresp); + yyjson_val *items = + fd ? jget(yyjson_doc_get_root(fd), "result.items") : NULL; + size_t n = yyjson_arr_size(items); + int idx = -1; + for (size_t i = 0; i < n; i++) { + yyjson_val *it = yyjson_arr_get(items, i); + if (vint(it, "id") == a->fy) + idx = (int)i; + } + if (idx < 0) + idx = (int)n - 1; + int start = idx - (AR_YEARS - 1); + if (start < 0) + start = 0; + for (int i = start; i <= idx && nyears < AR_YEARS; i++) { + yyjson_val *it = yyjson_arr_get(items, (size_t)i); + char gargs[64]; + snprintf(gargs, sizeof gargs, "{\"fiscal_year\":%lld}", + (long long)vint(it, "id")); + char *gresp = client_rpc(&a->conn, "report.general_ledger", + a->session, a->org, gargs); + if (!gresp || !client_ok(gresp)) { + show_error("Årsredovisning", gresp); + free(gresp); + yyjson_doc_free(fd); + free(fresp); + return; + } + yyjson_doc *gd = parse(gresp); + yyjson_val *g = + gd ? jget(yyjson_doc_get_root(gd), "result") : NULL; + if (g) + ar_load_year(&years[nyears++], g); + yyjson_doc_free(gd); + free(gresp); + } + yyjson_doc_free(fd); + } + free(fresp); + + char bargs[64]; + snprintf(bargs, sizeof bargs, "{\"fiscal_year\":%lld}", (long long)a->fy); - char *iresp = - client_rpc(&a->conn, "report.income_statement", a->session, - a->org, args); char *bresp = client_rpc(&a->conn, "report.balance_sheet", a->session, - a->org, args); - char *oresp = - client_rpc(&a->conn, "org.get", a->session, a->org, "{}"); - if (!iresp || !client_ok(iresp) || !bresp || !client_ok(bresp)) { - show_error("Årsredovisning", - iresp && !client_ok(iresp) ? iresp : bresp); - free(iresp); + a->org, bargs); + char *oresp = client_rpc(&a->conn, "org.get", a->session, a->org, + "{}"); + char *yresp = client_rpc(&a->conn, "fiscal_year.get", a->session, + a->org, "{}"); + char *mresp = client_rpc(&a->conn, "board.list", a->session, a->org, + "{}"); + if (!bresp || !client_ok(bresp) || !oresp || !client_ok(oresp) || + !yresp || !client_ok(yresp) || !mresp || !client_ok(mresp) || + nyears == 0) { + show_error("Årsredovisning", bresp && !client_ok(bresp) ? bresp + : oresp); free(bresp); free(oresp); + free(yresp); + free(mresp); return; } - char *pis_resp = NULL; - int64_t pf = prev_fy_id(a); - if (pf) { - char pargs[64]; - snprintf(pargs, sizeof pargs, "{\"fiscal_year\":%lld}", - (long long)pf); - pis_resp = client_rpc(&a->conn, "report.income_statement", - a->session, a->org, pargs); - } - yyjson_doc *id = parse(iresp), *bd = parse(bresp); - yyjson_doc *pid = pis_resp ? parse(pis_resp) : NULL; - yyjson_val *is = id ? jget(yyjson_doc_get_root(id), "result") : NULL; + yyjson_doc *bd = parse(bresp), *od = parse(oresp), *yd = parse(yresp); yyjson_val *bs = bd ? jget(yyjson_doc_get_root(bd), "result") : NULL; - yyjson_val *pis = - pid ? jget(yyjson_doc_get_root(pid), "result") : NULL; - char *onr = oresp ? jstr_dup(oresp, "result.org_nr") : NULL; - char *city = oresp ? jstr_dup(oresp, "result.city") : NULL; - if (is && bs && !have_div) { - char fargs[48]; - snprintf(fargs, sizeof fargs, "{\"id\":%lld}", (long long)a->fy); - char *fresp = client_rpc(&a->conn, "fiscal_year.get", a->session, - a->org, fargs); - if (fresp && client_ok(fresp)) - div_ore = jint_val(fresp, "result.dividend_ore", 0); - free(fresp); - have_div = 1; - int64_t dispon = ar_bs_sum(bs, 2091, 2098) + ar_is_result(is); - char cur[48]; - kr_format(div_ore, cur, sizeof cur); - char *val = prompt("Föreslagen utdelning (kr): ", cur, 0); - if (val) { - int64_t v = 0; - char dtmp[48]; - kr_format(dispon, dtmp, sizeof dtmp); - if (parse_kr(val, &v) != 0) - message("Årsredovisning", "Ogiltigt belopp: %s", val); - else if (v > dispon) - message("Årsredovisning", "Utdelningen överstiger" - " disponibla medel (%s kr).", - dtmp); - else if (v != div_ore) { - char uargs[96]; - snprintf(uargs, sizeof uargs, - "{\"id\":%lld,\"dividend_ore\":%lld}", - (long long)a->fy, (long long)v); - char *uresp = client_rpc(&a->conn, "fiscal_year.update", - a->session, a->org, uargs); - if (uresp && client_ok(uresp)) - div_ore = v; - else - show_error("Årsredovisning", uresp); - free(uresp); - } - } + yyjson_val *org = od ? jget(yyjson_doc_get_root(od), "result") : NULL; + yyjson_val *fyj = yd ? jget(yyjson_doc_get_root(yd), "result") : NULL; + char *onr = org ? jstr_dup(oresp, "result.org_nr") : NULL; + char *city = org ? jstr_dup(oresp, "result.city") : NULL; + char *desc = org ? jstr_dup(oresp, "result.description") : NULL; + int64_t shares = org ? jint_val(oresp, "result.shares", 0) : 0; + char board_text[1024] = ""; + char first_board[128] = "[namn]"; + size_t nb = jarr_size(mresp, "result.items"); + for (size_t i = 0; i < nb && strlen(board_text) < 900; i++) { + char path[64]; + snprintf(path, sizeof path, "result.items.%zu.title", i); + char *ti = jstr_dup(mresp, path); + snprintf(path, sizeof path, "result.items.%zu.name", i); + char *nm = jstr_dup(mresp, path); + char line[256]; + snprintf(line, sizeof line, "%s %s\n\n", ti ? ti : "", + nm ? nm : ""); + strncat(board_text, line, + sizeof board_text - strlen(board_text) - 1); + if (i == 0) + snprintf(first_board, sizeof first_board, "%s", + nm && *nm ? nm : "[namn]"); + free(ti); + free(nm); } struct buf t; buf_init(&t); - if (is && bs) + if (bs && nyears > 0) fmt_arsredovisning(&t, a, onr && *onr ? onr : "[org.nr]", - city && *city ? city : "[Ort]", is, pis, bs, - div_ore); + city && *city ? city : "[Ort]", + desc ? desc : "", shares, fyj, board_text, + first_board, bs, years, nyears); else buf_line(&t, "Kunde inte läsa rapporterna."); buf_append(&t, "", 1); @@ -6279,13 +6685,14 @@ static void arsredovisning_screen(struct app *a) buf_free(&t); free(onr); free(city); - yyjson_doc_free(id); + free(desc); yyjson_doc_free(bd); - yyjson_doc_free(pid); - free(iresp); + yyjson_doc_free(od); + yyjson_doc_free(yd); free(bresp); free(oresp); - free(pis_resp); + free(yresp); + free(mresp); if (again != 1 && again != 2) return; } @@ -6314,6 +6721,8 @@ static void open_scene(struct app *a, const char *scene) company_screen(a); else if (strcmp(scene, "bokslut") == 0) bokslut_screen(a); + else if (strcmp(scene, "yearinfo") == 0) + yearinfo_screen(a); } static void do_reload(struct app *a, const char *self) @@ -6348,10 +6757,10 @@ static void do_reload(struct app *a, const char *self) } static const char *const MAIN_ITEMS[] = { - "Verifikat", "Underlag (inkorg)", "Ingående balans", - "Mallar", "Rapporter", "Revision", - "Inställningar", "Företagsuppgifter", "Bokslut", - "Räkenskapsår", "Logga ut / avsluta", + "Verifikat", "Underlag (inkorg)", "Ingående balans", + "Mallar", "Rapporter", "Revision", + "Inställningar", "Företagsuppgifter", "Bokslut", + "Information om året", "Räkenskapsår", "Logga ut / avsluta", }; static void dashboard(struct app *a) @@ -6360,7 +6769,7 @@ static void dashboard(struct app *a) for (;;) { if (g_reload) return; - int sel = menu("Bokf", MAIN_ITEMS, 11, 1, &last_sel); + int sel = menu("Bokf", MAIN_ITEMS, 12, 1, &last_sel); snprintf(g_scene, sizeof g_scene, "%s", "dashboard"); if (g_quit || g_reload) return; @@ -6401,9 +6810,12 @@ static void dashboard(struct app *a) open_scene(a, "bokslut"); break; case 9: - select_fiscal_year(a); + open_scene(a, "yearinfo"); break; case 10: + select_fiscal_year(a); + break; + case 11: return; } } @@ -6741,8 +7153,8 @@ static void usage(FILE *f) " --fy ID select fiscal year directly\n" " --session ID reuse an open session (or BOKFD_SESSION)\n" " --screen NAME start in a view: dashboard, vouchers, inbox,\n" - " ib, templates, reports, bokslut, audit,\n" - " settings, company\n" + " ib, templates, reports, bokslut, yearinfo,\n" + " audit, settings, company\n" " --version\n"); } |
