aboutsummaryrefslogtreecommitdiff
path: root/clients/screens_settings.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-20 22:00:01 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-20 22:00:01 +0200
commit10a535ace098144980d0ffd1593f6384c39cc221 (patch)
tree53a7c6c5026d479c4b6d5fd4e5b341474cd190fa /clients/screens_settings.c
parent3a5a6d3b04dc4e17a42225afb243eecb3502ead6 (diff)
downloadbokf-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.c424
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;
+ }
+}