diff --git a/lib/manage_configs.py b/lib/manage_configs.py index 82f8839..d7d821c 100644 --- a/lib/manage_configs.py +++ b/lib/manage_configs.py @@ -99,9 +99,19 @@ class RuleDesignerSQLData(SQLBasedConfig): class SivasBOM(JsonBasedConfig): """Auslesen der Assembly-Informationen zu einer angegebenen Baugruppennummer""" - def __init__(self, config_dir: str, config_file: str, load_local_json=False): + def __init__(self, + asm_id, + config_dir: str, + config_file: str, + load_local_json=False, + excluded_ids: list=None): + super().__init__(config_dir, config_file) + + self.main_id = asm_id self._load_local_json = load_local_json + self.excluded_ids_by_user = excluded_ids + self._excluded_ids_in_bom = None self._bom_data = None self._child_ids = None self._parent_children = None @@ -112,7 +122,8 @@ class SivasBOM(JsonBasedConfig): def create_asm_json(self) -> bool: """Erstellt JSON mit den aktuellen Baugruppeninformationen""" - update_database.get_sivas_bom(self._config_file.removesuffix(".json")) + if not self._load_local_json: + update_database.get_sivas_bom(self._config_file.removesuffix(".json")) @property @@ -135,16 +146,19 @@ class SivasBOM(JsonBasedConfig): self._bom_data = {} - if not self._load_local_json: - try: - self.create_asm_json() - except ValueError: - pass + try: + self.create_asm_json() + except ValueError: + pass + try: self._bom_data = self.load_json_from_file() except FileNotFoundError: pass + if self.excluded_ids_by_user is not None: + self._remove_excluded_ids_from_bom() + return self._bom_data @property @@ -201,13 +215,81 @@ class SivasBOM(JsonBasedConfig): self._bom_data[id].append(metadata) return self._bom_data + def _remove_excluded_ids_from_bom(self) -> None: + """Entfernt ID's aus den Stücklistendaten, die nicht enthalten sein sollen""" + + # Ausgeschlossene Teilenummern, die vom User angegeben wurden, können nicht einfach aus der Stückliste entfernt werden. + # Sie müssen zunächst aufbereiten werden, denn: + # - Ausgeschlossene Nummern, die kein Bestandteil der aktuellen Baugruppe sind, sind nicht von Interesse + # - Kinder von ausgeschlossenen Teilenummern, die vom User nicht angegeben wurden sind ggf. von Interesse, müssen aber zunächst + # ermittelt werden + + ids_to_remove = self.excluded_ids_in_bom + + if not ids_to_remove: + return + + if self.main_id in ids_to_remove: + raise ValueError(f"Haupt-Teilenummer '{self.main_id}' darf nicht ausgeschlossen sein. Bitte $CREMIG_CFG/BOM-Settings.cfg -> Sektion 'ExcludedPartnumbersInBOM' prüfen, falls diese Datei zur Definition der ausgeschlossenen Teilenummern verwendet wurde") + + filtered_bom_data = {} + for id, usages in self.bom_data.items(): + + if id in ids_to_remove: + continue + + for properties in usages: + + if properties["Parent"] in ids_to_remove: + continue + if id not in filtered_bom_data: + filtered_bom_data[id] = [] + + filtered_bom_data[id].append(properties) + + self._bom_data = filtered_bom_data + + @property + def excluded_ids_in_bom(self) -> list: + + """ Gibt alle Teilenummern zurück, die in der SIVAS-Stückliste vorliegen, aber davon ausgeschlossen sein sollen. """ + + if self._excluded_ids_in_bom is None: + + excluded_ids_by_user = self.excluded_ids_by_user + + to_remove = {} + for id, usages in self.bom_data.items(): + + # Teilenummern zunächst als ausgeschlossen anzusehen und das später zu widerrufen erleichtert die Definition der Logik + to_remove[id] = True + + # Teilenummern, die vom User ausgeschlossen wurden, aber kein Bestandteil dieser Baugruppe sind, interessieren uns nicht! + if id in excluded_ids_by_user: + continue + + # Kinder von ausgeschlossenen Teilenummern werden ebenfalls als ausgeschlossen betrachtet, außer sie sind Kinder von + # weiteren Teilenummern, die nicht augeschlossen sein sollen + # Hinweis: + # Für die Prüfung, ob das Kind in weiteren Teilenummern verbaut ist, würde sich die Methode "parent_children" anbieten. + # Die ist aber Lazy, sprich, wenn wir die jetzt aufrufen, arbeitet sie mit den Stücklistendaten, vor dem die Teilenummern + # entfernt wurden. Die Laziness dafür zu entfernen macht aber auch keinen Sinn, weil diese Methode bei der Erstellung von + # baumförmigen Migrationsübersichten ständig aufgerufen wird, d.h. die Performance würde darunter stark leiden! + + for properties in usages: + if properties["Parent"] not in excluded_ids_by_user: + to_remove[id] = False + + self._excluded_ids_in_bom = list(filter(lambda id: to_remove[id] is True, to_remove)) + return self._excluded_ids_in_bom + @property def longest_child_id(self) -> str: """Gibt die längste Teilenummer von allen Stücklistenpositionen zurück""" if self._longest_child_id is None: self._longest_child_id = max(self.child_ids) - return max(self.child_ids) + return self._longest_child_id @property def max_bom_depth(self) -> int: @@ -225,13 +307,13 @@ class SivasBOM(JsonBasedConfig): def get_prop_val_for_single_id(self, id:str, property_name:str) -> str: """Gibt eine Eigenschaft von einer einzelnen Teilenummer aus der Stückliste zurück""" - if id not in self._bom_data: + if id not in self.bom_data: raise ValueError("Teilenummer befindet sich nicht in der Baugruppe!") - if property_name not in self._bom_data[id][0]: + if property_name not in self.bom_data[id][0]: return - return self._bom_data[id][0][property_name] + return self.bom_data[id][0][property_name] def get_prop_val_for_all_ids(self, property_name) -> dict: """Gibt eine Eigenschaft zu allen Teilenummern der Stückliste zurück (z.B. Bezeichnung)""" @@ -345,21 +427,18 @@ class SivasTeilestamm(CSVBasedConfig): self._load_local_csv = load_local_csv self._data = None - @staticmethod - def create_csvs(ids): + def create_csvs(self, ids): """Erstellt CSV-Datei(en) mit den Teilestamminformationen zu der / den angegebenen Teilenummer(n)""" - update_database.get_sivas_teilestamm(ids) + if not self._load_local_csv: + update_database.get_sivas_teilestamm(ids) @property def teilestamm_data(self): """Gibt CSV-Daten als Dataframe zurück""" if self._data is None: - - if not self._load_local_csv: - self.create_csvs(self._config_file.removesuffix(".csv")) - + self.create_csvs(self._config_file.removesuffix(".csv")) self._data = self.load_csv_from_file() return self._data @@ -687,6 +766,24 @@ class GuiConfig(IniBasedConfig): config_file=spezific_file super().__init__(config_dir, config_file=config_file) +class BOMConfig(IniBasedConfig): + def __init__(self, config_dir=os.environ.get("CREMIG_CFG"), config_file="BOM-Settings.cfg"): + self._config_dir = config_dir + self.config_file = config_file + super().__init__(config_dir, config_file) + + @property + def config_dir(self): + return self._config_dir + + @config_dir.setter + def config_dir(self, directory): + if not os.path.isdir(directory): + raise ValueError("Verzeichnis existiert nicht!") + self._config_dir = directory + + def get_excluded_ids(self, section_name:str="ExcludedPartnumbersInBOM") -> tuple: + return tuple(self.get_section_options(section_name)) class Configs: """Sammlung aller Configdateien """ @@ -707,6 +804,7 @@ class Configs: self.TOSGruppen = TOSGroupsConfig(cfg_dir) self.Materialien = MaterialTransConfig(cfg_dir) + self.bom_config = BOMConfig(cfg_dir) self._teilenummern = None self._blacklist = None