Compare commits

...

21 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
27 changed files with 926 additions and 224 deletions

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

@ -16,7 +16,6 @@
#include "genericcore.h"
#include "model/generalsortfiltermodel.h"
#include "model/metadata.h"
#include "model/modelsummary.h"
#include "model/tablemodel.h"
#include "widgets/biddingroundcontrol.h"
#include "widgets/comboboxdelegate.h"
@ -75,6 +74,10 @@ MainWindow::MainWindow(QWidget* parent)
onCurrentChanged(QModelIndex(), QModelIndex());
setupEventTab();
// #ifndef QT_DEBUG
initServerConnection();
// #endif
}
MainWindow::~MainWindow() { delete ui; }
@ -563,3 +566,11 @@ void MainWindow::setupEventTab() {
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

@ -120,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

@ -16,14 +16,23 @@ SummaryWidget::SummaryWidget(std::shared_ptr<ModelSummary> modelSummary, QWidget
QVBoxLayout* mainLayout = new QVBoxLayout(this);
/// monthly need
QHBoxLayout* footerLayout = new QHBoxLayout();
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);
@ -50,6 +59,11 @@ void SummaryWidget::onNExpectedBiddingChanged(int 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:
@ -140,6 +154,9 @@ void SummaryWidget::setupConnections() {
// "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,

View File

@ -18,6 +18,7 @@ class SummaryWidget : public QWidget {
private slots:
void onFinancialNeedChanged(int newFinancialNeed);
void onNExpectedBiddingChanged(int newNExpected);
void onNTotalExpectedSharesChanged(qreal newNExpected);
void onNPlacedBiddingsChanged(const int roundNumber);
void onBiddingSumChanged(const int roundNumber);
@ -31,8 +32,9 @@ class SummaryWidget : public QWidget {
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;
const int m_financialNeed = 13942;
QSpinBox* m_financialNeedBox = nullptr;
QLabel* m_nTotalExpectedShares = nullptr;
QLabel* m_rowCountValueLabel;
QLabel* m_nExpectedBiddingsValueLabel;

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,6 +15,7 @@
#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"
@ -155,9 +156,40 @@ 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() {
@ -230,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

@ -47,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);

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

@ -10,6 +10,8 @@ ModelSummary::ModelSummary(std::shared_ptr<TableModel> model, QObject* parent)
// 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);
@ -29,6 +31,8 @@ QBindable<int> ModelSummary::bindableRowCount() {
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(); }

View File

@ -19,6 +19,8 @@ class ModelSummary : public QObject {
int rowCount() const;
QBindable<int> bindableRowCount();
qreal nTotalExpectedShares() const;
int nExpectedBiddings() const;
int nPlacedBiddings1() const;
int nPlacedBiddings2() const;
@ -36,6 +38,7 @@ class ModelSummary : public QObject {
void rowCountChanged();
void nExpectedBiddingsChanged();
void nTotalExpectedSharesChanged();
void nPlacedBiddingsChanged(const int roundNumber);
void biddingSumChanged(const int roundNumber);
void biddingAverageChanged(const int roundNumber);

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;
@ -231,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!";
@ -282,27 +340,28 @@ void TableModel::insertItems(int startPosition,
m_undoStack->push(insertCommand);
}
void TableModel::onRowCountChanged(const QModelIndex& parent, int first, int last) {
Q_UNUSED(first);
Q_UNUSED(last);
if (parent != QModelIndex()) {
return;
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;
}
}
emit rowCountChanged();
emit nExpectedBiddingsChanged();
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);
qInfo() << "Biddings expected for " << result << "shares!";
return result;
}
int TableModel::nExpectedBiddings() const {
@ -345,6 +404,7 @@ qreal TableModel::biddingAverage2() const {
qInfo() << "average calculation (2):" << averageBidding;
return averageBidding;
}
qreal TableModel::biddingAverage3() const {
const UserRoles biddingRole = Bidding3Role;
const qreal averageBidding = averageBiddingAmount(biddingRole);
@ -352,6 +412,30 @@ qreal TableModel::biddingAverage3() const {
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...";
@ -378,18 +462,19 @@ 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());
// NEXT check which roles are changed & trigger only changed properties
if (roles.contains(BiddingTypeRole) || roles.contains(ShareAmountRole)) {
if (roles.contains(BiddingTypeRole) || roles.contains(ShareAmountRole) ||
roles.contains(ShareTypeRole)) {
emit nExpectedBiddingsChanged();
emit nTotalExpectedSharesChanged();
}
if (roles.contains(ShareAmountRole) || roles.contains(ShareTypeRole)) {
emit nPlacedBiddingsChanged(1);
@ -518,6 +603,15 @@ bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex,
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++;
@ -529,6 +623,15 @@ int TableModel::nPlacedBiddings(const UserRoles biddingRole) const {
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;
@ -553,10 +656,14 @@ qreal TableModel::averageBiddingAmount(const UserRoles biddingRole) const {
qreal TableModel::totalSharesWithBiddings(const UserRoles biddingRole) const {
qreal result = 0;
for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) {
const qreal bidding = (*i)->data(biddingRole).toReal();
const qreal shareAmount = (*i)->data(ShareAmountRole).toReal();
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;
const bool isValid = bidding != 0 && shareAmount != 0.0;
if (isValid) {
// qInfo() << "Including entry in bidding average calculation. MailRole:"
// << (*i)->data(MailRole);
@ -575,3 +682,58 @@ qreal TableModel::totalSharesWithBiddings(const UserRoles biddingRole) const {
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

@ -4,6 +4,7 @@
#include <QAbstractTableModel>
#include "metadata.h"
class bidding;
class QUndoStack;
class ModelItem;
@ -44,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;
@ -57,6 +63,7 @@ class TableModel : public QAbstractTableModel {
const QModelIndex& parentIndex = QModelIndex());
/// property functions
qreal nTotalExpectedShares() const;
int nExpectedBiddings() const;
int nPlacedBiddings1() const;
int nPlacedBiddings2() const;
@ -74,6 +81,7 @@ class TableModel : public QAbstractTableModel {
void rowCountChanged();
void nExpectedBiddingsChanged();
void nTotalExpectedSharesChanged();
void nPlacedBiddingsChanged(const int roundNumber);
void biddingSumChanged(const int roundNumber);
void biddingAverageChanged(const int roundNumber);
@ -105,6 +113,9 @@ class TableModel : public QAbstractTableModel {
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);
}