#include "mainwindow.h" #include "basketmodel.h" #include "config.h" #include "jsonutil.h" #include "pricedialog.h" #include "reportdialog.h" #include "salemodel.h" #include "sellerdialog.h" #include "settingsdialog.h" #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; constexpr int STATUSBAR_TIMEOUT = 5000; MainWindow::MainWindow() { ui_.setupUi(this); marketplace_ = std::make_unique(); marketplace_->loadFromDb(); statusBar()->showMessage("Gespeicherte Daten wurden geladen.", STATUSBAR_TIMEOUT); BasketModel* model = new BasketModel(getMarketplace(), ui_.basketView); ui_.basketView->setModel(model); ui_.basketView->setColumnHidden(0, true); // hide the uuid setWindowTitle("KIMA2 - Kasse Nr. " + QSettings().value("global/cashPointNo").toString()); setSaleModel(); connect(ui_.actionQuit, &QAction::triggered, qApp, QApplication::quit); connect(ui_.newAction, &QAction::triggered, this, [=]() { if (marketplace_->getSellers().size() == 0 && marketplace_->getSales().size() == 0) { return; } auto dlgResult = QMessageBox(QMessageBox::Icon::Warning, "Sind Sie sicher?", "Möchten Sie wirklich alle gespeicherten Daten verwerfen?", QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, this) .exec(); if (dlgResult == QMessageBox::No) return; delete ui_.salesView->model(); dynamic_cast(ui_.basketView->model())->cancelSale(); marketplace_->clear(); setSaleModel(); }); ui_.sellerNoEdit->installEventFilter(this); ui_.givenSpinBox->installEventFilter(this); connect(ui_.actionEditSeller, &QAction::triggered, this, &MainWindow::onActionEditSellerTriggered); connect(ui_.paidButton, &QPushButton::clicked, this, &MainWindow::onPaidButtonTriggered); connect(ui_.givenSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged), this, &MainWindow::onGivenSpinBoxValueChanged); connect(ui_.basketView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::onBasketViewSelectionChanged); connect(ui_.cancelArticleButton, &QPushButton::clicked, this, &MainWindow::onCancelArticleButtonClicked); connect(ui_.cancelSaleButton, &QPushButton::clicked, this, &MainWindow::onCancelSaleButtonClicked); connect(ui_.printSaleReceiptButton, &QPushButton::clicked, this, &MainWindow::onPrintSaleReceiptButtonClicked); connect(ui_.cancelAllArticlesButton, &QPushButton::clicked, this, &MainWindow::onCancelAllArticlesButtonClicked); connect(ui_.aboutQtAction, &QAction::triggered, this, &MainWindow::onAboutQt); connect(ui_.aboutAction, &QAction::triggered, this, &MainWindow::onAbout); connect(ui_.openManualAction, &QAction::triggered, this, []() { auto locations = QStandardPaths::standardLocations(QStandardPaths::DataLocation); for (auto location : locations) { if (QFile::exists(location + QString("/Benutzerhandbuch.pdf"))) { QDesktopServices::openUrl( QUrl(QString("file:///") + location + QString("/Benutzerhandbuch.pdf"), QUrl::TolerantMode)); } } }); connect(ui_.licenseAction, &QAction::triggered, this, [=]() { QString licenseText( "Copyright © 2018 Martin Brodbeck\n\n" "Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der " "zugehörigen Dokumentationen (die \"Software\") erhält, die Erlaubnis erteilt, " "sie uneingeschränkt zu nutzen, inklusive und ohne Ausnahme mit dem Recht, " "sie zu verwenden, zu kopieren, zu verändern, zusammenzufügen, zu " "veröffentlichen, zu verbreiten, und Personen, denen diese Software überlassen " "wird, diese Rechte zu verschaffen, unter den folgenden Bedingungen:\n\n" "Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen " "Kopien oder Teilkopien der Software beizulegen.\n\n" "DIE SOFTWARE WIRD OHNE JEDE AUSDRÜCKLICHE ODER IMPLIZIERTE " "GARANTIE BEREITGESTELLT, EINSCHLIESSLICH DER GARANTIE ZUR BENUTZUNG " "FÜR DEN VORGESEHENEN ODER EINEM BESTIMMTEN ZWECK SOWIE JEGLICHER " "RECHTSVERLETZUNG, JEDOCH NICHT DARAUF BESCHRÄNKT. IN KEINEM FALL " "SIND DIE AUTOREN ODER COPYRIGHTINHABER FÜR JEGLICHEN SCHADEN ODER " "SONSTIGE ANSPRÜCHE HAFTBAR ZU MACHEN, OB INFOLGE DER ERFÜLLUNG " "EINES VERTRAGES, EINES DELIKTES ODER ANDERS IM ZUSAMMENHANG MIT DER " "SOFTWARE ODER SONSTIGER VERWENDUNG DER SOFTWARE ENTSTANDEN."); QMessageBox::information(this, "Lizenzinformation", licenseText); }); connect(ui_.reportAction, &QAction::triggered, this, [=]() { ReportDialog(this).exec(); }); connect(ui_.configAction, &QAction::triggered, this, [=]() { int result = SettingsDialog(this).exec(); if (result == QDialog::Accepted) { delete ui_.salesView->model(); marketplace_->loadFromDb(); setSaleModel(); } this->setWindowTitle("KIMA2 - Kasse Nr. " + QSettings().value("global/cashPointNo").toString()); }); connect(ui_.importSellerExcelAction, &QAction::triggered, this, &MainWindow::onImportSellerExcelActionTriggered); connect(ui_.importSellerJsonAction, &QAction::triggered, this, &MainWindow::onImportSellerJsonActionTriggered); connect(ui_.exportSellerJsonAction, &QAction::triggered, this, &MainWindow::onExportSellerJsonActionTriggered); connect(ui_.exportSalesJsonAction, &QAction::triggered, this, &MainWindow::onExportSalesJsonActionTriggered); connect(ui_.importSalesJsonAction, &QAction::triggered, this, &MainWindow::onImportSalesJsonActionTriggered); readGeometry(); setWindowIcon(QIcon(":/misc/kima2.ico")); } void MainWindow::onActionEditSellerTriggered() { auto dialog = std::make_unique(this); int retCode = dialog->exec(); delete ui_.salesView->model(); if (retCode == QDialog::Accepted) { marketplace_->sortSellers(); marketplace_->storeToDb(); statusBar()->showMessage("Änderungen an den Verkäufer-Stammdaten gespeichert.", STATUSBAR_TIMEOUT); } else { statusBar()->showMessage("Änderungen an den Verkäufer-Stammdaten verworfen!", STATUSBAR_TIMEOUT); } setSaleModel(); } void MainWindow::setSaleModel() { ui_.salesView->setModel(new SaleModel(getMarketplace(), ui_.salesView)); ui_.salesView->setColumnHidden(2, true); ui_.salesView->resizeColumnToContents(0); ui_.printSaleReceiptButton->setEnabled(false); ui_.cancelSaleButton->setEnabled(false); connect(static_cast(ui_.basketView->model()), &BasketModel::basketDataChanged, static_cast(ui_.salesView->model()), &SaleModel::onBasketDataChanged); connect(ui_.salesView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::onSalesViewSelectionChanged); } void MainWindow::onPaidButtonTriggered() { if (marketplace_->basketSize() > 0) { QString lastPrice{marketplace_->getBasketSumAsString().c_str()}; dynamic_cast(ui_.basketView->model())->finishSale(); ui_.salesView->resizeColumnToContents(0); ui_.lastPriceLabel1->setText(lastPrice); ui_.lastPriceLabel2->setText(lastPrice); ui_.basketSumLabel->setText(formatCentAsEuroString(0).c_str()); ui_.drawbackLabel->setText(formatCentAsEuroString(0).c_str()); ui_.drawbackContainerWidget->setEnabled(false); ui_.sellerNoEdit->setFocus(); statusBar()->showMessage("Verkaufsvorgang erfolgreich durchgeführt.", STATUSBAR_TIMEOUT); } } bool MainWindow::eventFilter(QObject* target, QEvent* event) { if (target == ui_.sellerNoEdit) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key::Key_Enter || keyEvent->key() == Qt::Key::Key_Return) { if (keyEvent->modifiers() == Qt::ControlModifier) { checkSellerNo(true); } else { checkSellerNo(false); } return true; } } } else if (target == ui_.givenSpinBox) { if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key::Key_Enter || keyEvent->key() == Qt::Key::Key_Return) { if (keyEvent->modifiers() == Qt::ControlModifier) { onPaidButtonTriggered(); return true; } } else if (keyEvent->key() == Qt::Key::Key_Escape) { ui_.drawbackLabel->setText(formatCentAsEuroString(0).c_str()); ui_.drawbackContainerWidget->setEnabled(false); ui_.sellerNoEdit->setFocus(); } } } return QMainWindow::eventFilter(target, event); } void MainWindow::checkSellerNo(bool ctrlPressed) { using std::regex, std::regex_match, std::smatch; auto inputText = ui_.sellerNoEdit->text().toStdString(); if (inputText.empty()) { if (ctrlPressed == false) { onPaidButtonTriggered(); } else if (marketplace_->getBasket().size() > 0) { ui_.drawbackContainerWidget->setEnabled(true); ui_.givenSpinBox->setFocus(); ui_.givenSpinBox->selectAll(); } return; } regex pattern{R"(\d{1,5})"}; smatch result; if (!regex_match(inputText, result, pattern)) { ui_.sellerNoEdit->clear(); return; } int sellerNo = std::stoi(result[0]); auto seller = marketplace_->findSellerWithSellerNo(sellerNo); if (seller) { PriceDialog priceDialog(this); auto dialogResult = priceDialog.exec(); if (dialogResult == QDialog::Accepted) { int price = priceDialog.getPrice(); std::string desc = priceDialog.getDescription(); dynamic_cast(ui_.basketView->model())->addArticle(seller, price, desc); ui_.basketSumLabel->setText(marketplace_->getBasketSumAsString().c_str()); } } ui_.sellerNoEdit->clear(); } void MainWindow::onGivenSpinBoxValueChanged(double value) { int givenInCent = std::round(value * 100); int basketSumInCent = marketplace_->getBasketSumInCent(); int drawback = givenInCent - basketSumInCent; ui_.drawbackLabel->setText(formatCentAsEuroString(drawback).c_str()); } void MainWindow::onBasketViewSelectionChanged(const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected) { if (selected.size() > 0) { ui_.cancelArticleButton->setEnabled(true); } else { ui_.cancelArticleButton->setEnabled(false); } } void MainWindow::onSalesViewSelectionChanged(const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected) { if (selected.size() > 0) { ui_.cancelSaleButton->setEnabled(true); if (!selected.indexes()[0].parent().isValid()) ui_.printSaleReceiptButton->setEnabled(true); else ui_.printSaleReceiptButton->setEnabled(false); } else { ui_.cancelSaleButton->setEnabled(false); ui_.printSaleReceiptButton->setEnabled(false); } } void MainWindow::onCancelArticleButtonClicked([[maybe_unused]] bool checked) { auto selModel = ui_.basketView->selectionModel(); if (selModel->hasSelection() == false) return; auto dlgResult = QMessageBox(QMessageBox::Icon::Warning, "Sind Sie sicher?", "Möchten Sie wirklich den Artikel stornieren?", QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, this) .exec(); if (dlgResult == QMessageBox::No) return; auto indexes = selModel->selectedRows(); std::sort(indexes.begin(), indexes.end()); // Deleting the rows, beginning with the last one! for (auto iter = indexes.constEnd() - 1; iter >= indexes.constBegin(); --iter) { ui_.basketView->model()->removeRow(iter->row()); } } void MainWindow::onCancelSaleButtonClicked([[maybe_unused]] bool checked) { auto selModel = ui_.salesView->selectionModel(); if (selModel->hasSelection() == false) return; auto dlgResult = QMessageBox(QMessageBox::Icon::Warning, "Sind Sie sicher?", "Möchten Sie wirklich stornieren?", QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, this) .exec(); if (dlgResult == QMessageBox::No) return; auto indexes = selModel->selectedRows(); std::sort(indexes.begin(), indexes.end()); // Deleting the rows, beginning with the last one! for (auto iter = indexes.constEnd() - 1; iter >= indexes.constBegin(); --iter) { ui_.salesView->model()->removeRow(iter->row(), iter->parent()); } } void MainWindow::onPrintSaleReceiptButtonClicked([[maybe_unused]] bool checked) { auto selModel = ui_.salesView->selectionModel(); if (selModel->hasSelection() == false) return; QSettings settings{}; QString posPrinterDevice = settings.value("global/posPrinterDevice", "").toString(); QString posPrinterEndpoint = settings.value("global/posPrinterEndpoint", "").toString(); auto indexes = selModel->selectedRows(); auto& sale = marketplace_->getSales().at(indexes[0].row()); auto printerDevice = convertToPosPrinterDevice(posPrinterDevice.toStdString(), posPrinterEndpoint.toStdString()); std::unique_ptr printer; if (printerDevice) { printer = std::make_unique(*printerDevice); } else { printer = std::make_unique(); } if (printer->isValid()) printer->printSaleReceipt(sale.get()); } void MainWindow::onCancelAllArticlesButtonClicked([[maybe_unused]] bool checked) { if (ui_.basketView->model()->rowCount() == 0) return; auto dlgResult = QMessageBox(QMessageBox::Icon::Warning, "Sind Sie sicher?", "Möchten Sie wirklich *alle* Artikel des aktuellen Einkaufs stornieren?", QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, this) .exec(); if (dlgResult == QMessageBox::No) return; dynamic_cast(ui_.basketView->model())->cancelSale(); } void MainWindow::onAboutQt() { QMessageBox::aboutQt(this); } void MainWindow::onAbout() { QMessageBox::about( this, "Über", QString("

KIMA2 - Version ") + PROJECT_VERSION + "

" "

KIMA2 ist ein kleines Kassenprogramm für Kindersachenmärkte.

" "

Copyright © Martin Brodbeck <info@rustysoft.de>

"); } void MainWindow::onImportSellerExcelActionTriggered() { if (!marketplace_->getSales().empty()) { QMessageBox(QMessageBox::Icon::Information, "Import nicht möglich", "Der Import ist nicht möglich, da schon Verkäufe getätigt wurden.", QMessageBox::StandardButton::Ok, this) .exec(); return; } QMessageBox( QMessageBox::Icon::Information, "Bitte beachten", "Achtung: Importieren Sie die Verkäuferdaten nur auf einer (!) " "KIMA2-Installation.
" "Verteilen Sie die Daten auf die anderen Installationen unbedingt über eine JSON-Datei!", QMessageBox::StandardButton::Ok, this) .exec(); auto filename = QFileDialog::getOpenFileName(this, "Verkäufer importieren", QString(), "Excel Dateien (*.xlsx *.xls)"); if (filename.isEmpty()) return; fs::path filePath(filename.toStdWString()); ExcelReader::readSellersFromFile(filePath, marketplace_.get()); } void MainWindow::onImportSellerJsonActionTriggered() { if (!marketplace_->getSales().empty()) { QMessageBox(QMessageBox::Icon::Information, "Import nicht möglich", "Der Import ist nicht möglich, da schon Verkäufe getätigt wurden.", QMessageBox::StandardButton::Ok, this) .exec(); return; } auto filename = QFileDialog::getOpenFileName(this, "Verkäufer importieren", QString(), "JSON Dateien (*.json)"); if (filename.isEmpty()) return; fs::path filePath(filename.toStdWString()); JsonUtil::importSellers(filePath, marketplace_.get()); } void MainWindow::onExportSellerJsonActionTriggered() { auto filename = QFileDialog::getSaveFileName(this, "Verkäufer exportieren", QString(), "JSON Dateien (*.json)"); if (filename.isEmpty()) return; fs::path filePath(filename.toStdWString()); JsonUtil::exportSellers(filePath, marketplace_.get()); } void MainWindow::onExportSalesJsonActionTriggered() { QSettings settings; auto filename = QFileDialog::getSaveFileName(this, "Umsätze/Transaktionen exportieren", QString(), "JSON Dateien (*.json)"); if (filename.isEmpty()) return; fs::path filePath(filename.toStdWString()); JsonUtil::exportSales(filePath, marketplace_.get(), settings.value("global/cashPointNo").toInt()); } void MainWindow::onImportSalesJsonActionTriggered() { QSettings settings; auto filename = QFileDialog::getOpenFileName(this, "Umsätze/Transaktionen importieren", QString(), "JSON Dateien (*.json)"); if (filename.isEmpty()) return; fs::path filePath(filename.toStdWString()); delete ui_.salesView->model(); try { JsonUtil::importSales(filePath, marketplace_.get(), settings.value("global/cashPointNo").toInt()); } catch (std::runtime_error& err) { QMessageBox(QMessageBox::Icon::Warning, "Import nicht möglich", err.what(), QMessageBox::Ok, this) .exec(); } setSaleModel(); } void MainWindow::writeGeometry() { QSettings settings; settings.beginGroup("mainwindow"); settings.setValue("size", size()); settings.setValue("pos", pos()); settings.endGroup(); } void MainWindow::readGeometry() { QSettings settings; settings.beginGroup("mainwindow"); resize(settings.value("size", QSize(800, 600)).toSize()); move(settings.value("pos", QPoint(200, 200)).toPoint()); settings.endGroup(); } void MainWindow::closeEvent(QCloseEvent* event) { writeGeometry(); event->accept(); }