diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-20 22:00:01 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-20 22:00:01 +0200 |
| commit | 10a535ace098144980d0ffd1593f6384c39cc221 (patch) | |
| tree | 53a7c6c5026d479c4b6d5fd4e5b341474cd190fa /clients/screens_ib.c | |
| parent | 3a5a6d3b04dc4e17a42225afb243eecb3502ead6 (diff) | |
| download | bokf-10a535ace098144980d0ffd1593f6384c39cc221.tar.gz bokf-10a535ace098144980d0ffd1593f6384c39cc221.zip | |
tui: split bokftui into screen files
Diffstat (limited to 'clients/screens_ib.c')
| -rw-r--r-- | clients/screens_ib.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/clients/screens_ib.c b/clients/screens_ib.c new file mode 100644 index 0000000..ca35562 --- /dev/null +++ b/clients/screens_ib.c @@ -0,0 +1,354 @@ +#include <dirent.h> +#include <errno.h> +#include <locale.h> +#include <math.h> +#include <ncursesw/ncurses.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "client.h" +#include "tui.h" +#include "formula.h" +#include "util.h" +#include "version.h" +#include "yyjson.h" +#include "ui.h" + +/* ------------------------------------------------------------------ */ +/* ingående balans (series IB) */ +/* ------------------------------------------------------------------ */ + +struct tui_ibrow { + char account[16]; + char amount[32]; + char text[64]; +}; + +/* Loads the net IB per account for the active fiscal year. */ +/* Effective opening balances of the selected year: the year's IB vouchers + plus all earlier movements, exactly what the reports show. */ +static int ib_load(struct app *a, char ***out_acc, int64_t **out_amt, + int *out_n) +{ + *out_acc = NULL; + *out_amt = NULL; + *out_n = 0; + char args[128]; + snprintf(args, sizeof args, + "{\"fiscal_year\":%lld,\"include_zero\":true}", + (long long)a->fy); + char *resp = client_rpc(&a->conn, "report.trial_balance", a->session, + a->org, args); + if (!resp || !client_ok(resp)) { + show_error("Ingående balans", resp); + free(resp); + return -1; + } + size_t n = jarr_size(resp, "result.accounts"); + char **accs = xcalloc(n ? n : 1, sizeof(char *)); + int64_t *amts = xcalloc(n ? n : 1, sizeof(int64_t)); + int count = 0; + for (size_t i = 0; i < n; i++) { + char path[64]; + snprintf(path, sizeof path, "result.accounts.%zu.ib_ore", i); + int64_t ib = jint_val(resp, path, 0); + if (ib == 0) + continue; + snprintf(path, sizeof path, "result.accounts.%zu.account", i); + char *acc = jstr_dup(resp, path); + if (!acc) + continue; + accs[count] = acc; + amts[count] = ib; + count++; + } + free(resp); + *out_acc = accs; + *out_amt = amts; + *out_n = count; + return 0; +} + +static int64_t ib_old_lookup(char **accs, int64_t *amts, int n, + const char *account) +{ + for (int i = 0; i < n; i++) + if (strcmp(accs[i], account) == 0) + return amts[i]; + return 0; +} + +struct ibform { + struct app *a; + struct tui_rt rt; + struct tui_ibrow rows[64]; + char **old_acc; + int64_t *old_amt; + int nold; +}; + +static void ib_cell(void *ud, int row, int col, char **buf, size_t *cap) +{ + struct ibform *ib = ud; + struct tui_ibrow *r = &ib->rows[row]; + if (col == 0) { + *buf = r->account; + *cap = sizeof r->account; + } else if (col == 2) { + *buf = r->amount; + *cap = sizeof r->amount; + } else { + *buf = r->text; + *cap = sizeof r->text; + } +} + +static void ib_info(void *ud, int row, char *buf, size_t n) +{ + struct ibform *ib = ud; + const char *nm = acct_name(ib->a, ib->rows[row].account); + snprintf(buf, n, "%s", nm ? nm : ""); +} + +static void ib_footer(void *ud, char *buf, size_t n) +{ + (void)ud; + snprintf(buf, n, "Positiva belopp = debet, negativa = kredit. Summan ska" + " bli noll."); +} + +static const char *ib_check(struct ibform *ib, int *used_out, char *msg, + size_t msglen) +{ + int64_t sum = 0; + int used = 0; + for (int i = 0; i < ib->rt.nrows; i++) { + if (!ib->rows[i].account[0]) + continue; + if (!acct_exists(ib->a, ib->rows[i].account)) { + snprintf(msg, msglen, "Konto %.15s finns inte (rad %d).", + ib->rows[i].account, i + 1); + return msg; + } + double kr = 0; + if (ib->rows[i].amount[0] && + !parse_x_double(ib->rows[i].amount, &kr)) { + snprintf(msg, msglen, "Ogiltigt belopp på rad %d: '%.20s'.", i + 1, + ib->rows[i].amount); + return msg; + } + sum += (int64_t)llround(kr * 100.0); + used++; + } + if (used == 0) + return "Ange minst ett konto."; + if (sum != 0) { + snprintf(msg, msglen, "Summan måste bli noll (nu %lld öre).", + (long long)sum); + return msg; + } + *used_out = used; + return NULL; +} + +/* Posts the delta against the existing IB so nothing is ever edited. */ +static int ib_post_delta(struct ibform *ib) +{ + struct app *a = ib->a; + 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, "date", a->fy_start); + yyjson_mut_obj_add_strcpy(d, o, "description", "Ingående balans"); + yyjson_mut_obj_add_strcpy(d, o, "series", "IB"); + char *ref = util_random_id("tui-", 8); + yyjson_mut_obj_add_strcpy(d, o, "client_ref", ref); + free(ref); + yyjson_mut_val *arr = yyjson_mut_arr(d); + int posted = 0; + for (int i = 0; i < ib->rt.nrows; i++) { + if (!ib->rows[i].account[0]) + continue; + double kr = 0; + if (ib->rows[i].amount[0] && + !parse_x_double(ib->rows[i].amount, &kr)) + kr = 0; + int64_t new_amt = (int64_t)llround(kr * 100.0); + int64_t prev_amt = + ib_old_lookup(ib->old_acc, ib->old_amt, ib->nold, + ib->rows[i].account); + int64_t delta = new_amt - prev_amt; + if (delta == 0) + continue; + yyjson_mut_val *ro = yyjson_mut_arr_add_obj(d, arr); + yyjson_mut_obj_add_strcpy(d, ro, "account", ib->rows[i].account); + yyjson_mut_obj_add_int(d, ro, "debit_ore", delta > 0 ? delta : 0); + yyjson_mut_obj_add_int(d, ro, "credit_ore", delta < 0 ? -delta : 0); + if (ib->rows[i].text[0]) + yyjson_mut_obj_add_strcpy(d, ro, "description", + ib->rows[i].text); + posted++; + } + for (int i = 0; i < ib->nold; i++) { + int still = 0; + for (int j = 0; j < ib->rt.nrows; j++) + if (strcmp(ib->rows[j].account, ib->old_acc[i]) == 0) { + still = 1; + break; + } + if (!still && ib->old_amt[i] != 0) { + yyjson_mut_val *ro = yyjson_mut_arr_add_obj(d, arr); + yyjson_mut_obj_add_strcpy(d, ro, "account", ib->old_acc[i]); + yyjson_mut_obj_add_int(d, ro, "debit_ore", + ib->old_amt[i] < 0 ? -ib->old_amt[i] : 0); + yyjson_mut_obj_add_int(d, ro, "credit_ore", + ib->old_amt[i] > 0 ? ib->old_amt[i] : 0); + posted++; + } + } + if (posted == 0) { + yyjson_mut_doc_free(d); + tui_message("Ingående balans", "Ingen ändring."); + return 0; + } + yyjson_mut_obj_add_val(d, o, "rows", arr); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *resp = + client_rpc(&a->conn, "voucher.post", a->session, a->org, args); + free(args); + if (resp && client_ok(resp)) { + int64_t num = jint_val(resp, "result.number", 0); + tui_message("Ingående balans", + "Sparat som IB %lld (%d justerade rader).", + (long long)num, posted); + free(resp); + return 1; + } + show_error("Kunde inte spara ingående balans", resp); + free(resp); + return 0; +} + +static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) +{ + struct ibform ib; + memset(&ib, 0, sizeof ib); + ib.a = a; + ib.old_acc = old_acc; + ib.old_amt = old_amt; + ib.nold = nold; + if (nold > 63) { + tui_message("Ingående balans", + "För många konton (%d) för att redigera här.", nold); + return 0; + } + for (int i = 0; i < nold && i < 63; i++) { + snprintf(ib.rows[i].account, sizeof ib.rows[i].account, "%s", + old_acc[i]); + tui_kr_format(old_amt[i], ib.rows[i].amount, + sizeof ib.rows[i].amount); + } + int text_w = COLS - 51; + if (text_w < 6) + text_w = 6; + struct tui_rt_col cols[4] = { + { "Konto", 2, 6, TUI_RT_EDIT }, + { "Namn", 9, 24, TUI_RT_INFO }, + { "Belopp", 34, 14, TUI_RT_EDIT }, + { "Text", 49, text_w, TUI_RT_EDIT }, + }; + tui_rt_init(&ib.rt, nold > 0 ? nold : 1, 64, 4, cols, 5, ib_cell, ib_info, + &ib); + tui_rt_set_footer(&ib.rt, ib_footer); + + for (;;) { + int rr = tui_rt_run("Ingående balans", &ib.rt, + "Tab = byta fält F5 = validera ^X = rensa rad" + " ^Enter = spara Esc = avbryt"); + if (rr == -1) + return 0; + char msg[256]; + int used = 0; + const char *problem = ib_check(&ib, &used, msg, sizeof msg); + if (problem) { + tui_message("Ingående balans", "%s", problem); + continue; + } + if (rr == -2) { + tui_message("Validering OK", "%d konton, summan är noll.", used); + continue; + } + if (ib_post_delta(&ib)) + return 1; + } +} + +struct ibpager { + struct app *a; + char **accs; + int64_t *amts; + int n; +}; + +static int ib_view_key(void *ud, int ch) +{ + struct ibpager *p = ud; + if (ch == 'e' || ch == 'E' || ch == TUI_KEY_CTRL_N) { + if (ib_form(p->a, p->accs, p->amts, p->n)) + return TUI_HOOK_REFRESH; + return TUI_HOOK_STAY; + } + return TUI_HOOK_NONE; +} + +/* The list is a read-only table with no selection: tui_pager shows the + columns and the sum as plain text, and the e/^N keys run the editor. */ +void ib_screen(struct app *a) +{ + for (;;) { + char **accs = NULL; + int64_t *amts = NULL; + int n = 0; + if (ib_load(a, &accs, &amts, &n) != 0) + return; + struct buf t; + buf_init(&t); + buf_line(&t, "Räkenskapsår %s ingående balans (effektiv)\n\n", + a->fy_label); + buf_line(&t, "%-7s%-53s%s\n\n", "Konto", "Namn", "Belopp"); + int64_t sum = 0; + for (int i = 0; i < n; i++) { + sum += amts[i]; + char amount[32], nbuf[128]; + tui_kr_format(amts[i], amount, sizeof amount); + const char *nm = acct_name(a, accs[i]); + snprintf(nbuf, sizeof nbuf, "%s", nm ? nm : ""); + tui_pad_field(nbuf, sizeof nbuf, 50); + buf_line(&t, "%-6s %s %14s\n", accs[i], nbuf, amount); + } + if (n == 0) + buf_line(&t, "Ingen ingående balans registrerad ännu.\n"); + char sumbuf[32]; + tui_kr_format(sum, sumbuf, sizeof sumbuf); + buf_line(&t, "\nSumma: %s\n", sumbuf); + buf_append(&t, "\0", 1); + struct ibpager p = { a, accs, amts, n }; + int ret = tui_pager_hook("Ingående balans", (const char *)t.p, + "e/^N = redigera", 0, ib_view_key, &p); + buf_free(&t); + for (int i = 0; i < n; i++) + free(accs[i]); + free(accs); + free(amts); + if (ret == 0) + return; + } +} |
