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")