پرش به محتوای اصلی
Beancount.io Logo

خودکارسازی حسابداری با bea

با bea کتاب‌های Beancount خود را اسکریپت‌نویسی کنید: ledger را به‌صراحت حل کنید، پاکت JSON را با jq تجزیه کنید، روی کدهای خروجی شاخه بزنید، بدون نظارت اجرا کنید و یک بررسی شبانه را زمان‌بندی کنید.

یک اسکریپت bea را با چهار تصمیم هدایت می‌کند: کدام ledger را می‌خواند، --json برای خروجی قابل‌خواندن توسط ماشین، jq برای مقداری که نیاز دارد، و کد خروجی که روی آن شاخه می‌زند. این راهنما این چهار تصمیم را از ابتدا تا انتها مرور می‌کند و سپس آن‌ها را زمان‌بندی می‌کند.

شما به bea روی ماشینی که job را اجرا می‌کند و یک ledger که بتواند به آن دسترسی داشته باشد نیاز دارید. اگر در حال شروع کتاب‌های جدید هستید، ابتدا شروع سریع CLI را دنبال کنید. هر واقعیت درباره پرچم‌ها، کلیدهای پاکت و کدهای خروجی در مرجع CLI Beancount جستجو می‌شود، نه اینکه اینجا تکرار شود.

انتخاب صریح ledger

فایل را نام‌گذاری کنید. یک فرمان محلی هدف خود را از --file، سپس $BEA_FILE، و سپس ./main.bean در دایرکتوری کاری حل می‌کند، و یک job زمان‌بندی‌شده به‌ندرت جایی که شما فکر می‌کنید اجرا می‌شود.

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

گزینه‌های سراسری قبل از فرمان قرار می‌گیرند، مانند bea --file main.bean check. اگر فایل حل‌شده وجود نداشته باشد، فرمان با کد 2 خارج می‌شود و هر سه منبع را نام می‌برد، بنابراین یک اشتباه تایپی در یک ورودی cron با صدای بلند شکست می‌خورد به‌جای اینکه کتاب‌های اشتباه را اعتبارسنجی کند. هدف‌گیری میزبانی‌شده از طریق پرچم --ledger هنوز وجود ندارد؛ bea هرگز یک فایل محلی را به‌طور ضمنی آپلود نمی‌کند.

خواندن پاکت JSON

پرچم سراسری --json را اضافه کنید و هر فرمان پشتیبانی‌شده با همان پاکت پاسخ می‌دهد: bea، target، data، truncated، و limit در فهرست‌های محدود. مقادیر عددی رشته‌های اعشاری هستند و تاریخ‌ها ISO با فرمت YYYY-MM-DD هستند، بنابراین یک مقدار برای مقایسه بدون ورود یک عدد اعشاری ممیز شناور به خط لوله امن است. کلیدهای پاکت در مرجع JSON و کدهای خروجی جدول‌بندی شده‌اند.

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}'

آن سه فرمان — report، list و import — شکل نتایج خود را حفظ می‌کنند، بنابراین یک مسیر jq که بر اساس آن‌ها نوشته شده معتبر می‌ماند. bea --json check و bea --json query نیز امروز پاکت را منتشر می‌کنند، اما آن‌ها فرمان‌هایی هستند که به اجرایی‌های بومی Beancount سپرده می‌شوند، بنابراین یک اسکریپت باید روی وضعیت خروجی check کلید بزند به‌جای شکل خروجی آن. سیاست تکراری خود را عمداً انتخاب کنید: --duplicates همچنان زمانی که یک import به تصمیم نیاز دارد الزامی است، همانطور که راهنمای import توضیح می‌دهد.

توقف روی کد خروجی درست

روی وضعیت شاخه بزنید و قبل از تلاش مجدد برای هر چیزی که می‌نویسد، شیء خطا را بخوانید. در حالت --json، یک شکست هیچ چیزی به stdout و دقیقاً یک شیء به stderr نمی‌نویسد، که error.category آن کلاس را نام می‌برد: 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

خروجی 4 همان موردی است که یک اسکریپت هرگز نباید کورکورانه دوباره تلاش کند: به این معنی است که نتیجه یک تعارض یا ناشناخته است، مانند یک ویرایش خارجی که در میانه نوشتن می‌رسد یا یک هدف init که از قبل وجود دارد. ledger را بررسی کنید، سپس از یک خواندن تازه دوباره تلاش کنید. خروجی 1 خطاهای اعتبارسنجی و هر خطای زمان اجرای دیگر را پوشش می‌دهد؛ error.details خطاهای فردی ledger را حمل می‌کند، و error.result حمل می‌کند که یک نوشتن جزئی واقعاً چه کاری انجام داد. یک خروجی غیرصفر هرگز تضمین نمی‌کند که چیزی تغییر نکرده است.

اجرا بدون ترمینال

bea توقف درخواست را خودش انجام می‌دهد. --no-input هر زمان که stdin یک ترمینال نباشد، هر زمان که --json تنظیم شده باشد، و هر زمان که CI truthy باشد — 1، true، yes یا on — به‌طور ضمنی اعمال می‌شود. در آن حالت، یک تأییدیه گم‌شده با خروجی 2 شکست می‌خورد به‌جای اینکه برای همیشه منتظر بماند.

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

خواندن‌ها در یک ترمینال ملایم و در هر جای دیگر سخت‌گیرانه هستند. وقتی ledger خطاهای بارگذاری دارد، query، list و report با 1 تحت --json، تحت stdout خط لوله‌شده، تحت CI truthy، یا با --strict خارج می‌شوند؛ پرچم --allow-errors خود فرمان را رد کنید تا به‌جای آن پاسخ جزئی را بپذیرید، که همچنین ledger_valid: false را تنظیم می‌کند و ledger_errors را در JSON پر می‌کند. --strict تصویر آینه است: حتی در یک ترمینال پاسخ‌های جزئی را رد می‌کند، که همان چیزی است که وقتی یک انسان همان اسکریپت را با دست اجرا می‌کند می‌خواهید. bea check گزینه --allow-errors ندارد — گزارش خطاها کل کار آن است — و همیشه وقتی هر خطایی پیدا می‌کند با 1 خارج می‌شود. BEA_NO_UPDATE_NOTIFIER=1 را تنظیم کنید تا اعلان به‌روزرسانی غیرفعال را ساکت کنید؛ یک CI truthy از قبل این کار را انجام می‌دهد.

زمان‌بندی یک بررسی

هر شب یک اعتبارسنجی اجرا کنید و بگذارید کد خروجی هشدار باشد. هر دو بلوک زیر الگو هستند — مسیرها، برنامه زمان‌بندی و اجراکننده متعلق به شماست.

# 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

نسخه را زمانی که job باید قابل‌بازتولید باشد پین کنید، و پین را رها کنید وقتی ترجیح می‌دهید انتشارها را دنبال کنید. CI از قبل روی GitHub Actions truthy است، بنابراین درخواست‌ها خاموش هستند و اعلان به‌روزرسانی قبل از اینکه چیزی را تنظیم کنید ساکت است. هیچ مرحله قالب‌بندی عمداً اینجا نیست. یک job زمان‌بندی‌شده نباید فایل‌هایی را که مجبور نبود بازنویسی کند، بنابراین در یک hook پیش از commit به bea format --check برسید، که هیچ چیزی را لمس نمی‌کند و وقتی یک فایل به قالب‌بندی نیاز دارد با 1 خارج می‌شود.

استفاده از یک اعتبارنامه میزبانی‌شده در یک job

BEA_TOKEN را از فروشگاه اسرار ارائه‌دهنده CI خود تنظیم کنید و به‌کلی از ورود مرورگر صرف‌نظر کنید. توکن از محیط خوانده می‌شود و هرگز روی دیسک نوشته نمی‌شود، بنابراین هیچ چیز در دایرکتوری خانه اجراکننده برای job بعدی که پیدا کند قرار نمی‌گیرد.

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

خروجی 0 به این معنی است که اعتبارنامه حل شده و پاکت حسابی را که به آن تعلق دارد نام می‌برد؛ خروجی 3 با error.category از نوع auth به این معنی است که حل نشده، و پیام یک اعتبارنامه تنظیم‌نشده را از یک اعتبارنامه ردشده متمایز می‌کند. bea cloud logout هیچ کاری با توکنی که به این روش ارائه شده انجام نمی‌دهد — آن را نه لغو می‌کند و نه تنظیم‌نشده می‌کند، زیرا ممکن است job دیگری آن را به اشتراک بگذارد — بنابراین یک توکن نشت‌شده را از داشبورد لغو کنید. فرمان‌های محلی به هیچ اعتبارنامه‌ای نیاز ندارند؛ فقط bea cloud و bea ask به سرویس میزبانی‌شده می‌رسند. فهرست کامل متغیرها در مرجع تنظیمات است.

همه چیز به JSON پاسخ نمی‌دهد. bea ask حالت JSON را به‌کلی رد می‌کند، bea cloud login به یک انسان نیاز دارد، و یک bea cloud logout یا bea cloud ledger clone موفق هیچ شیء موفقیت JSON برنمی‌گرداند — به‌جای آن وضعیت خروجی آن‌ها را بخوانید. خروجی راهنما، نسخه و تکمیل‌کننده پوسته متنی می‌ماند.

مراحل بعدی

منبع: https://beancount.io/fa/docs/Solutions/automate-bookkeeping-with-bea