Compare commits

12 Commits

Author SHA256 Message Date
Yuri Becker
76fdc880c7 Implement search 2025-04-24 01:37:09 +02:00
Yuri Becker
45f19d80d0 Use ORM for applicants 2025-04-21 23:45:33 +02:00
Yuri Becker
0ae153617b Fetch applicant from database 2025-04-19 00:55:12 +02:00
Yuri Becker
2832becccf Style EmployeesTable 2025-04-16 12:02:21 +02:00
Yuri Becker
cf5822c950 Somewhat fix errors when adding an applicant 2025-04-16 11:00:06 +02:00
Yuri Becker
f0382a960e Validate applicant and call database procedure 2025-04-15 15:24:31 +02:00
Yuri Becker
a720dfebeb Add fields in ApplicantForm 2025-04-08 11:23:16 +02:00
Yuri Becker
ddf35a55a8 Stub ApplicantForm 2025-04-03 15:33:28 +02:00
Yuri Becker
1e9ba40b6b Add global back button 2025-04-03 14:46:36 +02:00
Yuri Becker
fc9b1c0801 Use original icon names 2025-04-03 13:18:08 +02:00
Yuri Becker
3083406b1b Use common StackView 2025-04-03 13:06:37 +02:00
Yuri Becker
f172468ba6 Extract Employee View into own dir 2025-04-03 10:56:42 +02:00
108 changed files with 1592 additions and 1450 deletions

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

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

4
.idea/discord.xml generated
View File

@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DiscordProjectSettings"> <component name="DiscordProjectSettings">
<option name="show" value="APPLICATION" /> <option name="show" value="PROJECT_FILES" />
<option name="nameOverrideEnabled" value="true" />
<option name="nameOverrideText" value="the qml thing (╯°□°)╯︵ ┻━┻" />
<option name="description" value="" /> <option name="description" value="" />
<option name="applicationTheme" value="default" /> <option name="applicationTheme" value="default" />
<option name="iconsTheme" value="default" /> <option name="iconsTheme" value="default" />

View File

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

View File

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

5
.idea/qmlSettings.xml generated
View File

@@ -6,6 +6,11 @@
<entry key=""> <entry key="">
<value> <value>
<PerProfileState> <PerProfileState>
<option name="myExtraQmlPaths">
<list>
<option value="$PROJECT_DIR$/TeroStyle" />
</list>
</option>
<option name="myLSPEnabled" value="true" /> <option name="myLSPEnabled" value="true" />
<option name="myQmlFormatEnabled" value="true" /> <option name="myQmlFormatEnabled" value="true" />
</PerProfileState> </PerProfileState>

6
.idea/sqldialects.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/doc/db_schemer_v1.1-pyqcrm-202503171158_clean.sql" dialect="MariaDB" />
</component>
</project>

View File

@@ -1,180 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../js/qmldict.js" as JsLib
ColumnLayout
{
id: colPar
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
implicitWidth: parent.width
Label
{
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
id: headline
text: qsTr("Mitarbeiter / Bewerber hinzufügen")
font.pixelSize: 35
}
ButtonGroup
{
buttons: radio.children
onClicked:
{
checkFields()
personalData.requiredField()
}
}
Row
{
Layout.fillWidth: true
id: radio
//Layout.columnSpan: 2
RadioButton
{
checked: true
text: qsTr("Bewerber")
}
RadioButton
{
text: qsTr("Mitarbeiter")
}
}
// ScrollView
// {
// Layout.fillHeight: true
// Layout.fillWidth: true
// implicitWidth: parent.width
// ColumnLayout
// {
// anchors.fill: parent
// //implicitWidth: parent.width
// //width: parent.width
// //height: parent.height
RowLayout
{
Layout.fillWidth: true
//implicitWidth: parent.width
spacing: 50
Frame
{
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
//implicitWidth: parent.width
ApplicantPersonalData
{
id: personalData
width: parent.width
}
}
Frame
{
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
visible: radio.children[1].checked
ColumnLayout
{
Layout.alignment: Qt.AlignTop
implicitWidth: parent.width
ApplicantBankData
{
id: bankAccount
}
ApplicantNationalInsurance
{
id: nationalInsurance
}
ApplicantVarious
{
id: applicantVarious
}
}
}
}
Item
{
Layout.fillHeight: true
}
RowLayout
{
Layout.fillWidth: true
Layout.alignment: Qt.AlignRight
Button
{
text: qsTr("Abbrechen")
onClicked: appLoader.source = "EmployeeTable.qml"
}
Button
{
id: saveBtn
text: qsTr("Speichern")
enabled: false
onClicked:
{
var new_applicant
if (radio.children[0].checked)
{
// Ein Bewerber
new_applicant = JsLib.parseForm(personalData)
employee_model.addEmployee(new_applicant, true)
// appLoader.source = "EmployeeTable.qml"
// console.log(JSON.stringify (new_applicant))
}
else
{
// Ein Mitarbeiter
// console.log(personalData, bankAccount, nationalInsurance, applicantVarious)
new_applicant = JsLib.parseForm(personalData, bankAccount, nationalInsurance, applicantVarious)
employee_model.addEmployee(new_applicant, false)
// var new_contact = JsLib.addApplicant(addContactLayout)
// contact_model.addContact(new_contact)
// console.log(JSON.stringify (new_applicant))
}
}
}
}
Component.onCompleted:
{
employee_model.addedNewEmployee.connect(onAddNewEmployee)
}
// }
// } // ScrollView
function onAddNewEmployee(added)
{
if (added)
console.log('addedsuccesfully')
else
console.log('failedtoadd')
appLoader.source = 'EmployeeTable.qml'
}
function checkFields()
{
if(radio.children[1].checked)
{
if(!personalData.checkPersonalField())
saveBtn.enabled = false
else
saveBtn.enabled = true
}
else if (!personalData.checkPersonalField())
saveBtn.enabled = false
else
saveBtn.enabled = true
}
}

View File

@@ -59,7 +59,7 @@ ColumnLayout
Button Button
{ {
text: qsTr("Abbrechen") text: qsTr("Abbrechen")
onClicked: appLoader.source = "CustomerTable.qml" onClicked: contentStack.pop()
} }
Button Button
{ {
@@ -72,7 +72,7 @@ ColumnLayout
{ {
new_business = JsLib.parseForm(customerView) new_business = JsLib.parseForm(customerView)
business_model.addBusiness(new_business, 0) business_model.addBusiness(new_business, 0)
appLoader.source = "CustomerTable.qml" contentStack.pop()
} }
else else
{ {
@@ -99,7 +99,7 @@ ColumnLayout
{ {
var con_id = arguments[0] var con_id = arguments[0]
business_model.addBusiness(new_business, con_id) business_model.addBusiness(new_business, con_id)
appLoader.source = "CustomerTable.qml" contentStack.pop()
} }
} }

View File

@@ -61,7 +61,7 @@ ColumnLayout
Button Button
{ {
text: qsTr("Abbrechen") text: qsTr("Abbrechen")
onClicked: appLoader.source = "ObjectTable.qml" onClicked: contentStack.pop()
} }
Button Button
{ {
@@ -105,7 +105,7 @@ ColumnLayout
object_model.viewCriterion("Alle") object_model.viewCriterion("Alle")
} }
appLoader.source = "ObjectTable.qml" contentStack.pop()
} }
} }

View File

@@ -47,7 +47,7 @@ ColumnLayout
Button Button
{ {
text: qsTr("Abbrechen") text: qsTr("Abbrechen")
onClicked: appLoader.source = "OfferTable.qml" onClicked: contentStack.pop()
} }
Button Button
{ {

View File

@@ -12,8 +12,7 @@ ColumnLayout
Button Button
{ {
text: qsTr("Zurück") text: qsTr("Zurück")
//Layout.columnSpan: 2 onClicked: contentStack.pop()
onClicked: customersStack.pop()
} }
SplitView SplitView

View File

@@ -1,21 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
Item {
anchors.fill: parent
property var availableFilters: ["Name", "Adresse", "PLZ", "Ort"]
StackView
{
id: customersStack
anchors.fill: parent
initialItem: "CustomersTable.qml"
}
}

View File

@@ -4,6 +4,7 @@ import QtQuick.Controls
import Qt.labs.qmlmodels import Qt.labs.qmlmodels
ColumnLayout { ColumnLayout {
property var availableFilters: ["Name", "Adresse", "PLZ", "Ort"]
function viewCriterion(criterion) function viewCriterion(criterion)
{ {
@@ -15,7 +16,7 @@ ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: Dimensions.l spacing: Dimensions.l
Component.onCompleted: customersStack.pop() Component.onCompleted: contentStack.pop()
RowLayout RowLayout
{ {
@@ -67,7 +68,7 @@ ColumnLayout {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
icon.source: "qrc:/images/PlusCircle.svg" icon.source: "qrc:/images/PlusCircle.svg"
text: qsTr("Kunde Hinzufügen") text: qsTr("Kunde Hinzufügen")
onClicked: appLoader.source = "AddCustomer.qml" onClicked: contentStack.push("AddCustomer.qml")
} }
} }
ColumnLayout ColumnLayout
@@ -153,7 +154,7 @@ ColumnLayout {
onDoubleClicked: { onDoubleClicked: {
business_model.onRowClicked(row); business_model.onRowClicked(row);
customersStack.push("CustomerDetails.qml", { contentStack.push("CustomerDetails.qml", {
selectedClient: row selectedClient: row
}); });
} }

View File

@@ -1,27 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item
{
property int selectedEmployee: -1
id: emDet
ColumnLayout
{
Label
{
text: qsTr("Ausgewählter Mitarbeiter " + selectedEmployee)
}
Button
{
text: qsTr("Mitarbeiter zeigen")
onClicked: employeesStack.pop()
}
}
Component.onCompleted:
{
employee_model.onRowClicked(selectedEmployee)
}
}

View File

@@ -1,16 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
Item {
anchors.fill: parent
StackView {
id: employeesStack
anchors.fill: parent
initialItem: "EmployeesTable.qml"
}
}

View File

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

View File

@@ -0,0 +1,102 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../../js/qmldict.js" as JsLib
ColumnLayout {
id: colPar
function checkFields() {
if (!personalData.checkPersonalField())
saveBtn.enabled = false;
else
saveBtn.enabled = true;
}
function onAddNewEmployee(added) {
if (added) {
console.log('addedsuccesfully');
contentStack.pop();
} else {
console.log('failedtoadd');
}
}
Layout.fillHeight: true
Layout.fillWidth: true
anchors.fill: parent
implicitWidth: parent.width
Component.onCompleted: {
employee_model.addedNewEmployee.connect(onAddNewEmployee);
}
Label {
id: headline
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
font.pixelSize: 35
text: qsTr("Mitarbeiter / Bewerber hinzufügen")
}
RowLayout {
Layout.fillWidth: true
spacing: Dimensions.l
Frame {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.horizontalStretchFactor: 1
EmployeePersonalData {
id: personalData
implicitWidth: parent.width
}
}
Frame {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.horizontalStretchFactor: 1
ColumnLayout {
Layout.alignment: Qt.AlignTop
implicitWidth: parent.width
EmployeeBankData {
id: bankAccount
}
EmployeeNationalInsurance {
id: nationalInsurance
}
EmployeeVarious {
id: applicantVarious
}
}
}
}
Item {
Layout.fillHeight: true
}
RowLayout {
Layout.alignment: Qt.AlignRight
Layout.fillWidth: true
Button {
text: qsTr("Abbrechen")
onClicked: contentStack.pop()
}
Button {
id: saveBtn
enabled: false
text: qsTr("Speichern")
onClicked: {
const new_applicant = JsLib.parseForm(personalData, bankAccount, nationalInsurance, applicantVarious);
employee_model.addEmployee(new_applicant);
}
}
}
}

View File

@@ -0,0 +1,218 @@
import QtQuick
import QtQuick.Controls.impl
import QtQuick.Layouts
import TeroStyle
ColumnLayout {
readonly property int fieldM: 235
readonly property int fieldS: 110
readonly property bool valid: emailAddress.acceptableInput && firstName.acceptableInput && houseNumber.acceptableInput && lastName.acceptableInput && mobileNumber.acceptableInput && phoneNumber.acceptableInput && salutation.acceptableInput&& street.acceptableInput && title.acceptableInput && zipCode.acceptableInput
readonly property var value: QtObject {
readonly property string emailAddress: emailAddress.text ?? ""
readonly property string firstName: firstName.text ?? ""
readonly property string houseNumber: houseNumber.text ?? ""
readonly property string lastName: lastName.text ?? ""
readonly property string mobileNumber: mobileNumber.text ?? ""
readonly property string phoneNumber: phoneNumber.text ?? ""
readonly property string salutation: salutation.text ?? ""
readonly property string street: street.text ?? ""
readonly property string title: title.currentText
readonly property int zipCode: zipCode.currentIndex
}
function setValue(value) {
title.currentIndex = value.title ?? 0;
firstName.text = value.firstName ?? "";
lastName.text = value.lastName ?? "";
street.text = value.street ?? "";
houseNumber.text = value.houseNumber ?? "";
zipCode.currentIndex = value.zipCode ?? -1;
phoneNumber.text = value.phoneNumber ?? "";
mobileNumber.text = value.mobileNumber ?? "";
emailAddress.text = value.emailAddress ?? "";
salutation.text = value.salutation ?? 0;
}
spacing: Dimensions.l
IconLabel {
color: Colors.foreground
font: Typography.h2
icon.color: Colors.foreground
icon.height: Typography.h2.pixelSize
icon.source: "qrc:/images/UserCircle"
icon.width: Typography.h2.pixelSize
spacing: Dimensions.m
text: qsTr("Stammdaten")
}
RowLayout {
spacing: Dimensions.m
Field {
label: qsTr("Anrede")
ComboBox {
id: title
implicitWidth: fieldM
model: [qsTr("Keine Angabe"), qsTr("Herr"), qsTr("Frau")]
onCurrentTextChanged: {
switch (title.currentIndex) {
case 1:
salutation.text = "Sehr geehrter Herr ";
break;
case 2:
salutation.text = "Sehr geehrte Frau ";
break;
default:
salutation.text = "Guten Tag ";
}
}
}
}
Field {
label: qsTr("Vorname")
mandatory: true
TextField {
id: firstName
implicitWidth: fieldM
placeholderText: qsTr("Max")
validator: NotEmptyValidator {
}
}
}
Field {
label: qsTr("Nachname")
mandatory: true
TextField {
id: lastName
implicitWidth: fieldM
placeholderText: qsTr("Mustermann")
validator: NotEmptyValidator {
}
}
}
}
RowLayout {
spacing: Dimensions.m
Field {
label: qsTr("Straße")
TextField {
id: street
implicitWidth: fieldM
placeholderText: qsTr("Musterstraße")
}
}
Field {
label: qsTr("Hausnummer")
TextField {
id: houseNumber
implicitWidth: fieldS
placeholderText: qsTr("1a")
}
}
Field {
label: qsTr("PLZ")
ComboBox {
id: zipCode
currentIndex: -1
editable: true
implicitWidth: fieldS
model: address_model
textRole: "display"
onActivated: currentValue
onCurrentIndexChanged: city.currentIndex = zipCode.currentIndex
}
}
Field {
label: qsTr("Ort")
ComboBox {
id: city
currentIndex: -1
editable: true
implicitWidth: fieldM
model: address_model
textRole: "city"
}
}
}
IconLabel {
color: Colors.foreground
font: Typography.h2
icon.color: Colors.foreground
icon.height: Typography.h2.pixelSize
icon.source: "qrc:/images/Phone"
icon.width: Typography.h2.pixelSize
spacing: Dimensions.m
text: qsTr("Kontakt")
}
RowLayout {
spacing: Dimensions.m
Field {
label: qsTr("Telefonnummer")
TextField {
id: phoneNumber
implicitWidth: fieldM
placeholderText: "+49 1234 567890"
validator: OptionalPhoneNumberValidator {
}
}
}
Field {
label: qsTr("Mobil")
TextField {
id: mobileNumber
implicitWidth: fieldM
placeholderText: "+49 123 4567891011"
validator: OptionalPhoneNumberValidator {
}
}
}
Field {
label: qsTr("E-Mail Adresse")
TextField {
id: emailAddress
implicitWidth: fieldM
placeholderText: "tero@example.org"
validator: OptionalEmailAddressValidator {
}
}
}
Field {
label: qsTr("Briefanrede")
TextField {
id: salutation
implicitWidth: fieldM
}
}
}
}

View File

@@ -0,0 +1,34 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import TeroStyle
ColumnLayout {
property int row: -1
anchors.fill: parent
spacing: Dimensions.l
onRowChanged: {
if (row !== -1) {
applicantForm.setValue(applicantModel.applicant(row))
}
}
ApplicantForm {
id: applicantForm
Layout.alignment: Qt.AlignTop
Layout.fillHeight: true
Layout.verticalStretchFactor: 1
}
RowLayout {
spacing: Dimensions.l
Button {
text: qsTr("Als Mitarbeiter:in Einstellen")
icon.source: "qrc:/images/InboxArrowDown.svg"
}
}
}

View File

@@ -1,444 +1,409 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import TeroStyle
GridLayout GridLayout {
{
id: personalData id: personalData
columns: 4
Label function checkPersonalField() {
{ return (firstname.text.trim() && lastname.text.trim() && street.text.trim() && houseno.text.trim() && (postcode.editText.trim() || postcode.currentText.trim()) && (city.editText.trim() || city.currentText.trim()) && birthday.text.trim() && phonenumber.text.trim() && cellphone.text.trim() && email.text.trim() && jobdescription.text.trim() && contractstart.text.trim() && contractend.text.trim() && briefAnrede.text.trim());
text: qsTr("Anrede")
Layout.alignment: Qt.AlignRight
} }
ComboBox function requiredField() {
{ const pf = "Pflichtfeld";
property string name: "title" street.placeholderText = pf;
phonenumber.placeholderText = pf;
cellphone.placeholderText = pf;
email.placeholderText = pf;
jobdescription.placeholderText = pf;
contractstart.placeholderText = pf;
contractend.placeholderText = pf;
briefAnrede.placeholderText = pf;
houseno.placeholderText = pf;
}
columns: 4
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Anrede")
}
ComboBox {
id: title id: title
property string name: "title"
Layout.columnSpan: 3
Layout.fillWidth: true Layout.fillWidth: true
editable: false editable: false
Layout.columnSpan: 3
model: [qsTr("Keine Angabe"), qsTr("Herr"), qsTr("Frau")] model: [qsTr("Keine Angabe"), qsTr("Herr"), qsTr("Frau")]
onCurrentTextChanged:
{ onCurrentTextChanged: {
switch (title.currentIndex) switch (title.currentIndex) {
{
case 1: case 1:
briefAnrede.text = "Sehr geehrter Herr " briefAnrede.text = "Sehr geehrter Herr ";
break break;
case 2: case 2:
briefAnrede.text = "Sehr geehrte Frau " briefAnrede.text = "Sehr geehrte Frau ";
break break;
default: default:
briefAnrede.text = "Guten Tag " briefAnrede.text = "Guten Tag ";
} }
} }
} }
Label Label {
{ Layout.alignment: Qt.AlignRight
text: qsTr("Vorname*") text: qsTr("Vorname*")
Layout.alignment: Qt.AlignRight
} }
TextField TextField {
{
property string name: "firstname"
id: firstname id: firstname
Layout.fillWidth: true
onTextChanged: checkFields()
Layout.columnSpan: 3
}
Label
{
text: qsTr("Nachname*")
Layout.alignment: Qt.AlignRight
}
TextField
{
property string name: "lastname"
id: lastname
Layout.fillWidth: true
onTextChanged: checkFields()
Layout.columnSpan: 3
}
Label
{
text: qsTr("Straße")
Layout.alignment: Qt.AlignRight
}
TextField
{
property string name: "street"
id: street
Layout.fillWidth: true
placeholderTextColor: "red"
onTextChanged: checkFields()
property string name: "firstname"
Layout.columnSpan: 3
Layout.fillWidth: true
onTextChanged: checkFields()
} }
Label Label {
{
text: qsTr("Nr.")
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("Nachname*")
} }
TextField TextField {
{ id: lastname
property string name: "houseno"
id: houseno property string name: "lastname"
Layout.columnSpan: 3
Layout.fillWidth: true
onTextChanged: checkFields()
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Straße")
}
TextField {
id: street
property string name: "street"
Layout.fillWidth: true Layout.fillWidth: true
placeholderTextColor: "red" placeholderTextColor: "red"
onTextChanged: checkFields() onTextChanged: checkFields()
validator: RegularExpressionValidator }
{ Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Nr.")
}
TextField {
id: houseno
property string name: "houseno"
Layout.fillWidth: true
placeholderTextColor: "red"
validator: RegularExpressionValidator {
regularExpression: /([0-9a-zA-Z\-]{1,6})/ regularExpression: /([0-9a-zA-Z\-]{1,6})/
} }
onTextChanged: checkFields()
} }
Label Label {
{ Layout.alignment: Qt.AlignRight
text: qsTr("PLZ") text: qsTr("PLZ")
Layout.alignment: Qt.AlignRight
} }
ComboBox {
ComboBox
{
property string name: "postcode"
id: postcode id: postcode
property string name: "postcode"
Layout.fillWidth: true Layout.fillWidth: true
currentIndex: -1
editable: true editable: true
onEditTextChanged: checkFields()
onCurrentTextChanged: checkFields()
onActivated: currentValue
model: address_model model: address_model
popup.height: 300
textRole: "display" textRole: "display"
popup.height: 300
currentIndex: -1 validator: PostcodeValidator {
}
onActivated: currentValue
onCurrentIndexChanged: city.currentIndex = postcode.currentIndex onCurrentIndexChanged: city.currentIndex = postcode.currentIndex
validator: RegularExpressionValidator
{
regularExpression: /([^$][0-9]{1,4})/
}
}
Label
{
text: qsTr("Ort")
Layout.alignment: Qt.AlignRight
}
ComboBox
{
property string name: "city"
id: city
Layout.fillWidth: true
editable: true
onEditTextChanged: checkFields()
onCurrentTextChanged: checkFields() onCurrentTextChanged: checkFields()
model: address_model onEditTextChanged: checkFields()
textRole: "city" }
popup.height: 300 Label {
currentIndex: -1 Layout.alignment: Qt.AlignRight
text: qsTr("Ort")
}
ComboBox {
id: city
property string name: "city"
Layout.fillWidth: true
currentIndex: -1
editable: true
model: address_model
popup.height: 300
textRole: "city"
onCurrentTextChanged: checkFields()
onEditTextChanged: checkFields()
} }
Label Label {
{ Layout.alignment: Qt.AlignRight
text: qsTr("Geburtsname") text: qsTr("Geburtsname")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
} }
TextField TextField {
{
property string name: "birthname"
id: birthname id: birthname
Layout.fillWidth: true
onTextChanged: checkFields() property string name: "birthname"
Layout.columnSpan: 3 Layout.columnSpan: 3
visible: radio.children[1].checked Layout.fillWidth: true
onTextChanged: checkFields()
} }
Label Label {
{
text: qsTr("Geburtsdatum*")
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked text: qsTr("Geburtsdatum*")
} }
TextField TextField {
{
property string name: "birthday"
id: birthday id: birthday
Layout.fillWidth: true
onTextChanged: checkFields() property string name: "birthday"
Layout.columnSpan: 3 Layout.columnSpan: 3
visible: radio.children[1].checked Layout.fillWidth: true
validator: RegularExpressionValidator
{ validator: RegularExpressionValidator {
regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/ regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/
} }
Keys.onPressed: (event)=>
{
if (event.key !== Qt.Key_Backspace)
{
var len = birthday.length
var bd = birthday.text
if (len === 2 || len === 5) birthday.text = bd + "."
}
}
}
Label Keys.onPressed: event => {
{ if (event.key !== Qt.Key_Backspace) {
var len = birthday.length;
var bd = birthday.text;
if (len === 2 || len === 5)
birthday.text = bd + ".";
}
}
onTextChanged: checkFields()
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Geburtsort*") text: qsTr("Geburtsort*")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
} }
TextField TextField {
{
property string name: "placeofbirth"
id: placeofbirth id: placeofbirth
Layout.fillWidth: true
onTextChanged: checkFields() property string name: "placeofbirth"
Layout.columnSpan: 3 Layout.columnSpan: 3
visible: radio.children[1].checked Layout.fillWidth: true
onTextChanged: checkFields()
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Telefonnummer")
}
TextField {
id: phonenumber
property string name: "phone"
Layout.columnSpan: 3
Layout.fillWidth: true
placeholderTextColor: "red"
validator: RegularExpressionValidator {
regularExpression: /([+0-9]{1})([0-9]{1,17})/
} }
Label
{
text: qsTr("Telefonnummer")
Layout.alignment: Qt.AlignRight
}
TextField
{
property string name: "phone"
id: phonenumber
Layout.fillWidth: true
placeholderTextColor: "red"
Layout.columnSpan: 3
onTextChanged: checkFields() onTextChanged: checkFields()
validator: RegularExpressionValidator
{
regularExpression: /([+0-9]{1})([0-9]{1,17})/
} }
} Label {
Label Layout.alignment: Qt.AlignRight
{
text: qsTr("Mobil") text: qsTr("Mobil")
Layout.alignment: Qt.AlignRight
} }
TextField TextField {
{
property string name: "mobile"
id: cellphone id: cellphone
property string name: "mobile"
Layout.columnSpan: 3
Layout.fillWidth: true Layout.fillWidth: true
placeholderTextColor: "red" placeholderTextColor: "red"
Layout.columnSpan: 3
onTextChanged: checkFields() validator: RegularExpressionValidator {
validator: RegularExpressionValidator
{
regularExpression: /([+0-9]{1})([0-9]{1,17})/ 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
placeholderTextColor: "red"
Layout.columnSpan: 3
onTextChanged: checkFields() onTextChanged: checkFields()
placeholderText: qsTr("beispiel@domain.de")
validator: RegularExpressionValidator
{
regularExpression: /([\+!#$%&\*\\/\=?\^_`\.{|}\~\-\_0-9A-Za-z]{1,185})@([0-9A-Za-z\.\-\_]{1,64})\.([a-zA-z]{2,5})/
} }
} Label {
Label
{
text: qsTr("Familienstand")
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked text: qsTr("E-Mail")
} }
ComboBox TextField {
{ id: email
property string name: "maritalstatus"
property string name: "email"
Layout.columnSpan: 3
Layout.fillWidth: true
placeholderText: qsTr("beispiel@domain.de")
placeholderTextColor: "red"
validator: EmailAddressValidator {
}
onTextChanged: checkFields()
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Familienstand")
}
ComboBox {
id: maritalstatus id: maritalstatus
property string name: "maritalstatus"
Layout.columnSpan: 3
Layout.fillWidth: true Layout.fillWidth: true
editable: false editable: false
model: [qsTr("ledig"), qsTr("verheiratet"), qsTr("verwitwet"), qsTr("geschieden")] model: [qsTr("ledig"), qsTr("verheiratet"), qsTr("verwitwet"), qsTr("geschieden")]
visible: radio.children[1].checked
Layout.columnSpan: 3
} }
Label Label {
{ Layout.alignment: Qt.AlignRight
text: qsTr("Stundenlohn") text: qsTr("Stundenlohn")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
} }
TextField TextField {
{
property string name: "salary"
id: salary id: salary
Layout.fillWidth: true
visible: radio.children[1].checked property string name: "salary"
placeholderTextColor: "red"
Layout.columnSpan: 3 Layout.columnSpan: 3
Layout.fillWidth: true
placeholderTextColor: "red"
onTextChanged: checkFields() onTextChanged: checkFields()
} }
Label Label {
{ Layout.alignment: Qt.AlignRight
text: qsTr("Jobbeschreibung") text: qsTr("Jobbeschreibung")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
} }
TextField TextField {
{
property string name: "jobdesc"
id: jobdescription id: jobdescription
Layout.fillWidth: true
visible: radio.children[1].checked property string name: "jobdesc"
placeholderTextColor: "red"
Layout.columnSpan: 3 Layout.columnSpan: 3
Layout.fillWidth: true
placeholderTextColor: "red"
onTextChanged: checkFields() onTextChanged: checkFields()
} }
Label Label {
{ Layout.alignment: Qt.AlignRight
text: qsTr("Vertragsbeginn") text: qsTr("Vertragsbeginn")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
} }
TextField TextField {
{
property string name: "contractstart"
id: contractstart id: contractstart
Layout.fillWidth: true
visible: radio.children[1].checked property string name: "contractstart"
placeholderTextColor: "red"
Layout.columnSpan: 3 Layout.columnSpan: 3
onTextChanged: checkFields() Layout.fillWidth: true
validator: RegularExpressionValidator placeholderTextColor: "red"
{
validator: RegularExpressionValidator {
regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/ regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/
} }
Keys.onPressed: (event)=>
{
if (event.key !== Qt.Key_Backspace)
{
var len = contractstart.length
var bd = contractstart.text
if (len === 2 || len === 5) contractstart.text = bd + "."
}
}
}
Label
{
text: qsTr("Vertragsende")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
}
TextField
{
property string name: "contractend"
id: contractend
Layout.fillWidth: true
visible: radio.children[1].checked
placeholderTextColor: "red"
Layout.columnSpan: 3
onTextChanged: checkFields()
validator: RegularExpressionValidator
{
regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/
}
Keys.onPressed: (event)=>
{
if (event.key !== Qt.Key_Backspace)
{
var len = contractend.length
var bd = contractend.text
if (len === 2 || len === 5) contractend.text = bd + "."
}
}
}
Label
{
text: qsTr("Arbeitszeiten Tage")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
}
ComboBox
{
property string name: "workdays"
id: workdays
Layout.fillWidth: true
visible: radio.children[1].checked
model: ["1","2","3","4","5","6","7"]
}
Label
{
text: qsTr("Stunden")
Layout.alignment: Qt.AlignRight
visible: radio.children[1].checked
}
ComboBox
{
property string name: "workhours"
id: workhours
Layout.fillWidth: true
visible: radio.children[1].checked
model: ["1","2","3","4","5","6","7","8"]
}
Label
{
text: qsTr("Briefanrede")
Layout.alignment: Qt.AlignRight
}
TextField
{
property string name: "formofaddress"
id: briefAnrede
Layout.fillWidth: true
placeholderTextColor: "red"
Layout.columnSpan: 3
onTextChanged: checkFields()
}
Item Keys.onPressed: event => {
{ if (event.key !== Qt.Key_Backspace) {
Layout.fillHeight: true var len = contractstart.length;
var bd = contractstart.text;
if (len === 2 || len === 5)
contractstart.text = bd + ".";
}
}
onTextChanged: checkFields()
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Vertragsende")
}
TextField {
id: contractend
property string name: "contractend"
Layout.columnSpan: 3
Layout.fillWidth: true
placeholderTextColor: "red"
validator: RegularExpressionValidator {
regularExpression: /((^|)(0[1-9]{1}|[1-2]{1}[0-9]{1}|3[0-1]))\.((^|)(0[1-9]{1}|1[0-2]{1}))\.((^|)(196[0-9]{1}|19[7-9]{1}[0-9]{1}|20[0-9]{2}))/
}
Keys.onPressed: event => {
if (event.key !== Qt.Key_Backspace) {
var len = contractend.length;
var bd = contractend.text;
if (len === 2 || len === 5)
contractend.text = bd + ".";
}
}
onTextChanged: checkFields()
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Arbeitszeiten Tage")
}
ComboBox {
id: workdays
property string name: "workdays"
Layout.fillWidth: true
model: ["1", "2", "3", "4", "5", "6", "7"]
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Stunden")
}
ComboBox {
id: workhours
property string name: "workhours"
Layout.fillWidth: true
model: ["1", "2", "3", "4", "5", "6", "7", "8"]
}
Label {
Layout.alignment: Qt.AlignRight
text: qsTr("Briefanrede")
}
TextField {
id: briefAnrede
property string name: "formofaddress"
Layout.columnSpan: 3
Layout.fillWidth: true
placeholderTextColor: "red"
onTextChanged: checkFields()
}
Item {
Layout.columnSpan: 4 Layout.columnSpan: 4
} Layout.fillHeight: true
function checkPersonalField()
{
if (radio.children[0].checked)
{
return (firstname.text.trim() && lastname.text.trim())
}
else
{
return (firstname.text.trim() && lastname.text.trim() && street.text.trim() && houseno.text.trim()
&& (postcode.editText.trim() || postcode.currentText.trim())
&& (city.editText.trim() || city.currentText.trim())
&& birthday.text.trim() && phonenumber.text.trim()
&& cellphone.text.trim() && email.text.trim() && jobdescription.text.trim()
&& contractstart.text.trim() && contractend.text.trim() && briefAnrede.text.trim())
}
}
function requiredField()
{
var pf = (radio.children[1].checked)?"Pflichtfeld":""
street.placeholderText = pf
phonenumber.placeholderText = pf
cellphone.placeholderText = pf
email.placeholderText = pf
jobdescription.placeholderText = pf
contractstart.placeholderText = pf
contractend.placeholderText = pf
briefAnrede.placeholderText = pf
houseno.placeholderText = pf
} }
} }

View File

@@ -0,0 +1,143 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
ColumnLayout {
anchors.fill: parent
spacing: Dimensions.l
RowLayout {
Layout.fillWidth: true
spacing: Dimensions.l
SearchBar {
onSubmitted: (query) => {
applicantModel.searchQuery = query
}
}
QuickFilter {
model: ListModel {
ListElement {
name: "Alle"
selected: true
text: qsTr("Alle")
}
ListElement {
name: "Bewerber"
selected: false
text: qsTr("Bewerber")
}
ListElement {
name: "Mitarbeiter"
selected: false
text: qsTr("Kunde")
}
ListElement {
name: "Erledigt"
selected: false
text: qsTr("Erledigt")
}
}
onSelectedChanged: name => {
employee_model.viewCriterion(name);
}
}
Button {
Layout.alignment: Qt.AlignRight
flat: true
icon.source: "qrc:/images/PlusCircle.svg"
text: qsTr("Bewerber Hinzufügen")
onClicked: contentStack.push("AddApplicant.qml")
}
Button {
Layout.alignment: Qt.AlignRight
flat: true
icon.source: "qrc:/images/PlusCircle.svg"
text: qsTr("Mitarbeiter Hinzufügen")
onClicked: contentStack.push("AddEmployee.qml")
}
}
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 2
HorizontalHeaderView {
movableColumns: true
syncView: employeesTable
delegate: Rectangle {
Layout.fillWidth: true
color: Colors.primary
implicitHeight: 33
implicitWidth: 1
Text {
color: Colors.primaryContrast
elide: Text.ElideRight
font: Typography.smallBold
height: parent.height
horizontalAlignment: Text.AlignLeft
padding: Dimensions.s
text: model.display
verticalAlignment: Text.AlignVCenter
width: parent.width
}
}
}
TableView {
id: employeesTable
Layout.fillHeight: true
Layout.fillWidth: true
columnSpacing: 2
model: applicantModel
resizableColumns: true
rowSpacing: 2
selectionBehavior: TableView.SelectRows
z: 1
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
delegate: Rectangle {
required property bool selected
color: selected ? Colors.primaryHighlight : Colors.transparent
implicitHeight: 33
implicitWidth: employeesTable.width / employeesTable.columns
Text {
color: Colors.foreground
elide: Text.ElideRight
font: Typography.small
height: parent.height
padding: Dimensions.s
text: (model.display === null || model.display === undefined) ? "" : model.display
verticalAlignment: Text.AlignVCenter
width: parent.width
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
contentStack.push("EmployeeDetails.qml", { row });
}
onEntered: {
employeesTable.selectionModel.select(employeesTable.model.index(row, 0), ItemSelectionModel.SelectCurrent | ItemSelectionModel.Rows);
}
}
}
selectionModel: ItemSelectionModel {
model: employeesTable.model
}
}
}
}

1
Gui/Employees/qmldir Normal file
View File

@@ -0,0 +1 @@
module Employees

View File

@@ -1,187 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
ColumnLayout
{
function viewEmployees(criterion)
{
employee_model.viewCriterion(criterion)
// employee_model.viewCriterion(criterion, showProcessed.checked, showFired.checked, showEveryone.checked)
}
anchors.fill: parent
spacing: Dimensions.l
Component.onCompleted: employeesStack.pop()
// property var availableFilters: ["Name", "Adresse", "PLZ", "Ort", "Status"]
RowLayout
{
Layout.fillWidth: true
spacing: Dimensions.l
SearchBar
{
id:searchBar
}
QuickFilter
{
onSelectedChanged: (name) =>
{
business_model.viewCriterion(name)
}
model: ListModel
{
ListElement
{
name: "Alle"
text: qsTr("Alle")
selected: true
}
ListElement
{
name: "Bewerber"
text: qsTr("Bewerber")
selected: false
}
ListElement
{
name: "Mitarbeiter"
text: qsTr("Kunde")
selected: false
}
ListElement
{
name: "Erledigt"
text: qsTr("Erledigt")
selected: false
}
}
}
Button
{
id: addEmployeeBtn
text: qsTr("Mitarbeiter Hinzufügen")
icon.source: "qrc:/images/PlusCircle.svg"
Layout.alignment: Qt.AlignRight
flat: true
onClicked: appLoader.source = "AddApplicant.qml"
}
}
HorizontalHeaderView
{
id: employeeTableHeader
Layout.fillWidth: true
syncView: appliEmpTable
implicitHeight: 40
movableColumns: true //@disable-check M16
delegate: Rectangle
{
color: addEmployeeBtn.palette.alternateBase
border.color: 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: palette.text
}
}
}
TableView
{
id: appliEmpTable
//Layout.fillHeight: true
//height: tableParent.height - (viewCriterion.height + employeeTableHeader.height)
Layout.fillWidth: true
Layout.fillHeight: true
columnSpacing: 1
rowSpacing: 2
alternatingRows: true
resizableColumns: true
model: employee_model
selectionBehavior: TableView.SelectRows
z: 1
ScrollBar.vertical: ScrollBar
{
policy: appliEmpTable.contentHeight > appliEmpTable.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
}
selectionModel: ItemSelectionModel
{
id: selModel
model: appliEmpTable.model
}
delegate:Rectangle
{
required property bool selected
required property bool current
implicitWidth: appliEmpTable.width / appliEmpTable.columns
implicitHeight: 25
color: selected
? addEmployeeBtn.palette.highlight //palette.highlight
: (appliEmpTable.alternatingRows && row % 2 !== 0
? addEmployeeBtn.palette.base // palette.base
: addEmployeeBtn.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: palette.text
}
MouseArea
{
id: mouseArea
property bool hovered:false
anchors.fill: parent
hoverEnabled: true
onDoubleClicked:
{
employeesStack.push("EmployeeDetails.qml", {selectedEmployee: row});
}
onEntered:
{
appliEmpTable.selectionModel.select(appliEmpTable.model.index(row, 0), ItemSelectionModel.SelectCurrent | ItemSelectionModel.Rows)
}
}
}
}
Item
{
Layout.fillWidth: true
}
}
// function viewEmployees(criterion)
// {
// employee_model.viewCriterion(criterion, showProcessed.checked, showFired.checked, showEveryone.checked)
// }
// Component.onCompleted: employeesStack.pop()

View File

@@ -77,7 +77,7 @@ Item
title: qsTr("Wiederherstellen") title: qsTr("Wiederherstellen")
buttons: MessageDialog.Yes | MessageDialog.No buttons: MessageDialog.Yes | MessageDialog.No
onAccepted: recoveryPaswordDialog.open() onAccepted: recoveryPaswordDialog.open()
onRejected: gotoLogin() onRejected: contentStack.replace("LoginSreen.qml")
} }
MessageDialog MessageDialog
@@ -147,7 +147,7 @@ Item
if (!adminAvailable) config.saveRecoveryKey(saveRecoveryDialog.currentFile, recpass) if (!adminAvailable) config.saveRecoveryKey(saveRecoveryDialog.currentFile, recpass)
else config.getRecoveryKey(saveRecoveryDialog.currentFile, recpass) else config.getRecoveryKey(saveRecoveryDialog.currentFile, recpass)
gotoLogin() contentStack.replace("LoginSreen.qml")
} }
onRejected: onRejected:

View File

@@ -4,20 +4,19 @@ import QtQuick.Controls
import QtQuick.Dialogs import QtQuick.Dialogs
import QtQuick.Layouts import QtQuick.Layouts
Item { Item {
property string recpass: "" property string recpass: ""
function dbConnectionFailed(msg) { function dbConnectionFailed(msg) {
oschkar.notificationBox.informativeText = msg; notifications.notificationDialog.informativeText = msg;
oschkar.notificationBox.text = "Verbindung zum Datenbankserver verloren"; notifications.notificationDialog.text = "Verbindung zum Datenbankserver verloren";
oschkar.notificationBox.open(); notifications.notificationDialog.open();
} }
function getEncryptionKey() { function getEncryptionKey() {
recoveryPaswordDialog.open(); recoveryPaswordDialog.open();
} }
function loggedin() { function loggedin() {
appLoader.source = "Dashboard.qml"; contentStack.replace("Dashboard.qml");
} }
anchors.fill: parent anchors.fill: parent
@@ -79,8 +78,8 @@ Item {
} }
Button { Button {
Layout.topMargin: Dimensions.m Layout.topMargin: Dimensions.m
implicitWidth: parent.width
icon.source: "qrc:/images/ArrowRightEndOnRectangle.svg" icon.source: "qrc:/images/ArrowRightEndOnRectangle.svg"
implicitWidth: parent.width
text: qsTr("Login") text: qsTr("Login")
onClicked: { onClicked: {
@@ -132,8 +131,7 @@ Item {
onRejected: quit() onRejected: quit()
} }
Notifications { Notifications {
id: oschkar id: notifications
} }
} }
} }

View File

@@ -3,92 +3,79 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
ColumnLayout { ColumnLayout {
property bool onSubPage: false
height: parent.height height: parent.height
spacing: 0 spacing: 0
z: 3
Component.onCompleted: {
onSubPage = Qt.binding(() => contentStack.depth > 1);
}
anchors { anchors {
left: parent.left left: parent.left
top: parent.top top: parent.top
} }
ButtonGroup { ButtonGroup {
id: buttonBar id: mainNav
} }
BarButton { BarButton {
id: dashBoard ButtonGroup.group: mainNav
icon.source: "qrc:/images/Square3Stack3D-Outline.svg"
ButtonGroup.group: buttonBar target: "/Gui/Dashboard.qml"
icon.source: "qrc:/images/dash.svg"
text: qsTr("Dashboard") text: qsTr("Dashboard")
visible: !onSubPage
onClicked: {
appLoader.source = "Dashboard.qml";
}
} }
BarButton { BarButton {
id: kunden ButtonGroup.group: mainNav
icon.source: "qrc:/images/UserGroup-Outline.svg"
ButtonGroup.group: buttonBar target: "/Gui/CustomersTable.qml"
icon.source: "qrc:/images/customer.svg"
text: qsTr("Kunden") text: qsTr("Kunden")
visible: !onSubPage
onClicked: {
appLoader.source = "CustomerTable.qml";
}
} }
BarButton { BarButton {
id: objekt ButtonGroup.group: mainNav
icon.source: "qrc:/images/BuildingOffice2-Outline.svg"
ButtonGroup.group: buttonBar target: "/Gui/ObjectsTable.qml"
icon.source: "qrc:/images/object.svg"
text: qsTr("Objekt") text: qsTr("Objekt")
visible: !onSubPage
onClicked: {
appLoader.source = "ObjectTable.qml";
}
} }
BarButton { BarButton {
id: mitarbeiter ButtonGroup.group: mainNav
icon.source: "qrc:/images/Identification-Outline.svg"
ButtonGroup.group: buttonBar target: "/Gui/Employees/EmployeesTable.qml"
icon.source: "qrc:/images/employee.svg"
text: qsTr("Mitarbeiter") text: qsTr("Mitarbeiter")
visible: !onSubPage
onClicked: {
appLoader.source = "EmployeeTable.qml";
}
} }
BarButton { BarButton {
id: offers ButtonGroup.group: mainNav
icon.source: "qrc:/images/RectangleStack-Outline.svg"
ButtonGroup.group: buttonBar target: "/Gui/OffersTable.qml"
icon.source: "qrc:/images/offer.svg"
text: qsTr("Angebote") text: qsTr("Angebote")
visible: !onSubPage
onClicked: {
appLoader.source = "OfferTable.qml";
}
} }
BarButton { BarButton {
id: abrechnung ButtonGroup.group: mainNav
icon.source: "qrc:/images/Wallet-Outline.svg"
ButtonGroup.group: buttonBar
icon.source: "qrc:/images/invoice.svg"
text: qsTr("Abrechnung") text: qsTr("Abrechnung")
visible: !onSubPage
}
BarButton {
icon.source: "qrc:/images/ArrowLeftCircle-Outline.svg"
text: qsTr("Zurück")
visible: onSubPage
checkable: false
onClicked: contentStack.pop();
} }
Item { Item {
id: hspacer
Layout.fillHeight: true Layout.fillHeight: true
} }
BarButton { BarButton {
id: atajos
Layout.bottomMargin: Dimensions.s
checkable: false checkable: false
flat: true
icon.source: "qrc:/images/Bars3.svg" icon.source: "qrc:/images/Bars3.svg"
implicitHeight: 90
implicitWidth: 90
onClicked: mainMenu.open() onClicked: mainMenu.open()
@@ -101,7 +88,8 @@ ColumnLayout {
onTriggered: { onTriggered: {
// TODO: Check if logged-in user is admin first!! // TODO: Check if logged-in user is admin first!!
appLoader.source = "PyqcrmConf.qml"; contentStack.push("PyqcrmConf.qml");
} }
} }
MenuSeparator { MenuSeparator {

View File

@@ -1,74 +1,27 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
Rectangle ColumnLayout {
{
anchors.fill: parent
color: "slateblue"
Rectangle
{
id: info
anchors.horizontalCenter: parent.horizontalCenter
color: "slateblue"
implicitHeight: 55
implicitWidth: parent.width / 4
y: parent.height / 4
Text
{
anchors.centerIn: parent anchors.centerIn: parent
spacing: Dimensions.s
height: implicitHeight
H1 {
Layout.alignment: Qt.AlignCenter
text: qsTr("Keine Verbindung zur Datenbank!") text: qsTr("Keine Verbindung zur Datenbank!")
color: "moccasin" color: Colors.foreground
font.bold: true
font.pixelSize: 45
} }
H2 {
Layout.alignment: Qt.AlignCenter
text: qsTr("Programm kann nicht starten…")
color: Colors.foreground
} }
Button {
Rectangle Layout.topMargin: Dimensions.l
{ Layout.alignment: Qt.AlignCenter
id: nostart
anchors.top: info.bottom
color: "slateblue"
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: 55
implicitWidth: parent.width / 4
Text
{
text: qsTr("Programm kann nicht starten..")
color: "moccasin"
anchors.centerIn: parent
font.bold: true
font.pixelSize: 45
}
}
Rectangle
{
anchors.top: nostart.bottom
anchors.topMargin: 25
anchors.horizontalCenter: parent.horizontalCenter
color: "slateblue"
implicitHeight: 55
implicitWidth: parent.width / 4
Button
{
width: parent.width
height: 75
Text
{
text: qsTr("Beenden") text: qsTr("Beenden")
color: "moccasin"
anchors.centerIn: parent
font.bold: true
font.pixelSize: 45
}
anchors.centerIn: parent
background: Rectangle
{
color: "dodgerblue"
radius: 50
}
onClicked: Qt.quit() onClicked: Qt.quit()
} }
} }
}

View File

@@ -3,8 +3,6 @@ import QtQuick.Dialogs
Item Item
{ {
id: oschkar
property alias notificationBox: notificationDialog
MessageDialog MessageDialog
{ {
id: notificationDialog id: notificationDialog

View File

@@ -16,7 +16,7 @@ Item
Button Button
{ {
text: qsTr("Zurück zu den Objekten") text: qsTr("Zurück zu den Objekten")
onClicked: objectsStack.pop() onClicked: contentStack.pop()
} }
} }

View File

@@ -1,22 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
Item {
property var availableFilters: [""]
anchors.fill: parent
StackView
{
id: objectsStack
anchors.fill: parent
initialItem: "ObjectsTable.qml"
}
}

View File

@@ -5,6 +5,8 @@ import Qt.labs.qmlmodels
ColumnLayout ColumnLayout
{ {
property var availableFilters: [""]
anchors.fill: parent anchors.fill: parent
spacing: Dimensions.l spacing: Dimensions.l
@@ -21,7 +23,7 @@ ColumnLayout
Component.onCompleted: Component.onCompleted:
{ {
contact_model.objectContactAdded.connect(onObjectContactAdded) contact_model.objectContactAdded.connect(onObjectContactAdded)
objectsStack.pop() contentStack.pop()
} }
RowLayout RowLayout
{ {
@@ -70,7 +72,7 @@ ColumnLayout
icon.source: "qrc:/images/PlusCircle.svg" icon.source: "qrc:/images/PlusCircle.svg"
text: qsTr("Objekt Hinzufügen") text: qsTr("Objekt Hinzufügen")
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
onClicked: appLoader.source = "AddObject.qml" onClicked: contentStack.push("AddObject.qml")
} }
} }
@@ -172,7 +174,7 @@ ColumnLayout
hoverEnabled: true hoverEnabled: true
onDoubleClicked: onDoubleClicked:
{ {
objectsStack.push("ObjectDetails.qml", {selectedObject: row}); contentStack.push("ObjectDetails.qml", {selectedObject: row});
} }
onEntered: onEntered:
{ {

View File

@@ -1,17 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
Item
{
anchors.fill: parent
StackView
{
id: offersStack
anchors.fill: parent
initialItem: "OffersTable.qml"
}
}

View File

@@ -3,75 +3,64 @@ import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Qt.labs.qmlmodels import Qt.labs.qmlmodels
ColumnLayout ColumnLayout {
{ function viewOffers(criterion) {
anchors.fill: parent
spacing: Dimensions.l
function viewOffers(criterion)
{
//offer_model.viewCriterion(criterion) //offer_model.viewCriterion(criterion)
} }
RowLayout anchors.fill: parent
{ spacing: Dimensions.l
RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Dimensions.l spacing: Dimensions.l
SearchBar
{ SearchBar {
id: searchBar id: searchBar
}
QuickFilter
{
onSelectedChanged: (name) =>
{
business_model.viewCriterion(name)
}
model: ListModel
{
ListElement
{
}
QuickFilter {
model: ListModel {
ListElement {
name: "Alle" name: "Alle"
text: qsTr("Alle")
selected: true selected: true
text: qsTr("Alle")
} }
ListElement ListElement {
{
name: "Offen" name: "Offen"
selected: false
text: qsTr("Offen") text: qsTr("Offen")
selected: false
} }
ListElement ListElement {
{
name: "Abgeschlossen" name: "Abgeschlossen"
text: qsTr("Abgeschlossen")
selected: false selected: false
text: qsTr("Abgeschlossen")
}
ListElement {
name: "Erledigt"
selected: false
text: qsTr("Erledigt")
}
} }
ListElement onSelectedChanged: name => {
{ business_model.viewCriterion(name);
name: "Erledigt"
text: qsTr("Erledigt")
selected: false
} }
} }
} Button {
Button
{
id: addOfferBtn id: addOfferBtn
text: qsTr("Angebote Hinzufügen")
icon.source: "qrc:/images/PlusCircle.svg"
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
flat: true flat: true
onClicked: appLoader.source = "AddOffer.qml" icon.source: "qrc:/images/PlusCircle.svg"
text: qsTr("Angebote Hinzufügen")
onClicked: contentStack.push("AddOffer.qml")
} }
} }
Item { Item {
id: spacer id: spacer
Layout.fillHeight: true Layout.fillHeight: true
} }
} }

View File

@@ -101,7 +101,7 @@ Item
Button Button
{ {
text: qsTr("Ablehnen") text: qsTr("Ablehnen")
onClicked: appLoader.source = "Dashboard.qml" onClicked: contentStack.pop()
} }
Button Button

View File

@@ -97,7 +97,7 @@ Item
title: qsTr("Wiederherstellen") title: qsTr("Wiederherstellen")
buttons: MessageDialog.Yes | MessageDialog.No buttons: MessageDialog.Yes | MessageDialog.No
onAccepted: recoveryPasswordDialog.open() onAccepted: recoveryPasswordDialog.open()
onRejected: gotoLogin() onRejected: contentStack.replace("LoginSreen.qml")
} }
MessageDialog MessageDialog

View File

@@ -10,10 +10,6 @@ ApplicationWindow {
property string confile: "" property string confile: ""
property alias settingsFileDialog: settingsFiledialog property alias settingsFileDialog: settingsFiledialog
function goToLogin() {
appLoader.source = "LoginScreen.qml";
navigation.visible = true;
}
function showWindow(why) { function showWindow(why) {
if (why === 3) { if (why === 3) {
systray.setVisible(false); systray.setVisible(false);
@@ -21,25 +17,25 @@ ApplicationWindow {
} }
} }
color: Colors.mantle
font: Typography.body font: Typography.body
height: Screen.height * .85 height: Screen.height * .85
palette.window: Colors.mantle
palette.placeholderText: Colors.interactive
palette.text: Colors.foreground palette.text: Colors.foreground
title: "TERO Personal" title: "TERO Personal"
visible: true visible: true
width: Screen.width * .75 width: Screen.width * .75
Component.onCompleted: { Component.onCompleted: {
config.configurationReady.connect(goToLogin);
systray.activated.connect(showWindow); systray.activated.connect(showWindow);
if (bad_config) { if (bad_config) {
importDialog.open(); importDialog.open();
} else { } else {
if (db_con) if (db_con)
appLoader.source = "LoginScreen.qml"; contentStack.replace("LoginScreen.qml")
else else
appLoader.source = "NoDbConnection.qml"; contentStack.replace("NoDbConnection.qml");
} }
} }
onClosing: close => { onClosing: close => {
@@ -63,7 +59,7 @@ ApplicationWindow {
Navigation { Navigation {
id: navigation id: navigation
visible: bad_config || !db_con ? false : true visible: !(bad_config || !db_con)
} }
PrinterDialog { PrinterDialog {
id: printerDialog id: printerDialog
@@ -82,17 +78,15 @@ ApplicationWindow {
id: contentBackground id: contentBackground
anchors { anchors {
bottom: parent.bottom bottom: parent.bottom
left: navigation.right left: navigation.visible ? navigation.right : parent.left
right: parent.right right: parent.right
top: parent.top top: parent.top
} }
color: Colors.background color: Colors.background
} }
Loader { StackView {
id: appLoader id: contentStack
property alias window: appWindow
anchors { anchors {
fill: contentBackground fill: contentBackground
@@ -109,7 +103,7 @@ ApplicationWindow {
title: qsTr("Einstellungen importieren") title: qsTr("Einstellungen importieren")
onAccepted: settingsFiledialog.open() onAccepted: settingsFiledialog.open()
onRejected: appLoader.source = "Firststart.qml" onRejected: contentStack.replace("Firststart.qml")
} }
FileDialog { FileDialog {
id: settingsFiledialog id: settingsFiledialog

View File

@@ -1,2 +1,2 @@
module gui module gui
TopBar 1.0 Navigation.qml Navigation 1.0 Navigation.qml

View File

@@ -6,6 +6,8 @@ import QtQuick.Templates as T
T.ToolButton { T.ToolButton {
id: control id: control
property string target
checkable: true checkable: true
icon.color: Colors.foreground icon.color: Colors.foreground
icon.height: 36 icon.height: 36
@@ -18,16 +20,16 @@ T.ToolButton {
readonly property color color: control.checked ? Colors.primaryShade : control.hovered ? Colors.primary : Colors.foreground readonly property color color: control.checked ? Colors.primaryShade : control.hovered ? Colors.primary : Colors.foreground
IconLabel { IconLabel {
icon.color: parent.color
icon.source: control.icon.source
icon.height: control.icon.height
icon.width: control.icon.width
color: parent.color color: parent.color
icon.color: parent.color
icon.height: control.icon.height
icon.source: control.icon.source
icon.width: control.icon.width
x: parent.width * .5 - width * .5 x: parent.width * .5 - width * .5
} }
Label { Label {
font: Typography.small
color: parent.color color: parent.color
font: Typography.smaller
text: control.text text: control.text
x: parent.width * .5 - width * .5 x: parent.width * .5 - width * .5
} }
@@ -49,4 +51,11 @@ T.ToolButton {
onPressed: mouse => mouse.accepted = false onPressed: mouse => mouse.accepted = false
} }
onClicked: {
if(!target) {
return
}
contentStack.replace(target)
}
} }

View File

@@ -25,6 +25,7 @@ T.Button {
*/ */
property bool isFieldButton: false property bool isFieldButton: false
height: isFieldButton ? parent.height : null height: isFieldButton ? parent.height : null
icon.color: Colors.primaryContrast icon.color: Colors.primaryContrast
icon.height: 21 icon.height: 21
@@ -43,12 +44,12 @@ T.Button {
border.color: Colors.interactive border.color: Colors.interactive
border.width: isFieldButton ? 1 : 0 border.width: isFieldButton ? 1 : 0
bottomLeftRadius: topLeftRadius bottomLeftRadius: topLeftRadius
color: !control.hovered ? Colors.primary : Colors.primaryLighter color: !control.enabled ? Colors.disabled : !control.hovered ? Colors.primary : Colors.primaryLighter
radius: Dimensions.radius radius: Dimensions.radius
topLeftRadius: isFieldButton ? 0 : radius topLeftRadius: isFieldButton ? 0 : radius
} }
contentItem: I.IconLabel { contentItem: I.IconLabel {
color: Colors.primaryContrast color: !control.enabled ? Colors.disabledForeground : Colors.primaryContrast
display: control.display display: control.display
font: control.font font: control.font
icon: control.icon icon: control.icon

View File

@@ -12,11 +12,14 @@ QtObject {
readonly property color primaryContrast: "#fdfdfd" readonly property color primaryContrast: "#fdfdfd"
readonly property color primaryLighter: Qt.lighter(primary, 1.5) readonly property color primaryLighter: Qt.lighter(primary, 1.5)
readonly property color primaryShade: theme === dark ? primaryLighter : Qt.darker(primary, 1.5) readonly property color primaryShade: theme === dark ? primaryLighter : Qt.darker(primary, 1.5)
readonly property color primaryHighlight: theme === dark ? Qt.darker(primary, 2- Colors.highlightOpacity) : Qt.lighter(primary, 2- Colors.highlightOpacity)
readonly property color foreground: theme === dark ? "#fdfdfd" : "#110b0c" readonly property color foreground: theme === dark ? "#fdfdfd" : "#110b0c"
readonly property color background: theme === dark ? "#303136" : "#eff1f5" readonly property color background: theme === dark ? "#303136" : "#eff1f5"
readonly property color mantle: theme === dark ? "#1E1E23" : "#e7e9ef" readonly property color mantle: theme === dark ? "#1E1E23" : "#e7e9ef"
readonly property color interactive: theme === dark ? "#878b97" : "#d9d9da" readonly property color interactive: theme === dark ? "#878b97" : "#d9d9da"
readonly property color error: theme === dark ? "#ff2264" : "#ff004b" readonly property color error: theme === dark ? "#ff2264" : "#ff004b"
readonly property color disabled: theme === dark ? Qt.darker(interactive, 1.9) : Qt.darker(interactive, 1.3)
readonly property color disabledForeground: theme === dark ? Qt.darker(foreground, 1.4) : Qt.lighter(foreground, 1.9)
readonly property color transparent: "transparent" readonly property color transparent: "transparent"
readonly property double highlightOpacity: .3 readonly property double highlightOpacity: .3

View File

@@ -33,36 +33,16 @@ T.ComboBox {
border.color: Colors.interactive border.color: Colors.interactive
border.width: 1 border.width: 1
color: Colors.mantle color: Colors.mantle
// height: parent.height
radius: Dimensions.radius radius: Dimensions.radius
width: parent.width width: parent.width
} }
// delegate: MenuItem {
// id: menuItem
// required property int index
// required property var model
// highlighted: control.highlightedIndex === index
// hoverEnabled: control.hoverEnabled
// text: model[control.textRole]
// width: control.width
// background: Rectangle {
// color: menuItem.down || menuItem.highlighted ? Colors.primary : "transparent"
// height: menuItem.height
// width: menuItem.width
// }
// }
delegate: ItemDelegate { delegate: ItemDelegate {
required property var model required property var model
required property int index required property int index
width: ListView.view.width width: ListView.view.width
text: model[control.textRole] text: model[control.textRole]
// palette.text: control.palette.text
// palette.highlightedText: control.palette.highlightedText
// font.weight: control.currentIndex === index ? Font.DemiBold : Font.Normal
highlighted: control.highlightedIndex === index highlighted: control.highlightedIndex === index
hoverEnabled: control.hoverEnabled hoverEnabled: control.hoverEnabled
} }
@@ -125,8 +105,4 @@ T.ComboBox {
} }
} }
} }
Component.onCompleted:
{
console.log(control.implicitContentHeight)
}
} }

View File

@@ -0,0 +1,5 @@
import QtQuick
RegularExpressionValidator {
regularExpression: /([\+!#$%&\*\\/\=?\^_`\.{|}\~\-\_0-9A-Za-z]{1,185})@([0-9A-Za-z\.\-\_]{1,64})\.([a-zA-z]{2,5})/
}

View File

@@ -5,11 +5,16 @@ import QtQuick.Layouts
ColumnLayout ColumnLayout
{ {
required property string label required property string label
/**
* Adds an asterisk after the label, informing the user that this field
* is mandatory.
*/
property bool mandatory: false
spacing: Dimensions.s spacing: Dimensions.s
Label Label
{ {
text: label text: label + (mandatory ? "*" : "")
font: Typography.body font: Typography.body
} }
} }

5
TeroStyle/H1.qml Normal file
View File

@@ -0,0 +1,5 @@
import QtQuick
Text {
font: Typography.h1
}

5
TeroStyle/H2.qml Normal file
View File

@@ -0,0 +1,5 @@
import QtQuick
Text {
font: Typography.h2
}

View File

@@ -0,0 +1,5 @@
import QtQuick
RegularExpressionValidator {
regularExpression: /^\S+.*\S+$/
}

View File

@@ -0,0 +1,5 @@
import QtQuick
RegularExpressionValidator {
regularExpression: /^$|([\+!#$%&\*\\/\=?\^_`\.{|}\~\-\_0-9A-Za-z]{1,185})@([0-9A-Za-z\.\-\_]{1,64})\.([a-zA-z]{2,5})/
}

View File

@@ -0,0 +1,5 @@
import QtQuick
RegularExpressionValidator {
regularExpression: /^$|([+0-9])([0-9\s]{1,17})/
}

View File

@@ -0,0 +1,5 @@
import QtQuick
RegularExpressionValidator {
regularExpression: /([+0-9])([0-9\s]{1,17})/
}

View File

@@ -0,0 +1,5 @@
import QtQuick
RegularExpressionValidator {
regularExpression: /([^$][0-9]{1,4})/
}

View File

@@ -4,11 +4,23 @@ import QtQuick.Layouts
TextField { TextField {
id: field
signal submitted(query: string)
Layout.preferredWidth: 300 Layout.preferredWidth: 300
placeholderText: qsTr("Suche") placeholderText: qsTr("Suche")
Keys.onReturnPressed: {
field.submitted(field.text);
}
Button { Button {
icon.source: "qrc:/images/MagnifyingGlass.svg" icon.source: "qrc:/images/MagnifyingGlass.svg"
isFieldButton: true isFieldButton: true
onClicked: {
field.submitted(field.text)
}
} }
} }

View File

@@ -4,6 +4,7 @@ import QtQuick.Templates as T
T.TextField T.TextField
{ {
id: control id: control
background: Rectangle background: Rectangle
{ {
id: background id: background

View File

@@ -11,7 +11,25 @@ Item {
letterSpacing: 0, letterSpacing: 0,
kerning: true kerning: true
}) })
readonly property font small: ({ readonly property font small: ({
family: body.family,
pointSize: 14,
weight: Font.Medium,
letterSpacing: body.letterSpacing,
kerning: body.kerning
})
readonly property font smallBold: ({
family: small.family,
pointSize: small.pointSize,
weight: Font.Bold,
letterSpacing: small.letterSpacing,
kerning: small.kerning
})
readonly property font smaller: ({
family: body.family, family: body.family,
pointSize: 11, pointSize: 11,
weight: Font.DemiBold, weight: Font.DemiBold,
@@ -25,10 +43,9 @@ Item {
letterSpacing: body.letterSpacing, letterSpacing: body.letterSpacing,
kerning: body.kerning kerning: body.kerning
}) })
readonly property font h2: readonly property font h2: ({
({
family: body.family, family: body.family,
pointSize: 20, pointSize: 28,
weight: body.weight, weight: body.weight,
letterSpacing: body.letterSpacing, letterSpacing: body.letterSpacing,
kerning: body.kerning, kerning: body.kerning,

View File

@@ -2,13 +2,19 @@ module TeroStyle
singleton Colors Colors.qml singleton Colors Colors.qml
singleton Dimensions Dimensions.qml singleton Dimensions Dimensions.qml
singleton Typography Typography.qml singleton Typography Typography.qml
BarButton BarButton.qml
Button Button.qml Button Button.qml
ComboBox ComboBox.qml ComboBox ComboBox.qml
EmailAddressValidator EmailAddressValidator.qml
Field Field.qml Field Field.qml
TextField TextField.qml H1 H1.qml
BarButton BarButton.qml H2 H2.qml
Label Label.qml Label Label.qml
NotEmptyValidator NotEmptyValidator.qml
OptionalEmailAddressValidator OptionalEmailAddressValidator.qml
PhoneNumberValidator PhoneNumberValidator.qml
OptionalPhoneNumberValidator OptionalPhoneNumberValidator.qml
PostcodeValidator PostcodeValidator.qml
QuickFilter QuickFilter.qml QuickFilter QuickFilter.qml
SearchBar SearchBar.qml
TextField TextField.qml

View File

@@ -1,4 +0,0 @@
# pyqcrm
CRM - Tero
Python + QML

14
docker-compose.yml Normal file
View File

@@ -0,0 +1,14 @@
services:
mariadb:
image: mariadb:latest
volumes:
- mariadb:/var/lib/mysql
ports:
- 127.0.0.1:8000:3306
environment:
MARIADB_ROOT_PASSWORD: pyqcrm
MARIADB_USER: pyqcrm
MARIADB_PASSWORD: pyqcrm
MARIADB_DATABASE: pyqcrm
volumes:
mariadb:

View File

@@ -0,0 +1,3 @@
<svg fill="none" stroke-width="2" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-slot="icon">
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 9-3 3m0 0 3 3m-3-3h7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 288 B

View File

Before

Width:  |  Height:  |  Size: 521 B

After

Width:  |  Height:  |  Size: 521 B

3
images/CheckCircle.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-slot="icon">
<path clip-rule="evenodd" fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 409 B

View File

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 539 B

View File

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

After

Width:  |  Height:  |  Size: 824 B

View File

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 501 B

3
images/Phone.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-slot="icon">
<path clip-rule="evenodd" fill-rule="evenodd" d="M1.5 4.5a3 3 0 0 1 3-3h1.372c.86 0 1.61.586 1.819 1.42l1.105 4.423a1.875 1.875 0 0 1-.694 1.955l-1.293.97c-.135.101-.164.249-.126.352a11.285 11.285 0 0 0 6.697 6.697c.103.038.25.009.352-.126l.97-1.293a1.875 1.875 0 0 1 1.955-.694l4.423 1.105c.834.209 1.42.959 1.42 1.82V19.5a3 3 0 0 1-3 3h-2.25C8.552 22.5 1.5 15.448 1.5 6.75V4.5Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 514 B

View File

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 589 B

View File

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 418 B

3
images/UserCircle.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-slot="icon">
<path clip-rule="evenodd" fill-rule="evenodd" d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 539 B

View File

Before

Width:  |  Height:  |  Size: 733 B

After

Width:  |  Height:  |  Size: 733 B

View File

Before

Width:  |  Height:  |  Size: 490 B

After

Width:  |  Height:  |  Size: 490 B

19
lib/Config.py Normal file
View File

@@ -0,0 +1,19 @@
from typing import TypedDict
class EncryptionConfig(TypedDict):
ENCRYPTION_KEY_VALID: str
ENCRYPTION_KEY: str
class DatabaseConfig(TypedDict):
DB_HOST: str
DB_USER: str
DB_PASS: str
DB_NAME: str
DB_PORT: str
class Config(TypedDict):
database: DatabaseConfig
pyqcrm: EncryptionConfig

View File

@@ -1,27 +1,28 @@
# This Python file uses the following encoding: utf-8 # This Python file uses the following encoding: utf-8
import toml
from platformdirs import user_config_dir
from pathlib import Path
from PySide6.QtCore import QObject, Slot, Signal
from .Vermasseln import Vermasseln
import shutil
from urllib.parse import urlparse
from .DB.DbManager import DbManager
import os import os
from Crypto.Random import get_random_bytes
from base64 import b64encode from base64 import b64encode
from pathlib import Path
from typing import Optional
from urllib.parse import urlparse
import toml
from Crypto.Random import get_random_bytes
from PySide6.QtCore import QObject, Slot, Signal
from peewee import OperationalError
from platformdirs import user_config_dir
from .Config import Config, DatabaseConfig
from .DB.UserManager import UserManager from .DB.UserManager import UserManager
from .PyqcrmFlags import PyqcrmFlags from .PyqcrmFlags import PyqcrmFlags
from .Vermasseln import Vermasseln
from .domain.BaseModel import init_database_from_config
class ConfigLoader(QObject): class ConfigLoader(QObject):
__config = None __config: Optional[Config] = None
__version = "0.1-alpha" __version = "0.1-alpha"
__check_enc_key = True __check_enc_key = True
dbConnectionError = Signal(str, bool) dbConnectionError = Signal(str, bool)
adminUserError = Signal(str, bool) adminUserError = Signal(str, bool)
adminNotAsvailable = Signal() adminNotAsvailable = Signal()
@@ -31,7 +32,6 @@ class ConfigLoader(QObject):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# print(f"In {__file__} file, __init__()")
self.config_dir = user_config_dir() + '/pyqcrm' self.config_dir = user_config_dir() + '/pyqcrm'
config_dir = Path(self.config_dir) config_dir = Path(self.config_dir)
if config_dir.exists(): if config_dir.exists():
@@ -41,13 +41,11 @@ class ConfigLoader(QObject):
else: else:
config_dir.mkdir(0o750, True, True) config_dir.mkdir(0o750, True, True)
@Slot(dict, result=bool) @Slot(dict, result=bool)
def setConfig(self, app_config): def setConfig(self, app_config: Config):
# print(f"In {__file__} file, setConfig()")
if not self.__config: if not self.__config:
base_conf = self.__initializeConfig() base_conf = self.__initializeConfig()
conf = self.__checkDbConnection(app_config) conf = self._is_db_connectable(app_config['database'])
app_config = toml.dumps(app_config) app_config = toml.dumps(app_config)
if conf: if conf:
app_config = base_conf + app_config app_config = base_conf + app_config
@@ -58,24 +56,21 @@ class ConfigLoader(QObject):
self.configurationReady.emit() self.configurationReady.emit()
def __initializeConfig(self): def __initializeConfig(self):
# 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"
conf = conf + f"ENCRYPTION_KEY_VALID = \"No\"\n" conf = conf + f"ENCRYPTION_KEY_VALID = \"No\"\n"
conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n" conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n"
return conf return conf
def __checkDbConnection(self, db_config): def _is_db_connectable(self, config: DatabaseConfig):
# print(f"In {__file__} file, __checkDbConnection()") try:
con = DbManager(db_config['database']).getConnection() init_database_from_config(config)
if con:
self.dbConnectionError.emit("Connection OK", True) self.dbConnectionError.emit("Connection OK", True)
return True return True
else: except OperationalError as e:
self.dbConnectionError.emit("Connection fehlgeschlagen", False) self.dbConnectionError.emit("Connection fehlgeschlagen", False)
return False return False
def __saveConfig(self): def __saveConfig(self):
# print(f"In {__file__} file, saveConfig()") # print(f"In {__file__} file, saveConfig()")
try: try:
@@ -86,7 +81,6 @@ class ConfigLoader(QObject):
except FileNotFoundError: except FileNotFoundError:
print("Konnte die Konfiguration nicht speichern.") print("Konnte die Konfiguration nicht speichern.")
def __checkAdminUser(self): def __checkAdminUser(self):
# print(f"In {__file__} file, __checkAdminUser()") # print(f"In {__file__} file, __checkAdminUser()")
result = UserManager().checkAdmin() result = UserManager().checkAdmin()
@@ -111,7 +105,6 @@ class ConfigLoader(QObject):
self.backupEncryptionKey.emit() self.backupEncryptionKey.emit()
return admin return admin
@Slot(str, str) @Slot(str, str)
def __saveData(self, recovery_file, recovery_password, data): def __saveData(self, recovery_file, recovery_password, data):
# print(f"In {__file__} file, __saveData()") # print(f"In {__file__} file, __saveData()")
@@ -165,7 +158,6 @@ class ConfigLoader(QObject):
else: else:
return None return None
def __invalidateEncryptionKey(self): def __invalidateEncryptionKey(self):
# 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'
@@ -200,13 +192,7 @@ class ConfigLoader(QObject):
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
def __configLoad(self): def __configLoad(self):
# 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:
config = f.read() config = f.read()
@@ -216,17 +202,11 @@ class ConfigLoader(QObject):
print("Konnte die Konfiguration nicht laden.") print("Konnte die Konfiguration nicht laden.")
except TypeError: except TypeError:
print(f"Invalid Configuration: {__file__}") print(f"Invalid Configuration: {__file__}")
except Exception as e:
print(str(e))
def get_config(self) -> Optional[Config]:
def getConfig(self):
# print(f"In {__file__} file, getConfig()")
# print(self.__config)
return self.__config return self.__config
def __setRecoveryPassword(self, key, salt=None): def __setRecoveryPassword(self, key, salt=None):
# print(f"In {__file__} file, __setRecoveryPassword()")
key = Vermasseln.userPasswordHash(key, salt) key = Vermasseln.userPasswordHash(key, salt)
return key.split("$") return key.split("$")
@@ -239,10 +219,9 @@ class ConfigLoader(QObject):
@Slot(str, str) @Slot(str, str)
def backupConfig(self, filename, password): def backupConfig(self, filename, password):
conf_file = toml.dumps(self.getConfig()) conf_file = toml.dumps(self.get_config())
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):
self.__config.update(db) self.__config.update(db)
@@ -251,7 +230,7 @@ class ConfigLoader(QObject):
@Slot(result=dict) @Slot(result=dict)
def getDbConf(self): def getDbConf(self):
try: try:
return self.__config['database'] return self.__config['database'] if self.__config is not None else None
except KeyError as ex: except KeyError as ex:
print(f"Missing database configuration: {ex}") print(f"Missing database configuration: {ex}")
return None return None
@@ -282,9 +261,7 @@ class ConfigLoader(QObject):
print(f"Missing configuration: {ex}") print(f"Missing configuration: {ex}")
return False return False
@Slot(str, str) @Slot(str, str)
def backupEncryptkey(self, filename, password): def backupEncryptkey(self, filename, password):
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,20 +1,19 @@
from .DbManager import DbManager
import mariadb
import json import json
import mariadb
from lib.domain.BaseModel import database
class AddressDAO: class AddressDAO:
__cur = None __cur = None
def __init__(self): def __init__(self):
#print(f"*** File: {__file__}, init()") self.__con = database.connection()
self.__con = DbManager().getConnection()
if self.__con: if self.__con:
self.__cur = self.__con.cursor() self.__cur = self.__con.cursor()
def __importPlz(self): def __importPlz(self):
with open("pfad zur datei", "r") as plz: with open("pfad zur datei", "r") as plz:
postcodes = json.load(plz) postcodes = json.load(plz)
@@ -55,7 +54,9 @@ class AddressDAO:
print(i[4], i[3], i[2], i[8], i[7]) print(i[4], i[3], i[2], i[8], i[7])
self.__cur.execute("INSERT INTO country (country, countryshort, nationality, iso2, iso3) VALUES (%s, %s, %s, %s, %s)", (i[4], i[3], i[2], i[8], i[7])) self.__cur.execute(
"INSERT INTO country (country, countryshort, nationality, iso2, iso3) VALUES (%s, %s, %s, %s, %s)",
(i[4], i[3], i[2], i[8], i[7]))
old = i[4] old = i[4]
except mariadb.OperationalError as e: except mariadb.OperationalError as e:
print(f"Database Error: {e}") print(f"Database Error: {e}")
@@ -63,9 +64,6 @@ class AddressDAO:
self.__con.commit() self.__con.commit()
print("FINISHED") # print("FINISHED") #
def getAddressData(self, all=True, zipcode=None): def getAddressData(self, all=True, zipcode=None):
try: try:
if self.__cur: if self.__cur:
@@ -76,5 +74,3 @@ class AddressDAO:
return None return None
except mariadb.Error as e: except mariadb.Error as e:
print(str(e)) print(str(e))

View File

@@ -2,6 +2,7 @@ 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):
def __init__(self): def __init__(self):
@@ -11,9 +12,9 @@ class AddressModel(QAbstractListModel):
def rowCount(self, parent=QModelIndex()): def rowCount(self, parent=QModelIndex()):
return len(self.__address_data) return len(self.__address_data)
def data(self, index, role = Qt.DisplayRole): def data(self, index, role=Qt.ItemDataRole.DisplayRole):
row = index.row() row = index.row()
if role == Qt.DisplayRole: if role == Qt.ItemDataRole.DisplayRole:
data = self.__address_data[row][2] data = self.__address_data[row][2]
return data return data
elif role == PyqcrmDataRoles.CITY_ROLE: elif role == PyqcrmDataRoles.CITY_ROLE:
@@ -23,20 +24,11 @@ class AddressModel(QAbstractListModel):
def roleNames(self): def roleNames(self):
return { return {
Qt.DisplayRole: b"display", Qt.ItemDataRole.DisplayRole: b"display",
PyqcrmDataRoles.CITY_ROLE: b"city", PyqcrmDataRoles.CITY_ROLE: b"city",
} }
def setData(self):
pass
@Slot(bool, str) @Slot(bool, str)
def getAddresses(self, all, zipcode): def getAddresses(self, all, zipcode):
data = AddressDAO().getAddressData(all, zipcode) data = AddressDAO().getAddressData(all, zipcode)
return data return data

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

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

View File

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

View File

@@ -1,7 +1,7 @@
from .DbManager import DbManager
import json import json
import mariadb import mariadb
from PySide6.QtCore import QObject, Signal from PySide6.QtCore import QObject, Signal
from lib.domain.BaseModel import database
class BusinessDAO(QObject): class BusinessDAO(QObject):
@@ -12,7 +12,7 @@ class BusinessDAO(QObject):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.__con = DbManager().getConnection() self.__con = database.connection()
if self.__con: if self.__con:
self.__cur = self.__con.cursor() self.__cur = self.__con.cursor()
@@ -47,10 +47,3 @@ class BusinessDAO(QObject):
except mariadb.Error as e: except mariadb.Error as e:
print(str(e)) print(str(e))

View File

@@ -73,7 +73,7 @@ class BusinessModel(QAbstractTableModel):
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().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__getData() self.__getData()

View File

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

View File

@@ -14,7 +14,7 @@ class ContactModel(QObject):
super().__init__() super().__init__()
# print(f"*** File: {__file__}, __init__()") # print(f"*** File: {__file__}, __init__()")
#self.logger = logging.getLogger() #self.logger = logging.getLogger()
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__contact_dao = ContactDAO() self.__contact_dao = ContactDAO()
self.__contact_dao.newObjectContactAdded.connect(self.objectContactAdded) self.__contact_dao.newObjectContactAdded.connect(self.objectContactAdded)

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal
from PySide6.QtQml import QJSValue
from .EmployeeDAO import EmployeeDAO from .EmployeeDAO import EmployeeDAO
# from ..PyqcrmFlags import PyqcrmFlags, PyqcrmAppliEmpyFlags
from ..ConfigLoader import ConfigLoader from ..ConfigLoader import ConfigLoader
import re import re
@@ -9,10 +10,8 @@ class EmployeeModel(QAbstractTableModel):
addedNewEmployee = Signal(bool) addedNewEmployee = Signal(bool)
__data = None __data = None
__employee_dao = None __employee_dao = None
__visible_index = None
__visible_columns = None __visible_columns = None
__col_name = "" __col_name = ""
__employee_dao = None
__col_skip = 2 __col_skip = 2
__everyone = True __everyone = True
@@ -20,16 +19,32 @@ class EmployeeModel(QAbstractTableModel):
super().__init__() super().__init__()
self.__employee_dao = EmployeeDAO() self.__employee_dao = EmployeeDAO()
self.__employee_dao.newEmployeeAdded.connect(self.__refreshView) self.__employee_dao.newEmployeeAdded.connect(self.__refreshView)
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__getData() self.__getData()
@Slot(dict, bool) @Slot(dict)
def addEmployee(self, new_employee, applicant = True): def addEmployee(self, new_employee):
if 'worklicense' in new_employee: if 'worklicense' in new_employee:
new_employee['worklicense'] = int(new_employee['worklicense']) new_employee['worklicense'] = int(new_employee['worklicense'])
new_employee['residencetype'] = int(new_employee['residencetype']) new_employee['residencetype'] = int(new_employee['residencetype'])
self.__employee_dao.addEmployee(new_employee, self.__key, applicant) self.__employee_dao.addApplicant(new_employee, self.__key, False)
@Slot(QJSValue)
def addApplicant(self, applicant: QJSValue):
self.__employee_dao.addApplicant({
"city": applicant.property("city").toString(),
"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):
@@ -39,7 +54,8 @@ class EmployeeModel(QAbstractTableModel):
def __getData(self, criterion="Alle", processed=False, fired=False, every_state=True): def __getData(self, criterion="Alle", processed=False, fired=False, every_state=True):
self.beginResetModel() self.beginResetModel()
rows, self.__visible_columns = self.__employee_dao.getEmployees(self.__key, criterion, processed, fired, every_state) rows, self.__visible_columns = self.__employee_dao.getEmployees(self.__key, criterion, processed, fired,
every_state)
self.__data = rows self.__data = rows
self.endResetModel() self.endResetModel()
@@ -49,43 +65,27 @@ class EmployeeModel(QAbstractTableModel):
def columnCount(self, parent=QModelIndex()): def columnCount(self, parent=QModelIndex()):
return len(self.__visible_columns) - self.__col_skip return len(self.__visible_columns) - self.__col_skip
@Slot(str, bool, bool, bool) @Slot(str)
def viewCriterion(self, criterion, processed, fired, every_state): def viewCriterion(self, criterion):
self.__everyone = True if criterion == 'Alle' else False self.__everyone = criterion == 'Alle'
if self.__everyone and criterion != "Alle":
self.__col_skip = 2 self.__col_skip = 2
else: self.__getData(criterion, criterion == 'Erledigt', False, criterion == 'Alle')
self.__col_skip = 2
self.__getData(criterion, processed, fired, every_state)
def data(self, index, role= Qt.DisplayRole): def data(self, index, role=Qt.ItemDataRole.DisplayRole):
if role == Qt.DisplayRole: if role == Qt.ItemDataRole.DisplayRole:
row = self.__data[index.row()] row = self.__data[index.row()]
applicant_col = index.column() + self.__col_skip applicant_col = index.column() + self.__col_skip
tr = row[applicant_col] #if type(row[index.column() + 2]) is str else str(row[index.column() + 2], "utf-8") tr = row[applicant_col]
if applicant_col == 2 and self.__everyone: if applicant_col == 2 and self.__everyone:
tr = 'Ja' if tr == 1 else 'Nein' tr = 'Ja' if tr == 1 else 'Nein'
else: else:
if tr: if tr:
tr = re.sub("Keine Angabe ", "", tr) tr = re.sub("Keine Angabe ", "", tr)
#print(f"Data: {tr}")
# return row[index.column() + 2]
return tr return tr
return None return None
def headerData(self, section, orientation, role = Qt.DisplayRole): def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole: if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
self.__col_name = self.__visible_columns[section + self.__col_skip] self.__col_name = self.__visible_columns[section + self.__col_skip]
return self.__col_name return self.__col_name
return super().headerData(section, orientation, role) return super().headerData(section, orientation, role)
@Slot(int)
def onRowClicked(self, row):
#print(self.__data)
print(f"Selected table row: {row}, corresponding DB ID: {self.__data[row][0]}")
#if not self.__employee_dict['employee'] or self.__data[row][0] != self.__employee_dict['employee']['id']:
#self.__employee = self.__employee_dao.getEmployee(self.__data[row][0], self.__key)
#print(self.__business)
#self.__getEmployeeInfo()
# self.__getContactInfo()
# print(self.__business_dict)

View File

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

View File

@@ -1,6 +1,5 @@
from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal
from .ObjectDAO import ObjectDAO from .ObjectDAO import ObjectDAO
# from ..PyqcrmFlags import PyqcrmFlags, PyqcrmAppliEmpyFlags
from ..ConfigLoader import ConfigLoader from ..ConfigLoader import ConfigLoader
import re import re
import json import json
@@ -23,7 +22,7 @@ class ObjectModel(QAbstractTableModel):
super().__init__() super().__init__()
self.__object_dao = ObjectDAO() self.__object_dao = ObjectDAO()
self.__object_dao.newObjectAdded.connect(self.__refreshView) self.__object_dao.newObjectAdded.connect(self.__refreshView)
self.__conf = ConfigLoader().getConfig() self.__conf = ConfigLoader().get_config()
self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY'] self.__key = self.__conf['pyqcrm']['ENCRYPTION_KEY']
self.__object_dao.newObjectAdded.connect(self.objectAdded) self.__object_dao.newObjectAdded.connect(self.objectAdded)
self.__getData() self.__getData()

View File

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

View File

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

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

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

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

@@ -0,0 +1,22 @@
from peewee import Model, MySQLDatabase
from lib.Config import DatabaseConfig
database: MySQLDatabase = MySQLDatabase(None)
def init_database_from_config(config: DatabaseConfig):
database.init(
host=config['DB_HOST'],
user=config['DB_USER'],
password=config['DB_PASS'],
database=config['DB_NAME'],
port=int(config['DB_PORT']),
connect_timeout=5,
)
database.connect()
class BaseModel(Model):
class Meta:
database = database

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

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

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

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

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

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

57
main.py
View File

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

View File

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

View File

@@ -1,28 +1,27 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>qtquickcontrols2.conf</file> <file>qtquickcontrols2.conf</file>
<file>images/ArrowLeftCircle-Outline.svg</file>
<file>images/ArrowRightEndOnRectangle.svg</file> <file>images/ArrowRightEndOnRectangle.svg</file>
<file>images/Bars3.svg</file> <file>images/Bars3.svg</file>
<file>images/BuildingOffice2-Outline.svg</file>
<file>images/CheckCircle.svg</file>
<file>images/ChevronDown.svg</file> <file>images/ChevronDown.svg</file>
<file>images/Funnel.svg</file> <file>images/Funnel.svg</file>
<file>images/Identification-Outline.svg</file>
<file>images/InboxArrowDown.svg</file>
<file>images/MagnifyingGlass.svg</file> <file>images/MagnifyingGlass.svg</file>
<file>images/Newspaper-Outline.svg</file>
<file>images/Phone.svg</file>
<file>images/PlusCircle.svg</file> <file>images/PlusCircle.svg</file>
<file>images/RectangleStack-Outline.svg</file>
<file>images/Square3Stack3D-Outline.svg</file>
<file>images/tero.jpg</file> <file>images/tero.jpg</file>
<file>sounds/error.ogg</file> <file>images/UserCircle.svg</file>
<file>sounds/fail2c.ogg</file> <file>images/UserGroup-Outline.svg</file>
<file>sounds/puzzerr.ogg</file> <file>images/Wallet-Outline.svg</file>
<file>sounds/sysnotify.ogg</file>
<file>sounds/wrong.ogg</file>
<file>fonts/RobotoCondensed.otf</file> <file>fonts/RobotoCondensed.otf</file>
<file>README</file> <file>README</file>
<file>LICENSE</file> <file>LICENSE</file>
<file>images/tero.jpg</file>
<file>images/dash.svg</file>
<file>images/invoice.svg</file>
<file>images/offer.svg</file>
<file>images/employee.svg</file>
<file>images/customer.svg</file>
<file>images/contract.svg</file>
<file>images/object.svg</file>
</qresource> </qresource>
</RCC> </RCC>

34
qml.qrc
View File

@@ -7,32 +7,21 @@
<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/AddCustomer.qml</file>
<file>Gui/CustomerTable.qml</file>
<file>Gui/Dashboard.qml</file> <file>Gui/Dashboard.qml</file>
<file>Gui/EmployeeTable.qml</file>
<file>Gui/main.qml</file> <file>Gui/main.qml</file>
<file>Gui/SearchBar.qml</file>
<file>js/qmldict.js</file> <file>js/qmldict.js</file>
<file>Gui/CustomerView.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/ObjectTable.qml</file>
<file>Gui/AddObject.qml</file> <file>Gui/AddObject.qml</file>
<file>Gui/ObjectView.qml</file> <file>Gui/ObjectView.qml</file>
<file>Gui/ObjectAddOns.qml</file> <file>Gui/ObjectAddOns.qml</file>
<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/AddApplicant.qml</file>
<file>Gui/ApplicantPersonalData.qml</file>
<file>Gui/ApplicantBankData.qml</file>
<file>Gui/ApplicantNationalInsurance.qml</file>
<file>Gui/ApplicantVarious.qml</file>
<file>Gui/CustomersTable.qml</file> <file>Gui/CustomersTable.qml</file>
<file>Gui/CustomerDetails.qml</file> <file>Gui/CustomerDetails.qml</file>
<file>Gui/ObjectsTable.qml</file> <file>Gui/ObjectsTable.qml</file>
<file>Gui/EmployeesTable.qml</file>
<file>Gui/EmployeeDetails.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>
@@ -48,20 +37,39 @@
<file>Gui/MiscConf.qml</file> <file>Gui/MiscConf.qml</file>
<file>Gui/UtilityDialogs.qml</file> <file>Gui/UtilityDialogs.qml</file>
<file>Gui/OffersTable.qml</file> <file>Gui/OffersTable.qml</file>
<file>Gui/OfferTable.qml</file> <file>Gui/Employees/AddApplicant.qml</file>
<file>Gui/Employees/AddEmployee.qml</file>
<file>Gui/Employees/ApplicantForm.qml</file>
<file>Gui/Employees/EmployeePersonalData.qml</file>
<file>Gui/Employees/EmployeeBankData.qml</file>
<file>Gui/Employees/EmployeeNationalInsurance.qml</file>
<file>Gui/Employees/EmployeeVarious.qml</file>
<file>Gui/Employees/EmployeeDetails.qml</file>
<file>Gui/Employees/EmployeesTable.qml</file>
<file>Gui/Employees/qmldir</file>
<file>TeroStyle/BarButton.qml</file>
<file>TeroStyle/Button.qml</file> <file>TeroStyle/Button.qml</file>
<file>TeroStyle/Colors.qml</file> <file>TeroStyle/Colors.qml</file>
<file>TeroStyle/ComboBox.qml</file> <file>TeroStyle/ComboBox.qml</file>
<file>TeroStyle/Dimensions.qml</file> <file>TeroStyle/Dimensions.qml</file>
<file>TeroStyle/EmailAddressValidator.qml</file>
<file>TeroStyle/Field.qml</file> <file>TeroStyle/Field.qml</file>
<file>TeroStyle/H1.qml</file>
<file>TeroStyle/H2.qml</file>
<file>TeroStyle/Label.qml</file> <file>TeroStyle/Label.qml</file>
<file>TeroStyle/NotEmptyValidator.qml</file>
<file>TeroStyle/OptionalEmailAddressValidator.qml</file>
<file>TeroStyle/OptionalPhoneNumberValidator.qml</file>
<file>TeroStyle/PhoneNumberValidator.qml</file>
<file>TeroStyle/PostcodeValidator.qml</file>
<file>TeroStyle/qmldir</file> <file>TeroStyle/qmldir</file>
<file>TeroStyle/QuickFilter.qml</file> <file>TeroStyle/QuickFilter.qml</file>
<file>TeroStyle/SearchBar.qml</file>
<file>TeroStyle/TextField.qml</file> <file>TeroStyle/TextField.qml</file>
<file>TeroStyle/Typography.qml</file> <file>TeroStyle/Typography.qml</file>
<file>TeroStyle/BarButton.qml</file>
<file>Gui/AddOffer.qml</file> <file>Gui/AddOffer.qml</file>
<file>Gui/AddNewOffer.qml</file> <file>Gui/AddNewOffer.qml</file>
</qresource> </qresource>
<qresource prefix="/Common"/>
<qresource prefix="/TeroStyle"/> <qresource prefix="/TeroStyle"/>
</RCC> </RCC>

View File

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

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More