From f0382a960ecce6cc67f898bda49d661e901591cf17f248954398591d31deed84 Mon Sep 17 00:00:00 2001 From: Yuri Becker Date: Tue, 15 Apr 2025 15:24:31 +0200 Subject: [PATCH] Validate applicant and call database procedure --- Gui/Employees/AddApplicant.qml | 19 ++++++- Gui/Employees/AddEmployee.qml | 39 +------------ Gui/Employees/ApplicantForm.qml | 63 +++++++++++++++++---- TeroStyle/Button.qml | 4 +- TeroStyle/Colors.qml | 2 + TeroStyle/Field.qml | 7 ++- TeroStyle/NotEmptyValidator.qml | 5 ++ TeroStyle/OptionalEmailAddressValidator.qml | 5 ++ TeroStyle/OptionalPhoneNumberValidator.qml | 5 ++ TeroStyle/TextField.qml | 1 + TeroStyle/qmldir | 3 + lib/DB/EmployeeDAO.py | 3 +- lib/DB/EmployeeModel.py | 62 +++++++++++++------- lib/DB/ObjectModel.py | 1 - qml.qrc | 4 ++ 15 files changed, 149 insertions(+), 74 deletions(-) create mode 100644 TeroStyle/NotEmptyValidator.qml create mode 100644 TeroStyle/OptionalEmailAddressValidator.qml create mode 100644 TeroStyle/OptionalPhoneNumberValidator.qml diff --git a/Gui/Employees/AddApplicant.qml b/Gui/Employees/AddApplicant.qml index 47cad5d..630fa6b 100644 --- a/Gui/Employees/AddApplicant.qml +++ b/Gui/Employees/AddApplicant.qml @@ -6,23 +6,38 @@ ColumnLayout { anchors.fill: parent spacing: Dimensions.l + Component.onCompleted: { + employee_model.addedNewEmployee.connect(successful => { + if (successful) + contentStack.pop(); + }); + } + ApplicantForm { + id: applicantForm + Layout.alignment: Qt.AlignTop Layout.fillHeight: true Layout.verticalStretchFactor: 1 } RowLayout { - spacing: Dimensions.l 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: { + employee_model.addApplicant(applicantForm.value); + } } } } \ No newline at end of file diff --git a/Gui/Employees/AddEmployee.qml b/Gui/Employees/AddEmployee.qml index 6d42851..9ce7e0e 100644 --- a/Gui/Employees/AddEmployee.qml +++ b/Gui/Employees/AddEmployee.qml @@ -7,12 +7,7 @@ ColumnLayout { id: colPar function checkFields() { - if (radio.children[1].checked) { - if (!personalData.checkPersonalField()) - saveBtn.enabled = false; - else - saveBtn.enabled = true; - } else if (!personalData.checkPersonalField()) + if (!personalData.checkPersonalField()) saveBtn.enabled = false; else saveBtn.enabled = true; @@ -49,20 +44,6 @@ ColumnLayout { personalData.requiredField(); } } - Row { - id: radio - - Layout.fillWidth: true - - //Layout.columnSpan: 2 - RadioButton { - checked: true - text: qsTr("Bewerber") - } - RadioButton { - text: qsTr("Mitarbeiter") - } - } RowLayout { Layout.fillWidth: true spacing: 50 @@ -121,22 +102,8 @@ ColumnLayout { text: qsTr("Speichern") 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)) - } + const new_applicant = JsLib.parseForm(personalData, bankAccount, nationalInsurance, applicantVarious); + employee_model.addEmployee(new_applicant, false); } } } diff --git a/Gui/Employees/ApplicantForm.qml b/Gui/Employees/ApplicantForm.qml index d66a815..5a6f0f9 100644 --- a/Gui/Employees/ApplicantForm.qml +++ b/Gui/Employees/ApplicantForm.qml @@ -6,6 +6,20 @@ import TeroStyle ColumnLayout { readonly property int fieldM: 235 readonly property int fieldS: 110 + readonly property bool valid: city.acceptableInput && email.acceptableInput && firstname.acceptableInput && lastname.acceptableInput && mobile.acceptableInput && phone.acceptableInput && postcode.acceptableInput && formofaddress.acceptableInput && title.acceptableInput + readonly property var value: QtObject { + readonly property string city: (city.editText ? city.editText : city.currentText) ?? "" + readonly property string email: email.text + readonly property string firstname: firstname.text + readonly property string formofaddress: formofaddress.currentText ?? "" + readonly property string houseno: houseno.text ?? "" + readonly property string lastname: lastname.text + readonly property string mobile: mobile.text + readonly property string phone: phone.text + readonly property string postcode: (postcode.editText ? postcode.editText : postcode.currentText) ?? "" + readonly property string street: (street.editText ? street.editText : street.currentText) ?? "" + readonly property string title: title.currentText + } spacing: Dimensions.l @@ -34,31 +48,43 @@ ColumnLayout { onCurrentTextChanged: { switch (title.currentIndex) { case 1: - salutation.text = "Sehr geehrter Herr "; + formofaddress.text = "Sehr geehrter Herr "; break; case 2: - salutation.text = "Sehr geehrte Frau "; + formofaddress.text = "Sehr geehrte Frau "; break; default: - salutation.text = "Guten Tag "; + formofaddress.text = "Guten Tag "; } } } } Field { - label: qsTr("Vorname*") + label: qsTr("Vorname") + mandatory: true TextField { + id: firstname + implicitWidth: fieldM - placeholderText: "Max" + placeholderText: qsTr("Max") + + validator: NotEmptyValidator { + } } } Field { - label: qsTr("Nachname*") + label: qsTr("Nachname") + mandatory: true TextField { + id: lastname + implicitWidth: fieldM placeholderText: qsTr("Mustermann") + + validator: NotEmptyValidator { + } } } } @@ -66,6 +92,8 @@ ColumnLayout { spacing: Dimensions.m Field { + id: street + label: qsTr("Straße") TextField { @@ -74,6 +102,8 @@ ColumnLayout { } } Field { + id: houseno + label: qsTr("Hausnummer") TextField { @@ -86,6 +116,8 @@ ColumnLayout { ComboBox { id: postcode + + currentIndex: -1 editable: true implicitWidth: fieldS model: address_model @@ -97,14 +129,19 @@ ColumnLayout { } Field { label: qsTr("Ort") + mandatory: true ComboBox { id: city + currentIndex: -1 editable: true implicitWidth: fieldM model: address_model textRole: "city" + + validator: NotEmptyValidator { + } } } } @@ -125,10 +162,12 @@ ColumnLayout { label: qsTr("Telefonnummer") TextField { + id: phone + implicitWidth: fieldM placeholderText: "+49 1234 567890" - validator: PhoneNumberValidator { + validator: OptionalPhoneNumberValidator { } } } @@ -136,10 +175,12 @@ ColumnLayout { label: qsTr("Mobil") TextField { + id: mobile + implicitWidth: fieldM placeholderText: "+49 123 4567891011" - validator: PhoneNumberValidator { + validator: OptionalPhoneNumberValidator { } } } @@ -147,10 +188,12 @@ ColumnLayout { label: qsTr("E-Mail Adresse") TextField { + id: email + implicitWidth: fieldM placeholderText: "tero@example.org" - validator: EmailAddressValidator { + validator: OptionalEmailAddressValidator { } } } @@ -158,7 +201,7 @@ ColumnLayout { label: qsTr("Briefanrede") TextField { - id: salutation + id: formofaddress implicitWidth: fieldM } diff --git a/TeroStyle/Button.qml b/TeroStyle/Button.qml index fa4cb19..c0858b4 100644 --- a/TeroStyle/Button.qml +++ b/TeroStyle/Button.qml @@ -44,12 +44,12 @@ T.Button { border.color: Colors.interactive border.width: isFieldButton ? 1 : 0 bottomLeftRadius: topLeftRadius - color: !control.hovered ? Colors.primary : Colors.primaryLighter + color: !control.enabled ? Colors.disabled : !control.hovered ? Colors.primary : Colors.primaryLighter radius: Dimensions.radius topLeftRadius: isFieldButton ? 0 : radius } contentItem: I.IconLabel { - color: Colors.primaryContrast + color: !control.enabled ? Colors.disabledForeground : Colors.primaryContrast display: control.display font: control.font icon: control.icon diff --git a/TeroStyle/Colors.qml b/TeroStyle/Colors.qml index af7cb98..6331749 100644 --- a/TeroStyle/Colors.qml +++ b/TeroStyle/Colors.qml @@ -17,6 +17,8 @@ QtObject { readonly property color mantle: theme === dark ? "#1E1E23" : "#e7e9ef" readonly property color interactive: theme === dark ? "#878b97" : "#d9d9da" 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 double highlightOpacity: .3 diff --git a/TeroStyle/Field.qml b/TeroStyle/Field.qml index e0696fb..85647fd 100644 --- a/TeroStyle/Field.qml +++ b/TeroStyle/Field.qml @@ -5,11 +5,16 @@ import QtQuick.Layouts ColumnLayout { 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 Label { - text: label + text: label + (mandatory ? "*" : "") font: Typography.body } } diff --git a/TeroStyle/NotEmptyValidator.qml b/TeroStyle/NotEmptyValidator.qml new file mode 100644 index 0000000..c7fb80a --- /dev/null +++ b/TeroStyle/NotEmptyValidator.qml @@ -0,0 +1,5 @@ +import QtQuick + +RegularExpressionValidator { + regularExpression: /^\S+.*\S+$/ +} diff --git a/TeroStyle/OptionalEmailAddressValidator.qml b/TeroStyle/OptionalEmailAddressValidator.qml new file mode 100644 index 0000000..4543008 --- /dev/null +++ b/TeroStyle/OptionalEmailAddressValidator.qml @@ -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})/ +} diff --git a/TeroStyle/OptionalPhoneNumberValidator.qml b/TeroStyle/OptionalPhoneNumberValidator.qml new file mode 100644 index 0000000..24ff8b2 --- /dev/null +++ b/TeroStyle/OptionalPhoneNumberValidator.qml @@ -0,0 +1,5 @@ +import QtQuick + +RegularExpressionValidator { + regularExpression: /^$|([+0-9])([0-9\s]{1,17})/ +} diff --git a/TeroStyle/TextField.qml b/TeroStyle/TextField.qml index 90e9229..94c4e94 100644 --- a/TeroStyle/TextField.qml +++ b/TeroStyle/TextField.qml @@ -4,6 +4,7 @@ import QtQuick.Templates as T T.TextField { id: control + background: Rectangle { id: background diff --git a/TeroStyle/qmldir b/TeroStyle/qmldir index b461990..fa71985 100644 --- a/TeroStyle/qmldir +++ b/TeroStyle/qmldir @@ -10,7 +10,10 @@ Field Field.qml H1 H1.qml H2 H2.qml Label Label.qml +NotEmptyValidator NotEmptyValidator.qml +OptionalEmailAddressValidator OptionalEmailAddressValidator.qml PhoneNumberValidator PhoneNumberValidator.qml +OptionalPhoneNumberValidator OptionalPhoneNumberValidator.qml PostcodeValidator PostcodeValidator.qml QuickFilter QuickFilter.qml SearchBar SearchBar.qml diff --git a/lib/DB/EmployeeDAO.py b/lib/DB/EmployeeDAO.py index 5c0cb0c..b3ad69f 100644 --- a/lib/DB/EmployeeDAO.py +++ b/lib/DB/EmployeeDAO.py @@ -2,7 +2,6 @@ from .DbManager import DbManager import json import mariadb from PySide6.QtCore import QObject, Signal -# from ..PyqcrmFlags import PyqcrmAppliEmpyFlags class EmployeeDAO(QObject): @@ -35,7 +34,7 @@ class EmployeeDAO(QObject): #self.__all_cols = [desc[0] for desc in self.__cur.description] return self.__cur.fetchall() #, self.__all_cols else: - return None #, None + return None except mariadb.Error as e: print(str(e)) diff --git a/lib/DB/EmployeeModel.py b/lib/DB/EmployeeModel.py index 8876603..e5357aa 100644 --- a/lib/DB/EmployeeModel.py +++ b/lib/DB/EmployeeModel.py @@ -1,4 +1,8 @@ -from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal +import json + +from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal, QJsonDocument +from PySide6.QtQml import QJSValue + from .EmployeeDAO import EmployeeDAO # from ..PyqcrmFlags import PyqcrmFlags, PyqcrmAppliEmpyFlags from ..ConfigLoader import ConfigLoader @@ -24,11 +28,27 @@ class EmployeeModel(QAbstractTableModel): self.__getData() @Slot(dict, bool) - def addEmployee(self, new_employee, applicant = True): + def addEmployee(self, new_employee): if 'worklicense' in new_employee: new_employee['worklicense'] = int(new_employee['worklicense']) new_employee['residencetype'] = int(new_employee['residencetype']) - self.__employee_dao.addEmployee(new_employee, self.__key, applicant) + self.__employee_dao.addEmployee(new_employee, self.__key, False) + + @Slot(QJSValue) + def addApplicant(self, applicant: QJSValue): + self.__employee_dao.addEmployee({ + "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) def __refreshView(self, added): @@ -36,16 +56,17 @@ class EmployeeModel(QAbstractTableModel): self.__getData() self.addedNewEmployee.emit(added) - 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() - 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.endResetModel() - def rowCount(self, parent= QModelIndex()): - return len (self.__data) + def rowCount(self, parent=QModelIndex()): + return len(self.__data) - def columnCount(self, parent= QModelIndex()): + def columnCount(self, parent=QModelIndex()): return len(self.__visible_columns) - self.__col_skip @Slot(str) @@ -54,22 +75,23 @@ class EmployeeModel(QAbstractTableModel): self.__col_skip = 2 self.__getData(criterion, criterion == 'Erledigt', False, criterion == 'Alle') - def data(self, index, role= Qt.DisplayRole): + def data(self, index, role=Qt.DisplayRole): if role == Qt.DisplayRole: row = self.__data[index.row()] applicant_col = index.column() + self.__col_skip - tr = row[applicant_col] #if type(row[index.column() + 2]) is str else str(row[index.column() + 2], "utf-8") + tr = row[ + applicant_col] # if type(row[index.column() + 2]) is str else str(row[index.column() + 2], "utf-8") if applicant_col == 2 and self.__everyone: tr = 'Ja' if tr == 1 else 'Nein' else: if tr: - tr = re.sub("Keine Angabe ","", tr) - #print(f"Data: {tr}") + tr = re.sub("Keine Angabe ", "", tr) + # print(f"Data: {tr}") # return row[index.column() + 2] return tr return None - def headerData(self, section, orientation, role = Qt.DisplayRole): + def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: self.__col_name = self.__visible_columns[section + self.__col_skip] return self.__col_name @@ -77,11 +99,11 @@ class EmployeeModel(QAbstractTableModel): @Slot(int) def onRowClicked(self, row): - #print(self.__data) + # 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) + # 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) diff --git a/lib/DB/ObjectModel.py b/lib/DB/ObjectModel.py index ba19df5..34b2d08 100644 --- a/lib/DB/ObjectModel.py +++ b/lib/DB/ObjectModel.py @@ -1,6 +1,5 @@ from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot, Signal from .ObjectDAO import ObjectDAO -# from ..PyqcrmFlags import PyqcrmFlags, PyqcrmAppliEmpyFlags from ..ConfigLoader import ConfigLoader import re import json diff --git a/qml.qrc b/qml.qrc index 55a4029..9d1b12c 100644 --- a/qml.qrc +++ b/qml.qrc @@ -57,7 +57,11 @@ TeroStyle/H1.qml TeroStyle/H2.qml TeroStyle/Label.qml + TeroStyle/NotEmptyValidator.qml + TeroStyle/OptionalEmailAddressValidator.qml + TeroStyle/OptionalPhoneNumberValidator.qml TeroStyle/PhoneNumberValidator.qml + TeroStyle/PostcodeValidator.qml TeroStyle/qmldir TeroStyle/QuickFilter.qml TeroStyle/SearchBar.qml