rechnung/rechnung.sh
qbi feb3b49abb Einleitung eingebaut
Im Text der Rechnung muss es zu Anfang eine Einleitung geben. Dort kann
man auf Verträge, Absprachen, Beauftragungen etc. Bezug nehmen. Ich habe
das gemäß dem Muster der POSITION eingebaut.
2021-10-10 12:15:59 +02:00

601 lines
14 KiB
Bash
Executable file

#!/bin/sh -
# file: rechnung.sh
# date: 02.05.2021
# user: bernd@nr18.space
# desc: Liest verschiedene Kunden und Dienstleistungen aus der Datei
# rechnung.ini und erstellt daraus Rechnungen mit Hilfe eines
# Latex-Templates.
set -u
set -e
# ein paar Sicherheitsvorkehrungen
# --------------------------------
OLDIFS=$IFS
IFS='
'
unset -f command
unset -f unalias
\unalias -a
OLDPATH=$PATH
PATH=/bin:/usr/bin
export PATH
# Benötigte Programme
# -------------------
TR_BIN=$(which tr 2>/dev/null || echo "tr")
BC_BIN=$(which bc 2>/dev/null || echo "bc")
SED_BIN=$(which sed 2>/dev/null || echo "sed")
CUT_BIN=$(which cut 2>/dev/null || echo "cut")
DATE_BIN=$(which date 2>/dev/null || echo "date")
TPUT_BIN=$(which tput 2>/dev/null || echo "tput")
PDFLATEX_BIN=$(which pdflatex 2>/dev/null || echo "pdflatex")
KPSEWHICH_BIN=$(which kpsewhich 2>/dev/null || echo "kpsewhich")
GIT_BIN=$(which git 2>/dev/null || echo "git")
LATEX_BIN=$(which latex 2>/dev/null || echo "latex")
RUBBER_BIN=$(which rubber 2>/dev/null || echo "rubber")
# Nicht auf rubber prüfen; Programm ist optional
# Nicht auf git prüfen; wird nur benötigt, wenn rechnung.sty nicht existiert.
# Nicht auf latex prüfen; wird nur benötigt, wenn rechnung.sty nicht texistiert.
EXECUTABLES_TO_CHECK="$BC_BIN $TR_BIN $SED_BIN $CUT_BIN $TPUT_BIN $DATE_BIN $PDFLATEX_BIN $KPSEWHICH_BIN"
GENPDF=""
if [ -x "$RUBBER_BIN" ]; then
GENPDF="$RUBBER_BIN -d "
else
GENPDF="$PDFLATEX_BIN -halt-on-error "
fi
# Variablen definieren
# --------------------
PROGRAMM_NAME=$(basename "$0")
PROGRAMM_VERSION="0.0.1"
VERBOSITY=1
STY_HOME="$HOME/texmf/tex/latex"
STY_RECHNUNG="rechnung.sty"
RECHNUNGSFILE="rechnung.ini"
TEMPLATEFILE="template.ltx"
LCOFILE="absender.lco"
LCO=$(basename $LCOFILE .lco)
INSTALL_LCO_FILE=0
SEDFILE="sed.txt"
LINE_BUFFER=""
KEY_BUFFER=""
VALUE_BUFFER=""
KUNDE=""
ADRESSE=""
ANREDE=""
ANZAHL=""
POSITION=""
EINLEITUNG=""
SHORT=""
BETRAG=""
LFDNR=""
# Farben definieren
# -----------------
NUMBER_OF_COLORS=$($TPUT_BIN colors)
COLORED_OUTPUT=0
RED="$(tput bold)$(tput setaf 1)"
GREEN="$(tput bold)$(tput setaf 2)"
YELLOW="$(tput bold)$(tput setaf 3)"
BLUE="$(tput bold)$(tput setaf 4)"
NORMAL="$(tput bold)$(tput sgr0)"
# Ausgabefunktionen für Terminal
# ------------------------------
print_start() {
msg=$*
if [ "$VERBOSITY" -eq 0 ]; then
return
else
if [ "$COLORED_OUTPUT" -eq 1 ]; then
printf "%s[+]%s %s" "$GREEN" "$NORMAL" "$msg"
else
printf " Failed"
fi
fi
}
print_failed() {
msg=$*
if [ "$VERBOSITY" -eq 0 ]; then
return
else
if [ "$COLORED_OUTPUT" -eq 1 ]; then
printf "%s Failed%s\n" "$RED" "$NORMAL"
else
printf " Failed\n"
fi
if [ -n "$msg" ]; then
print_error "$msg"
fi
fi
}
print_ok() {
if [ "$VERBOSITY" -eq 0 ]; then
return
else
if [ "$COLORED_OUTPUT" -eq 1 ]; then
printf "%s OK%s\n" "$GREEN" "$NORMAL"
else
printf " OK\n"
fi
fi
}
print_deko() {
msg=$*
if [ "$VERBOSITY" -eq 0 ]; then
return
else
if [ "$COLORED_OUTPUT" -eq 1 ]; then
echo "${BLUE}${msg}${NORMAL}"
else
echo "$msg"
fi
fi
}
print_info() {
msg=$*
if [ "$VERBOSITY" -eq 0 ]; then
return
else
if [ "$COLORED_OUTPUT" -eq 1 ]; then
echo "${GREEN}[+]$NORMAL $msg"
else
echo "[-] $msg"
fi
fi
}
print_warn() {
msg=$*
if [ "$VERBOSITY" -eq 0 ]; then
return
else
if [ "$COLORED_OUTPUT" -eq 1 ]; then
echo "${YELLOW}[-]$NORMAL $msg"
else
echo "[-] $msg"
fi
fi
}
print_error() {
msg=$*
if [ "$COLORED_OUTPUT" -eq 1 ]; then
echo "${RED}[-]$NORMAL $msg"
else
echo "[-] $msg"
fi
}
failed_and_exit() {
print_failed
exit "$*"
}
# Diverse Checks
# --------------
check_readable() {
# testet, ob die übergebene datei kein symlink ist, existiert und lesbar
# ist. schlägt einer der tests fehl, wird das programm beendet.
print_start "Datei $1 ist lesbar ..."
if test -h "$1"; then
target=$(get_link_target "$1")
print_failed "$1 ist ein symbolischer Link auf $target"
exit 1
fi
if test -f "$1" && test -r "$1"; then
print_ok
else
failed_and_exit 1
fi
}
check_sty() (
# sucht in einer subshell nach dem paket rechnung.sty. gibt true oder
# false zurück.
print_start "Suche nach $STY_RECHNUNG ..."
if $KPSEWHICH_BIN "$STY_RECHNUNG" > /dev/null 2>&1 ; then
print_ok
return 0
else
print_failed
return 1
fi
)
check_binaries() (
# die funktion prüft in einer subshell, ob alle benötigten binaries
# installiert sind. sollte das binary ein symlink auf eine ander datei
# sein, wird geprüft, ob sich diese im PATH befindet und nicht ebenfalls
# ein symlink ist. schlägt eine der prüfungen fehl, wird das programm
# beendet.
program=
binaries=$*
for program in $binaries; do
print_start "Suche nach $program ..."
if test ! -x "$program"; then
print_failed "Das Programm $program wird benötigt, aber nicht gefunden."
quit 3
else
if [ -h "$program" ]; then
program=$(get_link_target "$program")
fi
if ! which "$program" > /dev/null 2>&1 ; then
print_failed "$program ist nicht im Pfad"
fi
print_ok
fi
done
)
get_link_target() (
# die funktion bekommt einen symbolischen link und folgt ihm bis zum
# ende. mit dem echo-befehl wird das ziel an die aufrufende funktion
# zurück gegeben.
program=$1
while [ -h "$program" ]; do
program=$(readlink "$program")
done
echo "$program"
)
is_option() (
# die funktion überprüft in einer subshell, ob die übergebene zeile ein
# = als trennzeichen enthält. gibt ja oder falsch zurück.
line="$*"
if [ "${line#*=}" != "$line" ]; then
return 0
else
return 1
fi
)
check_rechnunsdaten() (
# die funktion überprüft in einer subshell, ob alle variablen belegt
# sind. ist eine variable leer, wird der name der variablen ausgegeben
# und ein fehler (1) zurück gegeben.
print_start "Prüfe Rechnungsdaten ..."
if test -z "$KUNDE"; then
print_failed "Das Feld Kunde ist leer"
return 1
elif test -z "$ADRESSE"; then
print_failed "Das Feld Adresse ist leer"
return 1
elif test -z "$ANREDE"; then
print_failed "Das Feld Anrede ist leer"
return 1
elif test -z "$ANZAHL"; then
print_failed "Das Feld Anzahl ist leer"
return 1
elif test -z "$POSITION"; then
print_failed "Das Feld Position ist leer"
return 1
elif test -z "$EINLEITUNG"; then
print_failed "Das Feld Einleitung ist leer"
return 1
elif test -z "$SHORT"; then
print_failed "Das Feld Short ist leer"
return 1
elif test -z "$BETRAG"; then
print_failed "Das Feld Betrag ist leer"
return 1
elif test -z "$LFDNR"; then
print_failed "Das Feld LfdNr ist leer"
return 1
else
print_ok
return 0
fi
)
# Whitespaces entfernen
# in Buffer speichern
# ---------------------
trim_line() {
set -f
set -- "$*"
LINE_BUFFER=$(printf '%s' "$*")
set +f
}
trim_key() {
set -f
set -- "$*"
KEY_BUFFER=$(printf '%s' "$*")
set +f
}
trim_value() {
set -f
set -- "$*"
VALUE_BUFFER=$(printf '%s' "$*")
set +f
}
# LaTeX-Paket rechnung.sty installieren
# -------------------------------------
install_sty() (
old_pwd=$(pwd)
check_binaries "$GIT_BIN $LATEX_BIN"
print_start "Hole Repo von $STY_URL ..."
if $GIT_BIN clone "$STY_URL" >/dev/null 2>&1; then
print_ok
cd rechnung
mkdir -p "$STY_HOME/rechnung/"
print_start "Kompiliere $STY_RECHNUNG ..."
if $LATEX_BIN rechnung.ins >/dev/null 2>&1; then
print_ok
print_start "Kopiere $STY_RECHNUNG nach $STY_HOME/rechnung ..."
if cp rechnung.sty "$STY_HOME/rechnung/"; then
print_ok
else
failed_and_exit 3
fi
cd "$old_pwd"
rm -rf rechnung/
print_info "Paket $STY_RECHNUNG erfolgreich installiert"
else
print_error "Installation fehlgeschlagen"
exit 4
fi
else
print_error "Klonen des Repos fehlgeschlagen"
exit 5
fi
)
# wenn ein neuer Kunde beginnt, alle Variablen neu initialisieren
# ---------------------------------------------------------------
reset_values() {
print_info "Beginne neue Rechnung"
LINE_BUFFER=""
KEY_BUFFER=""
VALUE_BUFFER=""
KUNDE=""
ADRESSE=""
ANREDE=""
ANZAHL=""
POSITION=""
EINLEITUNG=""
SHORT=""
BETRAG=""
LFDNR=""
}
set_value() {
key=$(echo "$*" | $CUT_BIN -d "=" -f1)
value=$(echo "$*" | $CUT_BIN -d "=" -f2)
trim_key "$key"
trim_value "$value"
case $KEY_BUFFER in
kunde) KUNDE="$VALUE_BUFFER";;
adresse) ADRESSE="$VALUE_BUFFER";;
anrede) ANREDE="$VALUE_BUFFER";;
anzahl) ANZAHL="$VALUE_BUFFER";;
position) POSITION="$VALUE_BUFFER";;
einleitung) EINLEITUNG="$VALUE_BUFFER";;
betrag) BETRAG="$VALUE_BUFFER";;
lfdnr) LFDNR="$VALUE_BUFFER";;
short) SHORT="$VALUE_BUFFER";;
*) print_warn "Unbekanntes Feld: $KEY_BUFFER";;
esac
}
make_invoice() (
# die funktion erstellt in einer subshell die rechnung. schlägt die
# prüfung der rechnungsdaten fehl, kehrt sie zur aufrufenden shell
# zurück. ansonst wird eine kopie des latex-templates erstellt, die
# darin befindlichen platzhalter durch die entsprechenden variablen zu
# ersetzen und das ganze zu kompilieren.
if ! check_rechnunsdaten ; then
return 1
fi
dead_days="14"
if [ "$(date +%u)" -gt 5 ]; then
dead_days="16"
fi
month_ascii=$(date '+%B')
inv_date=$(date '+%Y%m%d')
inv_dead=$(date -d "+${dead_days}days" '+%d.\\,%m.\\,%Y')
rechnungsnummer="$inv_date--$LFDNR"
subject="Rechnung zu $SHORT"
filename="$(date '+%Y%m%d')_Rechnung_${month_ascii}_$(echo "$KUNDE" | $TR_BIN " " "_").ltx"
buffer="$KUNDE; $ADRESSE"
anschrift="$(echo "$buffer" | sed 's/;/\\\\\\\\\\ /g')"
print_info "Erstelle Rechnung: $filename"
{
printf "s!#LCOFILE#!%s!\n" "$LCO"
printf "s!#SUBJECT#!%s!\n" "$subject"
printf "s!#ANREDE#!%s!\n" "$ANREDE"
printf "s!#ANSCHRIFT#!%s!\n" "$anschrift"
printf "s!#RECHNUNGSNUMMER#!%s!\n" "$rechnungsnummer"
printf "s!#ANZAHL#!%s!\n" "$ANZAHL"
printf "s!#POSITION#!%s!\n" "$POSITION"
printf "s!#EINLEITUNG#!%s!\n" "$EINLEITUNG"
printf "s!#BETRAG#!%s!\n" "$BETRAG"
printf "s!#DEADLINE#!%s!\n" "$inv_dead"
} >$SEDFILE
cp "$TEMPLATEFILE" "$filename"
$SED_BIN -i -f "$SEDFILE" "$filename"
if ! $GENPDF "$filename" 1>&2 >/dev/null; then
print_error "Kompilierung von $filename fehlgeschlagen"
logfile=$(echo "$filename" | sed -e s/ltx/log/)
print_error "$(grep "Error" "$logfile")"
else
print_info "Rechnung $filename erfolgreich erstellt"
fi
if [ -x "$RUBBER_BIN" ]; then
"$RUBBER_BIN" --clean "$filename"
fi
)
proced_ini_line() {
line=$*
first=$(echo "$line" | cut -c1-1)
# leerzeilen ignorieren
if [ "$line" = "" ]; then
return
# kommentarzeilen ignorieren
elif [ "$first" = "#" ]; then
return
# start einer neuen rechnung
elif [ "$line" = "[start]" ]; then
reset_values
# ende einer rechnung
elif [ "$line" = "[ende]" ]; then
make_invoice
# alle anderen zeilen
else
if is_option "$line"; then
set_value "$line"
else
print_warn "Invalid syntax: $line"
fi
fi
}
display_help() (
echo "Usage: $PROGRAMM_NAME [-f rechnungsfile] [-h] [-l lco-file]"
)
display_version() (
echo "$PROGRAMM_NAME version $PROGRAMM_VERSION"
)
quit() {
printf "\n"
printf "%s" "$BLUE"
printf "[-] Exit%s\n" "$NORMAL"
exit 1
}
# --- Programmstart --- #
# --------------------- #
# CLI Optionen auswerten
while getopts f:hi:l:qv opt
do
case $opt in
f) RECHNUNGSFILE=$OPTARG;;
h) display_help; exit 0;;
i) INSTALL_LCO_FILE=1;;
l) LCOFILE=$OPTARG;;
q) VERBOSITY=0;;
v) display_version; exit 0;;
*) display_help; exit 1;;
esac
done
# Farbe aktivieren wenn vorhanden
if [ -n "$NUMBER_OF_COLORS" ] && [ "$NUMBER_OF_COLORS" -ge 8 ]; then
COLORED_OUTPUT=1
fi
print_deko " ========================"
print_deko " = $PROGRAMM_NAME v$PROGRAMM_VERSION ="
print_deko " ========================"
# Verschiedene Basic Checks
if [ "$(id -u)" -eq 0 ]; then
print_error "$0: Das Skript darf nicht als Root ausgeführt werden."
exit 1
fi
check_binaries "$EXECUTABLES_TO_CHECK"
check_readable "$RECHNUNGSFILE"
check_readable "$TEMPLATEFILE"
check_readable "$LCOFILE"
if ! check_sty; then
print_warn "Verwenden sie -i um $STY_RECHNUNG zu installieren"
if [ $INSTALL_LCO_FILE -eq 0 ]; then
exit 7
else
install_sty
fi
fi
# Kundendatei einlesen und rechnungen erstellen
while IFS= read -r rawline
do
trim_line "$rawline"
proced_ini_line "$LINE_BUFFER"
done < "$RECHNUNGSFILE"