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)

This commit is contained in:
2025-08-23 00:06:32 +02:00
parent f63f5c8332
commit 8826b2e132
+189 -13
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["bom"]:
if args["verbose"]:
output = repr(new_bom)
print(output)
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":
filedir = args["out_dir"]
filenames = []
file_data = []
# 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()
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.append(f"BOM_{args["parent_code"]}_new.csv")
file_data.append(bom_data)
if args["open_csv"]:
subprocess.run(['cmd', '/c', 'start', '', filepath], shell=True)
# 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
@@ -828,5 +861,148 @@ class BOMProcessing:
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()