aboutsummaryrefslogtreecommitdiffstats
path: root/examples/tools/finance.sh
blob: 0656b66e448534d9f9cdc88382e3d0f483b70c19 (plain)
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
#!/bin/bash
# desktop-assistant tool: personal finance from Firefly III (read-only).
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# finance.sh WHAT [SINCE] [ACCOUNT]: WHAT is balances (active asset
# accounts), budgets (this month, spent against the limit) or spending
# (withdrawals from SINCE, YYYY-MM-DD, default the first of this month,
# optionally only from ACCOUNT: total and the 10 largest). Only firefly's list commands run here: nothing is recorded
# or edited.

set -uo pipefail

ff() { firefly "$@" || { echo "firefly $1 $2 failed" >&2; exit 1; }; }

case "${1:-}" in
balances)
    ff account list --type asset | jq -r '.[] | select(.active)
        | "\(.name): \(.current_balance | tonumber * 100 | round / 100) \(.currency_code)"'
    ;;
budgets)
    ff budget list | jq -r '.[] | select(.active)
        | "\(.name): spent \([.spent[].sum | tonumber] | add // 0 | fabs * 100 | round / 100)"
          + (if .auto_budget_amount then " of \(.auto_budget_amount | tonumber)" else "" end)
          + " \(.currency_code // "")"'
    ;;
spending)
    since="${2:-$(date +%Y-%m-01)}"
    date -d "$since" >/dev/null 2>&1 || { echo "since must be YYYY-MM-DD" >&2; exit 1; }
    account=()
    if [ -n "${3:-}" ]; then
        # firefly's own "no such account" lists every expense account too;
        # the model only needs the user's own accounts to retry.
        names=$(ff account list --type asset | jq -r '.[] | select(.active) | .name')
        grep -qixF -- "$3" <<<"$names" \
            || { echo "no account named $3; accounts: $(paste -sd, <<<"$names")" >&2; exit 1; }
        # The per-account listing needs an end date, or the API rejects it.
        account=(--account "$3" --until "$(date +%F)")
    fi
    json=$(firefly tx list --since "$since" "${account[@]}" --all --flat) || exit 1
    jq -r --arg since "$since" --arg acct "${3:-all accounts}" '
        [.[] | select(.type == "withdrawal") | .amount |= tonumber]
        | "spent from \($acct) since \($since): \(map(.amount) | add // 0 | . * 100 | round / 100) in \(length) payments",
          (sort_by(-.amount)[:10][]
           | "\(.date[:10]) \(.amount * 100 | round / 100) \(.destination_name): \(.description)")' <<<"$json"
    ;;
*)
    echo "what must be balances, budgets or spending" >&2
    exit 1
    ;;
esac