Compare commits

...

8 Commits

Author SHA1 Message Date
s.steuer 9b3c3859cc get_teilestamm.py wird nicht mehr benötigt, siehe commit 4f740e81ad 2025-03-31 12:53:12 +02:00
s.steuer 22b0cf8afc In Treeview bzw. Excel-Export des Migrationsüberblicks werden Nummern nun blau markiert, wenn diese zwar in RuleDesigner migriert wurden, dort aber nocht keine Freigabe erfolgt ist. Hierfür wird der RuleDesigner-Status der Nummern live aus der RuleDesigner-Datenbank abgerufen. Dadurch ergibt sich ein detaillierterer Überblick über den aktuellen Migrationsstand, da der Großteil der Migrationsarbeit erst nach der Migration ins PDM erfolgt (Aufbereitung der Modelle, Zusammenbau der Baugruppe, etc.). Abgesehen davon wird die Bezeichnung der obersten Baugruppe jetzt ebenfalls im Migrationsüberblick angezeigt, was bisher nicht der Fall war. Hierfür wird eine CSV-Datei des SIVAS-Teilestamms zur Baugruppennummer abgeleitet daraus 'Bezeichnung 1' ausgelesen. 2025-03-29 22:19:59 +01:00
s.steuer e2bfa2aac6 FIX - An erstes Argument (BG-ID) in Methode 'get_sivas_dbase' in 'update_database.py' wurde der komplette Dateipfad zur JSON übergeben, obwohl nur die Teilenummer, bzw. max. die Teilenummer mit Dateiendung übergeben werden darf 2025-03-09 21:40:36 +01:00
s.steuer 5f3615548b Treeview angepasst, damit die Bezeichnung der obersten Baugruppe angezeigt wird, da dies bisher nicht der Fall war. Da die Bezeichnung nicht in der Baugruppen-JSON aufgeführt ist, ist es erforderlich die CSV mit den Teilestamm-Metadaten zur Baugruppennummer herunterzuladen. Damit dies möglich ist, wurde wurde in manage_configs.py eine neue Subclass 'TeilestammMetadata' ergänzt. In compare_lists.py wurden dabei weitere Flags ergänzt, mit denen gesteuert werden kann, ob die Bezeichnung aus der lokalen CSV-Datei gezogen werden sollen, oder ob die CSV mit jedem Programmlauf neu erstellt wird. Defaultmäßig wird sie mit jedem Programmlauf neu erstellt 2025-03-09 21:22:15 +01:00
s.steuer 4f740e81ad Das Einladen der JSON- und RuleDesigner-Daten erfolgt nun allein i.V.m 'manage_configs.py' und nicht mehr i.V.m der Klasse FetchSivasTeilestamm in der Datei 'get_teilestamm.py'. Dies wurde einerseits durchgeführt, um die Anzahl an Referenzen zu verringern, hauptsächlich jedoch, weil in der Klasse 'FetchSivasTeilestamm' die Klasse 'MigrationsStatus' in 'manage_configs.py' aufgerufen wird, in der wiederum Objekte der Klassen 'SivasAssemblyData' und 'RuleDesignerConfig' initiiert werden. Das ist sehr ineffizient, weil 'SivasAssemblyData' bereits in dieser Datei, also 'controller.py' initiiert wird. Es wurde zudem ergänzt, dass nun auch die lokale Version der CSV-Datei, die die RD-Teilenummern enthält geladen werden kann, ohne den aktuellen Stand vom Server zu kopieren. 2025-03-09 00:37:10 +01:00
s.steuer 6f188649ff 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 2025-03-09 00:28:09 +01:00
s.steuer beb1fdc0da 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. 2025-03-09 00:16:27 +01:00
s.steuer da4eed34c3 Klassen 'Partnumbers', 'SivasPartnumbers' und 'RuleDesignerPartnumbers' entfernt und stattdessen mit den Klassen in manage_configs.py verknüpft, um Redundanzen zu vermeiden. Zudem doppeltes Laden der genannten Klassen in der Klasse 'RuleDesignerComparison', die jetzt 'MigrationStatus' heißt, gefixt. Abgesehen davon durch Aufrufen von Methoden in den __init__-Methoden mittels lazy-Initialisierung per property-Dekoratoren behoben. 2025-03-08 18:16:17 +01:00
9 changed files with 521 additions and 474 deletions
-1
View File
@@ -1,5 +1,4 @@
@echo off
CALL 01_get_rd_dbase.bat
CALL manage_interpreter.bat activate_interpreter
python %CREMIG_LIB%\compare_lists.py %*
CALL manage_interpreter.bat deactivate_interpreter
+4
View File
@@ -32,7 +32,11 @@ set SIVAS_TEILESTAMM=\\195.243.223.3\sivas\jit\programme\KSbExcelExportSivasTeil
set SIVAS_EXCEL_EXPORT_DIR=%CREMIG_DATA%
set SIEMENS_DATA_PREP_TOOL=C:\Program Files\Siemens\Solid Edge 2023\Program\DataPrepUtility.exe
set LOG_EDITOR=notepad.exe
set RD_SQL_DATABASE_CREDENTIALS=\\fileserver01\abteilungen\Entwicklung\Administration\Lizenzen\RDSQLDatabaseCredentials.bat
if exist %RD_SQL_DATABASE_CREDENTIALS% (
call %RD_SQL_DATABASE_CREDENTIALS%
)
rem Um den Python-Interpreter auf U:\ zu nutzen, Kommentar (rem) in nachfolgender Zeile entfernen
rem set NETWORK_INTERPRETER_PATH=\\ssg.local\freigaben\Allgemein\Programmierumgebung\Python312
+16
View File
@@ -0,0 +1,16 @@
[Status]
0=VERFÜGBAR
1=IN BEARBEITUNG
2=IN ÜBERARBEITUNG
3=FREIGEGEBEN
4=FREIGEGEBEN
10=VORLÄUFIG
11=PROTOTYP
12=IN PRÜFUNG
13=UNGÜLTIG
101=BAGATELLÄNDERUNG
102=REVISION VERFÜGBAR
103=ZUR PRÜFUNG
104=REVISION IN PRÜFUNG
105=REVISION ZUR PRÜFUNG
106=GESPERRT
+221 -276
View File
@@ -1,271 +1,118 @@
import update_database
import warnings
#Wegen Pandas Deprecation eingebaut
warnings.filterwarnings("ignore", category=DeprecationWarning)
from manage_configs import SivasAssemblyData, RuleDesignerDatabaseConfig, MigrationStatus, TeilestammMetadata
import argparse
import os
import json
import pandas as pd
import openpyxl
"""
Dieses Programm kann:
- eine Excel-Datei exportieren, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer angegebenen Nummer migriert werden müssen.
Sie gibt dabei Auskunft darüber, welche Teilenummern bereits migriert wurden und welche noch migriert werden müssen.
- die oben beschriebene Darstellung in einer Excel-Datei auch als Treeview auf der Kommandozeile darstellen
- die oben Darstellung in der Excel-Datei als Baum in der Konsole ausgeben
"""
class Environment:
def __init__(self):
self.lib_dir = os.environ.get('CREMIG_LIB')
self.data_dir = os.environ.get('CREMIG_DATA')
self.out_dir = os.environ.get('CREMIG_WORK')
self.ruledesigner_csv_filename = os.environ.get('RD_DATABASE_NAME')
self.vba_macro_filename = os.environ.get('VBA_MACRO_NAME')
self.vba_macro_filepath = os.path.join(self.lib_dir, self.vba_macro_filename)
class Partnumbers:
def __init__(self, filename: str, file_dir: str):
self.file_dir = file_dir
self.filename = filename
def load_from_json(self):
"""Reads partnumbers from a json-file"""
config_path = os.path.join(self.file_dir, self.filename + ".json")
try:
with open(config_path, encoding='utf-8') as json_file:
return json.load(json_file)
except FileNotFoundError:
update_database.get_sivas_dbase(self.filename, self.file_dir)
return self.load_from_json()
def load_from_csv(self):
"""Reads partnumbers from a csv-file"""
config_path = os.path.join(self.file_dir, self.filename)
try:
df = pd.read_csv(config_path, sep=';', converters={"Teilenummern":str})
return tuple(df["Teilenummern"])
except FileNotFoundError:
update_database.get_rd_dbase(self.file_dir)
return self.load_from_csv()
class SivasPartnumbers(Partnumbers):
def __init__(self, main_sivas_id: str, file_dir: str):
self.file_dir = file_dir
self.main_sivas_id = main_sivas_id
self._partnumbers = None
super().__init__(file_dir=self.file_dir, filename=self.main_sivas_id)
def lazy_partnumber_init(function):
"""Decorator prevents time-consuming initialization of sivas partnumbers if method doesn't require them"""
def wrapper(self):
if self._partnumbers is None:
self._partnumbers = self.__class__.mro()[1].load_from_json(self) # Using super() isn't possible here
return function(self)
return wrapper
@lazy_partnumber_init
def get_partnumbers(self) -> set:
"""Returns all partnumbers (id's) of the provided json-file"""
ids = set()
for child_id, top_level_assemblies_of_child in self._partnumbers.items():
ids.add(child_id)
for top_level_assembly in top_level_assemblies_of_child:
top_level_assembly_id = top_level_assembly["Parent"]
ids.add(top_level_assembly_id)
return ids
def get_longest_partnumber(self) -> str:
"""Returns the longest partnumber in the provided json-file"""
return max(self.get_partnumbers())
@lazy_partnumber_init
def get_ordered_parent_children(self, no_duplicates: bool = False) -> dict:
"""Replicates the partnumber structure of the given BOM (bill of material) from sivas. The Order of the partnumbers is identical to the position numbers in the BOM"""
unordered_parent_children = {}
for parents in self._partnumbers:
for children in self._partnumbers[parents]:
if children["Parent"] not in unordered_parent_children:
unordered_parent_children[children["Parent"]] = {}
pos = children["Position"].lstrip(".").split("/")[0]
pos_in_list = int(pos)
unordered_parent_children[children["Parent"]][pos_in_list] = parents
# Second dict necessary, because children aren't sorted according to their position numbers in the SIVAS-BOM
ordered_parent_children = {}
for key, values in unordered_parent_children.items():
for subkey, subvalue in sorted(values.items()):
if key not in ordered_parent_children:
ordered_parent_children[key] = []
ordered_parent_children[key].append(subvalue)
return ordered_parent_children
@lazy_partnumber_init
def get_max_bom_depth(self) -> int:
"""Returns the maximum nesting-depth (level) of the provided assembly partnumber as an integer"""
bom_levels = []
for values in self._partnumbers.values():
for value in values:
current_bom_level = value["Position"].count(".")
bom_levels.append(current_bom_level)
return max(bom_levels)
@lazy_partnumber_init
def get_partnumber_descriptions(self) -> dict:
descriptions = {}
for parent, values in self._partnumbers.items():
for value in values:
descriptions[parent] = value["Bezeichnung"]
return descriptions
class RuleDesignerPartnumbers(Partnumbers):
def __init__(self, ruledesigner_ids_dir: str, ruledesigner_ids_filename: str):
self.ruledesigner_ids_dir = ruledesigner_ids_dir
self.ruledesigner_ids_filename = ruledesigner_ids_filename
super().__init__(file_dir=self.ruledesigner_ids_dir, filename=self.ruledesigner_ids_filename)
def get_partnumbers(self):
return super().load_from_csv()
def update_database(self):
return update_database.get_rd_dbase(self.ruledesigner_ids_dir)
class SivasRuleDesignerComparison:
def __init__(self, sivas_ids_dir, sivas_main_id, rd_ids_dir, rd_ids_filename):
self.sivas = SivasPartnumbers(main_sivas_id=sivas_main_id, file_dir=sivas_ids_dir)
self.ruledesigner = RuleDesignerPartnumbers(ruledesigner_ids_dir=rd_ids_dir, ruledesigner_ids_filename=rd_ids_filename)
def get_migration_status(self) -> dict:
"""Checks if given sivas-partnumbers have been migrated been migrated into RuleDesigner"""
sivas_ids = self.sivas.get_partnumbers()
ruledesigner_ids = self.ruledesigner.get_partnumbers()
migration_completed = {}
for sivas_partnumber in sivas_ids:
migration_completed[sivas_partnumber] = True if sivas_partnumber in ruledesigner_ids else False
return migration_completed
class ExcelExport:
def __init__(self, main_id, sivas_ids_file_dir, rd_ids_file_dir, rd_ids_filename):
self.main_id = main_id
self.environment = Environment()
self._sivas = SivasPartnumbers(main_sivas_id=main_id, file_dir=sivas_ids_file_dir)
self._ruledesigner = RuleDesignerPartnumbers(ruledesigner_ids_dir=rd_ids_file_dir, ruledesigner_ids_filename=rd_ids_filename)
self._comparison = SivasRuleDesignerComparison(sivas_ids_dir=sivas_ids_file_dir, sivas_main_id=main_id, rd_ids_dir=rd_ids_file_dir, rd_ids_filename=rd_ids_filename)
self._parent_children = self._sivas.get_ordered_parent_children()
self._ruledesigner_ids = self._ruledesigner.get_partnumbers()
def __init__(self,
asm_id,
asm_json_dir,
asm_json_filename,
asm_csv_dir,
asm_csv_filename,
rd_dbase_dir,
rd_dbase_filename,
load_local_asm_json,
load_local_asm_csv,
load_local_rd_dbase,
no_blue_ids):
self.asm_id = asm_id
self.asm_data = SivasAssemblyData(asm_json_dir, asm_json_filename, load_local_asm_json)
self.asm_metadata = TeilestammMetadata(asm_csv_dir, asm_csv_filename, load_local_asm_csv)
self.rd_data = RuleDesignerDatabaseConfig(rd_dbase_dir, rd_dbase_filename, load_local_rd_dbase)
self._migration_status = None
@property
def migration_status(self):
if self._migration_status is None:
self._migration_status = MigrationStatus(self.asm_data.child_ids, self.rd_data.ids)
return self._migration_status
def write_excel(self):
data = self._create_excel_data()
output_dir = environment.out_dir
out_dir = os.environ.get("CREMIG_WORK")
out_path = os.path.join(out_dir, self.asm_id + ".xlsm")
destiny_filepath = os.path.join(output_dir, self.main_id + ".xlsm")
workbook = openpyxl.load_workbook(self.environment.vba_macro_filepath, keep_vba=True)
workbook_path = os.path.join(os.environ.get("CREMIG_LIB"), os.environ.get("VBA_MACRO_NAME"))
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 it 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 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(destiny_filepath)
print(f"Excel-Datei erfolgreich in '{output_dir}' geschrieben...")
workbook.save(out_path)
print(f"Excel-Datei erfolgreich in '{out_dir}' geschrieben...")
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._comparison.get_migration_status()
descriptions = self._sivas.get_partnumber_descriptions()
migrated_ids = self.migration_status.ids_in_rd
child_descriptions:dict = self.asm_data.get_bom_property_values("Bezeichnung")
parent_description:str = self.asm_metadata.get_property_values("Bezeichnung1")
descriptions = child_descriptions.copy()
descriptions[asm_id] = parent_description
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)
assessment.append('')
comment.append('')
if id not in descriptions:
description.append("")
else:
description.append(descriptions[partnumber])
migration_status.append("VERFÜGBAR" if migrated[partnumber] is True else "IMPORTIEREN")
evaluation.append('')
comment.append('')
description.append(descriptions[id])
if id in migrated_ids:
if id in self.migration_status.ids_in_progress:
migration_status.append("PDM-Migration abgeschlossen")
else:
migration_status.append("Migration abgeschlossen (freigegeben)")
else:
migration_status.append("Migration ausstehend")
data = {
"Sortierung": sorting,
"Teilenummer": prt_num,
"Bezeichnung": description,
"Migrationsstatus": migration_status,
"Beurteilung": evaluation,
"Beurteilung": assessment,
"Bemerkung": comment
}
return data
@@ -284,22 +131,22 @@ class ExcelExport:
"""
if parent is None:
parent = self.main_id
parent = self.asm_id
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
class Treeview:
# For correct display of ANSI escape sequences
@@ -315,20 +162,55 @@ class Treeview:
console_colors = {"RED": "\033[41m",
"GREEN": "\033[42m",
"BLUE": "\033[44m",
"RESET": "\033[0m"
}
def __init__(self, main_partnumber, sivas_src_file, rd_src_dir, rd_src_file):
self.main_partnumber = main_partnumber
self._sivas = SivasPartnumbers(main_sivas_id=main_partnumber, file_dir=sivas_src_file)
self._ruledesigner = RuleDesignerPartnumbers(ruledesigner_ids_dir=rd_src_dir, ruledesigner_ids_filename=rd_src_file)
self._sivas_rd_comparison = SivasRuleDesignerComparison(sivas_main_id=main_partnumber, sivas_ids_dir=sivas_src_file, rd_ids_dir=rd_src_dir, rd_ids_filename=rd_src_file)
self._parent_children = self._sivas.get_ordered_parent_children()
self._descriptions = self._sivas.get_partnumber_descriptions()
self._max_tree_depth = self._sivas.get_max_bom_depth()
self._longest_partnumber = self._sivas.get_longest_partnumber()
self._migration_status = self._sivas_rd_comparison.get_migration_status()
def __init__(self,
asm_id,
asm_json_dir,
asm_json_filename,
asm_csv_dir,
asm_csv_filename,
rd_dbase_dir,
rd_dbase_filename,
load_local_asm_json,
load_local_asm_csv,
load_local_rd_dbase,
no_blue_ids):
self.asm_id = asm_id
self.asm_bom_data = SivasAssemblyData(asm_json_dir, asm_json_filename, load_local_asm_json)
self.asm_metadata = TeilestammMetadata(asm_csv_dir, asm_csv_filename, load_local_asm_csv)
self.rd_data = RuleDesignerDatabaseConfig(rd_dbase_dir, rd_dbase_filename, load_local_rd_dbase)
self.no_blue_ids = no_blue_ids
self._migration_status = None
self._longest_entry = None
@property
def migration_status(self):
if self._migration_status is None:
self._migration_status = MigrationStatus(self.asm_bom_data.child_ids, self.rd_data.ids)
return self._migration_status
@property
def longest_entry(self):
if self._longest_entry is None:
longest_color_code = max(Treeview.console_colors.values(), key=len)
longest_connector_symbol = max(Treeview.connector_symbols.values(), key=len)
padding_after_id = 2
if self._longest_entry is None:
self._longest_entry = (self.asm_bom_data.max_bom_depth) * len(Treeview.prefix_types["PIPE"]) \
+ len(longest_connector_symbol) \
+ len(longest_color_code) \
+ len(self.asm_bom_data.longest_child_id) \
+ len(Treeview.console_colors["RESET"]) \
+ padding_after_id
return self._longest_entry
def generate_tree(self):
self._tree_header()
@@ -339,17 +221,23 @@ class Treeview:
"""Prints root number (number for which the user wants to see the tree) to the console"""
background_color = self._set_text_background_color(self.main_partnumber)
print(f"{background_color}{self.main_partnumber}{Treeview.console_colors['RESET']}")
id = self.asm_id
color = self._set_text_background_color(id)
text_before_description = f"{color}{id}{Treeview.console_colors['RESET']}".ljust(self.longest_entry)
description = self.asm_metadata.get_property_values("Bezeichnung1")
print(f"{text_before_description}{description}")
def _tree_body(self, parent_id=None, prefix=""):
"""Prints the body (all partnumbers except root/main-partnumber) of the tree to the console"""
if parent_id is None:
parent_id = self.main_partnumber
parent_id = self.asm_id
children_ids = self._parent_children[parent_id]
parent_children = self.asm_bom_data.parent_children
children_ids = parent_children[parent_id]
for index, child_id in enumerate(children_ids):
@@ -360,99 +248,156 @@ 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)
def _tree_summary(self):
amount_all_sivas_ids = len(self._migration_status)
amount_migrated_ids = sum(migration_status == True for migration_status in self._migration_status.values())
amount_unmigrated_ids = amount_all_sivas_ids - amount_migrated_ids
amount_in_rd = len(self.migration_status.ids_in_rd)
amount_pending = len(self.migration_status.ids_pending)
progress = int(amount_migrated_ids / amount_all_sivas_ids * 100)
if not self.no_blue_ids:
amount_in_work = len(self.migration_status.ids_in_progress)
amount_released = amount_in_rd - amount_in_work
progress = int(amount_in_rd / len(self.asm_bom_data.child_ids) * 100)
column_width = 5
border_padding = "".ljust(column_width, "")
empty_padding = "".ljust(column_width, " ")
print(f"""
╭─────────────────────────────────────{border_padding}
red_entry = f"{Treeview.console_colors['RED'] } {Treeview.console_colors['RESET']}│ Migration ausstehend │ {str(amount_pending).ljust(column_width)}"
if not self.no_blue_ids:
blue_entry = f"{Treeview.console_colors['BLUE'] } {Treeview.console_colors['RESET']}│ PDM-Migration abgeschlossen │ {str(amount_in_work).ljust(column_width)}\n"
green_entry = f"{Treeview.console_colors['GREEN']} {Treeview.console_colors['RESET']}│ Migration abgeschlossen (Freigegeben) │ {str(amount_released).ljust(column_width)}\n"
else:
blue_entry = ""
green_entry = f"{Treeview.console_colors['GREEN']} {Treeview.console_colors['RESET']}│ Migration abgeschlossen │ {str(amount_in_rd).ljust(column_width)}\n"
red_entry = f"{Treeview.console_colors['RED'] } {Treeview.console_colors['RESET']}│ Migration ausstehend │ {str(amount_pending).ljust(column_width)}"
header = f"""
╭───────────────────────────────────────────{border_padding}
│ Auswertung {empty_padding}
├───────────────────────────────────┬─{border_padding}
│ Anzahl abgeschlossene Migrationen │ {str(amount_migrated_ids).ljust(column_width)}
│ Anzahl fehlende Migrationen │ {str(amount_unmigrated_ids).ljust(column_width)}
├───────────────────────────────────┼─{border_padding}
├─┬───────────────────────────────────────┬─{border_padding}
"""
progress = f"""
├─┴───────────────────────────────────────┼─{border_padding}
│ Migrationsfortschritt │ {f"{progress}%".ljust(column_width)}
╰───────────────────────────────────┴─{border_padding}
""")
╰─────────────────────────────────────────┴─{border_padding}
"""
print(header + green_entry + blue_entry + red_entry + progress)
def _print_entry(self, child_id, prefix, connector, color):
"""Prints tree entry"""
"""Eintrag in die Konsole drucken"""
# Necessary for calculating the padding-width after the partnumber
longest_entry = self._max_tree_depth * len(Treeview.prefix_types["PIPE"]) \
+ len(connector) \
+ len(self._longest_partnumber) \
+ 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._descriptions[child_id]
text_before_description = f"{prefix}{connector}{color}{child_id}{Treeview.console_colors['RESET']}".ljust(self.longest_entry)
description = self.asm_bom_data.get_bom_property_values("Bezeichnung")[child_id]
print(f"{text_before_description}{description}")
def _last_child_check(self, child_index, children):
"""Checks, if current child is the last child of the assembly"""
"""Prüft, ob aktuelle ID das letzte Kind der Baugruppe ist"""
return True if child_index == len(children) - 1 else False
def _set_connector_symbol(self, child_is_last_child):
"""Chooses connector symbol for current tree entry"""
"""Legt Verbindungssymbol für aktuellen Eintrag im Baum fest"""
return Treeview.connector_symbols["ELBOW"] if child_is_last_child else Treeview.connector_symbols["T"]
def _prefix(self, prefix, child_is_last_child):
"""Creates prefix (part before the connector symbol) for each tree entry"""
"""Erstellt für jeden Eintrag im Baum einen Präfix (Teil vor dem Verbindersymbol) fest"""
prefix += Treeview.prefix_types["EMPTY"] if child_is_last_child else Treeview.prefix_types["PIPE"]
return prefix
def _set_text_background_color(self, partnumber):
def _set_text_background_color(self, id):
"""Sets background color of partnumber in console depending on its migration status
- Migration completed: green
- Migration pending: red"""
"""Legt Hintergrundfarbe der Teilenummer basierend auf ihrem Migrationsstatus fest:
return Treeview.console_colors["GREEN"] if self._migration_status[partnumber] is True else Treeview.console_colors["RED"]
- grün: Migration vollständig abgeschlossen und freigegeben
- blau: Migration ins PDM abgeschlossen
- rot: Migration ausstehend"""
if id in self.migration_status.ids_pending:
return Treeview.console_colors["RED"]
if not self.no_blue_ids:
if id in self.migration_status.ids_in_progress:
return Treeview.console_colors["BLUE"]
return Treeview.console_colors["GREEN"]
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('-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('-rd_ids_filedir', 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('-rd_ids_filename', 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('-rd_ids_local', 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')
parser.add_argument('-asm_json_filedir', action='store', default=os.environ.get("CREMIG_DATA"), help='Legt das Verzeichnis fest, in dem die JSON-Datei mit den Baugruppeninformationen liegt. Default: Umgebungsvariable %CREMIG_DATA%', metavar='directory')
parser.add_argument('-asm_json_local', 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('-asm_csv_filedir', action='store', default=os.environ.get("CREMIG_DATA"), help='Legt das Verzeichnis fest, in dem die CSV-Datei mit den Teilestamm-Metadaten der Baugruppennummer liegt. Default: Umgebungsvariable %CREMIG_DATA%', metavar='directory')
parser.add_argument('-asm_csv_local', action='store_true', default=False, help='Legt fest, ob die Metadaten-CSV der Baugruppennummer bei jedem Programmlauf neu geladen wird. Default: Neu laden bei jedem Programmlauf')
parser.add_argument('-no_blue_ids', action='store_true', default=False, help='Legt fest, ob blaue IDs im Baum angezeigt werden sollen. Erforderlich, wenn keine Verbindung zu RDServer besteht, da SQL-Abfrage durchgeführt werden muss')
args = parser.parse_args()
environment = Environment()
asm_id = args.sivasid.lstrip()
asm_bom_filename = f'{asm_id}.json'
asm_metadata_filename = f'{asm_id}.csv'
if not args.no_blue_ids:
if not os.path.isfile(os.environ.get("RD_SQL_DATABASE_CREDENTIALS")):
print(f"ACHTUNG: Blau hinterlegte Nummern (Teilenummern die ins PDM migriert wurden, aber aktuell nicht freigegeben sind) werden nicht dargestellt, da nicht auf die Login-Daten für die RD-SQL-Datenbank zugegriffen werden kann. Bitte prüfen, ob die Login-Datei '{os.environ.get("RD_SQL_DATABASE_CREDENTIALS")}' existiert. Auch prüfen, ob die Netzlaufwerke verbunden sind!")
args.no_blue_ids = True
if args.excel:
num = args.sivasid.lstrip()
excel_comparison = ExcelExport(num, environment.data_dir, environment.data_dir, environment.ruledesigner_csv_filename)
excel_comparison = ExcelExport(asm_id=asm_id,
asm_json_dir=args.asm_json_filedir,
asm_json_filename=asm_bom_filename,
asm_csv_dir=args.rd_ids_filedir,
asm_csv_filename=asm_metadata_filename,
rd_dbase_dir=args.rd_ids_filedir,
rd_dbase_filename=args.rd_ids_filename,
load_local_asm_json=args.asm_json_local,
load_local_asm_csv=args.asm_csv_local,
load_local_rd_dbase=args.asm_csv_local,
no_blue_ids=args.no_blue_ids)
excel_comparison.write_excel()
elif args.tree:
num = args.sivasid.lstrip()
treeview_comparison = Treeview(num, environment.data_dir, environment.data_dir, environment.ruledesigner_csv_filename)
treeview_comparison = Treeview(asm_id=asm_id,
asm_json_dir=args.asm_json_filedir,
asm_json_filename=asm_bom_filename,
asm_csv_dir=args.rd_ids_filedir,
asm_csv_filename=asm_metadata_filename,
rd_dbase_dir=args.rd_ids_filedir,
rd_dbase_filename=args.rd_ids_filename,
load_local_asm_json=args.asm_json_local,
load_local_asm_csv=args.asm_csv_local,
load_local_rd_dbase=args.rd_ids_local,
no_blue_ids=args.no_blue_ids)
treeview_comparison.generate_tree()
else:
parser.print_help()
+19 -30
View File
@@ -2,8 +2,7 @@ import os
from tkinter import messagebox
import update_database
from manage_configs import SivasAssemblyData
from get_teilestamm import FetchSivasTeilestamm
from manage_configs import SivasAssemblyData, RuleDesignerDatabaseConfig, MigrationStatus
"""
Contains the logic for the MVC of CreoMigrate Gui
@@ -96,6 +95,8 @@ class Controller(Observer):
ret = self._environment.cfg_dir
elif env_path == "log":
ret = self._environment.log_dir
elif env_path == "rd_dbase_filename":
ret = self._environment.ruledesigner_csv_filename
else:
raise Exception("this path is not defined "+env_path)
return ret
@@ -114,36 +115,26 @@ class Controller(Observer):
orange[red] = True
return orange
def get_csv_files_of_asm(self, asmid, uselocal):
""" sich die Einzelteile als .csv und den Aufbau des Assemblies als .json holen """
def get_csv_files_of_asm(self, asm_id, load_local_asm_json, load_local_rd_dbase):
""" Holt sich die Metadaten der Stücklistenpositionen als CSV"""
data_dir = self.get_env("data")
sivas_teilestamm = FetchSivasTeilestamm(asmid, data_dir)
rd_dbase_filename = self.get_env("rd_dbase_filename")
# falls uselocal True, dann keine Serverabfrage, sondern mit den lokalen Daten arbeiten
sivas_teilestamm.fetch_from_db(uselocal)
if not uselocal:
asm_data = SivasAssemblyData(data_dir, asm_id + '.json', load_local_asm_json)
rd_data = RuleDesignerDatabaseConfig(data_dir, rd_dbase_filename, load_local_rd_dbase)
migration_status = MigrationStatus(asm_data.child_ids, rd_data.ids)
if not load_local_asm_json:
try:
sivas_teilestamm.get_unmigrated_teilstamm_csvs()
update_database.get_sivas_teilestamm(migration_status.ids_pending, data_dir)
except:
self.message_error('Abfrage hat nicht funktioniert', 'sivas_db_error')
return
redids = sivas_teilestamm.get_red_ids()
sad = SivasAssemblyData(data_dir, asmid + '.json')
parent_children = sad.get_parent_children()
self._model.set_red_ids(redids)
self._model.set_parent_children(parent_children)
self._model.set_asm_id(asmid)
def get_bom_properties(self, asmid):
""" Holt sich aus Stückliste alle Eigenschaften der Stücklistenpositionen (z.B. Bezeichnung)"""
data_dir = self.get_env("data")
assembly_data = SivasAssemblyData(data_dir, asmid + '.json')
bom_properties = assembly_data.get_all_bom_properties()
self._model.set_bom_properties(bom_properties)
self._model.set_red_ids(migration_status.ids_pending)
self._model.set_parent_children(asm_data.parent_children)
self._model.set_asm_id(asm_id)
self._model.set_bom_properties(asm_data.all_bom_properties)
def set_model(self, model):
self._model = model
@@ -162,15 +153,13 @@ class Controller(Observer):
def get_current_asm_id(self):
self._model.get_asm_id()
def set_current_asm_id(self, asm_id, loadlocal=False):
def set_current_asm_id(self, asm_id, load_local_asm_json=False, load_local_rd_dbase=False):
if not asm_id.isnumeric():
return
# TODO save current given data. In case, ask whether you should save or drop
self._model.set_asm_id(asm_id)
# hole alle Daten vom Server
self.get_csv_files_of_asm(asm_id, loadlocal)
self.get_bom_properties(asm_id)
#self._load_cid_data2model(asm_id)
self.get_csv_files_of_asm(asm_id, load_local_asm_json, load_local_rd_dbase)
def set_current_item_id(self, cid):
"""setze eine neue Item ID """
@@ -301,7 +290,7 @@ class Controller(Observer):
elif key == 'cb_ladelokal_asm':
current_asmid = self.data_from_view('asm_id')
# hole dir nur die lokalen Daten. Keine Datenbankabfrage
self.set_current_asm_id(current_asmid, True)
self.set_current_asm_id(current_asmid, load_local_asm_json=True, load_local_rd_dbase=True)
elif key == 'cb_3dsuchen':
self.suche_3d()
-50
View File
@@ -1,50 +0,0 @@
import argparse
import os
from manage_configs import MigrationStatus
import update_database
class FetchSivasTeilestamm:
def __init__(self, assembly_json_name, assembly_json_out_dir=os.environ.get('CREMIG_DATA'), csv_out_dir=os.environ.get('CREMIG_DATA')):
self.assembly_json_out_dir = assembly_json_out_dir
self.assembly_json_name = str(assembly_json_name).replace('.json', "") + ".json"
self.csv_out_dir = csv_out_dir
self.migration = None
def fetch_from_db(self, use_local=False):
self.migration = MigrationStatus(self.assembly_json_out_dir, self.assembly_json_name)
if use_local:
self.migration.use_local()
def get_unmigrated_teilstamm_csvs(self):
"""Generiert CSV-Dateien für alle noch nicht migrierten Stücklistenteile der angegebenen Baugruppennummer"""
if self.migration == None:
raise Exception("not yet connected to local data or database")
unmigrated_ids = self.migration.get_unmigrated_ids()
update_database.get_sivas_teilestamm(unmigrated_ids, self.csv_out_dir)
def get_red_ids(self):
if self.migration == None:
raise Exception("not yet connected to local data or database")
return self.migration.get_unmigrated_ids()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='fetches data from sivas or ruledesigner fusion', prog='update_database')
parser.add_argument('-s', '--sivasid', action='store', help='fetch teilestamm-data for all unmigrated parts in the bom of a given asm-id. Specifiy multiple ids as comma separated values without space, e.g. 200000089,200000090', metavar='id(s)')
parser.add_argument('-l', '--uselocal', action='store_true', help='Do not connect to database. Use local files (if already existing) for the given asm-id', default=False)
args = parser.parse_args()
out_dir = os.environ.get('CREMIG_DATA')
if args.sivasid:
sivas_asm_id = args.sivasid.strip().replace(".json","")
sivas_teilestamm = FetchSivasTeilestamm(sivas_asm_id, out_dir)
if args.uselocal:
sivas_teilestamm.use_local(args.uselocal)
sivas_teilestamm.fetch_from_db()
if args.uselocal == False:
sivas_teilestamm.get_unmigrated_teilstamm_csvs(args.uselocal)
else:
parser.print_help()
+230 -86
View File
@@ -1,6 +1,7 @@
import os
import json
from configparser import ConfigParser, RawConfigParser
import pymssql
from configparser import ConfigParser
import pandas as pd
import update_database
@@ -10,15 +11,20 @@ Diese Lib verwaltet alle Config dateien (als .cfg oder als .json) von CreoMigrat
"""
class JsonBasedConfig:
"""Klasse mit generellen Methoden für alle Config Objekte mit json files"""
def __init__(self, config_dir, config_file):
def __init__(self, config_dir: str, config_file: str):
"""merke dir den Pfad zu den Configdateien"""
self._config_dir = config_dir
self._config_file = config_file
self._config_path = os.path.join(config_dir, config_file)
def load_json_from_file(self):
"""lade das json file vom gegeben Pfad in ein json Objekt"""
try:
with open(self._config_path, 'r', encoding='utf-8') as fh:
content = fh.read()
return json.loads(content)
except FileNotFoundError:
raise ValueError("Bitte prüfen, ob korrekte Teilenummer, bzw. korrekter Dateiname angegeben wurde.")
def from_json(self, json_str):
"""nimm den Json String zur Initialisierung des Objektes"""
@@ -27,8 +33,10 @@ class JsonBasedConfig:
class CSVBasedConfig:
def __init__(self, config_dir, config_file):
def __init__(self, config_dir: str, config_file: str):
"""merke dir den Pfad zu den Configdateien"""
self._config_dir = config_dir
self._config_file = config_file
self._config_path = os.path.join(config_dir, config_file)
def load_csv_from_file(self):
@@ -36,41 +44,115 @@ class CSVBasedConfig:
return pd.read_csv(self._config_path, sep=";", dtype="object")
class SQLBasedConfig:
def __init__(self, server, user, pw, dbase):
self._server = server
self._user = user
self._pw = pw
self._dbase = dbase
def query_db(self, sql_statement) -> list:
"""Führt Datenbankabfrage aus und gibt Daten zurück"""
with pymssql.connect(self._server, self._user, self._pw, self._dbase) as conn:
with conn.cursor(as_dict=True) as cursor:
try:
cursor.execute(sql_statement)
except pymssql.ProgrammingError:
print("SQL-Statement konnte nicht verarbeitet werden!")
raise
ret = cursor.fetchall()
return ret
class RuleDesignerSQLData(SQLBasedConfig):
def __init__(self,
server=os.environ.get("RD_SQL_DATABASE_SERVER"),
user=os.environ.get("RD_SQL_DATABASE_USER"),
pw=os.environ.get("RD_SQL_DATABASE_PW"),
dbase=os.environ.get("RD_SQL_DATABASE_NAME")):
super().__init__(server, user, pw, dbase)
self._rd_statuses = None
@property
def rd_statuses(self) -> dict:
"""Gibt Teilenummer und zugehörigen Status zurück"""
if self._rd_statuses is None:
# Datenbankabfrage aller Teilenummern (codCodice) und der zugehörigen Status (codStatus)
sql_statement = "SELECT [codCodice] AS ID, [codStatus] AS STATUS FROM [FUSION_PDM].[dbo].[CODICI]"
sql_data = self.query_db(sql_statement)
# Jeder Teilenummer den sprechenden Stsatusnamen, statt der internen Statusnummer zuweisen
# Dafür laden der Mapping-Datei für die Statusnummern (z.B. 3 = "FREIGEGEBEN")
rd_status_cfg = IniBasedConfig(os.environ.get("CREMIG_CFG"), "RuleDesignerStatusnamen.cfg")
rd_status_sections = rd_status_cfg.sections()
rd_status_names = rd_status_cfg.get_dict(rd_status_sections[0])
self._rd_statuses = {}
for id_status_dict in sql_data:
id = id_status_dict["ID"]
status = id_status_dict["STATUS"]
self._rd_statuses[id] = rd_status_names[str(status)]
return self._rd_statuses
class SivasAssemblyData(JsonBasedConfig):
"""Auslesen der Assembly-Informationen zu einer angegebenen Baugruppennummer"""
def __init__(self, config_dir, config_file):
def __init__(self, config_dir: str, config_file: str, 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_file.removesuffix(".json"))
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 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)
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"]
assembly_ids.add(parent_id)
return assembly_ids
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."""
if self._parent_children is None:
unordered_parent_children = {}
for child_id, parent_properties in self.assembly_data.items():
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])
@@ -86,106 +168,170 @@ class SivasAssemblyData(JsonBasedConfig):
if parent not in ordered_parent_children:
ordered_parent_children[parent] = []
ordered_parent_children[parent].append(child)
return ordered_parent_children
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)"""
if self._max_bom_depth is None:
bom_levels = []
for parents in self.assembly_data.values():
for parents in self.asm_data.values():
for parent in parents:
current_bom_level = parent["Position"].count(".")
bom_levels.append(current_bom_level)
return max(bom_levels)
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 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._rd_statuses = None
self._overview = None
self._ids_in_rd = None
self._ids_in_progress = None
self._ids_pending = 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 ids_in_rd(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._ids_in_rd is None:
self._ids_in_rd = [id for id, status in self.id_status.items() if status is True]
return self._ids_in_rd
def get_migrated_ids(self) -> list:
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, die bereits in RuleDesigner migriert wurden"""
@property
def ids_in_progress(self) -> list:
"""Gibt migrierte Stücklistenpositionen zurück, die aktuell eine Freigabe der Baugruppe verhindern, da sie sich in Arbeit befinden"""
migrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is True]
return migrated_ids
def get_unmigrated_ids(self) -> list:
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, deren Migration in RuleDesigner noch aussteht"""
if self._ids_in_progress is None:
unmigrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is False]
return unmigrated_ids
rd_sql_data = RuleDesignerSQLData()
rd_statuses = rd_sql_data.rd_statuses
self._ids_in_progress = []
for id in self.ids_in_rd:
if rd_statuses[id] != "FREIGEGEBEN":
self._ids_in_progress.append(id)
return self._ids_in_progress
@property
def ids_pending(self) -> list:
"""Gibt alle Stücklistenpositionen zurück, die noch nicht migriert sind"""
if self._ids_pending is None:
self._ids_pending = [id for id, status in self.id_status.items() if status is False]
return self._ids_pending
class TeilestammMetadata(CSVBasedConfig):
def __init__(self, config_dir, config_file, load_local_csv=False):
super().__init__(config_dir, config_file)
self._load_local_csv = load_local_csv
self._csv_data = None
@staticmethod
def create_csvs(ids):
"""Erstellt CSV-Datei(en) mit den Teilestamminformationen zu der / den angegebenen Teilenummer(n)"""
try:
update_database.get_sivas_teilestamm(ids)
except:
raise ValueError("CSV(s) konnte nicht generiert werden!")
@property
def csv_data(self):
"""Gibt CSV-Daten als Dataframe zurück"""
if self._csv_data is None:
if not self._load_local_csv:
self.create_csvs(self._config_file.removesuffix(".csv"))
self._csv_data = self.load_csv_from_file()
return self._csv_data
def get_property_values(self, property_name) -> str:
"""Gibt Wert aus der CSV für den angegebenen Spaltennamen zurück"""
ret = self.csv_data[property_name][0]
return ret
class TOSGroupsConfig(JsonBasedConfig):
@@ -525,7 +671,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 +685,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:
@@ -551,5 +697,3 @@ class Configs:
lines = f.read().splitlines()
self._blacklist = lines
return self._blacklist
+2 -2
View File
@@ -63,7 +63,7 @@ def get_rd_dbase(out_dir=os.environ.get('CREMIG_DATA')):
print(f"File with ruldesigner-partnumbers ${csv_source_path} doesn't exist. Check VPN connection and %RD_DATABASE_SOURCE% + %RD_DATABASE_NAME% in cremig_setenv.bat")
sys.exit(0)
def get_sivas_dbase(sivas_id, out_dir):
def get_sivas_dbase(sivas_id, out_dir=os.environ.get('CREMIG_DATA')):
sivas_exe_path = os.environ.get('SIVAS_DATABASE_QUERY')
sivas_id = sivas_id.replace(".json", "")
@@ -86,7 +86,7 @@ def get_sivas_dbase(sivas_id, out_dir):
print(f"Can't execute SIVAS-database query. Check VPN connection and check the path of %SIVAS_DATABASE_QUERY% in cremig_setenv.bat")
return False
def get_sivas_teilestamm(sivas_ids, out_dir, overwrite=True):
def get_sivas_teilestamm(sivas_ids, out_dir=os.environ.get('CREMIG_DATA'), overwrite=True):
"""call the sivas exe for getting all csv files with the item data for the given sivas ids"""
sivas_teilestamm_exe = os.environ.get('SIVAS_TEILESTAMM')
Binary file not shown.