;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 | ||||
| ... | ||||
| 
 | ||||
| { 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 | ||||
| ... | ||||
| 
 | ||||
| { 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