summaryrefslogtreecommitdiff
path: root/clients/bokftui.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-18 23:41:59 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-18 23:41:59 +0200
commit49e44668c14abacef17f0f11be39cbd0faa7ecb9 (patch)
treedd8248143def675fd033abf390c06567f78d5c4c /clients/bokftui.c
parente9fc91dbc8314a25a0d71b7ee4344d3fc4ccfd5e (diff)
downloadbokf-49e44668c14abacef17f0f11be39cbd0faa7ecb9.tar.gz
bokf-49e44668c14abacef17f0f11be39cbd0faa7ecb9.zip
bokslut, INK2/SRU export and attachment markersv0.1.30
- bokslut.post: year-end bookings (manual entries, periodiseringsfond, skatt at a given rate, resultatdisposition) with a dry-run plan through the normal ledger path; the resultatrapport separates bokslutsdispositioner and skatt per K2. - sru.export: INFO.SRU + BLANKETTER.SRU (INK2/INK2R/INK2S) from the official 2025P4 field tables and the BAS mapping, with manual INK2S adjustments, submitter defaults and unmapped-account detection; the TUI writes both files from Rapporter -> Inkomstdeklaration. - voucher.list carries attachment_count and the voucher list marks vouchers with underlag with an x column. - date_prompt restores the cursor state so it stops blinking at the bottom after the report date prompts.
Diffstat (limited to 'clients/bokftui.c')
-rw-r--r--clients/bokftui.c214
1 files changed, 144 insertions, 70 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c
index d446974..6c50b1b 100644
--- a/clients/bokftui.c
+++ b/clients/bokftui.c
@@ -373,7 +373,7 @@ static char *date_prompt(const char *label, const char *def)
attroff(A_BOLD);
size_t pos = (size_t)-1;
int fresh = 1;
- curs_set(1);
+ int prev = curs_set(1);
for (;;) {
move(y, (int)strlen(label) + 3);
clrtoeol();
@@ -384,10 +384,14 @@ static char *date_prompt(const char *label, const char *def)
move(y, (int)strlen(label) + 3 + (int)disp_width_n(buf, cp));
refresh();
int ch = ui_getch();
- if (ch == '\n' || ch == '\r' || ch == KEY_ENTER)
+ if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) {
+ curs_set(prev == ERR ? 0 : prev);
return buf;
- if (ch == 27)
+ }
+ if (ch == 27) {
+ curs_set(prev == ERR ? 0 : prev);
return NULL;
+ }
date_field_edit(buf, sizeof buf, &pos, ch, &fresh);
}
}
@@ -2599,8 +2603,12 @@ static void vouchers_screen(struct app *a)
char *date = jstr_dup(resp, path);
snprintf(path, sizeof path, "result.items.%zu.description", i);
char *desc = jstr_dup(resp, path);
- snprintf(line, sizeof line, "%s %-*s %s", date ? date : "",
- idw, idstr[i], desc ? desc : "");
+ snprintf(path, sizeof path, "result.items.%zu.attachment_count",
+ i);
+ int atts = (int)jint_val(resp, path, 0);
+ snprintf(line, sizeof line, "%c %s %-*s %s",
+ atts ? 'x' : ' ', date ? date : "", idw, idstr[i],
+ desc ? desc : "");
items[i] = xstrdup(line);
free(date);
free(desc);
@@ -3161,6 +3169,47 @@ static void is_print_group(struct buf *t, yyjson_val *res, yyjson_val *prev,
*pre = sp;
}
+static int is_range_has(yyjson_val *res, int lo, int hi)
+{
+ for (int k = 0; k < 2; k++) {
+ yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++)
+ if (acct_in(vstr(yyjson_arr_get(arr, i), "account"), lo, hi))
+ return 1;
+ }
+ return 0;
+}
+
+/* Rows in one account range, printed with the previous-year column; the
+ signed display sums are added to *sum / *sum_pre. */
+static void is_range_rows(struct buf *t, yyjson_val *res, yyjson_val *prev,
+ int lo, int hi, int64_t *sum, int64_t *sum_pre)
+{
+ for (int k = 0; k < 2; k++) {
+ yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
+ yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
+ size_t n = yyjson_arr_size(arr);
+ for (size_t i = 0; i < n; i++) {
+ yyjson_val *row = yyjson_arr_get(arr, i);
+ const char *acc = vstr(row, "account");
+ if (!acct_in(acc, lo, hi))
+ continue;
+ int64_t c = k == 0 ? vint(row, "amount_ore")
+ : -vint(row, "amount_ore");
+ int64_t p = is_prev_lookup(prev, acc, k != 0);
+ char nm[128], a1[48], a2[48];
+ pad_label(nm, sizeof nm, vstr(row, "name"), 48);
+ amt_col(a1, sizeof a1, 15, c);
+ amt_col(a2, sizeof a2, 15, p);
+ buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2);
+ *sum += c;
+ *sum_pre += p;
+ }
+ }
+}
+
static void fmt_resultat(struct buf *t, const struct app *a, yyjson_val *res,
yyjson_val *prev)
{
@@ -3205,45 +3254,14 @@ static void fmt_resultat(struct buf *t, const struct app *a, yyjson_val *res,
}
is_summary(t, "Summa rörelsekostnader", exp, exp_pre, 0);
- /* finansiella poster */
- int64_t fin = 0, fin_pre = 0, booked = 0, booked_pre = 0;
- int has_fin = 0, has_booked = 0;
- for (int k = 0; k < 2; k++) {
- yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
- yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
- size_t n = yyjson_arr_size(arr);
- for (size_t i = 0; i < n; i++) {
- yyjson_val *row = yyjson_arr_get(arr, i);
- const char *acc = vstr(row, "account");
- if (acct_in(acc, 8000, 8899))
- has_fin = 1;
- else if (acct_in(acc, 8900, 8999))
- has_booked = 1;
- }
- }
- if (has_fin) {
+ int64_t fin = 0, fin_pre = 0, disp = 0, disp_pre = 0;
+ int64_t tax = 0, tax_pre = 0, booked = 0, booked_pre = 0;
+ int has_disp = is_range_has(res, 8800, 8899);
+ int has_tax = is_range_has(res, 8900, 8989);
+ int has_booked = is_range_has(res, 8999, 8999);
+ if (is_range_has(res, 8000, 8599)) {
buf_line(t, "\nFinansiella poster\n");
- for (int k = 0; k < 2; k++) {
- yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
- yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
- size_t n = yyjson_arr_size(arr);
- for (size_t i = 0; i < n; i++) {
- yyjson_val *row = yyjson_arr_get(arr, i);
- const char *acc = vstr(row, "account");
- if (!acct_in(acc, 8000, 8899))
- continue;
- int64_t c = k == 0 ? vint(row, "amount_ore")
- : -vint(row, "amount_ore");
- int64_t p = is_prev_lookup(prev, acc, k != 0);
- char nm[128], a1[48], a2[48];
- pad_label(nm, sizeof nm, vstr(row, "name"), 48);
- amt_col(a1, sizeof a1, 15, c);
- amt_col(a2, sizeof a2, 15, p);
- buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2);
- fin += c;
- fin_pre += p;
- }
- }
+ is_range_rows(t, res, prev, 8000, 8599, &fin, &fin_pre);
is_summary(t, "Summa", fin, fin_pre, 0);
}
int64_t ror = rev + exp, ror_pre = rev_pre + exp_pre;
@@ -3252,41 +3270,34 @@ static void fmt_resultat(struct buf *t, const struct app *a, yyjson_val *res,
buf_rule(t, IS_RULE_W);
is_summary(t, "Rörelseresultat", ror, ror_pre, 0);
is_summary(t, "Resultat efter finansiella poster", after, after_pre, 1);
- is_summary(t, "Resultat före skatt", after, after_pre, 1);
+ if (has_disp) {
+ buf_line(t, "\nBokslutsdispositioner\n");
+ is_range_rows(t, res, prev, 8800, 8899, &disp, &disp_pre);
+ is_summary(t, "Summa", disp, disp_pre, 0);
+ }
+ int64_t before_tax = after + disp, before_tax_pre = after_pre + disp_pre;
+ is_summary(t, "Resultat före skatt", before_tax, before_tax_pre, 1);
+ if (has_tax) {
+ buf_line(t, "\nSkatt\n");
+ is_range_rows(t, res, prev, 8900, 8989, &tax, &tax_pre);
+ is_summary(t, "Summa", tax, tax_pre, 0);
+ }
+ int64_t ars = before_tax + tax, ars_pre = before_tax_pre + tax_pre;
buf_line(t, "\n");
buf_rule(t, IS_RULE_W);
- is_summary(t, "Årets resultat", after, after_pre, 0);
+ is_summary(t, "Årets resultat", ars, ars_pre, 0);
if (has_booked) {
- buf_line(t, "Bokfört resultat\n");
- for (int k = 0; k < 2; k++) {
- yyjson_val *sec = yyjson_obj_get(res, IS_KEYS[k]);
- yyjson_val *arr = sec ? yyjson_obj_get(sec, "accounts") : NULL;
- size_t n = yyjson_arr_size(arr);
- for (size_t i = 0; i < n; i++) {
- yyjson_val *row = yyjson_arr_get(arr, i);
- const char *acc = vstr(row, "account");
- if (!acct_in(acc, 8900, 8999))
- continue;
- int64_t c = -vint(row, "amount_ore");
- int64_t p = is_prev_lookup(prev, acc, 1);
- char nm[128], a1[48], a2[48];
- pad_label(nm, sizeof nm, vstr(row, "name"), 48);
- amt_col(a1, sizeof a1, 15, c);
- amt_col(a2, sizeof a2, 15, p);
- buf_line(t, " %5s %s %s %s %s\n", acc, nm, a1, a1, a2);
- booked += c;
- booked_pre += p;
- }
- }
+ buf_line(t, "\nBokfört resultat\n");
+ is_range_rows(t, res, prev, 8999, 8999, &booked, &booked_pre);
is_summary(t, "Summa", booked, booked_pre, 0);
buf_line(t, "\n");
buf_rule(t, IS_RULE_W);
- is_summary(t, "Ej bokfört resultat", after + booked,
- after_pre + booked_pre, 0);
+ is_summary(t, "Ej bokfört resultat", ars + booked,
+ ars_pre + booked_pre, 0);
} else {
buf_line(t, "\n");
buf_rule(t, IS_RULE_W);
- is_summary(t, "Ej bokfört resultat", after, after_pre, 0);
+ is_summary(t, "Ej bokfört resultat", ars, ars_pre, 0);
}
}
@@ -3619,6 +3630,64 @@ static void eskd_save(struct app *a, const char *from, const char *to)
free(resp);
}
+static void sru_save(struct app *a)
+{
+ const char *home = getenv("HOME");
+ char def[512];
+ snprintf(def, sizeof def, "%s/Downloads", home && *home ? home : ".");
+ struct stat sb;
+ if (stat(def, &sb) != 0 || !S_ISDIR(sb.st_mode))
+ snprintf(def, sizeof def, "%s", home && *home ? home : ".");
+ char *dir = prompt("Spara SRU-filer i: ", def, 0);
+ if (!dir || !*dir)
+ return;
+ char full[512];
+ if (dir[0] == '~' && (dir[1] == '/' || dir[1] == '\0'))
+ snprintf(full, sizeof full, "%s%s", home ? home : "", dir + 1);
+ else
+ snprintf(full, sizeof full, "%s", dir);
+ char args[64];
+ snprintf(args, sizeof args, "{\"fiscal_year\":%lld}", (long long)a->fy);
+ char *resp = client_rpc(&a->conn, "sru.export", a->session, a->org, args);
+ if (!resp || !client_ok(resp)) {
+ show_error("Kunde inte skapa SRU-filerna", resp);
+ free(resp);
+ return;
+ }
+ const char *keys[2] = { "result.info.content_base64",
+ "result.blanketter.content_base64" };
+ const char *names[2] = { "INFO.SRU", "BLANKETTER.SRU" };
+ char saved[2][600] = { "", "" };
+ int ok = 0;
+ for (int i = 0; i < 2; i++) {
+ char *b64 = jstr_dup(resp, keys[i]);
+ unsigned char *data = NULL;
+ size_t n = 0;
+ if (!b64 || util_b64_decode(b64, strlen(b64), &data, &n) != 0) {
+ free(b64);
+ continue;
+ }
+ snprintf(saved[i], sizeof saved[i], "%s/%s", full, names[i]);
+ FILE *f = fopen(saved[i], "wb");
+ if (f && fwrite(data, 1, n, f) == n) {
+ fclose(f);
+ ok++;
+ } else {
+ if (f)
+ fclose(f);
+ message("SRU", "Kunde inte skriva %s: %s", saved[i],
+ strerror(errno));
+ }
+ free(data);
+ free(b64);
+ }
+ free(resp);
+ if (ok == 2)
+ message("SRU", "Sparat:\n%s\n%s\n\nLadda upp båda i Skatteverkets"
+ " filöverföring.",
+ saved[0], saved[1]);
+}
+
static void reports_screen(struct app *a)
{
static const char *const report_items[] = {
@@ -3628,16 +3697,21 @@ static void reports_screen(struct app *a)
"Huvudbok",
"Verifikationslista",
"Momsdeklaration",
+ "Inkomstdeklaration (INK2/SRU)",
"Kontolista (alla konton)",
};
static int last_sel = 0;
for (;;) {
if (g_quit)
return;
- int sel = menu("Rapporter", report_items, 7, 0, &last_sel);
+ int sel = menu("Rapporter", report_items, 8, 0, &last_sel);
if (sel < 0)
return;
if (sel == 6) {
+ sru_save(a);
+ continue;
+ }
+ if (sel == 7) {
accounts_report(a);
continue;
}