Klasse MigrationsStatus entfernt und stattdessen auf gleichnamige Klasse in manage_configs.py referenziert, um Redundanzen zu vermeiden. Zudem Flags zur Definierung des JSON-Verzeichnises, des Namens und Verzeichnisses für die CSV mit den RuleDesigner-Teilenummern ergänzt. Außerdem Flags vorgesehen, mit denen gesteuert werden kann, ob die die lokalen JSON-Dateien sowie die lokale CSV der RuleDesigner-Teilenummern verwendet werden soll. Defaultmäßig sollen bei jedem Programmlauf nun immer die neusten Daten vom Server geholt werden, bzw. immer eine JSON generiert werden, da bisher sonst nicht immer die Aktualität der dargestellten Daten sichergestellt war.

This commit is contained in:
2025-03-09 00:16:27 +01:00
parent da4eed34c3
commit beb1fdc0da
+52 -165
View File
@@ -1,8 +1,5 @@
from manage_configs import SivasAssemblyData, RuleDesignerDatabaseConfig
from manage_configs import SivasAssemblyData, RuleDesignerDatabaseConfig, MigrationStatus
import warnings
#Wegen Pandas Deprecation eingebaut
warnings.filterwarnings("ignore", category=DeprecationWarning)
import argparse
import os
import openpyxl
@@ -14,92 +11,20 @@ Dieses Programm kann:
- die oben beschriebene Darstellung in einer Excel-Datei auch als Treeview auf der Kommandozeile darstellen
"""
class MigrationStatus:
def __init__(self, asm_child_ids: tuple, rd_ids: tuple):
self.asm_child_ids = asm_child_ids
self.rd_ids = rd_ids
self._overview = None
self._green_ids = None
self._yellow_ids = None
self._red_ids = None
@property
def overview(self) -> dict:
"""Returns dict with children of the given assembly and their migration status (True = migration complete, False = migration pending)"""
if self._overview is None:
self._overview = {}
for child_id in self.asm_child_ids:
self._overview[child_id] = True if child_id in self.rd_ids else False
return self._overview
@property
def green_ids(self) -> list:
"""Returns migrated child-ids of given assembly"""
if self._green_ids is None:
self._green_ids = [id for id, status in self.overview.items() if status is True]
return self._green_ids
@property
def red_ids(self) -> list:
"""Returns unmigrated child-ids of given assembly"""
if self._red_ids is None:
self._red_ids = [id for id, status in self.overview.items() if status is False]
return self._red_ids
@property
def yellow_ids(self) -> list:
"""Returns migrated child-ids of given assembly, which prevent the asm from being released, because they are currently being modified"""
pass
class ExcelExport:
def __init__(self, asm_id, asm_json_dir, asm_json_filename, rd_dbase_dir, rd_dbase_filename):
def __init__(self, asm_id, asm_json_dir, asm_json_filename, rd_dbase_dir, rd_dbase_filename, load_local_asm_json, load_local_rd_dbase):
self.asm_id = asm_id
self._asm_data = SivasAssemblyData(asm_json_dir, asm_json_filename)
self._rd_dbase = RuleDesignerDatabaseConfig(rd_dbase_dir, rd_dbase_filename)
self._asm_child_ids = None
self._rd_ids = None
self.asm_data = SivasAssemblyData(asm_json_dir, asm_json_filename, load_local_asm_json)
self.rd_data = RuleDesignerDatabaseConfig(rd_dbase_dir, rd_dbase_filename, load_local_rd_dbase)
self._migration_status = None
self._parent_children = None
self._id_descriptions = None
@property
def rd_ids(self):
if self._rd_ids is None:
self._rd_ids = self._rd_dbase.get_ruledesigner_ids()
return self._rd_ids
@property
def migration_status(self):
if self._migration_status is None:
self._migration_status = MigrationStatus(self.asm_child_ids, self.rd_ids)
self._migration_status = MigrationStatus(self.asm_data.child_ids, self.rd_data.ids)
return self._migration_status
@property
def parent_children(self) -> dict:
if self._parent_children is None:
self._parent_children = self._asm_data.get_parent_children()
return self._parent_children
@property
def id_descriptions(self):
if self._id_descriptions is None:
self._id_descriptions = self._asm_data.get_bom_property_values("Bezeichnung")
return self._id_descriptions
@property
def asm_child_ids(self):
if self._asm_child_ids is None:
self._asm_child_ids = self._asm_data.get_assembly_ids()
return self._asm_child_ids
def write_excel(self):
data = self._create_excel_data()
@@ -110,19 +35,19 @@ class ExcelExport:
workbook = openpyxl.load_workbook(workbook_path, keep_vba=True)
worksheet = workbook.active
# Headers
# Spaltenüberschriften
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
# Values below Headers
# Werte unter Spaltenüberschriften
for value, column in zip(data.values(), worksheet.iter_cols(min_row=2, max_col=len(data), max_row=len(data["Sortierung"])+1)):
for cell, element in zip(column, value):
cell.value = element
# Renaming the sheet is necessary, because that triggers the execution of the VBA-Macro
# The renaming must be performed after inserting the data into the spreadsheet, because the conditional formatting, which is part of the vba-macro, can't be performed
# when no data is in the spreadsheet
# Umbenennung des Blattes erforderlich, da die Umbenennung die Ausführung des VBA-Makros triggert.
# Die Umbenennung muss NACH dem Einfügen der Daten in das Blatt erfolgen, da die bedingte Formatierung,
# die ebenfalls Bestandteil des Makros ist, nicht ausgeführt werden kann, solange sich keine Daten im Blatt befinden
worksheet.title = "Ergebnis"
workbook.save(out_path)
@@ -130,32 +55,32 @@ class ExcelExport:
def _create_excel_data(self) -> dict:
# Columns in excel-spreadsheet: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung
# Sortierung: Minus-separated hierarchy of the partnumber in the BOM structure
# Teilenummer: SIVAS-partnumber
# Bezeichnung: SIVAS-description of the partnumber
# Migrationsstatus: "IMPORTIEREN" = partnumber hasn't been migrated yet, "VERFÜGBAR" = partnumber has been migrated
# All the other columns remain empty, because their values are entered in the excel spreadsheet
# Spalten im Arbeitsblatt: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung
# Sortierung: Minus-separierte Hierarchie der Teilenummern in der Stücklistenstruktur
# Teilenummer: SIVAS-Teilenummer
# Bezeichnung: SIVAS-Bezeichnung der Teilenummer
# Migrationsstatus: "IMPORTIEREN" = Migration ausstehend, "VERFÜGBAR" = Migration abgeschlossen
# Alle weiteren Spalten bleiben leer, da sie händisch vom User zu befüllen sind
sorting = self._create_sorting_string()
migrated = self.migration_status.green_ids
descriptions = self.id_descriptions
migrated_ids = self.migration_status.green_ids
descriptions = self.asm_data.get_bom_property_values("Bezeichnung")
prt_num = []
description = []
migration_status = []
evaluation = []
assessment = []
comment = []
for partnumber_string in sorting:
partnumber = partnumber_string.split("-")[-1]
prt_num.append(partnumber)
if partnumber not in descriptions:
for id_str in sorting:
id = id_str.split("-")[-1]
prt_num.append(id)
if id not in descriptions:
description.append("")
else:
description.append(descriptions[partnumber])
migration_status.append("VERFÜGBAR" if partnumber in migrated else "IMPORTIEREN")
evaluation.append('')
description.append(descriptions[id])
migration_status.append("VERFÜGBAR" if id in migrated_ids else "IMPORTIEREN")
assessment.append('')
comment.append('')
data = {
@@ -163,7 +88,7 @@ class ExcelExport:
"Teilenummer": prt_num,
"Bezeichnung": description,
"Migrationsstatus": migration_status,
"Beurteilung": evaluation,
"Beurteilung": assessment,
"Bemerkung": comment
}
return data
@@ -186,13 +111,14 @@ class ExcelExport:
if sorting_strings is None:
sorting_strings = [parent]
children = self.parent_children[parent]
parent_children = self.asm_data.parent_children
children = parent_children[parent]
for child in children:
sorting_strings.append(f"{previous_parents}-{parent}-{child}".lstrip("-"))
if child in self.parent_children:
if child in parent_children:
self._create_sorting_string(parent=child, previous_parents=previous_parents + f"-{parent}", sorting_strings=sorting_strings)
return sorting_strings
@@ -216,60 +142,18 @@ class Treeview:
}
def __init__(self, asm_id, asm_json_dir, asm_json_filename, rd_dbase_dir, rd_dbase_filename):
def __init__(self, asm_id, asm_json_dir, asm_json_filename, rd_dbase_dir, rd_dbase_filename, load_local_asm_json, load_local_rd_dbase):
self.asm_id = asm_id
self._asm_data = SivasAssemblyData(asm_json_dir, asm_json_filename)
self._rd_dbase = RuleDesignerDatabaseConfig(rd_dbase_dir, rd_dbase_filename)
self._asm_child_ids = None
self._rd_ids = None
self.asm_data = SivasAssemblyData(asm_json_dir, asm_json_filename, load_local_asm_json)
self.rd_data = RuleDesignerDatabaseConfig(rd_dbase_dir, rd_dbase_filename, load_local_rd_dbase)
self._migration_status = None
self._parent_children = None
self._id_descriptions = None
self._max_tree_depth = None
self._longest_partnumber = None
@property
def asm_child_ids(self):
if self._asm_child_ids is None:
self._asm_child_ids = self._asm_data.get_assembly_ids()
return self._asm_child_ids
@property
def rd_ids(self):
if self._rd_ids is None:
self._rd_ids = self._rd_dbase.get_ruledesigner_ids()
return self._rd_ids
@property
def migration_status(self):
if self._migration_status is None:
self._migration_status = MigrationStatus(self.asm_child_ids, self.rd_ids)
self._migration_status = MigrationStatus(self.asm_data.child_ids, self.rd_data.ids)
return self._migration_status
@property
def parent_children(self) -> dict:
if self._parent_children is None:
self._parent_children = self._asm_data.get_parent_children()
return self._parent_children
@property
def id_descriptions(self):
if self._id_descriptions is None:
self._id_descriptions = self._asm_data.get_bom_property_values("Bezeichnung")
return self._id_descriptions
@property
def max_tree_depth(self):
if self._max_tree_depth is None:
self._max_tree_depth = self._asm_data.get_max_bom_depth()
return self._max_tree_depth
@property
def longest_partnumber(self):
if self._longest_partnumber is None:
self._longest_partnumber = self._asm_data.get_longest_assembly_id()
return self._longest_partnumber
def generate_tree(self):
self._tree_header()
self._tree_body()
@@ -289,7 +173,8 @@ class Treeview:
if parent_id is None:
parent_id = self.asm_id
children_ids = self.parent_children[parent_id]
parent_children = self.asm_data.parent_children
children_ids = parent_children[parent_id]
for index, child_id in enumerate(children_ids):
@@ -300,7 +185,7 @@ class Treeview:
self._print_entry(child_id, prefix, connector_symbol, partnumber_background_color)
if child_id in self._parent_children:
if child_id in parent_children:
next_prefix = self._prefix(prefix, is_last_assembly_child)
self._tree_body(parent_id=child_id, prefix=next_prefix)
@@ -309,7 +194,7 @@ class Treeview:
amount_migrated_ids = len(self.migration_status.green_ids)
amount_unmigrated_ids = len(self.migration_status.red_ids)
progress = int(amount_migrated_ids / len(self.asm_child_ids) * 100)
progress = int(amount_migrated_ids / len(self.asm_data.child_ids) * 100)
column_width = 5
border_padding = "".ljust(column_width, "")
@@ -331,14 +216,14 @@ class Treeview:
"""Prints tree entry"""
# Necessary for calculating the padding-width after the partnumber
longest_entry = self.max_tree_depth * len(Treeview.prefix_types["PIPE"]) \
longest_entry = self.asm_data.max_bom_depth * len(Treeview.prefix_types["PIPE"]) \
+ len(connector) \
+ len(self.longest_partnumber) \
+ len(self.asm_data.longest_child_id) \
+ len(Treeview.console_colors["RESET"]) \
+ len(Treeview.console_colors["RED"]) # other color codes (ansi escape sequences) could have been used instead since they all have the same length
text_before_description = f"{prefix}{connector}{color}{child_id}{Treeview.console_colors['RESET']}".ljust(longest_entry)
description = self.id_descriptions[child_id]
description = self.asm_data.get_bom_property_values("Bezeichnung")[child_id]
print(f"{text_before_description} {description}")
@@ -371,17 +256,19 @@ class Treeview:
- Migration completed: green
- Migration pending: red"""
return Treeview.console_colors["GREEN"] if self.migration_status.overview[partnumber] is True else Treeview.console_colors["RED"]
return Treeview.console_colors["GREEN"] if self.migration_status.id_status[partnumber] is True else Treeview.console_colors["RED"]
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Das Programm gibt Auskunft über den aktuellen Migrationsstand', prog='compare_lists')
parser = argparse.ArgumentParser(description='Programm gibt Auskunft über den aktuellen Migrationsstand', prog='Display Migration Status')
parser.add_argument('-s', '--sivasid', 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')
parser.add_argument('-rddir', '--rd_dbase_dir', action='store', default=os.environ.get("CREMIG_DATA"))
parser.add_argument('-rdfn', '--rd_dbase_filename', action='store', default=os.environ.get("RD_DATABASE_NAME"))
parser.add_argument('-asmdir', '--asm_json_dir', action='store', default=os.environ.get("CREMIG_DATA"))
parser.add_argument('-t', '--tree', action='store_true', help='Visualisiert Baugruppenstruktur als Baumstruktur mit farbiger Hervorhebung der Stücklistenpositionen in Abhängigkeit vom Migrationsstatus')
parser.add_argument('-e', '--excel', action='store_true', help='Exportiert Baugruppenstruktur als Excel-Datei mit farbiger Hervorhebung der Stücklistenpositionen, in Abhängigkeit vom Migrationsstatus')
parser.add_argument('-rddir', '--rd_dbase_dir',required=False, action='store', default=os.environ.get("CREMIG_DATA"), help='Legt das Verzeichnis fest, in dem die CSV-Datei liegt, in der die RuleDesigner-Teilenummern aufgelistet sind. Default: Umgebungsvariable %CREMIG_DATA%')
parser.add_argument('-rdfn', '--rd_dbase_filename', required=False, action='store', default=os.environ.get("RD_DATABASE_NAME"), help='Name der CSV-Datei, in der die RuleDesigner-Teilenummern aufgelistet sind. Default: Umgebungsvariable %RD_DATABASE_NAME%')
parser.add_argument('-asmdir', '--asm_json_dir', required=False, action='store', default=os.environ.get("CREMIG_DATA"), help='Legt das Verzeichnis fest, JSON-Datei mit den Baugruppeninformationen liegen soll. Default: Umgebungsvariable %CREMIG_DATA%', metavar='filename')
parser.add_argument('-ljson', '--local_asm_json', required=False, action='store_true', default=False, help='Legt fest, ob die JSON der Baugruppe bei jedem Programmlauf neu geladen wird, oder ob die lokale Datei eingelesen werden soll. Default: Neu laden bei jedem Programmlauf')
parser.add_argument('-lcsv', '--local_rd_csv', required=False, action='store_true', default=False, help='Legt fest, ob die CSV, die die RuleDesigner-Teilenummern enthält (RD-Teilenummer.csv) bei jedem Programmlauf vom Server in das lokale Verzeichnis kopiert werden soll. Default: Kopieren bei jedem Programmlauf')
args = parser.parse_args()
@@ -389,10 +276,10 @@ if __name__ == '__main__':
asm_json_filename = f'{asm_id}.json'
if args.excel:
excel_comparison = ExcelExport(asm_id, args.asm_json_dir, asm_json_filename, args.rd_dbase_dir, args.rd_dbase_filename)
excel_comparison = ExcelExport(asm_id, args.asm_json_dir, asm_json_filename, args.rd_dbase_dir, args.rd_dbase_filename, args.local_asm_json, args.local_rd_csv)
excel_comparison.write_excel()
elif args.tree:
treeview_comparison = Treeview(asm_id, args.asm_json_dir, asm_json_filename, args.rd_dbase_dir, args.rd_dbase_filename)
treeview_comparison = Treeview(asm_id, args.asm_json_dir, asm_json_filename, args.rd_dbase_dir, args.rd_dbase_filename, args.local_asm_json, args.local_rd_csv)
treeview_comparison.generate_tree()
else:
parser.print_help()