Compare commits

...

30 Commits

Author SHA1 Message Date
a6847f2661 Fixed the column names in CSV export. 2026-02-23 13:28:13 +01:00
3431e281c3 CSV import: Added "AGA"-> "erarbeitet", "ja"-> "bezahlt" and "teils"-> "teils/teils" replacements for ShareType values to support the new spreadsheet entries. 2026-02-23 13:27:53 +01:00
d800ecfed7 The calculation of how many harvest shares are expected in total. 2026-02-21 22:54:59 +01:00
6b383b8f55 Added more model tests and fixed the calculations for nPlacedBiddings, biddingSum and totalSharesWithBiddings if there are conflicting entries (shareType: "erarbeitet" but a bidding is placed). 2026-02-21 22:53:29 +01:00
85afc9b329 Converted non critical qCritical() calls into qInfo() calls. 2026-02-21 19:38:35 +01:00
f3e2dbf309 Added two simple tests for the table model. 2026-02-21 19:37:36 +01:00
2021830239 Setting the server URL for displaying the users bidding link on program start from settings. Doesn't get updated after settings have been changed. 2026-02-21 12:37:26 +01:00
74470ba605 Defaulting the bidding type to "" in new item dialog. 2026-02-21 12:27:15 +01:00
6cd205506f If no authToken is stored in settings but email and password are on startup the log_in route is triggered to receive a new token. This token will be used to access a protected API. 2026-02-20 16:44:58 +01:00
064da850c4 Clicking the sendInvite button triggers a post request to the server to send a bidding invite via mail. 2026-02-19 20:54:24 +01:00
06589e5a09 Cleaning up TableModel::updateBiddings function 2026-02-19 13:20:07 +01:00
d381e1ab8c Merging received bidding into the model. 2026-02-19 13:10:55 +01:00
440333b589 Routing the received biddings to the model. Just debug outputs there yet. 2026-02-19 11:18:46 +01:00
21b8de96d8 Minor refactoring to move JSON processing into JsonParser. 2026-02-19 11:02:10 +01:00
4519a2de3f Added missing #include to core file. 2026-02-19 10:59:39 +01:00
369addac44 Fixed the hard coded server url to login a user per access token. 2026-02-19 10:57:27 +01:00
f34e1521c4 Merge branch 'feature/onlineUserAccounts' into develop 2026-02-19 10:16:16 +01:00
7d31ac8806 When receiving online user credentials insert them into the model. 2026-02-19 09:10:06 +01:00
faf01d6e15 Online account can now be created from the EditItemDialog. The response isn't processed properly yet. 2026-02-18 11:24:40 +01:00
7a8859843e Added onlineID and accessUrl widgets to the edit item dialog layout. No functionality yet. 2026-02-17 11:42:29 +01:00
f1a436ead0 Added ShareType changes to the conditions to emit nExpectedBiddingsChanged. 2026-02-17 11:38:38 +01:00
25bfc196a1 Counting "bezahlt" & "teils/teils" share type explicitly for expected biddings, instead of the absence of values (emptiness, "erarbeitet"). 2026-02-17 10:13:01 +01:00
bfb8e43c34 Disabling the progress status bar if more biddings are placed than expected. (Easy way to indicate a problem in the GUI). 2026-02-17 09:05:45 +01:00
c38bacc387 Merge branch 'feature/modelSummary' into develop 2026-02-17 08:18:57 +01:00
546d6ea3ef Added two skips in calculation of amount of expected biddings (depending on share type & share amount). 2026-02-16 19:00:06 +01:00
06e96916ed Fixed a crash bug when the application is quit. Because of intertwining shared pointer and the Qt ownership of child objects the model and the model summary were tried to be destroyed two times. 2026-02-16 18:50:44 +01:00
1e64dda701 Migrated old visualization of the bidding round statuses with donut charts from the legacy BeetRound project. 2026-02-16 18:48:05 +01:00
f8201ead71 Added a placeholder for the configuration of the monthly financial need. 2026-02-16 11:46:01 +01:00
f200fff99e Added a second bound property (nExpectedBiddings) & cleaned up the code a little. 2026-02-16 09:24:58 +01:00
dac9ac46f2 Added a ModelSummary class to make the overview over the model content accessible via QProperty system & a SummaryWidget to show this information. Only property rowCount as a proof of concept. Other properties will follow. 2026-02-15 16:36:13 +01:00
33 changed files with 1668 additions and 208 deletions

View File

@ -11,7 +11,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools Charts)
set(TS_FILES ${TARGET_APP}_en_US.ts)
@ -37,6 +37,9 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
widgets/comboboxdelegate.h widgets/comboboxdelegate.cpp
widgets/spinboxdelegate.h widgets/spinboxdelegate.cpp
widgets/biddingroundcontrol.h widgets/biddingroundcontrol.cpp
widgets/summarywidget.h widgets/summarywidget.cpp
widgets/biddingroundstatuswidget.h widgets/biddingroundstatuswidget.cpp
widgets/biddingroundprogresslayout.h widgets/biddingroundprogresslayout.cpp
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET ${TARGET_APP} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
@ -63,7 +66,7 @@ endif()
include_directories(${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_link_libraries(${TARGET_APP} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Charts)
target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/)
target_link_libraries(${TARGET_APP} PRIVATE BeetRoundCore)

View File

@ -8,8 +8,7 @@
EditItemDialog::EditItemDialog(QTableView* tableView, QWidget* parent)
: AbstractDialog(QDialogButtonBox::Ok, parent)
, m_tableView(tableView)
, m_qrCodeDisplay(new QLabel("QR Code")) {}
, m_tableView(tableView) {}
void EditItemDialog::createContent() {
if (m_contentContainer) {
@ -26,11 +25,13 @@ void EditItemDialog::createContent() {
m_detailMapper->setModelMappings(m_tableView);
innerLayout->addWidget(m_detailMapper);
updateQRCode();
connect(m_detailMapper, &ItemDetailMapper::contentChanged, this, &EditItemDialog::updateQRCode);
innerLayout->addWidget(m_qrCodeDisplay);
m_outerLayout->insertWidget(0, m_contentContainer);
/// online user stuff
connect(m_detailMapper, &ItemDetailMapper::createOnlineAccountTriggered, this,
&EditItemDialog::createOnlineAccountTriggered);
connect(m_detailMapper, &ItemDetailMapper::sendInviteMailTriggered, this,
&EditItemDialog::sendInviteMailTriggered);
}
void EditItemDialog::accept() {
@ -42,16 +43,3 @@ void EditItemDialog::reject() {
m_detailMapper->revert();
QDialog::reject();
}
void EditItemDialog::updateQRCode(const QString text) {
QImage unscaledImage;
if (text.isEmpty()) {
unscaledImage = QImage("://no-picture-taking.png");
} else {
unscaledImage = m_generator.generateQr(text);
}
QImage image = unscaledImage.scaled(250, 250);
m_qrCodeDisplay->setPixmap(QPixmap::fromImage(image));
m_qrCodeDisplay->setToolTip(text);
}

View File

@ -1,7 +1,6 @@
#ifndef EDITITEMDIALOG_H
#define EDITITEMDIALOG_H
#include "QrCodeGenerator.h"
#include "abstractdialog.h"
class QDoubleSpinBox;
@ -20,34 +19,17 @@ class EditItemDialog : public AbstractDialog {
/// AbstractDialog interface
void createContent() override;
signals:
void createOnlineAccountTriggered(const QString& mailAddress);
void sendInviteMailTriggered(const QString& mailAddress);
public slots:
void accept() override;
void reject() override;
private slots:
void updateQRCode(const QString text = "");
private:
QTableView* m_tableView = nullptr;
ItemDetailMapper* m_detailMapper;
QLabel* m_nameLabel = nullptr;
QLineEdit* m_nameEdit = nullptr;
QLabel* m_descriptionLabel = nullptr;
QLineEdit* m_descriptionEdit = nullptr;
QLabel* m_infoLabel = nullptr;
QLineEdit* m_infoEdit = nullptr;
QLabel* m_amountLabel = nullptr;
QSpinBox* m_amountBox = nullptr;
QLabel* m_factorLabel = nullptr;
QDoubleSpinBox* m_factorBox = nullptr;
QLabel* m_qrCodeDisplay = nullptr;
QrCodeGenerator m_generator;
};
#endif // EDITITEMDIALOG_H

View File

@ -139,7 +139,7 @@ void NewItemDialog::resetContent() {
m_firstNameEdit->setText("");
m_shareTypeBox->setCurrentIndex(0);
m_amountSpinBox->setValue(1);
m_biddingTypeBox->setCurrentIndex(0);
m_biddingTypeBox->setCurrentIndex(4);
m_bidding1SpinBox->setValue(0);
m_bidding2SpinBox->setValue(0);
m_bidding3SpinBox->setValue(0);

View File

@ -27,12 +27,12 @@ void SettingsDialog::createContent() {
serverLayout->addWidget(m_urlEdit, 0, 1);
QLabel* emailLabel = new QLabel("Email:");
m_emailEdit = new QLineEdit();
m_emailEdit->setEnabled(false);
// m_emailEdit->setEnabled(false);
serverLayout->addWidget(emailLabel, 1, 0);
serverLayout->addWidget(m_emailEdit, 1, 1);
QLabel* passwordLabel = new QLabel("Password:");
m_passwordEdit = new QLineEdit();
m_passwordEdit->setEnabled(false);
// m_passwordEdit->setEnabled(false);
m_passwordEdit->setEchoMode(QLineEdit::Password);
serverLayout->addWidget(passwordLabel, 2, 0);
serverLayout->addWidget(m_passwordEdit, 2, 1);

View File

@ -20,6 +20,7 @@
#include "widgets/biddingroundcontrol.h"
#include "widgets/comboboxdelegate.h"
#include "widgets/spinboxdelegate.h"
#include "widgets/summarywidget.h"
static int intColumnWidth = 30;
@ -73,6 +74,10 @@ MainWindow::MainWindow(QWidget* parent)
onCurrentChanged(QModelIndex(), QModelIndex());
setupEventTab();
// #ifndef QT_DEBUG
initServerConnection();
// #endif
}
MainWindow::~MainWindow() { delete ui; }
@ -349,6 +354,8 @@ void MainWindow::setupModelViews() {
ui->tableView->setModel((QAbstractItemModel*)m_proxyModel.get());
ui->tableView->setSortingEnabled(true);
ui->tableView->setColumnWidth(0, intColumnWidth);
m_modelSummary = m_core->getModelSummary();
}
void MainWindow::createActions() {
@ -554,5 +561,16 @@ void MainWindow::setupEventTab() {
connect(m_core.get(), &GenericCore::currentBiddingRoundChanged, m_biddingRoundControl.get(),
&BiddingRoundControl::onCurrentBiddingRoundChanged);
SummaryWidget* summaryWidget = new SummaryWidget(m_modelSummary, this);
containerLayout->addWidget(summaryWidget);
ui->tabWidget->insertTab(0, containerWidget, "Event (&1)");
}
void MainWindow::initServerConnection() {
connect(m_editItemDialog.get(), &EditItemDialog::createOnlineAccountTriggered, m_core.get(),
&GenericCore::onCreateOnlineAccountTriggered);
connect(m_editItemDialog.get(), &EditItemDialog::sendInviteMailTriggered, m_core.get(),
&GenericCore::onSendInviteMailTriggered);
emit m_core->loginAndStoreAuthToken();
}

View File

@ -17,6 +17,7 @@ QT_END_NAMESPACE
class GenericCore;
class TableModel;
class GeneralSortFilterModel;
class ModelSummary;
class NewItemDialog;
class EditItemDialog;
class BiddingRoundControl;
@ -73,9 +74,12 @@ class MainWindow : public QMainWindow {
unique_ptr<GenericCore> m_core;
shared_ptr<GeneralSortFilterModel> m_proxyModel;
shared_ptr<ModelSummary> m_modelSummary;
QUndoStack* m_modelUndoStack;
unique_ptr<QUndoView> m_modelUndoView;
unique_ptr<BiddingRoundControl> m_biddingRoundControl;
/// File actions
unique_ptr<QAction> m_newFileAct;
unique_ptr<QAction> m_openAct;
@ -116,5 +120,7 @@ class MainWindow : public QMainWindow {
void createGuiDialogs();
void setupEventTab();
void initServerConnection();
};
#endif // MAINWINDOW_H

View File

@ -1,5 +1,6 @@
#include "itemdetailmapper.h"
#include <data/settingshandler.h>
#include <QAbstractItemModel>
#include <QComboBox>
#include <QDataWidgetMapper>
@ -15,7 +16,8 @@
#include "model/metadata.h"
ItemDetailMapper::ItemDetailMapper(QWidget* parent)
: QWidget{parent} {
: QWidget{parent}
, m_qrCodeDisplay(new QLabel("QR Code")) {
/// model mapping
m_mapper = std::make_unique<QDataWidgetMapper>(this);
/// BUG: If multiple columns are changed not all changes are applied.
@ -35,7 +37,8 @@ ItemDetailMapper::ItemDetailMapper(QWidget* parent)
connect(m_nextButton, &QAbstractButton::clicked, this, &ItemDetailMapper::toNext);
/// the individual widgets
// REFACTOR deduce label names and types from meta data & use a factory
// REFACTOR deduce types from meta data & use a factory
/// left layout
m_numberLabel = new QLabel(GET_HEADER_FOR_COLUMN(0));
m_numberBox = new QSpinBox();
m_numberBox->setMaximum(1000);
@ -86,36 +89,89 @@ ItemDetailMapper::ItemDetailMapper(QWidget* parent)
m_mailEdit->setMinimumWidth(200);
m_mailLabel->setBuddy(m_mailEdit);
QGridLayout* layout = new QGridLayout();
layout->addWidget(m_numberLabel, 0, 0, 1, 1);
layout->addWidget(m_numberBox, 0, 1, 1, 1);
layout->addWidget(m_lastNameLabel, 1, 0, 1, 1);
layout->addWidget(m_lastNameEdit, 1, 1, 1, 1);
layout->addWidget(m_firstNameLabel, 2, 0, 1, 1);
layout->addWidget(m_firstNameEdit, 2, 1, 1, 1);
layout->addWidget(m_shareTypeLabel, 3, 0, 1, 1);
layout->addWidget(m_shareTypeBox, 3, 1, 1, 1);
layout->addWidget(m_amountLabel, 4, 0, 1, 1);
layout->addWidget(m_amountSpinBox, 4, 1, 1, 1);
layout->addWidget(m_biddingTypeLabel, 5, 0, 1, 1);
layout->addWidget(m_biddingTypeBox, 5, 1, 1, 1);
layout->addWidget(m_bidding1Label, 6, 0, 1, 1);
layout->addWidget(m_bidding1SpinBox, 6, 1, 1, 1);
layout->addWidget(m_bidding2Label, 7, 0, 1, 1);
layout->addWidget(m_bidding2SpinBox, 7, 1, 1, 1);
layout->addWidget(m_bidding3Label, 8, 0, 1, 1);
layout->addWidget(m_bidding3SpinBox, 8, 1, 1, 1);
layout->addWidget(m_depotWish1Label, 9, 0, 1, 1);
layout->addWidget(m_depotWish1Edit, 9, 1, 1, 1);
layout->addWidget(m_depotWish2Label, 10, 0, 1, 1);
layout->addWidget(m_depotWish2Edit, 10, 1, 1, 1);
layout->addWidget(m_mailLabel, 11, 0, 1, 1);
layout->addWidget(m_mailEdit, 11, 1, 1, 1);
/// right layout
m_createOnlineAccountButton = new QPushButton(tr("&Create account"));
m_sendInviteMailButton = new QPushButton(tr("&Send invite"));
layout->addWidget(m_previousButton, 12, 0, 1, 1);
layout->addWidget(m_nextButton, 12, 1, 1, 1);
m_onlineIdLabel = new QLabel(GET_HEADER_FOR_COLUMN(12));
m_onlineIdDisplay = new QLineEdit();
m_onlineIdDisplay->setReadOnly(true);
m_onlineIdLabel->setBuddy(m_onlineIdDisplay);
setLayout(layout);
m_accessCodeLabel = new QLabel(GET_HEADER_FOR_COLUMN(13));
m_accessCodeDisplay = new QLineEdit();
m_accessCodeDisplay->setReadOnly(true);
m_accessCodeLabel->setBuddy(m_onlineIdDisplay);
updateQRCode();
connect(this, &ItemDetailMapper::contentChanged, this, &ItemDetailMapper::updateQRCode);
m_accessUrlDisplay = new QLineEdit();
m_accessUrlDisplay->setReadOnly(true);
QGridLayout* leftLayout = new QGridLayout();
leftLayout->addWidget(m_numberLabel, 0, 0, 1, 1);
leftLayout->addWidget(m_numberBox, 0, 1, 1, 1);
leftLayout->addWidget(m_lastNameLabel, 1, 0, 1, 1);
leftLayout->addWidget(m_lastNameEdit, 1, 1, 1, 1);
leftLayout->addWidget(m_firstNameLabel, 2, 0, 1, 1);
leftLayout->addWidget(m_firstNameEdit, 2, 1, 1, 1);
leftLayout->addWidget(m_shareTypeLabel, 3, 0, 1, 1);
leftLayout->addWidget(m_shareTypeBox, 3, 1, 1, 1);
leftLayout->addWidget(m_amountLabel, 4, 0, 1, 1);
leftLayout->addWidget(m_amountSpinBox, 4, 1, 1, 1);
leftLayout->addWidget(m_biddingTypeLabel, 5, 0, 1, 1);
leftLayout->addWidget(m_biddingTypeBox, 5, 1, 1, 1);
leftLayout->addWidget(m_bidding1Label, 6, 0, 1, 1);
leftLayout->addWidget(m_bidding1SpinBox, 6, 1, 1, 1);
leftLayout->addWidget(m_bidding2Label, 7, 0, 1, 1);
leftLayout->addWidget(m_bidding2SpinBox, 7, 1, 1, 1);
leftLayout->addWidget(m_bidding3Label, 8, 0, 1, 1);
leftLayout->addWidget(m_bidding3SpinBox, 8, 1, 1, 1);
leftLayout->addWidget(m_depotWish1Label, 9, 0, 1, 1);
leftLayout->addWidget(m_depotWish1Edit, 9, 1, 1, 1);
leftLayout->addWidget(m_depotWish2Label, 10, 0, 1, 1);
leftLayout->addWidget(m_depotWish2Edit, 10, 1, 1, 1);
leftLayout->addWidget(m_mailLabel, 11, 0, 1, 1);
leftLayout->addWidget(m_mailEdit, 11, 1, 1, 1);
leftLayout->addWidget(m_previousButton, 12, 0, 1, 1);
leftLayout->addWidget(m_nextButton, 12, 1, 1, 1);
/// right layout
QVBoxLayout* rightLayout = new QVBoxLayout;
QHBoxLayout* onlineAccountControlLayout = new QHBoxLayout;
onlineAccountControlLayout->addWidget(m_createOnlineAccountButton);
onlineAccountControlLayout->addWidget(m_sendInviteMailButton);
rightLayout->addLayout(onlineAccountControlLayout);
QGridLayout* rightGridLayout = new QGridLayout();
rightGridLayout->addWidget(m_onlineIdLabel, 0, 0, 1, 1);
rightGridLayout->addWidget(m_onlineIdDisplay, 0, 1, 1, 1);
rightGridLayout->addWidget(m_accessCodeLabel, 1, 0, 1, 1);
rightGridLayout->addWidget(m_accessCodeDisplay, 1, 1, 1, 1);
rightGridLayout->addWidget(m_qrCodeDisplay, 2, 0, 6, 2);
rightGridLayout->addWidget(m_accessUrlDisplay, 9, 0, 1, 2);
rightLayout->addLayout(rightGridLayout);
QHBoxLayout* outerLayout = new QHBoxLayout;
outerLayout->addLayout(leftLayout);
outerLayout->addLayout(rightLayout);
setLayout(outerLayout);
const QVariantMap serverSettings = SettingsHandler::getSettings("Server");
const QString urlValue = serverSettings.value("url").toString();
m_serverUrl = urlValue;
/// online user account
connect(m_mailEdit, &QLineEdit::textChanged, this, &ItemDetailMapper::onMailEditChanged);
connect(m_onlineIdDisplay, &QLineEdit::textChanged, this, &ItemDetailMapper::onOnlineIDChanged);
connect(m_accessCodeDisplay, &QLineEdit::textChanged, this,
&ItemDetailMapper::onAccessCodeChanged);
connect(m_createOnlineAccountButton, &QAbstractButton::clicked, this,
&ItemDetailMapper::onCreateOnlineAccountTriggered);
connect(m_sendInviteMailButton, &QAbstractButton::clicked, this,
&ItemDetailMapper::onSendInviteMailTriggered);
}
void ItemDetailMapper::setModelMappings(QTableView* tableView) {
@ -138,6 +194,9 @@ void ItemDetailMapper::setModelMappings(QTableView* tableView) {
m_mapper->addMapping(m_depotWish2Edit, 10);
m_mapper->addMapping(m_mailEdit, 11);
m_mapper->addMapping(m_onlineIdDisplay, 12);
m_mapper->addMapping(m_accessCodeDisplay, 13);
m_mapper->setCurrentIndex(m_selectionModel->currentIndex().row());
connect(m_model, &QAbstractItemModel::rowsInserted, this, &ItemDetailMapper::rowsInserted);
@ -152,16 +211,6 @@ bool ItemDetailMapper::submit() { return m_mapper->submit(); }
void ItemDetailMapper::revert() { m_mapper->revert(); }
void ItemDetailMapper::onCurrentIndexChanged(const QModelIndex& current,
const QModelIndex& /*previous*/) {
if (!isEnabled()) {
setEnabled(true);
}
m_mapper->setCurrentModelIndex(current);
updateButtons(current.row());
emitContentChanged(current);
}
void ItemDetailMapper::rowsInserted(const QModelIndex& parent, int start, int end) {
updateButtons(m_mapper->currentIndex());
}
@ -201,11 +250,66 @@ void ItemDetailMapper::updateButtons(int row) {
m_nextButton->setEnabled(row < m_model->rowCount() - 1);
}
void ItemDetailMapper::emitContentChanged(const QModelIndex& currentIndex) {
// BUG QR-Code isn't updated after changes through the ItemDetailMapper #18
QString toStringText = "";
if (currentIndex.isValid()) {
toStringText = currentIndex.data(ToStringRole).toString();
void ItemDetailMapper::onCurrentIndexChanged(const QModelIndex& current,
const QModelIndex& /*previous*/) {
if (!isEnabled()) {
setEnabled(true);
}
emit contentChanged(toStringText);
m_mapper->setCurrentModelIndex(current);
updateButtons(current.row());
}
void ItemDetailMapper::updateQRCode(const QString text) {
QImage unscaledImage;
if (text.isEmpty()) {
unscaledImage = QImage("://no-picture-taking.png");
} else {
unscaledImage = m_generator.generateQr(text);
}
QImage image = unscaledImage.scaled(250, 250);
m_qrCodeDisplay->setPixmap(QPixmap::fromImage(image));
m_qrCodeDisplay->setToolTip(text);
}
void ItemDetailMapper::onMailEditChanged(const QString& text) {
if (text.isEmpty()) {
m_createOnlineAccountButton->setEnabled(false);
m_sendInviteMailButton->setEnabled(false);
} else {
onOnlineIDChanged(m_onlineIdDisplay->text());
onAccessCodeChanged(m_accessCodeDisplay->text());
}
}
void ItemDetailMapper::onOnlineIDChanged(const QString& text) {
if (text.isEmpty()) {
m_createOnlineAccountButton->setEnabled(true);
m_sendInviteMailButton->setEnabled(false);
} else {
m_createOnlineAccountButton->setEnabled(false);
m_sendInviteMailButton->setEnabled(true);
}
}
void ItemDetailMapper::onAccessCodeChanged(const QString& accessCode) {
if (accessCode.isEmpty()) {
m_sendInviteMailButton->setEnabled(false);
m_accessUrlDisplay->setText("");
updateQRCode("");
} else {
m_sendInviteMailButton->setEnabled(true);
const QString accessUrl = m_serverUrl + "/log_in/" + accessCode;
// const QString accessUrl = "https://bietrunde.das-gruene-zebra.de/log_in/" + text;
m_accessUrlDisplay->setText(accessUrl);
updateQRCode(accessUrl);
}
}
void ItemDetailMapper::onCreateOnlineAccountTriggered() {
emit createOnlineAccountTriggered(m_mailEdit->text());
}
void ItemDetailMapper::onSendInviteMailTriggered() {
emit sendInviteMailTriggered(m_mailEdit->text());
}

View File

@ -15,6 +15,8 @@ class QItemSelectionModel;
class QStringListModel;
class QTableView;
#include "QrCodeGenerator.h"
class ItemDetailMapper : public QWidget {
Q_OBJECT
public:
@ -28,14 +30,23 @@ class ItemDetailMapper : public QWidget {
signals:
void contentChanged(const QString text);
void createOnlineAccountTriggered(const QString& mailAddress);
void sendInviteMailTriggered(const QString& mailAddress);
private slots:
void onCurrentIndexChanged(const QModelIndex& current, const QModelIndex& previous);
void rowsInserted(const QModelIndex& parent, int start, int end);
void rowsRemoved(const QModelIndex& parent, int start, int end);
void toPrevious();
void toNext();
void updateButtons(int row);
void emitContentChanged(const QModelIndex& currentIndex);
void onCurrentIndexChanged(const QModelIndex& current, const QModelIndex& previous);
void updateQRCode(const QString text = "");
void onMailEditChanged(const QString& text);
void onOnlineIDChanged(const QString& text);
void onAccessCodeChanged(const QString& accessCode);
void onCreateOnlineAccountTriggered();
void onSendInviteMailTriggered();
private:
/// *** members ***
@ -46,7 +57,9 @@ class ItemDetailMapper : public QWidget {
std::unique_ptr<QDataWidgetMapper> m_mapper;
/// GUI elements
QString m_serverUrl;
/// *** GUI elements ***
/// left layout
QLabel* m_numberLabel;
QSpinBox* m_numberBox;
@ -82,7 +95,21 @@ class ItemDetailMapper : public QWidget {
QLabel* m_mailLabel;
QLineEdit* m_mailEdit;
/// Model mapper stuff
/// right layout
QPushButton* m_createOnlineAccountButton = nullptr;
QPushButton* m_sendInviteMailButton = nullptr;
QLabel* m_onlineIdLabel;
QLineEdit* m_onlineIdDisplay = nullptr;
QLabel* m_accessCodeLabel;
QLineEdit* m_accessCodeDisplay = nullptr;
QLabel* m_qrCodeDisplay = nullptr;
QrCodeGenerator m_generator;
QLineEdit* m_accessUrlDisplay = nullptr;
/// *** Model mapper stuff ***
QPushButton* m_nextButton;
QPushButton* m_previousButton;
};

View File

@ -0,0 +1,56 @@
#include "biddingroundprogresslayout.h"
#include <QLabel>
#include <QProgressBar>
BiddingRoundProgressLayout::BiddingRoundProgressLayout(const int nCurrentBiddings,
const int nExpectedBiddings,
QWidget* parent)
: QHBoxLayout(parent)
, m_nCurrentBiddings(nCurrentBiddings)
, m_nExpectedBiddings(nExpectedBiddings) {
QLabel* nBidsText = new QLabel("Abgegeben:");
addWidget(nBidsText);
m_nBiddingsLabel = new QLabel(QString::number(m_nCurrentBiddings));
addWidget(m_nBiddingsLabel);
QLabel* nExpectedText = new QLabel("Erwartet:");
addWidget(nExpectedText);
m_nExpectedBiddingLabel = new QLabel(QString::number(m_nExpectedBiddings));
addWidget(m_nExpectedBiddingLabel);
m_biddingRoundProgressBar = new QProgressBar(parent);
m_biddingRoundProgressBar->setMinimum(0);
m_biddingRoundProgressBar->setMaximum(m_nExpectedBiddings);
m_biddingRoundProgressBar->setValue(m_nCurrentBiddings);
addWidget(m_biddingRoundProgressBar);
updateEnabledStatus();
}
void BiddingRoundProgressLayout::setCurrentBiddings(const int value) {
m_nCurrentBiddings = value;
const QString bidCountString = QString::number(value);
m_nBiddingsLabel->setText(bidCountString);
m_biddingRoundProgressBar->setValue(value);
updateEnabledStatus();
}
void BiddingRoundProgressLayout::setExpectedBiddings(const int value) {
m_nExpectedBiddings = value;
const QString expectedBiddingsString = QString::number(value);
m_nExpectedBiddingLabel->setText(expectedBiddingsString);
m_biddingRoundProgressBar->setMaximum(value);
updateEnabledStatus();
}
void BiddingRoundProgressLayout::updateEnabledStatus() {
if (m_nCurrentBiddings > m_nExpectedBiddings) {
m_biddingRoundProgressBar->setEnabled(false);
} else {
m_biddingRoundProgressBar->setEnabled(true);
}
}

View File

@ -0,0 +1,30 @@
#ifndef BIDDINGROUNDPROGRESSLAYOUT_H
#define BIDDINGROUNDPROGRESSLAYOUT_H
#include <QHBoxLayout>
class QLabel;
class QProgressBar;
class BiddingRoundProgressLayout : public QHBoxLayout {
Q_OBJECT
public:
BiddingRoundProgressLayout(const int nCurrentBiddings,
const int nExpectedBiddings,
QWidget* parent = nullptr);
void setCurrentBiddings(const int value);
void setExpectedBiddings(const int value);
private:
int m_nCurrentBiddings;
int m_nExpectedBiddings;
QLabel* m_nBiddingsLabel = nullptr;
QLabel* m_nExpectedBiddingLabel = nullptr;
QProgressBar* m_biddingRoundProgressBar = nullptr;
void updateEnabledStatus();
};
#endif // BIDDINGROUNDPROGRESSLAYOUT_H

View File

@ -0,0 +1,110 @@
#include "biddingroundstatuswidget.h"
#include <QChartView>
#include <QLabel>
#include <QPieSeries>
#include <QVBoxLayout>
BiddingRoundStatusWidget::BiddingRoundStatusWidget(const QString& title,
const int placedBiddings,
const int expectedBiddings,
const int financialNeed,
const int currentSum,
const qreal currentAverage,
QWidget* parent)
: QWidget{parent}
, m_title(title)
, m_financialNeed(financialNeed)
, m_currentSum(currentSum) {
m_moneyDonutSeries = new QPieSeries();
m_moneyDonutSeries->setHoleSize(0.35);
const QString sumText = QString("Summe: %1 €").arg(currentSum);
m_sumSlice = m_moneyDonutSeries->append(sumText, currentSum);
m_sumSlice->setLabelVisible();
const int currentGap = m_financialNeed - currentSum;
const QString gapText = QString("offen: %1 €").arg(currentGap);
m_gapSlice = m_moneyDonutSeries->append(gapText, currentGap);
m_gapSlice->setLabelVisible();
m_chartView = new QChartView();
m_chartView->setMinimumWidth(100);
// m_chartView->setMaximumWidth(500);
m_chartView->setRenderHint(QPainter::Antialiasing);
m_chartView->chart()->setTitle(m_title);
m_chartView->chart()->addSeries(m_moneyDonutSeries);
m_chartView->chart()->legend()->setAlignment(Qt::AlignBottom);
m_chartView->chart()->setTheme(QChart::ChartThemeBlueCerulean);
m_chartView->chart()->legend()->setFont(QFont("Arial", 8));
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(m_chartView, 5);
QBoxLayout* progressLayout = createProgressLayout(placedBiddings, expectedBiddings);
layout->addLayout(progressLayout);
/// bidding average
QHBoxLayout* averageLayout = new QHBoxLayout();
m_biddingAverageDescription = new QLabel("Durchschnittsgebot: ");
const QString currencyString = createCurrencyString(currentAverage);
m_biddingAverageValue = new QLabel(currencyString);
averageLayout->addWidget(m_biddingAverageDescription);
averageLayout->addWidget(m_biddingAverageValue);
layout->addLayout(averageLayout);
setLayout(layout);
}
void BiddingRoundStatusWidget::onExpectedBiddingsChanged(const int nExpected) {
m_biddingRoundProgress->setExpectedBiddings(nExpected);
}
void BiddingRoundStatusWidget::onNPlacedBiddingsChanged(const int nPlaced) {
m_biddingRoundProgress->setCurrentBiddings(nPlaced);
}
void BiddingRoundStatusWidget::onBiddingSumChanged(const int sum) {
m_currentSum = sum;
updateDonutChart();
}
void BiddingRoundStatusWidget::onBiddingAverageChanged(const qreal average) {
if (m_biddingAverageValue) {
m_biddingAverageValue->setText(createCurrencyString(average));
}
}
void BiddingRoundStatusWidget::onFinancialNeedChanged(const int financialNeed) {
m_financialNeed = financialNeed;
updateDonutChart();
}
QBoxLayout* BiddingRoundStatusWidget::createProgressLayout(const int nPlaced, const int nExpected) {
QVBoxLayout* progressLayout = new QVBoxLayout();
m_biddingRoundProgress = new BiddingRoundProgressLayout(nPlaced, nExpected);
progressLayout->addLayout(m_biddingRoundProgress);
return progressLayout;
}
void BiddingRoundStatusWidget::updateDonutChart() {
int currentGap;
if (m_currentSum < m_financialNeed) {
currentGap = m_financialNeed - m_currentSum;
} else {
currentGap = 0;
}
const QString sumText = QString("Summe: %1 €").arg(m_currentSum);
m_sumSlice->setValue(m_currentSum);
m_sumSlice->setLabel(sumText);
const QString gapText = QString("offen: %1 €").arg(currentGap);
m_gapSlice->setValue(currentGap);
m_gapSlice->setLabel(gapText);
const bool gapLabelVisible = currentGap != 0;
m_gapSlice->setLabelVisible(gapLabelVisible);
}
QString BiddingRoundStatusWidget::createCurrencyString(const qreal value) const {
return QString::number(value) + "";
}

View File

@ -0,0 +1,53 @@
#ifndef BIDDINGROUNDSTATUSWIDGET_H
#define BIDDINGROUNDSTATUSWIDGET_H
#include "biddingroundprogresslayout.h"
#include <QWidget>
class QBoxLayout;
class QChartView;
class QPieSeries;
class QPieSlice;
class BiddingRoundStatusWidget : public QWidget {
Q_OBJECT
public:
explicit BiddingRoundStatusWidget(const QString& title,
const int placedBiddings,
const int expectedBiddings,
const int financialNeed,
const int currentSum,
const qreal currentAverage,
QWidget* parent = nullptr);
public slots:
void onExpectedBiddingsChanged(const int nExpected);
void onNPlacedBiddingsChanged(const int nPlaced);
void onBiddingSumChanged(const int sum);
void onBiddingAverageChanged(const qreal average);
void onFinancialNeedChanged(const int financialNeed);
signals:
private:
QString m_title;
int m_financialNeed;
int m_currentSum;
QChartView* m_chartView = nullptr;
QPieSeries* m_moneyDonutSeries = nullptr;
QPieSlice* m_sumSlice = nullptr;
QPieSlice* m_gapSlice = nullptr;
QLabel* m_biddingAverageDescription = nullptr;
QLabel* m_biddingAverageValue = nullptr;
BiddingRoundProgressLayout* m_biddingRoundProgress = nullptr;
QBoxLayout* createProgressLayout(const int nPlaced, const int nExpected);
void updateDonutChart();
QString createCurrencyString(const qreal value) const;
};
#endif // BIDDINGROUNDSTATUSWIDGET_H

View File

@ -0,0 +1,166 @@
#include "summarywidget.h"
#include "biddingroundstatuswidget.h"
#include <QLabel>
#include <QProperty>
#include <QSpinBox>
#include <QVBoxLayout>
#include <model/modelsummary.h>
SummaryWidget::SummaryWidget(std::shared_ptr<ModelSummary> modelSummary, QWidget* parent)
: QWidget{parent}
, m_modelSummary(modelSummary) {
/// Layouting
QVBoxLayout* mainLayout = new QVBoxLayout(this);
/// monthly need
QHBoxLayout* footerLayout = new QHBoxLayout();
QLabel* nTotalSharesLabel = new QLabel("Erwartete Ernteanteile:");
const qreal expectedShares = m_modelSummary->nTotalExpectedShares();
m_nTotalExpectedShares = new QLabel(QString::number(expectedShares));
QLabel* financialNeedLabel = new QLabel("monatlicher Finanzbedarf:");
m_financialNeedBox = new QSpinBox();
m_financialNeedBox->setMaximum(50000);
m_financialNeedBox->setValue(m_financialNeed);
connect(m_financialNeedBox, &QSpinBox::valueChanged, this,
&SummaryWidget::onFinancialNeedChanged);
footerLayout->addWidget(nTotalSharesLabel);
footerLayout->addWidget(m_nTotalExpectedShares);
footerLayout->addStretch(1);
footerLayout->addWidget(financialNeedLabel);
footerLayout->addWidget(m_financialNeedBox);
footerLayout->addStretch(1);
QBoxLayout* roundsLayout = createBiddingOverviewLayout();
mainLayout->addLayout(roundsLayout);
mainLayout->addLayout(footerLayout);
mainLayout->setSpacing(50);
setLayout(mainLayout);
setupConnections();
}
void SummaryWidget::onFinancialNeedChanged(int newFinancialNeed) {
m_biddingStatus1->onFinancialNeedChanged(newFinancialNeed);
m_biddingStatus2->onFinancialNeedChanged(newFinancialNeed);
m_biddingStatus3->onFinancialNeedChanged(newFinancialNeed);
}
void SummaryWidget::onNExpectedBiddingChanged(int newNExpected) {
m_biddingStatus1->onExpectedBiddingsChanged(newNExpected);
m_biddingStatus2->onExpectedBiddingsChanged(newNExpected);
m_biddingStatus3->onExpectedBiddingsChanged(newNExpected);
}
void SummaryWidget::onNTotalExpectedSharesChanged(qreal newNExpected) {
const QString expectedSharesString = QString::number(newNExpected);
m_nTotalExpectedShares->setText(expectedSharesString);
}
void SummaryWidget::onNPlacedBiddingsChanged(const int roundNumber) {
switch (roundNumber) {
case 1:
m_biddingStatus1->onNPlacedBiddingsChanged(m_modelSummary->nPlacedBiddings1());
break;
case 2:
m_biddingStatus2->onNPlacedBiddingsChanged(m_modelSummary->nPlacedBiddings2());
break;
case 3:
m_biddingStatus3->onNPlacedBiddingsChanged(m_modelSummary->nPlacedBiddings3());
break;
default:
qWarning() << "Unknown round number:" << roundNumber;
break;
}
}
void SummaryWidget::onBiddingSumChanged(const int roundNumber) {
switch (roundNumber) {
case 1:
m_biddingStatus1->onBiddingSumChanged(m_modelSummary->biddingSum1());
break;
case 2:
m_biddingStatus2->onBiddingSumChanged(m_modelSummary->biddingSum2());
break;
case 3:
m_biddingStatus3->onBiddingSumChanged(m_modelSummary->biddingSum3());
break;
default:
qWarning() << "Unknown round number:" << roundNumber;
break;
}
}
void SummaryWidget::onBiddingAverageChanged(const int roundNumber) {
switch (roundNumber) {
case 1:
m_biddingStatus1->onBiddingAverageChanged(m_modelSummary->biddingAverage1());
break;
case 2:
m_biddingStatus2->onBiddingAverageChanged(m_modelSummary->biddingAverage2());
break;
case 3:
m_biddingStatus3->onBiddingAverageChanged(m_modelSummary->biddingAverage3());
break;
default:
qWarning() << "Unknown round number:" << roundNumber;
break;
}
}
QBoxLayout* SummaryWidget::createBiddingOverviewLayout() {
QHBoxLayout* layout = new QHBoxLayout();
const int expectedBiddings = m_modelSummary->nExpectedBiddings();
/// bidding round 1
const int placedBiddings1 = m_modelSummary->nPlacedBiddings1();
const int biddingSum1 = m_modelSummary->biddingSum1();
const qreal biddingAverage1 = m_modelSummary->biddingAverage1();
m_biddingStatus1 =
make_unique<BiddingRoundStatusWidget>("Bietrunde 1", placedBiddings1, expectedBiddings,
m_financialNeed, biddingSum1, biddingAverage1);
/// bidding round 2
const int placedBiddings2 = m_modelSummary->nPlacedBiddings2();
const int biddingSum2 = m_modelSummary->biddingSum2();
const qreal biddingAverage2 = m_modelSummary->biddingAverage2();
m_biddingStatus2 =
make_unique<BiddingRoundStatusWidget>("Bietrunde 2", placedBiddings2, expectedBiddings,
m_financialNeed, biddingSum2, biddingAverage2);
/// bidding round 3
const int placedBiddings3 = m_modelSummary->nPlacedBiddings3();
const int biddingSum3 = m_modelSummary->biddingSum3();
const qreal biddingAverage3 = m_modelSummary->biddingAverage3();
m_biddingStatus3 =
make_unique<BiddingRoundStatusWidget>("Bietrunde 3", placedBiddings3, expectedBiddings,
m_financialNeed, biddingSum3, biddingAverage3);
layout->addWidget(m_biddingStatus1.get());
layout->addWidget(m_biddingStatus2.get());
layout->addWidget(m_biddingStatus3.get());
return layout;
}
void SummaryWidget::setupConnections() {
// TODO figure out how to encapsulate each property binding into a dedicated function:
// "bindProperty(&bindable, &signal, &getter, widget)"
QObject::connect(m_modelSummary.get(), &ModelSummary::nExpectedBiddingsChanged,
[&]() { onNExpectedBiddingChanged(m_modelSummary->nExpectedBiddings()); });
QObject::connect(m_modelSummary.get(), &ModelSummary::nTotalExpectedSharesChanged, this, [&]() {
onNTotalExpectedSharesChanged(m_modelSummary->nTotalExpectedShares());
});
QObject::connect(m_modelSummary.get(), &ModelSummary::nPlacedBiddingsChanged, this,
&SummaryWidget::onNPlacedBiddingsChanged);
QObject::connect(m_modelSummary.get(), &ModelSummary::biddingSumChanged, this,
&SummaryWidget::onBiddingSumChanged);
QObject::connect(m_modelSummary.get(), &ModelSummary::biddingAverageChanged, this,
&SummaryWidget::onBiddingAverageChanged);
}

View File

@ -0,0 +1,47 @@
#ifndef SUMMARYWIDGET_H
#define SUMMARYWIDGET_H
#include <QWidget>
class BiddingRoundStatusWidget;
class ModelSummary;
class QLabel;
class QSpinBox;
class QBoxLayout;
class SummaryWidget : public QWidget {
Q_OBJECT
public:
explicit SummaryWidget(std::shared_ptr<ModelSummary> modelSummary, QWidget* parent = nullptr);
private slots:
void onFinancialNeedChanged(int newFinancialNeed);
void onNExpectedBiddingChanged(int newNExpected);
void onNTotalExpectedSharesChanged(qreal newNExpected);
void onNPlacedBiddingsChanged(const int roundNumber);
void onBiddingSumChanged(const int roundNumber);
void onBiddingAverageChanged(const int roundNumber);
private:
std::shared_ptr<ModelSummary> m_modelSummary;
std::unique_ptr<BiddingRoundStatusWidget> m_biddingStatus1;
std::unique_ptr<BiddingRoundStatusWidget> m_biddingStatus2;
std::unique_ptr<BiddingRoundStatusWidget> m_biddingStatus3;
// TODO read from settings (maybe via model/core; maybe set in constructor)
const int m_financialNeed = 13942;
QSpinBox* m_financialNeedBox = nullptr;
QLabel* m_nTotalExpectedShares = nullptr;
QLabel* m_rowCountValueLabel;
QLabel* m_nExpectedBiddingsValueLabel;
/// functions
QBoxLayout* createBiddingOverviewLayout();
void setupConnections();
};
#endif // SUMMARYWIDGET_H

View File

@ -32,6 +32,7 @@ add_library(${TARGET_APP} STATIC
model/tablemodel.h model/tablemodel.cpp
model/modelitem.h model/modelitem.cpp
model/generalsortfiltermodel.h model/generalsortfiltermodel.cpp
model/modelsummary.h model/modelsummary.cpp
model/commands/insertrowscommand.h model/commands/insertrowscommand.cpp
model/commands/removerowscommand.h model/commands/removerowscommand.cpp
model/commands/edititemcommand.h model/commands/edititemcommand.cpp

View File

@ -23,9 +23,12 @@ QList<ModelItemValues> CsvParser::getItemsFromCSVFile(const QString& fileName) {
bool CsvParser::exportToCSVFile(const QList<QStringList>& rows, const QString& filePath) {
Document doc(std::string(), LabelParams(0, -1));
const QList<QString> headerNames = GET_HEADER_NAMES();
for (int column = 0; column < headerNames.size(); ++column) {
doc.SetColumnName(column, headerNames.at(column).toStdString());
// const QList<QString> headerNames = GET_HEADER_NAMES();
const int columnCount = USER_FACING_ROLES.size();
for (int column = 0; column < columnCount; ++column) {
const UserRoles role = GET_ROLE_FOR_COLUMN(column);
std::string columnName = ROLE_NAMES.value(role).toStdString();
doc.SetColumnName(column, columnName);
}
for (int row = 0; row < rows.size(); ++row) {
QStringList rowValues = rows.at(row);
@ -129,8 +132,22 @@ ModelItemValues CsvParser::getItemValuesForRow(
QVariant CsvParser::parseItemValue(const int role, const std::string& valueString) {
QVariant result;
if (STRING_ROLES.contains(role)) {
/// string values
result = QString::fromStdString(valueString);
if (role == ShareTypeRole) {
if (valueString == "AGA") {
result = "erarbeitet";
} else if (valueString == "ja") {
result = "bezahlt";
} else if (valueString == "teils") {
result = "teils/teils";
} else {
result = "";
}
} else {
/// string values
result = QString::fromStdString(valueString);
}
} else if (INT_ROLES.contains(role)) {
/// int values

View File

@ -4,6 +4,7 @@
#include <QJsonObject>
#include "../model/metadata.h"
#include "../structs.h"
QList<ModelItemValues> JsonParser::toItemValuesList(const QByteArray& jsonData,
const QString& rootValueName) {
@ -70,7 +71,64 @@ QByteArray JsonParser::itemValuesListToJson(const QList<ModelItemValues>& itemVa
return jsonDoc.toJson(QJsonDocument::Compact);
}
JsonParser::JsonParser() {}
QByteArray JsonParser::toJsonDoc(const QHash<QString, QVariant>& values,
const QString& objectName) {
QJsonDocument jsonDoc;
QJsonObject rootObject;
QJsonObject itemObject;
QHashIterator<QString, QVariant> i(values);
while (i.hasNext()) {
i.next();
const QString key = i.key();
const QVariant value = i.value();
itemObject.insert(key, value.toString());
}
rootObject.insert(objectName, itemObject);
jsonDoc.setObject(rootObject);
return jsonDoc.toJson(QJsonDocument::Compact);
}
ModelItemValues JsonParser::serverUserCredentialsToItemValues(const QJsonDocument& jsonDoc) {
ModelItemValues values;
const QJsonObject rootObject = jsonDoc["data"].toObject();
const QString emailAddress = rootObject["email"].toString();
const QString uuid = rootObject["id"].toString();
const QString token = rootObject["token"].toString();
values.insert(MailRole, emailAddress);
values.insert(OnlineIdRole, uuid);
values.insert(AccessCodeRole, token);
return values;
}
QList<bidding> JsonParser::extractBiddings(const QJsonDocument& jsonDoc) {
QList<bidding> result;
if (jsonDoc.isEmpty()) {
return result;
}
QJsonArray itemArray = JsonParser::extractItemArray(jsonDoc, "data");
foreach (QJsonValue value, itemArray) {
// REFACTOR implement & use "JsonParser::parseServerResponse(const QJsonObject& object,
// QHash<QString, int> entries)"
QJsonObject itemJsonObject = value.toObject();
bidding values{.userId = itemJsonObject.value("user_id").toString(),
.biddingRound = itemJsonObject.value("bidding_round").toInt(),
.amount = itemJsonObject.value("amount").toInt(),
.depotWishOne = itemJsonObject.value("depot_wish_one").toString(),
.depotWishTwo = itemJsonObject.value("depot_wish_two").toString()};
result.append(values);
}
return result;
}
QJsonArray JsonParser::extractItemArray(const QJsonDocument& doc, const QString& objectName) {
QJsonArray itemArray;
@ -95,6 +153,8 @@ ModelItemValues JsonParser::jsonObjectToItemValues(const QJsonObject& itemJsonOb
return values;
}
JsonParser::JsonParser() {}
pair<int, QVariant> JsonParser::getKeyValuePair(const QJsonObject& itemJsonObject, const int role) {
QVariant result;
const QJsonValue jsonValue = itemJsonObject[ROLE_NAMES.value(role)];

View File

@ -9,6 +9,7 @@ class QByteArray;
class QJsonArray;
typedef QMap<int, QVariant> ModelItemValues;
class bidding;
using namespace std;
@ -18,13 +19,22 @@ class JsonParser {
const QString& rootValueName = "");
static QByteArray itemValuesListToJson(const QList<ModelItemValues>& itemValuesList,
const QString& objectName = "");
static QByteArray toJsonDoc(const QHash<QString, QVariant>& Values,
const QString& objectName = "");
private:
explicit JsonParser();
static ModelItemValues serverUserCredentialsToItemValues(const QJsonDocument& jsonDoc);
static QList<bidding> extractBiddings(const QJsonDocument& jsonDoc);
// static ModelItemValues parseServerResponse(const QJsonDocument& jsonDoc,
// QHash<QString, int> entries);
// static ModelItemValues parseServerResponse(const QJsonObject& object,
// QHash<QString, int> entries);
static QJsonArray extractItemArray(const QJsonDocument& doc, const QString& objectName);
static ModelItemValues jsonObjectToItemValues(const QJsonObject& itemJsonObject);
private:
explicit JsonParser();
static pair<int, QVariant> getKeyValuePair(const QJsonObject& itemJsonObject, const int role);
};

View File

@ -15,13 +15,13 @@
#include "constants.h"
#include "data/filehandler.h"
#include "data/settingshandler.h"
#include "formats/jsonparser.h"
#include "model/generalsortfiltermodel.h"
#include "model/metadata.h"
#include "model/modelsummary.h"
#include "model/tablemodel.h"
#include "network/servercommunicator.h"
using namespace std;
GenericCore::GenericCore() {
qDebug() << "Creating core...";
@ -97,6 +97,8 @@ std::shared_ptr<GeneralSortFilterModel> GenericCore::getSortFilterModel() const
return m_sortFilterModel;
}
shared_ptr<ModelSummary> GenericCore::getModelSummary() const { return m_modelSummary; }
/**
* Save items to default file (in standard location).
* @brief GenericCore::saveItems Saves item fo file.
@ -154,15 +156,48 @@ bool GenericCore::isSyncServerSetup() const {
}
}
void GenericCore::onBiddingsChanged(int round, QList<bidding> biddings) {
qInfo() << "onBiddingsChanged: round:" << round << "- biddings:" << biddings.count();
void GenericCore::onLoginSuccessful() {
qInfo() << "Login successful.";
emit displayStatusMessage("Login successful.");
emit sendGetRequest(GetCurrentBiddingRound);
}
void GenericCore::onBiddingsChanged(const QList<bidding> biddings) {
qInfo() << "onBiddingsChanged: biddings:" << biddings.count();
// NEXT merge biddings into model
m_mainModel->updateBiddings(biddings);
}
void GenericCore::onCreateOnlineAccountTriggered(const QString& mailAddress) {
qInfo() << "Creating online account for:" << mailAddress;
QHash<QString, QVariant> hash;
hash.insert("email", mailAddress);
const QByteArray jsonDoc = JsonParser::toJsonDoc(hash, "user");
emit sendPostRequest(RegisterUser, jsonDoc);
}
void GenericCore::onOnlineUserAccountReceived(const QString mailAddress,
const QString uuid,
const QString accessToken) {
m_mainModel->setOnlineCredentials(mailAddress, uuid, accessToken);
const QString message = QString("Online credentials received for: %1").arg(mailAddress);
emit displayStatusMessage(message);
}
void GenericCore::onSendInviteMailTriggered(const QString& mailAddress) {
qInfo() << "Sending invite mail to:" << mailAddress;
const QString serverUrl = m_serverCommunicator->getUserLoginUrl();
const QJsonDocument mailInviteJsonDoc = m_mainModel->getMailInviteJsonDoc(mailAddress, serverUrl);
emit sendPostRequest(MailInvite, mailInviteJsonDoc.toJson());
}
void GenericCore::setupModels() {
m_mainModel = make_shared<TableModel>(m_modelUndoStack, this);
m_mainModel = make_shared<TableModel>(m_modelUndoStack);
m_sortFilterModel = make_shared<GeneralSortFilterModel>(m_mainModel);
m_modelSummary = make_shared<ModelSummary>(m_mainModel);
/// QAbstractItemModelTester
#ifdef QT_DEBUG
m_mainModelTester = make_unique<QAbstractItemModelTester>(
@ -227,6 +262,7 @@ void GenericCore::applyServerConfiguration() {
if (!urlValue.isEmpty()) {
const QString emailValue = serverSettings.value("email").toString();
const QString passwordValue = serverSettings.value("password").toString();
m_serverCommunicator->setServerConfiguration(urlValue, emailValue, passwordValue);
const QString authToken = serverSettings.value("token").toString();
m_serverCommunicator->setServerConfiguration(urlValue, emailValue, passwordValue, authToken);
}
}

View File

@ -14,8 +14,11 @@ class QString;
class TableModel;
class GeneralSortFilterModel;
class ModelSummary;
class ServerCommunicator;
using namespace std;
class GenericCore : public QObject {
Q_OBJECT
@ -30,8 +33,10 @@ class GenericCore : public QObject {
void triggerApplicationUpdate(const bool saveChanges);
QUndoStack* getModelUndoStack() const;
std::shared_ptr<TableModel> getModel() const;
std::shared_ptr<GeneralSortFilterModel> getSortFilterModel() const;
shared_ptr<TableModel> getModel() const;
shared_ptr<GeneralSortFilterModel> getSortFilterModel() const;
shared_ptr<ModelSummary> getModelSummary() const;
void saveItems();
void importCSVFile(const QString& filePath);
@ -42,14 +47,24 @@ class GenericCore : public QObject {
bool isSyncServerSetup() const;
public slots:
void onBiddingsChanged(int round, QList<bidding> biddings);
void onLoginSuccessful();
void onBiddingsChanged(const QList<bidding> biddings);
void onCreateOnlineAccountTriggered(const QString& mailAddress);
void onOnlineUserAccountReceived(const QString mailAddress,
const QString uuid,
const QString accessToken);
void onSendInviteMailTriggered(const QString& mailAddress);
signals:
void displayStatusMessage(QString message);
/// *** server communication ***
void loginAndStoreAuthToken();
/// request signals
void sendGetRequest(GetRequestTypes type, QVariant data = QVariant());
void sendPostRequest(PostRequestTypes type, QByteArray data);
/// response signals
void currentBiddingRoundChanged(int round, bool isRunning);
@ -60,10 +75,11 @@ class GenericCore : public QObject {
private:
QUndoStack* m_modelUndoStack;
std::shared_ptr<TableModel> m_mainModel;
std::shared_ptr<GeneralSortFilterModel> m_sortFilterModel;
std::unique_ptr<QAbstractItemModelTester> m_mainModelTester;
std::unique_ptr<QAbstractItemModelTester> m_proxyModelTester;
shared_ptr<TableModel> m_mainModel;
shared_ptr<GeneralSortFilterModel> m_sortFilterModel;
shared_ptr<ModelSummary> m_modelSummary;
unique_ptr<QAbstractItemModelTester> m_mainModelTester;
unique_ptr<QAbstractItemModelTester> m_proxyModelTester;
void setupModels();
void initModelData();
@ -71,7 +87,7 @@ class GenericCore : public QObject {
QString getMaintenanceToolFilePath() const;
/// Network communication
std::unique_ptr<ServerCommunicator> m_serverCommunicator;
unique_ptr<ServerCommunicator> m_serverCommunicator;
void setupServerConfiguration();
void applyServerConfiguration();
};

View File

@ -91,6 +91,7 @@ enum GetRequestTypes {
GetBiddingsOfSpecificRound,
GetBiddingsOfHighestRound
};
enum PostRequestTypes { LogInAdmin, RegisterUser, MailInvite };
/// functions
static UserRoles GET_ROLE_FOR_COLUMN(const int column) {

View File

@ -0,0 +1,48 @@
#include "modelsummary.h"
#include "tablemodel.h"
ModelSummary::ModelSummary(std::shared_ptr<TableModel> model, QObject* parent)
: QObject(parent)
, m_model(model) {
Q_ASSERT(model);
connect(m_model.get(), &TableModel::rowCountChanged, this, &ModelSummary::rowCountChanged);
// TODO ? use existing model signals (dataChanged(role),...) instead of special signals
connect(m_model.get(), &TableModel::nExpectedBiddingsChanged, this,
&ModelSummary::nExpectedBiddingsChanged);
connect(m_model.get(), &TableModel::nTotalExpectedSharesChanged, this,
&ModelSummary::nTotalExpectedSharesChanged);
connect(m_model.get(), &TableModel::nPlacedBiddingsChanged, this,
&ModelSummary::nPlacedBiddingsChanged);
connect(m_model.get(), &TableModel::biddingSumChanged, this, &ModelSummary::biddingSumChanged);
connect(m_model.get(), &TableModel::biddingAverageChanged, this,
&ModelSummary::biddingAverageChanged);
}
ModelSummary::~ModelSummary() {}
int ModelSummary::rowCount() const {
const int nRows = m_model->rowCount();
return nRows;
}
QBindable<int> ModelSummary::bindableRowCount() {
m_rowCount = m_model->rowCount();
return &m_rowCount;
}
qreal ModelSummary::nTotalExpectedShares() const { return m_model->nTotalExpectedShares(); }
int ModelSummary::nExpectedBiddings() const { return m_model->nExpectedBiddings(); }
int ModelSummary::nPlacedBiddings1() const { return m_model->nPlacedBiddings1(); }
int ModelSummary::nPlacedBiddings2() const { return m_model->nPlacedBiddings2(); }
int ModelSummary::nPlacedBiddings3() const { return m_model->nPlacedBiddings3(); }
int ModelSummary::biddingSum1() const { return m_model->biddingSum1(); }
int ModelSummary::biddingSum2() const { return m_model->biddingSum2(); }
int ModelSummary::biddingSum3() const { return m_model->biddingSum3(); }
qreal ModelSummary::biddingAverage1() const { return m_model->biddingAverage1(); }
qreal ModelSummary::biddingAverage2() const { return m_model->biddingAverage2(); }
qreal ModelSummary::biddingAverage3() const { return m_model->biddingAverage3(); }

View File

@ -0,0 +1,52 @@
#ifndef MODELSUMMARY_H
#define MODELSUMMARY_H
#include <QObject>
#include <QProperty>
class TableModel;
using namespace std;
class ModelSummary : public QObject {
Q_OBJECT
Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged BINDABLE bindableRowCount)
public:
ModelSummary(shared_ptr<TableModel> model, QObject* parent = nullptr);
~ModelSummary();
int rowCount() const;
QBindable<int> bindableRowCount();
qreal nTotalExpectedShares() const;
int nExpectedBiddings() const;
int nPlacedBiddings1() const;
int nPlacedBiddings2() const;
int nPlacedBiddings3() const;
int biddingSum1() const;
int biddingSum2() const;
int biddingSum3() const;
qreal biddingAverage1() const;
qreal biddingAverage2() const;
qreal biddingAverage3() const;
signals:
void rowCountChanged();
void nExpectedBiddingsChanged();
void nTotalExpectedSharesChanged();
void nPlacedBiddingsChanged(const int roundNumber);
void biddingSumChanged(const int roundNumber);
void biddingAverageChanged(const int roundNumber);
private:
shared_ptr<TableModel> m_model;
Q_OBJECT_BINDABLE_PROPERTY(ModelSummary, int, m_rowCount, &ModelSummary::rowCountChanged);
};
#endif // MODELSUMMARY_H

View File

@ -1,16 +1,17 @@
#include "tablemodel.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include "../formats/jsonparser.h"
#include "../structs.h"
#include "commands/edititemcommand.h"
#include "commands/insertrowscommand.h"
#include "commands/removerowscommand.h"
#include "metadata.h"
#include "modelitem.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
QByteArray TableModel::generateExampleItems() {
QJsonDocument doc = QJsonDocument();
QJsonObject rootObject;
@ -46,7 +47,10 @@ QByteArray TableModel::generateExampleItems() {
TableModel::TableModel(QUndoStack* undoStack, QObject* parent)
: QAbstractTableModel{parent}
, m_undoStack(undoStack) {}
, m_undoStack(undoStack) {
connect(this, &TableModel::rowsInserted, this, &TableModel::onRowCountChanged);
connect(this, &TableModel::rowsRemoved, this, &TableModel::onRowCountChanged);
}
Qt::ItemFlags TableModel::flags(const QModelIndex& index) const {
if (!index.isValid()) {
@ -228,6 +232,63 @@ bool TableModel::updateItem(const ModelItemValues& itemValues) {
}
}
void TableModel::setOnlineCredentials(const QString& mail,
const QString& uuid,
const QString& token) {
QModelIndex itemIndex = getIndexByRoleValue(mail, MailRole);
setItemData(itemIndex, {{OnlineIdRole, uuid}, {AccessCodeRole, token}});
}
QJsonDocument TableModel::getMailInviteJsonDoc(const QString& mail,
const QString& serverUrl) const {
QJsonDocument doc = QJsonDocument();
QModelIndex index = getIndexByRoleValue(mail, MailRole);
if (index.isValid()) {
QJsonObject rootObject;
const QString user_id = data(index, OnlineIdRole).toString();
const QString email = data(index, MailRole).toString();
const QString name = data(index, FullNameRole).toString();
const QString token = data(index, AccessCodeRole).toString();
const QString accessUrl = serverUrl + "/" + token;
QJsonObject userObject;
userObject.insert("user_id", user_id);
userObject.insert("email", email);
userObject.insert("name", name);
userObject.insert("access_url", accessUrl);
rootObject.insert("user", userObject);
doc.setObject(rootObject);
}
return doc;
}
void TableModel::updateBiddings(const QList<bidding> biddings) {
QListIterator<bidding> i(biddings);
while (i.hasNext()) {
const bidding localBidding = i.next();
qInfo() << "Processing bidding:";
const int biddingRound = localBidding.biddingRound;
if (biddingRound == 0 || biddingRound > 3) {
qWarning() << "Bidding round exceeds valid bounds. Ignoring...";
continue;
}
const QString uuid = localBidding.userId;
const QModelIndex itemIndex = getIndexByRoleValue(localBidding.userId, OnlineIdRole);
if (itemIndex.isValid()) {
qInfo() << "Found Uuid for user:" << itemIndex.data(MailRole);
const QMap<int, QVariant> values = getItemValues(localBidding);
setItemData(itemIndex, values);
}
}
}
bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) {
if (parentIndex != QModelIndex()) {
qWarning() << "Removing of child rows is not supported yet!";
@ -279,6 +340,102 @@ void TableModel::insertItems(int startPosition,
m_undoStack->push(insertCommand);
}
qreal TableModel::nTotalExpectedShares() const {
qreal result = 0;
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
/// if BiddingTypeRole is not empty
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
if (biddingType.isEmpty() || shareAmount == 0.0) {
continue;
}
/// add amount
const QString shareType = (*i)->data(ShareTypeRole).toString();
if (shareType == "bezahlt") {
result += shareAmount;
} else if (shareType == "teils/teils") {
result += shareAmount / 2;
} else {
qInfo() << "Share type not 'bezahlt' nor 'teils/teils'. Will be ignored..";
qDebug() << "Share type:" << shareType;
}
}
qInfo() << "Biddings expected for " << result << "shares!";
return result;
}
int TableModel::nExpectedBiddings() const {
int result = 0;
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
if (biddingType.isEmpty()) {
continue;
}
const double shareAmount = (*i)->data(ShareAmountRole).toDouble();
if (shareAmount == 0.0) {
continue;
}
const QString shareType = (*i)->data(ShareTypeRole).toString();
if (shareType == "bezahlt" || shareType == "teils/teils") {
result++;
}
}
return result;
}
int TableModel::nPlacedBiddings1() const { return nPlacedBiddings(Bidding1Role); }
int TableModel::nPlacedBiddings2() const { return nPlacedBiddings(Bidding2Role); }
int TableModel::nPlacedBiddings3() const { return nPlacedBiddings(Bidding3Role); }
int TableModel::biddingSum1() const { return biddingSum(Bidding1Role); }
int TableModel::biddingSum2() const { return biddingSum(Bidding2Role); }
int TableModel::biddingSum3() const { return biddingSum(Bidding3Role); }
qreal TableModel::biddingAverage1() const {
const UserRoles biddingRole = Bidding1Role;
const qreal averageBidding = averageBiddingAmount(biddingRole);
qInfo() << "average calculation (1):" << averageBidding;
return averageBidding;
}
qreal TableModel::biddingAverage2() const {
const UserRoles biddingRole = Bidding2Role;
const qreal averageBidding = averageBiddingAmount(biddingRole);
qInfo() << "average calculation (2):" << averageBidding;
return averageBidding;
}
qreal TableModel::biddingAverage3() const {
const UserRoles biddingRole = Bidding3Role;
const qreal averageBidding = averageBiddingAmount(biddingRole);
qInfo() << "average calculation (3):" << averageBidding;
return averageBidding;
}
void TableModel::onRowCountChanged(const QModelIndex& parent, int first, int last) {
Q_UNUSED(first);
Q_UNUSED(last);
if (parent != QModelIndex()) {
return;
}
emit rowCountChanged();
emit nExpectedBiddingsChanged();
emit nTotalExpectedSharesChanged();
emit nPlacedBiddingsChanged(1);
emit nPlacedBiddingsChanged(2);
emit nPlacedBiddingsChanged(3);
emit biddingSumChanged(1);
emit biddingSumChanged(2);
emit biddingSumChanged(3);
emit biddingAverageChanged(1);
emit biddingAverageChanged(2);
emit biddingAverageChanged(3);
}
void TableModel::execInsertItems(const int firstRow, const QList<ModelItemValues> valueList) {
const int nRows = valueList.size();
qDebug() << "Inserting" << nRows << "items...";
@ -305,14 +462,48 @@ void TableModel::execEditItemData(const int row, const QMap<int, QVariant>& chan
bool isDataChanged = item->setItemData(changedValues);
if (isDataChanged) {
/// FIXME due to the mapping from roles to the DisplayRole of different columns the complete row
/// is getting notified about (potential) data changes; dataChanged should be called only for
/// the affected columns
/// FIXME due to the mapping from roles to the DisplayRole of different columns the complete
/// row is getting notified about (potential) data changes; dataChanged should be called only
/// for the affected columns
const QModelIndex firstIndex = this->index(row, 0);
const QModelIndex lastIndex = this->index(row, USER_FACING_ROLES.size() - 1);
QList<int> roles = changedValues.keys();
roles.insert(0, Qt::DisplayRole);
emit dataChanged(firstIndex, lastIndex, roles.toVector());
if (roles.contains(BiddingTypeRole) || roles.contains(ShareAmountRole) ||
roles.contains(ShareTypeRole)) {
emit nExpectedBiddingsChanged();
emit nTotalExpectedSharesChanged();
}
if (roles.contains(ShareAmountRole) || roles.contains(ShareTypeRole)) {
emit nPlacedBiddingsChanged(1);
emit nPlacedBiddingsChanged(2);
emit nPlacedBiddingsChanged(3);
emit biddingSumChanged(1);
emit biddingSumChanged(2);
emit biddingSumChanged(3);
emit biddingAverageChanged(1);
emit biddingAverageChanged(2);
emit biddingAverageChanged(3);
} else {
/// no changes to share amount or type, but maybe to the biddings:
if (roles.contains(Bidding1Role)) {
emit nPlacedBiddingsChanged(1);
emit biddingSumChanged(1);
emit biddingAverageChanged(1);
}
if (roles.contains(Bidding2Role)) {
emit nPlacedBiddingsChanged(2);
emit biddingSumChanged(2);
emit biddingAverageChanged(2);
}
if (roles.contains(Bidding3Role)) {
emit nPlacedBiddingsChanged(3);
emit biddingSumChanged(3);
emit biddingAverageChanged(3);
}
}
}
}
@ -408,3 +599,141 @@ bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex,
return true;
}
}
int TableModel::nPlacedBiddings(const UserRoles biddingRole) const {
int result = 0;
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
const QString shareType = (*i)->data(ShareTypeRole).toString();
if (biddingType.isEmpty() || shareAmount == 0.0) {
continue;
}
if (shareType == "erarbeitet" || shareType == "") {
continue;
}
int localBidding = (*i)->data(biddingRole).toInt();
if (localBidding > 0) {
result++;
}
}
return result;
}
int TableModel::biddingSum(const UserRoles biddingRole) const {
int result = 0;
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
const QString shareType = (*i)->data(ShareTypeRole).toString();
if (biddingType.isEmpty() || shareAmount == 0.0) {
continue;
}
if (shareType == "erarbeitet" || shareType == "") {
continue;
}
result += (*i)->data(biddingRole).toInt();
}
return result;
}
qreal TableModel::averageBiddingAmount(const UserRoles biddingRole) const {
qInfo() << "Calculating average bidding for role:" << ROLE_NAMES.value(biddingRole);
const qreal localTotalSharesWithBiddings = totalSharesWithBiddings(biddingRole);
if (localTotalSharesWithBiddings <= 0) {
qInfo() << "No biddings found. Aborting calculation...";
return 0;
}
const qreal localTotalBiddingAmount = biddingSum(biddingRole);
const qreal alternateBiddingAverage = localTotalBiddingAmount / localTotalSharesWithBiddings;
qDebug() << "Total bidding amount:" << localTotalBiddingAmount;
qDebug() << "Total bidding shares:" << localTotalSharesWithBiddings;
return alternateBiddingAverage;
}
qreal TableModel::totalSharesWithBiddings(const UserRoles biddingRole) const {
qreal result = 0;
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
const QString biddingType = (*i)->data(BiddingTypeRole).toString();
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
if (biddingType.isEmpty() || shareAmount == 0.0) {
continue;
}
const int bidding = (*i)->data(biddingRole).toInt();
const QString shareType = (*i)->data(ShareTypeRole).toString();
const bool isValid = bidding != 0 && shareAmount != 0.0;
if (isValid) {
// qInfo() << "Including entry in bidding average calculation. MailRole:"
// << (*i)->data(MailRole);
// qreal bidForWholeShare = 0;
if (shareType == "bezahlt") {
result += shareAmount;
} else if (shareType == "teils/teils") {
result += shareAmount / 2;
} else {
qInfo() << "Share type not 'bezahlt' nor 'teils/teils'. Will be ignored..";
qDebug() << "Share type:" << shareType;
continue;
}
}
}
qInfo() << "Biddings exist for " << result << "shares!";
return result;
}
QModelIndex TableModel::getIndexByRoleValue(const QString& valueString, const int role) const {
const QString text =
QString("Searching list item with value '%1' for role '%2'").arg(valueString, role);
qInfo() << text;
int itemRow = -1;
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
const QString mailValue = (*i)->data(role).toString();
qDebug() << "found value:" << mailValue;
if (valueString == mailValue) {
itemRow = i - m_items.constBegin();
qInfo() << "Found item at row:" << itemRow;
break;
} else {
qDebug() << "Not the right item. Continuing search...";
continue;
}
}
if (itemRow < 0 || itemRow >= m_items.length()) {
qWarning() << "Couldn't find item with:" << valueString << " matching role" << role
<< "- Returning...";
return QModelIndex();
}
const QModelIndex itemIndex = index(itemRow, 0);
return itemIndex;
}
QMap<int, QVariant> TableModel::getItemValues(const bidding bid) {
const int biddingRound = bid.biddingRound;
const int amount = bid.amount;
const QString depotWishOne = bid.depotWishOne;
const QString depotWishTwo = bid.depotWishTwo;
UserRoles biddingRole;
switch (biddingRound) {
case 1:
biddingRole = Bidding1Role;
break;
case 2:
biddingRole = Bidding2Role;
break;
case 3:
biddingRole = Bidding3Role;
break;
default:
break;
}
if (biddingRound == 1) {
return {{biddingRole, amount}, {DepotWish1Role, depotWishOne}, {DepotWish2Role, depotWishTwo}};
} else {
return {{biddingRole, amount}};
}
}

View File

@ -2,7 +2,9 @@
#define TABLEMODEL_H
#include <QAbstractTableModel>
#include "metadata.h"
class bidding;
class QUndoStack;
class ModelItem;
@ -43,6 +45,11 @@ class TableModel : public QAbstractTableModel {
QString updateItemsFromJson(const QByteArray& jsonData);
bool updateItem(const ModelItemValues& itemValues);
void setOnlineCredentials(const QString& mail, const QString& uuid, const QString& token);
QJsonDocument getMailInviteJsonDoc(const QString& mail, const QString& serverUrl) const;
void updateBiddings(const QList<bidding> biddings);
public slots:
// bool insertRows(int position, int rows, const QModelIndex& parentIndex = QModelIndex())
// override;
@ -55,6 +62,33 @@ class TableModel : public QAbstractTableModel {
const QList<ModelItemValues>& itemValuesList,
const QModelIndex& parentIndex = QModelIndex());
/// property functions
qreal nTotalExpectedShares() const;
int nExpectedBiddings() const;
int nPlacedBiddings1() const;
int nPlacedBiddings2() const;
int nPlacedBiddings3() const;
int biddingSum1() const;
int biddingSum2() const;
int biddingSum3() const;
qreal biddingAverage1() const;
qreal biddingAverage2() const;
qreal biddingAverage3() const;
signals:
void rowCountChanged();
void nExpectedBiddingsChanged();
void nTotalExpectedSharesChanged();
void nPlacedBiddingsChanged(const int roundNumber);
void biddingSumChanged(const int roundNumber);
void biddingAverageChanged(const int roundNumber);
private slots:
void onRowCountChanged(const QModelIndex& parent, int first, int last);
private:
/// *** members ***
// TODO shared_ptr -> unique_ptr
@ -74,6 +108,14 @@ class TableModel : public QAbstractTableModel {
QModelIndex searchItemIndex(const ModelItemValues givenItemValues) const;
bool isItemEqualToItemValues(const QModelIndex& itemIndex,
const ModelItemValues givenItemValues) const;
int nPlacedBiddings(const UserRoles biddingRole) const;
int biddingSum(const UserRoles biddingRole) const;
qreal averageBiddingAmount(const UserRoles biddingRole) const;
qreal totalSharesWithBiddings(const UserRoles biddingRole) const;
QModelIndex getIndexByRoleValue(const QString& valueString, const int role) const;
QMap<int, QVariant> getItemValues(const bidding bid);
};
#endif // TABLEMODEL_H

View File

@ -5,10 +5,17 @@
// TODO add namespace
static const QString ROUTE_USER_LOG_IN = "/log_in";
static const QString apiPrefix = "/api/";
static const QString ROUTE_ITEMS = apiPrefix + "items";
static const QString ROUTE_ADMIN_LOG_IN = apiPrefix + "log_in";
static const QString ROUTE_REGISTER_USER = apiPrefix + "users";
static const QString ROUTE_MAIL_INVITE = apiPrefix + "invite";
static const QString ROUTE_BIDDINGROUNDS = apiPrefix + "bidding_rounds";
static const QString ROUTE_CURRENT_BIDDINGROUND = ROUTE_BIDDINGROUNDS + "/get_current";
static const QString ROUTE_START_BIDDINGROUND = ROUTE_BIDDINGROUNDS + "/start_new";

View File

@ -5,10 +5,13 @@
#include <QRestAccessManager>
#include <QRestReply>
#include "../formats/jsonparser.h"
#include "../genericcore.h"
#include "../structs.h"
#include "apiroutes.h"
#include "../data/settingshandler.h"
using namespace Qt::StringLiterals;
ServerCommunicator::ServerCommunicator(GenericCore* core)
@ -18,9 +21,16 @@ ServerCommunicator::ServerCommunicator(GenericCore* core)
m_restManager = std::make_shared<QRestAccessManager>(&m_netManager);
m_serviceApi = std::make_shared<QNetworkRequestFactory>();
connect(core, &GenericCore::loginAndStoreAuthToken, this, &ServerCommunicator::onLoginTriggered);
connect(this, &ServerCommunicator::loginSuccessful, core, &GenericCore::onLoginSuccessful);
connect(m_core, &GenericCore::sendGetRequest, this,
&ServerCommunicator::onSendGetRequestTriggered);
connect(m_core, &GenericCore::sendPostRequest, this,
&ServerCommunicator::onSendPostRequestTriggered);
connect(this, &ServerCommunicator::biddingsChanged, m_core, &GenericCore::onBiddingsChanged);
connect(this, &ServerCommunicator::onlineUserAccountReceived, m_core,
&GenericCore::onOnlineUserAccountReceived);
}
bool ServerCommunicator::sslSupported() const {
@ -44,73 +54,24 @@ void ServerCommunicator::setUrl(const QUrl& url) {
emit urlChanged();
}
void ServerCommunicator::fetchItems() {
/// Set up a GET request
m_restManager->get(m_serviceApi->createRequest(ROUTE_ITEMS), this, [this](QRestReply& reply) {
if (reply.isSuccess()) {
qInfo() << "Fetching items successful.";
const QJsonDocument doc = reply.readJson().value();
emit itemsFetched(doc.toJson());
} else {
if (reply.hasError()) {
const QString errorString = reply.errorString();
qCritical() << "ERROR:" << errorString;
emit itemsFetchFailure(errorString);
} else {
int statusCode = reply.httpStatus();
qCritical() << "ERROR:" << statusCode;
emit itemsFetchFailure(QString::number(statusCode));
emit itemsFetchFailure(QString("HTTP status code: %1").arg(statusCode));
}
}
});
}
void ServerCommunicator::postItems(const QByteArray& jsonData) {
QNetworkReply* reply = m_restManager->post(m_serviceApi->createRequest(ROUTE_ITEMS), jsonData);
QObject::connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray responseData = reply->readAll();
const QString message = QString("POST successful! Response: %1").arg(responseData);
qInfo() << message;
emit postRequestSuccessful(responseData);
} else {
const QString message = QString("Error: %1").arg(reply->errorString());
qDebug() << message;
emit postRequestFailure(message);
}
reply->deleteLater();
});
}
void ServerCommunicator::deleteItem(const QString& id) {
const QString deleteRoute = QString("%1/%2").arg(ROUTE_ITEMS, id);
QNetworkReply* reply = m_restManager->deleteResource(m_serviceApi->createRequest(deleteRoute));
QObject::connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray responseData = reply->readAll();
const QString message = QString("DELETE successful! Response: %1").arg(responseData);
qInfo() << message;
emit deleteRequestSuccessful(responseData);
} else {
const QString message = QString("Error: %1").arg(reply->errorString());
qDebug() << message;
emit deleteRequestFailure(message);
}
reply->deleteLater();
});
}
void ServerCommunicator::setServerConfiguration(const QString url,
const QString email,
const QString password) {
const QString password,
const QString authToken) {
setUrl(url);
m_email = email;
m_password = password;
m_email = email;
m_password = password;
m_authToken = authToken;
if (!m_authToken.isEmpty()) {
m_serviceApi->setBearerToken(authToken.toLatin1());
}
}
QString ServerCommunicator::getUserLoginUrl() const {
const QString logInUrl = m_serviceApi->baseUrl().toString() + ROUTE_USER_LOG_IN;
return logInUrl;
}
void ServerCommunicator::onSendGetRequestTriggered(const GetRequestTypes type,
@ -146,6 +107,7 @@ void ServerCommunicator::onSendGetRequestTriggered(const GetRequestTypes type,
return;
}
m_serviceApi->setBearerToken(m_authToken.toLatin1());
const QNetworkRequest request = m_serviceApi->createRequest(path);
m_restManager->get(request, this, [this, type](QRestReply& reply) {
@ -191,8 +153,115 @@ void ServerCommunicator::onGetReplyFailure(const GetRequestTypes type, const QSt
m_core->displayStatusMessage(message);
}
void ServerCommunicator::onSendPostRequestTriggered(const PostRequestTypes type,
const QByteArray data) {
QString path;
switch (type) {
case LogInAdmin:
path = ROUTE_ADMIN_LOG_IN;
break;
case RegisterUser:
path = ROUTE_REGISTER_USER;
break;
case MailInvite:
path = ROUTE_MAIL_INVITE;
break;
default:
qWarning() << "No route found for PostRequestType:" << type;
break;
}
// TODO move into default case of switch statement?
if (path.isEmpty()) {
qDebug() << "Empty path -> Not sending a request.";
return;
}
m_serviceApi->setBearerToken(m_authToken.toLatin1());
const QNetworkRequest request = m_serviceApi->createRequest(path);
m_restManager->post(request, data, this, [this, type](QRestReply& reply) {
if (reply.isSuccess()) {
int statusCode = reply.httpStatus();
qInfo() << "Request successful. Status code:" << statusCode;
const QJsonDocument doc = reply.readJson().value();
onPostReplySuccessful(type, doc);
} else {
if (reply.hasError()) {
const QString errorString = reply.errorString();
qWarning() << "Network error:" << errorString;
onPostReplyFailure(type, errorString);
} else {
int statusCode = reply.httpStatus();
qWarning() << "Request not successful:" << statusCode;
qInfo() << "Content:" << reply.readJson();
onPostReplyFailure(type, QString("HTTP status code: %1").arg(statusCode));
}
}
});
}
void ServerCommunicator::onPostReplySuccessful(const PostRequestTypes type,
const QJsonDocument doc) {
switch (type) {
case LogInAdmin:
qInfo() << "Admin successfully logged in:" << type;
handleLogInReply(doc);
break;
case RegisterUser:
qInfo() << "Register user successful:" << type;
onlineUserAccountReply(doc);
break;
case MailInvite:
qInfo() << "Mail invite successful sent:" << type;
mailInviteSentReply(doc);
break;
default:
qWarning() << "Can't match request type:" << type;
break;
}
}
void ServerCommunicator::onPostReplyFailure(const PostRequestTypes type,
const QString errorString) {
const QString message =
QString("Request of type %1 returned: %2").arg(QString::number(type), errorString);
// NEXT improve error message to the UI;
emit m_core->displayStatusMessage(message);
}
QByteArray ServerCommunicator::createLoginBody() {
QHash<QString, QVariant> values;
values.insert("email", m_email);
values.insert("password", m_password);
return JsonParser::toJsonDoc(values, "admin");
}
void ServerCommunicator::handleLogInReply(const QJsonDocument jsonDoc) {
QJsonObject rootObject = jsonDoc.object();
bool hasErrors = rootObject.contains(QString("errors"));
if (hasErrors) {
qCritical() << "Reply has error(s)!";
QString errorString = rootObject["errors"].toString();
emit m_core->displayStatusMessage(errorString);
} else {
qInfo() << "Reply has no error(s)!";
const QJsonObject dataObject = rootObject["data"].toObject();
const QString authToken = dataObject["token"].toString();
m_authToken = authToken;
SettingsHandler::saveSettings({{"token", authToken}}, "Server");
emit loginSuccessful();
emit m_core->displayStatusMessage("Successfully logged in.");
}
}
void ServerCommunicator::currentBiddingRoundChangedReply(const QJsonDocument jsonDoc) {
qInfo() << "Current bidding round received.";
// REFACTOR implement & use "JsonParser::parseServerResponse(const QJsonDocument& jsonDoc,
// QHash<QString, int> entries)" (generalized version of
// "serverUserCredentialsToItemValues")
const QJsonObject rootObject = jsonDoc["data"].toObject();
const int roundNumber = rootObject["round_number"].toInt();
const bool stopped = rootObject["stopped"].toBool();
@ -204,16 +273,49 @@ void ServerCommunicator::currentBiddingRoundChangedReply(const QJsonDocument jso
}
void ServerCommunicator::currentBiddingsReply(const QJsonDocument jsonDoc) {
qCritical() << "currentBiddingsReply:" << jsonDoc;
qInfo() << "currentBiddingsReply:" << jsonDoc;
// NEXT extract biddings from jsonDoc
const int roundNumber = 0;
const bidding dummyBidding{.userId = QUuid(),
.biddingRound = 0,
.amount = 123,
.depotWishOne = "one",
.depotWishTwo = "two"};
const QList<bidding> biddings{dummyBidding};
const QList<bidding> biddings = JsonParser::extractBiddings(jsonDoc);
emit biddingsChanged(roundNumber, biddings);
emit biddingsChanged(biddings);
}
void ServerCommunicator::onlineUserAccountReply(const QJsonDocument jsonDoc) {
qInfo() << "Online user account received.";
ModelItemValues values = JsonParser::serverUserCredentialsToItemValues(jsonDoc);
emit onlineUserAccountReceived(values[MailRole].toString(), values[OnlineIdRole].toString(),
values[AccessCodeRole].toString());
}
void ServerCommunicator::mailInviteSentReply(const QJsonDocument jsonDoc) {
qInfo() << "Invitation mail successfully sent.";
emit m_core->displayStatusMessage("Invitation mail successfully sent.");
}
void ServerCommunicator::onLoginTriggered() {
qInfo() << "Login triggered...";
if (m_email.isEmpty() || m_password.isEmpty()) {
emit m_core->displayStatusMessage(
"Missing email or password in settings! Not trying to log in.");
return;
}
if (m_authToken.isEmpty()) {
/// get new authToken
qInfo() << "Creating a new authToken!";
const QByteArray loginBody = createLoginBody();
onSendPostRequestTriggered(LogInAdmin, loginBody);
} else {
/// try authToken
qWarning() << "Validity check of token not implemented yet!!!";
qInfo() << "Assuming validity of token...";
emit loginSuccessful();
// TODO try validity of token and trigger log_in if not valid
// try default route to test access;
// if (access denied) {
// m_apiClient->sendAPIRequestPost(ROUTE_LOG_IN_ADMIN, );
// }
}
}

View File

@ -22,16 +22,22 @@ class ServerCommunicator : public QObject {
QUrl url() const;
void setUrl(const QUrl& url);
void setServerConfiguration(const QString url, const QString email, const QString password);
void setServerConfiguration(const QString url,
const QString email,
const QString password,
const QString authToken);
QString getUserLoginUrl() const;
public slots:
void onLoginTriggered();
void onSendGetRequestTriggered(const GetRequestTypes type, QVariant data);
void onGetReplySuccessful(const GetRequestTypes type, const QJsonDocument doc);
void onGetReplyFailure(const GetRequestTypes type, const QString errorString);
void fetchItems();
void postItems(const QByteArray& jsonData);
void deleteItem(const QString& id);
void onSendPostRequestTriggered(const PostRequestTypes type, const QByteArray data);
void onPostReplySuccessful(const PostRequestTypes type, const QJsonDocument doc);
void onPostReplyFailure(const PostRequestTypes type, const QString errorString);
signals:
void urlChanged();
@ -43,8 +49,13 @@ class ServerCommunicator : public QObject {
void deleteRequestSuccessful(const QByteArray responseData);
void deleteRequestFailure(const QString errorString);
void loginSuccessful();
void currentBiddingRoundChanged(int round, bool isRunning);
void biddingsChanged(int round, QList<bidding> biddings);
void biddingsChanged(QList<bidding> biddings);
void onlineUserAccountReceived(const QString mailAddress,
const QString uuid,
const QString accessToken);
private:
GenericCore* m_core = nullptr;
@ -55,11 +66,17 @@ class ServerCommunicator : public QObject {
QString m_email;
QString m_password;
// QString m_authToken;
QString m_authToken;
QByteArray createLoginBody();
/// reply parser
void handleLogInReply(const QJsonDocument jsonDoc);
void currentBiddingRoundChangedReply(const QJsonDocument jsonDoc);
void currentBiddingsReply(const QJsonDocument jsonDoc);
void onlineUserAccountReply(const QJsonDocument jsonDoc);
void mailInviteSentReply(const QJsonDocument jsonDoc);
};
#endif // SERVERCOMMUNICATOR_H

View File

@ -4,7 +4,7 @@
#include <QUuid>
struct bidding {
QUuid userId;
QString userId;
int biddingRound;
int amount;
QString depotWishOne;

View File

@ -17,7 +17,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core LinguistTools)
add_executable(${TARGET_APP} core_test.cpp)
add_executable(${TARGET_APP} core_test.cpp
model_test.cpp)
target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/include)

View File

@ -9,7 +9,7 @@ inline void PrintTo(const QString& qString, ::std::ostream* os) { *os << qUtf8Pr
QT_END_NAMESPACE
TEST(CoreTests, TestEqualString) {
const QString coreName("GenericCore");
const QString coreName("BeetRoundCore");
const QString coreVersion("0.1.0");
const auto expected = QString("%1 (Version %2)").arg(coreName).arg(coreVersion);
auto core = std::make_unique<GenericCore>();

View File

@ -0,0 +1,131 @@
#include <gtest/gtest.h>
#include <QString>
#include <QtGui/QUndoCommand>
#include "../../libs/BeetRoundCore/model/tablemodel.h"
QT_BEGIN_NAMESPACE
inline void PrintTo(const QString& qString, ::std::ostream* os) { *os << qUtf8Printable(qString); }
QT_END_NAMESPACE
struct ModelTest : testing::Test {
std::shared_ptr<TableModel> model;
ModelTest() { model = make_shared<TableModel>(new QUndoStack()); }
virtual ~ModelTest() {}
};
struct ModelTestWithData : ModelTest {
ModelTestWithData() {
QList<ModelItemValues> itemList;
ModelItemValues itemValues1 = {{Bidding1Role, 100},
{ShareTypeRole, "bezahlt"},
{ShareAmountRole, 1},
{BiddingTypeRole, "digital"}};
itemList.append(itemValues1);
ModelItemValues itemValues2 = {{Bidding1Role, 100},
{ShareTypeRole, "bezahlt"},
{ShareAmountRole, 1},
{BiddingTypeRole, ""}};
itemList.append(itemValues2);
ModelItemValues itemValues3 = {{Bidding1Role, 50},
{ShareTypeRole, "teils/teils"},
{ShareAmountRole, 1},
{BiddingTypeRole, "paper"}};
itemList.append(itemValues3);
ModelItemValues itemValues4 = {{Bidding1Role, 50},
{ShareTypeRole, "bezahlt"},
{ShareAmountRole, 0.5},
{BiddingTypeRole, "paper"}};
itemList.append(itemValues4);
ModelItemValues itemValues5 = {{Bidding1Role, 100},
{ShareTypeRole, "erarbeitet"},
{ShareAmountRole, 1},
{BiddingTypeRole, "paper"}};
itemList.append(itemValues5);
ModelItemValues itemValues6 = {{Bidding1Role, 0},
{ShareTypeRole, "bezahlt"},
{ShareAmountRole, 1},
{BiddingTypeRole, "online"}};
itemList.append(itemValues6);
model->insertItems(0, itemList);
}
};
/// empty model
TEST_F(ModelTest, TestRowCountEmpty) {
const int expectedRowCount = 0;
const auto actualRowCount = model->rowCount();
ASSERT_EQ(expectedRowCount, actualRowCount);
}
TEST_F(ModelTest, TestBidding1Average) {
const int expected = 0;
const auto actualBidding1Average = model->biddingAverage1();
ASSERT_EQ(expected, actualBidding1Average);
}
TEST_F(ModelTest, ExpectedPlacedBiddings) {
const int expected = 0;
const auto expectedPlacedBiddings = model->nExpectedBiddings();
ASSERT_EQ(expected, expectedPlacedBiddings);
}
TEST_F(ModelTest, ExpectedTotalShareAmount) {
const int expected = 0;
const auto nTotalExpectedShares = model->nTotalExpectedShares();
ASSERT_EQ(expected, nTotalExpectedShares);
}
/// model with data
TEST_F(ModelTestWithData, TestRowCount) {
const int expected = 6;
const auto actual = model->rowCount();
ASSERT_EQ(expected, actual);
}
TEST_F(ModelTestWithData, TestBidding1Average) {
const qreal expected = 100;
const auto actual = model->biddingAverage1();
ASSERT_EQ(expected, actual);
}
TEST_F(ModelTestWithData, ExpectedPlacedBiddings) {
const int expected = 4;
const auto actual = model->nExpectedBiddings();
ASSERT_EQ(expected, actual);
}
TEST_F(ModelTestWithData, PlacedBiddings1) {
const int expected = 3;
const auto actual = model->nPlacedBiddings1();
ASSERT_EQ(expected, actual);
}
TEST_F(ModelTestWithData, NTotalExpectedShares) {
const qreal expected = 3;
const auto actual = model->nTotalExpectedShares();
ASSERT_EQ(expected, actual);
}