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_settings.c | |
| parent | 3a5a6d3b04dc4e17a42225afb243eecb3502ead6 (diff) | |
| download | bokf-10a535ace098144980d0ffd1593f6384c39cc221.tar.gz bokf-10a535ace098144980d0ffd1593f6384c39cc221.zip | |
tui: split bokftui into screen files
Diffstat (limited to 'clients/screens_settings.c')
| -rw-r--r-- | clients/screens_settings.c | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/clients/screens_settings.c b/clients/screens_settings.c new file mode 100644 index 0000000..6440150 --- /dev/null +++ b/clients/screens_settings.c @@ -0,0 +1,424 @@ +#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" + +struct company_field { + const char *key; + const char *label; + int numeric; /* 0 = text, 1 = month (1-12), 2 = unsigned integer */ +}; + +static const struct company_field COMPANY_FIELDS[] = { + { "name", "Företagsnamn", 0 }, + { "org_nr", "Organisationsnummer", 0 }, + { "vat_nr", "Momsregistreringsnummer", 0 }, + { "address", "Adress", 0 }, + { "postal_code", "Postnummer", 0 }, + { "city", "Ort", 0 }, + { "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 }, +}; + +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); + 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 || + strcmp(v, "year") == 0; + if (strcmp(f->key, "framework") == 0) + return strcmp(v, "K2") == 0 || strcmp(v, "K3") == 0; + 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 = tui_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 ansbuf[512]; char *ans = tui_prompt_into(ansbuf, sizeof ansbuf, q, "n", 0) ? ansbuf : NULL; + 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)) { + tui_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[512] = "", titlebuf[512] = ""; + int ok_name = tui_prompt_into(namebuf, sizeof namebuf, "Namn: ", + "", 0); + int ok_title = ok_name && namebuf[0] + ? tui_prompt_into(titlebuf, sizeof titlebuf, + "Titel: ", + "Styrelseledamot", 0) + : 0; + if (ok_name && namebuf[0] && ok_title && titlebuf[0]) { + 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", titlebuf); + 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)) + tui_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) { + tui_message("Styrelseledamöter", + "Endast ägare kan ändra styrelseledamöterna."); + } else { + char namebuf[512] = "", titlebuf[512] = ""; + int ok_name = tui_prompt_into(namebuf, sizeof namebuf, + "Namn: ", + names[s] ? names[s] : "", 0); + int ok_title = ok_name && namebuf[0] + ? tui_prompt_into( + titlebuf, sizeof titlebuf, "Titel: ", + titles[s] ? titles[s] : "", 0) + : 0; + if (ok_name && namebuf[0] && ok_title && titlebuf[0]) { + 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", titlebuf); + 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)) + tui_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; + } +} + +void company_screen(struct app *a) +{ + static int sel = 0; + const int nf = (int)(sizeof COMPANY_FIELDS / sizeof COMPANY_FIELDS[0]); + const int ninv = 2; + static const char *const invkeys[2] = { "invoice_bankgiro", + "invoice_our_ref" }; + static const char *const invlabels[2] = { "Bankgiro", + "Vår referens (faktura)" }; + int owner = a->role[0] && strcmp(a->role, "owner") == 0; + for (;;) { + if (g_quit) + return; + char *resp = client_rpc(&a->conn, "org.get", a->session, a->org, "{}"); + if (!resp || !client_ok(resp)) { + show_error("Företagsuppgifter", resp); + free(resp); + return; + } + char *vals[20]; + struct tui_form_field ff[20]; + memset(ff, 0, sizeof ff); + for (int i = 0; i < nf; i++) { + char path[64]; + vals[i] = xcalloc(512, 1); + snprintf(path, sizeof path, "result.%s", COMPANY_FIELDS[i].key); + if (COMPANY_FIELDS[i].numeric) { + snprintf(vals[i], 512, "%lld", + (long long)jint_val(resp, path, 0)); + } else { + char *v = jstr_dup(resp, path); + snprintf(vals[i], 512, "%s", v ? v : ""); + free(v); + } + ff[i].label = COMPANY_FIELDS[i].label; + ff[i].value = vals[i]; + ff[i].cap = 512; + ff[i].kind = TUI_F_TEXT; + } + free(resp); + char *sresp = + client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); + for (int i = 0; i < ninv; i++) { + char path[64]; + int idx = nf + i; + vals[idx] = xcalloc(512, 1); + snprintf(path, sizeof path, "result.%s", invkeys[i]); + char *v = sresp ? jstr_dup(sresp, path) : NULL; + snprintf(vals[idx], 512, "%s", v ? v : ""); + free(v); + ff[idx].label = invlabels[i]; + ff[idx].value = vals[idx]; + ff[idx].cap = 512; + ff[idx].kind = TUI_F_TEXT; + } + free(sresp); + ff[nf + ninv].label = "Styrelseledamöter"; + ff[nf + ninv].kind = TUI_F_ACTION; + int r = tui_form_run("Företagsuppgifter", ff, nf + ninv + 1, owner, + &sel); + if (r >= 0 && r < nf) { + if (!company_field_valid(&COMPANY_FIELDS[r], vals[r])) { + tui_message("Ogiltigt värde", "%s", COMPANY_FIELDS[r].label); + } else { + yyjson_mut_doc *d = yyjson_mut_doc_new(NULL); + yyjson_mut_val *o = yyjson_mut_obj(d); + yyjson_mut_doc_set_root(d, o); + if (COMPANY_FIELDS[r].numeric) + yyjson_mut_obj_add_int(d, o, COMPANY_FIELDS[r].key, + strtoll(vals[r], NULL, 10)); + else + yyjson_mut_obj_add_strcpy(d, o, COMPANY_FIELDS[r].key, + vals[r]); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *rr = client_rpc(&a->conn, "org.update", a->session, + a->org, args); + free(args); + if (!rr || !client_ok(rr)) + show_error("Kunde inte spara", rr); + free(rr); + } + } else if (r >= nf && r < nf + ninv) { + 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, "key", invkeys[r - nf]); + yyjson_mut_obj_add_strcpy(d, o, "value", vals[r]); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *rr = client_rpc(&a->conn, "settings.set", a->session, + a->org, args); + free(args); + if (!rr || !client_ok(rr)) + show_error("Kunde inte spara", rr); + free(rr); + } + if (r == nf + ninv) + board_screen(a); + for (int i = 0; i < nf + ninv; i++) + free(vals[i]); + if (r == TUI_FORM_BACK) + return; + } +} + +static const char *const SETTING_KEYS[] = { + "default_series", + "attachment_dir", + "invoice_receivable_account", + "invoice_revenue_account", + "smtp_host", + "smtp_port", + "smtp_user", + "smtp_from", + "smtp_reply_to", + "smtp_security", + "smtp_password", +}; +static const char *const SETTING_LABELS[] = { + "Standardserie", + "Bilagornas mapp", + "Fordringskonto", + "Intäktskonto", + "SMTP-server", + "SMTP-port", + "SMTP-användare", + "SMTP-avsändare", + "SMTP-svarsadress", + "SMTP-säkerhet", + "SMTP-lösenord", +}; +static const char *const SECURITY_CHOICES[] = { "starttls", "tls", "plain" }; + +void settings_screen(struct app *a) +{ + static int sel = 0; + const int nset = (int)(sizeof SETTING_KEYS / sizeof SETTING_KEYS[0]); + for (;;) { + if (g_quit) + return; + char *resp = + client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); + if (!resp || !client_ok(resp)) { + show_error("Inställningar", resp); + free(resp); + return; + } + int pw_set = jbool_val(resp, "result.smtp_password_set", 0); + char pwstatus[192]; + snprintf(pwstatus, sizeof pwstatus, + "SMTP-lösenord (lämna tomt för oförändrat): %s", + pw_set ? "satt" : "inte satt"); + char *vals[16]; + struct tui_form_field ff[16]; + memset(ff, 0, sizeof ff); + int sec_choice = 0; + for (int i = 0; i < nset; i++) { + char path[64], *v; + vals[i] = xcalloc(512, 1); + snprintf(path, sizeof path, "result.%s", SETTING_KEYS[i]); + v = jstr_dup(resp, path); + snprintf(vals[i], 512, "%s", v ? v : ""); + free(v); + const char *key = SETTING_KEYS[i]; + ff[i].label = SETTING_LABELS[i]; + ff[i].value = vals[i]; + ff[i].cap = 512; + ff[i].kind = TUI_F_TEXT; + if (strcmp(key, "smtp_security") == 0) { + ff[i].kind = TUI_F_CHOICE; + ff[i].choices = SECURITY_CHOICES; + ff[i].nchoices = 3; + ff[i].choice = &sec_choice; + for (int k = 0; k < 3; k++) + if (strcmp(vals[i], SECURITY_CHOICES[k]) == 0) + sec_choice = k; + } else if (strcmp(key, "smtp_password") == 0) { + ff[i].mask = 1; + } + } + free(resp); + struct tui_form_action acts[1]; + memset(acts, 0, sizeof acts); + acts[0].label = pwstatus; + acts[0].enabled = -1; /* dim status row, not selectable */ + int r = tui_form_run_actions( + "Inställningar", ff, nset, 1, acts, 1, + "upp/ned/Home/End Enter = ändra F5 = uppdatera" + " Esc/q = tillbaka ^C = avsluta", + &sel); + if (r >= 0 && r < nset) { + const char *key = SETTING_KEYS[r]; + const char *val = vals[r]; + if (strcmp(key, "smtp_security") == 0) + val = SECURITY_CHOICES[sec_choice]; + if (strcmp(key, "smtp_password") == 0 && !*vals[r]) { + /* empty means "keep the stored secret" */ + } else { + 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, "key", key); + yyjson_mut_obj_add_strcpy(d, o, "value", val); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *rr = client_rpc(&a->conn, "settings.set", a->session, + a->org, args); + free(args); + if (rr && client_ok(rr)) { + snprintf(vals[r], 512, "%s", val); + if (strcmp(key, "default_series") == 0) + snprintf(a->default_series, + sizeof a->default_series, "%s", val); + else if (strcmp(key, "attachment_dir") == 0) + snprintf(a->attachment_dir, + sizeof a->attachment_dir, "%s", val); + } else { + show_error("Kunde inte spara inställningen", rr); + } + free(rr); + } + } + for (int i = 0; i < nset; i++) + free(vals[i]); + if (r == TUI_FORM_BACK) + return; + } +} |
