From 62a92f0af0442c78277c3c97c9efe6fef8145625 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Mon, 21 Sep 2026 21:43:44 +0200 Subject: settings: per-feature voucher series and console invoice number; tui: F9 hints --- clients/screens_bokslut.c | 2 +- clients/screens_ib.c | 4 +- clients/screens_invoices.c | 2 +- clients/screens_payroll.c | 4 +- clients/screens_settings.c | 89 ++++++++++++++++++++++++++++++++++++--------- clients/screens_templates.c | 2 +- clients/screens_vouchers.c | 19 +++++++++- clients/tui.c | 4 +- clients/ui.c | 7 +++- clients/ui.h | 1 + 10 files changed, 105 insertions(+), 29 deletions(-) (limited to 'clients') diff --git a/clients/screens_bokslut.c b/clients/screens_bokslut.c index 3f69982..96c6fe9 100644 --- a/clients/screens_bokslut.c +++ b/clients/screens_bokslut.c @@ -242,7 +242,7 @@ void bokslut_screen(struct app *a) ff[7].cap = sizeof rate; ff[7].kind = TUI_F_TEXT; tui_form_hint("upp/ned/Tab = flytta Enter = ändra/utför F5 = visa" - " bokslutsplan ^Enter/F9 = bokför planen (frågar" + " bokslutsplan F9 = bokför planen (frågar" " först) Esc/q = tillbaka ^C = avsluta"); int r = tui_form_run_actions("Bokslut", ff, nf + 2, can_edit, acts, 5, NULL, &sel); diff --git a/clients/screens_ib.c b/clients/screens_ib.c index 8950667..2d62297 100644 --- a/clients/screens_ib.c +++ b/clients/screens_ib.c @@ -167,7 +167,7 @@ static int ib_post_delta(struct ibform *ib) 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"); + yyjson_mut_obj_add_strcpy(d, o, "series", a->ib_series); char *ref = util_random_id("tui-", 8); yyjson_mut_obj_add_strcpy(d, o, "client_ref", ref); free(ref); @@ -272,7 +272,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) for (;;) { int rr = tui_rt_run("Ingående balans", &ib.rt, "Tab = byta fält F5 = validera ^X = rensa rad" - " ^Enter/F9 = spara Esc = avbryt"); + " F9 = spara Esc = avbryt"); if (rr == -1) return 0; char msg[256]; diff --git a/clients/screens_invoices.c b/clients/screens_invoices.c index ca59eef..8fb32eb 100644 --- a/clients/screens_invoices.c +++ b/clients/screens_invoices.c @@ -1092,7 +1092,7 @@ static int64_t invoices_new(struct app *a) tui_rt_set_fields(&f.rt, ff, 7); const char *hint = "Enter = välj/ändra Tab = byta fält F5 = förhandsvisa" - " ^Enter/F9 = utfärda Esc = avbryt"; + " F9 = utfärda Esc = avbryt"; int64_t out = 0; for (;;) { int rr = tui_rt_run("Ny faktura", &f.rt, hint); diff --git a/clients/screens_payroll.c b/clients/screens_payroll.c index 23d765f..234778c 100644 --- a/clients/screens_payroll.c +++ b/clients/screens_payroll.c @@ -1101,7 +1101,7 @@ static int64_t payroll_run_screen(struct app *a, int64_t id) { status, -1, NULL }, { totals, -1, NULL }, { "Förhandsvisa (F5)", 1, NULL }, - { "Bokför körning (^Enter/F9)", post_reason ? 0 : 1, + { "Bokför körning (F9)", post_reason ? 0 : 1, post_reason }, { "Lönebesked (PDF)", payslip_reason ? 0 : 1, payslip_reason }, { "AGI-underlag", agi_reason ? 0 : 1, agi_reason }, @@ -1124,7 +1124,7 @@ static int64_t payroll_run_screen(struct app *a, int64_t id) snprintf(title, sizeof title, "Ny lönekörning"); int editable = can_write && !posted; tui_form_hint("upp/ned/Tab = flytta Enter = ändra/utför" - " F5 = förhandsvisa ^Enter/F9 = bokför" + " F5 = förhandsvisa F9 = bokför" " Esc/q = tillbaka ^C = avsluta"); int ret = tui_form_run_actions(title, ff, 2, editable, acts, 7, NULL, &focus); diff --git a/clients/screens_settings.c b/clients/screens_settings.c index 8a31620..7730ff4 100644 --- a/clients/screens_settings.c +++ b/clients/screens_settings.c @@ -199,6 +199,7 @@ struct setting_field { const char *const *choices; int nchoices; int mask; + int invoice_number; }; static void setting_save(struct app *a, const struct setting_field *f, @@ -218,9 +219,11 @@ static void setting_save(struct app *a, const struct setting_field *f, char *rr = client_rpc(&a->conn, "settings.set", a->session, a->org, args); free(args); if (rr && client_ok(rr)) { - if (strcmp(f->key, "default_series") == 0) + if (strcmp(f->key, "series_voucher") == 0) snprintf(a->default_series, sizeof a->default_series, "%s", value); + else if (strcmp(f->key, "series_ib") == 0) + snprintf(a->ib_series, sizeof a->ib_series, "%s", value); else if (strcmp(f->key, "attachment_dir") == 0) snprintf(a->attachment_dir, sizeof a->attachment_dir, "%s", value); @@ -283,12 +286,42 @@ static void settings_form(struct app *a, const char *title, acts[0].enabled = -1; } free(resp); + for (int i = 0; i < n; i++) { + if (!fields[i].invoice_number) + continue; + char *seq = client_rpc(&a->conn, "invoice.sequence_get", + a->session, a->org, "{}"); + if (seq && client_ok(seq)) + snprintf(vals[i], 512, "%lld", + (long long)jint_val(seq, "result.next_number", 1)); + free(seq); + } tui_form_hint("upp/ned/Home/End Enter = ändra F5 = uppdatera" " Esc/q = tillbaka ^C = avsluta"); int r = tui_form_run_actions(title, ff, n, can_edit, acts, show_password_status ? 1 : 0, NULL, focus); - if (r >= 0 && r < n) { + if (r >= 0 && r < n && fields[r].invoice_number) { + if (!(a->role[0] && strcmp(a->role, "owner") == 0)) { + tui_message(title, "Kräver ägarbehörighet."); + } else { + char *end = NULL; + long v = strtol(vals[r], &end, 10); + if (!end || *end || v < 1) { + tui_message(title, + "Nästa fakturanummer måste vara ett positivt" + " heltal."); + } else { + char args[64]; + snprintf(args, sizeof args, "{\"next_number\":%ld}", v); + char *rr = client_rpc(&a->conn, "invoice.sequence_set", + a->session, a->org, args); + if (!rr || !client_ok(rr)) + show_error(title, rr); + free(rr); + } + } + } else if (r >= 0 && r < n) { const char *val = vals[r]; if (fields[r].choices) val = fields[r].choices[choice[r]]; @@ -304,21 +337,29 @@ static void settings_form(struct app *a, const char *title, static const char *const SECURITY_CHOICES[] = { "starttls", "tls", "plain" }; static const struct setting_field INVOICE_SETTINGS[] = { - { "default_series", "Standardserie", NULL, 0, 0 }, - { "invoice_receivable_account", "Fordringskonto", NULL, 0, 0 }, - { "invoice_revenue_account", "Intäktskonto", NULL, 0, 0 }, - { "invoice_bankgiro", "Bankgiro", NULL, 0, 0 }, - { "invoice_our_ref", "Vår referens (faktura)", NULL, 0, 0 }, + { "invoice_next_number", "Nästa fakturanummer", NULL, 0, 0, 1 }, + { "invoice_receivable_account", "Fordringskonto", NULL, 0, 0, 0 }, + { "invoice_revenue_account", "Intäktskonto", NULL, 0, 0, 0 }, + { "invoice_bankgiro", "Bankgiro", NULL, 0, 0, 0 }, + { "invoice_our_ref", "Vår referens (faktura)", NULL, 0, 0, 0 }, +}; + +static const struct setting_field SERIES_SETTINGS[] = { + { "series_voucher", "Manuella verifikat", NULL, 0, 0, 0 }, + { "series_invoice", "Fakturor", NULL, 0, 0, 0 }, + { "series_payroll", "Lön", NULL, 0, 0, 0 }, + { "series_bokslut", "Bokslut", NULL, 0, 0, 0 }, + { "series_ib", "Ingående balans", NULL, 0, 0, 0 }, }; static const struct setting_field SMTP_SETTINGS[] = { - { "smtp_host", "SMTP-server", NULL, 0, 0 }, - { "smtp_port", "SMTP-port", NULL, 0, 0 }, - { "smtp_user", "SMTP-användare", NULL, 0, 0 }, - { "smtp_from", "SMTP-avsändare", NULL, 0, 0 }, - { "smtp_reply_to", "SMTP-svarsadress", NULL, 0, 0 }, - { "smtp_security", "SMTP-säkerhet", SECURITY_CHOICES, 3, 0 }, - { "smtp_password", "SMTP-lösenord", NULL, 0, 1 }, + { "smtp_host", "SMTP-server", NULL, 0, 0, 0 }, + { "smtp_port", "SMTP-port", NULL, 0, 0, 0 }, + { "smtp_user", "SMTP-användare", NULL, 0, 0, 0 }, + { "smtp_from", "SMTP-avsändare", NULL, 0, 0, 0 }, + { "smtp_reply_to", "SMTP-svarsadress", NULL, 0, 0, 0 }, + { "smtp_security", "SMTP-säkerhet", SECURITY_CHOICES, 3, 0, 0 }, + { "smtp_password", "SMTP-lösenord", NULL, 0, 1, 0 }, }; static int settings_can_write(struct app *a) @@ -343,6 +384,14 @@ static void smtp_settings_screen(struct app *a) settings_can_write(a), 1, &sel); } +static void series_settings_screen(struct app *a) +{ + static int sel = 0; + settings_form(a, "Verifikationsserier", SERIES_SETTINGS, + (int)(sizeof SERIES_SETTINGS / sizeof SERIES_SETTINGS[0]), + settings_can_write(a), 0, &sel); +} + static const char *const SYSTEM_ITEMS[] = { "Skattetabeller", "Revision", @@ -435,6 +484,7 @@ static void company_data_screen(struct app *a) static const char *const COMPANY_ITEMS[] = { "Företagsuppgifter", "Fakturauppgifter", + "Verifikationsserier", "E-post (SMTP)", "Styrelseledamöter", MK_SECTION "Register", @@ -462,18 +512,21 @@ void company_screen(struct app *a) invoice_settings_screen(a); break; case 2: - smtp_settings_screen(a); + series_settings_screen(a); break; case 3: + smtp_settings_screen(a); + break; + case 4: board_screen(a); break; - case 5: + case 6: employees_screen(a); break; - case 6: + case 7: customers_screen(a); break; - case 7: + case 8: rules_screen(a); break; default: diff --git a/clients/screens_templates.c b/clients/screens_templates.c index 5777742..e55028d 100644 --- a/clients/screens_templates.c +++ b/clients/screens_templates.c @@ -234,7 +234,7 @@ static int template_form(struct app *a, const char *load_name) tui_rt_set_fields(&tf.rt, ff, 3); const char *title = load_name ? "Redigera mall" : "Ny mall"; const char *hint = "Enter = ändra fält Tab = byta fält F5 = validera" - " ^X = rensa rad ^Enter/F9 = spara Esc = avbryt"; + " ^X = rensa rad F9 = spara Esc = avbryt"; for (;;) { int rr = tui_rt_run(title, &tf.rt, hint); if (rr == -1) diff --git a/clients/screens_vouchers.c b/clients/screens_vouchers.c index 5e1b006..d884b6e 100644 --- a/clients/screens_vouchers.c +++ b/clients/screens_vouchers.c @@ -743,6 +743,23 @@ static int64_t vform_post(struct vform *vf, int dry) replayed ? " — redan bokfört (idempotent)" : ""); free(ser); free(r); + if (strcmp(vf->series, a->default_series) != 0) { + yyjson_mut_doc *sd = yyjson_mut_doc_new(NULL); + yyjson_mut_val *so = yyjson_mut_obj(sd); + yyjson_mut_doc_set_root(sd, so); + yyjson_mut_obj_add_strcpy(sd, so, "key", "series_voucher"); + yyjson_mut_obj_add_strcpy(sd, so, "value", vf->series); + char *sargs = yyjson_mut_write(sd, 0, NULL); + yyjson_mut_doc_free(sd); + if (sargs) { + char *sr = client_rpc(&a->conn, "settings.set", + a->session, a->org, sargs); + free(sr); + free(sargs); + } + snprintf(a->default_series, sizeof a->default_series, "%s", + vf->series); + } free(args); return id; } @@ -836,7 +853,7 @@ int64_t vouchers_new_prefill(struct app *a, const struct voucher_prefill *p) tui_rt_normalize(&vf.rt); const char *hint = "Enter = ändra fält Tab = byta fält F4 = mall" " ^F = bifoga fil F5 = validera ^X = rensa rad" - " ^Enter/F9 = bokför Esc = avbryt"; + " F9 = bokför Esc = avbryt"; for (;;) { int rr = tui_rt_run("Nytt verifikat", &vf.rt, hint); if (rr == -1) diff --git a/clients/tui.c b/clients/tui.c index 30761a8..db5599d 100644 --- a/clients/tui.c +++ b/clients/tui.c @@ -1615,7 +1615,7 @@ static int form_run(const char *title, struct tui_form_field *f, int nf, h = na > 0 ? (can_edit ? "upp/ned/Tab = flytta Enter = ändra/utför" - " F5 = uppdatera ^Enter/F9 = spara" + " F5 = uppdatera F9 = spara" " Esc/q = tillbaka ^C = avsluta" : "upp/ned/Tab = flytta Enter = utför" " F5 = uppdatera Esc/q = tillbaka" @@ -1623,7 +1623,7 @@ static int form_run(const char *title, struct tui_form_field *f, int nf, " (endast behöriga kan ändra)") : (can_edit ? "upp/ned/Home/End Enter = ändra" - " F5 = uppdatera ^Enter/F9 = spara" + " F5 = uppdatera F9 = spara" " Esc/q = tillbaka ^C = avsluta" : "upp/ned/Home/End F5 = uppdatera" " Esc/q = tillbaka ^C = avsluta" diff --git a/clients/ui.c b/clients/ui.c index ca6906f..e6f6e07 100644 --- a/clients/ui.c +++ b/clients/ui.c @@ -217,12 +217,17 @@ void app_refresh_context(struct app *a) free(resp); snprintf(a->default_series, sizeof a->default_series, "%s", "A"); + snprintf(a->ib_series, sizeof a->ib_series, "%s", "IB"); resp = client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); if (resp && client_ok(resp)) { - char *ser = jstr_dup(resp, "result.default_series"); + char *ser = jstr_dup(resp, "result.series_voucher"); if (ser && *ser) snprintf(a->default_series, sizeof a->default_series, "%s", ser); free(ser); + char *ib = jstr_dup(resp, "result.series_ib"); + if (ib && *ib) + snprintf(a->ib_series, sizeof a->ib_series, "%s", ib); + free(ib); } free(resp); diff --git a/clients/ui.h b/clients/ui.h index b37eb4f..b4ce5f5 100644 --- a/clients/ui.h +++ b/clients/ui.h @@ -42,6 +42,7 @@ struct app { int64_t employee_sel; /* last selected employee id in the list view */ int64_t payroll_sel; /* last selected payroll run id in the list view */ char default_series[16]; + char ib_series[16]; char attachment_dir[256]; long max_attachment_bytes; char fy_label[64]; -- cgit v1.3