diff --git a/.gitignore b/.gitignore index 6732e72..fab7372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,73 @@ -# C++ objects and libs +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- -*.slo -*.lo -*.o +*~ +*.autosave *.a -*.la -*.lai +*.core +*.moc +*.o +*.obj +*.orig +*.rej *.so -*.dll -*.dylib - -# Qt-es - +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc /.qmake.cache /.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -Makefile* -*build-* -# QtCreator +# qtcreator generated files +*.pro.user* -*.autosave +# xemacs temporary files +*.flc -# QtCtreator Qml -*.qmlproject.user -*.qmlproject.user.* +# Vim temporary files +.*.swp -# QtCtreator CMake -CMakeLists.txt.user* +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe diff --git a/dialogs/aboutmedialog.cpp b/dialogs/aboutmedialog.cpp new file mode 100644 index 0000000..57e00cb --- /dev/null +++ b/dialogs/aboutmedialog.cpp @@ -0,0 +1,21 @@ +#include "aboutmedialog.h" +#include "ui_aboutmedialog.h" + +AboutMeDialog::AboutMeDialog(const Zeiterfassung::UserInfo &userInfo, QWidget *parent) : + QDialog(parent), + ui(new Ui::AboutMeDialog), + m_userInfo(userInfo) +{ + ui->setupUi(this); + + ui->spinBoxUserId->setValue(userInfo.userId); + ui->lineEditEmail->setText(userInfo.email); + ui->lineEditLongUsername->setText(userInfo.longUsername); + ui->lineEditText->setText(userInfo.text); + ui->lineEditUsername->setText(userInfo.username); +} + +AboutMeDialog::~AboutMeDialog() +{ + delete ui; +} diff --git a/dialogs/aboutmedialog.h b/dialogs/aboutmedialog.h new file mode 100644 index 0000000..bbb76e1 --- /dev/null +++ b/dialogs/aboutmedialog.h @@ -0,0 +1,23 @@ +#ifndef ABOUTMEDIALOG_H +#define ABOUTMEDIALOG_H + +#include + +#include "zeiterfassung.h" + +namespace Ui { class AboutMeDialog; } + +class AboutMeDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AboutMeDialog(const Zeiterfassung::UserInfo &userInfo, QWidget *parent = 0); + ~AboutMeDialog(); + +private: + Ui::AboutMeDialog *ui; + const Zeiterfassung::UserInfo &m_userInfo; +}; + +#endif // ABOUTMEDIALOG_H diff --git a/dialogs/aboutmedialog.ui b/dialogs/aboutmedialog.ui new file mode 100644 index 0000000..07f8bcc --- /dev/null +++ b/dialogs/aboutmedialog.ui @@ -0,0 +1,120 @@ + + + AboutMeDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + <h1>About me</h1> + + + + + + + + + User-ID: + + + spinBoxUserId + + + + + + + E-Mail: + + + lineEditEmail + + + + + + + Long username: + + + lineEditLongUsername + + + + + + + Text: + + + lineEditText + + + + + + + Username: + + + lineEditUsername + + + + + + + true + + + 16777215 + + + + + + + true + + + + + + + true + + + + + + + true + + + + + + + true + + + + + + + + + + diff --git a/dialogs/authenticationdialog.cpp b/dialogs/authenticationdialog.cpp new file mode 100644 index 0000000..5de7cf2 --- /dev/null +++ b/dialogs/authenticationdialog.cpp @@ -0,0 +1,34 @@ +#include "authenticationdialog.h" +#include "ui_authenticationdialog.h" + +AuthenticationDialog::AuthenticationDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AuthenticationDialog) +{ + ui->setupUi(this); +} + +AuthenticationDialog::~AuthenticationDialog() +{ + delete ui; +} + +QString AuthenticationDialog::username() const +{ + return ui->lineEditUsername->text(); +} + +void AuthenticationDialog::setUsername(const QString &username) +{ + ui->lineEditUsername->setText(username); +} + +QString AuthenticationDialog::password() const +{ + return ui->lineEditPassword->text(); +} + +void AuthenticationDialog::setPassword(const QString &password) +{ + ui->lineEditPassword->setText(password); +} diff --git a/dialogs/authenticationdialog.h b/dialogs/authenticationdialog.h new file mode 100644 index 0000000..dc3c35c --- /dev/null +++ b/dialogs/authenticationdialog.h @@ -0,0 +1,28 @@ +#ifndef AUTHENTICATIONDIALOG_H +#define AUTHENTICATIONDIALOG_H + +#include + +namespace Ui { +class AuthenticationDialog; +} + +class AuthenticationDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AuthenticationDialog(QWidget *parent = 0); + ~AuthenticationDialog(); + + QString username() const; + void setUsername(const QString &username); + + QString password() const; + void setPassword(const QString &password); + +private: + Ui::AuthenticationDialog *ui; +}; + +#endif // AUTHENTICATIONDIALOG_H diff --git a/dialogs/authenticationdialog.ui b/dialogs/authenticationdialog.ui new file mode 100644 index 0000000..f1fd258 --- /dev/null +++ b/dialogs/authenticationdialog.ui @@ -0,0 +1,133 @@ + + + AuthenticationDialog + + + + 0 + 0 + 394 + 169 + + + + Dialog + + + + + + + + + 48 + 48 + + + + + 48 + 48 + + + + :/zeiterfassung/images/authentication.png + + + true + + + + + + + <h1>Authentication</h1> + + + + + + + + + + + Username: + + + lineEditUsername + + + + + + + Password: + + + lineEditPassword + + + + + + + + + + QLineEdit::Password + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + AuthenticationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AuthenticationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/dialogs/buchungdialog.cpp b/dialogs/buchungdialog.cpp new file mode 100644 index 0000000..a825205 --- /dev/null +++ b/dialogs/buchungdialog.cpp @@ -0,0 +1,55 @@ +#include "buchungdialog.h" +#include "ui_buchungdialog.h" + +BuchungDialog::BuchungDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::BuchungDialog) +{ + ui->setupUi(this); + ui->timeEditTime->setTime(QTime::currentTime()); +} + +BuchungDialog::~BuchungDialog() +{ + delete ui; +} + +QTime BuchungDialog::getTime() const +{ + return ui->timeEditTime->time(); +} + +void BuchungDialog::setTime(const QTime &time) +{ + ui->timeEditTime->setTime(time); +} + +QTime BuchungDialog::getTimespan() const +{ + return ui->timeEditTimespan->time(); +} + +void BuchungDialog::setTimespan(const QTime ×pan) +{ + ui->timeEditTimespan->setTime(timespan); +} + +QString BuchungDialog::getType() const +{ + return ui->comboBoxType->currentText(); +} + +void BuchungDialog::setType(const QString &type) +{ + ui->comboBoxType->setCurrentText(type); +} + +QString BuchungDialog::getText() const +{ + return ui->lineEditText->text(); +} + +void BuchungDialog::setText(const QString &text) +{ + ui->lineEditText->setText(text); +} diff --git a/dialogs/buchungdialog.h b/dialogs/buchungdialog.h new file mode 100644 index 0000000..fdee1ff --- /dev/null +++ b/dialogs/buchungdialog.h @@ -0,0 +1,34 @@ +#ifndef BUCHUNGDIALOG_H +#define BUCHUNGDIALOG_H + +#include + +namespace Ui { +class BuchungDialog; +} + +class BuchungDialog : public QDialog +{ + Q_OBJECT + +public: + explicit BuchungDialog(QWidget *parent = 0); + ~BuchungDialog(); + + QTime getTime() const; + void setTime(const QTime &time); + + QTime getTimespan() const; + void setTimespan(const QTime ×pan); + + QString getType() const; + void setType(const QString &type); + + QString getText() const; + void setText(const QString &text); + +private: + Ui::BuchungDialog *ui; +}; + +#endif // BUCHUNGDIALOG_H diff --git a/dialogs/buchungdialog.ui b/dialogs/buchungdialog.ui new file mode 100644 index 0000000..9dbe081 --- /dev/null +++ b/dialogs/buchungdialog.ui @@ -0,0 +1,139 @@ + + + BuchungDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + <h1>Buchung</h1> + + + + + + + + + Time: + + + timeEditTime + + + + + + + Timespan: + + + timeEditTimespan + + + + + + + Type: + + + comboBoxType + + + + + + + Text: + + + lineEditText + + + + + + + HH:mm:ss + + + + + + + HH:mm:ss + + + + + + + + + + true + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + BuchungDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BuchungDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/dialogs/kontierungdialog.cpp b/dialogs/kontierungdialog.cpp new file mode 100644 index 0000000..8899131 --- /dev/null +++ b/dialogs/kontierungdialog.cpp @@ -0,0 +1,88 @@ +#include "kontierungdialog.h" +#include "ui_kontierungdialog.h" + +#include +#include +#include + +KontierungDialog::KontierungDialog(Zeiterfassung &erfassung, const Zeiterfassung::UserInfo &userInfo, + const QVector &projekte, QWidget *parent) : + QDialog(parent), + ui(new Ui::KontierungDialog), + m_erfassung(erfassung), + m_userInfo(userInfo) +{ + ui->setupUi(this); + + for(const auto& projekt : projekte) + ui->comboBoxProjekt->addItem(projekt.label % " (" % projekt.value % ')', projekt.value); +} + +KontierungDialog::~KontierungDialog() +{ + delete ui; +} + +QTime KontierungDialog::getTime() const +{ + return ui->timeEditTime->time(); +} + +void KontierungDialog::setTime(const QTime &time) +{ + ui->timeEditTime->setTime(time); +} + +QTime KontierungDialog::getTimespan() const +{ + return ui->timeEditTimespan->time(); +} + +void KontierungDialog::setTimespan(const QTime ×pan) +{ + ui->timeEditTimespan->setTime(timespan); +} + +QString KontierungDialog::getProjekt() const +{ + return ui->comboBoxProjekt->currentData().toString(); +} + +void KontierungDialog::setProjekt(const QString &projekt) +{ + auto index = ui->comboBoxProjekt->findData(projekt); + if(index >= 0) + ui->comboBoxProjekt->setCurrentIndex(index); + else + qWarning() << "could not find projekt" << projekt; +} + +QString KontierungDialog::getSubprojekt() const +{ + return ui->lineEditSubprojekt->text(); +} + +void KontierungDialog::setSubprojekt(const QString &subprojekt) +{ + ui->lineEditSubprojekt->setText(subprojekt); +} + +QString KontierungDialog::getWorkpackage() const +{ + return ui->lineEditWorkpackage->text(); +} + +void KontierungDialog::setWorkpackage(const QString &workpackage) +{ + ui->lineEditWorkpackage->setText(workpackage); +} + +QString KontierungDialog::getText() const +{ + return ui->lineEditText->text(); +} + +void KontierungDialog::setText(const QString &text) +{ + ui->lineEditText->setText(text); +} diff --git a/dialogs/kontierungdialog.h b/dialogs/kontierungdialog.h new file mode 100644 index 0000000..5ed1ec4 --- /dev/null +++ b/dialogs/kontierungdialog.h @@ -0,0 +1,43 @@ +#ifndef KONTIERUNGDIALOG_H +#define KONTIERUNGDIALOG_H + +#include + +#include "zeiterfassung.h" + +namespace Ui { class KontierungDialog; } + +class KontierungDialog : public QDialog +{ + Q_OBJECT + +public: + explicit KontierungDialog(Zeiterfassung &erfassung, const Zeiterfassung::UserInfo &userInfo, + const QVector &projekte, QWidget *parent = 0); + ~KontierungDialog(); + + QTime getTime() const; + void setTime(const QTime &time); + + QTime getTimespan() const; + void setTimespan(const QTime ×pan); + + QString getProjekt() const; + void setProjekt(const QString &projekt); + + QString getSubprojekt() const; + void setSubprojekt(const QString &subprojekt); + + QString getWorkpackage() const; + void setWorkpackage(const QString &workpackage); + + QString getText() const; + void setText(const QString &text); + +private: + Ui::KontierungDialog *ui; + Zeiterfassung &m_erfassung; + const Zeiterfassung::UserInfo &m_userInfo; +}; + +#endif // KONTIERUNGDIALOG_H diff --git a/dialogs/kontierungdialog.ui b/dialogs/kontierungdialog.ui new file mode 100644 index 0000000..6a3ba6a --- /dev/null +++ b/dialogs/kontierungdialog.ui @@ -0,0 +1,168 @@ + + + KontierungDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + <h1>Kontierung</h1> + + + + + + + + + Time: + + + timeEditTime + + + + + + + Timespan: + + + timeEditTimespan + + + + + + + Projekt: + + + comboBoxProjekt + + + + + + + Subprojekt: + + + lineEditSubprojekt + + + + + + + Workpackage: + + + lineEditWorkpackage + + + + + + + Text: + + + lineEditText + + + + + + + HH:mm:ss + + + + + + + HH:mm:ss + + + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + KontierungDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + KontierungDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/eventloopwithstatus.cpp b/eventloopwithstatus.cpp new file mode 100644 index 0000000..ea70956 --- /dev/null +++ b/eventloopwithstatus.cpp @@ -0,0 +1,24 @@ +#include "eventloopwithstatus.h" + +EventLoopWithStatus::EventLoopWithStatus(QObject *parent) : + QEventLoop(parent) +{ + +} + +bool EventLoopWithStatus::success() const +{ + return m_success; +} + +const QString &EventLoopWithStatus::message() const +{ + return m_message; +} + +void EventLoopWithStatus::quitWithStatus(bool success, const QString &message) +{ + m_success = success; + m_message = message; + quit(); +} diff --git a/eventloopwithstatus.h b/eventloopwithstatus.h new file mode 100644 index 0000000..ecdcbb0 --- /dev/null +++ b/eventloopwithstatus.h @@ -0,0 +1,24 @@ +#ifndef EVENTLOOPWITHSTATUS_H +#define EVENTLOOPWITHSTATUS_H + +#include + +class EventLoopWithStatus : public QEventLoop +{ + Q_OBJECT + +public: + EventLoopWithStatus(QObject *parent = Q_NULLPTR); + + bool success() const; + const QString &message() const; + +public Q_SLOTS: + void quitWithStatus(bool success, const QString &message); + +private: + bool m_success; + QString m_message; +}; + +#endif // EVENTLOOPWITHSTATUS_H diff --git a/images/authentication.png b/images/authentication.png new file mode 100644 index 0000000..4685f3b Binary files /dev/null and b/images/authentication.png differ diff --git a/images/next.png b/images/next.png new file mode 100644 index 0000000..0df2a6c Binary files /dev/null and b/images/next.png differ diff --git a/images/previous.png b/images/previous.png new file mode 100644 index 0000000..2bf9523 Binary files /dev/null and b/images/previous.png differ diff --git a/images/quit.png b/images/quit.png new file mode 100644 index 0000000..77c586c Binary files /dev/null and b/images/quit.png differ diff --git a/images/refresh.png b/images/refresh.png new file mode 100644 index 0000000..f3585b7 Binary files /dev/null and b/images/refresh.png differ diff --git a/images/splash.png b/images/splash.png new file mode 100644 index 0000000..cc66e5f Binary files /dev/null and b/images/splash.png differ diff --git a/images/today.png b/images/today.png new file mode 100644 index 0000000..d0923ae Binary files /dev/null and b/images/today.png differ diff --git a/main.cpp b/main.cpp new file mode 100755 index 0000000..860f310 --- /dev/null +++ b/main.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include + +#include "zeiterfassung.h" +#include "eventloopwithstatus.h" +#include "dialogs/authenticationdialog.h" +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QCoreApplication::setOrganizationDomain(QStringLiteral("brunner.ninja")); + QCoreApplication::setOrganizationName(QStringLiteral("db-software")); + QCoreApplication::setApplicationName(QStringLiteral("zeiterfassung")); + QCoreApplication::setApplicationVersion(QStringLiteral("1.0")); + + QSplashScreen splashScreen(QPixmap(":/zeiterfassung/images/splash.png")); + splashScreen.showMessage(QObject::tr("Loading settings...")); + splashScreen.show(); + + QSettings settings; + + if(settings.value("url").isNull()) + { + bool ok; + auto url = QInputDialog::getText(&splashScreen, QObject::tr("Base url"), + QObject::tr("Please enter the base url to the Zeiterfassung:"), + QLineEdit::Normal, QString(), &ok); + if(!ok) + return -1; + settings.setValue("url", url); + } + + splashScreen.showMessage(QObject::tr("Loading login page...")); + + Zeiterfassung erfassung(settings.value("url").toString()); + + { + EventLoopWithStatus eventLoop; + QObject::connect(&erfassung, &Zeiterfassung::loginPageFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + again1: + erfassung.doLoginPage(); + eventLoop.exec(); + + if(!eventLoop.success()) + { + bool ok; + QMessageBox::warning(&splashScreen, QObject::tr("Could not access Zeiterfassung"), + QObject::tr("The Zeiterfassung could not be accessed:\n\n%0").arg(eventLoop.message())); + + auto url = QInputDialog::getText(&splashScreen, QObject::tr("Base url"), + QObject::tr("Please enter the base url to the Zeiterfassung:"), + QLineEdit::Normal, settings.value("url").toString(), &ok); + if(!ok) + return -1; + settings.setValue("url", url); + erfassung.setUrl(url); + goto again1; + } + } + + splashScreen.showMessage(QObject::tr("Authenticating...")); + + if(settings.value("username").isNull() || settings.value("password").isNull()) + { + AuthenticationDialog dialog(&splashScreen); + if(dialog.exec() != QDialog::Accepted) + return -1; + settings.setValue("username", dialog.username()); + settings.setValue("password", dialog.password()); + } + + { + EventLoopWithStatus eventLoop; + QObject::connect(&erfassung, &Zeiterfassung::loginFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + again2: + erfassung.doLogin(settings.value("username").toString(), settings.value("password").toString()); + eventLoop.exec(); + + if(!eventLoop.success()) + { + QMessageBox::warning(&splashScreen, QObject::tr("Could not authenticate with Zeiterfassung"), + QObject::tr("The Zeiterfassung authentication was not successful:\n\n%0").arg(eventLoop.message())); + + AuthenticationDialog dialog(&splashScreen); + dialog.setUsername(settings.value("username").toString()); + dialog.setPassword(settings.value("password").toString()); + if(dialog.exec() != QDialog::Accepted) + return -1; + settings.setValue("username", dialog.username()); + settings.setValue("password", dialog.password()); + + goto again2; + } + } + + splashScreen.showMessage(QObject::tr("Getting user information...")); + + Zeiterfassung::UserInfo userInfo; + + { + EventLoopWithStatus eventLoop; + QObject::connect(&erfassung, &Zeiterfassung::userInfoFinished, + [&](bool success, const QString &message, const Zeiterfassung::UserInfo &_userInfo) { + Q_UNUSED(message) + if(success) + userInfo = _userInfo; + }); + QObject::connect(&erfassung, &Zeiterfassung::userInfoFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + erfassung.doUserInfo(); + eventLoop.exec(); + + if(!eventLoop.success()) + { + QMessageBox::warning(&splashScreen, QObject::tr("Could not get user information!"), + QObject::tr("Could not get user information:\n\n%0").arg(eventLoop.message())); + + return -1; + } + } + + MainWindow mainWindow(settings, erfassung, userInfo); + mainWindow.show(); + + splashScreen.finish(&mainWindow); + + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..aa47820 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,978 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "eventloopwithstatus.h" +#include "dialogs/aboutmedialog.h" +#include "dialogs/buchungdialog.h" +#include "dialogs/kontierungdialog.h" +#include "models/buchungenmodel.h" +#include "models/kontierungenmodel.h" + +MainWindow::MainWindow(QSettings &settings, Zeiterfassung &erfassung, const Zeiterfassung::UserInfo &userInfo, QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + m_settings(settings), + m_erfassung(erfassung), + m_userInfo(userInfo), + m_buchungenModel(new BuchungenModel(erfassung, this)), + m_kontierungenModel(new KontierungenModel(erfassung, this)), + m_flag(false) +{ + ui->setupUi(this); + + setWindowTitle(tr("Zeiterfassung - %0").arg(m_userInfo.text)); + + ui->actionQuit->setShortcut(QKeySequence::Quit); + + connect(ui->actionToday, &QAction::triggered, [=](){ ui->dateEditDate->setDate(QDate::currentDate()); }); + + ui->actionRefresh->setShortcut(QKeySequence::Refresh); + connect(ui->actionRefresh, &QAction::triggered, this, &MainWindow::refresh); + + connect(ui->actionAboutMe, &QAction::triggered, [=](){ AboutMeDialog(userInfo, this).exec(); }); + connect(ui->actionAboutQt, &QAction::triggered, [=](){ QMessageBox::aboutQt(this); }); + + ui->dateEditDate->setDate(QDate::currentDate()); + connect(ui->dateEditDate, &QDateTimeEdit::dateChanged, this, &MainWindow::refresh); + refresh(); + + connect(ui->pushButtonPrev, &QAbstractButton::pressed, this, &MainWindow::pushButtonPrevPressed); + connect(ui->pushButtonNext, &QAbstractButton::pressed, this, &MainWindow::pushButtonNextPressed); + + ui->timeEditTime->setTime(timeNormalise(QTime::currentTime())); + + connect(&m_erfassung, &Zeiterfassung::getProjekteFinished, + this, &MainWindow::getProjekteFinished); + erfassung.doGetProjekte(userInfo.userId); + + ui->comboBoxProjekt->setMaxVisibleItems(10); + + ui->comboBoxSubprojekt->lineEdit()->setPlaceholderText(tr("Subprojekt")); + ui->comboBoxWorkpackage->lineEdit()->setPlaceholderText(tr("Workpackage")); + ui->comboBoxText->lineEdit()->setPlaceholderText(tr("Text")); + + updateComboboxes(); + + connect(ui->pushButtonStart, &QAbstractButton::pressed, this, &MainWindow::pushButtonStartPressed); + connect(ui->pushButtonEnd, &QAbstractButton::pressed, this, &MainWindow::pushButtonEndPressed); + + ui->treeViewBuchungen->setModel(m_buchungenModel); + ui->treeViewKontierungen->setModel(m_kontierungenModel); + + connect(ui->treeViewBuchungen, &QWidget::customContextMenuRequested, + this, &MainWindow::contextMenuBuchung); + connect(ui->treeViewKontierungen, &QWidget::customContextMenuRequested, + this, &MainWindow::contextMenuKontierung); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::refresh() +{ + ui->actionToday->setEnabled(false); + ui->actionRefresh->setEnabled(false); + ui->dateEditDate->setReadOnly(true); + ui->timeEditTime->setEnabled(false); + ui->comboBoxProjekt->setEnabled(false); + ui->comboBoxSubprojekt->setEnabled(false); + ui->comboBoxWorkpackage->setEnabled(false); + ui->comboBoxText->setEnabled(false); + ui->pushButtonStart->setEnabled(false); + ui->pushButtonEnd->setEnabled(false); + ui->treeViewBuchungen->setEnabled(false); + ui->treeViewKontierungen->setEnabled(false); + + auto waitForBuchugen = m_buchungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date()); + if(waitForBuchugen) + { + connect(m_buchungenModel, &BuchungenModel::refreshFinished, + this, &MainWindow::refreshBuchungenFinished); + } + + auto waitForKontierungen = m_kontierungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date()); + if(waitForKontierungen) + { + connect(m_kontierungenModel, &KontierungenModel::refreshFinished, + this, &MainWindow::refreshKontierungenFinished); + } + + if(!waitForBuchugen || !waitForKontierungen) + QMessageBox::warning(this, tr("Unknown error occured."), tr("An unknown error occured.")); + + if(waitForBuchugen || waitForKontierungen) + m_flag = waitForBuchugen == waitForKontierungen; + else + { + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + } +} + +void MainWindow::getProjekteFinished(bool success, const QString &message, const QVector &projekte) +{ + disconnect(&m_erfassung, &Zeiterfassung::getProjekteFinished, + this, &MainWindow::getProjekteFinished); + + if(!success) + { + QMessageBox::warning(this, tr("Could not load Buchungen!"), tr("Could not load Buchungen:\n\n%0").arg(message)); + return; + } + + m_projekte = projekte; + + updateComboboxes(); +} + +void MainWindow::refreshBuchungenFinished(bool success, const QString &message) +{ + disconnect(m_buchungenModel, &BuchungenModel::refreshFinished, + this, &MainWindow::refreshBuchungenFinished); + + if(success) + ui->treeViewBuchungen->setEnabled(true); + + if(m_flag) + m_flag = false; + else + validateEntries(); + + if(!success) + QMessageBox::warning(Q_NULLPTR, tr("Could not refresh Buchungen!"), tr("Could not refresh Buchungen:\n\n%0").arg(message)); +} + +void MainWindow::refreshKontierungenFinished(bool success, const QString &message) +{ + disconnect(m_kontierungenModel, &KontierungenModel::refreshFinished, + this, &MainWindow::refreshKontierungenFinished); + + if(success) + ui->treeViewKontierungen->setEnabled(true); + + if(m_flag) + m_flag = false; + else + validateEntries(); + + if(!success) + QMessageBox::warning(Q_NULLPTR, tr("Could not refresh Kontierungen!"), tr("Could not refresh Kontierungen:\n\n%0").arg(message)); +} + +void MainWindow::contextMenuBuchung(const QPoint &pos) +{ + auto index = ui->treeViewBuchungen->indexAt(pos); + + if(index.isValid()) + { + auto buchung = m_buchungenModel->getBuchung(index); + + QMenu menu; + auto editAction = menu.addAction(tr("Edit")); + auto deleteAction = menu.addAction(tr("Delete")); + auto selectedAction = menu.exec(ui->treeViewBuchungen->viewport()->mapToGlobal(pos)); + if(selectedAction == editAction) + { + BuchungDialog dialog(this); + dialog.setTime(buchung.time); + dialog.setTimespan(buchung.timespan); + dialog.setType(buchung.type); + dialog.setText(buchung.text); + again1: + if(dialog.exec() == QDialog::Accepted) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::updateBuchungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doUpdateBuchung(buchung.id, m_userInfo.userId, ui->dateEditDate->date(), + dialog.getTime(), dialog.getTimespan(), + dialog.getType(), dialog.getText()); + eventLoop.exec(); + + if(eventLoop.success()) + { + ui->actionToday->setEnabled(false); + ui->actionRefresh->setEnabled(false); + ui->dateEditDate->setReadOnly(true); + ui->timeEditTime->setEnabled(false); + ui->comboBoxProjekt->setEnabled(false); + ui->comboBoxSubprojekt->setEnabled(false); + ui->comboBoxWorkpackage->setEnabled(false); + ui->comboBoxText->setEnabled(false); + ui->pushButtonStart->setEnabled(false); + ui->pushButtonEnd->setEnabled(false); + ui->treeViewBuchungen->setEnabled(false); + + if(m_buchungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date())) + { + connect(m_buchungenModel, &BuchungenModel::refreshFinished, + this, &MainWindow::refreshBuchungenFinished); + m_flag = false; + } + else + { + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + } + } + else + { + QMessageBox::warning(this, tr("Could not update Buchung!"), tr("Could not update Buchung:\n\n%0").arg(eventLoop.message())); + goto again1; + } + } + } + else if(selectedAction == deleteAction) + { + QMessageBox msgBox; + msgBox.setText("Do you really want to delete the Buchung?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + if(msgBox.exec() == QMessageBox::Yes) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::deleteBuchungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doDeleteBuchung(buchung.id); + eventLoop.exec(); + + if(eventLoop.success()) + { + ui->actionToday->setEnabled(false); + ui->actionRefresh->setEnabled(false); + ui->dateEditDate->setReadOnly(true); + ui->timeEditTime->setEnabled(false); + ui->comboBoxProjekt->setEnabled(false); + ui->comboBoxSubprojekt->setEnabled(false); + ui->comboBoxWorkpackage->setEnabled(false); + ui->comboBoxText->setEnabled(false); + ui->pushButtonStart->setEnabled(false); + ui->pushButtonEnd->setEnabled(false); + ui->treeViewBuchungen->setEnabled(false); + + if(m_buchungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date())) + { + connect(m_buchungenModel, &BuchungenModel::refreshFinished, + this, &MainWindow::refreshBuchungenFinished); + m_flag = false; + } + else + { + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + } + } + else + QMessageBox::warning(this, tr("Could not delete Buchung!"), tr("Could not delete Buchung:\n\n%0").arg(eventLoop.message())); + } + } + } + else + { + QMenu menu; + auto createAction = menu.addAction(tr("Create")); + auto selectedAction = menu.exec(ui->treeViewBuchungen->viewport()->mapToGlobal(pos)); + if(selectedAction == createAction) + { + BuchungDialog dialog(this); + dialog.setTime(QTime::currentTime()); + again2: + if(dialog.exec() == QDialog::Accepted) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::createBuchungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doCreateBuchung(m_userInfo.userId, ui->dateEditDate->date(), + dialog.getTime(), dialog.getTimespan(), + dialog.getType(), dialog.getText()); + eventLoop.exec(); + + if(eventLoop.success()) + { + ui->actionToday->setEnabled(false); + ui->actionRefresh->setEnabled(false); + ui->dateEditDate->setReadOnly(true); + ui->timeEditTime->setEnabled(false); + ui->comboBoxProjekt->setEnabled(false); + ui->comboBoxSubprojekt->setEnabled(false); + ui->comboBoxWorkpackage->setEnabled(false); + ui->comboBoxText->setEnabled(false); + ui->pushButtonStart->setEnabled(false); + ui->pushButtonEnd->setEnabled(false); + ui->treeViewBuchungen->setEnabled(false); + + if(m_buchungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date())) + { + connect(m_buchungenModel, &BuchungenModel::refreshFinished, + this, &MainWindow::refreshBuchungenFinished); + m_flag = false; + } + else + { + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + } + } + else + { + QMessageBox::warning(this, tr("Could not create Buchung!"), tr("Could not create Buchung:\n\n%0").arg(eventLoop.message())); + goto again2; + } + } + } + } +} + +void MainWindow::contextMenuKontierung(const QPoint &pos) +{ + auto index = ui->treeViewKontierungen->indexAt(pos); + + if(index.isValid()) + { + auto kontierung = m_kontierungenModel->getKontierung(index); + + QMenu menu; + auto editAction = menu.addAction(tr("Edit")); + auto deleteAction = menu.addAction(tr("Delete")); + auto selectedAction = menu.exec(ui->treeViewKontierungen->viewport()->mapToGlobal(pos)); + if(selectedAction == editAction) + { + KontierungDialog dialog(m_erfassung, m_userInfo, m_projekte, this); + dialog.setTime(kontierung.time); + dialog.setTimespan(kontierung.timespan); + dialog.setProjekt(kontierung.projekt); + dialog.setSubprojekt(kontierung.subprojekt); + dialog.setWorkpackage(kontierung.workpackage); + dialog.setText(kontierung.text); + again1: + if(dialog.exec() == QDialog::Accepted) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::updateKontierungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doUpdateKontierung(kontierung.id, m_userInfo.userId, ui->dateEditDate->date(), + dialog.getTime(), dialog.getTimespan(), + dialog.getProjekt(), dialog.getSubprojekt(), + dialog.getWorkpackage(), dialog.getText()); + eventLoop.exec(); + + if(eventLoop.success()) + { + ui->actionToday->setEnabled(false); + ui->actionRefresh->setEnabled(false); + ui->dateEditDate->setReadOnly(true); + ui->timeEditTime->setEnabled(false); + ui->comboBoxProjekt->setEnabled(false); + ui->comboBoxSubprojekt->setEnabled(false); + ui->comboBoxWorkpackage->setEnabled(false); + ui->comboBoxText->setEnabled(false); + ui->pushButtonStart->setEnabled(false); + ui->pushButtonEnd->setEnabled(false); + ui->treeViewKontierungen->setEnabled(false); + + if(m_kontierungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date())) + { + connect(m_kontierungenModel, &KontierungenModel::refreshFinished, + this, &MainWindow::refreshKontierungenFinished); + m_flag = false; + } + else + { + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + } + } + else + { + QMessageBox::warning(this, tr("Could not update Kontierung!"), tr("Could not update Kontierung:\n\n%0").arg(eventLoop.message())); + goto again1; + } + } + } + else if(selectedAction == deleteAction) + { + QMessageBox msgBox; + msgBox.setText("Do you really want to delete the Kontierung?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + if(msgBox.exec() == QMessageBox::Yes) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::deleteKontierungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doDeleteKontierung(kontierung.id); + eventLoop.exec(); + + if(eventLoop.success()) + { + ui->actionToday->setEnabled(false); + ui->actionRefresh->setEnabled(false); + ui->dateEditDate->setReadOnly(true); + ui->timeEditTime->setEnabled(false); + ui->comboBoxProjekt->setEnabled(false); + ui->comboBoxSubprojekt->setEnabled(false); + ui->comboBoxWorkpackage->setEnabled(false); + ui->comboBoxText->setEnabled(false); + ui->pushButtonStart->setEnabled(false); + ui->pushButtonEnd->setEnabled(false); + ui->treeViewKontierungen->setEnabled(false); + + if(m_kontierungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date())) + { + connect(m_kontierungenModel, &KontierungenModel::refreshFinished, + this, &MainWindow::refreshKontierungenFinished); + m_flag = false; + } + else + { + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + } + } + else + QMessageBox::warning(this, tr("Could not delete Kontierung!"), tr("Could not delete Kontierung:\n\n%0").arg(eventLoop.message())); + } + } + } + else + { + QMenu menu; + auto createAction = menu.addAction(tr("Create")); + auto selectedAction = menu.exec(ui->treeViewKontierungen->viewport()->mapToGlobal(pos)); + if(selectedAction == createAction) + { + KontierungDialog dialog(m_erfassung, m_userInfo, m_projekte, this); + again2: + if(dialog.exec() == QDialog::Accepted) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::createKontierungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doCreateKontierung(m_userInfo.userId, ui->dateEditDate->date(), + dialog.getTime(), dialog.getTimespan(), + dialog.getProjekt(), dialog.getSubprojekt(), + dialog.getWorkpackage(), dialog.getText()); + eventLoop.exec(); + + if(eventLoop.success()) + { + ui->actionToday->setEnabled(false); + ui->actionRefresh->setEnabled(false); + ui->dateEditDate->setReadOnly(true); + ui->timeEditTime->setEnabled(false); + ui->comboBoxProjekt->setEnabled(false); + ui->comboBoxSubprojekt->setEnabled(false); + ui->comboBoxWorkpackage->setEnabled(false); + ui->comboBoxText->setEnabled(false); + ui->pushButtonStart->setEnabled(false); + ui->pushButtonEnd->setEnabled(false); + ui->treeViewKontierungen->setEnabled(false); + + if(m_kontierungenModel->refresh(m_userInfo.userId, ui->dateEditDate->date(), ui->dateEditDate->date())) + { + connect(m_kontierungenModel, &KontierungenModel::refreshFinished, + this, &MainWindow::refreshKontierungenFinished); + m_flag = false; + } + else + { + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + } + } + else + { + QMessageBox::warning(this, tr("Could not create Kontierung!"), tr("Could not create Kontierung:\n\n%0").arg(eventLoop.message())); + goto again2; + } + } + } + } +} + +void MainWindow::pushButtonPrevPressed() +{ + ui->dateEditDate->setDate(ui->dateEditDate->date().addDays(-1)); +} + +void MainWindow::pushButtonNextPressed() +{ + ui->dateEditDate->setDate(ui->dateEditDate->date().addDays(1)); +} + +void MainWindow::pushButtonStartPressed() +{ + if(m_buchungenModel->rbegin() == m_buchungenModel->rend() || + m_buchungenModel->rbegin()->type == QStringLiteral("G")) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::createBuchungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doCreateBuchung(m_userInfo.userId, ui->dateEditDate->date(), + timeNormalise(ui->timeEditTime->time()), QTime(0, 0), + QStringLiteral("K"), QStringLiteral("")); + eventLoop.exec(); + + if(!eventLoop.success()) + { + QMessageBox::warning(this, tr("Could not create Buchung!"), tr("Could not create Buchung:\n\n%0").arg(eventLoop.message())); + refresh(); + return; + } + } + + if(m_kontierungenModel->rbegin() != m_kontierungenModel->rend()) + { + auto kontierung = *m_kontierungenModel->rbegin(); + if(kontierung.timespan == QTime(0, 0)) + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::updateKontierungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + auto timespan = timeBetween(m_lastKontierungStart, ui->timeEditTime->time()); + qDebug() << "timespan" << timespan; + + m_erfassung.doUpdateKontierung(kontierung.id, m_userInfo.userId, kontierung.date, + kontierung.time, timespan, + kontierung.projekt, kontierung.subprojekt, + kontierung.workpackage, kontierung.text); + eventLoop.exec(); + + if(eventLoop.success()) + m_kontierungTime = timeAdd(m_kontierungTime, timespan); + else + { + QMessageBox::warning(this, tr("Could not update Kontierung!"), tr("Could not update Kontierung:\n\n%0").arg(eventLoop.message())); + refresh(); + return; + } + } + } + + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::createKontierungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doCreateKontierung(m_userInfo.userId, ui->dateEditDate->date(), + m_kontierungTime, QTime(0, 0), + ui->comboBoxProjekt->currentData().toString(), ui->comboBoxSubprojekt->currentText(), + ui->comboBoxWorkpackage->currentText(), ui->comboBoxText->currentText()); + eventLoop.exec(); + + if(!eventLoop.success()) + { + QMessageBox::warning(this, tr("Could not create Kontierung!"), tr("Could not create Kontierung:\n\n%0").arg(eventLoop.message())); + refresh(); + return; + } + + { + QStringList projekte = m_settings.value("projekte", QStringList()).toStringList(); + projekte.removeAll(ui->comboBoxProjekt->currentData().toString()); + projekte.prepend(ui->comboBoxProjekt->currentData().toString()); + m_settings.setValue("projekte", projekte); + } + + if(!ui->comboBoxSubprojekt->currentText().trimmed().isEmpty()) + { + QStringList subprojekte = m_settings.value("subprojekte", QStringList()).toStringList(); + subprojekte.removeAll(ui->comboBoxSubprojekt->currentText()); + subprojekte.prepend(ui->comboBoxSubprojekt->currentText()); + m_settings.setValue("subprojekte", subprojekte); + } + + if(!ui->comboBoxWorkpackage->currentText().trimmed().isEmpty()) + { + QStringList workpackages = m_settings.value("workpackages", QStringList()).toStringList(); + workpackages.removeAll(ui->comboBoxWorkpackage->currentText()); + workpackages.prepend(ui->comboBoxWorkpackage->currentText()); + m_settings.setValue("workpackages", workpackages); + } + + if(!ui->comboBoxText->currentText().trimmed().isEmpty()) + { + QStringList texte = m_settings.value("texte", QStringList()).toStringList(); + texte.removeAll(ui->comboBoxText->currentText()); + texte.prepend(ui->comboBoxText->currentText()); + m_settings.setValue("texte", texte); + } + + updateComboboxes(); + + refresh(); +} + +void MainWindow::pushButtonEndPressed() +{ + { + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::createBuchungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + m_erfassung.doCreateBuchung(m_userInfo.userId, ui->dateEditDate->date(), + timeNormalise(ui->timeEditTime->time()), QTime(0, 0), + QStringLiteral("G"), QStringLiteral("")); + eventLoop.exec(); + + if(!eventLoop.success()) + { + QMessageBox::warning(this, tr("Could not create Buchung!"), tr("Could not create Buchung:\n\n%0").arg(eventLoop.message())); + refresh(); + return; + } + } + + { + auto kontierung = *m_kontierungenModel->rbegin(); + Q_ASSERT(kontierung.timespan == QTime(0, 0)); + + EventLoopWithStatus eventLoop; + connect(&m_erfassung, &Zeiterfassung::updateKontierungFinished, &eventLoop, &EventLoopWithStatus::quitWithStatus); + + auto timespan = timeBetween(m_lastKontierungStart, ui->timeEditTime->time()); + qDebug() << "timespan" << timespan; + + m_erfassung.doUpdateKontierung(kontierung.id, m_userInfo.userId, kontierung.date, + kontierung.time, timespan, + kontierung.projekt, kontierung.subprojekt, + kontierung.workpackage, kontierung.text); + eventLoop.exec(); + + if(eventLoop.success()) + m_kontierungTime = timeAdd(m_kontierungTime, timespan); + else + { + QMessageBox::warning(this, tr("Could not update Kontierung!"), tr("Could not update Kontierung:\n\n%0").arg(eventLoop.message())); + refresh(); + return; + } + } + + refresh(); +} + +void MainWindow::validateEntries() +{ + ui->actionToday->setEnabled(true); + ui->actionRefresh->setEnabled(true); + ui->dateEditDate->setReadOnly(false); + ui->pushButtonStart->setText(tr("Start")); + + if(!ui->treeViewBuchungen->isEnabled()) + return; + + if(!ui->treeViewKontierungen->isEnabled()) + return; + + auto buchungenIter = m_buchungenModel->constBegin(); + auto kontierungenIter = m_kontierungenModel->constBegin(); + + m_kontierungTime = QTime(0, 0); + auto buchungTimespan = QTime(0, 0); + + while(true) + { + if(buchungenIter == m_buchungenModel->constEnd() && + kontierungenIter == m_kontierungenModel->constEnd()) + { + ui->timeEditTime->setEnabled(true); + ui->comboBoxProjekt->setEnabled(true); + ui->comboBoxSubprojekt->setEnabled(true); + ui->comboBoxWorkpackage->setEnabled(true); + ui->comboBoxText->setEnabled(true); + ui->pushButtonStart->setEnabled(true); + + return; + } + + if(buchungenIter == m_buchungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Missing Buchung."))); + return; + } + + auto startBuchung = *buchungenIter++; + qDebug() << "startBuchung" << startBuchung.time; + if(startBuchung.type != QStringLiteral("K")) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Expected Buchung for Kommen, instead got type %0\nBuchung ID: %1").arg(startBuchung.type).arg(startBuchung.id))); + return; + } + + m_lastKontierungStart = startBuchung.time; + + if(kontierungenIter == m_kontierungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Missing Kontierung."))); + return; + } + + auto kontierung = *kontierungenIter++; + if(kontierung.time != m_kontierungTime) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Expected time %0 but got %1 Kontierung.\nKontierung ID: %2") + .arg(m_kontierungTime.toString("HH:mm:ss")) + .arg(kontierung.time.toString("HH:mm:ss")) + .arg(kontierung.id))); + return; + } + + if(kontierung.timespan == QTime(0, 0)) + { + if(buchungenIter != m_buchungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("There is another Buchung after an unfinished Kontierung.\nBuchung ID: %0\nKontierung ID: %1") + .arg(buchungenIter->id) + .arg(kontierung.id))); + return; + } + + if(kontierungenIter != m_kontierungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("There is another Kontierung after an unfinished Kontierung.\nKontierung ID: %0\nKontierung ID: %1") + .arg(kontierungenIter->id) + .arg(kontierung.id))); + return; + } + + ui->pushButtonStart->setText(tr("Switch")); + ui->pushButtonEnd->setEnabled(true); + goto after; + } + else + { + m_kontierungTime = timeAdd(m_kontierungTime, kontierung.timespan); + m_lastKontierungStart = timeAdd(m_lastKontierungStart, kontierung.timespan); + + if(buchungenIter == m_buchungenModel->constEnd()) + { + while(true) + { + if(kontierungenIter == m_kontierungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("The last Kontierung is finished without Gehen-Buchung\nKontierung ID: %0") + .arg(kontierung.id))); + return; + } + + kontierung = *kontierungenIter++; + if(kontierung.time != m_kontierungTime) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Expected time %0 but got %1 Kontierung.\nKontierung ID: %2") + .arg(m_kontierungTime.toString("HH:mm:ss")) + .arg(kontierung.time.toString("HH:mm:ss")) + .arg(kontierung.id))); + return; + } + + if(kontierung.timespan == QTime(0, 0)) + { + if(kontierungenIter != m_kontierungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("There is another Kontierung after an unfinished Kontierung.\nKontierung ID: %0\nKontierung ID: %1") + .arg(kontierung.id) + .arg(kontierungenIter->id))); + return; + } + + ui->pushButtonStart->setText(tr("Switch")); + ui->pushButtonEnd->setEnabled(true); + goto after; + } + else + { + m_kontierungTime = timeAdd(m_kontierungTime, kontierung.timespan); + m_lastKontierungStart = timeAdd(m_lastKontierungStart, kontierung.timespan); + } + } + } + else + { + auto endBuchung = *buchungenIter++; + qDebug() << "endBuchung" << endBuchung.time; + if(endBuchung.type != QStringLiteral("G")) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Expected Buchung for Gehen, instead got type %0\nBuchung ID: %1").arg(endBuchung.type).arg(endBuchung.id))); + return; + } + + buchungTimespan = timeAdd(buchungTimespan, timeBetween(startBuchung.time, endBuchung.time)); + qDebug() << "buchungTimespan" << buchungTimespan; + + while(m_kontierungTime < buchungTimespan) + { + if(kontierungenIter == m_kontierungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Missing Kontierung! Time not filled: %0 - %1") + .arg(m_kontierungTime.toString("HH:mm:ss")) + .arg(buchungTimespan.toString("HH:mm:ss")))); + return; + } + + kontierung = *kontierungenIter++; + if(kontierung.time != m_kontierungTime) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("Expected time %0 but got %1 Kontierung.\nKontierung ID: %2") + .arg(m_kontierungTime.toString("HH:mm:ss")) + .arg(kontierung.time.toString("HH:mm:ss")) + .arg(kontierung.id))); + return; + } + + if(kontierung.timespan == QTime(0, 0)) + { + if(buchungenIter != m_buchungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("There is another Buchung after an unfinished Kontierung.\nBuchung ID: %0\nKontierung ID: %1") + .arg(buchungenIter->id) + .arg(kontierung.id))); + return; + } + + if(kontierungenIter != m_kontierungenModel->constEnd()) + { + QMessageBox::warning(this, tr("Illegal state!"), tr("Your Buchungen and Kontierungen for this day are in an invalid state:\n\n%0") + .arg(tr("There is another Kontierung after an unfinished Kontierung.\nKontierung ID: %0\nKontierung ID: %1") + .arg(kontierungenIter->id) + .arg(kontierung.id))); + return; + } + + ui->pushButtonStart->setText(tr("Switch")); + ui->pushButtonEnd->setEnabled(true); + goto after; + } + else + { + m_kontierungTime = timeAdd(m_kontierungTime, kontierung.timespan); + } + } + } + } + } + + after: + + qDebug() << "m_kontierTime" << m_kontierungTime; + + ui->timeEditTime->setEnabled(true); + ui->comboBoxProjekt->setEnabled(true); + ui->comboBoxSubprojekt->setEnabled(true); + ui->comboBoxWorkpackage->setEnabled(true); + ui->comboBoxText->setEnabled(true); + ui->pushButtonStart->setEnabled(true); +} + +void MainWindow::updateComboboxes() +{ + ui->comboBoxProjekt->clear(); + + { + auto preferedProjekte = m_settings.value("projekte", QStringList()).toStringList(); + + for(const auto &preferedProjekt : preferedProjekte) + { + for(const auto &projekt : m_projekte) + { + if(preferedProjekt == projekt.value) + ui->comboBoxProjekt->addItem(projekt.label % " (" % projekt.value % ')', projekt.value); + } + } + + if(preferedProjekte.count()) + { + ui->comboBoxProjekt->addItem(QStringLiteral("--------------")); + + auto model = qobject_cast(ui->comboBoxProjekt->model()); + auto item = model->item(ui->comboBoxProjekt->count() - 1); + item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); + } + + for(const auto &projekt : m_projekte) + { + if(!preferedProjekte.contains(projekt.value)) + ui->comboBoxProjekt->addItem(projekt.label % " (" % projekt.value % ')', projekt.value); + } + } + + ui->comboBoxSubprojekt->clear(); + + { + auto subprojekte = m_settings.value("subprojekte", QStringList()).toStringList(); + for(const auto &subprojekt : subprojekte) + ui->comboBoxSubprojekt->addItem(subprojekt); + if(subprojekte.count()) + ui->comboBoxSubprojekt->setCurrentText(QString()); + } + + ui->comboBoxWorkpackage->clear(); + + { + auto workpackages = m_settings.value("workpackages", QStringList()).toStringList(); + for(const auto &workpackage : workpackages) + ui->comboBoxWorkpackage->addItem(workpackage); + if(workpackages.count()) + ui->comboBoxWorkpackage->setCurrentText(QString()); + } + + ui->comboBoxText->clear(); + + { + auto texte = m_settings.value("texte", QStringList()).toStringList(); + for(const auto &text : texte) + ui->comboBoxText->addItem(text); + if(texte.count()) + ui->comboBoxText->setCurrentText(QString()); + } +} + +int MainWindow::timeToSeconds(const QTime &time) +{ + return QTime(0, 0).secsTo(time); +} + +QTime MainWindow::timeBetween(const QTime &l, const QTime &r) +{ + Q_ASSERT(l <= r); + return QTime(0, 0).addSecs(l.secsTo(r)); +} + +QTime MainWindow::timeAdd(const QTime &l, const QTime &r) +{ + Q_ASSERT(timeToSeconds(l) + timeToSeconds(r) < 86400); + return l.addSecs(QTime(0, 0).secsTo(r)); +} + +QTime MainWindow::timeNormalise(const QTime &time) +{ + return time.addSecs(-time.second()); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..65eaa87 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,57 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "zeiterfassung.h" + +namespace Ui { class MainWindow; } +class BuchungenModel; +class KontierungenModel; + +class QSettings; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QSettings &settings, Zeiterfassung &erfassung, const Zeiterfassung::UserInfo &userInfo, QWidget *parent = 0); + ~MainWindow(); + +private Q_SLOTS: + void refresh(); + void getProjekteFinished(bool success, const QString &message, const QVector &projekte); + void refreshBuchungenFinished(bool success, const QString &message); + void refreshKontierungenFinished(bool success, const QString &message); + void contextMenuBuchung(const QPoint &pos); + void contextMenuKontierung(const QPoint &pos); + void pushButtonPrevPressed(); + void pushButtonNextPressed(); + void pushButtonStartPressed(); + void pushButtonEndPressed(); + +private: + void validateEntries(); + void updateComboboxes(); + + static int timeToSeconds(const QTime &time); + static QTime timeBetween(const QTime &l, const QTime &r); + static QTime timeAdd(const QTime &l, const QTime &r); + static QTime timeNormalise(const QTime &time); + + Ui::MainWindow *ui; + QSettings &m_settings; + Zeiterfassung &m_erfassung; + const Zeiterfassung::UserInfo &m_userInfo; + QVector m_projekte; + + BuchungenModel *m_buchungenModel; + KontierungenModel *m_kontierungenModel; + + bool m_flag; + QTime m_kontierungTime; + QTime m_lastKontierungStart; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..01ff48d --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,322 @@ + + + MainWindow + + + + 0 + 0 + 1242 + 413 + + + + MainWindow + + + + + + + + + true + + + + + + + + + + + :/zeiterfassung/images/previous.png:/zeiterfassung/images/previous.png + + + + + + + + + + + :/zeiterfassung/images/next.png:/zeiterfassung/images/next.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + + + + false + + + + 200 + 0 + + + + + + + + false + + + + 150 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + false + + + + 150 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + false + + + + 150 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + false + + + Start + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + End + + + + + + + + + Qt::Vertical + + + + Buchungen + + + + + + false + + + Qt::CustomContextMenu + + + + + + + + Kontierungen + + + + + + false + + + Qt::CustomContextMenu + + + + + + + + + + + + + 0 + 0 + 1242 + 22 + + + + + &File + + + + + + &About + + + + + + + + &View + + + + + + + + + + + + + :/zeiterfassung/images/quit.png:/zeiterfassung/images/quit.png + + + &Quit + + + + + About &Me + + + + + About &zeiterfassung + + + + + About &Qt + + + + + false + + + + :/zeiterfassung/images/today.png:/zeiterfassung/images/today.png + + + &Today + + + + + false + + + + :/zeiterfassung/images/refresh.png:/zeiterfassung/images/refresh.png + + + &Refresh + + + + + + + + + actionQuit + triggered() + MainWindow + close() + + + -1 + -1 + + + 458 + 296 + + + + + diff --git a/models/buchungenmodel.cpp b/models/buchungenmodel.cpp new file mode 100644 index 0000000..1abf472 --- /dev/null +++ b/models/buchungenmodel.cpp @@ -0,0 +1,106 @@ +#include "buchungenmodel.h" + +BuchungenModel::BuchungenModel(Zeiterfassung &erfassung, QObject *parent) : + QAbstractListModel(parent), + m_erfassung(erfassung) +{ + +} + +int BuchungenModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_buchungen.count(); +} + +int BuchungenModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return 5; +} + +QVariant BuchungenModel::data(const QModelIndex &index, int role) const +{ + Q_ASSERT(index.row() < m_buchungen.count()); + const auto &buchung = m_buchungen.at(index.row()); + + switch(role) + { + case Qt::DisplayRole: + case Qt::EditRole: + switch(index.column()) + { + case 0: return buchung.id; + case 1: return buchung.time; + case 2: return buchung.timespan; + case 3: return buchung.type; + case 4: return buchung.text; + } + } + + return QVariant(); +} + +QVariant BuchungenModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch(orientation) + { + case Qt::Horizontal: + switch(role) + { + case Qt::DisplayRole: + case Qt::EditRole: + switch(section) + { + case 0: return tr("ID"); + case 1: return tr("Time"); + case 2: return tr("Timespan"); + case 3: return tr("Type"); + case 4: return tr("Text"); + } + } + } + + return QVariant(); +} + +Zeiterfassung::Buchung BuchungenModel::getBuchung(const QModelIndex &index) const +{ + if(!index.isValid()) + return Zeiterfassung::Buchung(); + + Q_ASSERT(index.row() <= m_buchungen.count()); + return m_buchungen.at(index.row()); +} + +bool BuchungenModel::refresh(int userId, const QDate &from, const QDate &to) +{ + if(!m_erfassung.doGetBuchungen(userId, from, to)) + return false; + + beginResetModel(); + m_buchungen.clear(); + endResetModel(); + + connect(&m_erfassung, &Zeiterfassung::getBuchungenFinished, + this, &BuchungenModel::getBuchungenFinished); + + return true; +} + +void BuchungenModel::getBuchungenFinished(bool success, const QString &message, const QVector &buchungen) +{ + disconnect(&m_erfassung, &Zeiterfassung::getBuchungenFinished, + this, &BuchungenModel::getBuchungenFinished); + + if(success) + { + beginResetModel(); + m_buchungen = buchungen; + endResetModel(); + } + + Q_EMIT refreshFinished(success, message); +} diff --git a/models/buchungenmodel.h b/models/buchungenmodel.h new file mode 100644 index 0000000..115b1af --- /dev/null +++ b/models/buchungenmodel.h @@ -0,0 +1,68 @@ +#ifndef BUCHUNGENMODEL_H +#define BUCHUNGENMODEL_H + +#include +#include + +#include "zeiterfassung.h" + +class BuchungenModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit BuchungenModel(Zeiterfassung &erfassung, QObject *parent = nullptr); + + // QAbstractItemModel interface + int rowCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + + Zeiterfassung::Buchung getBuchung(const QModelIndex &index) const; + + bool refresh(int userId, const QDate &from, const QDate &to); + + typedef QVector::iterator iterator; + typedef QVector::const_iterator const_iterator; + typedef QVector::reverse_iterator reverse_iterator; + typedef QVector::const_reverse_iterator const_reverse_iterator; + +#if !defined(QT_STRICT_ITERATORS) || defined(Q_QDOC) + //inline iterator begin() { return m_buchungen.begin(); } + inline const_iterator begin() const Q_DECL_NOTHROW { return m_buchungen.begin(); } + inline const_iterator cbegin() const Q_DECL_NOTHROW { return m_buchungen.cbegin(); } + inline const_iterator constBegin() const Q_DECL_NOTHROW { return m_buchungen.constBegin(); } + //inline iterator end() { return m_buchungen.end(); } + inline const_iterator end() const Q_DECL_NOTHROW { return m_buchungen.end(); } + inline const_iterator cend() const Q_DECL_NOTHROW { return m_buchungen.cend(); } + inline const_iterator constEnd() const Q_DECL_NOTHROW { return m_buchungen.constEnd(); } +#else + //inline iterator begin(iterator = iterator()) { return m_buchungen.begin(); } + inline const_iterator begin(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_buchungen.begin(); } + inline const_iterator cbegin(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return d->constBegin(); } + inline const_iterator constBegin(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_buchungen.constBegin(); } + //inline iterator end(iterator = iterator()) { return m_buchungen.end(); } + inline const_iterator end(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_buchungen.end(); } + inline const_iterator cend(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_buchungen.cend(); } + inline const_iterator constEnd(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_buchungen.constEnd(); } +#endif + //reverse_iterator rbegin() { return m_buchungen.rbegin(); } + //reverse_iterator rend() { return m_buchungen.rbegin(); } + const_reverse_iterator rbegin() const Q_DECL_NOTHROW { return m_buchungen.rbegin(); } + const_reverse_iterator rend() const Q_DECL_NOTHROW { return m_buchungen.rend(); } + const_reverse_iterator crbegin() const Q_DECL_NOTHROW { return m_buchungen.crbegin(); } + const_reverse_iterator crend() const Q_DECL_NOTHROW { return m_buchungen.crend(); } + +Q_SIGNALS: + void refreshFinished(bool success, const QString &message); + +private Q_SLOTS: + void getBuchungenFinished(bool success, const QString &message, const QVector &buchungen); + +private: + Zeiterfassung &m_erfassung; + QVector m_buchungen; +}; + +#endif // BUCHUNGENMODEL_H diff --git a/models/kontierungenmodel.cpp b/models/kontierungenmodel.cpp new file mode 100644 index 0000000..7ba5194 --- /dev/null +++ b/models/kontierungenmodel.cpp @@ -0,0 +1,110 @@ +#include "kontierungenmodel.h" + +KontierungenModel::KontierungenModel(Zeiterfassung &erfassung, QObject *parent) : + QAbstractListModel(parent), + m_erfassung(erfassung) +{ + +} + +int KontierungenModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_kontierungen.count(); +} + +int KontierungenModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return 7; +} + +QVariant KontierungenModel::data(const QModelIndex &index, int role) const +{ + Q_ASSERT(index.row() < m_kontierungen.count()); + const auto &kontierung = m_kontierungen.at(index.row()); + + switch(role) + { + case Qt::DisplayRole: + case Qt::EditRole: + switch(index.column()) + { + case 0: return kontierung.id; + case 1: return kontierung.time; + case 2: return kontierung.timespan; + case 3: return kontierung.projekt; + case 4: return kontierung.subprojekt; + case 5: return kontierung.workpackage; + case 6: return kontierung.text; + } + } + + return QVariant(); +} + +QVariant KontierungenModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch(orientation) + { + case Qt::Horizontal: + switch(role) + { + case Qt::DisplayRole: + case Qt::EditRole: + switch(section) + { + case 0: return tr("ID"); + case 1: return tr("Time"); + case 2: return tr("Timespan"); + case 3: return tr("Projekt"); + case 4: return tr("Subprojekt"); + case 5: return tr("Workpackage"); + case 6: return tr("Text"); + } + } + } + + return QVariant(); +} + +Zeiterfassung::Kontierung KontierungenModel::getKontierung(const QModelIndex &index) const +{ + if(!index.isValid()) + return Zeiterfassung::Kontierung(); + + Q_ASSERT(index.row() <= m_kontierungen.count()); + return m_kontierungen.at(index.row()); +} + +bool KontierungenModel::refresh(int userId, const QDate &from, const QDate &to) +{ + if(!m_erfassung.doGetKontierungen(userId, from, to)) + return false; + + beginResetModel(); + m_kontierungen.clear(); + endResetModel(); + + connect(&m_erfassung, &Zeiterfassung::getKontierungenFinished, + this, &KontierungenModel::getKontierungenFinished); + + return true; +} + +void KontierungenModel::getKontierungenFinished(bool success, const QString &message, const QVector &kontierungen) +{ + disconnect(&m_erfassung, &Zeiterfassung::getKontierungenFinished, + this, &KontierungenModel::getKontierungenFinished); + + if(success) + { + beginResetModel(); + m_kontierungen = kontierungen; + endResetModel(); + } + + Q_EMIT refreshFinished(success, message); +} diff --git a/models/kontierungenmodel.h b/models/kontierungenmodel.h new file mode 100644 index 0000000..a666025 --- /dev/null +++ b/models/kontierungenmodel.h @@ -0,0 +1,68 @@ +#ifndef KONTIERUNGENMODEL_H +#define KONTIERUNGENMODEL_H + +#include +#include + +#include "zeiterfassung.h" + +class KontierungenModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit KontierungenModel(Zeiterfassung &erfassung, QObject *parent = nullptr); + + // QAbstractItemModel interface + int rowCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + + Zeiterfassung::Kontierung getKontierung(const QModelIndex &index) const; + + bool refresh(int userId, const QDate &from, const QDate &to); + + typedef QVector::iterator iterator; + typedef QVector::const_iterator const_iterator; + typedef QVector::reverse_iterator reverse_iterator; + typedef QVector::const_reverse_iterator const_reverse_iterator; + +#if !defined(QT_STRICT_ITERATORS) || defined(Q_QDOC) + //inline iterator begin() { return m_kontierungen.begin(); } + inline const_iterator begin() const Q_DECL_NOTHROW { return m_kontierungen.begin(); } + inline const_iterator cbegin() const Q_DECL_NOTHROW { return m_kontierungen.cbegin(); } + inline const_iterator constBegin() const Q_DECL_NOTHROW { return m_kontierungen.constBegin(); } + //inline iterator end() { return m_kontierungen.end(); } + inline const_iterator end() const Q_DECL_NOTHROW { return m_kontierungen.end(); } + inline const_iterator cend() const Q_DECL_NOTHROW { return m_kontierungen.cend(); } + inline const_iterator constEnd() const Q_DECL_NOTHROW { return m_kontierungen.constEnd(); } +#else + //inline iterator begin(iterator = iterator()) { return m_kontierungen.begin(); } + inline const_iterator begin(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_kontierungen.begin(); } + inline const_iterator cbegin(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return d->constBegin(); } + inline const_iterator constBegin(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_kontierungen.constBegin(); } + //inline iterator end(iterator = iterator()) { return m_kontierungen.end(); } + inline const_iterator end(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_kontierungen.end(); } + inline const_iterator cend(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_kontierungen.cend(); } + inline const_iterator constEnd(const_iterator = const_iterator()) const Q_DECL_NOTHROW { return m_kontierungen.constEnd(); } +#endif + //reverse_iterator rbegin() { return m_kontierungen.rbegin(); } + //reverse_iterator rend() { return m_kontierungen.rbegin(); } + const_reverse_iterator rbegin() const Q_DECL_NOTHROW { return m_kontierungen.rbegin(); } + const_reverse_iterator rend() const Q_DECL_NOTHROW { return m_kontierungen.rend(); } + const_reverse_iterator crbegin() const Q_DECL_NOTHROW { return m_kontierungen.crbegin(); } + const_reverse_iterator crend() const Q_DECL_NOTHROW { return m_kontierungen.crend(); } + +Q_SIGNALS: + void refreshFinished(bool success, const QString &message); + +private Q_SLOTS: + void getKontierungenFinished(bool success, const QString &message, const QVector &kontierungen); + +private: + Zeiterfassung &m_erfassung; + QVector m_kontierungen; +}; + +#endif // KONTIERUNGENMODEL_H diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..967df7a --- /dev/null +++ b/resources.qrc @@ -0,0 +1,11 @@ + + + images/splash.png + images/authentication.png + images/next.png + images/previous.png + images/quit.png + images/refresh.png + images/today.png + + diff --git a/zeiterfassung.conf b/zeiterfassung.conf new file mode 100644 index 0000000..36a5d44 --- /dev/null +++ b/zeiterfassung.conf @@ -0,0 +1,8 @@ +[General] +password=HAHA +projekte=0000001142, 0000010001, SONSTIGES +subprojekte= +texte= +url=http://localhost:8080/evoApps/ +username=danielb +workpackages=[D.1315], [M.0200] diff --git a/zeiterfassung.cpp b/zeiterfassung.cpp new file mode 100644 index 0000000..877943e --- /dev/null +++ b/zeiterfassung.cpp @@ -0,0 +1,817 @@ +#include "zeiterfassung.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QString QJsonValue::toString() const{ return toString(QString()); } + +Zeiterfassung::Zeiterfassung(const QString &url, QObject *parent) : + QObject(parent), + m_url(url), + m_manager(new QNetworkAccessManager(this)), + m_replies { Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, + Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR } +{ +} + +const QString &Zeiterfassung::url() const +{ + return m_url; +} + +void Zeiterfassung::setUrl(const QString &url) +{ + if(m_url != url) + Q_EMIT urlChanged(m_url = url); +} + +bool Zeiterfassung::doLoginPage() +{ + if(m_replies.login) + { + qWarning() << "another loginPage already processing!"; + return false; + } + + QNetworkRequest request(QUrl(m_url % "pages/login.jsp")); + + m_replies.loginPage = m_manager->get(request); + connect(m_replies.loginPage, &QNetworkReply::finished, this, &Zeiterfassung::loginPageRequestFinished); + + return true; +} + +bool Zeiterfassung::doLogin(const QString &username, const QString &password) +{ + if(m_replies.login) + { + qWarning() << "another login already processing!"; + return false; + } + + QNetworkRequest request(QUrl(m_url % "pages/j_spring_security_check")); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded")); + request.setMaximumRedirectsAllowed(0); + + m_replies.login = m_manager->post(request, QStringLiteral("j_username=%0&j_password=%1&login=Anmelden").arg(username).arg(password).toUtf8()); + connect(m_replies.login, &QNetworkReply::finished, this, &Zeiterfassung::loginRequestFinished); + + return true; +} + +bool Zeiterfassung::doUserInfo() +{ + if(m_replies.userInfo) + { + qWarning() << "another userInfo already processing!"; + return false; + } + + QNetworkRequest request(QUrl(m_url % "json/evoAppsUserInfoDialogController/load-EvoAppsUserInfoTO")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("home")); + + m_replies.userInfo = m_manager->get(request); + connect(m_replies.userInfo, &QNetworkReply::finished, this, &Zeiterfassung::userInfoRequestFinished); + + return true; +} + +bool Zeiterfassung::doGetBuchungen(int userId, const QDate &start, const QDate &end) +{ + if(m_replies.getBuchungen) + { + qWarning() << "another getBuchungen already processing!"; + return false; + } + + QNetworkRequest request(QUrl(QStringLiteral("%0json/bookings?start=%1&end=%2&pnrLst=%3") + .arg(m_url) + .arg(start.toString(QStringLiteral("yyyyMMdd"))) + .arg(end.toString(QStringLiteral("yyyyMMdd"))) + .arg(userId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + m_replies.getBuchungen = m_manager->get(request); + connect(m_replies.getBuchungen, &QNetworkReply::finished, + this, &Zeiterfassung::getBuchungenRequestFinished); + + return true; +} + +bool Zeiterfassung::doCreateBuchung(int userId, const QDate &date, const QTime &time, const QTime ×pan, const QString &type, const QString &text) +{ + if(m_replies.createBuchung) + { + qWarning() << "another createBuchung already processing!"; + return false; + } + + QNetworkRequest request(QUrl(m_url % "json/booking")); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = date.toString("yyyyMMdd").toInt(); + obj[QStringLiteral("bookingTime")] = time.toString("Hmmss").toInt(); + obj[QStringLiteral("bookingTimespan")] = timespan.toString("Hmmss").toInt(); + obj[QStringLiteral("bookingType")] = type; + obj[QStringLiteral("hourCategory")] = QStringLiteral(""); + obj[QStringLiteral("empfEinh")] = QStringLiteral(""); + obj[QStringLiteral("bewEinh")] = QStringLiteral(""); + obj[QStringLiteral("text")] = text; + + m_replies.createBuchung = m_manager->post(request, QJsonDocument(obj).toJson()); + connect(m_replies.createBuchung, &QNetworkReply::finished, + this, &Zeiterfassung::createBuchungRequestFinished); + + return true; +} + +bool Zeiterfassung::doUpdateBuchung(int buchungId, int userId, const QDate &date, const QTime &time, const QTime ×pan, const QString &type, const QString &text) +{ + if(m_replies.updateBuchung) + { + qWarning() << "another updateBuchung already processing!"; + return false; + } + + QNetworkRequest request(QUrl(QStringLiteral("%0json/booking/%1").arg(m_url).arg(buchungId))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("bookingNr")] = buchungId; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = date.toString("yyyyMMdd").toInt(); + obj[QStringLiteral("bookingTime")] = time.toString("Hmmss").toInt(); + obj[QStringLiteral("bookingTimespan")] = timespan.toString("Hmmss").toInt(); + obj[QStringLiteral("bookingType")] = type; + obj[QStringLiteral("hourCategory")] = QStringLiteral(""); + obj[QStringLiteral("empfEinh")] = QStringLiteral(""); + obj[QStringLiteral("bewEinh")] = QStringLiteral(""); + obj[QStringLiteral("text")] = text; + + m_replies.updateBuchung = m_manager->put(request, QJsonDocument(obj).toJson()); + connect(m_replies.updateBuchung, &QNetworkReply::finished, + this, &Zeiterfassung::updateBuchungRequestFinished); + + return true; +} + +bool Zeiterfassung::doDeleteBuchung(int buchungId) +{ + if(m_replies.deleteBuchung) + { + qWarning() << "another deleteBuchung already processing!"; + return false; + } + + QNetworkRequest request(QUrl(QStringLiteral("%0json/booking/%1?text=") + .arg(m_url) + .arg(buchungId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + m_replies.deleteBuchung = m_manager->deleteResource(request); + connect(m_replies.deleteBuchung, &QNetworkReply::finished, + this, &Zeiterfassung::deleteBuchungRequestFinished); + + return true; +} + +bool Zeiterfassung::doGetKontierungen(int userId, const QDate &start, const QDate &end) +{ + if(m_replies.getKontierungen) + { + qWarning() << "another getKontierungen already processing!"; + return false; + } + + QNetworkRequest request(QUrl(QStringLiteral("%0json/azebooking?start=%1&end=%2&pnrLst=%3") + .arg(m_url) + .arg(start.toString(QStringLiteral("yyyyMMdd"))) + .arg(end.toString(QStringLiteral("yyyyMMdd"))) + .arg(userId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + m_replies.getKontierungen = m_manager->get(request); + connect(m_replies.getKontierungen, &QNetworkReply::finished, + this, &Zeiterfassung::getKontierungenRequestFinished); + + return true; +} + +bool Zeiterfassung::doCreateKontierung(int userId, const QDate &date, const QTime &time, const QTime ×pan, const QString &projekt, const QString &subprojekt, const QString &workpackage, const QString &text) +{ + if(m_replies.createKontierung) + { + qWarning() << "another createKontierung already processing!"; + return false; + } + + QNetworkRequest request(QUrl(m_url % "json/azebooking")); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("bookingNr")] = QJsonValue::Null; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = date.toString("yyyyMMdd").toInt(); + obj[QStringLiteral("bookingTime")] = time.toString("Hmmss").toInt(); + obj[QStringLiteral("bookingTimespan")] = timespan.toString("Hmmss").toInt(); + obj[QStringLiteral("text")] = text; + { + QJsonArray koWertList; + { + QJsonObject obj; + obj[QStringLiteral("value")] = projekt; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = subprojekt; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = workpackage; + koWertList << obj; + } + obj[QStringLiteral("koWertList")] = koWertList; + } + + m_replies.createKontierung = m_manager->post(request, QJsonDocument(obj).toJson()); + connect(m_replies.createKontierung, &QNetworkReply::finished, + this, &Zeiterfassung::createKontierungRequestFinished); + + return true; +} + +bool Zeiterfassung::doUpdateKontierung(int kontierungId, int userId, const QDate &date, const QTime &time, const QTime ×pan, const QString &projekt, const QString &subprojekt, const QString &workpackage, const QString &text) +{ + if(m_replies.updateKontierung) + { + qWarning() << "another updateKontierung already processing!"; + return false; + } + + QNetworkRequest request(QUrl(QStringLiteral("%0json/azebooking/%1").arg(m_url).arg(kontierungId))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("bookingNr")] = kontierungId; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = date.toString("yyyyMMdd").toInt(); + obj[QStringLiteral("bookingTime")] = time.toString("Hmmss").toInt(); + obj[QStringLiteral("bookingTimespan")] = timespan.toString("Hmmss").toInt(); + obj[QStringLiteral("bookingType")] = QJsonValue::Null; + obj[QStringLiteral("hourCategory")] = QJsonValue::Null; + obj[QStringLiteral("bewEinh")] = QJsonValue::Null; + obj[QStringLiteral("empfEinh")] = QJsonValue::Null; + obj[QStringLiteral("einstuf")] = 0; + obj[QStringLiteral("text")] = text; + { + QJsonArray koWertList; + { + QJsonObject obj; + obj[QStringLiteral("value")] = projekt; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = subprojekt; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = workpackage; + koWertList << obj; + } + obj[QStringLiteral("koWertList")] = koWertList; + } + + m_replies.updateKontierung = m_manager->put(request, QJsonDocument(obj).toJson()); + connect(m_replies.updateKontierung, &QNetworkReply::finished, + this, &Zeiterfassung::updateKontierungRequestFinished); + + return true; +} + +bool Zeiterfassung::doDeleteKontierung(int kontierungId) +{ + if(m_replies.deleteKontierung) + { + qWarning() << "another deleteKontierung already processing!"; + return false; + } + + QNetworkRequest request(QUrl(QStringLiteral("%0json/azebooking/%1") + .arg(m_url) + .arg(kontierungId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + m_replies.deleteKontierung = m_manager->deleteResource(request); + connect(m_replies.deleteKontierung, &QNetworkReply::finished, + this, &Zeiterfassung::deleteKontierungRequestFinished); + + return true; +} + +bool Zeiterfassung::doGetProjekte(int userId, QDate date) +{ + if(m_replies.getProjekte) + { + qWarning() << "another getProjekte already processing!"; + return false; + } + + QNetworkRequest request(QUrl(QStringLiteral("%0json/combobox?persnr=%1&date=%2&dqkey=KOST&kowert0=&kowert1=&kowert2=&term=") + .arg(m_url) + .arg(userId) + .arg(date.toString(QStringLiteral("yyyyMMdd"))))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + m_replies.getProjekte = m_manager->get(request); + connect(m_replies.getProjekte, &QNetworkReply::finished, this, &Zeiterfassung::getProjekteRequestFinished); + + return true; +} + +void Zeiterfassung::loginPageRequestFinished() +{ + if(m_replies.loginPage->error() != QNetworkReply::NoError) + { + Q_EMIT loginPageFinished(false, tr("Request error occured: %0").arg(m_replies.loginPage->error())); + goto end; + } + + if(!m_replies.loginPage->readAll().contains(QByteArrayLiteral("evoApps Anmeldung"))) + { + Q_EMIT loginPageFinished(false, tr("Could not find necessary keywords in login page!")); + goto end; + } + + Q_EMIT loginPageFinished(true, QString()); + + end: + m_replies.loginPage->deleteLater(); + m_replies.loginPage = Q_NULLPTR; +} + +void Zeiterfassung::loginRequestFinished() +{ + if(m_replies.login->error() != QNetworkReply::NoError) + { + Q_EMIT loginFinished(false, tr("Request error occured: %0").arg(m_replies.login->error())); + goto end; + } + + if(!m_replies.login->hasRawHeader(QByteArrayLiteral("Location"))) + { + Q_EMIT loginFinished(false, tr("Request did not contain a Location header.")); + goto end; + } + + { + auto location = m_replies.login->rawHeader(QByteArrayLiteral("Location")); + + if(location == QByteArrayLiteral("/evoApps/pages/home.jsp")) + { + Q_EMIT loginFinished(true, QString()); + goto end; + } + else if(location == QByteArrayLiteral("/evoApps/pages/login.jsp?error=user")) + { + Q_EMIT loginFinished(false, tr("Authentication failure. Please check username and password.")); + goto end; + } + else + { + Q_EMIT loginFinished(false, tr("An unknown authentication failure occured. Redirected to: %0").arg(QString(location))); + goto end; + } + } + + end: + m_replies.login->deleteLater(); + m_replies.login = Q_NULLPTR; +} + +void Zeiterfassung::userInfoRequestFinished() +{ + if(m_replies.userInfo->error() != QNetworkReply::NoError) + { + Q_EMIT userInfoFinished(false, tr("Request error occured: %0").arg(m_replies.userInfo->error()), UserInfo()); + goto end; + } + + { + QJsonParseError error; + auto document = QJsonDocument::fromJson(m_replies.userInfo->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT userInfoFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), UserInfo()); + goto end; + } + + if(!document.isObject()) + { + Q_EMIT userInfoFinished(false, tr("JSON document is not an object!"), UserInfo()); + goto end; + } + + auto rootObj = document.object(); + + if(!rootObj.contains(QStringLiteral("evoAppsUser"))) + { + Q_EMIT userInfoFinished(false, tr("JSON does not contain evoAppsUser!"), UserInfo()); + goto end; + } + + auto evoAppsUser = rootObj.value(QStringLiteral("evoAppsUser")); + + if(!evoAppsUser.isObject()) + { + Q_EMIT userInfoFinished(false, tr("evoAppsUser is not an object!"), UserInfo()); + goto end; + } + + auto evoAppsUserObj = evoAppsUser.toObject(); + + Q_EMIT userInfoFinished(true, QString(), { + evoAppsUserObj.value(QStringLiteral("persNr")).toInt(), + evoAppsUserObj.value(QStringLiteral("email")).toString(), + evoAppsUserObj.value(QStringLiteral("longUsername")).toString(), + evoAppsUserObj.value(QStringLiteral("text")).toString(), + evoAppsUserObj.value(QStringLiteral("username")).toString() + }); + } + + end: + m_replies.userInfo->deleteLater(); + m_replies.userInfo = Q_NULLPTR; +} + +void Zeiterfassung::getBuchungenRequestFinished() +{ + if(m_replies.getBuchungen->error() != QNetworkReply::NoError) + { + Q_EMIT getBuchungenFinished(false, tr("Request error occured: %0").arg(m_replies.getBuchungen->error()), {}); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_replies.getBuchungen->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT getBuchungenFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), {}); + goto end; + } + + if(!document.isArray()) + { + Q_EMIT getBuchungenFinished(false, tr("JSON document is not an array!"), {}); + goto end; + } + + auto arr = document.array(); + QVector buchungen; + + for(const auto &val : arr) + { + auto obj = val.toObject(); + + buchungen.append({ + obj.value(QStringLiteral("bookingNr")).toInt(), + QDate::fromString(QString::number(obj.value(QStringLiteral("bookingDate")).toInt()), QStringLiteral("yyyyMMdd")), + QTime::fromString(QString("%0").arg(obj.value(QStringLiteral("bookingTime")).toInt(), 6, 10, QChar('0')), QStringLiteral("HHmmss")), + QTime::fromString(QString("%0").arg(obj.value(QStringLiteral("bookingTimespan")).toInt(), 6, 10, QChar('0')), QStringLiteral("HHmmss")), + obj.value(QStringLiteral("bookingType")).toString(), + obj.value(QStringLiteral("text")).toString() + }); + } + + Q_EMIT getBuchungenFinished(true, QString(), buchungen); + } + + end: + m_replies.getBuchungen->deleteLater(); + m_replies.getBuchungen = Q_NULLPTR; +} + +void Zeiterfassung::createBuchungRequestFinished() +{ + if(m_replies.createBuchung->error() != QNetworkReply::NoError) + { + Q_EMIT createBuchungFinished(false, tr("Request error occured: %0").arg(m_replies.createBuchung->error()), -1); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_replies.createBuchung->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT createBuchungFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), -1); + goto end; + } + + if(!document.isObject()) + { + Q_EMIT createBuchungFinished(false, tr("JSON document is not an object!"), -1); + goto end; + } + + auto obj = document.object(); + + if(!obj.contains(QStringLiteral("bookingNr"))) + { + Q_EMIT createBuchungFinished(false, tr("JSON does not contain bookingNr!"), -1); + goto end; + } + + auto buchungId = obj.value(QStringLiteral("bookingNr")).toInt(); + + Q_EMIT createBuchungFinished(true, QString(), buchungId); + } + + end: + m_replies.createBuchung->deleteLater(); + m_replies.createBuchung = Q_NULLPTR; +} + +void Zeiterfassung::updateBuchungRequestFinished() +{ + if(m_replies.updateBuchung->error() != QNetworkReply::NoError) + { + Q_EMIT updateBuchungFinished(false, tr("Request error occured: %0").arg(m_replies.updateBuchung->error()), -1); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_replies.updateBuchung->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT updateBuchungFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), -1); + goto end; + } + + if(!document.isObject()) + { + Q_EMIT updateBuchungFinished(false, tr("JSON document is not an object!"), -1); + goto end; + } + + auto obj = document.object(); + + if(!obj.contains(QStringLiteral("bookingNr"))) + { + Q_EMIT updateBuchungFinished(false, tr("JSON does not contain bookingNr!"), -1); + goto end; + } + + auto buchungId = obj.value(QStringLiteral("bookingNr")).toInt(); + + Q_EMIT updateBuchungFinished(true, QString(), buchungId); + } + + end: + m_replies.updateBuchung->deleteLater(); + m_replies.updateBuchung = Q_NULLPTR; +} + +void Zeiterfassung::deleteBuchungRequestFinished() +{ + if(m_replies.deleteBuchung->error() != QNetworkReply::NoError) + { + Q_EMIT deleteBuchungFinished(false, tr("Request error occured: %0").arg(m_replies.deleteBuchung->error())); + goto end; + } + + //should be empty, so nothing to check... + Q_EMIT deleteBuchungFinished(true, QString()); + + end: + m_replies.deleteBuchung->deleteLater(); + m_replies.deleteBuchung = Q_NULLPTR; +} + +void Zeiterfassung::getKontierungenRequestFinished() +{ + if(m_replies.getKontierungen->error() != QNetworkReply::NoError) + { + Q_EMIT getKontierungenFinished(false, tr("Request error occured: %0").arg(m_replies.getKontierungen->error()), {}); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_replies.getKontierungen->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT getKontierungenFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), {}); + goto end; + } + + if(!document.isArray()) + { + Q_EMIT getKontierungenFinished(false, tr("JSON document is not an array!"), {}); + goto end; + } + + auto arr = document.array(); + QVector kontierungen; + + for(const auto &val : arr) + { + auto obj = val.toObject(); + + auto koWertList = obj.value(QStringLiteral("koWertList")).toArray(); + + kontierungen.append({ + obj.value(QStringLiteral("bookingNr")).toInt(), + QDate::fromString(QString::number(obj.value(QStringLiteral("bookingDate")).toInt()), QStringLiteral("yyyyMMdd")), + QTime::fromString(QString("%0").arg(obj.value(QStringLiteral("bookingTime")).toInt(), 6, 10, QChar('0')), QStringLiteral("HHmmss")), + QTime::fromString(QString("%0").arg(obj.value(QStringLiteral("bookingTimespan")).toInt(), 6, 10, QChar('0')), QStringLiteral("HHmmss")), + obj.value(QStringLiteral("text")).toString(), + koWertList.at(0).toObject().value(QStringLiteral("value")).toString(), + koWertList.at(1).toObject().value(QStringLiteral("value")).toString(), + koWertList.at(2).toObject().value(QStringLiteral("value")).toString() + }); + } + + Q_EMIT getKontierungenFinished(true, QString(), kontierungen); + } + + end: + m_replies.getKontierungen->deleteLater(); + m_replies.getKontierungen = Q_NULLPTR; +} + +void Zeiterfassung::createKontierungRequestFinished() +{ + if(m_replies.createKontierung->error() != QNetworkReply::NoError) + { + Q_EMIT createKontierungFinished(false, tr("Request error occured: %0").arg(m_replies.createKontierung->error()), -1); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_replies.createKontierung->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT createKontierungFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), -1); + goto end; + } + + if(!document.isObject()) + { + Q_EMIT createKontierungFinished(false, tr("JSON document is not an object!"), -1); + goto end; + } + + auto obj = document.object(); + + if(!obj.contains(QStringLiteral("bookingNr"))) + { + Q_EMIT createKontierungFinished(false, tr("JSON does not contain bookingNr!"), -1); + goto end; + } + + auto kontierungId = obj.value(QStringLiteral("bookingNr")).toInt(); + + Q_EMIT createKontierungFinished(true, QString(), kontierungId); + } + + end: + m_replies.createKontierung->deleteLater(); + m_replies.createKontierung = Q_NULLPTR; +} + +void Zeiterfassung::updateKontierungRequestFinished() +{ + if(m_replies.updateKontierung->error() != QNetworkReply::NoError) + { + Q_EMIT updateKontierungFinished(false, tr("Request error occured: %0").arg(m_replies.updateKontierung->error()), -1); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_replies.updateKontierung->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT updateKontierungFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), -1); + goto end; + } + + if(!document.isObject()) + { + Q_EMIT updateKontierungFinished(false, tr("JSON document is not an object!"), 0); + goto end; + } + + auto obj = document.object(); + + if(!obj.contains(QStringLiteral("bookingNr"))) + { + Q_EMIT updateKontierungFinished(false, tr("JSON does not contain bookingNr!"), 0); + goto end; + } + + auto kontierungId = obj.value(QStringLiteral("bookingNr")).toInt(); + + Q_EMIT updateKontierungFinished(true, QString(), kontierungId); + } + + end: + m_replies.updateKontierung->deleteLater(); + m_replies.updateKontierung = Q_NULLPTR; +} + +void Zeiterfassung::deleteKontierungRequestFinished() +{ + if(m_replies.deleteKontierung->error() != QNetworkReply::NoError) + { + Q_EMIT deleteKontierungFinished(false, tr("Request error occured: %0").arg(m_replies.deleteKontierung->error())); + goto end; + } + + //only contains deleted id, so nothing to check here + Q_EMIT deleteKontierungFinished(true, QString()); + + end: + m_replies.deleteKontierung->deleteLater(); + m_replies.deleteKontierung = Q_NULLPTR; +} + +void Zeiterfassung::getProjekteRequestFinished() +{ + if(m_replies.getProjekte->error() != QNetworkReply::NoError) + { + Q_EMIT getProjekteFinished(false, tr("Request error occured: %0").arg(m_replies.getProjekte->error()), {}); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_replies.getProjekte->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + Q_EMIT getProjekteFinished(false, tr("Parsing JSON failed: %0").arg(error.errorString()), {}); + goto end; + } + + if(!document.isObject()) + { + Q_EMIT getProjekteFinished(false, tr("JSON document is not an object!"), {}); + goto end; + } + + auto rootObj = document.object(); + + if(!rootObj.contains(QStringLiteral("elements"))) + { + Q_EMIT getProjekteFinished(false, tr("JSON does not contain elements!"), {}); + goto end; + } + + auto elements = rootObj.value(QStringLiteral("elements")); + + if(!elements.isArray()) + { + Q_EMIT getProjekteFinished(false, tr("elements is not an array!"), {}); + goto end; + } + + auto elementsArr = elements.toArray(); + QVector projekte; + + for(const auto &val : elementsArr) + { + auto obj = val.toObject(); + + projekte.append({ + obj.value(QStringLiteral("label")).toString(), + obj.value(QStringLiteral("value")).toString() + }); + } + + Q_EMIT getProjekteFinished(true, QString(), projekte); + } + + end: + m_replies.getProjekte->deleteLater(); + m_replies.getProjekte = Q_NULLPTR; +} diff --git a/zeiterfassung.h b/zeiterfassung.h new file mode 100644 index 0000000..5a668f4 --- /dev/null +++ b/zeiterfassung.h @@ -0,0 +1,142 @@ +#ifndef ZEITERFASSUNG_H +#define ZEITERFASSUNG_H + +#include +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +class Zeiterfassung : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged) + +public: + explicit Zeiterfassung(const QString &url, QObject *parent = 0); + + const QString &url() const; + void setUrl(const QString &url); + + struct UserInfo + { + int userId; + QString email; + QString longUsername; + QString text; + QString username; + }; + + struct Buchung + { + int id; + QDate date; + QTime time; + QTime timespan; + QString type; + QString text; + }; + + struct Kontierung + { + int id; + QDate date; + QTime time; + QTime timespan; + QString text; + QString projekt; + QString subprojekt; + QString workpackage; + }; + + struct Projekt + { + QString label; + QString value; + }; + +public Q_SLOTS: + bool doLoginPage(); + bool doLogin(const QString &username, const QString &password); + bool doUserInfo(); + + bool doGetBuchungen(int userId, const QDate &start, const QDate &end); + bool doCreateBuchung(int userId, const QDate &date, const QTime &time, const QTime ×pan, + const QString &type, const QString &text); + bool doUpdateBuchung(int buchungId, int userId, const QDate &date, const QTime &time, + const QTime ×pan, const QString &type, const QString &text); + bool doDeleteBuchung(int buchungId); + + bool doGetKontierungen(int userId, const QDate &start, const QDate &end); + bool doCreateKontierung(int userId, const QDate &date, const QTime &time, const QTime ×pan, + const QString &projekt, const QString &subprojekt, const QString &workpackage, + const QString &text); + bool doUpdateKontierung(int kontierungId, int userId, const QDate &date, const QTime &time, + const QTime ×pan, const QString &projekt, const QString &subprojekt, + const QString &workpackage, const QString &text); + bool doDeleteKontierung(int kontierungId); + + bool doGetProjekte(int userId, QDate date = QDate::currentDate()); + +Q_SIGNALS: + void urlChanged(const QString &url); + + void loginPageFinished(bool success, const QString &message); + void loginFinished(bool success, const QString &message); + void userInfoFinished(bool success, const QString &message, const UserInfo &userInfo); + + void getBuchungenFinished(bool success, const QString &message, const QVector &buchungen); + void createBuchungFinished(bool success, const QString &message, int buchungId); + void updateBuchungFinished(bool success, const QString &message, int buchungId); + void deleteBuchungFinished(bool success, const QString &message); + + void getKontierungenFinished(bool success, const QString &message, const QVector &kontierungen); + void createKontierungFinished(bool success, const QString &message, int buchungId); + void updateKontierungFinished(bool success, const QString &message, int buchungId); + void deleteKontierungFinished(bool success, const QString &message); + + void getProjekteFinished(bool success, const QString &message, const QVector &projekte); + +private Q_SLOTS: + void loginPageRequestFinished(); + void loginRequestFinished(); + void userInfoRequestFinished(); + + void getBuchungenRequestFinished(); + void createBuchungRequestFinished(); + void updateBuchungRequestFinished(); + void deleteBuchungRequestFinished(); + + void getKontierungenRequestFinished(); + void createKontierungRequestFinished(); + void updateKontierungRequestFinished(); + void deleteKontierungRequestFinished(); + + void getProjekteRequestFinished(); + +private: + QString m_url; + QNetworkAccessManager *m_manager; + + struct { + QNetworkReply *loginPage; + QNetworkReply *login; + QNetworkReply *userInfo; + + QNetworkReply *getBuchungen; + QNetworkReply *createBuchung; + QNetworkReply *updateBuchung; + QNetworkReply *deleteBuchung; + + QNetworkReply *getKontierungen; + QNetworkReply *createKontierung; + QNetworkReply *updateKontierung; + QNetworkReply *deleteKontierung; + + QNetworkReply *getProjekte; + } m_replies; +}; + +#endif // ZEITERFASSUNG_H diff --git a/zeiterfassung.pro b/zeiterfassung.pro new file mode 100755 index 0000000..72e140b --- /dev/null +++ b/zeiterfassung.pro @@ -0,0 +1,47 @@ +QT += network gui widgets + +CONFIG += c++11 +CONFIG -= app_bundle + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += main.cpp \ + zeiterfassung.cpp \ + mainwindow.cpp \ + dialogs/aboutmedialog.cpp \ + dialogs/authenticationdialog.cpp \ + dialogs/buchungdialog.cpp \ + models/buchungenmodel.cpp \ + dialogs/kontierungdialog.cpp \ + models/kontierungenmodel.cpp \ + eventloopwithstatus.cpp + +HEADERS += \ + zeiterfassung.h \ + mainwindow.h \ + dialogs/aboutmedialog.h \ + dialogs/authenticationdialog.h \ + dialogs/buchungdialog.h \ + models/buchungenmodel.h \ + dialogs/kontierungdialog.h \ + models/kontierungenmodel.h \ + eventloopwithstatus.h + +RESOURCES += \ + resources.qrc + +FORMS += \ + mainwindow.ui \ + dialogs/aboutmedialog.ui \ + dialogs/authenticationdialog.ui \ + dialogs/buchungdialog.ui \ + dialogs/kontierungdialog.ui