Compare commits
3 Commits
77e472e016
...
b05b5de039
Author | SHA1 | Date | |
---|---|---|---|
b05b5de039 | |||
3566c9ba3e | |||
d88469e711 |
43
.github/workflows/python-package.yml
vendored
Normal file
43
.github/workflows/python-package.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
||||||
|
|
||||||
|
name: Python package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v3
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y unixodbc
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install flake8 pytest
|
||||||
|
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||||
|
pip install -e .
|
||||||
|
- name: Lint with flake8
|
||||||
|
run: |
|
||||||
|
# stop the build if there are Python syntax errors or undefined names
|
||||||
|
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||||
|
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||||
|
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||||
|
- name: Test with pytest
|
||||||
|
run: |
|
||||||
|
pytest .
|
9
Changelog.md
Normal file
9
Changelog.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 06.05.2023 v1.0.1
|
||||||
|
- Code-Cleanup mit Hilfe von flake8
|
||||||
|
- Bugfix: neue Python 3.10 Syntax entfernt
|
||||||
|
- kleinere Verbesserungen an Doku
|
||||||
|
|
||||||
|
## 04.05.2023 v1.0.0
|
||||||
|
erste veröffentlichte Version
|
@ -58,7 +58,7 @@ der für die Umgebung korrekte Mandant automatisch verwendet wird.
|
|||||||
Anbindung eigener Tools
|
Anbindung eigener Tools
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Ursprünglich wurde `PyAPplus64` für die Anbindung einer APplus-Anpassung geschrieben. Dieses ist
|
Ursprünglich wurde `PyAPplus64` für die Anbindung einer APplus-Anpassung geschrieben. Diese Anpassung ist
|
||||||
als Windows-Service auf einem eigenen Rechner installiert und überwacht dort ein bestimmtes Verzeichnis.
|
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
|
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.
|
und die Ergebnisse an APplus gemeldet. Dafür werden DB-Operationen aber auch SOAP-Calls benutzt.
|
||||||
|
@ -14,6 +14,8 @@ sys.path.append('../src/')
|
|||||||
project = 'PyAPplus64'
|
project = 'PyAPplus64'
|
||||||
copyright = '2023, Thomas Tuerk'
|
copyright = '2023, Thomas Tuerk'
|
||||||
author = 'Thomas Tuerk'
|
author = 'Thomas Tuerk'
|
||||||
|
version = '1.0.1'
|
||||||
|
release = '1.0.1'
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
@ -21,12 +23,12 @@ author = 'Thomas Tuerk'
|
|||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.duration',
|
'sphinx.ext.duration',
|
||||||
'sphinx.ext.doctest',
|
'sphinx.ext.doctest',
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
'sphinx.ext.autosummary',
|
'sphinx.ext.autosummary',
|
||||||
]
|
]
|
||||||
|
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
exclude_patterns = [] # type: ignore
|
exclude_patterns = [] # type: ignore
|
||||||
|
|
||||||
language = 'de'
|
language = 'de'
|
||||||
|
|
||||||
@ -53,4 +55,4 @@ latex_elements = {
|
|||||||
|
|
||||||
autodoc_type_aliases = {
|
autodoc_type_aliases = {
|
||||||
'SqlValue': 'SqlValue'
|
'SqlValue': 'SqlValue'
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ das Deploy-, das Test- und das Prod-System. Ein Beispiel ist im Unterverzeichnis
|
|||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
Damit nicht in jedem Script immer wieder neu die Konfig-Dateien ausgewählt werden müssen, werden die Konfigs für
|
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,
|
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.
|
so dass das Config-Verzeichnis und die darin enthaltenen Configs einfach zur Verfügung stehen.
|
||||||
|
|
||||||
.. literalinclude:: ../../examples/applus_configs.py
|
.. literalinclude:: ../../examples/applus_configs.py
|
||||||
@ -26,6 +26,15 @@ so dass das Config-Verzeichnis und die darin enthaltenen Configs einfach zur Ver
|
|||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
|
|
||||||
|
``read_settings.py``
|
||||||
|
-----------------------
|
||||||
|
Einfaches Beispiel für Auslesen der SysConf und bestimmter Einstellungen.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/read_settings.py
|
||||||
|
:language: python
|
||||||
|
:lines: 9-
|
||||||
|
:linenos:
|
||||||
|
|
||||||
``check_dokumente.py``
|
``check_dokumente.py``
|
||||||
-----------------------
|
-----------------------
|
||||||
Einfaches Beispiel für lesenden und schreibenden Zugriff auf APplus Datenbank.
|
Einfaches Beispiel für lesenden und schreibenden Zugriff auf APplus Datenbank.
|
||||||
|
@ -10,10 +10,11 @@ import PyAPplus64
|
|||||||
import applus_configs
|
import applus_configs
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
def main(confFile : pathlib.Path, outfile : str) -> None:
|
|
||||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
|
||||||
|
|
||||||
# Einfache SQL-Anfrage
|
def main(confFile: pathlib.Path, outfile: str) -> None:
|
||||||
|
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||||
|
|
||||||
|
# Einfache SQL-Anfrage
|
||||||
sql1 = ("select Material, count(*) as Anzahl from ARTIKEL "
|
sql1 = ("select Material, count(*) as Anzahl from ARTIKEL "
|
||||||
"group by MATERIAL having MATERIAL is not null "
|
"group by MATERIAL having MATERIAL is not null "
|
||||||
"order by Anzahl desc")
|
"order by Anzahl desc")
|
||||||
@ -21,7 +22,7 @@ def main(confFile : pathlib.Path, outfile : str) -> None:
|
|||||||
|
|
||||||
# Sql Select-Statements können auch über SqlStatementSelect zusammengebaut
|
# Sql Select-Statements können auch über SqlStatementSelect zusammengebaut
|
||||||
# werden. Die ist bei vielen, komplizierten Bedingungen teilweise hilfreich.
|
# werden. Die ist bei vielen, komplizierten Bedingungen teilweise hilfreich.
|
||||||
sql2 = PyAPplus64.SqlStatementSelect("ARTIKEL")
|
sql2 = PyAPplus64.SqlStatementSelect("ARTIKEL")
|
||||||
sql2.addFields("Material", "count(*) as Anzahl")
|
sql2.addFields("Material", "count(*) as Anzahl")
|
||||||
sql2.addGroupBy("MATERIAL")
|
sql2.addGroupBy("MATERIAL")
|
||||||
sql2.having.addConditionFieldIsNotNull("MATERIAL")
|
sql2.having.addConditionFieldIsNotNull("MATERIAL")
|
||||||
@ -33,4 +34,4 @@ def main(confFile : pathlib.Path, outfile : str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main(applus_configs.serverConfYamlTest, "myout.xlsx")
|
main(applus_configs.serverConfYamlTest, "myout.xlsx")
|
||||||
|
@ -14,4 +14,3 @@ configdir = basedir.joinpath("config")
|
|||||||
serverConfYamlDeploy = configdir.joinpath("applus-server-deploy.yaml")
|
serverConfYamlDeploy = configdir.joinpath("applus-server-deploy.yaml")
|
||||||
serverConfYamlTest = configdir.joinpath("applus-server-test.yaml")
|
serverConfYamlTest = configdir.joinpath("applus-server-test.yaml")
|
||||||
serverConfYamlProd = configdir.joinpath("applus-server-prod.yaml")
|
serverConfYamlProd = configdir.joinpath("applus-server-prod.yaml")
|
||||||
|
|
||||||
|
@ -9,23 +9,29 @@
|
|||||||
import pathlib
|
import pathlib
|
||||||
import PyAPplus64
|
import PyAPplus64
|
||||||
import applus_configs
|
import applus_configs
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
def main(confFile : pathlib.Path, docDir:str, updateDB:bool) -> None:
|
|
||||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
|
||||||
|
|
||||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("ARTIKEL");
|
def main(confFile: pathlib.Path, updateDB: bool, docDir: Optional[str] = None) -> None:
|
||||||
sql.addFields("ID", "ARTIKEL", "DOCUMENTS");
|
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||||
sql.where.addConditionFieldStringNotEmpty("DOCUMENTS");
|
|
||||||
|
if docDir is None:
|
||||||
|
docDir = str(server.scripttool.getInstallPathWebServer().joinpath("DocLib"))
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
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__":
|
if __name__ == "__main__":
|
||||||
main(applus_configs.serverConfYamlTest, "somedir\\WebServer\\DocLib", False)
|
main(applus_configs.serverConfYamlTest, False)
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
#
|
#
|
||||||
# Dies ist für Administrationszwecke gedacht. Anwendungsbeispiel wäre,
|
# Dies ist für Administrationszwecke gedacht. Anwendungsbeispiel wäre,
|
||||||
# dass ein Artikel mit langem Arbeitsplan und Stückliste im Test-System erstellt wird.
|
# 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
|
# Viele der Positionen enthalten Nachauflöse-Scripte, die im Test-System
|
||||||
# getestet werden. Diese vielen Scripte per Hand zu kopieren ist aufwändig
|
# getestet werden. Diese vielen Scripte per Hand zu kopieren ist aufwändig
|
||||||
# und Fehleranfällig und kann mit solchen Admin-Scripten automatisiert werden.
|
# und Fehleranfällig und kann mit solchen Admin-Scripten automatisiert werden.
|
||||||
|
|
||||||
@ -23,19 +23,20 @@ import pathlib
|
|||||||
import PyAPplus64
|
import PyAPplus64
|
||||||
import applus_configs
|
import applus_configs
|
||||||
import logging
|
import logging
|
||||||
import yaml
|
import yaml
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
def main(confFile:pathlib.Path, artikel:str, artikelNeu:str|None=None) -> None:
|
def main(confFile: pathlib.Path, artikel: str, artikelNeu: Optional[str] = None) -> None:
|
||||||
# Server verbinden
|
# Server verbinden
|
||||||
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
server = PyAPplus64.applus.applusFromConfigFile(confFile)
|
||||||
|
|
||||||
# DuplicateBusinessObject für Artikel erstellen
|
# DuplicateBusinessObject für Artikel erstellen
|
||||||
dArt = PyAPplus64.duplicate.loadDBDuplicateArtikel(server, artikel);
|
dArt = PyAPplus64.duplicate.loadDBDuplicateArtikel(server, artikel)
|
||||||
|
|
||||||
# DuplicateBusinessObject zur Demonstration in YAML konvertieren und zurück
|
# DuplicateBusinessObject zur Demonstration in YAML konvertieren und zurück
|
||||||
dArtYaml = yaml.dump(dArt);
|
dArtYaml = yaml.dump(dArt)
|
||||||
print(dArtYaml);
|
print(dArtYaml)
|
||||||
dArt2 = yaml.load(dArtYaml, Loader=yaml.UnsafeLoader)
|
dArt2 = yaml.load(dArtYaml, Loader=yaml.UnsafeLoader)
|
||||||
|
|
||||||
# Neue Artikel-Nummer bestimmen und DuplicateBusinessObject in DB schreiben
|
# Neue Artikel-Nummer bestimmen und DuplicateBusinessObject in DB schreiben
|
||||||
@ -44,16 +45,15 @@ def main(confFile:pathlib.Path, artikel:str, artikelNeu:str|None=None) -> None:
|
|||||||
artikelNeu = server.nextNumber("Artikel")
|
artikelNeu = server.nextNumber("Artikel")
|
||||||
|
|
||||||
if not (dArt is None):
|
if not (dArt is None):
|
||||||
dArt.setFields({"artikel" : artikelNeu})
|
dArt.setFields({"artikel": artikelNeu})
|
||||||
res = dArt.insert(server);
|
res = dArt.insert(server)
|
||||||
print(res);
|
print(res)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Logger Einrichten
|
# Logger Einrichten
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
# logger = logging.getLogger("PyAPplus64.applus_db");
|
# logger = logging.getLogger("PyAPplus64.applus_db");
|
||||||
# logger.setLevel(logging.ERROR)
|
# logger.setLevel(logging.ERROR)
|
||||||
|
|
||||||
main(applus_configs.serverConfYamlTest, "my-artikel", artikelNeu="my-artikel-copy")
|
main(applus_configs.serverConfYamlTest, "my-artikel", artikelNeu="my-artikel-copy")
|
||||||
|
|
||||||
|
@ -11,61 +11,64 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import PyAPplus64
|
import PyAPplus64
|
||||||
import applus_configs
|
import applus_configs
|
||||||
import pandas as pd # type: ignore
|
import pandas as pd # type: ignore
|
||||||
import pathlib
|
import pathlib
|
||||||
from typing import *
|
from typing import Tuple, Union, Optional
|
||||||
|
|
||||||
|
|
||||||
def ladeAlleWerkstattauftragMengenabweichungen(
|
def ladeAlleWerkstattauftragMengenabweichungen(
|
||||||
server:PyAPplus64.APplusServer,
|
server: PyAPplus64.APplusServer,
|
||||||
cond:PyAPplus64.SqlCondition|str|None=None) -> pd.DataFrame:
|
cond: Union[PyAPplus64.SqlCondition, str, None] = None) -> pd.DataFrame:
|
||||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAG w");
|
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAG w")
|
||||||
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
||||||
|
|
||||||
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION")
|
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION")
|
||||||
sql.addFields("(w.MENGE-w.MENGE_IST) as MENGENABWEICHUNG")
|
sql.addFields("(w.MENGE-w.MENGE_IST) as MENGENABWEICHUNG")
|
||||||
sql.addFieldsTable("w", "MENGE", "MENGE_IST",
|
sql.addFieldsTable("w", "MENGE", "MENGE_IST",
|
||||||
"APLAN as ARTIKEL", "NAME as ARTIKELNAME")
|
"APLAN as ARTIKEL", "NAME as ARTIKELNAME")
|
||||||
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
||||||
|
|
||||||
sql.where.addConditionFieldGe("w.STATUS", 5)
|
sql.where.addConditionFieldGe("w.STATUS", 5)
|
||||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||||
sql.where.addCondition(cond)
|
sql.where.addCondition(cond)
|
||||||
sql.order="w.UPDDATE"
|
sql.order = "w.UPDDATE"
|
||||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql);
|
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql)
|
||||||
|
|
||||||
# Add Links
|
# Add Links
|
||||||
df = dfOrg.copy();
|
df = dfOrg.copy()
|
||||||
df = df.drop(columns=["ID"]);
|
df = df.drop(columns=["ID"])
|
||||||
# df = df[['POSITION', 'BAUFTRAG', 'MENGE']] # reorder / filter columns
|
# df = df[['POSITION', 'BAUFTRAG', 'MENGE']] # reorder / filter columns
|
||||||
|
|
||||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||||
lambda r: r.POSITION,
|
dfOrg,
|
||||||
|
lambda r: r.POSITION,
|
||||||
lambda r: server.makeWebLinkWauftrag(
|
lambda r: server.makeWebLinkWauftrag(
|
||||||
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
||||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||||
lambda r: r.BAUFTRAG,
|
dfOrg,
|
||||||
|
lambda r: r.BAUFTRAG,
|
||||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||||
|
|
||||||
colNames = {
|
colNames = {
|
||||||
"BAUFTRAG" : "Betriebsauftrag",
|
"BAUFTRAG": "Betriebsauftrag",
|
||||||
"POSITION" : "Pos",
|
"POSITION": "Pos",
|
||||||
"MENGENABWEICHUNG" : "Mengenabweichung",
|
"MENGENABWEICHUNG": "Mengenabweichung",
|
||||||
"MENGE" : "Menge",
|
"MENGE": "Menge",
|
||||||
"MENGE_IST" : "Menge-Ist",
|
"MENGE_IST": "Menge-Ist",
|
||||||
"ARTIKEL" : "Artikel",
|
"ARTIKEL": "Artikel",
|
||||||
"ARTIKELNAME" : "Artikel-Name",
|
"ARTIKELNAME": "Artikel-Name",
|
||||||
"UPDDATE" : "geändert am",
|
"UPDDATE": "geändert am",
|
||||||
"UPDNAME" : "geändert von"
|
"UPDNAME": "geändert von"
|
||||||
}
|
}
|
||||||
df.rename(columns=colNames, inplace=True);
|
df.rename(columns=colNames, inplace=True)
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
def ladeAlleWerkstattauftragPosMengenabweichungen(
|
def ladeAlleWerkstattauftragPosMengenabweichungen(
|
||||||
server : PyAPplus64.APplusServer,
|
server: PyAPplus64.APplusServer,
|
||||||
cond:PyAPplus64.SqlCondition|str|None=None) -> pd.DataFrame:
|
cond: Union[PyAPplus64.SqlCondition, str, None] = None) -> pd.DataFrame:
|
||||||
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAGPOS w");
|
sql = PyAPplus64.sql_utils.SqlStatementSelect("WAUFTRAGPOS w")
|
||||||
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
sql.addLeftJoin("personal p", "w.UPDUSER = p.PERSONAL")
|
||||||
|
|
||||||
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION", "AG")
|
sql.addFieldsTable("w", "ID", "BAUFTRAG", "POSITION", "AG")
|
||||||
@ -74,49 +77,53 @@ def ladeAlleWerkstattauftragPosMengenabweichungen(
|
|||||||
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
sql.addFields("w.UPDDATE", "p.NAME as UPDNAME")
|
||||||
|
|
||||||
sql.where.addConditionFieldEq("w.STATUS", 4)
|
sql.where.addConditionFieldEq("w.STATUS", 4)
|
||||||
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
sql.where.addCondition("abs(w.MENGE-w.MENGE_IST) > 0.001")
|
||||||
sql.where.addCondition(cond)
|
sql.where.addCondition(cond)
|
||||||
sql.order="w.UPDDATE"
|
sql.order = "w.UPDDATE"
|
||||||
|
|
||||||
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql);
|
dfOrg = PyAPplus64.pandas.pandasReadSql(server, sql)
|
||||||
|
|
||||||
# Add Links
|
# Add Links
|
||||||
df = dfOrg.copy();
|
df = dfOrg.copy()
|
||||||
df = df.drop(columns=["ID"]);
|
df = df.drop(columns=["ID"])
|
||||||
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
df['POSITION'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||||
lambda r: r.POSITION,
|
dfOrg,
|
||||||
|
lambda r: r.POSITION,
|
||||||
lambda r: server.makeWebLinkWauftrag(
|
lambda r: server.makeWebLinkWauftrag(
|
||||||
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
bauftrag=r.BAUFTRAG, accessid=r.ID))
|
||||||
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
df['BAUFTRAG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||||
lambda r: r.BAUFTRAG,
|
dfOrg,
|
||||||
|
lambda r: r.BAUFTRAG,
|
||||||
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
lambda r: server.makeWebLinkBauftrag(bauftrag=r.BAUFTRAG))
|
||||||
df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(dfOrg,
|
df['AG'] = PyAPplus64.pandas.mkHyperlinkDataframeColumn(
|
||||||
lambda r: r.AG,
|
dfOrg,
|
||||||
|
lambda r: r.AG,
|
||||||
lambda r: server.makeWebLinkWauftragPos(
|
lambda r: server.makeWebLinkWauftragPos(
|
||||||
bauftrag=r.BAUFTRAG, position=r.POSITION, accessid=r.ID))
|
bauftrag=r.BAUFTRAG, position=r.POSITION, accessid=r.ID))
|
||||||
|
|
||||||
# Demo zum Hinzufügen einer berechneten Spalte
|
# Demo zum Hinzufügen einer berechneten Spalte
|
||||||
# df['BAUFPOSAG'] = PyAPplus64.pandas.mkDataframeColumn(dfOrg,
|
# df['BAUFPOSAG'] = PyAPplus64.pandas.mkDataframeColumn(dfOrg,
|
||||||
# lambda r: "{}.{} AG {}".format(r.BAUFTRAG, r.POSITION, r.AG))
|
# lambda r: "{}.{} AG {}".format(r.BAUFTRAG, r.POSITION, r.AG))
|
||||||
|
|
||||||
# Rename Columns
|
# Rename Columns
|
||||||
colNames = {
|
colNames = {
|
||||||
"BAUFTRAG" : "Betriebsauftrag",
|
"BAUFTRAG": "Betriebsauftrag",
|
||||||
"POSITION" : "Pos",
|
"POSITION": "Pos",
|
||||||
"AG" : "AG",
|
"AG": "AG",
|
||||||
"MENGENABWEICHUNG" : "Mengenabweichung",
|
"MENGENABWEICHUNG": "Mengenabweichung",
|
||||||
"MENGE" : "Menge",
|
"MENGE": "Menge",
|
||||||
"MENGE_IST" : "Menge-Ist",
|
"MENGE_IST": "Menge-Ist",
|
||||||
"ARTIKEL" : "Artikel",
|
"ARTIKEL": "Artikel",
|
||||||
"UPDDATE" : "geändert am",
|
"UPDDATE": "geändert am",
|
||||||
"UPDNAME" : "geändert von"
|
"UPDNAME": "geändert von"
|
||||||
}
|
}
|
||||||
df.rename(columns=colNames, inplace=True);
|
df.rename(columns=colNames, inplace=True)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
def computeInYearMonthCond(field : str, year:int|None=None,
|
|
||||||
month:int|None=None) -> PyAPplus64.SqlCondition | None:
|
def computeInYearMonthCond(field: str, year: Optional[int] = None,
|
||||||
if not (year is None):
|
month: Optional[int] = None) -> Optional[PyAPplus64.SqlCondition]:
|
||||||
|
if not (year is None):
|
||||||
if month is None:
|
if month is None:
|
||||||
return PyAPplus64.sql_utils.SqlConditionDateTimeFieldInYear(field, year)
|
return PyAPplus64.sql_utils.SqlConditionDateTimeFieldInYear(field, year)
|
||||||
else:
|
else:
|
||||||
@ -124,59 +131,67 @@ def computeInYearMonthCond(field : str, year:int|None=None,
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def computeFileName(year:int|None=None, month:int|None=None) -> str:
|
|
||||||
if year is None:
|
def computeFileName(year: Optional[int] = None, month: Optional[int] = None) -> str:
|
||||||
return 'mengenabweichungen-all.xlsx';
|
if year is None:
|
||||||
|
return 'mengenabweichungen-all.xlsx'
|
||||||
else:
|
else:
|
||||||
if month is None:
|
if month is None:
|
||||||
return 'mengenabweichungen-{:04d}.xlsx'.format(year);
|
return 'mengenabweichungen-{:04d}.xlsx'.format(year)
|
||||||
else:
|
else:
|
||||||
return 'mengenabweichungen-{:04d}-{:02d}.xlsx'.format(year, month);
|
return 'mengenabweichungen-{:04d}-{:02d}.xlsx'.format(year, month)
|
||||||
|
|
||||||
def _exportInternal(server: PyAPplus64.APplusServer, fn:str,
|
|
||||||
cond:Union[PyAPplus64.SqlCondition, str, None]) -> int:
|
def _exportInternal(server: PyAPplus64.APplusServer, fn: str,
|
||||||
|
cond: Union[PyAPplus64.SqlCondition, str, None]) -> int:
|
||||||
df1 = ladeAlleWerkstattauftragMengenabweichungen(server, cond)
|
df1 = ladeAlleWerkstattauftragMengenabweichungen(server, cond)
|
||||||
df2 = ladeAlleWerkstattauftragPosMengenabweichungen(server, cond)
|
df2 = ladeAlleWerkstattauftragPosMengenabweichungen(server, cond)
|
||||||
print ("erzeuge " + fn);
|
print("erzeuge " + fn)
|
||||||
PyAPplus64.pandas.exportToExcel(fn, [(df1, "WAuftrag"), (df2, "WAuftrag-Pos")], addTable=True)
|
PyAPplus64.pandas.exportToExcel(fn, [(df1, "WAuftrag"), (df2, "WAuftrag-Pos")], addTable=True)
|
||||||
return len(df1.index) + len(df2.index)
|
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,
|
def exportVonBis(server: PyAPplus64.APplusServer, fn: str,
|
||||||
year:int|None=None, month:int|None=None) -> int:
|
von: Optional[datetime.datetime], bis: Optional[datetime.datetime]) -> int:
|
||||||
cond=computeInYearMonthCond("w.UPDDATE", year=year, month=month)
|
cond = PyAPplus64.sql_utils.SqlConditionDateTimeFieldInRange("w.UPDDATE", von, bis)
|
||||||
|
return _exportInternal(server, fn, cond)
|
||||||
|
|
||||||
|
|
||||||
|
def exportYearMonth(server: PyAPplus64.APplusServer,
|
||||||
|
year: Optional[int] = None, month: Optional[int] = None) -> int:
|
||||||
|
cond = computeInYearMonthCond("w.UPDDATE", year=year, month=month)
|
||||||
fn = computeFileName(year=year, month=month)
|
fn = computeFileName(year=year, month=month)
|
||||||
return _exportInternal(server, fn, cond)
|
return _exportInternal(server, fn, cond)
|
||||||
|
|
||||||
def computePreviousMonthYear(cyear : int, cmonth :int) -> Tuple[int, int]:
|
|
||||||
|
def computePreviousMonthYear(cyear: int, cmonth: int) -> Tuple[int, int]:
|
||||||
if cmonth == 1:
|
if cmonth == 1:
|
||||||
return (cyear-1, 12)
|
return (cyear-1, 12)
|
||||||
else:
|
else:
|
||||||
return (cyear, cmonth-1);
|
return (cyear, cmonth-1)
|
||||||
|
|
||||||
def computeNextMonthYear(cyear : int, cmonth :int) -> Tuple[int, int]:
|
|
||||||
|
def computeNextMonthYear(cyear: int, cmonth: int) -> Tuple[int, int]:
|
||||||
if cmonth == 12:
|
if cmonth == 12:
|
||||||
return (cyear+1, 1)
|
return (cyear+1, 1)
|
||||||
else:
|
else:
|
||||||
return (cyear, cmonth+1);
|
return (cyear, cmonth+1)
|
||||||
|
|
||||||
|
|
||||||
|
def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
|
||||||
|
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||||
|
|
||||||
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()
|
now = datetime.date.today()
|
||||||
(cmonth, cyear) = (now.month, now.year)
|
(cmonth, cyear) = (now.month, now.year)
|
||||||
(pyear, pmonth) = computePreviousMonthYear(cyear, cmonth);
|
(pyear, pmonth) = computePreviousMonthYear(cyear, cmonth)
|
||||||
|
|
||||||
# Ausgaben
|
# Ausgaben
|
||||||
exportYearMonth(server, cyear, cmonth) # Aktueller Monat
|
exportYearMonth(server, cyear, cmonth) # Aktueller Monat
|
||||||
exportYearMonth(server, pyear, pmonth) # Vorheriger Monat
|
exportYearMonth(server, pyear, pmonth) # Vorheriger Monat
|
||||||
# export(cyear) # aktuelles Jahr
|
# export(cyear) # aktuelles Jahr
|
||||||
# export(cyear-1) # letztes Jahr
|
# export(cyear-1) # letztes Jahr
|
||||||
# export() # alles
|
# export() # alles
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main(applus_configs.serverConfYamlTest)
|
main(applus_configs.serverConfYamlTest)
|
||||||
|
@ -6,15 +6,16 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
import PySimpleGUI as sg # type: ignore
|
import PySimpleGUI as sg # type: ignore
|
||||||
import mengenabweichung
|
import mengenabweichung
|
||||||
import datetime
|
import datetime
|
||||||
import PyAPplus64
|
import PyAPplus64
|
||||||
import applus_configs
|
import applus_configs
|
||||||
import pathlib
|
import pathlib
|
||||||
from typing import *
|
from typing import Tuple, Optional, Union
|
||||||
|
|
||||||
def parseDate (dateS:str) -> Tuple[datetime.datetime|None, bool]:
|
|
||||||
|
def parseDate(dateS: str) -> Tuple[Optional[datetime.datetime], bool]:
|
||||||
if dateS is None or dateS == '':
|
if dateS is None or dateS == '':
|
||||||
return (None, True)
|
return (None, True)
|
||||||
else:
|
else:
|
||||||
@ -24,14 +25,17 @@ def parseDate (dateS:str) -> Tuple[datetime.datetime|None, bool]:
|
|||||||
sg.popup_error("Fehler beim Parsen des Datums '{}'".format(dateS))
|
sg.popup_error("Fehler beim Parsen des Datums '{}'".format(dateS))
|
||||||
return (None, False)
|
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 == '':
|
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.")
|
sg.popup_error("Es wurde keine Ausgabedatei ausgewählt.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -41,23 +45,24 @@ def createFile(server:PyAPplus64.APplusServer, fileS:str, vonS:str, bisS:str)->N
|
|||||||
sg.popup_ok("{} Datensätze erfolgreich in Datei '{}' geschrieben.".format(c, file))
|
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:
|
def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
|
||||||
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||||
|
|
||||||
layout = [
|
layout = [
|
||||||
[sg.Text(('Bitte geben Sie an, für welchen Zeitraum die '
|
[sg.Text(('Bitte geben Sie an, für welchen Zeitraum die '
|
||||||
'Mengenabweichungen ausgegeben werden sollen:'))],
|
'Mengenabweichungen ausgegeben werden sollen:'))],
|
||||||
[sg.Text('Von (einschließlich)', size=(15,1)), sg.InputText(key='Von'),
|
[sg.Text('Von (einschließlich)', size=(15, 1)), sg.InputText(key='Von'),
|
||||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||||
target="Von", format='%d.%m.%Y')],
|
target="Von", format='%d.%m.%Y')],
|
||||||
[sg.Text('Bis (ausschließlich)', size=(15,1)), sg.InputText(key='Bis'),
|
[sg.Text('Bis (ausschließlich)', size=(15, 1)), sg.InputText(key='Bis'),
|
||||||
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
sg.CalendarButton("Kalender", close_when_date_chosen=True,
|
||||||
target="Bis", format='%d.%m.%Y')],
|
target="Bis", format='%d.%m.%Y')],
|
||||||
[sg.Text('Ausgabedatei', size=(15,1)), sg.InputText(key='File'),
|
[sg.Text('Ausgabedatei', size=(15, 1)), sg.InputText(key='File'),
|
||||||
sg.FileSaveAs(button_text="wählen", target="File",
|
sg.FileSaveAs(button_text="wählen",
|
||||||
file_types = (('Excel Files', '*.xlsx'),),
|
target="File",
|
||||||
default_extension = ".xlsx")],
|
file_types=(('Excel Files', '*.xlsx'),),
|
||||||
[sg.Button("Aktueller Monat"), sg.Button("Letzter Monat"),
|
default_extension=".xlsx")],
|
||||||
|
[sg.Button("Aktueller Monat"), sg.Button("Letzter Monat"),
|
||||||
sg.Button("Aktuelles Jahr"), sg.Button("Letztes Jahr")],
|
sg.Button("Aktuelles Jahr"), sg.Button("Letztes Jahr")],
|
||||||
[sg.Button("Speichern"), sg.Button("Beenden")]
|
[sg.Button("Speichern"), sg.Button("Beenden")]
|
||||||
]
|
]
|
||||||
@ -66,33 +71,34 @@ def main(confFile : str|pathlib.Path, user:str|None=None, env:str|None=None) ->
|
|||||||
window = sg.Window("Mengenabweichung " + systemName, layout)
|
window = sg.Window("Mengenabweichung " + systemName, layout)
|
||||||
now = datetime.date.today()
|
now = datetime.date.today()
|
||||||
(cmonth, cyear) = (now.month, now.year)
|
(cmonth, cyear) = (now.month, now.year)
|
||||||
(pyear, pmonth) = mengenabweichung.computePreviousMonthYear(cyear, cmonth);
|
(pyear, pmonth) = mengenabweichung.computePreviousMonthYear(cyear, cmonth)
|
||||||
(nyear, nmonth) = mengenabweichung.computeNextMonthYear(cyear, cmonth);
|
(nyear, nmonth) = mengenabweichung.computeNextMonthYear(cyear, cmonth)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
if event == sg.WIN_CLOSED or event == 'Beenden':
|
if event == sg.WIN_CLOSED or event == 'Beenden':
|
||||||
break
|
break
|
||||||
if event == 'Aktueller Monat':
|
if event == 'Aktueller Monat':
|
||||||
window['Von'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear));
|
window['Von'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear))
|
||||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(nmonth, nyear));
|
window['Bis'].update(value="01.{:02d}.{:04d}".format(nmonth, nyear))
|
||||||
if event == 'Letzter Monat':
|
if event == 'Letzter Monat':
|
||||||
window['Von'].update(value="01.{:02d}.{:04d}".format(pmonth, pyear));
|
window['Von'].update(value="01.{:02d}.{:04d}".format(pmonth, pyear))
|
||||||
window['Bis'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear));
|
window['Bis'].update(value="01.{:02d}.{:04d}".format(cmonth, cyear))
|
||||||
if event == 'Aktuelles Jahr':
|
if event == 'Aktuelles Jahr':
|
||||||
window['Von'].update(value="01.01.{:04d}".format(cyear));
|
window['Von'].update(value="01.01.{:04d}".format(cyear))
|
||||||
window['Bis'].update(value="01.01.{:04d}".format(cyear+1));
|
window['Bis'].update(value="01.01.{:04d}".format(cyear+1))
|
||||||
if event == 'Letztes Jahr':
|
if event == 'Letztes Jahr':
|
||||||
window['Von'].update(value="01.01.{:04d}".format(cyear-1));
|
window['Von'].update(value="01.01.{:04d}".format(cyear-1))
|
||||||
window['Bis'].update(value="01.01.{:04d}".format(cyear));
|
window['Bis'].update(value="01.01.{:04d}".format(cyear))
|
||||||
if event == 'Speichern':
|
if event == 'Speichern':
|
||||||
try:
|
try:
|
||||||
createFile(server, values.get('File', None),
|
createFile(server, values.get('File', None),
|
||||||
values.get('Von', None), values.get('Bis', None))
|
values.get('Von', None), values.get('Bis', None))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sg.popup_error_with_traceback("Beim Erzeugen der Excel-Datei trat ein Fehler auf:", e);
|
sg.popup_error_with_traceback("Beim Erzeugen der Excel-Datei trat ein Fehler auf:", e)
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main(applus_configs.serverConfYamlProd)
|
main(applus_configs.serverConfYamlProd)
|
||||||
|
53
examples/read_settings.py
Normal file
53
examples/read_settings.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
# Einfaches Script, das verschiedene Werte des Servers ausliest.
|
||||||
|
# Dies sind SysConfig-Einstellungen, aber auch der aktuelle Mandant,
|
||||||
|
# Systemnamen, ...
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import PyAPplus64
|
||||||
|
import applus_configs
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Optional[str] = None) -> None:
|
||||||
|
server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||||
|
|
||||||
|
print("\n\nSysConf Lookups:")
|
||||||
|
|
||||||
|
print(" Default Auftragsart:", server.sysconf.getString("STAMM", "DEFAULTAUFTRAGSART"))
|
||||||
|
print(" Auftragsarten:")
|
||||||
|
arten = server.sysconf.getList("STAMM", "AUFTRAGSART", sep='\n')
|
||||||
|
if not arten:
|
||||||
|
arten = []
|
||||||
|
for a in arten:
|
||||||
|
print(" - " + a)
|
||||||
|
|
||||||
|
print(" Firmen-Nr. automatisch vergeben:", server.sysconf.getBoolean("STAMM", "FIRMAAUTOMATIK"))
|
||||||
|
print(" Anzahl Artikelstellen:", server.sysconf.getInt("STAMM", "ARTKLASSIFNRLAENGE"))
|
||||||
|
|
||||||
|
print("\n\nScriptTool:")
|
||||||
|
|
||||||
|
print(" CurrentDate:", server.scripttool.getCurrentDate())
|
||||||
|
print(" CurrentTime:", server.scripttool.getCurrentTime())
|
||||||
|
print(" CurrentDateTime:", server.scripttool.getCurrentDateTime())
|
||||||
|
print(" LoginName:", server.scripttool.getLoginName())
|
||||||
|
print(" UserName:", server.scripttool.getUserName())
|
||||||
|
print(" UserFullName:", server.scripttool.getUserFullName())
|
||||||
|
print(" SystemName:", server.scripttool.getSystemName())
|
||||||
|
print(" Mandant:", server.scripttool.getMandant())
|
||||||
|
print(" MandantName:", server.scripttool.getMandantName())
|
||||||
|
print(" InstallPath:", server.scripttool.getInstallPath())
|
||||||
|
print(" InstallPathAppServer:", server.scripttool.getInstallPathAppServer())
|
||||||
|
print(" InstallPathWebServer:", server.scripttool.getInstallPathWebServer())
|
||||||
|
print(" ServerInfo - Version:", server.scripttool.getServerInfo().find("version").text)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(applus_configs.serverConfYamlTest)
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "PyAPplus64"
|
name = "PyAPplus64"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
|
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
|
||||||
]
|
]
|
||||||
|
@ -27,4 +27,4 @@ from .sql_utils import (
|
|||||||
try:
|
try:
|
||||||
from . import pandas
|
from . import pandas
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from . import applus_db
|
from . import applus_db
|
||||||
from . import applus_server
|
from . import applus_server
|
||||||
from . import applus_sysconf
|
from . import applus_sysconf
|
||||||
@ -17,18 +15,17 @@ from . import sql_utils
|
|||||||
import yaml
|
import yaml
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from zeep import Client
|
from zeep import Client
|
||||||
import pyodbc # type: ignore
|
import pyodbc # type: ignore
|
||||||
from typing import *
|
from typing import TYPE_CHECKING, Optional, Any, Callable, Dict, Sequence, Set, List
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _typeshed import FileDescriptorOrPath
|
from _typeshed import FileDescriptorOrPath
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class APplusServer:
|
class APplusServer:
|
||||||
"""
|
"""
|
||||||
Verbindung zu einem APplus DB und App Server mit Hilfsfunktionen für den komfortablen Zugriff.
|
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
|
:param db_settings: die Einstellungen für die Verbindung mit der Datenbank
|
||||||
:type db_settings: APplusDBSettings
|
:type db_settings: APplusDBSettings
|
||||||
:param server_settings: die Einstellungen für die Verbindung mit dem APplus App Server
|
:param server_settings: die Einstellungen für die Verbindung mit dem APplus App Server
|
||||||
@ -36,46 +33,48 @@ class APplusServer:
|
|||||||
:param web_settings: die Einstellungen für die Verbindung mit dem APplus Web Server
|
:param web_settings: die Einstellungen für die Verbindung mit dem APplus Web Server
|
||||||
:type web_settings: APplusWebServerSettings
|
:type web_settings: APplusWebServerSettings
|
||||||
"""
|
"""
|
||||||
def __init__(self, db_settings : applus_db.APplusDBSettings, server_settings : applus_server.APplusAppServerSettings, web_settings : applus_server.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
|
self.db_settings: applus_db.APplusDBSettings = db_settings
|
||||||
"""Die Einstellungen für die Datenbankverbindung"""
|
"""Die Einstellungen für die Datenbankverbindung"""
|
||||||
|
|
||||||
self.web_settings : applus_server.APplusWebServerSettings = web_settings
|
self.web_settings: applus_server.APplusWebServerSettings = web_settings
|
||||||
"""Die Einstellungen für die Datenbankverbindung"""
|
"""Die Einstellungen für die Datenbankverbindung"""
|
||||||
|
|
||||||
self.db_conn = db_settings.connect()
|
self.db_conn = db_settings.connect()
|
||||||
"""
|
"""
|
||||||
Eine pyodbc-Connection zur APplus DB. Diese muss genutzt werden, wenn mehrere Operationen in einer Transaktion
|
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.
|
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.
|
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)
|
||||||
self.server_conn : applus_server.APplusServerConnection = applus_server.APplusServerConnection(server_settings);
|
|
||||||
"""erlaubt den Zugriff auf den AppServer"""
|
"""erlaubt den Zugriff auf den AppServer"""
|
||||||
|
|
||||||
self.sysconf : applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self);
|
self.sysconf: applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self)
|
||||||
"""erlaubt den Zugriff auf die Sysconfig"""
|
"""erlaubt den Zugriff auf die Sysconfig"""
|
||||||
|
|
||||||
self.scripttool : applus_scripttool.APplusScriptTool = applus_scripttool.APplusScriptTool(self);
|
self.scripttool: applus_scripttool.APplusScriptTool = applus_scripttool.APplusScriptTool(self)
|
||||||
"""erlaubt den einfachen Zugriff auf Funktionen des ScriptTools"""
|
"""erlaubt den einfachen Zugriff auf Funktionen des ScriptTools"""
|
||||||
|
|
||||||
self.client_table = self.server_conn.getClient("p2core","Table");
|
self.client_table = self.server_conn.getClient("p2core", "Table")
|
||||||
self.client_xml = self.server_conn.getClient("p2core","XML");
|
self.client_xml = self.server_conn.getClient("p2core", "XML")
|
||||||
self.client_nummer = self.server_conn.getClient("p2system", "Nummer")
|
self.client_nummer = self.server_conn.getClient("p2system", "Nummer")
|
||||||
|
|
||||||
def reconnectDB(self) -> None:
|
def reconnectDB(self) -> None:
|
||||||
try:
|
try:
|
||||||
self.db_conn.close()
|
self.db_conn.close()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.db_conn = self.db_settings.connect()
|
self.db_conn = self.db_settings.connect()
|
||||||
|
|
||||||
def completeSQL(self, sql : sql_utils.SqlStatement, raw:bool=False) -> str:
|
def completeSQL(self, sql: sql_utils.SqlStatement, raw: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
Vervollständigt das SQL-Statement. Es wird z.B. der Mandant hinzugefügt.
|
Vervollständigt das SQL-Statement. Es wird z.B. der Mandant hinzugefügt.
|
||||||
|
|
||||||
:param sql: das SQL Statement
|
:param sql: das SQL Statement
|
||||||
:type sql: sql_utils.SqlStatement
|
:type sql: sql_utils.SqlStatement
|
||||||
:param raw: soll completeSQL ausgeführt werden? Falls True, wird die Eingabe zurückgeliefert
|
:param raw: soll completeSQL ausgeführt werden? Falls True, wird die Eingabe zurückgeliefert
|
||||||
@ -86,55 +85,55 @@ class APplusServer:
|
|||||||
if raw:
|
if raw:
|
||||||
return str(sql)
|
return str(sql)
|
||||||
else:
|
else:
|
||||||
return self.client_table.service.getCompleteSQL(sql);
|
return self.client_table.service.getCompleteSQL(sql)
|
||||||
|
|
||||||
def dbQueryAll(self, sql : sql_utils.SqlStatement, *args:Any, raw:bool=False,
|
def dbQueryAll(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False,
|
||||||
apply:Optional[Callable[[pyodbc.Row],Any]]=None) -> Any:
|
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
|
"""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."""
|
vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw);
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
return applus_db.rawQueryAll(self.db_conn, sqlC, *args, apply=apply)
|
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]:
|
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."""
|
"""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])
|
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:
|
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.
|
"""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."""
|
Das SQL wird zunächst vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw);
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
applus_db.rawQuery(self.db_conn, sqlC, f, *args)
|
applus_db.rawQuery(self.db_conn, sqlC, f, *args)
|
||||||
|
|
||||||
def dbQuerySingleRow(self, sql:sql_utils.SqlStatement, *args:Any, raw:bool=False) -> Optional[pyodbc.Row]:
|
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."""
|
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll. Diese Zeile wird geliefert."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw);
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
return applus_db.rawQuerySingleRow(self.db_conn, sqlC, *args)
|
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]]:
|
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.
|
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll.
|
||||||
Diese Zeile wird als Dictionary geliefert."""
|
Diese Zeile wird als Dictionary geliefert."""
|
||||||
row = self.dbQuerySingleRow(sql, *args, raw=raw);
|
row = self.dbQuerySingleRow(sql, *args, raw=raw)
|
||||||
if row:
|
if row:
|
||||||
return applus_db.row_to_dict(row);
|
return applus_db.row_to_dict(row)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def dbQuerySingleValue(self, sql:sql_utils.SqlStatement, *args:Any, raw:bool=False) -> Any:
|
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.
|
"""Führt eine SQL Query aus, die maximal einen Wert zurückliefern soll.
|
||||||
Dieser Wert oder None wird geliefert."""
|
Dieser Wert oder None wird geliefert."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw);
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
return applus_db.rawQuerySingleValue(self.db_conn, sqlC, *args)
|
return applus_db.rawQuerySingleValue(self.db_conn, sqlC, *args)
|
||||||
|
|
||||||
def isDBTableKnown(self, table : str) -> bool:
|
def isDBTableKnown(self, table: str) -> bool:
|
||||||
"""Prüft, ob eine Tabelle im System bekannt ist"""
|
"""Prüft, ob eine Tabelle im System bekannt ist"""
|
||||||
sql = "select count(*) from SYS.TABLES T where T.NAME=?"
|
sql = "select count(*) from SYS.TABLES T where T.NAME=?"
|
||||||
c = self.dbQuerySingleValue(sql, table);
|
c = self.dbQuerySingleValue(sql, table)
|
||||||
return (c > 0)
|
return (c > 0)
|
||||||
|
|
||||||
def getClient(self, package : str, name : str) -> Client:
|
def getClient(self, package: str, name: str) -> Client:
|
||||||
"""Erzeugt einen zeep - Client.
|
"""Erzeugt einen zeep - Client.
|
||||||
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden.
|
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden.
|
||||||
Wird als *package* "p2core" und als *name* "Table" verwendet und der
|
Wird als *package* "p2core" und als *name* "Table" verwendet und der
|
||||||
resultierende client "client" genannt, dann kann
|
resultierende client "client" genannt, dann kann
|
||||||
z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen
|
z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen
|
||||||
des SQLs angefordert werden.
|
des SQLs angefordert werden.
|
||||||
@ -146,9 +145,9 @@ class APplusServer:
|
|||||||
:return: den Client
|
:return: den Client
|
||||||
:rtype: Client
|
:rtype: Client
|
||||||
"""
|
"""
|
||||||
return self.server_conn.getClient(package, name);
|
return self.server_conn.getClient(package, name)
|
||||||
|
|
||||||
def getTableFields(self, table:str, isComputed:Optional[bool]=None) -> Set[str]:
|
def getTableFields(self, table: str, isComputed: Optional[bool] = None) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Liefert die Namen aller Felder einer Tabelle.
|
Liefert die Namen aller Felder einer Tabelle.
|
||||||
|
|
||||||
@ -158,34 +157,32 @@ class APplusServer:
|
|||||||
:rtype: {str}
|
:rtype: {str}
|
||||||
"""
|
"""
|
||||||
sql = sql_utils.SqlStatementSelect("SYS.TABLES T")
|
sql = sql_utils.SqlStatementSelect("SYS.TABLES T")
|
||||||
join = sql.addInnerJoin("SYS.COLUMNS C");
|
join = sql.addInnerJoin("SYS.COLUMNS C")
|
||||||
join.on.addConditionFieldsEq("T.Object_ID", "C.Object_ID")
|
join.on.addConditionFieldsEq("T.Object_ID", "C.Object_ID")
|
||||||
if not (isComputed == None):
|
if not (isComputed is None):
|
||||||
join.on.addConditionFieldEq("c.is_computed", isComputed)
|
join.on.addConditionFieldEq("c.is_computed", isComputed)
|
||||||
sql.addFields("C.NAME")
|
sql.addFields("C.NAME")
|
||||||
|
|
||||||
sql.where.addConditionFieldEq("t.name", sql_utils.SqlParam())
|
sql.where.addConditionFieldEq("t.name", sql_utils.SqlParam())
|
||||||
return sql_utils.normaliseDBfieldSet(self.dbQueryAll(sql, table, apply=lambda r : r.NAME));
|
return sql_utils.normaliseDBfieldSet(self.dbQueryAll(sql, table, apply=lambda r: r.NAME))
|
||||||
|
|
||||||
def getUniqueFieldsOfTable(self, table : str) -> Dict[str, List[str]]:
|
def getUniqueFieldsOfTable(self, table: str) -> Dict[str, List[str]]:
|
||||||
"""
|
"""
|
||||||
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
||||||
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
||||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||||
müssen.
|
müssen.
|
||||||
"""
|
"""
|
||||||
return applus_db.getUniqueFieldsOfTable(self.db_conn, table)
|
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 useXML(self, xml : str) -> Any:
|
def mkUseXMLRowInsert(self, table: str) -> applus_usexml.UseXmlRowInsert:
|
||||||
"""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.
|
Erzeugt ein Objekt zum Einfügen eines neuen DB-Eintrags.
|
||||||
|
|
||||||
:param table: DB-Tabelle in die eingefügt werden soll
|
:param table: DB-Tabelle in die eingefügt werden soll
|
||||||
:type table: str
|
:type table: str
|
||||||
:return: das XmlRow-Objekt
|
:return: das XmlRow-Objekt
|
||||||
@ -194,13 +191,13 @@ class APplusServer:
|
|||||||
|
|
||||||
return applus_usexml.UseXmlRowInsert(self, table)
|
return applus_usexml.UseXmlRowInsert(self, table)
|
||||||
|
|
||||||
def mkUseXMLRowUpdate(self, table : str, id : int) -> applus_usexml.UseXmlRowUpdate:
|
def mkUseXMLRowUpdate(self, table: str, id: int) -> applus_usexml.UseXmlRowUpdate:
|
||||||
return applus_usexml.UseXmlRowUpdate(self, table, id)
|
return applus_usexml.UseXmlRowUpdate(self, table, id)
|
||||||
|
|
||||||
def mkUseXMLRowInsertOrUpdate(self, table : str) -> applus_usexml.UseXmlRowInsertOrUpdate:
|
def mkUseXMLRowInsertOrUpdate(self, table: str) -> applus_usexml.UseXmlRowInsertOrUpdate:
|
||||||
"""
|
"""
|
||||||
Erzeugt ein Objekt zum Einfügen oder Updaten eines DB-Eintrags.
|
Erzeugt ein Objekt zum Einfügen oder Updaten eines DB-Eintrags.
|
||||||
|
|
||||||
:param table: DB-Tabelle in die eingefügt werden soll
|
:param table: DB-Tabelle in die eingefügt werden soll
|
||||||
:type table: string
|
:type table: string
|
||||||
:return: das XmlRow-Objekt
|
:return: das XmlRow-Objekt
|
||||||
@ -209,70 +206,69 @@ class APplusServer:
|
|||||||
|
|
||||||
return applus_usexml.UseXmlRowInsertOrUpdate(self, table)
|
return applus_usexml.UseXmlRowInsertOrUpdate(self, table)
|
||||||
|
|
||||||
|
def mkUseXMLRowDelete(self, table: str, id: int) -> applus_usexml.UseXmlRowDelete:
|
||||||
|
return applus_usexml.UseXmlRowDelete(self, table, id)
|
||||||
|
|
||||||
def mkUseXMLRowDelete(self, table:str, id:int) -> applus_usexml.UseXmlRowDelete :
|
def execUseXMLRowDelete(self, table: str, id: int) -> None:
|
||||||
return applus_usexml.UseXmlRowDelete(self, table, id)
|
|
||||||
|
|
||||||
def execUseXMLRowDelete(self, table:str, id:int) -> None:
|
|
||||||
delRow = self.mkUseXMLRowDelete(table, id)
|
delRow = self.mkUseXMLRowDelete(table, id)
|
||||||
delRow.delete();
|
delRow.delete()
|
||||||
|
|
||||||
def nextNumber(self, obj : str) -> str:
|
def nextNumber(self, obj: str) -> str:
|
||||||
"""
|
"""
|
||||||
Erstellt eine neue Nummer für das Objekt und legt diese Nummer zurück.
|
Erstellt eine neue Nummer für das Objekt und legt diese Nummer zurück.
|
||||||
"""
|
"""
|
||||||
return self.client_nummer.service.nextNumber(obj)
|
return self.client_nummer.service.nextNumber(obj)
|
||||||
|
|
||||||
def makeWebLink(self, base : str, **kwargs : Any) -> str :
|
def makeWebLink(self, base: str, **kwargs: Any) -> str:
|
||||||
if not self.web_settings.baseurl:
|
if not self.web_settings.baseurl:
|
||||||
raise Exception("keine Webserver-BaseURL gesetzt");
|
raise Exception("keine Webserver-BaseURL gesetzt")
|
||||||
|
|
||||||
url = str(self.web_settings.baseurl) + base;
|
url = str(self.web_settings.baseurl) + base
|
||||||
firstArg = True
|
firstArg = True
|
||||||
for arg, argv in kwargs.items():
|
for arg, argv in kwargs.items():
|
||||||
if not (argv == None):
|
if not (argv is None):
|
||||||
if firstArg:
|
if firstArg:
|
||||||
firstArg = False;
|
firstArg = False
|
||||||
url += "?"
|
url += "?"
|
||||||
else:
|
else:
|
||||||
url += "&"
|
url += "&"
|
||||||
url += arg + "=" + urllib.parse.quote(str(argv))
|
url += arg + "=" + urllib.parse.quote(str(argv))
|
||||||
return url;
|
return url
|
||||||
|
|
||||||
def makeWebLinkWauftragPos(self, **kwargs : Any) -> str:
|
def makeWebLinkWauftragPos(self, **kwargs: Any) -> str:
|
||||||
return self.makeWebLink("wp/wauftragPosRec.aspx", **kwargs);
|
return self.makeWebLink("wp/wauftragPosRec.aspx", **kwargs)
|
||||||
|
|
||||||
def makeWebLinkWauftrag(self, **kwargs : Any) -> str :
|
def makeWebLinkWauftrag(self, **kwargs: Any) -> str:
|
||||||
return self.makeWebLink("wp/wauftragRec.aspx", **kwargs);
|
return self.makeWebLink("wp/wauftragRec.aspx", **kwargs)
|
||||||
|
|
||||||
def makeWebLinkBauftrag(self, **kwargs : Any) -> str :
|
def makeWebLinkBauftrag(self, **kwargs: Any) -> str:
|
||||||
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs);
|
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def applusFromConfigDict(yamlDict: Dict[str, Any], user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
||||||
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"""
|
"""Läd Einstellungen aus einer Config und erzeugt daraus ein APplus-Objekt"""
|
||||||
if user is None or user=='':
|
if user is None or user == '':
|
||||||
user = yamlDict["appserver"]["user"]
|
user = yamlDict["appserver"]["user"]
|
||||||
if env is None or env=='':
|
if env is None or env == '':
|
||||||
env = yamlDict["appserver"]["env"]
|
env = yamlDict["appserver"]["env"]
|
||||||
app_server = applus_server.APplusAppServerSettings(
|
app_server = applus_server.APplusAppServerSettings(
|
||||||
appserver=yamlDict["appserver"]["server"],
|
appserver=yamlDict["appserver"]["server"],
|
||||||
appserverPort=yamlDict["appserver"]["port"],
|
appserverPort=yamlDict["appserver"]["port"],
|
||||||
user=user, # type: ignore
|
user=user, # type: ignore
|
||||||
env=env)
|
env=env)
|
||||||
web_server = applus_server.APplusWebServerSettings(
|
web_server = applus_server.APplusWebServerSettings(
|
||||||
baseurl=yamlDict.get("webserver", {}).get("baseurl", None)
|
baseurl=yamlDict.get("webserver", {}).get("baseurl", None)
|
||||||
)
|
)
|
||||||
dbparams = applus_db.APplusDBSettings(
|
dbparams = applus_db.APplusDBSettings(
|
||||||
server=yamlDict["dbserver"]["server"],
|
server=yamlDict["dbserver"]["server"],
|
||||||
database=yamlDict["dbserver"]["db"],
|
database=yamlDict["dbserver"]["db"],
|
||||||
user=yamlDict["dbserver"]["user"],
|
user=yamlDict["dbserver"]["user"],
|
||||||
password=yamlDict["dbserver"]["password"]);
|
password=yamlDict["dbserver"]["password"])
|
||||||
return APplusServer(dbparams, app_server, web_server);
|
return APplusServer(dbparams, app_server, web_server)
|
||||||
|
|
||||||
def applusFromConfigFile(yamlfile : 'FileDescriptorOrPath',
|
|
||||||
user:Optional[str]=None, env:Optional[str]=None) -> APplusServer:
|
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"""
|
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
||||||
yamlDict = {}
|
yamlDict = {}
|
||||||
with open(yamlfile, "r") as stream:
|
with open(yamlfile, "r") as stream:
|
||||||
@ -280,8 +276,8 @@ def applusFromConfigFile(yamlfile : 'FileDescriptorOrPath',
|
|||||||
|
|
||||||
return applusFromConfigDict(yamlDict, user=user, env=env)
|
return applusFromConfigDict(yamlDict, user=user, env=env)
|
||||||
|
|
||||||
def applusFromConfig(yamlString : str, user:Optional[str]=None, env:Optional[str]=None) -> APplusServer:
|
|
||||||
|
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"""
|
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
||||||
yamlDict = yaml.safe_load(yamlString)
|
yamlDict = yaml.safe_load(yamlString)
|
||||||
return applusFromConfigDict(yamlDict, user=user, env=env)
|
return applusFromConfigDict(yamlDict, user=user, env=env)
|
||||||
|
|
||||||
|
@ -6,29 +6,27 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
import pyodbc # type: ignore
|
||||||
|
|
||||||
import pyodbc # type: ignore
|
|
||||||
import logging
|
import logging
|
||||||
from .sql_utils import SqlStatement
|
from .sql_utils import SqlStatement
|
||||||
from . import sql_utils
|
from . import sql_utils
|
||||||
from typing import *
|
from typing import List, Dict, Set, Any, Optional, Callable, Sequence
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__);
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class APplusDBSettings:
|
class APplusDBSettings:
|
||||||
"""
|
"""
|
||||||
Einstellungen, mit welcher DB sich verbunden werden soll.
|
Einstellungen, mit welcher DB sich verbunden werden soll.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server : str, database : str, user : str, password : str):
|
def __init__(self, server: str, database: str, user: str, password: str):
|
||||||
self.server = server
|
self.server = server
|
||||||
self.database = database;
|
self.database = database
|
||||||
self.user = user
|
self.user = user
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
|
|
||||||
def getConnectionString(self) -> str:
|
def getConnectionString(self) -> str:
|
||||||
"""Liefert den ODBC Connection-String für die Verbindung.
|
"""Liefert den ODBC Connection-String für die Verbindung.
|
||||||
:return: den Connection-String
|
:return: den Connection-String
|
||||||
@ -37,7 +35,7 @@ class APplusDBSettings:
|
|||||||
"Server="+self.server+";"
|
"Server="+self.server+";"
|
||||||
"Database="+self.database+";"
|
"Database="+self.database+";"
|
||||||
"UID="+self.user+";"
|
"UID="+self.user+";"
|
||||||
"PWD="+self.password + ";")
|
"PWD="+self.password + ";")
|
||||||
|
|
||||||
def connect(self) -> pyodbc.Connection:
|
def connect(self) -> pyodbc.Connection:
|
||||||
"""Stellt eine neue Verbindung her und liefert diese zurück.
|
"""Stellt eine neue Verbindung her und liefert diese zurück.
|
||||||
@ -45,22 +43,23 @@ class APplusDBSettings:
|
|||||||
return pyodbc.connect(self.getConnectionString())
|
return pyodbc.connect(self.getConnectionString())
|
||||||
|
|
||||||
|
|
||||||
|
def row_to_dict(row: pyodbc.Row) -> Dict[str, Any]:
|
||||||
def row_to_dict(row : pyodbc.Row) -> Dict[str, Any]:
|
|
||||||
"""Konvertiert eine Zeile in ein Dictionary"""
|
"""Konvertiert eine Zeile in ein Dictionary"""
|
||||||
return dict(zip([t[0] for t in row.cursor_description], row))
|
return dict(zip([t[0] for t in row.cursor_description], row))
|
||||||
|
|
||||||
def _logSQLWithArgs(sql : SqlStatement, *args : Any) -> None:
|
|
||||||
|
def _logSQLWithArgs(sql: SqlStatement, *args: Any) -> None:
|
||||||
if args:
|
if args:
|
||||||
logger.debug("executing '{}' with args {}".format(str(sql), str(args)))
|
logger.debug("executing '{}' with args {}".format(str(sql), str(args)))
|
||||||
else:
|
else:
|
||||||
logger.debug("executing '{}'".format(str(sql)))
|
logger.debug("executing '{}'".format(str(sql)))
|
||||||
|
|
||||||
|
|
||||||
def rawQueryAll(
|
def rawQueryAll(
|
||||||
cnxn : pyodbc.Connection,
|
cnxn: pyodbc.Connection,
|
||||||
sql : SqlStatement,
|
sql: SqlStatement,
|
||||||
*args : Any,
|
*args: Any,
|
||||||
apply : Optional[Callable[[pyodbc.Row], Any]]=None) -> Sequence[Any]:
|
apply: Optional[Callable[[pyodbc.Row], Any]] = None) -> Sequence[Any]:
|
||||||
"""
|
"""
|
||||||
Führt eine SQL Query direkt aus und liefert alle Zeilen zurück.
|
Führt eine SQL Query direkt aus und liefert alle Zeilen zurück.
|
||||||
Wenn apply gesetzt ist, wird die Funktion auf jeder Zeile ausgeführt und das Ergebnis ausgeben, die nicht None sind.
|
Wenn apply gesetzt ist, wird die Funktion auf jeder Zeile ausgeführt und das Ergebnis ausgeben, die nicht None sind.
|
||||||
@ -69,48 +68,52 @@ def rawQueryAll(
|
|||||||
with cnxn.cursor() as cursor:
|
with cnxn.cursor() as cursor:
|
||||||
cursor.execute(str(sql), *args)
|
cursor.execute(str(sql), *args)
|
||||||
|
|
||||||
rows = cursor.fetchall();
|
rows = cursor.fetchall()
|
||||||
if apply is None:
|
if apply is None:
|
||||||
return rows
|
return rows
|
||||||
else:
|
else:
|
||||||
res = []
|
res = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
rr = apply(r)
|
rr = apply(r)
|
||||||
if not (rr == None):
|
if not (rr is None):
|
||||||
res.append(rr)
|
res.append(rr)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def rawQuery(cnxn : pyodbc.Connection, sql : sql_utils.SqlStatement, f : Callable[[pyodbc.Row], None], *args : Any) -> None:
|
|
||||||
|
def rawQuery(cnxn: pyodbc.Connection, sql: sql_utils.SqlStatement, f: Callable[[pyodbc.Row], None], *args: Any) -> None:
|
||||||
"""Führt eine SQL Query direkt aus und führt für jede Zeile die übergeben Funktion aus."""
|
"""Führt eine SQL Query direkt aus und führt für jede Zeile die übergeben Funktion aus."""
|
||||||
_logSQLWithArgs(sql, *args)
|
_logSQLWithArgs(sql, *args)
|
||||||
with cnxn.cursor() as cursor:
|
with cnxn.cursor() as cursor:
|
||||||
cursor.execute(str(sql), *args)
|
cursor.execute(str(sql), *args)
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
f(row);
|
f(row)
|
||||||
|
|
||||||
def rawQuerySingleRow(cnxn : pyodbc.Connection, sql : SqlStatement, *args : Any) -> Optional[pyodbc.Row]:
|
|
||||||
|
def rawQuerySingleRow(cnxn: pyodbc.Connection, sql: SqlStatement, *args: Any) -> Optional[pyodbc.Row]:
|
||||||
"""Führt eine SQL Query direkt aus, die maximal eine Zeile zurückliefern soll. Diese Zeile wird geliefert."""
|
"""Führt eine SQL Query direkt aus, die maximal eine Zeile zurückliefern soll. Diese Zeile wird geliefert."""
|
||||||
_logSQLWithArgs(sql, *args)
|
_logSQLWithArgs(sql, *args)
|
||||||
with cnxn.cursor() as cursor:
|
with cnxn.cursor() as cursor:
|
||||||
cursor.execute(str(sql), *args)
|
cursor.execute(str(sql), *args)
|
||||||
return cursor.fetchone();
|
return cursor.fetchone()
|
||||||
|
|
||||||
def rawQuerySingleValue(cnxn : pyodbc.Connection, sql : SqlStatement, *args : Any) -> Any:
|
|
||||||
|
def rawQuerySingleValue(cnxn: pyodbc.Connection, sql: SqlStatement, *args: Any) -> Any:
|
||||||
"""Führt eine SQL Query direkt aus, die maximal einen Wert zurückliefern soll. Dieser Wert oder None wird geliefert."""
|
"""Führt eine SQL Query direkt aus, die maximal einen Wert zurückliefern soll. Dieser Wert oder None wird geliefert."""
|
||||||
_logSQLWithArgs(sql, *args)
|
_logSQLWithArgs(sql, *args)
|
||||||
with cnxn.cursor() as cursor:
|
with cnxn.cursor() as cursor:
|
||||||
cursor.execute(str(sql), *args)
|
cursor.execute(str(sql), *args)
|
||||||
row = cursor.fetchone();
|
row = cursor.fetchone()
|
||||||
if row:
|
if row:
|
||||||
return row[0];
|
return row[0]
|
||||||
else:
|
else:
|
||||||
return None;
|
return None
|
||||||
|
|
||||||
def getUniqueFieldsOfTable(cnxn : pyodbc.Connection, table : str) -> Dict[str, List[str]] :
|
|
||||||
|
def getUniqueFieldsOfTable(cnxn: pyodbc.Connection, table: str) -> Dict[str, List[str]]:
|
||||||
"""
|
"""
|
||||||
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
Liefert alle Spalten einer Tabelle, die eindeutig sein müssen.
|
||||||
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
Diese werden als Dictionary gruppiert nach Index-Namen geliefert.
|
||||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||||
müssen.
|
müssen.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -123,7 +126,7 @@ def getUniqueFieldsOfTable(cnxn : pyodbc.Connection, table : str) -> Dict[str, L
|
|||||||
sql.addFields("i.name AS INDEX_NAME", "COL_NAME(ic.OBJECT_ID,ic.column_id) AS COL")
|
sql.addFields("i.name AS INDEX_NAME", "COL_NAME(ic.OBJECT_ID,ic.column_id) AS COL")
|
||||||
_logSQLWithArgs(sql)
|
_logSQLWithArgs(sql)
|
||||||
|
|
||||||
indices : Dict[str, List[str]] = {}
|
indices: Dict[str, List[str]] = {}
|
||||||
with cnxn.cursor() as cursor:
|
with cnxn.cursor() as cursor:
|
||||||
cursor.execute(str(sql))
|
cursor.execute(str(sql))
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
@ -135,31 +138,31 @@ def getUniqueFieldsOfTable(cnxn : pyodbc.Connection, table : str) -> Dict[str, L
|
|||||||
|
|
||||||
class DBTableIDs():
|
class DBTableIDs():
|
||||||
"""Klasse, die Mengen von IDs gruppiert nach Tabellen speichert"""
|
"""Klasse, die Mengen von IDs gruppiert nach Tabellen speichert"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.data : Dict[str, Set[int]]= {}
|
|
||||||
|
|
||||||
def add(self, table:str, *ids : int) -> None:
|
def __init__(self) -> None:
|
||||||
|
self.data: Dict[str, Set[int]] = {}
|
||||||
|
|
||||||
|
def add(self, table: str, *ids: int) -> None:
|
||||||
"""
|
"""
|
||||||
fügt Eintrag hinzu
|
fügt Eintrag hinzu
|
||||||
|
|
||||||
:param table: die Tabelle
|
:param table: die Tabelle
|
||||||
:type table: str
|
:type table: str
|
||||||
:param id: die ID
|
:param id: die ID
|
||||||
"""
|
"""
|
||||||
table = table.upper()
|
table = table.upper()
|
||||||
if not (table in self.data):
|
if not (table in self.data):
|
||||||
self.data[table] = set(ids);
|
self.data[table] = set(ids)
|
||||||
else:
|
else:
|
||||||
self.data[table].update(ids)
|
self.data[table].update(ids)
|
||||||
|
|
||||||
def getTable(self, table : str) -> Set[int]:
|
def getTable(self, table: str) -> Set[int]:
|
||||||
"""
|
"""
|
||||||
Liefert die Menge der IDs für eine bestimmte Tabelle.
|
Liefert die Menge der IDs für eine bestimmte Tabelle.
|
||||||
|
|
||||||
:param table: die Tabelle
|
:param table: die Tabelle
|
||||||
:type table: str
|
:type table: str
|
||||||
:return: die IDs
|
:return: die IDs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
table = table.upper()
|
table = table.upper()
|
||||||
@ -167,5 +170,3 @@ class DBTableIDs():
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return str(self.data)
|
return str(self.data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,22 +6,22 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from .applus import APplusServer
|
from .applus import APplusServer
|
||||||
from . import sql_utils
|
from . import sql_utils
|
||||||
import lxml.etree as ET # type: ignore
|
import lxml.etree as ET # type: ignore
|
||||||
from typing import *
|
from typing import Optional, Tuple, Set
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
class XMLDefinition:
|
class XMLDefinition:
|
||||||
"""Repräsentation eines XML-Dokuments"""
|
"""Repräsentation eines XML-Dokuments"""
|
||||||
|
|
||||||
def __init__(self, root : ET.Element) -> None:
|
def __init__(self, root: ET.Element) -> None:
|
||||||
self.root : ET.Element = root
|
self.root: ET.Element = root
|
||||||
"""das Root-Element, repräsentiert "object" aus Datei."""
|
"""das Root-Element, repräsentiert "object" aus Datei."""
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return ET.tostring(self.root, encoding = "unicode")
|
return ET.tostring(self.root, encoding="unicode")
|
||||||
|
|
||||||
def getDuplicate(self) -> Tuple[Set[str], bool]:
|
def getDuplicate(self) -> Tuple[Set[str], bool]:
|
||||||
"""
|
"""
|
||||||
@ -30,11 +30,11 @@ class XMLDefinition:
|
|||||||
:return: Tuple aus allen Properties und ob dies aus- (True) oder ein-(False) zuschließen sind.
|
:return: Tuple aus allen Properties und ob dies aus- (True) oder ein-(False) zuschließen sind.
|
||||||
:rtype: Tuple[Set[str], bool]
|
:rtype: Tuple[Set[str], bool]
|
||||||
"""
|
"""
|
||||||
res : Set[str] = set()
|
res: Set[str] = set()
|
||||||
excl = True;
|
excl = True
|
||||||
dupl = self.root.find("duplicate")
|
dupl = self.root.find("duplicate")
|
||||||
if (dupl is None):
|
if (dupl is None):
|
||||||
return (res, excl);
|
return (res, excl)
|
||||||
|
|
||||||
exclS = dupl.get("type", default="exclude")
|
exclS = dupl.get("type", default="exclude")
|
||||||
excl = exclS.casefold() == "exclude"
|
excl = exclS.casefold() == "exclude"
|
||||||
@ -43,7 +43,7 @@ class XMLDefinition:
|
|||||||
v = e.get("ref")
|
v = e.get("ref")
|
||||||
if not (v is None):
|
if not (v is None):
|
||||||
res.add(sql_utils.normaliseDBfield(str(v)))
|
res.add(sql_utils.normaliseDBfield(str(v)))
|
||||||
|
|
||||||
return (res, excl)
|
return (res, excl)
|
||||||
|
|
||||||
|
|
||||||
@ -55,8 +55,8 @@ class APplusScriptTool:
|
|||||||
:type server: APplusServerConnection
|
:type server: APplusServerConnection
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server : APplusServer) -> None:
|
def __init__(self, server: APplusServer) -> None:
|
||||||
self.client = server.getClient("p2script", "ScriptTool")
|
self.client = server.getClient("p2script", "ScriptTool")
|
||||||
|
|
||||||
def getCurrentDate(self) -> str:
|
def getCurrentDate(self) -> str:
|
||||||
@ -76,41 +76,59 @@ class APplusScriptTool:
|
|||||||
|
|
||||||
def getUserFullName(self) -> str:
|
def getUserFullName(self) -> str:
|
||||||
return self.client.service.getUserFullName()
|
return self.client.service.getUserFullName()
|
||||||
|
|
||||||
def getSystemName(self) -> str:
|
def getSystemName(self) -> str:
|
||||||
return self.client.service.getSystemName()
|
return self.client.service.getSystemName()
|
||||||
|
|
||||||
def getXMLDefinitionString(self, obj:str, mandant:str="") -> str:
|
def getInstallPath(self) -> str:
|
||||||
|
"""
|
||||||
|
Liefert den Installionspfad des Appservers
|
||||||
|
"""
|
||||||
|
return self.client.service.getInstallPath()
|
||||||
|
|
||||||
|
def getInstallPathAppServer(self) -> pathlib.Path:
|
||||||
|
"""
|
||||||
|
Liefert den Installionspfad des Appservers als PathLib-Path
|
||||||
|
"""
|
||||||
|
return pathlib.Path(self.getInstallPath())
|
||||||
|
|
||||||
|
def getInstallPathWebServer(self) -> pathlib.Path:
|
||||||
|
"""
|
||||||
|
Liefert den Installionspfad des Webservers als PathLib-Path
|
||||||
|
"""
|
||||||
|
return self.getInstallPathAppServer().parents[0].joinpath("WebServer")
|
||||||
|
|
||||||
|
def getXMLDefinitionString(self, obj: str, mandant: str = "") -> str:
|
||||||
"""
|
"""
|
||||||
Läd die XML-Defintion als String vom APPServer. Auch wenn kein XML-Dokument im Dateisystem gefunden wird,
|
Läd die XML-Defintion als String vom APPServer. Auch wenn kein XML-Dokument im Dateisystem gefunden wird,
|
||||||
wird ein String zurückgeliefert, der einen leeren Top-"Object" Knoten enthält. Für gefundene XML-Dokumente
|
wird ein String zurückgeliefert, der einen leeren Top-"Object" Knoten enthält. Für gefundene XML-Dokumente
|
||||||
gibt es zusätzlich einen Top-"MD5"-Knoten.
|
gibt es zusätzlich einen Top-"MD5"-Knoten.
|
||||||
|
|
||||||
:param obj: das Objekt, dessen Definition zu laden ist, "Artikel" läd z.B. "ArtikelDefinition.xml"
|
:param obj: das Objekt, dessen Definition zu laden ist, "Artikel" läd z.B. "ArtikelDefinition.xml"
|
||||||
:type obj: str
|
:type obj: str
|
||||||
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
||||||
:type mandant: str optional
|
:type mandant: str optional
|
||||||
:return: das gefundene XML-Dokument als String
|
:return: das gefundene XML-Dokument als String
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
return self.client.service.getXMLDefinition2(obj, "")
|
return self.client.service.getXMLDefinition2(obj, "")
|
||||||
|
|
||||||
def getXMLDefinition(self, obj:str, mandant:str="", checkFileExists:bool=False) -> Optional[ET.Element]:
|
def getXMLDefinition(self, obj: str, mandant: str = "", checkFileExists: bool = False) -> Optional[ET.Element]:
|
||||||
"""
|
"""
|
||||||
Läd die XML-Definition als String vom APPServer. und parst das XML in ein minidom-Dokument.
|
Läd die XML-Definition als String vom APPServer. und parst das XML in ein minidom-Dokument.
|
||||||
|
|
||||||
:param obj: das Objekt, dessen Definition zu laden ist, "Artikel" läd z.B. "ArtikelDefinition.xml"
|
:param obj: das Objekt, dessen Definition zu laden ist, "Artikel" läd z.B. "ArtikelDefinition.xml"
|
||||||
:type obj: str
|
:type obj: str
|
||||||
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
||||||
:type mandant: str optional
|
:type mandant: str optional
|
||||||
:return: das gefundene und mittels ElementTree geparste XML-Dokument
|
:return: das gefundene und geparste XML-Dokument
|
||||||
:rtype: ET.Element
|
:rtype: ET.Element
|
||||||
"""
|
"""
|
||||||
return ET.fromstring(self.getXMLDefinitionString(obj, mandant=mandant))
|
return ET.fromstring(self.getXMLDefinitionString(obj, mandant=mandant))
|
||||||
|
|
||||||
def getXMLDefinitionObj(self, obj:str, mandant:str="") -> Optional[XMLDefinition]:
|
def getXMLDefinitionObj(self, obj: str, mandant: str = "") -> Optional[XMLDefinition]:
|
||||||
"""
|
"""
|
||||||
Benutzt getXMLDefinitionObj und liefert den Top-Level "Object" Knoten zurück, falls zusätzlich
|
Benutzt getXMLDefinitionObj und liefert den Top-Level "Object" Knoten zurück, falls zusätzlich
|
||||||
ein MD5 Knoten existiert, also falls das Dokument wirklich vom Dateisystem geladen werden konnte.
|
ein MD5 Knoten existiert, also falls das Dokument wirklich vom Dateisystem geladen werden konnte.
|
||||||
Ansonten wird None geliefert.
|
Ansonten wird None geliefert.
|
||||||
|
|
||||||
@ -118,22 +136,21 @@ class APplusScriptTool:
|
|||||||
:type obj: str
|
:type obj: str
|
||||||
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
:param mandant: der Mandant, dessen XML-Doku geladen werden soll, wenn "" wird der Standard-Mandant verwendet
|
||||||
:type mandant: str optional
|
:type mandant: str optional
|
||||||
:return: das gefundene und mittels ElementTree geparste XML-Dokument
|
:return: das gefundene und geparste XML-Dokument
|
||||||
:rtype: Optional[XMLDefinition]
|
:rtype: Optional[XMLDefinition]
|
||||||
"""
|
"""
|
||||||
e = self.getXMLDefinition(obj, mandant=mandant);
|
e = self.getXMLDefinition(obj, mandant=mandant)
|
||||||
if e is None:
|
if e is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if e.find("md5") is None:
|
if e.find("md5") is None:
|
||||||
return None;
|
return None
|
||||||
|
|
||||||
o = e.find("object")
|
o = e.find("object")
|
||||||
if o is None:
|
if o is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return XMLDefinition(o);
|
return XMLDefinition(o)
|
||||||
|
|
||||||
|
|
||||||
def getMandant(self) -> str:
|
def getMandant(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -146,3 +163,21 @@ class APplusScriptTool:
|
|||||||
Liefert den Namen des aktuellen Mandanten
|
Liefert den Namen des aktuellen Mandanten
|
||||||
"""
|
"""
|
||||||
return self.client.service.getCurrentClientProperty("NAME")
|
return self.client.service.getCurrentClientProperty("NAME")
|
||||||
|
|
||||||
|
def getServerInfoString(self) -> str:
|
||||||
|
"""
|
||||||
|
Liefert Informationen zum Server als String. Dieser String repräsentiert ein XML Dokument.
|
||||||
|
|
||||||
|
:return: das XML-Dokument als String
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return self.client.service.getP2plusServerInfo()
|
||||||
|
|
||||||
|
def getServerInfo(self) -> Optional[ET.Element]:
|
||||||
|
"""
|
||||||
|
Liefert Informationen zum Server als ein XML Dokument.
|
||||||
|
|
||||||
|
:return: das gefundene und geparste XML-Dokument
|
||||||
|
:rtype: ET.Element
|
||||||
|
"""
|
||||||
|
return ET.fromstring(self.getServerInfoString())
|
||||||
|
@ -6,9 +6,7 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
from requests import Session # type: ignore
|
||||||
|
|
||||||
from requests import Session # type: ignore
|
|
||||||
from requests.auth import HTTPBasicAuth # type: ignore # or HTTPDigestAuth, or OAuth1, etc.
|
from requests.auth import HTTPBasicAuth # type: ignore # or HTTPDigestAuth, or OAuth1, etc.
|
||||||
from zeep import Client
|
from zeep import Client
|
||||||
from zeep.transports import Transport
|
from zeep.transports import Transport
|
||||||
@ -20,24 +18,25 @@ class APplusAppServerSettings:
|
|||||||
"""
|
"""
|
||||||
Einstellungen, mit welchem APplus App-Server sich verbunden werden soll.
|
Einstellungen, mit welchem APplus App-Server sich verbunden werden soll.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, appserver : str, appserverPort : int, user : str, env : Optional[str] = None):
|
def __init__(self, appserver: str, appserverPort: int, user: str, env: Optional[str] = None):
|
||||||
self.appserver = appserver
|
self.appserver = appserver
|
||||||
self.appserverPort = appserverPort
|
self.appserverPort = appserverPort
|
||||||
self.user = user
|
self.user = user
|
||||||
self.env = env
|
self.env = env
|
||||||
|
|
||||||
|
|
||||||
class APplusWebServerSettings:
|
class APplusWebServerSettings:
|
||||||
"""
|
"""
|
||||||
Einstellungen, mit welchem APplus Web-Server sich verbunden werden soll.
|
Einstellungen, mit welchem APplus Web-Server sich verbunden werden soll.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, baseurl:Optional[str]=None):
|
def __init__(self, baseurl: Optional[str] = None):
|
||||||
self.baseurl : Optional[str] = baseurl;
|
self.baseurl: Optional[str] = baseurl
|
||||||
try:
|
try:
|
||||||
assert (isinstance(self.baseurl, str))
|
assert (isinstance(self.baseurl, str))
|
||||||
if not (self.baseurl == None) and not (self.baseurl[-1] == "/"):
|
if not (self.baseurl is None) and not (self.baseurl[-1] == "/"):
|
||||||
self.baseurl = self.baseurl + "/";
|
self.baseurl = self.baseurl + "/"
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -48,8 +47,8 @@ class APplusServerConnection:
|
|||||||
:param settings: die Einstellungen für die Verbindung mit dem APplus Server
|
:param settings: die Einstellungen für die Verbindung mit dem APplus Server
|
||||||
:type settings: APplusAppServerSettings
|
:type settings: APplusAppServerSettings
|
||||||
"""
|
"""
|
||||||
def __init__(self, settings : APplusAppServerSettings) -> None:
|
def __init__(self, settings: APplusAppServerSettings) -> None:
|
||||||
userEnv = settings.user;
|
userEnv = settings.user
|
||||||
if (settings.env):
|
if (settings.env):
|
||||||
userEnv += "|" + settings.env
|
userEnv += "|" + settings.env
|
||||||
|
|
||||||
@ -58,14 +57,14 @@ class APplusServerConnection:
|
|||||||
|
|
||||||
self.transport = Transport(cache=SqliteCache(), session=session)
|
self.transport = Transport(cache=SqliteCache(), session=session)
|
||||||
# self.transport = Transport(session=session)
|
# self.transport = Transport(session=session)
|
||||||
self.clientCache : Dict[str, Client] = {}
|
self.clientCache: Dict[str, Client] = {}
|
||||||
self.settings=settings;
|
self.settings = settings
|
||||||
self.appserverUrl = "http://" + settings.appserver + ":" + str(settings.appserverPort) + "/";
|
self.appserverUrl = "http://" + settings.appserver + ":" + str(settings.appserverPort) + "/"
|
||||||
|
|
||||||
def getClient(self, package : str, name : str) -> Client:
|
def getClient(self, package: str, name: str) -> Client:
|
||||||
"""Erzeugt einen zeep - Client.
|
"""Erzeugt einen zeep - Client.
|
||||||
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden.
|
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden.
|
||||||
Wird als *package* "p2core" und als *name* "Table" verwendet und der
|
Wird als *package* "p2core" und als *name* "Table" verwendet und der
|
||||||
resultierende client "client" genannt, dann kann
|
resultierende client "client" genannt, dann kann
|
||||||
z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen
|
z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen
|
||||||
des SQLs angefordert werden.
|
des SQLs angefordert werden.
|
||||||
@ -77,11 +76,11 @@ class APplusServerConnection:
|
|||||||
:return: den Client
|
:return: den Client
|
||||||
:rtype: Client
|
:rtype: Client
|
||||||
"""
|
"""
|
||||||
url = package+"/"+name;
|
url = package+"/"+name
|
||||||
try:
|
try:
|
||||||
return self.clientCache[url];
|
return self.clientCache[url]
|
||||||
except:
|
except:
|
||||||
fullClientUrl = self.appserverUrl + url + ".jws?wsdl"
|
fullClientUrl = self.appserverUrl + url + ".jws?wsdl"
|
||||||
client = Client(fullClientUrl, transport=self.transport)
|
client = Client(fullClientUrl, transport=self.transport)
|
||||||
self.clientCache[url] = client;
|
self.clientCache[url] = client
|
||||||
return client;
|
return client
|
||||||
|
@ -6,11 +6,9 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
from typing import TYPE_CHECKING, Optional, Dict, Any, Callable, Sequence
|
||||||
|
|
||||||
from typing import *
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .applus import APplusServer
|
from .applus import APplusServer
|
||||||
|
|
||||||
|
|
||||||
@ -22,38 +20,38 @@ class APplusSysConf:
|
|||||||
:type server: APplusServer
|
:type server: APplusServer
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server : 'APplusServer') -> None:
|
def __init__(self, server: 'APplusServer') -> None:
|
||||||
self.client = server.getClient("p2system", "SysConf")
|
self.client = server.getClient("p2system", "SysConf")
|
||||||
self.cache : Dict[str, type] = {}
|
self.cache: Dict[str, type] = {}
|
||||||
|
|
||||||
def clearCache(self) -> None:
|
def clearCache(self) -> None:
|
||||||
self.cache = {};
|
self.cache = {}
|
||||||
|
|
||||||
def _getGeneral(self, ty:str, f : Callable[[str, str], Any], module:str, name:str, useCache:bool) -> Any:
|
def _getGeneral(self, ty: str, f: Callable[[str, str], Any], module: str, name: str, useCache: bool) -> Any:
|
||||||
cacheKey = module + "/" + name + "/" + ty;
|
cacheKey = module + "/" + name + "/" + ty
|
||||||
if useCache and cacheKey in self.cache:
|
if useCache and cacheKey in self.cache:
|
||||||
return self.cache[cacheKey]
|
return self.cache[cacheKey]
|
||||||
else:
|
else:
|
||||||
v = f(module, name);
|
v = f(module, name)
|
||||||
self.cache[cacheKey] = v;
|
self.cache[cacheKey] = v
|
||||||
return v;
|
return v
|
||||||
|
|
||||||
def getString(self, module:str, name:str, useCache:bool=True) -> str:
|
def getString(self, module: str, name: str, useCache: bool = True) -> str:
|
||||||
return self._getGeneral("string", self.client.service.getString, module, name, useCache);
|
return self._getGeneral("string", self.client.service.getString, module, name, useCache)
|
||||||
|
|
||||||
def getInt(self, module:str, name:str, useCache:bool=True) -> int:
|
def getInt(self, module: str, name: str, useCache: bool = True) -> int:
|
||||||
return self._getGeneral("int", self.client.service.getInt, module, name, useCache);
|
return self._getGeneral("int", self.client.service.getInt, module, name, useCache)
|
||||||
|
|
||||||
def getDouble(self, module:str, name:str, useCache:bool=True) -> float:
|
def getDouble(self, module: str, name: str, useCache: bool = True) -> float:
|
||||||
return self._getGeneral("double", self.client.service.getDouble, module, name, useCache);
|
return self._getGeneral("double", self.client.service.getDouble, module, name, useCache)
|
||||||
|
|
||||||
def getBoolean(self, module:str, name:str, useCache:bool=True) -> bool:
|
def getBoolean(self, module: str, name: str, useCache: bool = True) -> bool:
|
||||||
return self._getGeneral("boolean", self.client.service.getBoolean, module, name, useCache);
|
return self._getGeneral("boolean", self.client.service.getBoolean, module, name, useCache)
|
||||||
|
|
||||||
def getList(self, module : str, name:str, useCache:bool=True, sep:str=",") -> Optional[Sequence[str]]:
|
def getList(self, module: str, name: str, useCache: bool = True, sep: str = ",") -> Optional[Sequence[str]]:
|
||||||
s = self.getString(module, name, useCache=useCache);
|
s = self.getString(module, name, useCache=useCache)
|
||||||
if (s == None or s == ""):
|
if (s is None or s == ""):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return s.split(sep);
|
return s.split(sep)
|
||||||
|
@ -6,26 +6,23 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
import lxml.etree as ET # type: ignore
|
||||||
|
|
||||||
import lxml.etree as ET # type: ignore
|
|
||||||
from . import sql_utils
|
from . import sql_utils
|
||||||
import datetime
|
import datetime
|
||||||
from typing import *
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .applus import APplusServer
|
from .applus import APplusServer
|
||||||
|
|
||||||
|
|
||||||
|
def _formatValueForXMLRow(v: Any) -> str:
|
||||||
def _formatValueForXMLRow(v : Any) -> str:
|
|
||||||
"""Hilfsfunktion zum Formatieren eines Wertes für XML"""
|
"""Hilfsfunktion zum Formatieren eines Wertes für XML"""
|
||||||
if (v is None):
|
if (v is None):
|
||||||
return "";
|
return ""
|
||||||
if isinstance(v, (int, float)):
|
if isinstance(v, (int, float)):
|
||||||
return str(v);
|
return str(v)
|
||||||
elif isinstance(v, str):
|
elif isinstance(v, str):
|
||||||
return v;
|
return v
|
||||||
elif isinstance(v, datetime.datetime):
|
elif isinstance(v, datetime.datetime):
|
||||||
return v.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
return v.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||||
elif isinstance(v, datetime.date):
|
elif isinstance(v, datetime.date):
|
||||||
@ -38,7 +35,7 @@ def _formatValueForXMLRow(v : Any) -> str:
|
|||||||
|
|
||||||
class UseXmlRow:
|
class UseXmlRow:
|
||||||
"""
|
"""
|
||||||
Klasse, die eine XML-Datei erzeugen kann, die mittels p2core.useXML
|
Klasse, die eine XML-Datei erzeugen kann, die mittels p2core.useXML
|
||||||
genutzt werden kann. Damit ist es möglich APplus BusinessObjekte zu
|
genutzt werden kann. Damit ist es möglich APplus BusinessObjekte zu
|
||||||
erzeugen, ändern und zu löschen. Im Gegensatz zu direkten DB-Zugriffen,
|
erzeugen, ändern und zu löschen. Im Gegensatz zu direkten DB-Zugriffen,
|
||||||
werden diese Anfragen über den APP-Server ausgeführt. Dabei werden
|
werden diese Anfragen über den APP-Server ausgeführt. Dabei werden
|
||||||
@ -46,13 +43,13 @@ class UseXmlRow:
|
|||||||
Als sehr einfaches Beispiel wird z.B. INSDATE oder UPDDATE automatisch gesetzt.
|
Als sehr einfaches Beispiel wird z.B. INSDATE oder UPDDATE automatisch gesetzt.
|
||||||
Interessanter sind automatische Änderungen und Checks.
|
Interessanter sind automatische Änderungen und Checks.
|
||||||
|
|
||||||
Bei der Benutzung wird zunächst ein Objekt erzeugt, dann evtl.
|
Bei der Benutzung wird zunächst ein Objekt erzeugt, dann evtl.
|
||||||
mittels :meth:`addField` Felder hinzugefügt und schließlich mittels
|
mittels :meth:`addField` Felder hinzugefügt und schließlich mittels
|
||||||
:meth:`exec` an den AppServer übergeben.
|
:meth:`exec` an den AppServer übergeben.
|
||||||
Normalerweise sollte die Klasse nicht direkt, sondern über Unterklassen
|
Normalerweise sollte die Klasse nicht direkt, sondern über Unterklassen
|
||||||
für das Einfügen, Ändern oder Löschen benutzt werden.
|
für das Einfügen, Ändern oder Löschen benutzt werden.
|
||||||
|
|
||||||
:param applus: Verbindung zu APplus
|
:param applus: Verbindung zu APplus
|
||||||
:type applus: APplusServer
|
:type applus: APplusServer
|
||||||
:param table: die Tabelle
|
:param table: die Tabelle
|
||||||
:type table: str
|
:type table: str
|
||||||
@ -60,18 +57,18 @@ class UseXmlRow:
|
|||||||
:type cmd: str
|
:type cmd: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, applus : 'APplusServer', table : str, cmd : str) -> None:
|
def __init__(self, applus: 'APplusServer', table: str, cmd: str) -> None:
|
||||||
self.applus = applus
|
self.applus = applus
|
||||||
self.table = table
|
self.table = table
|
||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
self.fields : Dict[str, Any] = {}
|
self.fields: Dict[str, Any] = {}
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.toprettyxml()
|
return self.toprettyxml()
|
||||||
|
|
||||||
def _buildXML(self) -> ET.Element :
|
def _buildXML(self) -> ET.Element:
|
||||||
"""Hilfsfunktion, die das eigentliche XML baut"""
|
"""Hilfsfunktion, die das eigentliche XML baut"""
|
||||||
row = ET.Element("row", cmd=self.cmd, table=self.table, nsmap={ "dt" : "urn:schemas-microsoft-com:datatypes"});
|
row = ET.Element("row", cmd=self.cmd, table=self.table, nsmap={"dt": "urn:schemas-microsoft-com:datatypes"})
|
||||||
|
|
||||||
for name, value in self.fields.items():
|
for name, value in self.fields.items():
|
||||||
child = ET.Element(name)
|
child = ET.Element(name)
|
||||||
@ -80,20 +77,19 @@ class UseXmlRow:
|
|||||||
|
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
def toprettyxml(self) -> str:
|
||||||
def toprettyxml(self)->str:
|
|
||||||
"""
|
"""
|
||||||
Gibt das formatierte XML aus. Dieses kann per useXML an den AppServer übergeben werden.
|
Gibt das formatierte XML aus. Dieses kann per useXML an den AppServer übergeben werden.
|
||||||
Dies wird mittels :meth:`exec` automatisiert.
|
Dies wird mittels :meth:`exec` automatisiert.
|
||||||
"""
|
"""
|
||||||
return ET.tostring(self._buildXML(), encoding = "unicode", pretty_print=True)
|
return ET.tostring(self._buildXML(), encoding="unicode", pretty_print=True)
|
||||||
|
|
||||||
def getField(self, name:str) -> Any:
|
def getField(self, name: str) -> Any:
|
||||||
"""Liefert den Wert eines gesetzten Feldes"""
|
"""Liefert den Wert eines gesetzten Feldes"""
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
return None
|
return None
|
||||||
name = sql_utils.normaliseDBfield(name);
|
name = sql_utils.normaliseDBfield(name)
|
||||||
|
|
||||||
if name in self.fields:
|
if name in self.fields:
|
||||||
return self.fields[name]
|
return self.fields[name]
|
||||||
@ -102,21 +98,21 @@ class UseXmlRow:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def checkFieldSet(self, name:Optional[str]) -> bool:
|
def checkFieldSet(self, name: Optional[str]) -> bool:
|
||||||
"""Prüft, ob ein Feld gesetzt wurde"""
|
"""Prüft, ob ein Feld gesetzt wurde"""
|
||||||
if name is None:
|
if name is None:
|
||||||
return False
|
return False
|
||||||
name = sql_utils.normaliseDBfield(name)
|
name = sql_utils.normaliseDBfield(name)
|
||||||
return (name in self.fields) or (name == "MANDANT")
|
return (name in self.fields) or (name == "MANDANT")
|
||||||
|
|
||||||
def checkFieldsSet(self, *names : str) -> bool:
|
def checkFieldsSet(self, *names: str) -> bool:
|
||||||
"""Prüft, ob alle übergebenen Felder gesetzt sind"""
|
"""Prüft, ob alle übergebenen Felder gesetzt sind"""
|
||||||
for n in names:
|
for n in names:
|
||||||
if not (self.checkFieldSet(n)):
|
if not (self.checkFieldSet(n)):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def addField(self, name:str|None, value:Any) -> None:
|
def addField(self, name: Optional[str], value: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Fügt ein Feld zum Row-Node hinzu.
|
Fügt ein Feld zum Row-Node hinzu.
|
||||||
|
|
||||||
@ -124,15 +120,14 @@ class UseXmlRow:
|
|||||||
:type name: string
|
:type name: string
|
||||||
:param value: Wert des Feldes
|
:param value: Wert des Feldes
|
||||||
"""
|
"""
|
||||||
if name is None:
|
if name is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.fields[sql_utils.normaliseDBfield(name)] = value
|
self.fields[sql_utils.normaliseDBfield(name)] = value
|
||||||
|
|
||||||
|
def addTimestampField(self, id: int, ts: Optional[bytes] = None) -> None:
|
||||||
def addTimestampField(self, id:int, ts:Optional[bytes]=None) -> None:
|
|
||||||
"""
|
"""
|
||||||
Fügt ein Timestamp-Feld hinzu. Wird kein Timestamp übergeben, wird mittels der ID der aktuelle
|
Fügt ein Timestamp-Feld hinzu. Wird kein Timestamp übergeben, wird mittels der ID der aktuelle
|
||||||
Timestamp aus der DB geladen. Dabei kann ein Fehler auftreten.
|
Timestamp aus der DB geladen. Dabei kann ein Fehler auftreten.
|
||||||
Ein Timestamp-Feld ist für Updates und das Löschen nötig um sicherzustellen, dass die richtige
|
Ein Timestamp-Feld ist für Updates und das Löschen nötig um sicherzustellen, dass die richtige
|
||||||
Version des Objekts geändert oder gelöscht wird. Wird z.B. ein Objekt aus der DB geladen, inspiziert
|
Version des Objekts geändert oder gelöscht wird. Wird z.B. ein Objekt aus der DB geladen, inspiziert
|
||||||
@ -140,59 +135,58 @@ class UseXmlRow:
|
|||||||
So wird sichergestellt, dass nicht ein anderer User zwischenzeitlich Änderungen vornahm. Ist dies
|
So wird sichergestellt, dass nicht ein anderer User zwischenzeitlich Änderungen vornahm. Ist dies
|
||||||
der Fall, wird dann bei "exec" eine Exception geworfen.
|
der Fall, wird dann bei "exec" eine Exception geworfen.
|
||||||
|
|
||||||
:param id: DB-id des Objektes dessen Timestamp hinzugefügt werden soll
|
:param id: DB-id des Objektes dessen Timestamp hinzugefügt werden soll
|
||||||
:type id: string
|
:type id: string
|
||||||
:param ts: Fester Timestamp der verwendet werden soll, wenn None wird der Timestamp aus der DB geladen.
|
:param ts: Fester Timestamp der verwendet werden soll, wenn None wird der Timestamp aus der DB geladen.
|
||||||
:type ts: bytes
|
:type ts: bytes
|
||||||
"""
|
"""
|
||||||
if ts is None:
|
if ts is None:
|
||||||
ts = self.applus.dbQuerySingleValue("select timestamp from " + self.table + " where id = ?", id);
|
ts = self.applus.dbQuerySingleValue("select timestamp from " + self.table + " where id = ?", id)
|
||||||
if ts:
|
if ts:
|
||||||
self.addField("timestamp", ts.hex());
|
self.addField("timestamp", ts.hex())
|
||||||
else:
|
else:
|
||||||
raise Exception("kein Eintrag in Tabelle '" + self.table + " mit ID " + str(id) + " gefunden")
|
raise Exception("kein Eintrag in Tabelle '" + self.table + " mit ID " + str(id) + " gefunden")
|
||||||
|
|
||||||
|
def addTimestampIDFields(self, id: int, ts: Optional[bytes] = None) -> None:
|
||||||
def addTimestampIDFields(self, id:int, ts:Optional[bytes]=None) -> None:
|
|
||||||
"""
|
"""
|
||||||
Fügt ein Timestamp-Feld sowie ein Feld id hinzu. Wird kein Timestamp übergeben, wird mittels der ID der aktuelle
|
Fügt ein Timestamp-Feld sowie ein Feld id hinzu. Wird kein Timestamp übergeben, wird mittels der ID der aktuelle
|
||||||
Timestamp aus der DB geladen. Dabei kann ein Fehler auftreten. Intern wird :meth:`addTimestampField` benutzt.
|
Timestamp aus der DB geladen. Dabei kann ein Fehler auftreten. Intern wird :meth:`addTimestampField` benutzt.
|
||||||
|
|
||||||
:param id: DB-id des Objektes dessen Timestamp hinzugefügt werden soll
|
:param id: DB-id des Objektes dessen Timestamp hinzugefügt werden soll
|
||||||
:type id: string
|
:type id: string
|
||||||
:param ts: Fester Timestamp der verwendet werden soll, wenn None wird der Timestamp aus der DB geladen.
|
:param ts: Fester Timestamp der verwendet werden soll, wenn None wird der Timestamp aus der DB geladen.
|
||||||
:type ts: bytes
|
:type ts: bytes
|
||||||
"""
|
"""
|
||||||
self.addField("id", id)
|
self.addField("id", id)
|
||||||
self.addTimestampField(id, ts=ts);
|
self.addTimestampField(id, ts=ts)
|
||||||
|
|
||||||
def exec(self) -> Any:
|
def exec(self) -> Any:
|
||||||
"""
|
"""
|
||||||
Führt die UseXmlRow mittels useXML aus. Je nach Art der Zeile wird etwas zurückgeliefert oder nicht.
|
Führt die UseXmlRow mittels useXML aus. Je nach Art der Zeile wird etwas zurückgeliefert oder nicht.
|
||||||
In jedem Fall kann eine Exception geworfen werden.
|
In jedem Fall kann eine Exception geworfen werden.
|
||||||
"""
|
"""
|
||||||
return self.applus.useXML(self.toprettyxml());
|
return self.applus.useXML(self.toprettyxml())
|
||||||
|
|
||||||
|
|
||||||
class UseXmlRowInsert(UseXmlRow):
|
class UseXmlRowInsert(UseXmlRow):
|
||||||
"""
|
"""
|
||||||
Klasse, die eine XML-Datei für das Einfügen eines neuen Datensatzes erzeugen kann.
|
Klasse, die eine XML-Datei für das Einfügen eines neuen Datensatzes erzeugen kann.
|
||||||
|
|
||||||
:param applus: Verbindung zu APplus
|
:param applus: Verbindung zu APplus
|
||||||
:type applus: APplusServer
|
:type applus: APplusServer
|
||||||
:param table: die Tabelle
|
:param table: die Tabelle
|
||||||
:type table: string
|
:type table: string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, applus:'APplusServer', table:str) -> None:
|
def __init__(self, applus: 'APplusServer', table: str) -> None:
|
||||||
super().__init__(applus, table, "insert");
|
super().__init__(applus, table, "insert")
|
||||||
|
|
||||||
def insert(self) -> int:
|
def insert(self) -> int:
|
||||||
"""
|
"""
|
||||||
Führt das insert aus. Entweder wird dabei eine Exception geworfen oder die ID des neuen Eintrags zurückgegeben.
|
Führt das insert aus. Entweder wird dabei eine Exception geworfen oder die ID des neuen Eintrags zurückgegeben.
|
||||||
Dies ist eine Umbenennung von :meth:`exec`.
|
Dies ist eine Umbenennung von :meth:`exec`.
|
||||||
"""
|
"""
|
||||||
return super().exec();
|
return super().exec()
|
||||||
|
|
||||||
|
|
||||||
class UseXmlRowDelete(UseXmlRow):
|
class UseXmlRowDelete(UseXmlRow):
|
||||||
@ -201,7 +195,7 @@ class UseXmlRowDelete(UseXmlRow):
|
|||||||
Die Felder `id` und `timestamp` werden automatisch gesetzt.
|
Die Felder `id` und `timestamp` werden automatisch gesetzt.
|
||||||
Dies sind die einzigen Felder, die gesetzt werden sollten.
|
Dies sind die einzigen Felder, die gesetzt werden sollten.
|
||||||
|
|
||||||
:param applus: Verbindung zu APplus
|
:param applus: Verbindung zu APplus
|
||||||
:type applus: APplusServer
|
:type applus: APplusServer
|
||||||
:param table: die Tabelle
|
:param table: die Tabelle
|
||||||
:type table: string
|
:type table: string
|
||||||
@ -211,17 +205,16 @@ class UseXmlRowDelete(UseXmlRow):
|
|||||||
:type ts: bytes optional
|
:type ts: bytes optional
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, applus:'APplusServer', table:str, id:int, ts:Optional[bytes]=None) -> None:
|
def __init__(self, applus: 'APplusServer', table: str, id: int, ts: Optional[bytes] = None) -> None:
|
||||||
super().__init__(applus, table, "delete");
|
super().__init__(applus, table, "delete")
|
||||||
self.addTimestampIDFields(id, ts=ts);
|
self.addTimestampIDFields(id, ts=ts)
|
||||||
|
|
||||||
|
|
||||||
def delete(self) -> None:
|
def delete(self) -> None:
|
||||||
"""
|
"""
|
||||||
Führt das delete aus. Evtl. wird dabei eine Exception geworfen.
|
Führt das delete aus. Evtl. wird dabei eine Exception geworfen.
|
||||||
Dies ist eine Umbenennung von :meth:`exec`.
|
Dies ist eine Umbenennung von :meth:`exec`.
|
||||||
"""
|
"""
|
||||||
super().exec();
|
super().exec()
|
||||||
|
|
||||||
|
|
||||||
class UseXmlRowUpdate(UseXmlRow):
|
class UseXmlRowUpdate(UseXmlRow):
|
||||||
@ -229,7 +222,7 @@ class UseXmlRowUpdate(UseXmlRow):
|
|||||||
Klasse, die eine XML-Datei für das Ändern eines neuen Datensatzes, erzeugen kann.
|
Klasse, die eine XML-Datei für das Ändern eines neuen Datensatzes, erzeugen kann.
|
||||||
Die Felder `id` und `timestamp` werden automatisch gesetzt.
|
Die Felder `id` und `timestamp` werden automatisch gesetzt.
|
||||||
|
|
||||||
:param applus: Verbindung zu APplus
|
:param applus: Verbindung zu APplus
|
||||||
:type applus: APplusServer
|
:type applus: APplusServer
|
||||||
:param table: die Tabelle
|
:param table: die Tabelle
|
||||||
:type table: string
|
:type table: string
|
||||||
@ -239,18 +232,16 @@ class UseXmlRowUpdate(UseXmlRow):
|
|||||||
:type ts: bytes optional
|
:type ts: bytes optional
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, applus : 'APplusServer', table : str, id : int, ts:Optional[bytes]=None) -> None:
|
def __init__(self, applus: 'APplusServer', table: str, id: int, ts: Optional[bytes] = None) -> None:
|
||||||
super().__init__(applus, table, "update");
|
super().__init__(applus, table, "update")
|
||||||
self.addTimestampIDFields(id, ts=ts);
|
self.addTimestampIDFields(id, ts=ts)
|
||||||
|
|
||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
"""
|
"""
|
||||||
Führt das update aus. Evtl. wird dabei eine Exception geworfen.
|
Führt das update aus. Evtl. wird dabei eine Exception geworfen.
|
||||||
Dies ist eine Umbenennung von :meth:`exec`.
|
Dies ist eine Umbenennung von :meth:`exec`.
|
||||||
"""
|
"""
|
||||||
super().exec();
|
super().exec()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UseXmlRowInsertOrUpdate(UseXmlRow):
|
class UseXmlRowInsertOrUpdate(UseXmlRow):
|
||||||
@ -258,34 +249,33 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
|||||||
Klasse, die eine XML-Datei für das Einfügen oder Ändern eines neuen Datensatzes, erzeugen kann.
|
Klasse, die eine XML-Datei für das Einfügen oder Ändern eines neuen Datensatzes, erzeugen kann.
|
||||||
Die Methode `checkExists` erlaubt es zu prüfen, ob ein Objekt bereits existiert. Dafür werden die
|
Die Methode `checkExists` erlaubt es zu prüfen, ob ein Objekt bereits existiert. Dafür werden die
|
||||||
gesetzten Felder mit den Feldern aus eindeutigen Indices verglichen. Existiert ein Objekt bereits, wird
|
gesetzten Felder mit den Feldern aus eindeutigen Indices verglichen. Existiert ein Objekt bereits, wird
|
||||||
ein Update ausgeführt, ansonsten ein Insert. Bei Updates werden die Felder `id` und `timestamp`
|
ein Update ausgeführt, ansonsten ein Insert. Bei Updates werden die Felder `id` und `timestamp`
|
||||||
automatisch gesetzt.
|
automatisch gesetzt.
|
||||||
|
|
||||||
:param applus: Verbindung zu APplus
|
:param applus: Verbindung zu APplus
|
||||||
:type applus: APplusServer
|
:type applus: APplusServer
|
||||||
:param table: die Tabelle
|
:param table: die Tabelle
|
||||||
:type table: string
|
:type table: string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, applus : 'APplusServer', table : str) -> None:
|
def __init__(self, applus: 'APplusServer', table: str) -> None:
|
||||||
super().__init__(applus, table, "");
|
super().__init__(applus, table, "")
|
||||||
|
|
||||||
|
def checkExists(self) -> Optional[int]:
|
||||||
def checkExists(self) -> int|None:
|
|
||||||
"""
|
"""
|
||||||
Prüft, ob der Datensatz bereits in der DB existiert.
|
Prüft, ob der Datensatz bereits in der DB existiert.
|
||||||
Ist dies der Fall, wird die ID geliefert, sonst None
|
Ist dies der Fall, wird die ID geliefert, sonst None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Baue Bedingung
|
# Baue Bedingung
|
||||||
cond = sql_utils.SqlConditionOr();
|
cond = sql_utils.SqlConditionOr()
|
||||||
for idx, fs in self.applus.getUniqueFieldsOfTable(self.table).items():
|
for idx, fs in self.applus.getUniqueFieldsOfTable(self.table).items():
|
||||||
if (self.checkFieldsSet(*fs)):
|
if (self.checkFieldsSet(*fs)):
|
||||||
condIdx = sql_utils.SqlConditionAnd();
|
condIdx = sql_utils.SqlConditionAnd()
|
||||||
for f in fs:
|
for f in fs:
|
||||||
condIdx.addConditionFieldEq(f, self.getField(f))
|
condIdx.addConditionFieldEq(f, self.getField(f))
|
||||||
cond.addCondition(condIdx)
|
cond.addCondition(condIdx)
|
||||||
|
|
||||||
sql = sql_utils.SqlStatementSelect(self.table, "id")
|
sql = sql_utils.SqlStatementSelect(self.table, "id")
|
||||||
sql.where = cond
|
sql.where = cond
|
||||||
return self.applus.dbQuerySingleValue(sql)
|
return self.applus.dbQuerySingleValue(sql)
|
||||||
@ -293,12 +283,12 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
|||||||
def insert(self) -> int:
|
def insert(self) -> int:
|
||||||
"""Führt ein Insert aus. Existiert das Objekt bereits, wird eine Exception geworfen."""
|
"""Führt ein Insert aus. Existiert das Objekt bereits, wird eine Exception geworfen."""
|
||||||
|
|
||||||
r = UseXmlRowInsert(self.applus, self.table)
|
r = UseXmlRowInsert(self.applus, self.table)
|
||||||
for k, v in self.fields.items():
|
for k, v in self.fields.items():
|
||||||
r.addField(k, v)
|
r.addField(k, v)
|
||||||
return r.insert();
|
return r.insert()
|
||||||
|
|
||||||
def update(self, id:Optional[int]=None, ts:Optional[bytes]=None) -> int:
|
def update(self, id: Optional[int] = None, ts: Optional[bytes] = None) -> int:
|
||||||
"""Führt ein Update aus. Falls ID oder Timestamp nicht übergeben werden, wird
|
"""Führt ein Update aus. Falls ID oder Timestamp nicht übergeben werden, wird
|
||||||
nach einem passenden Objekt gesucht. Existiert das Objekt nicht, wird eine Exception geworfen."""
|
nach einem passenden Objekt gesucht. Existiert das Objekt nicht, wird eine Exception geworfen."""
|
||||||
|
|
||||||
@ -307,23 +297,23 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
|||||||
|
|
||||||
if id is None:
|
if id is None:
|
||||||
raise Exception("Update nicht möglich, da kein Objekt für Update gefunden.")
|
raise Exception("Update nicht möglich, da kein Objekt für Update gefunden.")
|
||||||
|
|
||||||
r = UseXmlRowUpdate(self.applus, self.table, id, ts=ts)
|
r = UseXmlRowUpdate(self.applus, self.table, id, ts=ts)
|
||||||
for k, v in self.fields.items():
|
for k, v in self.fields.items():
|
||||||
r.addField(k, v)
|
r.addField(k, v)
|
||||||
r.update();
|
r.update()
|
||||||
return id
|
return id
|
||||||
|
|
||||||
def exec(self) -> int:
|
def exec(self) -> int:
|
||||||
"""
|
"""
|
||||||
Führt entweder ein Update oder ein Insert durch. Dies hängt davon ab, ob das Objekt bereits in
|
Führt entweder ein Update oder ein Insert durch. Dies hängt davon ab, ob das Objekt bereits in
|
||||||
der DB existiert. In jedem Fall wird die ID des erzeugten oder geänderten Objekts geliefert.
|
der DB existiert. In jedem Fall wird die ID des erzeugten oder geänderten Objekts geliefert.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = self.checkExists();
|
id = self.checkExists()
|
||||||
if id == None:
|
if id is None:
|
||||||
return self.insert()
|
return self.insert()
|
||||||
else:
|
else:
|
||||||
return self.update(id=id)
|
return self.update(id=id)
|
||||||
|
|
||||||
def updateOrInsert(self) -> int:
|
def updateOrInsert(self) -> int:
|
||||||
@ -332,4 +322,4 @@ class UseXmlRowInsertOrUpdate(UseXmlRow):
|
|||||||
Dies ist eine Umbenennung von :meth:`exec`.
|
Dies ist eine Umbenennung von :meth:`exec`.
|
||||||
Es wird die ID des Eintrages geliefert
|
Es wird die ID des Eintrages geliefert
|
||||||
"""
|
"""
|
||||||
return self.exec();
|
return self.exec()
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Dupliziert ein oder mehrere APplus Business-Objekte
|
Dupliziert ein oder mehrere APplus Business-Objekte
|
||||||
"""
|
"""
|
||||||
@ -16,58 +14,57 @@ from . import sql_utils
|
|||||||
from . import applus_db
|
from . import applus_db
|
||||||
from . import applus_usexml
|
from . import applus_usexml
|
||||||
from .applus import APplusServer
|
from .applus import APplusServer
|
||||||
import pyodbc # type: ignore
|
import pyodbc # type: ignore
|
||||||
import traceback
|
import traceback
|
||||||
import logging
|
import logging
|
||||||
from typing import *
|
from typing import List, Set, Optional, Dict, Tuple, Sequence, Any, Union
|
||||||
|
|
||||||
logger = logging.getLogger(__name__);
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
noCopyFields = sql_utils.normaliseDBfieldSet({"INSUSER", "UPDDATE", "TIMESTAMP", "MANDANT", "GUID", "ID", "TIMESTAMP_A", "INSDATE", "ID_A", "UPDUSER"})
|
noCopyFields = sql_utils.normaliseDBfieldSet({"INSUSER", "UPDDATE", "TIMESTAMP", "MANDANT", "GUID", "ID", "TIMESTAMP_A", "INSDATE", "ID_A", "UPDUSER"})
|
||||||
"""Menge von Feld-Namen, die nie kopiert werden sollen."""
|
"""Menge von Feld-Namen, die nie kopiert werden sollen."""
|
||||||
|
|
||||||
|
|
||||||
def getFieldsToCopyForTable(server : APplusServer, table : str, force:bool=True) -> Set[str]:
|
def getFieldsToCopyForTable(server: APplusServer, table: str, force: bool = True) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Bestimmt die für eine Tabelle zu kopierenden Spalten. Dazu wird in den XML-Definitionen geschaut.
|
Bestimmt die für eine Tabelle zu kopierenden Spalten. Dazu wird in den XML-Definitionen geschaut.
|
||||||
Ist dort 'include' hinterlegt, werden diese Spalten verwendet. Ansonsten alle nicht generierten Spalten,
|
Ist dort 'include' hinterlegt, werden diese Spalten verwendet. Ansonsten alle nicht generierten Spalten,
|
||||||
ohne die 'exclude' Spalten. In jedem Fall werden Spalten wie "ID", die nie kopiert werden sollten, entfernt.
|
ohne die 'exclude' Spalten. In jedem Fall werden Spalten wie "ID", die nie kopiert werden sollten, entfernt.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
xmlDefs = server.scripttool.getXMLDefinitionObj(table)
|
xmlDefs = server.scripttool.getXMLDefinitionObj(table)
|
||||||
fields : Set[str]
|
fields: Set[str]
|
||||||
if (xmlDefs is None):
|
if (xmlDefs is None):
|
||||||
if not force:
|
if not force:
|
||||||
raise Exception ("Keine XML-Definitionen für '{}' gefunden".format(table));
|
raise Exception("Keine XML-Definitionen für '{}' gefunden".format(table))
|
||||||
(fields, excl) = (set(), True)
|
(fields, excl) = (set(), True)
|
||||||
else:
|
else:
|
||||||
(fields, excl) = xmlDefs.getDuplicate()
|
(fields, excl) = xmlDefs.getDuplicate()
|
||||||
if not excl:
|
if not excl:
|
||||||
return fields.difference(noCopyFields)
|
return fields.difference(noCopyFields)
|
||||||
|
|
||||||
allFields = server.getTableFields(table, isComputed=False)
|
allFields = server.getTableFields(table, isComputed=False)
|
||||||
return allFields.difference(fields).difference(noCopyFields);
|
return allFields.difference(fields).difference(noCopyFields)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FieldsToCopyForTableCache():
|
class FieldsToCopyForTableCache():
|
||||||
"""
|
"""
|
||||||
Cache für welche Felder für welche Tabelle kopiert werden sollen
|
Cache für welche Felder für welche Tabelle kopiert werden sollen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server : APplusServer) -> None:
|
|
||||||
self.server = server
|
|
||||||
self.cache : Dict[str, Set[str]]= {}
|
|
||||||
|
|
||||||
def getFieldsToCopyForTable(self, table : str) -> Set[str]:
|
def __init__(self, server: APplusServer) -> None:
|
||||||
|
self.server = server
|
||||||
|
self.cache: Dict[str, Set[str]] = {}
|
||||||
|
|
||||||
|
def getFieldsToCopyForTable(self, table: str) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Bestimmt die für eine Tabelle zu kopierenden Spalten. Dazu wird in den XML-Definitionen geschaut.
|
Bestimmt die für eine Tabelle zu kopierenden Spalten. Dazu wird in den XML-Definitionen geschaut.
|
||||||
Ist dort 'include' hinterlegt, werden diese Spalten verwendet. Ansonsten alle nicht generierten Spalten,
|
Ist dort 'include' hinterlegt, werden diese Spalten verwendet. Ansonsten alle nicht generierten Spalten,
|
||||||
ohne die 'exclude' Spalten. In jedem Fall werden Spalten wie "ID", die nie kopiert werden sollten, entfernt.
|
ohne die 'exclude' Spalten. In jedem Fall werden Spalten wie "ID", die nie kopiert werden sollten, entfernt.
|
||||||
"""
|
"""
|
||||||
if (table is None):
|
if (table is None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
t = table.upper()
|
t = table.upper()
|
||||||
fs = self.cache.get(t, None)
|
fs = self.cache.get(t, None)
|
||||||
if not (fs is None):
|
if not (fs is None):
|
||||||
@ -78,14 +75,14 @@ class FieldsToCopyForTableCache():
|
|||||||
return fs
|
return fs
|
||||||
|
|
||||||
|
|
||||||
def initFieldsToCopyForTableCacheIfNeeded(server : APplusServer, cache : Optional[FieldsToCopyForTableCache]) -> FieldsToCopyForTableCache:
|
def initFieldsToCopyForTableCacheIfNeeded(server: APplusServer, cache: Optional[FieldsToCopyForTableCache]) -> FieldsToCopyForTableCache:
|
||||||
"""
|
"""
|
||||||
Hilfsfunktion, die einen Cache erzeugt, falls dies noch nicht geschehen ist.
|
Hilfsfunktion, die einen Cache erzeugt, falls dies noch nicht geschehen ist.
|
||||||
"""
|
"""
|
||||||
if cache is None:
|
if cache is None:
|
||||||
return FieldsToCopyForTableCache(server)
|
return FieldsToCopyForTableCache(server)
|
||||||
else:
|
else:
|
||||||
return cache;
|
return cache
|
||||||
|
|
||||||
|
|
||||||
class DuplicateBusinessObject():
|
class DuplicateBusinessObject():
|
||||||
@ -94,34 +91,34 @@ class DuplicateBusinessObject():
|
|||||||
Dies beinhaltet Daten zu abhängigen Objekten sowie die Beziehung zu diesen Objekten. Zu einem Artikel
|
Dies beinhaltet Daten zu abhängigen Objekten sowie die Beziehung zu diesen Objekten. Zu einem Artikel
|
||||||
wird z.B. der Arbeitsplan gespeichert, der wiederum Arbeitsplanpositionen enthält. Als Beziehung ist u.a.
|
wird z.B. der Arbeitsplan gespeichert, der wiederum Arbeitsplanpositionen enthält. Als Beziehung ist u.a.
|
||||||
hinterlegt, dass das Feld "APLAN" der Arbeitsplans dem Feld "ARTIKEL" des Artikels entsprechen muss und dass
|
hinterlegt, dass das Feld "APLAN" der Arbeitsplans dem Feld "ARTIKEL" des Artikels entsprechen muss und dass
|
||||||
"APLAN" aus den Positionen, "APLAN" aus dem APlan entsprichen muss. So kann beim Duplizieren ein
|
"APLAN" aus den Positionen, "APLAN" aus dem APlan entsprichen muss. So kann beim Duplizieren ein
|
||||||
anderer Name des Artikels gesetzt werden und automatisch die Felder der abhängigen Objekte angepasst werden.
|
anderer Name des Artikels gesetzt werden und automatisch die Felder der abhängigen Objekte angepasst werden.
|
||||||
Einige Felder der Beziehung sind dabei statisch, d.h. können direkt aus den zu speichernden Daten abgelesen werden.
|
Einige Felder der Beziehung sind dabei statisch, d.h. können direkt aus den zu speichernden Daten abgelesen werden.
|
||||||
Andere Felder sind dynamisch, d.h. das Parent-Objekt muss in der DB angelegt werden, damit ein solcher dynamischer Wert erstellt
|
Andere Felder sind dynamisch, d.h. das Parent-Objekt muss in der DB angelegt werden, damit ein solcher dynamischer Wert erstellt
|
||||||
und geladen werden kann. Ein typisches Beispiel für ein dynamisches Feld ist "GUID".
|
und geladen werden kann. Ein typisches Beispiel für ein dynamisches Feld ist "GUID".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, table : str, fields : Dict[str, Any], fieldsNotCopied:Dict[str, Any]={}, allowUpdate:bool=False) -> None:
|
def __init__(self, table: str, fields: Dict[str, Any], fieldsNotCopied: Dict[str, Any] = {}, allowUpdate: bool = False) -> None:
|
||||||
self.table = table
|
self.table = table
|
||||||
"""für welche Tabelle ist das BusinessObject"""
|
"""für welche Tabelle ist das BusinessObject"""
|
||||||
|
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
"""die Daten"""
|
"""die Daten"""
|
||||||
|
|
||||||
self.fieldsNotCopied = fieldsNotCopied
|
self.fieldsNotCopied = fieldsNotCopied
|
||||||
"""Datenfelder, die im Original vorhanden sind, aber nicht kopiert werden sollen"""
|
"""Datenfelder, die im Original vorhanden sind, aber nicht kopiert werden sollen"""
|
||||||
|
|
||||||
self.dependentObjs : List[Dict[str, Any]] = []
|
self.dependentObjs: List[Dict[str, Any]] = []
|
||||||
"""Abhängige Objekte"""
|
"""Abhängige Objekte"""
|
||||||
|
|
||||||
self.allowUpdate = allowUpdate
|
self.allowUpdate = allowUpdate
|
||||||
"""Erlaube Updates statt Fehlern, wenn Objekt schon in DB existiert"""
|
"""Erlaube Updates statt Fehlern, wenn Objekt schon in DB existiert"""
|
||||||
|
|
||||||
def addDependentBusinessObject(self, dObj : Optional['DuplicateBusinessObject'], *args : Tuple[str, str]) -> None:
|
def addDependentBusinessObject(self, dObj: Optional['DuplicateBusinessObject'], *args: Tuple[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Fügt ein neues Unterobjekt zum DuplicateBusinessObject hinzu.
|
Fügt ein neues Unterobjekt zum DuplicateBusinessObject hinzu.
|
||||||
Dabei handelt es sich selbst um ein DuplicateBusinessObject, das zusammen mit dem
|
Dabei handelt es sich selbst um ein DuplicateBusinessObject, das zusammen mit dem
|
||||||
Parent-Objekt dupliziert werden sollen. Zum Beispiel sollen zu einem
|
Parent-Objekt dupliziert werden sollen. Zum Beispiel sollen zu einem
|
||||||
Auftrag auch die Positionen dupliziert werden.
|
Auftrag auch die Positionen dupliziert werden.
|
||||||
Zusätzlich zum Objekt selbst können mehrere (keine, eine oder viele)
|
Zusätzlich zum Objekt selbst können mehrere (keine, eine oder viele)
|
||||||
Paare von Feldern übergeben werden. Ein Paar ("pf", "sf") verbindet das
|
Paare von Feldern übergeben werden. Ein Paar ("pf", "sf") verbindet das
|
||||||
@ -136,20 +133,20 @@ class DuplicateBusinessObject():
|
|||||||
:param args: Liste von Tupeln, die Parent- und Sub-Objekt-Felder miteinander verbinden
|
:param args: Liste von Tupeln, die Parent- und Sub-Objekt-Felder miteinander verbinden
|
||||||
"""
|
"""
|
||||||
if (dObj is None):
|
if (dObj is None):
|
||||||
return
|
return
|
||||||
|
|
||||||
args2= {}
|
args2 = {}
|
||||||
for f1, f2 in args:
|
for f1, f2 in args:
|
||||||
args2[sql_utils.normaliseDBfield(f1)] = sql_utils.normaliseDBfield(f2)
|
args2[sql_utils.normaliseDBfield(f1)] = sql_utils.normaliseDBfield(f2)
|
||||||
|
|
||||||
self.dependentObjs.append({
|
self.dependentObjs.append({
|
||||||
"dependentObj" : dObj,
|
"dependentObj": dObj,
|
||||||
"connection" : args2
|
"connection": args2
|
||||||
})
|
})
|
||||||
|
|
||||||
def getField(self, field:str, onlyCopied:bool=False) -> Any:
|
def getField(self, field: str, onlyCopied: bool = False) -> Any:
|
||||||
"""
|
"""
|
||||||
Schlägt den Wert eines Feldes nach. Wenn onlyCopied gesetzt ist, werden nur Felder zurückgeliefert, die auch kopiert
|
Schlägt den Wert eines Feldes nach. Wenn onlyCopied gesetzt ist, werden nur Felder zurückgeliefert, die auch kopiert
|
||||||
werden sollen.
|
werden sollen.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -159,40 +156,40 @@ class DuplicateBusinessObject():
|
|||||||
|
|
||||||
if (not onlyCopied) and (f in self.fieldsNotCopied):
|
if (not onlyCopied) and (f in self.fieldsNotCopied):
|
||||||
return self.fieldsNotCopied[f]
|
return self.fieldsNotCopied[f]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def insert(self, server : APplusServer) -> applus_db.DBTableIDs:
|
def insert(self, server: APplusServer) -> applus_db.DBTableIDs:
|
||||||
"""
|
"""
|
||||||
Fügt alle Objekte zur DB hinzu. Es wird die Menge der IDs der erzeugten
|
Fügt alle Objekte zur DB hinzu. Es wird die Menge der IDs der erzeugten
|
||||||
Objekte gruppiert nach Tabellen erzeugt. Falls ein Datensatz schon
|
Objekte gruppiert nach Tabellen erzeugt. Falls ein Datensatz schon
|
||||||
existiert, wird dieser entweder aktualisiert oder eine Fehlermeldung
|
existiert, wird dieser entweder aktualisiert oder eine Fehlermeldung
|
||||||
geworfen. Geliefert wird die Menge aller Eingefügten Objekte mit ihrer ID.
|
geworfen. Geliefert wird die Menge aller Eingefügten Objekte mit ihrer ID.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res = applus_db.DBTableIDs()
|
res = applus_db.DBTableIDs()
|
||||||
|
|
||||||
def insertDO(do : 'DuplicateBusinessObject') -> Optional[int]:
|
def insertDO(do: 'DuplicateBusinessObject') -> Optional[int]:
|
||||||
nonlocal res
|
nonlocal res
|
||||||
insertRow : applus_usexml.UseXmlRow
|
insertRow: applus_usexml.UseXmlRow
|
||||||
if do.allowUpdate:
|
if do.allowUpdate:
|
||||||
insertRow = server.mkUseXMLRowInsertOrUpdate(do.table);
|
insertRow = server.mkUseXMLRowInsertOrUpdate(do.table)
|
||||||
else:
|
else:
|
||||||
insertRow = server.mkUseXMLRowInsert(do.table);
|
insertRow = server.mkUseXMLRowInsert(do.table)
|
||||||
|
|
||||||
for f, v in do.fields.items():
|
for f, v in do.fields.items():
|
||||||
insertRow.addField(f, v)
|
insertRow.addField(f, v)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
id = insertRow.exec()
|
id = insertRow.exec()
|
||||||
res.add(do.table, id)
|
res.add(do.table, id)
|
||||||
return id
|
return id
|
||||||
except:
|
except:
|
||||||
msg = traceback.format_exc();
|
msg = traceback.format_exc()
|
||||||
logger.error("Exception inserting BusinessObjekt: %s\n%s", str(insertRow), msg)
|
logger.error("Exception inserting BusinessObjekt: %s\n%s", str(insertRow), msg)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def insertDep(do : 'DuplicateBusinessObject', doID : int, so : 'DuplicateBusinessObject', connect : Dict[str,str]) -> None:
|
def insertDep(do: 'DuplicateBusinessObject', doID: int, so: 'DuplicateBusinessObject', connect: Dict[str, str]) -> None:
|
||||||
nonlocal res
|
nonlocal res
|
||||||
|
|
||||||
# Abbruch, wenn do nicht eingefügt wurde
|
# Abbruch, wenn do nicht eingefügt wurde
|
||||||
@ -206,10 +203,10 @@ class DuplicateBusinessObject():
|
|||||||
so.fields[fs] = do.fields[fd]
|
so.fields[fs] = do.fields[fd]
|
||||||
else:
|
else:
|
||||||
connectMissing[fd] = fs
|
connectMissing[fd] = fs
|
||||||
|
|
||||||
# load missing fields from DB
|
# load missing fields from DB
|
||||||
if len(connectMissing) > 0:
|
if len(connectMissing) > 0:
|
||||||
sql = sql_utils.SqlStatementSelect(do.table);
|
sql = sql_utils.SqlStatementSelect(do.table)
|
||||||
sql.where.addConditionFieldEq("id", doID)
|
sql.where.addConditionFieldEq("id", doID)
|
||||||
for fd in connectMissing:
|
for fd in connectMissing:
|
||||||
sql.addFields(fd)
|
sql.addFields(fd)
|
||||||
@ -224,19 +221,17 @@ class DuplicateBusinessObject():
|
|||||||
if not (id is None):
|
if not (id is None):
|
||||||
insertDeps(so, id)
|
insertDeps(so, id)
|
||||||
|
|
||||||
|
def insertDeps(do: 'DuplicateBusinessObject', doID: int) -> None:
|
||||||
def insertDeps(do : 'DuplicateBusinessObject', doID : int) -> None:
|
|
||||||
for so in do.dependentObjs:
|
for so in do.dependentObjs:
|
||||||
insertDep(do, doID, so["dependentObj"], so["connection"])
|
insertDep(do, doID, so["dependentObj"], so["connection"])
|
||||||
|
|
||||||
topID = insertDO(self)
|
topID = insertDO(self)
|
||||||
if not (topID is None):
|
if not (topID is None):
|
||||||
insertDeps(self, topID)
|
insertDeps(self, topID)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def setFields(self, upds: Dict[str, Any]) -> None:
|
||||||
def setFields(self, upds : Dict[str, Any]) -> None:
|
|
||||||
"""
|
"""
|
||||||
Setzt Felder des DuplicateBusinessObjektes und falls nötig seiner Unterobjekte.
|
Setzt Felder des DuplicateBusinessObjektes und falls nötig seiner Unterobjekte.
|
||||||
So kann zum Beispiel die Nummer vor dem Speichern geändert werden.
|
So kann zum Beispiel die Nummer vor dem Speichern geändert werden.
|
||||||
@ -244,11 +239,11 @@ class DuplicateBusinessObject():
|
|||||||
:param upds: Dictionary mit zu setzenden Werten
|
:param upds: Dictionary mit zu setzenden Werten
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setFieldsInternal(dobj : 'DuplicateBusinessObject', upds : Dict[str, Any]) -> None:
|
def setFieldsInternal(dobj: 'DuplicateBusinessObject', upds: Dict[str, Any]) -> None:
|
||||||
# setze alle Felder des Hauptobjekts
|
# setze alle Felder des Hauptobjekts
|
||||||
for f, v in upds.items():
|
for f, v in upds.items():
|
||||||
dobj.fields[f] = v
|
dobj.fields[f] = v
|
||||||
|
|
||||||
# verarbeite alle Subobjekte
|
# verarbeite alle Subobjekte
|
||||||
for su in dobj.dependentObjs:
|
for su in dobj.dependentObjs:
|
||||||
subupds = {}
|
subupds = {}
|
||||||
@ -256,70 +251,66 @@ class DuplicateBusinessObject():
|
|||||||
if fp in upds:
|
if fp in upds:
|
||||||
subupds[fs] = upds[fp]
|
subupds[fs] = upds[fp]
|
||||||
setFieldsInternal(su["dependentObj"], subupds)
|
setFieldsInternal(su["dependentObj"], subupds)
|
||||||
|
|
||||||
|
|
||||||
updsNorm : Dict[str, Any] = {}
|
updsNorm: Dict[str, Any] = {}
|
||||||
for f, v in upds.items():
|
for f, v in upds.items():
|
||||||
updsNorm[sql_utils.normaliseDBfield(f)] = v
|
updsNorm[sql_utils.normaliseDBfield(f)] = v
|
||||||
setFieldsInternal(self, updsNorm)
|
setFieldsInternal(self, updsNorm)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _loadDBDuplicateBusinessObjectDict(
|
def _loadDBDuplicateBusinessObjectDict(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
table : str,
|
table: str,
|
||||||
row : pyodbc.Row,
|
row: pyodbc.Row,
|
||||||
cache:Optional[FieldsToCopyForTableCache]=None,
|
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||||
allowUpdate:bool=False) -> Optional[DuplicateBusinessObject]:
|
allowUpdate: bool = False) -> Optional[DuplicateBusinessObject]:
|
||||||
"""
|
"""
|
||||||
Hilfsfunktion, die ein DuplicateBusinessObjekt erstellt. Die Daten stammen aus
|
Hilfsfunktion, die ein DuplicateBusinessObjekt erstellt. Die Daten stammen aus
|
||||||
einer PyOdbc Zeile. So ist es möglich, mit nur einem SQL-Statement,
|
einer PyOdbc Zeile. So ist es möglich, mit nur einem SQL-Statement,
|
||||||
mehrere DuplicateBusinessObjekte zu erstellen.
|
mehrere DuplicateBusinessObjekte zu erstellen.
|
||||||
|
|
||||||
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
||||||
:param table: Tabelle für das neue DuplicateBusinessObjekt
|
:param table: Tabelle für das neue DuplicateBusinessObjekt
|
||||||
:param row: die Daten als PyODBC Zeile
|
:param row: die Daten als PyODBC Zeile
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:return: das neue DuplicateBusinessObject
|
:return: das neue DuplicateBusinessObject
|
||||||
"""
|
"""
|
||||||
table = table.upper();
|
table = table.upper()
|
||||||
|
|
||||||
def getFieldsToCopy() -> Set[str]:
|
def getFieldsToCopy() -> Set[str]:
|
||||||
if cache is None:
|
if cache is None:
|
||||||
return getFieldsToCopyForTable(server, table)
|
return getFieldsToCopyForTable(server, table)
|
||||||
else:
|
else:
|
||||||
return cache.getFieldsToCopyForTable(table)
|
return cache.getFieldsToCopyForTable(table)
|
||||||
|
|
||||||
|
|
||||||
def getFields() -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
def getFields() -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
||||||
ftc = getFieldsToCopy()
|
ftc = getFieldsToCopy()
|
||||||
fields = {}
|
fields = {}
|
||||||
fieldsNotCopied = {}
|
fieldsNotCopied = {}
|
||||||
for f, v in applus_db.row_to_dict(row).items():
|
for f, v in applus_db.row_to_dict(row).items():
|
||||||
f = sql_utils.normaliseDBfield(f);
|
f = sql_utils.normaliseDBfield(f)
|
||||||
if f in ftc:
|
if f in ftc:
|
||||||
fields[f] = v
|
fields[f] = v
|
||||||
else:
|
else:
|
||||||
fieldsNotCopied[f] = v
|
fieldsNotCopied[f] = v
|
||||||
return (fields, fieldsNotCopied)
|
return (fields, fieldsNotCopied)
|
||||||
|
|
||||||
|
|
||||||
if (row is None):
|
if (row is None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
(fields, fieldsNotCopied) = getFields()
|
(fields, fieldsNotCopied) = getFields()
|
||||||
return DuplicateBusinessObject(table, fields, fieldsNotCopied=fieldsNotCopied, allowUpdate=allowUpdate)
|
return DuplicateBusinessObject(table, fields, fieldsNotCopied=fieldsNotCopied, allowUpdate=allowUpdate)
|
||||||
|
|
||||||
|
|
||||||
def loadDBDuplicateBusinessObject(
|
def loadDBDuplicateBusinessObject(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
table : str,
|
table: str,
|
||||||
cond : sql_utils.SqlCondition,
|
cond: sql_utils.SqlCondition,
|
||||||
cache : Optional[FieldsToCopyForTableCache]=None,
|
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||||
allowUpdate : bool = False) -> Optional[DuplicateBusinessObject]:
|
allowUpdate: bool = False) -> Optional[DuplicateBusinessObject]:
|
||||||
"""
|
"""
|
||||||
Läd ein einzelnes DuplicateBusinessObjekt aus der DB. Die Bedingung sollte dabei
|
Läd ein einzelnes DuplicateBusinessObjekt aus der DB. Die Bedingung sollte dabei
|
||||||
einen eindeutigen Datensatz auswählen. Werden mehrere zurückgeliefert, wird ein
|
einen eindeutigen Datensatz auswählen. Werden mehrere zurückgeliefert, wird ein
|
||||||
zufälliger ausgewählt. Wird kein Datensatz gefunden, wird None geliefert.
|
zufälliger ausgewählt. Wird kein Datensatz gefunden, wird None geliefert.
|
||||||
|
|
||||||
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
||||||
@ -328,30 +319,31 @@ def loadDBDuplicateBusinessObject(
|
|||||||
:type table: str
|
:type table: str
|
||||||
:param cond: SQL-Bedingung zur Auswahl eines Objektes
|
:param cond: SQL-Bedingung zur Auswahl eines Objektes
|
||||||
:type cond: sql_utils.SqlCondition
|
:type cond: sql_utils.SqlCondition
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
:param allowUpdate: ist Update statt Insert erlaubt?
|
:param allowUpdate: ist Update statt Insert erlaubt?
|
||||||
:type allowUpdate: bool
|
:type allowUpdate: bool
|
||||||
:return: das neue DuplicateBusinessObject
|
:return: das neue DuplicateBusinessObject
|
||||||
:rtype: Optional[DuplicateBusinessObject]
|
:rtype: Optional[DuplicateBusinessObject]
|
||||||
"""
|
"""
|
||||||
table = table.upper();
|
table = table.upper()
|
||||||
|
|
||||||
def getRow() -> pyodbc.Row:
|
def getRow() -> pyodbc.Row:
|
||||||
sql = sql_utils.SqlStatementSelect(table)
|
sql = sql_utils.SqlStatementSelect(table)
|
||||||
sql.setTop(1)
|
sql.setTop(1)
|
||||||
sql.where.addCondition(cond);
|
sql.where.addCondition(cond)
|
||||||
return server.dbQuerySingleRow(sql)
|
return server.dbQuerySingleRow(sql)
|
||||||
|
|
||||||
return _loadDBDuplicateBusinessObjectDict(server, table, getRow(), cache=cache, allowUpdate=allowUpdate);
|
return _loadDBDuplicateBusinessObjectDict(server, table, getRow(), cache=cache, allowUpdate=allowUpdate)
|
||||||
|
|
||||||
|
|
||||||
def loadDBDuplicateBusinessObjectSimpleCond(
|
def loadDBDuplicateBusinessObjectSimpleCond(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
table : str,
|
table: str,
|
||||||
field : str,
|
field: str,
|
||||||
value : Optional[Union[sql_utils.SqlValue, bool]],
|
value: Union[sql_utils.SqlValue, bool, None],
|
||||||
cache : Optional[FieldsToCopyForTableCache]=None,
|
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||||
allowUpdate : bool = False) -> Optional[DuplicateBusinessObject]:
|
allowUpdate: bool = False) -> Optional[DuplicateBusinessObject]:
|
||||||
"""
|
"""
|
||||||
Wrapper für loadDBDuplicateBusinessObject, das eine einfache Bedingung benutzt,
|
Wrapper für loadDBDuplicateBusinessObject, das eine einfache Bedingung benutzt,
|
||||||
bei der ein Feld einen bestimmten Wert haben muss.
|
bei der ein Feld einen bestimmten Wert haben muss.
|
||||||
@ -363,21 +355,21 @@ def loadDBDuplicateBusinessObjectSimpleCond(
|
|||||||
:param field: Feld für Bedingung
|
:param field: Feld für Bedingung
|
||||||
:type field: str
|
:type field: str
|
||||||
:param value: Wert des Feldes für Bedingung
|
:param value: Wert des Feldes für Bedingung
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
:return: das neue DuplicateBusinessObject
|
:return: das neue DuplicateBusinessObject
|
||||||
:rtype: Optional[DuplicateBusinessObject]
|
:rtype: Optional[DuplicateBusinessObject]
|
||||||
"""
|
"""
|
||||||
cond = sql_utils.SqlConditionFieldEq(field, value)
|
cond = sql_utils.SqlConditionFieldEq(field, value)
|
||||||
return loadDBDuplicateBusinessObject(server, table, cond, cache=cache, allowUpdate=allowUpdate)
|
return loadDBDuplicateBusinessObject(server, table, cond, cache=cache, allowUpdate=allowUpdate)
|
||||||
|
|
||||||
|
|
||||||
def loadDBDuplicateBusinessObjects(
|
def loadDBDuplicateBusinessObjects(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
table : str,
|
table: str,
|
||||||
cond : sql_utils.SqlCondition,
|
cond: sql_utils.SqlCondition,
|
||||||
cache : Optional[FieldsToCopyForTableCache]=None,
|
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||||
allowUpdate : bool = False) -> Sequence[DuplicateBusinessObject]:
|
allowUpdate: bool = False) -> Sequence[DuplicateBusinessObject]:
|
||||||
"""
|
"""
|
||||||
Läd eine Liste von DuplicateBusinessObjekten aus der DB. Die Bedingung kann mehrere Datensätze auswählen.
|
Läd eine Liste von DuplicateBusinessObjekten aus der DB. Die Bedingung kann mehrere Datensätze auswählen.
|
||||||
|
|
||||||
@ -387,7 +379,7 @@ def loadDBDuplicateBusinessObjects(
|
|||||||
:type table: str
|
:type table: str
|
||||||
:param cond: SQL-Bedingung zur Auswahl eines Objektes
|
:param cond: SQL-Bedingung zur Auswahl eines Objektes
|
||||||
:type cond: sql_utils.SqlCondition
|
:type cond: sql_utils.SqlCondition
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
:return: Liste der neuen DuplicateBusinessObjects
|
:return: Liste der neuen DuplicateBusinessObjects
|
||||||
:rtype: Sequence[DuplicateBusinessObject]
|
:rtype: Sequence[DuplicateBusinessObject]
|
||||||
@ -395,20 +387,21 @@ def loadDBDuplicateBusinessObjects(
|
|||||||
table = table.upper()
|
table = table.upper()
|
||||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||||
|
|
||||||
def processRow(r : pyodbc.Row) -> Optional[DuplicateBusinessObject]:
|
def processRow(r: pyodbc.Row) -> Optional[DuplicateBusinessObject]:
|
||||||
return _loadDBDuplicateBusinessObjectDict(server, table, r, cache=cache, allowUpdate=allowUpdate)
|
return _loadDBDuplicateBusinessObjectDict(server, table, r, cache=cache, allowUpdate=allowUpdate)
|
||||||
|
|
||||||
sql = sql_utils.SqlStatementSelect(table)
|
sql = sql_utils.SqlStatementSelect(table)
|
||||||
sql.where.addCondition(cond)
|
sql.where.addCondition(cond)
|
||||||
return server.dbQueryAll(sql, apply=processRow)
|
return server.dbQueryAll(sql, apply=processRow)
|
||||||
|
|
||||||
|
|
||||||
def loadDBDuplicateBusinessObjectsSimpleCond(
|
def loadDBDuplicateBusinessObjectsSimpleCond(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
table : str,
|
table: str,
|
||||||
field : str,
|
field: str,
|
||||||
value : Optional[Union[sql_utils.SqlValue, bool]],
|
value: Union[sql_utils.SqlValue, bool, None],
|
||||||
cache : Optional[FieldsToCopyForTableCache]=None,
|
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||||
allowUpdate : bool = False) -> Sequence[DuplicateBusinessObject]:
|
allowUpdate: bool = False) -> Sequence[DuplicateBusinessObject]:
|
||||||
"""
|
"""
|
||||||
Wrapper für loadDBDuplicateBusinessObjects, das eine einfache Bedingung benutzt,
|
Wrapper für loadDBDuplicateBusinessObjects, das eine einfache Bedingung benutzt,
|
||||||
bei der ein Feld einen bestimmten Wert haben muss.
|
bei der ein Feld einen bestimmten Wert haben muss.
|
||||||
@ -419,90 +412,91 @@ def loadDBDuplicateBusinessObjectsSimpleCond(
|
|||||||
:type table: str
|
:type table: str
|
||||||
:param field: Feld für Bedingung
|
:param field: Feld für Bedingung
|
||||||
:param value: Wert des Feldes für Bedingung
|
:param value: Wert des Feldes für Bedingung
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
:return: Liste der neuen DuplicateBusinessObjects
|
:return: Liste der neuen DuplicateBusinessObjects
|
||||||
:rtype: Sequence[DuplicateBusinessObject]
|
:rtype: Sequence[DuplicateBusinessObject]
|
||||||
"""
|
"""
|
||||||
cond = sql_utils.SqlConditionFieldEq(field, value)
|
cond = sql_utils.SqlConditionFieldEq(field, value)
|
||||||
return loadDBDuplicateBusinessObjects(server, table, cond, cache=cache, allowUpdate=allowUpdate)
|
return loadDBDuplicateBusinessObjects(server, table, cond, cache=cache, allowUpdate=allowUpdate)
|
||||||
|
|
||||||
|
|
||||||
# Im Laufe der Zeit sollten load-Funktionen für verschiedene BusinessObjekte
|
# Im Laufe der Zeit sollten load-Funktionen für verschiedene BusinessObjekte
|
||||||
# erstellt werden. Dies erfolgt immer, wenn eine solche Funktion wirklich
|
# erstellt werden. Dies erfolgt immer, wenn eine solche Funktion wirklich
|
||||||
# benutzt werden soll
|
# benutzt werden soll
|
||||||
|
|
||||||
def loadDBDuplicateAPlan(
|
def loadDBDuplicateAPlan(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
aplan : str,
|
aplan: str,
|
||||||
cache:Optional[FieldsToCopyForTableCache]=None) -> Optional[DuplicateBusinessObject]:
|
cache: Optional[FieldsToCopyForTableCache] = None) -> Optional[DuplicateBusinessObject]:
|
||||||
"""
|
"""
|
||||||
Erstelle DuplicateBusinessObject für einzelnen Arbeitsplan.
|
Erstelle DuplicateBusinessObject für einzelnen Arbeitsplan.
|
||||||
|
|
||||||
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
||||||
:type server: APplusServer
|
:type server: APplusServer
|
||||||
:param aplan: Aplan, der kopiert werden soll.
|
:param aplan: Aplan, der kopiert werden soll.
|
||||||
:type aplan: str
|
:type aplan: str
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
:return: das neue DuplicateBusinessObject
|
:return: das neue DuplicateBusinessObject
|
||||||
:rtype: DuplicateBusinessObject
|
:rtype: DuplicateBusinessObject
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||||
boMain = loadDBDuplicateBusinessObjectSimpleCond(server, "aplan", "APLAN", aplan, cache=cache)
|
boMain = loadDBDuplicateBusinessObjectSimpleCond(server, "aplan", "APLAN", aplan, cache=cache)
|
||||||
if boMain is None:
|
if boMain is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for so in loadDBDuplicateBusinessObjectsSimpleCond(server, "aplanpos", "APLAN", aplan, cache=cache):
|
for so in loadDBDuplicateBusinessObjectsSimpleCond(server, "aplanpos", "APLAN", aplan, cache=cache):
|
||||||
boMain.addDependentBusinessObject(so, ("aplan", "aplan"))
|
boMain.addDependentBusinessObject(so, ("aplan", "aplan"))
|
||||||
|
|
||||||
return boMain
|
return boMain
|
||||||
|
|
||||||
|
|
||||||
def loadDBDuplicateStueli(server : APplusServer, stueli : str, cache:Optional[FieldsToCopyForTableCache]=None) -> Optional[DuplicateBusinessObject]:
|
def loadDBDuplicateStueli(server: APplusServer, stueli: str, cache: Optional[FieldsToCopyForTableCache] = None) -> Optional[DuplicateBusinessObject]:
|
||||||
"""
|
"""
|
||||||
Erstelle DuplicateBusinessObject für einzelne Stückliste.
|
Erstelle DuplicateBusinessObject für einzelne Stückliste.
|
||||||
|
|
||||||
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
||||||
:type server: APplusServer
|
:type server: APplusServer
|
||||||
:param stueli: Stückliste, die kopiert werden soll.
|
:param stueli: Stückliste, die kopiert werden soll.
|
||||||
:type stueli: str
|
:type stueli: str
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
:return: das neue DuplicateBusinessObject
|
:return: das neue DuplicateBusinessObject
|
||||||
:rtype: Optional[DuplicateBusinessObject]
|
:rtype: Optional[DuplicateBusinessObject]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||||
boMain = loadDBDuplicateBusinessObjectSimpleCond(server, "stueli", "stueli", stueli, cache=cache)
|
boMain = loadDBDuplicateBusinessObjectSimpleCond(server, "stueli", "stueli", stueli, cache=cache)
|
||||||
if boMain is None:
|
if boMain is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for so in loadDBDuplicateBusinessObjectsSimpleCond(server, "stuelipos", "stueli", stueli, cache=cache):
|
for so in loadDBDuplicateBusinessObjectsSimpleCond(server, "stuelipos", "stueli", stueli, cache=cache):
|
||||||
boMain.addDependentBusinessObject(so, ("stueli", "stueli"))
|
boMain.addDependentBusinessObject(so, ("stueli", "stueli"))
|
||||||
|
|
||||||
return boMain
|
return boMain
|
||||||
|
|
||||||
|
|
||||||
def addSachgruppeDependentObjects(
|
def addSachgruppeDependentObjects(
|
||||||
do : DuplicateBusinessObject,
|
do: DuplicateBusinessObject,
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
cache:Optional[FieldsToCopyForTableCache]=None) -> None:
|
cache: Optional[FieldsToCopyForTableCache] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Fügt Unterobjekte hinzu, die die Sachgruppenwerte kopieren.
|
Fügt Unterobjekte hinzu, die die Sachgruppenwerte kopieren.
|
||||||
|
|
||||||
:param do: zu erweiterndes DuplicateBusinessObject
|
:param do: zu erweiterndes DuplicateBusinessObject
|
||||||
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
||||||
:type server: APplusServer
|
:type server: APplusServer
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||||
klasse = do.fields.get(sql_utils.normaliseDBfield("SACHGRUPPENKLASSE"), None)
|
klasse = do.fields.get(sql_utils.normaliseDBfield("SACHGRUPPENKLASSE"), None)
|
||||||
if (klasse == None):
|
if (klasse is None):
|
||||||
# keine Klasse gesetzt, nichts zu kopieren
|
# keine Klasse gesetzt, nichts zu kopieren
|
||||||
return
|
return
|
||||||
|
|
||||||
# bestimme alle Gruppen
|
# bestimme alle Gruppen
|
||||||
def loadGruppen() -> Sequence[str]:
|
def loadGruppen() -> Sequence[str]:
|
||||||
@ -511,7 +505,7 @@ def addSachgruppeDependentObjects(
|
|||||||
sql.where.addConditionFieldEq("tabelle", do.table)
|
sql.where.addConditionFieldEq("tabelle", do.table)
|
||||||
return server.dbQueryAll(sql, apply=lambda r: r.sachgruppe)
|
return server.dbQueryAll(sql, apply=lambda r: r.sachgruppe)
|
||||||
|
|
||||||
gruppen = loadGruppen();
|
gruppen = loadGruppen()
|
||||||
|
|
||||||
# Gruppe bearbeiten
|
# Gruppe bearbeiten
|
||||||
def processGruppen() -> None:
|
def processGruppen() -> None:
|
||||||
@ -525,26 +519,24 @@ def addSachgruppeDependentObjects(
|
|||||||
for so in loadDBDuplicateBusinessObjects(server, "sachwert", cond, cache=cache, allowUpdate=True):
|
for so in loadDBDuplicateBusinessObjects(server, "sachwert", cond, cache=cache, allowUpdate=True):
|
||||||
do.addDependentBusinessObject(so, ("guid", "instanzguid"))
|
do.addDependentBusinessObject(so, ("guid", "instanzguid"))
|
||||||
|
|
||||||
|
|
||||||
processGruppen()
|
processGruppen()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def loadDBDuplicateArtikel(
|
def loadDBDuplicateArtikel(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
artikel : str,
|
artikel: str,
|
||||||
cache:Optional[FieldsToCopyForTableCache]=None,
|
cache: Optional[FieldsToCopyForTableCache] = None,
|
||||||
dupAplan:bool=True,
|
dupAplan: bool = True,
|
||||||
dupStueli:bool=True) -> Optional[DuplicateBusinessObject]:
|
dupStueli: bool = True) -> Optional[DuplicateBusinessObject]:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Erstelle DuplicateBusinessObject für einzelnen Artikel.
|
Erstelle DuplicateBusinessObject für einzelnen Artikel.
|
||||||
|
|
||||||
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
:param server: Verbindung zum APP-Server, benutzt zum Nachschlagen der zu kopierenden Felder
|
||||||
:type server: APplusServer
|
:type server: APplusServer
|
||||||
:param artikel: Artikel, der kopiert werden soll
|
:param artikel: Artikel, der kopiert werden soll
|
||||||
:type artikel: str
|
:type artikel: str
|
||||||
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
:param cache: Cache, so dass benötigte Felder nicht immer wieder neu berechnet werden müssen
|
||||||
:type cache: Optional[FieldsToCopyForTableCache]
|
:type cache: Optional[FieldsToCopyForTableCache]
|
||||||
:param dupAplan: Arbeitsplan duplizieren?
|
:param dupAplan: Arbeitsplan duplizieren?
|
||||||
:type dupAplan: bool optional
|
:type dupAplan: bool optional
|
||||||
@ -554,7 +546,7 @@ def loadDBDuplicateArtikel(
|
|||||||
:rtype: DuplicateBusinessObject
|
:rtype: DuplicateBusinessObject
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache);
|
cache = initFieldsToCopyForTableCacheIfNeeded(server, cache)
|
||||||
boArt = loadDBDuplicateBusinessObjectSimpleCond(server, "artikel", "ARTIKEL", artikel, cache=cache)
|
boArt = loadDBDuplicateBusinessObjectSimpleCond(server, "artikel", "ARTIKEL", artikel, cache=cache)
|
||||||
if boArt is None:
|
if boArt is None:
|
||||||
return None
|
return None
|
||||||
@ -563,7 +555,7 @@ def loadDBDuplicateArtikel(
|
|||||||
if dupAplan:
|
if dupAplan:
|
||||||
boAplan = loadDBDuplicateAPlan(server, artikel, cache=cache)
|
boAplan = loadDBDuplicateAPlan(server, artikel, cache=cache)
|
||||||
boArt.addDependentBusinessObject(boAplan, ("artikel", "aplan"))
|
boArt.addDependentBusinessObject(boAplan, ("artikel", "aplan"))
|
||||||
|
|
||||||
if dupStueli:
|
if dupStueli:
|
||||||
boStueli = loadDBDuplicateStueli(server, artikel, cache=cache)
|
boStueli = loadDBDuplicateStueli(server, artikel, cache=cache)
|
||||||
boArt.addDependentBusinessObject(boStueli, ("artikel", "stueli"))
|
boArt.addDependentBusinessObject(boStueli, ("artikel", "stueli"))
|
||||||
|
@ -8,112 +8,111 @@
|
|||||||
|
|
||||||
"""Pandas Interface für PyAPplus64."""
|
"""Pandas Interface für PyAPplus64."""
|
||||||
|
|
||||||
from typing import Annotated as Ann
|
import pandas as pd # type: ignore
|
||||||
import pandas as pd # type: ignore
|
from pandas._typing import AggFuncType, FilePath, WriteExcelBuffer # type: ignore
|
||||||
from pandas._typing import AggFuncType, FilePath, WriteExcelBuffer # type: ignore
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
import traceback
|
import traceback
|
||||||
from .applus import APplusServer
|
from .applus import APplusServer
|
||||||
from .applus import sql_utils
|
from .applus import sql_utils
|
||||||
from typing import *
|
from typing import Optional, Callable, Sequence, Tuple, Any, Union
|
||||||
|
|
||||||
|
|
||||||
def createSqlAlchemyEngine(server : APplusServer) -> sqlalchemy.Engine:
|
def createSqlAlchemyEngine(server: APplusServer) -> sqlalchemy.Engine:
|
||||||
"""Erzeugt eine SqlAlchemy-Engine für die Verbindung zur DB."""
|
"""Erzeugt eine SqlAlchemy-Engine für die Verbindung zur DB."""
|
||||||
return sqlalchemy.create_engine(sqlalchemy.engine.URL.create("mssql+pyodbc", query={"odbc_connect": server.db_settings.getConnectionString()}))
|
return sqlalchemy.create_engine(sqlalchemy.engine.URL.create("mssql+pyodbc", query={"odbc_connect": server.db_settings.getConnectionString()}))
|
||||||
|
|
||||||
|
|
||||||
def pandasReadSql(
|
def pandasReadSql(
|
||||||
server : APplusServer,
|
server: APplusServer,
|
||||||
sql : sql_utils.SqlStatement,
|
sql: sql_utils.SqlStatement,
|
||||||
raw:bool=False,
|
raw: bool = False,
|
||||||
engine:Optional[sqlalchemy.Engine]=None) -> pd.DataFrame:
|
engine: Optional[sqlalchemy.Engine] = None) -> pd.DataFrame:
|
||||||
"""Wrapper für pd.read_sql für sqlalchemy-engine.
|
"""Wrapper für pd.read_sql für sqlalchemy-engine.
|
||||||
|
|
||||||
:param server: APplusServer für Datenbankverbindung und complete-SQL
|
:param server: APplusServer für Datenbankverbindung und complete-SQL
|
||||||
:type server: APplusServer
|
:type server: APplusServer
|
||||||
:param sql: das SQL-statement
|
:param sql: das SQL-statement
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if engine is None:
|
if engine is None:
|
||||||
engine = createSqlAlchemyEngine(server);
|
engine = createSqlAlchemyEngine(server)
|
||||||
with engine.connect() as conn:
|
with engine.connect() as conn:
|
||||||
return pd.read_sql(sqlalchemy.text(server.completeSQL(sql, raw=raw)), conn)
|
return pd.read_sql(sqlalchemy.text(server.completeSQL(sql, raw=raw)), conn)
|
||||||
|
|
||||||
|
|
||||||
def _createHyperLinkGeneral(genOrg : Callable[[], str|int|float], genLink: Callable[[], str]) -> str|int|float:
|
def _createHyperLinkGeneral(genOrg: Callable[[], Union[str, int, float]], genLink: Callable[[], str]) -> Union[str, int, float]:
|
||||||
"""
|
"""
|
||||||
Hilfsfunktion zum Generieren eines Excel-Links.
|
Hilfsfunktion zum Generieren eines Excel-Links.
|
||||||
|
|
||||||
:param genLink: Funktion, die Parameter aufgerufen wird und einen Link generiert
|
:param genLink: Funktion, die Parameter aufgerufen wird und einen Link generiert
|
||||||
"""
|
"""
|
||||||
org:str|int|float=""
|
org: Union[str, int, float] = ""
|
||||||
org2:str|int|float
|
org2: Union[str, int, float]
|
||||||
try:
|
try:
|
||||||
org = genOrg();
|
org = genOrg()
|
||||||
if not org:
|
if not org:
|
||||||
return org
|
return org
|
||||||
else :
|
else:
|
||||||
if isinstance(org, (int, float)):
|
if isinstance(org, (int, float)):
|
||||||
org2 = org;
|
org2 = org
|
||||||
else:
|
else:
|
||||||
org2 = "\"" + str(org).replace("\"", "\"\"") + "\""
|
org2 = "\"" + str(org).replace("\"", "\"\"") + "\""
|
||||||
|
|
||||||
return "=HYPERLINK(\"{}\", {})".format(genLink(), org2)
|
return "=HYPERLINK(\"{}\", {})".format(genLink(), org2)
|
||||||
except:
|
except:
|
||||||
msg = traceback.format_exc();
|
msg = traceback.format_exc()
|
||||||
print ("Exception: {}".format(msg))
|
print("Exception: {}".format(msg))
|
||||||
return org
|
return org
|
||||||
|
|
||||||
|
|
||||||
def mkDataframeColumn(df : pd.DataFrame, makeValue : AggFuncType) -> pd.Series:
|
def mkDataframeColumn(df: pd.DataFrame, makeValue: AggFuncType) -> pd.Series:
|
||||||
"""
|
"""
|
||||||
Erzeugt für alle Zeilen eines Dataframes eine neuen Wert. Dies wird benutzt, um eine Spalte zu berechnen.
|
Erzeugt für alle Zeilen eines Dataframes eine neuen Wert. Dies wird benutzt, um eine Spalte zu berechnen.
|
||||||
Diese kann eine Originalspalte ersetzen, oder neu hinzugefügt werden.
|
Diese kann eine Originalspalte ersetzen, oder neu hinzugefügt werden.
|
||||||
|
|
||||||
:param df: der Dataframe
|
:param df: der Dataframe
|
||||||
:param makeValue: Funktion, die eine Zeile als Parameter bekommt und den neuen Wert berechnet
|
:param makeValue: Funktion, die eine Zeile als Parameter bekommt und den neuen Wert berechnet
|
||||||
"""
|
"""
|
||||||
def mkValueWrapper(r): # type: ignore
|
def mkValueWrapper(r): # type: ignore
|
||||||
try:
|
try:
|
||||||
return makeValue(r)
|
return makeValue(r)
|
||||||
except:
|
except:
|
||||||
msg = traceback.format_exc();
|
msg = traceback.format_exc()
|
||||||
print ("Exception: {}".format(msg))
|
print("Exception: {}".format(msg))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if (len(df.index) > 0):
|
if (len(df.index) > 0):
|
||||||
return df.apply(mkValueWrapper, axis=1)
|
return df.apply(mkValueWrapper, axis=1)
|
||||||
else:
|
else:
|
||||||
return df.apply(lambda r: "", axis=1);
|
return df.apply(lambda r: "", axis=1)
|
||||||
|
|
||||||
|
|
||||||
def mkHyperlinkDataframeColumn(df : pd.DataFrame, makeOrig : AggFuncType, makeLink : Callable[[Any], str]) -> pd.Series :
|
def mkHyperlinkDataframeColumn(df: pd.DataFrame, makeOrig: AggFuncType, makeLink: Callable[[Any], str]) -> pd.Series:
|
||||||
"""
|
"""
|
||||||
Erzeugt für alle Zeilen eines Dataframes einen Hyperlink. Dies wird benutzt, um eine Spalte mit einem Hyperlink zu berechnen.
|
Erzeugt für alle Zeilen eines Dataframes einen Hyperlink. Dies wird benutzt, um eine Spalte mit einem Hyperlink zu berechnen.
|
||||||
Diese kann eine Originalspalte ersetzen, oder neu hinzugefügt werden.
|
Diese kann eine Originalspalte ersetzen, oder neu hinzugefügt werden.
|
||||||
|
|
||||||
:param df: der Dataframe
|
:param df: der Dataframe
|
||||||
:param makeOrig: Funktion, die eine Zeile als Parameter bekommt und den Wert berechnet, der angezeigt werden soll
|
:param makeOrig: Funktion, die eine Zeile als Parameter bekommt und den Wert berechnet, der angezeigt werden soll
|
||||||
:param makeLink: Funktion, die eine Zeile als Parameter bekommt und den Link berechnet
|
:param makeLink: Funktion, die eine Zeile als Parameter bekommt und den Link berechnet
|
||||||
"""
|
"""
|
||||||
if (len(df.index) > 0):
|
if (len(df.index) > 0):
|
||||||
return df.apply(lambda r: _createHyperLinkGeneral(lambda : makeOrig(r), lambda : makeLink(r)), axis=1)
|
return df.apply(lambda r: _createHyperLinkGeneral(lambda: makeOrig(r), lambda: makeLink(r)), axis=1)
|
||||||
else:
|
else:
|
||||||
return df.apply(lambda r: "", axis=1);
|
return df.apply(lambda r: "", axis=1)
|
||||||
|
|
||||||
|
|
||||||
def exportToExcel(
|
def exportToExcel(
|
||||||
filename:FilePath | WriteExcelBuffer | pd.ExcelWriter,
|
filename: Union[FilePath, WriteExcelBuffer, pd.ExcelWriter],
|
||||||
dfs : Sequence[Tuple[pd.DataFrame, str]],
|
dfs: Sequence[Tuple[pd.DataFrame, str]],
|
||||||
addTable:bool=True) -> None:
|
addTable: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Schreibt eine Menge von Dataframes in eine Excel-Tabelle
|
Schreibt eine Menge von Dataframes in eine Excel-Tabelle
|
||||||
|
|
||||||
:param filename: Name der Excel-Datei
|
:param filename: Name der Excel-Datei
|
||||||
:param dfs: Liste von Tupeln aus DataFrames und Namen von Sheets.
|
:param dfs: Liste von Tupeln aus DataFrames und Namen von Sheets.
|
||||||
"""
|
"""
|
||||||
with pd.ExcelWriter(filename, engine='xlsxwriter') as writer:
|
with pd.ExcelWriter(filename, engine='xlsxwriter') as writer:
|
||||||
for (df, name) in dfs:
|
for (df, name) in dfs:
|
||||||
df.to_excel(writer, sheet_name=name, index=False, header=True)
|
df.to_excel(writer, sheet_name=name, index=False, header=True)
|
||||||
ws = writer.sheets[name]
|
ws = writer.sheets[name]
|
||||||
@ -126,6 +125,4 @@ def exportToExcel(
|
|||||||
ws.add_table(0, 0, max_row, max_col - 1, {'columns': column_settings})
|
ws.add_table(0, 0, max_row, max_col - 1, {'columns': column_settings})
|
||||||
|
|
||||||
# Spaltenbreiten anpassen
|
# Spaltenbreiten anpassen
|
||||||
ws.autofit();
|
ws.autofit()
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -6,13 +6,12 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#-*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import datetime
|
import datetime
|
||||||
from typing import *
|
from typing import Set, Union
|
||||||
|
|
||||||
def checkDirExists(dir : Union[str, pathlib.Path]) -> pathlib.Path:
|
|
||||||
|
def checkDirExists(dir: Union[str, pathlib.Path]) -> pathlib.Path:
|
||||||
"""Prüft, ob ein Verzeichnis existiert. Ist dies nicht möglich, wird eine Exception geworfen.
|
"""Prüft, ob ein Verzeichnis existiert. Ist dies nicht möglich, wird eine Exception geworfen.
|
||||||
|
|
||||||
:param dir: das Verzeichnis
|
:param dir: das Verzeichnis
|
||||||
@ -26,19 +25,19 @@ def checkDirExists(dir : Union[str, pathlib.Path]) -> pathlib.Path:
|
|||||||
|
|
||||||
dir = dir.resolve()
|
dir = dir.resolve()
|
||||||
if not (dir.exists()):
|
if not (dir.exists()):
|
||||||
raise Exception("Verzeichnis '" + str(dir) + "' nicht gefunden");
|
raise Exception("Verzeichnis '" + str(dir) + "' nicht gefunden")
|
||||||
|
|
||||||
if not (dir.is_dir()):
|
if not (dir.is_dir()):
|
||||||
raise Exception("'" + str(dir) + "' ist kein Verzeichnis");
|
raise Exception("'" + str(dir) + "' ist kein Verzeichnis")
|
||||||
return dir;
|
return dir
|
||||||
|
|
||||||
|
|
||||||
def formatDateTimeForAPplus(v : Union[datetime.datetime, datetime.date, datetime.time]) -> str:
|
def formatDateTimeForAPplus(v: Union[datetime.datetime, datetime.date, datetime.time]) -> str:
|
||||||
"""Formatiert ein Datum oder eine Uhrzeit für APplus"""
|
"""Formatiert ein Datum oder eine Uhrzeit für APplus"""
|
||||||
if (v == None):
|
if v is None:
|
||||||
return "";
|
return ""
|
||||||
elif isinstance(v, str):
|
elif isinstance(v, str):
|
||||||
return v;
|
return v
|
||||||
elif isinstance(v, datetime.datetime):
|
elif isinstance(v, datetime.datetime):
|
||||||
return v.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
return v.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
||||||
elif isinstance(v, datetime.date):
|
elif isinstance(v, datetime.date):
|
||||||
@ -47,10 +46,11 @@ def formatDateTimeForAPplus(v : Union[datetime.datetime, datetime.date, datetime
|
|||||||
return v.strftime("%H:%M:%S.%f")[:-3]
|
return v.strftime("%H:%M:%S.%f")[:-3]
|
||||||
else:
|
else:
|
||||||
return str(v)
|
return str(v)
|
||||||
|
|
||||||
def containsOnlyAllowedChars(charset : Set[str], s : str) -> bool:
|
|
||||||
|
def containsOnlyAllowedChars(charset: Set[str], s: str) -> bool:
|
||||||
"""Enthält ein String nur erlaubte Zeichen?"""
|
"""Enthält ein String nur erlaubte Zeichen?"""
|
||||||
for c in s:
|
for c in s:
|
||||||
if not (c in charset):
|
if not (c in charset):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -7,17 +7,17 @@
|
|||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
from PyAPplus64 import applus_db
|
from PyAPplus64 import applus_db
|
||||||
import datetime
|
|
||||||
|
|
||||||
def test_DBTableIDs1() -> None:
|
def test_DBTableIDs1() -> None:
|
||||||
ids = applus_db.DBTableIDs();
|
ids = applus_db.DBTableIDs()
|
||||||
assert (str(ids) == "{}")
|
assert (str(ids) == "{}")
|
||||||
ids.add("t1", 1)
|
ids.add("t1", 1)
|
||||||
assert (str(ids) == "{'T1': {1}}")
|
assert (str(ids) == "{'T1': {1}}")
|
||||||
ids.add("t1", 2,3,4)
|
ids.add("t1", 2, 3, 4)
|
||||||
assert (str(ids) == "{'T1': {1, 2, 3, 4}}")
|
assert (str(ids) == "{'T1': {1, 2, 3, 4}}")
|
||||||
assert (ids.getTable("T1") == {1, 2, 3, 4})
|
assert (ids.getTable("T1") == {1, 2, 3, 4})
|
||||||
assert (ids.getTable("T2") == set())
|
assert (ids.getTable("T2") == set())
|
||||||
ids.add("t2", 2,3,4)
|
ids.add("t2", 2, 3, 4)
|
||||||
assert (ids.getTable("T2") == {2,3,4})
|
assert (ids.getTable("T2") == {2, 3, 4})
|
||||||
assert (str(ids) == "{'T1': {1, 2, 3, 4}, 'T2': {2, 3, 4}}")
|
assert (str(ids) == "{'T1': {1, 2, 3, 4}, 'T2': {2, 3, 4}}")
|
||||||
|
@ -9,284 +9,350 @@
|
|||||||
from PyAPplus64 import sql_utils
|
from PyAPplus64 import sql_utils
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
def test_normaliseDBField1() -> None:
|
|
||||||
|
def test_normaliseDBField1() -> None:
|
||||||
assert (sql_utils.normaliseDBfield("aAa") == "AAA")
|
assert (sql_utils.normaliseDBfield("aAa") == "AAA")
|
||||||
assert (sql_utils.normaliseDBfield("a#Aa") == "A#AA")
|
assert (sql_utils.normaliseDBfield("a#Aa") == "A#AA")
|
||||||
assert (sql_utils.normaliseDBfield("2") == "2")
|
assert (sql_utils.normaliseDBfield("2") == "2")
|
||||||
|
|
||||||
def test_normaliseDBFieldSet() -> None:
|
|
||||||
|
def test_normaliseDBFieldSet() -> None:
|
||||||
assert (sql_utils.normaliseDBfieldSet(set()) == set())
|
assert (sql_utils.normaliseDBfieldSet(set()) == set())
|
||||||
assert (sql_utils.normaliseDBfieldSet({"aAa", "b", "c", "2"}) == {"2", "AAA", "B", "C"})
|
assert (sql_utils.normaliseDBfieldSet({"aAa", "b", "c", "2"}) == {"2", "AAA", "B", "C"})
|
||||||
|
|
||||||
def test_normaliseDBFieldList() -> None:
|
|
||||||
|
def test_normaliseDBFieldList() -> None:
|
||||||
assert (sql_utils.normaliseDBfieldList([]) == [])
|
assert (sql_utils.normaliseDBfieldList([]) == [])
|
||||||
assert (sql_utils.normaliseDBfieldList(["aAa", "b", "c", "2"]) == ["AAA", "B", "C", "2"])
|
assert (sql_utils.normaliseDBfieldList(["aAa", "b", "c", "2"]) == ["AAA", "B", "C", "2"])
|
||||||
|
|
||||||
|
|
||||||
def test_SqlField1() -> None:
|
def test_SqlField1() -> None:
|
||||||
assert (str(sql_utils.SqlField("abc")) == "ABC")
|
assert (str(sql_utils.SqlField("abc")) == "ABC")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlField2() -> None:
|
def test_SqlField2() -> None:
|
||||||
assert (str(sql_utils.SqlField("t.abc")) == "T.ABC")
|
assert (str(sql_utils.SqlField("t.abc")) == "T.ABC")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlParam() -> None:
|
def test_SqlParam() -> None:
|
||||||
assert (str(sql_utils.sqlParam) == "?")
|
assert (str(sql_utils.sqlParam) == "?")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlDateTime() -> None:
|
def test_SqlDateTime() -> None:
|
||||||
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
||||||
assert (str(sql_utils.SqlDateTime(dt)) == "2023-01-12T09:59:12.002")
|
assert (str(sql_utils.SqlDateTime(dt)) == "2023-01-12T09:59:12.002")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlDate() -> None:
|
def test_SqlDate() -> None:
|
||||||
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
||||||
assert (str(sql_utils.SqlDate(dt)) == "20230112")
|
assert (str(sql_utils.SqlDate(dt)) == "20230112")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValueString1() -> None:
|
def test_formatSqlValueString1() -> None:
|
||||||
assert(sql_utils.formatSqlValueString("") == "''");
|
assert (sql_utils.formatSqlValueString("") == "''")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValueString2() -> None:
|
def test_formatSqlValueString2() -> None:
|
||||||
assert(sql_utils.formatSqlValueString("abc") == "'abc'");
|
assert (sql_utils.formatSqlValueString("abc") == "'abc'")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValueString3() -> None:
|
def test_formatSqlValueString3() -> None:
|
||||||
assert(sql_utils.formatSqlValueString("a b c") == "'a b c'");
|
assert (sql_utils.formatSqlValueString("a b c") == "'a b c'")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValueString4() -> None:
|
def test_formatSqlValueString4() -> None:
|
||||||
assert(sql_utils.formatSqlValueString("a \"b\" c") == "'a \"b\" c'");
|
assert (sql_utils.formatSqlValueString("a \"b\" c") == "'a \"b\" c'")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValueString5() -> None:
|
def test_formatSqlValueString5() -> None:
|
||||||
assert(sql_utils.formatSqlValueString("a 'b'\nc") == "'a ''b''\nc'");
|
assert (sql_utils.formatSqlValueString("a 'b'\nc") == "'a ''b''\nc'")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValue1() -> None:
|
def test_formatSqlValue1() -> None:
|
||||||
assert(sql_utils.formatSqlValue(2) == "2");
|
assert (sql_utils.formatSqlValue(2) == "2")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValue2() -> None:
|
def test_formatSqlValue2() -> None:
|
||||||
assert(sql_utils.formatSqlValue(2.4) == "2.4");
|
assert (sql_utils.formatSqlValue(2.4) == "2.4")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValue3() -> None:
|
def test_formatSqlValue3() -> None:
|
||||||
assert(sql_utils.formatSqlValue("AA") == "'AA'");
|
assert (sql_utils.formatSqlValue("AA") == "'AA'")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValue4() -> None:
|
def test_formatSqlValue4() -> None:
|
||||||
assert(sql_utils.formatSqlValue(sql_utils.SqlField("aa")) == "AA");
|
assert (sql_utils.formatSqlValue(sql_utils.SqlField("aa")) == "AA")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValue5() -> None:
|
def test_formatSqlValue5() -> None:
|
||||||
assert(sql_utils.formatSqlValue(0) == "0");
|
assert (sql_utils.formatSqlValue(0) == "0")
|
||||||
|
|
||||||
|
|
||||||
def test_formatSqlValue6() -> None:
|
def test_formatSqlValue6() -> None:
|
||||||
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
dt = datetime.datetime(year=2023, month=1, day=12, hour=9, minute=59, second=12, microsecond=2344)
|
||||||
assert(sql_utils.formatSqlValue(sql_utils.SqlDateTime(dt)) == "'2023-01-12T09:59:12.002'");
|
assert (sql_utils.formatSqlValue(sql_utils.SqlDateTime(dt)) == "'2023-01-12T09:59:12.002'")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionTrue() -> None:
|
def test_SqlConditionTrue() -> None:
|
||||||
assert(str(sql_utils.SqlConditionTrue()) == "(1=1)");
|
assert (str(sql_utils.SqlConditionTrue()) == "(1=1)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFalse() -> None:
|
def test_SqlConditionFalse() -> None:
|
||||||
assert(str(sql_utils.SqlConditionFalse()) == "(1=0)");
|
assert (str(sql_utils.SqlConditionFalse()) == "(1=0)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionBool1() -> None:
|
def test_SqlConditionBool1() -> None:
|
||||||
assert(str(sql_utils.SqlConditionBool(True)) == "(1=1)");
|
assert (str(sql_utils.SqlConditionBool(True)) == "(1=1)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionBool2() -> None:
|
def test_SqlConditionBool2() -> None:
|
||||||
assert(str(sql_utils.SqlConditionBool(False)) == "(1=0)");
|
assert (str(sql_utils.SqlConditionBool(False)) == "(1=0)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionIsNull() -> None:
|
def test_SqlConditionIsNull() -> None:
|
||||||
cond = sql_utils.SqlConditionIsNull("AA");
|
cond = sql_utils.SqlConditionIsNull("AA")
|
||||||
assert(str(cond) == "('AA' is null)");
|
assert (str(cond) == "('AA' is null)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionIsNotNull() -> None:
|
def test_SqlConditionIsNotNull() -> None:
|
||||||
cond = sql_utils.SqlConditionIsNotNull("AA");
|
cond = sql_utils.SqlConditionIsNotNull("AA")
|
||||||
assert(str(cond) == "('AA' is not null)");
|
assert (str(cond) == "('AA' is not null)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionNot() -> None:
|
def test_SqlConditionNot() -> None:
|
||||||
cond1 = sql_utils.SqlConditionIsNull("AA");
|
cond1 = sql_utils.SqlConditionIsNull("AA")
|
||||||
cond = sql_utils.SqlConditionNot(cond1);
|
cond = sql_utils.SqlConditionNot(cond1)
|
||||||
assert(str(cond) == "(not ('AA' is null))");
|
assert (str(cond) == "(not ('AA' is null))")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionStringStartsWith() -> None:
|
def test_SqlConditionStringStartsWith() -> None:
|
||||||
cond = sql_utils.SqlConditionStringStartsWith("f", "a'an")
|
cond = sql_utils.SqlConditionStringStartsWith("f", "a'an")
|
||||||
assert(str(cond) == "(left(F, 4) = 'a''an')");
|
assert (str(cond) == "(left(F, 4) = 'a''an')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionIn1() -> None:
|
def test_SqlConditionIn1() -> None:
|
||||||
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), [])
|
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), [])
|
||||||
assert(str(cond) == "(1=0)");
|
assert (str(cond) == "(1=0)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionIn2() -> None:
|
def test_SqlConditionIn2() -> None:
|
||||||
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), ["a"])
|
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), ["a"])
|
||||||
assert(str(cond) == "(F = 'a')");
|
assert (str(cond) == "(F = 'a')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionIn3() -> None:
|
def test_SqlConditionIn3() -> None:
|
||||||
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), ["a", "a'A", "b", "c"])
|
cond = sql_utils.SqlConditionIn(sql_utils.SqlField("f"), ["a", "a'A", "b", "c"])
|
||||||
assert(str(cond) == "(F in ('a', 'a''A', 'b', 'c'))");
|
assert (str(cond) == "(F in ('a', 'a''A', 'b', 'c'))")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionStringNotEmpty1() -> None:
|
def test_SqlConditionStringNotEmpty1() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldStringNotEmpty("f")
|
cond = sql_utils.SqlConditionFieldStringNotEmpty("f")
|
||||||
assert(str(cond) == "(F is not null and F != '')");
|
assert (str(cond) == "(F is not null and F != '')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq1() -> None:
|
def test_SqlConditionEq1() -> None:
|
||||||
cond = sql_utils.SqlConditionEq("f1", None)
|
cond = sql_utils.SqlConditionEq("f1", None)
|
||||||
assert(str(cond) == "('f1' is null)");
|
assert (str(cond) == "('f1' is null)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq2() -> None:
|
def test_SqlConditionEq2() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(None, "f1")
|
cond = sql_utils.SqlConditionEq(None, "f1")
|
||||||
assert(str(cond) == "('f1' is null)");
|
assert (str(cond) == "('f1' is null)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq3() -> None:
|
def test_SqlConditionEq3() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), sql_utils.SqlField("f2"))
|
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), sql_utils.SqlField("f2"))
|
||||||
assert(str(cond) == "(F1 = F2)");
|
assert (str(cond) == "(F1 = F2)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq4() -> None:
|
def test_SqlConditionEq4() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), "aa'a")
|
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), "aa'a")
|
||||||
assert(str(cond) == "(F1 = 'aa''a')");
|
assert (str(cond) == "(F1 = 'aa''a')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq5() -> None:
|
def test_SqlConditionEq5() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), 2)
|
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), 2)
|
||||||
assert(str(cond) == "(F1 = 2)");
|
assert (str(cond) == "(F1 = 2)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq6() -> None:
|
def test_SqlConditionEq6() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), True)
|
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), True)
|
||||||
assert(str(cond) == "(F1 = 1)");
|
assert (str(cond) == "(F1 = 1)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq7() -> None:
|
def test_SqlConditionEq7() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), False)
|
cond = sql_utils.SqlConditionEq(sql_utils.SqlField("f1"), False)
|
||||||
assert(str(cond) == "(F1 = 0 OR F1 is null)");
|
assert (str(cond) == "(F1 = 0 OR F1 is null)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq8() -> None:
|
def test_SqlConditionEq8() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(True, sql_utils.SqlField("f1"))
|
cond = sql_utils.SqlConditionEq(True, sql_utils.SqlField("f1"))
|
||||||
assert(str(cond) == "(F1 = 1)");
|
assert (str(cond) == "(F1 = 1)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq9() -> None:
|
def test_SqlConditionEq9() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(False, sql_utils.SqlField("f1"))
|
cond = sql_utils.SqlConditionEq(False, sql_utils.SqlField("f1"))
|
||||||
assert(str(cond) == "(F1 = 0 OR F1 is null)");
|
assert (str(cond) == "(F1 = 0 OR F1 is null)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq10() -> None:
|
def test_SqlConditionEq10() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(False, True)
|
cond = sql_utils.SqlConditionEq(False, True)
|
||||||
assert(str(cond) == "(1=0)");
|
assert (str(cond) == "(1=0)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionEq11() -> None:
|
def test_SqlConditionEq11() -> None:
|
||||||
cond = sql_utils.SqlConditionEq(True, True)
|
cond = sql_utils.SqlConditionEq(True, True)
|
||||||
assert(str(cond) == "(1=1)");
|
assert (str(cond) == "(1=1)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldEq1() -> None:
|
def test_SqlConditionFieldEq1() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldEq("f1", None)
|
cond = sql_utils.SqlConditionFieldEq("f1", None)
|
||||||
assert(str(cond) == "(F1 is null)");
|
assert (str(cond) == "(F1 is null)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldEq2() -> None:
|
def test_SqlConditionFieldEq2() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldEq("f1", sql_utils.SqlField("f2"))
|
cond = sql_utils.SqlConditionFieldEq("f1", sql_utils.SqlField("f2"))
|
||||||
assert(str(cond) == "(F1 = F2)");
|
assert (str(cond) == "(F1 = F2)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldEq3() -> None:
|
def test_SqlConditionFieldEq3() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldEq("f1", "aa'a")
|
cond = sql_utils.SqlConditionFieldEq("f1", "aa'a")
|
||||||
assert(str(cond) == "(F1 = 'aa''a')");
|
assert (str(cond) == "(F1 = 'aa''a')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldEq4() -> None:
|
def test_SqlConditionFieldEq4() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldEq("f1", 2)
|
cond = sql_utils.SqlConditionFieldEq("f1", 2)
|
||||||
assert(str(cond) == "(F1 = 2)");
|
assert (str(cond) == "(F1 = 2)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldEq5() -> None:
|
def test_SqlConditionFieldEq5() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldEq("f1", sql_utils.sqlParam)
|
cond = sql_utils.SqlConditionFieldEq("f1", sql_utils.sqlParam)
|
||||||
assert(str(cond) == "(F1 = ?)");
|
assert (str(cond) == "(F1 = ?)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionLt1() -> None:
|
def test_SqlConditionLt1() -> None:
|
||||||
cond = sql_utils.SqlConditionLt(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionLt(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F < '20221212')");
|
assert (str(cond) == "(F < '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionLt2() -> None:
|
def test_SqlConditionLt2() -> None:
|
||||||
cond = sql_utils.SqlConditionLt(2, sql_utils.SqlField("f"))
|
cond = sql_utils.SqlConditionLt(2, sql_utils.SqlField("f"))
|
||||||
assert(str(cond) == "(2 < F)");
|
assert (str(cond) == "(2 < F)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionGt1() -> None:
|
def test_SqlConditionGt1() -> None:
|
||||||
cond = sql_utils.SqlConditionGt(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionGt(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F > '20221212')");
|
assert (str(cond) == "(F > '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionGt2() -> None:
|
def test_SqlConditionGt2() -> None:
|
||||||
cond = sql_utils.SqlConditionGt(2, sql_utils.SqlField("f"))
|
cond = sql_utils.SqlConditionGt(2, sql_utils.SqlField("f"))
|
||||||
assert(str(cond) == "(2 > F)");
|
assert (str(cond) == "(2 > F)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionLe1() -> None:
|
def test_SqlConditionLe1() -> None:
|
||||||
cond = sql_utils.SqlConditionLe(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionLe(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F <= '20221212')");
|
assert (str(cond) == "(F <= '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionLe2() -> None:
|
def test_SqlConditionLe2() -> None:
|
||||||
cond = sql_utils.SqlConditionLe(2, sql_utils.SqlField("f"))
|
cond = sql_utils.SqlConditionLe(2, sql_utils.SqlField("f"))
|
||||||
assert(str(cond) == "(2 <= F)");
|
assert (str(cond) == "(2 <= F)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionGe1() -> None:
|
def test_SqlConditionGe1() -> None:
|
||||||
cond = sql_utils.SqlConditionGe(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionGe(sql_utils.SqlField("f"), sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F >= '20221212')");
|
assert (str(cond) == "(F >= '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionGe2() -> None:
|
def test_SqlConditionGe2() -> None:
|
||||||
cond = sql_utils.SqlConditionGe(2, sql_utils.SqlField("f"))
|
cond = sql_utils.SqlConditionGe(2, sql_utils.SqlField("f"))
|
||||||
assert(str(cond) == "(2 >= F)");
|
assert (str(cond) == "(2 >= F)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldLt1() -> None:
|
def test_SqlConditionFieldLt1() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldLt("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionFieldLt("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F < '20221212')");
|
assert (str(cond) == "(F < '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldLe1() -> None:
|
def test_SqlConditionFieldLe1() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldLe("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionFieldLe("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F <= '20221212')");
|
assert (str(cond) == "(F <= '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldGt1() -> None:
|
def test_SqlConditionFieldGt1() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldGt("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionFieldGt("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F > '20221212')");
|
assert (str(cond) == "(F > '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionFieldGe1() -> None:
|
def test_SqlConditionFieldGe1() -> None:
|
||||||
cond = sql_utils.SqlConditionFieldGe("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
cond = sql_utils.SqlConditionFieldGe("f", sql_utils.SqlDate(datetime.date(year=2022, month=12, day=12)))
|
||||||
assert(str(cond) == "(F >= '20221212')");
|
assert (str(cond) == "(F >= '20221212')")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionAnd1() -> None:
|
def test_SqlConditionAnd1() -> None:
|
||||||
conj = sql_utils.SqlConditionAnd();
|
conj = sql_utils.SqlConditionAnd()
|
||||||
assert(str(conj) == "(1=1)");
|
assert (str(conj) == "(1=1)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionAnd2() -> None:
|
def test_SqlConditionAnd2() -> None:
|
||||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||||
conj = sql_utils.SqlConditionAnd();
|
conj = sql_utils.SqlConditionAnd()
|
||||||
conj.addCondition(cond1)
|
conj.addCondition(cond1)
|
||||||
assert(str(conj) == "cond1");
|
assert (str(conj) == "cond1")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionAnd3() -> None:
|
def test_SqlConditionAnd3() -> None:
|
||||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||||
conj = sql_utils.SqlConditionAnd();
|
conj = sql_utils.SqlConditionAnd()
|
||||||
conj.addCondition(cond1)
|
conj.addCondition(cond1)
|
||||||
conj.addCondition(cond2)
|
conj.addCondition(cond2)
|
||||||
assert(str(conj) == "(cond1 AND cond2)");
|
assert (str(conj) == "(cond1 AND cond2)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionAnd4() -> None:
|
def test_SqlConditionAnd4() -> None:
|
||||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||||
cond3 = sql_utils.SqlConditionPrepared("cond3");
|
cond3 = sql_utils.SqlConditionPrepared("cond3")
|
||||||
conj = sql_utils.SqlConditionAnd();
|
conj = sql_utils.SqlConditionAnd()
|
||||||
conj.addCondition(cond1)
|
conj.addCondition(cond1)
|
||||||
conj.addCondition(cond2)
|
conj.addCondition(cond2)
|
||||||
conj.addCondition(cond3)
|
conj.addCondition(cond3)
|
||||||
assert(str(conj) == "(cond1 AND cond2 AND cond3)");
|
assert (str(conj) == "(cond1 AND cond2 AND cond3)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionOr1() -> None:
|
def test_SqlConditionOr1() -> None:
|
||||||
conj = sql_utils.SqlConditionOr();
|
conj = sql_utils.SqlConditionOr()
|
||||||
assert(str(conj) == "(1=0)");
|
assert (str(conj) == "(1=0)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionOr2() -> None:
|
def test_SqlConditionOr2() -> None:
|
||||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||||
conj = sql_utils.SqlConditionOr();
|
conj = sql_utils.SqlConditionOr()
|
||||||
conj.addCondition(cond1)
|
conj.addCondition(cond1)
|
||||||
assert(str(conj) == "cond1");
|
assert (str(conj) == "cond1")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionOr3() -> None:
|
def test_SqlConditionOr3() -> None:
|
||||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||||
conj = sql_utils.SqlConditionOr();
|
conj = sql_utils.SqlConditionOr()
|
||||||
conj.addCondition(cond1)
|
conj.addCondition(cond1)
|
||||||
conj.addCondition(cond2)
|
conj.addCondition(cond2)
|
||||||
assert(str(conj) == "(cond1 OR cond2)");
|
assert (str(conj) == "(cond1 OR cond2)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlConditionOr4() -> None:
|
def test_SqlConditionOr4() -> None:
|
||||||
cond1 = sql_utils.SqlConditionPrepared("cond1");
|
cond1 = sql_utils.SqlConditionPrepared("cond1")
|
||||||
cond2 = sql_utils.SqlConditionPrepared("cond2");
|
cond2 = sql_utils.SqlConditionPrepared("cond2")
|
||||||
cond3 = sql_utils.SqlConditionPrepared("cond3");
|
cond3 = sql_utils.SqlConditionPrepared("cond3")
|
||||||
conj = sql_utils.SqlConditionOr();
|
conj = sql_utils.SqlConditionOr()
|
||||||
conj.addCondition(cond1)
|
conj.addCondition(cond1)
|
||||||
conj.addCondition(cond2)
|
conj.addCondition(cond2)
|
||||||
conj.addCondition(cond3)
|
conj.addCondition(cond3)
|
||||||
assert(str(conj) == "(cond1 OR cond2 OR cond3)");
|
assert (str(conj) == "(cond1 OR cond2 OR cond3)")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlStatementSelect1() -> None:
|
def test_SqlStatementSelect1() -> None:
|
||||||
sql = sql_utils.SqlStatementSelect("tabelle t")
|
sql = sql_utils.SqlStatementSelect("tabelle t")
|
||||||
@ -323,6 +389,7 @@ def test_SqlStatementSelect2() -> None:
|
|||||||
sql.addJoin("left join t3 on cond3")
|
sql.addJoin("left join t3 on cond3")
|
||||||
assert (str(sql) == "SELECT * FROM t1 left join t2 on cond2 left join t3 on cond3")
|
assert (str(sql) == "SELECT * FROM t1 left join t2 on cond2 left join t3 on cond3")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlStatementSelect4() -> None:
|
def test_SqlStatementSelect4() -> None:
|
||||||
sql = sql_utils.SqlStatementSelect("t")
|
sql = sql_utils.SqlStatementSelect("t")
|
||||||
sql.where.addCondition("cond1")
|
sql.where.addCondition("cond1")
|
||||||
@ -331,9 +398,10 @@ def test_SqlStatementSelect4() -> None:
|
|||||||
sql.where.addCondition("cond2")
|
sql.where.addCondition("cond2")
|
||||||
assert (str(sql) == "SELECT * FROM t WHERE ((cond1) AND (cond2))")
|
assert (str(sql) == "SELECT * FROM t WHERE ((cond1) AND (cond2))")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlStatementSelect5() -> None:
|
def test_SqlStatementSelect5() -> None:
|
||||||
sql = sql_utils.SqlStatementSelect("t")
|
sql = sql_utils.SqlStatementSelect("t")
|
||||||
cond = sql_utils.SqlConditionOr();
|
cond = sql_utils.SqlConditionOr()
|
||||||
sql.where.addCondition(cond)
|
sql.where.addCondition(cond)
|
||||||
cond.addCondition("cond1")
|
cond.addCondition("cond1")
|
||||||
assert (str(sql) == "SELECT * FROM t WHERE (cond1)")
|
assert (str(sql) == "SELECT * FROM t WHERE (cond1)")
|
||||||
@ -341,9 +409,10 @@ def test_SqlStatementSelect5() -> None:
|
|||||||
cond.addCondition("cond2")
|
cond.addCondition("cond2")
|
||||||
assert (str(sql) == "SELECT * FROM t WHERE ((cond1) OR (cond2))")
|
assert (str(sql) == "SELECT * FROM t WHERE ((cond1) OR (cond2))")
|
||||||
|
|
||||||
|
|
||||||
def test_SqlStatementSelect6() -> None:
|
def test_SqlStatementSelect6() -> None:
|
||||||
sql = sql_utils.SqlStatementSelect("t")
|
sql = sql_utils.SqlStatementSelect("t")
|
||||||
sql.where = sql_utils.SqlConditionOr();
|
sql.where = sql_utils.SqlConditionOr()
|
||||||
sql.where.addCondition("cond1")
|
sql.where.addCondition("cond1")
|
||||||
assert (str(sql) == "SELECT * FROM t WHERE (cond1)")
|
assert (str(sql) == "SELECT * FROM t WHERE (cond1)")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user