Zum Inhalt springen
Familienkonto

Command-line tool

sh
bin/fk <command> [options]
docker compose run --rm cli <command> [options]

The same modules and the same rules as the web interface, driven from a terminal. It exists for the jobs a browser is bad at - migrations, seeding, producing a tax bundle from a cron job, importing a bank file - and for seeing what the application thinks without clicking through it.

bin/fk is the program itself, with #!/usr/bin/env -S jennifer run on the first line and the executable bit set. It works out where the checkout is from its own path - fs.realpath(os.ARGS[0]), so a symlink is followed - and reads .env and writes var/receipts and var/exports relative to that, not to wherever you are standing. Link it onto PATH and fk report works from any directory. jennifer run bin/fk does the same thing for anyone who prefers to say so.

--household is only needed when more than one family has members; a normal single-family installation never needs it.

Payments and their proof

sh
bin/fk settle --from martin --to anna --amount 45,00 --method cash
bin/fk receipt 0 auszug.pdf --payment 12 --kind bank_statement --as martin

--method is one of transfer, cash, revolut, card, other; without it a payment is recorded as a transfer. --kind is one of receipt, invoice, bank_statement, payment_slip or other, and defaults to receipt; payment_slip is the proof of the one transfer - the banking app's confirmation, the counter receipt - where bank_statement is a whole month of them. The receipt command takes an expense number as its first argument, or 0 together with --payment to hang the file on a settlement instead - one owner or the other, never both.

Which database

APP_DB_DSN decides, and the process environment beats the value in .env. That is worth knowing when a terminal disagrees with the browser: a server started with APP_DB_DSN=...expenses_demo and a bin/fk run in a plain shell are looking at two different databases, and neither is wrong.

bin/fk doctor prints the one it is using, without the password:

Datenbank
  Adresse            127.0.0.1:3307/expenses_demo
  Verbindung         steht
  sql_mode           STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION
  strict             ja
  offene Migrationen 0

Run with no command at all, fk prints its usage and stops.

Consistency

doctor ends with a section that checks what the schema cannot. Foreign keys hold the shape of the data; these hold its arithmetic, and every one of them is a rule that, broken, shows up as a balance nobody can explain rather than as an error:

Consistency
  shares add up to the expense           ok
  shares not settled beyond their value  ok
  payments not spent twice               ok
  allocations stay in one family         ok
  accepted shares have both sides        ok
  categories belong to their family      ok
  payment parties are members            ok
  share parties are members              ok
  dedicated payments name a category     ok
  receipt files present                  ok
  nothing to report

A failing check reports how many rows break it and the first few ids — 1 row(s), e.g. #18315 — and stops counting at 50, which it says as 50+. It reads and changes nothing: what to do about a finding depends on how it got there. receipt files present walks the attachment rows and looks for the file on disk; the usual cause of a miss is a restore that brought the database back without APP_RECEIPT_DIR, and the first thing it breaks is the Finanzamt bundle.

Commands

CommandDoes
doctorcheck the installation: database, directories, tools, and the consistency of the books
migrate up|down|statusschema migrations
household-add <name>create a family, with its categories
household-listfamilies and their members
user-add <user> [--admin]create an account that belongs to no family yet
demo-seed --yeswipe everything and install the demonstration families
member-add <user> --roleadd a person, creating the account if new
expense-addrecord an expense
expense-listlist expenses
pendingwhat is waiting for approval
review <nr> --as <user>accept, or --reject to reject
receipt <nr> <file> --asattach a document
income-add / income-lista child's own income
balancewallets, and what the adults owe each other
settle --from --torecord a payment; without --amount it settles everything open, --purpose <code> dedicates it to one category
report --child --yeara report; --tax restricts it to tax-relevant entries
export --child --yearbuild the PDF and ZIP bundle
backup [--schedule]one archive of the database and the receipts, or one a day
mail [send|status|retry|test]take the queued notification mails to the relay
receipt-rehashgive every attachment its SHA-512 and store one file once
trailread a family's audit trail; --purge N removes entries older than N days

Examples

sh
# Anna paid for a schoolbook that a parent bears
familienkonto expense-add --child anna --amount 45,20 --merchant Thalia \
    --description "Schulbuch Mathematik" --category schule --bearer martin

# something she paid for herself: no approval needed, still counts for tax
familienkonto expense-add --child anna --amount 15,00 --merchant Kino \
    --description Kinokarte --category freizeit --private

familienkonto pending
familienkonto review 3 --as martin
familienkonto balance

# settle everything open, with a generated payment reference
familienkonto settle --from martin --to anna

# a lump sum in advance
familienkonto settle --from martin --to anna --amount 200,00

# money that is for the rent and nothing else: it waits for a Wohnen expense
# instead of being spent on the next schoolbook
familienkonto settle --from martin --to anna --amount 890,00 --purpose wohnen

familienkonto report --child anna --year 2026 --tax
familienkonto export --child anna --year 2026

The bank-CSV import is switched off

config.IMPORT_ENABLED is false, and both the web routes and the import command are registered behind it. With the flag down /import is a 404 and familienkonto import is an unknown command; nothing dispatches to importCommand, importPage, importPreview or importCommit, and nothing links to them.

The code is not deleted and its tests still run - bankcsv and bankimport are covered as they were. What is unfinished is not the parsing but the question above it: how a family should reconcile a bank export against entries it has already recorded by hand. Turning it back on is one true in src/core/config.j, one line in bin/fk, and one entry in each of that file's two command maps.

Output

Plain text, aligned into columns, and English throughout - the web interface is the family's, this is the operator's, and reading a log line, a stack trace and a command's output in one sitting should not mean changing language halfway. The German error catalogue is deliberately not installed here: the domain modules throw English sentences and that is what an operator sees. Amounts and dates keep the format the data is in.

The report draws the same coverage bar the browser and the PDF show:

  [####################|###################] 100,0 % (threshold met)

Errors say what is wrong rather than that something went wrong:

error: Martin Maier is not a child of this family
error: cannot read that amount: fünf
error: Anna Maier may not review expenses

A mistyped command line is answered the same way, with the usage of the command that was typed rather than the interpreter's own error struct:

$ bin/fk expense-add
error: missing required flag: --child

Usage: expense-add [options]
...

Role checks are enforced here too - review --as anna is refused, not merely discouraged.

The first administrator

An empty installation has no families, and member-add needs one. user-add is the way in:

sh
bin/fk user-add root --name "Administration" --admin

That account belongs to no family - which is the point, an administrator runs the installation rather than living in it - and can create the rest through Verwaltung in the web interface.

Demonstration data

demo-seed empties the database and installs two families - the ones the German guides are written around. It refuses unless APP_AUTH_MODE=dev, and --yes is required, because it deletes everything first.

sh
bash scripts/demo-seed.sh          # into a database of its own

The data lives in src/app/demodata.j, so this command, the button on the development login page and the screenshots in docs/images/ all install exactly the same thing.