diff --git a/lib/process_bom.py b/lib/process_bom.py index ccb3375..6dcd972 100644 --- a/lib/process_bom.py +++ b/lib/process_bom.py @@ -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 @@ -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()