1 Commits

17 changed files with 186 additions and 700 deletions

View File

@@ -1,13 +1,14 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls
import TeroStyle import TeroStyle
import Js import Js
ColumnLayout { ScrollView
{
ColumnLayout {
anchors.fill: parent
spacing: Dimensions.l spacing: Dimensions.l
Component.onCompleted: { Component.onCompleted: {
employee_model.addedNewEmployee.connect(successful => { employee_model.addedNewEmployee.connect(successful => {
if (successful) if (successful)
@@ -47,4 +48,5 @@ ColumnLayout {
} }
} }
} }
}
} }

View File

@@ -5,10 +5,6 @@ import Js
ScrollView ScrollView
{ {
id: scroll
width: parent.width
height: parent.height
function checkFields() { function checkFields() {
if (!personalData.checkPersonalField()) if (!personalData.checkPersonalField())
saveBtn.enabled = false; saveBtn.enabled = false;
@@ -23,14 +19,14 @@ ScrollView
console.log('failedtoadd'); console.log('failedtoadd');
} }
} }
ColumnLayout { ColumnLayout {
id: colPar id: colPar
height: Screen.desktopAvailableHeight
width: scroll.width
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
anchors.fill: parent
implicitWidth: parent.width implicitWidth: parent.width
Component.onCompleted: { Component.onCompleted: {
@@ -42,12 +38,15 @@ ScrollView
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
font.pixelSize: 35 font.pixelSize: 35
text: qsTr("Mitarbeiter hinzufügen") text: qsTr("Mitarbeiter / Bewerber hinzufügen")
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Dimensions.l spacing: Dimensions.l
Frame { Frame {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.fillWidth: true Layout.fillWidth: true

View File

@@ -292,10 +292,6 @@ GridLayout {
Layout.fillWidth: true Layout.fillWidth: true
placeholderTextColor: "red" placeholderTextColor: "red"
validator: RegularExpressionValidator {
regularExpression: /([0-9]{1,3})/
}
onTextChanged: checkFields() onTextChanged: checkFields()
} }
Label { Label {

View File

@@ -27,7 +27,7 @@ ColumnLayout {
ListElement { ListElement {
name: "Mitarbeiter" name: "Mitarbeiter"
selected: false selected: false
text: qsTr("Mitarbeiter") text: qsTr("Kunde")
} }
ListElement { ListElement {
name: "Erledigt" name: "Erledigt"

View File

@@ -4,12 +4,18 @@ import QtQuick.Controls
import QtQuick.Dialogs import QtQuick.Dialogs
import Js import Js
ColumnLayout ScrollView
{ {
id: scroll
width: parent.width
height: parent.height
ColumnLayout
{
height: Screen.desktopAvailableHeight
width: scroll.width
property var new_object: null property var new_object: null
//property alias checkAddContact: checkAddContact //property alias checkAddContact: checkAddContact
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 15 spacing: 15
Label Label
{ {
@@ -138,6 +144,8 @@ ColumnLayout
// } // }
// } // }
}
function checkFields() function checkFields()
{ {
if(checkAddObjectContact.checked) if(checkAddObjectContact.checked)

View File

@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import "qrc:/TeroStyle"
Window Window
{ {

View File

@@ -1,7 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
//import "qrc:/TeroStyle"
Item Item
{ {

0
TeroStyle/Button.qml Executable file → Normal file
View File

View File

@@ -1,15 +0,0 @@
import QtQuick
import QtQuick.Controls
Item
{
id: teroStyle
anchors.fill: parent
Rectangle
{
anchors.fill: parent
color: "dodgerblue"
}
}

0
TeroStyle/qmldir Executable file → Normal file
View File

View File

@@ -1,56 +1,4 @@
"""! @brief Defines the configuration class.""" # This Python file uses the following encoding: utf-8
##
# @file ConfigLoader.py
#
# @brief Defines the ConfigLoader class.
#
# @section description_configloader Description
# Defines the base class for the program configuration.
# - ConfigLoader (base class)
#
# @section libraries_configloader Libraries/Modules
# - <a href="https://docs.python.org/3/library/os.html">os</a> standard library
# - Access to system specific information.
# - <a href="https://docs.python.org/3/library/urllib.html">urllib</a> Python package
# - Collects several modules for working with URLs.
# - <a href="https://pypi.org/project/toml">toml</a> Python library
# - A Python library for parsing and creating TOML.
# - <a href="https://pypi.org/project/platformdirs/">platformdirs</a> Python package
# - Determining appropriate platform-specific dirs.
# - <a href="https://docs.python.org/3/library/pathlib.html">pathlib</a> Python module
# - Offers classes representing filesystem paths with semantics appropriate for different operating systems.
# - <a href="https://docs.python.org/3/library/shutil.html">shutil</a> Python module
# - Offers a number of high-level operations on files and collections of files.
# - <a href="https://docs.python.org/3/library/base64.html">base64</a> Python standard library
# - Provides functions for encoding binary data to printable ASCII characters and decoding such encodings back to binary data.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QObject.html">QObject</a> PySide6 class
# - The base class of all Qt objects.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Slot.html">Slot</a> PySide6 function
# - A function that is called in response to a particular signal.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Signal.html">Signal</a> PySide6 class
# - Provides a way to declare and connect Qt signals in a pythonic way.
# - <a href="https://pycryptodome.readthedocs.io/en/latest/src/api.html">Crypto</a> Python package
# - Provides cryptographic functionalities.
# - DbManager local class
# - Provides a singleton database connection for the entire program.
# - UserManager local class
# - Provides a model to handle users.
# - PyqcrmFlags local ENUM
# - Provides ENUMS to facilitate working in the program.
# - Vermasseln local class
# - Provides encryption functionality for the program.
#
# @section notes_configloader Notes
# - Needs a database connection.
#
# @section todo_configloader TODO
# - None.
#
# @section author_configloader Author(s)
# - Created by Linuxero on 03/14/2025.
# - Modified by Linuxero on 03/14/2025.
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
import toml import toml
from platformdirs import user_config_dir from platformdirs import user_config_dir
from pathlib import Path from pathlib import Path
@@ -69,10 +17,6 @@ from .PyqcrmFlags import PyqcrmFlags
class ConfigLoader(QObject): class ConfigLoader(QObject):
"""! The ConfigLoader class.
Defines the class utilized by all different parts of the program.
Handles the local configuration of the whole program.
"""
__config = None __config = None
__version = "0.1-alpha" __version = "0.1-alpha"
__check_enc_key = True __check_enc_key = True
@@ -86,8 +30,6 @@ class ConfigLoader(QObject):
invalidEncryptionKey = Signal() invalidEncryptionKey = Signal()
def __init__(self): def __init__(self):
"""! The ConfigLoader class initializer.
"""
super().__init__() super().__init__()
# print(f"In {__file__} file, __init__()") # print(f"In {__file__} file, __init__()")
self.config_dir = user_config_dir() + '/pyqcrm' self.config_dir = user_config_dir() + '/pyqcrm'
@@ -102,10 +44,6 @@ class ConfigLoader(QObject):
@Slot(dict, result = bool) @Slot(dict, result = bool)
def setConfig(self, app_config): def setConfig(self, app_config):
"""! Prepares the configuration of the program.
@param app_config The configuration as a dictionary.
@return True on success, False on failure.
"""
# print(f"In {__file__} file, setConfig()") # print(f"In {__file__} file, setConfig()")
if not self.__config: if not self.__config:
base_conf = self.__initializeConfig() base_conf = self.__initializeConfig()
@@ -120,8 +58,6 @@ class ConfigLoader(QObject):
self.configurationReady.emit() self.configurationReady.emit()
def __initializeConfig(self): def __initializeConfig(self):
"""! Creates the initial configuration of the program.
"""
# print(f"In {__file__} file, __initializeConfig()") # print(f"In {__file__} file, __initializeConfig()")
self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8") self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8")
conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n" conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n"
@@ -130,10 +66,6 @@ class ConfigLoader(QObject):
return conf return conf
def __checkDbConnection(self, db_config): def __checkDbConnection(self, db_config):
"""! Tests for a valid database connection.
@param db_config The configuration of the database connection as a dictionary.
@return True on success, False on failure.
"""
# print(f"In {__file__} file, __checkDbConnection()") # print(f"In {__file__} file, __checkDbConnection()")
con = DbManager(db_config['database']).getConnection() con = DbManager(db_config['database']).getConnection()
if con: if con:
@@ -145,8 +77,6 @@ class ConfigLoader(QObject):
def __saveConfig(self): def __saveConfig(self):
"""! Saves the configuration of the program.
"""
# print(f"In {__file__} file, saveConfig()") # print(f"In {__file__} file, saveConfig()")
try: try:
with open (self.config_dir + '/pyqcrm.toml', 'w') as f: with open (self.config_dir + '/pyqcrm.toml', 'w') as f:
@@ -158,9 +88,6 @@ class ConfigLoader(QObject):
def __checkAdminUser(self): def __checkAdminUser(self):
"""! Checks for a valid admin account of the program.
@return True on success, False on failure.
"""
# print(f"In {__file__} file, __checkAdminUser()") # print(f"In {__file__} file, __checkAdminUser()")
result = UserManager().checkAdmin() result = UserManager().checkAdmin()
if not result: if not result:
@@ -173,10 +100,6 @@ class ConfigLoader(QObject):
@Slot(dict, result= bool) @Slot(dict, result= bool)
def addAdminUser(self, user_config): def addAdminUser(self, user_config):
"""! Adds an admin account.
@param user_config The credentials of the admin account as a dictionary.
@return True on success, False on failure.
"""
# print(f"In {__file__} file, addAdminUser()") # print(f"In {__file__} file, addAdminUser()")
admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser() admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser()
if not admin: if not admin:
@@ -191,12 +114,6 @@ class ConfigLoader(QObject):
@Slot(str, str) @Slot(str, str)
def __saveData(self, recovery_file, recovery_password, data): def __saveData(self, recovery_file, recovery_password, data):
"""! Generic function to save backups to a file.
This function emits a configurationReady signal.
@param recovery_file The full path of the backup file.
@param recovery_password password to secure the backup file.
@param data The content of the backup file.
"""
# print(f"In {__file__} file, __saveData()") # print(f"In {__file__} file, __saveData()")
local = False local = False
rp = self.__setRecoveryPassword(recovery_password) rp = self.__setRecoveryPassword(recovery_password)
@@ -216,11 +133,6 @@ class ConfigLoader(QObject):
@Slot(str, str) @Slot(str, str)
def getRecoveryKey(self, recovery_file, recovery_password): def getRecoveryKey(self, recovery_file, recovery_password):
"""! Loads the encryption key from a backup.
This function emits a configurationReady signal.
@param recovery_file The full path of the backup file.
@param recovery_password password to secure the backup file.
"""
rec_file = urlparse(recovery_file) rec_file = urlparse(recovery_file)
rec_file = rec_file.path rec_file = rec_file.path
if os.name == "nt": if os.name == "nt":
@@ -238,11 +150,6 @@ class ConfigLoader(QObject):
print(str(e)) print(str(e))
def __parseImport(self, rec_file, recovery_password): def __parseImport(self, rec_file, recovery_password):
"""! Loads the content from a backup.
@param rec_file The full path of the backup file.
@param recovery_password password used to secure the backup file.
@return The content on success, None on failure.
"""
local = False local = False
with open(rec_file, "r") as f: with open(rec_file, "r") as f:
@@ -259,40 +166,23 @@ class ConfigLoader(QObject):
return None return None
def __invalidateEncryptionKey(self): def __invalidateEncryptionKey(self):
"""! Flag the encryption key as invalid.
"""
# print(f"In {__file__} file, __invalidateEncryptionKey()") # print(f"In {__file__} file, __invalidateEncryptionKey()")
self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'No' self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'No'
self.__saveConfig() self.__saveConfig()
@Slot() @Slot()
def checkEncryptionKey(self): def checkEncryptionKey(self):
"""! Checks the validity of the encryption key.
This function emits an invalidEncryptionKey signal.
"""
# print(f"In {__file__} file, __checkEncryptionKey()") # print(f"In {__file__} file, __checkEncryptionKey()")
if self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] == 'No': if self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] == 'No':
self.invalidEncryptionKey.emit() self.invalidEncryptionKey.emit()
def __checkRecoveryPassword(self, recovery_password, password, salt): def __checkRecoveryPassword(self, recovery_password, password, salt):
"""! Generic function to save backups to a file.
This function emits a configurationReady signal.
@param recovery_password The password from the backup file.
@param password The password used when creating the backup file.
@param salt A salt to hash the password.
@return A password.
"""
# print(f"In {__file__} file, __checkRecoveryPassword()") # print(f"In {__file__} file, __checkRecoveryPassword()")
rp = self.__setRecoveryPassword(recovery_password, salt) rp = self.__setRecoveryPassword(recovery_password, salt)
return rp[1] == password return rp[1] == password
@Slot(str, str) # todo: non local encryption @Slot(str, str) # todo: non local encryption
def importConfig(self, confile, password): def importConfig(self, confile, password):
"""! Generic function to import configuration from a backup.
This function emits a invalidEncryptionKey signal.
@param conffile The path of the backup file.
@param password The password used when creating the backup file.
"""
confile = urlparse(confile) confile = urlparse(confile)
confile = confile.path confile = confile.path
if os.name == "nt": if os.name == "nt":
@@ -310,9 +200,6 @@ class ConfigLoader(QObject):
print(str(e)) print(str(e))
def __configLoad(self): def __configLoad(self):
"""! Loads the program configuration.
This function emits a configurationReady signal.
"""
# print(f"In {__file__} file, __configLoad()") # print(f"In {__file__} file, __configLoad()")
try: try:
with open (self.config_dir + '/pyqcrm.toml', 'r') as f: with open (self.config_dir + '/pyqcrm.toml', 'r') as f:
@@ -328,9 +215,6 @@ class ConfigLoader(QObject):
def getConfig(self): def getConfig(self):
"""! Returns the program configuration.
@return configuration as a toml file.
"""
# print(f"In {__file__} file, getConfig()") # print(f"In {__file__} file, getConfig()")
# print(self.__config) # print(self.__config)
return self.__config return self.__config
@@ -349,27 +233,17 @@ class ConfigLoader(QObject):
@Slot(str, str) @Slot(str, str)
def backupConfig(self, filename, password): def backupConfig(self, filename, password):
"""! Saves the program configuration.
@param filename the path of the backup file.
@param password the password to secure the backup.
"""
conf_file = toml.dumps(self.getConfig()) conf_file = toml.dumps(self.getConfig())
self.__saveData(filename, password, conf_file) self.__saveData(filename, password, conf_file)
@Slot(dict) @Slot(dict)
def saveDbConf(self, db = None): def saveDbConf(self, db = None):
"""! Saves/Upates the database configuration.
@param db Database configuration as a dictionary.
"""
self.__config.update(db) self.__config.update(db)
self.__saveConfig() self.__saveConfig()
@Slot(result = dict) @Slot(result = dict)
def getDbConf(self): def getDbConf(self):
"""! Loads the database configuration.
@return Database configuration as a dictionary on success, None on failure.
"""
try: try:
return self.__config['database'] return self.__config['database']
except KeyError as ex: except KeyError as ex:
@@ -378,17 +252,11 @@ class ConfigLoader(QObject):
@Slot(dict) @Slot(dict)
def saveCompanyInfo(self, company = None): def saveCompanyInfo(self, company = None):
"""! Saves/Upates the company information.
@param company Company configuration as a dictionary.
"""
self.__config.update(company) self.__config.update(company)
self.__saveConfig() self.__saveConfig()
@Slot(result = dict) @Slot(result = dict)
def getCompanyInfo(self): def getCompanyInfo(self):
"""! Loads the company information.
@return Company information as a dictionary on success, None on failure.
"""
try: try:
return self.__config['company'] return self.__config['company']
except KeyError as ex: except KeyError as ex:
@@ -397,17 +265,11 @@ class ConfigLoader(QObject):
@Slot(dict) @Slot(dict)
def saveMiscConf(self, misc_conf = None): def saveMiscConf(self, misc_conf = None):
"""! Saves/Upates the miscellaneous configuration.
@param misc_conf Extra configuration as a dictionary.
"""
self.__config.update(misc_conf) self.__config.update(misc_conf)
self.__saveConfig() self.__saveConfig()
@Slot(result = bool) @Slot(result = bool)
def systray(self): def systray(self):
"""! Loads the system tray configuration.
@return boolean if system tray is set to be used, False can also be returned on failure.
"""
try: try:
return self.__config['misc']['SYSTRAY'] return self.__config['misc']['SYSTRAY']
except KeyError as ex: except KeyError as ex:
@@ -417,10 +279,6 @@ class ConfigLoader(QObject):
@Slot(str, str) @Slot(str, str)
def backupEncryptkey(self, filename, password): def backupEncryptkey(self, filename, password):
"""! Saves/Upates the encryption key.
@param filename Path to save the key.
@param password Password to secure the backup.
"""
encrypt_key = self.__config['pyqcrm']['ENCRYPTION_KEY'] encrypt_key = self.__config['pyqcrm']['ENCRYPTION_KEY']
self.__saveData(filename, password, encrypt_key) self.__saveData(filename, password, encrypt_key)

View File

@@ -1,47 +1,11 @@
"""! @brief Defines the low-level DAO class to handle addresses."""
##
# @file AddressDAO.py
#
# @brief Defines the AddressDAO class.
#
# @section description_addressdao Description
# Defines the low-lever DAO class to handle CRUD operations on addresses.
# - AddressDAO (DAO class)
#
# @section libraries_addressdao Libraries/Modules
# - <a href="https://pypi.org/project/mariadb/">mariadb</a> Python module
# - MariaDB Connector/Python enables python programs to access MariaDB and MySQL databases, using an API which is compliant with the Python DB API 2.0 (PEP-249).
# - <a href="https://docs.python.org/3/library/json.html">json</a> Python standard library
# - JSON encoder and decoder.
# - <a href="https://docs.python.org/3/library/random.html">DbManager</a> Local class
# - Singleton class to provide for the database connection.
#
# @section notes_addressdao Notes
# - None.
#
# @section todo_addressdao TODO
# - None.
#
# @section author_addressdao Author(s)
# - Created by Linuxero on 03/14/2025.
# - Modified by Linuxero on 03/14/2025.
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
from .DbManager import DbManager from .DbManager import DbManager
import mariadb import mariadb
import json import json
class AddressDAO: class AddressDAO:
"""! The AddressDAO class.
Defines a low-level class utilized for DAO.
Handles the address CRUD operations.
"""
__cur = None __cur = None
def __init__(self): def __init__(self):
"""! The AddressDAO class initializer.
"""
#print(f"*** File: {__file__}, init()") #print(f"*** File: {__file__}, init()")
self.__con = DbManager().getConnection() self.__con = DbManager().getConnection()
@@ -50,11 +14,8 @@ class AddressDAO:
def __importPlz(self): def __importPlz(self):
"""! Utility function to import zipcodes from an external databases. with open("/home/dstoppek/Coden/Projekte/pyqcrm/doc/postleitzahl.json", "r") as plz:
"""
with open("pfad zur datei", "r") as plz:
postcodes = json.load(plz) postcodes = json.load(plz)
country = "Deutschland" country = "Deutschland"
@@ -76,9 +37,7 @@ class AddressDAO:
print("FINISHED")# print("FINISHED")#
def __importCountry(self): def __importCountry(self):
"""! Utility function to import countries information from an external databases. with open("/home/dstoppek/Coden/Projekte/pyqcrm/doc/staaten.json", "r") as country:
"""
with open("pfad zur datei", "r") as country:
countries = json.load(country) countries = json.load(country)
old = "" old = ""
try: try:
@@ -108,11 +67,6 @@ class AddressDAO:
def getAddressData(self, all = True, zipcode = None): def getAddressData(self, all = True, zipcode = None):
"""! Loads available addresses in the program.
@param all Filter to get specific addresses as a boolean.
@param zipcode Specific zipcode pattern.
@return Found addresses as a dictionary on success, None on failure.
"""
try: try:
if self.__cur: if self.__cur:
self.__cur.callproc("getAddress", (all, zipcode,)) self.__cur.callproc("getAddress", (all, zipcode,))
@@ -124,3 +78,4 @@ class AddressDAO:
print(str(e)) print(str(e))

View File

@@ -1,63 +1,17 @@
"""! @brief Defines the model class to handle addresses."""
##
# @file AddressModel.py
#
# @brief Defines the AddressModel class.
#
# @section description_addressmodel Description
# Defines the model class to handle CRUD operations on addresses.
# - AddressModel (Model class)
#
# @section libraries_addressmodel Libraries/Modules
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractListModel.html">QAbstractListModel</a> PySid6 core Class
# - Provides an abstract model that can be subclassed to create one-dimensional list models.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QModelIndex.html">QModelIndex</a> PySid6 core Class
# - Used to locate data in a data model.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Slot.html">Slot</a> PySide6 function
# - A function that is called in response to a particular signal.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Qt.html">Qt</a> PySid6 core Class
# - A namespace contains miscellaneous identifiers used throughout the Qt library.
# - <a href="">AddressDAO</a> Local class
# - Defines the low-lever DAO class to handle CRUD operations on addresses.
#
# @section notes_addressmodel Notes
# - None.
#
# @section todo_addressmodel TODO
# - None.
#
# @section author_addressmodel Author(s)
# - Created by Linuxero on 03/14/2025.
# - Modified by Linuxero on 03/14/2025.
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
from PySide6.QtCore import QAbstractListModel, Qt, Slot, QModelIndex from PySide6.QtCore import QAbstractListModel, Qt, Slot, QModelIndex
from .AddressDAO import AddressDAO from .AddressDAO import AddressDAO
from ..PyqcrmDataRoles import PyqcrmDataRoles from ..PyqcrmDataRoles import PyqcrmDataRoles
class AddressModel(QAbstractListModel): class AddressModel(QAbstractListModel):
"""! The AddressModel class.
Defines a model class utilized to handle data.
Inherits from QAbstractListModel
Handles the address data operations.
"""
def __init__(self): def __init__(self):
"""! The AddressModel class initializer.
"""
super().__init__() super().__init__()
self.__address_data = AddressDAO().getAddressData() self.__address_data = AddressDAO().getAddressData()
def rowCount(self, parent = QModelIndex()): def rowCount(self, parent = QModelIndex()):
"""! Returns the number of rows under the given parent.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.rowCount">rowCount()</a>
"""
return len(self.__address_data) return len(self.__address_data)
def data(self, index, role = Qt.DisplayRole): def data(self, index, role = Qt.DisplayRole):
"""! Returns the data stored under the given role for the item referred to by the index.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.data">data()</a>
"""
row = index.row() row = index.row()
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
data = self.__address_data[row][5] data = self.__address_data[row][5]
@@ -71,9 +25,6 @@ class AddressModel(QAbstractListModel):
return None return None
def roleNames(self): def roleNames(self):
"""! Returns the models role names.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.roleNames">roleNames()</a>
"""
return { return {
Qt.DisplayRole: b"display", Qt.DisplayRole: b"display",
PyqcrmDataRoles.CITY_ROLE: b"city", PyqcrmDataRoles.CITY_ROLE: b"city",
@@ -85,11 +36,6 @@ class AddressModel(QAbstractListModel):
@Slot(bool, str) @Slot(bool, str)
def getAddresses(self, all, zipcode): def getAddresses(self, all, zipcode):
"""! Loads the addresses from the storage backend.
@param all Boolean to specify whether all addresses to be returned or not.
@param zipcode String to look up addresses following a specific zipcode.
@return Returns a dictionary containing the addresses.
"""
data = AddressDAO().getAddressData(all, zipcode) data = AddressDAO().getAddressData(all, zipcode)
return data return data

View File

@@ -1,63 +1,16 @@
"""! @brief Defines the model class to handle type of client."""
##
# @file BTypeModel.py
#
# @brief Defines the BTypeModel class.
#
# @section description_businesstypemodel Description
# Defines the model class to handle CRUD operations on customers types.
# - BTypeModel (Model class)
#
# @section libraries_businesstypemodel Libraries/Modules
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractListModel.html">QAbstractListModel</a> PySid6 core Class
# - Provides an abstract model that can be subclassed to create one-dimensional list models.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QModelIndex.html">QModelIndex</a> PySid6 core Class
# - Used to locate data in a data model.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Slot.html">Slot</a> PySide6 function
# - A function that is called in response to a particular signal.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Qt.html">Qt</a> PySid6 core Class
# - A namespace contains miscellaneous identifiers used throughout the Qt library.
# - <a href="">BTypeDAO</a> Local class
# - Defines the low-lever DAO class to handle CRUD operations on customers types.
#
# @section notes_businesstypemodel Notes
# - None.
#
# @section todo_businesstypemodel TODO
# - None.
#
# @section author_businesstypemodel Author(s)
# - Created by Linuxero on 03/14/2025.
# - Modified by Linuxero on 03/14/2025.
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
from PySide6.QtCore import QAbstractListModel, Qt, QModelIndex from PySide6.QtCore import QAbstractListModel, Qt, QModelIndex
from .BTypeDAO import BTypeDAO from .BTypeDAO import BTypeDAO
class BTypeModel(QAbstractListModel): class BTypeModel(QAbstractListModel):
"""! The BTypeModel class.
Defines a model class utilized to handle data.
Inherits from QAbstractListModel
Handles the customers types data operations.
"""
def __init__(self): def __init__(self):
"""! The AddressModel class initializer.
"""
super().__init__() super().__init__()
self.__btype_data = BTypeDAO().getBType() self.__btype_data = BTypeDAO().getBType()
def rowCount(self, parent = QModelIndex()): def rowCount(self, parent = QModelIndex()):
"""! Returns the number of rows under the given parent.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.rowCount">rowCount()</a>
"""
return len(self.__btype_data) return len(self.__btype_data)
def data(self, index, role = Qt.DisplayRole): def data(self, index, role = Qt.DisplayRole):
"""! Returns the data stored under the given role for the item referred to by the index.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.data">data()</a>
"""
row = index.row() row = index.row()
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
data= self.__btype_data[row][1] data= self.__btype_data[row][1]

View File

@@ -1,41 +1,4 @@
"""! @brief Defines the model class to handle customers.""" # This Python file uses the following encoding: utf-8
##
# @file BusinessModel.py
#
# @brief Defines the BusinessModel class.
#
# @section description_businessmodel Description
# Defines the model class to handle CRUD operations on customers.
# - BusinessModel (Model class)
#
# @section libraries_businessmodel Libraries/Modules
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractListModel.html">QAbstractListModel</a> PySid6 core Class
# - Provides an abstract model that can be subclassed to create one-dimensional list models.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QModelIndex.html">QModelIndex</a> PySid6 core Class
# - Used to locate data in a data model.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Slot.html">Slot</a> PySide6 function
# - A function that is called in response to a particular signal.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Signal.html">Signal</a> PySide6 class
# - Provides a way to declare and connect Qt signals in a pythonic way.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/Qt.html">Qt</a> PySid6 core Class
# - A namespace contains miscellaneous identifiers used throughout the Qt library.
# - <a href="">BusinessDAO</a> Local class
# - Defines the low-lever DAO class to handle CRUD operations on customers.
# - <a href="">ConfigLoader</a> Local class
# - Defines the base class for the program configuration.
#
# @section notes_businessmodel Notes
# - None.
#
# @section todo_businessmodel TODO
# - None.
#
# @section author_businessmodel Author(s)
# - Created by Linuxero on 03/14/2025.
# - Modified by Linuxero on 03/14/2025.
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal
from .BusinessDAO import BusinessDAO from .BusinessDAO import BusinessDAO
# from ..PyqcrmFlags import PyqcrmFlags # from ..PyqcrmFlags import PyqcrmFlags
@@ -99,11 +62,6 @@ from ..ConfigLoader import ConfigLoader
class BusinessModel(QAbstractTableModel): class BusinessModel(QAbstractTableModel):
"""! The BusinessModel class.
Defines a model class utilized to handle data.
Inherits from QAbstractListModel
Handles the customers data operations.
"""
__visible_index = {} __visible_index = {}
__visible_columns = None __visible_columns = None
__col_name = "" __col_name = ""
@@ -112,8 +70,6 @@ class BusinessModel(QAbstractTableModel):
__business_dict = {'business':{}} #,'contact':{}} __business_dict = {'business':{}} #,'contact':{}}
def __init__(self): def __init__(self):
"""! The AddressModel class initializer.
"""
super().__init__() super().__init__()
self.__business_dao = BusinessDAO() self.__business_dao = BusinessDAO()
self.__business_dao.newBusinessAdded.connect(self.__refreshView) self.__business_dao.newBusinessAdded.connect(self.__refreshView)
@@ -122,17 +78,12 @@ class BusinessModel(QAbstractTableModel):
self.__getData() self.__getData()
def __getData(self, criterion = "Alle"): def __getData(self, criterion = "Alle"):
"""! Returns the customers data according to the model.
@param criterion String to specify which customers are to be fetched.
"""
self.beginResetModel() self.beginResetModel()
rows, self.__visible_columns = self.__business_dao.getBusiness(self.__key, criterion) rows, self.__visible_columns = self.__business_dao.getBusiness(self.__key, criterion)
self.__data = rows self.__data = rows
self.endResetModel() self.endResetModel()
def __getBusinessInfo(self): def __getBusinessInfo(self):
"""! Fetches detailed information about a customer.
"""
self.__business_dict['business']['id'] = self.__business[0][0] self.__business_dict['business']['id'] = self.__business[0][0]
self.__business_dict['business']['contactid'] = self.__business[0][1] self.__business_dict['business']['contactid'] = self.__business[0][1]
self.__business_dict['business']['company'] = self.__business[0][2] self.__business_dict['business']['company'] = self.__business[0][2]
@@ -149,30 +100,18 @@ class BusinessModel(QAbstractTableModel):
self.__business_dict['business']['city'] = self.__business[0][13] self.__business_dict['business']['city'] = self.__business[0][13]
def rowCount(self, parent= QModelIndex()): def rowCount(self, parent= QModelIndex()):
"""! Returns the number of rows under the given parent.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.rowCount">rowCount()</a>
"""
return len (self.__data) return len (self.__data)
def columnCount(self, parent= QModelIndex()): def columnCount(self, parent= QModelIndex()):
"""! Returns the number of columns for the children of the given parent.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.columnCount">columnCount()</a>
"""
return len(self.__visible_columns) - 1 return len(self.__visible_columns) - 1
def data(self, index, role = Qt.DisplayRole): def data(self, index, role = Qt.DisplayRole):
"""! Returns the data stored under the given role for the item referred to by the index.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.data">data()</a>
"""
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
row = self.__data[index.row()] row = self.__data[index.row()]
return row[index.column() + 1] return row[index.column() + 1]
return None return None
def headerData(self, section, orientation, role= Qt.DisplayRole): def headerData(self, section, orientation, role= Qt.DisplayRole):
"""! Returns the data for the given role and section in the header with the specified orientation.
Ref. <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QAbstractItemModel.html#PySide6.QtCore.QAbstractItemModel.headerData">headerData()</a>
"""
if orientation == Qt.Horizontal and role ==Qt.DisplayRole: if orientation == Qt.Horizontal and role ==Qt.DisplayRole:
self.__col_name = self.__visible_columns[section + 1] self.__col_name = self.__visible_columns[section + 1]
return self.__col_name return self.__col_name
@@ -193,9 +132,6 @@ class BusinessModel(QAbstractTableModel):
@Slot(int) @Slot(int)
def onRowClicked(self, row): def onRowClicked(self, row):
"""! Handles a selected customer from the GUI.
@param row The number of the customer in the data model.
"""
#print(self.__data) #print(self.__data)
#print(f"Selected table row: {row}, corresponding DB ID: {self.__data[row][0]}") #print(f"Selected table row: {row}, corresponding DB ID: {self.__data[row][0]}")
if not self.__business_dict['business'] or self.__data[row][0] != self.__business_dict['business']['id']: if not self.__business_dict['business'] or self.__data[row][0] != self.__business_dict['business']['id']:
@@ -207,31 +143,19 @@ class BusinessModel(QAbstractTableModel):
@Slot(result = dict) @Slot(result = dict)
def getClientDetails(self): def getClientDetails(self):
"""! Fetches a selected customer from the GUI.
@return A dictionary containing all information of a customer.
"""
return self.__business_dict return self.__business_dict
@Slot(str) @Slot(str)
def viewCriterion(self, criterion): def viewCriterion(self, criterion):
"""! Updates the customers view.
@param criterion The criterion used to look for customers.
"""
self.__getData(criterion) self.__getData(criterion)
@Slot(dict, int) @Slot(dict, int)
def addBusiness(self, business, contact_id): def addBusiness(self, business, contact_id):
"""! Saves a customer view.
@param business The customer to be saved.
@param contact_id The contact person id if available.
"""
self.__business_dao.addBusiness(business, contact_id) self.__business_dao.addBusiness(business, contact_id)
@Slot() @Slot()
def __refreshView(self): def __refreshView(self):
"""! Updates the customers view.
"""
self.__getData() self.__getData()
@Slot(dict) @Slot(dict)

View File

@@ -1,59 +1,17 @@
"""! @brief Defines the encryption class.""" # This Python file uses the following encoding: utf-8
##
# @file Vermasseln.py
#
# @brief Defines the Vermasseln class.
#
# @section description_vermasseln Description
# Defines the base class for the program encryption mechansim.
# - Vermasseln (base class)
#
# @section libraries_configloader Libraries/Modules
# - <a href="https://docs.python.org/3/library/platform.html">platform</a> Python standard library
# - Access to underlying platforms identifying data.
# - <a href="https://docs.python.org/3/library/base64.html">base64</a> Python standard library
# - Provides functions for encoding binary data to printable ASCII characters and decoding such encodings back to binary data.
# - <a href="https://docs.python.org/3/library/random.html">random</a> Python module
# - Implements pseudo-random number generators for various distributions.
# - <a href="https://docs.python.org/3/library/string.html">string</a> Python standard library
# - Common string operations.
# - <a href="https://pycryptodome.readthedocs.io/en/latest/src/api.html">Crypto</a> Python package
# - Provides cryptographic functionalities.
#
# @section notes_vermasseln Notes
# - None.
#
# @section todo_vermasseln TODO
# - None.
#
# @section author_vermasseln Author(s)
# - Created by Linuxero on 03/14/2025.
# - Modified by Linuxero on 03/14/2025.
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
from Crypto.Cipher import AES from Crypto.Cipher import AES
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
import platform import platform
import bcrypt import bcrypt
from Crypto.Hash import SHA256, SHA3_512 from Crypto.Hash import SHA256, SHA3_512
# from Crypto.Protocol.KDF import PBKDF2 from Crypto.Protocol.KDF import PBKDF2
# from Crypto.Random import get_random_bytes from Crypto.Random import get_random_bytes
import random import random
import string import string
class Vermasseln: class Vermasseln:
"""! The Vermasseln class.
Defines the class utilized by different parts of the program.
Handles the encryption/decryption of the whole program.
"""
def oscarVermasseln(self, data, local= True): def oscarVermasseln(self, data, local= True):
"""! Encrypts data.
@param data The data to encrypt.
@param local Is the encryption local to the host or not?
@return encrypted data.
"""
b_data = data.encode("utf-8") b_data = data.encode("utf-8")
cipher = self.__vermasslungsKobold(local) cipher = self.__vermasslungsKobold(local)
@@ -64,11 +22,6 @@ class Vermasseln:
return storable_data return storable_data
def entschluesseln(self, data, local = True): def entschluesseln(self, data, local = True):
"""! Decrypts data.
@param data The data to decrypt.
@param local Is the encryption local to the host or not?
@return decrypted data on success, None on failure.
"""
try: try:
data_list = data.split(".") data_list = data.split(".")
encoded_data = [b64decode(x) for x in data_list] encoded_data = [b64decode(x) for x in data_list]
@@ -86,10 +39,6 @@ class Vermasseln:
return decrypted_data return decrypted_data
def __vermasslungsKobold(self, local = True): def __vermasslungsKobold(self, local = True):
"""! Prepares the encryption key.
@param local Is the encryption local to the host or not?
@return encryption key.
"""
key = platform.processor().encode("utf-8") if local else b"(==daniishtverhaftetwegensexy#)" key = platform.processor().encode("utf-8") if local else b"(==daniishtverhaftetwegensexy#)"
key = key[0:31] key = key[0:31]
hash_key = SHA256.new(key) hash_key = SHA256.new(key)
@@ -100,11 +49,6 @@ class Vermasseln:
@classmethod @classmethod
def userPasswordHash(self, password, salt = None): def userPasswordHash(self, password, salt = None):
"""! Hashes data.
@param password The data to hash.
@param salt The salt to use if available.
@return hashed data.
"""
if not salt: if not salt:
salt = "".join(random.choice(string.ascii_letters + string.digits) for i in range (32)) salt = "".join(random.choice(string.ascii_letters + string.digits) for i in range (32))
hash_pw = (salt + password).encode("utf-8") hash_pw = (salt + password).encode("utf-8")

92
main.py
View File

@@ -1,63 +1,4 @@
# # !/home/linuxero/proj/tero/pyqcrm/.qtcreator/venv-3.13.1/bin/python3 # # !/home/linuxero/proj/tero/pyqcrm/.qtcreator/venv-3.13.1/bin/python3
"""! @brief CRM for cleaning services written in Python, PySide6 and QtQuick."""
##
# @mainpage PYQCRM - Qt for Python CRM
#
# @section description_main Description
# A CRM program for digitally manageing the business of cleaning
# services company.
#
# @section notes_main Notes
# - The project is built using QtCreator 6.8.x
# - Minimum Python version 3.13.x
# - PySide6
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
##
# @file main.py
#
# @brief Main entry point of the program
#
# @section description_pyqcrm Description
# Initialization of the program goes here. Various global functions called from the entry point.
# The GUI application is set up and started here.
#
# @section libraries_main Libraries/Modules
# - <a href="https://docs.python.org/3/library/os.html">os</a> standard library
# - Access to environ, a mapping object of keys and values to set an environment variable.
# - <a href="https://docs.python.org/3/library/sys.html">sys</a> standard library
# - Access to argv and system functions.
# - <a href="https://docs.python.org/3/library/logging.html">logging</a> standard library
# - Access to logging functions.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtNetwork/QLocalServer.html">QLocalServer</a> PySide6 class
# - Provides a local socket based server.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtNetwork/QLocalSocket.html">QLocalSocket</a> PySide6 class
# - Provides a local socket.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/QSystemTrayIcon.html">QSystemTrayIcon</a> PySide6 class
# - Provides an icon for the application in the system tray.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtGui/QIcon.html">QIcon</a> PySide6 class
# - Provides scalable icons in different modes and states.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtGui/QGuiApplication.html">QGuiApplication</a> PySide6 class
# - Manages the GUI applications control flow and main settings.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtQml/QQmlApplicationEngine.html">QQmlApplicationEngine</a> PySide6 class
# - Provides a convenient way to load an application from a single QML file.
# - <a href="https://doc.qt.io/qtforpython-6/PySide6/QtCore/QIODevice.html">QIODevice</a> PySide6 class
# - Base interface class of all I/O devices in Qt.
# - lib module (local)
# - Backbone classes to run the program.
#
# @section notes_pyqcrm Notes
# - Install dependencies (See requirements.txt).
#
# @section todo_pyqcrm TODO
# - A lot of nice-to-haves.
#
# @section author_pyqcrm Author(s)
# - Created by Linuxero on 03/14/2025.
# - Modified by Linuxero on 03/14/2025.
#
# Copyright (c) 2025 Schnaxero. All rights reserved.
# Imports
import os import os
import sys import sys
import logging import logging
@@ -79,10 +20,9 @@ from lib.DB.EmployeeModel import EmployeeModel
from lib.DB.ObjectModel import ObjectModel from lib.DB.ObjectModel import ObjectModel
from lib.Printers import Printers from lib.Printers import Printers
# Environment settings
## Allow local file read.
os.environ['QML_XHR_ALLOW_FILE_READ'] = '1' os.environ['QML_XHR_ALLOW_FILE_READ'] = '1'
# [pyqcrm] # [pyqcrm]
# program-name="" # program-name=""
# version= # version=
@@ -95,40 +35,20 @@ os.environ['QML_XHR_ALLOW_FILE_READ'] = '1'
# name="" # name=""
# type="" # type=""
# Global Constants
## Configuration availability.
bad_config = False bad_config = False
## Database connection available.
db_con = False db_con = False
## The model class for address manipulation.
address_model = None address_model = None
## The model class for customer manipulation.
business_model = None business_model = None
## The model class for customer type manipulation.
business_type = None business_type = None
## The model class for contact manipulation.
contact_model = None contact_model = None
## The model class for employee manipulation.
employee_model = None employee_model = None
## The model class for object manipulation.
object_model = None object_model = None
## The class of available printers on the system.
printers = None printers = None
## The logged-in user.
user = None user = None
# Functions
def initializeProgram(): def initializeProgram():
"""! Initializes the program."""
print(f"In {__file__} file, 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, bad_config, business_model, user, business_type, contact_model, employee_model, object_model, db_con, printers
if not bad_config: if not bad_config:
@@ -146,15 +66,14 @@ def initializeProgram():
object_model = ObjectModel() object_model = ObjectModel()
publishContext() publishContext()
def configReady(): def configReady():
"""! Slot to respond to the validity of the configuration."""
global bad_config global bad_config
bad_config = False bad_config = False
initializeProgram() initializeProgram()
def publishContext(): def publishContext():
"""! Connect necessary modules to the QML GUI."""
# print(f"In {__file__} file, 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, bad_config, business_model, user, business_type, contact_model, object_model, employee_model, printers
engine.rootContext().setContextProperty("loggedin_user", user) engine.rootContext().setContextProperty("loggedin_user", user)
@@ -165,9 +84,8 @@ def publishContext():
engine.rootContext().setContextProperty("employee_model", employee_model) engine.rootContext().setContextProperty("employee_model", employee_model)
engine.rootContext().setContextProperty("object_model", object_model) engine.rootContext().setContextProperty("object_model", object_model)
# Entry point of the program
if __name__ == "__main__": if __name__ == "__main__":
#QResource.registerResource("rc_qml.py")
app = QGuiApplication(sys.argv) app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine() engine = QQmlApplicationEngine()