diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | clients/bokftui.c | 9 | ||||
| -rw-r--r-- | clients/screens_dashboard.c | 43 | ||||
| -rw-r--r-- | clients/screens_settings.c | 27 | ||||
| -rw-r--r-- | clients/ui.c | 19 | ||||
| -rw-r--r-- | clients/ui.h | 3 | ||||
| -rw-r--r-- | docs/PAYROLL.md | 4 | ||||
| -rw-r--r-- | docs/PROTOCOL.md | 17 | ||||
| -rw-r--r-- | docs/STATE.md | 18 | ||||
| -rw-r--r-- | docs/TUI-GUIDELINES.md | 12 | ||||
| -rwxr-xr-x | scripts/tui-golden.py | 37 |
11 files changed, 118 insertions, 72 deletions
@@ -14,3 +14,4 @@ dropzone/ var/ .env build-gate*/ +__pycache__/ diff --git a/clients/bokftui.c b/clients/bokftui.c index a047ef5..2dc23ba 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -45,7 +45,7 @@ static const struct scene SCENES[] = { { "audit", audit_screen }, { "company", company_screen }, { "settings", company_screen }, /* ^R/--screen compatibility */ - { "system", system_settings_screen }, + { "system", system_screen }, { "bokslut", bokslut_screen }, { "yearinfo", bokslut_screen }, /* ^R compatibility */ }; @@ -146,11 +146,14 @@ static void config_load(struct app *a) snprintf(a->socket, sizeof a->socket, "%s", val); else if (strcmp(line, "user") == 0 && !a->username[0]) snprintf(a->username, sizeof a->username, "%s", val); + else if (strcmp(line, "attachment_dir") == 0 && + !a->attachment_dir[0]) + snprintf(a->attachment_dir, sizeof a->attachment_dir, "%s", val); } fclose(f); } -static void config_save(const struct app *a) +void config_save(struct app *a) { char path[512]; config_path(path, sizeof path); @@ -161,6 +164,8 @@ static void config_save(const struct app *a) fprintf(f, "server=%s\n", a->socket); if (a->username[0]) fprintf(f, "user=%s\n", a->username); + if (a->attachment_dir[0]) + fprintf(f, "attachment_dir=%s\n", a->attachment_dir); fclose(f); chmod(path, 0600); } diff --git a/clients/screens_dashboard.c b/clients/screens_dashboard.c index 9deca37..83b4f6a 100644 --- a/clients/screens_dashboard.c +++ b/clients/screens_dashboard.c @@ -268,18 +268,11 @@ static void select_fiscal_year(struct app *a) } } static const char *const MAIN_ITEMS[] = { - MK_SECTION "Bokföring", "Verifikat", "Underlag (inkorg)", "Bankavstämning", "Mallar", - MK_SECTION "Fakturering", "Fakturor", - MK_SECTION "Lön", "Lönekörningar", - MK_SECTION "Rapporter & bokslut", "Rapporter", "Bokslut", - "Bolaget", - MK_SECTION "System", - "Skattetabeller", "Revision", "Systeminställningar", - MK_SECTION "Räkenskapsår", + "Bolaget", "System", "Ingående balans", "Räkenskapsår", "Logga ut / avsluta", }; @@ -305,52 +298,46 @@ void dashboard(struct app *a) if (sel < 0) continue; /* Esc/back at the top level never exits */ switch (sel) { - case 1: + case 0: open_scene(a, "vouchers"); break; - case 2: + case 1: open_scene(a, "inbox"); break; - case 3: + case 2: open_scene(a, "bank"); break; - case 4: + case 3: open_scene(a, "templates"); break; - case 6: + case 4: open_scene(a, "invoices"); break; - case 8: + case 5: open_scene(a, "payroll"); break; - case 10: + case 6: open_scene(a, "reports"); break; - case 11: + case 7: open_scene(a, "bokslut"); break; - case 12: + case 8: open_scene(a, "company"); break; - case 14: - open_scene(a, "tax_tables"); - break; - case 15: - open_scene(a, "audit"); - break; - case 16: + case 9: open_scene(a, "system"); break; - case 18: + case 10: open_scene(a, "ib"); break; - case 19: + case 11: select_fiscal_year(a); break; - case 20: + case 12: return; default: - break; /* section header */ + break; } } } diff --git a/clients/screens_settings.c b/clients/screens_settings.c index 227ee68..8a31620 100644 --- a/clients/screens_settings.c +++ b/clients/screens_settings.c @@ -321,10 +321,6 @@ static const struct setting_field SMTP_SETTINGS[] = { { "smtp_password", "SMTP-lösenord", NULL, 0, 1 }, }; -static const struct setting_field SYSTEM_SETTINGS[] = { - { "attachment_dir", "Bilagornas mapp", NULL, 0, 0 }, -}; - static int settings_can_write(struct app *a) { return a->role[0] && (strcmp(a->role, "owner") == 0 || @@ -347,12 +343,27 @@ static void smtp_settings_screen(struct app *a) settings_can_write(a), 1, &sel); } -void system_settings_screen(struct app *a) +static const char *const SYSTEM_ITEMS[] = { + "Skattetabeller", + "Revision", +}; + +void system_screen(struct app *a) { static int sel = 0; - settings_form(a, "Systeminställningar", SYSTEM_SETTINGS, - (int)(sizeof SYSTEM_SETTINGS / sizeof SYSTEM_SETTINGS[0]), - settings_can_write(a), 0, &sel); + for (;;) { + if (g_quit) + return; + int r = tui_menu("System", SYSTEM_ITEMS, + (int)(sizeof SYSTEM_ITEMS / sizeof SYSTEM_ITEMS[0]), + 0, &sel); + if (r < 0) + return; + if (r == 0) + payroll_tax_tables_screen(a); + else if (r == 1) + audit_screen(a); + } } static void company_data_screen(struct app *a) diff --git a/clients/ui.c b/clients/ui.c index a95ce44..ca6906f 100644 --- a/clients/ui.c +++ b/clients/ui.c @@ -217,17 +217,12 @@ void app_refresh_context(struct app *a) free(resp); snprintf(a->default_series, sizeof a->default_series, "%s", "A"); - a->attachment_dir[0] = '\0'; resp = client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); if (resp && client_ok(resp)) { char *ser = jstr_dup(resp, "result.default_series"); if (ser && *ser) snprintf(a->default_series, sizeof a->default_series, "%s", ser); free(ser); - char *dir = jstr_dup(resp, "result.attachment_dir"); - if (dir && *dir) - snprintf(a->attachment_dir, sizeof a->attachment_dir, "%s", dir); - free(dir); } free(resp); @@ -332,14 +327,18 @@ static void expand_path(const char *in, char *out, size_t n) Returns a malloc'd path, or NULL on cancel. */ char *file_browser(struct app *a, const char *start_dir) { - (void)a; if (g_quit) return NULL; char dir[1024]; - const char *start = (start_dir && *start_dir) ? start_dir : getenv("HOME"); + const char *home = getenv("HOME"); + const char *start = (start_dir && *start_dir) ? start_dir : home; expand_path(start, dir, sizeof dir); - if (!dir[0]) - snprintf(dir, sizeof dir, "."); + struct stat sb; + if (!dir[0] || stat(dir, &sb) != 0 || !S_ISDIR(sb.st_mode)) { + expand_path(home && *home ? home : ".", dir, sizeof dir); + if (!dir[0]) + snprintf(dir, sizeof dir, "."); + } for (;;) { DIR *d = opendir(dir); if (!d) { @@ -408,6 +407,8 @@ char *file_browser(struct app *a, const char *start_dir) continue; } free(ents); + snprintf(a->attachment_dir, sizeof a->attachment_dir, "%.255s", dir); + config_save(a); return xstrdup(full); } } diff --git a/clients/ui.h b/clients/ui.h index 2b93106..b37eb4f 100644 --- a/clients/ui.h +++ b/clients/ui.h @@ -102,6 +102,7 @@ extern const struct is_group IS_GROUPS[IS_GROUPS_N]; /* defined in bokftui.c, used by screens */ void open_scene(struct app *a, const char *name); void config_mkdirs(const char *path); +void config_save(struct app *a); /* Optional prefill for the new-voucher form (bank reconciliation). */ struct voucher_prefill { @@ -130,12 +131,12 @@ void audit_screen(struct app *a); void templates_screen(struct app *a); void ib_screen(struct app *a); void company_screen(struct app *a); -void system_settings_screen(struct app *a); void invoices_screen(struct app *a); void customers_screen(struct app *a); void bokslut_screen(struct app *a); void employees_screen(struct app *a); void payroll_screen(struct app *a); void payroll_tax_tables_screen(struct app *a); +void system_screen(struct app *a); #endif diff --git a/docs/PAYROLL.md b/docs/PAYROLL.md index 7d7bdba..b78aeba 100644 --- a/docs/PAYROLL.md +++ b/docs/PAYROLL.md @@ -229,8 +229,8 @@ the base is the gross. renders and stores nothing. See `PROTOCOL.md` §7.12. - **TUI — Lön** (done, `clients/screens_payroll.c`): `Lönekörningar` (list, Ctrl+N for a new run) under Lön, `Anställda` under Bolaget, and a - **Skattetabeller** screen (System, since the tables are national) with - status, the Skatteverket fetch and the offline file import. + **Skattetabeller** screen (via System, since the tables are national) + with status, the Skatteverket fetch and the offline file import. - The run screen shows the preview (F5), posts with Ctrl+Enter after confirmation, then offers the buttons **Lönebesked** (saves/opens the PDF), **AGI-underlag** (`payroll.agi` in a pager, owner only) and diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 1e9f6fa..76197d9 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -378,8 +378,9 @@ resolved rows in a dry run. `default_series` (1–8 characters, e.g. `A`, `V-`, `A `) is used when `voucher.post` carries no `series` and as the default series for new -templates. `attachment_dir` (a path, up to 255 characters) is the folder the -TUI file browser opens in when attaching underlag. `bank_account` (digits +templates. `attachment_dir` (a path, up to 255 characters) is accepted for +compatibility, but the TUI file browser now remembers the directory of the +last picked attachment client-side. `bank_account` (digits only, up to 10 characters, default `1930`) is the account `bank.import` uses when the request carries no `account`. `invoice_receivable_account` (default `1510`) and `invoice_revenue_account` (default `3001`) are the receivable and @@ -941,8 +942,8 @@ commands. Implemented screens (0.1.0-dev): tax table/column, e-mail). Ctrl+N creates, Enter edits (an empty personnummer keeps the stored one), `d` archives/reactivates; F5 validates with a dry run and Ctrl+Enter saves. -- **Skattetabeller** (System) — stored tax table years, the current year's - status, fetch time and source. Owners fetch Skatteverket's official +- **Skattetabeller** (via **System**) — stored tax table years, the current + year's status, fetch time and source. Owners fetch Skatteverket's official monthly table (`payroll.tax_tables_fetch`) or import a file offline (`payroll.tax_tables_import`). - **Rapporter** — saldobalans, resultaträkning, balansräkning, moms, @@ -968,9 +969,11 @@ commands. Implemented screens (0.1.0-dev): password), both `settings.set` and open to bookkeepers; **Styrelseledamöter**; and the registers **Anställda**, **Kunder** and **Momsregler**. -- **Systeminställningar** (System) — bilagornas mapp, the file browser's - start directory. -- **Revision** (System) — chain verification and behandlingshistorik. +- **System** — the hub with **Skattetabeller** and **Revision**. The file + browser starts in the directory of the last picked attachment (remembered + in the client's `tui.conf`) and falls back to `$HOME` when it is gone. +- **Revision** (via **System**) — chain verification and + behandlingshistorik. **Ctrl+N is the universal "add" key**: it starts a new verifikat from the dashboard, the voucher list and the voucher detail view; a new mall from the diff --git a/docs/STATE.md b/docs/STATE.md index b8c9581..39fa44e 100644 --- a/docs/STATE.md +++ b/docs/STATE.md @@ -34,14 +34,16 @@ unit tests and the docs consistency check. avgifter), Anställda under Företag and the Skattetabeller fetch/import/status screen under System. A pty scenario posts a run and fetches its lönebesked, so the payslip PDF path is exercised end to end. -- **Menu (2026-09-21)**: the dashboard has the sections Bokföring (with - Mallar), Fakturering, Lön, Rapporter & bokslut, System (Skattetabeller, - Revision, Systeminställningar) and Räkenskapsår, plus the standalone - **Bolaget** hub: Företagsuppgifter, Fakturauppgifter, E-post (SMTP), - Styrelseledamöter and the registers Anställda, Kunder and Momsregler. - Inställningar is gone: standardserie, fordrings-/intäktskonto, bankgiro, - vår referens and SMTP live under Bolaget, bilagornas mapp under System. - `--screen settings` remains an alias for Bolaget. +- **Menu (2026-09-21)**: the dashboard is one flat list without section + headings: Verifikat, Underlag, Bankavstämning, Mallar, Fakturor, + Lönekörningar, Rapporter, Bokslut, Bolaget, System, Ingående balans, + Räkenskapsår, Logga ut. **Bolaget** is the master-data hub + (Företagsuppgifter, Fakturauppgifter, E-post (SMTP), Styrelseledamöter + and the registers Anställda, Kunder, Momsregler); **System** holds + Skattetabeller and Revision. Inställningar is gone, and bilagornas mapp + is no longer a setting: the file browser remembers the last pick + directory in `tui.conf` and falls back to `$HOME`. `--screen settings` + remains an alias for Bolaget. ## Open decisions diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md index 7e0d404..94c3b5c 100644 --- a/docs/TUI-GUIDELINES.md +++ b/docs/TUI-GUIDELINES.md @@ -135,11 +135,13 @@ written compactly as `^N`, `^A`, `^C`, `^R` to save width. `BACK`, `REFRESH` or `SUBMIT` (the widget returns as on `Esc`/`F5`/ `Ctrl+Enter`). Hooks exist on `tui_form_run_hook`, `tui_rt_set_key`, `tui_pager_hook` and `tui_select_list_hook`. -- File browser (`file_browser`): starts in the org's `attachment_dir` (or - `$HOME`; a leading `~` is expanded to `$HOME`), `.. (uppåt)` is the first row, directories sort first with a - trailing `/`, hidden files are skipped. Enter enters a directory or picks a - file; `Esc` cancels. Selected files are uploaded immediately as unlinked - underlag and linked when the voucher is posted. +- File browser (`file_browser`): starts in the directory where the last + attachment was picked (remembered in `tui.conf`), falling back to `$HOME` + when it is gone (a leading `~` is expanded to `$HOME`), `.. (uppåt)` is + the first row, directories sort first with a trailing `/`, hidden files + are skipped. Enter enters a directory or picks a file; `Esc` cancels. + Picking a file remembers its directory. Selected files are uploaded + immediately as unlinked underlag and linked when the voucher is posted. ## Messages diff --git a/scripts/tui-golden.py b/scripts/tui-golden.py index d4a6076..9df5ed4 100755 --- a/scripts/tui-golden.py +++ b/scripts/tui-golden.py @@ -66,7 +66,7 @@ SCENARIOS = [ "name": "dashboard", "screen": "dashboard", "expect": ["{org_name}", "{fy_label}", "{fy_start}", "{fy_end}", - "Bolaget", "System", "Skattetabeller"], + "Bolaget", "System", "Räkenskapsår"], }, { "name": "audit-ok", @@ -120,6 +120,21 @@ SCENARIOS = [ ], }, { + "name": "inbox-file-browser", + "screen": "inbox", + "expect": ["Underlag (inkorg)"], + "steps": [ + { + "keys": ["a"], + "expect": ["Välj fil — /"], + }, + { + "keys": ["esc"], + "expect": ["Underlag (inkorg)"], + }, + ], + }, + { "name": "bank-new-voucher", "screen": "bank", "expect": ["Bankavstämning", "GOLDEN INSÄTTNING", "CDON"], @@ -246,7 +261,25 @@ SCENARIOS = [ { "name": "system", "screen": "system", - "expect": ["Systeminställningar", "Bilagornas mapp"], + "expect": ["System", "Skattetabeller", "Revision"], + "steps": [ + { + "keys": ["1"], + "expect": ["Skattetabeller", "Status: skattetabeller för"], + }, + { + "keys": ["esc"], + "expect": ["System", "Revision"], + }, + { + "keys": ["2"], + "expect": ["Revision", "Verifiera hashkedjan"], + }, + { + "keys": ["esc"], + "expect": ["System", "Skattetabeller"], + }, + ], }, { "name": "tax-tables", |
