import argparse import os import pandas as pd import json """ Dieses Programm : - holt sich die CSV-Datei, in der alle exisitierenden RuleDesigner-Teilenummern enthalten sind - holt sich die json-Datei, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer gewünschten Teilenummer migriert werden müssen - exportiert alle Nummern aus der JSON-Datei in eine Excel Datei. Die Excel zeigt, welche Teilenummern bereits migriert wurden und welche noch migriert werden müssen. """ def create_sorter_string(sivasData, sivasId): # Funktion setzt rekursiv einen String zusammen, der anzeigt, wo sich eine Teilenummer in der Hierarchie der obersten, zu migrierenden Baugruppe befindet. # Beispiel: # Die Teilenummer "123456" ist das Halbzeug der Teilenummer "234567". "234567" ist wiederum in der Baugruppe "345678" eingebaut. # Die Funktion gibt dann folgenden Sortier-String zurück: # 345678-234567-123456 parent = sivasData[sivasId]['Parent'] if parent not in sivasData: return f"{parent}-{sivasId}" recurval = create_sorter_string(sivasData, parent) return f"{recurval}-{sivasId}" def create_excel_export(existingIds, sivasData, sivasId, outPath): # 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 # Bestandsabfrage: IMPORTIEREN oder VERFÜGBAR -> "IMPORTIEREN" = Teilenummer muss noch migriert werden, "VERFÜGBAR" = Teilenummer wurde bereits migriert # Restliche Spalten bleiben leer, da sie erst in Excel befüllt werden sortierung = [] teilenummer = [] bezeichnung = [] migrationsstatus = [] teilebeurteilung = [] zusatzbemerkung = [] for item in sivasData: sortierung.append(create_sorter_string(sivasData, item)) teilenummer.append(item) bezeichnung.append(sivasData[item]['Bezeichnung']) existing = "VERFÜGBAR" if existingIds[item] is True else False migrationsstatus.append(existing) teilebeurteilung.append('') zusatzbemerkung.append('') data = { "Sortierung": sortierung, "Teilenummer": teilenummer, "Bezeichnung": bezeichnung, "Migrationsstatus": migrationsstatus, "Teile Beurteilung": teilebeurteilung, "Zusatzbemerkung": zusatzbemerkung } excel_filename = os.path.join(outPath, sivasId + ".xlsx") df = pd.DataFrame.from_dict(data) df.to_excel(excel_filename, sheet_name="Ergebnis", index_label="Sortierung") def read_sivas_nums(jsonPath): with open(jsonPath, encoding='utf-8') as json_file: return json.load(json_file) def read_ruledesigner_nums(rdDataBasePath): df = pd.read_csv(rdDataBasePath, sep=';', converters={"Teilenummer":str}) return tuple(df["Teilenummer"]) def compareFiles(rdDataBasePath, jsonPath, sivasId, outPath): # Liest: # - die CSV-Datei ein, in der alle RuleDesigner-Nummern aufgelistet sind # - die JSON-Datei ein, in der alle Sivas-Teilenummern, die i.V.m einer Teilenummer ebenfalls migriert werden müssen ruledesigner_ids = read_ruledesigner_nums(rdDataBasePath) sivas_ids = read_sivas_nums(jsonPath) # Prüft, ob die Sivas-Nummern bereits migriert wurden existingIds = {} for sivas_id in sivas_ids: existingIds[sivas_id] = True if sivas_id in ruledesigner_ids else False # Erstellt das work-Verzeichnis, falls dieses noch nicht exisitert if not os.path.isdir(outPath): print(f"Fehlendes work-Verzeichnis unter '{outPath}' erstellt") os.mkdir(outPath) # Schreibt eine Excel-Datei mit den korrekten Spalten raus create_excel_export(existingIds, sivas_ids, sivasId, outPath) def compare_treeview(json_path, ruledesigner_path): ruledesigner_nums = read_ruledesigner_nums(ruledesigner_path) sivas_nums = read_sivas_nums(json_path) # Verbinder-Symbole symbols = { "pipe": "│ ", "empty": " ", "elbow": "└──", "t": "├──" } # ANSI-Escape-Sequenzen für Einfärbung des Terminals abhängig vom Migrationsstatus. # Codes, siehe https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 # BG = Hintergrundfarbe colors = { "BG_RED": "\033[41m", "BG_GREEN": "\033[42m", "RESET": "\033[0m" } prefix = "" # Erstellt dict mit allen Parents (Keys) und weist den Parents deren Kinder als Werte zu. # Wird später benötigt, um zu bestimmen, ob eine Teilenummer das letzte Kind eines Parents ist d.h. wenn ein Parent z.B. 4 Kinder # besitzt, ob es sich um das letzte, also 4. Kind handelt. In diesem Fall darf unter der Nummer kein Pipe-Symbol erscheinen parents_children = {} for sivas_num in sivas_nums: parent = sivas_nums[sivas_num]["Parent"] if parent not in parents_children: parents_children[parent] = [] parents_children[parent].append(sivas_num) max_tree_depth = max(i["Position"].count(".") for i in sivas_nums.values()) # Längste Sivas-Teilenummer (weil evtl. >9, wenn mit Underline) max_sivas_num_len = max([len(i) for i in sivas_nums.keys()]) max_description_len = max([len(i["Bezeichnung"]) for i in sivas_nums.values()]) # Erstellung des Treeviews mit Spalte "Bezeichnung" for i, sivas_num in enumerate(sivas_nums): # Für Verarbeitung der ANSI-Escape-Sequenzen, # siehe https://stackoverflow.com/questions/12492810/python-how-can-i-make-the-ansi-escape-codes-to-work-also-in-windows os.system("color") parent = sivas_nums[sivas_num]["Parent"] description = sivas_nums[sivas_num]["Bezeichnung"] position = sivas_nums[sivas_num]["Position"] # Wird später für Zusammensetzung des Präfix benötigt if (i + 1) != len(sivas_nums): next_key = list(sivas_nums)[i + 1] next_key_pos = sivas_nums[next_key]["Position"] # Ermittelt das Level des aktuellen und nächsten Keys bezogen auf die Nummern-Hierarchie current_key_level = position.count(".") next_key_level = next_key_pos.count(".") # └── als Verbinder, wenn letzter Knotenpunkt, sonst ├── connector_symbol = symbols['elbow'] if sivas_num == parents_children[parent][-1] else symbols['t'] # Farbliche Terminal-Darstellung abhängig vom Migrationsstatus: grün = migriert, rot = noch nicht migriert output_color = f"{colors['BG_GREEN']}" if sivas_num in ruledesigner_nums else f"{colors['BG_RED']}" # Bei oberster Teilenummer soll kein Verbinder dargestellt werden. if i == 0: print(f"{output_color}{parent}{colors['RESET']}") tree_str = f"{prefix}{connector_symbol}{output_color}{sivas_num}{colors['RESET']}" len_tree_str = len(tree_str) - len(output_color) - len(colors['RESET']) # Anzahl der Leerzeichen zwischen dem Tree und der Bezeichnungsspalte pad_tree = ((max_tree_depth * len(symbols['pipe']) + len(symbols['t']) + max_sivas_num_len) - len_tree_str + 2) * " " # Anzahl der Leerzeichen zwischen dem Tree und der Kommentarspalte pad_description = (max_description_len - len(description) + 2) * " " print(f"{tree_str}{pad_tree}{description}{pad_description}") # Nachfolgend wird der Präfix vor der Nummer für den NÄCHSTEN Durchlauf zusammengesetzt # Der Präfix-Teil setzt sich fallabhängig aus Pipe-Symbolen (| ) und/oder Leerraum ( ) zusammen. # Prüft, ob die aktuelle Nummer das letzte Kind des Parents ist if sivas_num == parents_children[parent][-1]: # Prüft, ob das aktuelle Teil auch ein Parent ist. # Wenn ja, muss unter dem connector-Symbol der aktuellen Nummer (also im nächsten Durchlauf) Leerraum sein if sivas_num in parents_children: prefix += symbols["empty"] # Baumende auf der aktuellen Ebene ist erreicht. Die nächste Teilenummer (wenn nicht das Gesamtende des Baums erreicht ist) # liegt unter der aktuellen Ebene. Der bisherige Präfix-String muss folglich gekürzt werden. Die Anzahl der Zeichen, # die gekürzt werden müssen 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 haben eine Länge von 3) else: prefix_trim_len = (next_key_level - current_key_level) * len(symbols["pipe"]) prefix = prefix[:prefix_trim_len] else: # 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 if sivas_num in parents_children: prefix += symbols["pipe"] if __name__ == '__main__': parser = argparse.ArgumentParser(description='fetches data from sivas or ruledesigner fusion', prog='update_database') parser.add_argument('-n', '--number', action='store', help='SIVAS-Number') parser.add_argument('-s', '--sivas', action='store', help='fetch data of this id from sivas fo local database folder') parser.add_argument('-d', '--database', action='store', default="RD_Bestandspool.csv", help='local filename of the latest fetched RuleDesigner Database') parser.add_argument('-t', '--tree', action='store_true', help='displays partnumber hierarchy for given part number in flag -s as treeview. green = migrated, red = not yet migrated') args = parser.parse_args() # fetching the names of all existing cad files from the local file dataDir = os.environ.get('CREMIG_DATA') rdDataBasePath = os.path.join(dataDir, args.database) # get the path to ask for further json files exePath = os.environ.get('SIVAS_DATABASE_QUERY') # in case the infos for this cad file are already there sivasId = str(args.sivas) jsonPath = os.path.join(dataDir, sivasId + ".json") # there the results are written outPath = os.environ.get('CREMIG_WORK') sivas_id = str(args.number) json_path = os.path.join(dataDir, sivas_id + ".json") if args.sivas: compareFiles(rdDataBasePath, jsonPath, sivasId, outPath) if args.tree: compare_treeview(json_path, rdDataBasePath)