Compare commits

..

56 Commits

Author SHA1 Message Date
m.stangl b2127b70c9 Add CLAUDE.md for Claude Code guidance
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:20:21 +02:00
Benjamin_Mueller db46c38ac6 Werkstoff CC493K - GC hinzugefügt 2026-05-05 17:20:01 +02:00
Benjamin_Mueller af69b98365 Materialübersetzung für Ck60 hinzugefügt 2026-05-05 15:48:52 +02:00
Benjamin_Mueller 87ff81d001 Werkstoff 1.1221 (C60E) hinzugefügt 2026-05-05 14:27:10 +02:00
Benjamin_Mueller cbc2afa9e1 Redundante Werkstoffübersetzung hinzugefügt 2026-02-11 09:30:14 +01:00
Benjamin_Mueller 47e70d952e Werkstoff & redundante Werkstoffübersetzung hinzugefügt 2026-02-10 13:03:09 +01:00
Benjamin_Mueller 5a1fa5a7e4 Installationsvoraussetzung erstellt 2026-02-10 11:20:24 +01:00
Benjamin_Mueller 4defeb11f0 Neue Farbausprägung hinzugefügt 2026-02-10 11:17:49 +01:00
Benjamin_Mueller 2591d78716 Installationsvoraussetzung hinzugefügt. Excel in 64 Bit zwingend erforderlich. 32 Bit ist nicht ausreichend 2026-01-08 09:04:45 +01:00
Benjamin_Mueller ac99ca73a5 Farbe Transparent hinzugefügt 2025-11-05 10:17:55 +01:00
Benjamin_Mueller 17cbd61fb4 Werkstoff PVC-P (weich) hinzugefügt) 2025-11-05 10:11:03 +01:00
Benjamin_Mueller f18f8cae2d Materialübersetzung für 1.4301 hinzugefügt und die verschiedenen Übersetzungen zueinander geordnet. 2025-10-30 11:21:39 +01:00
Benjamin_Mueller 7fb778125b Werkstoff hinzugefügt 2025-10-23 10:53:07 +02:00
Benjamin_Mueller a279c581e3 Weitere Rohteilnummer hinzugefügt 2025-10-16 12:30:35 +02:00
Benjamin_Mueller aa73ffb444 Verschiedene Werkstoffe der Auswahlliste hinzugefügt 2025-10-16 12:26:57 +02:00
s.steuer 61cd933de3 FIX - Bei Definierung der automatischen Zusatzinformationen muss geprüft werden, ob die aktuell ausgewählte Nummer über eine Stückliste verfügt. Die Methode 'has_bom', die dafür implementiert wurde hat jedoch nur zurückgegeben, ob die oberste Nummer, nach der gesucht wurde eine Stückliste hat oder nicht. Dies wurde nun behoben, indem zunächst alle Kinder der ausgewählten Nummer geholt werden. Wenn diese 'None' sind hat die ausgewählte Teilenummer keine Stückliste. Die Methode 'has_bom' sowie die Methode 'set_bom_existance' werden daher nun nicht mehr benötigt. Alle entsprechenden Referenzen wurden entfernt. 2025-08-27 16:54:04 +02:00
Benjamin_Mueller 9ef23a735a Werkstoff 'PE-UHMW antistatisch' wurde hinzugefügt 2025-07-29 10:28:16 +02:00
s.steuer 089dadd52b FIX - Wurde der Radiobutton 'Schulung' ausgewählt und anschließend ein weiteres Feld geändert (z.B. Kategorie), dann wurde immer wieder zurück auf den 'Produktiv'-Radiobutton umgeschaltet. Die neu ergänzte Callback-Funktion 'cb_datenbank_neu' verhindert dies nun, d.h. der gewählte Wert (z.B. Schulung) bleibt so lange erhalten, bis ein anderes Teil migriert wird. Defaultwert ist und bleibt weiterhin 'Produktiv' 2025-07-27 22:15:50 +02:00
s.steuer 9f5f4150f1 FIX - fehlenden Backslash nach C:\Users\s.steuer\Documents\Pythonprojekte\CreoMigrate\bin ergänzt, da in cremig_setenv.bat die Umgebungsvariable CREMIG_BIN am Ende ohne Backslash definiert ist 2025-07-26 22:22:05 +02:00
s.steuer c64327cbe7 FIX - Beistellungs- und Dispoaktiv-Eigenschaft darf nur dann eingelesen werden, wenn auch eine Stückliste vorliegt, da andernfalls auch keine JSON vorliegt, aus der die Eigenschaften gelesen werden könnten. Aktuell wurde in diesem Fall eine Fehlermeldung ausgegeben 2025-07-26 20:56:27 +02:00
s.steuer 9e25ca15bc FIX - Backslashes durch noramle Slashes ersetzt, da '\R' = Carriage Return, wodurch eine Syntaxwarnung ausgegeben wird 2025-07-26 20:43:40 +02:00
s.steuer 754ae39364 FIX - Bei der Ermittlung, ob sich Lieferantenbeistellungen oder dispoinaktive Teile in der Stückliste befinden wurden auch die Teilenummern aus tieferen Stücklistenebenen betrachtet. Es dürfen aber nur die Teilenummern der aktiven Stücklistenebene in Betracht gezogen werden. Dies wurde behoben. 2025-07-24 13:30:51 +02:00
s.steuer 6590674d8e FIX - '- BG' aus SE-Eigenschaft 'Title' entfernt, da ' - BG' laut Entscheidung von Hr. Rost nicht mehr in der Baugruppenbezeichnung stehen darf. Im Feld 'Bezeichnung 4' wurde zudem das anfängliche Leerzeichen vor dem Strich in ' - BG' entfernt, da Bezeichnung 4 jetzt auf der Zeichnungsstückliste in der Spalte 'Bemerkung' abgerufen wird und ein Großteil der Baugruppen im System kein Leerzeichen vor dem Strich hat (bei allen 2er-Nummern und bei den Baugruppen, die noch mit der Excel migriert wurden). 2025-07-24 11:34:15 +02:00
s.steuer 6c31b9fbf3 siehe JIRA SUR-645 - Fehlendes Leerzeichen bei Ersetzung von Semikola in den Feldern 'Änderungstext' und 'Freigabevermerk' ergänzt / Logik der Feldprüfung hinsichtlich unzulässigen Zeichen überarbeitet. Konkret werden nun nicht mehr die Werte in der Spalte 'SIVAS-Werte' geprüft, wenn zusätzlich Eintragungen in der Spalte 'Überschriebene Werte' vorliegen / Ergänzend zu SUR-645 folgende Korrektur vorgenommen: Der korrigierte Text im Feld 'Freigabevermerk' (wenn Semikola im SIVAS-Text vorlagen) wurde bisher wieder gelöscht, sobald eine PDM-Kategorie ausgewählt wurde. Diese Löschung erfolgt nun nicht mehr, d.h. der überschriebene Text liegt nun auch nach Wahl der Kategorie weiterhin vor 2025-07-13 18:11:28 +02:00
s.steuer f5cc53253e siehe JIRA SUR-644 - Fehlt eine Nummer in der Blacklist wird nun bei jedem Klick auf 'Migrieren' in der GUI geprüft, ob die Nummer in der ERP_BOM.ini vorliegt. Fehlt sie, erscheint wie gewohnt die Fehlermeldung, dass die Nummer in der Datei fehlt - eine Migration erfolgt also nicht. Erst wenn die Meldung mit 'OK' bestätigt und die Nummer anschließend in die .ini-Datei hinzugefügt wurde kann die Migration mit dem nächsten Klick auf 'Migrieren' erfolgreich abgeschlossen werden. 2025-07-11 23:35:44 +02:00
s.steuer cf4b632cba Logik des Feldes 'Zusatzinformationen automatisch' überarbeitet. Konkret wurde die Erkennung der Fälle überarbeitet, bei denen Zusatzinformationen ausgegeben werden sollen. Es wird nun zunächst geprüft, ob die verschiedenen Einzelfälle, z.B. ob es sich um eine Trümmerbaugruppe handelt, auf die zu migrierende Nummer zutreffen. Ist dies der Fall wird der 'Treffer' gesammelt. Am Ende werden alle Treffer miteinander zu einem String kombiniert. Dieser kombinierte String wird anschließend in der 'Zusatzinformationen.cfg' gesucht. Bei Übereinstimmung wird der zugehörige Text aus der 'Zusatzinformationen.cfg' in das GUI-Textfeld geschrieben. Die Überarbeitung wurde durchgeführt, weil es immer mehr Fälle wurden und es mit der bisherigen Logik unübersichtlich wurde, wo der neue Fall eingefügt werden soll. Zudem entstanden Redundanzen. Die neue Logik sollte deutlich dynamischer, wartbarer und einfacher erweiterbar als die bisherige Variante sein. Es muss jedoch in der Zusatzinformationen.cfg darauf geachtet werden, dass die Optionen korrekt geschrieben werden, da das Programm sonst keine Übereinstimmung finden kann 2025-07-11 22:28:54 +02:00
s.steuer 95fb80dd57 Zeichenbegrenzung auf 45 Zeichen bei Baugruppen und Schweißbaugruppen wird nicht mehr benötigt, da Suffix '- BG' gem. Anordnung GF nicht mehr in Bezeichnungen vorliegen darf. 2025-07-11 16:38:13 +02:00
Benjamin_Mueller b7fd1da5b8 Folgende Änderungen wurden vorgenommen: cremig_setenv.bat wurde so modifiziert, das ein dynamischer Pfadwechsel des Tools 'DataPrepUtility' möglich ist nach einem Solid Edge Versionswechsels. controller.py Fehlermeldung über nicht gefundenen Pfad wurde verfeinert, war zu ungenau. Näheres zu den Änderungen siehe JIRA SUR-648 2025-06-26 16:32:44 +02:00
Benjamin_Mueller e342ace5c3 FIX - Korrektur der Pfadangaben um verwechslung mit Syntaxbefehlen zu vermeiden 2025-06-25 12:07:19 +02:00
Benjamin_Mueller fb710b73e0 Infromationstext des Fensters 'Fehlend in Blacklist' um Pfadangabe der Blacklist und bitte um Programmneustart ergänzt 2025-06-25 09:59:21 +02:00
s.steuer c813a8fc1b FIX - Werkstoffbezeichnungen aus SIVAS, die mit einer Dezimalzahl, wie z.B. '1.4301' oder '1.4305' wurden als float-Werte eingelesen. Die Werte aus der Materialübersetzung.json wurden wiederum als Strings behandelt, dadurch konnte keine Übereinstimmung gefunden werden. Zukünftig werden alle Werkstoffe aus der CSV als String eingelesen 2025-06-23 16:13:40 +02:00
s.steuer f8b895cec7 In 'update_database.py' Namen korrigiert + In 02_get_sivas_dbase.bat Aufruf des fehlenden Schalters ergänzt 2025-06-04 15:51:42 +02:00
Benjamin_Mueller cb20f31f10 Werkstoff Murtfeldt Original 'S' grün der Werkstoff.cfg hinzugefügt 2025-06-03 11:42:38 +02:00
Benjamin_Mueller a6a7d9c3e9 Auswahlmöglichkeit für andere Bildschirmauflösung in gui-b.mueller.cfg hinzugefügt 2025-05-20 16:49:59 +02:00
Benjamin_Mueller 05709677ac TOS-Gruppenstruktur erweitert 2025-05-20 16:39:13 +02:00
s.steuer 54606dce88 Da die Initialisierung des Netzwerkinterpreters auf U: so lange dauert wurde eine Meldung eingebaut, dass aktuell die Initialisierung erfolt, da der User ansonsten evtl. denkt, dass das Script nicht funktionier / nichts macht 2025-04-27 20:22:23 +02:00
s.steuer 2f2dd83e2b FIX - Da als Argument in '--rm_bom_ids_cfg' sowie in '--rm_summary_ids_cfg' die Umgebungsvariable $CREMIG_CFG aufgerufen wird muss cremig_setenv.bat zu diesem Zeitpunkt aufgerufen sein. Bisher wurde sie allerdings erst in manage_interpreters.bat eingelesen. Folglich wurde ein Fehler ausgegeben 2025-04-27 20:19:33 +02:00
s.steuer 0eda150f8b Debugger in allen Konfigurationen an neuen VSCode-Standard angepasst + Schalter in Konfiguration für compare_lists.py korrigiert + Syntax-Korrektur: Die vesrchiedenen Konfigurationen werden jetzt mit Kommata getrennt 2025-04-27 17:55:19 +02:00
s.steuer 58b16509da Basierend auf Commit d905896 werden nun Teilenummern, die in $CREMIG_CFG/BOM-Settings.cfg -> Sektion 'ExcludedIDsInBOM' definiert sind aus der Darstellung des Treeviews ausgeschlossen. Darüber hinaus wird nun bei Ausführung von 'Lade lokal' in der GUI die lokale Teilestamm-CSV gezogen. Bisher wurde immer vom Server generiert, was vor allem bei Tests über einen Remoteclient sehr langsam ist. 2025-04-27 17:36:41 +02:00
s.steuer 39bd9e01e2 Basierend auf Commit 21528e2 Schalter '--rm_bom_ids_cfg' und '--rm_summary_ids_cfg' ergänzt. Folglich werden bei Ausführung dieses Batch-Scripts die Teilenummern, die in $CREMIG_CFG/BOM-Settings.cfg definiert sind aus der Darstellung des Baumes, bzw. der Zusammenfassung ausgeschlossen. Darüber hinaus Präfix 'Baugruppennummer' zur Angabe der Artikelnummer, für die der Baum darstellt werden soll in 'Artikelnummer' umbenannt, da mittlerweile auch für Teilenummern ohne Stückliste der Migrationsstand angezeigt werden kann. 2025-04-27 17:32:41 +02:00
s.steuer 21528e2353 Basierend auf Commit d905896 können jetzt im Treeview Nummern aus der Stücklistenstruktur sowie aus der Zusammenfassung ausgeschlossen werden. Nummern, die explizit oder implizit ausgeschlossen wurden (implizit, wenn sie Bestandteil einer Baugruppe sind, die ausgeschlossen wurde), dann werden diese Nummern in der Zusammenfassung aufgelistet, um dem User zu verdeutlichen, dass die Darstellung des Baumes, bzw. der Zusammenfassung von der tatsächlichen Struktur abweicht. Zur Angabe der ausgeschlossenen Nummern aus dem Baum können die neuen Schalter '--rm_bom_ids_str' und '--rm_bom_ids_cfg' genutzt werden. Bei ersterem können die Nummern als Argumente nacheinander mit Leerzeichen getrennt angegeben werden. Bei zweiterem Fall wird eine Konfigurationsdatei mit darin definiertem Sektionsnamen angegeben. Analog dazu gibt es zum Ausschluss der Teilenummern aus den Anzahlen der Zusammenfassung die neuen Schalter '--rm_summary_ids_str' und '--rm_summary_ids_cfg'. Allgemein wurde auch die Angabe der Argumente konkretisiert und z.B. durch CustomActions zur Validierung der Argumente verbessert. 2025-04-27 17:29:09 +02:00
s.steuer d905896891 Klasse 'BOMConfig' sowie Methoden in Klasse 'SivasBOM' ergänzt, damit Teilenummern aus der Stücklistenstruktur herausgefiltert werden können. Aktuell ist es so festgelegt, dass wenn ein User ausgeschlossene Teilenummern an die Klasse 'SivasBOM' übergibt, diese dann auch automatisch aus der Stückliste ausgeschlossen werden, d.h. zum Ausschließen der der Nummern ist kein expliziter Aufruf der Methode '_remove_excluded_ids_from_bom' erforderlich. Hierbei gilt die Prämisse: Wenn Du ausgeschlossene Nummern angibst, möchtest Du sie auch nicht sehen. Weiso hättest Du sie sonst anggegeben? 2025-04-27 17:19:48 +02:00
s.steuer 1635610aff Config, zur Beeinflussung der Stücklisteninformationen, die defaultmäßig aus der JSON gezogen werden, ergänzt. Es wurden vorerst die Sektionen 'ExcludedPartnumbersInBOM' und 'ExcludedPartnumbersInSummary' vorgesehen. In ersterer können Teilenummern aus der Stücklistenstruktur ausgeschlossen werden. In zweiterer können Teilenummern in Zusammenfassung zur Stücklistenstruktur ausgeschlossen werden, d.h. die Nummer wird dann zwar bei einer Darstellung der Stücklistenstruktur sichtbar sein, sie wird nicht in der Zusammenfassung aufgeführt. 2025-04-27 17:11:41 +02:00
s.steuer e083f3c2ac In CreoMigrate (GUI-Applikation) können jetzt auch Sivas-Nummern, die kein Bestandteil der Menge der natürlichen Zahlen sind (z.B. 'TOS Auswahl FGN') migriert werden. 2025-04-20 18:07:07 +02:00
s.steuer 2a601ec3c1 In CreoMigrate (GUI-Applikation) können nun auch Teilenummern ohne Stückliste migriert werden. Hierfür wurde in Methode 'get_csv_files_of_asm' von 'controller.py' ein Aufruf der Methode 'extend_asm_bom' aus der Klasse 'SivasBOM' in 'manage_configs.py' ergänzt. Innerhalb der Methode wird die angegebene Teilenummer aus dem Eingabefeld in der GUI, d.h. die Hauptnummer in die Stücklistendaten aufgenommen - standardmäßig ist sie nicht enthalten. Als Oberbaugruppe dieser Nummer wird dabei die fiktive Baugruppe 'None' definiert. Durch diese Ergänzung in den Stücklistendaten wird eine Teilenummer ohne Stückliste also selbst zu einem Bestandteil der fiktiven Baugruppe 'None'. Somit wird also wie bisher nur mit Baugruppen gearbeitet, wodurch die Grundlogik zur Erstellung des Treeviews im Programm gleich bleibt. Lediglich die Vorgehensweise, wenn parent = 'None' gilt musste in den Treeview-Methoden angepasst werden. Im Zuge dessen wurde auch die Methode '_has_childs' der Treeview-Klasse in 'gui.py' angepasst, um sie effizienter zu machen. Konkret wird nun nicht mehr jedes mal durch das parent_children_mapping-dict gelooped werden, um herauszufinden, ob die angegebene Nummer enthalten ist. Da alle Nummern, die als Key in genanntem Dict vorliegen immer Kinder haben muss stattdessen nur geprüft werden, ob die Nummer als Key vorliegt. 2025-04-20 17:40:52 +02:00
s.steuer f01f08cda9 2. Backslash ergänzt, damit 1. Backslash korrekt interpretiert wird 2025-04-20 12:22:49 +02:00
s.steuer 832aaf2088 Variablennamen wurden in 'manage_configs.py' geändert (siehe commit 623f4a1) -> wurde in dieser Datei nachgezogen. Zudem in Methode 'extend_asm_bom' den Key 'Parent' mit aufgenommen, damit die Methode 'parent_children' in 'manage_configs.py' funktioniert. Dadurch kann nun auch der Migrationsstatus von Nummern ohne Stückliste im Treeview korrekt dargestellt werden. 2025-04-20 12:12:08 +02:00
s.steuer 623f4a12e1 In 'manage_configs.py' Klasse 'SivasAssemblyData' in 'SivasBOM' und Klasse 'TeilestammMetadata' in 'SivasTeilestamm' umbenannt, da eindeutiger. In 'controller.py' Namen entsprechend aktualisiert. Zudem in 'manage_configs.py' Klasse 'SivasBOM' die Methode 'asm_data' in 'bom_data' umbenannt. Darüber hinaus gibt die Methode nun ein leeres Dict zurück, wenn keine JSON vorliegt oder keine generiert werden konnte. Dies wurde vorgesehen, um im Migrationsüberblick (Treeview) sowie dem Migrationstool (CreoMigrate) auch Teilenummern ohne Stückliste, d.h. ohne JSON abbilden zu können (genaue Funktionsweise siehe mehrzeiliger Kommentar in genannter Methode). Abgesehen davon Methode 'property_names' komplett entfernt, da diese fehlerhaft funktioniert hat, da jedes Mal durch alle Kinder der Assembly geloopt wurde und die Eigenschaften des aktuellen Kindes wie z.B. 'Bezeichnung', 'Parent', etc. ausgelesen wurden. Der Fehler liegt darin, dass die Eigenschaften der Nummer aus der vorherigen Iteration komplett überschrieben, aber nicht ergänzt wurden, falls in der aktuellen Iteration Eigenschaften vorliegen, die in der vorherigen noch nicht vorlagen. Um dieses Problem ganz zu umgehen, wurden die Methoden 'get_prop_val_for_all_ids' und 'all_bom_properties' umgeschrieben, sodass die Eigenschaften direkt innerhalb dieser Funktionen geholt werden 2025-04-20 12:05:46 +02:00
s.steuer 56ff5010c7 In Funktion 'get_sivas_bom' wird nun in Fällen, bei denen keine JSON erzeugt werden, ein ValueError geschmissen, der ggf. später bei Bedarf per Exception gehandelt werden kann + In Funktion 'get_sivas_teilestamm' wird nun geprüft, ob das Tool 'KSbExcelExportSivasTeilestamm.exe' von Hr. Jakob mit den angegebenen Nummern tatsächlich valide CSV's generiert hat. Das ist deshalb von Bedeutung, weil das Tool auch dann CSV's erzeugt, wenn die Werte, die man per Flag '-teilenr' bereitstellt, nicht als Nummer in SIVAS vorliegen, wie z.B. 'abc' 2025-04-20 11:43:07 +02:00
s.steuer 0c54145655 In update_database.py Funktion 'get_sivas_dbase' in 'get_sivas_bom' umbenannt, da mit dieser Funktion ausschließlich die Stückliste geholt wird und somit spezifischer ist. In der Datei 'manage_configs.py' Funktionen folglich analog umbenannt. Ansonsten Funktionen in 'update_database.py' denested, um Lesbarkeit zu verbessern und Print-Statements in Deutsch geändert, da aktuell ein Mix aus Deutsch und Englisch vorliegt, was zu Verwirrung führen kann. Zudem Docstrings ergänzt und Fehler in den Print-statements behoben. Keine funktionalen Änderungen vorgenommen 2025-04-19 16:40:53 +02:00
s.steuer 3393e7ce47 Umgebungsvariable 'SIVAS_DATABASE_QUERY' in 'SIVAS_BOM' umbenannt, weil auch für die Generierung der Metadaten-CSV eine 'Database-Query' durchgeführt wird, d.h. Bezeichnung ist mehrdeutig. 'SIVAS_BOM' deshalb, weil mit dem Programm 'Sivas2Json', dessen Pfad in 'SIVAS_BOM' gespeichert ist, die Stückliste einer Baugruppe als JSON ausgegeben wird. Passt damit bezeichnungstechnisch zur Umgebungsvariable 'SIVAS_TEILESTAMM', weil mit dem Programm, dessen Pfad in genannter Umgebungsvariable gespeichert ist die Teilstamm-CSV generiert wird. 2025-04-18 23:08:22 +02:00
s.steuer 807b6c79f4 Datei redundant, da die Erstellung der Metadaten-CSV des Teilestamms bereits über die Funktion 'get_teilestamm.py' in 'update_database.py' erfolgt. 2025-04-18 22:53:45 +02:00
s.steuer 80000c15c3 Die Stücklistendaten, die aus der JSON-Datei des Programms 'Sivas2Json' erzeugt werden, wird jetzt immer die Teilenummer der Hauptbaugruppe ergänzt. Dadurch kann die Methode '_print_tree' auch die Nummer der Hauptbaugruppe schreiben. Bisher war hierfür die separate Methode '_tree_header' erforderlich. Für die Aufnahme der Hauptbaugruppe in die Stücklistendaten ist die neue Methode '_add_asm_id_to_bom' zuständig. Die Anpassung wurde vorbereitend für die nächste Erweiterung dieses Programms ergänzt, mit der es möglich sein soll, den Migrationsstand von einzelnen Teilenummern, d.h. Nummern, die keine Baugruppen sind, anzuzeigen. 2025-04-18 22:13:07 +02:00
s.steuer fe302ef950 In der Methode 'child_ids' war es unnötig für die Ermittlung aller Stücklistenpositionen das 'Parent'-Attribut der aufgelisteten Teilenummern in der JSON zu betrachten, da die Teilenummern in der JSON bereits alle Kinder der ASM darstellen / Methode 'extend_asm_bom' ergänzt, damit die Stückliste um eine Nummer ergänzt werden kann (z.B. die Nummer der Oberbaugruppe selbst, da diese aktuell in der JSON nicht ausgegeben wird). Dies wurde ergänzt, damit zukünftig auch der Migrationsstand von einzelnen Teilenummern, die keine Baugruppen sind, angezeigt werden kann, bzw. einzelne Teilenummern migriert werden können. Die entsprechende Anpassung des Treeview- bzw. GUI-Programms erfolgt in einem späteren Commit. Zudem wurde die Methode 'get_prop_val_for_single_id' ergänzt, da im Treeview-Programm bisher bei jedem Eintrag des Baums die Methode 'get_prop_val_for_all_ids' aufgerufen wurde, in der jedes Mal eine bestimmte Eigenschaft, wie z.B. 'Bezeichnung' für alle Stücklistenpositionen der Hauptbaugruppe ermittelt wurde. Das war sehr ineffizient, weil die Eigenschaft nur für den aktuellen Eintrag, d.h. die aktuelle Teilenummer, die im Baum dargestellt werden soll, benötigt wird, aber nicht für alle Stücklistenpositionen. 2025-04-18 22:03:03 +02:00
s.steuer efef193612 FIX - Die Metadaten-CSV von Teilenummern mit Leerzeichen, z.B. 'TOS Auswahl FGN' wurde bisher nicht abgeleitet. Dies funktioniert nun. Zudem Denesting durchgeführt und spezifischere Error-Checks eingebaut 2025-04-14 21:47:13 +02:00
s.steuer a1a7f45f06 pymssql in requirements aufgenommen (wird für Datenbankabfrage benötigt) und sonstige Dependencies upgedated. 2025-03-31 17:27:19 +02:00
24 changed files with 939 additions and 578 deletions
+18 -13
View File
@@ -9,27 +9,32 @@
{
"name": "Python: Aktuelle Datei",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
}
},
{
"name": "Python: CompareLists",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"args": [
"-n 400102196",
"-t"
"400102530",
"tree",
"--local_bom",
"--local_teilestamm",
"--local_rd_ids",
"--rm_bom_ids_cfg", "C:\\Users\\s.steuer\\Documents\\Pythonprojekte\\CreoMigrate\\cfg\\BOM-Settings.cfg", "ExcludedPartnumbersInBOM",
"--rm_summary_ids_cfg", "C:\\Users\\s.steuer\\Documents\\Pythonprojekte\\CreoMigrate\\cfg\\BOM-Settings.cfg", "ExcludedPartnumbersInSummary"
],
}
},
{
"name": "Python: create TOS sring",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
@@ -37,10 +42,10 @@
"args": [
"--console"
],
}
},
{
"name": "fetch blacklist from server",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
@@ -48,10 +53,10 @@
"args": [
"--blacklist"
],
}
},
{
"name": "Python: Build Demo Gui",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
@@ -59,10 +64,10 @@
"args": [
"-d"
],
}
},
{
"name": "Python: Build Real Gui",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
+54
View File
@@ -0,0 +1,54 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Projektübersicht
**CreoMigrate** enthält Skripte zur Migration von Creo-Altdaten nach Solid Edge / Rule
Designer. Ziel ist es, alte 3D-Daten aus Creo mit aktuellen SIVAS-Informationen in die
neue Konstruktionsumgebung zu überführen. Der Fokus liegt auf automatisierbaren Prozessen
mit wenig Nutzereingriff (Datenbank-Abgleich, Listenvergleich, TOS-String-Erzeugung).
## Projektstruktur
```text
CreoMigrate/
├── bin/ # Nummerierte Workflow-Batch-Skripte (01..06) + GUI/Setup
├── lib/ # Python-Quellcode
├── Include/ # Zusätzliche Includes
├── cfg/ # Konfiguration
├── data/ # Datenbanken / Eingabedaten
├── doc/ # Dokumentation
├── log/ # Log-Dateien
└── work/ # Arbeitsdateien
```
## Workflow-Skripte (bin/)
Die Batch-Skripte sind in Ausführungsreihenfolge nummeriert:
1. `01_get_rd_dbase.bat` — Rule-Designer-Bestand ableiten
2. `02_get_sivas_dbase.bat` — SIVAS-Datenbank ableiten
3. `03_get_blacklist_dbase.bat` — Blacklist erzeugen
4. `04_compare_lists.bat` — Listen vergleichen
5. `05_get_sivas_teilestamm.bat` — Teilestamm aus SIVAS
6. `06_migrationsstatus_treeview.bat` — Migrationsstatus als Treeview
Weitere: `gui.bat` (GUI), `create_tos_string.bat` / `write_tos_string_to_file.bat`
(TOS-Strings), `install_py.bat`, `cremig_setenv.bat`, `get_cmd.bat`.
## Kernmodule (lib/)
- `CreoMigrateGui.py`, `gui.py`, `controller.py` — GUI + Steuerung (Tkinter)
- `compare_lists.py` — Listenvergleich
- `create_tos.py` — TOS-String-Erzeugung
- `caditem.py` — Datenmodell CAD-Item
- `update_database.py`, `manage_configs.py` — DB/Config
- `test_*.py` — Tests
## Setup
```cmd
bin\install_py.bat
```
Legt die virtuelle Umgebung an und installiert `lib/requirements.txt`.
+2 -2
View File
@@ -29,5 +29,5 @@ Vorteile:
- Loslösen von Excel als Bedienumgebung, somit nicht an Version oder Hersteller gebunden.
- Problemloseres Ausrollen auf mehrere Benutzer
Installationsvoraussetzungen:
- Tabellenkalkulationsprogramm (Excel) in 64-Bit Version -> Dies ist wichtig, da unter 32-Bit das Migrationstool von Siemens (DataPrep-Tool) nicht korrekt funktioniert!
+1 -1
View File
@@ -1,5 +1,5 @@
@echo off
IF "%1" == "-s" (set SIVAS_ID=%2) else (set /p SIVAS_ID=Enter Sivas ID:)
CALL manage_interpreter.bat activate_interpreter
python %CREMIG_LIB%\update_database.py --sivasid=%SIVAS_ID%
python %CREMIG_LIB%\update_database.py --sivasid=%SIVAS_ID% --bom
CALL manage_interpreter.bat deactivate_interpreter
+3 -2
View File
@@ -1,5 +1,6 @@
@echo off
CALL cremig_setenv.bat
:start
set /p "id=Baugruppennummer: "
CALL 04_compare_lists.bat --sivasid "%id%" -t
set /p "id=Artikelnummer: "
CALL 04_compare_lists.bat "%id%" tree --rm_bom_ids_cfg %CREMIG_CFG%\BOM-Settings.cfg ExcludedPartnumbersInBOM --rm_summary_ids_cfg %CREMIG_CFG%\BOM-Settings.cfg ExcludedPartnumbersInSummary
goto start
+6 -2
View File
@@ -10,6 +10,10 @@ set CREMIG_DATA=%CREMIG%\data
set CREMIG_WORK=%CREMIG%\work
set CREMIG_LOG=%CREMIG%\log
rem Hilfsvariable um den Programmpfad zu Solid Egde immer an die aktuelle Version anzupassen. Dieser ändert sich jedesmal beim Update der Jahresversionen (Solid Edge hängt immer die Jahreszahl der Version im Ordnernamen an).
rem Deshalb muss dieser Pfad möglichst automatisch ausgelesen werden. Da auch immer das Programm Keyshot in diesem Zuge mitinstalliert wird, konnte eine Umgebungsvariable gefunden werden, die denselben Pfad verwendet wie die DataPrepUtility.exe
set SOLIDEDGE_VERSION=%KEYSHOT_EXTERNAL_LICENSE_FOLDER%
if not exist %CREMIG%\work mkdir %CREMIG%\work
if not exist %CREMIG%\log mkdir %CREMIG%\log
if not exist %CREMIG%\data mkdir %CREMIG%\data
@@ -27,10 +31,10 @@ set RD_DATABASE_NAME=RD_Teilenummern.csv
set BLACKLIST_DATABASE_SOURCE=\\ruledesigner\RDF_Daten\Konfigurator\CustomVaulting
set BLACKLIST_DATABASE_NAME=ERP_BOM.ini
set VBA_MACRO_NAME=vba_format_excel.xlsm
set SIVAS_DATABASE_QUERY=Y:\jit\programme\Sivas2json.exe
set SIVAS_BOM=Y:\jit\programme\Sivas2json.exe
set SIVAS_TEILESTAMM=\\195.243.223.3\sivas\jit\programme\KSbExcelExportSivasTeilestamm.exe
set SIVAS_EXCEL_EXPORT_DIR=%CREMIG_DATA%
set SIEMENS_DATA_PREP_TOOL=C:\Program Files\Siemens\Solid Edge 2023\Program\DataPrepUtility.exe
set SIEMENS_DATA_PREP_TOOL=%SOLIDEDGE_VERSION%\DataPrepUtility.exe
set LOG_EDITOR=notepad.exe
set RD_SQL_DATABASE_CREDENTIALS=\\fileserver01\abteilungen\Entwicklung\Administration\Lizenzen\RDSQLDatabaseCredentials.bat
+1
View File
@@ -17,6 +17,7 @@ deactivate
goto :eof
:activate_interpreter_network
ECHO Initialisiere Python-Interpreter in %NETWORK_INTERPRETER_PATH%...
SET OLD_PATH=%PATH%
SET PATH=%NETWORK_INTERPRETER_PATH%;%PATH%
goto :eof
+2 -2
View File
@@ -9,11 +9,11 @@ set "Namensanfang=TOS-Gruppenstring_"
set "Dateiname=%Namensanfang%!datetime!.txt"
REM Führe die folgende .bat aus mit dem gesetzten Schalter -o um eine Datei mit dem Code zu generieren
CALL %CREMIG_BIN%create_tos_string.bat -o %Dateiname%
CALL %CREMIG_BIN%\create_tos_string.bat -o %Dateiname%
echo Die Datei wurde in %CREMIG_WORK% mit dem Namen: %Dateiname% gespeichert
echo Bitte pflegen sie den Inhalt dieser Datei in die jeweiligen CampiEsterni.ini unter CALC LINK10 ein.
echo:
endlocal
pause
pause
+14
View File
@@ -0,0 +1,14 @@
[ExcludedPartnumbersInBOM]
#============================================================================================================================
# Teilenummern, die in der Migrationsübersicht (u.a. im Baum von CreoMigrate) nicht dargestellt sein sollen
#============================================================================================================================
MA0000008
MA0000005
[ExcludedPartnumbersInSummary]
#============================================================================================================================
# Teilenummern, die in der Zusammenfassung der Migrationsübersicht nicht enthalten sein sollen
# Wenn die Nummer bereits in der Sektion "ExcludedPartnumbersInBOM" enthalten ist, muss sie hier nicht nochmal aufgelistet werden
#============================================================================================================================
+2
View File
@@ -1,4 +1,5 @@
[Farbauswahl]
PANTONE 2175 C
RAL 1004 Goldgelb
RAL 2000 Gelborange
RAL 3002 Kaminrot
@@ -23,6 +24,7 @@ Grün
Blau
siehe Oben
Ocker
Transparent
[FarbeEloxieren]
Rot
+115 -97
View File
@@ -1,8 +1,32 @@
{
"VA": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"1.4301": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"Werkstofftyp": "Stahl"
},
"X5CrNi18-10": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"Werkstofftyp": "Stahl"
},
"1.4301 (X5CrNi18-10)": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"Werkstofftyp": "Stahl"
},
"1.4301f / 59381": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"Werkstofftyp": "Stahl"
},
"X5CrNi 18-10": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"Werkstofftyp": "Stahl"
},
"X5CrNi 18-9": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"Werkstofftyp": "Stahl"
},
"1.4305": {
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"Werkstofftyp": "Stahl"
@@ -15,14 +39,42 @@
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"Werkstofftyp": "Stahl"
},
"X10CrNiS 18-9": {
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"Werkstofftyp": "Stahl"
},
"X8CrNiS 18-9": {
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"Werkstofftyp": "Stahl"
},
"X8CrNiS18-9": {
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"Werkstofftyp": "Stahl"
},
"X12CrNi17 7": {
"Werkstoff": "1.4310 (X10CrNi18-8)",
"Werkstofftyp": "Stahl"
},
"Aluminium": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"AlCuMgPb": {
"Werkstoff": "EN AW-2007 (3.1645)",
"Werkstofftyp": "Aluminium"
},
"3.3206.72": {
"Werkstoff": "EN AW-6063-T66",
"Werkstofftyp": "Aluminium"
},
"AlSiMg 0,5 F25": {
"Werkstoff": "EN AW-6063-T66",
"Werkstofftyp": "Aluminium"
},
"F25": {
"Werkstoff": "EN AW-6063-T66",
"Werkstofftyp": "Aluminium"
},
"AlMgSi0.5": {
"Werkstoff": "EN AW-6060 (3.3206)",
"Werkstofftyp": "Aluminium"
@@ -31,25 +83,37 @@
"Werkstoff": "EN AW-6060 (3.3206)",
"Werkstofftyp": "Aluminium"
},
"AlSiMg 0,5 F22": {
"AlMgSi 0.5": {
"Werkstoff": "EN AW-6060 (3.3206)",
"Werkstofftyp": "Aluminium"
},
"AlMgSi0.5 F22": {
"Werkstoff": "EN AW-6060-T66 (3.3206-T66)",
"Werkstofftyp": "Aluminium"
},
"AlSiMg 0,5 F25": {
"Werkstoff": "EN AW-6063-T66",
"AlSiMg 0,5 F22": {
"Werkstoff": "EN AW-6060-T66 (3.3206-T66)",
"Werkstofftyp": "Aluminium"
},
"AlMg3": {
"Werkstoff": "EN AW-5754 (3.3535)",
"Werkstofftyp": "Aluminium"
},
"St": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"C45": {
"Werkstoff": "1.0503 (C45)",
"Werkstofftyp": "Stahl"
},
"F25": {
"Werkstoff": "EN AW-6063-T66",
"Werkstofftyp": "Aluminium"
"Ck60": {
"Werkstoff": "1.1221 (C60E)",
"Werkstofftyp": "Stahl"
},
"S355JO": {
"Werkstoff": "1.0553 (S355J0)",
"Werkstofftyp": "Stahl"
},
"1.0038 (S235JR)": {
"Werkstoff": "1.0038 (S235JR)",
@@ -63,18 +127,6 @@
"Werkstoff": "1.0038 (S235JR)",
"Werkstofftyp": "Stahl"
},
"S235JRG2C": {
"Werkstoff": "1.0122 (S235JRC)",
"Werkstofftyp": "Stahl"
},
"S235JRG2C+C": {
"Werkstoff": "1.0122 (S235JRC)",
"Werkstofftyp": "Stahl"
},
"S235JR+Cr": {
"Werkstoff": "1.0038+CR (S235JR+CR)",
"Werkstofftyp": "Stahl"
},
"St-37": {
"Werkstoff": "1.0038 (S235JR)",
"Werkstofftyp": "Stahl"
@@ -83,26 +135,35 @@
"Werkstoff": "1.0038 (S235JR)",
"Werkstofftyp": "Stahl"
},
"X5CrNi18-10": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"S235 JR": {
"Werkstoff": "1.0038 (S235JR)",
"Werkstofftyp": "Stahl"
},
"X8CrNiS 18-9": {
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"S235JR galv. verzinkt Fe/Zn 8-15 B": {
"Werkstoff": "1.0038 (S235JR)",
"Werkstofftyp": "Stahl",
"Oberfläche": "verzinken"
},
"S235JR+Cr": {
"Werkstoff": "1.0038+CR (S235JR+CR)",
"Werkstofftyp": "Stahl"
},
"X8CrNiS18-9": {
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"S235JRG2C": {
"Werkstoff": "1.0122 (S235JRC)",
"Werkstofftyp": "Stahl"
},
"1.4301 (X5CrNi18-10)": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"S235JRG2C+C": {
"Werkstoff": "1.0122 (S235JRC)",
"Werkstofftyp": "Stahl"
},
"X5CrNi 18-10": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"S235JRC": {
"Werkstoff": "1.0122 (S235JRC)",
"Werkstofftyp": "Stahl"
},
"Kunststo": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"PA6 GF30": {
"Werkstoff": "PA 6 GF 30",
"Werkstofftyp": "Kunst-/ Verbundstoff",
@@ -130,14 +191,15 @@
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)"
},
"S235JR galv. verzinkt Fe/Zn 8-15 B": {
"Werkstoff": "1.0038 (S235JR)",
"Werkstofftyp": "Stahl",
"Oberfläche": "verzinken"
"PA 66 GF 50": {
"Werkstoff": "PA 66 GF 50",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)"
},
"VA": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
"PC GF 10": {
"Werkstoff": "PC GF 10",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)"
},
"POM schw": {
"Werkstoff": "POM",
@@ -151,12 +213,24 @@
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Schwarz"
},
"POM sch": {
"Werkstoff": "POM",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Schwarz"
},
"POM weiß": {
"Werkstoff": "POM",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Weiß"
},
"POM natur": {
"Werkstoff": "POM",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Natur"
},
"POM": {
"Werkstoff": "POM",
"Werkstofftyp": "Kunst-/ Verbundstoff",
@@ -172,11 +246,13 @@
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)"
},
"-": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
"PUR D44 ": {
"Werkstoff": "Bitte individuell wählen!",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Bitte individuell wählen!"
},
"St": {
"-": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
@@ -184,10 +260,6 @@
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"S235 JR": {
"Werkstoff": "1.0038 (S235JR)",
"Werkstofftyp": "Stahl"
},
"-----": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
@@ -216,62 +288,8 @@
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"AlCuMgPb": {
"Werkstoff": "EN AW-2007 (3.1645)",
"Werkstofftyp": "Aluminium"
},
"AlMgSi 0.5": {
"Werkstoff": "EN AW-6060 (3.3206)",
"Werkstofftyp": "Aluminium"
},
"Kunststo": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"Federsta": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"S355JO": {
"Werkstoff": "1.0553 (S355J0)",
"Werkstofftyp": "Stahl"
},
"X10CrNiS 18-9": {
"Werkstoff": "1.4305 (X8CrNiS18-9)",
"Werkstofftyp": "Stahl"
},
"POM natur": {
"Werkstoff": "POM",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Natur"
},
"POM sch": {
"Werkstoff": "POM",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Schwarz"
},
"1.4301f / 59381": {
"Werkstoff": "1.4301 (X5CrNi18-10)",
"Werkstofftyp": "Stahl"
},
"PUR D44 ": {
"Werkstoff": "Bitte individuell wählen!",
"Werkstofftyp": "Kunst-/ Verbundstoff",
"Oberfläche": "Masterbatch (Grundfarbe)",
"Farbe": "Bitte individuell wählen!"
},
"Aluminium": {
"Werkstoff": "false",
"Message": "Bitte individuell wählen!"
},
"AlMgSi0.5 F22": {
"Werkstoff": "EN AW-6060-T66 (3.3206-T66)",
"Werkstofftyp": "Aluminium"
},
"S235JRC": {
"Werkstoff": "1.0122 (S235JRC)",
"Werkstofftyp": "Stahl"
}
}
+11 -2
View File
@@ -238,7 +238,7 @@
"43": "Sensorik",
"44": "Kabel",
"45": "Installationsmaterial",
"46": "Frei",
"46": "Divers",
"47": "Frei",
"48": "Frei",
"49": "Sonstiges"
@@ -250,6 +250,7 @@
"43": "TOS_Automation_Sensorik",
"44": "TOS_Kabel",
"45": "TOS_Installation",
"46": "TOS_Auto_Divers",
"49": "TOS_Automation_Sonstiges"
}
},
@@ -366,9 +367,17 @@
},
"reference-ids": {}
},
"TOS_Auto_Divers": {
"TOS-level": 3,
"_comment" : "46 - Automation -> Divers",
"children": {
"460": "Auswahl Lichttaster"
},
"reference-ids": {}
},
"TOS_Automation_Sonstiges": {
"TOS-level": 3,
"_comment" : "45 - Automation -> Sonstiges",
"_comment" : "49 - Automation -> Sonstiges",
"children": {
"490": "Beschriftung",
"491": "Schüttgut"
+9
View File
@@ -24,6 +24,7 @@ Schwermetall
1.0715+C (11SMn30+C)
1.0917 (DX51D+Z)
1.1191 (C45E)
1.1221 (C60E)
1.2842 (90MnCrV8)
1.4035 (X45CrS13)
1.4301 (X5CrNi18-10)
@@ -31,6 +32,7 @@ Schwermetall
1.4310 (X10CrNi18-8)
1.5023 (38Si7)
1.7131 (16MnCr5)
1.7225 (42CrMo4)
siehe Hersteller
siehe Stückliste
@@ -54,6 +56,7 @@ CR/SBR-A65
Hostaform 13031 MB1.5
Irogran A87
Murtfeldt "S" 1000
Murtfeldt Original "S" grün
PA
PA 2200
PA 2200 SLS
@@ -63,13 +66,16 @@ PA 6 GF 30
PA 66
PA 66 GF 25
PA 66 GF 30
PA 66 GF 50
PA GF 25
PBT
PC
PC GF 10
PE
PE-HD
PE-LD
PE-UHMW
PE-UHMW antistatisch
POM
POM-C
POM-H
@@ -83,13 +89,16 @@ PUR
PUR - 95 Shore A
PUR D44 - 80 Shore A
PVC
PVC-P (weich)
PVC-U
siehe Hersteller
siehe Stückliste
TPU
TPU - 95 Shore A
TPU - 98 Shore A
[Schwermetall]
CC493K - GC (CuSn7Zn4Pb7 - C - GC)
CW459K (CuSn8P)
CW614N (CuZn39Pb3)
CW708R (CuZn31Si)
+6 -6
View File
@@ -8,10 +8,10 @@ Dispoinaktiv=ACHTUNG BG mit dispoinaktiven Stücklistenpositionen. Es wird keine
[ComboTextblöcke]
KleinbuchstabeTruemmer=ACHTUNG Index in Sivas mit Kleinbuchstaben + Trümmerbaugruppe. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
KleinbuchstabeBlacklist=ACHTUNG Index in Sivas mit Kleinbuchstaben + BG mit Beistellungen. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
KleinbuchstabeBlacklistBG=ACHTUNG Index in Sivas mit Kleinbuchstaben + BG mit Beistellungen. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
KleinbuchstabeWerkstoffdiskrepanz=ACHTUNG Index in Sivas mit Kleinbuchstaben + Materialdiskrepanz. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
KleinbuchstabeDispoinaktiv=ACHTUNG Index in Sivas mit Kleinbuchstaben + BG mit Dispoinaktiven Stücklistenpositionen. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
TruemmerBlacklist=ACHTUNG Trümmerbaugruppe + BG mit Beistellungen. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
TruemmerBlacklistBG=ACHTUNG Trümmerbaugruppe + BG mit Beistellungen. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
TruemmerWerkstoffdiskrepanz=ACHTUNG Trümmerbaugruppe + Materialdiskrepanz. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
TruemmerDispoinaktiv=ACHTUNG Trümmerbaugruppe + BG mit dispoinaktiven Stücklistenpositionen. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
KleinbuchstabeTruemmerBlacklist=ACHTUNG Index in Sivas mit Kleinbuchstaben + Trümmerbaugruppe + BG mit Beistellungen. Ausführliche Informationen zu erforderlichen Tätigkeiten / potentiellen Fehlern vgl. Konstruktionsrichtlinien
@@ -20,8 +20,8 @@ BlacklistHZDispoinaktiv=ACHTUNG Bearbeitetes Halbzeug mit beigestellten und disp
BlacklistBGDispoinaktiv=ACHTUNG BG mit beigestellten und dispoinaktiven Stücklistenpositionen. Es wird keine Stückliste übergeben. Diese muss bei einer Änderung händisch angelegt und archiviert werden. Hierfür die Verantwortlichen für den Teilestamm informieren.
;Nachfolgende Kombinationen können nicht auftreten:
;- BlacklistWerkstoffdiskrepanz
;- BlacklistBGWerkstoffdiskrepanz
;- KleinbuchstabeTruemmerWerkstoffdiskrepanz
;- KleinbuchstabeBlacklistWerkstoffdiskrepanz
;- TruemmerBlacklistWerkstoffdiskrepanz=
;- KleinbuchstabeTruemmerBlacklistWerkstoffdiskrepanz=
;- KleinbuchstabeBlacklistBGWerkstoffdiskrepanz
;- TruemmerBlacklistBGWerkstoffdiskrepanz
;- KleinbuchstabeTruemmerBlacklistBGWerkstoffdiskrepanz
+9 -3
View File
@@ -1,4 +1,10 @@
[Fenster]
CanvasBreite=1080
CanvasHoehe=1490
Dimension=1080x1490
#Büro-Auflösung (hochformat)
CanvasBreite=1080
CanvasHoehe=1490
Dimension=1080x1490
# Homeoffice Auflösung (querformat)
# CanvasBreite=1000
# CanvasHoehe=1350
# Dimension=1100x1400
+1 -1
View File
@@ -28,7 +28,7 @@ def build_gui(model, configs, env, sivasid=None, uselocal=True):
controller.set_env(env)
controller.set_config(configs)
if sivasid:
controller.get_csv_files_of_asm(sivasid, uselocal)
controller.get_csv_files_of_asm(sivasid, load_local_bom=uselocal, load_local_teilestamm=uselocal, load_local_rd_ids=uselocal)
logic = GuiLogic(model, configs)
model.add_logic(logic)
+118 -56
View File
@@ -104,6 +104,7 @@ class CadItem(DataModel):
# ist also eigentlich kein Teil der gui
"truemmer",
# Felder der Comboboxen. benannt wie deren callbacks
"cb_datenbank_neu",
"cb_pdm_kategorie_neu",
"cb_halbzeug_neu",
"cb_halbzeugoberflaeche",
@@ -389,7 +390,7 @@ class CadItem(DataModel):
text_neu = None
# semikolon müssen rausfliegen
if re.search(';', text):
text_neu = re.sub(r"\s*;\s*", " /", text, flags=re.I)
text_neu = re.sub(r"\s*;\s*", " / ", text, flags=re.I)
if author:
if text_neu:
text_neu = '{aenderungstext} (von {author})'.format(aenderungstext=text_neu, author=author)
@@ -477,7 +478,7 @@ class CadItem(DataModel):
if "freigabevermerk_sivas" in translated_data:
text = translated_data["freigabevermerk_sivas"]
if re.search(';', text):
translated_data["freigabevermerk_neu"] = re.sub(r"\s*;\s*", " /", text, flags=re.I)
translated_data["freigabevermerk_neu"] = re.sub(r"\s*;\s*", " / ", text, flags=re.I)
# Spezial Hersteller: aus "80312 - Rehau AG & Co." wird "Rehau AG & Co."
if "hersteller_sivas" in translated_data:
@@ -537,7 +538,7 @@ class CadItem(DataModel):
"""lädt die SIVAS csv Datei in die Felder für die Oberfläche """
if not os.path.exists(path):
return None
df = pd.read_csv(path, sep=';', converters={"Version":str})
df = pd.read_csv(path, sep=';', converters={"Version":str, "Werkstoff": str})
ret = dict()
# baue aus der gegebenen Zeile ein dict
d = df.to_dict()
@@ -650,6 +651,20 @@ class CadItem(DataModel):
if not self.get_gui_data(key_sivas):
if not self.get_gui_data(key_neu):
missing.append(self.key2label[key_neu])
def get_key_with_data(self, key_sivas: str, key_neu: str, key_list: list = []):
''' Vergleicht Inhalt von SIVAS-Feld und überschriebenem Feld:
- Sind nur das überschriebene oder das überschriebe und das SIVAS-Feld befüllt, wird das überschriebene zurückgegeben
- Ist nur das SIVAS-Feld befüllt, wird das SIVAS-Feld zurückgegeben
- Ist keines von beiden Feldern befüllt, wird None zurückgegeben '''
if not self.get_gui_data(key_neu):
if not self.get_gui_data(key_sivas):
return
key_list.append(key_sivas)
else:
key_list.append(key_neu)
return key_list
def check_pflichtfelder(self):
"""Allgemeine Prüfungen:
@@ -754,13 +769,36 @@ class CadItem(DataModel):
return (True, {})
def check_improper_chars(self, invalid_chars):
""" überprüft ob inder Oberfläche nicht erlaubte Zeichen eingetragen wurden"""
wrongchars = dict()
for key in self.key2label:
if key == "model3d_neu": # 3D Teil Pfad ausnehmen
continue
if key == "material_sivas": # Wert in GUI-Spalte "SIVAS-Werte in Feld "Material" nicht berücksichtigen
continue
"""
Überprüft, ob in den Eingabefeldern der GUI unzulässige Zeichen eingetragen wurden.
Die unzulässigen Zeichen sind in cfg/InvalidCharacters.txt aufgelistet.
Ursprung dieser Liste ist //ruledesigner/RDF_Daten/Konfigurator/CustomVaulting/ERP_Item.ini -> Option "InvalidCharacters"
"""
wrongchars = {}
keys_to_check = []
# Bei diesen Feldern soll geprüft werden, ob sie einen Inhalt aufweisen. Wenn nicht, muss auch nicht geprüft werden, ob sich darin unzulässige Zeichen befinden.
check_key_for_data = ["halbzeugnummer_neu", "halbzeuglaenge_neu", "zusatzinfos_auto_text", "zusatzinfos_bendef_text"]
keys_with_data = list(filter(lambda key: self.get_gui_data(key) != "", check_key_for_data))
keys_to_check + keys_with_data
# Mapping SIVAS-Felder und Überschriebene Felder. Hier soll jeweils geprüft werden, welches der beiden einen Wert enthält.
# Wenn beide Felder einen Wert enthalten soll nur das überschriebene Feld nach unzulässigen Zeichen durchsucht werden, ansonsten das SIVAS-Feld
compare_keys_for_data = { "bezeichnung1_sivas": "bezeichnung1_neu",
"tosgruppe_sivas": "tosgruppe_neu",
"hersteller_sivas": "hersteller_neu",
"ersatzfuer_sivas": "ersatzfuer_neu",
"ersetztdurch_sivas": "ersetztdurch_neu",
"herstteilenr_sivas": "herstteilenr_neu",
"material_sivas": "material_neu",
"aenderungsindex_sivas": "aenderungsindex_neu",
"aenderungstext_sivas": "aenderungstext_neu",
"freigabevermerk_sivas": "freigabevermerk_neu",
}
for sivas_key, neu_key in compare_keys_for_data.items():
self.get_key_with_data(sivas_key, neu_key, keys_to_check)
for key in keys_to_check:
content = self.get_gui_data(key)
for c in invalid_chars:
if c in content:
@@ -787,18 +825,14 @@ class CadItem(DataModel):
def check_content(self):
"""Überprüfe die Inhalte nach Längen, Zeichenketten und korrekten Kombinationen der Eingaben"""
# Prüfung Bezeichnungslänge (darf max. 50 Zeichen betragen)
pdm_kategorie_neu = self.get_gui_data("pdm_kategorie_neu")
content = self.get_content('bezeichnung1_sivas', 'bezeichnung1_neu')
if pdm_kategorie_neu == "Baugruppe" or pdm_kategorie_neu == "Schweissbaugruppe":
if len(content) > 45:
self.message_error('Bezeichnung1 zu gross', 'bezeichnung_zu_gross_45')
return False
else:
if len(content) > 50:
self.message_error('Bezeichnung1 zu gross', 'bezeichnung_zu_gross_50')
return False
if len(content) > 50:
self.message_error('Bezeichnung1 zu gross', 'bezeichnung_zu_gross_50')
return False
# Rüstcode und Sögeliste
# Rüstcode und Sägeliste
ruestcode = self.get_gui_data("ruestcode_sivas")
halbzeugOhneSaege_neu = self.get_gui_data("halbzeugOhneSaege_neu")
if ruestcode == "SRC Standard Rüstcode" and halbzeugOhneSaege_neu == "Sägeliste nicht benötigt":
@@ -1135,9 +1169,6 @@ class CadItem(DataModel):
bezeichnung1 = self.get_content('bezeichnung1_sivas', 'bezeichnung1_neu')
title = bezeichnung1
data["KategorieNE"] = pdm_kategorie_neu
# Bezeichnung um "- BG" ergänzen
if pdm_kategorie_neu == "Baugruppe" or pdm_kategorie_neu == "Schweissbaugruppe":
title = bezeichnung1 + " - BG"
data["Title"] = title
# Aus Halbzeug
@@ -1181,7 +1212,7 @@ class CadItem(DataModel):
bezeichnung4 = ""
if pdm_kategorie_neu == "Baugruppe" or pdm_kategorie_neu == "Schweissbaugruppe":
bezeichnung4 = " - BG"
bezeichnung4 = "- BG"
data["Bezeichnung4"] = bezeichnung4
# Author
@@ -1474,7 +1505,9 @@ class GuiLogic():
"""gibt ein dict zurück, welches neu im Modell gesetzt wird"""
ret = dict()
if key == 'cb_pdm_kategorie_neu':
if key == 'cb_datenbank_neu':
ret = self.cb_datenbank_neu(value)
elif key == 'cb_pdm_kategorie_neu':
ret = self.cb_pdm_kategorie_neu(value)
elif key == 'cb_neuaufbau':
ret = self.cb_neuaufbau(value)
@@ -1542,7 +1575,9 @@ class GuiLogic():
wegen der Abhängigkeiten der Comboboxen anschliessend folgen sollen
"""
ret = list()
if key == 'cb_pdm_kategorie_neu':
if key == 'cb_datenbank_neu':
ret = []
elif key == 'cb_pdm_kategorie_neu':
ret = ['cb_neuaufbau', 'cb_aus_halbzeug_neu', 'cb_3Dmodel_neu', 'cb_halbzeugOhneSaege_neu', 'cb_ruestcode_neu',
'cb_fertigungsart_neu', 'cb_allgemeintoleranz_neu', 'cb_fosGruppe_neu', 'cb_kalkulationsart_neu', 'cb_beschaffungsart_neu',
'cb_mengeneinheit_neu', 'cb_halbzeug_neu', 'cb_dispogruppe_neu', 'cb_dispolaufgruppe_neu', 'cb_hersteller_neu', 'cb_werkstoffdiskrepanz',
@@ -1616,6 +1651,14 @@ class GuiLogic():
raise Exception("this callback does not exist "+key)
return ret
def cb_datenbank_neu(self, selected):
ret = dict()
if selected is None:
ret['datenbank'] = "Produktiv" # Default
else:
ret['datenbank'] = selected
return ret
def cb_pdm_kategorie_neu(self, selected):
"""setzt die Dropdownliste mit
<undefiniert>, Baugruppe, Einzelteil, Kaufteil, Schweissbaugruppe
@@ -2522,6 +2565,7 @@ class GuiLogic():
pdm_kategorie_neu = self._model.get_gui_data("pdm_kategorie_neu")
version_sivas = str(self._model.get_gui_data("version_sivas"))
freigabe_sivas = self._model.get_gui_data("freigabevermerk_sivas")
freigabe_neu = self._model.get_gui_data("freigabevermerk_neu") # Brauchen wir für den Fall, dass der SIVAS-Freigabevermerk beim Einlesen bereits transformiert wurden, da er ein Semikolon enthielt.
if selected is None:
# Default Vorbelegung
@@ -2534,59 +2578,77 @@ class GuiLogic():
default = 'Anpassung an neues Indexsystem, Zeichnung mit Versionsnummer {version} weiterhin gültig'.format(version=version_sivas)
else:
default = '-'
else:
if freigabe_neu is not None:
default = freigabe_neu
ret['freigabevermerk_neu'] = default
else:
ret['freigabevermerk_neu'] = selected
return ret
def cb_zusatzinfos_auto_text(self, selected):
ret = dict()
ret = dict()
aenderungsindex_sivas = self._model.get_gui_data("aenderungsindex_sivas")
truemmer = self._model.get_gui_data("truemmer")
ruestcode = self._model.get_gui_data("ruestcode_sivas")
teilenummer = str(self._model.get_gui_data("teilenummer"))
werkstoffdiskr_neu = self._model.get_gui_data("werkstoffdiskr_neu")
aus_halbzeug_neu = self._model.get_gui_data("aus_halbzeug_neu")
aus_halbzeug = self._model.get_gui_data("aus_halbzeug_neu")
active_id = self._model.get_current_item_id()
children_of_active_id = self._model.get_all_children_of_id(active_id)
# Ermittlung, ob sich in der Stückliste der ausgewählten Teilenummer Beistellungsteile oder nicht-dispoaktive Teile befinden
beistellungen = False
dispoinaktiv = False
if children_of_active_id is not None:
all_beistellungen = self._model.get_bom_properties("Lieferantenbeistellung")
all_dispoaktiv = self._model.get_bom_properties("Dispoaktiv")
active_id = self._model.get_current_item_id()
children_of_active_id = self._model.get_all_children_of_id(active_id)
for child in children_of_active_id:
if all_beistellungen[child] == "True":
beistellungen = True
if all_dispoaktiv[child] == "False":
dispoinaktiv = True
# Liest Optionsnamen aus 'Zusatzinformationen.cfg'
cfg_single_options = dict.fromkeys(self._config.Zusatz.get_section_options("EinzelTextblöcke"), "EinzelTextblöcke")
cfg_combo_options = dict.fromkeys(self._config.Zusatz.get_section_options("ComboTextblöcke"), "ComboTextblöcke")
all_cfg_options = cfg_single_options | cfg_combo_options
if selected is None:
text = ''
try:
blacklist = self._config.get_blacklist()
self._config.get_blacklist()
except:
self._model.message_error('Blacklist nicht vorhanden', 'blacklist_not_exist')
return {}
# Prüft, ob einer der nachfolgenden Fälle auf die Teilenummer zutrifft, die im Moment migriert wird
matches = []
if aenderungsindex_sivas and aenderungsindex_sivas.islower():
if truemmer == 'J' or ruestcode is None:
text = self._config.Zusatz.get('ComboTextblöcke', 'KleinbuchstabeTruemmer')
if teilenummer in blacklist:
text = self._config.Zusatz.get('ComboTextblöcke', 'KleinbuchstabeTruemmerBlacklist')
elif teilenummer in blacklist:
text = self._config.Zusatz.get('ComboTextblöcke', 'KleinbuchstabeBlacklist')
elif werkstoffdiskr_neu == True:
text = self._config.Zusatz.get('ComboTextblöcke', 'KleinbuchstabeWerkstoffdiskrepanz')
matches.append("Kleinbuchstabe")
if truemmer == 'J' or ruestcode is None:
matches.append("Truemmer")
if werkstoffdiskr_neu:
matches.append("Werkstoffdiskrepanz")
if dispoinaktiv:
matches.append("Dispoinaktiv")
if beistellungen:
if aus_halbzeug:
matches.append("BlacklistHZ")
else:
text = self._config.Zusatz.get('EinzelTextblöcke', 'Kleinbuchstabe')
text = text.format(aenderungsindex=aenderungsindex_sivas)
elif truemmer == 'J' or ruestcode is None:
if teilenummer in blacklist:
text = self._config.Zusatz.get('ComboTextblöcke', 'TruemmerBlacklist')
elif werkstoffdiskr_neu == True:
text = self._config.Zusatz.get('ComboTextblöcke', 'TruemmerWerkstoffdiskrepanz')
else:
text = self._config.Zusatz.get('EinzelTextblöcke', 'Truemmer')
elif teilenummer in blacklist:
if aus_halbzeug_neu:
text = self._config.Zusatz.get('EinzelTextblöcke', 'BlacklistHZ')
else:
text = self._config.Zusatz.get('EinzelTextblöcke', 'BlacklistBG')
elif werkstoffdiskr_neu == True:
text = self._config.Zusatz.get('EinzelTextblöcke', 'Werkstoffdiskrepanz')
else:
pass
matches.append("BlacklistBG")
# Sucht sich die zutreffende Option aus der "Zusatzinformationen.cfg", die mit den gefundenen Fällen übereinstimmen
# heraus und gibt den Optionstext zurück. Bei fehlender Übereinstimmung wird kein Text geschrieben
if len(matches) > 0:
match_len = len("".join(matches))
for option in all_cfg_options:
if all(match in option for match in matches) and len(option) == match_len:
text = self._config.Zusatz.get(all_cfg_options[option], option)
break
ret['zusatzinfos_auto_text'] = text
else:
+196 -113
View File
@@ -1,7 +1,8 @@
from manage_configs import SivasAssemblyData, RuleDesignerDatabaseConfig, MigrationStatus, TeilestammMetadata
from manage_configs import SivasBOM, RuleDesignerDatabaseConfig, MigrationStatus, SivasTeilestamm, BOMConfig
import argparse
import os
import openpyxl
from itertools import zip_longest
"""
Dieses Programm kann:
@@ -10,6 +11,23 @@ Dieses Programm kann:
- die oben Darstellung in der Excel-Datei als Baum in der Konsole ausgeben
"""
class GetExcludedIDsFromCFG(argparse.Action):
def __call__(self, parser, namespace, values, option_string = None):
bom_config = BOMConfig(os.path.dirname(values[0]), os.path.basename(values[0]))
excluded_ids = bom_config.get_excluded_ids(values[1])
setattr(namespace, self.dest, excluded_ids)
class LStrip(argparse.Action):
def __call__(self, parser, namespace, values, option_string = None):
setattr(namespace, self.dest, values.lstrip())
class LocateSQLCredentials(argparse.Action):
def __call__(self, parser, namespace, values, option_string = None):
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!")
setattr(namespace, self.dest, True)
class ExcelExport:
def __init__(self,
@@ -25,8 +43,8 @@ class ExcelExport:
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.asm_data = SivasBOM(asm_json_dir, asm_json_filename, load_local_asm_json)
self.asm_metadata = SivasTeilestamm(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
@@ -75,7 +93,7 @@ class ExcelExport:
sorting = self._create_sorting_string()
migrated_ids = self.migration_status.ids_in_rd
child_descriptions:dict = self.asm_data.get_bom_property_values("Bezeichnung")
child_descriptions:dict = self.asm_data.get_prop_val_for_all_ids("Bezeichnung")
parent_description:str = self.asm_metadata.get_property_values("Bezeichnung1")
descriptions = child_descriptions.copy()
@@ -168,21 +186,26 @@ class Treeview:
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):
asm_id:str,
asm_json_dir:str,
asm_json_filename:str,
asm_csv_dir:str,
asm_csv_filename:str,
rd_dbase_dir:str,
rd_dbase_filename:bool=False,
load_local_asm_json:bool=False,
load_local_asm_csv:bool=False,
load_local_rd_dbase:bool=False,
no_blue_ids:bool=False,
excluded_bom_ids:list=None,
excluded_summary_ids:list=None):
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.asm_bom = SivasBOM(asm_id, asm_json_dir, asm_json_filename, load_local_asm_json, excluded_bom_ids)
self.asm_teilestamm = SivasTeilestamm(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.all_excluded_summary_ids = excluded_summary_ids
self._excluded_summary_ids_in_bom = None
self.no_blue_ids = no_blue_ids
self._migration_status = None
self._longest_entry = None
@@ -190,7 +213,7 @@ class Treeview:
@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)
self._migration_status = MigrationStatus(self.asm_bom.child_ids, self.rd_data.ids)
return self._migration_status
@property
@@ -204,64 +227,85 @@ class Treeview:
if self._longest_entry is None:
self._longest_entry = (self.asm_bom_data.max_bom_depth) * len(Treeview.prefix_types["PIPE"]) \
self._longest_entry = (self.asm_bom.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(self.asm_bom.longest_child_id) \
+ len(Treeview.console_colors["RESET"]) \
+ padding_after_id
return self._longest_entry
def generate_tree(self):
self._tree_header()
self._tree_body()
self._tree_summary()
def _tree_header(self):
def _add_asm_id_to_bom(self):
"""Standardmäßig ist die oberste Baugruppennummer kein Bestandteil der JSON-Datei, in der die Baugruppenstruktur aufgelistet ist und
die über das Programm 'Sivas2Json' von Hr. Jakob generiert wird. Damit der Baum die Bezeichnung der obersten Baugruppennummer im
Baum angezeigt wird ist es jedoch erforderlich, dass die Nummer mitsamt ihrer Bezeichnung in der JSON enthalten ist"""
metadata = {"Bezeichnung": self.asm_teilestamm.get_property_values("Bezeichnung1"),
"Parent": None,
"Position": "10/0"}
"""Prints root number (number for which the user wants to see the tree) to the console"""
self.asm_bom.extend_asm_bom(self.asm_id, metadata)
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")
def generate_tree(self):
self._add_asm_id_to_bom()
self._print_tree()
self._print_summary()
print(f"{text_before_description}{description}")
def _tree_body(self, parent_id=None, prefix=""):
def _print_tree(self, parent_id=None, prefix=""):
"""Prints the body (all partnumbers except root/main-partnumber) of the tree to the console"""
"""Gibt die Baugruppenstruktur in Form eines Baumes in der Konsole aus"""
if parent_id is None:
parent_id = self.asm_id
parent_children = self.asm_bom_data.parent_children
parent_children = self.asm_bom.parent_children
children_ids = parent_children[parent_id]
for index, child_id in enumerate(children_ids):
for i, id in enumerate(children_ids):
is_last_assembly_child = self._last_child_check(index, children_ids)
last_child = self._last_child_check(i, children_ids)
connector_symbol = self._set_connector_symbol(id, last_child)
background_color = self._set_background_color(id)
connector_symbol = self._set_connector_symbol(is_last_assembly_child)
partnumber_background_color = self._set_text_background_color(child_id)
self._print_entry(id, prefix, connector_symbol, background_color)
self._print_entry(child_id, prefix, connector_symbol, partnumber_background_color)
if id in parent_children:
next_prefix = self._set_next_prefix(prefix, id, last_child)
self._print_tree(parent_id=id, prefix=next_prefix)
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 _remove_ids_from_summary_amount(self, ids: list) -> list:
"""
Reduziert die Anzahl in der Zusammenfassung (z.B. der nicht migrierten Teilenummern),
um die Anzahl an Teilenummern, die vom User aus der Zusammenfassung ausgeschlossen wurden
"""
def _tree_summary(self):
if self.all_excluded_summary_ids is None:
return ids
return list(filter(lambda id: id not in self.excluded_summary_ids_in_bom, ids))
amount_in_rd = len(self.migration_status.ids_in_rd)
amount_pending = len(self.migration_status.ids_pending)
@property
def excluded_summary_ids_in_bom(self) -> list:
""" Gibt alle Teilenummern zurück, die im Baum vorliegen, aber nicht in der Anzahl der Zusammenfassung enthalten sein sollen"""
if self._excluded_summary_ids_in_bom is None:
self._excluded_summary_ids_in_bom = list(filter(lambda id: id in self.asm_bom.bom_data, self.all_excluded_summary_ids))
return self._excluded_summary_ids_in_bom
def _print_summary(self):
ids_in_rd = self.migration_status.ids_in_rd
ids_pending = self.migration_status.ids_pending
amount_in_rd = len(self._remove_ids_from_summary_amount(ids_in_rd))
amount_pending = len(self._remove_ids_from_summary_amount(ids_pending))
if not self.no_blue_ids:
amount_in_work = len(self.migration_status.ids_in_progress)
ids_in_progress = self.migration_status.ids_in_progress
amount_in_work = len(self._remove_ids_from_summary_amount(ids_in_progress))
amount_released = amount_in_rd - amount_in_work
progress = int(amount_in_rd / len(self.asm_bom_data.child_ids) * 100)
child_ids = self.asm_bom.child_ids
progress = int(amount_in_rd / len(self._remove_ids_from_summary_amount(child_ids)) * 100)
column_width = 5
border_padding = "".ljust(column_width, "")
@@ -286,49 +330,78 @@ class Treeview:
│ Auswertung {empty_padding}
├─┬───────────────────────────────────────┬─{border_padding}
"""
progress = f"""
progress_segment = f"""
├─┴───────────────────────────────────────┼─{border_padding}
│ Migrationsfortschritt │ {f"{progress}%".ljust(column_width)}
╰─────────────────────────────────────────┴─{border_padding}
"""
print(header + green_entry + blue_entry + red_entry + progress)
bottom_row = f"╰─────────────────────────────────────────┴─{border_padding}"
exclusion_description = ""
excluded_id_list = ""
if self.asm_bom.excluded_ids_in_bom or self.excluded_summary_ids_in_bom:
exclusion_description = f"""├─────────────────────────────────────────┴─{border_padding}
{empty_padding}
├───────────────────────────────────────────{border_padding}
│ Ausgeschlossene Teilenummern {empty_padding}
│──────────────────────┬────────────────────{border_padding}
│ Baum │ Auswertung {empty_padding}
├──────────────────────┼────────────────────{border_padding}
"""
for bom_id, smry_id in zip_longest(self.asm_bom.excluded_ids_in_bom, self.excluded_summary_ids_in_bom, fillvalue=9*" "):
excluded_id_list += f"{ bom_id}{smry_id} {empty_padding}\n"
bottom_row = f"╰──────────────────────┴────────────────────{border_padding}"
print(header + green_entry + blue_entry + red_entry + progress_segment + exclusion_description + excluded_id_list + bottom_row)
def _print_entry(self, child_id, prefix, connector, color):
"""Eintrag in die Konsole drucken"""
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]
description = self.asm_bom.get_prop_val_for_single_id(child_id, "Bezeichnung")
print(f"{text_before_description}{description}")
def _last_child_check(self, child_index, children):
def _last_child_check(self, child_idx, children_ids):
"""Prüft, ob aktuelle ID das letzte Kind der Baugruppe ist"""
return True if child_index == len(children) - 1 else False
return True if child_idx == len(children_ids) - 1 else False
def _set_connector_symbol(self, child_is_last_child):
def _set_connector_symbol(self, child_id, last_child):
"""Legt Verbindungssymbol für aktuellen Eintrag im Baum fest"""
return Treeview.connector_symbols["ELBOW"] if child_is_last_child else Treeview.connector_symbols["T"]
if child_id == self.asm_id: # Oberste Baugruppennummer wird immer ohne Verbindersymbol dargestellt
return ""
if not last_child:
return Treeview.connector_symbols["T"]
return Treeview.connector_symbols["ELBOW"]
def _prefix(self, prefix, child_is_last_child):
def _set_next_prefix(self, prefix, child_id, last_child):
"""Erstellt für jeden Eintrag im Baum einen Präfix (Teil vor dem Verbindersymbol) fest"""
"""Legt den Teil VOR dem Verbindersymbol für den nächsten Eintrag im Baum fest"""
prefix += Treeview.prefix_types["EMPTY"] if child_is_last_child else Treeview.prefix_types["PIPE"]
if child_id == self.asm_id: # Erste Stücklistenposition der obersten Baugruppennummer hat nie Präfix
return ""
prefix += Treeview.prefix_types["EMPTY"] if last_child else Treeview.prefix_types["PIPE"]
return prefix
def _set_text_background_color(self, id):
def _set_background_color(self, id):
"""Legt Hintergrundfarbe der Teilenummer basierend auf ihrem Migrationsstatus fest:
@@ -345,59 +418,69 @@ class Treeview:
return Treeview.console_colors["GREEN"]
if __name__ == '__main__':
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='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')
def main():
args = parser.parse_args()
parser = argparse.ArgumentParser(description='Programm gibt Auskunft über den aktuellen Migrationsstand einer SIVAS-Teilenummer',
prog='compare_lists',
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=50)) # Hilfetext auch bei längeren Argumenten in gleicher Zeile wie Argument darstellen
asm_id = args.sivasid.lstrip()
asm_bom_filename = f'{asm_id}.json'
asm_metadata_filename = f'{asm_id}.csv'
# Custom Actions
parser.register("action", "GetIDsFromCFG", GetExcludedIDsFromCFG)
parser.register("action", "lstrip", LStrip)
parser.register("action", "LocateSQLCredentials", LocateSQLCredentials)
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
parser.add_argument('sivas_id', action='lstrip', help='Teilenummer, für die Migrationsüberblick generiert werden soll.', metavar="SIVAS-Teilenummer")
parser.add_argument('output_type', choices=["tree", "excel"], help="'tree' = Darstellung des Überblicks als Baum in der Konsole. 'excel' = Export des Überblicks als Excel")
parser.add_argument('--rd_ids_filepath', action='store', default=os.path.join(os.environ.get("CREMIG_DATA"), "RD_Teilenummern.csv"), help='Verzeichnis der CSV-Datei mit den RuleDesigner-Nummern. Default: $CREMIG_DATA', metavar="filepath")
parser.add_argument('--local_rd_ids', action='store_true', default=False, help='Lokales laden der CSV mit den RuleDesigner-Nummern (RD-Teilenummer.csv) statt Kopieren von Server')
parser.add_argument('--bom_filedir', action='store', default=os.environ.get("CREMIG_DATA"),help='Verzeichnis der JSON-Datei mit den Stücklisteninformationen. Default: $CREMIG_DATA', metavar='dir')
parser.add_argument('--local_bom', action='store_true', default=False, help='Lokales laden der JSON mit den Stücklisteninformationen statt Generierung')
parser.add_argument('--teilestamm_filedir', action='store', default=os.environ.get("CREMIG_DATA"), help='Verzeichnis der CSV mit den SIVAS-Teilestamminformationen. Default: $CREMIG_DATA', metavar='dir')
parser.add_argument('--local_teilestamm', action='store_true', default=False, help='Lokales Laden der CSV mit den Teilestamminformationen statt Generierung')
parser.add_argument('--no_blue_ids', action="store_true", default=False, help='Nummern, die in RD vorliegen, aber nicht freigegeben sind, gleich wie freigegeben Nummern einfärben')
if args.excel:
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)
exclude_from_overview = parser.add_mutually_exclusive_group()
exclude_from_overview.add_argument('--rm_bom_ids_str', action="store", nargs="*", default=None, dest="excluded_bom_ids", help="Angabe aller Nummern, die vom Migrationsüberblick ausgeschlossen sein sollen", metavar="id1 id2")
exclude_from_overview.add_argument('--rm_bom_ids_cfg', action="GetIDsFromCFG", nargs=2, default=None, dest="excluded_bom_ids", help="cfg-Pfad & Sektion, in der Nummern aufgelistet sind, die nicht im Überblick dargestellt sein sollen", metavar=("path", "section"))
excel_comparison.write_excel()
elif args.tree:
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)
exclude_from_summary = parser.add_mutually_exclusive_group()
exclude_from_summary.add_argument('--rm_summary_ids_str', action="store", nargs="*", default=None, dest="excluded_summary_ids", help="Angabe aller Nummern, die von der Zusammenfassung ausgeschlossen sein sollen", metavar="id1 id2")
exclude_from_summary.add_argument('--rm_summary_ids_cfg', action="GetIDsFromCFG", nargs=2, default=None, dest="excluded_summary_ids", help="cfg-Pfad & Sektion, in der Nummern aufgelistet sind, die von der Zusammenfassung ausgeschlossen sein sollen", metavar=("path", "section"))
treeview_comparison.generate_tree()
else:
parser.print_help()
args = parser.parse_args()
match args.output_type:
case "excel":
excel_comparison = ExcelExport(asm_id=args.sivas_id,
asm_json_dir=args.bom_filedir,
asm_json_filename=f'{args.sivas_id}.json',
asm_csv_dir=args.teilestamm_filedir,
asm_csv_filename=f"{args.sivas_id}.csv",
rd_dbase_dir=os.path.dirname(args.rd_ids_filepath),
rd_dbase_filename=os.path.basename(args.rd_ids_filepath),
load_local_asm_json=args.local_bom,
load_local_asm_csv=args.local_teilestamm,
load_local_rd_dbase=args.local_rd_ids,
no_blue_ids=args.no_blue_ids)
excel_comparison.write_excel()
case "tree":
treeview_comparison = Treeview(asm_id=args.sivas_id,
asm_json_dir=args.bom_filedir,
asm_json_filename=f'{args.sivas_id}.json',
asm_csv_dir=args.teilestamm_filedir,
asm_csv_filename=f'{args.sivas_id}.csv',
rd_dbase_dir=os.path.dirname(args.rd_ids_filepath),
rd_dbase_filename=os.path.basename(args.rd_ids_filepath),
load_local_asm_json=args.local_bom,
load_local_asm_csv=args.local_teilestamm,
load_local_rd_dbase=args.local_rd_ids,
no_blue_ids=args.no_blue_ids,
excluded_bom_ids=args.excluded_bom_ids,
excluded_summary_ids=args.excluded_summary_ids)
treeview_comparison.generate_tree()
main()
+32 -27
View File
@@ -2,7 +2,7 @@ import os
from tkinter import messagebox
import update_database
from manage_configs import SivasAssemblyData, RuleDesignerDatabaseConfig, MigrationStatus
from manage_configs import SivasBOM, SivasTeilestamm, RuleDesignerDatabaseConfig, MigrationStatus
"""
Contains the logic for the MVC of CreoMigrate Gui
@@ -41,11 +41,11 @@ messages = {
"export_not_sucessful":'Export der Migrationsdatei fehlgeschlagen. Überprüfe Schreibrechte für {path}.',
"export_not_sucessful_file_open":'Export fehlgeschlagen. Datei offen?',
"blacklist_not_exist": 'Bitte Blacklist vom Server holen',
"wrong_impftool_path": 'Pfad zur DataPrepUtility.exe nicht gefunden. Bitte setenv.bat prüfen.' ,
"wrong_impftool_path": 'Pfad zur DataPrepUtility.exe nicht gefunden. Bitte Pfad unter ...\\bin\\cremig_setenv.bat prüfen.',
"too_many_logfiles":'Keine eindeutige Logdatei zum aktuellen DataPrep Prozess gefunden. Bitte löschen Sie die überflüssigen .log files',
"some_error_in_log":'Log Datei enthält einen Fehler. Bitte prüfen.',
"preparation_sucessful":'Preparation mit der Datei {path} erfolgreich',
"missing_in_blacklist":"{teilenummer} fehlt in der Blacklist, da sich in dessen Stückliste beigestellte oder dispo-inaktive Positionen befinden.\n\nBitte {teilenummer} in die Blacklist aufnehmen!",
"missing_in_blacklist":"{teilenummer} fehlt in der Blacklist, da sich in dessen Stückliste beigestellte oder dispo-inaktive Positionen befinden.\n\nBitte {teilenummer} in die Blacklist aufnehmen! \n\nDiese befindet sich in folgender Datei:\n//ruledesigner\\RDF_Daten\\Konfigurator\\CustomVaulting\\ERP_BOM.ini\n\nDanach dieses Programm erneut starten um die aktualisierte Blacklist zu laden",
"halbzeug_not_in_RDids":"Halbzeug-Teilenummer '{halbzeug_id}' fehlt in '{rdids_path}'.\n\nEntweder wurde das Halbzeug noch nicht migriert, oder der aktuelle Stand von RD_Teilenummern.csv muss geladen werden.\n\nZum Laden des aktuellen Standes von RD_Teilenummern.csv auf 'Suche' unter dem Eingabefeld der Baugruppennummer klicken und dieses Programm neu starten"
}
@@ -115,26 +115,30 @@ class Controller(Observer):
orange[red] = True
return orange
def get_csv_files_of_asm(self, asm_id, load_local_asm_json, load_local_rd_dbase):
def get_csv_files_of_asm(self, asm_id, load_local_bom=False, load_local_teilestamm=False, load_local_rd_ids=False):
""" Holt sich die Metadaten der Stücklistenpositionen als CSV"""
data_dir = self.get_env("data")
rd_dbase_filename = self.get_env("rd_dbase_filename")
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)
excluded_bom_ids = self._config.bom_config.get_excluded_ids("ExcludedPartnumbersInBOM") # Artikelnummern wie z.B. "MA0000006" sollen nicht im Baum dargestellt werden
if not load_local_asm_json:
try:
update_database.get_sivas_teilestamm(migration_status.ids_pending, data_dir)
except:
self.message_error('Abfrage hat nicht funktioniert', 'sivas_db_error')
return
sivas_bom = SivasBOM(asm_id, data_dir, asm_id + '.json', load_local_bom, excluded_bom_ids)
self._model.set_red_ids(migration_status.ids_pending)
self._model.set_parent_children(asm_data.parent_children)
sivas_bom.extend_asm_bom(asm_id, {"Parent": None, "Position": "10/0"}) # Ergänzt Stücklistendaten um eine fiktive Baugruppe "None", in der die Haupt-Teilenummer als Pos. 1 vorliegt. Dadurch können auch Teilenummern ohne Stückliste korrekt abgebildet werden
rd_data = RuleDesignerDatabaseConfig(data_dir, rd_dbase_filename, load_local_rd_ids)
migration_status = MigrationStatus(sivas_bom.child_ids, rd_data.ids)
pending_ids = migration_status.ids_pending
sivas_teilestamm = SivasTeilestamm(data_dir, asm_id + '.csv', load_local_csv=load_local_teilestamm)
sivas_teilestamm.create_csvs(pending_ids)
self._model.set_red_ids(pending_ids)
self._model.set_parent_children(sivas_bom.parent_children)
self._model.set_asm_id(asm_id)
self._model.set_bom_properties(asm_data.all_bom_properties)
self._model.set_bom_properties(sivas_bom.all_bom_properties)
def set_model(self, model):
self._model = model
@@ -153,13 +157,11 @@ class Controller(Observer):
def get_current_asm_id(self):
self._model.get_asm_id()
def set_current_asm_id(self, asm_id, load_local_asm_json=False, load_local_rd_dbase=False):
if not asm_id.isnumeric():
return
def set_current_asm_id(self, asm_id, load_local_bom=False, load_local_teilestamm=False, load_local_rd_ids=False):
# 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, load_local_asm_json, load_local_rd_dbase)
self.get_csv_files_of_asm(asm_id, load_local_bom, load_local_teilestamm, load_local_rd_ids)
def set_current_item_id(self, cid):
"""setze eine neue Item ID """
@@ -183,8 +185,8 @@ class Controller(Observer):
# falls eine .json Datei davon im Work Ordner existiert, dann diese zuerst versuchen ins Modell zu laden
self._view.clear_rightframes()
self._model.clear()
isok = self._model.from_json_file(self.get_env("out"), cid)
if isok == False:
saved_json_exists = self._model.from_json_file(self.get_env("out"), cid)
if saved_json_exists == False:
# ansonsten die .csv Datei mit den SIVAS Infos aus data holen
loaded = self._model.sivas_data_from_csv(cid)
if loaded == None:
@@ -282,15 +284,17 @@ class Controller(Observer):
elif key == 'cb_zurücksetzen':
self.zuruecksetzen()
elif key == 'cb_suche_asm':
# kopiere Blacklist von Server
# hole dir die in die GUI eingetragene Nummer
update_database.get_blacklist(self.get_env("data"))
# hole dir den in die gui eingetragene Nummer
current_asmid = self.data_from_view('asm_id')
self.set_current_asm_id(current_asmid)
if current_asmid != '':
self.set_current_asm_id(current_asmid)
elif key == 'cb_ladelokal_asm':
# hole dir nur die lokalen Daten. Keine Datenbankabfrage außer Blacklist
update_database.get_blacklist(self.get_env("data"))
current_asmid = self.data_from_view('asm_id')
# hole dir nur die lokalen Daten. Keine Datenbankabfrage
self.set_current_asm_id(current_asmid, load_local_asm_json=True, load_local_rd_dbase=True)
if current_asmid != '':
self.set_current_asm_id(current_asmid, load_local_bom=True, load_local_teilestamm=True, load_local_rd_ids=True)
elif key == 'cb_3dsuchen':
self.suche_3d()
@@ -317,6 +321,7 @@ class Controller(Observer):
return
# Pruefung ob assembly-id in Blacklist fehlt
update_database.get_blacklist(self.get_env("data"))
(ok, missing_blacklist_id) = self._model.check_blacklist_completeness()
if ok == False:
id = ''.join(missing_blacklist_id)
-51
View File
@@ -1,51 +0,0 @@
import argparse
import os
import shutil
import subprocess
"""
Dieses Programm:
- Holt sich alle notwendigen Einzelteil-Informationen aus SIVAS in Tabellenform
"""
def get_sivas_teilestamm(sivas_id, out_dir):
sivas_id = sivas_id.strip()
sivas_teilestamm_exe = os.environ.get('SIVAS_TEILESTAMM')
if os.path.isfile(sivas_teilestamm_exe):
if os.path.isdir(out_dir):
print(f"Attempting to generate a json-file of all parts to be migrated in relation with '{sivas_id}'")
source_path = os.path.join(os.path.expandvars("%USERPROFILE%"), "AppData", "Local", "Temp", "from_siv.csv")
to_path = os.path.join(out_dir, sivas_id + ".csv")
if os.path.isfile(os.path.join(to_path)):
print("file already there. done")
exit()
subprocess.run(f"{sivas_teilestamm_exe} userid=sivas password=forte database=sivas program=ExcelUeb LFDNR=500 EINSTIEG=SQL PARAMETER1={sivas_id}")
if os.path.isfile(os.path.join(source_path)):
shutil.move(source_path, to_path)
print("done")
else:
print(f"Failure - Couldn't generate {sivas_id}.json. Check for any spelling/typing errors. Otherwise given number is a part without a BOM.")
exit()
else:
print(f"'Output directory for .json-file {out_dir}' doesn't exist.")
else:
print(f"Can't execute SIVAS-database query. Check if you have established a VPN connection to the company's network and check the path of %SIVAS_DATABASE_QUERY% in cremig_setenv.bat")
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 data of this id from sivas fo local database folder', metavar='id')
args = parser.parse_args()
out_dir = os.environ.get('CREMIG_DATA')
if args.sivasid:
get_sivas_teilestamm(args.sivasid, out_dir)
else:
parser.print_help()
+23 -19
View File
@@ -55,10 +55,7 @@ class AllFrames():
class TreeView():
def __init__(self, frame, nrow=0):
# a treeview
# hier Anzahl der sichtbaren Zeilen des Baums angeben:
self.tree = ttk.Treeview(frame, height=65)
# TODO hier anstatt root die Statistik rein schreiben.
#self.tree.heading('#0', text='root', anchor=tk.W)
self.tree = ttk.Treeview(frame, height=65) # 'height' steuert Anzahl der sichtbaren Zeilen im Baum
self.tree.grid(row=nrow, column=0, sticky=tk.NSEW)
# add a scrollbar
@@ -75,7 +72,9 @@ class TreeView():
done_items = set()
undone_items = set()
for parent,children in parent_children_mapping.items():
if parent in red_ids:
if parent is None:
pass
elif parent in red_ids:
undone_items.add(parent)
else:
done_items.add(parent)
@@ -108,18 +107,16 @@ class TreeView():
self.tree.delete(c)
def _has_childs(self, sid, parent_children_mapping):
'''gibt zurück ob sid ein ein Zwischenknoten eines Baume ist, also kein Blatt hat'''
for parent,children in parent_children_mapping.items():
if sid == parent:
return True
return False
'''gibt zurück ob sivas-id ein Zwischenknoten eines Baume ist, also kein Blatt hat'''
return True if sid in parent_children_mapping else False
def create(self, parent_children_mapping, red_ids, orange_ids):
pool = dict()
self.set_heading_text(parent_children_mapping, red_ids)
for parent, children in parent_children_mapping.items():
# parent erzeugen, falls es diese nicht schon gibt (check der pool ids)
if parent not in pool:
if parent is None:
parent_id = None
elif parent not in pool:
if parent not in red_ids: rowtags = ('good')
elif parent in orange_ids: rowtags = ('orange')
else: rowtags = ('missing')
@@ -148,7 +145,9 @@ class TreeView():
child_id = self.tree.insert('', tk.END, text=child, tags=rowtags, open=True)
pool[child] = child_id # hier werden die keys - sivas Nummern auch überschrieben
self.trid2sivasId[child_id] = child # merken der eindeutigen Zuordnung für den späteren Doppelklick
self.tree.move(child_id, parent_id, 'end')
if parent_id is not None:
self.tree.move(child_id, parent_id, 'end')
# https://www.askpython.com/python-modules/tkinter/tkinter-colors
#self.tree.tag_configure('good', background='LawnGreen')
@@ -303,11 +302,18 @@ class RightFramePDM(AllFrames):
self.datenbank_label = ttk.Label(frame, text="Datenbank:", foreground='blue' )
self.datenbank_label.grid(column=col_left, row=self.rownum, sticky=tk.W, padx=3, pady=3)
self.datenbank = tk.StringVar()
self.datenbank.set("Schulung") # default
self.datenbank_prod = ttk.Radiobutton(frame, text="Produktiv", variable=self.datenbank, value="Produktiv")
self.datenbank_prod = ttk.Radiobutton(frame,
text="Produktiv",
variable=self.datenbank,
value="Produktiv",
command=lambda: self.callback('cb_datenbank_neu', self.datenbank.get()))
self.datenbank_prod .grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=3, pady=3)
self.rownum += 1
self.datenbank_schul = ttk.Radiobutton(frame, text="Schulung", variable=self.datenbank, value="Schulung")
self.datenbank_schul = ttk.Radiobutton(frame,
text="Schulung",
variable=self.datenbank,
value="Schulung",
command=lambda: self.callback('cb_datenbank_neu', self.datenbank.get()))
self.datenbank_schul .grid(column=col_left+2, row=self.rownum, sticky=tk.W, padx=3, pady=3)
self.rownum += 1
@@ -596,10 +602,8 @@ class RightFrameBezeichnung(AllFrames):
def trace_bezeichnung_neu(self, var, index, mode):
""" Spezial: Bei zu langem Eintrag die Farbe des Textes auffällig machen """
text = self.bezeichnung1_neu.get()
if len(text) < 45:
if len(text) < 51:
self.bezeichnung1_neu_entry.configure(foreground='black')
elif len(text) < 51:
self.bezeichnung1_neu_entry.configure(foreground='orange')
else:
self.bezeichnung1_neu_entry.configure(foreground='red')
+200 -68
View File
@@ -19,12 +19,9 @@ class JsonBasedConfig:
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.")
with open(self._config_path, 'r', encoding='utf-8') as fh:
content = fh.read()
return json.loads(content)
def from_json(self, json_str):
"""nimm den Json String zur Initialisierung des Objektes"""
@@ -99,50 +96,77 @@ class RuleDesignerSQLData(SQLBasedConfig):
return self._rd_statuses
class SivasAssemblyData(JsonBasedConfig):
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._asm_data = None
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
self._longest_child_id = None
self._max_bom_depth = None
self._property_names = None
self._all_bom_properties = None
def create_asm_json(self):
def create_asm_json(self) -> bool:
"""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!")
if not self._load_local_json:
update_database.get_sivas_bom(self._config_file.removesuffix(".json"))
@property
def asm_data(self) -> dict:
"""Liest die Baugruppen-JSON ein und gibt deren Informationen zurück"""
if self._asm_data is None:
def bom_data(self) -> dict:
""" Liest die JSON-Datei der Baugruppenstückliste ein und speichert die Informationen als dict. """
if not self._load_local_json:
if self._bom_data is None:
"""
Falls die JSON nicht existiert oder nicht generiert werden kann, soll die Möglichkeit bestehen,
diese eigenständig zu erweitern, da in der JSON z.B. die Nummer der obersten Baugruppe standardmäßig
nicht vorliegt. Generierungs- und Einlese-Fehler werden daher bewusst ignoriert, um stattdessen ein leeres
dict zurückzugeben, das anschließend erweitert werden kann.
Die Möglichkeit zur Erweiterung der Stückliste ermöglicht es zudem, Teilenummern ohne Stückliste abzubilden.
Das funktioniert, indem man die Teilenummer ohne eigene Stückliste als Kind einer fiktiven Baugruppe "None"
ansieht / definiert. Dadurch ist keine separate Logik erforderlich, die sich nur mit Teilenummern
ohne Stückliste befasst.
"""
self._bom_data = {}
try:
self.create_asm_json()
except ValueError:
pass
self._asm_data = self.load_json_from_file()
try:
self._bom_data = self.load_json_from_file()
except FileNotFoundError:
pass
return self._asm_data
if self.excluded_ids_by_user is not None:
self._remove_excluded_ids_from_bom()
return self._bom_data
@property
def child_ids(self) -> set:
"""Gibt alle Teilenummern der Stückliste zurück (auch Teilenummern aus tieferen Baugruppenebenen)"""
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)
self._child_ids = set(self.bom_data.keys())
return self._child_ids
@property
@@ -152,7 +176,7 @@ class SivasAssemblyData(JsonBasedConfig):
if self._parent_children is None:
unordered_parent_children = {}
for child_id, parent_properties in self.asm_data.items():
for child_id, parent_properties in self.bom_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])
@@ -170,6 +194,94 @@ class SivasAssemblyData(JsonBasedConfig):
ordered_parent_children[parent].append(child)
self._parent_children = ordered_parent_children
return self._parent_children
def extend_asm_bom(self, id, metadata:dict) -> dict:
"""Ergänzt die Stückliste um eine weitere Teilenummer unter Berücksichtigung des vorgegebenen Formats der ausgegebenen JSON
des Programms 'Sivas2Json' von Hr. Jakob
Aktuell ist die JSON wie folgt aufgebaut:
{
"Stücklistenposition 1": [{Metadaten aus 1. SIVAS-Stammstückliste, in der die Stücklistenposition verbaut ist},
{Metadaten aus 2. SIVAS-Stammstückliste, in der die Stücklistenposition verbaut ist (falls die oberste Baugruppe weitere Unterbaugruppen enthält, in denen diese Nummer ebenfalls verbaut ist)},
...
]
"Stücklistenposition 2": [{...},
{...}
]
}"""
if id not in self.bom_data:
self._bom_data[id] = []
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:
@@ -177,7 +289,7 @@ class SivasAssemblyData(JsonBasedConfig):
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:
@@ -185,33 +297,32 @@ class SivasAssemblyData(JsonBasedConfig):
if self._max_bom_depth is None:
bom_levels = []
for parents in self.asm_data.values():
for parents in self.bom_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
@property
def property_names(self) -> list:
"""Gibt alle Eigenschaftsnamen zurück, die in der Assembly-json-Datei enthalten sind"""
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 self._property_names is None:
for parent_data in self.asm_data.values():
self._property_names = list(parent_data[0])
return self._property_names
def get_bom_property_values(self, property) -> dict:
"""Gibt eine Eigenschaft zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)"""
if id not in self.bom_data:
raise ValueError("Teilenummer befindet sich nicht in der Baugruppe!")
if property not in self.property_names:
raise ValueError("Angegebener Eigenschaftsname exisitert nicht!")
if property_name not in self.bom_data[id][0]:
return
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)"""
id_property_mapping = {}
for child, parent_data in self.asm_data.items():
for properties in parent_data:
if property in properties:
id_property_mapping[child] = properties[property]
for child_id, asm_usages in self.bom_data.items():
child_properties = asm_usages[0]
id_property_mapping[child_id] = None if property_name not in child_properties else child_properties[property_name]
return id_property_mapping
@property
@@ -219,9 +330,16 @@ class SivasAssemblyData(JsonBasedConfig):
"""Gibt alle Eigenschaften zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)"""
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)
for child_id, asm_usages in self.bom_data.items():
child_properties = asm_usages[0]
for property_name, property_val in child_properties.items():
if property_name not in self._all_bom_properties:
self._all_bom_properties[property_name] = {}
self._all_bom_properties[property_name][child_id] = property_val
return self._all_bom_properties
@@ -235,7 +353,7 @@ class RuleDesignerDatabaseConfig(CSVBasedConfig):
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()
update_database.get_ruledesigner_ids()
except:
raise ValueError("CSV mit RuleDesigner-Teilenummern konnte nicht ins lokale Verzeichnis kopiert werden!")
@@ -303,34 +421,31 @@ class MigrationStatus:
return self._ids_pending
class TeilestammMetadata(CSVBasedConfig):
class SivasTeilestamm(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
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)"""
try:
if not self._load_local_csv:
update_database.get_sivas_teilestamm(ids)
except:
raise ValueError("CSV(s) konnte nicht generiert werden!")
@property
def csv_data(self):
def teilestamm_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"))
if self._data is None:
self.create_csvs(self._config_file.removesuffix(".csv"))
self._data = self.load_csv_from_file()
self._csv_data = self.load_csv_from_file()
return self._csv_data
return self._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]
ret = self.teilestamm_data[property_name][0]
return ret
@@ -651,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 """
@@ -671,8 +804,8 @@ class Configs:
self.TOSGruppen = TOSGroupsConfig(cfg_dir)
self.Materialien = MaterialTransConfig(cfg_dir)
self.bom_config = BOMConfig(cfg_dir)
self._teilenummern = None
self._blacklist = None
def get_cfg_path(self):
return self._cfg_dir
@@ -692,8 +825,7 @@ class Configs:
def get_blacklist(self):
if not self._data_dir:
raise Exception("set_data_dir not called. path not set")
if not self._blacklist:
with open(os.path.join(self._data_dir, 'blacklist.txt')) as f:
lines = f.read().splitlines()
self._blacklist = lines
with open(os.path.join(self._data_dir, 'blacklist.txt')) as f:
lines = f.read().splitlines()
self._blacklist = lines
return self._blacklist
+9 -8
View File
@@ -1,8 +1,9 @@
numpy==1.26.1
pandas==2.1.1
python-dateutil==2.8.2
pytz==2023.3.post1
six==1.16.0
tzdata==2023.3
et-xmlfile==1.1.0
openpyxl==3.1.2
et_xmlfile==2.0.0
numpy==2.2.3
openpyxl==3.1.5
pandas==2.2.3
pymssql==2.3.2
python-dateutil==2.9.0.post0
pytz==2025.1
six==1.17.0
tzdata==2025.1
+107 -105
View File
@@ -4,23 +4,36 @@ import os
import shutil
import subprocess
import sys
import csv
"""
Dieses Programm:
- Kopiert die CSV-Datei mit allen RuleDesigner-Nummern
- Fragt Sivas ab und generiert dabei eine json-Datei, in der alle Teilenummern aufgelistet sind,
die in Verbindung mit einer angegebenen Teilenummer migriert werden müssen.
Diese Library erzeugt Dateien mit RuleDesigner-, bzw. SIVAS-Informationen,
bzw. stellt diese durch Kopieren / Verschieben von bereits vorhandenen
Dateien auf Netzlaufwerken bereit
"""
def get_blacklist(outdir):
"""read out list of sivas Ids which are not allowed from network drive
'//ruledesigner//DF_Daten//onfigurator//ustomVaulting//RP_BOM.ini'
within the section 'Settings'
in the key 'PartNumbersToDrop'
"""
Schreibt Teilenummern aus der nachfolgenden .ini-Datei in eine .txt-Datei:
-> Dateipfad: //ruledesigner/RDF_Daten/Configurator/CustomVaulting/ERP_BOM.ini'
-> Abschnitt: 'Settings'
-> Schlüssel: 'PartNumbersToDrop'
Bei diesen Teilenummern darf keine Stücklistenübergabe an SIVAS erfolgen, da sich die Stücklistenstruktur
zwischen dem CAD und SIVAS unterscheidet (vorrangig bei Baugruppen mit Lieferantenbeistellungen)
"""
blacklist_net_share = os.environ.get("BLACKLIST_DATABASE_SOURCE")
blacklist_ini_filename = os.environ.get("BLACKLIST_DATABASE_NAME")
blacklist_source_path = os.path.join(blacklist_net_share, blacklist_ini_filename)
config = configparser.RawConfigParser()
if not os.path.isfile(blacklist_source_path):
raise FileNotFoundError(f"Kein Zugriff auf ini-Datei '{blacklist_source_path}' mit den Blacklist-Teilenummern möglich. Bitte $BLACKLIST_DATABASE_SOURCE & $BLACKLIST_DATABASE_NAME in cremig_setenv.bat sowie VPN-Verbindung prüfen")
try:
config.read(blacklist_source_path)
except configparser.DuplicateOptionError:
@@ -28,137 +41,126 @@ def get_blacklist(outdir):
content = config.get('Settings','PartNumbersToDrop')
if type(content) == list:
content = content[0] # Wird benötigt, weil der String mit den Nummern aus der Blackliste manchmal in eine Liste gepackt wird, manchmal nicht - warum wissen die Götter...
if isinstance(content, list):
content = content[0] # Manchmal wird String mit den Blacklist-Nummern in einer Liste geschrieben - warum wissen die Götter...
if len(content) == 0 or len(content) == 0:
print(f"import of '{blacklist_ini_filename}' from '{blacklist_net_share}' failed")
exit
if len(content) == 0:
raise ValueError(f"Öffnen der Blacklist-Datei'{blacklist_ini_filename}' in '{blacklist_net_share}' fehlgeschlagen")
blacklist = content.split(';')
print("writing txt-file of blacklist ids...")
outfile = os.path.join(outdir, 'blacklist.txt')
print(f"Schreibe Blacklist-Teilenummern in '{outfile}'...")
with open(outfile, 'w', encoding='utf-8') as fh:
fh.write('\n'.join(blacklist))
print("done")
def get_rd_dbase(out_dir=os.environ.get('CREMIG_DATA')):
def get_ruledesigner_ids(out_dir=os.environ.get('CREMIG_DATA')):
"""Kopiert CSV-Datei, in der alle RuleDesigner-Teilenummern aufgelistet sind, vom Netzlaufwerk in ein lokales Verzeichnis"""
rd_csv_source_dir = os.environ.get("RD_DATABASE_SOURCE")
rd_csv_filename = os.environ.get("RD_DATABASE_NAME")
csv_source_path = os.path.join(rd_csv_source_dir, rd_csv_filename)
csv_destiny_path = os.path.join(out_dir, rd_csv_filename)
if os.path.isfile(csv_source_path):
csv_destiny_path = os.path.join(out_dir, rd_csv_filename)
if not os.path.isfile(csv_source_path):
raise FileNotFoundError(f"CSV-Datei mit RuleDesigner-Teilenummern existiert nicht unter '{csv_source_path}'. Bitte $RD_DATABASE_SOURCE & $RD_DATABASE_NAME in cremig_setenv.bat prüfen.")
if os.path.isdir(out_dir):
print("Copying CSV-file of RuleDesigner database...")
shutil.copyfile(csv_source_path, csv_destiny_path)
print("done")
else:
print(f"Can't copy RuleDesigner-database, because output directory ${out_dir} doesn't exist. Check %CREMIG_DATA% in cremig_setenv.bat")
sys.exit(0)
else:
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)
if not os.path.isdir(out_dir):
raise NotADirectoryError(f"Zielverzeichnis '{out_dir}' für CSV-Datei mit RuleDesigner-Teilenummern existiert nicht. Bitte $CREMIG_DATA in cremig_setenv.bat und VPN-Verbindung prüfen")
def get_sivas_dbase(sivas_id, out_dir=os.environ.get('CREMIG_DATA')):
sivas_exe_path = os.environ.get('SIVAS_DATABASE_QUERY')
print(f"Kopiere '{rd_csv_source_dir}\\{rd_csv_filename}'...")
shutil.copyfile(csv_source_path, csv_destiny_path)
def get_sivas_bom(sivas_id, out_dir=os.environ.get('CREMIG_DATA')):
"""Generiert JSON-Datei, in der die Stücklisteninformationen zu einer SIVAS-Teilenummer mit Stammstückliste gespeichert sind.
Die Erstellung der JSON-Datei erfolgt unter Verwendung des externen Sivas2Json-Programms von Hr. Jakob"""
sivas2json_exe_path = os.environ.get('SIVAS_BOM')
sivas_id = sivas_id.replace(".json", "")
if os.path.isfile(sivas_exe_path):
if os.path.isdir(out_dir):
print(f"Attempting to generate a json-file of all parts to be migrated in relation with '{sivas_id}'")
subprocess.run(f'{sivas_exe_path} -o {out_dir} --format json -number "{sivas_id}"')
if os.path.isfile(os.path.join(out_dir, sivas_id + ".json")):
print("done")
return True
else:
print(f"Failure - Couldn't generate {sivas_id}.json. Check for any spelling/typing errors. Otherwise given number is a part without a BOM.")
return False
else:
print(f"'Output directory for .json-file {out_dir}' doesn't exist. Check cremig_setenv.bat or VPN connection.")
return False
else:
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
if not os.path.isfile(sivas2json_exe_path):
raise FileNotFoundError(f"Programmpfad '{sivas2json_exe_path}' nicht erreichbar. Bitte prüfen, ob Zugriff auf Laufwerk Y: besteht. Andernfalls $SIVAS_BOM in cremig_setenv.bat sowie VPN-Verbindung prüfen")
if not os.path.isdir(out_dir):
raise NotADirectoryError(f"Ausgabeverzeichnis '{out_dir}' für die Stücklisten-JSON existiert nicht. Bitte $SIVAS_BOM in cremig_setenv.bat oder VPN-Verbindung prüfen.")
print(f"Erstelle JSON mit Stücklisteninformationen...")
subprocess.run(f'{sivas2json_exe_path} -o {out_dir} --format json -number "{sivas_id}"')
if not os.path.isfile(os.path.join(out_dir, sivas_id + ".json")):
raise ValueError(f"{sivas_id}.json konnte nicht erzeugt werden. Evtl. Nummer ohne Stückliste.")
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"""
"""
Generiert für jede angegebene SIVAS-Teilenummer eine CSV-Datei, in der die Teilestamm-Informationen gespeichert sind
Es kann eine oder mehrere Nummern angegeben werden. Mehrere Nummern sind entweder als kommaseparierter String oder
in Form einer Liste anzugeben
"""
sivas_teilestamm_exe = os.environ.get('SIVAS_TEILESTAMM')
if not os.path.isfile(sivas_teilestamm_exe):
raise FileNotFoundError(f"Programmpfad '{sivas_teilestamm_exe}' nicht erreichbar. Bitte prüfen, ob Zugriff auf Laufwerk Y: besteht. Andernfalls $SIVAS_TEILESTAMM in cremig_setenv.bat sowie VPN-Verbindung prüfen")
if not os.path.isdir(out_dir):
raise NotADirectoryError(f"Ausgabeverzeichnis '{out_dir}' für die Teilestamm-CSV existiert nicht. Bitte $SIVAS_TEILESTAMM in cremig_setenv.bat oder VPN-Verbindung prüfen.")
if isinstance(sivas_ids, str):
sivas_ids = list(set(sivas_ids.strip().split(",")))
ids_to_generate = []
for id in sivas_ids:
id = id.replace(" ", "")
if not overwrite:
out_file = os.path.join(out_dir, id + ".csv")
if not os.path.isfile(out_file):
ids_to_generate.append(id)
continue
else:
ids_to_generate.append(id)
if not id.isnumeric():
print(f"Partnumber '{id}' contains invalid characters. Please check given partnumber. Multiple partnumbers have to be specified as comma separated values without space, e.g. 200000089,200000090")
if not overwrite and os.path.isfile(os.path.join(out_dir, id + ".csv")):
continue
ids_to_generate.append(id)
if os.path.isfile(sivas_teilestamm_exe):
if os.path.isdir(out_dir):
if len(ids_to_generate) == 0:
print("CSV-Datei(en) existieren bereits oder angegebene Teilenummer(n) sind bereits migriert")
return
if len(ids_to_generate) == 0:
print("CSV-file(s) already exist or given partnumber(s) are migrated.")
return
ids_to_generate_str = ",".join([f'"{id}"' for id in ids_to_generate]) # Anführungszeichen erforderlich, damit Art.Nr. mit Leerzeichen, z.B. "TOS Auswahl ILS" funktionieren
else:
print(f"Attempting to generate csv-file(s) for given partnumber(s)...")
os.environ["SIVAS_EXCEL_EXPORT_DIR"] = out_dir # Umgebungsvariable legt Ausgabeverzeichnis der CSV-Dateien im Programm 'KSbExcelExportSivasTeilestamm' fest. Der Variablenname ist durch genanntes Programm vorgegeben, siehe help-Text des Programms!
print(f"Erstelle CSV(s) mit Teilestamminformationen...")
subprocess.run(f'{sivas_teilestamm_exe} -teilenr={ids_to_generate_str} -open_target_dir=n')
ids_to_generate = ",".join(ids_to_generate)
os.environ["SIVAS_EXCEL_EXPORT_DIR"] = out_dir
subprocess.run(f"{sivas_teilestamm_exe} -teilenr={ids_to_generate} -open_target_dir=n")
else:
print(f"Output directory for .csv-file(s) ${out_dir} doesn't exist.")
else:
print(f"Can't execute SIVAS-database query. Check VPN connection or check the path of %SIVAS_DATABASE_QUERY% in cremig_setenv.bat")
# Prüft Existenz der angegebenen SIVAS-Nummern, indem geschaut wird, ob die CSV-Dateien 2 Zeilen aufweisen
# Nicht über Returncode des Subprocess ermittelbar, da dieser immer 0 ist, auch wenn die Nr. nicht in SIVAS existiert
for id in ids_to_generate:
csv_path = os.path.join(out_dir, id + ".csv")
with open(csv_path, encoding="utf-8", newline="") as csv_file:
csv_data = csv.reader(csv_file, delimiter=";")
line_nums = sum(1 for row in csv_data)
if line_nums != 2:
raise ValueError(f"SIVAS-Teilenummer '{id}' existiert nicht! Bitte prüfen, ob Nummer korrekt geschrieben wurde!")
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 data of this id from sivas fo local database folder', metavar='id')
parser.add_argument('-r', '--rd', action='store_true', help='fetch list of existing parts from network drive')
parser.add_argument('-b', '--blacklist', action='store_true', help='fetch list of not allowed parts from network drive')
parser.add_argument('-t', '--teilestamm', action='store_true', help='fetch teilestamm-data for given partnumbers as csv')
parser.add_argument('-w', '--overwrite', action='store_true', help='vorhandene CSV-Dateien werden nicht erneut aus SIVAS abgefragt')
parser = argparse.ArgumentParser(description='Erzeugt Dateien mit RuleDesigner-, bzw. SIVAS-Informationen, bzw. stellt diese bereit', prog='update_database')
parser.add_argument('-b', '--blacklist', action='store_true', help='Schreibe .txt-Datei mit allen Teilenummern, bei denen keine Stücklistenübergabe aus dem CAD nach SIVAS erfolgen darf')
parser.add_argument('-d', '--out_dir', action='store', default=os.environ.get('CREMIG_DATA'), help='Ausgabeverzeichnis für Datei', metavar="dir")
parser.add_argument('-m', '--bom', action='store_true', help='Schreibe Stückliste einer SIVAS-Teilenummer als JSON-Datei')
parser.add_argument('-o', '--overwrite', action='store_true', default=False, help='Überschreibe Datei, wenn diese bereits in Zielverzeichnis vorliegt')
parser.add_argument('-r', '--rd_ids', action='store_true' ,help='Kopiere CSV-Datei, in der alle RuleDesigner-Teilenummern aufgelistet sind')
parser.add_argument('-s', '--sivasid', action='store', help='SIVAS-Teilenummer', metavar='id')
parser.add_argument('-t', '--teilestamm', action='store_true', help='Schreibe Teilestamm-Medataten einer SIVAS-Teilenummer als CSV-Datei')
args = parser.parse_args()
out_dir = os.environ.get('CREMIG_DATA')
if args.rd:
get_rd_dbase(out_dir)
elif args.sivasid:
if args.teilestamm:
overwrite = args.overwrite
if overwrite:
get_sivas_teilestamm(args.sivasid, out_dir, overwrite = True)
else:
get_sivas_teilestamm(args.sivasid, out_dir, overwrite = False)
else:
get_sivas_dbase(args.sivasid, out_dir)
elif args.blacklist:
get_blacklist(out_dir)
else:
if len(sys.argv) == 1:
parser.print_help()
if args.rd_ids:
get_ruledesigner_ids(args.out_dir)
if args.blacklist:
get_blacklist(args.out_dir)
if args.sivasid:
if args.teilestamm:
get_sivas_teilestamm(args.sivasid, args.out_dir, overwrite = True)
if args.bom:
get_sivas_bom(args.sivasid, args.out_dir)