diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-18 08:38:33 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-18 08:38:33 +0200 |
| commit | 702d9327e5f979deff392dceeced2dd16d1203e1 (patch) | |
| tree | a07e73525be134bbd0c92f7128e5501306660232 | |
| parent | 2a98cce8434f7ea27f777d9e6422a1eb92ba8be7 (diff) | |
| download | bokf-545826c1b714cf5a8f4b70fc1f9e5ee0961c7855.tar.gz bokf-545826c1b714cf5a8f4b70fc1f9e5ee0961c7855.zip | |
bokftui: Företagsuppgifter screen; org.update dry-runv0.1.7
- owner-editable company details form (read-only for other roles)
- org.update honors dry_run; covered in tests
| -rw-r--r-- | clients/bokftui.c | 161 | ||||
| -rw-r--r-- | docs/PROTOCOL.md | 3 | ||||
| -rw-r--r-- | src/commands.c | 24 | ||||
| -rw-r--r-- | tests/test_core.c | 14 |
4 files changed, 197 insertions, 5 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c index 16c9941..ccbce14 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -3606,6 +3606,157 @@ static void ib_screen(struct app *a) } } +struct company_field { + const char *key; + const char *label; + int numeric; +}; + +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 }, + { "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); + return end && *end == '\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; +} + +static void company_screen(struct app *a) +{ + const int nf = (int)(sizeof COMPANY_FIELDS / sizeof COMPANY_FIELDS[0]); + 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 = xcalloc(nf, sizeof(char *)); + for (int i = 0; i < nf; i++) { + char path[64]; + snprintf(path, sizeof path, "result.%s", COMPANY_FIELDS[i].key); + if (COMPANY_FIELDS[i].numeric) { + char tmp[32]; + snprintf(tmp, sizeof tmp, "%lld", + (long long)jint_val(resp, path, 0)); + vals[i] = xstrdup(tmp); + } else { + char *v = jstr_dup(resp, path); + vals[i] = v ? v : xstrdup(""); + } + } + free(resp); + + int sel = 0; + int back = 0; + for (;;) { + frame("Företagsuppgifter"); + 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[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 == sel) { + attron(A_REVERSE); + mvaddnstr(5 + i, 2, line, COLS - 4); + attroff(A_REVERSE); + } else { + if (!owner) + attron(A_DIM); + mvaddnstr(5 + i, 2, line, COLS - 4); + if (!owner) + attroff(A_DIM); + } + } + hints(owner + ? "upp/ned Enter = ändra F5 = uppdatera" + " Esc/q = tillbaka Ctrl+C = avsluta" + : "upp/ned F5 = uppdatera Esc/q = tillbaka" + " Ctrl+C = avsluta (endast ägare kan ändra)"); + 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 (!owner) { + message("Företagsuppgifter", + "Endast ägare kan ändra företagsuppgifterna."); + continue; + } + char label[160]; + snprintf(label, sizeof label, "%s: ", + COMPANY_FIELDS[sel].label); + char *val = prompt(label, vals[sel], 0); + if (!val) + continue; + if (!company_field_valid(&COMPANY_FIELDS[sel], val)) { + message("Ogiltigt värde", "%s", + COMPANY_FIELDS[sel].label); + 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); + if (COMPANY_FIELDS[sel].numeric) + yyjson_mut_obj_add_int(d, o, COMPANY_FIELDS[sel].key, + strtoll(val, NULL, 10)); + else + yyjson_mut_obj_add_strcpy(d, o, COMPANY_FIELDS[sel].key, + val); + char *args = yyjson_mut_write(d, 0, NULL); + yyjson_mut_doc_free(d); + char *r = client_rpc(&a->conn, "org.update", a->session, + a->org, args); + free(args); + 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]); + free(vals); + if (back || g_quit) + return; + } +} + static const char *const SETTING_KEYS[] = { "default_series", "attachment_dir" }; static const char *const SETTING_LABELS[] = { "Standardserie", @@ -3785,13 +3936,14 @@ static void templates_screen(struct app *a) static const char *const MAIN_ITEMS[] = { "Verifikat", "Underlag (inkorg)", "Ingående balans", "Mallar", "Rapporter", "Revision", - "Inställningar", "Byt räkenskapsår", "Logga ut / avsluta", + "Inställningar", "Företagsuppgifter", "Byt räkenskapsår", + "Logga ut / avsluta", }; static void dashboard(struct app *a) { for (;;) { - int sel = menu("Bokf", MAIN_ITEMS, 9, 1); + int sel = menu("Bokf", MAIN_ITEMS, 10, 1); if (g_quit) return; if (sel == -4) { @@ -3825,9 +3977,12 @@ static void dashboard(struct app *a) settings_screen(a); break; case 7: - select_fiscal_year(a); + company_screen(a); break; case 8: + select_fiscal_year(a); + break; + case 9: return; } } diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index f3d7cac..cc2cb57 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -418,6 +418,9 @@ commands. Implemented screens (0.1.0-dev): - **Underlag** — inbox of unlinked attachments; `a` uploads a file. - **Rapporter** — saldobalans, resultaträkning, balansräkning, moms and kontolista (all accounts with type, moms treatment, SRU and status). +- **Företagsuppgifter** — the org record (name, org number, VAT number, + address, e-mail, phone, moms period, framework, fiscal-year start month), + editable in place; only owners can save, others see it read-only. - **Revision** — chain verification and behandlingshistorik. **Ctrl+N is the universal "add" key**: it starts a new verifikat from the diff --git a/src/commands.c b/src/commands.c index 948c42c..64212f7 100644 --- a/src/commands.c +++ b/src/commands.c @@ -704,6 +704,28 @@ static yyjson_mut_val *h_org_update(struct req *r) strcmp(framework, "K3") != 0) return fail(r, "INVALID_ARGS", "framework must be K2 or K3"); + static const char *const keys[9] = { "name", "org_nr", "vat_nr", + "address", "postal_code", "city", + "country", "email", "phone" }; + const char *texts[9] = { name, org_nr, vat_nr, address, postal, + city, country, email, phone }; + + if (r->dry_run) { + yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); + for (int i = 0; i < 9; i++) + if (texts[i]) + yyjson_mut_obj_add_strcpy(r->rdoc, o, keys[i], texts[i]); + if (fy_month) + yyjson_mut_obj_add_int(r->rdoc, o, "fiscal_year_start_month", + fy_month); + if (moms) + yyjson_mut_obj_add_strcpy(r->rdoc, o, "moms_period", moms); + if (framework) + yyjson_mut_obj_add_strcpy(r->rdoc, o, "framework", framework); + yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true); + return o; + } + sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( r->db, @@ -721,8 +743,6 @@ static yyjson_mut_val *h_org_update(struct req *r) -1, &st, NULL) != SQLITE_OK) return fail(r, "INTERNAL", "database error"); sqlite3_bind_int64(st, 1, r->org_id); - const char *texts[9] = { name, org_nr, vat_nr, address, postal, - city, country, email, phone }; for (int i = 0; i < 9; i++) { if (texts[i]) sqlite3_bind_text(st, i + 2, texts[i], -1, SQLITE_TRANSIENT); diff --git a/tests/test_core.c b/tests/test_core.c index bf5bfe4..fa808ac 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -421,6 +421,20 @@ int main(void) CHECK_STR(d, "error.code", "INVALID_ARGS"); yyjson_doc_free(d); + d = call(reqf("{\"v\":1,\"id\":\"8e\",\"cmd\":\"org.update\"," + "\"session\":\"%s\",\"org\":%d,\"dry_run\":true,\"args\":" + "{\"city\":\"Nyköping\"}}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK(jbool(d, "result.dry_run")); + yyjson_doc_free(d); + d = call(reqf("{\"v\":1,\"id\":\"8f\",\"cmd\":\"org.get\"," + "\"session\":\"%s\",\"org\":%d}", + g_session, (int)org_id)); + CHECK_OK(d); + CHECK_STR(d, "result.city", "Sollentuna"); + yyjson_doc_free(d); + d = call(reqf("{\"v\":1,\"id\":\"9\",\"cmd\":\"session.use_org\"," "\"session\":\"%s\",\"args\":{\"org\":%d}}", g_session, (int)org_id)); |
