]> git.plutz.net Git - invoices/blob - invoices.sh
9ed6dfa56388fb2822d3c4129b2212e9c2ae12ac
[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=}" \
171              |grep -m1 -xE '\.[0-9]+|[0-9]+\.?[0-9]*' || printf 0)"
172     printf '[tr
173             [td [textarea name=time
174 %s] ]
175             [td [textarea name=work
176 %s] ]
177             [td [input type=number name=hours value="%g" step=any] ]
178             [td %s]
179    ]' "$(UNSTRING "${time#time=}" |HTML)" \
180       "$(UNSTRING "${work#work=}" |HTML)" \
181       "$hours" \
182       "$(awk "BEGIN { printf \"%.2f €\", ${hours} * ${hourly}; }")"
183   done
184 )
185             [tr [td colspan=4 
186             $(case $taxtype in
187               (nett)  printf 'Sum: %7.2f €[br] + VAT: %7.2f €[br] [strong Total:] %7.2f €' \
188                       $nett $tax $gross ;;
189               (gross) printf '[strong Total:] %7.2f €[br] incl. nett: %7.2f €[br] + VAT: %7.2f €' \
190                       $gross $nett $tax ;;
191               (*) printf '[strong Total:] %.2f €' $nett ;;
192             esac)
193             ]]
194           ]
195           [select name=status
196              [option value=open      $( [ $status = open      ] && printf selected=selected ) Open]
197              [option value=sent      $( [ $status = sent      ] && printf selected=selected ) Sent]
198              [option value=resent    $( [ $status = resent    ] && printf selected=selected ) Resent]
199              [option value=paid      $( [ $status = paid      ] && printf selected=selected ) Paid]
200              [option value=cancelled $( [ $status = cancelled ] && printf selected=selected ) Cancelled]
201           ]
202           [submit "genpdf" "$tid" Export PDF]
203           [submit "update" "$tid" Update]
204         ]
205         EOF
206 }
207
208 invoice_total(){
209   local id="$1" sender client date number vat vatrate \
210         total=0 caddress hourly time work hours
211
212   if [ -f "invoices/$id" ]; then
213     read -r sender client date number vat vatrate hourly x<<-EOF
214         $(sed q "invoices/$id")
215         EOF
216
217     [ "${hourly#hourly=}" -gt 0 ] 2>&- \
218     && hourly="${hourly#hourly=}" \
219     || hourly=0
220     [ "${vatrate#vatrate=}" -ge 0 ] 2>&- \
221     && vatrate="${vatrate#vatrate=}" \
222     || vatrate=19
223
224     sed 1d "invoices/$id" \
225     | { while read -r time work hours; do
226         [ "${hours#hours=}" ] 2>&- \
227         && hours="${hours#hours=}" \
228         || hours=0
229         total=$(awk "BEGIN { printf \"%.2f\", ${total} + ${hours} * ${hourly}; }")
230       done
231       case $vat in
232         vat=nett)
233           awk "BEGIN {
234             printf \"nett       %.2f    %.2f    %.2f\",
235               $total, int($total * $vatrate + .5) / 100,
236               $total + int($total * $vatrate + .5) / 100
237           }" ;;
238         vat=gross)
239           awk "BEGIN {
240             printf \"gross      %.2f    %.2f    %.2f\",
241               $total - int($total / (100 + $vatrate) * $vatrate * 100 + .5) / 100,
242               int($total / (100 + $vatrate) * $vatrate * 100 + .5) / 100, $total
243           }" ;;
244         *)
245           awk "BEGIN {
246             printf \"notax      %.2f    %.2f    %.2f\",
247               $total, 0, $total
248           }" ;;
249       esac
250     }
251   else
252     printf %.2f\\n 0
253   fi
254 }
255
256 update_invoice(){
257   local id="$(POST id |checkid)" extra=0 tid
258   tid="$(transid invoices/$id)"
259
260   if [ "$(POST update)" = "$tid" ] || [ "$(POST genpdf)" = "$tid" ]; then
261     mkdir -p invoices
262
263     for n in "$(POST_COUNT time)" "$(POST_COUNT work)" "$(POST_COUNT hours)"; do
264       [ "$n" -gt "$extra" ] && extra="$n"
265     done
266
267     { printf 'sender=%s client=%s       date=%s number=%s       vat=%s  vatrate=%s      hourly=%s       status=%s\n' \
268         "$(POST sender)" "$(POST client)" \
269         "$(date -d "$(POST date)" +%s)" \
270         "$(POST number |STRING)" \
271         "$(POST vat |grep -m1 -xE 'smallbusiness|gross|nett')" \
272         "$(POST vatrate |grep -m1 -xE '[0-9]+')" \
273         "$(POST hourly |grep -m1 -xE '[0-9]+')" \
274         "$(POST status |grep -m1 -xE 'open|sent|resent|paid|cancelled')"
275       for n in $(seq 1 $extra); do
276         printf 'time=%s work=%s hours=%s\n' \
277           "$(POST time $n |STRING)" "$(POST work $n |STRING)" \
278           "$(POST hours $n |STRING)" \
279         | grep -xvF 'time=\     work=\  hours=0'
280       done
281     } >"invoices/$id"
282
283     [ -d .git ] && {
284       git add "invoices/$id"
285       git commit -m 'Update invoice info for "'"$(POST number)"'"' -- "invoices/$id"
286     } >/dev/null
287   fi
288   if [ "$(POST genpdf)" ]; then
289     read -r sender client date x<"invoices/$id"
290     read -r saddress x <"senders/${sender#sender=}"
291     read -r caddress x <"clients/${client#client=}"
292     filename="Rechnung $(UNSTRING "${saddress#address=}" |sed 1q) an $(UNSTRING "${caddress#address=}" |sed 1q) $(date -d@"${date#date=}" +%F).pdf"
293
294     . $_EXEC/odtgen.sh
295     genpdf "$id"
296     REDIRECT "/export/${id}.pdf/$(URL "${filename}" |sed s/%0D//g)"
297     exit 0
298   fi
299   REDIRECT "/invoices/$id"
300 }