Fummeljob hierum darum

This commit is contained in:
2025-02-12 09:01:38 +01:00
parent b162117a80
commit bfd1d0974d
31 changed files with 1386 additions and 61 deletions

View File

@@ -143,9 +143,7 @@ Frame
var bd = birthday.text var bd = birthday.text
if (len === 2 || len === 5) birthday.text = bd + "." if (len === 2 || len === 5) birthday.text = bd + "."
} }
} }
} }
Label Label

277
Gui/AddNewObject.qml Normal file
View File

@@ -0,0 +1,277 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
GridLayout
{
id: newObject
columns: 4
Layout.fillWidth: true
Layout.fillHeight: true
rowSpacing: 9
Label
{
text: qsTr("Firma")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
ComboBox
{
property string name: "business"
id: business
editable: true
Layout.fillWidth: true
Layout.columnSpan: 3
}
//// New grid row
Label
{
text: qsTr("Straße")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
property string name: "street"
id: street
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
onTextChanged: checkFields()
placeholderText: "Pflichtfeld"
placeholderTextColor: "red"
}
Label
{
text: qsTr("Nr.")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
property string name: "houseno"
id: houseno
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
onTextChanged: checkFields()
placeholderText: "Pflichtfeld"
placeholderTextColor: "red"
}
// New grid row
Label
{
text: qsTr("PLZ")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
ComboBox
{
property string name: "postcode"
id: postcode
Layout.fillWidth: true
}
Label
{
text: qsTr("Ort")
Layout.alignment: Qt.AlignRight
}
ComboBox
{
property string name: "city"
id: city
Layout.fillWidth: true
editable: true
onEditTextChanged: checkFields()
onCurrentTextChanged: checkFields()
model: address_model
textRole: "city"
popup.height: 300
popup.y: postcode.y + 5 - (postcode.height * 2)
currentIndex: -1
}
// New grid row
Label
{
text: qsTr("Parteien")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
SpinBox
{
id: parteien
Layout.fillWidth: true
from: 1
to: 100
value: 1
}
Label
{
text: qsTr("Stockwerke")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
SpinBox
{
id: floors
Layout.fillWidth: true
from: 1
to: 100
value: 1
}
// New grid row
Label
{
text: qsTr("Zwischenetage")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
ComboBox
{
property string name: "mezzanin"
id: mezzanin
Layout.fillWidth: true
editable: false
model: [qsTr("Jööö"), qsTr("Nöööööööööööööööööööööööööö")]
}
Label
{
text: qsTr("Aufzug")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
ComboBox
{
property string name: "lift"
id: lift
Layout.fillWidth: true
editable: false
model: [qsTr("Jööö"), qsTr("Nöööööööööööööööööööööööööö")]
}
//New grid row
Label
{
text: qsTr("Fenster")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
ComboBox
{
property string name: "windows"
id: windows
Layout.fillWidth: true
editable: false
model: [qsTr("Jööö"), qsTr("Nöööööööööööööööööööööööööö")]
onCurrentIndexChanged: nrWindows.enabled = (windows.currentIndex === 0)? true: false
}
Label
{
text: qsTr("Anzahl")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
SpinBox
{
id: nrWindows
Layout.fillWidth: true
from: 0
to: 100
value: 0
}
// New grid row
CheckBox
{
id: ladder
text: qsTr("Leiter")
Layout.alignment: Qt.AlignRight
checked: false
onCheckStateChanged:
{
//checkFields()
}
}
CheckBox
{
id: accessible
text: qsTr("Erreichbar")
Layout.alignment: Qt.AlignRight
checked: false
onCheckStateChanged:
{
//checkFields()
}
}
Label
{
text: qsTr("Besonderheiten")
Layout.alignment: Qt.AlignRight
}
ComboBox
{
property string name: "remarks"
id: remarks
Layout.fillWidth: true
editable: false
textRole: "display"
}
//// New grid row
Label
{
text: qsTr("kontaktdaten")
Layout.alignment: Qt.AlignRight | Qt.AlignTop
}
ComboBox
{
property string name: "contact"
id: contact
Layout.fillWidth: true
editable: false
model: [qsTr("Beirat"), qsTr("Hausmeister")]
}
Label
{
text: qsTr("Reingunsmittel wo?")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
property string name: "cleansing"
id: cleamsing
Layout.fillWidth: true
placeholderText: "Pflichtfeld"
placeholderTextColor: "red"
}
Item
{
Layout.fillHeight: true
}
}

View File

@@ -41,9 +41,9 @@ ColumnLayout
{ {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.fillWidth: true Layout.fillWidth: true
ObjectView AddNewObject
{ {
id: objectView id: newObject
width: parent.width width: parent.width
} }
} }

View File

@@ -182,10 +182,18 @@ GridLayout
Layout.columnSpan: 3 Layout.columnSpan: 3
visible: radio.children[1].checked visible: radio.children[1].checked
validator: RegularExpressionValidator validator: RegularExpressionValidator
{ {
regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/ regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/
} }
Keys.onPressed: (event)=>
{
if (event.key !== Qt.Key_Backspace)
{
var len = birthday.length
var bd = birthday.text
if (len === 2 || len === 5) birthday.text = bd + "."
}
}
} }
Label Label

View File

@@ -0,0 +1,174 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
GridLayout
{
columns: 2
rowSpacing: 25
Layout.leftMargin: 7
// Grid row
ColumnLayout
{
Layout.columnSpan: 2
Label
{
id: contactLabel
color: "darksalmon"
font.bold: true
text: qsTr("Ansprechpartner")
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['salute'] + " " + contact['contact']['fname'] + " " + contact['contact']['lname']: ""
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Geburtsdatum")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['birthday']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("E-Mail")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['email']: ""
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Position")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['position']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Priorität")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['priority']: ""
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Telefon")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['phone']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Handy")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['cell']: ""
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Abrechnung")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['invoice']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Mahnung")
font.bold: true
}
Label
{
color: "goldenrod"
text: contact? contact['contact']['reminder']: ""
}
}
// Grid row
Item
{
Layout.columnSpan: 2
Layout.fillHeight: true
}
Component.onCompleted:
{
if (contact && contact['contact']['salute'] === "Frau")
contactLabel.text = qsTr("Ansprechpartnerin")
}
}

View File

@@ -2,26 +2,61 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
Item ColumnLayout
{ {
property int selectedClient: -1 property int selectedClient: -1
property var client: null
property var contact: null
id: clDet id: clDet
ColumnLayout
{
Label
{
text: qsTr("Ausgewählter Kunde " + selectedClient)
}
Button Button
{ {
text: qsTr("Kunden zeigen") text: qsTr("Zurück")
//Layout.columnSpan: 2
onClicked: customersStack.pop() onClicked: customersStack.pop()
} }
SplitView
{
id: clDetView
Layout.fillHeight: true
Layout.fillWidth: true
leftPadding: 9
rightPadding: 9
CustomerDetailsView
{
id: customerDetails
}
CustomerContactDetails
{
id: contactDetails
visible: false
}
NoCustomerContact
{
id: noCustomerContact
visible: false
}
}
Item
{
//Layout.columnSpan: 2
Layout.fillHeight: true
} }
Component.onCompleted: Component.onCompleted:
{ {
business_model.onRowClicked(selectedClient) //business_model.onRowClicked(selectedClient)
client = business_model.getClientDetails()
if (client['business']['contactid'] > 0)
{
contact = contact_model.getContactDetails(client['business']['contactid'])
contactDetails.visible = true
}
else noCustomerContact.visible = true
} }
} }

225
Gui/CustomerDetailsView.qml Normal file
View File

@@ -0,0 +1,225 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
GridLayout
{
columns: 2
rowSpacing: 25
SplitView.preferredWidth: clDetView.width / 3 * 1.8
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Steuer-ID")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['tax']? client['business']['tax']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Anmerkungen")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['info']? client['business']['info']: ""
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Kundenname")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['company']
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("CEO")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['ceo']
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Telefon")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['phone']? client['business']['phone']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Handy")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['cell']? client['business']['cell']: ""
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Webseite")
font.bold: true
}
Label
{
id: clientWebsite
color: "goldenrod"
font.underline: false
text: client['business']['website']? '<a href="' + client['business']['website'] + '">' + client['business']['website'] + '</a>': ""
onLinkActivated:
{
var web_protocol = /^((http|https):\/\/)/;
var client_website = !web_protocol.test(client['business']['website'])? "https://" + client['business']['website']: client['business']['website'];
Qt.openUrlExternally(client_website)
}
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("E-Mail")
font.bold: true
}
Label
{
id: clientEmail
color: "goldenrod"
text: client['business']['email']? '<a href="mailto:' + client['business']['email'] + '">' + client['business']['email'] + '</a>': ""
onLinkActivated: Qt.openUrlExternally('mailto:' + client['business']['email'])
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Straße")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['street']? client['business']['tax']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Haus-Nr.")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['house']? client['business']['house']: ""
}
}
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("PLZ")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['zip']? client['business']['zip']: ""
}
}
ColumnLayout
{
Label
{
color: "darksalmon"
text: qsTr("Stadt")
font.bold: true
}
Label
{
color: "goldenrod"
text: client['business']['city']? client['business']['city']: ""
}
}
// Grid row
// Item
// {
// Layout.columnSpan: 2
// Layout.fillHeight: true
// }
}

View File

@@ -145,6 +145,7 @@ Item
hoverEnabled: true hoverEnabled: true
onDoubleClicked: onDoubleClicked:
{ {
business_model.onRowClicked(row)
customersStack.push("CustomerDetails.qml", {selectedClient: row}); customersStack.push("CustomerDetails.qml", {selectedClient: row});
} }

View File

@@ -83,6 +83,13 @@ Item
placeholderText: qsTr ("Benutzernamen eingeben") placeholderText: qsTr ("Benutzernamen eingeben")
implicitWidth: 300 implicitWidth: 300
font: hussarPrint.font font: hussarPrint.font
focus: true
onAccepted:
{
if (benutzerName.text.trim() && passwort.text.trim())
loggedin_user.login(benutzerName.text.trim(), passwort.text)
else if(benutzerName.text.trim()) passwort.forceActiveFocus()
}
} }
} }
@@ -110,6 +117,12 @@ Item
implicitWidth: 300 implicitWidth: 300
font: hussarPrint.font font: hussarPrint.font
echoMode: TextInput.Password echoMode: TextInput.Password
onAccepted:
{
if (benutzerName.text.trim() && passwort.text.trim())
loggedin_user.login(benutzerName.text.trim(), passwort.text)
else if(passwort.text.trim()) benutzerName.forceActiveFocus()
}
} }
} }
@@ -193,6 +206,7 @@ Item
config.invalidEncryptionKey.connect(getEncryptionKey) config.invalidEncryptionKey.connect(getEncryptionKey)
config.checkEncryptionKey() config.checkEncryptionKey()
loggedin_user.noDbConnection.connect(dbConnectionFailed) loggedin_user.noDbConnection.connect(dbConnectionFailed)
benutzerName.forceActiveFocus()
} }
function loggedin() function loggedin()

33
Gui/NoCustomerContact.qml Normal file
View File

@@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
GridLayout
{
columns: 2
rowSpacing: 25
// Grid row
ColumnLayout
{
Label
{
color: "darksalmon"
font.bold: true
text: qsTr("Kein Ansprechpartner gefunden")
}
Label
{
color: "goldenrod"
text: qsTr("Was willst du tun?")
}
}
// Grid row
Item
{
Layout.columnSpan: 2
Layout.fillHeight: true
}
}

134
Gui/PrinterDialog.qml Normal file
View File

@@ -0,0 +1,134 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Window
{
property alias printerDialog: printDialog
property var printers: null
id: printDialog
title: qsTr("PYQCRM - Drucker")
color: palette.base
minimumWidth: 300
maximumWidth: 600
minimumHeight: 150
maximumHeight: 150
ColumnLayout
{
spacing: 9
y: 15
implicitWidth: parent.width
RowLayout
{
Layout.fillWidth: true
Layout.leftMargin: 5
Layout.rightMargin: 5
Label
{
id: printersLabel
Layout.alignment: Qt.AlignRight
text: qsTr("Drucker")
}
ComboBox
{
id: allPrinters
model: printers
Layout.fillWidth: true
}
}
RowLayout
{
Layout.leftMargin: 5
Layout.rightMargin: 5
Layout.fillWidth: true
Label
{
Layout.minimumWidth: printersLabel.width
Layout.alignment: Qt.AlignRight
text: qsTr("Kopie")
}
SpinBox
{
id: copiesSpinBox
from: 1
to: 10
value: 1
}
Item
{
Layout.fillWidth: true
}
}
RowLayout
{
Layout.leftMargin: 5
Layout.rightMargin: 5
Layout.fillWidth: true
CheckBox
{
id: colorPrint
text: qsTr("Farbe")
Layout.minimumWidth: printersLabel.width
}
Item
{
Layout.fillWidth: true
}
}
RowLayout
{
Layout.leftMargin: 5
Layout.rightMargin: 5
Layout.fillWidth: true
Item
{
Layout.fillWidth: true
}
Button
{
id: printButton
text: qsTr("Drucken")
onClicked:
{
var copies = copiesSpinBox.value > 1? copiesSpinBox.value + " copies": "one copy"
console.log("Printing ", copies, " using ", allPrinters.currentText);
printDialog.close();
}
}
Button
{
text: qsTr("Ablehnen")
onClicked: printDialog.close();
}
}
Item
{
Layout.fillHeight: true
}
}
onVisibleChanged:
{
copiesSpinBox.value = 1
colorPrint.checked = true
}
Component.onCompleted:
{
printers = sys_printers.getPrinters()
if (sys_printers.getDefaultPrinter())
allPrinters.currentIndex = allPrinters.indexOfValue(sys_printers.getDefaultPrinter())
}
}

47
Gui/ReadMe.qml Normal file
View File

@@ -0,0 +1,47 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Window
{
property alias readMeWin: readMeWin
id: readMeWin
width: 400
height: 300
title: "PYQCRM - README"
color: palette.base
ScrollView
{
anchors.fill: parent
TextArea
{
id: readMe
anchors.fill: parent
readOnly: true
wrapMode: TextArea.Wrap
color: "darksalmon"
Component.onCompleted:
{
var filePath = "qrc:/README";
var xhr = new XMLHttpRequest();
xhr.open("GET", filePath, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE)
{
if (xhr.status === 200)
{
readMe.text = xhr.responseText;
}
else
{
readMe.text = qsTr("Datei nicht gefunden!");
}
}
};
xhr.send();
}
}
}
}

View File

@@ -172,7 +172,32 @@ RowLayout
icon.color: "red" icon.color: "red"
flat: true flat: true
Layout.rightMargin: 9 Layout.rightMargin: 9
} onClicked: mainMenu.open()
Menu {
id: mainMenu
MenuItem
{
text: qsTr("Benutzer-Verwaltung")
onTriggered: appLoader.source = "UsersPage.qml"
}
MenuSeparator {}
MenuItem { text: qsTr("Als PDF exportieren") }
MenuSeparator {}
MenuItem { text: qsTr("Drucken") }
MenuItem
{
text: qsTr("Erweiterter Druck")
onTriggered: printerDialog.show()
}
MenuSeparator {}
MenuItem
{
text: qsTr("Über PYQCRM")
onTriggered: readMeWin.show()
}
}
}
} }

16
Gui/UsersPage.qml Normal file
View File

@@ -0,0 +1,16 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Item
{
anchors.fill: parent
Label
{
text: qsTr("Benutzer-Verwaltung")
anchors.centerIn: parent
font.pixelSize: 57
font.bold: true
}
}

View File

@@ -26,6 +26,16 @@ ApplicationWindow
visible: bad_config || !db_con ? false: true visible: bad_config || !db_con ? false: true
} }
PrinterDialog
{
id: printerDialog
}
ReadMe
{
id: readMeWin
}
Item Item
{ {
id: mainView id: mainView

1
LICENSE Normal file
View File

@@ -0,0 +1 @@
Nicht zu verwenden ohne Zahlung!

54
README Normal file
View File

@@ -0,0 +1,54 @@
PYQCRM - Python QtQuick CRM System
Einleitung:
----------------
PYQCRM entstand als Teil einer Abschlusspraxis und dem Bedarf der TERO GmbH an einem Managementprogramm für den internen Gebrauch.
Erstellung:
----------------
Das Program wurde mit den folgenen Techologien entwickelt:
- IDE: Qt-Creator 6.8.x
- Framework: QtQuick - QML 6.8.x
- Sprache: Python 3.13.x
- Datenbank: MariaDB 10.11.x
- Sonstiges: JavaScript
Installation:
------------------
Möglicherweise ist für deine Plattform ein Installationspaket verfügbar. Prüfe es also zuerst.
Es gibt zwei möglichkeiten das Program zu verwenden:
Möglichkeit I:
1. Datenbank einrichten (MaraiDB SQL-Schema verfügbar)
2. Python installiert und eingerichtet
3. Das Program in einen bevorzugten Pfad kopieren.
4. Erstelle einen Link zum main.py-Skript und stelle ihn so ein, dass er mit deinem Python-Interpreter ausgeführt wird
Möglichkeit II:
1. Datenbank einrichten (MaraiDB SQL-Schema verfügbar)
2. Die ausführbare Version des Programs in einen bevorzugten Pfad kopieren
3. Erstelle einen Link zur ausführbaren Datei, die für dein System erstellt wurde (pyqcrm/pyqcrm.exe)
Kudos:
----------
Wenn dir dieses Kunstwerk gefällt, kannst du uns deine Wertschätzung einfach dadurch zeigen, dass du jedem von uns einen Bungalow, einen 760IL BMW, lebenslange Flugtickets inklusive Begleitung kaufst und als Geschenk ein paar Millionen Euro auf ein Schweizer Bankkonto überweist.
Team:
---------
Marcopolo
Schnacke
Renegade

View File

@@ -1,29 +1,88 @@
CRM für Objektbetreuung Reinigungsservice CRM für Objektbetreuung Reinigungsservice
Aktueller Stand CRM Sebastian
Umsetzung mit Python + QML
- Kunden anlegen
- Abrufen und anzeigen vorhandender Daten aus der Datenbank
- GUI vorhanden
Minimal Voraussetzung: Minimal Voraussetzung:
- Kunden anlegen (Hausverwaltung Krefeld)
- Objekt anlegen Mitarbeiter
- Ansprechpartner anlegen - Dokumenten Management System (DMS)
- Arbeitnehmer anlegen
- Arbeitsauftrag anlegen
- Rechnung schreiben (Zugferd Modus)
- Dokumenten Managment System (DMS)
- Arbeitszeitdokumentation - Arbeitszeitdokumentation
- Urlaubsplaner - Urlaubsplaner
- Arbeitnehmer anlegen
Nice To Have: Nice To Have:
- WhatsApp - WhatsApp
- Telefon - Telefon
- Social Media Uploader - Social Media Uploader
Dokumentenvorlagen
Kunden
- Kunden anlegen (Hausverwaltung Krefeld)
- Dokumenten Management System (DMS)
- Ansprechpartner anlegen
RG auch in CC Funktion
Nice To Have:
Dokumentenvorlagen
Dashboard
Abrechnung
- Rechnung schreiben (Zugferd Modus)
- Fixkostenerfassung
Rechnungsarchiv
Mahnfunktion
Objekt
- Objekt anlegen
- Ansprechpartner anlegen
- Dokumenten Management System (DMS)
Str. (Pflicht)
Hausnummer (Pflicht)
PLZ
Ort (Pflicht)
Parteien (Anzahl)
Stockwerke (Anzahl)
Zwischenetage (ja/nein)
Aufzug (ja/nein)
Fenster (ja/nein) anschließend Anzahl + ohne Leiter erreichbar (ja/nein)
Besonderheiten (Infofeld)
Kontaktdaten Hausmeister / Beirat
Reinigungsmittel zu finden? ( Wo ist es im Kellerraum, Dachgeschoss, 2 Tür von links?)
Foto Upload wäre toll ;-)
Leistungen
Treppenhausreinigung
Garten
siehe Homepage !!!
Angebot
Auftrag
- Arbeitsauftrag anlegen
Pflegehinweise zum Objekt
BG Sicherheitsdatenblätter
Reinigungsmittel erhalten am ?
Preis ab Datum
Objektkontrolle (Wann wurde das Objekt das letzte Mal kontrolliert?) Ähnlich wie Preis mit Datum
Preise
Schlüssel
Auswertung
Stundenkalender

View File

@@ -41,7 +41,7 @@ class ConfigLoader(QObject):
else: else:
config_dir.mkdir(0o750, True, True) config_dir.mkdir(0o750, True, True)
@Slot(dict, result= bool) @Slot(dict, result = bool)
def setConfig(self, app_config): def setConfig(self, app_config):
# print(f"In {__file__} file, setConfig()") # print(f"In {__file__} file, setConfig()")
if not self.__config: if not self.__config:

View File

@@ -27,6 +27,17 @@ class BusinessDAO(QObject):
except mariadb.Error as e: except mariadb.Error as e:
print(str(e)) print(str(e))
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
else:
return None #, None
except mariadb.Error as e:
print(str(e))
def addBusiness(self, data, contact_id): def addBusiness(self, data, contact_id):
try: try:
if self.__cur: if self.__cur:

View File

@@ -65,6 +65,8 @@ class BusinessModel(QAbstractTableModel):
__visible_index = {} __visible_index = {}
__col_name = "" __col_name = ""
__business_dao = None __business_dao = None
__business = None
__business_dict = {'business':{}} #,'contact':{}}
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -80,6 +82,22 @@ class BusinessModel(QAbstractTableModel):
self.__data = rows self.__data = rows
self.endResetModel() self.endResetModel()
def __getBusinessInfo(self):
self.__business_dict['business']['id'] = self.__business[0][0]
self.__business_dict['business']['contactid'] = self.__business[0][1]
self.__business_dict['business']['company'] = self.__business[0][2]
self.__business_dict['business']['phone'] = self.__business[0][3]
self.__business_dict['business']['cell'] = self.__business[0][4]
self.__business_dict['business']['email'] = self.__business[0][5]
self.__business_dict['business']['website'] = self.__business[0][6]
self.__business_dict['business']['ceo'] = self.__business[0][7]
self.__business_dict['business']['info'] = self.__business[0][8]
self.__business_dict['business']['tax'] = self.__business[0][9]
self.__business_dict['business']['street'] = self.__business[0][10]
self.__business_dict['business']['house'] = self.__business[0][11]
self.__business_dict['business']['zip'] = self.__business[0][12]
self.__business_dict['business']['city'] = self.__business[0][13]
def rowCount(self, parent= QModelIndex()): def rowCount(self, parent= QModelIndex()):
return len (self.__data) return len (self.__data)
@@ -113,7 +131,17 @@ class BusinessModel(QAbstractTableModel):
@Slot(int) @Slot(int)
def onRowClicked(self, row): def onRowClicked(self, row):
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']:
self.__business = self.__business_dao.getOneBusiness(self.__data[row][0], self.__key)
#print(self.__business)
self.__getBusinessInfo()
# self.__getContactInfo()
# print(self.__business_dict)
@Slot(result = dict)
def getClientDetails(self):
return self.__business_dict
@Slot(str) @Slot(str)
def viewCriterion(self, criterion): def viewCriterion(self, criterion):
@@ -134,5 +162,3 @@ class BusinessModel(QAbstractTableModel):
def updateTable(self): def updateTable(self):
pass pass

View File

@@ -30,4 +30,15 @@ class ContactDAO:
except Exception as e: except Exception as e:
print("PYT: " + str(e)) print("PYT: " + str(e))
def getContact(self, contact_id, enc_key = None):
try:
if self.__cur:
self.__cur.callproc("getCustomerContact", (contact_id, enc_key,))
#self.__all_cols = [desc[0] for desc in self.__cur.description]
return self.__cur.fetchall() #, self.__all_cols
else:
return None #, None
except mariadb.Error as e:
print(str(e))

View File

@@ -5,6 +5,10 @@ import logging
class ContactModel(QObject): class ContactModel(QObject):
contactIdReady = Signal(int) contactIdReady = Signal(int)
__contact = None
__contact_dict = {'contact':{}}
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# print(f"*** File: {__file__}, __init__()") # print(f"*** File: {__file__}, __init__()")
@@ -27,5 +31,29 @@ class ContactModel(QObject):
i = ContactDAO().addContact(contact, self.__key) i = ContactDAO().addContact(contact, self.__key)
self.contactIdReady.emit(i) self.contactIdReady.emit(i)
def __getContact(self, contact):
self.__contact = ContactDAO().getContact(contact, self.__key)
self.__getContactInfo()
@Slot(int, result = dict)
def getContactDetails(self, contact):
self.__getContact(contact)
#print(self.__contact_dict)
return self.__contact_dict
def __getContactInfo(self):
self.__contact_dict['contact']['id'] = self.__contact[0][0]
self.__contact_dict['contact']['salute'] = self.__contact[0][1]
self.__contact_dict['contact']['fname'] = self.__contact[0][2].decode("utf-8")
self.__contact_dict['contact']['lname'] = self.__contact[0][3].decode("utf-8")
self.__contact_dict['contact']['phone'] = self.__contact[0][4].decode("utf-8")
self.__contact_dict['contact']['cell'] = self.__contact[0][5].decode("utf-8")
self.__contact_dict['contact']['position'] = self.__contact[0][6]
self.__contact_dict['contact']['email'] = self.__contact[0][7].decode("utf-8")
self.__contact_dict['contact']['birthday'] = self.__contact[0][8].decode("utf-8")
self.__contact_dict['contact']['priority'] = "Ja" if self.__contact[0][9] else "Nein"
self.__contact_dict['contact']['invoice'] = "Ja" if self.__contact[0][10] else "Nein"
self.__contact_dict['contact']['reminder'] = "Ja" if self.__contact[0][11] else "Nein"

View File

@@ -1,9 +1,13 @@
from .DbManager import DbManager from .DbManager import DbManager
from ..PyqcrmFlags import PyqcrmFlags from ..PyqcrmFlags import PyqcrmFlags
from ..Vermasseln import Vermasseln from ..Vermasseln import Vermasseln
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput #from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput : Not working well with Nuitka
import soundfile as sf
import sounddevice as sd
from .UserDAO import UserDAO from .UserDAO import UserDAO
from PySide6.QtCore import Slot, QObject, Signal, QUrl from PySide6.QtCore import Slot, QObject, Signal, QUrl, QFile
import tempfile
class UserManager(QObject): class UserManager(QObject):
@@ -61,12 +65,24 @@ class UserManager(QObject):
if user: if user:
self.__checkPassword(password, user[2]) self.__checkPassword(password, user[2])
else: else:
player = QMediaPlayer(self) fail_src = ":/sounds/fail2c.ogg"
audioOutput = QAudioOutput(self) with tempfile.NamedTemporaryFile(suffix='.ogg') as ogg_file:
player.setAudioOutput(audioOutput) failure_sound = QFile(fail_src)
player.setSource(QUrl("qrc:/sounds/fail2c.ogg")) if not failure_sound.open(QFile.ReadOnly):
audioOutput.setVolume(150) print(f"Failed to open resource file: {fail_src}")
player.play() 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): def __checkPassword(self, password, hash_password):
pw_list = hash_password.split("$") pw_list = hash_password.split("$")

23
lib/Printers.py Normal file
View File

@@ -0,0 +1,23 @@
from PySide6.QtCore import QObject, Slot
from PySide6.QtPrintSupport import QPrinterInfo
class Printers(QObject):
__printers = None
__default_printer = None
__default_printer_name = None
__available_printers = []
def __init__(self):
super().__init__()
self.__printers = QPrinterInfo.availablePrinters()
self.__available_printers = QPrinterInfo.availablePrinterNames()
self.__default_printer = QPrinterInfo.defaultPrinter()
self.__default_printer_name = QPrinterInfo.defaultPrinterName()
@Slot(result = list)
def getPrinters(self):
return self.__available_printers
@Slot(result = str)
def getDefaultPrinter(self):
return self.__default_printer_name

81
lib/PyqcrmPDF.py Normal file
View File

@@ -0,0 +1,81 @@
from reportlab.lib.pagesizes import A4 #, letter...etc
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
'''
Need to check for sender and receiver sections
Other alternatives can be pdf-gen, pdf-generator, pdf-play, pdf34, abdul-987-pdf
'''
class PyqcrmPDF:
__pyq_doc = None
__pyq_pdf = None
__pyq_usable_width = 0
__pyq_usable_height = 0
__pyq_lr_margin = 20
__pyq_tb_margin = 25
__line = 0
__line_height = 14
__doc_title = "PYQCRM PDF Document"
__doc_author = "The PYQCRM Team"
__doc_subject = "PYQCRM Document"
def __init__(self, doc_title, doc_subject, pdf_file):
self.__pyq_usable_width = A4[0] - self.__pyq_lr_margin - self.__pyq_lr_margin
self.__pyq_usable_height = A4[1] - self.__pyq_tb_margin - self.__pyq_tb_margin
self.__line = self.__pyq_usable_height + self.__pyq_tb_margin
self.__pyq_pdf = canvas.Canvas(pdf_file, pagesize=A4)
self.__pyq_pdf.setAuthor(self.__doc_author)
self.__pyq_pdf.setTitle(doc_title if doc_title else self.__doc_title)
self.__pyq_pdf.setSubject(doc_subject if doc_subject else self.__doc_subject)
self.__pyq_doc = self.__pyq_pdf.beginText(self.__pyq_lr_margin, self.__line)
self.__pyq_doc.setFont("Helvetica", 12)
def addLine(self, line):
#self.__pyq_pdf.drawString(self.__pyq_lr_margin, self.__line, line) : Need to check if it does the trick!
# print(f"Line No.: {self.__line}")
# print(f"Line: {line}")
if self.__pyq_doc.getY() < self.__pyq_tb_margin:
# print("creating a new page...")
self.__pyq_pdf.drawText(self.__pyq_doc)
self.__pyq_pdf.showPage()
self.__line = self.__pyq_usable_height + self.__pyq_tb_margin
# print(f"Line No.: {self.__line}")
self.__pyq_doc = self.__pyq_pdf.beginText(self.__pyq_lr_margin, self.__line)
self.__pyq_doc.setFont("Helvetica", 12)
if pdfmetrics.stringWidth(line, "Helvetica", 12) > self.__pyq_usable_width:
# print(f"Line width: {pdfmetrics.stringWidth(line, 'Helvetica', 12)}, Available width: {self.__pyq_usable_width}")
words = line.split(' ')
line = ''
for word in words:
# print(f"Line: {line}")
if self.__pyq_doc.getY() < self.__pyq_tb_margin:
# print("creating a new page...")
self.__pyq_pdf.drawText(self.__pyq_doc)
self.__pyq_pdf.showPage()
self.__line = self.__pyq_usable_height + self.__pyq_tb_margin
# print(f"Line No.: {self.__line}")
self.__pyq_doc = self.__pyq_pdf.beginText(self.__pyq_lr_margin, self.__line)
self.__pyq_doc.setFont("Helvetica", 12)
if pdfmetrics.stringWidth(line + word + ' ', "Helvetica", 12) <= self.__pyq_usable_width:
line = line + word + ' '
else:
self.__pyq_doc.textLine(line)
line = word + ' '
# print(f"Last line: {line}")
if line:
# print(f"Available line: {line}")
self.__pyq_doc.textLine(line)
self.__line = self.__line + self.__line_height
def saveDoc(self):
self.__pyq_pdf.drawText(self.__pyq_doc)
self.__pyq_pdf.showPage()
self.__pyq_pdf.save()

13
main.py
View File

@@ -1,4 +1,5 @@
# # !/home/linuxero/proj/tero/pyqcrm/.qtcreator/venv-3.13.1/bin/python3 # # !/home/linuxero/proj/tero/pyqcrm/.qtcreator/venv-3.13.1/bin/python3
import os
import sys import sys
import logging import logging
from PySide6.QtGui import QGuiApplication from PySide6.QtGui import QGuiApplication
@@ -13,9 +14,10 @@ from lib.DB.UserManager import UserManager
from lib.DB.AddressModel import AddressModel from lib.DB.AddressModel import AddressModel
from lib.DB.BTypeModel import BTypeModel from lib.DB.BTypeModel import BTypeModel
from lib.DB.ContactModel import ContactModel from lib.DB.ContactModel import ContactModel
from lib.Printers import Printers
os.environ['QML_XHR_ALLOW_FILE_READ'] = '1'
# [pyqcrm] # [pyqcrm]
# program-name="" # program-name=""
@@ -35,14 +37,16 @@ address_model = None
business_model = None business_model = None
business_type = None business_type = None
contact_model = None contact_model = None
printers = None
user = None user = None
def initializeProgram(): def initializeProgram():
#print(f"In {__file__} file, initializeProgram()") #print(f"In {__file__} file, initializeProgram()")
global address_model, bad_config, business_model, user, business_type, contact_model, db_con global address_model, bad_config, business_model, user, business_type, contact_model, db_con, printers
if not bad_config: if not bad_config:
dbconf = config.getConfig()['database'] dbconf = config.getConfig()['database']
DbManager(dbconf) DbManager(dbconf)
printers = Printers()
if DbManager().getConnection(): if DbManager().getConnection():
db_con = True db_con = True
user = UserManager() user = UserManager()
@@ -55,7 +59,7 @@ def initializeProgram():
def publishContext(): def publishContext():
# 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 global engine, address_model, bad_config, business_model, user, business_type, contact_model, printers
engine.rootContext().setContextProperty("loggedin_user", user) engine.rootContext().setContextProperty("loggedin_user", user)
engine.rootContext().setContextProperty("business_model", business_model) engine.rootContext().setContextProperty("business_model", business_model)
engine.rootContext().setContextProperty("address_model", address_model) engine.rootContext().setContextProperty("address_model", address_model)
@@ -76,14 +80,13 @@ if __name__ == "__main__":
config = ConfigLoader() config = ConfigLoader()
if not config.getConfig(): if not config.getConfig():
bad_config = True bad_config = True
config.configurationReady.connect(initializeProgram) config.configurationReady.connect(initializeProgram)
else: else:
initializeProgram() initializeProgram()
engine.rootContext().setContextProperty("sys_printers", printers)
engine.rootContext().setContextProperty("bad_config", bad_config) # print(f"Fehler: {i}") engine.rootContext().setContextProperty("bad_config", bad_config) # print(f"Fehler: {i}")
engine.rootContext().setContextProperty("db_con", db_con) engine.rootContext().setContextProperty("db_con", db_con)
engine.rootContext().setContextProperty("config", config) engine.rootContext().setContextProperty("config", config)

View File

@@ -20,6 +20,8 @@
"lib/DB/ContactDAO.py", "lib/DB/ContactDAO.py",
"lib/DB/ContactModel.py", "lib/DB/ContactModel.py",
"lib/DB/EmployeeModel.py", "lib/DB/EmployeeModel.py",
"lib/DB/EmployeeDAO.py" "lib/DB/EmployeeDAO.py",
"lib/Printers.py",
"lib/PyqcrmPDF.py"
] ]
} }

View File

@@ -18,5 +18,7 @@
<file>fonts/LittleBirdsRegular.ttf</file> <file>fonts/LittleBirdsRegular.ttf</file>
<file>fonts/ReginaldScript.ttf</file> <file>fonts/ReginaldScript.ttf</file>
<file>images/account.svg</file> <file>images/account.svg</file>
<file>README</file>
<file>LICENSE</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -35,5 +35,12 @@
<file>Gui/EmployeesTable.qml</file> <file>Gui/EmployeesTable.qml</file>
<file>Gui/EmployeeDetails.qml</file> <file>Gui/EmployeeDetails.qml</file>
<file>Gui/ObjectDetails.qml</file> <file>Gui/ObjectDetails.qml</file>
<file>Gui/AddNewObject.qml</file>
<file>Gui/PrinterDialog.qml</file>
<file>Gui/CustomerContactDetails.qml</file>
<file>Gui/NoCustomerContact.qml</file>
<file>Gui/CustomerDetailsView.qml</file>
<file>Gui/ReadMe.qml</file>
<file>Gui/UsersPage.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -4,6 +4,10 @@ pycryptodome
psutil psutil
toml toml
mariadb mariadb
soundfile
sounddevice
reportlab