-
Notifications
You must be signed in to change notification settings - Fork 1
/
MAS_Reader.py
523 lines (409 loc) · 19.5 KB
/
MAS_Reader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# # Eigenschaften der Automaten Dateien
## https://github.com/Soulchemist97/MAS-3tech-Reader ##
## Benötigte Module laden ##
import os # Anwählen von Ordnern
import io # Arbeiten mit Filestreams
import pandas as pd # Tabellen-Modul
from datetime import datetime as dt # Formatiert Datumswerte
import re # RegEx Sucht Patterns in Strings
from string import whitespace # Leerzeichen entfernen
import shutil # Kopieren, Verschieben in andere Ordner
try:
from fpdf import FPDF # PDF Creator
# PDF-Dokumentation https://pyfpdf.readthedocs.io/en/latest/Tutorial/index.html
except:
print("Fehlendes PDF-Modul: FPDF")
def create_Ordner(Ordner: str):
"""Checkt auf vorhandene Ordner und erstellt ggf. neuen Ordner
Args:
Ordner (str): Ordnerpfad zum neuen oder bereits existierenden Ordner
Returns:
Verzeichnis: Ordnerpfad als String
"""
Verzeichnis = Ordner # Verzeichnisse zusammensetzen ohne + "/"
Vorhanden = os.path.isdir(Verzeichnis) # Prüfen ob Vorhanden
if Vorhanden != True:
os.makedirs(Verzeichnis) # Erstelle Verzeichnis
else:
pass
return Verzeichnis
def DeleteEmptyFolder(Pfad: str):
"""
Löscht leere UnterOrdner:
1. Checkt ob Ordner existiert
2. Checkt auf Unterordner
3. Wenn Unterornder Leer sind, werden sie gelöscht
Args:
Pfad (str): Pfad des übergeordneten Ordners. Löscht alle leeren Unterordner
"""
FolderExists = os.path.isdir(Pfad)
if FolderExists:
SubFolder = os.listdir(Pfad)
for sub in SubFolder:
SubFolder_Path = os.path.join(Pfad, sub)
##Skippen bei Dateien
CheckForFile = os.path.isfile(SubFolder_Path)
if CheckForFile:
continue
SubFolderExists = os.path.isdir(SubFolder_Path)
FolderIsEmpty = True if len(os.listdir(SubFolder_Path)) == 0 else False
if SubFolderExists & FolderIsEmpty:
shutil.rmtree(SubFolder_Path)
print(SubFolder_Path, "Deleted")
Regex_Patterns = {
"Datum": r"\d{2}\.(0[1-9]|1[0-2])\.\d{4}", # Langes Datum
"Date": r"\d{2}\.(0[1-9]|1[0-2])\.\d{2}", # Kurzes Datum
"Uhrzeit": r"\d{2}:\d{2}:\d{2}",
"Zulassungsnummer": r"\d{9}",
"Ausdruck": r"(A|B) \d{3}",
"Geraetetyp": r"\x1bK\"",
"Ablaufdatum": r"\d{4}/\d{2}",
"Geld": r"\d+(.\d{1,2})"
}
def Extract_Value(Wort: str, Regex_Pattern: str, Lines):
"""
Wort in Zeile suchen und aus dieser Zeile nach einem RegEx-Pattern den Wert erhalten
"""
for i in Lines:
a = re.search(Wort, i) # Suche Wort in Zeile
if a != None: # Wenn Wort gefunden
Z = re.search(Regex_Pattern, i)
if Z != None:
return Z[0]
def Extract_OtherValue(Wort: str, Lines, Versatz: int = 1, Remove_Spaces: bool = True):
"""Wort in Zeile suchen und aus anderer Zeile den Wert erhalten
Args:
Wort (str): Wort in bestimmter Zeile.
Lines (list): Liste der verwendeten Zeilen. Der zu durchsuchende Text
Versatz (int, optional): Zeilenentfernung von der Zeile per Wort. Standard = 1.
Remove_Spaces (bool, optional): Leerzeichen entfernen oder nicht. Standard = True.
Returns:
Wert: Gibt Wert nach Regex-Pattern aus.
"""
index = 0 # Zeilenindex
for i in Lines:
a = re.search(Wort, i) # Suche Zeile mit Wort
if a != None: # Wenn Wort gefunden
Z = re.sub(' +', ' ', Lines[index + Versatz]) # Zeile Versetzt um die gefundene ohne Multi-spaces
if Z != None:
return Z
index += 1
class Rechnung():
# Standardwerte falls nichts gefunden wurde
Datum_Anfang, Datum_Ende = None, None
Ausdruck_Nr = None
Ablaufdatum = "--/--"
Saldo_1 = None
Saldo_2 = None
Einsaetze = None
Gewinne = None
Zulassung = None
Geraetetyp = None
def __init__(self, Aufstellort, Pfad):
"""Erstellen eines Rechnungs Objektes
Args:
Aufstellort (str): Name des Aufstellortes bspw. der Bar.
Pfad (Str or file-like Object): Dateipfad oder Objekt der open(...)-Funktion
"""
self.Ort = Aufstellort
if type(Pfad) != io.TextIOWrapper: # File-like Object
self.Pfad = Pfad
Input_File = Pfad
FileStreamCheck = False
elif type(Pfad) == io.TextIOWrapper:
self.Pfad = Pfad.name
Input_File = Pfad
FileStreamCheck = True
self.old_name = os.path.split(self.Pfad)[-1] # Pfad.split("\\")[-1]
_, self.FileExtension = os.path.splitext(self.Pfad)
self.Auslesen(Input_File, FileStreamCheck)
def Auslesen(self, fileInput,
FileStream_Check=False): # Auslesen der Daten aus Datei, Auflisten der Werte und Ausgabe der Dateiname
"""
Liest aus verschiedenen Dokumentstrukturen die Daten je Quittung aus mittels Regex Patterns.
"""
if FileStream_Check == False:
### Datei öffnen und Liste aus Zeilen ausgeben
File_Obj = open(fileInput, "r", encoding='utf8', errors='ignore') # Öffnen der Datei
else:
File_Obj = fileInput
File_Lines = File_Obj.readlines() # Liste aller Zeilen
self.File_Lines = File_Lines
######### Parameter auslesen ###########
self.Zulassung = Extract_Value(Wort="ZULASSUNG", Regex_Pattern=Regex_Patterns["Zulassungsnummer"],
Lines=File_Lines, )
self.Ausdruck_Nr = Extract_Value(Wort="AUSDRUCK", Regex_Pattern=Regex_Patterns["Ausdruck"], Lines=File_Lines, )
if self.Ausdruck_Nr == None:
self.Ausdruck_Nr = Extract_Value(Wort="KOPIE", Regex_Pattern=Regex_Patterns["Ausdruck"], Lines=File_Lines, )
self.Ablaufdatum = Extract_Value(Wort="ABLAUF", Regex_Pattern=Regex_Patterns["Ablaufdatum"], Lines=File_Lines, )
# self.Geraetetyp = Extract_Value(Wort="BAUART",Regex_Pattern=,Lines=File_Lines,)
def MoneyFloat(Wort="GEWINNE", Regex_Pattern=Regex_Patterns["Geld"], Lines=File_Lines) -> float:
"""Search Money values and returns them as float object
Args:
Wort (str, optional): Wort in Zeile der Geldsumme. Defaults to "GEWINNE".
Regex_Pattern (_type_, optional): Pattern für Geld. Defaults to Regex_Patterns["Geld"].
Lines (_type_, optional): Textzeilen. Defaults to File_Lines.
Returns:
Money_Value (float): Formatted money value
"""
Money = Extract_Value(Wort, Regex_Pattern, Lines, )
if Money != None:
Money_wo_spaces = Money.translate({ord(c): None for c in whitespace}) # Spaces entfernen
Money: float = float(Money_wo_spaces.replace(",", ".")) # Leerzeichen entfernen
return Money
# Regex SALDO (1) && SALDO (2) checken
self.Saldo_1 = MoneyFloat(Wort=" \(1", Regex_Pattern=Regex_Patterns["Geld"], Lines=File_Lines, )
self.Saldo_2 = MoneyFloat(Wort=" \(2", Regex_Pattern=Regex_Patterns["Geld"], Lines=File_Lines, )
self.Einsaetze = MoneyFloat(Wort="EINSAETZE", Regex_Pattern=Regex_Patterns["Geld"], Lines=File_Lines, )
self.Gewinne = MoneyFloat(Wort="GEWINNE", Regex_Pattern=Regex_Patterns["Geld"], Lines=File_Lines)
self.Einwurf = MoneyFloat(Wort="EINWURF", Regex_Pattern=Regex_Patterns["Geld"], Lines=File_Lines)
self.Auswurf = MoneyFloat(Wort="AUSWURF", Regex_Pattern=Regex_Patterns["Geld"], Lines=File_Lines)
##################
##Datum beziehen##
##################
try:
EndDatum_string = Extract_OtherValue(Wort="KASSIERUNG VOM", Lines=File_Lines, Versatz=2)
EndDatum_string = re.search(Regex_Patterns["Datum"], EndDatum_string)
AnfangsDatum_String = Extract_OtherValue(Wort="LETZTE KASSIERUNG", Lines=File_Lines, Versatz=2)
if AnfangsDatum_String == None:
AnfangsDatum_String = Extract_OtherValue(Wort="INBETRIEBNAHME", Lines=File_Lines, Versatz=2)
AnfangsDatum_String = re.search(Regex_Patterns["Datum"], AnfangsDatum_String)
self.Datum_Anfang, self.Datum_Ende = AnfangsDatum_String[0], EndDatum_string[0]
except:
print(fileInput, "Datum konnte nicht ausgelesen werden")
### Datum ausgelesen ###
self.Geraetetyp = Extract_OtherValue(Regex_Patterns["Geraetetyp"], Lines=File_Lines, Versatz=1)
Dateiname = f"{self.Ort} [{self.Datum_Anfang}-{self.Datum_Ende}]({str(self.Ausdruck_Nr)})({str(self.Zulassung)}){self.FileExtension}"
self.Dateiname = Dateiname
if FileStream_Check == False:
File_Obj.close() # Schließen der Datei
return Dateiname
def __str__(self):
Str_Box = f"""{self.Dateiname} \n
Ablauf: {self.Ablaufdatum} \r
Saldo 1: {self.Saldo_1} € \r
Saldo 2: {self.Saldo_2} € \r
Einsaetze: {self.Einsaetze} € \r
Gewinne: {self.Gewinne} € \r
Einwurf: {self.Einwurf} € \r
Auswurf: {self.Auswurf} € \r
Geraetetyp: {self.Geraetetyp} \r
"""
return Str_Box
def __repr__(self):
return self.Dateiname
def open(self): # Datei öffnen
os.startfile(self.Pfad)
def get_line_index(self, wort: str) -> int:
with open(self.Pfad, "r", encoding='utf8', errors='ignore') as file:
for line_number, line in enumerate(file, start=1):
if wort in line:
print(f"Zeile {line_number}:", line)
return line_number
def pdf(self, Zeilen: int = 100):
"""
Erstellt PDF der ersten x Zeilen
Args:
Zeilen (int, optional): Zeilen des PDFs. Defaults to 100.
"""
pdf = FPDF() # PDF-Klasse
pdf.add_page()
pdf.set_font("Arial", size=10)
lines = open(self.Pfad, "r", encoding='utf8', errors='ignore')
### Schneiden ###
Strings = [L for L in lines]
if Zeilen != 0:
Strings = Strings[0:Zeilen]
# ------#
for line in Strings:
pdf.cell(180, 4, txt=line, ln=1, align='L') # Width, height,
PDF_Pfad = os.path.join(create_Ordner(r"pdf/" + self.Ort), self.Dateiname.replace(self.FileExtension, ".pdf"))
try:
pdf.output(PDF_Pfad)
lines.close()
except UnicodeEncodeError:
print(self.Dateiname, ": PDF konnte aufgrund von Sonderzeichen nicht erstellt werden")
class Aufstellort():
def Auflisten(self):
"""
Listen für alle wichtigen Eigenschaften in der Datei erstellen
"""
self.Dateinamen = [Rechnung.Dateiname for Rechnung in self.Rechnungen]
self.Aufstellorte = [Rechnung.Ort for Rechnung in self.Rechnungen]
self.Zulassungen = [Rechnung.Zulassung for Rechnung in self.Rechnungen]
self.Ausdruck_Nummern = [Rechnung.Ausdruck_Nr for Rechnung in self.Rechnungen]
self.Geraetetypen = [Rechnung.Geraetetyp for Rechnung in self.Rechnungen]
self.Daten_Anfang = [Rechnung.Datum_Anfang for Rechnung in self.Rechnungen]
self.Daten_Ende = [Rechnung.Datum_Ende for Rechnung in self.Rechnungen]
self.Ablaufdaten = [Rechnung.Ablaufdatum for Rechnung in self.Rechnungen]
self.Saldo_1_Liste = [Rechnung.Saldo_1 for Rechnung in self.Rechnungen]
self.Saldo_2_Liste = [Rechnung.Saldo_2 for Rechnung in self.Rechnungen]
self.Einsaetze = [Rechnung.Einsaetze for Rechnung in self.Rechnungen]
self.Gewinne = [Rechnung.Gewinne for Rechnung in self.Rechnungen]
self.Einwuerfe = [Rechnung.Einwurf for Rechnung in self.Rechnungen]
self.Auswuerfe = [Rechnung.Auswurf for Rechnung in self.Rechnungen]
# Dictionary für DataFrame
self.dataset = {"Aufstellort": self.Aufstellorte, "Ausdruck_Nr": self.Ausdruck_Nummern,
"Zulassungsnummer": self.Zulassungen, "Geraetetyp": self.Geraetetypen,
"Anfangsdatum": self.Daten_Anfang, "Enddatum": self.Daten_Ende,
"Ablaufdatum": self.Ablaufdaten, "Saldo1": self.Saldo_1_Liste, "Saldo2": self.Saldo_2_Liste,
"Einsaetze": self.Einsaetze, "Gewinne": self.Gewinne,
"Einwurf": self.Einwuerfe, "Auswurf": self.Auswuerfe
}
def dataframe(self):
"""
Aufgelistete Eigenschaften als Panda Dataframe formattieren
"""
self.df = pd.DataFrame(self.dataset)
return self.df
def __init__(self, Ort: str = None, Pfad: str = None):
"""Initialisiert Aufstellort mit vollständigem Pfad oder dem Namen im Ordner INPUT.
Args:
Ort (str, optional): Name des Ordners in INPUT. Defaults to None.
Pfad (str, optional): Ordnerpfad des Aufstellorts. Defaults to None.
Raises:
ValueError: Fehler, wenn keine der beiden Werte gesetzt wurde.
"""
if Ort == None and Pfad == None:
OrtError = "Ort in Input oder Ordnerpfad benötigt"
raise ValueError(OrtError)
if Pfad == None:
self.Ort = Ort
self.Input = os.path.join(Input_dir, self.Ort)
elif Pfad != None:
self.Input = Pfad
self.Ort = Pfad.split("\\")[-1]
DeleteEmptyFolder(os.path.join(*self.Input.split("\\"))) # Löscht Unterordner wenn Leer
SubFolder_exists = os.path.isdir(os.path.join(self.Input, "Kass-Daten"))
if SubFolder_exists:
self.Input = os.path.join(Input_dir, self.Ort, "Kass-Daten")
self.Output = os.path.join(Output_dir, self.Ort) # Ausgabe-Ordner
self.Dateien = os.listdir(self.Input) # Liste aller Dateien
Pfade = [os.path.join(self.Input, Rechnung) for Rechnung in self.Dateien]
## Liste aller Klassen-Objekte ##
self.Rechnungen = [Rechnung(self.Ort, Quittung) for Quittung in Pfade]
self.Auflisten() # Listen aller Eigenschaften erstellen
self.dataframe() # Dataframe erstellen
def __str__(self): # Print-Befehlausgabe
Quittungen_str_list = [str(Rechnung) for Rechnung in self.Rechnungen]
Quittungen_str = ''.join(Quittungen_str_list) # Langer Kombinierter String
return Quittungen_str
def __len__(self): # Anzahl Rechnungen ausgeben
return len(self.Rechnungen)
def __repr__(self):
return self.Ort
def __iter__(self): # Ausgabe als Liste bzw. Iterierbarkeit
return iter(self.Rechnungen)
def Verschieben(self, Loeschen="n", remove="n"):
create_Ordner(self.Output) # Ordner erstellen
for Rechnung in self.Rechnungen:
copy_from = Rechnung.Pfad # Alter Pfad
copy_to = os.path.join(self.Output, Rechnung.old_name)
Old_Name = copy_to
New_Name = os.path.join(self.Output, Rechnung.Dateiname)
try:
if remove == "y":
shutil.move(copy_from, New_Name) # Verschieben auf einmal
continue
shutil.copy(copy_from, copy_to) # Kopieren
os.rename(Old_Name, New_Name) # Umbennenen
if Loeschen == "y": # Loeschen der alten Datei An/Aus
os.remove(copy_from)
else:
pass # Wenn nicht, weitermachen
except FileExistsError:
print(Rechnung, "doppelt")
pass
except OSError:
print(Rechnung, "Parameterfehler")
pass
except IndexError:
print(Rechnung, "Index-Error")
pass
def pdf(self, cut=True, N_Zeilen: int = 100):
"""
Text Dateien der Quiitungen als PDF ausgeben
Args:
cut (bool, optional): Ab einer Zeile geschnitten oder vollständig umgewandelt. Defaults to True.
N_Zeilen (int, optional): Anzahl Zeilen, bis zu der abgeschnitten wird. Defaults to 100.
"""
for Quittung in self.Rechnungen:
if cut == False or N_Zeilen == 0:
Quittung.pdf(Zeilen=0)
else:
Quittung.pdf(Zeilen=N_Zeilen)
def Excel(self):
"""
Wichtige Daten aus den Rechnungen für einen Aufstellort in einer Excel Datei ausgeben
"""
self.df.to_excel(create_Ordner("Zusammenfassungen") + "/" + self.Ort + "_Zusammenfassung.xlsx", index=False)
return self.df
def store(self):
"""
Exportiert Datensatz als binäres Format
"""
import pickle
create_Ordner("Database")
filename = f"Database/{self.Ort}_Database.dbs"
with open(filename, "wb") as Out_File:
pickle.dump(self, Out_File)
"""
with open("Aufstellort_Database","rb") as in_file:
new_Loc = pickle.load(in_file)
"""
if __name__ == "__main__":
print("Datei aus Input Ordner werden geladen")
## Ordnerpfade zum befüllen mit Quittungen ##
Input_dir = create_Ordner("Input") # "Input"
Output_dir = create_Ordner("Output") # Output
# Aufstellorte Indizieren
Orte = os.listdir(Input_dir)
Locations = [Aufstellort(Ort) for Ort in Orte]
Bool_Dict = {
"y": True,
"Y": True,
"Yes": True,
"yes": True,
"Ja": True,
"ja": True,
"N": False,
"n": False,
"Nein": False,
"nein": False}
Bool_Dict.setdefault("n")
# Eingabeaufforderungen
Print_Frage = input("Dateiinfos im Terminal ausgeben? (y): ")
if Bool_Dict.get(Print_Frage):
for Loc in Locations:
print(Loc)
Remove_Frage = input("Verschieben (y), Kopieren (c), Nichts (n) : ")
Excel_Frage = Bool_Dict.get(input("Save Excel Files? y: "))
PDF_Frage = Bool_Dict.get(input("Save as PDF? (y) : "))
if PDF_Frage:
PDF_Zeilen = input("Anzahl Zeilen der PDFs? (0 = Alle; E = Einsatze; S = Saldo 2): ")
try:
PDF_Zeilen: int = int(PDF_Zeilen)
except ValueError:
...
# E eine Zeile nach Einsatze
# S eine Zeile nach Saldo (2)
for Location in Locations:
if PDF_Frage:
if type(PDF_Zeilen) is int:
Location.pdf(cut=True, N_Zeilen=PDF_Zeilen)
else:
for Quittung in Location.Rechnungen:
Saldo_lines: int = Quittung.get_line_index(wort="SALDO (2)") + 1
Einsaetze_lines: int = Quittung.get_line_index(wort="EINSAETZE") + 1
if PDF_Zeilen == "E" or PDF_Zeilen == "e":
Quittung.pdf(Zeilen=Einsaetze_lines)
if PDF_Zeilen == "S" or PDF_Zeilen == "s":
Quittung.pdf(Zeilen=Saldo_lines)
if Remove_Frage == "y" or Remove_Frage == "c":
Location.Verschieben(remove=Remove_Frage)
if Excel_Frage:
Location.Excel()
### Delete Empty Folders ###
DeleteEmptyFolder(Input_dir)
DeleteEmptyFolder(Output_dir)
DeleteEmptyFolder("pdf")