1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
# bokf — project state
Settled decisions live in `docs/DECISIONS.md`; this file is the live snapshot.
Read with `AGENTS.md` (rules) and `docs/TUI-GUIDELINES.md` (UI conventions).
## Status
Working self-hosted bookkeeping system, not production-proven. Backend ledger
core is complete, as are invoicing, the payroll engine and the payroll TUI;
filing/year-end work remains. TUI is usable and exercised by `make test-pty`
(golden screen-text scenarios, now including the payroll run and the
lönebesked); `make test` covers the server/protocol/ledger, the TUI widget
unit tests and the docs consistency check.
## Resume here (2026-09-22)
- **Deklaration 2025/2026 (org 2, 2026-09-22)**: the imported year is closed
by the source's `Stäng intäktskonton/kostnadskonton`, so the resultatrapport
(and the INK2/SRU derived from it) showed 0. `report.income_statement` now
skips those vouchers (deployed in `v0.1.65`), which gives
**241 817,48 kr** resultat efter skatt för 2025/2026. In the TUI the
"Bokfört resultat" section is absent for such years (the close goes straight
to 2099). Two **data drifts** to settle with the accountant before filing:
the imported FY 2022/2023 result transfer is **17 857,63 kr** short of that
year's P&L (its tax was never booked as an expense), so the derived balance
sheet does not balance by that amount; and 2099 is 1,07 kr off the P&L
result. Locked years stay locked — a correction belongs in the open year
(2026/2027). The owner can now run Bokslutshubben → Inkomstdeklaration.
- **Deployed**: `v0.1.65`, healthy on `nas` (Alpine runtime, static aarch64
binaries cross-compiled on this machine — ~20 s, the host only assembles
the image; image 33.7 MB, no `libssl3`). `v0.1.55` brought the payroll
server waves (schema v11: employees, tax tables, lönebesked) and the
payroll TUI; `v0.1.56` the **Bolaget** hub; `v0.1.57` the flat main menu
without section headings, the **System** hub (Skattetabeller, Revision)
and the client-side remembered attachment directory; `v0.1.58` loads the
system CA bundle explicitly so the static binaries can verify TLS (the
Skatteverket fetch, SMTP, static clients); `v0.1.59` adds the keyboard
protocols (though gnome-terminal/VTE cannot send Ctrl+Enter); `v0.1.60`
the per-feature voucher series (Bolaget → Verifikationsserier, IB
configurable with legacy `IB` still recognized), Nästa fakturanummer in
Fakturauppgifter and `F9`-only save hints; `v0.1.61` lets underlag be
granskade (text in a pager, other files in the desktop viewer) or
nedladdade from the voucher detail and the inbox; `v0.1.62` brings invoice
**text rows**, `u = duplicera`, `b = kvittera betalning` (`invoice.pay`
links a payment voucher) and rejects name-like `smtp_from` values
(schema v12: `invoice_rows.is_text`, `invoices.paid_date`/
`payment_voucher_id`); `v0.1.63` prints the organization name in the
invoice and lönebesked headers and adds the shared document colour setting
`document_header_color`; `v0.1.64` is the TUI interaction-model spec (docs
only, no runtime change); `v0.1.65` carries earlier IB vouchers forward,
restarts P&L at each year, skips imported `Stäng` closings in the
income statement and the momsomföring in the VAT report. The live database migrated v9 → v11 on the v0.1.55
startup and v11 → v12 on the v0.1.62 startup, each with the automatic
pre-migration snapshot in `var/db/backup/`. `main` and the tags are pushed
to `nas`.
- **Invoices (2026-09-22)**: the follow-ups are done and deployed in
`v0.1.62` — free-text rows, `u = duplicera`, `b = kvittera betalning`
(`invoice.pay`, strict receivable check) and `smtp_from` address
validation. `test_core` has `invoice_extras`; the pty suite has
`invoice-duplicate` and `invoice-pay`. Remaining invoice work is in the
backlog (credit notes, per-row account, kundreskontra, partial payments).
- **Document header (2026-09-22, deployed in `v0.1.63`)**: the invoice
header bar prints the organization name instead of the hard-coded
`MAKANDRA AB` outline (scaled down and truncated with `...` if long).
The shared document setting `document_header_color` (`#rrggbb`, default
`#314c59`, editable in Bolaget → Fakturauppgifter) colours the header bar
of both the invoice and the lönebesked, and the lönebesked prints the
employer name there too (it previously used the Comfortaa wordmark). The
invoice's `FAKTURA` outline is unchanged; the `MAKANDRA AB` outlines in
`src/wordmark.h` are now unused at runtime.
- **TUI interaction model (2026-09-22)**: the UX session settled two focus
modes, three entity lifecycles, explicit `Spara` in every savable form and
`<UTKAST>` drafts persisted to `$XDG_CACHE_HOME/bokf/drafts.json`
(deletable from both the list and the editor), plus one `tui_action`
registry per screen behind `F2` (no `§`). The **Kunder pilot is
implemented** (`clients/drafts.[ch]`, `tui_action_menu()`,
`tui_action_hint()`, `<UTKAST>`, `Spara` row, F2 menu, delete draft) with
unit tests, the pty scenarios `customer-draft`/`customer-draft-save` and a
green `make gate`. It is in `main` (`07f5b14`), pushed and part of `v0.1.65`.
**Next session**: get the human's Ctrl+R test feedback (Bolaget → Kunder:
Ctrl+N, type, Esc, F2, Spara, Radera utkast) and then continue the
rollout per backlog item 17: the other registers, explicit `Spara` in the
settings forms, `tui_rt` action menus. Review points kept in
`TUI-GUIDELINES.md`: drafts of encrypted fields (an employee's
personnummer) and `Enter` on action rows. Spec in `TUI-GUIDELINES.md`
"Interaction model"; decisions in `DECISIONS.md` #28.
- **IB carry-forward (2026-09-22, deployed in `v0.1.65`)**:
reports and `sie.export` carry earlier years' `IB` vouchers into the
opening balance (Makandra's 2021 IB voucher held aktiekapital 2081/1940,
which vanished from every later year) and restart P&L accounts at each
fiscal-year start (they used to accumulate since 2021). SIE writes
`#IB`/`#UB` only for balance accounts and `#RES` only for P&L. Makandra's
opening balances now show the 17 857,63 kr 2022/23 gap openly (balance
accounts' IB sums to that instead of 0). Merged together with
`eff/imported-closings`, whose "Stäng" skip now applies only to
`sie_import` vouchers. `test_core` has `ib_carry` and `imported_closings`.
- **Momsrapport (2026-09-22, deployed in `v0.1.65`)**:
`report.vat` (and `report.vat_eskd`) skips the momsomföring (vouchers with
a 2650 row) and SIE-imported `Stäng` closings. Makandra's 2025/26 report
was all zeros because V107 (Momsdeklaration) and V109 were counted; the
rules themselves were fine. `test_core` has `vat_settlement`.
- **Mail configuration**: Makandra AB (org 2) has **no** `smtp_*` settings
in bokf, so `invoice.send` there is `SMTP_NOT_CONFIGURED` (fine if
invoices are sent elsewhere — set them up when wanted). Mock AB (org 1)
has host/port/user/password but its stored `smtp_from` is the name
"Anders Bergsten": change it to an e-mail address in **Bolaget → E-post
(SMTP)** before sending; the new validation refuses names on save.
- **Next tasks (payroll follow-ups)**: fold `payroll.settings_get/set` into
`settings.get/set` (cmd_settings.c was busy during wave 1); settle the
over-80k % rule (SKV 433 leaves it ambiguous — currently a clear
`INVALID_ARGS`); move `invoice.send` to the shared `src/mail.c`. The TUI
has no screen for the payroll account/rate settings yet (they are
defaults and can stay server-side until the fold).
- **Done in the TUI wave**: `clients/screens_payroll.c` with Lönekörningar
(list + Ctrl+N, run screen with F5 preview, Ctrl+Enter post after
confirmation and the action rows Lönebesked, AGI-underlag, Betala skatt &
avgifter), Anställda under Bolaget 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 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 with Nästa fakturanummer,
**Verifikationsserier**, 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
None open. Completed items that used to be listed here are archived in
`docs/DECISIONS.md`.
## Backlog (prioritized, from COMPLIANCE.md §10 and the audit)
1. ~~eSKD file generation~~ done: `report.vat_eskd`.
2. ~~Bokslut automation~~ done: `bokslut.post` + TUI Bokslut screen.
3. ~~K2 årsredovisning draft and SRU files~~ done: TUI draft + `sru.export`.
4. ~~`audit.verify` voucher chain~~ done (also attachments with `full:true`).
5. ~~`report.general_ledger` / `report.voucher_list`~~ done.
6. ~~`describe` argument schemas~~ done (`CMD_ARGS`).
7. ~~Pre-migration `VACUUM INTO` snapshot~~ done.
8. ~~Docker image + compose~~ done (no registry, no systemd unit).
9. Password change, user disable, TOTP.
10. ~~Bank import/reconciliation (CSV first)~~ phase 1 (SEB CSV + matching,
schema v8) and phase 2 (Ctrl+N prefills the voucher and auto-matches it,
`9b1021e`) done. Phase 3 (`bank_rule.*`) **dropped by the user
2026-09-21**; PSD2 only if it ever hurts.
11a. Imported history years with source P&L closings: decided, no importer
change (see DECISIONS.md).
11. ~~SIE import only into an empty fiscal year~~ chronological multi-year
import works; each year must still target an empty fiscal year.
12. TUI polish: horizontal scrolling in long text fields, bracketed paste.
13. ~~Payroll TUI (wave 3)~~ done (`clients/screens_payroll.c`); the
remaining payroll follow-ups are listed under "Resume here". No
employee is registered yet in the real orgs.
14. ~~Invoice text rows, duplicate and payment registration~~ done in
`v0.1.62` (schema v12). Remaining invoice follow-ups when needed:
credit notes (`invoice.credit`), per-row account in the invoice form,
kundreskontra view, partial payments.
15. `make test-pty` speed if it ever exceeds ~60 s: parallelise the
independent scenarios (each has its own rig) and add `--only` symmetry;
measure first.
16. Test fixtures (`t_fresh_org()`) + one test file per domain
(`tests/core_<domain>.c`) so `--only` stops cascading; pilot with one
domain.
17. Interaction-model rollout (spec: `TUI-GUIDELINES.md` "Interaction
model", decisions #28): **Kunder done** 2026-09-22 (widget layer
`tui_action`/`F2`, `clients/drafts.[ch]`, drafts/`<UTKAST>`/`Spara`,
pty scenarios), awaiting the human's Ctrl+R test feedback. Remaining:
the other register screens, explicit `Spara` in the settings forms
(replacing per-field autosave), and `tui_rt` action menus.
Original entries for the struck items are in `docs/DECISIONS.md`.
## Environment / how to run
- **Deployed**: `scripts/deploy.sh` (latest `v0.1.65`, healthy on nas).
Live daemon `tls:bokf.makandra.eu:8788`, token
`~/.config/bokf/migration-token` (scopes `read,write`; owner-only actions
like closing years must be done by the human in the TUI). Git remote
`origin` is `nas:/mnt/data/git-repos/bokf.git` (push `main` and tags).
`sh scripts/install-hooks.sh` once per checkout points `core.hooksPath` at
`.githooks/`, so every push runs `make gate` (clean `-Werror` build in
`build-gate/`, the suite and ASan); it is installed here.
`deploy/Dockerfile.cross` + `deploy/cross-build.sh` produce static aarch64
`bokfd`/`bokfctl` in ~20 s when the host architecture differs; the image is
assembled on the host from `.prebuilt` (see docs/DEPLOY.md).
- **Local test rig** (transient, `/tmp`): daemon
`./build/bokfd --db /tmp/opencode/bokf-local/t.db --socket
/tmp/opencode/bokf-local/sock`, org 1, login `admin`/`testpass123`.
Drive the TUI over a pty with `scripts/tui-sandbox.sh -- ./build/bokftui
--socket /tmp/opencode/bokf-local/sock --org 1 --fy 1 ...` plus a small
driver that feeds keys and an ANSI renderer (recreate if gone; arrows are
`ESC O B/A`, Tab `\t`, `^X` `\x18`, `^Enter` `ESC[27;5;13~`, F5
`ESC[15~`). Never test against the live daemon.
- Demo: db `~/bokf-demo/bokfd.db`, socket `~/bokf-demo/bokfd.sock`,
pid file `~/bokf-demo/bokfd.pid`; login `admin` / `demo1234`.
Start TUI: `cd ~/work/bokf && BOKFD_SOCKET=$HOME/bokf-demo/bokfd.sock \
BOKFD_USER=admin BOKFD_PASSWORD=demo1234 ./build/bokftui`
- Restart daemon: kill the pid file's process, then
`BOKFD_BACKUP_DIR=$HOME/bokf-demo/backup \
BOKFD_EXPORT_DIR=$HOME/bokf-demo/export setsid nohup \
./build/bokfd --db $HOME/bokf-demo/bokfd.db \
--socket $HOME/bokf-demo/bokfd.sock > $HOME/bokf-demo/daemon.log 2>&1 &`
- The user's own early instance was `/tmp/x.db` + `/tmp/bokfd.sock`
(schema v1, old binary) — recreate or migrate it with the current build if
it is still wanted.
- TUI smoke tests: drive over a pty with `script -qec`; function-key escape
sequences are timing-sensitive there (not an app bug). Arrows arrive as
application-mode sequences (`ESC O B` for Down), not `ESC [ B`, because
curses enables the keypad. `Ctrl+N/C/F` are single bytes and reliable.
Always wrap the run in `scripts/tui-sandbox.sh -- ./build/bokftui ...`: it
isolates `XDG_CONFIG_HOME`/`XDG_CACHE_HOME` so a test can never overwrite
the real `~/.config/bokf/tui.conf` or `~/.cache/bokf/tui.log`.
## Known caveats
- Saving with Ctrl+Enter needs a terminal that speaks xterm
`modifyOtherKeys` level 2 or the Kitty keyboard protocol; gnome-terminal/VTE
sends neither, so the hints advertise `F9`, which works everywhere.
- Developer tooling: the `g_cmd_<domain>[]` tables in `src/cmd_*.c` carry
declarative argument schemas (`CMD_ARGS`); `describe` emits them and the
dispatcher validates before the handler runs. `make check` (part of
`make test`) fails when a command or error code is missing from
`PROTOCOL.md` (`scripts/check-consistency.sh`) or when the generated
command catalogue is stale (`make gen-protocol`). `make test-asan`/
`test-ubsan` build `test_core` with sanitizers; `make test-pty` runs
`scripts/tui-golden.py` against a throwaway `/tmp` daemon; `make gate`
is the pre-push check (clean `-Werror` build in `build-gate/` + tests +
ASan).
- Never commit unless the human asks.
- SQLite files must not be backed up live with restic; use
`backup.snapshot` (`VACUUM INTO`) and point restic at the snapshots.
- Schema version is 12 (v3 moms rules; v4/v6 year info; v5 org
description/shares + board members; v7 attachments append-only triggers;
v8 bank reconciliation; v9 invoicing + widened `vouchers.source` with a
table rebuild; v10 payroll + `payroll`/`payroll_tax` sources, same
rebuild; v11 `employees.email`; v12 `invoice_rows.is_text` and
`invoices.paid_date`/`payment_voucher_id`); forward migrations are in
`db.c`.
## Makandra driftstatus (org 2)
- **Org**: Makandra AB, org 2. Räkenskapsår (id): 2022=3, 2023=4, 2024=5,
2025=6, 2026=7, **2027=2 (öppet, aktuellt)**. Bokslut/AR/deklaration görs
för det år som är valt i sessionen.
- **FK2027**: importerade Kapitas-böcker + 30 bokförda verifikat (V21–V50)
för bank/skatt maj–sep 2026: V49 makulerar en dubblett (V20) och V50 är
Hetzner-förskottet (nedan). 1930 stämmer mot banken utom **CDON
2 409 kr** (väntar på kvittots del 2–4; bokförs när det kommer) och
Hetzner-kortköpet 2026-09-21 som ännu inte är importerat. 1630 = 40 721
(exakt enligt Skatteverket).
- **Hetzner-förskott (2026-09-22)**: V50, datum 2026-09-21 (bankens
bokföringsdatum), "Förskott Hetzner 100 EUR": **D 1790** 1 156,39 /
**K 1930** 1 156,39, med båda PDF:erna som underlag. Matchas mot banken
när nästa SEB-fil (efter 2026-09-18) importeras. Förbrukningen bokförs
när Hetzner-fakturorna kommer (se reglerna nedan).
- **Underlag**: 309 attachment i org 2 (alla historikdokument + insamlade
underlag, inkl. de två Hetzner-PDF:erna). Bank-/SKV-utdrag ligger i
`~/Makandra AB/{bank,skatteverket}` (Syncthing), källkorpus i
`~/Downloads/Makandra AB-…/Bokföring/`.
- **Stängning**: 2022–2026 ska stängas av ägaren via **Räkenskapsår** i
TUI:n; låt FK2027 vara öppen till nästa bokslut.
- **Deklaration**: FK2026 är deklarerad av revisorn. FK2027 deklareras
våren 2027 (INK2/SRU via Bokslutshubben → Inkomstdeklaration).
- **Årshäftet**: fylls i Bokslutshubben (händelser, stämma, utdelning +
datum, medelantal, noter). OBS: `dividend_ore` för FK2027 kan vara ett
testvärde (10 000) — kontrollera före AR/deklaration.
- **Beslut/regler från bokföringsarbetet**: inga bokföringar utan
godkännande; låsta år förblir låsta (rättelser görs i aktuellt år); SIE-
importören ändras inte och importerad data "manipuleras" inte;
historikårens P&L nettar noll pga källsystemets stängningar (AR hoppar
över "Stäng"-verifikat i flerårsöversikten); utdelning bokförs vid stämman
med mallen **Utdelning** (D 2099/K 2898); pappersoriginal finns i fysisk
pärm (får refereras i efterhand, även i stängda år); leverantörsförskott
och förbrukningssaldo (Hetzner) bokförs som **D 1790/K 1930** vid
påfyllning och **D 6540 + D 2645 / K 2614 / K 1790** vid förbrukning;
kortköp bokförs på **bankens bokföringsdatum** (lättare att härleda och
matcha vid import).
- **Lön (2026-09-21)**: bara ägaren är anställd, men schema och kommandon är
fleranvändarklara. Lönemotorn (schema v11: anställda, skattetabeller,
lönekörningar, lönebesked + mejl) och TUI-sektionen Lön är klara, men
**ingen anställd är registrerad än** (personnummer, lön, skattetabell och
kolumn fylls i under Register → Anställda). Konton enligt historiken:
7210/7510/2710/2731/1630. Skattetabellerna hämtas med en knapp från
Skatteverket (varning när året är gammalt), personnumret är krypterat i
databasen. Ingen semesteruppbokning; arbetsgivardeklarationen (AGI) görs
manuellt med underlaget från `payroll.agi`.
|