255 lines
11 KiB
Python
255 lines
11 KiB
Python
import update_database
|
|
import argparse
|
|
import os
|
|
import json
|
|
import pandas as pd
|
|
import openpyxl
|
|
|
|
"""
|
|
Dieses Programm kann:
|
|
- eine Excel-Datei exportieren, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer angegebenen Nummer migriert werden müssen.
|
|
Sie gibt dabei Auskunft darüber, welche Teilenummern bereits migriert wurden und welche noch migriert werden müssen.
|
|
- die oben beschriebene Darstellung in einer Excel-Datei auch als Treeview auf der Kommandozeile darstellen
|
|
"""
|
|
|
|
class Compare:
|
|
|
|
lib_dir = os.environ.get('CREMIG_LIB')
|
|
data_dir = os.environ.get('CREMIG_DATA')
|
|
out_dir = os.environ.get('CREMIG_WORK')
|
|
ruledesigner_csv_filename = os.environ.get('RD_DATABASE_NAME')
|
|
ruledesigner_csv_filepath = os.path.join(data_dir, ruledesigner_csv_filename)
|
|
vba_macro_filename = os.environ.get('VBA_MACRO_NAME')
|
|
vba_macro_filepath = os.path.join(lib_dir, vba_macro_filename)
|
|
|
|
def __init__(self, main_sivas_id):
|
|
self.main_sivas_id = main_sivas_id
|
|
self.sivas_json_path = os.path.join(Compare.data_dir, main_sivas_id + ".json")
|
|
self.sivas_ids = self.read_sivas_ids()
|
|
self.ruledesigner_ids = self.read_ruledesigner_ids()
|
|
|
|
|
|
def read_sivas_ids(self):
|
|
|
|
# Metohde liest die JSON-Datei ein, in der alle Sivas-Teilenummern aufgelistet sind, die zusammen mit der Hauptnummer migriert werden müssen
|
|
|
|
try:
|
|
with open(self.sivas_json_path, encoding='utf-8') as json_file:
|
|
return json.load(json_file)
|
|
|
|
except FileNotFoundError:
|
|
# JSON-Datei wurde noch nicht generiert
|
|
update_database.get_sivas_dbase(self.main_sivas_id, Compare.data_dir)
|
|
return self.read_sivas_ids()
|
|
|
|
|
|
def read_ruledesigner_ids(self):
|
|
|
|
# Methode liest die CSV-Datei ein, in der alle RuleDesigner-Nummern aufgelistet sind
|
|
|
|
try:
|
|
df = pd.read_csv(Compare.ruledesigner_csv_filepath, sep=';', converters={"Teilenummern":str})
|
|
return tuple(df["Teilenummern"])
|
|
|
|
except FileNotFoundError:
|
|
# CSV-Datei wurde noch nicht ins %CREMIG_DATA% kopiert
|
|
update_database.get_rd_dbase(Compare.data_dir)
|
|
return self.read_ruledesigner_ids()
|
|
|
|
|
|
def check_migration_status(self):
|
|
|
|
# Methode prüft, ob die Sivas-Nummern bereits migriert wurden
|
|
|
|
migrated = {}
|
|
for id in self.sivas_ids:
|
|
migrated[id] = True if id in self.ruledesigner_ids else False
|
|
return migrated
|
|
|
|
|
|
def create_sorting_string(self, id):
|
|
|
|
# Methode setzt rekursiv einen String zusammen, der anzeigt, wo sich eine Teilenummer in der Hierarchie der obersten Baugruppe befindet.
|
|
# Beispiel:
|
|
# Die Teilenummer "123456" ist das Halbzeug der Teilenummer "234567". "234567" ist wiederum in der Baugruppe "345678" eingebaut.
|
|
# Die Methode gibt dann folgenden String zurück: 345678-234567-123456
|
|
|
|
parent = self.sivas_ids[id][0]['Parent']
|
|
if parent not in self.sivas_ids:
|
|
return f"{parent}-{id}"
|
|
recur_val = self.create_sorting_string(parent)
|
|
return f"{recur_val}-{id}"
|
|
|
|
|
|
def create_excel_export(self):
|
|
|
|
# Spalten in Excel-Datei: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung
|
|
# Sortierung: Minus-separierter "Pfad" dieser Teilenummer im Assembly / im Halbzeug
|
|
# Teilenummer: Sivas-Teilenummer
|
|
# Bezeichnung: SIVAS-Bezeichnung der Teilenummer
|
|
# Migrationsstatus: "IMPORTIEREN" = Teilenummer muss noch migriert werden, "VERFÜGBAR" = Teilenummer wurde bereits migriert
|
|
# Restliche Spalten bleiben leer, weil sie in Excel befüllt werden
|
|
|
|
sorting = []
|
|
prt_num = []
|
|
description = []
|
|
migration_status = []
|
|
evaluation = []
|
|
comment = []
|
|
|
|
migrated = self.check_migration_status()
|
|
|
|
for id in self.sivas_ids:
|
|
sorting.append(self.create_sorting_string(id))
|
|
prt_num.append(id)
|
|
description.append(self.sivas_ids[id][0]['Bezeichnung'])
|
|
migration_status.append("VERFÜGBAR" if migrated[id] is True else "IMPORTIEREN")
|
|
evaluation.append('')
|
|
comment.append('')
|
|
|
|
data = {
|
|
"Sortierung": sorting,
|
|
"Teilenummer": prt_num,
|
|
"Bezeichnung": description,
|
|
"Migrationsstatus": migration_status,
|
|
"Beurteilung": evaluation,
|
|
"Bemerkung": comment
|
|
}
|
|
|
|
destiny_filepath = os.path.join(Compare.out_dir, self.main_sivas_id + ".xlsm")
|
|
|
|
workbook = openpyxl.load_workbook(Compare.vba_macro_filepath, keep_vba=True)
|
|
worksheet = workbook.active
|
|
|
|
# Überschriften eintragen
|
|
for header, column in zip(data, worksheet.iter_cols(min_row=1, max_col=len(data), max_row=1)):
|
|
for cell in column:
|
|
cell.value = header
|
|
|
|
# Werte (unter Überschriften) eintragen
|
|
for value, column in (zip(data.values(), worksheet.iter_cols(min_row=2, max_col=len(data), max_row=len(sorting)))):
|
|
for cell, element in zip(column, value):
|
|
cell.value = element
|
|
|
|
# Umbenennung des Blattes ist erforderlich, weil VBA Makro erst wirksam wird, wenn das Blatt nicht mehr "Sheet1" heißt
|
|
# Die Umbenennung darf nicht vor der Eintragung der Daten erfolgen, weil es sonst Probleme mit der bedingten Formatierung gibt
|
|
worksheet.title = "Ergebnis"
|
|
|
|
workbook.save(destiny_filepath)
|
|
print(f"Excel-Datei erfolgreich in '{Compare.out_dir}' geschrieben...")
|
|
|
|
|
|
def parent_children_mapping(self):
|
|
# Methode erstellt dict mit allen Parents (Keys) der json-Datei und weist den Parents deren Kinder als Werte zu.
|
|
parent_children = {}
|
|
for id in self.sivas_ids:
|
|
parent = self.sivas_ids[id][0]["Parent"]
|
|
if parent not in parent_children:
|
|
parent_children[parent] = []
|
|
parent_children[parent].append(id)
|
|
return parent_children
|
|
|
|
|
|
def get_excel_comments_for_treeview(self):
|
|
workbook = openpyxl.load()
|
|
pass
|
|
|
|
def create_treeview(self):
|
|
|
|
symbols = {
|
|
"pipe": "│ ",
|
|
"empty": " ",
|
|
"elbow": "└──",
|
|
"t": "├──"
|
|
}
|
|
|
|
# ANSI-Escape-Sequenzen für Einfärbung des Terminals abhängig vom Migrationsstatus.
|
|
# os.system wird für die Verarbeitung der Sequenzen benötigt
|
|
os.system("color")
|
|
colors = {
|
|
"RED": "\033[41m",
|
|
"GREEN": "\033[42m",
|
|
"RESET": "\033[0m"
|
|
}
|
|
|
|
prefix = ""
|
|
|
|
parent_children = self.parent_children_mapping()
|
|
|
|
max_tree_depth = max(id_value[0]["Position"].count(".") for id_value in self.sivas_ids.values())
|
|
max_sivas_num_len = max([len(id) for id in self.sivas_ids.keys()])
|
|
max_description_len = max([len(id_value[0]["Bezeichnung"]) for id_value in self.sivas_ids.values()])
|
|
|
|
for i, id in enumerate(self.sivas_ids):
|
|
|
|
parent = self.sivas_ids[id][0]["Parent"]
|
|
description = self.sivas_ids[id][0]["Bezeichnung"]
|
|
current_key_level = self.sivas_ids[id][0]["Position"].count(".")
|
|
|
|
# Aktueller Verbinder: └── , wenn letzter Knotenpunkt, sonst ├──
|
|
connector = symbols['elbow'] if id == parent_children[parent][-1] else symbols['t']
|
|
|
|
# grün = Teilenummer migriert, rot = Teilenummer noch nicht migriert
|
|
out_color = f"{colors['GREEN']}" if id in self.ruledesigner_ids else f"{colors['RED']}"
|
|
|
|
if i == 0:
|
|
# Bei oberster Teilenummer soll kein Verbinder dargestellt werden.
|
|
print(f"{out_color}{parent}{colors['RESET']}")
|
|
|
|
tree_str = f"{prefix}{connector}{out_color}{id}{colors['RESET']}"
|
|
len_tree_str = len(tree_str) - len(out_color + colors['RESET'])
|
|
|
|
pad_tree = (max_tree_depth * len(symbols['pipe']) + len(symbols['t']) + max_sivas_num_len - len_tree_str + 2) * " "
|
|
pad_description = (max_description_len - len(description) + 2) * " "
|
|
|
|
print(f"{tree_str}{pad_tree}{description}{pad_description}")
|
|
|
|
if (i + 1) != len(self.sivas_ids):
|
|
next_key = list(self.sivas_ids)[i + 1]
|
|
next_key_level = self.sivas_ids[next_key][0]["Position"].count(".")
|
|
else:
|
|
# Zu migrierende Nummer ist ein bearbeitetes Halbzeug. Programm muss hier beendet werden, da ansonsten später die
|
|
# nächste Teilenummer der json benötigt wird. Diese exisitert aber nicht, weil die json nur eine Teilenummer enthält
|
|
break
|
|
|
|
if id == parent_children[parent][-1]:
|
|
# Prüft, ob die aktuelle Nummer das letzte Kind des Parents ist
|
|
|
|
if id in parent_children:
|
|
# Prüft, ob das aktuelle Teil selbst ein Parent ist.
|
|
# Wenn ja, muss unter dem connector-Symbol der aktuellen Nummer (also im nächsten Durchlauf) Leerraum stehen
|
|
prefix += symbols["empty"]
|
|
|
|
else:
|
|
# Ende der aktuellen Gruppe an Teilenummern, die sich auf einer gemeinsamen Ebene befinden ist erreicht.
|
|
# Die nächste Teilenummer liegt eine oder mehrere Ebenen höher als die aktuelle Teilenummer (außer das Ende des gesamten Baumes ist erreicht).
|
|
# Der bisherige Präfix-String wird deshalb gekürzt. Die Anzahl der zu kürzenden Zeichen ergibt sich durch die Anzahl der Ebenen, die zwischen
|
|
# der aktuellen Teilenummer und der darauffolgenden Teilenummer liegen und der Länge des Pipe-Symbols, bzw. eines anderen Symbols (alle Symbole
|
|
# haben die gleiche Länge)
|
|
prefix_trim_len = (next_key_level - current_key_level) * len(symbols["pipe"])
|
|
prefix = prefix[:prefix_trim_len]
|
|
else:
|
|
if id in parent_children:
|
|
# Prüft erneut, ob das aktuelle Teil auch ein Parent ist.
|
|
# Wenn ja, muss dieses Mal unter der nächsten Nummer ein Pipe-Symbol kommen
|
|
prefix += symbols["pipe"]
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description='Das Programm gibt Auskunft über den aktuellen Migrationsstand', prog='compare_lists')
|
|
parser.add_argument('-n', '--number', action='store', required=True, help='Eingabe der SIVAS-Teilenummer, für die der Migrationsstatus angezeigt werden soll', metavar="id")
|
|
parser.add_argument('-t', '--tree', action='store_true', help='Erzeugt Baumdarstellung aller Teilenummern. grün = Teilenummer vorhanden, rot = Migration ausstehend')
|
|
parser.add_argument('-e', '--excel', action='store_true', help='Erzeugt Excel-Datei in der alle Teilenummern, die migriert werden müssen, aufgelistet sind')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.excel:
|
|
num = args.number.lstrip()
|
|
excel_comparison = Compare(num)
|
|
excel_comparison.create_excel_export()
|
|
elif args.tree:
|
|
num = args.number.lstrip()
|
|
treeview_comparison = Compare(num)
|
|
treeview_comparison.create_treeview()
|
|
else:
|
|
parser.print_help() |