From beb1fdc0da6891a8819cb70ae70b9cfef7dbd4c4 Mon Sep 17 00:00:00 2001 From: Simon Steuer Date: Sun, 9 Mar 2025 00:16:27 +0100 Subject: [PATCH] =?UTF-8?q?Klasse=20MigrationsStatus=20entfernt=20und=20st?= =?UTF-8?q?attdessen=20auf=20gleichnamige=20Klasse=20in=20manage=5Fconfigs?= =?UTF-8?q?.py=20referenziert,=20um=20Redundanzen=20zu=20vermeiden.=20Zude?= =?UTF-8?q?m=20Flags=20zur=20Definierung=20des=20JSON-Verzeichnises,=20des?= =?UTF-8?q?=20Namens=20und=20Verzeichnisses=20f=C3=BCr=20die=20CSV=20mit?= =?UTF-8?q?=20den=20RuleDesigner-Teilenummern=20erg=C3=A4nzt.=20Au=C3=9Fer?= =?UTF-8?q?dem=20Flags=20vorgesehen,=20mit=20denen=20gesteuert=20werden=20?= =?UTF-8?q?kann,=20ob=20die=20die=20lokalen=20JSON-Dateien=20sowie=20die?= =?UTF-8?q?=20lokale=20CSV=20der=20RuleDesigner-Teilenummern=20verwendet?= =?UTF-8?q?=20werden=20soll.=20Defaultm=C3=A4=C3=9Fig=20sollen=20bei=20jed?= =?UTF-8?q?em=20Programmlauf=20nun=20immer=20die=20neusten=20Daten=20vom?= =?UTF-8?q?=20Server=20geholt=20werden,=20bzw.=20immer=20eine=20JSON=20gen?= =?UTF-8?q?eriert=20werden,=20da=20bisher=20sonst=20nicht=20immer=20die=20?= =?UTF-8?q?Aktualit=C3=A4t=20der=20dargestellten=20Daten=20sichergestellt?= =?UTF-8?q?=20war.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/compare_lists.py | 217 +++++++++++-------------------------------- 1 file changed, 52 insertions(+), 165 deletions(-) diff --git a/lib/compare_lists.py b/lib/compare_lists.py index 8286dde..c21dae0 100644 --- a/lib/compare_lists.py +++ b/lib/compare_lists.py @@ -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,59 +142,17 @@ 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() @@ -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() \ No newline at end of file