Use ORM for applicants

This commit is contained in:
Yuri Becker
2025-04-21 23:45:33 +02:00
parent 0ae153617b
commit 45f19d80d0
45 changed files with 388 additions and 354 deletions

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="pyqcrm@bearybot.selfhost.co" uuid="ed28331b-481b-40e7-9295-d9cdae9fd4f2">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://bearybot.selfhost.co:8080/pyqcrm</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@@ -6,13 +6,6 @@ ColumnLayout {
anchors.fill: parent
spacing: Dimensions.l
Component.onCompleted: {
employee_model.addedNewEmployee.connect(successful => {
if (successful)
contentStack.pop();
});
}
ApplicantForm {
id: applicantForm
@@ -36,7 +29,8 @@ ColumnLayout {
text: qsTr("Speichern")
onClicked: {
employee_model.addApplicant(applicantForm.value);
applicantModel.createApplicant(applicantForm.value);
contentStack.pop();
}
}
}

View File

@@ -46,7 +46,7 @@ ColumnLayout {
Layout.fillWidth: true
Layout.horizontalStretchFactor: 1
ApplicantPersonalData {
EmployeePersonalData {
id: personalData
implicitWidth: parent.width
@@ -61,14 +61,14 @@ ColumnLayout {
Layout.alignment: Qt.AlignTop
implicitWidth: parent.width
ApplicantBankData {
EmployeeBankData {
id: bankAccount
}
ApplicantNationalInsurance {
EmployeeNationalInsurance {
id: nationalInsurance
}
ApplicantVarious {
EmployeeVarious {
id: applicantVarious
}

View File

@@ -6,19 +6,31 @@ import TeroStyle
ColumnLayout {
readonly property int fieldM: 235
readonly property int fieldS: 110
readonly property bool valid: city.acceptableInput && email.acceptableInput && firstname.acceptableInput && lastname.acceptableInput && mobile.acceptableInput && phone.acceptableInput && postcode.acceptableInput && formofaddress.acceptableInput && title.acceptableInput
readonly property bool valid: emailAddress.acceptableInput && firstName.acceptableInput && houseNumber.acceptableInput && lastName.acceptableInput && mobileNumber.acceptableInput && phoneNumber.acceptableInput && salutation.acceptableInput&& street.acceptableInput && title.acceptableInput && zipCode.acceptableInput
readonly property var value: QtObject {
readonly property string city: (city.editText ? city.editText : city.currentText) ?? ""
readonly property string email: email.text
readonly property string firstname: firstname.text
readonly property string formofaddress: formofaddress.currentText ?? ""
readonly property string houseno: houseno.text ?? ""
readonly property string lastname: lastname.text
readonly property string mobile: mobile.text
readonly property string phone: phone.text
readonly property string postcode: (postcode.editText ? postcode.editText : postcode.currentText) ?? ""
readonly property string street: (street.editText ? street.editText : street.currentText) ?? ""
readonly property string emailAddress: emailAddress.text ?? ""
readonly property string firstName: firstName.text ?? ""
readonly property string houseNumber: houseNumber.text ?? ""
readonly property string lastName: lastName.text ?? ""
readonly property string mobileNumber: mobileNumber.text ?? ""
readonly property string phoneNumber: phoneNumber.text ?? ""
readonly property string salutation: salutation.text ?? ""
readonly property string street: street.text ?? ""
readonly property string title: title.currentText
readonly property int zipCode: zipCode.currentIndex
}
function setValue(value) {
title.currentIndex = value.title ?? 0;
firstName.text = value.firstName ?? "";
lastName.text = value.lastName ?? "";
street.text = value.street ?? "";
houseNumber.text = value.houseNumber ?? "";
zipCode.currentIndex = value.zipCode ?? -1;
phoneNumber.text = value.phoneNumber ?? "";
mobileNumber.text = value.mobileNumber ?? "";
emailAddress.text = value.emailAddress ?? "";
salutation.text = value.salutation ?? 0;
}
spacing: Dimensions.l
@@ -48,13 +60,13 @@ ColumnLayout {
onCurrentTextChanged: {
switch (title.currentIndex) {
case 1:
formofaddress.text = "Sehr geehrter Herr ";
salutation.text = "Sehr geehrter Herr ";
break;
case 2:
formofaddress.text = "Sehr geehrte Frau ";
salutation.text = "Sehr geehrte Frau ";
break;
default:
formofaddress.text = "Guten Tag ";
salutation.text = "Guten Tag ";
}
}
}
@@ -64,7 +76,7 @@ ColumnLayout {
mandatory: true
TextField {
id: firstname
id: firstName
implicitWidth: fieldM
placeholderText: qsTr("Max")
@@ -78,7 +90,7 @@ ColumnLayout {
mandatory: true
TextField {
id: lastname
id: lastName
implicitWidth: fieldM
placeholderText: qsTr("Mustermann")
@@ -92,39 +104,30 @@ ColumnLayout {
spacing: Dimensions.m
Field {
id: street
label: qsTr("Straße")
mandatory: true
TextField {
id: street
implicitWidth: fieldM
placeholderText: qsTr("Musterstraße")
validator: NotEmptyValidator {
}
}
}
Field {
id: houseno
mandatory: true
label: qsTr("Hausnummer")
TextField {
id: houseNumber
implicitWidth: fieldS
placeholderText: qsTr("1a")
validator: NotEmptyValidator {
}
}
}
Field {
label: qsTr("PLZ")
mandatory: true
ComboBox {
id: postcode
id: zipCode
currentIndex: -1
editable: true
@@ -133,14 +136,11 @@ ColumnLayout {
textRole: "display"
onActivated: currentValue
onCurrentIndexChanged: city.currentIndex = postcode.currentIndex
validator: NotEmptyValidator {}
onCurrentIndexChanged: city.currentIndex = zipCode.currentIndex
}
}
Field {
label: qsTr("Ort")
mandatory: true
ComboBox {
id: city
@@ -150,9 +150,6 @@ ColumnLayout {
implicitWidth: fieldM
model: address_model
textRole: "city"
validator: NotEmptyValidator {
}
}
}
}
@@ -173,7 +170,7 @@ ColumnLayout {
label: qsTr("Telefonnummer")
TextField {
id: phone
id: phoneNumber
implicitWidth: fieldM
placeholderText: "+49 1234 567890"
@@ -186,7 +183,7 @@ ColumnLayout {
label: qsTr("Mobil")
TextField {
id: mobile
id: mobileNumber
implicitWidth: fieldM
placeholderText: "+49 123 4567891011"
@@ -199,7 +196,7 @@ ColumnLayout {
label: qsTr("E-Mail Adresse")
TextField {
id: email
id: emailAddress
implicitWidth: fieldM
placeholderText: "tero@example.org"
@@ -212,7 +209,7 @@ ColumnLayout {
label: qsTr("Briefanrede")
TextField {
id: formofaddress
id: salutation
implicitWidth: fieldM
}

View File

@@ -1,26 +1,34 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import TeroStyle
Item {
property var employee
property int row
ColumnLayout {
property int row: -1
anchors.fill: parent
spacing: Dimensions.l
onRowChanged: {
employee = employee_model.fetchApplicant(row);
if (row !== -1) {
applicantForm.setValue(applicantModel.applicant(row))
}
}
ColumnLayout {
Label {
text: qsTr("Ausgewählter Mitarbeiter ") + row
}
Label {
text: employee.postcode ?? ""
}
Button {
text: qsTr("Mitarbeiter zeigen")
ApplicantForm {
id: applicantForm
onClicked: contentStack.pop()
Layout.alignment: Qt.AlignTop
Layout.fillHeight: true
Layout.verticalStretchFactor: 1
}
RowLayout {
spacing: Dimensions.l
Button {
text: qsTr("Als Mitarbeiter:in Einstellen")
icon.source: "qrc:/images/InboxArrowDown.svg"
}
}
}

View File

@@ -92,7 +92,7 @@ ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columnSpacing: 2
model: employee_model
model: applicantModel
resizableColumns: true
rowSpacing: 2
selectionBehavior: TableView.SelectRows

View File

@@ -0,0 +1,4 @@
<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-slot="icon">
<path clip-rule="evenodd" fill-rule="evenodd" d="M5.478 5.559A1.5 1.5 0 0 1 6.912 4.5H9A.75.75 0 0 0 9 3H6.912a3 3 0 0 0-2.868 2.118l-2.411 7.838a3 3 0 0 0-.133.882V18a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3v-4.162c0-.299-.045-.596-.133-.882l-2.412-7.838A3 3 0 0 0 17.088 3H15a.75.75 0 0 0 0 1.5h2.088a1.5 1.5 0 0 1 1.434 1.059l2.213 7.191H17.89a3 3 0 0 0-2.684 1.658l-.256.513a1.5 1.5 0 0 1-1.342.829h-3.218a1.5 1.5 0 0 1-1.342-.83l-.256-.512a3 3 0 0 0-2.684-1.658H3.265l2.213-7.191Z"></path>
<path clip-rule="evenodd" fill-rule="evenodd" d="M12 2.25a.75.75 0 0 1 .75.75v6.44l1.72-1.72a.75.75 0 1 1 1.06 1.06l-3 3a.75.75 0 0 1-1.06 0l-3-3a.75.75 0 0 1 1.06-1.06l1.72 1.72V3a.75.75 0 0 1 .75-.75Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 824 B

View File

@@ -1,19 +1,18 @@
# This Python file uses the following encoding: utf-8
import toml
from platformdirs import user_config_dir
from pathlib import Path
from PySide6.QtCore import QObject, Slot, Signal
from .Vermasseln import Vermasseln
import shutil
from urllib.parse import urlparse
from .DB.DbManager import DbManager
import os
from Crypto.Random import get_random_bytes
from base64 import b64encode
from pathlib import Path
from urllib.parse import urlparse
import toml
from Crypto.Random import get_random_bytes
from PySide6.QtCore import QObject, Slot, Signal
from platformdirs import user_config_dir
from .DB.UserManager import UserManager
from .PyqcrmFlags import PyqcrmFlags
from .Vermasseln import Vermasseln
from .domain.BaseModel import database
class ConfigLoader(QObject):
@@ -21,7 +20,6 @@ class ConfigLoader(QObject):
__version = "0.1-alpha"
__check_enc_key = True
dbConnectionError = Signal(str, bool)
adminUserError = Signal(str, bool)
adminNotAsvailable = Signal()
@@ -31,7 +29,6 @@ class ConfigLoader(QObject):
def __init__(self):
super().__init__()
# print(f"In {__file__} file, __init__()")
self.config_dir = user_config_dir() + '/pyqcrm'
config_dir = Path(self.config_dir)
if config_dir.exists():
@@ -41,15 +38,13 @@ class ConfigLoader(QObject):
else:
config_dir.mkdir(0o750, True, True)
@Slot(dict, result = bool)
@Slot(dict, result=bool)
def setConfig(self, app_config):
# print(f"In {__file__} file, setConfig()")
if not self.__config:
base_conf = self.__initializeConfig()
conf = self.__checkDbConnection(app_config)
app_config = toml.dumps(app_config)
if conf:
if conf:
app_config = base_conf + app_config
self.__config = toml.loads(app_config)
self.__saveConfig()
@@ -65,45 +60,41 @@ class ConfigLoader(QObject):
conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n"
return conf
def __checkDbConnection(self, db_config):
# print(f"In {__file__} file, __checkDbConnection()")
con = DbManager(db_config['database']).getConnection()
if con:
def __checkDbConnection(self):
if database.is_closed():
self.dbConnectionError.emit("Connection OK", True)
return True
else:
self.dbConnectionError.emit("Connection fehlgeschlagen", False)
return False
def __saveConfig(self):
# print(f"In {__file__} file, saveConfig()")
try:
with open (self.config_dir + '/pyqcrm.toml', 'w') as f:
with open(self.config_dir + '/pyqcrm.toml', 'w') as f:
# print(self.__config)
config = Vermasseln().oscarVermasseln(toml.dumps(self.__config))
f.write(config)
except FileNotFoundError:
print("Konnte die Konfiguration nicht speichern.")
def __checkAdminUser(self):
# print(f"In {__file__} file, __checkAdminUser()")
result = UserManager().checkAdmin()
if not result:
#if not result[0][0] == 1:
# if not result[0][0] == 1:
self.adminUserError.emit("Kein Admin vorhanden", False)
return False
else:
self.adminUserError.emit("Admin vorhanden", True)
return True
@Slot(dict, result= bool)
@Slot(dict, result=bool)
def addAdminUser(self, user_config):
# print(f"In {__file__} file, addAdminUser()")
admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser()
if not admin:
#self.adminNotAvailable.emit()
# self.adminNotAvailable.emit()
self.adminUserError.emit("Benutzername nich verfügbar", False)
else:
self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'Yes'
@@ -111,7 +102,6 @@ class ConfigLoader(QObject):
self.backupEncryptionKey.emit()
return admin
@Slot(str, str)
def __saveData(self, recovery_file, recovery_password, data):
# print(f"In {__file__} file, __saveData()")
@@ -121,7 +111,7 @@ class ConfigLoader(QObject):
rf = Vermasseln().oscarVermasseln(rf, local)
rec_file = urlparse(recovery_file)
if os.name == "nt":
rec_file = rec_file [1:]
rec_file = rec_file[1:]
else:
rec_file = rec_file.path + ".pyqrec"
try:
@@ -136,7 +126,7 @@ class ConfigLoader(QObject):
rec_file = urlparse(recovery_file)
rec_file = rec_file.path
if os.name == "nt":
rec_file = rec_file [1:]
rec_file = rec_file[1:]
try:
ek = self.__parseImport(rec_file, recovery_password)
@@ -165,7 +155,6 @@ class ConfigLoader(QObject):
else:
return None
def __invalidateEncryptionKey(self):
# print(f"In {__file__} file, __invalidateEncryptionKey()")
self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'No'
@@ -182,7 +171,7 @@ class ConfigLoader(QObject):
rp = self.__setRecoveryPassword(recovery_password, salt)
return rp[1] == password
@Slot(str, str) # todo: non local encryption
@Slot(str, str) # todo: non local encryption
def importConfig(self, confile, password):
confile = urlparse(confile)
confile = confile.path
@@ -200,15 +189,10 @@ class ConfigLoader(QObject):
except Exception as e:
print(str(e))
def __configLoad(self):
# print(f"In {__file__} file, __configLoad()")
try:
with open (self.config_dir + '/pyqcrm.toml', 'r') as f:
with open(self.config_dir + '/pyqcrm.toml', 'r') as f:
config = f.read()
self.__config = toml.loads(Vermasseln().entschluesseln(config))
self.configurationReady.emit()
@@ -219,13 +203,12 @@ class ConfigLoader(QObject):
except Exception as e:
print(str(e))
def getConfig(self):
# print(f"In {__file__} file, getConfig()")
# print(self.__config)
return self.__config
def __setRecoveryPassword(self, key, salt = None):
def __setRecoveryPassword(self, key, salt=None):
# print(f"In {__file__} file, __setRecoveryPassword()")
key = Vermasseln.userPasswordHash(key, salt)
return key.split("$")
@@ -242,13 +225,12 @@ class ConfigLoader(QObject):
conf_file = toml.dumps(self.getConfig())
self.__saveData(filename, password, conf_file)
@Slot(dict)
def saveDbConf(self, db = None):
def saveDbConf(self, db=None):
self.__config.update(db)
self.__saveConfig()
@Slot(result = dict)
@Slot(result=dict)
def getDbConf(self):
try:
return self.__config['database']
@@ -257,11 +239,11 @@ class ConfigLoader(QObject):
return None
@Slot(dict)
def saveCompanyInfo(self, company = None):
def saveCompanyInfo(self, company=None):
self.__config.update(company)
self.__saveConfig()
@Slot(result = dict)
@Slot(result=dict)
def getCompanyInfo(self):
try:
return self.__config['company']
@@ -270,11 +252,11 @@ class ConfigLoader(QObject):
return None
@Slot(dict)
def saveMiscConf(self, misc_conf = None):
def saveMiscConf(self, misc_conf=None):
self.__config.update(misc_conf)
self.__saveConfig()
@Slot(result = bool)
@Slot(result=bool)
def systray(self):
try:
return self.__config['misc']['SYSTRAY']
@@ -282,9 +264,7 @@ class ConfigLoader(QObject):
print(f"Missing configuration: {ex}")
return False
@Slot(str, str)
def backupEncryptkey(self, filename, password):
encrypt_key = self.__config['pyqcrm']['ENCRYPTION_KEY']
self.__saveData(filename, password, encrypt_key)

View File

@@ -1,27 +1,26 @@
from .DbManager import DbManager
import mariadb
import json
import mariadb
from lib.domain.BaseModel import database
class AddressDAO:
__cur = None
def __init__(self):
#print(f"*** File: {__file__}, init()")
self.__con = DbManager().getConnection()
self.__con = database.connection()
if self.__con:
self.__cur = self.__con.cursor()
def __importPlz(self):
with open("pfad zur datei", "r") as plz:
postcodes = json.load(plz)
irgendwas = ""
try:
for i in postcodes:
test =i["plz_name"].split(",")
test = i["plz_name"].split(",")
for town in test:
if "u.a" in town:
town = town[:-4]
@@ -29,12 +28,12 @@ class AddressDAO:
if town:
print(f"PROCESSING {i['name']} {town}")
self.__cur.callproc("addZipCodes", (i["name"], town, irgendwas,))
#self.__cur.callproc("addZipCodes", ("56271", "Kleinmaischeid", irgendwas,))
# self.__cur.callproc("addZipCodes", ("56271", "Kleinmaischeid", irgendwas,))
except mariadb.OperationalError as e:
print(f"Database Error: {e}")
finally:
self.__con.commit()
print("FINISHED")#
print("FINISHED") #
def __importCountry(self):
with open("pfad zur datei", "r") as country:
@@ -55,18 +54,17 @@ class AddressDAO:
print(i[4], i[3], i[2], i[8], i[7])
self.__cur.execute("INSERT INTO country (country, countryshort, nationality, iso2, iso3) VALUES (%s, %s, %s, %s, %s)", (i[4], i[3], i[2], i[8], i[7]))
self.__cur.execute(
"INSERT INTO country (country, countryshort, nationality, iso2, iso3) VALUES (%s, %s, %s, %s, %s)",
(i[4], i[3], i[2], i[8], i[7]))
old = i[4]
except mariadb.OperationalError as e:
print(f"Database Error: {e}")
finally:
self.__con.commit()
print("FINISHED")#
print("FINISHED") #
def getAddressData(self, all = True, zipcode = None):
def getAddressData(self, all=True, zipcode=None):
try:
if self.__cur:
self.__cur.callproc("getAddress", (all, zipcode,))
@@ -76,5 +74,3 @@ class AddressDAO:
return None
except mariadb.Error as e:
print(str(e))

View File

@@ -2,18 +2,19 @@ from PySide6.QtCore import QAbstractListModel, Qt, Slot, QModelIndex
from .AddressDAO import AddressDAO
from ..PyqcrmDataRoles import PyqcrmDataRoles
class AddressModel(QAbstractListModel):
def __init__(self):
super().__init__()
self.__address_data = AddressDAO().getAddressData()
def rowCount(self, parent = QModelIndex()):
def rowCount(self, parent=QModelIndex()):
return len(self.__address_data)
def data(self, index, role = Qt.DisplayRole):
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
row = index.row()
if role == Qt.DisplayRole:
if role == Qt.ItemDataRole.DisplayRole:
data = self.__address_data[row][2]
return data
elif role == PyqcrmDataRoles.CITY_ROLE:
@@ -23,7 +24,7 @@ class AddressModel(QAbstractListModel):
def roleNames(self):
return {
Qt.DisplayRole: b"display",
Qt.ItemDataRole.DisplayRole: b"display",
PyqcrmDataRoles.CITY_ROLE: b"city",
}
@@ -34,9 +35,3 @@ class AddressModel(QAbstractListModel):
def getAddresses(self, all, zipcode):
data = AddressDAO().getAddressData(all, zipcode)
return data

76
lib/DB/ApplicantModel.py Normal file
View File

@@ -0,0 +1,76 @@
import uuid
from typing import List, Callable, Any
from PySide6.QtCore import QModelIndex, Qt, QAbstractTableModel, Slot
from PySide6.QtQml import QJSValue
from peewee import Select
from lib.domain.Applicant import Applicant
COLUMNS: list[Callable[[Applicant], Any]] = [
lambda applicant: applicant.first_name,
lambda applicant: applicant.last_name,
lambda applicant: applicant.zip_code.zip_code or None,
lambda applicant: applicant.zip_code.town.town if applicant.zip_code.id is not None else None
]
COLUMN_NAMES = ["Vorname", "Nachname", "PLZ", "Ort"]
class ApplicantModel(QAbstractTableModel):
_applicants: Select
def __init__(self) -> None:
super().__init__()
self._applicants = Applicant.select_table_data()
def rowCount(self, /, parent=...):
return len(self._applicants)
def columnCount(self, /, parent=...):
return len(COLUMNS)
def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole:
applicant = self._applicants[index.row()]
return COLUMNS[index.column()](applicant)
return None
@Slot(int, result=dict)
def applicant(self, row) -> dict:
applicant = Applicant.get_by_id(self._applicants[row].id)
return {
'title': applicant.title,
"firstName": applicant.first_name,
"lastName": applicant.last_name,
"street": applicant.street,
"houseNumber": applicant.house_number,
"zipCode": applicant.zip_code_id,
"phoneNumber": applicant.phone_number,
"mobileNumber": applicant.mobile_number,
"emailAddress": applicant.email_address,
"salutation": applicant.salutation
}
@Slot(QJSValue)
def createApplicant(self, values: QJSValue):
applicant = Applicant()
applicant.id = uuid.uuid4()
applicant.title = values.property("title").toInt()
applicant.first_name = values.property("firstName").toString()
applicant.last_name = values.property("lastName").toString()
applicant.street = values.property("street").toString() or None
applicant.house_number = values.property("houseNumber").toString() or None
if values.property("zipCode").toInt() != -1:
applicant.zip_code = values.property("zipCode").toInt()
applicant.phone_number = values.property("phoneNumber").toString() or None
applicant.mobile_number = values.property("mobileNumber").toString() or None
applicant.email_address = values.property("emailAddress").toString() or None
applicant.salutation = values.property("salutation").toString() or None
applicant.save(force_insert=True)
self._applicants = Applicant.select_table_data()
def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole:
return COLUMN_NAMES[section]
return None

View File

@@ -1,20 +1,20 @@
from .DbManager import DbManager
from lib.domain.BaseModel import database
class BTypeDAO:
__cur = None
def __init__(self):
#print(f"*** File: {__file__}, init()")
self.__con = DbManager().getConnection()
self.__con = database.connection()
if self.__con:
self.__cur = self.__con.cursor()
def getBType(self):
try:
if self.__cur:
self.__cur.callproc("getBtype", (None, None, ))
self.__cur.callproc("getBtype", (None, None,))
data = self.__cur.fetchall()
return(data)
return data
else:
return None
except mariadb.Error as e:

View File

@@ -1,7 +1,7 @@
from .DbManager import DbManager
import json
import mariadb
from PySide6.QtCore import QObject, Signal
from lib.domain.BaseModel import database
class BusinessDAO(QObject):
@@ -12,11 +12,11 @@ class BusinessDAO(QObject):
def __init__(self):
super().__init__()
self.__con = DbManager().getConnection()
self.__con = database.connection()
if self.__con:
self.__cur = self.__con.cursor()
def getBusiness(self, enc_key, criterion = "Alle"):
def getBusiness(self, enc_key, criterion="Alle"):
try:
if self.__cur:
self.__cur.callproc("getCustomerView", (enc_key, criterion,))
@@ -27,14 +27,14 @@ class BusinessDAO(QObject):
except mariadb.Error as e:
print(str(e))
def getOneBusiness(self, business_id, enc_key = None):
def getOneBusiness(self, business_id, enc_key=None):
try:
if self.__cur:
self.__cur.callproc("getCustomer", (business_id, enc_key,))
#self.__all_cols = [desc[0] for desc in self.__cur.description]
return self.__cur.fetchall() #, self.__all_cols
# self.__all_cols = [desc[0] for desc in self.__cur.description]
return self.__cur.fetchall() # , self.__all_cols
else:
return None #, None
return None # , None
except mariadb.Error as e:
print(str(e))
@@ -47,10 +47,3 @@ class BusinessDAO(QObject):
except mariadb.Error as e:
print(str(e))

View File

@@ -1,7 +1,9 @@
from .DbManager import DbManager
from PySide6.QtCore import QObject, Signal
import json
import mariadb
from PySide6.QtCore import QObject, Signal
from lib.domain.BaseModel import database
class ContactDAO(QObject):
@@ -9,8 +11,7 @@ class ContactDAO(QObject):
def __init__(self):
super().__init__()
#print(f"*** File: {__file__}, __init__()")
self.__con = DbManager().getConnection()
self.__con = database.connection()
if self.__con:
self.__cur = self.__con.cursor()

View File

@@ -1,46 +0,0 @@
import mariadb
class DbManager():
__connection = None
__con_param = None
__dbmanager = None
def __new__(cls, dbconf = None):
if cls.__dbmanager is None:
cls.__dbmanager = super(DbManager, cls).__new__(cls)
cls.__dbmanager.__initializeConfig(dbconf)
return cls.__dbmanager
def getConnection(cls):
#print(f"DB Manager: {cls.__dbmanager}")
#print(f"DB Connection: {cls.__connection}")
try:
if not cls.__connection or not cls.__connection.ping():
cls.__failure_notified = False
cls.__connection = mariadb.connect(**cls.__con_param)
except mariadb.InterfaceError as e:
cls.__connection = mariadb.connect(**cls.__con_param)
print(f"DbManager Connection (INTERFACE ERROR): {e}..reconnecting...")
except mariadb.Error as e:
if '(110)' in str(e):
print(f"File: {__file__}\n Database connection timed out (Check connection parameters or server running): {e}")
elif '(138)' in str(e):
print(f"File: {__file__}\n Database connection timed out (Check connection parameters or server running - initial handshake): {e}")
else:
print(f"File: {__file__}\n Database connection error: {e}")
cls.__connection = None
return cls.__connection
def __initializeConfig(cls, dbconf):
cls.__con_param = { 'user': dbconf['DB_USER'], 'password': dbconf['DB_PASS'],
'port': int (dbconf['DB_PORT']), 'host': dbconf['DB_HOST'],
'database': dbconf['DB_NAME'], 'connect_timeout': 5, 'autocommit': True,
}

View File

@@ -1,35 +1,41 @@
from .DbManager import DbManager
import json
import mariadb
from PySide6.QtCore import QObject, Signal
from lib.domain.BaseModel import database
class EmployeeDAO(QObject):
newEmployeeAdded = Signal(bool)
__all_cols = None
def __init__(self):
super().__init__()
self.__con = DbManager().getConnection()
self._connection = database.connection()
def getEmployees(self, enc_key, criterion="Alle", processed=False, fired=False, every_state=True):
cursor = self.__con.cursor()
cursor.callproc("getEmployeeTable", (criterion, processed, fired, every_state, enc_key,))
self.__all_cols = [desc[0] for desc in cursor.description]
result = cursor.fetchall(), self.__all_cols
cursor.close()
return result
cursor = self._connection.cursor()
try:
cursor.callproc("getEmployeeTable", (criterion, processed, fired, every_state, enc_key,))
all_cols = [desc[0] for desc in cursor.description]
result = cursor.fetchall(), all_cols
return result
finally:
cursor.close()
def fetchApplicant(self, employee_id, enc_key=None) -> dict:
cursor = self.__con.cursor(dictionary=True)
cursor.callproc("getApplicant", (employee_id, enc_key))
it = cursor.fetchone()
cursor.close()
return it
cursor = self._connection.cursor(dictionary=True)
try:
cursor.callproc("getApplicant", (employee_id, enc_key))
it = cursor.fetchone()
return it
finally:
cursor.close()
def addEmployee(self, data, enc_key, applicant=True):
cursor = self.__con.cursor()
cursor.callproc("addApplicant", (json.dumps(data), applicant, enc_key,))
self.__con.commit()
cursor.close()
self.newEmployeeAdded.emit(True)
def addApplicant(self, data, enc_key, applicant=True):
cursor = self._connection.cursor()
try:
cursor.callproc("addApplicant", (json.dumps(data), applicant, enc_key,))
self._connection.commit()
self.newEmployeeAdded.emit(True)
finally:
cursor.close()

View File

@@ -10,7 +10,6 @@ class EmployeeModel(QAbstractTableModel):
addedNewEmployee = Signal(bool)
__data = None
__employee_dao = None
__visible_index = None
__visible_columns = None
__col_name = ""
__col_skip = 2
@@ -18,7 +17,7 @@ class EmployeeModel(QAbstractTableModel):
def __init__(self):
super().__init__()
self.__employee_dao = EmployeeDAO()
self.__employee_dao = EmployeeDAO()
self.__employee_dao.newEmployeeAdded.connect(self.__refreshView)
self.__conf = ConfigLoader().getConfig()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
@@ -29,11 +28,11 @@ class EmployeeModel(QAbstractTableModel):
if 'worklicense' in new_employee:
new_employee['worklicense'] = int(new_employee['worklicense'])
new_employee['residencetype'] = int(new_employee['residencetype'])
self.__employee_dao.addEmployee(new_employee, self.__key, False)
self.__employee_dao.addApplicant(new_employee, self.__key, False)
@Slot(QJSValue)
def addApplicant(self, applicant: QJSValue):
self.__employee_dao.addEmployee({
self.__employee_dao.addApplicant({
"city": applicant.property("city").toString(),
"email": applicant.property("email").toString(),
"firstname": applicant.property("firstname").toString(),
@@ -72,29 +71,21 @@ class EmployeeModel(QAbstractTableModel):
self.__col_skip = 2
self.__getData(criterion, criterion == 'Erledigt', False, criterion == 'Alle')
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if role == Qt.ItemDataRole.DisplayRole:
row = self.__data[index.row()]
applicant_col = index.column() + self.__col_skip
tr = row[
applicant_col] # if type(row[index.column() + 2]) is str else str(row[index.column() + 2], "utf-8")
tr = row[applicant_col]
if applicant_col == 2 and self.__everyone:
tr = 'Ja' if tr == 1 else 'Nein'
else:
if tr:
tr = re.sub("Keine Angabe ", "", tr)
# print(f"Data: {tr}")
# return row[index.column() + 2]
return tr
return None
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
self.__col_name = self.__visible_columns[section + self.__col_skip]
return self.__col_name
return super().headerData(section, orientation, role)
@Slot(int, result=dict)
def fetchApplicant(self, row) -> dict:
employee_id = self.__data[row][0]
return self.__employee_dao.fetchApplicant(employee_id, self.__key)

View File

@@ -1,17 +1,14 @@
from .DbManager import DbManager
import json
import mariadb
from PySide6.QtCore import QObject, Signal
# from ..PyqcrmFlags import PyqcrmAppliEmpyFlags
from lib.domain.BaseModel import database
class ObjectDAO(QObject):
newObjectAdded = Signal(bool, int)
def __init__(self):
super().__init__()
#print(f"*** File: {__file__}, __init__()")
self.__con = DbManager().getConnection()
self.__con = database.connection()
if self.__con:
self.__cur = self.__con.cursor()

View File

@@ -1,20 +1,21 @@
# This Python file uses the following encoding: utf-8
from .DbManager import DbManager
from ..PyqcrmFlags import PyqcrmFlags
import mariadb
from PySide6.QtCore import QObject, Signal
from lib.domain.BaseModel import database
class UserDAO(QObject):
noDbConnection = Signal(str)
__cursor = None
def __init__(self):
#print(f"*** File: {__file__}, init()")
super().__init__()
self.__con = DbManager().getConnection()
self.__con = database.connection()
if self.__con:
self.__cur = self.__con.cursor()
def createUser(self, username, password, info, role= PyqcrmFlags.USER):
def createUser(self, username, password, info, role=PyqcrmFlags.USER):
user_created = False
try:
if self.__cur:
@@ -39,6 +40,3 @@ class UserDAO(QObject):
except mariadb.Error as e:
print(str(e))
self.noDbConnection.emit(str(e))

View File

@@ -1,35 +1,28 @@
from .DbManager import DbManager
from PySide6.QtCore import Slot, QObject, Signal
from lib.domain.BaseModel import database
from .UserDAO import UserDAO
from ..PyqcrmFlags import PyqcrmFlags
from ..Vermasseln import Vermasseln
#from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput : Not working well with Nuitka
import soundfile as sf
import sounddevice as sd
from .UserDAO import UserDAO
from PySide6.QtCore import Slot, QObject, Signal, QUrl, QFile
import tempfile
class UserManager(QObject):
loginOkay = Signal()
noDbConnection = Signal(str)
def __init__(self, user_config = None, role = None):
def __init__(self, user_config=None, role=None):
super().__init__()
self.__con = DbManager().getConnection()
self.__con = database.connection()
self.__user_dao = UserDAO()
self.__user_dao.noDbConnection.connect(self.noDbConnection)
if self.__con:
self.__cur = self.__con.cursor()
if user_config and role:
self.__username = user_config["PYQCRM_USER"]
self.__password = user_config["PYQCRM_USER_PASS"]
self.__info = user_config["PYQCRM_USER_INFO"]
self.__role = role if role == PyqcrmFlags.ADMIN else 0
def createUser(self):
self.__hashPassword()
user_created = self.__user_dao.createUser(self.__username, self.__password, self.__info, self.__role)
@@ -64,25 +57,6 @@ class UserManager(QObject):
user = self.__user_dao.getUser(username)
if user:
self.__checkPassword(password, user[2])
else:
fail_src = ":/sounds/fail2c.ogg"
with tempfile.NamedTemporaryFile(suffix='.ogg') as ogg_file:
failure_sound = QFile(fail_src)
if not failure_sound.open(QFile.ReadOnly):
print(f"Failed to open resource file: {fail_src}")
else:
ogg_file.write(failure_sound.readAll())
ogg_path = ogg_file.name
fail, samplerate = sf.read(ogg_path)
sd.play(fail, samplerate)
### Not working with Nuitka
# player = QMediaPlayer(self)
# audioOutput = QAudioOutput(self)
# player.setAudioOutput(audioOutput)
# player.setSource(QUrl("qrc:/sounds/fail2c.ogg"))
# audioOutput.setVolume(150)
# player.play()
def __checkPassword(self, password, hash_password):
pw_list = hash_password.split("$")
@@ -90,6 +64,3 @@ class UserManager(QObject):
hash_pw = Vermasseln.userPasswordHash(password, pw_list[0])
if hash_password == hash_pw:
self.loginOkay.emit()

31
lib/domain/Applicant.py Normal file
View File

@@ -0,0 +1,31 @@
from peewee import CharField, UUIDField, SmallIntegerField, TextField, ForeignKeyField, JOIN
from lib.domain.Town import Town
from lib.domain.BaseModel import BaseModel
from lib.domain.ZipCode import ZipCode
class Applicant(BaseModel):
class Meta:
table_name = "applicants"
id = UUIDField(primary_key=True)
title = SmallIntegerField(default=0)
first_name = CharField(null=False)
last_name = CharField(null=False)
street = CharField()
house_number = CharField()
zip_code = ForeignKeyField(ZipCode, column_name="zip_code", null=True)
phone_number = CharField()
mobile_number = CharField()
email_address = CharField()
salutation = TextField(null=False)
@classmethod
def select_table_data(cls):
return (
Applicant
.select(Applicant.id, Applicant.first_name, Applicant.last_name, ZipCode.id, ZipCode.zip_code, Town.town)
.join(ZipCode, join_type="LEFT JOIN")
.join(Town, join_type="LEFT JOIN")
)

8
lib/domain/BaseModel.py Normal file
View File

@@ -0,0 +1,8 @@
from peewee import Model, MySQLDatabase
database = MySQLDatabase(None)
class BaseModel(Model):
class Meta:
database = database

14
lib/domain/Country.py Normal file
View File

@@ -0,0 +1,14 @@
from peewee import AutoField, CharField
from lib.domain.BaseModel import BaseModel
class Country(BaseModel):
class Meta:
table_name = "country"
id = AutoField(column_name="countryid")
name = CharField(max_length=200, unique=True)
name_short = CharField(max_length=100, column_name="countryshort")
nationality = CharField(max_length=100)
iso2 = CharField(max_length=2, unique=True)
iso3 = CharField(max_length=3, unique=True)

12
lib/domain/Town.py Normal file
View File

@@ -0,0 +1,12 @@
from peewee import AutoField, CharField, ForeignKeyField
from lib.domain.BaseModel import BaseModel
from lib.domain.Country import Country
class Town(BaseModel):
class Meta:
table_name = "address"
id = AutoField(column_name="addressid")
town = CharField(max_length=50, column_name="city")
country = ForeignKeyField(Country, column_name="countryid", backref="towns")

13
lib/domain/ZipCode.py Normal file
View File

@@ -0,0 +1,13 @@
from peewee import AutoField, CharField, ForeignKeyField
from lib.domain.BaseModel import BaseModel
from lib.domain.Town import Town
class ZipCode(BaseModel):
class Meta:
table_name = "postcode"
id = AutoField(column_name="postcodeid")
zip_code = CharField(max_length=15, column_name="postcode")
town = ForeignKeyField(Town, backref="zip_codes", column_name="addressid")

62
main.py
View File

@@ -1,44 +1,35 @@
# # !/home/linuxero/proj/tero/pyqcrm/.qtcreator/venv-3.13.1/bin/python3
import os
import sys
import logging
from PySide6.QtNetwork import QLocalServer, QLocalSocket
from PySide6.QtWidgets import QSystemTrayIcon
from PySide6.QtGui import QGuiApplication, QIcon
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QIODevice
from lib.ConfigLoader import ConfigLoader
from lib.DB.BusinessModel import BusinessModel
# noinspection PyUnresolvedReferences
import rc_pyqcrm
# noinspection PyUnresolvedReferences
import rc_qml
from lib.DB.DbManager import DbManager
from lib.DB.UserManager import UserManager
from PySide6.QtCore import QIODevice
from PySide6.QtGui import QGuiApplication, QIcon
from PySide6.QtNetwork import QLocalServer, QLocalSocket
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtWidgets import QSystemTrayIcon
from lib.ConfigLoader import ConfigLoader
from lib.DB.AddressModel import AddressModel
from lib.DB.ApplicantModel import ApplicantModel
from lib.DB.BTypeModel import BTypeModel
from lib.DB.BusinessModel import BusinessModel
from lib.DB.ContactModel import ContactModel
from lib.DB.EmployeeModel import EmployeeModel
from lib.DB.ObjectModel import ObjectModel
from lib.DB.UserManager import UserManager
from lib.Printers import Printers
from lib.domain.BaseModel import database
os.environ['QML_XHR_ALLOW_FILE_READ'] = '1'
# [pyqcrm]
# program-name=""
# version=
# [database]
# server=""
# port=
# user=""
# password=""
# name=""
# type=""
bad_config = False
db_con = False
address_model = None
applicant_model = None
business_model = None
business_type = None
contact_model = None
@@ -49,17 +40,26 @@ user = None
def initializeProgram():
print(f"In {__file__} file, initializeProgram()")
global address_model, bad_config, business_model, user, business_type, contact_model, employee_model, object_model, db_con, printers
global address_model, applicant_model, bad_config, business_model, user, business_type, contact_model, employee_model, object_model, db_con, printers
if not bad_config:
dbconf = config.getConfig()['database']
DbManager(dbconf)
database.init(
host=dbconf['DB_HOST'],
user=dbconf['DB_USER'],
password=dbconf['DB_PASS'],
database=dbconf['DB_NAME'],
port=int(dbconf['DB_PORT']),
connect_timeout=5,
)
database.connect()
printers = Printers()
if DbManager().getConnection():
if not database.is_closed():
db_con = True
user = UserManager()
business_model = BusinessModel()
address_model = AddressModel()
applicant_model = ApplicantModel()
business_type = BTypeModel()
contact_model = ContactModel()
employee_model = EmployeeModel()
@@ -74,11 +74,11 @@ def configReady():
def publishContext():
# print(f"In {__file__} file, publishContext()")
global engine, address_model, bad_config, business_model, user, business_type, contact_model, object_model, employee_model, printers
global engine, address_model, applicant_model, bad_config, business_model, user, business_type, contact_model, object_model, employee_model, printers
engine.rootContext().setContextProperty("loggedin_user", user)
engine.rootContext().setContextProperty("business_model", business_model)
engine.rootContext().setContextProperty("address_model", address_model)
engine.rootContext().setContextProperty("applicantModel", applicant_model)
engine.rootContext().setContextProperty("business_type", business_type)
engine.rootContext().setContextProperty("contact_model", contact_model)
engine.rootContext().setContextProperty("employee_model", employee_model)
@@ -105,7 +105,7 @@ if __name__ == "__main__":
qml_file = "qrc:/Gui/main.qml"
icon = QIcon(":/images/tero.jpg")
icon = QIcon("qrc:/images/tero.jpg")
app.setWindowIcon(icon)
tray = QSystemTrayIcon()

View File

@@ -7,7 +7,6 @@
"lib/DB/BusinessModel.py",
"pyqcrm.qrc",
"qml.qrc",
"lib/DB/DbManager.py",
"lib/DB/UserManager.py",
"lib/PyqcrmFlags.py",
"lib/DB/UserDAO.py",

View File

@@ -9,6 +9,7 @@
<file>images/ChevronDown.svg</file>
<file>images/Funnel.svg</file>
<file>images/Identification-Outline.svg</file>
<file>images/InboxArrowDown.svg</file>
<file>images/MagnifyingGlass.svg</file>
<file>images/Newspaper-Outline.svg</file>
<file>images/Phone.svg</file>
@@ -19,14 +20,8 @@
<file>images/UserCircle.svg</file>
<file>images/UserGroup-Outline.svg</file>
<file>images/Wallet-Outline.svg</file>
<file>sounds/error.ogg</file>
<file>sounds/fail2c.ogg</file>
<file>sounds/puzzerr.ogg</file>
<file>sounds/sysnotify.ogg</file>
<file>sounds/wrong.ogg</file>
<file>fonts/RobotoCondensed.otf</file>
<file>README</file>
<file>LICENSE</file>
<file>images/tero.jpg</file>
</qresource>
</RCC>

View File

@@ -39,11 +39,11 @@
<file>Gui/OffersTable.qml</file>
<file>Gui/Employees/AddApplicant.qml</file>
<file>Gui/Employees/AddEmployee.qml</file>
<file>Gui/Employees/ApplicantPersonalData.qml</file>
<file>Gui/Employees/ApplicantBankData.qml</file>
<file>Gui/Employees/ApplicantForm.qml</file>
<file>Gui/Employees/ApplicantNationalInsurance.qml</file>
<file>Gui/Employees/ApplicantVarious.qml</file>
<file>Gui/Employees/EmployeePersonalData.qml</file>
<file>Gui/Employees/EmployeeBankData.qml</file>
<file>Gui/Employees/EmployeeNationalInsurance.qml</file>
<file>Gui/Employees/EmployeeVarious.qml</file>
<file>Gui/Employees/EmployeeDetails.qml</file>
<file>Gui/Employees/EmployeesTable.qml</file>
<file>Gui/Employees/qmldir</file>

View File

@@ -3,7 +3,8 @@ platformdirs
pycryptodome
psutil
toml
mariadb
soundfile
sounddevice
reportlab
peewee
pymysql

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.