From ec3526b9e4b9eb2abc2b7b1e6117a4be805b2a59 Mon Sep 17 00:00:00 2001 From: "s.steuer" Date: Fri, 8 Aug 2025 00:39:00 +0200 Subject: [PATCH] =?UTF-8?q?Ersten=20funktionierende=20Variante=20der=20neu?= =?UTF-8?q?en=20Verschiebelogik=20sowie=20CSVs=20zum=20Testen=20erg=C3=A4n?= =?UTF-8?q?zt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 15 ++ data/BOM_000000369.csv | 5 + data/BOM_000000370.csv | 2 + data/BOM_000000371.csv | 4 + data/BOM_000000372.csv | 5 + data/BOM_000000373.csv | 5 + data/BOM_000000374.csv | 6 + data/BOM_000000375.csv | 5 + data/BOM_000000376.csv | 3 + data/BOM_000000377.csv | 4 + data/BOM_000000378.csv | 2 + data/BOM_000000379.csv | 5 + data/BOM_000000380.csv | 6 + data/BOM_000000381.csv | 10 + data/BOM_000000382.csv | 19 ++ data/BOM_000000383.csv | 5 + data/sivas_replika.sql | 207 +++++++++++++++++ lib/verschiebelogik.py | 509 +++++++++++++++++++++++++++++++++++++++++ 18 files changed, 817 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 data/BOM_000000369.csv create mode 100644 data/BOM_000000370.csv create mode 100644 data/BOM_000000371.csv create mode 100644 data/BOM_000000372.csv create mode 100644 data/BOM_000000373.csv create mode 100644 data/BOM_000000374.csv create mode 100644 data/BOM_000000375.csv create mode 100644 data/BOM_000000376.csv create mode 100644 data/BOM_000000377.csv create mode 100644 data/BOM_000000378.csv create mode 100644 data/BOM_000000379.csv create mode 100644 data/BOM_000000380.csv create mode 100644 data/BOM_000000381.csv create mode 100644 data/BOM_000000382.csv create mode 100644 data/BOM_000000383.csv create mode 100644 data/sivas_replika.sql create mode 100644 lib/verschiebelogik.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6b76b4f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/data/BOM_000000369.csv b/data/BOM_000000369.csv new file mode 100644 index 0000000..403b8c3 --- /dev/null +++ b/data/BOM_000000369.csv @@ -0,0 +1,5 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000369;1;821106001;1;200 +000000369;2;822066207;1;0 +000000369;3;821106001;1;100 +000000369;4;829526517;1;0 diff --git a/data/BOM_000000370.csv b/data/BOM_000000370.csv new file mode 100644 index 0000000..8cf57d6 --- /dev/null +++ b/data/BOM_000000370.csv @@ -0,0 +1,2 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000370;1;821106001;1;200 diff --git a/data/BOM_000000371.csv b/data/BOM_000000371.csv new file mode 100644 index 0000000..5fd3ce8 --- /dev/null +++ b/data/BOM_000000371.csv @@ -0,0 +1,4 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000371;1;821106001;1;150 +000000371;2;829416035;1;0 +000000371;3;821106001;1;100 diff --git a/data/BOM_000000372.csv b/data/BOM_000000372.csv new file mode 100644 index 0000000..772a8bd --- /dev/null +++ b/data/BOM_000000372.csv @@ -0,0 +1,5 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000372;1;822066207;1;0 +000000372;2;821106001;1;150 +000000372;3;821106001;1;400 +000000372;4;821106001;1;100 diff --git a/data/BOM_000000373.csv b/data/BOM_000000373.csv new file mode 100644 index 0000000..bbad863 --- /dev/null +++ b/data/BOM_000000373.csv @@ -0,0 +1,5 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000373;1;821106001;1;100 +000000373;2;822066207;1;0 +000000373;3;821106001;1;200 +000000373;4;821106001;1;300 diff --git a/data/BOM_000000374.csv b/data/BOM_000000374.csv new file mode 100644 index 0000000..ec62f35 --- /dev/null +++ b/data/BOM_000000374.csv @@ -0,0 +1,6 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000374;1;821106001;1;200 +000000374;2;822066207;1;0 +000000374;3;829416035;1;0 +000000374;4;829526517;1;0 +000000374;5;829416093;1;0 diff --git a/data/BOM_000000375.csv b/data/BOM_000000375.csv new file mode 100644 index 0000000..057f6cc --- /dev/null +++ b/data/BOM_000000375.csv @@ -0,0 +1,5 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000375;1;821106001;1;200 +000000375;2;822066207;1;0 +000000375;3;821106001;1;250 +000000375;4;829416035;1;0 diff --git a/data/BOM_000000376.csv b/data/BOM_000000376.csv new file mode 100644 index 0000000..6bc9a21 --- /dev/null +++ b/data/BOM_000000376.csv @@ -0,0 +1,3 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000376;1;829416035;1;0 +000000376;2;822066207;1;0 diff --git a/data/BOM_000000377.csv b/data/BOM_000000377.csv new file mode 100644 index 0000000..84d7f0f --- /dev/null +++ b/data/BOM_000000377.csv @@ -0,0 +1,4 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000377;1;829416035;1;0 +000000377;2;822066207;1;0 +000000377;3;821106001;1;100 diff --git a/data/BOM_000000378.csv b/data/BOM_000000378.csv new file mode 100644 index 0000000..dde14b6 --- /dev/null +++ b/data/BOM_000000378.csv @@ -0,0 +1,2 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000378;1;829516004;1;200 diff --git a/data/BOM_000000379.csv b/data/BOM_000000379.csv new file mode 100644 index 0000000..a548ddc --- /dev/null +++ b/data/BOM_000000379.csv @@ -0,0 +1,5 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000379;1;821106001;1;200 +000000379;2;822066207;1;0 +000000379;3;821106001;1;100 +000000379;4;829526517;1;0 diff --git a/data/BOM_000000380.csv b/data/BOM_000000380.csv new file mode 100644 index 0000000..0834e71 --- /dev/null +++ b/data/BOM_000000380.csv @@ -0,0 +1,6 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000380;1;821096001;1;300 +000000380;2;829416035;1;0 +000000380;3;821106001;1;400 +000000380;4;821096001;1;250 +000000380;5;829516004;1;100 diff --git a/data/BOM_000000381.csv b/data/BOM_000000381.csv new file mode 100644 index 0000000..8fc4091 --- /dev/null +++ b/data/BOM_000000381.csv @@ -0,0 +1,10 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000381;1;829516004;1;200 +000000381;2;829516004;1;400 +000000381;3;822066026;1;400 +000000381;4;829416035;1;0 +000000381;5;822066089;1;250 +000000381;6;829526517;1;0 +000000381;7;829516004;1;600 +000000381;8;822066089;1;350 + diff --git a/data/BOM_000000382.csv b/data/BOM_000000382.csv new file mode 100644 index 0000000..9c142bb --- /dev/null +++ b/data/BOM_000000382.csv @@ -0,0 +1,19 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000382;1;829416031;1;200 +000000382;2;822064013;1;200 +000000382;3;822066026;1;400 +000000382;4;827076001;1;400 +000000382;5;822066089;1;250 +000000382;6;827076001;1;200 +000000382;7;829416031;1;100 +000000382;8;829516001;1;100 +000000382;9;827076001;1;150 +000000382;10;829526000;1;150 +000000382;11;822066089;1;350 +000000382;12;829516004;1;500 +000000382;13;829516004;1;200 +000000382;14;822064013;1;100 +000000382;15;829416035;1;0 +000000382;16;821096001;1;350 +000000382;17;829526517;1;0 + diff --git a/data/BOM_000000383.csv b/data/BOM_000000383.csv new file mode 100644 index 0000000..252554b --- /dev/null +++ b/data/BOM_000000383.csv @@ -0,0 +1,5 @@ +ParentCode;Positionsnummer;ChildCode;Menge;Uebergabelaenge +000000383;1;821106001;1;200 +000000383;2;821106001;4;400 +000000383;3;821096001;2;300 +000000383;4;829514035;2;0 diff --git a/data/sivas_replika.sql b/data/sivas_replika.sql new file mode 100644 index 0000000..e705b12 --- /dev/null +++ b/data/sivas_replika.sql @@ -0,0 +1,207 @@ +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, + "POSNR" INTEGER NOT NULL, + "KOMPTEILENR" TEXT NOT NULL, + "DISPO_AKTIV_JN" TEXT, + "MENGE" REAL NOT NULL, + "ABW_ANZAHL" INTEGER, + "LAENGE" REAL, + "ZUSCHNITT" TEXT, + "RUESTCODE" TEXT, + PRIMARY KEY("ID" AUTOINCREMENT) +); +CREATE TABLE IF NOT EXISTS "PARTS" ( + "TEILENR" INTEGER, + "BEZ" TEXT, + "BEZ2" TEXT, + "KZKAUF" TEXT, + "MENGENC" TEXT, + "ABW_ANZAHL_JN" TEXT, + "DISPO_AKTIV_JN" TEXT, + "GRNR" TEXT, + "DISPO_GRUPPE" TEXT, + "DISPOLAUF_GRUPPE" TEXT, + "ÄnderungsIndex" TEXT, + "HERSTELLERNAME" TEXT, + "TEILENR_HERSTELLER" TEXT, + "INFO2" TEXT, + "INFO16" TEXT, + "WERKSTOFF" TEXT, + "OBERFLAECHE" TEXT, + "TRUEMMER" TEXT, + "INFO2_JN" TEXT, + "RUESTCODE" TEXT, + "FERTIGUNGSART" TEXT, + "FOSGRUPPE" TEXT, + "INFO14" TEXT, + "INDEXERFASSER" TEXT, + "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") +); +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") +); +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'), + (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'), + (7,'000000369',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'), + (8,'000000369',30,'821106001','J',0.21,1,200.0,'10','SRC'), + (9,'000000369',40,'822066207','J',1.0,NULL,NULL,NULL,'SRC'), + (10,'000000371',10,'821106001','J',0.105,1,100.0,'5','SRC'), + (11,'000000371',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'), + (12,'000000371',30,'821106001','J',0.21,1,200.0,'10','SRC'), + (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'), + (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'), + (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'), + (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'), + (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'), + (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'), + (37,'000000379',20,'829416035','J',1.0,NULL,NULL,NULL,'SRC'), + (38,'000000379',30,'821106001','J',0.2,1,200.0,NULL,'SRC'), + (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'), + (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'), + (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'), + (821096001,'ALUPROFIL AP60',NULL,'K','M','J','J','3100','10','10','A',NULL,NULL,'980713',NULL,'EN AW-6060-T66 (3.3206-T66)','eloxieren','FALSCH','N','HZS','SN','0','Eigenbau','S.STEUER','5'), + (821106001,'ALUPROFIL AP110',NULL,'K','M','J','J','3100','10','10','A',NULL,NULL,'190723',NULL,'EN AW-6060-T66 (3.3206-T66)','eloxieren','FALSCH','N','HZS','SN','0','Eigenbau','B.MÜLLER','5'), + (822064013,'TEF GLEITLEISTE',NULL,'K','M','J','J','6','20','10','A',NULL,NULL,'981116',NULL,'Murtfeldt "S" 1000','Masterbatch (Grundfarbe)','FALSCH','N','HZS','EX','0','Eigenbau','B.MÜLLER','5'), + (822066026,'TEF GLEITLEISTENFÜHRUNG ALU-PROFIL',NULL,'K','M','J','J','6','20','10','A',NULL,NULL,'980914',NULL,'EN AW-6060-T66 (3.3206-T66)',NULL,'FALSCH','N','HZS','SN','0','Eigenbau','B.MÜLLER','5'), + (822066089,'GLEITLEISTENPROFIL F. BÖGEN NUTBREITE 11,5 MM',NULL,'K','M','J','J','6','20','10','A',NULL,NULL,'170306',NULL,'Murtfeldt "S" 1000','Masterbatch (Grundfarbe)','FALSCH','J','HZS','EX','0','Eigenbau','S.STEUER','5'), + (822066207,'LEISTE FÜR SCHIEBER',NULL,'K','Stck','N','J','6','20','10','A',NULL,NULL,'30225',NULL,'1.0038 (S235JR)',NULL,'FALSCH','N','SRC','BI','0','Eigenbau','B.MÜLLER','5'), + (827076001,'RUTSCHSTANGENPROFIL "S" GRÜN REGENERAT',NULL,'K','M','J','J','6','10','10','A',NULL,NULL,'920715',NULL,'Murtfeldt Original "S" grün','Masterbatch (Grundfarbe)','FALSCH','J','HZS','FR','0','Eigenbau','B.MÜLLER','5'), + (829416031,'STAUSTRECKE SP 73X100 - ELOXIERT',NULL,'K','M','J','J','6','10','20','A',NULL,NULL,'60327',NULL,'EN AW-6063-T66','eloxieren','FALSCH','N','HZS','SN','0','Eigenbau','B.MÜLLER','5'), + (829416035,'ES-EINWEISER RECHTS',NULL,'K','Stck','N','J',NULL,'10','20',NULL,NULL,NULL,'60303',NULL,'POM',NULL,'FALSCH','N','SRC',NULL,NULL,NULL,NULL,'5'), + (829516001,'LAUFPROFIL LP 2.0 - 87X32',NULL,'K','M','J','J','3003','10','20','A',NULL,NULL,'110426',NULL,'EN AW-6060-T66 (3.3206-T66)','eloxieren','FALSCH','N','HZS','SN','0','Eigenbau','B.MÜLLER','5'), + (829516004,'STAUSTRECKE SP 2.0 73X100 - ELOXIERT',NULL,'K','M','J','J','3004','10','20','A',NULL,NULL,'151130',NULL,'EN AW-6063-T66','eloxieren','FALSCH','N','HZS','SN','0','Eigenbau','B.MÜLLER','5'), + (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); +COMMIT; diff --git a/lib/verschiebelogik.py b/lib/verschiebelogik.py new file mode 100644 index 0000000..e0f78fb --- /dev/null +++ b/lib/verschiebelogik.py @@ -0,0 +1,509 @@ +import argparse +import csv +import os +import sqlite3 +from contextlib import closing + +def main(asm_partnum, sql_filedir, csv_filedir): + + build_sivas_from_file(sql_filedir) + csv_data = read_csv(csv_filedir, asm_partnum) + csv_to_db(csv_data) + create_new_bom(asm_partnum) + # print_new_bom(asm_partnum) + write_bom_as_csv(asm_partnum) + +def csv_to_db(csv_data: list[dict[str, str]]) -> None: + """Schreibt neue Stücklistendaten aus CSV in temporäre Datenbanktabelle.""" + + con.execute("""CREATE TABLE RD_COMP (ParentCode TEXT NOT NULL, + Positionsnummer INTEGER NOT NULL, + ChildCode TEXT NOT NULL, + Menge INTEGER NOT NULL, + Uebergabelaenge REAL NOT NULL, + PRIMARY KEY(Positionsnummer)) + """) + + for line in csv_data: + con.execute(""" INSERT INTO RD_COMP (ParentCode, Positionsnummer, ChildCode, Menge, Uebergabelaenge) + VALUES (:ParentCode, :Positionsnummer, :ChildCode, :Menge, :Uebergabelaenge)""", line) + +def dict_factory(cursor, row): + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + +def read_csv(filedir: str, partnumber: str) -> list[dict[str, str]]: + csv_path = os.path.join(filedir, f"BOM_{partnumber}.csv") + if not csv_path: + raise FileNotFoundError + + with open(csv_path, "r", encoding="utf-8", newline="") as csv_file: + reader = csv.DictReader(csv_file, delimiter=";") + csv_data = [] + for line in reader: + line["Positionsnummer"] += "0" # Angleichung Positionsnummer an SIVAS-Format (Pos. * 10) + csv_data.append(line) + + return csv_data + +def build_sivas_from_file(sql_filedir: str, sql_filename="sivas_replika.sql") -> None: + filepath = os.path.join(sql_filedir, sql_filename) + + if not filepath: + raise FileNotFoundError + + with open(filepath, "r", encoding="utf-8", newline="") as sql_file: + sql = sql_file.read() + + con.executescript(sql) + +def get_new_bom_data(parent_partnum:str) -> list[dict[str, str]]: + """ Positionsnummern und zugehörige Teilenummern aus neuer Stückliste holen""" + res = con.execute("SELECT * FROM RD_COMP WHERE ParentCode = :parent ORDER BY Positionsnummer", {"parent": parent_partnum}).fetchall() + return res + +def get_old_bom_ids(parent_partnum: str) -> list | None: + """Gibt ID's der Datensätze aus der alten Stückliste zurück""" + res = con.execute("SELECT ID FROM COMP WHERE PARENT = :parent", {"parent": parent_partnum}).fetchall() + return res + +def get_positions_to_move(parent_partnum: str) -> set | None: + """ + Gibt alle Positionsnummern zurück, die gemäß alter Logik verschoben werden + """ + res = con.execute(""" + SELECT RD.Positionsnummer AS POS + + FROM RD_COMP RD + INNER JOIN COMP C + ON RD.ParentCode = C.PARENT + AND RD.ChildCode = C.KOMPTEILENR + AND RD.Uebergabelaenge = COALESCE(C.LAENGE,0) + + WHERE RD.ParentCode = :parent + """, + {"parent": parent_partnum}).fetchall() + if res is not None: + res = set([line["POS"] for line in res]) + return res + +def identical_occurences_in_both_boms(parent_partnum: str, child_id: str) -> bool: + """ + Prüfung Fall 1.1: + """ + res = con.execute(""" + SELECT CASE + -- Prüfung 1: Liegen alle Teilenr.+Länge-Kombinationen aus der neuen Stückliste auch in der alten Stückliste vor? + WHEN NOT EXISTS ( + SELECT 1 + FROM RD_COMP + LEFT JOIN COMP + ON RD_COMP.ParentCode = COMP.PARENT + AND RD_COMP.ChildCode = COMP.KOMPTEILENR + AND RD_COMP.Uebergabelaenge = COALESCE(COMP.LAENGE, 0) + + WHERE COMP.KOMPTEILENR IS NULL + AND RD_COMP.ChildCode = :child + AND RD_COMP.ParentCode = :parent + ) + -- Prüfung 2: Liegen alle Teilenr.+Länge-Kombinationen aus der alten Stückliste auch in der neuen Stückliste vor? + AND NOT EXISTS ( + SELECT 1 + FROM COMP + LEFT JOIN RD_COMP + ON COMP.PARENT = RD_COMP.ParentCode + AND COMP.KOMPTEILENR = RD_COMP.ChildCode + AND COALESCE(COMP.LAENGE, 0) = RD_COMP.Uebergabelaenge + + WHERE RD_COMP.ChildCode IS NULL + AND COMP.KOMPTEILENR = :child + AND COMP.PARENT = :parent + ) + -- Wenn Prüfung 1 und 2 zutreffen, dann liegen alle Teilenr.+Länge-Kombinationen in beiden Stücklisten vor (Fall 1.1 trifft zu) + THEN 'True' + ELSE 'False' + END AS 'Fall 1.1' + """, + {"parent": parent_partnum, "child": child_id}).fetchone() + + return True if res["Fall 1.1"] == 'True' else False + +def occurences_at_same_pos_in_both_boms(parent_partnum: str, child_id: str) -> bool: + """ + Prüfung Fall 1.2 + """ + res = con.execute(""" + SELECT CASE + WHEN NOT EXISTS ( + -- Prüfung, ob in der neuen Stückliste an denselben Positionen wie in der alten Stückliste Längenvarianten zur angegebenen Teilenr. vorliegen + SELECT 1 + FROM COMP + LEFT JOIN RD_COMP + ON COMP.PARENT = RD_COMP.ParentCode + AND COMP.KOMPTEILENR = RD_COMP.ChildCode + AND COMP.POSNR = RD_COMP.Positionsnummer + + WHERE RD_COMP.ChildCode IS NULL + AND COMP.PARENT = :parent + AND COMP.KOMPTEILENR = :child + ) + AND NOT EXISTS ( + -- Prüfung, ob in der alten Stückliste an denselben Positionen wie in der neuen Stückliste Längenvarianten zur angegebenen Teilenr. vorliegen + SELECT 1 + FROM RD_COMP LEFT JOIN COMP + ON RD_COMP.ParentCode = COMP.PARENT + AND RD_COMP.ChildCode = COMP.KOMPTEILENR + AND RD_COMP.Positionsnummer = COMP.POSNR + + WHERE COMP.KOMPTEILENR IS NULL + AND RD_COMP.ParentCode = :parent + AND RD_COMP.ChildCode = :child + ) + -- Wenn beide Fallprüfungen zutreffend liegt die längenabhängige Teilenummer in beiden Stücklisten gleich oft vor und sie liegt in beiden + -- Stücklisten an den gleichen Positionsnummern + THEN 'True' + ELSE 'False' + END AS 'Fall 1.2' + """, + {"parent": parent_partnum, "child": child_id}).fetchone() + return True if res["Fall 1.2"] == 'True' else False + +def get_data_for_new_pos(parent_partnum: str, pos: str) -> list[dict[str, str]]: + """ + Fall 1.3 (Neuanlage von Positionen) + """ + res = con.execute(""" + SELECT RD.Positionsnummer AS POSNR, + RD.ChildCode AS KOMPTEILENR, + RD.ParentCode AS PARENT, + CASE -- Abw. Anz. = NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Menge' aus CSV + WHEN RD.Uebergabelaenge = 0 THEN NULL + ELSE RD.Menge + END AS ABW_ANZAHL, + CASE -- Mengenwert berechnen, wenn Pos. längenabhängig, sonst Wert 'Menge' aus CSV + WHEN RD.Uebergabelaenge = 0 THEN RD.Menge + ELSE RD.Menge * (RD.Uebergabelaenge + COALESCE(C.ZUSCHNITT,0)) / 1000 + END AS MENGE, + CASE -- LAENGE = NULL, wenn Pos. nicht längenabhängig, sonst Wert 'Uebergabelaenge' aus CSV + 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 + + -- Da die Positionen neu angelegt werden, werden die Defaultwerte verwendet, die zum Teil aus dem Teilestamm (PARTS) kommen. + LEFT JOIN PARTS P + ON RD.ChildCode = P.TEILENR + + WHERE C.KOMPTEILENR IS NULL + AND RD.ParentCode = :parent + AND RD.Positionsnummer = :posnr + """, + {"parent": parent_partnum, "posnr": pos}).fetchone() + return res + +def get_update_data_for_diff_pos(parent_partnum: str, pos: str) -> list[dict[str, str]]: + """ + Ermittelt die Daten, die an die neue Position verschoben werden sollen. + (Für Fall 1.1 und Fall 1.3) + """ + res = con.execute(""" + SELECT C.ID, + C.DISPO_AKTIV_JN, + C.ZUSCHNITT, + C.RUESTCODE, + RD.Positionsnummer AS POSNR, + B.BEDINGUNG, + R.REGEL, + + -- Bestimmt Wert '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 '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 + + FROM RD_COMP RD + INNER JOIN COMP C + ON RD.ParentCode = C.PARENT + AND RD.ChildCode = C.KOMPTEILENR + AND RD.Uebergabelaenge = COALESCE(C.LAENGE, 0) + + LEFT JOIN VAR_TEIL_BEDINGUNG_2 B + ON C.ID = B.COMP_ID + + LEFT JOIN STKL_REGEL R + ON C.ID = R.COMP_ID + + WHERE RD.ParentCode = :parent + AND RD.Positionsnummer = :posnr + """, + {"parent": parent_partnum, "posnr": pos}).fetchone() + return res + +def get_update_data_for_same_pos(parent_partnum: str, pos: str) -> list[dict[str, str]]: + """ + Ermittelt die Daten, die an dieselbe Position verschoben werden sollen + (Für Fall 1.2) + """ + res = con.execute(""" + SELECT C.ID, + C.ZUSCHNITT, + C.DISPO_AKTIV_JN, + C.RUESTCODE, + RD.Positionsnummer AS POSNR, + B.BEDINGUNG, + R.REGEL, + + -- 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 + + FROM RD_COMP RD + INNER JOIN COMP C + ON RD.ParentCode = C.PARENT + AND RD.ChildCode = C.KOMPTEILENR + AND RD.Positionsnummer = C.POSNR + + LEFT JOIN VAR_TEIL_BEDINGUNG_2 B + ON C.ID = B.COMP_ID + + LEFT JOIN STKL_REGEL R + ON C.ID = R.COMP_ID + + WHERE RD.ParentCode = :parent + AND RD.Positionsnummer = :posnr + """, + {"parent": parent_partnum, "posnr": pos}).fetchone() + return res + +def create_new_bom(parent_partnum:str) -> None: + + new_bom_data = get_new_bom_data(parent_partnum) + + # Ermittlung, welche Teilenummern unter Fall 1.1 , 1.2 oder 1.3 fallen + # Nicht im folgenden Loop, um mehrfache Fallprüfung 1.1 und 1.2 zu vermeiden (wenn längenabhängige Teilenummern mehrfach in Stückliste vorliegen) + partnum_cases = {} + for result_line in new_bom_data: + child_partnum = result_line["ChildCode"] + if child_partnum not in partnum_cases: + if identical_occurences_in_both_boms(parent_partnum, child_partnum): + partnum_cases[child_partnum] = 1.1 + elif occurences_at_same_pos_in_both_boms(parent_partnum, child_partnum): + partnum_cases[child_partnum] = 1.2 + else: + partnum_cases[child_partnum] = 1.3 + + # Ermittlung unter welchen Fall der Logik, die jeweilige Positionsnummer der neuen Stückliste fällt + pos_to_update = [] # Sammelt Positionsnummern (inkl. Daten), die upgedated werden sollen + pos_to_insert = [] # Sammelt Positionsnummern (inkl. Daten), die eingefügt werden sollen + pos_to_del = [] # Sammelt Positionsnummern, die gelöscht werden sollen + moved_partnums = [] # Sammelt Positionsnummern, die verschoben werden. Dient später zur Ermittlung der zu löschenden Positionen + + for pos_data in new_bom_data: + child_partnum = pos_data["ChildCode"] + child_pos = pos_data["Positionsnummer"] + + # Fall 1.1 (Kann nicht mit 1. Fall in Fall 1.3.1 (siehe unten) fusioniert werden, weil sonst z.B. bei Beispiel 3 fälschlicherweise diese Logik greifen würde) + if partnum_cases[child_partnum] == 1.1: + data = get_update_data_for_diff_pos(parent_partnum, child_pos) + pos_to_update.append(data) + moved_partnums.append(data["ID"]) + + # Fall 1.2 + elif partnum_cases[child_partnum] == 1.2: + data = get_update_data_for_same_pos(parent_partnum, child_pos) + pos_to_update.append(data) + moved_partnums.append(data["ID"]) + + # Fall 1.3 + elif partnum_cases[child_partnum] == 1.3: + + # Fall 1.3.1 - Verschiebung von Positionen + pos_to_move = get_positions_to_move(parent_partnum) + if child_pos in pos_to_move: + data = get_update_data_for_diff_pos(parent_partnum, child_pos) + pos_to_update.append(data) + moved_partnums.append(data["ID"]) + + # Fall 1.3.2 - Neuanlage von Positionen + else: + data = get_data_for_new_pos(parent_partnum, child_pos) + pos_to_insert.append(data) + else: + raise NotImplementedError("Fall ist nicht vorgesehen (dürfte laut spezifizierter Logik nicht auftreten). Prüfung erforderlich!") + + # Positionen aus der alten Stückliste, die nicht verschoben werden müssen gelöscht werden. + # Positionen, die in der neuen Stückliste neu eingefügt werden wirken sich nicht auf die Positionen der alten Stückliste aus und bleiben + # deshalb bei der Bestimmung der zu löschenden Positionen unberücksichtigt. + old_bom_ids = get_old_bom_ids(parent_partnum) + for idx in old_bom_ids: + if idx["ID"] not in moved_partnums: + pos_to_del.append(idx) + + # Datenbankaktionen durchführen, um die neue Stückliste zu schreiben. + if pos_to_del: + delete_pos(pos_to_del) + if pos_to_insert: + insert_pos(pos_to_insert) + if pos_to_update: + update_pos(pos_to_update) + +def insert_pos(new_data: dict[str, str]) -> None: + """ + Fügt die Positionen in die Stücklist ein, bei denen keine Verschiebung von der alten Stückliste erfolgt ist. + Diese Positionen werden stets ohne TOS-Regeln oder -Bedingungen angelegt. + """ + con.executemany("""INSERT INTO COMP (PARENT, POSNR, KOMPTEILENR, DISPO_AKTIV_JN, MENGE, ABW_ANZAHL, LAENGE, ZUSCHNITT, RUESTCODE) + VALUES (:PARENT, :POSNR, :KOMPTEILENR, :DISPO_AKTIV_JN, :MENGE, :ABW_ANZAHL, :LAENGE, :ZUSCHNITT, :RUESTCODE)""", + new_data) + +def delete_pos(ids: list[dict[str, str]]) -> None: + """ + - Entfernt die Positionen aus der Stückliste (COMP), die nicht verschoben werden und nicht mehr in der alten Stückliste vorliegen. + - Entfernt die TOS-Regeln aus STKL_REGEL und TOS-Bedingungen aus VAR_TEIL_BEDINGUNG_2 zu den oben genannten Positionen. + """ + con.executemany("DELETE FROM COMP WHERE ID = :ID", ids) + con.executemany("DELETE FROM VAR_TEIL_BEDINGUNG_2 WHERE COMP_ID = :ID", ids) + con.executemany("DELETE FROM STKL_REGEL WHERE COMP_ID = :ID", ids) + +def update_pos(new_data: list[dict[str, str]]) -> None: + """ + - Verschiebt Positionsdaten von einer Position in der alten Stückliste zu einer Position in der neuen Stückliste + - Aktualisiert TOS-Regeln in STKL_REGEL und + """ + con.executemany(""" + UPDATE COMP + SET POSNR = :POSNR, + ABW_ANZAHL = :ABW_ANZAHL, + MENGE = :MENGE, + LAENGE = :LAENGE, + ZUSCHNITT = :ZUSCHNITT, + DISPO_AKTIV_JN = :DISPO_AKTIV_JN, + RUESTCODE = :RUESTCODE + WHERE ID = :ID + """, new_data) + + update_bedingungen = [pos_data for pos_data in new_data if pos_data["BEDINGUNG"] is not None] + update_regeln = [pos_data for pos_data in new_data if pos_data["REGEL"] is not None] + + if update_bedingungen: + con.executemany("UPDATE VAR_TEIL_BEDINGUNG_2 SET BEDINGUNG = :BEDINGUNG WHERE COMP_ID = :ID", update_bedingungen) + if update_regeln: + con.executemany("UPDATE STKL_REGEL SET REGEL = :REGEL WHERE COMP_ID = :ID", update_regeln) + +def print_new_bom(parent_partnum: str) -> None: + res = con.execute(""" + SELECT C.POSNR AS Pos, + C.KOMPTEILENR AS 'Art.-Nr.', + C.MENGE As Menge, + C.ABW_ANZAHL AS 'Abw. Anzahl', + C.LAENGE AS Länge, + C.ZUSCHNITT AS Zuschnitt, + B.BEDINGUNG AS Bedingung, + R.REGEL AS Regel, + BSP.Beispielnr AS Beispiel + + FROM COMP C + LEFT JOIN VAR_TEIL_BEDINGUNG_2 B + ON C.ID = B.COMP_ID + + LEFT JOIN STKL_REGEL R + ON C.ID = R.COMP_ID + + LEFT JOIN Beispiele BSP + ON C.PARENT = BSP.Parent_ID + + WHERE C.PARENT = :parent + ORDER BY POSNR + """, + {"parent": parent_partnum}).fetchall() + + for line in res: + print(line) + +def write_bom_as_csv(parent_partnum: str) -> None: + out_path = f".data/BOM_{parent_partnum}_new.csv" + + res: list[dict[str, str]] + res = con.execute(""" + SELECT C.POSNR AS Pos, + C.KOMPTEILENR AS 'Art.-Nr.', + C.MENGE As Menge, + C.ABW_ANZAHL AS 'Abw. Anzahl', + C.LAENGE AS Länge, + C.ZUSCHNITT AS Zuschnitt, + B.BEDINGUNG AS Bedingung, + R.REGEL AS Regel, + BSP.Beispielnr AS Beispiel + + FROM COMP C + LEFT JOIN VAR_TEIL_BEDINGUNG_2 B + ON C.ID = B.COMP_ID + + LEFT JOIN STKL_REGEL R + ON C.ID = R.COMP_ID + + LEFT JOIN Beispiele BSP + ON C.PARENT = BSP.Parent_ID + + WHERE C.PARENT = :parent + ORDER BY POSNR + """, + {"parent": args["partnumber"]}).fetchall() + + with open("test.csv", "w", encoding="utf-8", newline="") as csv_file: + writer = csv.DictWriter(csv_file, delimiter=";", fieldnames=res[0].keys()) + writer.writeheader() + writer.writerows(res) + + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-cd", "--csv_dir", help="Verzeichnis, in der CSV-Datei mit den neuen Stücklisteninformationen liegt", default=r".\data") + parser.add_argument("-sd", "--sql_dir", help="Verzeichnis, in der SQL-Datei mit den Informationen zur Teilreplizierung der SIVAS-Datenbank liegt", default=r".\data") + parser.add_argument("-n", "--partnumber", help="Teilenummer der Baugruppe", type=str, default="000000382") + args: dict = vars(parser.parse_args()) + + # Vorerst wird echte bei jedem Lauf neue db-Datei erstellt, da es mit Datenbank im RAM (:memory:) teilweise zu fehlerhaften Abfragen kam + if "sivas_replika.db" in os.listdir("."): + os.remove("./sivas_replika.db") + + with closing(sqlite3.connect("./sivas_replika.db", autocommit=True)) as con: + con.row_factory = dict_factory + main(args["partnumber"], args["sql_dir"], args["csv_dir"]) \ No newline at end of file