2 Commits

Author SHA256 Message Date
bf3bf8fe3e remove test https push main.py 2025-05-14 11:09:17 +02:00
fff434a3b5 test https push in main.py 2025-05-14 11:08:13 +02:00
46 changed files with 1304 additions and 30752 deletions

2
.gitignore vendored
View File

@@ -210,7 +210,7 @@ dmypy.json
# pytype static type analyzer # pytype static type analyzer
.pytype/ .pytype/
.qtcreator/ .qtcreator/
*.pyproject.user* *.pyproject.user
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/

120
Gui/AddCustomer.qml Normal file
View File

@@ -0,0 +1,120 @@
// import QtQuick
// import QtQuick.Layouts
// import QtQuick.Controls
// import QtQuick.Dialogs
// import Js
// ColumnLayout
// {
// property var new_business: null
// Layout.fillWidth: true
// Layout.fillHeight: true
// spacing: 15
// Label
// {
// text: qsTr("Kunden anlegen")
// horizontalAlignment: Text.AlignHCenter
// Layout.fillWidth: true
// font.pixelSize: 35
// }
// CheckBox
// {
// id: checkAddContact
// text: qsTr("Ansprechpartner hinzufügen")
// Layout.alignment: Qt.AlignRight
// checked: false
// onCheckStateChanged:
// {
// checkFields()
// }
// }
// RowLayout
// {
// id: addCustomer
// Layout.fillWidth: true
// Layout.fillHeight: true
// spacing: 45
// Frame
// {
// Layout.alignment: Qt.AlignTop
// Layout.fillWidth: true
// CustomerView
// {
// id: customerView
// width: parent.width
// }
// }
// AddContact
// {
// id: addContactFrame
// visible: checkAddContact.checked
// }
// }
// RowLayout
// {
// Layout.fillHeight: true
// Layout.alignment: Qt.AlignRight
// Button
// {
// text: qsTr("Abbrechen")
// onClicked: contentStack.pop()
// }
// Button
// {
// id: saveBtn
// text: qsTr("Speichern")
// enabled: false
// onClicked:
// {
// if (!checkAddContact.checked)
// {
// new_business = JsLib.parseForm(customerView)
// business_model.addBusiness(new_business, 0)
// contentStack.pop()
// }
// else
// {
// new_business = JsLib.parseForm(customerView)
// var new_contact = JsLib.parseForm(addContactFrame.contactGrid)
// contact_model.addContact(new_contact)
// }
// }
// }
// }
// Item
// {
// id: spacer3
// Layout.fillHeight: true
// }
// //Component.onCompleted: contact_model.contactIdReady.connect(onContactId)
// Connections
// {
// target: contact_model
// function onContactIdReady()
// {
// var con_id = arguments[0]
// business_model.addBusiness(new_business, con_id)
// contentStack.pop()
// }
// }
// function checkFields()
// {
// if(checkAddContact.checked)
// {
// if(!customerView.checkBusinessField() || !addContactFrame.checkContactField())
// saveBtn.enabled = false
// else
// saveBtn.enabled = true
// }
// else if (!customerView.checkBusinessField())
// saveBtn.enabled = false
// else
// saveBtn.enabled = true
// }
// }

View File

@@ -4,17 +4,16 @@ import QtQuick.Controls
import QtQuick.Dialogs import QtQuick.Dialogs
import Js import Js
import Gui import Gui
ScrollView ScrollView
{ {
id: scroll id: scroll
width: parent.width width: parent.width
height: parent.height height: parent.height
property var new_business: null
ColumnLayout ColumnLayout
{ {
property var new_business: null
height: Screen.desktopAvailableHeight height: Screen.desktopAvailableHeight
width: scroll.width width: scroll.width
Layout.fillWidth: true Layout.fillWidth: true
@@ -78,13 +77,12 @@ ScrollView
{ {
id: saveBtn id: saveBtn
text: qsTr("Speichern") text: qsTr("Speichern")
enabled: false enabled: true
onClicked: onClicked:
{ {
if (!checkAddContact.checked) if (!checkAddContact.checked)
{ {
new_business = JsLib.parseForm(customerView) new_business = JsLib.parseForm(customerView)
console.log(JSON.stringify(new_business))
business_model.addBusiness(new_business, 0) business_model.addBusiness(new_business, 0)
contentStack.pop() contentStack.pop()
} }
@@ -117,8 +115,6 @@ ScrollView
} }
} }
}
function checkFields() function checkFields()
{ {
if(checkAddContact.checked) if(checkAddContact.checked)
@@ -134,3 +130,4 @@ ScrollView
saveBtn.enabled = true saveBtn.enabled = true
} }
} }
}

View File

@@ -276,7 +276,7 @@ GridLayout
} }
function checkBusinessField() function checkBusinessField()
{ {
if (!firmenName.text.trim() || !streetid.text.trim() || !housenoid.text.trim()) if (!firmenName.text.trim() || !streetid.text.trim())
{ {
return false return false

View File

@@ -13,10 +13,10 @@ ColumnLayout {
anchors.fill: parent
spacing: Dimensions.l spacing: Dimensions.l
// Component.onCompleted: contentStack.pop() Component.onCompleted: contentStack.pop()
RowLayout RowLayout
{ {

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")
}
}

61
Gui/CustomerDetails.qml Normal file
View File

@@ -0,0 +1,61 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout
{
property int selectedClient: -1
property var client: null
property var contact: null
id: clDet
Button
{
text: qsTr("Zurück")
onClicked: contentStack.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:
{
//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
// }
}

271
Gui/CustomerView.qml Normal file
View File

@@ -0,0 +1,271 @@
// import QtQuick
// import QtQuick.Controls
// import QtQuick.Layouts
// GridLayout
// {
// id: customerView
// columns: 4
// Layout.fillWidth: true
// Layout.fillHeight: true
// rowSpacing: 9
// property alias businesstxt: firmenName
// property alias street: streetid
// property alias postcodetxt: postcode
// property alias citytxt: city
// Label
// {
// id: lblFirmenName
// text: qsTr("Firmenname*")
// Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
// }
// TextField
// {
// property string name: "business"
// id: firmenName
// Layout.fillWidth: true
// Layout.alignment: Qt.AlignVCenter
// onTextChanged: checkFields()
// Layout.columnSpan: 3
// }
// Label
// {
// text: qsTr("Straße*")
// Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
// }
// TextField
// {
// property string name: "street"
// id: streetid
// Layout.fillWidth: true
// onTextChanged: checkFields()
// }
// Label
// {
// text: qsTr("Nr.*")
// Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
// }
// TextField
// {
// property string name: "houseno"
// id: housenoid
// Layout.fillWidth: true
// onTextChanged: checkFields()
// validator: RegularExpressionValidator
// {
// regularExpression: /([0-9a-zA-Z\-]{1,6})/
// }
// }
// Label
// {
// text: qsTr("PLZ")
// Layout.alignment: Qt.AlignRight
// }
// ComboBox
// {
// property string name: "postcode"
// id: postcode
// Layout.fillWidth: true
// editable: true
// onCurrentTextChanged: checkFields()
// onEditTextChanged: checkFields()
// onActivated: currentValue
// model: address_model
// textRole: "display"
// popup.height: 300
// currentIndex: -1
// onCurrentIndexChanged: city.currentIndex = postcode.currentIndex
// Layout.columnSpan: 3
// validator: RegularExpressionValidator
// {
// regularExpression: /([0-9]{1,5})/
// }
// }
// Label
// {
// text: qsTr("Ort")
// Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
// }
// ComboBox
// {
// property string name: "city"
// id: city
// Layout.fillWidth: true
// editable: true
// onEditTextChanged: checkFields()
// onCurrentTextChanged: checkFields()
// model: address_model
// textRole: "city"
// popup.height: 300
// currentIndex: -1
// Layout.columnSpan: 3
// }
// Label
// {
// text: qsTr("Telefon")
// Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
// }
// TextField
// {
// property string name: "telephone"
// id: telephone
// Layout.fillWidth: true
// Layout.columnSpan: 3
// validator: RegularExpressionValidator
// {
// regularExpression: /([+0-9]{1})([0-9]{1,17})/
// }
// }
// Label
// {
// text: qsTr("Mobil")
// Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
// }
// TextField
// {
// property string name: "cellphone"
// id: cellphone
// Layout.fillWidth: true
// Layout.columnSpan: 3
// validator: RegularExpressionValidator
// {
// regularExpression: /([+0-9]{1})([0-9]{1,17})/
// }
// }
// Label
// {
// text: qsTr("E-Mail")
// Layout.alignment: Qt.AlignRight
// }
// TextField
// {
// property string name: "email"
// id: email
// Layout.fillWidth: true
// placeholderText: qsTr("beispiel@domain.de")
// Layout.columnSpan: 3
// validator: RegularExpressionValidator
// {
// regularExpression: /([\+!#$%&\*\\/\=?\^_`\.{|}\~\-\_0-9A-Za-z]{1,185})@([0-9A-Za-z\.\-\_]{1,64})\.([a-zA-z]{2,5})/
// }
// }
// Label
// {
// text: qsTr("Homepage")
// Layout.alignment: Qt.AlignRight
// }
// TextField
// {
// property string name: "homepage"
// id: homepage
// Layout.fillWidth: true
// Layout.columnSpan: 3
// placeholderText: "www.oschkarischtverhaftetwegensexy.jinx"
// }
// Label
// {
// text: qsTr("Geschäftsführer")
// Layout.alignment: Qt.AlignRight
// }
// TextField
// {
// property string name: "ceo"
// id: ceo
// Layout.fillWidth: true
// Layout.columnSpan: 3
// }
// Label
// {
// text: qsTr("USt-IdNr")
// Layout.alignment: Qt.AlignRight
// }
// TextField
// {
// property string name: "taxno"
// id: taxno
// Layout.fillWidth: true
// Layout.columnSpan: 3
// }
// Label
// {
// text: qsTr("Typ")
// Layout.alignment: Qt.AlignRight
// }
// ComboBox
// {
// property string name: "typeid"
// id: typeid
// Layout.fillWidth: true
// editable: false
// model: business_type
// textRole: "display"
// Layout.columnSpan: 3
// }
// Label
// {
// text: qsTr("Info")
// Layout.alignment: Qt.AlignRight | Qt.AlignTop
// }
// ScrollView
// {
// id: infoView
// Layout.fillWidth: true
// Layout.preferredHeight: 100
// Layout.columnSpan: 3
// ScrollBar.horizontal: ScrollBar
// {
// policy: ScrollBar.AlwaysOn
// }
// TextArea
// {
// property string name: "customerinfo"
// id: customerInfo
// implicitWidth: parent.width
// wrapMode: TextEdit.Wrap
// background: Rectangle
// {
// color: customerInfo.palette.base
// border.color: customerInfo.activeFocus? customerInfo.palette.highlight: customerInfo.palette.base
// }
// }
// }
// function checkBusinessField()
// {
// if (!firmenName.text.trim() || !streetid.text.trim())
// {
// return false
// }
// else
// {
// if (!postcode.editText.trim() || !postcode.currentText || !city.editText.trim() || !city.currentText)
// return false
// else
// return true
// }
// }
// }

177
Gui/CustomersTable.qml Normal file
View File

@@ -0,0 +1,177 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
ColumnLayout {
property var availableFilters: ["Name", "Adresse", "PLZ", "Ort"]
function viewCriterion(criterion)
{
business_model.viewCriterion(criterion.text);
}
anchors.fill: parent
spacing: Dimensions.l
Component.onCompleted: contentStack.pop()
RowLayout
{
Layout.fillWidth: true
spacing: Dimensions.l
SearchBar
{
}
QuickFilter {
onSelectedChanged: (name) => {
business_model.viewCriterion(name)
}
model: ListModel {
ListElement {
name: "Alle"
text: qsTr("Alle")
selected: true
}
ListElement {
name: "Interessent"
text: qsTr("Interessent")
selected: false
}
ListElement {
name: "Kunde"
text: qsTr("Kunde")
selected: false
}
ListElement {
name: "Lieferant"
text: qsTr("Lieferant")
selected: false
}
ListElement {
name: "Erledigt"
text: qsTr("Erledigt")
selected: false
}
}
}
Button
{
id: addCustomer
Layout.alignment: Qt.AlignRight
icon.source: "qrc:/images/PlusCircle.svg"
text: qsTr("Kunde Hinzufügen")
onClicked: contentStack.push("AddCustomer.qml")
}
}
ColumnLayout
{
Layout.fillWidth: true
Layout.fillHeight: true
Layout.verticalStretchFactor: 1
clip: true
HorizontalHeaderView
{
id: horizontalHeader
Layout.fillWidth: true
implicitHeight: 40
movableColumns: true //@disable-check M16
syncView: customerTable
delegate: Rectangle
{
Layout.fillWidth: true
border.color: addCustomer.palette.base
color: addCustomer.palette.alternateBase
implicitHeight: 40
implicitWidth: 1
Text
{
color: addCustomer.palette.text
elide: Text.ElideRight
height: parent.height
horizontalAlignment: Text.AlignHCenter
text: model.display
verticalAlignment: Text.AlignVCenter
width: parent.width
}
}
}
TableView {
id: customerTable
property real newWidth: 0
Layout.fillHeight: true
Layout.fillWidth: true
alternatingRows: true
columnSpacing: 1
model: business_model
resizableColumns: true
rowSpacing: 2
selectionBehavior: TableView.SelectRows
z: 1
ScrollBar.vertical: ScrollBar {
policy: customerTable.contentHeight > customerTable.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
}
delegate: Rectangle {
required property bool current
required property bool selected
color: selected ? addCustomer.palette.highlight //palette.highlight
: (customerTable.alternatingRows && row % 2 !== 0 ? addCustomer.palette.base // palette.base
: addCustomer.palette.alternateBase) //palette.alternateBase)
implicitHeight: 25
implicitWidth: customerTable.width / customerTable.columns
Text {
color: addCustomer.palette.text
elide: Text.ElideRight
height: parent.height
leftPadding: 9
text: model.display == null ? "" : model.display // @disable-check M126
verticalAlignment: Text.AlignVCenter
width: parent.width
}
MouseArea {
id: mouseArea
property bool hovered: false
anchors.fill: parent
hoverEnabled: true
onDoubleClicked: {
business_model.onRowClicked(row);
contentStack.push("CustomerDetails.qml", {
selectedClient: row
});
}
onEntered: {
customerTable.selectionModel.select(customerTable.model.index(row, 0), ItemSelectionModel.SelectCurrent | ItemSelectionModel.Rows);
}
}
}
selectionModel: ItemSelectionModel {
id: selModel
model: customerTable.model
}
}
}
Item {
Layout.fillHeight: true
}
}

View File

@@ -1,10 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import TeroStyle import TeroStyle
import Js
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: Dimensions.l spacing: Dimensions.l
@@ -15,34 +13,29 @@ ColumnLayout {
}); });
} }
ApplicantForm ApplicantForm {
{
id: applicantForm id: applicantForm
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.fillHeight: true Layout.fillHeight: true
Layout.verticalStretchFactor: 1 Layout.verticalStretchFactor: 1
} }
RowLayout RowLayout {
{
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
spacing: Dimensions.l spacing: Dimensions.l
Button Button {
{
icon.source: "qrc:/images/ArrowLeftCircle-Outline.svg" icon.source: "qrc:/images/ArrowLeftCircle-Outline.svg"
text: qsTr("Verwerfen") text: qsTr("Verwerfen")
onClicked: contentStack.pop() onClicked: contentStack.pop()
} }
Button Button {
{
enabled: applicantForm.valid enabled: applicantForm.valid
icon.source: "qrc:/images/CheckCircle.svg" icon.source: "qrc:/images/CheckCircle.svg"
text: qsTr("Speichern") text: qsTr("Speichern")
onClicked: onClicked: {
{
employee_model.addApplicant(applicantForm.value); employee_model.addApplicant(applicantForm.value);
} }
} }

View File

@@ -2,12 +2,8 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Js import Js
ColumnLayout {
ScrollView id: colPar
{
id: scroll
width: parent.width
height: parent.height
function checkFields() { function checkFields() {
if (!personalData.checkPersonalField()) if (!personalData.checkPersonalField())
@@ -24,13 +20,9 @@ ScrollView
} }
} }
ColumnLayout {
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,7 +34,7 @@ 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
@@ -107,4 +99,3 @@ ScrollView
} }
} }
} }
}

View File

@@ -8,20 +8,18 @@ ColumnLayout
readonly property int fieldM: 235 readonly property int fieldM: 235
readonly property int fieldS: 110 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: city.acceptableInput && email.acceptableInput && firstname.acceptableInput && lastname.acceptableInput && mobile.acceptableInput && phone.acceptableInput && postcode.acceptableInput && formofaddress.acceptableInput && title.acceptableInput
readonly property var value: QtObject readonly property var value: QtObject {
{
readonly property string city: (city.editText ? city.editText : city.currentText) ?? "" readonly property string city: (city.editText ? city.editText : city.currentText) ?? ""
readonly property string email: email.text readonly property string email: email.text
readonly property string firstname: firstname.text readonly property string firstname: firstname.text
readonly property string formofaddress: formofaddress.text readonly property string formofaddress: formofaddress.currentText ?? ""
readonly property string houseno: houseno.text readonly property string houseno: houseno.text ?? ""
readonly property string lastname: lastname.text readonly property string lastname: lastname.text
readonly property string mobile: mobile.text readonly property string mobile: mobile.text
readonly property string phone: phone.text readonly property string phone: phone.text
readonly property string postcode: (postcode.editText ? postcode.editText : postcode.currentText) ?? "" readonly property string postcode: (postcode.editText ? postcode.editText : postcode.currentText) ?? ""
readonly property string street: street.text readonly property string street: (street.editText ? street.editText : street.currentText) ?? ""
readonly property string title: title.currentText readonly property string title: title.currentText
readonly property string country: (country.editText ? country.editText : country.currentText) ?? ""
} }
spacing: Dimensions.l spacing: Dimensions.l
@@ -43,7 +41,6 @@ ColumnLayout
label: qsTr("Anrede") label: qsTr("Anrede")
ComboBox { ComboBox {
// property string name: "title"
id: title id: title
implicitWidth: fieldM implicitWidth: fieldM
@@ -63,14 +60,11 @@ ColumnLayout
} }
} }
} }
Field Field {
{
label: qsTr("Vorname") label: qsTr("Vorname")
mandatory: true mandatory: true
TextField TextField {
{
// property string name: "firstname"
id: firstname id: firstname
implicitWidth: fieldM implicitWidth: fieldM
@@ -85,7 +79,6 @@ ColumnLayout
mandatory: true mandatory: true
TextField { TextField {
// property string name: "lastname"
id: lastname id: lastname
implicitWidth: fieldM implicitWidth: fieldM
@@ -99,45 +92,39 @@ ColumnLayout
RowLayout { RowLayout {
spacing: Dimensions.m spacing: Dimensions.m
Field Field {
{ id: street
label: qsTr("Straße") label: qsTr("Straße")
mandatory: true mandatory: true
TextField TextField {
{
// property string name: "street"
id: street
implicitWidth: fieldM implicitWidth: fieldM
placeholderText: qsTr("Musterstraße") placeholderText: qsTr("Musterstraße")
validator: NotEmptyValidator { validator: NotEmptyValidator {
} }
} }
} }
Field Field {
{ id: houseno
mandatory: true mandatory: true
label: qsTr("Hausnummer") label: qsTr("Hausnummer")
TextField TextField {
{
// property string name: "houseno"
id: houseno
implicitWidth: fieldS implicitWidth: fieldS
placeholderText: qsTr("1a") placeholderText: qsTr("1a")
validator: NotEmptyValidator { validator: NotEmptyValidator {
} }
} }
} }
Field Field {
{
label: qsTr("PLZ") label: qsTr("PLZ")
mandatory: true mandatory: true
ComboBox ComboBox {
{
// property string name: "postcode"
id: postcode id: postcode
currentIndex: -1 currentIndex: -1
@@ -152,14 +139,11 @@ ColumnLayout
validator: NotEmptyValidator {} validator: NotEmptyValidator {}
} }
} }
Field Field {
{
label: qsTr("Ort") label: qsTr("Ort")
mandatory: true mandatory: true
ComboBox ComboBox {
{
// property string name: "city"
id: city id: city
currentIndex: -1 currentIndex: -1
@@ -172,22 +156,6 @@ ColumnLayout
} }
} }
} }
Field
{
label: qsTr("Land")
mandatory: true
ComboBox
{
// property string name: "country"
id: country
currentIndex: 37
editable: true
implicitWidth: fieldM
model: address_model
textRole: "country"
}
}
} }
IconLabel { IconLabel {
color: Colors.foreground color: Colors.foreground
@@ -206,7 +174,6 @@ ColumnLayout
label: qsTr("Telefonnummer") label: qsTr("Telefonnummer")
TextField { TextField {
// property string name: "phone"
id: phone id: phone
implicitWidth: fieldM implicitWidth: fieldM
@@ -216,13 +183,10 @@ ColumnLayout
} }
} }
} }
Field Field {
{
label: qsTr("Mobil") label: qsTr("Mobil")
TextField TextField {
{
// property string name: "mobile"
id: mobile id: mobile
implicitWidth: fieldM implicitWidth: fieldM
@@ -232,13 +196,10 @@ ColumnLayout
} }
} }
} }
Field Field {
{
label: qsTr("E-Mail Adresse") label: qsTr("E-Mail Adresse")
TextField TextField {
{
// property string name: "email"
id: email id: email
implicitWidth: fieldM implicitWidth: fieldM
@@ -248,13 +209,10 @@ ColumnLayout
} }
} }
} }
Field Field {
{
label: qsTr("Briefanrede") label: qsTr("Briefanrede")
TextField TextField {
{
// property string name: "formofaddress"
id: formofaddress id: formofaddress
implicitWidth: fieldM implicitWidth: fieldM

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

@@ -4,6 +4,7 @@ import QtQuick.Controls
import Qt.labs.qmlmodels import Qt.labs.qmlmodels
ColumnLayout { ColumnLayout {
anchors.fill: parent
spacing: Dimensions.l spacing: Dimensions.l
RowLayout { RowLayout {
@@ -27,7 +28,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

@@ -19,8 +19,8 @@ Item {
contentStack.replace("Dashboard.qml"); contentStack.replace("Dashboard.qml");
} }
// anchors.fill: parent anchors.fill: parent
// anchors.topMargin: Dimensions.l anchors.topMargin: Dimensions.l
Component.onCompleted: { Component.onCompleted: {
loggedin_user.loginOkay.connect(loggedin); loggedin_user.loginOkay.connect(loggedin);

View File

@@ -38,7 +38,7 @@ ColumnLayout {
BarButton { BarButton {
ButtonGroup.group: mainNav ButtonGroup.group: mainNav
icon.source: "qrc:/images/BuildingOffice2-Outline.svg" icon.source: "qrc:/images/BuildingOffice2-Outline.svg"
target: "/Gui/Objects/ObjectsTable.qml" target: "/Gui/ObjectsTable.qml"
text: qsTr("Objekt") text: qsTr("Objekt")
visible: !onSubPage visible: !onSubPage
} }

View File

@@ -1,255 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
GridLayout
{
id: newObject
columns: 4
Layout.fillWidth: true
Layout.fillHeight: true
rowSpacing: 9
//// New grid row
Label
{
text: qsTr("Land")
Layout.alignment: Qt.AlignRight
visible: false
}
ComboBox
{
property string name: "country"
id: country
Layout.fillWidth: true
editable: true
// onEditTextChanged: checkFields()
// onCurrentTextChanged: checkFields()
model: address_model
textRole: "country"
popup.height: 300
currentIndex: 37
visible: false
}
Label
{
text: qsTr("PLZ")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
ComboBox
{
property string name: "postcode"
id: postcode
Layout.fillWidth: true
editable: true
onCurrentTextChanged: checkFields()
onEditTextChanged: checkFields()
onActivated: currentValue
model: address_model
textRole: "display"
popup.height: 300
currentIndex: -1
onCurrentIndexChanged: city.currentIndex = postcode.currentIndex
validator: RegularExpressionValidator
{
regularExpression: /([0-9]{1,5})/
}
}
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
currentIndex: -1
}
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()
}
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()
}
// New grid row
// New grid row
Label
{
text: qsTr("Parteien")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
SpinBox
{
property string name: "units"
id: partitions
Layout.fillWidth: true
from: 1
to: 100
value: 1
editable: true
}
Label
{
text: qsTr("Stockwerke")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
SpinBox
{
property string name: "floors"
id: floors
Layout.fillWidth: true
from: 1
to: 100
value: 1
editable: true
}
// 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("Ja"), qsTr("Nein")]
}
Label
{
text: qsTr("Aufzug")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
ComboBox
{
property string name: "lift"
id: lift
Layout.fillWidth: true
editable: false
model: [qsTr("Ja"), qsTr("Nein")]
}
//New grid row
Label
{
text: qsTr("Objekt-Nr.")
Layout.alignment: Qt.AlignRight
}
TextField
{
property string name: "objectno"
id: objectno
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
placeholderText: qsTr("0 oder leer um eine Nummer automatisch zu generieren")
placeholderTextColor: "pink"
}
Label
{
text: qsTr("Besonderheiten")
Layout.alignment: Qt.AlignRight
}
TextField
{
property string name: "remarks"
id: remarks
Layout.fillWidth: true
}
//// New grid row
Label
{
text: qsTr("Reinigungsmittel wo?*")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
property string name: "cleaningproducts"
id: cleaningproducts
Layout.fillWidth: true
onTextChanged: checkFields()
}
Item
{
Layout.fillHeight: true
}
function checkObjectField()
{
return street.text.trim() && houseno.text.trim() &&
(postcode.editText.trim() || postcode.currentText.trim()) &&
(city.editText.trim() || city.currentText.trim()) &&
cleaningproducts.text.trim()
}
}

View File

@@ -1,155 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Dialogs
import Js
ColumnLayout
{
property var new_object: null
//property alias checkAddContact: checkAddContact
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 15
Label
{
text: qsTr("Objekt anlegen")
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
font.pixelSize: 35
}
CheckBox
{
id: checkAddObjectContact
text: qsTr("Ansprechpartner hinzufügen")
Layout.alignment: Qt.AlignRight
checked: false
onCheckStateChanged:
{
checkFields()
}
}
RowLayout
{
id: addObject
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 45
Frame
{
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
AddNewObject
{
id: newObject
width: parent.width
}
}
ObjectAddOns
{
id: addObjectLayout
visible: checkAddObjectContact.checked
}
}
RowLayout
{
Layout.fillHeight: true
Layout.alignment: Qt.AlignRight
Button
{
text: qsTr("Abbrechen")
onClicked: contentStack.pop()
}
Button
{
property var new_object: null
id: saveBtn
text: qsTr("Speichern")
enabled: false
onClicked:
{
new_object = JsLib.parseForm(newObject)
// For Debugging
console.log(JSON.stringify(new_object))
//
new_object['lift'] = new_object['lift'] === 'Ja' ? 1 : 0
new_object['mezzanin'] = new_object['mezzanin'] === 'Ja' ? 1 : 0
object_model.addObject(new_object)
}
}
}
Item
{
id: spacer3
Layout.fillHeight: true
}
Component.onCompleted:
{
//object_model.objectAdded.connect(onObjectAdded)
//contact_model.objectContactAdded.connect(onObjectContact)
}
Connections
{
target: object_model
function onObjectIdReady()
{
var obj_id = arguments[0]
if (checkAddObjectContact.checked && obj_id)
{
var new_objecto = addObjectLayout.getForm()
contact_model.addObjectContact(new_objecto, obj_id)
object_model.viewCriterion("Alle")
}
contentStack.pop()
}
}
// function onObjectAdded(added, oid)
// {
// if (!added)
// console.log(qsTr("Fehler beim Objekt-Anlegen!"))
// if (checkAddObjectContact.checked && oid)
// {
// var new_objecto = addObjectLayout.getForm()
// contact_model.addObjectContact(new_objecto, oid)
// }
// else appLoader.source = "ObjectTable.qml"
// }
// function onObjectContact(added)
// {
// if (!added)
// console.log(qsTr("Fehler beim Objekt-Kontakt-Anlegen!"))
// else
// {
// //object_model.viewCriterion("Alle")
// appLoader.source = "ObjectTable.qml"
// }
// }
function checkFields()
{
if(checkAddObjectContact.checked)
{
if(!newObject.checkObjectField() || !addObjectLayout.contactPerson.contacts || !addObjectLayout.contactPerson.contacts.length)
saveBtn.enabled = false
else
saveBtn.enabled = true
}
else if (!newObject.checkObjectField())
saveBtn.enabled = false
else
saveBtn.enabled = true
}
}

View File

@@ -1,130 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
ApplicationWindow
{
id: addMitarbeiter
title: qsTr("Objekt - Neuer Mitarbeiter")
ColumnLayout
{
anchors.fill: parent
anchors.margins: 10
Label
{
text: qsTr("Mitarbeiter zuweisen")
Layout.alignment: Qt.AlignHCenter
font.pixelSize: 35
}
GridLayout
{
Layout.fillWidth: true
columns: 2
rowSpacing: 4
columnSpacing: 6
Label
{
text: qsTr("Eingesetzter Mitarbeiter")
Layout.alignment: Qt.AlignRight
}
ComboBox
{
id: assignee
Layout.fillWidth: true
}
Label
{
text: qsTr("Lohn Mitarbeiter pro Stunde")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: wage
Layout.fillWidth: true
}
Label
{
text: qsTr("Einsatzdauer")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: duration
Layout.fillWidth: true
}
Label
{
text: qsTr("Reinigungstage")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: cleanDays
Layout.fillWidth: true
}
Label
{
text: qsTr("Tätigkeiten")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: tasks
Layout.fillWidth: true
}
Label
{
text: qsTr("Ertrag")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: output
Layout.fillWidth: true
}
Item
{
Layout.fillHeight: true
Layout.columnSpan: 2
}
}
RowLayout
{
Layout.fillWidth: true
spacing: 5
Item
{
Layout.fillWidth: true
}
Button
{
text: qsTr("Abbrechen")
onClicked: addMitarbeiter.close()
}
Button
{
text: qsTr("Hinzufügen")
onClicked:
{
if (duration.text.trim() !== "" && wage.text.trim() !== "" && cleanDays.text.trim() !== "" && tasks.text.trim() !== "" && output.text.trim() !== "")
{
var ne = {
"assignee": assignee.currentText,
"duration": duration.text.trim(),
"wage": wage.text.trim(),
"cleandays": cleanDays.text.trim(),
"tasks": tasks.text.trim(),
"output": output.text.trim(),
};
addMitarbeiter.addNewEmployee(ne)
addMitarbeiter.close()
}
}
}
}
}
signal addNewEmployee(var new_employee)
}

View File

@@ -1,317 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
GridLayout
{
property var contacts: null
columns: 2
Layout.fillWidth: true
Label
{
text: qsTr("Position")
Layout.alignment: Qt.AlignRight | Qt.AlignTop
}
ComboBox
{
//property string name: "contacttype"
id: posizion
Layout.fillWidth: true
editable: false
model: [qsTr("Beirat"), qsTr("Hausmeister"), qsTr("Hausbewohner"), qsTr("Sonstiges")]
}
Label
{
text: qsTr("Anrede")
Layout.alignment: Qt.AlignRight
}
ComboBox
{
id: title
model: [qsTr("Herr"), qsTr("Frau"), qsTr("Keine Angabe")]
Layout.fillWidth: true
}
Label
{
text: qsTr("Vorname*")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: firstname
Layout.fillWidth: true
// onTextChanged: checkContactFields()
}
Label
{
text: qsTr("Nachname*")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: lastname
Layout.fillWidth: true
}
Label
{
text: mobile.text ? qsTr("Telefonnummer") : qsTr("Telefonnummer*")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: phonenumber
Layout.fillWidth: true
}
Label
{
text: phonenumber.text ? qsTr("Mobil") : qsTr("Mobil*")
Layout.alignment: Qt.AlignRight
}
TextField
{
id: mobile
Layout.fillWidth: true
}
RowLayout
{
Layout.fillWidth: true
Layout.columnSpan: 2
Item
{
Layout.fillWidth: true
}
Button
{
id: removeContact
text: qsTr("Entfernen")
enabled: false
onClicked:
{
if (contactView.highlightFollowsCurrentItem)
{
delete contacts[contactView.currentIndex]
contacts = contacts.filter(elm => elm)
contactModel.remove(contactView.currentIndex)
contactView.highlightFollowsCurrentItem = false
contactView.currentIndex = -1
if (Object.keys(contacts).length === 0)
{
enabled = false
console.log(contacts)
}
checkFields()
}
}
}
Button
{
id: addContact
text: qsTr("Hinzufügen")
enabled: firstname.text.trim() && lastname.text.trim() && (phonenumber.text.trim() || mobile.text.trim()) && (contacts === null || Object.keys(contacts).length < 3)
onClicked:
{
var num_contacts = 0
if (contacts !== null && contacts !== undefined) num_contacts = Object.keys(contacts).length
else contacts = []
if (num_contacts < 3 && firstname.text.trim() !== "" && lastname.text.trim() !== "" && (phonenumber.text.trim() !== "" || mobile.text.trim() !== ""))
{
contacts[num_contacts] = {}
contacts[num_contacts]["title"] = title.currentText
contacts[num_contacts]["position"] = posizion.currentText
contacts[num_contacts]["fname"] = firstname.text.trim()
contacts[num_contacts]["lname"] = lastname.text.trim()
contacts[num_contacts]["phone"] = phonenumber.text.trim()
contacts[num_contacts]["mobile"] = mobile.text.trim()
contactModel.append({name: title.currentText + " " + firstname.text.trim() + " " + lastname.text.trim(), phone: phonenumber.text.trim(), mobile: mobile.text.trim(), posizion: posizion.currentText})
if (checkFields())
{
saveBtn.enabled = true
}
firstname.text = ""
lastname.text = ""
phonenumber.text = ""
mobile.text = ""
posizion.currentIndex = 0
title.currentIndex = 0
removeContact.enabled = true
checkFields()
}
}
}
}
Label
{
text: qsTr("Ansprechpartner")
Layout.alignment: Qt.AlignRight | Qt.AlignTop
}
ListModel
{
id: contactModel
}
// Component
// {
// id: headline
// Row
// {
// spacing: 9
// Text
// {
// id: cpname
// text: qsTr("Name")
// font.bold: true
// horizontalAlignment: Text.AlignLeft
// color: "white"
// }
// Text
// {
// id: cpphone
// text: qsTr("Telefon")
// font.bold: true
// horizontalAlignment: Text.AlignLeft
// color: "white"
// }
// Text
// {
// id: cpmobile
// text: qsTr("Mobil")
// font.bold: true
// horizontalAlignment: Text.AlignLeft
// color: "white"
// }
// Text
// {
// id: cppos
// text: qsTr("Position")
// font.bold: true
// horizontalAlignment: Text.AlignLeft
// color: "white"
// }
// Text
// {
// id: cttype
// text: qsTr("Typ")
// font.bold: true
// horizontalAlignment: Text.AlignLeft
// color: "white"
// }
// }
// }
Component
{
id: highlight
Rectangle
{
width: parent.width
color: "lightsteelblue"; radius: 5
y: contactView.currentItem.y
Behavior on y
{
SpringAnimation
{
spring: 3
damping: 0.2
}
}
}
}
Rectangle
{
id: mainRect
Layout.fillWidth: true
implicitHeight: 100
color: firstname.palette.base
border.color: firstname.activeFocus? firstname.palette.highlight: firstname.palette.base
clip: true
ScrollView
{
id: objContactView
// Layout.fillWidth: true
// Layout.preferredHeight: 100
//Layout.columnSpan: 3
anchors.fill: mainRect
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ListView
{
id: contactView
anchors.fill: objContactView
// implicitHeight: objContactView.height
// implicitWidth: objContactView.width
model: contactModel
// header: headline
highlight: Rectangle { color: "slategray"; radius: 3}
highlightFollowsCurrentItem: false
//focus: true test
onActiveFocusChanged: if(!focus) currentIndex = -1
delegate: Item
{
width: contactView.width
height: 77
MouseArea
{
anchors.fill: parent
onClicked:
{
contactView.currentIndex = index
contactView.highlightFollowsCurrentItem = true
}
}
Column
{
anchors.margins: 5
//spacing: 3
Text
{
text: '<b>' + qsTr('Name: ') + '</b>' + model.name
horizontalAlignment: Text.AlignLeft
color: "white"
}
Text
{
text: '<b>' + qsTr('Telefon: ') + '</b>' + model.phone
horizontalAlignment: Text.AlignLeft
color: "white"
}
Text
{
text: '<b>' + qsTr('Handy: ') + '</b>' + model.mobile
horizontalAlignment: Text.AlignLeft
color: "white"
}
Text
{
text: '<b>' + qsTr('Position: ') + '</b>' + model.posizion
horizontalAlignment: Text.AlignLeft
color: "white"
}
} // Column
} // delegate
} // Listview
} // Scrollview
}
}

View File

@@ -1,125 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
GridLayout
{
// property var employeeForm: null
// property var employees: null
// id: oaoemployee
// columns: 2
// rows: 4
// Label
// {
// text: qsTr("Mitarbeiter")
// Layout.alignment: Qt.AlignRight | Qt.AlignTop
// }
// ListModel
// {
// id: employeeModel
// }
// Component
// {
// id: employeesHeader
// Row
// {
// Text
// {
// id: empName
// text: qsTr("Mitarbeiter")
// width: 175
// font.bold: true
// horizontalAlignment: Text.AlignLeft
// color: "black"
// }
// }
// }
// Rectangle
// {
// Layout.fillWidth: true
// implicitHeight: 75
// Layout.rowSpan: 2
// color: mitarbeiterhin.palette.base
// border.color: mitarbeiterhin.activeFocus? mitarbeiterhin.palette.highlight: mitarbeiterhin.palette.base
// ListView
// {
// id: employeesList
// //anchors.fill: parent
// implicitHeight: parent.height
// model: employeeModel
// header: employeesHeader
// delegate: Row
// {
// width: 200
// height: 15
// //padding: 7
// Text
// {
// text: model.namens
// }
// }
// }
// }
// RowLayout
// {
// Layout.columnSpan: 2
// Layout.fillWidth: true
// Item
// {
// Layout.fillWidth: true
// }
// Button
// {
// id: mitarbeiterraus
// text: qsTr("Mitarbeiter entfernen")
// }
// Button
// {
// id: mitarbeiterhin
// text: qsTr("Mitarbeiter hinzufügen")
// onClicked:
// {
// var nm = Qt.createComponent("AddObjectEmployee.qml")
// if (nm.status === Component.Ready)
// {
// employeeForm = nm.createObject (appWindow, {width: 600, height: 400})
// employeeForm.addNewEmployee.connect(onAddEmployee)
// employeeForm.show()
// }
// else console.log(nm.errorString())
// }
// }
// }
// function onAddEmployee(new_employee)
// {
// var num_employees = 0
// if (employees === null || employees === undefined) employees = {}
// else num_employees = Object.keys(employees).length;
// employees[num_employees] = {}
// employees[num_employees]["assignee"] = new_employee["assignee"];
// employees[num_employees]["duration"] = new_employee["duration"];
// employees[num_employees]["wage"] = new_employee["wage"];
// employees[num_employees]["cleandays"] = new_employee["cleandays"];
// employees[num_employees]["tasks"] = new_employee["tasks"];
// employees[num_employees]["output"] = new_employee["output"];
// employeeModel.append({namens: new_employee["assignee"]});
// }
}

View File

@@ -1,28 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Js
Frame
{
property alias contactPerson: oaocontactperson
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
ColumnLayout
{
id: addObjectLayout
width: parent.width
ObjectAddOnContactPerson
{
id: oaocontactperson
}
Item
{
Layout.fillHeight: true
}
}
function getForm()
{
return oaocontactperson.contacts
}
}

View File

@@ -1,27 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item
{
property int selectedObject: -1
id: obDet
ColumnLayout
{
Label
{
text: qsTr("Ausgewählter Objekt " + selectedObject)
}
Button
{
text: qsTr("Zurück zu den Objekten")
onClicked: contentStack.pop()
}
}
Component.onCompleted:
{
object_model.onRowClicked(selectedObject)
}
}

View File

@@ -1,238 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
GridLayout
{
id: objectView
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
}
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()
}
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()
}
Label
{
text: qsTr("PLZ*")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
property string name: "postcode"
id: postcode
Layout.fillWidth: true
onTextChanged: checkFields()
}
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
currentIndex: -1
}
Label
{
text: qsTr("Lohnanteil inkl. Fahrtkosten")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
id: lohnanteil
Layout.fillWidth: true
Layout.columnSpan: 3
}
Label
{
text: qsTr("Materialanteil")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
id: materialanteil
Layout.fillWidth: true
Layout.columnSpan: 3
}
Label
{
text: qsTr("Zusatz 1")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
id: zusatz1
Layout.fillWidth: true
Layout.columnSpan: 3
}
Label
{
text: qsTr("Zusatz 2")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
id: zusatz2
Layout.fillWidth: true
Layout.columnSpan: 3
}
Label
{
text: qsTr("Gesamt Netto")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{id: gesamtnetto
Layout.fillWidth: true
Layout.columnSpan: 3
}
Label
{
text: qsTr("MwSt")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
id: mwst
Layout.fillWidth: true
Layout.columnSpan: 3
}
Label
{
text: qsTr("Gesamt(Netto+MwSt)")
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
TextField
{
id: gesamt
Layout.fillWidth: true
Layout.columnSpan: 3
}
Label
{
text: qsTr("Zahlungsziel")
Layout.alignment: Qt.AlignRight
}
ComboBox
{
property string name: "zahlungsziel"
id: zahlungsziel
Layout.fillWidth: true
editable: false
textRole: "display"
Layout.columnSpan: 3
}
Label
{
text: qsTr("Info")
Layout.alignment: Qt.AlignRight | Qt.AlignTop
}
ScrollView
{
id: infoview
Layout.fillWidth: true
Layout.preferredHeight: 110
Layout.columnSpan: 3
ScrollBar.horizontal: ScrollBar
{
policy: ScrollBar.AlwaysOn
}
TextArea
{
id: objectInfo
property string name: "objectinfo"
implicitWidth: parent.width
wrapMode: TextEdit.Wrap
background: Rectangle
{
color: objectInfo.palette.base
border.color: objectInfo.activeFocus? objectInfo.palette.highlight: objectInfo.palette.base
width: parent.width
}
}
}
Item
{
Layout.fillHeight: true
}
}

View File

@@ -1,186 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
ColumnLayout
{
property var availableFilters: [""]
spacing: Dimensions.l
function viewCriterion(criterion)
{
business_model.viewCriterion(criterion.text);
}
function onObjectContactAdded(added)
{
console.log(added)
if (added) object_model.viewCriterion("")
}
Component.onCompleted:
{
contact_model.objectContactAdded.connect(onObjectContactAdded)
// contentStack.pop()
}
RowLayout
{
Layout.fillWidth: true
spacing: Dimensions.l
SearchBar
{
}
QuickFilter {
onSelectedChanged: (name) => {
business_model.viewCriterion(name)
}
model: ListModel {
ListElement {
name: "Alle"
text: qsTr("Alle")
selected: true
}
ListElement {
name: "Aktiv"
text: qsTr("Aktiv")
selected: false
}
ListElement {
name: "Ehemalig"
text: qsTr("Ehemalig")
selected: false
}
ListElement {
name: "Angebote"
text: qsTr("Angebote")
selected: false
}
}
}
Button
{
id: addObjectBtn
icon.source: "qrc:/images/PlusCircle.svg"
text: qsTr("Objekt Hinzufügen")
Layout.alignment: Qt.AlignRight
onClicked: contentStack.push("AddObject.qml")
}
}
ColumnLayout
{
id: tableColumn
Layout.fillWidth: true
Layout.fillHeight: true
Layout.verticalStretchFactor: 1
clip: true
// anchors
// {
// top: searchBar.bottom
// bottom: parent.bottom
// left: parent.left
// right: parent.right
// topMargin: 15
// }
HorizontalHeaderView
{
id: horizontalHeaderview
Layout.fillWidth: true
implicitHeight: 40
movableColumns: true //@disable-check M16
syncView: objectTable
delegate: Rectangle {
color: addObjectBtn.palette.alternateBase
border.color: addObjectBtn.palette.base
implicitHeight: 40
Layout.fillWidth: true
implicitWidth: 1
Text
{
text: model.display
elide: Text.ElideRight
width: parent.width
height: parent.height
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: addObjectBtn.palette.text
}
}
}
TableView
{
property real newWidth: 0
id: objectTable
z: 1
// height: tableColumn.height - horizontalHeaderview.height
Layout.fillHeight: true
Layout.fillWidth: true
columnSpacing: 1
rowSpacing: 2
model: object_model
alternatingRows: true
resizableColumns: true // @disable-check M16
selectionBehavior: TableView.SelectRows
ScrollBar.vertical: ScrollBar
{
policy: objectTable.contentHeight > objectTable.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
}
selectionModel: ItemSelectionModel
{
id: obmodel
model: objectTable.model
}
delegate:Rectangle
{
required property bool selected
required property bool current
implicitWidth: objectTable.width / objectTable.columns
implicitHeight: 25
color: selected
? addObjectBtn.palette.highlight //palette.highlight
: (objectTable.alternatingRows && row % 2 !== 0
? addObjectBtn.palette.base // palette.base
: addObjectBtn.palette.alternateBase) //palette.alternateBase)
Text
{
text: (model.display === null || model.display === undefined)? "": model.display
elide: Text.ElideRight
width: parent.width
height: parent.height
verticalAlignment: Text.AlignVCenter
leftPadding: 9 //@d isable-check M16
color: addObjectBtn.palette.text
}
MouseArea
{
property bool hovered: false
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onDoubleClicked:
{
contentStack.push("ObjectDetails.qml", {selectedObject: row});
}
onEntered:
{
objectTable.selectionModel.select(objectTable.model.index(row, 0), ItemSelectionModel.SelectCurrent | ItemSelectionModel.Rows)
}
}
}
}
}
Item {
Layout.fillHeight: true
}
}

View File

@@ -1 +0,0 @@
module Objects

View File

@@ -5,6 +5,7 @@ import Qt.labs.qmlmodels
ColumnLayout ColumnLayout
{ {
anchors.fill: parent
spacing: Dimensions.l spacing: Dimensions.l
function viewOffers(criterion) function viewOffers(criterion)
{ {

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
{ {

View File

@@ -22,14 +22,11 @@ function firstConf(tabs)
function parseForm(...form) function parseForm(...form)
{ {
let data_form = {}; let data_form = {};
for (var i = 0; i < form.length; i++) for (var i = 0; i < form.length; i++)
{ {
for (var j = 0; j < form[i].children.length; j++) for (var j = 0; j < form[i].children.length; j++)
{ {
console.log(form[i].children[j])
if (form[i].children[j].toString().startsWith("Combo")) if (form[i].children[j].toString().startsWith("Combo"))
{ {
if(form[i].children[j].editText) if(form[i].children[j].editText)

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

File diff suppressed because it is too large Load Diff

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:
@@ -258,41 +165,25 @@ class ConfigLoader(QObject):
else: else:
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":
@@ -309,10 +200,12 @@ class ConfigLoader(QObject):
except Exception as e: except Exception as e:
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 +221,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 +239,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 +258,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 +271,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 +285,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,27 +70,20 @@ 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)
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().getConfig()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
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

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

View File

@@ -1,6 +1,8 @@
import json import json
from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal, QJsonDocument from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal, QJsonDocument
from PySide6.QtQml import QJSValue, QJSValueIterator from PySide6.QtQml import QJSValue
from .EmployeeDAO import EmployeeDAO from .EmployeeDAO import EmployeeDAO
# from ..PyqcrmFlags import PyqcrmFlags, PyqcrmAppliEmpyFlags # from ..PyqcrmFlags import PyqcrmFlags, PyqcrmAppliEmpyFlags
from ..ConfigLoader import ConfigLoader from ..ConfigLoader import ConfigLoader
@@ -33,31 +35,20 @@ class EmployeeModel(QAbstractTableModel):
self.__employee_dao.addEmployee(new_employee, self.__key, False) self.__employee_dao.addEmployee(new_employee, self.__key, False)
@Slot(QJSValue) @Slot(QJSValue)
def addApplicant(self, new_applicant): def addApplicant(self, applicant: QJSValue):
data = {} self.__employee_dao.addEmployee({
it = QJSValueIterator(new_applicant) "city": applicant.property("city").toString(),
while it.hasNext(): "email": applicant.property("email").toString(),
it.next() "firstname": applicant.property("firstname").toString(),
if "function" in it.value().toString() or "objectName" == it.name(): "formofaddress": applicant.property("formofaddress").toString(),
continue "houseno": applicant.property("houseno").toString(),
data[it.name()] = it.value().toString() "lastname": applicant.property("lastname").toString(),
self.__employee_dao.addEmployee(data, self.__key) "mobile": applicant.property("mobile").toString(),
"phone": applicant.property("phone").toString(),
# @Slot(QJSValue) "postcode": applicant.property("postcode").toInt(),
# def addApplicant(self, applicant: QJSValue): "street": applicant.property("street").toString(),
# self.__employee_dao.addEmployee({ "title": applicant.property("title").toString(),
# "city": applicant.property("city").toString(), }, self.__key, True)
# "email": applicant.property("email").toString(),
# "firstname": applicant.property("firstname").toString(),
# "formofaddress": applicant.property("formofaddress").toString(),
# "houseno": applicant.property("houseno").toString(),
# "lastname": applicant.property("lastname").toString(),
# "mobile": applicant.property("mobile").toString(),
# "phone": applicant.property("phone").toString(),
# "postcode": applicant.property("postcode").toInt(),
# "street": applicant.property("street").toString(),
# "title": applicant.property("title").toString(),
# }, self.__key, True)
@Slot(bool) @Slot(bool)
def __refreshView(self, added): def __refreshView(self, added):

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")

101
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,8 +20,6 @@ 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]
@@ -95,40 +34,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:
@@ -138,23 +57,22 @@ def initializeProgram():
if DbManager().getConnection(): if DbManager().getConnection():
db_con = True db_con = True
user = UserManager() user = UserManager()
business_model = BusinessModel() # business_model = BusinessModel()
address_model = AddressModel() address_model = AddressModel()
business_type = BTypeModel() # business_type = BTypeModel()
contact_model = ContactModel() # contact_model = ContactModel()
employee_model = EmployeeModel() # employee_model = EmployeeModel()
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 +83,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()

18
qml.qrc
View File

@@ -6,8 +6,10 @@
<file>Gui/DbConfiguration.qml</file> <file>Gui/DbConfiguration.qml</file>
<file>Gui/LoginScreen.qml</file> <file>Gui/LoginScreen.qml</file>
<file>Gui/AddContact.qml</file> <file>Gui/AddContact.qml</file>
<file>Gui/AddCustomer.qml</file>
<file>Gui/Dashboard.qml</file> <file>Gui/Dashboard.qml</file>
<file>Gui/main.qml</file> <file>Gui/main.qml</file>
<file>Gui/CustomerView.qml</file>
<file>Gui/NoDbConnection.qml</file> <file>Gui/NoDbConnection.qml</file>
<file>Gui/Notifications.qml</file> <file>Gui/Notifications.qml</file>
<file>Gui/AddObject.qml</file> <file>Gui/AddObject.qml</file>
@@ -16,10 +18,15 @@
<file>Gui/ObjectAddOnContactPerson.qml</file> <file>Gui/ObjectAddOnContactPerson.qml</file>
<file>Gui/ObjectAddOnEmployee.qml</file> <file>Gui/ObjectAddOnEmployee.qml</file>
<file>Gui/AddObjectEmployee.qml</file> <file>Gui/AddObjectEmployee.qml</file>
<file>Gui/CustomersTable.qml</file>
<file>Gui/CustomerDetails.qml</file>
<file>Gui/ObjectsTable.qml</file> <file>Gui/ObjectsTable.qml</file>
<file>Gui/ObjectDetails.qml</file> <file>Gui/ObjectDetails.qml</file>
<file>Gui/AddNewObject.qml</file> <file>Gui/AddNewObject.qml</file>
<file>Gui/PrinterDialog.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/ReadMe.qml</file>
<file>Gui/UsersPage.qml</file> <file>Gui/UsersPage.qml</file>
<file>Gui/PyqcrmConf.qml</file> <file>Gui/PyqcrmConf.qml</file>
@@ -74,17 +81,6 @@
<file>Gui/Customer/CustomersTable.qml</file> <file>Gui/Customer/CustomersTable.qml</file>
<file>Gui/Customer/CustomerView.qml</file> <file>Gui/Customer/CustomerView.qml</file>
<file>Gui/Customer/qmldir</file> <file>Gui/Customer/qmldir</file>
<file>Gui/Customer/NoCustomerContact.qml</file>
<file>Gui/Objects/AddNewObject.qml</file>
<file>Gui/Objects/AddObject.qml</file>
<file>Gui/Objects/AddObjectEmployee.qml</file>
<file>Gui/Objects/ObjectAddOnContactPerson.qml</file>
<file>Gui/Objects/ObjectAddOnEmployee.qml</file>
<file>Gui/Objects/ObjectAddOns.qml</file>
<file>Gui/Objects/ObjectDetails.qml</file>
<file>Gui/Objects/ObjectsTable.qml</file>
<file>Gui/Objects/ObjectView.qml</file>
<file>Gui/Objects/qmldir</file>
</qresource> </qresource>
<qresource prefix="/TeroStyle"/> <qresource prefix="/TeroStyle"/>
</RCC> </RCC>