Merge branch 'main' of gitea:berhsi/rechnung
This commit is contained in:
commit
fb640c789d
2 changed files with 378 additions and 3 deletions
|
@ -1,5 +1,5 @@
|
|||
rechnung.sh
|
||||
-----------
|
||||
rechnung.sh / rechnung.py
|
||||
-------------------------
|
||||
|
||||
Erstellt aus einer Kundendatei Rechnungen.
|
||||
|
||||
|
@ -16,6 +16,6 @@ Installation
|
|||
------------
|
||||
|
||||
Kopieren Sie die Dateien template.ltx, octorech.lco, rechnung.ini und
|
||||
rechnung.sh in ein Verzeichnis Ihrer Wahl. Passen Sie die Dateien
|
||||
rechnung.{sh|py} in ein Verzeichnis Ihrer Wahl. Passen Sie die Dateien
|
||||
rechnung.ini und octorech.lco an Ihre Bedürfnisse an.
|
||||
|
||||
|
|
375
rechnung.py
Executable file
375
rechnung.py
Executable file
|
@ -0,0 +1,375 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# file: rechnung.py
|
||||
# date: 03.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.
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import locale
|
||||
import subprocess
|
||||
from datetime import date, timedelta
|
||||
|
||||
|
||||
# Diverse Variablen
|
||||
# -----------------
|
||||
|
||||
|
||||
|
||||
class DISPLAY():
|
||||
'''
|
||||
'''
|
||||
def __init__(self):
|
||||
self.red = '\x1B[31m'
|
||||
self.green = '\x1B[32m'
|
||||
self.yellow = '\x1B[33m'
|
||||
self.magenta = '\x1B[35m'
|
||||
self.reset = '\x1B[39m'
|
||||
self.colorify = False
|
||||
self.terminal_has_color()
|
||||
|
||||
def terminal_has_color(self):
|
||||
try:
|
||||
if os.environ["COLORTERM"] in ("256", "truecolor"):
|
||||
self.colorify = True
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def TRUE(self):
|
||||
'''
|
||||
'''
|
||||
if self.colorify == True:
|
||||
print(f"{self.green}OK{self.reset}")
|
||||
else: print("OK")
|
||||
|
||||
def FALSE(self):
|
||||
'''
|
||||
'''
|
||||
if self.colorify == True:
|
||||
print(f"{self.red}Failed{self.reset}")
|
||||
else: print("Failed")
|
||||
|
||||
def DEKO(self, msg, end='\n'):
|
||||
'''
|
||||
param 1: string
|
||||
param 2: string
|
||||
'''
|
||||
if self.colorify == True:
|
||||
print(f"{self.magenta}{msg}{self.reset}", end=end)
|
||||
else: print(f"{msg}", end=end)
|
||||
|
||||
def INFO(self, msg, end='\n'):
|
||||
'''
|
||||
param 1: string
|
||||
param 2: string
|
||||
'''
|
||||
if self.colorify == True:
|
||||
print(f"{self.green}[+]{self.reset} {msg}", end=end)
|
||||
else: print(f"[+] {msg}", end=end)
|
||||
|
||||
def WARN(self, msg, end='\n'):
|
||||
'''
|
||||
param 1: string
|
||||
'''
|
||||
if self.colorify == True:
|
||||
print(f"{self.yellow}[-]{self.reset} {msg}", end=end)
|
||||
else: print(f"[-] {msg}", end=end)
|
||||
|
||||
def ERROR(self, msg, end='\n'):
|
||||
'''
|
||||
param 1: string
|
||||
'''
|
||||
if self.colorify == True:
|
||||
print(f"{self.red}[-]{self.reset} {msg}", end=end)
|
||||
else: print(f"[-] {msg}", end=end)
|
||||
|
||||
|
||||
class KUNDE():
|
||||
'''
|
||||
'''
|
||||
def __init__(self):
|
||||
self.kunde = ""
|
||||
self.adresse = ""
|
||||
self.anrede = ""
|
||||
self.anzahl = ""
|
||||
self.position = ""
|
||||
self.short = ""
|
||||
self.betrag = ""
|
||||
self.lfdnr = ""
|
||||
self.error = False
|
||||
|
||||
|
||||
class RECHNUNG():
|
||||
'''
|
||||
'''
|
||||
def __init__(self):
|
||||
self.programm_name = "rechnung.py"
|
||||
self.programm_version = "0.0.1"
|
||||
self.rechnungsfile = "rechnung.ini"
|
||||
self.templatefile = "template.ltx"
|
||||
self.compiler = ""
|
||||
|
||||
self.D = DISPLAY()
|
||||
|
||||
locale.setlocale(locale.LC_TIME, "de_DE.utf8")
|
||||
|
||||
|
||||
def print_header(self):
|
||||
'''
|
||||
'''
|
||||
self.D.DEKO(" =============================")
|
||||
self.D.DEKO(" | rechnung.py v{} |".format(self.programm_version))
|
||||
self.D.DEKO(" =============================")
|
||||
|
||||
|
||||
def check_readable(self, filename):
|
||||
'''
|
||||
param 1: string
|
||||
'''
|
||||
self.D.INFO("Teste, ob {} ist lesbar ... ".format(filename), end="")
|
||||
response = os.access(filename, os.R_OK)
|
||||
if response == True:
|
||||
self.D.TRUE()
|
||||
else:
|
||||
self.D.FALSE
|
||||
sys.exit(1)
|
||||
|
||||
def check_program(self, program):
|
||||
'''
|
||||
param 1: string
|
||||
returns: boolean
|
||||
'''
|
||||
self.D.INFO(f"Teste, ob {program} installiert ist ... ", end="")
|
||||
response = subprocess.run(["which", program], capture_output=True)
|
||||
if response.returncode == 0:
|
||||
self.D.TRUE()
|
||||
self.compiler = program
|
||||
self.D.INFO(f"Setze {program} als Compiler.")
|
||||
return True
|
||||
else:
|
||||
self.D.FALSE()
|
||||
return False
|
||||
|
||||
def parse_buffer(self, buffer):
|
||||
'''
|
||||
param 1: liste
|
||||
'''
|
||||
for line in buffer:
|
||||
line = line.strip()
|
||||
if line == "" or line.startswith("#"):
|
||||
pass
|
||||
elif line.startswith("[start]"):
|
||||
self.K = KUNDE()
|
||||
elif line.startswith("[ende]"):
|
||||
self.make_invoice()
|
||||
del(self.K)
|
||||
else: self.set_customer_values(line)
|
||||
|
||||
def set_customer_values(self, line):
|
||||
'''
|
||||
param 1: string
|
||||
'''
|
||||
if line.find("=") == -1:
|
||||
self.D.WARN(f"Ungültige Syntax: {line}")
|
||||
return
|
||||
key, value = line.split("=")
|
||||
if key == "kunde":
|
||||
self.K.kunde = value.strip()
|
||||
elif key == "adresse":
|
||||
self.K.adresse = value.strip()
|
||||
elif key == "anrede":
|
||||
self.K.anrede = value.strip()
|
||||
elif key == "anzahl":
|
||||
self.K.anzahl = value.strip()
|
||||
elif key == "position":
|
||||
self.K.position = value.strip()
|
||||
elif key == "short":
|
||||
self.K.short = value.strip()
|
||||
elif key == "betrag":
|
||||
self.K.betrag = value.strip()
|
||||
elif key == "lfdnr":
|
||||
self.K.lfdnr = value.strip()
|
||||
else:
|
||||
self.D.WARN(f"Fehler beim Parsen von {self.K.kunde}")
|
||||
self.D.WARN(f"Ungültige Option: {key}")
|
||||
self.K.error = True
|
||||
|
||||
def make_invoice(self):
|
||||
'''
|
||||
'''
|
||||
buffer = None
|
||||
|
||||
self.D.INFO(f"Erstelle Rechung für Kunde {self.K.kunde}")
|
||||
for i in self.K.__dict__:
|
||||
if self.K.__dict__[i] == "":
|
||||
self.D.WARN(f"Leeres Feld: {i}")
|
||||
self.K.error = True
|
||||
if self.K.error is True:
|
||||
self.D.WARN(f"Kunde enthält einen Error. Erstelle keine Rechnung.")
|
||||
return
|
||||
|
||||
buffer = self.get_file_content(self.templatefile)
|
||||
if buffer is None:
|
||||
self.D.ERROR(f"Kann {self.templatefile} nicht lesen. Exit")
|
||||
sys.exit(4)
|
||||
latex = self.replace(buffer)
|
||||
sys.exit(0)
|
||||
|
||||
output_filename = self.build_output_filename()
|
||||
if output_filename == "":
|
||||
self.D.ERROR("Kann Dateinamen nicht erstellen")
|
||||
sys.exit(5)
|
||||
with open(output_filename, "w") as fd:
|
||||
for i in latex:
|
||||
fd.write(i)
|
||||
fd.close()
|
||||
|
||||
response = subprocess.run([self.compiler, output_filename], capture_output=True)
|
||||
if response.returncode != 0:
|
||||
self.D.ERROR(f"Fehler beim Compilieren für {self.K.kunde}")
|
||||
else:
|
||||
subprocess.run([self.compiler, output_filename], capture_output=True)
|
||||
self.D.INFO(f"Rechnung für {self.K.kunde} erfolgreich erstellt.")
|
||||
|
||||
def replace(self, buffer):
|
||||
'''
|
||||
'''
|
||||
latex = []
|
||||
for line in buffer:
|
||||
if line.find("#ANSCHRIFT#") != -1:
|
||||
anschrift = self.build_anschrift()
|
||||
line = line.replace("#ANSCHRIFT#", anschrift)
|
||||
elif line.find("#RECHNUNGSNUMMER#") != -1:
|
||||
invoice_number = self.build_invoice_number()
|
||||
line = line.replace("#RECHNUNGSNUMMER#", invoice_number)
|
||||
elif line.find("#SUBJECT#") != -1:
|
||||
subject = self.build_subject()
|
||||
line = line.replace("#SUBJECT#", subject)
|
||||
elif line.find("#ANREDE#") != -1:
|
||||
line = line.replace("#ANREDE#", self.K.anrede)
|
||||
elif line.find("#DEADLINE#") != -1:
|
||||
deadline = self.build_deadline()
|
||||
line = line.replace("#DEADLINE#", deadline)
|
||||
if line.find("#ANZAHL#") != -1:
|
||||
line = line.replace("#ANZAHL#", self.K.anzahl)
|
||||
if line.find("#POSITION#") != -1:
|
||||
line = line.replace("#POSITION#", self.K.position)
|
||||
if line.find("#BETRAG#") != -1:
|
||||
line = line.replace("#BETRAG#", self.K.betrag)
|
||||
latex.append(line)
|
||||
return latex
|
||||
|
||||
def build_output_filename(self):
|
||||
'''
|
||||
returns: string
|
||||
'''
|
||||
filename = ""
|
||||
kunde = self.K.kunde.replace(" ", "_")
|
||||
today = time.strftime("%Y%m%d")
|
||||
month = time.strftime("%B")
|
||||
filename = "_".join((today, "Rechnung", month, kunde))
|
||||
filename = ".".join((filename, "ltx"))
|
||||
return filename
|
||||
|
||||
def build_anschrift(self):
|
||||
'''
|
||||
returns: string
|
||||
'''
|
||||
anschrift = ""
|
||||
anschrift = self.K.kunde.strip()
|
||||
adressliste = self.K.adresse.split(";")
|
||||
for i in adressliste:
|
||||
anschrift = "\\\\".join((anschrift, i.strip()))
|
||||
return anschrift
|
||||
|
||||
def build_subject(self):
|
||||
'''
|
||||
returns: string
|
||||
'''
|
||||
try:
|
||||
subject = " ".join(("Rechnung zu", self.K.short.strip()))
|
||||
except Exception as e:
|
||||
self.D.ERROR("Fehler beim Erstellen des Subjects")
|
||||
self.D.ERROR(f"Error: {e}")
|
||||
self.K.error = True
|
||||
subject = ""
|
||||
return subject
|
||||
|
||||
def build_invoice_number(self):
|
||||
'''
|
||||
returns: string
|
||||
'''
|
||||
try:
|
||||
today = date.today().strftime("%Y%m%d")
|
||||
invoice_number = "-".join((today, self.K.lfdnr))
|
||||
except Exception as e:
|
||||
self.D.ERROR("Fehler beim Erstellen der Rechnungsnummer")
|
||||
self.D.ERROR(f"Error: {e}")
|
||||
self.K.error = True
|
||||
invoice_number = ""
|
||||
return invoice_number
|
||||
|
||||
def build_deadline(self):
|
||||
'''
|
||||
returns: string
|
||||
'''
|
||||
try:
|
||||
delta = timedelta(days=14)
|
||||
deadline = date.today()
|
||||
deadline = deadline + delta
|
||||
tupel = date.timetuple(deadline)
|
||||
delta = timedelta(days=1)
|
||||
while tupel.tm_wday in (5, 6):
|
||||
deadline = deadline + delta
|
||||
tupel = date.timetuple(deadline)
|
||||
deadline = str(deadline.strftime("%d.%m.%Y"))
|
||||
except Exception as e:
|
||||
self.D.ERROR("Fehler beim Erstellen der Deadline")
|
||||
self.D.ERROR(f"Error: {e}")
|
||||
self.K.error = True
|
||||
deadline = ""
|
||||
return deadline
|
||||
|
||||
def get_file_content(self, filename):
|
||||
'''
|
||||
param 1: string
|
||||
returns: liste
|
||||
'''
|
||||
buffer = None
|
||||
|
||||
with open(filename, "r") as fd:
|
||||
buffer = fd.readlines()
|
||||
fd.close()
|
||||
return buffer
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
'''
|
||||
|
||||
self.print_header()
|
||||
|
||||
for i in (self.rechnungsfile, self.templatefile):
|
||||
self.check_readable(i)
|
||||
|
||||
for program in ('pdflatex', 'xelatex', 'luatex'):
|
||||
if self.check_program(program) == True:
|
||||
break
|
||||
if self.compiler == "":
|
||||
self.D.ERROR("Kann keinen Compiler finden. Exit")
|
||||
sys.exit(2)
|
||||
|
||||
buffer = self.get_file_content(self.rechnungsfile)
|
||||
if buffer is None:
|
||||
self.D.ERROR(f"Kann {self.rechnungsfile} nicht lesen. Exit")
|
||||
sys.exit(3)
|
||||
self.parse_buffer(buffer)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
r = RECHNUNG()
|
||||
r.run()
|
Loading…
Reference in a new issue