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