Compare commits

...

10 Commits

Author SHA1 Message Date
s.steuer 1f82de5735 Testdatenbank für Programm 'DB Browser for SQLite' ergänzt (Mit diesem Programm wurden die SQL-Abfragen des Algorithmus gebaut) 2025-09-05 21:33:01 +02:00
s.steuer 5c8ca3e366 FIX - Bei der Freigabe von migrierten Baugruppen, bei denen keine Stückliste im Datensatz hinterlegt ist wird eine BOM_[...].csv ausgegeben, bei der in der Spalte 'Positionsnummer' keine Werte hinterlegt sind. Solche Stücklisten wurden fälschlicherweise eingelesen und von der Schnittstelle so interpretiert, als wären es komplett leere Stücklisten. Folglich wurden alle Positionsnummern aus der SIVAS-Stammstücklist entfernt. Dieses Problem wurde behoben. 2025-09-05 14:36:18 +02:00
s.steuer 4ece42825e Finalen Stand des Workflow 96 (Stand 250902) von Hr. Jakob ergänzt 2025-09-02 15:45:47 +02:00
s.steuer 2668eb89f8 Konfigurationen an geänderte Schalter in process_bom (siehe Commit 8826b2e) angeglichen. 2025-08-23 00:10:50 +02:00
s.steuer eaec4f591a Sektion 'Verwendung' an Argparse-Änderungen in process_bom (siehe Commit 8826b2e) angeglichen. 2025-08-23 00:08:52 +02:00
s.steuer 8826b2e132 Klasse 'AVWorkflowMessage' für die Bestimmung und Generierung der Workflowmeldung an die AV-Verantwortlichen implementiert / Klasse 'TOSWorkflowMessage' / Argparse-Schalter angepasst, sodass die AV-, bzw. TOS-Workflowmeldungen in der Konsole, oder als CSV-Dateien ausgegeben werden können. Schalter '-open_csv' entfernt, da nun ggf. auch mehrere CSV's gleichzeitig ausgegeben werden (für die Stückliste, für die Nummern der TOS-Meldung und die Nummern der AV-Meldung) 2025-08-23 00:06:32 +02:00
s.steuer f63f5c8332 Default-Rüstcodes in COMP an Teilestamm angeglichen + Foreign-Keys entfernt, da nicht benötigt 2025-08-23 00:02:32 +02:00
s.steuer 4ba0475629 Logik an die geänderten Tabellen (siehe Commit d745436) angepasst. Beim Update der TOS-Regeln und -Bedingungen wird zudem in VAR_TEIL_BEDINGUNG_2 und STKL_REGEL nur noch die Positionsnummer aktualisiert, aber nicht mehr die Bedingung oder Regel selbst. Das ist nicht nötig, weil ja eine Verschiebung erfolgt, also abgesehen von einer etwaigen Anpassung der Positionsnummer keine Änderungen erfolgen 2025-08-15 18:49:54 +02:00
s.steuer d745436431 Es wurde bisher nicht berücksichtigt, dass es die Verbindung zwischen den Tabellen COMP und VAR_TEIL_BEDINGUNG_2, bzw. STKL_REGEL nicht über eine Spalte COMP_ID, sondern über 2 Spalten 'UEBTEILENR' und 'POSNR' erfolgt. Die Daten der Tabellen wurden daran angepasst. Zudem wurden die Spaltenname von ID und PARENT in COMP korrigiert -> lauten in Wirklichkeit 'ROWID' und 'UEBTEILENR' 2025-08-15 18:46:14 +02:00
s.steuer f678b2ac2f Programmverwendung + Erklärung zu Fällen der Verschiebelogik ergänzt 2025-08-14 20:27:31 +02:00
7 changed files with 5439 additions and 342 deletions
+2 -2
View File
@@ -19,7 +19,7 @@
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"args": ["000000382", "cmd"]
"args": ["-bom", "-av", "-tos", "000000382", "cmd"]
},
{
"name": "OutputAsCSV",
@@ -28,7 +28,7 @@
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"args": ["000000382", "csv"]
"args": ["-bom", "-av", "-tos", "000000382", "csv"]
}
]
}
+418 -1
View File
@@ -1,3 +1,420 @@
# SchnittstellenanpassungRDSivas
Enthält die neue Verschiebelogik für Stücklistenpositionen.
Es sollen wegen folgender Problematik Anpassungen an der PDM-ERP-Schnittstelle (SIVAS-Workflow 96) durchgeführt werden:
Bei Stücklistenänderungen in SIVAS werden aktuell alle manuell durchgeführten Stücklistenänderungen (z.B. hinterlegen von Zuschnittswerten, Änderung des Rüstcodes, Entfernung des Dispoaktiv-Hakens, etc.) und hinterlegten TOS-Regeln / -Bedingungen entfernt.
Das liegt daran, dass die Stücklistenerstellung aktuell nach folgender, einfacher Logik erfolgt:
1. Workflow 96 löscht die gesamte alte Stammstückliste
2. Workflow 96 importiert die neue Stücklisten-CSV aus RuleDesigner und baut die Stückliste danach auf.
Wird nun vergessen, die manuellen Stücklistenänderungen / TOS-Regeln- und Bedingungen händisch in der SIVAS-Stammstückliste wiederherzustellen, stimmen unter Umständen Kalkulationen, Komissionierungen, usw. nicht mehr, was je nach Teilenummer und Auftrag einen großen finanziellen Schaden nach sich ziehen kann.
**Ziel der Schnittstellenanpassung**
Manuelle Stücklistenänderungen sowie TOS-Regeln und -Bedingungen sollen soweit möglich in die neue Stückliste übernommen werden.
Die Hauptaufgabe besteht folglich darin, eine Logik zu definieren, die feststellt, von welcher zu welcher Positionsnummer die Stücklistenpositionen infolge einer Stücklistenänderung verschoben wurden.
<ins>Die Feststellung dieser eindeutigen Zuordnung wird durch folgende Punkte erschwert</ins>
1. Die Stücklisten-CSV, die RuleDesigner erzeugt, beinhaltet weder Informationen zu den Spaltenüberschreibungen, noch Angaben über hinterlegte TOS-Regeln und/oder TOS-Bedingungen. Sie beinhaltet lediglich:
- Teilenummer der Baugruppe in der das Teil eingebaut ist (bzw. Teilenummer des bearbeiteten Halbzeuges)
- Positionsnummern
- Teilenummern von allen Stücklistenpositionen
- Mengen der Stücklistenpositionen (entspricht bei längenabhängigen Teilen der Spalte "Abweichende Menge" in SIVAS und bei nicht längenabhängigen Teilen der Spalte "Menge")
- Länge der Stücklistenpositionen (bei nicht-längenabhängigen Teilen = 0)
2. Bei nicht-längenabhängigen Teilen kann die Änderung der Positionsnummer allein anhand der Teilenummer identifiziert werden (da diese Teile immer genau 1x in der Stückliste auftreten (zwar evtl. mit unterschiedlicher Menge, aber immer nur mit einer Positionsnummer)). Bei längenabhängigen Teilen ist das allerdings nicht so einfach, weil eine längenabhängige Teilenummer mit mehreren Längen in derselben Stückliste vertreten sein kann (z.B. mit 1x mit Länge 100 und 1x mit Länge 200). Warum dies zu Problemen führt wird an folgendem Beispiel deutlich:
<table>
<tr><th>Alte Stückliste</th><th>Neue Stückliste</th></tr>
<tr><td>
| Pos. | Teilenr. |Abw. Menge | Menge | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-----------|-------|-------|-----------|-----------|
| 1 | 100000000 | 1 | 0,505 | 500 | 5 | 418 |
| 2 | 200000000 | - | 1 | - | - | - |
| 3 | 300000000 | - | 1 | - | - | - |
| 4 | 100000000 | 1 | 0,715 | 700 | 15 | 420 |
</td><td>
| Pos. | Teilenr. |Abw. Menge | Menge | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-----------|-------|-------|-----------|-----------|
| 1 | 100000000 | 2 | 0,610 | 300 | ? | ? |
| 2 | 200000000 | - | 1 | - | - | - |
| 3 | 100000000 | 2 | 0,830 | 400 | ? | ? |
| 4 | 400000000 | - | 1 | - | - | - |
</tr></table>
Hier stellt sich die z.B. die Frage, ob Pos. 1 aus der alten Stückliste zu Pos. 3 in der neuen Stückliste wurde, oder ob es sich immer noch um Pos. 1 handelt? Gleiches gilt für Pos. 3 in der alten Stückliste - hat sich dessen Länge von 700 auf 300 geändert, sodass es sich in der neuen Stückliste um Pos. 1 handelt, oder aber beträgt die Länge nun 400, sodass die neue Positionsnummer = 3 ist? Oder ist es ganz anders und Pos. 1 und 3 aus der alten Stückliste wurden beim konstruieren gelöscht und durch komplett neue Teile mit den Längen 300 und 400 ersetzt, die gar nichts mehr mit den beiden Teilen der alten Stückliste zu tun haben?
Es zeigt sich also, dass bei längenabhängigen Teilen oftmals eine eindeutige Zuordnung zwischen den Positionsnummern der alten und neuen Stückliste schwierig, bis unmöglich ist.
Die spezifizierte Logik siehe ([Logik](#verschiebelogik)) soll eine eindeutige Zuordnung in möglichst vielen Fällen herstellen. Nichtsdestotrotz verbleiben relativ viele Fällen, in denen dies nicht möglich ist. In solchen Fällen bleibt nichts anderes übrig, als die mehrdeutigen Positionen in der neuen Stüdckliste neu anzulegen, etwaige Änderungen der Spaltenwerte oder TOS-Regeln /-Bedingungen also zu entfernen. Damit diese Löschung jedoch nicht einfach so erfolgt, wird in diesen Fällen eine SIVAS-Workflowmeldung an die Verantwortlichen der Arbeitsvorbereitung, bzw. an die Verantwortlichen für das TOS geschickt, in der aufgelistet ist, welche Positionen in der alten Stückliste geänderte Spaltenwerte oder TOS-Regeln / -Bedingungen aufgewiesen haben. Folglich können sich die entsprechenden Personen dann nochmal manuell die Stammstückliste anschauen und evtl. gelöschte Bedingungen oder geänderte Spaltenwerte nachpflegen!
## Voraussetzungen
- Windows -> Korrekte Funktionsweise auf Linux/Mac voraussichtlich insbesondere wegen den Batch-Dateien im [bin-Verzeichnis](/bin) nicht gegeben!
- Es wird Python benötigt - Empfehlung: Version ≥ 3.12
## Installation
```bash
git clone http://gitea.schoenenberger.de/s.steuer/SchnittstellenanpassungRDSivas.git
cd SchnittstellenanpassungRDSivas\bin
install.bat
```
- install.bat erstellt die erforderlichen Verzeichnisse und generiert ein virtual environment.
- Es werden keine externen Packages installiert.
## Verwendung
### Batch-File
1. Ins [bin-Verzeichnis](./bin) wechseln
2. ```ProcessBOM.bat ``` mit einem der Dateinamen aus dem [data/in-Verzeichnis](/data/in) und dem Ausgabetyp "cmd" oder "csv" ausführen. Siehe hierzu [Verwendungsbeispiele](#verwendungsbeispiele)
### Verwendungsbeispiele
- Ausgabe der fertigen BOM von 000000369 in Konsole:
```cmd
ProcessBOM.bat -bom 000000369 cmd
```
- Ausgabe der fertigen BOM + AV-Workflowmeldung von 000000382 in Konsole:
```cmd
ProcessBOM.bat -bom -av 000000382 cmd
```
- Ausgabe der fertigen BOM + AV-Workflowmeldung + TOS-Workflowmeldung von 000000382 in Konsole:
```cmd
ProcessBOM.bat -bom -av -tos 000000382 cmd
```
- Ausgabe der fertigen BOM von 000000369 als CSV ins [data/out-Verzeichnis](/data/out) schreiben:
```cmd
ProcessBOM.bat -bom 000000369 csv
```
- Ausgabe der fertigen BOM + der Nummern, die in den AV- und TOS-Workflowmeldungen enthalten sind von 000000382 als separate CSV-Dateien ins [data/out-Verzeichnis](/data/out):
```cmd
ProcessBOM.bat -bom -av -tos 000000382 csv
```
### Command-Line
1. Virtual-environment aktivieren:
```bash
.venv\Scripts\activate
```
2. Umgebungsvariablen laden:
```bash
bin\setenv.bat
```
3. Ins [lib-Verzeichnis](./lib) wechseln:
```bash
cd lib
```
4. verschiebelogik.py ausführen (für alle verfügbaren Schalter siehe -h, bzw. siehe [Verwendungsbeispiele](#verwendungsbeispiele))
```bash
python process_bom.py [Dateiname aus data/in] {cmd|csv} ...
```
## Verschiebelogik
### Fall 1.1:
**Ziel:**
Längenabhängige Teilenummern identifizieren, deren Teilenr-Länge-Kombinationen identisch in der alten und neuen Stückliste vorliegen.
<ins>Zu erfüllende Kriterien, damit eine Teilenummer unter Fall 1.1 fällt:</ins>
- sie muss in beiden Stücklisten mit der gleichen Anzahl an Positionsnummern vertreten sein
- alle Kombinationen aus Teilenr. + Länge (bei längenabhängigen Teilenummern) müssen in beiden Stücklisten vorliegen
<br></br>
**Folge, für Positionsnummern, die Fall 1.1 zugeordnet werden können:**
Alle hinterlegten TOS-Regeln und -Bedingungen sowie alle manuell durchgeführten Spaltenanpassungen (z.B. Ergänzung eines Zuschnittswertes) werden an die Positionsnummer in der neuen Stückliste verschoben, an der dieselbe Teilenr-Länge-Kombination auftritt.
**Beispiele (siehe jeweils [Beispiele.pdf](doc/Beispiele.pdf))**
- Teilenummer 821106001 in Beispiel 1
- Teilenummer 822064013 in Beispiel 14
**Hinweise zu Fall 1.1:**
1. Die Verschiebung erfolgt auch dann, wenn die Teilenr-Länge-Kombination in der neuen Stückliste eine andere:
- Positionsnummer
- (abweichende) Menge
im Vergleich zur alten Stückliste
aufweist
<ins>Beispiel</ins>
<table>
<tr><th>Alte Stückliste</th><th>Neue Stückliste</th></tr>
<tr><td>
| Pos. | Teilenr. |Abw. Menge | Menge | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-----------|-------|-------|-----------|-----------|
| 1 | 100000000 | 1 | 0,505 | 500 | 5 | 418 |
| 2 | 200000000 | - | 1 | - | - | - |
| 3 | 300000000 | - | 1 | - | - | - |
| 4 | 100000000 | 1 | 0,715 | 700 | 15 | 420 |
</td><td>
| Pos. | Teilenr. |Abw. Menge | Menge | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-----------|-------|-------|-----------|-----------|
| 1 | 100000000 | 2 | 1,100 | 500 | 5 | 418 |
| 2 | 200000000 | - | 1 | - | - | - |
| 3 | 100000000 | 2 | 1,430 | 700 | 15 | 420 |
| 4 | 400000000 | - | 1 | - | - | - |
</tr></table>
<ins>Erklärung:</ins>
Fall 1.1 trifft hier auf die Teilenummer 100000000 zu, weil 100000000 in beiden Stücklisten:
- mit 2 Positionsnummern vertreten ist
- die Längen 500 & 700 aufweist (gleiche Teilenr-Länge-Kombinationen)
Folglich werden die manuellen Spaltenanpassungen (hier die Zuschnittswerte) sowie die TOS-Regeln und -Bedingungen (in diesem Fall sind nur Regeln definiert) an die neuen Positionsnummern in der neuen Stückliste verschoben.
Konkret wird bei folgenden Teilenr-Länge-Kombinationen:
- Teilenr: 100000000 + Länge: 500:
- der Zuschnittswert 5 und die TOS-Bedingung 418 von Pos. 1 in der alten zu Pos. 1 in der neuen Stückliste verschoben. Die Positionsnummern bleiben hier gleich. Beachte, dass sich die abweichende Menge der Position um 1 erhöht hat. Wie eingangs erwähnt spielt das aber keine Rolle, die Verschiebung erfolgt trotzdem!
- Teilenr: 100000000 + Länge: 700:
- der Zuschnittswert 15 und die TOS-Bedingung 420 von Pos. 4 in der alten zu Pos. 3 in der neuen Stückliste verschoben. Auch die abweichende Menge erhöht sich um 1. Für Fall 1.1 spielt jedoch weder die Positionsänderung, noch die Mengenanpassung eine Rolle. Ausschlaggebend ist allein, dass alle Teilenr-Längen-Kombinationen in beiden Stücklisten vorliegen! Dementsprechend erfolgt auch hier die Verschiebung des Zuschnittswertes und der TOS-Regel.
1. Fall 1.1 muss für jede längenabh. Teilenr. in der neuen Stückliste separat geprüft werden werden und falls zutreffend auf alle Positionsnummern dieser Teilenummer angewendet werden!
2. Implementierung: siehe [Programm](/lib/process_bom.py) -> Klasse *BOMProcessing* -> Methode *_get_case_11_positions()*
### Fall 1.2:
**Ziel:**
Längenabhängige Teilenummern identifizieren, die in beiden Stücklisten an denselben Positionen befinden.
<ins>Folgende Kriterien muss die längenabhängige Teilenummer erfüllen:</ins>
- sie muss in beiden Stücklisten gleich oft vorliegen (gleiche Anzahl an Positionsnummern)
- ihre Positionsnummern müssen in beiden Stücklisten identisch sein
**Folge, wenn Fall 1.2 zutrifft:**
Bei allen Positionsnummern der längenabh. Teilenr. werden TOS-Regeln und -Bedingungen, die hinterlegt wurden, bzw. alle
Spaltenanpassungen, die manuell in der SIVAS-Stammstückliste durchgeführt wurden (z.B. Ergänzung eines Zuschnittswertes)
an dieselbe Position wie in der alten Stückliste verschoben.
**Beispiele (siehe jeweils [Beispiele.pdf](doc/Beispiele.pdf))**
- Teilenummer 821106001 in Beispiel 2
- Teilenummer 821106001 in Beispiel 3
- Teilenummer 821096001 in Beispiel 12
- Teilenummern 822066026 + 822066089 in Beispiel 13
- Teilenummern 822066026 + 822066089 in Beispiel 14
**Hinweise zu Fall 1.2:**
1. Fall 1.2 muss für jede längenabh. Teilenr. in der neuen Stückliste separat geprüft werden werden!
<ins>Beispiel:</ins>
<table>
<tr><th>Alte Stückliste</th><th>Neue Stückliste</th></tr>
<tr><td>
| Pos. | Teilenr. | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-------|-----------|-----------|
| 1 | 100000000 | 500 | 5 | 418 |
| 2 | 400000000 | 600 | | |
| 3 | 100000000 | 700 | 15 | 420 |
| 4 | 300000000 | 800 | 20 | 421 |
| 5 | 500000000 | 900 | | |
</td><td>
| Pos. | Teilenr. | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-------|-----------|-----------|
| 1 | 100000000 | 100 | 5 | 418 |
| 2 | 200000000 | 200 | 10 | 419 |
| 3 | 100000000 | 300 | 15 | 420 |
| 4 | 300000000 | 400 | 20 | 421 |
| 5 | 400000000 | 500 | 25 | 422 |
</tr></table>
<ins>Die Prüfung von Fall 1.2 erfolgt für folgende Teilenummern:</ins>
- 100000000
- 400000000
- 300000000
- 500000000
<ins>Die Prüfung von Fall 1.2 erfolgt **NICHT** für folgende Teilenummern:</ins>
- 200000000 (da sie nicht in der neuen Stückliste vorliegt)
<ins>Fall 1.2 trifft auf folgenden Teilenummern zu:</ins>
- 100000000, weil diese Teilenr. in beiden Stücklisten 2x vorkommt und in beiden Stücklisten dieselben Positionsnummern aufweist (1 & 3)
Die Zuschnittswerte 5 und 15 sowie die TOS-Regeln 418 und 420 werden daher an dieselben Positionen wie in der alten Stücklisten übernommen. Konkret also von Pos. 1 zu Pos. 1 und von Pos. 3 zu Pos. 3.
- 300000000, weil diese Teilenr. in beiden Stücklisten 1x vorkommt und in beiden Stücklisten an Pos. 4 liegt
Der Zuschnittswert 5 und 15 sowie die TOS-Regeln 418 und 420 wird daher an dieselbe Position wie in der alten Stückliste, d.h. von Pos. 4 zu Pos. 4, verschoben
<ins>Fall 1.2 trifft NICHT auf folgenden Teilenummern zu:</ins>
- 400000000, da sie zwar in beiden Stücklisten genau 1x vorkommt, aber nicht an derselben Positionsnummer
Es erfolgt daher keine Verschiebung des Zuschnittes 25 und der TOS-Bedingung 422 von Pos. 5 zu Pos. 2
- 500000000, da sie neu in der neuen Stückliste ist
Neue Positionen werden immer ohne Spaltenänderungen oder TOS-Regeln /-Bedingungen angelegt!
2. Die Längen an denselben Positionsnummern dürfen unterschiedlich sein!
<ins>Beispiel:</ins>
<table>
<tr><th>Alte Stückliste</th><th>Neue Stückliste</th></tr>
<tr><td>
| Pos. | Teilenr. | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-------|-----------|-----------|
| 1 | 821106001 | 100 | 5 | 418 |
| 2 | 821106001 | 200 | 10 | 419 |
</td><td>
| Pos. | Teilenr. | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-------|-----------|-----------|
| 1 | 821106001 | 400 | 5 | 418 |
| 2 | 821106001 | 500 | 10 | 419 |
</tr></table>
<ins>Erklärung:</ins>
Die Zuschnittswerte 5 & 10 sowie die TOS-Regeln 418 & 419 werden an dieselbe Position wie in der alten Stückliste verschoben, obwohl sich die Längen zwischen der alten und neuen Stückliste komplett unterscheiden. Denn wie im Ziel beschrieben sind allein folgende Punkte ausschlaggebend, dass die Positionsnummern der Teilenummern unter Fall 1.2 fallen:
- dass die Anzahl an Positionsnummern einer Teilenummer in beiden Stücklisten gleich ist. Das ist hier gegeben, weil 821106001 in beiden Stücklisten mit 2 Positionsnummern vertreten ist
- dass ihre Positionsnummern in beiden Stücklisten identisch sind. Auch das ist der Fall, denn sie besitzt in beiden Stücklisten die Positionsnummern 1 und 2.
3. Wenn Fall 1.1 und Fall 1.2 gleichzeitig auf eine längenabh. Teilenr. zutreffen gilt Fall 1.1.
<ins>Beispiel:</ins>
<table>
<tr><th>Alte Stückliste</th><th>Neue Stückliste</th></tr>
<tr><td>
| Pos. | Teilenr. | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-------|-----------|-----------|
| 1 | 821106001 | 100 | 5 | 418 |
| 2 | 821106001 | 200 | 10 | 419 |
</td><td>
| Pos. | Teilenr. | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-------|-----------|-----------|
| 1 | 821106001 | 200 | ? | ? |
| 2 | 821106001 | 100 | ? | ? |
</tr></table>
<ins>Erklärung:</ins>
Hier trifft sowohl Fall 1.1, als auch Fall 1.2 zu:
- Fall 1.1, weil in beiden Stücklisten:
- Kombination aus Teilenr. 821106001 + Länge 100 vorliegt
- Kombination aus Teilenr. 821106001 + Länge 200 vorliegt
- Fall 1.2, weil:
- 821106001 in beiden Stücklisten 2x vorliegt
- 821106001 in beiden Stücklisten an derselben Position vorliegt (Pos. 1 & 2)
Die Frage ist nun, an welche Positionsnummern sollen die Zuschnittswerte 5 & 10 sowie die TOS-Regeln 418 & 419 verschoben werden:
(siehe Fragezeichen in neuen Stückliste)
Die eingangs beschriebene Regel liegt nun fest, dass in diesem Fall immer Fall 1.1 gilt, weil Fall 1.1 eine höhere Priorität als Fall 1.2 besitzt. Daraus folgt, dass die neue Stückliste wie folgt ausschaut (Verschiebung des Zuschnittswertes / der TOS-Regel von Pos. 1 in der alten Stückliste zu Pos. 2 in der Neuen, bzw. von Pos. 2 in der alten zu Pos. 1 in der neuen Stückliste):
| Pos. | Teilenr. | Länge | Zuschnitt | TOS-Regel |
|------|-----------|-------|-----------|-----------|
| 1 | 821106001 | 200 | 10 | 419 |
| 2 | 821106001 | 100 | 5 | 418 |
4. Implementierung: siehe [Programm](/lib/process_bom.py) -> Klasse *BOMProcessing* -> Methode *_get_case_12_positions()*
### Fall 1.3:
**Ziel:**
Positionsnummern identifizieren, die entweder:
- als neue Stücklistenposition eingefügt werden müssen (nachfolgend als Fall 1.31 bezeichnet)
- in die neue Stückliste verschoben werden sollen, aber nicht unter [Fall 1.1](#fall-11) oder [Fall 1.2](#fall-12) fallen (nachfolgend als Fall 1.32 bezeichnet)
<ins>Folgende Kriterien muss die Positionsnummer erfüllen, damit sie unter Fall 1.31 fällt:</ins>
- sie darf nur in der neuen Stückliste vorliegen
<ins>Folgende Kriterien muss die längenabhängige Teilenummer erfüllen, damit sie unter Fall 1.32 fällt:</ins>
- sie muss in beiden Stücklisten dieselbe Teilenr-Längen-Kombination aufweisen
**Folge, wenn Fall 1.31 zutrifft:**
- Die Position wird in der neuen Stückliste mit den Werten aus der RuleDesigner-Stücklisten-CSV neu angelegt (es erfolgt keine Verschiebung von Stücklisteninformationen aus der alten Stückliste). Die Spaltenwerte, die nicht in genannter Stücklisten-CSV enthalten sind, werden mit den Defaultwerten befüllt (so wie wenn die Position manuell in der SIVAS-Stückliste eingefügt wird). Defaultwerte sind abhängig von der Spalte entweder entweder *leer* sind, oder verfügen über die Werte der Teilenummer im Teilestamm (Tabelle PARTS)
- Die Position wird ohne TOS-Regeln oder -Bedingungen angelegt. Sie müssen wenn dann händisch von einem SIVAS-User in der Stammstückliste ergänzt werden. Im Zuge einer nachfolgenden Stücklistenanpassung erfolgt dann evtl. eine Verschiebung der Regeln und Bedingungen, sofern die zu diesem Zeitpunkt Fall 1.1, 1.2 oder 1.32 auf diese Positionsnummer zutrifft.
**Beispiele zu Fall 1.31 (siehe jeweils [Beispiele.pdf](doc/Beispiele.pdf))**
- Positionsnummer 4 der neuen Stückliste in Beispiel 1
- Positionsnummer 2 & 3 der neuen Stückliste in Beispiel 4
- Positionsnummer 4 der neuen Stückliste in Beispiel 5
- Positionsnummer 3, 4, 5 der neuen Stückliste in Beispiel 6
- Positionsnummer 3, 4 der neuen Stückliste in Beispiel 7
- Positionsnummer 3 der neuen Stückliste in Beispiel 9
- Positionsnummer 1 der neuen Stückliste in Beispiel 10
- Positionsnummer 4 der neuen Stückliste in Beispiel 11
- Positionsnummer 3, 5 der neuen Stückliste in Beispiel 12
- Positionsnummer 2, 6, 7 der neuen Stückliste in Beispiel 13
- Positionsnummer 4, 7, 9, 10, 12, 16, 17 der neuen Stückliste in Beispiel 14
**Hinweise zu Fall 1.31:**
1. Implementierung: siehe [Programm](/lib/process_bom.py) -> Klasse *BOMProcessing* -> Methode *_get_case_131_positions()*
**Folge, wenn Fall 1.32 zutrifft:**
Alle hinterlegten TOS-Regeln und -Bedingungen sowie alle manuell durchgeführten Spaltenanpassungen (z.B. Ergänzung eines Zuschnittswertes) werden an die Positionsnummer in der neuen Stückliste verschoben, an der dieselbe Teilenr-Länge-Kombination auftritt.
**Beispiele zu Fall 1.32 (siehe jeweils [Beispiele.pdf](doc/Beispiele.pdf))**
- Positionsnummer 2 der neuen Stückliste in Beispiel 1
- Positionsnummer 2 der neuen Stückliste in Beispiel 3
- Positionsnummer 1, 4 der neuen Stückliste in Beispiel 4
- Positionsnummer 1, 2, 3 der neuen Stückliste in Beispiel 5
- Positionsnummer 1, 2 der neuen Stückliste in Beispiel 6
- Positionsnummer 1, 2 der neuen Stückliste in Beispiel 7
- Positionsnummer 1, 2 der neuen Stückliste in Beispiel 8
- Positionsnummer 1, 2 der neuen Stückliste in Beispiel 9
- Positionsnummer 2 der neuen Stückliste in Beispiel 12
- Positionsnummer 1, 4 der neuen Stückliste in Beispiel 13
- Positionsnummer 1, 6, 8, 13, 15 der neuen Stückliste in Beispiel 14
**Hinweise zu Fall 1.32:**
1. Positionsnummern bei denen bereits Fall 1.1 greift, oder die unter Fall 1.2 fallen und deren Länge dabei in beiden Stücklisten gleich bleibt würden grundlegend ebenfalls unter Fall 1.32 fallen und somit identifiziert werden. Es gilt jedoch das Vorrangsprinzip, d.h. die Zuordnung zu Fall 1.1 oder Fall 1.2 erfolgt (sofern möglich) immer vor der Zuordnung zu Fall 1.32!
2. Die Verschiebung erfolgt auch dann, wenn die Teilenr-Länge-Kombination in der neuen Stückliste eine andere:
- Positionsnummer
- (abweichende) Menge
im Vergleich zur alten Stückliste aufweist.
3. Implementierung: siehe [Programm](/lib/process_bom.py) -> Klasse *BOMProcessing* -> Methode *_get_case_132_positions()*
+107 -127
View File
@@ -1,14 +1,7 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "Beispiele" (
"ID" INTEGER NOT NULL,
"Parent_ID" TEXT NOT NULL,
"Beispielnr" INTEGER,
PRIMARY KEY("ID" AUTOINCREMENT),
CONSTRAINT "FK_Parent_ID" FOREIGN KEY("Parent_ID") REFERENCES "COMP"("PARENT")
);
CREATE TABLE IF NOT EXISTS "COMP" (
"ID" INTEGER NOT NULL,
"PARENT" TEXT NOT NULL,
"ROWID" INTEGER NOT NULL,
"UEBTEILENR" TEXT NOT NULL,
"POSNR" INTEGER NOT NULL,
"KOMPTEILENR" TEXT NOT NULL,
"DISPO_AKTIV_JN" TEXT,
@@ -17,7 +10,7 @@ CREATE TABLE IF NOT EXISTS "COMP" (
"LAENGE" REAL,
"ZUSCHNITT" TEXT,
"RUESTCODE" TEXT,
PRIMARY KEY("ID" AUTOINCREMENT)
PRIMARY KEY("ROWID" AUTOINCREMENT)
);
CREATE TABLE IF NOT EXISTS "PARTS" (
"TEILENR" INTEGER,
@@ -47,96 +40,82 @@ CREATE TABLE IF NOT EXISTS "PARTS" (
"STATUS" TEXT
);
CREATE TABLE IF NOT EXISTS "STKL_REGEL" (
"ID" INTEGER NOT NULL,
"COMP_ID" INTEGER,
"REGEL" INTEGER NOT NULL,
PRIMARY KEY("ID" AUTOINCREMENT),
CONSTRAINT "FK_COMP_ID" FOREIGN KEY("COMP_ID") REFERENCES "COMP"("ID")
"ROWID" INTEGER NOT NULL,
"UEBTEILENR" TEXT,
"POSNR" INTEGER,
"LFD_NR" INTEGER NOT NULL,
PRIMARY KEY("ROWID" AUTOINCREMENT)
);
CREATE TABLE IF NOT EXISTS "VAR_TEIL_BEDINGUNG_2" (
"ID" INTEGER NOT NULL,
"COMP_ID" INTEGER NOT NULL,
"BEDINGUNG" INTEGER NOT NULL,
PRIMARY KEY("ID" AUTOINCREMENT),
CONSTRAINT "FK_COMP_ID" FOREIGN KEY("COMP_ID") REFERENCES "COMP"("ID")
"ROWID" INTEGER NOT NULL,
"UEBTEILENR" TEXT NOT NULL,
"POSNR" INTEGER NOT NULL,
"UEB_LEISTEN_NR" INTEGER NOT NULL,
PRIMARY KEY("ROWID" AUTOINCREMENT)
);
INSERT INTO "Beispiele" ("ID","Parent_ID","Beispielnr") VALUES (1,'000000369',1),
(2,'000000370',2),
(3,'000000371',3),
(4,'000000372',4),
(5,'000000373',5),
(6,'000000374',6),
(7,'000000375',7),
(8,'000000376',8),
(9,'000000377',9),
(10,'000000378',10),
(11,'000000379',11),
(12,'000000380',12),
(13,'000000381',13),
(14,'000000382',14);
INSERT INTO "COMP" ("ID","PARENT","POSNR","KOMPTEILENR","DISPO_AKTIV_JN","MENGE","ABW_ANZAHL","LAENGE","ZUSCHNITT","RUESTCODE") VALUES (1,'000000380',10,'821096001','J',0.22,1,200.0,'20','HZS'),
INSERT INTO "COMP" ("ROWID","UEBTEILENR","POSNR","KOMPTEILENR","DISPO_AKTIV_JN","MENGE","ABW_ANZAHL","LAENGE","ZUSCHNITT","RUESTCODE") VALUES (1,'000000380',10,'821096001','J',0.22,1,200.0,'20','HZS'),
(2,'000000380',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(3,'000000380',30,'200000168','J',1.0,NULL,NULL,NULL,'SRC'),
(4,'000000380',40,'821096001','J',0.11,1,100.0,'10','HZS'),
(5,'000000380',50,'821106001','J',0.23,1,200.0,'30','HZS'),
(6,'000000369',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(6,'000000369',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(7,'000000369',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(8,'000000369',30,'821106001','J',0.21,1,200.0,'10','SRC'),
(8,'000000369',30,'821106001','J',0.21,1,200.0,'10','HZS'),
(9,'000000369',40,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(10,'000000371',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(10,'000000371',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(11,'000000371',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(12,'000000371',30,'821106001','J',0.21,1,200.0,'10','SRC'),
(12,'000000371',30,'821106001','J',0.21,1,200.0,'10','HZS'),
(13,'000000371',40,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(14,'000000372',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(15,'000000372',20,'821106001','J',0.21,1,200.0,'10','SRC'),
(16,'000000372',30,'821106001','J',0.315,1,300.0,'15','SRC'),
(14,'000000372',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(15,'000000372',20,'821106001','J',0.21,1,200.0,'10','HZS'),
(16,'000000372',30,'821106001','J',0.315,1,300.0,'15','HZS'),
(17,'000000372',40,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(18,'000000373',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(19,'000000373',20,'821106001','J',0.21,1,200.0,'10','SRC'),
(18,'000000373',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(19,'000000373',20,'821106001','J',0.21,1,200.0,'10','HZS'),
(20,'000000373',30,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(21,'000000374',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(22,'000000374',20,'821106001','J',0.21,1,200.0,'10','SRC'),
(21,'000000374',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(22,'000000374',20,'821106001','J',0.21,1,200.0,'10','HZS'),
(23,'000000374',30,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(24,'000000374',40,'821106001','J',0.315,1,300.0,'15','SRC'),
(25,'000000375',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(26,'000000375',20,'821106001','J',0.21,1,200.0,'10','SRC'),
(24,'000000374',40,'821106001','J',0.315,1,300.0,'15','HZS'),
(25,'000000375',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(26,'000000375',20,'821106001','J',0.21,1,200.0,'10','HZS'),
(27,'000000375',30,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(28,'000000375',40,'821106001','J',0.315,1,300.0,'15','SRC'),
(29,'000000376',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(28,'000000375',40,'821106001','J',0.315,1,300.0,'15','HZS'),
(29,'000000376',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(30,'000000376',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(31,'000000376',30,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(32,'000000377',10,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(33,'000000377',20,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(34,'000000378',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(35,'000000370',10,'821106001','J',0.105,1,100.0,'5','SRC'),
(36,'000000379',10,'821106001','J',0.1,1,100.0,NULL,'SRC'),
(34,'000000378',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(35,'000000370',10,'821106001','J',0.105,1,100.0,'5','HZS'),
(36,'000000379',10,'821106001','J',0.1,1,100.0,NULL,'HZS'),
(37,'000000379',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(38,'000000379',30,'821106001','J',0.2,1,200.0,NULL,'SRC'),
(38,'000000379',30,'821106001','J',0.2,1,200.0,NULL,'HZS'),
(39,'000000379',40,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(40,'000000381',10,'821106001','J',0.107,1,100.0,'7','SRC'),
(41,'000000381',20,'829516004','J',0.207,1,200.0,'7','SRC'),
(42,'000000381',30,'822066026','J',0.302,1,300.0,'2','SRC'),
(43,'000000381',40,'829516004','J',0.307,1,300.0,'7','SRC'),
(44,'000000381',50,'822066089','J',0.153,1,150.0,'3','SRC'),
(40,'000000381',10,'829516004','J',0.107,1,100.0,'7','HZS'),
(41,'000000381',20,'829516004','J',0.207,1,200.0,'7','HZS'),
(42,'000000381',30,'822066026','J',0.302,1,300.0,'2','HZS'),
(43,'000000381',40,'829516004','J',0.307,1,300.0,'7','HZS'),
(44,'000000381',50,'822066089','J',0.153,1,150.0,'3','HZS'),
(45,'000000381',60,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(46,'000000381',70,'822066207','J',1.0,NULL,NULL,NULL,'SRC'),
(47,'000000381',80,'822066089','J',0.253,1,250.0,'3','SRC'),
(48,'000000382',10,'822064013','J',0.101,1,100.0,'1','SRC'),
(49,'000000382',20,'829416031','J',0.205,1,200.0,'5','SRC'),
(50,'000000382',30,'822066026','J',0.302,1,300.0,'2','SRC'),
(51,'000000382',40,'829516001','J',0.106,1,100.0,'6','SRC'),
(52,'000000382',50,'822066089','J',0.153,1,150.0,'3','SRC'),
(53,'000000382',60,'827076001','J',0.104,1,100.0,'4','SRC'),
(54,'000000382',70,'827076001','J',0.204,1,200.0,'4','SRC'),
(55,'000000382',80,'827076001','J',0.304,1,300.0,'4','SRC'),
(56,'000000382',90,'829516001','J',0.206,1,200.0,'6','SRC'),
(57,'000000382',100,'822064013','J',0.201,1,200.0,'1','SRC'),
(58,'000000382',110,'822066089','J',0.253,1,250.0,'3','SRC'),
(59,'000000382',120,'829516004','J',0.107,1,100.0,'7','SRC'),
(60,'000000382',130,'829516004','J',0.207,1,200.0,'7','SRC'),
(61,'000000382',140,'829516051','J',0.208,1,200.0,'8','SRC'),
(62,'000000382',150,'829516004','J',0.307,1,300.0,'7','SRC'),
(63,'000000382',160,'821106001','J',0.36,1,350.0,'10','SRC'),
(47,'000000381',80,'822066089','J',0.253,1,250.0,'3','HZS'),
(48,'000000382',10,'822064013','J',0.101,1,100.0,'1','HZS'),
(49,'000000382',20,'829416031','J',0.205,1,200.0,'5','HZS'),
(50,'000000382',30,'822066026','J',0.302,1,300.0,'2','HZS'),
(51,'000000382',40,'829516001','J',0.106,1,100.0,'6','HZS'),
(52,'000000382',50,'822066089','J',0.153,1,150.0,'3','HZS'),
(53,'000000382',60,'827076001','J',0.104,1,100.0,'4','HZS'),
(54,'000000382',70,'827076001','J',0.204,1,200.0,'4','HZS'),
(55,'000000382',80,'827076001','J',0.304,1,300.0,'4','HZS'),
(56,'000000382',90,'829516001','J',0.206,1,200.0,'6','HZS'),
(57,'000000382',100,'822064013','J',0.201,1,200.0,'1','HZS'),
(58,'000000382',110,'822066089','J',0.253,1,250.0,'3','HZS'),
(59,'000000382',120,'829516004','J',0.107,1,100.0,'7','HZS'),
(60,'000000382',130,'829516004','J',0.207,1,200.0,'7','HZS'),
(61,'000000382',140,'829516051','J',0.208,1,200.0,'8','HZS'),
(62,'000000382',150,'829516004','J',0.307,1,300.0,'7','HZS'),
(63,'000000382',160,'821106001','J',0.36,1,350.0,'10','HZS'),
(64,'000000382',170,'829416035','J',1.0,NULL,NULL,NULL,'SRC'),
(65,'000000382',180,'822066207','J',1.0,NULL,NULL,NULL,'SRC');
INSERT INTO "PARTS" ("TEILENR","BEZ","BEZ2","KZKAUF","MENGENC","ABW_ANZAHL_JN","DISPO_AKTIV_JN","GRNR","DISPO_GRUPPE","DISPOLAUF_GRUPPE","ÄnderungsIndex","HERSTELLERNAME","TEILENR_HERSTELLER","INFO2","INFO16","WERKSTOFF","OBERFLAECHE","TRUEMMER","INFO2_JN","RUESTCODE","FERTIGUNGSART","FOSGRUPPE","INFO14","INDEXERFASSER","STATUS") VALUES (200000168,'Außenspur SP-Bogen 30°',NULL,'K','Stck','N','J','6','10','20','C',NULL,NULL,NULL,NULL,'PA 6','Masterbatch (Grundfarbe)','FALSCH','N','SRC','SP','0','Eigenbau','B.MÜLLER','5'),
@@ -154,54 +133,55 @@ INSERT INTO "PARTS" ("TEILENR","BEZ","BEZ2","KZKAUF","MENGENC","ABW_ANZAHL_JN","
(829516051,'GLEITPROFIL - STAHLBANDFÜHRUNG FÜR ILS 2100',NULL,'K','M','J','J','3003','10','20','A',NULL,NULL,'110309',NULL,'Murtfeldt "S" 1000','Masterbatch (Grundfarbe)','FALSCH','J','HZS','EX','0','Eigenbau','B.MÜLLER','5'),
(829526000,'STRUKTUR-PROFIL',NULL,'K','M','J','J','6','10','20','A',NULL,NULL,'120214',NULL,'EN AW-6063-T66','eloxieren','FALSCH','N','HZS','SN','0','Eigenbau','S.STEUER','5'),
(829526517,'WEICHENPLATTE R-G - ILS II',NULL,'K','Stck','N','J','6','10','20','B',NULL,NULL,NULL,NULL,'1.0038 (S235JR)','verzinken','FALSCH','N','SRC','LW','0','Eigenbau','B.MÜLLER','5');
INSERT INTO "VAR_TEIL_BEDINGUNG_2" ("ID","COMP_ID","BEDINGUNG") VALUES (1,6,418),
(2,8,420),
(3,35,419),
(4,10,419),
(5,12,420),
(6,14,418),
(7,15,419),
(8,16,420),
(9,18,417),
(10,19,418),
(11,21,417),
(12,22,418),
(13,24,419),
(14,25,417),
(15,26,418),
(16,28,419),
(17,29,417),
(18,34,419),
(19,37,418),
(20,39,419),
(21,1,417),
(22,2,418),
(23,4,419),
(24,5,420),
(25,40,7),
(26,41,7),
(27,42,2),
(28,43,7),
(29,44,3),
(30,45,11),
(31,46,11),
(32,47,3),
(33,48,1),
(34,49,5),
(35,50,2),
(36,51,6),
(37,52,3),
(38,53,4),
(39,54,4),
(40,55,4),
(41,56,6),
(42,57,1),
(43,58,3),
(44,59,7),
(45,60,7),
(46,61,8),
(47,62,7),
(48,63,10),
(49,64,11),
(50,65,11);
INSERT INTO "STKL_REGEL" ("ROWID","UEBTEILENR","POSNR","LFD_NR") VALUES (1,'000000369',20,1);
INSERT INTO "VAR_TEIL_BEDINGUNG_2" ("ROWID","UEBTEILENR","POSNR","UEB_LEISTEN_NR") VALUES (1,'000000369',10,418),
(2,'000000369',30,420),
(3,'000000370',10,419),
(4,'000000371',10,419),
(5,'000000371',30,420),
(6,'000000372',10,418),
(7,'000000372',20,419),
(8,'000000372',30,420),
(9,'000000373',10,417),
(10,'000000373',20,418),
(11,'000000374',10,417),
(12,'000000374',20,418),
(13,'000000374',40,419),
(14,'000000375',10,417),
(15,'000000375',20,418),
(16,'000000375',40,419),
(17,'000000376',10,417),
(18,'000000378',10,419),
(19,'000000379',20,418),
(20,'000000379',40,419),
(21,'000000380',10,417),
(22,'000000380',20,418),
(23,'000000380',40,419),
(24,'000000380',50,420),
(25,'000000381',10,7),
(26,'000000381',20,7),
(27,'000000381',30,2),
(28,'000000381',40,7),
(29,'000000381',50,3),
(30,'000000381',60,11),
(31,'000000381',70,11),
(32,'000000381',80,3),
(33,'000000382',10,1),
(34,'000000382',20,5),
(35,'000000382',30,2),
(36,'000000382',40,6),
(37,'000000382',50,3),
(38,'000000382',60,4),
(39,'000000382',70,4),
(40,'000000382',80,4),
(41,'000000382',90,6),
(42,'000000382',100,1),
(43,'000000382',110,3),
(44,'000000382',120,7),
(45,'000000382',130,7),
(46,'000000382',140,8),
(47,'000000382',150,7),
(48,'000000382',160,10),
(49,'000000382',170,11),
(50,'000000382',180,11);
COMMIT;
File diff suppressed because it is too large Load Diff
+452 -212
View File
@@ -2,7 +2,6 @@ import argparse
import csv
import os
import sqlite3
import subprocess
def main():
parser = argparse.ArgumentParser(description='Programm erstellt finale SIVAS-Stückliste aus der RuleDesigner-Stücklisten-CSV unter Berücksichtigung der spezifizierten Verschiebelogik!',
@@ -12,17 +11,19 @@ def main():
parser.register("action", "store_after_check", ArgparseDirectoryCheck)
parser.register("action", "store_after_code_check", ArgparseParentcodeCheck)
parser.add_argument("parent_code", action="store_after_code_check", type=str, help="Teilenummer der Baugruppe")
parser.add_argument("--csv_dir", action="store_after_check", default=os.environ.get("PROJECT_DATA_IN"), metavar="dir", help="Verzeichnis, aus dem die Stücklisten-CSV aus RuleDesigner eingelesen werden soll. Default: /data")
parser.add_argument("-csv_dir", action="store_after_check", default=os.environ.get("PROJECT_DATA_IN"), metavar="dir", help="Verzeichnis, aus dem die Stücklisten-CSV aus RuleDesigner eingelesen werden soll. Default: /data")
parser.add_argument("-bom", action="store_true", help="Ausgabe der neuen, aufbereiteten Stückliste")
parser.add_argument("-av", action="store_true", help="Ausgabe der AV-Workflowmeldungen (falls vorhanden)")
parser.add_argument("-tos", action="store_true", help="Ausgabe der TOS-Workflowmeldungen (falls vorhanden)")
subparsers = parser.add_subparsers(title="output type", required=True, help="Legt fest, in welcher Form die Stückliste ausgegeben werden soll. cmd = In Konsole, csv = als csv-Datei", dest="output_type")
sp_cmd = subparsers.add_parser("cmd", help="Ausgabe der fertig bearbeiteten Stückliste in der Konsole")
sp_csv = subparsers.add_parser("csv", help="Ausgabe der fertig bearbeiteten Stückliste als CSV-Datei")
sp_cmd = subparsers.add_parser("cmd", help="Ausgabe der über die Flags -bom / -av / -tos ausgewählten Elemente in der Konsole")
sp_csv = subparsers.add_parser("csv", help="Ausgabe der über die Flags -bom / -av / -tos ausgewählten Elemente als CSV-Datei(en)")
sp_csv.register("action", "store_after_check", ArgparseDirectoryCheck)
sp_csv.add_argument("-v", "--verbose", action="store_true", help="Alle Spalten ausgeben")
sp_cmd.add_argument("-v", "--verbose", action="store_true", help="Alle Spalten ausgeben")
sp_csv.add_argument("-od", "--out_dir", action="store_after_check", default=os.environ.get("PROJECT_DATA_OUT"), metavar="dir", help="Ausgabeverzeichnis fertigen Stücklisten-CSV")
sp_csv.add_argument("-o", "--open_csv", action="store_true", help="CSV-Datei nach dem schreiben öffnen")
args: dict = vars(parser.parse_args())
if os.environ.get("DB_PATH") is not None:
@@ -37,6 +38,8 @@ def main():
csv_handler = CSVHandler()
sql_handler = SQLFileHandler()
bom_processing = BOMProcessing(db, args["parent_code"])
av_workflow_msg = AVWorkflowMessage(db, args["parent_code"])
tos_workflow_msg = TOSWorkflowMessage(db, args["parent_code"])
# SIVAS "nachbauen"
sql_source_filename = "sivas_replika.sql"
@@ -48,29 +51,59 @@ def main():
new_bom.create_tmp_table()
new_bom.insert_csv_data(csv_data)
# Positionen für AV- bzw. TOS-Workflowmeldungen generieren (muss vor dem Schreiben der neuen Stücklist erfolgen!)
av_workflow_msg.get_av_workflow_posnrs_and_codes()
tos_workflow_msg.get_tos_workflow_posnrs_and_codes()
# Neue Stückliste erstellen / schreiben
bom_processing.write_new_bom()
# Ausgabe in Konsole
if args["output_type"] == "cmd":
if args["verbose"]:
output = repr(new_bom)
print(output)
else:
print(new_bom)
if args["bom"]:
if args["verbose"]:
print(repr(new_bom))
else:
print(new_bom)
if args["av"]:
print(av_workflow_msg)
if args["tos"]:
print(tos_workflow_msg)
# Ausgabe als CSV-Datei(en)
elif args["output_type"] == "csv":
if args["verbose"]:
bom_data = new_bom.get_verbose_data()
else:
bom_data = new_bom.get_compact_data()
filedir = args["out_dir"]
filename = f"BOM_{args["parent_code"]}_final.csv"
filepath = os.path.join(filedir, filename)
csv_handler.write_from_dict(data=bom_data, filedir=filedir, filename=filename)
filenames = []
file_data = []
if args["open_csv"]:
subprocess.run(['cmd', '/c', 'start', '', filepath], shell=True)
# CSV-Ausgabe der aufbereiteten Stückliste
if args["bom"]:
if args["verbose"]:
bom_data = new_bom.get_verbose_data()
else:
bom_data = new_bom.get_compact_data()
filenames.append(f"BOM_{args["parent_code"]}_new.csv")
file_data.append(bom_data)
# CSV-Ausgabe der Positionsnummern aus der alten Stückliste, die Bestandteil der AV-Workflowmeldung sind
if args["av"]:
msg_data = av_workflow_msg.get_data()
filenames.append(f"AV_{args["parent_code"]}.csv")
file_data.append(msg_data)
# CSV-Ausgabe der Positionsnummern aus der alten Stückliste, die Bestandteil der TOS-Workflowmeldung sind
if args["tos"]:
msg_data = tos_workflow_msg.get_data()
filenames.append(f"TOS_{args["parent_code"]}.csv")
file_data.append(msg_data)
for name, data in zip(filenames, file_data):
csv_handler.write_from_dict(data=data, filedir=filedir, filename=name)
else:
NotImplementedError
@@ -232,32 +265,36 @@ class RD_COMP:
def get_verbose_data(self) -> list[dict[str]]:
res = self.db.con.execute("""
SELECT C.*, R.REGEL, B.BEDINGUNG
SELECT C.*, R.LFD_NR, B.UEB_LEISTEN_NR
FROM COMP C
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON C.ID = B.COMP_ID
ON C.UEBTEILENR = B.UEBTEILENR
AND C.POSNR = B.POSNR
LEFT JOIN STKL_REGEL R
ON C.ID = R.COMP_ID
ON C.UEBTEILENR = R.UEBTEILENR
AND C.POSNR = R.POSNR
WHERE C.PARENT = :parent_code
WHERE C.UEBTEILENR = :parent_code
ORDER BY C.POSNR
""", {"parent_code": self.parent_code}).fetchall()
return res
def get_compact_data(self) -> list[dict[str]]:
res = self.db.con.execute("""
SELECT C.POSNR, C.KOMPTEILENR, C.ABW_ANZAHL, C.MENGE, C.LAENGE, C.ZUSCHNITT, R.REGEL, B.Bedingung
SELECT C.POSNR, C.KOMPTEILENR, C.ABW_ANZAHL, C.MENGE, C.LAENGE, C.ZUSCHNITT, R.LFD_NR, B.UEB_LEISTEN_NR
FROM COMP C
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON C.ID = B.COMP_ID
ON C.UEBTEILENR = B.UEBTEILENR
AND C.POSNR = B.POSNR
LEFT JOIN STKL_REGEL R
ON C.ID = R.COMP_ID
ON C.UEBTEILENR = R.UEBTEILENR
AND C.POSNR = R.POSNR
WHERE C.PARENT = :parent_code
WHERE C.UEBTEILENR = :parent_code
ORDER BY C.POSNR
""", {"parent_code": self.parent_code}).fetchall()
return res
@@ -290,26 +327,26 @@ class BOMProcessing:
def _get_codes_case_12_or_13(self) -> None:
"""
Ermittelt alle, die unter Fall 1.2 oder Fall 1.3 fallen.
Ermittelt alle Teilenummern, die unter Fall 1.2 oder Fall 1.3 fallen.
Es wird dafür geprüft, welche Teilenummern NICHT unter Fall 1.1 fallen.
Wieso wird nicht geprüft, welche Teilenummern in Fall 1.1 liegen?
- Weil die SQL-Abfrage dann deutlich komplexer wird.
- Weil die SQL-Abfrage dann komplexer wird.
Die Prüfung läuft so ab:
1. Teilenr-Längen-Kombinationen aus der alten und neuen Stückliste mit UNION ALL zusammenfügen
2. Mit COUNT ermitteln, wie oft jede Teilenummer-Längen-Kombination in der zusammengefügten Stückliste auftritt
3. Die Teilenummern in die temporäre Tabelle TMP_CODES_CASE_12_OR_13 schreiben, deren Teilenr-Längen-Kombination 1x vorkommen
=> Diese Teilenummern unterliegen entweder Fall 1.2 oder Fall 1.3, aber definitiv nicht Fall 1.1, weil eine Zuordnung zu
Fall 1.1 nur dann möglich ist, wenn alle Teilenr-Längen-Kombinationen exakt 2x auftreten!
1. Teilenr-Längen-Kombinationen aus der alten und neuen Stückliste mit UNION ALL verschmelzen
2. Mit COUNT ermitteln, wie oft jede Teilenummer-Längen-Kombination in der verschmolzenen Stückliste auftritt
3. Die Teilenummern in die temporäre Tabelle TMP_CODES_CASE_12_OR_13 schreiben, sobald 1x eine Teilenr-Längen-Kombination auftritt, die nur 1x vorkommt
=> Diese Teilenummern unterliegen entweder Fall 1.2 oder Fall 1.3, aber definitiv nicht Fall 1.1, weil eine Zuordnung zu Fall 1.1 nur dann möglich ist,
wenn alle Teilenr-Längen-Kombinationen einer Teilenummer exakt 2x auftreten!
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_CODES_CASE_12_OR_13")
self.db.con.execute("CREATE TABLE TMP_CODES_CASE_12_OR_13 (KOMPTEILENR TEXT NOT NULL, PRIMARY KEY (KOMPTEILENR))")
self.db.con.execute("CREATE TABLE TMP_CODES_CASE_12_OR_13 (KOMPTEILENR TEXT NOT NULL)")
self.db.con.execute("""
WITH combined_bom AS (
SELECT C.KOMPTEILENR, COALESCE(C.LAENGE, 0) AS LAENGE
FROM COMP C
WHERE C.PARENT = :parent_code
WHERE C.UEBTEILENR = :parent_code
UNION ALL
@@ -334,24 +371,24 @@ class BOMProcessing:
Fall 1.3 umfasst alle Positionen, die entweder in der neuen Stückliste dazugekommen sind (Fall 1.31) oder deren
Teilenr-Längen-Kombination in beiden Stücklisten vorliegen (Fall 1.32).
Wenn Positionen bereits unter Fall 1.1 oder Fall 1.2 fallen, dann werden sie nicht Fall 1.32 zugeordnet, selbst wenn
sie Fall 1.32 auf diese Positionen zutreffen würde (Vorrangsprinzip).
Wenn Positionen bereits unter Fall 1.1 oder Fall 1.2 fallen, dann werden sie NICHT Fall 1.32 zugeordnet, selbst wenn
Fall 1.32 auf diese Positionen zutreffen würde.
Die Prüfung läuft so ab:
1. Teilenr-Posnr-Kombinationen aus der alten und neuen Stückliste mit UNION ALL zusammenfügen, sofern sich die Teilenummern in der
1. Alle Teilenr-Posnr-Kombinationen aus der alten und neuen Stückliste mit UNION ALL verschmelzen, sofern sich die Teilenummern in der
Liste an Teilenummern befindet, die unter Fall 1.2 oder Fall 1.3 fallen (siehe Methode _get_codes_case_12_or_13())
2. Mit COUNT ermitteln, wie oft jede Teilenr-Posnr-Kombination in der zusammengefügten Stückliste auftritt
3. Die Teilenummern ausgeben, deren Teilenr-Posnr-Kombination nur 1x vorkommen in die temporäre Tabelle TMP_CODES_CASE_13 schreiben
=> Diese Teilenummern unterliegen definitv Fall 1.3, weil in Fall 1.2 alle Teilenr-Posnr-Kombinationen exakt 2x auftreten müssen!
3. Die Teilenummern, deren Teilenr-Posnr-Kombination nur 1x auftritt, in die temporäre Tabelle TMP_CODES_CASE_13 schreiben
=> Diese Teilenummern unterliegen definitv Fall 1.3, weil in Fall 1.2 alle Teilenr-Posnr-Kombinationen einer Teilenummer exakt 2x auftreten müssen!
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_CODES_CASE_13")
self.db.con.execute("CREATE TABLE TMP_CODES_CASE_13 (KOMPTEILENR TEXT NOT NULL, PRIMARY KEY(KOMPTEILENR))")
self.db.con.execute("CREATE TABLE TMP_CODES_CASE_13 (KOMPTEILENR TEXT NOT NULL)")
self.db.con.execute("""
WITH combined_bom AS (
SELECT KOMPTEILENR, POSNR
FROM COMP
WHERE PARENT = :parent_code
WHERE UEBTEILENR = :parent_code
AND KOMPTEILENR IN (SELECT * FROM TMP_CODES_CASE_12_OR_13)
UNION ALL
@@ -378,31 +415,36 @@ class BOMProcessing:
Deren Ermittlung funktioniert so:
1. Teilenummern herausfiltern, die NICHT in der Tabelle TMP_CODES_CASE_12_OR_13 vorliegen
2. Zu den gefundenen Teilenummern:
- Parent-Teilenr
- Posnr. laut neuer Stückliste
- ID laut alter Stückliste
in die temporäre Tabelle TMP_POSNRS_CASE_11 schreiben.
2. Zu den gefundenen Teilenummern folgende Spaltenwerte ermitteln und in die temporäre Tabelle TMP_POSNRS_CASE_11 schreiben:
- Teilenr des Parents
- Positionsnummer laut alter Stückliste
- Positionsnummer laut neuer Stückliste
Hinweis:
- ID dient in der späteren UPDATE-Methode dazu, die Positionsnummer zu finden, die aktualisiert werden sollen
- Parent und POSNR werden benötigt, um die neuen Stücklistendaten laut neuer Stückliste identifizieren zu können
- Die Positionsnummern aus beiden Stücklisten werden benötigt, um bei der Ermittlung der Update-Daten die Zuordnung zwischen der alten und neuen
Stückliste herstellen zu können
- Die Teilenummer des Parents wird zusamment mit der Positionsnummer laut alter Stückliste dafür benötigt, um in den Tabellen STKL_REGEL und
VAR_TEIL_BEDINGUNG_2 die Zeilen zu ermitteln, die aktualisiert werden müssen
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_POSNRS_CASE_11")
self.db.con.execute("""CREATE TABLE TMP_POSNRS_CASE_11 (COMP_ID INTEGER NOT NULL,
PARENT TEXT NOT NULL,
POSNR INTEGER NOT NULL,
PRIMARY KEY(POSNR))""")
self.db.con.execute("""
INSERT INTO TMP_POSNRS_CASE_11 (COMP_ID, PARENT, POSNR)
SELECT C.ID AS COMP_ID, C.PARENT, RD.Positionsnummer AS POSNR
self.db.con.execute("""
CREATE TABLE TMP_POSNRS_CASE_11 (UEBTEILENR TEXT NOT NULL,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL)
""")
self.db.con.execute("""
INSERT INTO TMP_POSNRS_CASE_11 (UEBTEILENR, POS_ALT, POS_NEU)
SELECT C.UEBTEILENR, C.POSNR AS POS_ALT, RD.Positionsnummer AS POS_NEU
FROM RD_COMP RD
LEFT JOIN TMP_CODES_CASE_12_OR_13 TMP
ON RD.ChildCode = TMP.KOMPTEILENR
LEFT JOIN COMP C
ON RD.ParentCode = C.PARENT
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0)
@@ -416,69 +458,68 @@ class BOMProcessing:
Deren Ermittlung funktioniert so:
1. Teilenummern herausfiltern, die in der Tabelle TMP_CODES_CASE_12_OR_13, aber NICHT in Tabelle TMP_CODES_CASE_13 existieren
2. Zu den gefundenen Teilenummern:
- Parent-Teilenr
- Posnr. laut neuer Stückliste
- ID laut alter Stückliste
in die temporäre Tabelle TMP_POSNRS_CASE_12 schreiben.
2. Zu den gefundenen Teilenummern folgende Spaltenwerte ermitteln und in die temporäre Tabelle TMP_POSNRS_CASE_12 schreiben:
- Teilenr des Parents
- Positionsnummer laut alter Stückliste
- Positionsnummer laut neuer Stückliste
Hinweis:
- ID dient in der späteren UPDATE-Methode dazu, die Positionsnummer zu finden, die aktualisiert werden sollen
- Parent und POSNR werden benötigt, um die neuen Stücklistendaten laut neuer Stückliste identifizieren zu können
- Die Positionsnummern aus beiden Stücklisten werden benötigt, um bei der Ermittlung der Update-Daten die Zuordnung zwischen der alten und neuen
Stückliste herstellen zu können
- Die Teilenummer des Parents wird zusamment mit der Positionsnummer laut alter Stückliste dafür benötigt, um in den Tabellen STKL_REGEL und
VAR_TEIL_BEDINGUNG_2 die Zeilen zu ermitteln, die aktualisiert werden müssen
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_POSNRS_CASE_12")
self.db.con.execute("""
CREATE TABLE TMP_POSNRS_CASE_12 (COMP_ID INTEGER NOT NULL,
PARENT TEXT NOT NULL,
POSNR INTEGER NOT NULL,
PRIMARY KEY(POSNR))
CREATE TABLE TMP_POSNRS_CASE_12 (UEBTEILENR TEXT NOT NULL,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL)
""")
self.db.con.execute("""
INSERT INTO TMP_POSNRS_CASE_12 (COMP_ID, PARENT, POSNR)
INSERT INTO TMP_POSNRS_CASE_12 (UEBTEILENR, POS_ALT, POS_NEU)
SELECT C.ID AS COMP_ID, C.PARENT, RD.Positionsnummer AS POSNR
SELECT C.UEBTEILENR, C.POSNR AS POS_ALT, RD.Positionsnummer AS POS_NEU
FROM RD_COMP RD
LEFT JOIN TMP_CODES_CASE_13 TMP
ON RD.ChildCode = TMP.KOMPTEILENR
LEFT JOIN COMP C
ON RD.ParentCode = C.PARENT
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Positionsnummer = C.POSNR
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Positionsnummer = C.POSNR
WHERE RD.ParentCode = :parent_code
AND TMP.KOMPTEILENR IS NULL
AND RD.Positionsnummer NOT IN (SELECT POSNR FROM TMP_POSNRS_CASE_11);
AND RD.Positionsnummer NOT IN (SELECT POS_NEU FROM TMP_POSNRS_CASE_11)
""", {"parent_code": self.parent_code})
def _get_case_131_positions(self):
"""
Fall 1.31 - Positionsnummern holen, die neu angelegt werden müssen.
Fall 1.31 - Positionsnummern ermitteln, die neu angelegt werden müssen.
Deren Ermittlung funktioniert so:
1. Teilenummern herausfiltern, deren Teilenr-Längen-Kombination nur in der neuen Stückliste existieren und die zudem in der Tabelle
TMP_CODES_CASE_13 vorliegen.
1. Alle Positionsnummern herausfiltern, auf die folgende Kriterien zutreffen:
- Teilenr-Längen-Kombination der Position existiert nur in der neuen Stückliste existiert
- Teilenummer der Position ist in der Tabelle TMP_CODES_CASE_13 enthalten
Hinweis:
Es reicht nicht aus, lediglich zu prüfen, ob die Teilenr-Längen-Kombination nur in der neuen Stücklist vorliegt
Es reicht nicht aus, lediglich zu prüfen, ob die Teilenr-Längen-Kombination nur in der neuen Stückliste vorliegt
Grund:
Positionsnummern die unter Fall 1.2 fallen, dürfen in der neuen Stückliste eine andere Teilenr-Längen-Kombination
als in der alten Stückliste aufweisen, d.h. die Teilenr-Längen-Kombi laut neuer Stückliste liegt auch nur dort vor. Trotzdem
werden diese Positionsnummern nicht neu angelegt, sondern von der alten in die neuen Stückliste verschoben!
2. Zu den gefundenen Teilenummern:
- Parent-Teilenr
- Posnr. laut neuer Stückliste
- ID laut alter Stückliste
in die temporäre Tabelle TMP_POSNRS_CASE_131 schreiben.
Hinweis:
- ID dient in der späteren UPDATE-Methode dazu, die Positionsnummer zu finden, die aktualisiert werden sollen
- Parent und POSNR werden benötigt, um die neuen Stücklistendaten laut neuer Stückliste identifizieren zu können
"""
2. Alle gefundenen Positionen in die temporäre Tabelle TMP_POSNRS_CASE_131 schreiben (mehr Informationen werden nicht benötigt)
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_POSNRS_CASE_131")
self.db.con.execute("CREATE TABLE TMP_POSNRS_CASE_131 (POSNR INTEGER NOT NULL, PRIMARY KEY(POSNR))")
self.db.con.execute("CREATE TABLE TMP_POSNRS_CASE_131 (POSNR INTEGER NOT NULL)")
self.db.con.execute("""
INSERT INTO TMP_POSNRS_CASE_131
@@ -486,13 +527,13 @@ class BOMProcessing:
FROM RD_COMP RD
LEFT JOIN COMP C
ON RD.ParentCode = C.PARENT
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0)
WHERE RD.ParentCode = :parent_code
AND RD.ChildCode IN (SELECT * FROM TMP_CODES_CASE_13)
AND C.KOMPTEILENR IS NULL;
AND RD.ChildCode IN (SELECT KOMPTEILENR FROM TMP_CODES_CASE_13)
AND C.KOMPTEILENR IS NULL
""", {"parent_code": self.parent_code})
def _get_case_132_positions(self) -> None:
@@ -500,63 +541,76 @@ class BOMProcessing:
Fall 1.32 - Positionsnummern holen, die von der alten in die neue Stückliste verschoben werden
Deren Ermittlung funktioniert so:
1. Teilenummern herausfiltern, deren Teilenr-Längen-Kombination in beiden Stückliste existieren und die zudem in der Tabelle
TMP_CODES_CASE_13 vorliegen. Lediglich zu prüfen, ob die Teilenr-Längen-Kombination nur in der neuen Stückliste vorliegt reicht nicht aus!
1. Alle Positionsnummern herausfiltern, auf die folgende Kriterien zutreffen:
- Teilenr-Längen-Kombination existiert in beiden Stücklisten
- Teilenummer der Position ist in der Tabelle TMP_CODES_CASE_13 enthalten
Hinweis:
Es reicht nicht aus, lediglich zu prüfen, ob die Teilenr-Längen-Kombination in beiden Stücklisten vorliegt
Grund:
Positionsnummern die Fall 1.1 zugeordnet werden können oder Positionsnummern, die unter Fall 1.2 fallen und deren Länge dabei in beiden
Stücklisten gleich bleibt würden grundlegend ebenfalls unter Fall 1.32 fallen und somit identifiziert werden.
Es gilt jedoch das Vorrangsprinzip, d.h. die Zuordnung zu Fall 1.1 oder Fall 1.2 erfolgt (sofern möglich) immer vor der Zuordnung
Es gilt jedoch das Prioritätsprinzip, d.h. die Zuordnung zu Fall 1.1 oder Fall 1.2 erfolgt (sofern möglich) immer vor der Zuordnung
zu Fall 1.32!
2. Zu den gefundenen Teilenummern:
- Parent-Teilenr
- Posnr. laut neuer Stückliste
- ID laut alter Stückliste
in die temporäre Tabelle TMP_POSNRS_CASE_132 schreiben.
2. Zu den gefundenen Teilenummern folgende Spaltenwerte ermitteln und in die temporäre Tabelle TMP_POSNRS_CASE_132 schreiben:
- Teilenr des Parents
- Positionsnummer laut alter Stückliste
- Positionsnummer laut neuer Stückliste
Hinweis:
- ID dient in der späteren UPDATE-Methode dazu, die Positionsnummer zu finden, die aktualisiert werden sollen
- Parent und POSNR werden benötigt, um die neuen Stücklistendaten laut neuer Stückliste identifizieren zu können
- Die Positionsnummern aus beiden Stücklisten werden benötigt, um bei der Ermittlung der Update-Daten die Zuordnung zwischen der alten und neuen
Stückliste herstellen zu können
- Die Teilenummer des Parents wird zusamment mit der Positionsnummer laut alter Stückliste dafür benötigt, um in den Tabellen STKL_REGEL und
VAR_TEIL_BEDINGUNG_2 die Zeilen zu ermitteln, die aktualisiert werden müssen
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_POSNRS_CASE_132")
self.db.con.execute("""
CREATE TABLE TMP_POSNRS_CASE_132 (COMP_ID INTEGER NOT NULL,
PARENT TEXT NOT NULL,
POSNR INTEGER NOT NULL,
PRIMARY KEY(POSNR))""")
self.db.con.execute("""
INSERT INTO TMP_POSNRS_CASE_132 (COMP_ID, PARENT, POSNR)
SELECT C.ID AS COMP_ID, C.PARENT, RD.Positionsnummer AS POSNR
self.db.con.execute("""
CREATE TABLE TMP_POSNRS_CASE_132 (UEBTEILENR TEXT NOT NULL,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL)
""")
self.db.con.execute("""
INSERT INTO TMP_POSNRS_CASE_132 (UEBTEILENR, POS_ALT, POS_NEU)
SELECT C.UEBTEILENR, C.POSNR AS POS_ALT, RD.Positionsnummer AS POS_NEU
FROM RD_COMP RD
INNER JOIN COMP C
ON RD.ParentCode = C.PARENT
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0)
WHERE RD.ParentCode = :parent_code
AND RD.ChildCode IN (SELECT * FROM TMP_CODES_CASE_13)
AND RD.ChildCode IN (SELECT KOMPTEILENR FROM TMP_CODES_CASE_13)
""", {"parent_code": self.parent_code})
def delete(self):
def _get_ids_to_delete(self) -> None:
"""
Entfernt die Positionsnummern aus der alten Stückliste, die nicht mehr in der neuen Stückliste vorliegen.
Ermittelt alle Positionsnummern, die entfernt werden sollen
Hinweis:
Bei der Ermittlung der zu löschenden Datensätze dürfen die Positionsnummern, die verschoben werden NICHT berücksichtigt werden.
Die alte Lösch-Logik: "Die Teile die in der neuen Stückliste nicht drin sind, werden rausgelöscht" nur noch eingeschränkt anwendbar!
Bei der Ermittlung dürfen die Positionsnummern, die verschoben werden NICHT berücksichtigt werden. Die alte Lösch-Logik:
"Die Teile die in der neuen Stückliste nicht drin sind, werden rausgelöscht"
ist daher nur noch eingeschränkt anwendbar!
Grund:
Das liegt vor allem an Fall 1.2, denn per Definition darf in diesem Fall die Länge in der neuen Stückliste abweichen, solange alle
Teilenr-Posnr-Kombinationen in beiden Stücklisten identsich sind, siehe hierzu z.B. Pos. 3 in Beispiel 3 unter doc/Beispiele.pdf:
Dort liegt die Länge 200 aus der alten Stückliste nicht mehr in der neuen Stückliste vor. Pos. 3 würde also laut alter Logik gelöscht werden.
Da diese Positionsnummer aber unter Fall 1.2 fällt, wird sie in die neue Stückliste verschoben, d.h. sie darf nicht gelöscht werden!
Da diese Positionsnummer aber unter Fall 1.2 fällt, wird sie in die neue Stückliste verschoben, darf also nicht gelöscht werden!
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_IDS_TO_DELETE")
self.db.con.execute("CREATE TABLE TMP_IDS_TO_DELETE (ID INTEGER NOT NULL, PRIMARY KEY(ID))")
self.db.con.execute("""CREATE TABLE TMP_IDS_TO_DELETE (COMP_ROWID INTEGER NOT NULL,
VAR_TEIL_BED_2_ROWID INTEGER,
STKL_REGEL_ROWID INTEGER)
""")
self.db.con.execute("""
WITH COMBINED_UPDATE_DATA AS (
SELECT * FROM TMP_POSNRS_CASE_11
@@ -565,45 +619,55 @@ class BOMProcessing:
UNION
SELECT * FROM TMP_POSNRS_CASE_132
)
INSERT INTO TMP_IDS_TO_DELETE
INSERT INTO TMP_IDS_TO_DELETE (COMP_ROWID, VAR_TEIL_BED_2_ROWID, STKL_REGEL_ROWID)
SELECT C.ID
SELECT C.ROWID AS COMP_ROWID, B.ROWID AS VAR_TEIL_BED_2_ROWID, R.ROWID AS STKL_REGEL_ROWID
FROM COMP C
LEFT JOIN COMBINED_UPDATE_DATA CUD
ON C.ID = CUD.COMP_ID
ON C.UEBTEILENR = CUD.UEBTEILENR
AND C.POSNR = CUD.POS_ALT
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON C.UEBTEILENR = B.UEBTEILENR
AND C.POSNR = B.POSNR
LEFT JOIN STKL_REGEL R
ON C.UEBTEILENR = R.UEBTEILENR
AND C.POSNR = R.POSNR
WHERE C.PARENT = :parent_code
AND CUD.COMP_ID IS NULL
WHERE C.UEBTEILENR = :parent_code
AND CUD.UEBTEILENR IS NULL
""", {"parent_code": self.parent_code})
# Entfernung der Stücklistenpositionen aus COMP
self.db.con.execute("DELETE FROM COMP WHERE ID IN (SELECT ID FROM TMP_IDS_TO_DELETE)")
# Entfernung der TOS-Bedingungen aus VAR_TEIL_BEDINGUNG_2
self.db.con.execute("DELETE FROM VAR_TEIL_BEDINGUNG_2 WHERE COMP_ID IN (SELECT ID FROM TMP_IDS_TO_DELETE)")
# Entfernung der TOS-Regeln aus STKL_REGEL
self.db.con.execute("DELETE FROM STKL_REGEL WHERE COMP_ID IN (SELECT ID FROM TMP_IDS_TO_DELETE)")
def update(self):
def _get_update_data(self):
"""
Aktualisiert die Positionsnummern, die von der alten in die neue Stückliste verschoben werden sollen.
Ermittlung der neuen Stücklisteninformationen, mit denen die bestehenden Datzensätze überschrieben werden
Dies umfasst alle Positionsnummern, die unter die Fälle 1.1, 1.2 und 1.32 fallen. Sie wurden in den Methoden
_get_case_11_positions, _get_case_12_positions & _get_case_132_positions ermittelt und in die temporären
Tabellen TMP_POSNRS_CASE_11, TMP_POSNRS_CASE_12, TMP_POSNRS_CASE_132 geschrieben.
Die neuen Informationen werden für alle Positionsnummern bestimmt, die unter die Fälle 1.1, 1.2 und 1.32 fallen,
also die, die sich in den Tabellen TMP_POSNRS_CASE_11, TMP_POSNRS_CASE_12 & TMP_POSNRS_CASE_132 befinden.
Jetzt passiert folgendes:
1. Die Positionen aus den 3 Tabellen werden zu einer temporären Tabelle, ALL_POSNRS_TO_UPDATE kombiniert.
2. Zu allen Positionen aus in dieser kombinierten, temporären Tabelle werden dann die neuen Stücklisteninformationen aus COMP,
bzw. RD_COMP bestimmt und in eine weitere, temporäre, aber persistente Tabelle TMP_UPDATE_DATA geschrieben.
3. Durchführen der Updates in den Tabellen COMP (für die Stücklisteninformationen), VAR_TEIL_BEDINGUNG_2 (für die TOS-Bedingungen)
und STKL_REGEL für die TOS-Regeln
Hinweis:
Es werden in diesem Zuge auch die zugehörigen Zeilen-ID's zu den Positionsnummern in den Tabellen VAR_TEIL_BEDINGUNG_2 und STKL_REGEL ermittelt,
da in diesen Tabellen ebenfalls die Positionsnummern aktualisiert werden müssen, sofern welche vorliegen. Die Regeln und Bedingungen selbst
bleiben unverändert, da sie nur verschoben werden.
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_UPDATE_DATA")
self.db.con.execute("CREATE TABLE TMP_UPDATE_DATA (ID, POSNR, ABW_ANZAHL, MENGE, LAENGE, ZUSCHNITT, DISPO_AKTIV_JN, RUESTCODE, BEDINGUNG, REGEL)")
# Neue Stücklisteninformationen ermitteln
self.db.con.execute("""
CREATE TABLE TMP_UPDATE_DATA (COMP_ROWID INTEGER NOT NULL,
VAR_TEIL_BED_2_ROWID INTEGER,
STKL_REGEL_ROWID INTEGER,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL,
ABW_ANZAHL INTEGER,
MENGE REAL,
LAENGE REAL,
ZUSCHNITT REAL,
DISPO_AKTIV_JN TEXT,
RUESTCODE TEXT)
""")
self.db.con.execute("""
WITH ALL_POSNRS_TO_UPDATE AS (
SELECT * FROM TMP_POSNRS_CASE_11
@@ -612,54 +676,87 @@ class BOMProcessing:
UNION
SELECT * FROM TMP_POSNRS_CASE_132
)
INSERT INTO TMP_UPDATE_DATA (ID, POSNR, ABW_ANZAHL, MENGE, LAENGE, ZUSCHNITT, DISPO_AKTIV_JN, RUESTCODE, BEDINGUNG, REGEL)
INSERT INTO TMP_UPDATE_DATA (COMP_ROWID,
VAR_TEIL_BED_2_ROWID,
STKL_REGEL_ROWID,
POS_ALT,
POS_NEU,
ABW_ANZAHL,
MENGE,
LAENGE,
ZUSCHNITT,
DISPO_AKTIV_JN,
RUESTCODE)
SELECT C.ID,
RD.Positionsnummer AS POSNR,
SELECT C.ROWID AS COMP_ROWID,
B.ROWID AS VAR_TEIL_BED_2_ROWID,
R.ROWID AS STKL_REGEL_ROWID,
P.POS_ALT,
RD.Positionsnummer AS POS_NEU,
-- Bestimmt Wert für Spalte 'ABW_ANZAHL': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Menge
END AS ABW_ANZAHL,
-- Bestimmt Wert für Spalte 'ABW_ANZAHL': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Menge
END AS ABW_ANZAHL,
-- Bestimmt Wert für Spalte 'MENGE': Berechnung Dezimalwert, wenn Pos. längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN RD.Menge
ELSE RD.Menge * (RD.Uebergabelaenge + COALESCE(C.ZUSCHNITT, 0)) / 1000
END AS MENGE,
-- Bestimmt Wert für Spalte 'MENGE': Berechnung Dezimalwert, wenn Pos. längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN RD.Menge
ELSE RD.Menge * (RD.Uebergabelaenge + COALESCE(C.ZUSCHNITT, 0)) / 1000
END AS MENGE,
-- Bestimmt Wert für Spalte 'LAENGE': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Uebergabelaenge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Uebergabelaenge
END AS LAENGE,
-- Bestimmt Wert für Spalte 'LAENGE': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Uebergabelaenge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Uebergabelaenge
END AS LAENGE,
C.ZUSCHNITT,
C.DISPO_AKTIV_JN,
C.RUESTCODE,
B.BEDINGUNG,
R.REGEL
C.ZUSCHNITT,
C.DISPO_AKTIV_JN,
C.RUESTCODE
FROM ALL_POSNRS_TO_UPDATE CUD
FROM ALL_POSNRS_TO_UPDATE P
INNER JOIN COMP C
ON CUD.COMP_ID = C.ID
ON P.UEBTEILENR = C.UEBTEILENR
AND P.POS_ALT = C.POSNR
INNER JOIN RD_COMP RD
ON CUD.PARENT = RD.ParentCode
AND CUD.POSNR = RD.Positionsnummer
ON P.UEBTEILENR = RD.ParentCode
AND P.POS_NEU = RD.Positionsnummer
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON CUD.COMP_ID = B.COMP_ID
ON P.UEBTEILENR = B.UEBTEILENR
AND P.POS_ALT = B.POSNR
LEFT JOIN STKL_REGEL R
ON CUD.COMP_ID = R.COMP_ID
ON P.UEBTEILENR = R.UEBTEILENR
AND P.POS_ALT = R.POSNR
""")
def delete(self):
"""
- Entfernt die Positionsnummern aus COMP, die nicht mehr in der neuen Stückliste vorliegen
- Entfernt die TOS-Regeln und -Bedingungen der Positionsnummern aus VAR_TEIL_BEDINGUNG_2 und STKL_REGEL
"""
# Stücklistenpositionen entfernen
self.db.con.execute("DELETE FROM COMP WHERE ROWID IN (SELECT COMP_ROWID FROM TMP_IDS_TO_DELETE)")
# TOS-Bedingungen entfernen
self.db.con.execute("DELETE FROM VAR_TEIL_BEDINGUNG_2 WHERE ROWID IN (SELECT VAR_TEIL_BED_2_ROWID FROM TMP_IDS_TO_DELETE)")
# TOS-Regeln entfernen
self.db.con.execute("DELETE FROM STKL_REGEL WHERE ROWID IN (SELECT STKL_REGEL_ROWID FROM TMP_IDS_TO_DELETE)")
def update(self):
"""
Aktualisiert die Positionsnummern, deren Spaltenanpassungen und TOS-Regeln / -Bedingungen von der alten in die neue Stückliste übernommen werden sollen.
"""
# Stücklisteninformationen updaten
self.db.con.execute("""
UPDATE COMP
SET POSNR = UD.POSNR,
SET
POSNR = UD.POS_NEU,
ABW_ANZAHL = UD.ABW_ANZAHL,
MENGE = UD.MENGE,
LAENGE = UD.LAENGE,
@@ -667,79 +764,78 @@ class BOMProcessing:
DISPO_AKTIV_JN = UD.DISPO_AKTIV_JN,
RUESTCODE = UD.RUESTCODE
FROM TMP_UPDATE_DATA UD
WHERE COMP.ID = UD.ID
WHERE ROWID = UD.COMP_ROWID
""")
# TOS-Bedingungen updaten
self.db.con.execute("""
UPDATE VAR_TEIL_BEDINGUNG_2
SET BEDINGUNG = UD.BEDINGUNG
SET POSNR = UD.POS_NEU
FROM TMP_UPDATE_DATA UD
WHERE COMP_ID = UD.ID
WHERE ROWID = UD.VAR_TEIL_BED_2_ROWID
""")
# TOS-Regeln updaten
self.db.con.execute("""
UPDATE STKL_REGEL
SET REGEL = UD.REGEL
SET POSNR = UD.POS_NEU
FROM TMP_UPDATE_DATA UD
WHERE COMP_ID = UD.ID
WHERE ROWID = UD.STKL_REGEL_ROWID
""")
def insert(self):
"""
Neuanlage aller Positionen, die nur in der neuen Stückliste vorliegen.
Die Positionsnummern wurde bereits in der Methode _get_codes_case_131 ermittelt und in die temporäre Tabelle
TMP_POSNRS_CASE_131 geschrieben.
Neuanlage aller Positionen, die nur in der neuen Stückliste vorliegen (das sind die aus der Tabelle TMP_POSNRS_CASE_131)
Hinweise:
- Neue Positionen werden immer ohne TOS-Regeln oder -Bedingungen angelegt, d.h. es erfolgen keine INSERT-Statements
in den Tabellen VAR_TEIL_BEDINGUNG_2 oder STKL_REGEL!
- Zur Verdeutlichung, welche Werte z.B. in der Spalte Ruestcode oder Dispoaktiv nach der Neuanlage vorliegen, werden
die Defaultwerte aus der PARTS-Tabelle geholt. Ob ein solcher INSERT auch in SIVAS erforderlich ist, war zum Zeitpunkt
der Erstellung nicht klar.
der Erstellung dieses Scripts nicht klar.
"""
self.db.con.execute("""
INSERT INTO COMP (PARENT, POSNR, KOMPTEILENR, ABW_ANZAHL, MENGE, LAENGE, ZUSCHNITT, DISPO_AKTIV_JN, RUESTCODE)
INSERT INTO COMP (UEBTEILENR, POSNR, KOMPTEILENR, ABW_ANZAHL, MENGE, LAENGE, ZUSCHNITT, DISPO_AKTIV_JN, RUESTCODE)
SELECT RD.ParentCode AS PARENT,
RD.Positionsnummer AS POSNR,
RD.ChildCode AS KOMPTEILENR,
SELECT RD.ParentCode AS UEBTEILENR,
RD.Positionsnummer AS POSNR,
RD.ChildCode AS KOMPTEILENR,
-- Bestimmt Wert für Spalte 'ABW_ANZAHL': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Menge
END AS ABW_ANZAHL,
-- Bestimmt Wert für Spalte 'ABW_ANZAHL': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Menge
END AS ABW_ANZAHL,
-- Bestimmt Wert für Spalte 'MENGE': Berechnung Dezimalwert, wenn Pos. längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN RD.Menge
ELSE RD.Menge * (RD.Uebergabelaenge + COALESCE(C.ZUSCHNITT,0)) / 1000
END AS MENGE,
-- Bestimmt Wert für Spalte 'MENGE': Berechnung Dezimalwert, wenn Pos. längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN RD.Menge
ELSE RD.Menge * (RD.Uebergabelaenge + COALESCE(C.ZUSCHNITT, 0)) / 1000
END AS MENGE,
-- Bestimmt Wert für Spalte 'LAENGE': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Uebergabelaenge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Uebergabelaenge
END AS LAENGE,
NULL AS ZUSCHNITT,
P.DISPO_AKTIV_JN,
P.RUESTCODE
-- Bestimmt Wert für Spalte 'LAENGE': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Uebergabelaenge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Uebergabelaenge
END AS LAENGE,
NULL AS ZUSCHNITT,
P.DISPO_AKTIV_JN,
P.RUESTCODE
FROM RD_COMP RD
LEFT JOIN COMP C
ON RD.ChildCode = C.KOMPTEILENR
AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0)
AND RD.ParentCode = C.PARENT
AND RD.ParentCode = C.UEBTEILENR
LEFT JOIN PARTS P
ON RD.ChildCode = P.TEILENR
WHERE C.KOMPTEILENR IS NULL
AND RD.ParentCode = :parent_code
AND RD.Positionsnummer IN (SELECT * FROM TMP_POSNRS_CASE_131)
AND RD.Positionsnummer IN (SELECT POSNR FROM TMP_POSNRS_CASE_131)
""", {"parent_code": self.parent_code})
def drop_tmp_tables(self):
@@ -752,7 +848,6 @@ class BOMProcessing:
self.db.con.execute("DROP TABLE TMP_IDS_TO_DELETE")
self.db.con.execute("DROP TABLE TMP_UPDATE_DATA")
def write_new_bom(self) -> None:
self._get_codes_case_12_or_13()
self._get_codes_case_13()
@@ -760,9 +855,154 @@ class BOMProcessing:
self._get_case_12_positions()
self._get_case_131_positions()
self._get_case_132_positions()
self._get_ids_to_delete()
self._get_update_data()
self.delete()
self.insert()
self.update()
self.drop_tmp_tables()
class AVWorkflowMessage:
def __init__(self, db: Database, parent_code: str):
self.db = db
self.parent_code = parent_code
def __str__(self):
res = self.get_data()
msg = self._create_msg(res)
return msg
def drop_tmp_table(self):
self.db.con.execute("DROP TABLE TMP_AV_WORKFLOW_MESSAGE")
def get_av_workflow_posnrs_and_codes(self):
"""
Wenn Positionsnummern folgende Kriterien erfüllen, sollen Sie in einer Workflowmeldung an die AV-Verantwortlichen ausgegeben werden:
Kriterien:
WENN die Positionen zu einer längenabhängigen Teilenummer MIT Sägeliste gehören (d.h. Haken bei 'Halbzeug ohne Sägeliste' im Teilestamm ist nicht gesetzt)
UND diese längenabhängige Teilenummer in der alten und der neuen Stückliste vertreten ist
UND die Länge-Teilenummer-Kombination dieser Positionsnummer nur in der alten Stückliste vorliegt (aber nicht in der Neuen)
UND bei der Position mindestens eine der nachfolgenden Kriterien erfüllt ist:
- der Wert in der Spalte 'Rüstcode' weicht vom Rüstcode-Wert ab, den die zugehörige Teilenummer im Teilstamm aufweist
- in der Spalte 'Dispoaktiv' liegt kein Haken vor
- in der Spalte 'Zuschnitt' ist ein Wert definiert
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_AV_WORKFLOW_MESSAGE")
self.db.con.execute("""
CREATE TABLE TMP_AV_WORKFLOW_MESSAGE (POSNR INTEGER,
KOMPTEILENR TEXT)
""")
test = self.db.con.execute("""
--längenabhängige Teilenummern ermitteln, die in beiden Stücklisten vorliegen
WITH laengenabh_codes_in_both_boms AS (
SELECT DISTINCT ChildCode
FROM RD_COMP
WHERE ParentCode = :parent_code
AND ChildCode in (
SELECT DISTINCT KOMPTEILENR
FROM COMP
WHERE UEBTEILENR = :parent_code
AND COALESCE(LAENGE, 0) != 0
)
)
INSERT INTO TMP_AV_WORKFLOW_MESSAGE (POSNR, KOMPTEILENR)
SELECT C.POSNR, C.KOMPTEILENR
FROM COMP C
LEFT JOIN RD_COMP RD
ON C.UEBTEILENR = RD.ParentCode
AND C.KOMPTEILENR = RD.ChildCode
AND COALESCE(C.LAENGE, 0) = RD.Uebergabelaenge
INNER JOIN PARTS P
ON C.KOMPTEILENR = P.TEILENR
WHERE C.UEBTEILENR = :parent_code
AND C.KOMPTEILENR IN laengenabh_codes_in_both_boms -- Nur längenabhängige Teilenummern berücksichtigen, die in beiden Stücklisten vorliegen
AND RD.ParentCode IS NULL -- Nur Positionen berücksichtigen, deren Länge ausschließlich in der alten Stückliste vorliegt
AND P.INFO2_JN = "N" -- Nur Positionen berücksichtigen, deren zugehörige Teilenummer keinen Haken bei "Halbzeug ohne Sägeliste" im Teilestamm aufweist
AND ( -- Nur Positionen berücksichtigen, auf die min. eines der nachfolgenden Kriterien zutrifft:
C.ZUSCHNITT IS NOT NULL -- Zuschnittswert liegt vor
OR C.RUESTCODE != P.RUESTCODE -- Rüstcode der Position in der Stammstückliste ist abweichend zum Rüstcode der zugehörigen Teilenummer im Teilestamm
OR C.DISPO_AKTIV_JN = "N" -- Stücklistenpositionen besitzt keinen Haken in der Spalte "Dispoaktiv"
)
ORDER BY C.POSNR
""", {"parent_code": self.parent_code}).fetchall()
return test
def get_data(self) -> list[dict[str]]:
res = self.db.con.execute("SELECT POSNR, KOMPTEILENR FROM TMP_AV_WORKFLOW_MESSAGE").fetchall()
return res
def _create_msg(self, data: list[dict[str]] | None) -> str:
if not data:
return "\nAV-Meldung:\nKeine"
affected_positions_str = ", ".join([f"{pos["POSNR"]} - {pos["KOMPTEILENR"]}" for pos in data])
return f"\nAV-Meldung:\nBitte Stückliste {self.parent_code} prüfen. Vor der Änderung lagen bei folgenden Positionen manuelle Eintragungen / Änderungen in den Spalten „Zuschnitt“, „Rüstcode“ oder „Dispoaktiv“ vor: {affected_positions_str}"
class TOSWorkflowMessage:
def __init__(self, db: Database, parent_code: str):
self.db = db
self.parent_code = parent_code
def __str__(self):
res = self.get_data()
msg = self._create_msg(res)
return msg
def drop_tmp_table(self):
self.db.con.execute("DROP TABLE TMP_TOS_WORKFLOW_MESSAGE")
def get_tos_workflow_posnrs_and_codes(self) -> None:
"""
Wenn Positionsnummern in der Stückliste TOS-Regeln oder -Bedingungen aufweisen,
sollen Sie in einer Workflowmeldung an die TOS-Verantwortlichen ausgegeben werden.
"""
self.db.con.execute("DROP TABLE IF EXISTS TMP_TOS_WORKFLOW_MESSAGE")
self.db.con.execute("""
CREATE TABLE TMP_TOS_WORKFLOW_MESSAGE (POSNR INTEGER,
KOMPTEILENR TEXT)
""")
self.db.con.execute("""
INSERT INTO TMP_TOS_WORKFLOW_MESSAGE (POSNR, KOMPTEILENR)
SELECT C.POSNR, C.KOMPTEILENR
FROM COMP C
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON C.UEBTEILENR = B.UEBTEILENR
AND C.POSNR = B.POSNR
LEFT JOIN STKL_REGEL R
ON C.UEBTEILENR = R.UEBTEILENR
AND C.POSNR = R.POSNR
WHERE C.UEBTEILENR = :parent_code
AND (
B.UEB_LEISTEN_NR IS NOT NULL
OR R.LFD_NR IS NOT NULL
)
ORDER BY C.POSNR
""", {"parent_code": self.parent_code})
def get_data(self) -> list[dict[str]]:
res = self.db.con.execute("SELECT POSNR, KOMPTEILENR FROM TMP_TOS_WORKFLOW_MESSAGE").fetchall()
return res
def _create_msg(self, data: list[dict[str]] | None) -> str:
if not data:
return "\nTOS-Meldung:\nKeine"
affected_positions_str = ", ".join([f"{pos["POSNR"]} - {pos["KOMPTEILENR"]}" for pos in data])
return f"\nTOS-Meldung:\nBitte Stückliste {self.parent_code} prüfen. Vor der Änderung lagen bei folgenden Positionen TOS-Bedingungen vor: {affected_positions_str}"
if __name__ == "__main__":
main()
Binary file not shown.
+308
View File
@@ -0,0 +1,308 @@
<?xml version="1.0" encoding="UTF-8"?><sqlb_project><db path="test.db" readonly="0" foreign_keys="1" case_sensitive_like="0" temp_store="0" wal_autocheckpoint="1000" synchronous="2"/><attached/><window><main_tabs open="structure browser pragmas query" current="3"/></window><tab_structure><column_width id="0" width="300"/><column_width id="1" width="0"/><column_width id="2" width="100"/><column_width id="3" width="3270"/><column_width id="4" width="0"/><expanded_item id="0" parent="1"/><expanded_item id="1" parent="1"/><expanded_item id="2" parent="1"/><expanded_item id="3" parent="1"/></tab_structure><tab_browse><table title="PARTS" custom_title="0" dock_id="1" table="4,5:mainPARTS"/><dock_state state="000000ff00000000fd00000001000000020000058b00000395fc0100000001fc000000000000058b0000012100fffffffa000000000100000002fb000000160064006f0063006b00420072006f00770073006500310100000000ffffffff0000012100fffffffb000000160064006f0063006b00420072006f00770073006500340000000000ffffffff00000000000000000000058b0000000000000004000000040000000800000008fc00000000"/><default_encoding codec=""/><browse_table_settings><table schema="main" name="COMP" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_" freeze_columns="0"><sort><column index="1" mode="0"/></sort><column_widths><column index="1" value="50"/><column index="2" value="79"/><column index="3" value="48"/><column index="4" value="90"/><column index="5" value="107"/><column index="6" value="48"/><column index="7" value="87"/><column index="8" value="52"/><column index="9" value="73"/><column index="10" value="76"/><column index="11" value="72"/><column index="12" value="63"/><column index="13" value="72"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="PARTS" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_" freeze_columns="0"><sort><column index="6" mode="1"/></sort><column_widths><column index="1" value="78"/><column index="2" value="300"/><column index="3" value="39"/><column index="4" value="53"/><column index="5" value="64"/><column index="6" value="108"/><column index="7" value="107"/><column index="8" value="41"/><column index="9" value="97"/><column index="10" value="126"/><column index="12" value="112"/><column index="13" value="135"/><column index="14" value="54"/><column index="15" value="51"/><column index="16" value="218"/><column index="17" value="195"/><column index="18" value="73"/><column index="19" value="65"/><column index="20" value="76"/><column index="21" value="113"/><column index="22" value="76"/><column index="23" value="70"/><column index="24" value="102"/><column index="25" value="52"/></column_widths><filter_values><column index="4" value="M"/><column index="0" value="822066089"/></filter_values><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="RD_COMP" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_" freeze_columns="0"><sort><column index="1" mode="0"/></sort><column_widths><column index="1" value="35"/><column index="2" value="78"/><column index="3" value="105"/><column index="4" value="78"/><column index="5" value="45"/><column index="6" value="105"/><column index="7" value="122"/></column_widths><filter_values><column index="6" value="5"/></filter_values><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="STKL_REGEL" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_" freeze_columns="0"><sort/><column_widths><column index="1" value="35"/><column index="2" value="82"/><column index="3" value="134"/><column index="4" value="44"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table><table schema="main" name="VAR_TEIL_BEDINGUNG_2" show_row_id="0" encoding="" plot_x_axis="" unlock_view_pk="_rowid_" freeze_columns="0"><sort><column index="3" mode="0"/></sort><column_widths><column index="1" value="48"/><column index="2" value="140"/><column index="3" value="137"/><column index="4" value="102"/></column_widths><filter_values/><conditional_formats/><row_id_formats/><display_formats/><hidden_columns/><plot_y_axes/><global_filter/></table></browse_table_settings></tab_browse><tab_sql><sql name="1_create_tmp_not_identical">DROP TABLE IF EXISTS TMP_CODES_CASE_12_OR_13;
CREATE TABLE TMP_CODES_CASE_12_OR_13 (KOMPTEILENR TEXT NOT NULL, PRIMARY KEY (KOMPTEILENR));
WITH combined_bom AS (
SELECT C.KOMPTEILENR, COALESCE(C.LAENGE, 0) AS LAENGE
FROM COMP C
WHERE C.UEBTEILENR = '000000372'
UNION ALL
SELECT ChildCode, Uebergabelaenge
FROM RD_COMP
WHERE ParentCode = '000000372'
)
INSERT INTO TMP_CODES_CASE_12_OR_13
SELECT DISTINCT KOMPTEILENR
FROM (
SELECT KOMPTEILENR, LAENGE, COUNT(*) AS ANZAHL
FROM combined_bom
GROUP BY KOMPTEILENR, LAENGE
)
WHERE ANZAHL = 1;
SELECT * FROM TMP_CODES_CASE_12_OR_13</sql><sql name="2_create_tmp_not_same_pos">DROP TABLE IF EXISTS TMP_CODES_CASE_13;
CREATE TABLE TMP_CODES_CASE_13 (KOMPTEILENR TEXT NOT NULL, PRIMARY KEY(KOMPTEILENR));
WITH combined_bom AS (
SELECT KOMPTEILENR, POSNR
FROM COMP
WHERE UEBTEILENR = '000000372'
AND KOMPTEILENR IN (SELECT * FROM TMP_CODES_CASE_12_OR_13)
UNION ALL
SELECT ChildCode, Positionsnummer
FROM RD_COMP
WHERE ParentCode = '000000372'
AND ChildCode IN (SELECT * FROM TMP_CODES_CASE_12_OR_13)
)
INSERT INTO TMP_CODES_CASE_13 (KOMPTEILENR)
SELECT DISTINCT KOMPTEILENR
FROM (
SELECT KOMPTEILENR, COUNT(*) AS ANZAHL
FROM combined_bom
GROUP BY KOMPTEILENR, POSNR
)
WHERE ANZAHL = 1;
SELECT * FROM TMP_CODES_CASE_13</sql><sql name="3_get_case_11_pos">DROP TABLE IF EXISTS TMP_POSNRS_CASE_11;
CREATE TABLE TMP_POSNRS_CASE_11 (UEBTEILENR TEXT NOT NULL,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL,
PRIMARY KEY(POS_NEU));
INSERT INTO TMP_POSNRS_CASE_11 (UEBTEILENR, POS_ALT, POS_NEU)
SELECT C.UEBTEILENR, C.POSNR AS POS_ALT, RD.Positionsnummer AS POS_NEU
FROM RD_COMP RD
LEFT JOIN TMP_CODES_CASE_12_OR_13 TMP
ON RD.ChildCode = TMP.KOMPTEILENR
LEFT JOIN COMP C
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0)
WHERE RD.ParentCode = '000000372'
AND TMP.KOMPTEILENR IS NULL;
SELECT * FROM TMP_POSNRS_CASE_11</sql><sql name="4_get_case_12_pos">DROP TABLE IF EXISTS TMP_POSNRS_CASE_12;
CREATE TABLE TMP_POSNRS_CASE_12 (UEBTEILENR TEXT NOT NULL,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL,
PRIMARY KEY(POS_NEU));
INSERT INTO TMP_POSNRS_CASE_12 (UEBTEILENR, POS_ALT, POS_NEU)
SELECT C.UEBTEILENR, C.POSNR AS POS_ALT, RD.Positionsnummer AS POS_NEU
FROM RD_COMP RD
LEFT JOIN TMP_CODES_CASE_13 TMP
ON RD.ChildCode = TMP.KOMPTEILENR
LEFT JOIN COMP C
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Positionsnummer = C.POSNR
WHERE RD.ParentCode = '000000372'
AND TMP.KOMPTEILENR IS NULL
AND RD.Positionsnummer NOT IN (SELECT POSNR FROM TMP_POSNRS_CASE_11);
SELECT * FROM TMP_POSNRS_CASE_12;</sql><sql name="5_get_case_131_pos">DROP TABLE IF EXISTS TMP_POSNRS_CASE_131;
CREATE TABLE TMP_POSNRS_CASE_131 (POSNR INTEGER NOT NULL, PRIMARY KEY(POSNR));
INSERT INTO TMP_POSNRS_CASE_131
SELECT RD.Positionsnummer AS POSNR
FROM RD_COMP RD
LEFT JOIN COMP C
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0)
WHERE RD.ParentCode = '000000372'
AND RD.ChildCode IN (SELECT * FROM TMP_CODES_CASE_13)
AND C.KOMPTEILENR IS NULL;
SELECT * FROM TMP_POSNRS_CASE_131;</sql><sql name="6_get_case_132_pos">DROP TABLE IF EXISTS TMP_POSNRS_CASE_132;
CREATE TABLE TMP_POSNRS_CASE_132 (UEBTEILENR TEXT NOT NULL,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL,
PRIMARY KEY(POS_NEU));
INSERT INTO TMP_POSNRS_CASE_132 (UEBTEILENR, POS_ALT, POS_NEU)
SELECT C.UEBTEILENR, C.POSNR AS POS_ALT, RD.Positionsnummer AS POS_NEU
FROM RD_COMP RD
INNER JOIN COMP C
ON RD.ParentCode = C.UEBTEILENR
AND RD.ChildCode = C.KOMPTEILENR
AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0)
WHERE RD.ParentCode = '000000372'
AND RD.ChildCode IN (SELECT * FROM TMP_CODES_CASE_13);
SELECT * FROM TMP_POSNRS_CASE_132;</sql><sql name="7_get_ids_to_delete">WITH COMBINED_UPDATE_DATA AS (
SELECT * FROM TMP_POSNRS_CASE_11
UNION
SELECT * FROM TMP_POSNRS_CASE_12
UNION
SELECT * FROM TMP_POSNRS_CASE_132
)
--INSERT INTO TMP_IDS_TO_DELETE
SELECT C.ROWID AS COMP_ROWID, B.ROWID AS BED_ROWID, R.ROWID AS REG_ROWID
FROM COMP C
LEFT JOIN COMBINED_UPDATE_DATA CUD
ON C.UEBTEILENR = CUD.UEBTEILENR
AND C.POSNR = CUD.POS_ALT
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON C.UEBTEILENR = B.UEBTEILENR
AND C.POSNR = B.POSNR
LEFT JOIN STKL_REGEL R
ON C.UEBTEILENR = R.UEBTEILENR
AND C.POSNR = R.POSNR
WHERE C.UEBTEILENR = '000000372'
AND CUD.UEBTEILENR IS NULL</sql><sql name="get_update_data">DROP TABLE IF EXISTS TMP_UPDATE_DATA;
CREATE TABLE TMP_UPDATE_DATA (ROWID INTEGER NOT NULL,
POS_ALT INTEGER NOT NULL,
POS_NEU INTEGER NOT NULL,
ABW_ANZAHL INTEGER,
MENGE REAL,
LAENGE REAL,
ZUSCHNITT REAL,
DISPO_AKTIV_JN TEXT,
RUESTCODE TEXT,
BED_ROWID INTEGER,
REG_ROWID INTEGER,
UEB_LEISTEN_NR INTEGER,
LFD_NR INTEGER);
WITH ALL_POSNRS_TO_UPDATE AS (
SELECT * FROM TMP_POSNRS_CASE_11
UNION
SELECT * FROM TMP_POSNRS_CASE_12
UNION
SELECT * FROM TMP_POSNRS_CASE_132
)
INSERT INTO TMP_UPDATE_DATA (ROWID, POS_ALT, POS_NEU, ABW_ANZAHL, MENGE, LAENGE, ZUSCHNITT, DISPO_AKTIV_JN, RUESTCODE, BED_ROWID, REG_ROWID, UEB_LEISTEN_NR, LFD_NR)
SELECT C.ROWID,
P.POS_ALT,
RD.Positionsnummer AS POS_NEU,
-- Bestimmt Wert für Spalte 'ABW_ANZAHL': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Menge
END AS ABW_ANZAHL,
-- Bestimmt Wert für Spalte 'MENGE': Berechnung Dezimalwert, wenn Pos. längenabhängig, sonst Wert 'Menge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN RD.Menge
ELSE RD.Menge * (RD.Uebergabelaenge + COALESCE(C.ZUSCHNITT, 0)) / 1000
END AS MENGE,
-- Bestimmt Wert für Spalte 'LAENGE': NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Uebergabelaenge' aus CSV
CASE
WHEN RD.Uebergabelaenge = 0 THEN NULL
ELSE RD.Uebergabelaenge
END AS LAENGE,
C.ZUSCHNITT,
C.DISPO_AKTIV_JN,
C.RUESTCODE,
B.ROWID,
R.ROWID,
B.UEB_LEISTEN_NR,
R.LFD_NR
FROM ALL_POSNRS_TO_UPDATE P
INNER JOIN COMP C
ON P.UEBTEILENR = C.UEBTEILENR
AND P.POS_ALT = C.POSNR
INNER JOIN RD_COMP RD
ON P.UEBTEILENR = RD.ParentCode
AND P.POS_NEU = RD.Positionsnummer
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON P.UEBTEILENR = B.UEBTEILENR
AND P.POS_ALT = B.POSNR
LEFT JOIN STKL_REGEL R
ON P.UEBTEILENR = R.UEBTEILENR
AND P.POS_ALT = R.POSNR;
SELECT * FROM TMP_UPDATE_DATA</sql><sql name="TOS-Workflowmeldung">SELECT C.POSNR, C.KOMPTEILENR
FROM COMP C
LEFT JOIN VAR_TEIL_BEDINGUNG_2 B
ON C.UEBTEILENR = B.UEBTEILENR
AND C.POSNR = B.POSNR
LEFT JOIN STKL_REGEL R
ON C.UEBTEILENR = R.UEBTEILENR
AND C.POSNR = R.POSNR
WHERE C.UEBTEILENR = '000000382'
AND (
B.UEB_LEISTEN_NR IS NOT NULL
OR R.LFD_NR IS NOT NULL
)
ORDER BY C.POSNR</sql><sql name="AV-Workflowmeldungen*">--längenabhängige Teilenummern herausfinden, die in beiden Stücklisten vorliegen
WITH laengenabh_codes_in_both_boms AS (
SELECT DISTINCT ChildCode
FROM RD_COMP
WHERE ParentCode = '000000382'
AND ChildCode in (
SELECT DISTINCT KOMPTEILENR
FROM COMP
WHERE UEBTEILENR = '000000382'
AND COALESCE(LAENGE, 0) != 0
)
)
SELECT C.POSNR, C.KOMPTEILENR
FROM COMP C
LEFT JOIN RD_COMP RD
ON C.UEBTEILENR = RD.ParentCode
AND C.KOMPTEILENR = RD.ChildCode
AND COALESCE(C.LAENGE, 0) = RD.Uebergabelaenge
INNER JOIN PARTS P
ON C.KOMPTEILENR = P.TEILENR