Beispiele¶
Im Verzeichnis examples
finden sich Python Dateien, die die Verwendung von PyAPplus64 demonstrieren.
Config-Dateien¶
Viele Scripte teilen sich Einstellungen. Beispielsweise greifen fast alle Scripte irgendwie auf APplus zu und benötigen Informationen,
mit welchem APP-Server, welchem Web-Server und welcher Datenbank sie sich verbinden sollen. Solche Informationen, insbesondere die Passwörter, werden nicht in
jedem Script gespeichert, sondern nur in den Config-Dateien. Es bietet sich wohl meist an, 3 Konfigdateien zu erstellen, je eine für
das Deploy-, das Test- und das Prod-System. Ein Beispiel ist im Unterverzeichnis examples/applus-server.yaml
zu finden.
1# Einstellung für die Verbindung mit dem APP-, Web- und DB-Server.
2# Viele der Einstellungen sind im APplus Manager zu finden
3
4appserver : {
5 server : "some-server",
6 port : 2037,
7 user : "asol.projects",
8 env : "default-umgebung" # hier wirklich Umgebung, nicht Mandant verwenden
9}
10webserver : {
11 baseurl : "http://some-server/APplusProd6/"
12}
13dbserver : {
14 server : "some-server",
15 db : "APplusProd6",
16 user : "SA",
17 password : "your-db-password"
18}
Damit nicht in jedem Script immer wieder neu die Konfig-Dateien ausgewählt werden müssen, werden die Konfigs für
das Prod-, Test- und Deploy-System in examples/applus_configs.py
hinterlegt. Diese Datei wird in allen Scripten importiert,
so dass das Config-Verzeichnis und die darin enthaltenen Configs einfach zur Verfügung stehen.
1import pathlib
2
3basedir = pathlib.Path(__file__)
4configdir = basedir.joinpath("config")
5
6serverConfYamlDeploy = configdir.joinpath("applus-server-deploy.yaml")
7serverConfYamlTest = configdir.joinpath("applus-server-test.yaml")
8serverConfYamlProd = configdir.joinpath("applus-server-prod.yaml")
read_settings.py
¶
Einfaches Beispiel für Auslesen der SysConf und bestimmter Einstellungen.
1# Einfaches Script, das verschiedene Werte des Servers ausliest.
2# Dies sind SysConfig-Einstellungen, aber auch der aktuelle Mandant,
3# Systemnamen, ...
4
5import pathlib
6import PyAPplus64
7import applus_configs
8from typing import Optional, Union
9
10
11def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
12 server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
13
14 print("\n\nSysConf Lookups:")
15
16 print(" Default Auftragsart:", server.sysconf.getString("STAMM", "DEFAULTAUFTRAGSART"))
17 print(" Auftragsarten:")
18 arten = server.sysconf.getList("STAMM", "AUFTRAGSART", sep='\n')
19 if not arten:
20 arten = []
21 for a in arten:
22 print(" - " + a)
23
24 print(" Firmen-Nr. automatisch vergeben:", server.sysconf.getBoolean("STAMM", "FIRMAAUTOMATIK"))
25 print(" Anzahl Artikelstellen:", server.sysconf.getInt("STAMM", "ARTKLASSIFNRLAENGE"))
26
27 print("\n\nScriptTool:")
28
29 print(" CurrentDate:", server.scripttool.getCurrentDate())
30 print(" CurrentTime:", server.scripttool.getCurrentTime())
31 print(" CurrentDateTime:", server.scripttool.getCurrentDateTime())
32 print(" LoginName:", server.scripttool.getLoginName())
33 print(" UserName:", server.scripttool.getUserName())
34 print(" UserFullName:", server.scripttool.getUserFullName())
35 print(" SystemName:", server.scripttool.getSystemName())
36 print(" Mandant:", server.scripttool.getMandant())
37 print(" MandantName:", server.scripttool.getMandantName())
38 print(" InstallPath:", server.scripttool.getInstallPath())
39 print(" InstallPathAppServer:", server.scripttool.getInstallPathAppServer())
40 print(" InstallPathWebServer:", server.scripttool.getInstallPathWebServer())
41 print(" ServerInfo - Version:", server.scripttool.getServerInfo().find("version").text)
42
43
44if __name__ == "__main__":
45 main(applus_configs.serverConfYamlTest)
check_dokumente.py
¶
Einfaches Beispiel für lesenden und schreibenden Zugriff auf APplus Datenbank.
1import pathlib
2import PyAPplus64
3import applus_configs
4from typing import Optional
5
6
7def main(confFile: pathlib.Path, updateDB: bool, docDir: Optional[str] = None) -> None:
8 server = PyAPplus64.applus.applusFromConfigFile(confFile)
9
10 if docDir is None:
11 docDir = str(server.scripttool.getInstallPathWebServer().joinpath("DocLib"))
12
13 sql = PyAPplus64.sql_utils.SqlStatementSelect("ARTIKEL")
14 sql.addFields("ID", "ARTIKEL", "DOCUMENTS")
15 sql.where.addConditionFieldStringNotEmpty("DOCUMENTS")
16
17 for row in server.dbQueryAll(sql):
18 doc = pathlib.Path(docDir + row.DOCUMENTS)
19 if not doc.exists():
20 print("Bild '{}' für Artikel '{}' nicht gefunden".format(doc, row.ARTIKEL))
21
22 if updateDB:
23 upd = server.mkUseXMLRowUpdate("ARTIKEL", row.ID)
24 upd.addField("DOCUMENTS", None)
25 upd.update()
26
27
28if __name__ == "__main__":
29 main(applus_configs.serverConfYamlTest, False)
adhoc_report.py
¶
Sehr einfaches Beispiel zur Erstellung einer Excel-Tabelle aus einer SQL-Abfrage.
1import PyAPplus64
2import applus_configs
3import pathlib
4
5
6def main(confFile: pathlib.Path, outfile: str) -> None:
7 server = PyAPplus64.applus.applusFromConfigFile(confFile)
8
9 # Einfache SQL-Anfrage
10 sql1 = ("select Material, count(*) as Anzahl from ARTIKEL "
11 "group by MATERIAL having MATERIAL is not null "
12 "order by Anzahl desc")
13 df1 = PyAPplus64.pandas.pandasReadSql(server, sql1)
14
15 # Sql Select-Statements können auch über SqlStatementSelect zusammengebaut
16 # werden. Die ist bei vielen, komplizierten Bedingungen teilweise hilfreich.
17 sql2 = PyAPplus64.SqlStatementSelect("ARTIKEL")
18 sql2.addFields("Material", "count(*) as Anzahl")
19 sql2.addGroupBy("MATERIAL")
20 sql2.having.addConditionFieldIsNotNull("MATERIAL")
21 sql2.order = "Anzahl desc"
22 df2 = PyAPplus64.pandas.pandasReadSql(server, sql2)
23
24 # Ausgabe als Excel mit 2 Blättern
25 PyAPplus64.pandas.exportToExcel(outfile, [(df1, "Materialien"), (df2, "Materialien 2")], addTable=True)
26
27
28if __name__ == "__main__":
29 main(applus_configs.serverConfYamlTest, "myout.xlsx")
mengenabweichung.py
¶
Etwas komplizierteres Beispiel zur Erstellung einer Excel-Datei aus SQL-Abfragen.
1# Erzeugt Excel-Tabellen mit Werkstattaufträgen und Werkstattauftragspositionen mit Mengenabweichungen
2
3import datetime
4import PyAPplus64
5import applus_configs
6import pandas as pd # type: ignore
7import pathlib
8from typing import Tuple, Union, Optional
9
10
11def ladeAlleWerkstattauftragMengenabweichungen(
12 server: PyAPplus64.APplusServer,
13 cond: Union[PyAPplus64.SqlCondition, str, None] = None) -> pd.DataFrame:
14 sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAG w")
15 sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
16
17 sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION")
18 sql.addFields("(w.MENGE-w.MENGE_IST) as MENGENABWEICHUNG")
19 sql.addFieldsTable("w", "MENGE", "MENGE_IST",
20 "APLAN as ARTIKEL", "NAME as ARTIKELNAME")
21 sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
22
23 sql.where.addConditionFieldGe("w.STATUS", 5)
24 sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
25 sql.where.addCondition(cond)
26 sql.order = "w.UPDDATE"
27 dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql)
28
29 # Add Links
30 df = dfOrg.copy()
31 df = df.drop(columns=["ID"])
32 # df = df[['POSITION', 'BAUFTRAG', 'MENGE']] # reorder / filter columns
33
34 df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
35 dfOrg,
36 lambda r: r.POSITION,
37 lambda r: server.makeWebLinkWauftrag(
38 bauftrag=r.BAUFTRAG, accessid=r.ID))
39 df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
40 dfOrg,
41 lambda r: r.BAUFTRAG,
42 lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
43
44 colNames = {
45 "BAUFTRAG": "Betriebsauftrag",
46 "POSITION": "Pos",
47 "MENGENABWEICHUNG": "Mengenabweichung",
48 "MENGE": "Menge",
49 "MENGE_IST": "Menge-Ist",
50 "ARTIKEL": "Artikel",
51 "ARTIKELNAME": "Artikel-Name",
52 "UPDDATE": "geändert am",
53 "UPDNAME": "geändert von"
54 }
55 df.rename(columns=colNames, inplace=True)
56
57 return df
58
59
60def ladeAlleWerkstattauftragPosMengenabweichungen(
61 server: PyAPplus64.APplusServer,
62 cond: Union[PyAPplus64.SqlCondition, str, None] = None) -> pd.DataFrame:
63 sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAGPOS w")
64 sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
65
66 sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION", "AG")
67 sql.addFields("(w.MENGE-w.MENGE_IST) as MENGENABWEICHUNG")
68 sql.addFieldsTable("w", "MENGE", "MENGE_IST", "APLAN as ARTIKEL")
69 sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
70
71 sql.where.addConditionFieldEq("w.STATUS", 4)
72 sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
73 sql.where.addCondition(cond)
74 sql.order = "w.UPDDATE"
75
76 dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql)
77
78 # Add Links
79 df = dfOrg.copy()
80 df = df.drop(columns=["ID"])
81 df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
82 dfOrg,
83 lambda r: r.POSITION,
84 lambda r: server.makeWebLinkWauftrag(
85 bauftrag=r.BAUFTRAG, accessid=r.ID))
86 df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
87 dfOrg,
88 lambda r: r.BAUFTRAG,
89 lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
90 df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
91 dfOrg,
92 lambda r: r.AG,
93 lambda r: server.makeWebLinkWauftragPos(
94 bauftrag=r.BAUFTRAG, position=r.POSITION, accessid=r.ID))
95
96 # Demo zum Hinzufügen einer berechneten Spalte
97 # df['BAUFPOSAG'] = PyAPplus64.pandas.mkDataframeColumn(dfOrg,
98 # lambda r: "{}.{} AG {}".format(r.BAUFTRAG, r.POSITION, r.AG))
99
100 # Rename Columns
101 colNames = {
102 "BAUFTRAG": "Betriebsauftrag",
103 "POSITION": "Pos",
104 "AG": "AG",
105 "MENGENABWEICHUNG": "Mengenabweichung",
106 "MENGE": "Menge",
107 "MENGE_IST": "Menge-Ist",
108 "ARTIKEL": "Artikel",
109 "UPDDATE": "geändert am",
110 "UPDNAME": "geändert von"
111 }
112 df.rename(columns=colNames, inplace=True)
113 return df
114
115
116def computeInYearMonthCond(field: str, year: Optional[int] = None,
117 month: Optional[int] = None) -> Optional[PyAPplus64.SqlCondition]:
118 if not (year is None):
119 if month is None:
120 return PyAPplus64.sql_utils.SqlConditionDateTimeFieldInYear(field, year)
121 else:
122 return PyAPplus64.sql_utils.SqlConditionDateTimeFieldInMonth(field, year, month)
123 else:
124 return None
125
126
127def computeFileName(year: Optional[int] = None, month: Optional[int] = None) -> str:
128 if year is None:
129 return 'mengenabweichungen-all.xlsx'
130 else:
131 if month is None:
132 return 'mengenabweichungen-{:04d}.xlsx'.format(year)
133 else:
134 return 'mengenabweichungen-{:04d}-{:02d}.xlsx'.format(year, month)
135
136
137def _exportInternal(server: PyAPplus64.APplusServer, fn: str,
138 cond: Union[PyAPplus64.SqlCondition, str, None]) -> int:
139 df1 = ladeAlleWerkstattauftragMengenabweichungen(server, cond)
140 df2 = ladeAlleWerkstattauftragPosMengenabweichungen(server, cond)
141 print("erzeuge " + fn)
142 PyAPplus64.pandas.exportToExcel(fn, [(df1, "WAuftrag"), (df2, "WAuftrag-Pos")], addTable=True)
143 return len(df1.index) + len(df2.index)
144
145
146def exportVonBis(server: PyAPplus64.APplusServer, fn: str,
147 von: Optional[datetime.datetime], bis: Optional[datetime.datetime]) -> int:
148 cond = PyAPplus64.sql_utils.SqlConditionDateTimeFieldInRange("w.UPDDATE", von, bis)
149 return _exportInternal(server, fn, cond)
150
151
152def exportYearMonth(server: PyAPplus64.APplusServer,
153 year: Optional[int] = None, month: Optional[int] = None) -> int:
154 cond = computeInYearMonthCond("w.UPDDATE", year=year, month=month)
155 fn = computeFileName(year=year, month=month)
156 return _exportInternal(server, fn, cond)
157
158
159def computePreviousMonthYear(cyear: int, cmonth: int) -> Tuple[int, int]:
160 if cmonth == 1:
161 return (cyear-1, 12)
162 else:
163 return (cyear, cmonth-1)
164
165
166def computeNextMonthYear(cyear: int, cmonth: int) -> Tuple[int, int]:
167 if cmonth == 12:
168 return (cyear+1, 1)
169 else:
170 return (cyear, cmonth+1)
171
172
173def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
174 server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
175
176 now = datetime.date.today()
177 (cmonth, cyear) = (now.month, now.year)
178 (pyear, pmonth) = computePreviousMonthYear(cyear, cmonth)
179
180 # Ausgaben
181 exportYearMonth(server, cyear, cmonth) # Aktueller Monat
182 exportYearMonth(server, pyear, pmonth) # Vorheriger Monat
183 # export(cyear) # aktuelles Jahr
184 # export(cyear-1) # letztes Jahr
185 # export() # alles
186
187
188if __name__ == "__main__":
189 main(applus_configs.serverConfYamlTest)
mengenabweichung_gui.py
¶
Beispiel für eine sehr einfache GUI, die die Eingabe einfacher Parameter erlaubt. Die GUI wird um die Erzeugung von Excel-Dateien mit Mengenabweichungen gebaut.
1import PySimpleGUI as sg # type: ignore
2import mengenabweichung
3import datetime
4import PyAPplus64
5import applus_configs
6import pathlib
7from typing import Tuple, Optional, Union
8
9
10def parseDate(dateS: str) -> Tuple[Optional[datetime.datetime], bool]:
11 if dateS is None or dateS == '':
12 return (None, True)
13 else:
14 try:
15 return (datetime.datetime.strptime(dateS, '%d.%m.%Y'), True)
16 except:
17 sg.popup_error("Fehler beim Parsen des Datums '{}'".format(dateS))
18 return (None, False)
19
20
21def createFile(server: PyAPplus64.APplusServer, fileS: str, vonS: str, bisS: str) -> None:
22 (von, vonOK) = parseDate(vonS)
23 if not vonOK:
24 return
25
26 (bis, bisOK) = parseDate(bisS)
27 if not bisOK:
28 return
29
30 if (fileS is None) or fileS == '':
31 sg.popup_error("Es wurde keine Ausgabedatei ausgewählt.")
32 return
33 else:
34 file = pathlib.Path(fileS)
35
36 c = mengenabweichung.exportVonBis(server, file.as_posix(), von, bis)
37 sg.popup_ok("{} Datensätze erfolgreich in Datei '{}' geschrieben.".format(c, file))
38
39
40def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
41 server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
42
43 layout = [
44 [sg.Text(('Bitte geben Sie an, für welchen Zeitraum die '
45 'Mengenabweichungen ausgegeben werden sollen:'))],
46 [sg.Text('Von (einschließlich)', size=(15, 1)), sg.InputText(key='Von'),
47 sg.CalendarButton("Kalender", close_when_date_chosen=True,
48 target="Von", format='%d.%m.%Y')],
49 [sg.Text('Bis (ausschließlich)', size=(15, 1)), sg.InputText(key='Bis'),
50 sg.CalendarButton("Kalender", close_when_date_chosen=True,
51 target="Bis", format='%d.%m.%Y')],
52 [sg.Text('Ausgabedatei', size=(15, 1)), sg.InputText(key='File'),
53 sg.FileSaveAs(button_text="wählen",
54 target="File",
55 file_types=(('Excel Files', '*.xlsx'),),
56 default_extension=".xlsx")],
57 [sg.Button("Aktueller Monat"), sg.Button("Letzter Monat"),
58 sg.Button("Aktuelles Jahr"), sg.Button("Letztes Jahr")],
59 [sg.Button("Speichern"), sg.Button("Beenden")]
60 ]
61
62 systemName = server.scripttool.getSystemName() + "/" + server.scripttool.getMandant()
63 window = sg.Window("Mengenabweichung " + systemName, layout)
64 now = datetime.date.today()
65 (cmonth, cyear) = (now.month, now.year)
66 (pyear, pmonth) = mengenabweichung.computePreviousMonthYear(cyear, cmonth)
67 (nyear, nmonth) = mengenabweichung.computeNextMonthYear(cyear, cmonth)
68
69 while True:
70 event, values = window.read()
71 if event == sg.WIN_CLOSED or event == 'Beenden':
72 break
73 if event == 'Aktueller Monat':
74 window['Von'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear))
75 window['Bis'].update(value="01.{:02d}.{:04d}".format(nmonth, nyear))
76 if event == 'Letzter Monat':
77 window['Von'].update(value="01.{:02d}.{:04d}".format(pmonth, pyear))
78 window['Bis'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear))
79 if event == 'Aktuelles Jahr':
80 window['Von'].update(value="01.01.{:04d}".format(cyear))
81 window['Bis'].update(value="01.01.{:04d}".format(cyear+1))
82 if event == 'Letztes Jahr':
83 window['Von'].update(value="01.01.{:04d}".format(cyear-1))
84 window['Bis'].update(value="01.01.{:04d}".format(cyear))
85 if event == 'Speichern':
86 try:
87 createFile(server, values.get('File', None),
88 values.get('Von', None), values.get('Bis', None))
89 except Exception as e:
90 sg.popup_error_with_traceback("Beim Erzeugen der Excel-Datei trat ein Fehler auf:", e)
91
92 window.close()
93
94
95if __name__ == "__main__":
96 main(applus_configs.serverConfYamlProd)
copy_artikel.py
¶
Beispiel, wie Artikel inklusive Arbeitsplan und Stückliste dupliziert werden kann.
1import pathlib
2import PyAPplus64
3import applus_configs
4import logging
5import yaml
6from typing import Optional
7
8
9def main(confFile: pathlib.Path, artikel: str, artikelNeu: Optional[str] = None) -> None:
10 # Server verbinden
11 server = PyAPplus64.applus.applusFromConfigFile(confFile)
12
13 # DuplicateBusinessObject für Artikel erstellen
14 dArt = PyAPplus64.duplicate.loadDBDuplicateArtikel(server, artikel)
15
16 # DuplicateBusinessObject zur Demonstration in YAML konvertieren und zurück
17 dArtYaml = yaml.dump(dArt)
18 print(dArtYaml)
19 dArt2 = yaml.load(dArtYaml, Loader=yaml.UnsafeLoader)
20
21 # Neue Artikel-Nummer bestimmen und DuplicateBusinessObject in DB schreiben
22 # Man könnte hier genauso gut einen anderen Server verwenden
23 if (artikelNeu is None):
24 artikelNeu = server.nextNumber("Artikel")
25
26 if not (dArt is None):
27 dArt.setFields({"artikel": artikelNeu})
28 res = dArt.insert(server)
29 print(res)
30
31
32if __name__ == "__main__":
33 # Logger Einrichten
34 logging.basicConfig(level=logging.INFO)
35 # logger = logging.getLogger("PyAPplus64.applus_db");
36 # logger.setLevel(logging.ERROR)
37
38 main(applus_configs.serverConfYamlTest, "my-artikel", artikelNeu="my-artikel-copy")