Großes Refactoring - Dekorator für lazy-Initialisierung durch die in Python für diese Aufgabe vorgesehenen Property-Dekoratoren ersetzt. Durch diese Dekoratoren können zukünftig auch einfach Setter-Methoden ergänzt werden. Das Holen/Laden der JSON- und CSV-Dateien wurde zudem nun so umgebaut, dass dies nicht mehr in der Klasse 'MigrationsStatus' erfolgt, sondern in den Klassen 'SivasAssemblyData' und 'RuleDesignerDatabaseConfig', da dies thematisch dort auch hingehört. Die bisherige Vorgehensweise war einerseits verwirrend und zweitens ineffizient, weil in der Klasse 'MigrationsStatus' nochmal Objekte der Klassen 'SivasAssemblyData' und 'RuleDesignerDatabaseConfig' initialisiert werden mussten. Durch die Anpassung wird nun zudem die Datei 'get_teilestamm.py' nicht länger benötigt

This commit is contained in:
2025-03-09 00:28:09 +01:00
parent beb1fdc0da
commit 6f188649ff
+147 -103
View File
@@ -1,6 +1,6 @@
import os
import json
from configparser import ConfigParser, RawConfigParser
from configparser import ConfigParser
import pandas as pd
import update_database
@@ -39,153 +39,197 @@ class CSVBasedConfig:
class SivasAssemblyData(JsonBasedConfig):
"""Auslesen der Assembly-Informationen zu einer angegebenen Baugruppennummer"""
def __init__(self, config_dir, config_file):
def __init__(self, config_dir, config_file, load_local_json=False):
super().__init__(config_dir, config_file)
self.assembly_data = None
self._load_local_json = load_local_json
self._asm_data = None
self._child_ids = None
self._parent_children = None
self._longest_child_id = None
self._max_bom_depth = None
self._property_names = None
self._all_bom_properties = None
def _lazy_init_assembly_data(func):
"""Sorgt dafür, dass json-Datei der Baugruppe nur 1x geladen wird"""
def create_asm_json(self):
"""Erstellt JSON mit den aktuellen Baugruppeninformationen"""
try:
update_database.get_sivas_dbase(self._config_path, os.environ.get("CREMIG_DATA"))
except:
raise ValueError("JSON-Datei mit Baugruppeninformationen konnte nicht erstellt werden!")
def wrapper(self, *args, **kwargs):
if self.assembly_data is None:
self.assembly_data = self.load_json_from_file()
return func(self, *args, **kwargs)
return wrapper
@property
def asm_data(self) -> dict:
"""Liest die Baugruppen-JSON ein und gibt deren Informationen zurück"""
if self._asm_data is None:
@_lazy_init_assembly_data
def get_assembly_ids(self):
if not os.path.isfile(self._config_path):
raise FileNotFoundError(f"{self._config_path} existiert nicht. Bitte prüfen, ob die Nummer richtig geschrieben wurde.")
if not self._load_local_json:
self.create_asm_json()
self._asm_data = self.load_json_from_file()
return self._asm_data
@property
def child_ids(self) -> set:
"""Gibt alle Teilenummern der Stückliste zurück (auch Teilenummern aus tieferen Baugruppenebenen)"""
assembly_ids = set()
for child_id, parent_properties in self.assembly_data.items():
assembly_ids.add(child_id)
for parent_property in parent_properties:
parent_id = parent_property["Parent"]
assembly_ids.add(parent_id)
return assembly_ids
if self._child_ids is None:
self._child_ids = set()
for child_id, parent_properties in self.asm_data.items():
self._child_ids.add(child_id)
for parent_property in parent_properties:
parent_id = parent_property["Parent"]
self._child_ids.add(parent_id)
return self._child_ids
@_lazy_init_assembly_data
def get_parent_children(self) -> dict:
@property
def parent_children(self) -> dict:
"""Ordnet Stücklistenpositionen (Kinder) den Baugruppennummern (Eltern) zu. Die Kinder sind entsprechend ihrer Positionsnummern in den Stücklisten sortiert."""
unordered_parent_children = {}
if self._parent_children is None:
unordered_parent_children = {}
for child_id, parent_properties in self.assembly_data.items():
for parent_property in parent_properties:
parent_id = parent_property["Parent"]
child_posnr_in_parent_bom = int(parent_property["Position"].lstrip(".").split("/")[0])
for child_id, parent_properties in self.asm_data.items():
for parent_property in parent_properties:
parent_id = parent_property["Parent"]
child_posnr_in_parent_bom = int(parent_property["Position"].lstrip(".").split("/")[0])
if parent_id not in unordered_parent_children:
unordered_parent_children[parent_id] = {}
if parent_id not in unordered_parent_children:
unordered_parent_children[parent_id] = {}
unordered_parent_children[parent_id][child_posnr_in_parent_bom] = child_id
ordered_parent_children = {}
for parent, children in unordered_parent_children.items():
for pos_nr, child in sorted(children.items()):
if parent not in ordered_parent_children:
ordered_parent_children[parent] = []
ordered_parent_children[parent].append(child)
return ordered_parent_children
unordered_parent_children[parent_id][child_posnr_in_parent_bom] = child_id
ordered_parent_children = {}
for parent, children in unordered_parent_children.items():
for pos_nr, child in sorted(children.items()):
if parent not in ordered_parent_children:
ordered_parent_children[parent] = []
ordered_parent_children[parent].append(child)
self._parent_children = ordered_parent_children
return self._parent_children
def get_longest_assembly_id(self) -> str:
@property
def longest_child_id(self) -> str:
"""Gibt die längste Teilenummer von allen Stücklistenpositionen zurück"""
return max(self.get_assembly_ids())
if self._longest_child_id is None:
self._longest_child_id = max(self.child_ids)
return max(self.child_ids)
@_lazy_init_assembly_data
def get_max_bom_depth(self) -> int:
@property
def max_bom_depth(self) -> int:
"""Gibt die tiefste Stücklistenebene zurück (0 = höchste Ebene)"""
bom_levels = []
for parents in self.assembly_data.values():
for parent in parents:
current_bom_level = parent["Position"].count(".")
bom_levels.append(current_bom_level)
return max(bom_levels)
if self._max_bom_depth is None:
bom_levels = []
for parents in self.asm_data.values():
for parent in parents:
current_bom_level = parent["Position"].count(".")
bom_levels.append(current_bom_level)
self._max_bom_depth = max(bom_levels)
return self._max_bom_depth
@_lazy_init_assembly_data
def get_property_names(self) -> list:
@property
def property_names(self) -> list:
"""Gibt alle Eigenschaftsnamen zurück, die in der Assembly-json-Datei enthalten sind"""
for parent_data in self.assembly_data.values():
return list(parent_data[0])
if self._property_names is None:
for parent_data in self.asm_data.values():
self._property_names = list(parent_data[0])
return self._property_names
@_lazy_init_assembly_data
def get_bom_property_values(self, property) -> dict:
"""Gibt eine Eigenschaft zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)"""
if property not in self.get_property_names():
if property not in self.property_names:
raise ValueError("Angegebener Eigenschaftsname exisitert nicht!")
id_property_mapping = {}
for child, parent_data in self.assembly_data.items():
for child, parent_data in self.asm_data.items():
for properties in parent_data:
if property in properties:
id_property_mapping[child] = properties[property]
return id_property_mapping
@_lazy_init_assembly_data
def get_all_bom_properties(self) -> dict:
@property
def all_bom_properties(self) -> dict:
"""Gibt alle Eigenschaften zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)"""
all_properties = {}
property_names = self.get_property_names()
for property_name in property_names:
all_properties[property_name] = self.get_bom_property_values(property_name)
return all_properties
if self._all_bom_properties is None:
self._all_bom_properties = {}
for property_name in self.property_names:
self._all_bom_properties[property_name] = self.get_bom_property_values(property_name)
return self._all_bom_properties
class RuleDesignerDatabaseConfig(CSVBasedConfig):
"""Auslesen der Angaben aus der RuleDesigner-Datenbank-CSV"""
def __init__(self, config_dir=".", config_file="RD_Teilenummern.csv"):
"""Auslesen der CSV-Datei, in der alle RuleDesigner-Teilenummern aufgelistet sind"""
def __init__(self, config_dir=".", config_file="RD_Teilenummern.csv", load_local_csv=False):
super().__init__(config_dir, config_file)
self._load_local_csv = load_local_csv
self._ids = None
def get_ruledesigner_ids(self):
return tuple(self.load_csv_from_file()['Teilenummern'])
def copy_csv_from_server(self):
"""Kopiert CSV-Datei vom Server, in der alle Teilenummern, die sich in RuleDesigner befinden"""
try:
update_database.get_rd_dbase()
except:
raise ValueError("CSV mit RuleDesigner-Teilenummern konnte nicht ins lokale Verzeichnis kopiert werden!")
@property
def ids(self):
if self._ids is None:
if not os.path.isfile(self._config_path):
raise FileNotFoundError(f"{self._config_path} existiert nicht. Bitte Batchdatei unter 'bin/01_get_rd_dbase.bat' ausführen!")
if not self._load_local_csv:
self.copy_csv_from_server()
self._ids = tuple(self.load_csv_from_file()['Teilenummern'])
return self._ids
class MigrationStatus:
def __init__(self, file_dir, assembly_json):
self._file_dir = file_dir
self._assembly_json = assembly_json
self._uselocal = False
"""Klasse gibt Informationen zum aktuellen Migrationsstand zurück, indem geprüft wird, ob die Nummern aus Sivas, auch in RuleDesigner vorliegen"""
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._red_ids = None
def use_local(self):
self._uselocal = True
@property
def id_status(self) -> dict:
"""Gibt zurück, ob die Stücklistenpositionen der Baugruppe migriert sind, oder nicht"""
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
def get_migration_status(self) -> dict:
"""Gibt zurück, ob die Teilenummern der Baugruppe bereits migriert wurden, oder nicht"""
out_dir = self._file_dir
if self._uselocal == False:
ok = update_database.get_sivas_dbase(self._assembly_json, out_dir)
if not ok:
raise Exception("no json file could be generated")
update_database.get_rd_dbase()
else:
if not os.path.exists(os.path.join(out_dir, self._assembly_json)):
raise Exception("no local file exists "+ self._assembly_json)
self.sivas_assembly_data = SivasAssemblyData(out_dir, self._assembly_json)
sivas_assembly_ids = self.sivas_assembly_data.get_assembly_ids()
self.ruledesigner_data = RuleDesignerDatabaseConfig(out_dir)
ruledesigner_ids = self.ruledesigner_data.get_ruledesigner_ids()
@property
def green_ids(self) -> list:
"""Gibt alle Stücklistenpositionen zurück, die migriert sind"""
migration_status = {}
for sivas_assembly_id in sivas_assembly_ids:
migration_status[sivas_assembly_id] = True if sivas_assembly_id in ruledesigner_ids else False
return migration_status
if self._green_ids is None:
self._green_ids = [id for id, status in self.id_status.items() if status is True]
return self._green_ids
def get_migrated_ids(self) -> list:
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, die bereits in RuleDesigner migriert wurden"""
@property
def yellow_ids(self) -> list:
"""Gibt migrierte Stücklistenpositionen zurück, die aktuell eine Freigabe der Baugruppe verhindern, da sie sich derzeit in Arbeit befinden"""
pass
migrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is True]
return migrated_ids
@property
def red_ids(self) -> list:
"""Gibt alle Stücklistenpositionen zurück, die noch nicht migriert sind"""
def get_unmigrated_ids(self) -> list:
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, deren Migration in RuleDesigner noch aussteht"""
unmigrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is False]
return unmigrated_ids
if self._red_ids is None:
self._red_ids = [id for id, status in self.id_status.items() if status is False]
return self._red_ids
class TOSGroupsConfig(JsonBasedConfig):
@@ -525,7 +569,7 @@ class Configs:
self.TOSGruppen = TOSGroupsConfig(cfg_dir)
self.Materialien = MaterialTransConfig(cfg_dir)
self._TeileNummern = None
self._teilenummern = None
self._blacklist = None
def get_cfg_path(self):
@@ -539,9 +583,9 @@ class Configs:
def get_ruledesigner_ids(self):
if not self._data_dir:
raise Exception("set_data_dir not called. path not set")
if not self._TeileNummern:
self._TeileNummern = RuleDesignerDatabaseConfig(self._data_dir).get_ruledesigner_ids()
return self._TeileNummern
if not self._teilenummern:
self._teilenummern = RuleDesignerDatabaseConfig(self._data_dir, load_local_csv=True).ids
return self._teilenummern
def get_blacklist(self):
if not self._data_dir: