]> git.plutz.net Git - confetti/commitdiff
date time helper master
authorPaul Hänsch <paul@plutz.net>
Wed, 15 May 2024 14:37:59 +0000 (16:37 +0200)
committerPaul Hänsch <paul@plutz.net>
Wed, 15 May 2024 14:37:59 +0000 (16:37 +0200)
15 files changed:
cards/l10n.sh
cards/list.sh
cards/update_card.sh
cgilite/cgilite.awk
cgilite/common.css
cgilite/storage.sh
datetime.sh [new file with mode: 0755]
l10n.sh
ledgers/account.sh [new file with mode: 0755]
ledgers/delete.sh [new file with mode: 0755]
ledgers/iban_assign.awk
ledgers/iban_assign.sh [new file with mode: 0755]
ledgers/index.cgi
ledgers/set_iban.sh [new file with mode: 0755]
style.css

index 2d9dc06362d250a5719f378aa0e9ef8a61867bee..fde98e07350659d4e8a938c0175e03c9994bcbbf 100755 (executable)
@@ -37,6 +37,7 @@ l10n(){
     X-ZACK-LEAVEDATE) printf %s "Abmelde&shy;datum";;
     X-ZACK-JOINDATE_short)  printf %s "Anm.";;
     X-ZACK-LEAVEDATE_short) printf %s "Abm.";;
     X-ZACK-LEAVEDATE) printf %s "Abmelde&shy;datum";;
     X-ZACK-JOINDATE_short)  printf %s "Anm.";;
     X-ZACK-LEAVEDATE_short) printf %s "Abm.";;
+    X-IBAN) printf %s "IBAN";;
 
     *) l10n_global "$word";;
   esac
 
     *) l10n_global "$word";;
   esac
index 519005c08f780bc5e09dc64717fa72bd46425482..e12d760f5c0a2fa7c0b08182cc7c979716720736 100755 (executable)
@@ -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
          )]
            [ $(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 $(
          [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)]
             [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)]
                  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 .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
       [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
         $(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)]
       ]
         [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)]
       ]
index 2b87632aea3208def628e80226996b32e1eb171b..d57f5030a2431c564891d716c7f7a8ff5d769755 100755 (executable)
@@ -35,7 +35,7 @@ attfile="$_DATA/mappings/attendance"
 action="$(POST action)"
 newfield="$(POST newfield |grep -m 1 -xE '[A-Z][A-Z0-9-]*')"
 
 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
   newfield="${action##* }"
   action=addfield
 fi
index f16ed6a3281746d6182cd4f340eb15910f6c5a87..ebf44113d1b3333eec25166a81201d01b3cd3703 100644 (file)
@@ -1,5 +1,7 @@
 #!/bin/env awk -f
 
 #!/bin/env awk -f
 
+function debug(t) { printf "%s\n", t >>"/dev/stderr"; }
+
 function PATH( str,    seg, out ) {
   while ( str ) {
     seg = str;
 function PATH( str,    seg, out ) {
   while ( str ) {
     seg = str;
@@ -151,8 +153,9 @@ BEGIN {
   split("", _GET); split("", _POST); split("", _REF);
   split("", _HEADER); split("", _COOKIE);
 
   split("", _GET); split("", _POST); split("", _REF);
   split("", _HEADER); split("", _COOKIE);
 
-  if ( ENVIRON["REQUEST_METHOD"] )
+  if ( ENVIRON["REQUEST_METHOD"] ) {
     _cgilite_headers();
     _cgilite_headers();
-  else
+  } else {
     _cgilite_request();
     _cgilite_request();
+  }
 }
 }
index 30c3942eb5d8a2b4ef42e3fceb972b2d8c54a495..16e99f23befd1f945ee7efbd71707a48ccd367ad 100644 (file)
@@ -31,7 +31,7 @@ a {
   color: #068;
   word-break: break-word;
 }
   color: #068;
   word-break: break-word;
 }
-a.button {
+a.button, label.button {
   font-style: inherit;
   text-decoration: inherit;
   color: inherit;
   font-style: inherit;
   text-decoration: inherit;
   color: inherit;
@@ -86,7 +86,7 @@ h1 {
 }
 h2 { font-size: 1.125em; }
 
 }
 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;
   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; }
 
 }
 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;
 }
   box-shadow: .125em .125em .25em;
   cursor: pointer;
 }
index 17ea0d051eaf27233f8b5cd96f7396974c7a87f1..5c61df0a041e75df0e1ee0e81aa0c09078cdabc1 100755 (executable)
@@ -94,6 +94,21 @@ UNSTRING(){
   printf '%s\n' "$out"
 }
 
   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
 DBM() {
   local file="$1" cmd="$2"
   local k v key value
diff --git a/datetime.sh b/datetime.sh
new file mode 100755 (executable)
index 0000000..2b4bba9
--- /dev/null
@@ -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 d9e01d070ec63d00806cdee2d052763801dad0b7..4f13ad48be4e563e50d46ab097f9280d7139ff75 100755 (executable)
--- 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";;
     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";;
     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";;
 
     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&shy;teil&shy;nahme";;
     vcf_seed_label) printf "Anmeld.    Vorn.   Nachn.  Geb.Tag Geb.Monat       Geb.Jahr        Tel.    Mobil   ()      EMail   ()      Notiz";;
     # UI Labels Special
     course_attendance) printf %s "Kurs&shy;teil&shy;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 (executable)
index 0000000..d9d3f4b
--- /dev/null
@@ -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]<br/>
+                        [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 (executable)
index 0000000..7b41921
--- /dev/null
@@ -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/"
index 090d950bbfe513f974f6e051c45fdbce6a112791..0491617699a1989491e83bdd0ed718c65fa5c89e 100755 (executable)
@@ -1,5 +1,7 @@
 #!/bin/awk -f
 
 #!/bin/awk -f
 
+function dbg( text ) { print text >>"/dev/stderr"; }
+
 function STRING( inp ) {
   gsub(/\\/, "\\\\", inp);
   gsub(/\n/, "\\n",  inp);
 function STRING( inp ) {
   gsub(/\\/, "\\\\", inp);
   gsub(/\n/, "\\n",  inp);
@@ -41,19 +43,18 @@ BEGIN {
 
 /^BEGIN;:VCARD$/ { fn = n = id = iban = tmp = ""; }
 
 
 /^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;
 
 /^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) {
 
 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 {
 }
 
 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, / /);
   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;
   }
     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 (executable)
index 0000000..778d279
--- /dev/null
@@ -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
index 87af8b155435e09425c6c25e0004fce8a14593ed..f5ac6b5b3c9a8f6f6a3d1128e34ccbe5e92fc259 100755 (executable)
@@ -3,11 +3,31 @@
 . "$_EXEC/cgilite/storage.sh"
 . "$_EXEC/pdiread.sh"
 
 . "$_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;,.;'
 }
 
 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:]
 { 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 '  ]'
            "$(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
 } | yield_page ledgers
diff --git a/ledgers/set_iban.sh b/ledgers/set_iban.sh
new file mode 100755 (executable)
index 0000000..08c9a3b
--- /dev/null
@@ -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
index 98b839da1ffaa54f2ea1f170dfe7b3a0e246f98e..c00829e2ba5b2fb8c4d45cc7575e261b15515b81 100644 (file)
--- a/style.css
+++ b/style.css
@@ -368,6 +368,9 @@ body.categories form.namelist ul.namelist > li ul li {
   display: inline-block;
 }
 
   display: inline-block;
 }
 
+
+/* ======== Ledgers Page ======== */
+
 form.ibanassign,
 form.ledgers {
   padding: .125em 1em 0 1em;
 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.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;
   display: inline-block;
   vertical-align: top;
+  margin-right: .75em;
+  margin-bottom: 0;
 }
 .ibanassign fieldset.iban p.amount {
   font-weight: bold;
 }
 .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;
 }
 }