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:
+189
-13
@@ -2,7 +2,6 @@ import argparse
|
|||||||
import csv
|
import csv
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description='Programm erstellt finale SIVAS-Stückliste aus der RuleDesigner-Stücklisten-CSV unter Berücksichtigung der spezifizierten Verschiebelogik!',
|
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_check", ArgparseDirectoryCheck)
|
||||||
parser.register("action", "store_after_code_check", ArgparseParentcodeCheck)
|
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("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")
|
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_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 fertig bearbeiteten Stückliste als CSV-Datei")
|
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.register("action", "store_after_check", ArgparseDirectoryCheck)
|
||||||
|
|
||||||
sp_csv.add_argument("-v", "--verbose", action="store_true", help="Alle Spalten ausgeben")
|
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_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("-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())
|
args: dict = vars(parser.parse_args())
|
||||||
|
|
||||||
if os.environ.get("DB_PATH") is not None:
|
if os.environ.get("DB_PATH") is not None:
|
||||||
@@ -37,6 +38,8 @@ def main():
|
|||||||
csv_handler = CSVHandler()
|
csv_handler = CSVHandler()
|
||||||
sql_handler = SQLFileHandler()
|
sql_handler = SQLFileHandler()
|
||||||
bom_processing = BOMProcessing(db, args["parent_code"])
|
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"
|
# SIVAS "nachbauen"
|
||||||
sql_source_filename = "sivas_replika.sql"
|
sql_source_filename = "sivas_replika.sql"
|
||||||
@@ -48,29 +51,59 @@ def main():
|
|||||||
new_bom.create_tmp_table()
|
new_bom.create_tmp_table()
|
||||||
new_bom.insert_csv_data(csv_data)
|
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
|
# Neue Stückliste erstellen / schreiben
|
||||||
bom_processing.write_new_bom()
|
bom_processing.write_new_bom()
|
||||||
|
|
||||||
|
# Ausgabe in Konsole
|
||||||
if args["output_type"] == "cmd":
|
if args["output_type"] == "cmd":
|
||||||
|
|
||||||
|
if args["bom"]:
|
||||||
if args["verbose"]:
|
if args["verbose"]:
|
||||||
output = repr(new_bom)
|
print(repr(new_bom))
|
||||||
print(output)
|
|
||||||
else:
|
else:
|
||||||
print(new_bom)
|
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":
|
elif args["output_type"] == "csv":
|
||||||
|
filedir = args["out_dir"]
|
||||||
|
filenames = []
|
||||||
|
file_data = []
|
||||||
|
|
||||||
|
# CSV-Ausgabe der aufbereiteten Stückliste
|
||||||
|
if args["bom"]:
|
||||||
if args["verbose"]:
|
if args["verbose"]:
|
||||||
bom_data = new_bom.get_verbose_data()
|
bom_data = new_bom.get_verbose_data()
|
||||||
else:
|
else:
|
||||||
bom_data = new_bom.get_compact_data()
|
bom_data = new_bom.get_compact_data()
|
||||||
|
|
||||||
filedir = args["out_dir"]
|
filenames.append(f"BOM_{args["parent_code"]}_new.csv")
|
||||||
filename = f"BOM_{args["parent_code"]}_final.csv"
|
file_data.append(bom_data)
|
||||||
filepath = os.path.join(filedir, filename)
|
|
||||||
csv_handler.write_from_dict(data=bom_data, filedir=filedir, filename=filename)
|
|
||||||
|
|
||||||
if args["open_csv"]:
|
# CSV-Ausgabe der Positionsnummern aus der alten Stückliste, die Bestandteil der AV-Workflowmeldung sind
|
||||||
subprocess.run(['cmd', '/c', 'start', '', filepath], shell=True)
|
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:
|
else:
|
||||||
NotImplementedError
|
NotImplementedError
|
||||||
@@ -828,5 +861,148 @@ class BOMProcessing:
|
|||||||
self.insert()
|
self.insert()
|
||||||
self.update()
|
self.update()
|
||||||
self.drop_tmp_tables()
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user