Compare commits
64 Commits
5bd83324b2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b2127b70c9 | |||
| db46c38ac6 | |||
| af69b98365 | |||
| 87ff81d001 | |||
| cbc2afa9e1 | |||
| 47e70d952e | |||
| 5a1fa5a7e4 | |||
| 4defeb11f0 | |||
| 2591d78716 | |||
| ac99ca73a5 | |||
| 17cbd61fb4 | |||
| f18f8cae2d | |||
| 7fb778125b | |||
| a279c581e3 | |||
| aa73ffb444 | |||
| 61cd933de3 | |||
| 9ef23a735a | |||
| 089dadd52b | |||
| 9f5f4150f1 | |||
| c64327cbe7 | |||
| 9e25ca15bc | |||
| 754ae39364 | |||
| 6590674d8e | |||
| 6c31b9fbf3 | |||
| f5cc53253e | |||
| cf4b632cba | |||
| 95fb80dd57 | |||
| b7fd1da5b8 | |||
| e342ace5c3 | |||
| fb710b73e0 | |||
| c813a8fc1b | |||
| f8b895cec7 | |||
| cb20f31f10 | |||
| a6a7d9c3e9 | |||
| 05709677ac | |||
| 54606dce88 | |||
| 2f2dd83e2b | |||
| 0eda150f8b | |||
| 58b16509da | |||
| 39bd9e01e2 | |||
| 21528e2353 | |||
| d905896891 | |||
| 1635610aff | |||
| e083f3c2ac | |||
| 2a601ec3c1 | |||
| f01f08cda9 | |||
| 832aaf2088 | |||
| 623f4a12e1 | |||
| 56ff5010c7 | |||
| 0c54145655 | |||
| 3393e7ce47 | |||
| 807b6c79f4 | |||
| 80000c15c3 | |||
| fe302ef950 | |||
| efef193612 | |||
| a1a7f45f06 | |||
| 9b3c3859cc | |||
| 22b0cf8afc | |||
| e2bfa2aac6 | |||
| 5f3615548b | |||
| 4f740e81ad | |||
| 6f188649ff | |||
| beb1fdc0da | |||
| da4eed34c3 |
Vendored
+18
-13
@@ -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",
|
||||
|
||||
@@ -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`.
|
||||
@@ -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,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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@echo off
|
||||
CALL 01_get_rd_dbase.bat
|
||||
CALL manage_interpreter.bat activate_interpreter
|
||||
python %CREMIG_LIB%\compare_lists.py %*
|
||||
CALL manage_interpreter.bat deactivate_interpreter
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-2
@@ -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,12 +31,16 @@ 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
|
||||
|
||||
if exist %RD_SQL_DATABASE_CREDENTIALS% (
|
||||
call %RD_SQL_DATABASE_CREDENTIALS%
|
||||
)
|
||||
|
||||
rem Um den Python-Interpreter auf U:\ zu nutzen, Kommentar (rem) in nachfolgender Zeile entfernen
|
||||
rem set NETWORK_INTERPRETER_PATH=\\ssg.local\freigaben\Allgemein\Programmierumgebung\Python312
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
#============================================================================================================================
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[Status]
|
||||
0=VERFÜGBAR
|
||||
1=IN BEARBEITUNG
|
||||
2=IN ÜBERARBEITUNG
|
||||
3=FREIGEGEBEN
|
||||
4=FREIGEGEBEN
|
||||
10=VORLÄUFIG
|
||||
11=PROTOTYP
|
||||
12=IN PRÜFUNG
|
||||
13=UNGÜLTIG
|
||||
101=BAGATELLÄNDERUNG
|
||||
102=REVISION VERFÜGBAR
|
||||
103=ZUR PRÜFUNG
|
||||
104=REVISION IN PRÜFUNG
|
||||
105=REVISION ZUR PRÜFUNG
|
||||
106=GESPERRT
|
||||
+11
-2
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
+339
-311
@@ -1,271 +1,136 @@
|
||||
import update_database
|
||||
|
||||
import warnings
|
||||
#Wegen Pandas Deprecation eingebaut
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
from manage_configs import SivasBOM, RuleDesignerDatabaseConfig, MigrationStatus, SivasTeilestamm, BOMConfig
|
||||
import argparse
|
||||
import os
|
||||
import json
|
||||
import pandas as pd
|
||||
import openpyxl
|
||||
from itertools import zip_longest
|
||||
|
||||
"""
|
||||
Dieses Programm kann:
|
||||
- eine Excel-Datei exportieren, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer angegebenen Nummer migriert werden müssen.
|
||||
Sie gibt dabei Auskunft darüber, welche Teilenummern bereits migriert wurden und welche noch migriert werden müssen.
|
||||
- die oben beschriebene Darstellung in einer Excel-Datei auch als Treeview auf der Kommandozeile darstellen
|
||||
- die oben Darstellung in der Excel-Datei als Baum in der Konsole ausgeben
|
||||
"""
|
||||
|
||||
class Environment:
|
||||
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)
|
||||
|
||||
def __init__(self):
|
||||
self.lib_dir = os.environ.get('CREMIG_LIB')
|
||||
self.data_dir = os.environ.get('CREMIG_DATA')
|
||||
self.out_dir = os.environ.get('CREMIG_WORK')
|
||||
self.ruledesigner_csv_filename = os.environ.get('RD_DATABASE_NAME')
|
||||
self.vba_macro_filename = os.environ.get('VBA_MACRO_NAME')
|
||||
self.vba_macro_filepath = os.path.join(self.lib_dir, self.vba_macro_filename)
|
||||
class LStrip(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string = None):
|
||||
setattr(namespace, self.dest, values.lstrip())
|
||||
|
||||
|
||||
class Partnumbers:
|
||||
|
||||
def __init__(self, filename: str, file_dir: str):
|
||||
self.file_dir = file_dir
|
||||
self.filename = filename
|
||||
|
||||
def load_from_json(self):
|
||||
|
||||
"""Reads partnumbers from a json-file"""
|
||||
|
||||
config_path = os.path.join(self.file_dir, self.filename + ".json")
|
||||
|
||||
try:
|
||||
with open(config_path, encoding='utf-8') as json_file:
|
||||
return json.load(json_file)
|
||||
|
||||
except FileNotFoundError:
|
||||
update_database.get_sivas_dbase(self.filename, self.file_dir)
|
||||
return self.load_from_json()
|
||||
|
||||
def load_from_csv(self):
|
||||
|
||||
"""Reads partnumbers from a csv-file"""
|
||||
|
||||
config_path = os.path.join(self.file_dir, self.filename)
|
||||
|
||||
try:
|
||||
df = pd.read_csv(config_path, sep=';', converters={"Teilenummern":str})
|
||||
return tuple(df["Teilenummern"])
|
||||
|
||||
except FileNotFoundError:
|
||||
update_database.get_rd_dbase(self.file_dir)
|
||||
return self.load_from_csv()
|
||||
|
||||
|
||||
class SivasPartnumbers(Partnumbers):
|
||||
|
||||
def __init__(self, main_sivas_id: str, file_dir: str):
|
||||
self.file_dir = file_dir
|
||||
self.main_sivas_id = main_sivas_id
|
||||
self._partnumbers = None
|
||||
|
||||
super().__init__(file_dir=self.file_dir, filename=self.main_sivas_id)
|
||||
|
||||
|
||||
def lazy_partnumber_init(function):
|
||||
|
||||
"""Decorator prevents time-consuming initialization of sivas partnumbers if method doesn't require them"""
|
||||
|
||||
def wrapper(self):
|
||||
if self._partnumbers is None:
|
||||
self._partnumbers = self.__class__.mro()[1].load_from_json(self) # Using super() isn't possible here
|
||||
return function(self)
|
||||
return wrapper
|
||||
|
||||
@lazy_partnumber_init
|
||||
def get_partnumbers(self) -> set:
|
||||
|
||||
"""Returns all partnumbers (id's) of the provided json-file"""
|
||||
|
||||
ids = set()
|
||||
for child_id, top_level_assemblies_of_child in self._partnumbers.items():
|
||||
ids.add(child_id)
|
||||
for top_level_assembly in top_level_assemblies_of_child:
|
||||
top_level_assembly_id = top_level_assembly["Parent"]
|
||||
ids.add(top_level_assembly_id)
|
||||
|
||||
return ids
|
||||
|
||||
def get_longest_partnumber(self) -> str:
|
||||
|
||||
"""Returns the longest partnumber in the provided json-file"""
|
||||
|
||||
return max(self.get_partnumbers())
|
||||
|
||||
@lazy_partnumber_init
|
||||
def get_ordered_parent_children(self, no_duplicates: bool = False) -> dict:
|
||||
|
||||
"""Replicates the partnumber structure of the given BOM (bill of material) from sivas. The Order of the partnumbers is identical to the position numbers in the BOM"""
|
||||
|
||||
unordered_parent_children = {}
|
||||
|
||||
for parents in self._partnumbers:
|
||||
for children in self._partnumbers[parents]:
|
||||
|
||||
if children["Parent"] not in unordered_parent_children:
|
||||
unordered_parent_children[children["Parent"]] = {}
|
||||
|
||||
pos = children["Position"].lstrip(".").split("/")[0]
|
||||
pos_in_list = int(pos)
|
||||
unordered_parent_children[children["Parent"]][pos_in_list] = parents
|
||||
|
||||
# Second dict necessary, because children aren't sorted according to their position numbers in the SIVAS-BOM
|
||||
ordered_parent_children = {}
|
||||
for key, values in unordered_parent_children.items():
|
||||
for subkey, subvalue in sorted(values.items()):
|
||||
if key not in ordered_parent_children:
|
||||
ordered_parent_children[key] = []
|
||||
ordered_parent_children[key].append(subvalue)
|
||||
|
||||
return ordered_parent_children
|
||||
|
||||
@lazy_partnumber_init
|
||||
def get_max_bom_depth(self) -> int:
|
||||
|
||||
"""Returns the maximum nesting-depth (level) of the provided assembly partnumber as an integer"""
|
||||
|
||||
bom_levels = []
|
||||
for values in self._partnumbers.values():
|
||||
for value in values:
|
||||
current_bom_level = value["Position"].count(".")
|
||||
bom_levels.append(current_bom_level)
|
||||
|
||||
return max(bom_levels)
|
||||
|
||||
@lazy_partnumber_init
|
||||
def get_partnumber_descriptions(self) -> dict:
|
||||
descriptions = {}
|
||||
for parent, values in self._partnumbers.items():
|
||||
for value in values:
|
||||
descriptions[parent] = value["Bezeichnung"]
|
||||
return descriptions
|
||||
|
||||
|
||||
|
||||
class RuleDesignerPartnumbers(Partnumbers):
|
||||
|
||||
def __init__(self, ruledesigner_ids_dir: str, ruledesigner_ids_filename: str):
|
||||
|
||||
self.ruledesigner_ids_dir = ruledesigner_ids_dir
|
||||
self.ruledesigner_ids_filename = ruledesigner_ids_filename
|
||||
|
||||
super().__init__(file_dir=self.ruledesigner_ids_dir, filename=self.ruledesigner_ids_filename)
|
||||
|
||||
|
||||
def get_partnumbers(self):
|
||||
return super().load_from_csv()
|
||||
|
||||
def update_database(self):
|
||||
return update_database.get_rd_dbase(self.ruledesigner_ids_dir)
|
||||
|
||||
|
||||
|
||||
class SivasRuleDesignerComparison:
|
||||
|
||||
def __init__(self, sivas_ids_dir, sivas_main_id, rd_ids_dir, rd_ids_filename):
|
||||
self.sivas = SivasPartnumbers(main_sivas_id=sivas_main_id, file_dir=sivas_ids_dir)
|
||||
self.ruledesigner = RuleDesignerPartnumbers(ruledesigner_ids_dir=rd_ids_dir, ruledesigner_ids_filename=rd_ids_filename)
|
||||
|
||||
def get_migration_status(self) -> dict:
|
||||
|
||||
"""Checks if given sivas-partnumbers have been migrated been migrated into RuleDesigner"""
|
||||
|
||||
sivas_ids = self.sivas.get_partnumbers()
|
||||
ruledesigner_ids = self.ruledesigner.get_partnumbers()
|
||||
|
||||
migration_completed = {}
|
||||
for sivas_partnumber in sivas_ids:
|
||||
migration_completed[sivas_partnumber] = True if sivas_partnumber in ruledesigner_ids else False
|
||||
|
||||
return migration_completed
|
||||
|
||||
class 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, main_id, sivas_ids_file_dir, rd_ids_file_dir, rd_ids_filename):
|
||||
self.main_id = main_id
|
||||
self.environment = Environment()
|
||||
self._sivas = SivasPartnumbers(main_sivas_id=main_id, file_dir=sivas_ids_file_dir)
|
||||
self._ruledesigner = RuleDesignerPartnumbers(ruledesigner_ids_dir=rd_ids_file_dir, ruledesigner_ids_filename=rd_ids_filename)
|
||||
self._comparison = SivasRuleDesignerComparison(sivas_ids_dir=sivas_ids_file_dir, sivas_main_id=main_id, rd_ids_dir=rd_ids_file_dir, rd_ids_filename=rd_ids_filename)
|
||||
self._parent_children = self._sivas.get_ordered_parent_children()
|
||||
self._ruledesigner_ids = self._ruledesigner.get_partnumbers()
|
||||
def __init__(self,
|
||||
asm_id,
|
||||
asm_json_dir,
|
||||
asm_json_filename,
|
||||
asm_csv_dir,
|
||||
asm_csv_filename,
|
||||
rd_dbase_dir,
|
||||
rd_dbase_filename,
|
||||
load_local_asm_json,
|
||||
load_local_asm_csv,
|
||||
load_local_rd_dbase,
|
||||
no_blue_ids):
|
||||
self.asm_id = asm_id
|
||||
self.asm_data = 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
|
||||
|
||||
@property
|
||||
def migration_status(self):
|
||||
if self._migration_status is None:
|
||||
self._migration_status = MigrationStatus(self.asm_data.child_ids, self.rd_data.ids)
|
||||
return self._migration_status
|
||||
|
||||
def write_excel(self):
|
||||
|
||||
data = self._create_excel_data()
|
||||
output_dir = environment.out_dir
|
||||
out_dir = os.environ.get("CREMIG_WORK")
|
||||
out_path = os.path.join(out_dir, self.asm_id + ".xlsm")
|
||||
|
||||
destiny_filepath = os.path.join(output_dir, self.main_id + ".xlsm")
|
||||
|
||||
workbook = openpyxl.load_workbook(self.environment.vba_macro_filepath, keep_vba=True)
|
||||
workbook_path = os.path.join(os.environ.get("CREMIG_LIB"), os.environ.get("VBA_MACRO_NAME"))
|
||||
workbook = openpyxl.load_workbook(workbook_path, keep_vba=True)
|
||||
worksheet = workbook.active
|
||||
|
||||
# Headers
|
||||
# Spaltenüberschriften
|
||||
for header, column in zip(data, worksheet.iter_cols(min_row=1, max_col=len(data), max_row=1)):
|
||||
for cell in column:
|
||||
cell.value = header
|
||||
|
||||
# Values below Headers
|
||||
# Werte unter Spaltenüberschriften
|
||||
for value, column in zip(data.values(), worksheet.iter_cols(min_row=2, max_col=len(data), max_row=len(data["Sortierung"])+1)):
|
||||
for cell, element in zip(column, value):
|
||||
cell.value = element
|
||||
|
||||
# Renaming the sheet is necessary, because it triggers the execution of the VBA-Macro
|
||||
# The renaming must be performed AFTER inserting the data into the spreadsheet, because the conditional formatting, which is part of the macro, can't be performed
|
||||
# when no data is in the spreadsheet
|
||||
# Umbenennung des Blattes erforderlich, da die Umbenennung die Ausführung des VBA-Makros triggert.
|
||||
# Die Umbenennung muss NACH dem Einfügen der Daten in das Blatt erfolgen, da die bedingte Formatierung,
|
||||
# die ebenfalls Bestandteil des Makros ist, nicht ausgeführt werden kann, solange sich keine Daten im Blatt befinden
|
||||
worksheet.title = "Ergebnis"
|
||||
|
||||
workbook.save(destiny_filepath)
|
||||
print(f"Excel-Datei erfolgreich in '{output_dir}' geschrieben...")
|
||||
workbook.save(out_path)
|
||||
print(f"Excel-Datei erfolgreich in '{out_dir}' geschrieben...")
|
||||
|
||||
def _create_excel_data(self) -> dict:
|
||||
|
||||
# Columns in excel-spreadsheet: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung
|
||||
# Sortierung: Minus-separated hierarchy of the partnumber in the BOM structure
|
||||
# Teilenummer: SIVAS-partnumber
|
||||
# Bezeichnung: SIVAS-description of the partnumber
|
||||
# Migrationsstatus: "IMPORTIEREN" = partnumber hasn't been migrated yet, "VERFÜGBAR" = partnumber has been migrated
|
||||
# All the other columns remain empty, because their values are entered in the excel spreadsheet
|
||||
# Spalten im Arbeitsblatt: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung
|
||||
# Sortierung: Minus-separierte Hierarchie der Teilenummern in der Stücklistenstruktur
|
||||
# Teilenummer: SIVAS-Teilenummer
|
||||
# Bezeichnung: SIVAS-Bezeichnung der Teilenummer
|
||||
# Migrationsstatus: "IMPORTIEREN" = Migration ausstehend, "VERFÜGBAR" = Migration abgeschlossen
|
||||
# Alle weiteren Spalten bleiben leer, da sie händisch vom User zu befüllen sind
|
||||
|
||||
sorting = self._create_sorting_string()
|
||||
migrated = self._comparison.get_migration_status()
|
||||
descriptions = self._sivas.get_partnumber_descriptions()
|
||||
migrated_ids = self.migration_status.ids_in_rd
|
||||
child_descriptions:dict = self.asm_data.get_prop_val_for_all_ids("Bezeichnung")
|
||||
parent_description:str = self.asm_metadata.get_property_values("Bezeichnung1")
|
||||
|
||||
descriptions = child_descriptions.copy()
|
||||
descriptions[asm_id] = parent_description
|
||||
|
||||
prt_num = []
|
||||
description = []
|
||||
migration_status = []
|
||||
evaluation = []
|
||||
assessment = []
|
||||
comment = []
|
||||
|
||||
for partnumber_string in sorting:
|
||||
partnumber = partnumber_string.split("-")[-1]
|
||||
prt_num.append(partnumber)
|
||||
if partnumber not in descriptions:
|
||||
description.append("")
|
||||
else:
|
||||
description.append(descriptions[partnumber])
|
||||
migration_status.append("VERFÜGBAR" if migrated[partnumber] is True else "IMPORTIEREN")
|
||||
evaluation.append('')
|
||||
for id_str in sorting:
|
||||
id = id_str.split("-")[-1]
|
||||
prt_num.append(id)
|
||||
assessment.append('')
|
||||
comment.append('')
|
||||
|
||||
if id not in descriptions:
|
||||
description.append("")
|
||||
else:
|
||||
description.append(descriptions[id])
|
||||
|
||||
if id in migrated_ids:
|
||||
if id in self.migration_status.ids_in_progress:
|
||||
migration_status.append("PDM-Migration abgeschlossen")
|
||||
else:
|
||||
migration_status.append("Migration abgeschlossen (freigegeben)")
|
||||
|
||||
else:
|
||||
migration_status.append("Migration ausstehend")
|
||||
|
||||
data = {
|
||||
"Sortierung": sorting,
|
||||
"Teilenummer": prt_num,
|
||||
"Bezeichnung": description,
|
||||
"Migrationsstatus": migration_status,
|
||||
"Beurteilung": evaluation,
|
||||
"Beurteilung": assessment,
|
||||
"Bemerkung": comment
|
||||
}
|
||||
return data
|
||||
@@ -284,21 +149,21 @@ class ExcelExport:
|
||||
"""
|
||||
|
||||
if parent is None:
|
||||
parent = self.main_id
|
||||
parent = self.asm_id
|
||||
if sorting_strings is None:
|
||||
sorting_strings = [parent]
|
||||
|
||||
children = self._parent_children[parent]
|
||||
parent_children = self.asm_data.parent_children
|
||||
children = parent_children[parent]
|
||||
|
||||
for child in children:
|
||||
|
||||
sorting_strings.append(f"{previous_parents}-{parent}-{child}".lstrip("-"))
|
||||
|
||||
if child in self._parent_children:
|
||||
if child in parent_children:
|
||||
self._create_sorting_string(parent=child, previous_parents=previous_parents + f"-{parent}", sorting_strings=sorting_strings)
|
||||
|
||||
return sorting_strings
|
||||
|
||||
|
||||
class Treeview:
|
||||
|
||||
@@ -315,144 +180,307 @@ class Treeview:
|
||||
|
||||
console_colors = {"RED": "\033[41m",
|
||||
"GREEN": "\033[42m",
|
||||
"BLUE": "\033[44m",
|
||||
"RESET": "\033[0m"
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, main_partnumber, sivas_src_file, rd_src_dir, rd_src_file):
|
||||
self.main_partnumber = main_partnumber
|
||||
self._sivas = SivasPartnumbers(main_sivas_id=main_partnumber, file_dir=sivas_src_file)
|
||||
self._ruledesigner = RuleDesignerPartnumbers(ruledesigner_ids_dir=rd_src_dir, ruledesigner_ids_filename=rd_src_file)
|
||||
self._sivas_rd_comparison = SivasRuleDesignerComparison(sivas_main_id=main_partnumber, sivas_ids_dir=sivas_src_file, rd_ids_dir=rd_src_dir, rd_ids_filename=rd_src_file)
|
||||
self._parent_children = self._sivas.get_ordered_parent_children()
|
||||
self._descriptions = self._sivas.get_partnumber_descriptions()
|
||||
self._max_tree_depth = self._sivas.get_max_bom_depth()
|
||||
self._longest_partnumber = self._sivas.get_longest_partnumber()
|
||||
self._migration_status = self._sivas_rd_comparison.get_migration_status()
|
||||
def __init__(self,
|
||||
asm_id: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 = 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
|
||||
|
||||
@property
|
||||
def migration_status(self):
|
||||
if self._migration_status is None:
|
||||
self._migration_status = MigrationStatus(self.asm_bom.child_ids, self.rd_data.ids)
|
||||
return self._migration_status
|
||||
|
||||
@property
|
||||
def longest_entry(self):
|
||||
|
||||
if self._longest_entry is None:
|
||||
longest_color_code = max(Treeview.console_colors.values(), key=len)
|
||||
longest_connector_symbol = max(Treeview.connector_symbols.values(), key=len)
|
||||
|
||||
padding_after_id = 2
|
||||
|
||||
if self._longest_entry is None:
|
||||
|
||||
self._longest_entry = (self.asm_bom.max_bom_depth) * len(Treeview.prefix_types["PIPE"]) \
|
||||
+ len(longest_connector_symbol) \
|
||||
+ len(longest_color_code) \
|
||||
+ len(self.asm_bom.longest_child_id) \
|
||||
+ len(Treeview.console_colors["RESET"]) \
|
||||
+ padding_after_id
|
||||
return self._longest_entry
|
||||
|
||||
|
||||
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"}
|
||||
|
||||
self.asm_bom.extend_asm_bom(self.asm_id, metadata)
|
||||
|
||||
|
||||
def generate_tree(self):
|
||||
self._tree_header()
|
||||
self._tree_body()
|
||||
self._tree_summary()
|
||||
self._add_asm_id_to_bom()
|
||||
self._print_tree()
|
||||
self._print_summary()
|
||||
|
||||
def _tree_header(self):
|
||||
|
||||
"""Prints root number (number for which the user wants to see the tree) to the console"""
|
||||
|
||||
background_color = self._set_text_background_color(self.main_partnumber)
|
||||
print(f"{background_color}{self.main_partnumber}{Treeview.console_colors['RESET']}")
|
||||
def _print_tree(self, parent_id=None, prefix=""):
|
||||
|
||||
def _tree_body(self, parent_id=None, prefix=""):
|
||||
"""Gibt die Baugruppenstruktur in Form eines Baumes in der Konsole aus"""
|
||||
|
||||
"""Prints the body (all partnumbers except root/main-partnumber) of the tree to the console"""
|
||||
parent_children = self.asm_bom.parent_children
|
||||
children_ids = parent_children[parent_id]
|
||||
|
||||
if parent_id is None:
|
||||
parent_id = self.main_partnumber
|
||||
|
||||
children_ids = self._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 self._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_all_sivas_ids = len(self._migration_status)
|
||||
amount_migrated_ids = sum(migration_status == True for migration_status in self._migration_status.values())
|
||||
amount_unmigrated_ids = amount_all_sivas_ids - amount_migrated_ids
|
||||
@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"""
|
||||
|
||||
progress = int(amount_migrated_ids / amount_all_sivas_ids * 100)
|
||||
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:
|
||||
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
|
||||
|
||||
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, "─")
|
||||
empty_padding = "".ljust(column_width, " ")
|
||||
|
||||
print(f"""
|
||||
╭─────────────────────────────────────{border_padding}╮
|
||||
│ Auswertung {empty_padding}│
|
||||
├───────────────────────────────────┬─{border_padding}┤
|
||||
│ Anzahl abgeschlossene Migrationen │ {str(amount_migrated_ids).ljust(column_width)}│
|
||||
│ Anzahl fehlende Migrationen │ {str(amount_unmigrated_ids).ljust(column_width)}│
|
||||
├───────────────────────────────────┼─{border_padding}┤
|
||||
│ Migrationsfortschritt │ {f"{progress}%".ljust(column_width)}│
|
||||
╰───────────────────────────────────┴─{border_padding}╯
|
||||
""")
|
||||
|
||||
red_entry = f"│{Treeview.console_colors['RED'] } {Treeview.console_colors['RESET']}│ Migration ausstehend │ {str(amount_pending).ljust(column_width)}│"
|
||||
|
||||
if not self.no_blue_ids:
|
||||
blue_entry = f"│{Treeview.console_colors['BLUE'] } {Treeview.console_colors['RESET']}│ PDM-Migration abgeschlossen │ {str(amount_in_work).ljust(column_width)}│\n"
|
||||
green_entry = f"│{Treeview.console_colors['GREEN']} {Treeview.console_colors['RESET']}│ Migration abgeschlossen (Freigegeben) │ {str(amount_released).ljust(column_width)}│\n"
|
||||
|
||||
else:
|
||||
blue_entry = ""
|
||||
green_entry = f"│{Treeview.console_colors['GREEN']} {Treeview.console_colors['RESET']}│ Migration abgeschlossen │ {str(amount_in_rd).ljust(column_width)}│\n"
|
||||
red_entry = f"│{Treeview.console_colors['RED'] } {Treeview.console_colors['RESET']}│ Migration ausstehend │ {str(amount_pending).ljust(column_width)}│"
|
||||
|
||||
|
||||
|
||||
header = f"""
|
||||
╭───────────────────────────────────────────{border_padding}╮
|
||||
│ Auswertung {empty_padding}│
|
||||
├─┬───────────────────────────────────────┬─{border_padding}┤
|
||||
"""
|
||||
progress_segment = f"""
|
||||
├─┴───────────────────────────────────────┼─{border_padding}┤
|
||||
│ Migrationsfortschritt │ {f"{progress}%".ljust(column_width)}│
|
||||
"""
|
||||
|
||||
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):
|
||||
|
||||
"""Prints tree entry"""
|
||||
"""Eintrag in die Konsole drucken"""
|
||||
|
||||
# Necessary for calculating the padding-width after the partnumber
|
||||
longest_entry = self._max_tree_depth * len(Treeview.prefix_types["PIPE"]) \
|
||||
+ len(connector) \
|
||||
+ len(self._longest_partnumber) \
|
||||
+ len(Treeview.console_colors["RESET"]) \
|
||||
+ len(Treeview.console_colors["RED"]) # other color codes (ansi escape sequences) could have been used instead since they all have the same length
|
||||
text_before_description = f"{prefix}{connector}{color}{child_id}{Treeview.console_colors['RESET']}".ljust(self.longest_entry)
|
||||
description = self.asm_bom.get_prop_val_for_single_id(child_id, "Bezeichnung")
|
||||
|
||||
text_before_description = f"{prefix}{connector}{color}{child_id}{Treeview.console_colors['RESET']}".ljust(longest_entry)
|
||||
description = self._descriptions[child_id]
|
||||
|
||||
print(f"{text_before_description} {description}")
|
||||
print(f"{text_before_description}{description}")
|
||||
|
||||
|
||||
def _last_child_check(self, child_index, children):
|
||||
def _last_child_check(self, child_idx, children_ids):
|
||||
|
||||
"""Checks, if current child is the last child of the assembly"""
|
||||
"""Prüft, ob aktuelle ID das letzte Kind der Baugruppe ist"""
|
||||
|
||||
return True if child_index == len(children) - 1 else False
|
||||
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):
|
||||
|
||||
"""Chooses connector symbol for current tree entry"""
|
||||
"""Legt Verbindungssymbol für aktuellen Eintrag im Baum fest"""
|
||||
|
||||
return Treeview.connector_symbols["ELBOW"] if child_is_last_child else Treeview.connector_symbols["T"]
|
||||
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):
|
||||
|
||||
"""Creates prefix (part before the connector symbol) for each tree entry"""
|
||||
"""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, partnumber):
|
||||
def _set_background_color(self, id):
|
||||
|
||||
"""Sets background color of partnumber in console depending on its migration status
|
||||
- Migration completed: green
|
||||
- Migration pending: red"""
|
||||
"""Legt Hintergrundfarbe der Teilenummer basierend auf ihrem Migrationsstatus fest:
|
||||
|
||||
return Treeview.console_colors["GREEN"] if self._migration_status[partnumber] is True else Treeview.console_colors["RED"]
|
||||
- grün: Migration vollständig abgeschlossen und freigegeben
|
||||
- blau: Migration ins PDM abgeschlossen
|
||||
- rot: Migration ausstehend"""
|
||||
|
||||
|
||||
if id in self.migration_status.ids_pending:
|
||||
return Treeview.console_colors["RED"]
|
||||
|
||||
if not self.no_blue_ids:
|
||||
if id in self.migration_status.ids_in_progress:
|
||||
return Treeview.console_colors["BLUE"]
|
||||
|
||||
return Treeview.console_colors["GREEN"]
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Das Programm gibt Auskunft über den aktuellen Migrationsstand', prog='compare_lists')
|
||||
parser.add_argument('-s', '--sivasid', action='store', required=True, help='Eingabe der SIVAS-Teilenummer, für die der Migrationsstatus angezeigt werden soll', metavar="id")
|
||||
parser.add_argument('-t', '--tree', action='store_true', help='Erzeugt Baumdarstellung aller Teilenummern. grün = Teilenummer vorhanden, rot = Migration ausstehend')
|
||||
parser.add_argument('-e', '--excel', action='store_true', help='Erzeugt Excel-Datei in der alle Teilenummern, die migriert werden müssen, aufgelistet sind')
|
||||
|
||||
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
|
||||
|
||||
environment = Environment()
|
||||
# Custom Actions
|
||||
parser.register("action", "GetIDsFromCFG", GetExcludedIDsFromCFG)
|
||||
parser.register("action", "lstrip", LStrip)
|
||||
parser.register("action", "LocateSQLCredentials", LocateSQLCredentials)
|
||||
|
||||
if args.excel:
|
||||
num = args.sivasid.lstrip()
|
||||
excel_comparison = ExcelExport(num, environment.data_dir, environment.data_dir, environment.ruledesigner_csv_filename)
|
||||
excel_comparison.write_excel()
|
||||
elif args.tree:
|
||||
num = args.sivasid.lstrip()
|
||||
treeview_comparison = Treeview(num, environment.data_dir, environment.data_dir, environment.ruledesigner_csv_filename)
|
||||
treeview_comparison.generate_tree()
|
||||
else:
|
||||
parser.print_help()
|
||||
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')
|
||||
|
||||
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"))
|
||||
|
||||
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"))
|
||||
|
||||
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()
|
||||
+34
-40
@@ -2,8 +2,7 @@ import os
|
||||
from tkinter import messagebox
|
||||
import update_database
|
||||
|
||||
from manage_configs import SivasAssemblyData
|
||||
from get_teilestamm import FetchSivasTeilestamm
|
||||
from manage_configs import SivasBOM, SivasTeilestamm, RuleDesignerDatabaseConfig, MigrationStatus
|
||||
|
||||
"""
|
||||
Contains the logic for the MVC of CreoMigrate Gui
|
||||
@@ -42,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"
|
||||
}
|
||||
|
||||
@@ -96,6 +95,8 @@ class Controller(Observer):
|
||||
ret = self._environment.cfg_dir
|
||||
elif env_path == "log":
|
||||
ret = self._environment.log_dir
|
||||
elif env_path == "rd_dbase_filename":
|
||||
ret = self._environment.ruledesigner_csv_filename
|
||||
else:
|
||||
raise Exception("this path is not defined "+env_path)
|
||||
return ret
|
||||
@@ -114,36 +115,30 @@ class Controller(Observer):
|
||||
orange[red] = True
|
||||
return orange
|
||||
|
||||
def get_csv_files_of_asm(self, asmid, uselocal):
|
||||
""" sich die Einzelteile als .csv und den Aufbau des Assemblies als .json holen """
|
||||
def get_csv_files_of_asm(self, asm_id, load_local_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")
|
||||
sivas_teilestamm = FetchSivasTeilestamm(asmid, data_dir)
|
||||
rd_dbase_filename = self.get_env("rd_dbase_filename")
|
||||
|
||||
# falls uselocal True, dann keine Serverabfrage, sondern mit den lokalen Daten arbeiten
|
||||
sivas_teilestamm.fetch_from_db(uselocal)
|
||||
if not uselocal:
|
||||
try:
|
||||
sivas_teilestamm.get_unmigrated_teilstamm_csvs()
|
||||
except:
|
||||
self.message_error('Abfrage hat nicht funktioniert', 'sivas_db_error')
|
||||
return
|
||||
excluded_bom_ids = self._config.bom_config.get_excluded_ids("ExcludedPartnumbersInBOM") # Artikelnummern wie z.B. "MA0000006" sollen nicht im Baum dargestellt werden
|
||||
|
||||
redids = sivas_teilestamm.get_red_ids()
|
||||
sivas_bom = SivasBOM(asm_id, data_dir, asm_id + '.json', load_local_bom, excluded_bom_ids)
|
||||
|
||||
sad = SivasAssemblyData(data_dir, asmid + '.json')
|
||||
parent_children = sad.get_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
|
||||
|
||||
self._model.set_red_ids(redids)
|
||||
self._model.set_parent_children(parent_children)
|
||||
self._model.set_asm_id(asmid)
|
||||
rd_data = RuleDesignerDatabaseConfig(data_dir, rd_dbase_filename, load_local_rd_ids)
|
||||
|
||||
def get_bom_properties(self, asmid):
|
||||
""" Holt sich aus Stückliste alle Eigenschaften der Stücklistenpositionen (z.B. Bezeichnung)"""
|
||||
data_dir = self.get_env("data")
|
||||
assembly_data = SivasAssemblyData(data_dir, asmid + '.json')
|
||||
migration_status = MigrationStatus(sivas_bom.child_ids, rd_data.ids)
|
||||
pending_ids = migration_status.ids_pending
|
||||
|
||||
bom_properties = assembly_data.get_all_bom_properties()
|
||||
self._model.set_bom_properties(bom_properties)
|
||||
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(sivas_bom.all_bom_properties)
|
||||
|
||||
def set_model(self, model):
|
||||
self._model = model
|
||||
@@ -162,15 +157,11 @@ class Controller(Observer):
|
||||
|
||||
def get_current_asm_id(self):
|
||||
self._model.get_asm_id()
|
||||
def set_current_asm_id(self, asm_id, loadlocal=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, loadlocal)
|
||||
self.get_bom_properties(asm_id)
|
||||
#self._load_cid_data2model(asm_id)
|
||||
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 """
|
||||
@@ -194,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:
|
||||
@@ -293,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, 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()
|
||||
|
||||
@@ -328,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)
|
||||
|
||||
@@ -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()
|
||||
@@ -1,50 +0,0 @@
|
||||
import argparse
|
||||
import os
|
||||
from manage_configs import MigrationStatus
|
||||
import update_database
|
||||
|
||||
class FetchSivasTeilestamm:
|
||||
def __init__(self, assembly_json_name, assembly_json_out_dir=os.environ.get('CREMIG_DATA'), csv_out_dir=os.environ.get('CREMIG_DATA')):
|
||||
self.assembly_json_out_dir = assembly_json_out_dir
|
||||
self.assembly_json_name = str(assembly_json_name).replace('.json', "") + ".json"
|
||||
self.csv_out_dir = csv_out_dir
|
||||
self.migration = None
|
||||
|
||||
def fetch_from_db(self, use_local=False):
|
||||
self.migration = MigrationStatus(self.assembly_json_out_dir, self.assembly_json_name)
|
||||
if use_local:
|
||||
self.migration.use_local()
|
||||
|
||||
def get_unmigrated_teilstamm_csvs(self):
|
||||
"""Generiert CSV-Dateien für alle noch nicht migrierten Stücklistenteile der angegebenen Baugruppennummer"""
|
||||
if self.migration == None:
|
||||
raise Exception("not yet connected to local data or database")
|
||||
unmigrated_ids = self.migration.get_unmigrated_ids()
|
||||
update_database.get_sivas_teilestamm(unmigrated_ids, self.csv_out_dir)
|
||||
|
||||
def get_red_ids(self):
|
||||
if self.migration == None:
|
||||
raise Exception("not yet connected to local data or database")
|
||||
return self.migration.get_unmigrated_ids()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='fetches data from sivas or ruledesigner fusion', prog='update_database')
|
||||
parser.add_argument('-s', '--sivasid', action='store', help='fetch teilestamm-data for all unmigrated parts in the bom of a given asm-id. Specifiy multiple ids as comma separated values without space, e.g. 200000089,200000090', metavar='id(s)')
|
||||
parser.add_argument('-l', '--uselocal', action='store_true', help='Do not connect to database. Use local files (if already existing) for the given asm-id', default=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
out_dir = os.environ.get('CREMIG_DATA')
|
||||
|
||||
if args.sivasid:
|
||||
sivas_asm_id = args.sivasid.strip().replace(".json","")
|
||||
sivas_teilestamm = FetchSivasTeilestamm(sivas_asm_id, out_dir)
|
||||
if args.uselocal:
|
||||
sivas_teilestamm.use_local(args.uselocal)
|
||||
sivas_teilestamm.fetch_from_db()
|
||||
if args.uselocal == False:
|
||||
sivas_teilestamm.get_unmigrated_teilstamm_csvs(args.uselocal)
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
+23
-19
@@ -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')
|
||||
|
||||
|
||||
+395
-119
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
from configparser import ConfigParser, RawConfigParser
|
||||
import pymssql
|
||||
from configparser import ConfigParser
|
||||
import pandas as pd
|
||||
import update_database
|
||||
|
||||
@@ -10,8 +11,10 @@ Diese Lib verwaltet alle Config dateien (als .cfg oder als .json) von CreoMigrat
|
||||
"""
|
||||
class JsonBasedConfig:
|
||||
"""Klasse mit generellen Methoden für alle Config Objekte mit json files"""
|
||||
def __init__(self, config_dir, config_file):
|
||||
def __init__(self, config_dir: str, config_file: str):
|
||||
"""merke dir den Pfad zu den Configdateien"""
|
||||
self._config_dir = config_dir
|
||||
self._config_file = config_file
|
||||
self._config_path = os.path.join(config_dir, config_file)
|
||||
|
||||
def load_json_from_file(self):
|
||||
@@ -27,8 +30,10 @@ class JsonBasedConfig:
|
||||
|
||||
class CSVBasedConfig:
|
||||
|
||||
def __init__(self, config_dir, config_file):
|
||||
def __init__(self, config_dir: str, config_file: str):
|
||||
"""merke dir den Pfad zu den Configdateien"""
|
||||
self._config_dir = config_dir
|
||||
self._config_file = config_file
|
||||
self._config_path = os.path.join(config_dir, config_file)
|
||||
|
||||
def load_csv_from_file(self):
|
||||
@@ -36,156 +41,412 @@ class CSVBasedConfig:
|
||||
return pd.read_csv(self._config_path, sep=";", dtype="object")
|
||||
|
||||
|
||||
class SivasAssemblyData(JsonBasedConfig):
|
||||
class SQLBasedConfig:
|
||||
def __init__(self, server, user, pw, dbase):
|
||||
self._server = server
|
||||
self._user = user
|
||||
self._pw = pw
|
||||
self._dbase = dbase
|
||||
|
||||
def query_db(self, sql_statement) -> list:
|
||||
|
||||
"""Führt Datenbankabfrage aus und gibt Daten zurück"""
|
||||
with pymssql.connect(self._server, self._user, self._pw, self._dbase) as conn:
|
||||
with conn.cursor(as_dict=True) as cursor:
|
||||
try:
|
||||
cursor.execute(sql_statement)
|
||||
except pymssql.ProgrammingError:
|
||||
print("SQL-Statement konnte nicht verarbeitet werden!")
|
||||
raise
|
||||
ret = cursor.fetchall()
|
||||
return ret
|
||||
|
||||
class RuleDesignerSQLData(SQLBasedConfig):
|
||||
def __init__(self,
|
||||
server=os.environ.get("RD_SQL_DATABASE_SERVER"),
|
||||
user=os.environ.get("RD_SQL_DATABASE_USER"),
|
||||
pw=os.environ.get("RD_SQL_DATABASE_PW"),
|
||||
dbase=os.environ.get("RD_SQL_DATABASE_NAME")):
|
||||
super().__init__(server, user, pw, dbase)
|
||||
self._rd_statuses = None
|
||||
|
||||
@property
|
||||
def rd_statuses(self) -> dict:
|
||||
"""Gibt Teilenummer und zugehörigen Status zurück"""
|
||||
|
||||
if self._rd_statuses is None:
|
||||
|
||||
# Datenbankabfrage aller Teilenummern (codCodice) und der zugehörigen Status (codStatus)
|
||||
sql_statement = "SELECT [codCodice] AS ID, [codStatus] AS STATUS FROM [FUSION_PDM].[dbo].[CODICI]"
|
||||
sql_data = self.query_db(sql_statement)
|
||||
|
||||
# Jeder Teilenummer den sprechenden Stsatusnamen, statt der internen Statusnummer zuweisen
|
||||
# Dafür laden der Mapping-Datei für die Statusnummern (z.B. 3 = "FREIGEGEBEN")
|
||||
rd_status_cfg = IniBasedConfig(os.environ.get("CREMIG_CFG"), "RuleDesignerStatusnamen.cfg")
|
||||
rd_status_sections = rd_status_cfg.sections()
|
||||
rd_status_names = rd_status_cfg.get_dict(rd_status_sections[0])
|
||||
|
||||
|
||||
self._rd_statuses = {}
|
||||
for id_status_dict in sql_data:
|
||||
id = id_status_dict["ID"]
|
||||
status = id_status_dict["STATUS"]
|
||||
self._rd_statuses[id] = rd_status_names[str(status)]
|
||||
|
||||
return self._rd_statuses
|
||||
|
||||
|
||||
class SivasBOM(JsonBasedConfig):
|
||||
"""Auslesen der Assembly-Informationen zu einer angegebenen Baugruppennummer"""
|
||||
|
||||
def __init__(self, config_dir, config_file):
|
||||
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.assembly_data = None
|
||||
|
||||
def _lazy_init_assembly_data(func):
|
||||
"""Sorgt dafür, dass json-Datei der Baugruppe nur 1x geladen wird"""
|
||||
self.main_id = asm_id
|
||||
self._load_local_json = load_local_json
|
||||
self.excluded_ids_by_user = excluded_ids
|
||||
self._excluded_ids_in_bom = None
|
||||
self._bom_data = None
|
||||
self._child_ids = None
|
||||
self._parent_children = None
|
||||
self._longest_child_id = None
|
||||
self._max_bom_depth = None
|
||||
self._all_bom_properties = None
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self.assembly_data is None:
|
||||
self.assembly_data = self.load_json_from_file()
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
def create_asm_json(self) -> bool:
|
||||
"""Erstellt JSON mit den aktuellen Baugruppeninformationen"""
|
||||
|
||||
if not self._load_local_json:
|
||||
update_database.get_sivas_bom(self._config_file.removesuffix(".json"))
|
||||
|
||||
@_lazy_init_assembly_data
|
||||
def get_assembly_ids(self):
|
||||
|
||||
@property
|
||||
def bom_data(self) -> dict:
|
||||
""" Liest die JSON-Datei der Baugruppenstückliste ein und speichert die Informationen als dict. """
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
self._bom_data = self.load_json_from_file()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
if self.excluded_ids_by_user is not None:
|
||||
self._remove_excluded_ids_from_bom()
|
||||
|
||||
return self._bom_data
|
||||
|
||||
@property
|
||||
def child_ids(self) -> set:
|
||||
"""Gibt alle Teilenummern der Stückliste zurück (auch Teilenummern aus tieferen Baugruppenebenen)"""
|
||||
|
||||
assembly_ids = set()
|
||||
for child_id, parent_properties in self.assembly_data.items():
|
||||
assembly_ids.add(child_id)
|
||||
for parent_property in parent_properties:
|
||||
parent_id = parent_property["Parent"]
|
||||
assembly_ids.add(parent_id)
|
||||
return assembly_ids
|
||||
if self._child_ids is None:
|
||||
self._child_ids = set(self.bom_data.keys())
|
||||
return self._child_ids
|
||||
|
||||
@_lazy_init_assembly_data
|
||||
def get_parent_children(self) -> dict:
|
||||
@property
|
||||
def parent_children(self) -> dict:
|
||||
"""Ordnet Stücklistenpositionen (Kinder) den Baugruppennummern (Eltern) zu. Die Kinder sind entsprechend ihrer Positionsnummern in den Stücklisten sortiert."""
|
||||
|
||||
unordered_parent_children = {}
|
||||
if self._parent_children is None:
|
||||
unordered_parent_children = {}
|
||||
|
||||
for child_id, parent_properties in self.assembly_data.items():
|
||||
for parent_property in parent_properties:
|
||||
parent_id = parent_property["Parent"]
|
||||
child_posnr_in_parent_bom = int(parent_property["Position"].lstrip(".").split("/")[0])
|
||||
for child_id, parent_properties in self.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])
|
||||
|
||||
if parent_id not in unordered_parent_children:
|
||||
unordered_parent_children[parent_id] = {}
|
||||
if parent_id not in unordered_parent_children:
|
||||
unordered_parent_children[parent_id] = {}
|
||||
|
||||
unordered_parent_children[parent_id][child_posnr_in_parent_bom] = child_id
|
||||
unordered_parent_children[parent_id][child_posnr_in_parent_bom] = child_id
|
||||
|
||||
ordered_parent_children = {}
|
||||
for parent, children in unordered_parent_children.items():
|
||||
for pos_nr, child in sorted(children.items()):
|
||||
if parent not in ordered_parent_children:
|
||||
ordered_parent_children[parent] = []
|
||||
ordered_parent_children[parent].append(child)
|
||||
self._parent_children = ordered_parent_children
|
||||
return self._parent_children
|
||||
|
||||
def 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] = []
|
||||
|
||||
ordered_parent_children = {}
|
||||
for parent, children in unordered_parent_children.items():
|
||||
for pos_nr, child in sorted(children.items()):
|
||||
if parent not in ordered_parent_children:
|
||||
ordered_parent_children[parent] = []
|
||||
ordered_parent_children[parent].append(child)
|
||||
return ordered_parent_children
|
||||
self._bom_data[id].append(metadata)
|
||||
return self._bom_data
|
||||
|
||||
def get_longest_assembly_id(self) -> str:
|
||||
def _remove_excluded_ids_from_bom(self) -> None:
|
||||
"""Entfernt ID's aus den Stücklistendaten, die nicht enthalten sein sollen"""
|
||||
|
||||
# Ausgeschlossene Teilenummern, die vom User angegeben wurden, können nicht einfach aus der Stückliste entfernt werden.
|
||||
# Sie müssen zunächst aufbereiten werden, denn:
|
||||
# - Ausgeschlossene Nummern, die kein Bestandteil der aktuellen Baugruppe sind, sind nicht von Interesse
|
||||
# - Kinder von ausgeschlossenen Teilenummern, die vom User nicht angegeben wurden sind ggf. von Interesse, müssen aber zunächst
|
||||
# ermittelt werden
|
||||
|
||||
ids_to_remove = self.excluded_ids_in_bom
|
||||
|
||||
if not ids_to_remove:
|
||||
return
|
||||
|
||||
if self.main_id in ids_to_remove:
|
||||
raise ValueError(f"Haupt-Teilenummer '{self.main_id}' darf nicht ausgeschlossen sein. Bitte $CREMIG_CFG/BOM-Settings.cfg -> Sektion 'ExcludedPartnumbersInBOM' prüfen, falls diese Datei zur Definition der ausgeschlossenen Teilenummern verwendet wurde")
|
||||
|
||||
filtered_bom_data = {}
|
||||
for id, usages in self.bom_data.items():
|
||||
|
||||
if id in ids_to_remove:
|
||||
continue
|
||||
|
||||
for properties in usages:
|
||||
|
||||
if properties["Parent"] in ids_to_remove:
|
||||
continue
|
||||
if id not in filtered_bom_data:
|
||||
filtered_bom_data[id] = []
|
||||
|
||||
filtered_bom_data[id].append(properties)
|
||||
|
||||
self._bom_data = filtered_bom_data
|
||||
|
||||
@property
|
||||
def excluded_ids_in_bom(self) -> list:
|
||||
|
||||
""" Gibt alle Teilenummern zurück, die in der SIVAS-Stückliste vorliegen, aber davon ausgeschlossen sein sollen. """
|
||||
|
||||
if self._excluded_ids_in_bom is None:
|
||||
|
||||
excluded_ids_by_user = self.excluded_ids_by_user
|
||||
|
||||
to_remove = {}
|
||||
for id, usages in self.bom_data.items():
|
||||
|
||||
# Teilenummern zunächst als ausgeschlossen anzusehen und das später zu widerrufen erleichtert die Definition der Logik
|
||||
to_remove[id] = True
|
||||
|
||||
# Teilenummern, die vom User ausgeschlossen wurden, aber kein Bestandteil dieser Baugruppe sind, interessieren uns nicht!
|
||||
if id in excluded_ids_by_user:
|
||||
continue
|
||||
|
||||
# Kinder von ausgeschlossenen Teilenummern werden ebenfalls als ausgeschlossen betrachtet, außer sie sind Kinder von
|
||||
# weiteren Teilenummern, die nicht augeschlossen sein sollen
|
||||
# Hinweis:
|
||||
# Für die Prüfung, ob das Kind in weiteren Teilenummern verbaut ist, würde sich die Methode "parent_children" anbieten.
|
||||
# Die ist aber Lazy, sprich, wenn wir die jetzt aufrufen, arbeitet sie mit den Stücklistendaten, vor dem die Teilenummern
|
||||
# entfernt wurden. Die Laziness dafür zu entfernen macht aber auch keinen Sinn, weil diese Methode bei der Erstellung von
|
||||
# baumförmigen Migrationsübersichten ständig aufgerufen wird, d.h. die Performance würde darunter stark leiden!
|
||||
|
||||
for properties in usages:
|
||||
if properties["Parent"] not in excluded_ids_by_user:
|
||||
to_remove[id] = False
|
||||
|
||||
self._excluded_ids_in_bom = list(filter(lambda id: to_remove[id] is True, to_remove))
|
||||
return self._excluded_ids_in_bom
|
||||
|
||||
@property
|
||||
def longest_child_id(self) -> str:
|
||||
"""Gibt die längste Teilenummer von allen Stücklistenpositionen zurück"""
|
||||
|
||||
return max(self.get_assembly_ids())
|
||||
if self._longest_child_id is None:
|
||||
self._longest_child_id = max(self.child_ids)
|
||||
return self._longest_child_id
|
||||
|
||||
@_lazy_init_assembly_data
|
||||
def get_max_bom_depth(self) -> int:
|
||||
@property
|
||||
def max_bom_depth(self) -> int:
|
||||
"""Gibt die tiefste Stücklistenebene zurück (0 = höchste Ebene)"""
|
||||
|
||||
bom_levels = []
|
||||
for parents in self.assembly_data.values():
|
||||
for parent in parents:
|
||||
current_bom_level = parent["Position"].count(".")
|
||||
bom_levels.append(current_bom_level)
|
||||
return max(bom_levels)
|
||||
if self._max_bom_depth is None:
|
||||
bom_levels = []
|
||||
for parents in self.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
|
||||
|
||||
@_lazy_init_assembly_data
|
||||
def get_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"""
|
||||
|
||||
for parent_data in self.assembly_data.values():
|
||||
return list(parent_data[0])
|
||||
|
||||
@_lazy_init_assembly_data
|
||||
def get_bom_property_values(self, property) -> dict:
|
||||
"""Gibt eine Eigenschaft zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)"""
|
||||
if id not in self.bom_data:
|
||||
raise ValueError("Teilenummer befindet sich nicht in der Baugruppe!")
|
||||
|
||||
if property not in self.get_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.assembly_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
|
||||
|
||||
@_lazy_init_assembly_data
|
||||
def get_all_bom_properties(self) -> dict:
|
||||
@property
|
||||
def all_bom_properties(self) -> dict:
|
||||
"""Gibt alle Eigenschaften zu den Teilenummern der Stückliste zurück (z.B. Bezeichnung)"""
|
||||
|
||||
all_properties = {}
|
||||
property_names = self.get_property_names()
|
||||
for property_name in property_names:
|
||||
all_properties[property_name] = self.get_bom_property_values(property_name)
|
||||
return all_properties
|
||||
if self._all_bom_properties is None:
|
||||
|
||||
self._all_bom_properties = {}
|
||||
|
||||
for 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
|
||||
|
||||
|
||||
class RuleDesignerDatabaseConfig(CSVBasedConfig):
|
||||
"""Auslesen der Angaben aus der RuleDesigner-Datenbank-CSV"""
|
||||
def __init__(self, config_dir=".", config_file="RD_Teilenummern.csv"):
|
||||
"""Auslesen der CSV-Datei, in der alle RuleDesigner-Teilenummern aufgelistet sind"""
|
||||
def __init__(self, config_dir=".", config_file="RD_Teilenummern.csv", load_local_csv=False):
|
||||
super().__init__(config_dir, config_file)
|
||||
self._load_local_csv = load_local_csv
|
||||
self._ids = None
|
||||
|
||||
def get_ruledesigner_ids(self):
|
||||
return tuple(self.load_csv_from_file()['Teilenummern'])
|
||||
def copy_csv_from_server(self):
|
||||
"""Kopiert CSV-Datei vom Server, in der alle Teilenummern, die sich in RuleDesigner befinden"""
|
||||
try:
|
||||
update_database.get_ruledesigner_ids()
|
||||
except:
|
||||
raise ValueError("CSV mit RuleDesigner-Teilenummern konnte nicht ins lokale Verzeichnis kopiert werden!")
|
||||
|
||||
@property
|
||||
def ids(self):
|
||||
if self._ids is None:
|
||||
|
||||
if not self._load_local_csv:
|
||||
self.copy_csv_from_server()
|
||||
|
||||
self._ids = tuple(self.load_csv_from_file()['Teilenummern'])
|
||||
return self._ids
|
||||
|
||||
|
||||
class MigrationStatus:
|
||||
def __init__(self, file_dir, assembly_json):
|
||||
self._file_dir = file_dir
|
||||
self._assembly_json = assembly_json
|
||||
self._uselocal = False
|
||||
"""Klasse gibt Informationen zum aktuellen Migrationsstand zurück, indem geprüft wird, ob die Nummern aus Sivas, auch in RuleDesigner vorliegen"""
|
||||
def __init__(self, asm_child_ids: tuple, rd_ids: tuple):
|
||||
self.asm_child_ids = asm_child_ids
|
||||
self.rd_ids = rd_ids
|
||||
self._rd_statuses = None
|
||||
self._overview = None
|
||||
self._ids_in_rd = None
|
||||
self._ids_in_progress = None
|
||||
self._ids_pending = None
|
||||
|
||||
@property
|
||||
def id_status(self) -> dict:
|
||||
"""Gibt zurück, ob die Stücklistenpositionen der Baugruppe migriert sind, oder nicht"""
|
||||
if self._overview is None:
|
||||
self._overview = {}
|
||||
for child_id in self.asm_child_ids:
|
||||
self._overview[child_id] = True if child_id in self.rd_ids else False
|
||||
return self._overview
|
||||
|
||||
def use_local(self):
|
||||
self._uselocal = True
|
||||
@property
|
||||
def ids_in_rd(self) -> list:
|
||||
"""Gibt alle Stücklistenpositionen zurück, die migriert sind"""
|
||||
|
||||
def get_migration_status(self) -> dict:
|
||||
"""Gibt zurück, ob die Teilenummern der Baugruppe bereits migriert wurden, oder nicht"""
|
||||
out_dir = self._file_dir
|
||||
if self._uselocal == False:
|
||||
ok = update_database.get_sivas_dbase(self._assembly_json, out_dir)
|
||||
if not ok:
|
||||
raise Exception("no json file could be generated")
|
||||
update_database.get_rd_dbase()
|
||||
else:
|
||||
if not os.path.exists(os.path.join(out_dir, self._assembly_json)):
|
||||
raise Exception("no local file exists "+ self._assembly_json)
|
||||
self.sivas_assembly_data = SivasAssemblyData(out_dir, self._assembly_json)
|
||||
sivas_assembly_ids = self.sivas_assembly_data.get_assembly_ids()
|
||||
self.ruledesigner_data = RuleDesignerDatabaseConfig(out_dir)
|
||||
ruledesigner_ids = self.ruledesigner_data.get_ruledesigner_ids()
|
||||
if self._ids_in_rd is None:
|
||||
self._ids_in_rd = [id for id, status in self.id_status.items() if status is True]
|
||||
return self._ids_in_rd
|
||||
|
||||
migration_status = {}
|
||||
for sivas_assembly_id in sivas_assembly_ids:
|
||||
migration_status[sivas_assembly_id] = True if sivas_assembly_id in ruledesigner_ids else False
|
||||
return migration_status
|
||||
@property
|
||||
def ids_in_progress(self) -> list:
|
||||
"""Gibt migrierte Stücklistenpositionen zurück, die aktuell eine Freigabe der Baugruppe verhindern, da sie sich in Arbeit befinden"""
|
||||
|
||||
def get_migrated_ids(self) -> list:
|
||||
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, die bereits in RuleDesigner migriert wurden"""
|
||||
|
||||
migrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is True]
|
||||
return migrated_ids
|
||||
if self._ids_in_progress is None:
|
||||
|
||||
def get_unmigrated_ids(self) -> list:
|
||||
"""Gibt alle Teilenummern aus der Baugruppenstückliste zurück, deren Migration in RuleDesigner noch aussteht"""
|
||||
rd_sql_data = RuleDesignerSQLData()
|
||||
rd_statuses = rd_sql_data.rd_statuses
|
||||
|
||||
unmigrated_ids = [assembly_id for assembly_id, migration_status in self.get_migration_status().items() if migration_status is False]
|
||||
return unmigrated_ids
|
||||
self._ids_in_progress = []
|
||||
for id in self.ids_in_rd:
|
||||
if rd_statuses[id] != "FREIGEGEBEN":
|
||||
self._ids_in_progress.append(id)
|
||||
return self._ids_in_progress
|
||||
|
||||
@property
|
||||
def ids_pending(self) -> list:
|
||||
"""Gibt alle Stücklistenpositionen zurück, die noch nicht migriert sind"""
|
||||
|
||||
if self._ids_pending is None:
|
||||
self._ids_pending = [id for id, status in self.id_status.items() if status is False]
|
||||
return self._ids_pending
|
||||
|
||||
|
||||
class 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._data = None
|
||||
|
||||
def create_csvs(self, ids):
|
||||
"""Erstellt CSV-Datei(en) mit den Teilestamminformationen zu der / den angegebenen Teilenummer(n)"""
|
||||
|
||||
if not self._load_local_csv:
|
||||
update_database.get_sivas_teilestamm(ids)
|
||||
|
||||
@property
|
||||
def teilestamm_data(self):
|
||||
"""Gibt CSV-Daten als Dataframe zurück"""
|
||||
|
||||
if self._data is None:
|
||||
self.create_csvs(self._config_file.removesuffix(".csv"))
|
||||
self._data = self.load_csv_from_file()
|
||||
|
||||
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.teilestamm_data[property_name][0]
|
||||
return ret
|
||||
|
||||
|
||||
class TOSGroupsConfig(JsonBasedConfig):
|
||||
@@ -505,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 """
|
||||
@@ -525,8 +804,8 @@ class Configs:
|
||||
|
||||
self.TOSGruppen = TOSGroupsConfig(cfg_dir)
|
||||
self.Materialien = MaterialTransConfig(cfg_dir)
|
||||
self._TeileNummern = None
|
||||
self._blacklist = None
|
||||
self.bom_config = BOMConfig(cfg_dir)
|
||||
self._teilenummern = None
|
||||
|
||||
def get_cfg_path(self):
|
||||
return self._cfg_dir
|
||||
@@ -539,17 +818,14 @@ class Configs:
|
||||
def get_ruledesigner_ids(self):
|
||||
if not self._data_dir:
|
||||
raise Exception("set_data_dir not called. path not set")
|
||||
if not self._TeileNummern:
|
||||
self._TeileNummern = RuleDesignerDatabaseConfig(self._data_dir).get_ruledesigner_ids()
|
||||
return self._TeileNummern
|
||||
if not self._teilenummern:
|
||||
self._teilenummern = RuleDesignerDatabaseConfig(self._data_dir, load_local_csv=True).ids
|
||||
return self._teilenummern
|
||||
|
||||
def get_blacklist(self):
|
||||
if not self._data_dir:
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+108
-106
@@ -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):
|
||||
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.")
|
||||
|
||||
def get_sivas_teilestamm(sivas_ids, out_dir, overwrite=True):
|
||||
"""call the sivas exe for getting all csv files with the item data for the given sivas ids"""
|
||||
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):
|
||||
|
||||
"""
|
||||
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)
|
||||
Binary file not shown.
Reference in New Issue
Block a user