From: Paul Hänsch Date: Wed, 15 May 2024 14:37:59 +0000 (+0200) Subject: date time helper X-Git-Url: http://git.plutz.net/?p=confetti;a=commitdiff_plain;h=HEAD;hp=7658855512f84528af734a2a90f48cfed8d48d5c date time helper --- diff --git a/cards/l10n.sh b/cards/l10n.sh index 2d9dc06..fde98e0 100755 --- a/cards/l10n.sh +++ b/cards/l10n.sh @@ -37,6 +37,7 @@ l10n(){ X-ZACK-LEAVEDATE) printf %s "Abmelde­datum";; X-ZACK-JOINDATE_short) printf %s "Anm.";; X-ZACK-LEAVEDATE_short) printf %s "Abm.";; + X-IBAN) printf %s "IBAN";; *) l10n_global "$word";; esac diff --git a/cards/list.sh b/cards/list.sh index 519005c..e12d760 100755 --- a/cards/list.sh +++ b/cards/list.sh @@ -31,7 +31,10 @@ edit_card(){ [ $(pdi_count "$card" IMPP) -gt 0 ] && edit_item "$card" IMPP [ $(pdi_count "$card" URL ) -gt 0 ] && edit_item "$card" URL )] - [div .section .address $(edit_item "$card" ADR)] + [div .section .address $( + edit_item "$card" ADR + [ $(pdi_count "$card" X-IBAN) -gt 0 ] && edit_item "$card" X-IBAN + )] [div .section .note $(edit_item "$card" NOTE)] [div .section .attendance [h3 $(l10n course_attendance) ] [div .attendance $( @@ -65,7 +68,7 @@ edit_card(){ [div .item .newfield [select name="newfield" [option value="" disabled="disabled" selected="selected" $(l10n edit_addfieldtext)] - $(for f in NICKNAME EMAIL TEL IMPP ADR URL NOTE X-ZACK-LEAVEDATE; do + $(for f in NICKNAME EMAIL TEL IMPP ADR URL NOTE X-ZACK-LEAVEDATE X-IBAN; do printf '[option value="%s" %s] ' "$f" "$(l10n "$f")" done) ][button type="submit" name="action" value="addfield" $(l10n edit_addfield)] @@ -90,7 +93,7 @@ print_card(){ )] [div .section .phone . $(card_item "$card" TEL)] [div .section .message . $(card_item "$card" EMAIL IMPP URL)] - [div .section .address . $(card_item "$card" ADR)] + [div .section .address . $(card_item "$card" ADR X-IBAN)] [div .section .note . $(card_item "$card" NOTE)] [div .section .attendance [h3 $(l10n course_attendance) ] [ul $(grep -F " ${cardfile##*/}" "$_DATA/mappings/attendance" |while read each discard; do @@ -101,6 +104,7 @@ print_card(){ $(card_item "$card" CATEGORIES) ] [div .control + [a .button .item href="${_BASE}/ledgers/account.sh?card=${cardfile##*/}" $(l10n ledger)] [a .button .item href="${_BASE}/cards/edit_card.sh?card=${cardfile##*/}" $(l10n edit)] [a .button .item href="${_BASE}/cards/export_card.sh?card=${cardfile##*/}" $(l10n vcf_export)] ] diff --git a/cards/update_card.sh b/cards/update_card.sh index 2b87632..d57f503 100755 --- a/cards/update_card.sh +++ b/cards/update_card.sh @@ -35,7 +35,7 @@ attfile="$_DATA/mappings/attendance" action="$(POST action)" newfield="$(POST newfield |grep -m 1 -xE '[A-Z][A-Z0-9-]*')" -if printf '%s\n' "$action" |grep -qxE 'addfield [A-Z][A-Z0-9]*'; then +if printf '%s\n' "$action" |grep -qxE 'addfield [A-Z][A-Z0-9-]*'; then newfield="${action##* }" action=addfield fi diff --git a/cgilite/cgilite.awk b/cgilite/cgilite.awk index f16ed6a..ebf4411 100644 --- a/cgilite/cgilite.awk +++ b/cgilite/cgilite.awk @@ -1,5 +1,7 @@ #!/bin/env awk -f +function debug(t) { printf "%s\n", t >>"/dev/stderr"; } + function PATH( str, seg, out ) { while ( str ) { seg = str; @@ -151,8 +153,9 @@ BEGIN { split("", _GET); split("", _POST); split("", _REF); split("", _HEADER); split("", _COOKIE); - if ( ENVIRON["REQUEST_METHOD"] ) + if ( ENVIRON["REQUEST_METHOD"] ) { _cgilite_headers(); - else + } else { _cgilite_request(); + } } diff --git a/cgilite/common.css b/cgilite/common.css index 30c3942..16e99f2 100644 --- a/cgilite/common.css +++ b/cgilite/common.css @@ -31,7 +31,7 @@ a { color: #068; word-break: break-word; } -a.button { +a.button, label.button { font-style: inherit; text-decoration: inherit; color: inherit; @@ -86,7 +86,7 @@ h1 { } h2 { font-size: 1.125em; } -select, input, button, textarea, a.button { +select, input, button, textarea, a.button, label.button { display: inline-block; color: #000; background-color: #FFF; border: .5pt solid; @@ -103,7 +103,7 @@ input[type=radio], input[type=checkbox] { } input[type=number] { text-align: right; padding-right: 0; } -button, input[type=button], a.button { +button, input[type=button], a.button, label.button { box-shadow: .125em .125em .25em; cursor: pointer; } diff --git a/cgilite/storage.sh b/cgilite/storage.sh index 17ea0d0..5c61df0 100755 --- a/cgilite/storage.sh +++ b/cgilite/storage.sh @@ -94,6 +94,21 @@ UNSTRING(){ printf '%s\n' "$out" } +RXLITERAL(){ + # sed -E 's;[].*+?^${}()|\[];\\&;g' + local in out='' + [ $# -gt 0 ] && in="$*" || in="$(cat)" + while [ "$in" ]; do case $in in + [.+^\$\{\}\(\)\[\]\*\?\|\\]*) + out="${out}\\${in%"${in#?}"}"; in="${in#?}"; + ;; + *)out="${out}${in%%[.+^\$\{\}\(\)\[\]\*\?\|\\]*}" + in="${in#"${in%%[.+^\$\{\}\(\)\[\]\*\?\|\\]*}"}" + ;; + esac; done + printf '%s\n' "$out" +} + DBM() { local file="$1" cmd="$2" local k v key value diff --git a/datetime.sh b/datetime.sh new file mode 100755 index 0000000..2b4bba9 --- /dev/null +++ b/datetime.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +[ "$include_datetime" ] && return 0 +include_datetime="$0" + +# Copyright 2023 - 2024 Paul Hänsch +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +isdate(){ + local date="$1" y m d + + if printf %s "$date" \ + | grep -xEq '[0-9]{4}-((01|03|05|07|08|10|12)-(0[1-9]|[12][0-9]|3[01])|(04|06|09|11)-(0[1-9]|[12][0-9]|30)|02-(0[1-9]|[12][0-9]))' + then # y-m-d (ISO Date) + y="${date%%-*}" d="${date##*-}" m="${date%-*}" m="${m#*-}" + elif printf %s "$date" \ + | grep -xEq '((0?1|0?3|0?5|0?7|0?8|10|12)/(0?[1-9]|[12][0-9]|3[01])|(0?4|0?6|0?9|11)/(0?[1-9]|[12][0-9]|30)|0?2-(0[1-9]|[12][0-9]))/([0-9]{2}|[0-9]{4})' + then # m/d/y (US Date) + y="${date##*/}" m="${date%%/*}" d="${date%/*}" d="${d#*/}" + elif printf %s "$date" \ + | grep -xEq '((0?[1-9]|[12][0-9]|3[01])[\./](0?1|0?3|0?5|0?7|0?8|10|12)|(0?[1-9]|[12][0-9]|30)[\./](0?4|0?6|0?9|11)|(0[1-9]|[12][0-9])[\./]0?2)[\./]([0-9]{2}|[0-9]{4})' + then # d/m/y or d.m.y (European Date / German Date) + y="${date##*.}" d="${date%%.*}" m="${date%.*}" m="${m#*.}" + else + return 1 + fi + [ $y -lt 100 -a $y -gt 50 ] && y=$((y + 1900)) + [ $y -lt 100 -a $y -le 50 ] && y=$((y + 2000)) + date="$(printf "%04i-%02i-%02i" $y ${m#0} ${d#0})" + + # leap year + if [ "$m" -eq 2 -a "$d" -eq 29 ]; then + if [ "$((y % 400))" -eq 0 ]; then + : + elif [ "$((y % 100))" -eq 0 ]; then + return 1 + elif [ "$((y % 4))" -eq 0 ]; then + : + else + return 1 + fi + fi + + printf '%04i-%02i-%02i\n' "$y" "${m#0}" "${d#0}" + return 0 +} + +istime(){ + time="$1" h= m= + + if printf %s "$time" | grep -xEq '(0?[1-9]|1[012])(:[0-5][0-9])? ?(am|AM)\.?'; then + time="${time%?[aA][mM]}" h="${time%:*}" h="$(h % 12)" + [ "$h" != "$time" ] && m="${time#*:}" || m=0 + elif printf %s "$time" | grep -xEq '(0?[1-9]|1[012])(:[0-5][0-9])? ?(pm|PM)\.?'; then + time="${time%?[aA][mM]}" h="${time%:*}" h="$(h % 12 + 12)" + [ "$h" != "$time" ] && m="${time#*:}" || m=0 + elif printf %s "$time" | grep -xEq '(0?[0-9]|1[0-9]|2[0-3]):[0-5][0-9]'; then + time="${time%?[aA][mM]}" h="${time%:*}" m="${time#*:}" + else + return 1 + fi + + printf '%02i:%02i\n' "${h#0}" "${m#0}" + return 0 +} + +numdays(){ + # return number of days in a month (i.e. 28, 29, 30, or 31) + local y="$1" m="${2#0}" + + case $m in + 1|3|5|7|10|12) + printf 31\\n + ;; + 4|6|8|9|11) + printf 30\\n + ;; + 2) if [ "$((y % 400))" -eq 0 ]; then + printf 29\\n + elif [ "$((y % 100))" -eq 0 ]; then + printf 28\\n + elif [ "$((y % 4))" -eq 0 ]; then + printf 29\\n + else + printf 28\\n + fi + ;; + *) return 1;; + esac +} diff --git a/l10n.sh b/l10n.sh index d9e01d0..4f13ad4 100755 --- a/l10n.sh +++ b/l10n.sh @@ -85,6 +85,7 @@ l10n_global() { edit) printf %s "Bearbeiten";; edit_categories) printf %s "Kategorien Bearbeiten";; vcf_export) printf %s "Vcard Exportieren";; + ledger) printf %s "Buchungen";; control) printf %s "Aktionen";; delete) printf %s "entfernen";; edit_update) printf %s "Daten übernehmen";; @@ -115,6 +116,28 @@ l10n_global() { filter_cancel) printf %s "Filter löschen";; export_csv) printf %s "Liste als CSV-Datei";; + # Accounting page + Ledgers) printf %s "Buchungslisten";; + '%i IBANs are unassigned') printf %s "%i IBANs sind nicht zugewiesen";; + 'IBAN Assignments') printf %s "IBAN Zuweisung";; + 'Assign IBANs') printf %s "IBANs Zuweisen";; + 'Account') printf %s "Konto";; + + 'Accept Suggestions') printf %s "Vorschläge Akzeptieren";; + 'Ignore Suggestions') printf %s "Vorschläge Ignorieren";; + 'Submit Changes') printf %s "Änderungen Übernehmen";; + + 'Payments') printf %s "Zahlungen";; + 'Date') printf %s "Datum";; + 'Originator') printf %s "Auftraggeber";; + 'Reference Text') printf %s "Verwendungszweck";; + 'Amount') printf %s "Betrag";; + 'Balance') printf %s "Stand";; + 'Manual Record') printf %s "Manueller Eintrag";; + 'once') printf %s "einmalig";; + 'monthly until') printf %s "monatlich bis";; + 'until') printf %s "bis";; + # UI Labels Special course_attendance) printf %s "Kurs­teil­nahme";; vcf_seed_label) printf "Anmeld. Vorn. Nachn. Geb.Tag Geb.Monat Geb.Jahr Tel. Mobil () EMail () Notiz";; diff --git a/ledgers/account.sh b/ledgers/account.sh new file mode 100755 index 0000000..d9d3f4b --- /dev/null +++ b/ledgers/account.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +credit() { + printf '%+03i\n' "$1" \ + | sed -E 's;[0-9]{2}$;d&;; :0 s;([0-9])([0-9]{3}[dm]);\1m\2;; t0; y;dm;,.;' +} + +if [ "$REQUEST_METHOD" = POST ]; then + uid="$(POST uid)" + cfile="$(grep -lxF "UID;:${uid}" "${_DATA}/vcard/"*.vcf || grep -lxF "UID:${uid}" "${_DATA}/vcard/"*.vcf)" + REDIRECT "${_BASE}/ledgers/account.sh?card=${cfile##*/}" +fi + +. "${_EXEC}/cgilite/storage.sh" +. "${_EXEC}/pdiread.sh" +. "${_EXEC}/cards/l10n.sh" +. "${_EXEC}/cards/widgets.sh" + +cardfile="${_DATA}/vcard/$(GET card |PATH)" +if [ ! -f "$cardfile" ]; then + SET_COOKIE 0 message="Invalid account: $cardfile" + REDIRECT "${_BASE}/ledgers/" +fi + +cledger="${cardfile##*/}" +cledger="${_DATA}/ledgers/vcf.${cledger%.vcf}.account" + +{ card="$(pdi_load "$cardfile")" + cat <<-EOF + [h1 $(l10n Payments)] + [div .card #${cardfile##*/} + [div .section .basic . $( + card_item "$card" FN GENDER NICKNAME BDAY X-ZACK-JOINDATE X-ZACK-LEAVEDATE SOUND PHOTO LOGO + )] + [div .section .phone . $(card_item "$card" TEL)] + [div .section .message . $(card_item "$card" EMAIL IMPP URL)] + [div .section .address . $(card_item "$card" ADR X-IBAN)] + [div .section .note . $(card_item "$card" NOTE)] + [div .section .attendance [h3 $(l10n course_attendance) ] [ul + $(grep -F " ${cardfile##*/}" "$_DATA/mappings/attendance" |while read each discard; do + printf '[li [a .item .attendance href="%s/courses#%s" . %s]]' \ + "${_BASE}" "$each" \ + "$(pdi_value "$(pdi_load "$_DATA/ical/$each")" SUMMARY || l10n "(unnamed course)" |unescape |HTML)" + done |sort -k7)] + $(card_item "$card" CATEGORIES) + ] + ] + EOF + printf '[table .transactions [thead + [tr [th .date . %s][th .orig . %s][th .reference . %s][th .amount . %s][th .balance . %s]]' \ + "$(l10n Date)" "$(l10n Originator)" "$(l10n "Reference Text")" "$(l10n Amount)" "$(l10n Balance)" + printf '][tbody' + cnt="$(pdi_count "$card" X-IBAN)" + while [ "$cnt" -gt 0 ]; do + pdi_value "$card" X-IBAN "$cnt" |RXLITERAL + cnt=$((cnt - 1)) + done \ + | { + while read -r iban; do + grep -hE "^[^\t]+ [^\t]+ ${iban} " "${_DATA}/ledgers/"*.tbl + done + if [ -f "$cledger" ]; then + : + fi + } \ + | sort -n -k2 \ + | { total=0 + while read -r date dtstamp iban accname subject amount; do + total=$((total + amount)) + printf '[tr [td .date . %s][td .orig [span . %s][span . %s]][td .reference . %s][td .amount . %s][td .balance . %s]]' \ + "$date" "$(HTML "$iban")" \ + "$(UNSTRING "$accname" |HTML)" "$(UNSTRING "$subject" |HTML)" \ + "$(credit "$amount")" "$(credit "$total")" + done + } + printf '[tr [th colspan=5 . %s]]' "$(l10n 'Manual Record')" + printf '[tr [td .date [input type=date placeholder="%s" name=tdate]] + [td .orig [input id=trec_once type=radio name="trec" value="once" selected] + [label for=trec_once . %s]
+ [input id=trec_month type=radio name="trec" value="month"] + [label for=trec_month . %s] + [input type=date name="trec_until" placeholder="%s"]] + [td .reference . [textarea placeholder="%s" name=treference]] + [td .amount colspan=2 [input type=number placeholder="%s" name=tamount value=0.00 step=.01]]' \ + "$(l10n Date)" "$(l10n once)" "$(l10n "monthly until")" "$(l10n until)" "$(l10n "Reference Text")" "$(l10n Amount)" + printf ']]]' +} \ +| yield_page ledgers diff --git a/ledgers/delete.sh b/ledgers/delete.sh new file mode 100755 index 0000000..7b41921 --- /dev/null +++ b/ledgers/delete.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +delete="$(POST delete |PATH)" +dtable="${_DATA}/ledgers/${delete##*/}" + +if [ "$dtable" ]; then + rm -- "${dtable}" \ + || SET_COOKIE 0 message="Unable to delete ledger: \"$(HTML "$delete")\"" +else + SET_COOKIE 0 message="No such ledger: \"$(HTML "$delete")\"" +fi + +REDIRECT "${_BASE}/ledgers/" diff --git a/ledgers/iban_assign.awk b/ledgers/iban_assign.awk index 090d950..0491617 100755 --- a/ledgers/iban_assign.awk +++ b/ledgers/iban_assign.awk @@ -1,5 +1,7 @@ #!/bin/awk -f +function dbg( text ) { print text >>"/dev/stderr"; } + function STRING( inp ) { gsub(/\\/, "\\\\", inp); gsub(/\n/, "\\n", inp); @@ -41,19 +43,18 @@ BEGIN { /^BEGIN;:VCARD$/ { fn = n = id = iban = tmp = ""; } - /^UID;[^:]*:/ { uid = $0; sub(/^[^;]+;[^:]*:/, "", uid); } - /^FN;[^:]*:/ { fn = $0; sub(/^[^;]+;[^:]*:/, "", fn); } - /^N;[^:]*:/ { n = $0; sub(/^[^;]+;[^:]*:/, "", n); sub(/;.*$/, "", n); } -/^X-IBAN;[^:]*:/ { tmp = $0; sub(/^[^;]+;[^:]*:/, "", tmp); iban = iban ? iban " " tmp : tmp; } + /^UID;[^:]*:/ { uid = $0; sub(/^[^;]+;[^:]*:/, "", uid); } + /^FN;[^:]*:/ { fn = $0; sub(/^[^;]+;[^:]*:/, "", fn); } + /^N;[^:]*:/ { n = $0; sub(/^[^;]+;[^:]*:/, "", n); sub(/;.*$/, "", n); } +/^X-IBAN;[^:]*:/ { iban = $0; sub(/^[^;]+;[^:]*:/, "", iban); ibans[length(ibans)] = iban; } /^END;:VCARD$/ { uid_n[uid] = n; uid_fn[uid] = fn; uid_iban[uid] = iban; - split(iban, ibans, / /); - for (iban in ibans) iban_uid[iban] = iban_uid[iban] ? iban_uid[iban] " " uid : uid; - fn = n = uid = iban = tmp = ""; + for (iban in ibans) iban_uid[ibans[iban]] = iban_uid[ibans[iban]] ? iban_uid[ibans[iban]] " " uid : uid; + fn = n = uid = iban = tmp = ""; split("", ibans); } -strftime("%Y-%m-%d", $1, "UTC") == $2 && strftime("%Y-%m-%d", $3, "UTC") == $4 { ledger = 1; } +/^BEGIN:LEDGERS$/ { ledger = 1; } ledger && strftime("%Y-%m-%d", $2, "UTC") == $1 { if ($3 in iban_uid) { @@ -72,12 +73,6 @@ ledger && strftime("%Y-%m-%d", $2, "UTC") == $1 { } END { - for (iban in sure) { - line = "sure " iban; - split(iban_uid[iban], uids, / /); - for (uid in uids) line = line " " STRING(uid "/" uid_fn[uid]); - print line; - } for (iban in unsure) { line = "guess " iban " " STRING(unsure_rec[iban]); split(unsure[iban], uids, / /); @@ -88,4 +83,10 @@ END { line = "unknown " iban " " unknown[iban]; print line; } + for (iban in sure) { + line = "sure " iban; + split(sure[iban], uids, / /); + for (k in uids) line = line " " STRING(uids[k] "/" uid_fn[uids[k]]); + print line; + } } diff --git a/ledgers/iban_assign.sh b/ledgers/iban_assign.sh new file mode 100755 index 0000000..778d279 --- /dev/null +++ b/ledgers/iban_assign.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +credit() { + printf '%03i\n' "$1" \ + | sed -E 's;[0-9]{2}$;d&;; :0 s;([0-9])([0-9]{3}[dm]);\1m\2;; t0; y;dm;,.;' +} + +{ printf ' + [h1 . %s] + [form .ibanassign action="%s/ledgers/set_iban.sh" method=POST + [input type=hidden name=session_id value="%s"] + ' "$(l10n "IBAN Assignments")" "${_BASE}" "$SESSION_ID" + printf '[datalist id=lattendants .' + pdi_load "${_DATA}"/vcard/*.vcf |sed -n '/^FN\;:/!b; s;^FN\;:;;; p;' \ + | while read name; do + printf '[option value="%s"]' "$(HTML "$name")" + done + printf ']' + l10n_attendant="$(l10n attendant)" + printf %s\\n "$IBAN_ASSIGN" \ + | while read -r state iban data; do + iban="$(UNSTRING "$iban")" + [ ! "$iban" ] && iban="??????????" + printf '[input type=checkbox id="use_%s" name="use_%s" value=true]' "$iban" "$iban" + printf '[fieldset .iban .%s [legend . %s ]' \ + "$state" "$iban" + if [ $state = sure ]; then + for card in $data; do + uid="${card%%/*}" name="$(UNSTRING "${card#*/}")" + printf '[span .card . %s]' "$(HTML "${name}")" + done + : + elif [ $state = guess ]; then + record="$(UNSTRING "${data%% *}")" + cards="${data#* }" + date="${record%% *}" + principal="${record#* * * }" principal="${principal%% *}" + subject="${record#* * * * }" subject="${subject%% *}" + amount="${record#* * * * * }" amount="${amount%% *}" + printf '[p .principal . %s][p .date %s][p .amount %s][p .subject . %s]' \ + "$(UNSTRING "$principal" |HTML)" "$date" "$(credit "$amount")" "$(UNSTRING "$subject" |HTML)" + n=0; for card in $cards; do + n=$((n+1)); uid="${card%%/*}" name="$(UNSTRING "${card#*/}")" + cat <<-EOF + [input type=checkbox id="check_${iban}_$n" name="check_${iban}_$n" value=true checked=checked] + [input .card name="fn_${iban}_$n" value="$(HTML "$name")" .disabled tabindex="-1"] + [label .del for="check_${iban}_$n" . -] + EOF + done + for m in 1 2 3 4 5 6 7 8; do + cat <<-EOF + [input type=checkbox id="check_${iban}_$((n+m))" name="check_${iban}_$((n+m))" value=true] + [input .card name="fn_${iban}_$((n+m))" value="" placeholder="${l10n_attendant}" list="lattendants"] + [label .add for="check_${iban}_$((n+m))" . +] + EOF + done + printf '[label .button for="use_%s" . %s]' "$iban" "$(l10n Accept Suggestions)" + printf '[label .button for="use_%s" . %s]' "$iban" "$(l10n Ignore Suggestions)" + elif [ $state = unknown ]; then + date="${data%% *}" + principal="${data#* * * }" principal="${principal%% *}" + subject="${data#* * * * }" subject="${subject%% *}" + amount="${data#* * * * * }" amount="${amount%% *}" + printf '[p .principal . %s][p .date %s][p .amount %s][p .subject . %s]' \ + "$(UNSTRING "$principal" |HTML)" "$date" "$(credit "$amount")" "$(UNSTRING "$subject" |HTML)" + printf '[input name="check_" type=hidden][input type=hidden][label .del]' + n=0; for m in 1 2 3 4 5 6 7 8; do + cat <<-EOF + [input type=checkbox id="check_${iban}_$((n+m))" name="check_${iban}_$((n+m))" value=false] + [input .card name="fn_${iban}_$((n+m))" value="" placeholder="${l10n_attendant}" list="lattendants"] + [label .add for="check_${iban}_$((n+m))" . +] + EOF + done + printf '[label .button for="use_%s" . %s]' "$iban" "$(l10n Accept Suggestions)" + printf '[label .button for="use_%s" . %s]' "$iban" "$(l10n Ignore Suggestions)" + fi + printf ']' + done + printf '[button type=submit . %s]' "$(l10n Submit Changes)" + printf ' ]' +} | yield_page ledgers diff --git a/ledgers/index.cgi b/ledgers/index.cgi index 87af8b1..f5ac6b5 100755 --- a/ledgers/index.cgi +++ b/ledgers/index.cgi @@ -3,11 +3,31 @@ . "$_EXEC/cgilite/storage.sh" . "$_EXEC/pdiread.sh" +if [ "$(POST show_account)" ]; then + uid="$(POST show_account)" + cfile="$(grep -lxF "UID;:${uid}" "${_DATA}/vcard/"*.vcf || grep -lxF "UID:${uid}" "${_DATA}/vcard/"*.vcf)" + REDIRECT "${_BASE}/ledgers/account.sh?card=${cfile##*/}" + exit 0; +fi + credit() { printf '%03i\n' "$1" \ | sed -E 's;[0-9]{2}$;d&;; :0 s;([0-9])([0-9]{3}[dm]);\1m\2;; t0; y;dm;,.;' } +IBAN_ASSIGN="$( + { pdi_load "${_DATA}"/vcard/*.vcf + printf 'BEGIN:LEDGERS\n' + cat "${_DATA}"/ledgers/????-??-??\ -\ ????-??-??\ -\ ????.tbl + } | "${_EXEC}"/ledgers/iban_assign.awk + printf '\n' +)" + +if [ "${PATH_INFO%/iban_assign/}" != "${PATH_INFO}" ]; then + . "${_EXEC}/ledgers/iban_assign.sh" + exit 0 +fi + { printf ' [form .upload action="%s/ledgers/csv_upload.sh" method="POST" enctype="multipart/form-data" [label for=ledger_upload . %s:] @@ -28,46 +48,29 @@ credit() { "$(HTML "${ledger% - ????.tbl}")" "$(HTML "$ledger")" "$(l10n delete)" done printf ' ]' - printf ' - [form .ibanassign action="%s/ledgers/iban_assign.sh" method=POST - [input type=hidden name=session_id value="%s"] - [h3 . %s] - ' "${_BASE}" "$SESSION_ID" "$(l10n "IBAN Assignments")" - printf '[datalist id=lattendants .' - pdi_load "${_DATA}"/vcard/*.vcf |sed -n '/^FN\;:/!b; s;^FN\;:;;; p;' \ - | while read name; do - printf '[option value="%s"]' "$(HTML "$name")" - done - printf ']' - { pdi_load "${_DATA}"/vcard/*.vcf - cat "${_DATA}"/ledgers/????-??-??\ -\ ????-??-??\ -\ ????.tbl - } | "${_EXEC}"/ledgers/iban_assign.awk \ - | while read -r state iban data; do - printf '[fieldset .iban .%s [legend . %s ]' \ - "$state" "$iban" - if [ $state = sure ]; then - : - elif [ $state = guess ]; then - record="$(UNSTRING "${data%% *}")" - cards="${data#* }" - principal="${record#* * * }" principal="${principal%% *}" - subject="${record#* * * * }" subject="${subject%% *}" - amount="${record#* * * * * }" amount="${amount%% *}" - printf '[p .principal . %s][p .amount %s][p .subject . %s]' \ - "$(UNSTRING "$principal" |HTML)" "$(credit "$amount")" "$(UNSTRING "$subject" |HTML)" - printf '[h4 . %s]' "$(l10n Guesses)" - for card in $cards; do - uid="${card%%/*}" name="$(UNSTRING "${card#*/}")" - printf '[input .card key="cardfn" value="%s" placeholder="%s" list=lattendants]' "$(HTML "${name}")" "$(l10n attendent)" - done - elif [ $state = unknown ]; then - principal="${data#* * * }" principal="${principal%% *}" - subject="${data#* * * * }" subject="${subject%% *}" - amount="${data#* * * * * }" amount="${amount%% *}" - printf '[p .principal . %s][p .amount %s][p .subject . %s]' \ - "$(UNSTRING "$principal" |HTML)" "$(credit "$amount")" "$(UNSTRING "$subject" |HTML)" - fi - printf ']' - done - printf ' ]' + unassigned="$(printf %s\\n "$IBAN_ASSIGN" |grep -E '^guess|^unknown' |wc -l)" + cat <<-EOF + [div + [h1 . $(l10n IBAN Assignments)] + $(printf "$(l10n "%i IBANs are unassigned")" "$unassigned") + [a href="${_BASE}/ledgers/iban_assign/" . $(l10n Assign IBANs)] + ] + [form action="${_BASE}/ledgers/account.sh" method=POST + [select name=uid + $(printf %s\\n "$IBAN_ASSIGN" \ + | sed -E ' + /^sure /!d; + s;^sure [^\t]+;;; + s;([^\t]+)/([^\t]+);\1 \2\n;g; + s;\n$;; + ' \ + | while read uid fn; do + uid="$(HTML "$uid")" + fn="$(UNSTRING "$fn" |HTML)" + printf '[option value="%s" . %s]' "$uid" "$fn" + done) + ] + [button type="submit" . $(l10n Account)] + ] + EOF } | yield_page ledgers diff --git a/ledgers/set_iban.sh b/ledgers/set_iban.sh new file mode 100755 index 0000000..08c9a3b --- /dev/null +++ b/ledgers/set_iban.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +. "$_EXEC/pdiread.sh" +. "$_EXEC/session_lock.sh" + +UIDLIST="$( + pdi_load "$_DATA/vcard/"*.vcf \ + | sed -Ez ' + s/\nBEGIN;:VCARD\n([^\n]+\n)*FN;:([^\n]+)\n([^\n]+\n)*UID;:([^\n]+)\n([^\n]+\n)*END;:VCARD\n/UID:\4 FN:\2/g + ' + echo +)" + +for key in $(POST_KEYS); do case $key in + use_*) + use_iban="${use_iban} ${key#use_} " + ;; +esac; done + +[ "$use_iban" ] && for key in $(POST_KEYS); do case $key in + check_*_*) + iban="${key#check_}" iban="${iban%_*}" + [ ! "${use_iban##* "${iban}" *}" ] && check="${check} ${key#check_} " + ;; +esac; done + +{ printf '[ul .results' + for use in $check; do + iban="${use%_*}" + fn="$(POST "fn_${use}")" + uid="${UIDLIST%% FN:"$fn"${BR}*}" uid="${uid##*${BR}UID:}" + + # cfile="${_DATA}/vcard/${uid}.vcf" + cfile="$(grep -lxF "UID;:${uid}" "${_DATA}/vcard/"*.vcf || grep -lxF "UID:${uid}" "${_DATA}/vcard/"*.vcf)" + if SLOCK "$cfile" >/dev/null; then + card="$(pdi_load "$cfile")" + cnum="$(pdi_count "$card" X-IBAN)" + pdi_update_value "$card" X-IBAN "$((cnum + 1))" "$iban" >"$cfile" + printf '[li .success . [span .name . %s] [span .uid . (UID: %s)] assigned IBAN [span .iban . %s]]' \ + "$(HTML "$fn")" "$(HTML "$uid")" "$(HTML "$iban")" + RELEASE_SLOCK "$cfile" + else + printf '[li .error . [span .name . %s] [span .uid . (UID: %s)] is being edited elsewhere]' + "$(HTML "$fn")" "$(HTML "$uid")" + fi + done + printf ']' + printf '[a .button href="%s/ledgers/iban_assign/" . %s]' "${_BASE}" "$(l10n Back)" +} | yield_page ledgers_assign diff --git a/style.css b/style.css index 98b839d..c00829e 100644 --- a/style.css +++ b/style.css @@ -368,6 +368,9 @@ body.categories form.namelist ul.namelist > li ul li { display: inline-block; } + +/* ======== Ledgers Page ======== */ + form.ibanassign, form.ledgers { padding: .125em 1em 0 1em; @@ -388,12 +391,143 @@ form.ledgers { .ibanassign fieldset.iban p.principal { font-size: .875em; } -.ibanassign fieldset.iban p.amount, -.ibanassign fieldset.iban p.subject { +.ibanassign fieldset.iban p.date, +.ibanassign fieldset.iban p.amount { + font-size: .875em; display: inline-block; vertical-align: top; + margin-right: .75em; + margin-bottom: 0; } .ibanassign fieldset.iban p.amount { font-weight: bold; - margin-right: .75em; +} + +.ibanassign fieldset.iban.sure .card { margin-right: 1em; } + +.ibanassign fieldset.iban input[name^="fn_"].disabled { + pointer-events: none; +} +.ibanassign fieldset.iban input[name^="check_"], +.ibanassign fieldset.iban input[name^="check_"] + input, +.ibanassign fieldset.iban input[name^="check_"] + input + label { + display: none; +} +.ibanassign fieldset.iban input[name^="check_"]:checked + input, +.ibanassign fieldset.iban input[name^="check_"]:checked + input + label.del, +.ibanassign fieldset.iban input[name^="check_"] + input + label.del + input + input + label.add, +.ibanassign fieldset.iban input[name^="check_"]:checked + input + label + input + input + label.add { + display: inline; +} +.ibanassign fieldset.iban input[name^="check_"]:checked + input + label.add, +.ibanassign fieldset.iban input[name^="check_"] + input + label.del + input:checked + input + label.add, +.ibanassign fieldset.iban input[name^="check_"]:checked + input + label + input:checked + input + label.add { + display: none; +} + +.ibanassign fieldset.iban input[name^="check_"] + input + label { + vertical-align: bottom; + line-height: 2.5em; + padding: .375em .625em; + border: .5pt solid; +} +.ibanassign fieldset.iban input[name^="check_"] + input + label.add { + background-color: #DFD; + border-radius: 2pt; +} +.ibanassign fieldset.iban input[name^="check_"] + input + label.del { + margin: 0 .5em 0 -.25em; + background-color: #FDD; + border-radius: 0 2pt 2pt 0; +} + +.ibanassign input[name^="use_"] { + display: none; +} +.ibanassign input[name^="use_"]:checked + fieldset.iban.guess { background-color: #EFD; } +.ibanassign input[name^="use_"]:checked + fieldset.iban.unknown { background-color: #FED; } +.ibanassign input[name^="use_"]:checked + fieldset.iban input { + background-color: #DFD; + pointer-events: none; + border-color: #888; +} +.ibanassign input[name^="use_"]:checked + fieldset.iban label.del, +.ibanassign input[name^="use_"]:checked + fieldset.iban label.add { + display: none !important; +} + +.ibanassign fieldset.iban label[for^="use_"] { + display: block; + float: right; + padding: .25em .5em; + background-color: #DDF; + border: 1pt solid; +} + +.ibanassign input[name^="use_"] + fieldset.iban label[for^="use_"] { display: block; } +.ibanassign input[name^="use_"] + fieldset.iban label[for^="use_"] + label[for^="use_"] { display: none; } +.ibanassign input[name^="use_"]:checked + fieldset.iban label[for^="use_"] { display: none; } +.ibanassign input[name^="use_"]:checked + fieldset.iban label[for^="use_"] + label[for^="use_"] { display: block; } + +.ibanassign > button { + position: sticky; + bottom: 0; + margin: auto; + display: block; +} + +body.ledgers .transactions { + width: 98%; + width: calc(100% - 2em); + margin: auto; +} +body.ledgers .transactions thead { + position: sticky; + top: 0; + z-index: 1; +} +body.ledgers .transactions th { + text-align: left; + background-color: #FFF; +} +body.ledgers .transactions td { + vertical-align: top; + font-family: monospace; + font-size: 12pt; +} + +body.ledgers .transactions td:nth-child(2n) { + background-color: #DDD; +} +body.ledgers .transactions td:nth-child(2n + 1) { + background-color: #EEE; +} + +body.ledgers .transactions .date { + min-width: 8em; +} +body.ledgers .transactions .orig span { + display: block; +} +body.ledgers .transactions .amount, +body.ledgers .transactions .balance { + vertical-align: bottom; + min-width: 6em; + text-align: right; +} + +body.ledgers .transactions .reference textarea { + width: 100%; +} +body.ledgers .transactions .orig input[type=date], +body.ledgers .transactions .date input[type=date], +body.ledgers .transactions .amount input[type=number] { + display: block; + width: 100%; +} + +body.ledgers .transactions .orig input:not(:checked) + label + input[type=date] { + background-color: #DDD; + border-color: #888; + pointer-events: none; }