aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--clients/bokftui.c9
-rw-r--r--clients/screens_dashboard.c43
-rw-r--r--clients/screens_settings.c27
-rw-r--r--clients/ui.c19
-rw-r--r--clients/ui.h3
-rw-r--r--docs/PAYROLL.md4
-rw-r--r--docs/PROTOCOL.md17
-rw-r--r--docs/STATE.md18
-rw-r--r--docs/TUI-GUIDELINES.md12
-rwxr-xr-xscripts/tui-golden.py37
11 files changed, 118 insertions, 72 deletions
diff --git a/.gitignore b/.gitignore
index 89d9e89..3f9cea2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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",