mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-07 21:15:19 +03:00
;examples: invoicing: new invoice script example
This commit is contained in:
parent
7c4f5dc2bf
commit
7edcf77eae
79
examples/invoicing/invoice-script/README.md
Normal file
79
examples/invoicing/invoice-script/README.md
Normal file
@ -0,0 +1,79 @@
|
||||
Scripts adapted from a real-world setup, not guaranteed to be current or working.
|
||||
|
||||
Example:
|
||||
|
||||
Show invoice preview:
|
||||
```
|
||||
$ ./invoice abinvoice.tmpl.md client.ab.dev 200
|
||||
---
|
||||
papersize: letter
|
||||
margin-left: 20mm
|
||||
margin-right: 25mm
|
||||
margin-top: 20mm
|
||||
margin-bottom: 20mm
|
||||
...
|
||||
|
||||
![](logo.jpg){ width=100mm }\
|
||||
Joe Consultant | +1 (111) 111 1111 | joe@example.com | 500 Done Dr. #1, Work Ville, CA 10000, USA
|
||||
|
||||
Carl Client\
|
||||
AB Inc.\
|
||||
PO Box 11111\
|
||||
CA 20000\
|
||||
|
||||
December 4, 2021
|
||||
|
||||
# Invoice 202111cw
|
||||
|
||||
| Description | Rate | Qty | Total |
|
||||
|:--------------------------------------------|-------:|-------:|--------:|
|
||||
| Systems reliability engineering | $ 1111 | | $ 1111 |
|
||||
| On-call monitoring & tech support | $ 2222 | | $ 2222 |
|
||||
| Contractor/vendor management | $ 333 | | $ 333 |
|
||||
| Custom SW development & maintenance (Nov) | $ 444 | 2.00 | $ 888 |
|
||||
| Reimbursable expenses (Nov) | | | $ 200 |
|
||||
| | | | |
|
||||
| Total due | | | $ 4754 |
|
||||
| | | | |
|
||||
|
||||
|
||||
Terms: Now due. Your business is appreciated, thank you!
|
||||
```
|
||||
|
||||
Generate markdown and PDF invoices and sample journal entries:
|
||||
```
|
||||
$ ./invoice abinvoice.tmpl.md client.ab.dev 200 --md --pdf --txn
|
||||
wrote abinvoice202111.md
|
||||
Loading pages (1/6)
|
||||
Counting pages (2/6)
|
||||
Resolving links (4/6)
|
||||
Loading headers and footers (5/6)
|
||||
Printing pages (6/6)
|
||||
Done
|
||||
wrote abinvoice202111.pdf
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
2021-12-04 (202111) abinvoice | invoice $ 4754
|
||||
(assets:receivable:abinvoice:consulting) $ 4554 ; Nov hourly & Dec fixed fees
|
||||
;(assets:receivable:abinvoice:reimbursement) $ 200 ; Nov reimbursable expenses
|
||||
|
||||
; 2021-12-04 (202111) abinvoice | payment
|
||||
; ; receive full amount of invoice
|
||||
; assets:bank:checking $ 4754
|
||||
; assets:receivable:abinvoice:reimbursement $- 200
|
||||
; assets:receivable:abinvoice:consulting $- 4554 = ./invoice
|
||||
; ; recognise revenue (cash accounting)
|
||||
; (revenues:abinvoice) $- 4554
|
||||
; ; estimate tax due, tax-saved-on: ?, TODO:
|
||||
; (liabilities:tax:us:2021) $-1275 ; 28%
|
||||
; (liabilities:tax:st:2021) $-364 ; 8%
|
||||
; ; Total tax: $1639 ; 36%
|
||||
; ; Post-tax income: $2915
|
||||
|
||||
; 2021-12-04 save estimated tax from abinvoice 202111, received 2021-12-04
|
||||
; assets:bank:checking $-1639
|
||||
; assets:bank:savings:tax:us:2021 $1275
|
||||
; assets:bank:savings:tax:st:2021 $364
|
||||
|
||||
```
|
117
examples/invoicing/invoice-script/abinvoice.css
Normal file
117
examples/invoicing/invoice-script/abinvoice.css
Normal file
@ -0,0 +1,117 @@
|
||||
/* https://martinbetz.eu/articles/pandoc-invoices */
|
||||
|
||||
@charset "utf-8";
|
||||
|
||||
body {
|
||||
/* font-size: 10.5pt; */
|
||||
/* font-family: */
|
||||
/* "Avenir Next", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", */
|
||||
/* "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; */
|
||||
hyphens: auto;
|
||||
height: 250mm; /* 280 - 10 (top) - 20 (bottom) */
|
||||
line-height: 140%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Source Sans Code", Courier New, Courier, monospace;
|
||||
margin-left: 1pt;
|
||||
/* font-size:12pt; */
|
||||
}
|
||||
|
||||
a {
|
||||
color: black;
|
||||
margin-left: 1pt;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20pt;
|
||||
margin-top: 10mm;
|
||||
padding-top:0;
|
||||
/* margin-top: 6pt; */
|
||||
/* margin-bottom: 0; */
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16pt;
|
||||
/* margin-top: 20pt; */
|
||||
margin-top: 10mm;
|
||||
/* font-weight: normal; */
|
||||
/* margin-top: 0; */
|
||||
/* margin-bottom: 20pt; */
|
||||
}
|
||||
|
||||
p {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p:first-of-type {
|
||||
text-align: center;
|
||||
font-size: 9pt;
|
||||
word-spacing: 1pt;
|
||||
}
|
||||
|
||||
p:nth-of-type(2) {
|
||||
margin-top: 10mm;
|
||||
}
|
||||
|
||||
p:nth-of-type(3) {
|
||||
text-align: center;
|
||||
font-weight:bold;
|
||||
font-size:12pt;
|
||||
|
||||
}
|
||||
|
||||
/* p:nth-last-of-type(3) { */
|
||||
/* margin-top: 10mm; */
|
||||
/* } */
|
||||
|
||||
/* p:last-of-type { */
|
||||
/* text-align: center; */
|
||||
/* font-size: 9pt; */
|
||||
/* position: absolute; */
|
||||
/* bottom: 2mm; */
|
||||
/* margin-bottom: 0; */
|
||||
/* padding-bottom: 0; */
|
||||
/* color: #444; */
|
||||
/* } */
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table:nth-of-type(1) {
|
||||
border: 1px solid black;
|
||||
padding: 5pt;
|
||||
}
|
||||
|
||||
table:nth-of-type(1) td {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
table:nth-of-type(1) tr:nth-last-of-type(1) {
|
||||
font-weight: bold;
|
||||
}
|
||||
table:nth-of-type(1) tr:nth-last-of-type(1) td {
|
||||
/* border-top: 1px solid black; */
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
/* table:nth-of-type(1) td:nth-of-type(2) { */
|
||||
/* text-align: center; */
|
||||
/* } */
|
||||
|
||||
hr {
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
hr:last-of-type {
|
||||
position: absolute;
|
||||
bottom: 14mm;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
33
examples/invoicing/invoice-script/abinvoice.tmpl.md
Normal file
33
examples/invoicing/invoice-script/abinvoice.tmpl.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
papersize: letter
|
||||
margin-left: 20mm
|
||||
margin-right: 25mm
|
||||
margin-top: 20mm
|
||||
margin-bottom: 20mm
|
||||
...
|
||||
|
||||
![](logo.jpg){ width=100mm }\
|
||||
Joe Consultant | +1 (111) 111 1111 | joe@example.com | 500 Done Dr. #1, Work Ville, CA 10000, USA
|
||||
|
||||
Carl Client\
|
||||
AB Inc.\
|
||||
PO Box 11111\
|
||||
CA 20000\
|
||||
|
||||
$MONTH $DAY, $YEAR
|
||||
|
||||
# Invoice ${YEAR}${LMM}cw
|
||||
|
||||
| Description | Rate | Qty | Total |
|
||||
|:--------------------------------------------|-------:|-------:|--------:|
|
||||
| Systems reliability engineering | $ 1111 | | $ 1111 |
|
||||
| On-call monitoring & tech support | $ 2222 | | $ 2222 |
|
||||
| Contractor/vendor management | $ 333 | | $ 333 |
|
||||
| Custom SW development & maintenance ($LM) | $ 444 | $HRS | $ $AMT |
|
||||
| Reimbursable expenses ($LM) | | | $ $EXP |
|
||||
| | | | |
|
||||
| Total due | | | $ $TOT |
|
||||
| | | | |
|
||||
|
||||
|
||||
Terms: Now due. Your business is appreciated, thank you!
|
180
examples/invoicing/invoice-script/invoice
Executable file
180
examples/invoicing/invoice-script/invoice
Executable file
@ -0,0 +1,180 @@
|
||||
#!/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
|
||||
#
|
||||
|
||||
# if changing this period, hledger and date may need different values
|
||||
HLEDGERPERIOD='last month'
|
||||
DATECMDPERIOD='last month'
|
||||
|
||||
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
|
||||
|
||||
# on mac, use homebrew-installed GNU date
|
||||
if [ "$(builtin type -p gdate)" ]; then export date=gdate; else export date=date; fi
|
||||
|
||||
YEAR=$($date +%Y)
|
||||
MONTH=$($date +%B)
|
||||
MON=$($date +%b)
|
||||
MM=$($date +%m)
|
||||
DD=$($date +%d)
|
||||
DAY=$($date +%-d)
|
||||
LM=$($date +%b --date "$DATECMDPERIOD")
|
||||
LMM=$($date +%m --date "$DATECMDPERIOD")
|
||||
|
||||
# shellcheck disable=SC2001
|
||||
INVOICEBASE=$(basename "$TEMPLATE" | sed -e 's/\..*//')
|
||||
INVOICEDATED=$INVOICEBASE$YEAR$LMM
|
||||
INVOICEMD=$INVOICEDATED".md"
|
||||
INVOICEPDF=$INVOICEDATED".pdf"
|
||||
CSS=$INVOICEBASE".css"
|
||||
|
||||
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
|
0
examples/invoicing/invoice-script/logo.jpg
Normal file
0
examples/invoicing/invoice-script/logo.jpg
Normal file
2
examples/invoicing/invoice-script/time.timedot
Normal file
2
examples/invoicing/invoice-script/time.timedot
Normal file
@ -0,0 +1,2 @@
|
||||
2021-11-15
|
||||
client.ab.dev .... ....
|
Loading…
Reference in New Issue
Block a user