Salta al contenuto principale

Automatizza la contabilità con bea

Scrivi i tuoi registri Beancount con bea: risolvi il ledger esplicitamente, analizza l'envelope JSON con jq, dirama in base ai codici di uscita, esegui senza terminale e programma un controllo notturno.

Uno script guida bea con quattro decisioni: quale ledger legge, --json per un output leggibile dalla macchina, jq per il valore di cui ha bisogno e il codice di uscita su cui dirama. Questa guida illustra queste quattro decisioni dall'inizio alla fine e poi le programma.

Hai bisogno di bea sulla macchina che esegue il lavoro e di un ledger raggiungibile. Se stai iniziando nuovi registri, segui prima la Guida introduttiva alla CLI. Ogni fatto su flag, chiavi dell'envelope e codici di uscita è consultabile nel Riferimento CLI Beancount, non ripetuto qui.

Scegli il ledger esplicitamente

Nomina il file. Un comando locale risolve la sua destinazione da --file, poi da $BEA_FILE, poi da ./main.bean nella directory di lavoro, e un lavoro programmato raramente viene eseguito dove pensi.

bea --file ~/books/main.bean check
BEA_FILE=~/books/main.bean bea check
cd ~/books && bea check

Le opzioni globali vanno prima del comando, come in bea --file main.bean check. Se il file risolto non esiste, il comando esce con 2 e nomina tutte e tre le fonti, quindi un refuso in una voce cron fallisce in modo evidente invece di convalidare i registri sbagliati. Il targeting ospitato tramite un flag --ledger non esiste ancora; bea non carica mai un file locale implicitamente.

Leggi l'envelope JSON

Aggiungi --json globale e ogni comando supportato risponde con lo stesso envelope: bea, target, data, truncated e limit su liste limitate. Gli importi sono stringhe decimali e le date sono ISO YYYY-MM-DD, quindi un valore è sicuro da confrontare senza che un float entri mai nella pipeline. Le chiavi dell'envelope sono tabulate nel Riferimento JSON e codici di uscita.

bea --json --file main.bean report income-statement | jq .data.net_profit
bea --json --file main.bean list transaction --limit 2 | jq '.data[0].postings[0].units'
bea --json --file main.bean import statement.csv --csv date=Date,amount=Amount,payee=Payee \
  --account Assets:Checking --apply --duplicates skip | jq '.data | {written, ready, duplicates}'

Quei tre comandi — report, list e import — mantengono le loro forme di risultato, quindi un percorso jq scritto per loro resta valido. bea --json check e bea --json query emettono anche oggi l'envelope, ma sono i comandi affidati agli eseguibili Beancount nativi, quindi uno script dovrebbe basarsi sullo stato di uscita di check piuttosto che sulla sua forma di output. Scegli deliberatamente la tua politica sui duplicati: --duplicates è ancora richiesto quando un'importazione richiede una decisione, come spiega la procedura di importazione.

Fermati sul codice di uscita giusto

Dirama in base allo stato e leggi l'oggetto errore prima di riprovare qualsiasi cosa che scrive. In modalità --json, un errore non scrive nulla su stdout e esattamente un oggetto su stderr, la cui error.category nomina la classe: validation (1), usage (2), auth (3), conflict (4).

#!/usr/bin/env bash
set -euo pipefail
 
out=$(mktemp)
err=$(mktemp)
status=0
 
bea --json --file main.bean report income-statement >"$out" 2>"$err" || status=$?
 
case "$status" in
  0) jq -r '.data.net_profit | to_entries[] | "net profit: \(.value) \(.key)"' "$out" ;;
  4) echo "conflict — inspect the ledger before retrying" >&2
     jq -r '.error.message' "$err" >&2
     exit 4 ;;
  *) jq -r '.error | "\(.category) (exit \(.exit_code)): \(.message)"' "$err" >&2
     exit "$status" ;;
esac

L'uscita 4 è quella che uno script non deve mai riprovare alla cieca: significa che l'esito è un conflitto o è sconosciuto, come una modifica esterna che arriva a metà scrittura o una destinazione init che esiste già. Ispeziona il ledger, poi riprova da una lettura fresca. L'uscita 1 copre errori di convalida e qualsiasi altro errore di runtime; error.details porta i singoli errori del ledger e error.result porta ciò che una scrittura parziale ha effettivamente fatto. Un'uscita diversa da zero non garantisce mai che nulla sia cambiato.

Esegui senza terminale

bea smette di richiedere input da solo. --no-input è implicito ogni volta che stdin non è un terminale, ogni volta che --json è impostato e ogni volta che CI è veritiero — 1, true, yes o on. In quella modalità, una conferma mancante fallisce con uscita 2 invece di attendere per sempre.

CI=true BEA_NO_UPDATE_NOTIFIER=1 bea --json --file main.bean report balance-sheet
bea --json --file main.bean list transaction --limit 100 --sort oldest

Le letture sono permissive in un terminale e rigorose ovunque altro. Quando il ledger ha errori di caricamento, query, list e report escono con 1 sotto --json, sotto stdout pipe, sotto CI veritiero o con --strict; passa il --allow-errors del comando per accettare invece la risposta parziale, che imposta anche ledger_valid: false e riempie ledger_errors nel JSON. --strict è l'immagine speculare: rifiuta risposte parziali anche in un terminale, che è ciò che vuoi quando un essere umano esegue lo stesso script a mano. bea check non ha --allow-errors — segnalare errori è il suo intero lavoro — e esce sempre con 1 quando ne trova. Imposta BEA_NO_UPDATE_NOTIFIER=1 per silenziare l'avviso di aggiornamento passivo; un CI veritiero lo fa già.

Programma un controllo

Esegui una convalida ogni notte e lascia che il codice di uscita sia l'allerta. Entrambi i blocchi sotto sono modelli — i percorsi, la pianificazione e il runner sono tuoi.

# crontab -e — 07:15 daily; cron mails you only when bea exits nonzero
15 7 * * * BEA_NO_UPDATE_NOTIFIER=1 /opt/homebrew/bin/bea --file /home/alice/books/main.bean check
name: ledger
on:
  schedule:
    - cron: "15 7 * * *"
  push:
jobs:
  check:
    runs-on: ubuntu-latest
    env:
      BEA_NO_UPDATE_NOTIFIER: "1"
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - run: uv tool install beancount-io==0.1.0
      - run: bea --file main.bean check
      - run: bea --json --file main.bean report balance-sheet > balance-sheet.json

Fissa la versione quando il lavoro deve essere riproducibile e rimuovi la fissazione quando preferisci seguire le release. CI è già veritiero su GitHub Actions, quindi i prompt sono spenti e l'avviso di aggiornamento è silenzioso prima che tu imposti qualsiasi cosa. Qui non c'è un passo di formattazione di proposito. Un lavoro programmato non dovrebbe riscrivere file che non doveva, quindi ricorri a bea format --check in un hook pre-commit, che non tocca nulla e esce con 1 quando un file ha bisogno di formattazione.

Usa una credenziale ospitata in un lavoro

Imposta BEA_TOKEN, dal deposito segreto del tuo provider CI, e salta completamente l'accesso dal browser. Il token viene letto dall'ambiente e mai scritto su disco, quindi nulla finisce nella directory home del runner per il lavoro successivo da trovare.

export BEA_TOKEN="$YOUR_CI_SECRET"
bea --json cloud status

L'uscita 0 significa che la credenziale è stata risolta e l'envelope nomina l'account a cui appartiene; l'uscita 3 con error.category di auth significa che non lo è, e il messaggio distingue una credenziale non impostata da una rifiutata. bea cloud logout non fa nulla a un token fornito in questo modo — né lo revoca né lo annulla, poiché un altro lavoro potrebbe condividerlo — quindi revoca invece un token trapelato dal dashboard. I comandi locali non richiedono alcuna credenziale; solo bea cloud e bea ask raggiungono il servizio ospitato. L'elenco completo delle variabili è nel riferimento delle impostazioni.

Non tutto risponde in JSON. bea ask rifiuta categoricamente la modalità JSON, bea cloud login richiede un essere umano e un bea cloud logout o bea cloud ledger clone riuscito non restituisce alcun oggetto di successo JSON — leggi invece il loro stato di uscita. L'output di aiuto, versione e completamento della shell resta testuale.

Passaggi successivi

Fonte: https://beancount.io/it/docs/Solutions/automate-bookkeeping-with-bea