234 lines
10 KiB
Python
234 lines
10 KiB
Python
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)
|