aboutsummaryrefslogtreecommitdiff
path: root/clients
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-19 21:10:54 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-19 21:10:54 +0200
commitbeb52c4a2ca23f29dc992e145810a47241c7d43b (patch)
treeb8fff8dce1cb01d42a1783040d7270558831bd2e /clients
parent77c298b3b64f2493e509734df0084068ad24441f (diff)
downloadbokf-0.1.34.tar.gz
bokf-0.1.34.zip
årsredovisning: Information om året per räkenskapsår (schema v6)v0.1.34
Diffstat (limited to 'clients')
-rw-r--r--clients/bokftui.c1252
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");
}