From 99f98b045147ecfa3b163b0c9645ca750267dcc7 Mon Sep 17 00:00:00 2001 From: Zdenek Jonak Date: Wed, 14 Oct 2026 22:38:06 +0200 Subject: [PATCH] add new eet and qt 6.12 --- .gitmodules | 3 + CMakeLists.txt | 27 ++++- CMakePresets.json | 2 +- shop/CMakeLists.txt | 11 +- shop/data/voucher.cpp | 11 ++ shop/data/voucher.h | 5 + shop/eet/eetv4adapter.cpp | 152 ++++++++++++++++++++++++++ shop/eet/eetv4adapter.h | 34 ++++++ shop/paydvouchersdialog.cpp | 2 +- shop/receiptgenerator.cpp | 12 ++- shop/settings/shopsettings.cpp | 33 ++++++ shop/settings/shopsettings.h | 15 +++ shop/settings/shopsettingsform.cpp | 5 +- shop/settings/shopsettingsform.ui | 72 +++++++++++-- shop/shopoverview.cpp | 4 +- shop/shopservice.cpp | 168 +++++++++++++++-------------- tests/shop_tests.cpp | 49 +++++++++ 17 files changed, 505 insertions(+), 100 deletions(-) create mode 100644 shop/eet/eetv4adapter.cpp create mode 100644 shop/eet/eetv4adapter.h diff --git a/.gitmodules b/.gitmodules index 4cd0872..3642926 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "3rdparty/LimeReport"] path = 3rdparty/LimeReport url = https://github.com/fralx/LimeReport.git +[submodule "3rdparty/EetCpp"] + path = 3rdparty/EetCpp + url = https://gitea.itsolved.cz/ITsolved/EetCpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index cf77040..6ad9d56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,11 +3,35 @@ project(prodejna-root) include(CTest) option(ENABLE_ZINT "Enable LimeReport barcode support" ON) +option(ENABLE_EET_V4 "Enable EET v4 integration" OFF) -find_package(Qt6 6.11.2 REQUIRED COMPONENTS Core Core5Compat Test Gui Widgets PrintSupport Sql Qml Network Xml Svg) +set(EETCPP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/EetCpp" CACHE PATH + "Path to the EetCpp source tree when EET v4 integration is enabled") + +find_package(Qt6 6.12 REQUIRED COMPONENTS Core Core5Compat Test Gui Widgets PrintSupport Sql Qml Network Xml Svg) set(CMAKE_POLICY_VERSION_MINIMUM 3.10) +if(ENABLE_EET_V4) + if(NOT EETCPP_SOURCE_DIR) + message(FATAL_ERROR + "ENABLE_EET_V4 requires EETCPP_SOURCE_DIR pointing to the EetCpp source tree") + endif() + if(NOT EXISTS "${EETCPP_SOURCE_DIR}/CMakeLists.txt") + message(FATAL_ERROR + "EETCPP_SOURCE_DIR does not contain a CMakeLists.txt: ${EETCPP_SOURCE_DIR}") + endif() + + set(EETCPP_BUILD_PLAYGROUND OFF CACHE BOOL + "Build the EetCpp Playground CLI" FORCE) + set(_prodejna_build_testing "${BUILD_TESTING}") + set(BUILD_TESTING OFF) + add_subdirectory("${EETCPP_SOURCE_DIR}" + "${CMAKE_BINARY_DIR}/_deps/eetcpp-build" + EXCLUDE_FROM_ALL) + set(BUILD_TESTING "${_prodejna_build_testing}") +endif() + add_subdirectory(3rdparty/qdecimal) add_subdirectory(3rdparty/QxOrm) set(USE_QT6 ON CACHE BOOL "Build LimeReport with Qt6" FORCE) @@ -26,4 +50,3 @@ add_subdirectory(camp) if(BUILD_TESTING) add_subdirectory(tests) endif() - diff --git a/CMakePresets.json b/CMakePresets.json index bac2065..c1c57db 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,7 +8,7 @@ "configurePresets": [ { "name": "qt6-debug", - "displayName": "Qt 6.11.2+ Debug", + "displayName": "Qt 6.12+ Debug", "generator": "Ninja", "binaryDir": "${sourceDir}/build/presets/${presetName}", "cacheVariables": { diff --git a/shop/CMakeLists.txt b/shop/CMakeLists.txt index 396722a..eac1738 100644 --- a/shop/CMakeLists.txt +++ b/shop/CMakeLists.txt @@ -92,4 +92,13 @@ target_link_libraries(shop QxOrm core addressbook - ) \ No newline at end of file + ) + +if(ENABLE_EET_V4) + target_sources(shop PRIVATE + eet/eetv4adapter.cpp + eet/eetv4adapter.h + ) + target_link_libraries(shop EetCpp::EetCpp) + target_compile_definitions(shop PRIVATE PRODEJNA_EET_V4) +endif() \ No newline at end of file diff --git a/shop/data/voucher.cpp b/shop/data/voucher.cpp index 7de2e73..1b20909 100644 --- a/shop/data/voucher.cpp +++ b/shop/data/voucher.cpp @@ -30,6 +30,7 @@ namespace qx { t.data(&Voucher::m_eetPkp, "eetPkp"); t.data(&Voucher::m_eetBkp, "eetBkp"); t.data(&Voucher::m_eetFik, "eetFik"); + t.data(&Voucher::m_eetPok, "eetPok"); t.data(&Voucher::m_saveDateTime, "saveDateTime"); t.data(&Voucher::m_createdBy, "createdBy"); t.data(&Voucher::m_created, "created"); @@ -329,6 +330,16 @@ void Voucher::setEetFik(const QString &eetFik) m_eetFik = eetFik; } +QString Voucher::eetPok() const +{ + return m_eetPok; +} + +void Voucher::setEetPok(const QString &eetPok) +{ + m_eetPok = eetPok; +} + QDateTime Voucher::saveDateTime() const { return m_saveDateTime; diff --git a/shop/data/voucher.h b/shop/data/voucher.h index 240d34c..8ce66d0 100644 --- a/shop/data/voucher.h +++ b/shop/data/voucher.h @@ -36,6 +36,7 @@ class SHOPSHARED_EXPORT Voucher : public QObject Q_PROPERTY(QString eetBkp READ eetBkp WRITE setEetBkp) Q_PROPERTY(QString eetPkp READ eetPkp WRITE setEetPkp) Q_PROPERTY(QString eetFik READ eetFik WRITE setEetFik) + Q_PROPERTY(QString eetPok READ eetPok WRITE setEetPok) Q_PROPERTY(VoucherStatus status READ status WRITE setStatus) Q_PROPERTY(QString createdBy READ createdBy WRITE setCreatedBy) Q_PROPERTY(QString updatedBy READ updatedBy WRITE setUpdatedBy) @@ -147,6 +148,9 @@ public: QString eetFik() const; void setEetFik(const QString &eetFik); + QString eetPok() const; + void setEetPok(const QString &eetPok); + QDateTime saveDateTime() const; void setSaveDateTime(const QDateTime &saveDateTime); @@ -192,6 +196,7 @@ private: QString m_eetPkp; QString m_eetBkp; QString m_eetFik; + QString m_eetPok; QList> m_items; VoucherStatus m_status; SeasonPtr m_season; diff --git a/shop/eet/eetv4adapter.cpp b/shop/eet/eetv4adapter.cpp new file mode 100644 index 0000000..59c6721 --- /dev/null +++ b/shop/eet/eetv4adapter.cpp @@ -0,0 +1,152 @@ +#include "eetv4adapter.h" + +#include + +namespace +{ +void setError(QString *error, const QString &message) +{ + if (error != nullptr) + { + *error = message; + } +} + +QDateTime withExplicitOffset(const QDateTime &dateTime) +{ + if (!dateTime.isValid()) + { + return {}; + } + + return dateTime.timeSpec() == Qt::LocalTime + ? dateTime.toOffsetFromUtc(dateTime.offsetFromUtc()) + : dateTime; +} +} + +bool EetV4ShopConfig::validate(QString *error) const +{ + if (!endpoint.isValid() || endpoint.scheme().compare(QStringLiteral("https"), + Qt::CaseInsensitive) != 0) + { + setError(error, QStringLiteral("EET v4 endpoint must be a valid HTTPS URL.")); + return false; + } + if (taxpayerEic.isEmpty()) + { + setError(error, QStringLiteral("EET v4 taxpayer EIC must not be empty.")); + return false; + } + if (unitId < 1) + { + setError(error, QStringLiteral("EET v4 unit ID must be positive.")); + return false; + } + if (cashRegisterId.isEmpty()) + { + setError(error, QStringLiteral("EET v4 cash-register ID must not be empty.")); + return false; + } + if (timeoutMilliseconds < 1) + { + setError(error, QStringLiteral("EET v4 timeout must be positive.")); + return false; + } + return true; +} + +bool EetV4ShopAdapter::amountText(const QDecDouble &amount, + QString *text, + QString *error) +{ + QString value = QString::fromLatin1(amount.toString()); + if (value.contains(QRegularExpression(QStringLiteral("[eE]")))) + { + setError(error, QStringLiteral("Voucher amount must not use exponential notation.")); + return false; + } + + if (!value.contains(QLatin1Char('.'))) + { + value += QStringLiteral(".00"); + } + else + { + const int decimals = value.size() - value.indexOf(QLatin1Char('.')) - 1; + if (decimals > 2) + { + setError(error, QStringLiteral("Voucher amount has more than two decimal places.")); + return false; + } + value = value.leftJustified(value.size() + (2 - decimals), QLatin1Char('0')); + } + + EetV4Amount parsed; + if (!EetV4Amount::fromString(value, &parsed, error)) + { + return false; + } + + if (text != nullptr) + { + *text = parsed.toString(); + } + return true; +} + +bool EetV4ShopAdapter::createRequest(const Voucher &voucher, + const EetV4ShopConfig &config, + EetV4SaleRequest *request, + QString *error) +{ + if (request == nullptr) + { + setError(error, QStringLiteral("EET v4 request output must not be null.")); + return false; + } + if (!config.validate(error)) + { + return false; + } + + const QDateTime saleAt = withExplicitOffset(voucher.payDateTime()); + if (!saleAt.isValid()) + { + setError(error, QStringLiteral("Voucher payment date must be valid.")); + return false; + } + + QString totalText; + if (!amountText(voucher.totalPrice(), &totalText, error)) + { + return false; + } + + EetV4Amount total; + if (!EetV4Amount::fromString(totalText, &total, error)) + { + return false; + } + + EetV4SaleRequest result; + result.sentAt = withExplicitOffset(QDateTime::currentDateTime()); + result.saleAt = saleAt; + result.firstSubmission = voucher.eetStatus() == Voucher::EET_FOR_SEND; + result.verificationMode = config.verificationMode; + result.taxpayerEic = config.taxpayerEic; + result.unitId = config.unitId; + result.cashRegisterId = config.cashRegisterId; + result.receiptNumber = voucher.numSer(); + result.totalSale = total; + + QStringList validationErrors; + if (!result.validate(&validationErrors)) + { + setError(error, validationErrors.join(QStringLiteral("; "))); + return false; + } + + *request = result; + return true; +} diff --git a/shop/eet/eetv4adapter.h b/shop/eet/eetv4adapter.h new file mode 100644 index 0000000..112c642 --- /dev/null +++ b/shop/eet/eetv4adapter.h @@ -0,0 +1,34 @@ +#ifndef SHOP_EETV4ADAPTER_H +#define SHOP_EETV4ADAPTER_H + +#include + +#include + +#include "../data/voucher.h" + +struct EetV4ShopConfig +{ + QUrl endpoint; + QString taxpayerEic; + qint32 unitId{0}; + QString cashRegisterId; + int timeoutMilliseconds{30000}; + bool verificationMode{true}; + + bool validate(QString *error = nullptr) const; +}; + +class EetV4ShopAdapter +{ +public: + static bool createRequest(const Voucher &voucher, + const EetV4ShopConfig &config, + EetV4SaleRequest *request, + QString *error = nullptr); + +private: + static bool amountText(const QDecDouble &amount, QString *text, QString *error); +}; + +#endif // SHOP_EETV4ADAPTER_H diff --git a/shop/paydvouchersdialog.cpp b/shop/paydvouchersdialog.cpp index 862b5df..55f0905 100644 --- a/shop/paydvouchersdialog.cpp +++ b/shop/paydvouchersdialog.cpp @@ -47,7 +47,7 @@ PaydVouchersDialog::PaydVouchersDialog(QWidget *parent) : ui->tableItems->setColumnHidden(0, true); ui->tableItems->verticalScrollBar()->setStyleSheet("QScrollBar:vertical { width: 30px; }"); -#ifndef EET +#ifndef PRODEJNA_EET_V4 ui->btnEetNotSen->setVisible(false); ui->btnSendEet->setVisible(false); ui->label_2->setVisible(false); diff --git a/shop/receiptgenerator.cpp b/shop/receiptgenerator.cpp index 5652361..6904b0e 100644 --- a/shop/receiptgenerator.cpp +++ b/shop/receiptgenerator.cpp @@ -305,7 +305,17 @@ QByteArray ReceiptGenerator::generate() out.append("Datum:"); out.append(prepareString(m_voucher->payDateTime().toString())); out.append("\x0a"); - if (!m_voucher->eetBkp().isEmpty()) + if (!m_voucher->eetPok().isEmpty()) + { + out.append("Rezim EET: v4"); + out.append("\x0a"); + out.append("POK:"); + out.append("\x0a"); + out.append(prepareString(m_voucher->eetPok())); + out.append("\x0a"); + out.append("\x0a"); + } + else if (!m_voucher->eetBkp().isEmpty()) { out.append("Rezim EET: bezny"); out.append("\x0a"); diff --git a/shop/settings/shopsettings.cpp b/shop/settings/shopsettings.cpp index 71fd40f..53d5f60 100644 --- a/shop/settings/shopsettings.cpp +++ b/shop/settings/shopsettings.cpp @@ -6,6 +6,9 @@ ShopSettings::ShopSettings(QObject *parent) : QObject(parent) m_lettersPerLine = 48; m_eetActive = false; + m_eetKeyPassword = QStringLiteral("EET_PKCS12_PASSWORD"); + m_eetUnitId = 1; + m_eetTimeout = 30000; m_eetMode = 1; m_eetTest = 0; m_eetPlayground = 0; @@ -94,6 +97,36 @@ void ShopSettings::setEetRegisterId(const QString &eetRegisterId) m_eetRegisterId = eetRegisterId; } +QString ShopSettings::eetEndpoint() const +{ + return m_eetEndpoint; +} + +void ShopSettings::setEetEndpoint(const QString &eetEndpoint) +{ + m_eetEndpoint = eetEndpoint; +} + +int ShopSettings::eetUnitId() const +{ + return m_eetUnitId; +} + +void ShopSettings::setEetUnitId(int eetUnitId) +{ + m_eetUnitId = eetUnitId; +} + +int ShopSettings::eetTimeout() const +{ + return m_eetTimeout; +} + +void ShopSettings::setEetTimeout(int eetTimeout) +{ + m_eetTimeout = eetTimeout; +} + int ShopSettings::eetMode() const { return m_eetMode; diff --git a/shop/settings/shopsettings.h b/shop/settings/shopsettings.h index 42537bf..11a03ec 100644 --- a/shop/settings/shopsettings.h +++ b/shop/settings/shopsettings.h @@ -13,6 +13,9 @@ class ShopSettings : public QObject Q_PROPERTY(bool eetActive READ eetActive WRITE setEetActive) Q_PROPERTY(QString eetShopId READ eetShopId WRITE setEetShopId) Q_PROPERTY(QString eetRegisterId READ eetRegisterId WRITE setEetRegisterId) + Q_PROPERTY(QString eetEndpoint READ eetEndpoint WRITE setEetEndpoint) + Q_PROPERTY(int eetUnitId READ eetUnitId WRITE setEetUnitId) + Q_PROPERTY(int eetTimeout READ eetTimeout WRITE setEetTimeout) Q_PROPERTY(int eetMode READ eetMode WRITE setEetMode) Q_PROPERTY(QString eetCertificate READ eetCertificate WRITE setEetCertificate) Q_PROPERTY(QString eetKeyPassword READ eetKeyPassword WRITE setEetKeyPassword) @@ -65,6 +68,15 @@ public: QString eetRegisterId() const; void setEetRegisterId(const QString &eetRegisterId); + QString eetEndpoint() const; + void setEetEndpoint(const QString &eetEndpoint); + + int eetUnitId() const; + void setEetUnitId(int eetUnitId); + + int eetTimeout() const; + void setEetTimeout(int eetTimeout); + int eetMode() const; void setEetMode(int eetMode); @@ -119,6 +131,9 @@ private: bool m_eetActive; QString m_eetShopId; QString m_eetRegisterId; + QString m_eetEndpoint; + int m_eetUnitId; + int m_eetTimeout; int m_eetMode; QString m_eetCertificate; QString m_eetKeyPassword; diff --git a/shop/settings/shopsettingsform.cpp b/shop/settings/shopsettingsform.cpp index cf9bc79..933fe60 100644 --- a/shop/settings/shopsettingsform.cpp +++ b/shop/settings/shopsettingsform.cpp @@ -16,10 +16,13 @@ ShopSettingsForm::ShopSettingsForm(QWidget *parent) : registerBinding(ui->lettersPerLine); registerBinding(ui->byMessage); -#ifdef EET +#ifdef PRODEJNA_EET_V4 registerBinding(ui->eetActive); registerBinding(ui->eetShopId); registerBinding(ui->eetRegisterId); + registerBinding(ui->eetEndpoint); + registerBinding(ui->eetUnitId); + registerBinding(ui->eetTimeout); QList listModes; listModes << ComboData(0, tr("Simplifyed")) << ComboData(1, tr("Standard")); registerBinding(ui->eetMode, listModes); diff --git a/shop/settings/shopsettingsform.ui b/shop/settings/shopsettingsform.ui index b7da5d4..a459825 100644 --- a/shop/settings/shopsettingsform.ui +++ b/shop/settings/shopsettingsform.ui @@ -187,7 +187,7 @@ - Shop ID + Taxpayer EIC @@ -204,24 +204,54 @@ + + + + EET endpoint + + + + + + + + + Unit ID + + + + + + + 1 + + + 999999999 + + + 1 + + + + EET mode - + - + Certificate file - + @@ -249,28 +279,48 @@ - + - Private key password + Certificate password environment variable - + - - QLineEdit::Password + + EET_PKCS12_PASSWORD - + + + + Request timeout (ms) + + + + + + + 1000 + + + 600000 + + + 30000 + + + + Test mode - + Communication with playground diff --git a/shop/shopoverview.cpp b/shop/shopoverview.cpp index 0342f6e..a473f44 100644 --- a/shop/shopoverview.cpp +++ b/shop/shopoverview.cpp @@ -8,7 +8,7 @@ ShopOverview::ShopOverview(QWidget *parent) : { ui->setupUi(this); -#ifndef EET +#ifndef PRODEJNA_EET_V4 ui->labelUnsendEET->setVisible(false); ui->label_6->setVisible(false); #endif @@ -32,7 +32,7 @@ void ShopOverview::refresh() ui->labelSales->setText(QString::number(today.totalPrice().toDouble())); ui->labelNumSales->setText(QString::number(today.count())); -#ifdef EET +#ifdef PRODEJNA_EET_V4 VoucherSum unsend = srv.unsendEET(); ui->labelUnsendEET->setText(QString::number(unsend.count())); #endif diff --git a/shop/shopservice.cpp b/shop/shopservice.cpp index ce77cb1..92ca910 100644 --- a/shop/shopservice.cpp +++ b/shop/shopservice.cpp @@ -4,10 +4,11 @@ #include "settings/shopsettings.h" -#ifdef EET -#include +#ifdef PRODEJNA_EET_V4 +#include "eet/eetv4adapter.h" +#include #endif -#include +#include #include #include @@ -15,6 +16,15 @@ inline double round(double value) { return value < 0 ? -std::floor(0.5 - value) : std::floor(0.5 + value); } #endif +namespace +{ +bool &eetOnlineState() +{ + static bool online = true; + return online; +} +} + VoucherPtr ShopService::createVoucher() { QSharedPointer voucher(new Voucher); @@ -189,96 +199,95 @@ void ShopService::updateRelatedItem(VoucherItem* item, int countAdded) bool ShopService::processEet(VoucherPtr voucher, QString &message) { -#ifdef EET +#ifdef PRODEJNA_EET_V4 if (voucher->eetStatus() == Voucher::EET_NOT_ENTERING) { return true; } + if (!eetOnlineState()) + { + message = QStringLiteral("EET is offline; the voucher remains queued for sending."); + return false; + } SettingsService srvSettings("SHOP"); ShopSettingsPtr settings = srvSettings.loadSettings(); - loadSettings(); - EetRequest request; - request.setCelkTrzba(voucher->totalPrice().toDouble()); - request.setDatTrzby(voucher->payDateTime()); - request.setIdPokl(settings->eetRegisterId()); - request.setIdProvoz(settings->eetShopId()); - request.setPrvniZaslani(voucher->eetStatus() == Voucher::EET_FOR_SEND); - request.setDicPopl(m_gs->dic()); - request.setPoradCis(voucher->numSer()); - request.setDatOdesl(QDateTime::currentDateTime()); - request.setRezim((EetRequest::EetRezim)settings->eetMode()); + EetV4ShopConfig adapterConfig; + adapterConfig.endpoint = QUrl(settings->eetEndpoint()); + adapterConfig.taxpayerEic = settings->eetShopId(); + adapterConfig.unitId = settings->eetUnitId(); + adapterConfig.cashRegisterId = settings->eetRegisterId(); + adapterConfig.timeoutMilliseconds = settings->eetTimeout(); + adapterConfig.verificationMode = settings->eetTest(); - if (m_gs->vatPayer()) + EetV4SaleRequest request; + QString adapterError; + if (!EetV4ShopAdapter::createRequest(*voucher, adapterConfig, &request, &adapterError)) { - request.setDan1(voucher->vatAmountHigh().toDouble()); - request.setDan2(voucher->vatAmountFirstLower().toDouble()); - request.setDan3(voucher->VatAmountSecondLower().toDouble()); - - request.setZaklDan1(voucher->priceVatHigh().toDouble()); - request.setZaklDan2(voucher->priceVatFirstLower().toDouble()); - request.setZaklDan3(voucher->priceVatSecondLower().toDouble()); - request.setZaklNepodlDph(voucher->priceNoVat().toDouble()); + message = adapterError; + voucher->setEetStatus(Voucher::EET_ERROR); + update(voucher); + return false; } - EetSender *sender = new EetSender(this); - sender->setupSigner(settings->eetCertificate(), settings->eetKeyPassword()); - sender->setPlayground(settings->eetPlayground()); - - QEventLoop loop; - bool replyFinished = false; - - connect(sender, &EetSender::sendFinished, [this, voucher, sender, &loop, &replyFinished](){ - Transaction tx; - - voucher->setEetBkp(sender->resut()->bkp()); - voucher->setEetPkp(sender->resut()->pkp()); - voucher->setEetFik(sender->resut()->fik()); - - if (sender->resut()->status() == EetResult::RESPONSE_OK) - { - voucher->setEetSendDateTime(QDateTime::currentDateTime()); - voucher->setEetStatus(Voucher::EET_SENT); - } - else - { - voucher->setEetStatus(Voucher::EET_ERROR); - } - - this->update(voucher); - tx.commit(); - sender->deleteLater(); - - loop.quit(); - replyFinished = true; - }); - - sender->sendRequest(&request); - - if (!replyFinished) + const QString passwordVariable = settings->eetKeyPassword().trimmed(); + const QByteArray password = QProcessEnvironment::systemEnvironment() + .value(passwordVariable) + .toUtf8(); + if (passwordVariable.isEmpty() || password.isEmpty()) { - loop.exec(); + message = QStringLiteral("EET certificate password environment variable is missing."); + voucher->setEetStatus(Voucher::EET_ERROR); + update(voucher); + return false; } - auto addMessage = [&message](EetMessage *msg, const QString &label){ - if (message.isEmpty()) + EetV4ClientConfig clientConfig; + clientConfig.endpoint = adapterConfig.endpoint; + clientConfig.timeoutMilliseconds = adapterConfig.timeoutMilliseconds; + EetV4Client client(clientConfig); + EetV4Response response; + QString clientError; + if (!client.sendSale(request, + settings->eetCertificate(), + password, + &response, + &clientError)) + { + message = clientError; + voucher->setEetStatus(Voucher::EET_ERROR); + update(voucher); + return false; + } + + if (!response.isSuccess() || response.pok.isEmpty()) + { + message = response.message.isEmpty() + ? QStringLiteral("EET returned an unsuccessful response.") + : response.message; + voucher->setEetStatus(Voucher::EET_ERROR); + update(voucher); + return false; + } + + voucher->setEetPok(response.pok); + voucher->setEetSendDateTime(QDateTime::currentDateTime()); + voucher->setEetStatus(Voucher::EET_SENT); + update(voucher); + if (!response.warningCodes.isEmpty()) + { + message = QStringLiteral("EET warning codes: "); + for (int i = 0; i < response.warningCodes.size(); ++i) { - message = label + "\n"; + if (i > 0) + { + message += QStringLiteral(", "); + } + message += QString::number(response.warningCodes.at(i)); } - - message += QString::number(msg->code()) + ": " + msg->message(); - }; - - foreach (EetMessage *msg, sender->resut()->errors()) { - addMessage(msg, "Errors:"); } - - foreach (EetMessage *msg, sender->resut()->warnings()) { - addMessage(msg, "Warnings:"); - } - - return voucher->eetStatus() == Voucher::EET_SENT; + return true; #else return true; #endif @@ -286,15 +295,15 @@ bool ShopService::processEet(VoucherPtr voucher, QString &message) void ShopService::setEetOnline(bool online) { -#ifdef EET - EetSender::m_online = online; +#ifdef PRODEJNA_EET_V4 + eetOnlineState() = online; #endif } bool ShopService::isEetOnline() { -#ifdef EET - return EetSender::m_online; +#ifdef PRODEJNA_EET_V4 + return eetOnlineState(); #else return false; #endif @@ -562,4 +571,3 @@ VoucherSum ShopService::summary(const QString& where) { return sum; } - diff --git a/tests/shop_tests.cpp b/tests/shop_tests.cpp index b5ee344..85ecc93 100644 --- a/tests/shop_tests.cpp +++ b/tests/shop_tests.cpp @@ -2,6 +2,9 @@ #include #include "data/voucher.h" +#ifdef PRODEJNA_EET_V4 +#include "eet/eetv4adapter.h" +#endif class ShopTests : public QObject { @@ -45,6 +48,52 @@ private slots: QVERIFY(qAbs(sum.totalPrice().toDouble() - 20.0) < 0.0001); } + +#ifdef PRODEJNA_EET_V4 + void eetV4AdapterBuildsRequestFromVoucher() + { + Voucher voucher; + voucher.setNumSer(QStringLiteral("SHOP-42")); + voucher.setPayDateTime(QDateTime(QDate(2026, 10, 13), + QTime(20, 30), + Qt::OffsetFromUTC, + 7200)); + voucher.setTotalPrice(QDecDouble(QStringLiteral("19.90").toLatin1().constData())); + + EetV4ShopConfig config; + config.endpoint = QUrl(QStringLiteral("https://eet.example.test/eet")); + config.taxpayerEic = QStringLiteral("CZ123456789"); + config.unitId = 3; + config.cashRegisterId = QStringLiteral("KASA1"); + + EetV4SaleRequest request; + QString error; + QVERIFY2(EetV4ShopAdapter::createRequest(voucher, config, &request, &error), + qPrintable(error)); + QCOMPARE(request.receiptNumber, QStringLiteral("SHOP-42")); + QCOMPARE(request.totalSale.toString(), QStringLiteral("19.90")); + QCOMPARE(request.unitId, 3); + QCOMPARE(request.cashRegisterId, QStringLiteral("KASA1")); + QVERIFY(request.verificationMode); + QVERIFY(request.firstSubmission); + } + + void eetV4AdapterRejectsInvalidConfiguration() + { + Voucher voucher; + voucher.setNumSer(QStringLiteral("SHOP-42")); + voucher.setPayDateTime(QDateTime::currentDateTime()); + voucher.setTotalPrice(QDecDouble(QStringLiteral("1.00").toLatin1().constData())); + + EetV4ShopConfig config; + config.endpoint = QUrl(QStringLiteral("http://not-tls.example.test/eet")); + EetV4SaleRequest request; + QString error; + + QVERIFY(!EetV4ShopAdapter::createRequest(voucher, config, &request, &error)); + QVERIFY(error.contains(QStringLiteral("HTTPS"))); + } +#endif }; int main(int argc, char *argv[])