From: Paul Hänsch Date: Wed, 30 Sep 2020 08:34:12 +0000 (+0200) Subject: Merge commit 'fe67294a5c9b198ad3dd383dcedf53fab0015e1c' X-Git-Url: http://git.plutz.net/?p=invoices;a=commitdiff_plain;h=95b87c108d4b5829bb3184e3a2bd312140d87bbd;hp=fe67294a5c9b198ad3dd383dcedf53fab0015e1c Merge commit 'fe67294a5c9b198ad3dd383dcedf53fab0015e1c' --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be77cba --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +clients/ +invoices/ +senders/ +export/ +.*.swp +serverkey diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/cgilite.sh b/cgilite/cgilite.sh similarity index 100% rename from cgilite.sh rename to cgilite/cgilite.sh diff --git a/file.sh b/cgilite/file.sh similarity index 100% rename from file.sh rename to cgilite/file.sh diff --git a/html-sh.sed b/cgilite/html-sh.sed similarity index 100% rename from html-sh.sed rename to cgilite/html-sh.sed diff --git a/logging.sh b/cgilite/logging.sh similarity index 100% rename from logging.sh rename to cgilite/logging.sh diff --git a/session.sh b/cgilite/session.sh similarity index 100% rename from session.sh rename to cgilite/session.sh diff --git a/storage.sh b/cgilite/storage.sh similarity index 100% rename from storage.sh rename to cgilite/storage.sh diff --git a/clients.sh b/clients.sh new file mode 100755 index 0000000..052a0c8 --- /dev/null +++ b/clients.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +edit_client(){ + id="$1" + if [ -f "clients/$id" ]; then + read -r address hourly <"clients/$id" + fi + [ "$address" ] || address="address=" + [ "$hourly" ] || hourly="hourly=" + printf ' + [form method="POST" action="/update_client" + [hidden "id" "%s"] + + [label for=hourly Hourly Rate:] + [input #hourly type=number name=hourly value="%s"] + [submit "update" "%s" Update] + ]' \ + "$(HTML $id)" \ + "$(UNSTRING "${address#address=}" |HTML)" \ + "$(UNSTRING "${hourly#hourly=}" |grep -xE '[0-9]+')" \ + "$(transid "clients/$id")" +} + +list_clients(){ + [ -d clients/ ] && for c in clients/*; do + read -r address hourly <"$c" + address="$(UNSTRING "${address#address=}")" + [ "$address" ] || address="(no address)" + printf '[div .client .address %s + +[label Hourly Rate:] %s€ + [a href="/clients/%s" Edit]] + ' "$(HTML "$address")" \ + "$(HTML "${hourly#hourly=}")" \ + "$(HTML "${c#clients/}")" + done +} + +update_client(){ + local id="$(POST id |checkid)" + if [ "$(POST update)" = "$(transid "clients/$id")" ]; then + mkdir -p clients + printf 'address=%s hourly=%s' \ + "$(POST address |STRING)" "$(POST hourly |STRING)" \ + >"clients/$id" + + [ -d .git ] && { + git add "clients/$id" + git commit -m 'Update client info for "'"$(POST address |sed 1q)"'"' -- "clients/$id" + } >/dev/null + + REDIRECT "/clients/" + else + error Invalid Data "$id" "$(POST update)" + REDIRECT "/clients/$1" + fi +} diff --git a/invoices.cgi b/invoices.cgi new file mode 100755 index 0000000..674da89 --- /dev/null +++ b/invoices.cgi @@ -0,0 +1,92 @@ +#!/bin/sh + +_EXEC="$(realpath "${0%/*}")" +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/cgilite/logging.sh" +. "$_EXEC/cgilite/storage.sh" +. "$_EXEC/cgilite/session.sh" + +yield_page(){ +printf 'Content-Type: text/html; charset=utf-8\r\n\r\n' +"$_EXEC/cgilite/html-sh.sed" <.gitignore + git add clients/ invoices/ senders/ .gitignore + git commit -m 'initialisation of invoice repo' +} >&2 + +case $info in + /invoices.css) + . "$_EXEC/cgilite/file.sh" + FILE "$_EXEC/invoices.css" + ;; + /clients|/clients/) + . "$_EXEC/clients.sh" + { list_clients + printf '[a .new href="/clients/%s" New]' "$(timeid)" + } | yield_page clients + ;; + /clients/*) + . "$_EXEC/clients.sh" + edit_client "${info#/clients/}" |yield_page client + ;; + /update_client) + . "$_EXEC/clients.sh" + update_client + ;; + /senders|/senders/) + . "$_EXEC/senders.sh" + { list_senders + printf '[a .new href="/senders/%s" New]' "$(timeid)" + } | yield_page senders + ;; + /senders/*) + . "$_EXEC/senders.sh" + edit_sender "${info#/senders/}" |yield_page sender + ;; + /update_sender) + . "$_EXEC/senders.sh" + update_sender + ;; + /invoices|/invoices/) + . "$_EXEC/invoices.sh" + { printf '[a .new href="/invoices/%s" New]' "$(timeid)" + list_invoices + } | yield_page invoices + ;; + /invoices/*) + . "$_EXEC/invoices.sh" + edit_invoice "${info#/invoices/}" |yield_page invoice + ;; + /update_invoice) + . "$_EXEC/invoices.sh" + update_invoice + ;; + /export/*.pdf/*) + . "$_EXEC/cgilite/file.sh" + file="${info%/*}" + FILE "${file#/}" application/pdf + ;; + *) REDIRECT /invoices + ;; +esac diff --git a/invoices.css b/invoices.css new file mode 100755 index 0000000..592fb55 --- /dev/null +++ b/invoices.css @@ -0,0 +1,220 @@ +* { + margin: 0; padding: 0; + color: inherit; background-color: inherit; + font-size: medium; font-weight: normal; + text-decoration: none; + box-sizing: border-box; +} +body { + color: black; background-color: white; +} + +strong {font-weight: bold;} +em {text-decoration: italic;} + +h1 { + display: block; + margin: 1em 1em 0 1em; + font-weight: bold; + border-bottom: 1px solid #08F; +} + +/* ==================== MENU ==================== */ + +#menu { + margin: 0; padding: .5em 1em; + border-bottom: 1px solid black; +} +#menu a { + background-color: #EEF; + border: 1px solid black; + margin: .5em 1em .5em 0; + padding: .125em .5em; +} +#menu a:hover { + background-color: #FFF; + border-color: #888; +} + + +/* ==================== ADDRESS LISTS ==================== */ + +.invoices .invoice, +.clients .address, +.senders .address { + display: inline-block; + background-color: #EEE; + white-space: pre; + padding: 1em 1em .5em 1em; + margin: 1em 0 0 1em; + width: 20em; +} +.invoices .invoice { + white-space: normal; +} +.invoices .invoice h2{ + display: block; + letter-spacing: .25em; + text-align: center; + margin-bottom: -.5em; + font-size: 1.125em; +} +.invoices .invoice label { + font-weight: bold; +} +.invoices .invoice label::before { + white-space: pre; + content: '\0A'; +} +.invoices .invoice a, +.clients .address a, +.senders .address a, +a.new { + display: block; + background-color: #DEF; + text-align: center; + margin: .5em 3em 0 3em; + padding: .125em 1em; + border: 1px solid #08F; +} +.invoices .invoice a:hover, +.clients .address a:hover, +.senders .address a:hover, +a.new:hover { + border-color: #8EF; +} +a.new { + display: inline-block; + margin: 1em; +} + + +/* ==================== ADDRESS FORMS ==================== */ + +form select, +form textarea, +form input { + padding: .125em .5em; + background-color: #FFF; + text-align: left; + font-size: 12pt; +} +form input[type=number] { + width: 5em; + text-align: right; +} + +.invoice form, +.client form, +.sender form { + position: relative; + display: inline-block; + margin: 1em; padding: 1em; + background-color: #EEE; + text-align: right; +} +.client form textarea, +.sender form textarea { + display: block; + min-width: 30em; + min-height: 7em; + margin-bottom: .5em; +} +.sender form textarea { + min-height: 13em; +} +.client form label[for=hourly] { + margin-right: .5em; + line-height: 2em; + text-align: right; + font-weight: bold; +} +.invoice form button[type=submit], +.client form button[type=submit], +.sender form button[type=submit] { + margin: .5em 0 0 auto; + padding: .25em .5em; + background-color: #DEF; + border-radius: .25em; + border: 1px solid #08F; +} +.client form button[type=submit], +.sender form button[type=submit] { + display: block; +} + +/* ==================== INVOICE FORMS ==================== */ +.invoice form select { + display: inline-block; + min-width: 20em; + margin-bottom: .5em; +} + +.invoice form label { + font-weight: bold; + line-height: 2em; +} + +.invoice form input#date { + margin-right: .25em; +} + +.invoice form label::before { + white-space: pre; + content: '\0A'; +} +.invoice form label:first-of-type::before { + content: ''; +} + +.invoice form label[for=vatsb], +.invoice form label[for=vatrate] { + display: block; + text-align: left; +} +.invoice form label[for=vatnett], +.invoice form label[for=vatgross] { + display: inline-block; + float: left; margin-right: 1em; + line-height: 1em; +} +.invoice form input[type=radio] { display: none; } +.invoice form input[type=radio] + label::before { + content: '( ) '; +} +.invoice form input:checked + label[for^=vat]::before { + content: '(x) '; +} + +.invoice form table { + width: 30em; + margin-top: 1em; + border-spacing: 0; +} +.invoice form table tr > th { + text-align: left; + padding-left: .5em; + font-weight: bold; +} +.invoice form table tr:last-child { + font-family: monospace; + white-space: pre; + text-align: right; +} +.invoice form table tr > th:nth-child(3), +.invoice form table tr > td:nth-child(4), +.invoice form table tr > th:nth-child(4) { + text-align: right; +} +.invoice form table tr > :nth-child(1) { width: 7em; } +.invoice form table tr > :nth-child(2) { width: 15em; } +.invoice form table tr > :nth-child(3) { width: 4em; } +.invoice form table tr > :nth-child(4) { width: 4em; font-family: monospace; } +.invoice form table tr > * textarea, +.invoice form table tr > * input[type=number], +.invoice form table tr > * input { + display: block; + font-size: 12pt; + width: 100%; height: 100%; + height: 4.625em; +} diff --git a/invoices.sh b/invoices.sh new file mode 100755 index 0000000..a88bc7f --- /dev/null +++ b/invoices.sh @@ -0,0 +1,298 @@ +#!/bin/sh + +sender_list(){ + local select="$1" n name address iban bic + [ -d senders/ ] && for n in '' senders/*; do + [ "$n" ] && read -r address iban bic x<"$n" + name="$(UNSTRING "${address#address=}" |sed q |HTML)" + [ "${n#senders/}" = "$select" ] \ + && printf '' "${n#senders/}" "$name" \ + || printf '' "${n#senders/}" "$name" + done +} + +client_list(){ + local select="$1" n address hourly name + [ -d clients/ ] && for n in '' clients/*; do + [ "$n" ] && read -r address hourly x<"$n" + name="$(UNSTRING "${address#address=}" |sed q |HTML)" + [ "${n#clients/}" = "$select" ] \ + && printf '' "${n#clients/}" "$name" \ + || printf '' "${n#clients/}" "$name" + done +} + +list_invoices(){ + [ -d invoices/ ] || return 0 + + printf '[h1 Open]' + for i in invoices/*; do case "$(sed 1q <$i)" in + *status=open*) list_invoice "$i";; + *status=*) :;; + *) list_invoice "$i";; + esac; done + + for n in resent:Resent sent:Sent paid:Paid cancelled:Cancelled; do + printf '[h1 %s]' "${n#*:}" + for i in invoices/*; do case "$(sed 1q <$i)" in + *status=${n%:*}*) list_invoice "$i";; + esac; done + done +} + +list_invoice(){ + local i="$1" + local sender client date number vat vatrate iban bic hourly \ + taxtype nett tax gross total status + + read -r sender client date number vat vatrate hourly status x<<-EOF + $(sed q "$i") + EOF + + [ ! -f "senders/${sender#sender=}" ] \ + && sender="(unset)" \ + || read -r sender iban bic x<"senders/${sender#sender=}" + + [ ! -f "clients/${client#client=}" ] \ + && client="(unset)" \ + || read -r client hourly x<"clients/${client#client=}" + + [ "${date#date=}" -ge 0 ] 2>&- \ + && date="$(date -d "@${date#date=}" +%x)" \ + || date="(unset)" + + read -r taxtype nett tax gross x<<-EOF + $(invoice_total "${i#invoices/}") + EOF + case $taxtype in + nett) total="${nett} € + VAT";; + gross) total="${gross} € incl. VAT";; + *) total="${gross} €";; + esac + + case $status in + status=sent|status=resent|status=paid|status=cancelled) + status="${status#status=}" + ;; + *) status=open;; + esac + + printf '[div .invoice + [h2 + %s] + [label From:] %s [label To:] %s [label on] %s + [label Amount:] %s + [a href="/invoices/%s" Edit] + ]' "$(UNSTRING "${number#number=}" |HTML)" \ + "$(UNSTRING "${sender#address=}" |sed q |HTML)" \ + "$(UNSTRING "${client#address=}" |sed q |HTML)" "$(HTML "$date")" \ + "$total" \ + "$(HTML ${i#invoices/})" +} + +edit_invoice(){ + local id="$1" sender client date number vat vatrate caddress hourly \ + taxtype nett tax gross status + + if [ -f "invoices/$id" ]; then + read -r sender client date number vat vatrate hourly status x<<-EOF + $(sed q "invoices/$id") + EOF + fi + + case $status in + status=sent|status=resent|status=paid|status=cancelled) + status="${status#status=}" + ;; + *) status=open;; + esac + + [ "${date#date=}" -ge 0 ] 2>&- \ + && date="$(date -d "@${date#date=}" +%F)" \ + || date="$(date +%F)" + [ "${number#number=}" ] \ + && number="${number#number=}" \ + || number="$(date +%s)" + [ "${vatrate#vatrate=}" -ge 0 ] 2>&- \ + && vatrate="${vatrate#vatrate=}" \ + || vatrate=19 + + [ -f "clients/${client#client=}" ] \ + && read -r caddress chourly x<"clients/${client#client=}" + [ "${chourly#hourly=}" -ge 0 ] 2>&- \ + && chourly="${chourly#hourly=}" \ + || chourly=0 + [ "${hourly#hourly=}" -ge 0 ] 2>&- \ + && hourly="${hourly#hourly=}" \ + || hourly="${chourly}" + + tid="$(transid "invoices/$id")" + + read -r taxtype nett tax gross x<<-EOF + $(invoice_total "$id") + EOF + + cat <<-EOF + [form method="POST" action="/update_invoice" + [hidden "id" "$(HTML "$id")"] + + [label Sender:] + [select name=sender + $(sender_list "${sender#sender=}") + ] + + [label Client:] + [select name=client + $(client_list "${client#client=}") + ] + + [label for=number Invoice Number:] + [input #number name=number value="$(UNSTRING "${number}" |HTML)"] + + [label for=date Date:] + [input #date name=date value="${date}" placeholder="YYYY-MM-TT"] + + [label for=hourly Hourly Rate:] + [input #hourly type=number name=hourly value="${hourly}"] + + [radio "vat" "smallbusiness" #vatsb $([ "${vat#vat=}" = smallbusiness ] && printf checked) ] + [label for=vatsb Small business exemption from VAT] + [radio "vat" "nett" #vatnett $([ "${vat#vat=}" = nett ] && printf checked)] + [label for=vatnett Nett] + [radio "vat" "gross" #vatgross $([ "${vat#vat=}" = gross ] && printf checked)] + [label for=vatgross Gross] + [label for=vatrate VAT Rate: [input type=number name="vatrate" value="${vatrate}"]% ] + + [table + [tr [th Date] [th Work] [th Hours] [th Price] ] +$({ sed 1d "invoices/$id"; printf 'time= work= hours=\n'; } \ + | while read -r time work hours x; do + hours="$(UNSTRING "${hours#hours=}" |grep -m1 -xE '[0-9]+' || printf 0)" + printf '[tr + [td [textarea name=time +%s] ] + [td [textarea name=work +%s] ] + [td [input type=number name=hours value="%s"] ] + [td %s] + ]' "$(UNSTRING "${time#time=}" |HTML)" \ + "$(UNSTRING "${work#work=}" |HTML)" \ + "$hours" "$((hours * hourly)) €" + done +) + [tr [td colspan=4 + $(case $taxtype in + (nett) printf 'Sum: %7.2f €[br] + VAT: %7.2f €[br] [strong Total:] %7.2f €' \ + $nett $tax $gross ;; + (gross) printf '[strong Total:] %7.2f €[br] incl. nett: %7.2f €[br] + VAT: %7.2f €' \ + $gross $nett $tax ;; + (*) printf '[strong Total:] %.2f €' $nett ;; + esac) + ]] + ] + [select name=status + [option value=open $( [ $status = open ] && printf selected=selected ) Open] + [option value=sent $( [ $status = sent ] && printf selected=selected ) Sent] + [option value=resent $( [ $status = resent ] && printf selected=selected ) Resent] + [option value=paid $( [ $status = paid ] && printf selected=selected ) Paid] + [option value=cancelled $( [ $status = cancelled ] && printf selected=selected ) Cancelled] + ] + [submit "genpdf" "$tid" Export PDF] + [submit "update" "$tid" Update] + ] + EOF +} + +invoice_total(){ + local id="$1" sender client date number vat vatrate \ + total=0 caddress hourly time work hours + + if [ -f "invoices/$id" ]; then + read -r sender client date number vat vatrate hourly x<<-EOF + $(sed q "invoices/$id") + EOF + + [ "${hourly#hourly=}" -gt 0 ] 2>&- \ + && hourly="${hourly#hourly=}" \ + || hourly=0 + [ "${vatrate#vatrate=}" -ge 0 ] 2>&- \ + && vatrate="${vatrate#vatrate=}" \ + || vatrate=19 + + sed 1d "invoices/$id" \ + | { while read -r time work hours; do + [ "${hours#hours=}" -gt 0 ] 2>&- \ + && hours="${hours#hours=}" \ + || hours=0 + total=$((total + hours * hourly)) + done + case $vat in + vat=nett) + awk "BEGIN { + printf \"nett %.2f %.2f %.2f\", + $total, int($total * $vatrate + .5) / 100, + $total + int($total * $vatrate + .5) / 100 + }" ;; + vat=gross) + awk "BEGIN { + printf \"gross %.2f %.2f %.2f\", + $total - int($total / (100 + $vatrate) * $vatrate * 100 + .5) / 100, + int($total / (100 + $vatrate) * $vatrate * 100 + .5) / 100, $total + }" ;; + *) + awk "BEGIN { + printf \"notax %.2f %.2f %.2f\", + $total, 0, $total + }" ;; + esac + } + else + printf %.2f\\n 0 + fi +} + +update_invoice(){ + local id="$(POST id |checkid)" extra=0 tid + tid="$(transid invoices/$id)" + + if [ "$(POST update)" = "$tid" ] || [ "$(POST genpdf)" = "$tid" ]; then + mkdir -p invoices + + for n in "$(POST_COUNT time)" "$(POST_COUNT work)" "$(POST_COUNT hours)"; do + [ "$n" -gt "$extra" ] && extra="$n" + done + + { printf 'sender=%s client=%s date=%s number=%s vat=%s vatrate=%s hourly=%s status=%s\n' \ + "$(POST sender)" "$(POST client)" \ + "$(date -d "$(POST date)" +%s)" \ + "$(POST number |STRING)" \ + "$(POST vat |grep -m1 -xE 'smallbusiness|gross|nett')" \ + "$(POST vatrate |grep -m1 -xE '[0-9]+')" \ + "$(POST hourly |grep -m1 -xE '[0-9]+')" \ + "$(POST status |grep -m1 -xE 'open|sent|resent|paid|cancelled')" + for n in $(seq 1 $extra); do + printf 'time=%s work=%s hours=%s\n' \ + "$(POST time $n |STRING)" "$(POST work $n |STRING)" \ + "$(POST hours $n |STRING)" \ + | grep -xvF 'time= work= hours=0' + done + } >"invoices/$id" + + [ -d .git ] && { + git add "invoices/$id" + git commit -m 'Update invoice info for "'"$(POST number)"'"' -- "invoices/$id" + } >/dev/null + fi + if [ "$(POST genpdf)" ]; then + read -r sender client date x<"invoices/$id" + read -r saddress x <"senders/${sender#sender=}" + read -r caddress x <"clients/${client#client=}" + filename="Rechnung $(UNSTRING "${saddress#address=}" |sed 1q) an $(UNSTRING "${caddress#address=}" |sed 1q) $(date -d@"${date#date=}" +%F).pdf" + + . $_EXEC/odtgen.sh + genpdf "$id" + REDIRECT "/export/${id}.pdf/$(URL "${filename}" |sed s/%0D//g)" + exit 0 + fi + REDIRECT "/invoices/$id" +} diff --git a/odtgen.sh b/odtgen.sh new file mode 100755 index 0000000..67232a9 --- /dev/null +++ b/odtgen.sh @@ -0,0 +1,292 @@ +#!/bin/zsh + +odt_manifest(){ + cat <<-EOF + + + + + + + EOF +} + +odt_stylesheet(){ + local iban="$1" bic="$2" + + cat <<-EOF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bankverbindung - IBAN: + ${iban} + | BIC: + ${bic} + + + + + + EOF +} + +odt_content(){ + local id="$1" sender="$2" client="$3" date="$4" number="$5" vat="$6" vatrate="$7" hourly="$8" \ + taxtype nett tax gross + read -r taxtype nett tax gross <<-EOF + $(invoice_total "$id") + EOF + + cat <<-EOF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(date -d @${date} +%d.%m.%Y) + + + + $(HTML "$sender" |sed -r 's;( \;)? \;|(�?[Dd]\;)?�?[aA]\;|\r?$;;g') + + + + $(HTML "$client" |sed -r 's;( \;)? \;|(�?[Dd]\;)?�?[aA]\;|\r?$;;g') + + Rechnung ${number} + + + + Vergütung: ${hourly} € / Std.$(case $vat in + (smallbusiness) + printf 'Gemäß Kleinunternehmerregelung (§19 Umsatzsteuergesetz) wird keine Umsatzsteuer berechnet.';; + (nett) printf 'zuzüglich %i%% MwSt.' "$vatrate";; + (gross) printf '(einschließlich %i%% MwSt.)' "$vatrate";; + esac) + + + + + + + + + + Datum + + + Art der Leistung + + + Stundenzahl + + + Betrag + + + + $(sed 1d "invoices/$id" |while read -r time work hours; do + time="$(UNSTRING "${time#time=}")" + work="$(UNSTRING "${work#work=}")" + hours="$(UNSTRING "${hours#hours=}")" + cat <<-TROW + + + ${time} + + + ${work} + + + ${hours} + + + $((${hourly} * ${hours})) € + + + TROW + done) + + + + + $(case $taxtype in + (nett) cat <<-TCELL + + + Zwischensumme: + zzgl. ${vatrate}% MwSt.: + + zu zahlender Betrag: + + + + ${nett} € + ${tax} € + + ${gross} € + + TCELL + ;; + (gross) cat <<-TCELL + + zu zahlender Betrag: + + + enthaltene MwSt (${vatrate}%): + enthaltener Netto-Betrag: + + + ${gross} € + + + ${tax} € + ${nett} € + + TCELL + ;; + (*) cat <<-TCELL + + zu zahlender Betrag: + + + ${gross} € + + TCELL + ;; + esac) + + + + + + EOF +} + +genodt(){ + local id="$1" exdir="export/$1" + [ -d "$exdir" ] && { + rm "$exdir/content.xml" "$exdir/styles.xml" "$exdir/META-INF/manifest.xml" + rmdir "$exdir/META-INF/" "$exdir/" + } + + read -r sender client date number vat vatrate hourly x<<-EOF + $(sed q "invoices/$id") + EOF + read -r sender iban bic x<"senders/${sender#sender=}" + read -r client chourly x<"clients/${client#client=}" + + mkdir -p "$exdir/META-INF" + odt_manifest >"$exdir/META-INF/manifest.xml" + odt_stylesheet "$(UNSTRING "${iban#iban=}")" "$(UNSTRING "${bic#bic=}")" >"$exdir/styles.xml" + odt_content "$id" \ + "$(UNSTRING "${sender#address=}")" \ + "$(UNSTRING "${client#address=}")" \ + "${date#date=}" \ + "$(UNSTRING "${number#number=}")" \ + "${vat#vat=}" "${vatrate#vatrate=}" \ + "${hourly#hourly=}" \ + >"$exdir/content.xml" + + [ -f "export/${id}.odt" ] && rm "export/${id}.odt" + ( cd "$exdir" + zip -q "../${id}.odt" content.xml styles.xml META-INF/manifest.xml + ) + rm "$exdir/content.xml" "$exdir/styles.xml" "$exdir/META-INF/manifest.xml" + rmdir "$exdir/META-INF/" "$exdir/" +} + +genpdf(){ + local id="$1" + genodt "$id" + lowriter --convert-to pdf --outdir "export/" "export/${id}.odt" >/dev/null + rm "export/${id}.odt" +} + diff --git a/senders.sh b/senders.sh new file mode 100755 index 0000000..757ce22 --- /dev/null +++ b/senders.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +edit_sender(){ + local id="$1" address iban bic + [ -f "senders/$id" ] \ + && read -r address iban bic <"senders/$id" + + iban="$(UNSTRING "${iban#iban=}")" + bic="$(UNSTRING "${bic#bic=}")" + + [ "${address#address=}" ] \ + && address="$(UNSTRING "${address#address=}")" \ + || address="Name +Street +City + +Phone: +000 000000 + +Tax no. +xxx / 000 / ### +" + printf ' + [form method="POST" action="/update_sender" + [hidden "id" "%s"] + + [label IBAN:][input name=iban value="%s" placeholder=IBAN] + [label BIC:][input name=bic value="%s" placeholder=BIC] + [submit "update" "%s" Update] + ]' \ + "$(HTML $id)" "$(HTML "${address}")" \ + "$(HTML "${iban}")" "$(HTML "${bic}")" \ + "$(transid "senders/$id")" +} + +list_senders(){ + [ -d senders/ ] && for s in senders/*; do + read -r address iban bic <"$s" + + [ "${address#address=}" ] \ + && address="$(UNSTRING "${address#address=}")" \ + || address="(no address)" + + printf '[div .sender .address %s[a href="/senders/%s" Edit]] + ' "$(HTML "$address")" "$(HTML "${s#senders/}")" + done +} + +update_sender(){ + id="$(POST id |checkid)" + if [ "$(POST update)" = "$(transid "senders/$id")" ]; then + mkdir -p senders + printf 'address=%s iban=%s bic=%s\n' \ + $(POST address |STRING) $(POST iban |STRING) \ + $(POST bic |STRING) >"senders/$id" + + [ -d .git ] && { + git add "senders/$id" + git commit -m 'Update sender info for "'"$(POST address |sed 1q)"'"' -- "senders/$id" + } >/dev/null + + REDIRECT "/senders/" + else + REDIRECT "/senders/$id" + fi +}