mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-26 20:02:27 +03:00
186 lines
5.7 KiB
Bash
Executable File
186 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# shellcheck disable=SC2016
|
|
# Create invoices with hledger and pandoc.
|
|
# cf hledger/examples/invoicing, https://hledger.org/invoicing.html, https://martinbetz.eu/articles/pandoc-invoices
|
|
set -e
|
|
|
|
#PROG=$(basename "$0")
|
|
function usage() {
|
|
cat <<EOF
|
|
--------------------------------------------------------------------------------
|
|
invoice:
|
|
Make markdown or pdf invoices, optionally including last month's time
|
|
and expenses, from a markdown template and similarly-named .css file.
|
|
Requires hledger, pandoc, awk, GNU date, envsubst, python3, sed, tail.
|
|
|
|
$ invoice
|
|
Show this help.
|
|
|
|
$ invoice TEMPLATEFILE [TIMEACCTORAMT [EXPACCTORAMT]] [FLAGS]
|
|
Print a markdown invoice on stdout.
|
|
TIMEACCTORAMT and EXPACCTORAMT are time and expense accounts to query
|
|
with hledger, or if numeric, the hours and expense amounts directly.
|
|
With --md and/or --pdf, save it as markdown / PDF in current directory.
|
|
With --txn, print sample hledger journal entries on stdout.
|
|
|
|
EOF
|
|
}
|
|
|
|
ARGS=()
|
|
while [[ $# -gt 0 ]]; do
|
|
key="$1"
|
|
case $key in
|
|
-h|--help)
|
|
HELP=1
|
|
shift
|
|
;;
|
|
--md)
|
|
MD=1
|
|
shift
|
|
;;
|
|
--pdf)
|
|
PDF=1
|
|
shift
|
|
;;
|
|
--txn)
|
|
TXN=1
|
|
shift
|
|
;;
|
|
*)
|
|
if [[ "$1" != -* ]]
|
|
then
|
|
ARGS+=("$1")
|
|
shift
|
|
else
|
|
echo "Error: unknown option $1"
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
if [[ $HELP = 1 || ${#ARGS} -eq 0 ]]; then usage; exit; fi
|
|
|
|
DEFTIMEACCT=0
|
|
DEFEXPACCT=0
|
|
TEMPLATE="${ARGS[0]}"
|
|
TIMEACCT="${ARGS[1]:-$DEFTIMEACCT}"
|
|
EXPACCT="${ARGS[2]:-$DEFEXPACCT}"
|
|
|
|
# XXX FIXEDEXPS and RATE here, and printf widths below, must be kept synced with TEMPLATE
|
|
FIXEDEXPS=$(python3 -c "print(sum([ 1111, 2222, 333 ]))")
|
|
RATE=444
|
|
TIMELOG=./time.timedot
|
|
#
|
|
|
|
# on mac, use homebrew-installed GNU date
|
|
if [ "$(builtin type -p gdate)" ]; then export date=gdate; else export date=date; fi
|
|
|
|
# the billing date (today)
|
|
YEAR=$($date +%Y)
|
|
MONTH=$($date +%B)
|
|
MON=$($date +%b)
|
|
MM=$($date +%m)
|
|
DD=$($date +%d)
|
|
DAY=$($date +%-d)
|
|
|
|
# The period being billed, as a hledger report period.
|
|
HLEDGERPERIOD='last month'
|
|
# The exact year and month being billed, corresponding to the above.
|
|
YYYYMM=$(hledger -f /dev/null is -p "$HLEDGERPERIOD" | head -1 | cut -d' ' -f3)
|
|
LY=${YYYYMM:0:4}
|
|
LMM=${YYYYMM:5}
|
|
LM=$($date +%b --date "$YYYYMM-01")
|
|
|
|
# shellcheck disable=SC2001
|
|
INVOICEBASE=$(basename "$TEMPLATE" | sed -e 's/\..*//')
|
|
INVOICEDATED=$INVOICEBASE$YEAR$LMM
|
|
INVOICEMD=$INVOICEDATED".md"
|
|
INVOICEPDF=$INVOICEDATED".pdf"
|
|
CSS=$INVOICEBASE".css"
|
|
|
|
# Calculate time and reimbursable expenses
|
|
|
|
NUMRE="^[0-9]+([.][0-9]+)?$"
|
|
if [[ $TIMEACCT =~ $NUMRE ]]
|
|
then
|
|
HRS=$TIMEACCT
|
|
else
|
|
HRS=$(hledger -f "$TIMELOG" bal "$TIMEACCT" -1 "date:$HLEDGERPERIOD" -N | tail -1 | awk '{print $1}')
|
|
fi
|
|
if [[ $EXPACCT =~ $NUMRE ]]
|
|
then
|
|
EXP=$EXPACCT
|
|
else
|
|
EXP=$(hledger bal "$EXPACCT" "date:$HLEDGERPERIOD" amt:'>0' -N --layout=bare | tail -1 | awk '{print $1}')
|
|
fi
|
|
|
|
HRS="${HRS:-0}"
|
|
HRS=$(printf %4s "$HRS")
|
|
EXP=$(printf %5.0f "$EXP")
|
|
AMT=$(python3 -c "print(round( $HRS * $RATE ))")
|
|
AMT=$(printf %5s "$AMT")
|
|
REV=$(python3 -c "print(sum([ $FIXEDEXPS, $AMT ]))")
|
|
REV=$(printf %5s "$REV")
|
|
TOT=$(python3 -c "print(sum([ $FIXEDEXPS, $AMT, $EXP ]))")
|
|
TOT=$(printf %5s "$TOT")
|
|
export YEAR MONTH DAY LMM LM HRS AMT REV EXP TOT
|
|
|
|
if [[ $MD != 1 && $PDF != 1 ]]; then
|
|
# print markdown invoice
|
|
envsubst '$YEAR:$MONTH:$DAY:$LMM:$LM:$HRS:$EXP:$AMT:$TOT' <"$TEMPLATE"
|
|
|
|
else
|
|
if [[ $MD = 1 ]]; then
|
|
# save markdown invoice
|
|
envsubst '$YEAR:$MONTH:$DAY:$LMM:$LM:$HRS:$EXP:$AMT:$TOT' <"$TEMPLATE" >"$INVOICEMD"
|
|
echo "wrote $INVOICEMD"
|
|
fi
|
|
if [[ $PDF = 1 ]]; then
|
|
# save pdf invoice
|
|
envsubst '$YEAR:$MONTH:$DAY:$LMM:$LM:$HRS:$EXP:$AMT:$TOT' <"$TEMPLATE" \
|
|
| pandoc -t html5 --metadata title=" " --css "$CSS" -o "$INVOICEPDF"
|
|
echo "wrote $INVOICEPDF"
|
|
fi
|
|
fi
|
|
|
|
if [[ $TXN = 1 ]]; then
|
|
# generate sample journal entries
|
|
printf "\n--------------------------------------------------------------------------------\n\n"
|
|
USTAXRATE=0.28
|
|
STTAXRATE=0.08
|
|
CLIENT=$INVOICEBASE
|
|
USTAX=$(python3 -c "print(round( $REV * $USTAXRATE))")
|
|
#USTAX=$(printf %5s "$USTAX")
|
|
STTAX=$(python3 -c "print(round( $REV * $STTAXRATE))")
|
|
#STTAX=$(printf %5s "$STTAX")
|
|
TOTTAX=$(python3 -c "print($USTAX + $STTAX)")
|
|
#TOTTAX=$(printf %5s "$TOTTAX")
|
|
PTINC=$(python3 -c "print($REV - $TOTTAX)")
|
|
#PTINC=$(printf %5s "$PTINC")
|
|
|
|
envsubst '$CLIENT:$YEAR:$MM:$DD:$MON:$LMM:$LM:$REV:$EXP:$TOT:$USTAX:$STTAX:$TOTTAX:$PTINC' <<EOF
|
|
$YEAR-$MM-$DD (${YEAR}${LMM}) $CLIENT | invoice \$$TOT
|
|
(assets:receivable:$CLIENT:consulting) \$$REV ; $LM hourly & $MON fixed fees
|
|
;(assets:receivable:$CLIENT:reimbursement) \$$EXP ; $LM reimbursable expenses
|
|
|
|
; $YEAR-$MM-$DD (${YEAR}${LMM}) $CLIENT | payment
|
|
; ; receive full amount of invoice
|
|
; assets:bank:checking \$$TOT
|
|
; assets:receivable:$CLIENT:reimbursement \$-$EXP
|
|
; assets:receivable:$CLIENT:consulting \$-$REV = $0
|
|
; ; recognise revenue (cash accounting)
|
|
; (revenues:$CLIENT) \$-$REV
|
|
; ; estimate tax due, tax-saved-on: ?, TODO:
|
|
; (liabilities:tax:us:2021) \$-$USTAX ; 28%
|
|
; (liabilities:tax:st:2021) \$-$STTAX ; 8%
|
|
; ; Total tax: \$$TOTTAX ; 36%
|
|
; ; Post-tax income: \$$PTINC
|
|
|
|
; $YEAR-$MM-$DD save estimated tax from $CLIENT ${YEAR}${LMM}, received $YEAR-$MM-$DD
|
|
; assets:bank:checking \$-$TOTTAX
|
|
; assets:bank:savings:tax:us:2021 \$$USTAX
|
|
; assets:bank:savings:tax:st:2021 \$$STTAX
|
|
|
|
EOF
|
|
fi
|