]> git.plutz.net Git - invoices/blob - invoices.sh
sort invoice list, newest to oldest
[invoices] / invoices.sh
1 #!/bin/sh
2
3 sender_list(){
4   local select="$1" n name address iban bic
5   [ -d senders/ ] && for n in '' senders/*; do
6     [ "$n" ] &&  read -r address iban bic x<"$n"
7     name="$(UNSTRING "${address#address=}" |sed q |HTML)"
8     [ "${n#senders/}" = "$select" ] \
9     && printf '<option value="%s" selected=selected>%s</option>' "${n#senders/}" "$name" \
10     || printf '<option value="%s">%s</option>' "${n#senders/}" "$name"
11   done
12 }
13
14 client_list(){
15   local select="$1" n address hourly name
16   [ -d clients/ ] && for n in '' clients/*; do
17     [ "$n" ] && read -r address hourly x<"$n"
18     name="$(UNSTRING "${address#address=}" |sed q |HTML)"
19     [ "${n#clients/}" = "$select" ] \
20     && printf '<option value="%s" selected=selected>%s</option>' "${n#clients/}" "$name" \
21     || printf '<option value="%s">%s</option>' "${n#clients/}" "$name"
22   done
23 }
24
25 list_invoices(){
26   [ -d invoices/ ] || return 0
27
28   printf '[h1 Open]'
29   for i in invoices/*; do case "$(sed 1q <$i)" in
30     *status=open*) list_invoice "$i";;
31     *status=*) :;;
32     *) list_invoice "$i";;
33   esac; done
34
35   for n in resent:Resent sent:Sent paid:Paid cancelled:Cancelled; do
36     printf '[h1 %s]' "${n#*:}"
37     printf "%s\n" invoices/* \
38     | sort -r \
39     | while read i; do case "$(sed 1q <$i)" in
40       *status=${n%:*}*) list_invoice "$i";;
41     esac; done
42   done
43 }
44
45 list_invoice(){
46   local i="$1"
47   local sender client date number vat vatrate iban bic hourly \
48         taxtype nett tax gross total status
49
50   read -r sender client date number vat vatrate hourly status x<<-EOF
51         $(sed q "$i")
52         EOF
53
54   [ ! -f "senders/${sender#sender=}" ] \
55   && sender="(unset)" \
56   || read -r sender iban bic x<"senders/${sender#sender=}"
57
58   [ ! -f "clients/${client#client=}" ] \
59   && client="(unset)" \
60   || read -r client hourly x<"clients/${client#client=}"
61
62   [ "${date#date=}" -ge 0 ] 2>&- \
63   && date="$(date -d "@${date#date=}" +%x)" \
64   || date="(unset)"
65
66   read -r taxtype nett tax gross x<<-EOF
67         $(invoice_total "${i#invoices/}")
68         EOF
69   case $taxtype in
70     nett)  total="${nett} € + VAT";;
71     gross) total="${gross} € incl. VAT";;
72     *) total="${gross} €";;
73   esac
74
75   case $status in
76     status=sent|status=resent|status=paid|status=cancelled)
77       status="${status#status=}"
78     ;;
79     *) status=open;;
80   esac
81
82   printf '[div .invoice
83     [h2
84         %s]
85     [label From:] %s [label To:] %s [label on] %s
86     [label Amount:] %s
87     [a href="/invoices/%s" Edit]
88   ]' "$(UNSTRING "${number#number=}" |HTML)" \
89      "$(UNSTRING "${sender#address=}" |sed q |HTML)" \
90      "$(UNSTRING "${client#address=}" |sed q |HTML)" "$(HTML "$date")" \
91      "$total" \
92      "$(HTML ${i#invoices/})"
93 }
94
95 edit_invoice(){
96   local id="$1" sender client date number vat vatrate caddress hourly \
97         taxtype nett tax gross status
98
99   if [ -f "invoices/$id" ]; then
100     read -r sender client date number vat vatrate hourly status x<<-EOF
101         $(sed q "invoices/$id")
102         EOF
103   fi
104
105   case $status in
106     status=sent|status=resent|status=paid|status=cancelled)
107       status="${status#status=}"
108     ;;
109     *) status=open;;
110   esac
111
112   [ "${date#date=}" -ge 0 ] 2>&- \
113   && date="$(date -d "@${date#date=}" +%F)" \
114   || date="$(date +%F)"
115   [ "${number#number=}" ] \
116   && number="${number#number=}" \
117   || number="$(date +%s)"
118   [ "${vatrate#vatrate=}" -ge 0 ] 2>&- \
119   && vatrate="${vatrate#vatrate=}" \
120   || vatrate=19
121
122   [ -f "clients/${client#client=}" ] \
123   && read -r caddress chourly x<"clients/${client#client=}"
124   [ "${chourly#hourly=}" -ge 0 ] 2>&- \
125   && chourly="${chourly#hourly=}" \
126   || chourly=0
127   [ "${hourly#hourly=}" -ge 0 ] 2>&- \
128   && hourly="${hourly#hourly=}" \
129   || hourly="${chourly}"
130
131   tid="$(transid "invoices/$id")"
132
133   read -r taxtype nett tax gross x<<-EOF
134         $(invoice_total "$id")
135         EOF
136
137   cat <<-EOF 
138         [form method="POST" action="/update_invoice"
139           [hidden "id" "$(HTML "$id")"]
140         
141           [label Sender:]
142           [select name=sender
143             $(sender_list "${sender#sender=}")
144           ]
145         
146           [label Client:]
147           [select name=client
148             $(client_list "${client#client=}")
149           ]
150         
151           [label for=number Invoice Number:]
152           [input #number name=number value="$(UNSTRING "${number}" |HTML)"]
153         
154           [label for=date Date:]
155           [input #date name=date value="${date}" placeholder="YYYY-MM-TT"]
156         
157           [label for=hourly Hourly Rate:]
158           [input #hourly type=number name=hourly value="${hourly}"]
159
160           [radio "vat" "smallbusiness" #vatsb $([ "${vat#vat=}" = smallbusiness ] && printf checked) ]
161           [label for=vatsb Small business exemption from VAT]
162           [radio "vat" "nett" #vatnett $([ "${vat#vat=}" = nett ] && printf checked)]
163           [label for=vatnett Nett]
164           [radio "vat" "gross" #vatgross $([ "${vat#vat=}" = gross ] && printf checked)]
165           [label for=vatgross Gross]
166           [label for=vatrate VAT Rate: [input type=number name="vatrate" value="${vatrate}"]% ]
167         
168           [table
169             [tr [th Date] [th Work] [th Hours] [th Price] ]
170 $({ sed 1d "invoices/$id"; printf 'time= work= hours=\n'; } \
171   | while read -r time work hours x; do
172     hours="$(UNSTRING "${hours#hours=}" \
173              |grep -m1 -xE '\.[0-9]+|[0-9]+\.?[0-9]*' || printf 0)"
174     printf '[tr
175             [td [textarea name=time
176 %s] ]
177             [td [textarea name=work
178 %s] ]
179             [td [input type=number name=hours value="%g" step=any] ]
180             [td %s]
181    ]' "$(UNSTRING "${time#time=}" |HTML)" \
182       "$(UNSTRING "${work#work=}" |HTML)" \
183       "$hours" \
184       "$(awk "BEGIN { printf \"%.2f €\", ${hours} * ${hourly}; }")"
185   done
186 )
187             [tr [td colspan=4 
188             $(case $taxtype in
189               (nett)  printf 'Sum: %7.2f €[br] + VAT: %7.2f €[br] [strong Total:] %7.2f €' \
190                       $nett $tax $gross ;;
191               (gross) printf '[strong Total:] %7.2f €[br] incl. nett: %7.2f €[br] + VAT: %7.2f €' \
192                       $gross $nett $tax ;;
193               (*) printf '[strong Total:] %.2f €' $nett ;;
194             esac)
195             ]]
196           ]
197           [select name=status
198              [option value=open      $( [ $status = open      ] && printf selected=selected ) Open]
199              [option value=sent      $( [ $status = sent      ] && printf selected=selected ) Sent]
200              [option value=resent    $( [ $status = resent    ] && printf selected=selected ) Resent]
201              [option value=paid      $( [ $status = paid      ] && printf selected=selected ) Paid]
202              [option value=cancelled $( [ $status = cancelled ] && printf selected=selected ) Cancelled]
203           ]
204           [submit "genpdf" "$tid" Export PDF]
205           [submit "update" "$tid" Update]
206         ]
207         EOF
208 }
209
210 invoice_total(){
211   local id="$1" sender client date number vat vatrate \
212         total=0 caddress hourly time work hours
213
214   if [ -f "invoices/$id" ]; then
215     read -r sender client date number vat vatrate hourly x<<-EOF
216         $(sed q "invoices/$id")
217         EOF
218
219     [ "${hourly#hourly=}" -gt 0 ] 2>&- \
220     && hourly="${hourly#hourly=}" \
221     || hourly=0
222     [ "${vatrate#vatrate=}" -ge 0 ] 2>&- \
223     && vatrate="${vatrate#vatrate=}" \
224     || vatrate=19
225
226     sed 1d "invoices/$id" \
227     | { while read -r time work hours; do
228         [ "${hours#hours=}" ] 2>&- \
229         && hours="${hours#hours=}" \
230         || hours=0
231         total=$(awk "BEGIN { printf \"%.2f\", ${total} + ${hours} * ${hourly}; }")
232       done
233       case $vat in
234         vat=nett)
235           awk "BEGIN {
236             printf \"nett       %.2f    %.2f    %.2f\",
237               $total, int($total * $vatrate + .5) / 100,
238               $total + int($total * $vatrate + .5) / 100
239           }" ;;
240         vat=gross)
241           awk "BEGIN {
242             printf \"gross      %.2f    %.2f    %.2f\",
243               $total - int($total / (100 + $vatrate) * $vatrate * 100 + .5) / 100,
244               int($total / (100 + $vatrate) * $vatrate * 100 + .5) / 100, $total
245           }" ;;
246         *)
247           awk "BEGIN {
248             printf \"notax      %.2f    %.2f    %.2f\",
249               $total, 0, $total
250           }" ;;
251       esac
252     }
253   else
254     printf %.2f\\n 0
255   fi
256 }
257
258 update_invoice(){
259   local id="$(POST id |checkid)" extra=0 tid
260   tid="$(transid invoices/$id)"
261
262   if [ "$(POST update)" = "$tid" ] || [ "$(POST genpdf)" = "$tid" ]; then
263     mkdir -p invoices
264
265     for n in "$(POST_COUNT time)" "$(POST_COUNT work)" "$(POST_COUNT hours)"; do
266       [ "$n" -gt "$extra" ] && extra="$n"
267     done
268
269     { printf 'sender=%s client=%s       date=%s number=%s       vat=%s  vatrate=%s      hourly=%s       status=%s\n' \
270         "$(POST sender)" "$(POST client)" \
271         "$(date -d "$(POST date)" +%s)" \
272         "$(POST number |STRING)" \
273         "$(POST vat |grep -m1 -xE 'smallbusiness|gross|nett')" \
274         "$(POST vatrate |grep -m1 -xE '[0-9]+')" \
275         "$(POST hourly |grep -m1 -xE '[0-9]+')" \
276         "$(POST status |grep -m1 -xE 'open|sent|resent|paid|cancelled')"
277       for n in $(seq 1 $extra); do
278         printf 'time=%s work=%s hours=%s\n' \
279           "$(POST time $n |STRING)" "$(POST work $n |STRING)" \
280           "$(POST hours $n |STRING)" \
281         | grep -xvF 'time=\     work=\  hours=0'
282       done
283     } >"invoices/$id"
284
285     [ -d .git ] && {
286       git add "invoices/$id"
287       git commit -m 'Update invoice info for "'"$(POST number)"'"' -- "invoices/$id"
288     } >/dev/null
289   fi
290   if [ "$(POST genpdf)" ]; then
291     read -r sender client date x<"invoices/$id"
292     read -r saddress x <"senders/${sender#sender=}"
293     read -r caddress x <"clients/${client#client=}"
294     filename="Rechnung $(UNSTRING "${saddress#address=}" |sed 1q) an $(UNSTRING "${caddress#address=}" |sed 1q) $(date -d@"${date#date=}" +%F).pdf"
295
296     . $_EXEC/odtgen.sh
297     genpdf "$id"
298     REDIRECT "/export/${id}.pdf/$(URL "${filename}" |sed s/%0D//g)"
299     exit 0
300   fi
301   REDIRECT "/invoices/$id"
302 }