Initial commit
This commit is contained in:
commit
f8dd0a008d
|
@ -0,0 +1,4 @@
|
|||
/dist
|
||||
/docs/build/
|
||||
/src/PyAPplus64.egg-info
|
||||
__pycache__
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
|||
include examples
|
||||
include docs/*
|
||||
exclude docs/builddocspdf.sh
|
||||
include docs/source/*
|
||||
include src/PyAPplus64/py.typed
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# PyAPplus64
|
||||
|
||||
## Beschreibung
|
||||
Das Paket `PyAPplus64` enthält eine Sammlung von Python Tools für die Interaktion mit dem ERP-System APplus 6.4.
|
||||
Es sollte auch für andere APplus Versionen nützlich sein.
|
||||
|
||||
Zielgruppe sind APplus-Administratoren und Anpassungs-Entwickler. Die Tools erlauben u.a.
|
||||
|
||||
- einfacher Zugriff auf SOAP-Schnittstelle des App-Servers
|
||||
+ damit Zugriff auf SysConfig
|
||||
+ Zugriff auf Tools `nextNumber` für Erzeugung der nächsten Nummer für ein Business-Object
|
||||
+ ...
|
||||
- Zugriff auf APplus DB per direktem DB-Zugriff und mittels SOAP
|
||||
+ automatischer Aufruf von `completeSQL`, um per App-Server SQL-Statements um z.B. Mandanten erweitern zu lassen
|
||||
+ Tools für einfache Benutzung von `useXML`, d.h. für das Einfügen, Löschen und Ändern von Datensätzen
|
||||
mit Hilfe des APP-Servers. Genau wie bei Änderungen an Datensätzen über die Web-Oberfläche und im Gegensatz
|
||||
zum direkten Zugriff über die Datenbank werden dabei evtl. zusätzliche
|
||||
Checks ausgeführt, bestimmte Felder automatisch gesetzt oder bestimmte Aktionen angestoßen.
|
||||
- das Duplizieren von Datensätzen
|
||||
+ zu kopierende Felder aus XML-Definitionen werden ausgewertet
|
||||
+ Abhängige Objekte können einfach ebenfalls mit-kopiert werden
|
||||
+ Änderungen wie beispielsweise Nummer des Objektes möglich
|
||||
+ Unterstützung für Kopieren dyn. Attribute
|
||||
+ Anlage neuer Objekte mittels APP-Server
|
||||
+ Datensätze können zwischen Systemen kopiert und auch in Dateien gespeichert werden
|
||||
+ Beispiel: Kopieren von Artikeln mit Arbeitsplan und Stückliste zwischen Deploy- und Prod-System
|
||||
- einfaches Erstellen von Excel-Reports aus SQL-Abfragen
|
||||
+ mittels Pandas und XlsxWriter
|
||||
+ einfache Wrapper um andere Libraries, spart aber Zeit
|
||||
- ...
|
||||
|
||||
In `PyAPplus64` wurden die Features (vielleicht leicht verallgemeinert)
|
||||
implementiert, die ich für konkrete Aufgabenstellungen benötigte. Ich gehe davon
|
||||
aus, dass im Laufe der Zeit weitere Features hinzukommen.
|
||||
|
||||
## Warnung
|
||||
|
||||
`PyAPplus64` erlaubt den schreibenden Zugriff auf die APplus Datenbank und beliebige
|
||||
Aufrufe von SOAP-Methoden. Unsachgemäße Nutzung kann Ihre Daten zerstören. Benutzen Sie
|
||||
`PyAPplus64` daher bitte vorsichtig.
|
||||
|
||||
## Lizenz
|
||||
|
||||
`PyAPplus64` wurde unter MIT license veröffentlicht.
|
||||
|
||||
## Links
|
||||
|
||||
- Homepage https://www.thomas-tuerk.de/de/pyapplus64
|
||||
- Doku
|
||||
+ PDF https://www.thomas-tuerk.de/assets/PyAPplus64/pyapplus64.pdf
|
||||
+ HTML https://www.thomas-tuerk.de/assets/PyAPplus64/html/index.html
|
||||
- GIT-Repository https://git.thomas-tuerk.de/thtuerk/PyAPplus64
|
||||
- PyPI https://pypi.org/project/PyAPplus64/
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
sphinx-apidoc -T -f ../src/PyAPplus64 -o source/generated
|
||||
sphinx-build -a -E -b html source build/html
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
##
|
||||
## Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
##
|
||||
## This file is part of PyAPplus64.
|
||||
##
|
||||
|
||||
sphinx-apidoc -T -f ../src/PyAPplus64 -o source/generated
|
||||
sphinx-build -a -E -b latex source build/pdf
|
||||
cd build/pdf
|
||||
pdflatex pyapplus64.tex
|
||||
pdflatex pyapplus64.tex
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
|
@ -0,0 +1,40 @@
|
|||
Abhängigkeiten
|
||||
==============
|
||||
|
||||
pyodbc
|
||||
------
|
||||
Für die Datenbankverbindung wird ``pyodbc`` (``python -m pip install pyodbc``) verwendet.
|
||||
Der passende ODBC Treiber, MS SQL Server 2012 Native Client, wird zusätzlich benötigt.
|
||||
Dieser kann von Microsoft bezogen werden.
|
||||
|
||||
|
||||
zeep
|
||||
----
|
||||
Die Soap-Library ``zeep`` wird benutzt (``python -m pip install zeep``).
|
||||
|
||||
|
||||
PyYaml
|
||||
------
|
||||
|
||||
Die Library ``pyyaml`` wird für Config-Dateien benutzt (``python -m pip install pyyaml``).
|
||||
|
||||
|
||||
Sphinx
|
||||
------
|
||||
Diese Dokumentation ist mit Sphinx geschrieben.
|
||||
``python -m pip install sphinx``. Dokumentation ist im Unterverzeichnis
|
||||
`docs` zu finden. Sie kann mittels ``make.bat html`` erzeugt werden,
|
||||
dies ruft intern ``sphinx-build -M html source build`` auf. Die Dokumentation
|
||||
der Python-API sollte evtl. vorher
|
||||
mittels ``sphinx-apidoc -T -f ../src/PyAPplus64 -o source/generated`` erzeugt
|
||||
oder aktualisiert werden. Evtl. können 2 Aufrufe von ``make.bat html`` sinnvoll
|
||||
sein, falls sich die Struktur der Dokumentation ändert.
|
||||
Diese Aufrufe werden von ``builddocs.sh`` automatisiert.
|
||||
|
||||
Die erzeugte Doku findet sich im Verzeichnis ``build/html``.
|
||||
|
||||
|
||||
Pandas / SqlAlchemy / xlsxwriter
|
||||
--------------------------------
|
||||
Sollen Excel-Dateien mit Pandas erzeugt, werden, so muss Pandas, SqlAlchemy und xlsxwriter installiert sein
|
||||
(`python -m pip install pandas sqlalchemy xlsxwriter`).
|
|
@ -0,0 +1,92 @@
|
|||
typische Anwendungsfälle
|
||||
========================
|
||||
|
||||
einfache Admin-Aufgaben
|
||||
-----------------------
|
||||
|
||||
Selten auftretende Admin-Aufgaben lassen sich gut mittels Python-Scripten automatisieren.
|
||||
Es ist sehr einfach möglich, auf die DB, aber auch auf SOAP-Schnittstelle zuzugreifen.
|
||||
Ich habe dies vor allem für Wartungsarbeiten an Anpassungstabellen, die für eigene Erweiterungen
|
||||
entwickelt wurden, genutzt.
|
||||
|
||||
Als triviales Beispiel sucht folgender Code alle `DOCUMENTS` Einträge in
|
||||
Artikeln (angezeigt als `Bild` in `ArtikelRec.aspx`), für die Datei, auf die
|
||||
verwiesen wird, nicht im Dateisystem existiert. Diese fehlenden Dateien werden
|
||||
ausgegeben und das Feld `DOCUMENTS` gelöscht. Das Löschen erfolgt dabei über
|
||||
`useXML`, so dass die Felder `UPDDATE` und `UPDUSER` korrekt gesetzt werden.
|
||||
|
||||
.. literalinclude:: ../../examples/check_dokumente.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
Man kann alle Python Bibliotheken nutzen. Als Erweiterung wäre es in obigem Script
|
||||
zum Beispiel einfach möglich, alle BMP-Bilder zu suchen, nach PNG zu konvertieren
|
||||
und den DB-Eintrag anzupassen.
|
||||
|
||||
|
||||
Ad-hoc Reports
|
||||
--------------
|
||||
|
||||
APplus erlaubt es mittels Jasper-Reports, flexible und schöne Reports zu erzeugen.
|
||||
Dies funktioniert gut und ist für regelmäßig benutzte Reports sehr sinnvoll.
|
||||
|
||||
Für ad-hoc Reports, die nur sehr selten oder sogar nur einmal benutzt werden, ist die
|
||||
Erstellung eines Jasper-Reports und die Einbindung in APplus jedoch zu zeitaufwändig.
|
||||
Teilweise genügen die Ergebnisse einer SQL-Abfrage, die direkt im MS SQL Server abgesetzt
|
||||
werden kann. Wird es etwas komplizierter oder sollen die Ergebnisse noch etwas verarbeitet
|
||||
werden, bietet sich evtl. Python an.
|
||||
|
||||
Folgendes Script erzeugt zum Beispiel eine Excel-Tabelle, mit einer Übersicht,
|
||||
welche Materialen wie oft für Artikel benutzt werden:
|
||||
|
||||
.. literalinclude:: ../../examples/adhoc_report.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
Dieses kurze Script nutzt Standard-Pandas Methoden zur Erzeugung der Excel-Datei. Allerdings
|
||||
sind diese Methoden in den Aufrufen von `pandasReadSql` und `exportToExcel` gekapselt,
|
||||
so dass der Aufruf sehr einfach ist. Zudem ist es sehr einfach, die Verbindung zur Datenbank
|
||||
und zum APP-Server mittels der YAML-Konfigdatei herzustellen. Bei diesem
|
||||
Aufruf kann optional ein Nutzer und eine Umgebung übergeben werden, die die Standard-Werte aus
|
||||
der YAML-Datei überschreiben. `pandasReadSql` nutzt intern `completeSQL`, so dass
|
||||
der für die Umgebung korrekte Mandant automatisch verwendet wird.
|
||||
|
||||
|
||||
|
||||
Anbindung eigener Tools
|
||||
-----------------------
|
||||
|
||||
Ursprünglich wurde `PyAPplus64` für die Anbindung einer APplus-Anpassung geschrieben. Dieses ist
|
||||
als Windows-Service auf einem eigenen Rechner installiert und überwacht dort ein bestimmtes Verzeichnis.
|
||||
Bei Änderungen an Dateien in diesem Verzeichnis (Hinzufügen, Ändern, Löschen) werden die Dateien verarbeitet
|
||||
und die Ergebnisse an APplus gemeldet. Dafür werden DB-Operationen aber auch SOAP-Calls benutzt.
|
||||
Ebenso wird auf die SysConf zugegriffen.
|
||||
|
||||
Viele solcher Anpassungen lassen sich gut und sinnvoll im App-Server einrichten und als Job regelmäßig aufrufen.
|
||||
Im konkreten Fall wird jedoch für die Verarbeitung der Dateien viel Rechenzeit benötigt. Dies würde dadurch
|
||||
verschlimmert, dass die Dateien nicht auf der gleichen Maschine wie der App-Server liegen und somit
|
||||
viele, relativ langsame Dateizugriffe über das Netzwerk nötig wären. Hinzu kommt, dass die
|
||||
Verarbeitung der Dateien dank existierender Bibliotheken in Python einfacher ist.
|
||||
|
||||
`PyAPplus64` kann für solche Anpassungen eine interessante Alternative zur Implementierung im App-Server
|
||||
oder zur Entwicklung eines Tools ohne spezielle Bibliotheken sein. Nach Initialisierung des Servers::
|
||||
|
||||
import PyAPplus64
|
||||
|
||||
server = PyAPplus64.applus.applusFromConfigFile("my-applus-server.yaml")
|
||||
|
||||
bietet `P2APplus64` wie oben demonstriert einfachen lesenden und schreibenden Zugriff auf die DB. Hierbei
|
||||
werden automatisch die für die Umgebung nötigen Mandanten zu den SQL Statements hinzugefügt. Zudem ist einfach ein
|
||||
Zugriff auf die Sysconf möglich::
|
||||
|
||||
print (server.sysconf.getString("STAMM", "MYLAND"))
|
||||
print (server.sysconf.getList("STAMM", "EULAENDER"))
|
||||
|
||||
Dank der Bibliothek `zeep` ist es auch sehr einfach möglich, auf beliebige SOAP-Methoden zuzugreifen.
|
||||
Beispielsweise kann auf die Sys-Config auch händisch, d.h. durch direkten Aufruf einer SOAP-Methode,
|
||||
zugegriffen werden::
|
||||
|
||||
client = server.server_conn.getClient("p2system", "SysConf");
|
||||
print (client.service.getString("STAMM", "MYLAND"))
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
Beschreibung
|
||||
============
|
||||
|
||||
Das Paket `PyAPplus64` enthält eine Sammlung von Python Tools für die Interaktion mit dem ERP-System APplus 6.4.
|
||||
Es sollte auch für andere APplus Versionen nützlich sein.
|
||||
|
||||
Zielgruppe sind APplus-Administratoren und Anpassungs-Entwickler. `PyAPplus64` erlaubt u.a.
|
||||
|
||||
- einfacher Zugriff auf SOAP-Schnittstelle des APP-Servers
|
||||
+ damit Zugriff auf SysConfig
|
||||
+ Zugriff auf Tools wie z.B. `nextNumber` für Erzeugung der nächsten Nummer für ein Business-Object
|
||||
+ ...
|
||||
- Zugriff auf APplus DB per direktem DB-Zugriff und mittels SOAP
|
||||
+ automatischer Aufruf von `completeSQL`, um per AppServer SQL-Statements um z.B. Mandanten erweitern zu lassen
|
||||
+ Tools für einfache Benutzung von `useXML`, d.h. für das Einfügen, Löschen und Ändern von Datensätzen
|
||||
mit Hilfe des APP-Servers. Genau wie bei Änderungen an Datensätzen über die Web-Oberfläche und im Gegensatz
|
||||
zum direkten Zugriff über die Datenbank werden dabei evtl. zusätzliche
|
||||
Checks ausgeführt, bestimmte Felder automatisch gesetzt oder bestimmte Aktionen angestoßen.
|
||||
- das Duplizieren von Datensätzen
|
||||
+ zu kopierende Felder aus XML-Definitionen werden ausgewertet
|
||||
+ Abhängige Objekte können einfach ebenfalls mit-kopiert werden
|
||||
+ Änderungen wie beispielsweise Nummer des Objektes möglich
|
||||
+ Unterstützung für Kopieren dyn. Attribute
|
||||
+ Anlage neuer Objekte mittels APP-Server
|
||||
+ Datensätze können zwischen Systemen kopiert und auch in Dateien gespeichert werden
|
||||
+ Beispiel: Kopieren von Artikeln mit Arbeitsplan und Stückliste zwischen Deploy- und Prod-System
|
||||
- einfaches Erstellen von Excel-Reports aus SQL-Abfragen
|
||||
+ mittels Pandas und XlsxWriter
|
||||
+ einfache Wrapper um andere Libraries, spart aber Zeit
|
||||
- ...
|
||||
|
||||
In `PyAPplus64` wurden die Features (vielleicht leicht verallgemeinert)
|
||||
implementiert, die ich für konkrete Aufgabenstellungen benötigte. Ich gehe davon
|
||||
aus, dass im Laufe der Zeit weitere Features hinzukommen.
|
||||
|
||||
Warnung
|
||||
-------
|
||||
|
||||
`PyAPplus64` erlaubt den schreibenden Zugriff auf die APplus Datenbank und beliebige
|
||||
Aufrufe von SOAP-Methoden. Unsachgemäße Nutzung kann Ihre Daten zerstören. Benutzen Sie
|
||||
`PyAPplus64` daher bitte vorsichtig. Zudem kann ich leider nicht garantieren, dass `PyAPplus64` fehlerfrei ist.
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import pathlib
|
||||
import sys
|
||||
|
||||
sys.path.append('../src/')
|
||||
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'PyAPplus64'
|
||||
copyright = '2023, Thomas Tuerk'
|
||||
author = 'Thomas Tuerk'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.duration',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.autosummary',
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = [] # type: ignore
|
||||
|
||||
language = 'de'
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'nature'
|
||||
# html_static_path = ['_static']
|
||||
|
||||
source_suffix = {
|
||||
'.rst': 'restructuredtext',
|
||||
}
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
'papersize': 'a4paper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '11pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': PREAMBLE
|
||||
}
|
||||
|
||||
autodoc_type_aliases = {
|
||||
'SqlValue': 'SqlValue'
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
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.
|
||||
|
||||
.. literalinclude:: ../../examples/applus-server.yaml
|
||||
:language: yaml
|
||||
:linenos:
|
||||
|
||||
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 wird in allen Scripten importiert,
|
||||
so dass das Config-Verzeichnis und die darin enthaltenen Configs einfach zur Verfügung stehen.
|
||||
|
||||
.. literalinclude:: ../../examples/applus_configs.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
|
||||
``check_dokumente.py``
|
||||
-----------------------
|
||||
Einfaches Beispiel für lesenden und schreibenden Zugriff auf APplus Datenbank.
|
||||
|
||||
.. literalinclude:: ../../examples/check_dokumente.py
|
||||
:language: python
|
||||
:lines: 6-
|
||||
:linenos:
|
||||
|
||||
|
||||
``adhoc_report.py``
|
||||
-------------------
|
||||
Sehr einfaches Beispiel zur Erstellung einer Excel-Tabelle aus einer SQL-Abfrage.
|
||||
|
||||
.. literalinclude:: ../../examples/adhoc_report.py
|
||||
:language: python
|
||||
:lines: 7-
|
||||
:linenos:
|
||||
|
||||
|
||||
``mengenabweichung.py``
|
||||
-----------------------
|
||||
Etwas komplizierteres Beispiel zur Erstellung einer Excel-Datei aus SQL-Abfragen.
|
||||
|
||||
.. literalinclude:: ../../examples/mengenabweichung.py
|
||||
:language: python
|
||||
:lines: 9-
|
||||
:linenos:
|
||||
|
||||
``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.
|
||||
|
||||
.. literalinclude:: ../../examples/mengenabweichung_gui.pyw
|
||||
:language: python
|
||||
:lines: 7-
|
||||
:linenos:
|
||||
|
||||
``copy_artikel.py``
|
||||
-----------------------
|
||||
Beispiel, wie Artikel inklusive Arbeitsplan und Stückliste dupliziert werden kann.
|
||||
|
||||
.. literalinclude:: ../../examples/copy_artikel.py
|
||||
:language: python
|
||||
:lines: 21-
|
||||
:linenos:
|
|
@ -0,0 +1,93 @@
|
|||
PyAPplus64 package
|
||||
==================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
PyAPplus64.applus module
|
||||
------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.applus
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.applus\_db module
|
||||
----------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.applus_db
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.applus\_scripttool module
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.applus_scripttool
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.applus\_server module
|
||||
--------------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.applus_server
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.applus\_sysconf module
|
||||
---------------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.applus_sysconf
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.applus\_usexml module
|
||||
--------------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.applus_usexml
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.duplicate module
|
||||
---------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.duplicate
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.pandas module
|
||||
------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.pandas
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.sql\_utils module
|
||||
----------------------------
|
||||
|
||||
.. automodule:: PyAPplus64.sql_utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
PyAPplus64.utils module
|
||||
-----------------------
|
||||
|
||||
.. automodule:: PyAPplus64.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: PyAPplus64
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -0,0 +1,11 @@
|
|||
PyAPplus64 Dokumentation
|
||||
########################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
beschreibung
|
||||
anwendungen
|
||||
abhaengigkeiten.rst
|
||||
examples.rst
|
||||
generated/PyAPplus64
|
|
@ -0,0 +1,36 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
import pathlib
|
||||
|
||||
def main(confFile : pathlib.Path, outfile : str) -> None:
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
# Einfache SQL-Anfrage
|
||||
sql1 = ("select Material, count(*) as Anzahl from ARTIKEL "
|
||||
"group by MATERIAL having MATERIAL is not null "
|
||||
"order by Anzahl desc")
|
||||
df1 = PyAPplus64.pandas.pandasReadSql(server, sql1)
|
||||
|
||||
# Sql Select-Statements können auch über SqlStatementSelect zusammengebaut
|
||||
# werden. Die ist bei vielen, komplizierten Bedingungen teilweise hilfreich.
|
||||
sql2 = PyAPplus64.SqlStatementSelect("ARTIKEL")
|
||||
sql2.addFields("Material", "count(*) as Anzahl")
|
||||
sql2.addGroupBy("MATERIAL")
|
||||
sql2.having.addConditionFieldIsNotNull("MATERIAL")
|
||||
sql2.order = "Anzahl desc"
|
||||
df2 = PyAPplus64.pandas.pandasReadSql(server, sql2)
|
||||
|
||||
# Ausgabe als Excel mit 2 Blättern
|
||||
PyAPplus64.pandas.exportToExcel(outfile, [(df1, "Materialien"), (df2, "Materialien 2")], addTable=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest, "myout.xlsx")
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
# Einstellung für die Verbindung mit dem APP-, Web- und DB-Server.
|
||||
# Viele der Einstellungen sind im APplus Manager zu finden
|
||||
|
||||
appserver : {
|
||||
server : "some-server",
|
||||
port : 2037,
|
||||
user : "asol.projects",
|
||||
env : "default-umgebung" # hier wirklich Umgebung, nicht Mandant verwenden
|
||||
}
|
||||
webserver : {
|
||||
baseurl : "http://some-server/APplusProd6/"
|
||||
}
|
||||
dbserver : {
|
||||
server : "some-server",
|
||||
db : "APplusProd6",
|
||||
user : "SA",
|
||||
password : "your-db-password"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
import pathlib
|
||||
|
||||
basedir = pathlib.Path(__file__)
|
||||
configdir = basedir.joinpath("config")
|
||||
|
||||
serverConfYamlDeploy = configdir.joinpath("applus-server-deploy.yaml")
|
||||
serverConfYamlTest = configdir.joinpath("applus-server-test.yaml")
|
||||
serverConfYamlProd = configdir.joinpath("applus-server-prod.yaml")
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
import pathlib
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
|
||||
def main(confFile : pathlib.Path, docDir:str, updateDB:bool) -> None:
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("ARTIKEL");
|
||||
sql.addFields("ID", "ARTIKEL", "DOCUMENTS");
|
||||
sql.where.addConditionFieldStringNotEmpty("DOCUMENTS");
|
||||
|
||||
for row in server.dbQueryAll(sql):
|
||||
doc = pathlib.Path(docDir + row.DOCUMENTS);
|
||||
if not doc.exists():
|
||||
print("Bild '{}' für Artikel '{}' nicht gefunden".format(doc, row.ARTIKEL))
|
||||
|
||||
if updateDB:
|
||||
upd = server.mkUseXMLRowUpdate("ARTIKEL", row.ID);
|
||||
upd.addField("DOCUMENTS", None);
|
||||
upd.update();
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest, "somedir\\WebServer\\DocLib", False)
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
# Dieses Script demonstriert, wie mit Hilfe PyAPplus64.duplicate
|
||||
# BusinessObjekte dupliziert werden können.
|
||||
# Dies ist sowohl in der gleichen DB als auch in anderen DBs möglich.
|
||||
# So kann z.B. ein einzelner Artikel aus Test in Prod kopiert werden.
|
||||
# Ebenso ist es möglich, die Daten in einer Datei zwischenzuspeichern und
|
||||
# später irgendwo anders einzuspielen.
|
||||
#
|
||||
# Dies ist für Administrationszwecke gedacht. Anwendungsbeispiel wäre,
|
||||
# dass ein Artikel mit langem Arbeitsplan und Stückliste im Test-System erstellt wird.
|
||||
# Viele der Positionen enthalten Nachauflöse-Scripte, die im Test-System
|
||||
# getestet werden. Diese vielen Scripte per Hand zu kopieren ist aufwändig
|
||||
# und Fehleranfällig und kann mit solchen Admin-Scripten automatisiert werden.
|
||||
|
||||
import pathlib
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
import logging
|
||||
import yaml
|
||||
|
||||
|
||||
def main(confFile:pathlib.Path, artikel:str, artikelNeu:str|None=None) -> None:
|
||||
# Server verbinden
|
||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||
|
||||
# DuplicateBusinessObject für Artikel erstellen
|
||||
dArt = PyAPplus64.duplicate.loadDBDuplicateArtikel(server, artikel);
|
||||
|
||||
# DuplicateBusinessObject zur Demonstration in YAML konvertieren und zurück
|
||||
dArtYaml = yaml.dump(dArt);
|
||||
print(dArtYaml);
|
||||
dArt2 = yaml.load(dArtYaml, Loader=yaml.UnsafeLoader)
|
||||
|
||||
# Neue Artikel-Nummer bestimmen und DuplicateBusinessObject in DB schreiben
|
||||
# Man könnte hier genauso gut einen anderen Server verwenden
|
||||
if (artikelNeu is None):
|
||||
artikelNeu = server.nextNumber("Artikel")
|
||||
|
||||
if not (dArt is None):
|
||||
dArt.setFields({"artikel" : artikelNeu})
|
||||
res = dArt.insert(server);
|
||||
print(res);
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Logger Einrichten
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
# logger = logging.getLogger("PyAPplus64.applus_db");
|
||||
# logger.setLevel(logging.ERROR)
|
||||
|
||||
main(applus_configs.serverConfYamlTest, "my-artikel", artikelNeu="my-artikel-copy")
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
# Erzeugt Excel-Tabellen mit Werkstattaufträgen und Werkstattauftragspositionen mit Mengenabweichungen
|
||||
|
||||
import datetime
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
import pandas as pd # type: ignore
|
||||
import pathlib
|
||||
from typing import *
|
||||
|
||||
def ladeAlleWerkstattauftragMengenabweichungen(
|
||||
server:PyAPplus64.APplusServer,
|
||||
cond:PyAPplus64.SqlCondition|str|None=None) -> pd.DataFrame:
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAG w");
|
||||
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
||||
|
||||
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION")
|
||||
sql.addFields("(w.MENGE-w.MENGE_IST) as MENGENABWEICHUNG")
|
||||
sql.addFieldsTable("w", "MENGE", "MENGE_IST",
|
||||
"APLAN as ARTIKEL", "NAME as ARTIKELNAME")
|
||||
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
||||
|
||||
sql.where.addConditionFieldGe("w.STATUS", 5)
|
||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||
sql.where.addCondition(cond)
|
||||
sql.order="w.UPDDATE"
|
||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql);
|
||||
|
||||
# Add Links
|
||||
df = dfOrg.copy();
|
||||
df = df.drop(columns=["ID"]);
|
||||
# df = df[['POSITION', 'BAUFTRAG', 'MENGE']] # reorder / filter columns
|
||||
|
||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
lambda r: r.POSITION,
|
||||
lambda r: server.makeWebLinkWauftrag(
|
||||
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
lambda r: r.BAUFTRAG,
|
||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||
|
||||
colNames = {
|
||||
"BAUFTRAG" : "Betriebsauftrag",
|
||||
"POSITION" : "Pos",
|
||||
"MENGENABWEICHUNG" : "Mengenabweichung",
|
||||
"MENGE" : "Menge",
|
||||
"MENGE_IST" : "Menge-Ist",
|
||||
"ARTIKEL" : "Artikel",
|
||||
"ARTIKELNAME" : "Artikel-Name",
|
||||
"UPDDATE" : "geändert am",
|
||||
"UPDNAME" : "geändert von"
|
||||
}
|
||||
df.rename(columns=colNames, inplace=True);
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def ladeAlleWerkstattauftragPosMengenabweichungen(
|
||||
server : PyAPplus64.APplusServer,
|
||||
cond:PyAPplus64.SqlCondition|str|None=None) -> pd.DataFrame:
|
||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAGPOS w");
|
||||
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
||||
|
||||
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION", "AG")
|
||||
sql.addFields("(w.MENGE-w.MENGE_IST) as MENGENABWEICHUNG")
|
||||
sql.addFieldsTable("w", "MENGE", "MENGE_IST", "APLAN as ARTIKEL")
|
||||
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
||||
|
||||
sql.where.addConditionFieldEq("w.STATUS", 4)
|
||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||
sql.where.addCondition(cond)
|
||||
sql.order="w.UPDDATE"
|
||||
|
||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql);
|
||||
|
||||
# Add Links
|
||||
df = dfOrg.copy();
|
||||
df = df.drop(columns=["ID"]);
|
||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
lambda r: r.POSITION,
|
||||
lambda r: server.makeWebLinkWauftrag(
|
||||
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
lambda r: r.BAUFTRAG,
|
||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||
df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
||||
lambda r: r.AG,
|
||||
lambda r: server.makeWebLinkWauftragPos(
|
||||
bauftrag=r.BAUFTRAG, position=r.POSITION, accessid=r.ID))
|
||||
|
||||
# Demo zum Hinzufügen einer berechneten Spalte
|
||||
# df['BAUFPOSAG'] = PyAPplus64.pandas.mkDataframeColumn(dfOrg,
|
||||
# lambda r: "{}.{} AG {}".format(r.BAUFTRAG, r.POSITION, r.AG))
|
||||
|
||||
# Rename Columns
|
||||
colNames = {
|
||||
"BAUFTRAG" : "Betriebsauftrag",
|
||||
"POSITION" : "Pos",
|
||||
"AG" : "AG",
|
||||
"MENGENABWEICHUNG" : "Mengenabweichung",
|
||||
"MENGE" : "Menge",
|
||||
"MENGE_IST" : "Menge-Ist",
|
||||
"ARTIKEL" : "Artikel",
|
||||
"UPDDATE" : "geändert am",
|
||||
"UPDNAME" : "geändert von"
|
||||
}
|
||||
df.rename(columns=colNames, inplace=True);
|
||||
return df
|
||||
|
||||
def computeInYearMonthCond(field : str, year:int|None=None,
|
||||
month:int|None=None) -> PyAPplus64.SqlCondition | None:
|
||||
if not (year is None):
|
||||
if month is None:
|
||||
return PyAPplus64.sql_utils.SqlConditionDateTimeFieldInYear(field, year)
|
||||
else:
|
||||
return PyAPplus64.sql_utils.SqlConditionDateTimeFieldInMonth(field, year, month)
|
||||
else:
|
||||
return None
|
||||
|
||||
def computeFileName(year:int|None=None, month:int|None=None) -> str:
|
||||
if year is None:
|
||||
return 'mengenabweichungen-all.xlsx';
|
||||
else:
|
||||
if month is None:
|
||||
return 'mengenabweichungen-{:04d}.xlsx'.format(year);
|
||||
else:
|
||||
return 'mengenabweichungen-{:04d}-{:02d}.xlsx'.format(year, month);
|
||||
|
||||
def _exportInternal(server: PyAPplus64.APplusServer, fn:str,
|
||||
cond:Union[PyAPplus64.SqlCondition, str, None]) -> int:
|
||||
df1 = ladeAlleWerkstattauftragMengenabweichungen(server, cond)
|
||||
df2 = ladeAlleWerkstattauftragPosMengenabweichungen(server, cond)
|
||||
print ("erzeuge " + fn);
|
||||
PyAPplus64.pandas.exportToExcel(fn, [(df1, "WAuftrag"), (df2, "WAuftrag-Pos")], addTable=True)
|
||||
return len(df1.index) + len(df2.index)
|
||||
|
||||
def exportVonBis(server: PyAPplus64.APplusServer, fn:str,
|
||||
von:datetime.datetime|None, bis:datetime.datetime|None) -> int:
|
||||
cond = PyAPplus64.sql_utils.SqlConditionDateTimeFieldInRange("w.UPDDATE", von, bis)
|
||||
return _exportInternal(server, fn, cond)
|
||||
|
||||
def exportYearMonth(server: PyAPplus64.APplusServer,
|
||||
year:int|None=None, month:int|None=None) -> int:
|
||||
cond=computeInYearMonthCond("w.UPDDATE", year=year, month=month)
|
||||
fn = computeFileName(year=year, month=month)
|
||||
return _exportInternal(server, fn, cond)
|
||||
|
||||
def computePreviousMonthYear(cyear : int, cmonth :int) -> Tuple[int, int]:
|
||||
if cmonth == 1:
|
||||
return (cyear-1, 12)
|
||||
else:
|
||||
return (cyear, cmonth-1);
|
||||
|
||||
def computeNextMonthYear(cyear : int, cmonth :int) -> Tuple[int, int]:
|
||||
if cmonth == 12:
|
||||
return (cyear+1, 1)
|
||||
else:
|
||||
return (cyear, cmonth+1);
|
||||
|
||||
def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) -> None:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
|
||||
now = datetime.date.today()
|
||||
(cmonth, cyear) = (now.month, now.year)
|
||||
(pyear, pmonth) = computePreviousMonthYear(cyear, cmonth);
|
||||
|
||||
# Ausgaben
|
||||
exportYearMonth(server, cyear, cmonth) # Aktueller Monat
|
||||
exportYearMonth(server, pyear, pmonth) # Vorheriger Monat
|
||||
# export(cyear) # aktuelles Jahr
|
||||
# export(cyear-1) # letztes Jahr
|
||||
# export() # alles
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlTest)
|
|
@ -0,0 +1,98 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
import PySimpleGUI as sg # type: ignore
|
||||
import mengenabweichung
|
||||
import datetime
|
||||
import PyAPplus64
|
||||
import applus_configs
|
||||
import pathlib
|
||||
from typing import *
|
||||
|
||||
def parseDate (dateS:str) -> Tuple[datetime.datetime|None, bool]:
|
||||
if dateS is None or dateS == '':
|
||||
return (None, True)
|
||||
else:
|
||||
try:
|
||||
return (datetime.datetime.strptime(dateS, '%d.%m.%Y'), True)
|
||||
except:
|
||||
sg.popup_error("Fehler beim Parsen des Datums '{}'".format(dateS))
|
||||
return (None, False)
|
||||
|
||||
def createFile(server:PyAPplus64.APplusServer, fileS:str, vonS:str, bisS:str)->None:
|
||||
(von, vonOK) = parseDate(vonS)
|
||||
if not vonOK: return
|
||||
|
||||
(bis, bisOK) = parseDate(bisS)
|
||||
if not bisOK: return
|
||||
|
||||
if (fileS is None) or fileS == '':
|
||||
sg.popup_error("Es wurde keine Ausgabedatei ausgewählt.")
|
||||
return
|
||||
else:
|
||||
file = pathlib.Path(fileS)
|
||||
|
||||
c = mengenabweichung.exportVonBis(server, file.as_posix(), von, bis)
|
||||
sg.popup_ok("{} Datensätze erfolgreich in Datei '{}' geschrieben.".format(c, file))
|
||||
|
||||
|
||||
def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) -> None:
|
||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||
|
||||
layout = [
|
||||
[sg.Text(('Bitte geben Sie an, für welchen Zeitraum die '
|
||||
'Mengenabweichungen ausgegeben werden sollen:'))],
|
||||
[sg.Text('Von (einschließlich)', size=(15,1)), sg.InputText(key='Von'),
|
||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||
target="Von", format='%d.%m.%Y')],
|
||||
[sg.Text('Bis (ausschließlich)', size=(15,1)), sg.InputText(key='Bis'),
|
||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||
target="Bis", format='%d.%m.%Y')],
|
||||
[sg.Text('Ausgabedatei', size=(15,1)), sg.InputText(key='File'),
|
||||
sg.FileSaveAs(button_text="wählen", target="File",
|
||||
file_types = (('Excel Files', '*.xlsx'),),
|
||||
default_extension = ".xlsx")],
|
||||
[sg.Button("Aktueller Monat"), sg.Button("Letzter Monat"),
|
||||
sg.Button("Aktuelles Jahr"), sg.Button("Letztes Jahr")],
|
||||
[sg.Button("Speichern"), sg.Button("Beenden")]
|
||||
]
|
||||
|
||||
systemName = server.scripttool.getSystemName() + "/" + server.scripttool.getMandant()
|
||||
window = sg.Window("Mengenabweichung " + systemName, layout)
|
||||
now = datetime.date.today()
|
||||
(cmonth, cyear) = (now.month, now.year)
|
||||
(pyear, pmonth) = mengenabweichung.computePreviousMonthYear(cyear, cmonth);
|
||||
(nyear, nmonth) = mengenabweichung.computeNextMonthYear(cyear, cmonth);
|
||||
|
||||
while True:
|
||||
event, values = window.read()
|
||||
if event == sg.WIN_CLOSED or event == 'Beenden':
|
||||
break
|
||||
if event == 'Aktueller Monat':
|
||||
window['Von'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear));
|
||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(nmonth, nyear));
|
||||
if event == 'Letzter Monat':
|
||||
window['Von'].update(value="01.{:02d}.{:04d}".format(pmonth, pyear));
|
||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear));
|
||||
if event == 'Aktuelles Jahr':
|
||||
window['Von'].update(value="01.01.{:04d}".format(cyear));
|
||||
window['Bis'].update(value="01.01.{:04d}".format(cyear+1));
|
||||
if event == 'Letztes Jahr':
|
||||
window['Von'].update(value="01.01.{:04d}".format(cyear-1));
|
||||
window['Bis'].update(value="01.01.{:04d}".format(cyear));
|
||||
if event == 'Speichern':
|
||||
try:
|
||||
createFile(server, values.get('File', None),
|
||||
values.get('Von', None), values.get('Bis', None))
|
||||
except Exception as e:
|
||||
sg.popup_error_with_traceback("Beim Erzeugen der Excel-Datei trat ein Fehler auf:", e);
|
||||
|
||||
window.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(applus_configs.serverConfYamlProd)
|
|
@ -0,0 +1,4 @@
|
|||
[mypy]
|
||||
|
||||
disallow_incomplete_defs = True
|
||||
disallow_untyped_defs = True
|
|
@ -0,0 +1,33 @@
|
|||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "PyAPplus64"
|
||||
version = "1.0.0"
|
||||
authors = [
|
||||
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
|
||||
]
|
||||
description = "Verschiedene Hilfsmittel, um mit dem ERP System APplus zu interagieren. Dieses Packet wurde für APplus 6.4 entwickelt, funktioniert vermutlich aber auch mit anderen Versionen."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.6"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Other Audience",
|
||||
"Operating System :: Microsoft :: Windows"
|
||||
]
|
||||
dependencies = [
|
||||
'pyodbc',
|
||||
'PyYAML',
|
||||
'SQLAlchemy',
|
||||
'pandas',
|
||||
'XlsxWriter',
|
||||
'zeep'
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://www.thomas-tuerk.de/de/pyapplus64"
|
||||
repository = "https://git.thomas-tuerk.de/thtuerk/PyAPplus64"
|
||||
documentation = "https://www.thomas-tuerk.de/assets/PyAPplus64/html/index.html"
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
from . import utils
|
||||
from . import applus_db
|
||||
from . import applus_scripttool
|
||||
from . import applus_server
|
||||
from . import applus_sysconf
|
||||
from . import applus_usexml
|
||||
from . import applus
|
||||
from . import sql_utils
|
||||
from . import duplicate
|
||||
from . import utils
|
||||
|
||||
from .applus import APplusServer, applusFromConfigFile
|
||||
from .sql_utils import (
|
||||
SqlCondition,
|
||||
SqlStatement,
|
||||
SqlStatementSelect
|
||||
)
|
||||
|
||||
try:
|
||||
from . import pandas
|
||||
except:
|
||||
pass
|
|
@ -0,0 +1,287 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from . import applus_db
|
||||
from . import applus_server
|
||||
from . import applus_sysconf
|
||||
from . import applus_scripttool
|
||||
from . import applus_usexml
|
||||
from . import sql_utils
|
||||
import yaml
|
||||
import urllib.parse
|
||||
from zeep import Client
|
||||
import pyodbc # type: ignore
|
||||
from typing import *
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import FileDescriptorOrPath
|
||||
|
||||
|
||||
|
||||
class APplusServer:
|
||||
"""
|
||||
Verbindung zu einem APplus DB und App Server mit Hilfsfunktionen für den komfortablen Zugriff.
|
||||
|
||||
:param db_settings: die Einstellungen für die Verbindung mit der Datenbank
|
||||
:type db_settings: APplusDBSettings
|
||||
:param server_settings: die Einstellungen für die Verbindung mit dem APplus App Server
|
||||
:type server_settings: APplusAppServerSettings
|
||||
:param web_settings: die Einstellungen für die Verbindung mit dem APplus Web Server
|
||||
:type web_settings: APplusWebServerSettings
|
||||
"""
|
||||
def __init__(self, db_settings : applus_db.APplusDBSettings, server_settings : applus_server.APplusAppServerSettings, web_settings : applus_server.APplusWebServerSettings):
|
||||
|
||||
self.db_settings : applus_db.APplusDBSettings = db_settings
|
||||
"""Die Einstellungen für die Datenbankverbindung"""
|
||||
|
||||
self.web_settings : applus_server.APplusWebServerSettings = web_settings
|
||||
"""Die Einstellungen für die Datenbankverbindung"""
|
||||
|
||||
self.db_conn = db_settings.connect()
|
||||
"""
|
||||
Eine pyodbc-Connection zur APplus DB. Diese muss genutzt werden, wenn mehrere Operationen in einer Transaktion
|
||||
genutzt werden sollen. Ansonsten sind die Hilfsmethoden wie :meth:`APplusServer.dbQuery` zu bevorzugen.
|
||||
Diese Connection kann in Verbindung mit den Funktionen aus :mod:`PyAPplus64.applus_db` genutzt werden.
|
||||
"""
|
||||
|
||||
|
||||
self.server_conn : applus_server.APplusServerConnection = applus_server.APplusServerConnection(server_settings);
|
||||
"""erlaubt den Zugriff auf den AppServer"""
|
||||
|
||||
self.sysconf : applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self);
|
||||
"""erlaubt den Zugriff auf die Sysconfig"""
|
||||
|
||||
self.scripttool : applus_scripttool.APplusScriptTool = applus_scripttool.APplusScriptTool(self);
|
||||
"""erlaubt den einfachen Zugriff auf Funktionen des ScriptTools"""
|
||||
|
||||
self.client_table = self.server_conn.getClient("p2core","Table");
|
||||
self.client_xml = self.server_conn.getClient("p2core","XML");
|
||||
self.client_nummer = self.server_conn.getClient("p2system", "Nummer")
|
||||
|
||||
def reconnectDB(self) -> None:
|
||||
try:
|
||||
self.db_conn.close()
|
||||
except:
|
||||
pass
|
||||
self.db_conn = self.db_settings.connect()
|
||||
|
||||
def completeSQL(self, sql : sql_utils.SqlStatement, raw:bool=False) -> str:
|
||||
"""
|
||||
Vervollständigt das SQL-Statement. Es wird z.B. der Mandant hinzugefügt.
|
||||
|
||||
:param sql: das SQL Statement
|
||||
:type sql: sql_utils.SqlStatement
|
||||
:param raw: soll completeSQL ausgeführt werden? Falls True, wird die Eingabe zurückgeliefert
|
||||
:type raw: boolean
|
||||
:return: das vervollständigte SQL-Statement
|
||||
:rtype: str
|
||||
"""
|
||||
if raw:
|
||||
return str(sql)
|
||||
else:
|
||||
return self.client_table.service.getCompleteSQL(sql);
|
||||
|
||||
def dbQueryAll(self, sql : sql_utils.SqlStatement, *args:Any, raw:bool=False,
|
||||
apply:Optional[Callable[[pyodbc.Row],Any]]=None) -> Any:
|
||||
"""Führt eine SQL Query aus und liefert alle Zeilen zurück. Das SQL wird zunächst
|
||||
vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
||||
sqlC = self.completeSQL(sql, raw=raw);
|
||||
return applus_db.rawQueryAll(self.db_conn, sqlC, *args, apply=apply)
|
||||
|
||||
def dbQuerySingleValues(self, sql : sql_utils.SqlStatement, *args:Any, raw:bool=False) -> Sequence[Any]:
|
||||
"""Führt eine SQL Query aus, die nur eine Spalte zurückliefern soll."""
|
||||
return self.dbQueryAll(sql, *args, raw=raw, apply=lambda r: r[0])
|
||||
|
||||
def dbQuery(self, sql : sql_utils.SqlStatement, f : Callable[[pyodbc.Row], None], *args : Any, raw:bool=False) -> None:
|
||||
"""Führt eine SQL Query aus und führt für jede Zeile die übergeben Funktion aus.
|
||||
Das SQL wird zunächst vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
||||
sqlC = self.completeSQL(sql, raw=raw);
|
||||
applus_db.rawQuery(self.db_conn, sqlC, f, *args)
|
||||
|
||||
def dbQuerySingleRow(self, sql:sql_utils.SqlStatement, *args:Any, raw:bool=False) -> Optional[pyodbc.Row]:
|
||||
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll. Diese Zeile wird geliefert."""
|
||||
sqlC = self.completeSQL(sql, raw=raw);
|
||||
return applus_db.rawQuerySingleRow(self.db_conn, sqlC, *args)
|
||||
|
||||
def dbQuerySingleRowDict(self, sql:sql_utils.SqlStatement, *args:Any, raw:bool=False) -> Optional[Dict[str, Any]]:
|
||||
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll.
|
||||
Diese Zeile wird als Dictionary geliefert."""
|
||||
row = self.dbQuerySingleRow(sql, *args, raw=raw);
|
||||
if row:
|
||||
return applus_db.row_to_dict(row);
|
||||
else:
|
||||
return None
|
||||
|
||||
def dbQuerySingleValue(self, sql:sql_utils.SqlStatement, *args:Any, raw:bool=False) -> Any:
|
||||
"""Führt eine SQL Query aus, die maximal einen Wert zurückliefern soll.
|
||||
Dieser Wert oder None wird geliefert."""
|
||||
sqlC = self.completeSQL(sql, raw=raw);
|
||||
return applus_db.rawQuerySingleValue(self.db_conn, sqlC, *args)
|
||||
|
||||
def isDBTableKnown(self, table : str) -> bool:
|
||||
"""Prüft, ob eine Tabelle im System bekannt ist"""
|
||||
sql = "select count(*) from SYS.TABLES T where T.NAME=?"
|
||||
c = self.dbQuerySingleValue(sql, table);
|
||||
return (c > 0)
|
||||
|
||||
def getClient(self, package : str, name : str) -> Client:
|
||||
"""Erzeugt einen zeep - Client.
|
||||
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden.
|
||||
Wird als *package* "p2core" und als *name* "Table" verwendet und der
|
||||
resultierende client "client" genannt, dann kann
|
||||
z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen
|
||||
des SQLs angefordert werden.
|
||||
|
||||
:param package: das Packet, z.B. "p2core"
|
||||
:type package: str
|
||||
:param name: der Name im Packet, z.B. "Table"
|
||||
:type package: string
|
||||
:return: den Client
|
||||
:rtype: Client
|
||||
"""
|
||||
return self.server_conn.getClient(package, name);
|
||||
|
||||
def getTableFields(self, table:str, isComputed:Optional[bool]=None) -> Set[str]:
|
||||
"""
|
||||
Liefert die Namen aller Felder einer Tabelle.
|
||||
|
||||
:param table: Name der Tabelle
|
||||
:param isComputed: wenn gesetzt, werden nur die Felder geliefert, die berechnet werden oder nicht berechnet werden
|
||||
:return: Liste aller Feld-Namen
|
||||
:rtype: {str}
|
||||
"""
|
||||
sql = sql_utils.SqlStatementSelect("SYS.TABLES T")
|
||||
join = sql.addInnerJoin("SYS.COLUMNS C");
|
||||
join.on.addConditionFieldsEq("T.Object_ID", "C.Object_ID")
|
||||
if not (isComputed == None):
|
||||
join.on.addConditionFieldEq("c.is_computed", isComputed)
|
||||
sql.addFields("C.NAME")
|
||||
|
||||
sql.where.addConditionFieldEq("t.name", sql_utils.SqlParam())
|
||||
return sql_utils.normaliseDBfieldSet(self.dbQueryAll(sql, table, apply=lambda r : r.NAME));
|
||||
|
||||
def getUniqueFieldsOfTable(self, table : str) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
||||
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||
müssen.
|
||||
"""
|
||||
return applus_db.getUniqueFieldsOfTable(self.db_conn, table)
|
||||
|
||||
|
||||
def useXML(self, xml : str) -> Any:
|
||||
"""Ruft ``p2core.xml.usexml`` auf. Wird meist durch ein ``UseXMLRow-Objekt`` aufgerufen."""
|
||||
return self.client_xml.service.useXML(xml);
|
||||
|
||||
|
||||
def mkUseXMLRowInsert(self, table : str) -> applus_usexml.UseXmlRowInsert:
|
||||
"""
|
||||
Erzeugt ein Objekt zum Einfügen eines neuen DB-Eintrags.
|
||||
|
||||
:param table: DB-Tabelle in die eingefügt werden soll
|
||||
:type table: str
|
||||
:return: das XmlRow-Objekt
|
||||
:rtype: applus_usexml.UseXmlRowInsert
|
||||
"""
|
||||
|
||||
return applus_usexml.UseXmlRowInsert(self, table)
|
||||
|
||||
def mkUseXMLRowUpdate(self, table : str, id : int) -> applus_usexml.UseXmlRowUpdate:
|
||||
return applus_usexml.UseXmlRowUpdate(self, table, id)
|
||||
|
||||
def mkUseXMLRowInsertOrUpdate(self, table : str) -> applus_usexml.UseXmlRowInsertOrUpdate:
|
||||
"""
|
||||
Erzeugt ein Objekt zum Einfügen oder Updaten eines DB-Eintrags.
|
||||
|
||||
:param table: DB-Tabelle in die eingefügt werden soll
|
||||
:type table: string
|
||||
:return: das XmlRow-Objekt
|
||||
:rtype: applus_usexml.UseXmlRowInsertOrUpdate
|
||||
"""
|
||||
|
||||
return applus_usexml.UseXmlRowInsertOrUpdate(self, table)
|
||||
|
||||
|
||||
def mkUseXMLRowDelete(self, table:str, id:int) -> applus_usexml.UseXmlRowDelete :
|
||||
return applus_usexml.UseXmlRowDelete(self, table, id)
|
||||
|
||||
def execUseXMLRowDelete(self, table:str, id:int) -> None:
|
||||
delRow = self.mkUseXMLRowDelete(table, id)
|
||||
delRow.delete();
|
||||
|
||||
def nextNumber(self, obj : str) -> str:
|
||||
"""
|
||||
Erstellt eine neue Nummer für das Objekt und legt diese Nummer zurück.
|
||||
"""
|
||||
return self.client_nummer.service.nextNumber(obj)
|
||||
|
||||
def makeWebLink(self, base : str, **kwargs : Any) -> str :
|
||||
if not self.web_settings.baseurl:
|
||||
raise Exception("keine Webserver-BaseURL gesetzt");
|
||||
|
||||
url = str(self.web_settings.baseurl) + base;
|
||||
firstArg = True
|
||||
for arg, argv in kwargs.items():
|
||||
if not (argv == None):
|
||||
if firstArg:
|
||||
firstArg = False;
|
||||
url += "?"
|
||||
else:
|
||||
url += "&"
|
||||
url += arg + "=" + urllib.parse.quote(str(argv))
|
||||
return url;
|
||||
|
||||
def makeWebLinkWauftragPos(self, **kwargs : Any) -> str:
|
||||
return self.makeWebLink("wp/wauftragPosRec.aspx", **kwargs);
|
||||
|
||||
def makeWebLinkWauftrag(self, **kwargs : Any) -> str :
|
||||
return self.makeWebLink("wp/wauftragRec.aspx", **kwargs);
|
||||
|
||||
def makeWebLinkBauftrag(self, **kwargs : Any) -> str :
|
||||
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs);
|
||||
|
||||
|
||||
|
||||
def applusFromConfigDict(yamlDict:Dict[str, Any], user:Optional[str]=None, env:Optional[str]=None) -> APplusServer:
|
||||
"""Läd Einstellungen aus einer Config und erzeugt daraus ein APplus-Objekt"""
|
||||
if user is None or user=='':
|
||||
user = yamlDict["appserver"]["user"]
|
||||
if env is None or env=='':
|
||||
env = yamlDict["appserver"]["env"]
|
||||
app_server = applus_server.APplusAppServerSettings(
|
||||
appserver=yamlDict["appserver"]["server"],
|
||||
appserverPort=yamlDict["appserver"]["port"],
|
||||
user=user, # type: ignore
|
||||
env=env)
|
||||
web_server = applus_server.APplusWebServerSettings(
|
||||
baseurl=yamlDict.get("webserver", {}).get("baseurl", None)
|
||||
)
|
||||
dbparams = applus_db.APplusDBSettings(
|
||||
server=yamlDict["dbserver"]["server"],
|
||||
database=yamlDict["dbserver"]["db"],
|
||||
user=yamlDict["dbserver"]["user"],
|
||||
password=yamlDict["dbserver"]["password"]);
|
||||
return APplusServer(dbparams, app_server, web_server);
|
||||
|
||||
def applusFromConfigFile(yamlfile : 'FileDescriptorOrPath',
|
||||
user:Optional[str]=None, env:Optional[str]=None) -> APplusServer:
|
||||
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
||||
yamlDict = {}
|
||||
with open(yamlfile, "r") as stream:
|
||||
yamlDict = yaml.safe_load(stream)
|
||||
|
||||
return applusFromConfigDict(yamlDict, user=user, env=env)
|
||||
|
||||
def applusFromConfig(yamlString : str, user:Optional[str]=None, env:Optional[str]=None) -> APplusServer:
|
||||
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
||||
yamlDict = yaml.safe_load(yamlString)
|
||||
return applusFromConfigDict(yamlDict, user=user, env=env)
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||
#
|
||||
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://opensource.org/licenses/MIT.
|
||||
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
import pyodbc # type: ignore
|
||||
import logging
|
||||
from .sql_utils import SqlStatement
|
||||
from . import sql_utils
|
||||
from typing import *
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__);
|
||||
|
||||
class APplusDBSettings:
|
||||
"""
|
||||
Einstellungen, mit welcher DB sich verbunden werden soll.
|
||||
"""
|
||||
|
||||
def __init__(self, server : str, database : str, user : str, password : str):
|
||||
self.server = server
|
||||
self.database = database;
|
||||
self.user = user
|
||||
self.password = password
|
||||
|
||||
|
||||
def getConnectionString(self) -> str:
|
||||
"""Liefert den ODBC Connection-String für die Verbindung.
|
||||
:return: den Connection-String
|
||||
"""
|
||||
return ("Driver={SQL Server Native Client 11.0};"
|
||||
"Server="+self.server+";"
|
||||
"Database="+self.database+";"
|
||||
"UID="+self.user+";"
|
||||
"PWD="+self.password + ";")
|
||||
|
||||
def connect(self) -> pyodbc.Connection:
|
||||
"""Stellt eine neue Verbindung her und liefert diese zurück.
|
||||
"""
|
||||
return pyodbc.connect(self.getConnectionString())
|
||||
|
||||
|
||||
|
||||
def row_to_dict(row : pyodbc.Row) -> Dict[str, Any]:
|
||||
"""Konvertiert eine Zeile in ein Dictionary"""
|
||||
return dict(zip([t[0] for t in row.cursor_description], row))
|
||||
|
||||
def _logSQLWithArgs(sql : SqlStatement, *args : Any) -> None:
|
||||
if args:
|
||||
logger.debug("executing '{}' with args {}".format(str(sql), str(args)))
|
||||
else:
|
||||
logger.debug("executing '{}'".format(str(sql)))
|
||||
|
||||
def rawQueryAll(
|
||||
cnxn : pyodbc.Connection,
|
||||
sql : SqlStatement,
|
||||
*args : Any,
|
||||
apply : Optional[Callable[[pyodbc.Row], Any]]=None) -> Sequence[Any]:
|
||||