Compare commits

..

No commits in common. "master" and "v1.6.0" have entirely different histories.

29 changed files with 293 additions and 184 deletions

5
.gitmodules vendored
View file

@ -3,4 +3,7 @@
url = https://github.com/nlohmann/json.git url = https://github.com/nlohmann/json.git
[submodule "subprojects/singleapplication"] [submodule "subprojects/singleapplication"]
path = subprojects/singleapplication/singleapplication.git path = subprojects/singleapplication/singleapplication.git
url = https://github.com/itay-grudev/SingleApplication.git url = https://github.com/itay-grudev/SingleApplication.git
[submodule "subprojects/csv-parser"]
path = subprojects/csv-parser
url = https://github.com/vincentlaucsb/csv-parser.git

32
.vscode/settings.json vendored
View file

@ -65,37 +65,7 @@
"condition_variable": "cpp", "condition_variable": "cpp",
"mutex": "cpp", "mutex": "cpp",
"hash_map": "cpp", "hash_map": "cpp",
"future": "cpp", "future": "cpp"
"bit": "cpp",
"compare": "cpp",
"concepts": "cpp",
"forward_list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_set": "cpp",
"iterator": "cpp",
"memory_resource": "cpp",
"random": "cpp",
"semaphore": "cpp",
"stop_token": "cpp",
"__bit_reference": "cpp",
"__bits": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__hash_table": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__split_buffer": "cpp",
"__threading_support": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"__verbose_abort": "cpp",
"format": "cpp",
"ios": "cpp",
"locale": "cpp"
}, },
"C_Cpp.clang_format_path": "/usr/bin/clang-format", "C_Cpp.clang_format_path": "/usr/bin/clang-format",
"cmake.configureOnOpen": true, "cmake.configureOnOpen": true,

View file

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.8)
project(kima2 VERSION 1.8.1) project(kima2 VERSION 1.6.0)
set(CMAKE_MODULE_PATH "${CMAKE_HOME_DIRECTORY}/cmake") set(CMAKE_MODULE_PATH "${CMAKE_HOME_DIRECTORY}/cmake")
@ -17,7 +17,6 @@ else()
endif() endif()
configure_file(config.h.in ${PROJECT_BINARY_DIR}/config.h) configure_file(config.h.in ${PROJECT_BINARY_DIR}/config.h)
configure_file(de.rustysoft.kima2.metainfo.xml.in ${PROJECT_BINARY_DIR}/de.rustysoft.kima2.metainfo.xml)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
@ -80,8 +79,6 @@ else(WIN32 AND NOT UNIX)
DESTINATION share/${PROJECT_NAME}) DESTINATION share/${PROJECT_NAME})
install(FILES "${CMAKE_SOURCE_DIR}/misc/kima2.svg" install(FILES "${CMAKE_SOURCE_DIR}/misc/kima2.svg"
DESTINATION share/icons/hicolor/scalable/apps) DESTINATION share/icons/hicolor/scalable/apps)
install(FILES de.rustysoft.kima2.metainfo.xml
DESTINATION share/metainfo)
endif (WIN32 AND NOT UNIX) endif (WIN32 AND NOT UNIX)
if( MINGW ) if( MINGW )
@ -98,9 +95,10 @@ if( MINGW )
${MINGW_PATH}/libwinpthread-1.dll ${MINGW_PATH}/libwinpthread-1.dll
${MINGW_PATH}/libsqlite3-0.dll ${MINGW_PATH}/libsqlite3-0.dll
${MINGW_PATH}/libusb-1.0.dll ${MINGW_PATH}/libusb-1.0.dll
${MINGW_PATH}/libicuuc73.dll ${MINGW_PATH}/libxlnt.dll
${MINGW_PATH}/libicuin73.dll ${MINGW_PATH}/libicuuc71.dll
${MINGW_PATH}/libicudt73.dll ${MINGW_PATH}/libicuin71.dll
${MINGW_PATH}/libicudt71.dll
${MINGW_PATH}/libpcre2-16-0.dll ${MINGW_PATH}/libpcre2-16-0.dll
${MINGW_PATH}/libpcre2-8-0.dll ${MINGW_PATH}/libpcre2-8-0.dll
${MINGW_PATH}/zlib1.dll ${MINGW_PATH}/zlib1.dll
@ -117,7 +115,7 @@ if( MINGW )
${MINGW_PATH}/libmd4c.dll ${MINGW_PATH}/libmd4c.dll
${MINGW_PATH}/libbrotlicommon.dll ${MINGW_PATH}/libbrotlicommon.dll
${MINGW_PATH}/libbrotlidec.dll ${MINGW_PATH}/libbrotlidec.dll
#${MINGW_PATH}/libfmt.dll ${MINGW_PATH}/libfmt.dll
${MINGW_PATH}/libb2-1.dll ${MINGW_PATH}/libb2-1.dll
${MINGW_PATH}/libiconv-2.dll) ${MINGW_PATH}/libiconv-2.dll)
install(FILES ${MINGW_PATH}/../share/qt6/plugins/platforms/qwindows.dll install(FILES ${MINGW_PATH}/../share/qt6/plugins/platforms/qwindows.dll
@ -125,7 +123,7 @@ if( MINGW )
DESTINATION bin/platforms) DESTINATION bin/platforms)
#install(FILES ${MINGW_PATH}/../share/qt6/plugins/printsupport/windowsprintersupport.dll #install(FILES ${MINGW_PATH}/../share/qt6/plugins/printsupport/windowsprintersupport.dll
# DESTINATION bin/printsupport) # DESTINATION bin/printsupport)
install(FILES ${MINGW_PATH}/../share/qt6/translations/qtbase_de.qm install(FILES ${MINGW_PATH}/../share/qt5/translations/qtbase_de.qm
${MINGW_PATH}/../share/qt6/translations/qt_de.qm ${MINGW_PATH}/../share/qt6/translations/qt_de.qm
${MINGW_PATH}/../share/qt6/translations/qt_help_de.qm ${MINGW_PATH}/../share/qt6/translations/qt_help_de.qm
${MINGW_PATH}/../share/qt6/translations/qtmultimedia_de.qm ${MINGW_PATH}/../share/qt6/translations/qtmultimedia_de.qm

View file

@ -1,4 +1,4 @@
Copyright © 2018-2024 Martin Brodbeck Copyright © 2018-2022 Martin Brodbeck
Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der
zugehörigen Dokumentationen (die "Software") erhält, die Erlaubnis erteilt, zugehörigen Dokumentationen (die "Software") erhält, die Erlaubnis erteilt,

View file

@ -18,12 +18,14 @@ Ubuntu, Windows) angeboten. Bitte die Hinweise dort beachten.
### Selbst compilieren ### Selbst compilieren
KIMA2 benötigt folgende Libraries: KIMA2 benötigt folgende Libraries:
* Qt 6 * Qt5
* boost >= 1.80 * boost >= 1.62
* libusb-1.0 * libusb-1.0
* xlnt >= 1.3.0
* nlohmann-json (als 3rdparty submodule vorhanden) * nlohmann-json (als 3rdparty submodule vorhanden)
Da Features aus C++20 verwendet werden, sollte als Compiler mindestens GCC 12 verwendet werden. Da Features aus C++17 verwendet werden sowie std::filesystem, sollte als Compiler mindestens
GCC 8 verwendet werden.
Die Installationsschritte unter Linux sind wie folgt: Die Installationsschritte unter Linux sind wie folgt:
``` ```
@ -35,4 +37,4 @@ sudo make install
``` ```
Unter Windows muss vorab MinGW eingerichtet werden (z. B. MSYS2). Nach der Compilierung kann mit Unter Windows muss vorab MinGW eingerichtet werden (z. B. MSYS2). Nach der Compilierung kann mit
`cpack -G NSIS` ein Installationspaket erstellt werden. `cpack -G NSIS` ein Installationspaket erstellt werden.

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>de.rustysoft.kima2</id>
<name>KIMA2</name>
<summary>A small cash point program for childrens stuff markets</summary>
<metadata_license>MIT</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<releases>
<release version="@PROJECT_VERSION@" type="stable" date="2024-01-23" />
</releases>
<description>
<p>
A small cash point program for children's stuff markets. German language only.
</p>
</description>
<launchable type="desktop-id">de.rustysoft.kima2.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>https://rustysoft.de/images/software/kima2/screenshot.png</image>
</screenshot>
</screenshots>
</component>

Binary file not shown.

Binary file not shown.

View file

@ -1,4 +1,4 @@
project('kima2', 'cpp', default_options : ['cpp_std=c++20'], version : '1.8.0') project('kima2', 'cpp', default_options : ['cpp_std=c++20'], version : '1.6.0')
conf_data = configuration_data() conf_data = configuration_data()
conf_data.set('PROJECT_VERSION', '"' + meson.project_version() + '"') conf_data.set('PROJECT_VERSION', '"' + meson.project_version() + '"')

View file

@ -1,27 +1,21 @@
# Maintainer: Martin Brodbeck <martin at brodbeck-online dot de> # Maintainer: Martin Brodbeck <martin at brodbeck-online dot de>
pkgname=kima2 pkgname=kima2
pkgver=1.7.1 pkgver=1.5.0
pkgrel=1 pkgrel=1
pkgdesc="A small cash point program for children's things markets (German only)" pkgdesc="A small cash point program for children's things markets (German only)"
arch=('i686' 'x86_64') arch=('i686' 'x86_64')
url="http://www.rustysoft.de/software/kima2" url="http://www.rustysoft.de/?01_kima2"
license=('custom') license=('custom')
depends=('glibc' 'libusb' 'qt6-base' 'sqlite3') depends=('glibc' 'libusb' 'qt5-base' 'sqlite3' 'xlnt')
makedepends=('boost>=1.62') makedepends=('boost>=1.62')
source=(git+https://git.rustysoft.de/martin/kima2) source=($pkgname-$pkgver.tar.gz)
sha256sums=('SKIP') md5sums=('c0e6a64b5037675edce4ba8bc4639bd3')
build() { build() {
cd $pkgname if [ ! -d $pkgname/build ]; then
mkdir $pkgname/build
git checkout v$pkgver
git submodule init
git submodule update
if [ -d build ]; then
rm -rf build
fi fi
mkdir build && cd build cd $pkgname/build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DKIMA2_USE_EXTERNAL_JSON=OFF .. cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DKIMA2_USE_EXTERNAL_JSON=OFF ..
make make

View file

@ -3,8 +3,8 @@ Type=Application
Name=KIMA2 Name=KIMA2
GenericName=Cash Point Program GenericName=Cash Point Program
GenericName[de]=Kassenprogramm GenericName[de]=Kassenprogramm
Comment=A small cash point program for children's stuff markets Comment=A small cash point program
Comment[de]=Ein kleines Kassenprogramm für Kindersachenmärkte Comment[de]=Ein kleines Kassenprogramm
Exec=kima2 Exec=kima2
Icon=kima2 Icon=kima2
Categories=Office; Categories=Office;

View file

@ -13,6 +13,7 @@ BuildRequires: boost-date-time
BuildRequires: sqlite-devel BuildRequires: sqlite-devel
BuildRequires: libusb-devel BuildRequires: libusb-devel
BuildRequires: qt5-qtdeclarative-devel BuildRequires: qt5-qtdeclarative-devel
#BuildRequires: pkgconfig(xlnt)
#BuildRequires: pkgconfig(pthreads) #BuildRequires: pkgconfig(pthreads)
%description %description

View file

@ -1,12 +1,22 @@
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
find_package(Boost 1.78 REQUIRED) find_package(Boost 1.62 REQUIRED)
find_package(SQLite3 REQUIRED) find_package(SQLite3 REQUIRED)
# Because csv-parser needs threads: # Because csv-parser needs threads:
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
find_package(fmt) find_package(fmt)
if (MINGW)
find_package(XLNT REQUIRED STATIC)
else (MINGW)
find_package(PkgConfig REQUIRED)
pkg_check_modules(XLNT REQUIRED xlnt>=1.3)
endif (MINGW)
set(CORE_SOURCES set(CORE_SOURCES
database.cpp database.cpp
entity.cpp entity.cpp
@ -16,6 +26,7 @@ set(CORE_SOURCES
article.cpp article.cpp
sale.cpp sale.cpp
marketplace.cpp marketplace.cpp
excelreader.cpp
csvreader.cpp csvreader.cpp
jsonutil.cpp jsonutil.cpp
utils.cpp utils.cpp
@ -23,12 +34,12 @@ set(CORE_SOURCES
add_library(core STATIC ${CORE_SOURCES}) add_library(core STATIC ${CORE_SOURCES})
#target_include_directories(core PRIVATE ${PROJECT_SOURCE_DIR}/subprojects/csv-parser/single_include) target_include_directories(core PRIVATE ${PROJECT_SOURCE_DIR}/subprojects/csv-parser/single_include)
if (WIN32) if (WIN32)
target_link_libraries(core PRIVATE sqlite3 nlohmann_json::nlohmann_json) target_link_libraries(core PRIVATE sqlite3 nlohmann_json::nlohmann_json ${XLNT_LIBRARY} Threads::Threads fmt::fmt)
target_link_libraries(core PRIVATE bcrypt) target_link_libraries(core PRIVATE bcrypt)
else() else()
target_link_libraries(core PRIVATE sqlite3 nlohmann_json::nlohmann_json) target_link_libraries(core PRIVATE sqlite3 nlohmann_json::nlohmann_json ${XLNT_LIBRARIES} Threads::Threads fmt::fmt)
endif() endif()
target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)

View file

@ -3,8 +3,7 @@
#include <fstream> #include <fstream>
// #include <csv.hpp> #include <csv.hpp>
#include <boost/algorithm/string.hpp>
#ifdef DELETE #ifdef DELETE
#undef DELETE #undef DELETE
@ -14,16 +13,17 @@ namespace fs = std::filesystem;
std::size_t CsvReader::readSellersFromFile(const fs::path &filePath, Marketplace *market) std::size_t CsvReader::readSellersFromFile(const fs::path &filePath, Marketplace *market)
{ {
csv::CSVFormat format;
format.delimiter(';');
#if defined(_WIN64) || defined(_WIN32) #if defined(_WIN64) || defined(_WIN32)
// Windows: Somhow this is necessary in order to open file names with umlauts // Windows: Somhow this is necessary in order to open file names with umlauts
auto wide = filePath.wstring(); auto wide = filePath.wstring();
std::string fileName(wide.begin(), wide.end()); std::string fileName(wide.begin(), wide.end());
std::ifstream infile(fileName); csv::CSVReader csvReader(fileName, format);
#else #else
// csv::CSVReader csvReader(filePath.string(), format); csv::CSVReader csvReader(filePath.string(), format);
std::ifstream infile(filePath.string());
#endif #endif
for (auto &seller : market->getSellers()) { for (auto &seller : market->getSellers()) {
@ -32,35 +32,28 @@ std::size_t CsvReader::readSellersFromFile(const fs::path &filePath, Marketplace
market->storeToDb(true); market->storeToDb(true);
std::string line; for (csv::CSVRow &row : csvReader) {
if (!row[0].is_int()) {
while (getline(infile, line)) {
std::vector<std::string> strs;
boost::split(strs, line, boost::is_any_of(";"));
auto seller = std::make_unique<Seller>();
try {
int sellerNo = std::stoi(strs[0]);
seller->setSellerNo(sellerNo);
} catch (std::invalid_argument const &ex) {
continue; continue;
} }
if (isNumber(strs[1])) auto seller = std::make_unique<Seller>();
seller->setNumArticlesOffered(std::stoi(strs[1])); seller->setSellerNo(row[0].get<int>());
else if (row[1].is_int()) {
seller->setNumArticlesOffered(row[1].get<int>());
} else {
seller->setNumArticlesOffered(0); seller->setNumArticlesOffered(0);
}
// If both, first name and last name, are empty, use N. N. // If both, first name and last name, are empty, use N. N.
// Else, use the real values. // Else, use the real values.
if (strs[2].empty() && strs[2].empty()) { if (row[2].get<std::string>().empty() && row[3].get<std::string>().empty()) {
seller->setFirstName("N."); seller->setFirstName("N.");
seller->setLastName("N."); seller->setLastName("N.");
} else { } else {
std::string firstName = strs[2]; std::string firstName = row[2].get<std::string>();
seller->setFirstName(trim(firstName)); seller->setFirstName(trim(firstName));
std::string lastName = strs[3]; std::string lastName = row[3].get<std::string>();
seller->setLastName(trim(lastName)); seller->setLastName(trim(lastName));
} }

View file

@ -2,7 +2,7 @@
#include <chrono> #include <chrono>
#include <filesystem> #include <filesystem>
#include <format> #include <fmt/chrono.h>
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
@ -47,7 +47,7 @@ void Database::newDb()
fs::path destPath = sourcePath.parent_path() / sourcePath.stem(); fs::path destPath = sourcePath.parent_path() / sourcePath.stem();
auto chronoTime = std::chrono::system_clock::now(); auto chronoTime = std::chrono::system_clock::now();
std::string timeString = std::format("{0:%FT%H-%M-%S}", chronoTime); std::string timeString = fmt::format("{0:%FT%H-%M-%S}", chronoTime);
destPath += std::string("_") += timeString += ".db"; destPath += std::string("_") += timeString += ".db";

72
src/core/excelreader.cpp Normal file
View file

@ -0,0 +1,72 @@
#include "excelreader.h"
#include "utils.h"
#include <fstream>
#include <xlnt/xlnt.hpp>
namespace fs = std::filesystem;
std::size_t ExcelReader::readSellersFromFile(const fs::path &filePath, Marketplace *market)
{
xlnt::workbook wb;
std::ifstream mystream(filePath, std::ios::binary);
if (!mystream.is_open()) {
throw std::runtime_error("Could not open Excel file");
}
wb.load(mystream);
for (auto &seller : market->getSellers()) {
seller->setState(Seller::State::DELETE);
}
market->storeToDb(true);
auto ws = wb.sheet_by_index(0);
for (auto row : ws.rows(true)) {
// Skip the row if the first value is not a number (= seller no)
if (row[0].data_type() != xlnt::cell::type::number) {
continue;
}
auto seller = std::make_unique<Seller>();
seller->setSellerNo(row[0].value<int>());
seller->setNumArticlesOffered(row[1].value<int>());
// If both, first name and last name, are empty, use N. N.
// Else, use the real values.
if (row[2].value<std::string>().empty() && row[3].value<std::string>().empty()) {
seller->setFirstName("N.");
seller->setLastName("N.");
} else {
std::string firstName = row[2].value<std::string>();
seller->setFirstName(trim(firstName));
std::string lastName = row[3].value<std::string>();
seller->setLastName(trim(lastName));
}
market->getSellers().push_back(std::move(seller));
}
// Add one additional seller "RESERVE RESERVE"
auto seller = std::make_unique<Seller>();
seller->setSellerNo(market->getNextSellerNo());
seller->setFirstName("RESERVE");
seller->setLastName("RESERVE");
market->getSellers().push_back(std::move(seller));
// If there was no special seller "Sonderkonto" in import data, then create one
auto specialSeller = market->findSellerWithSellerNo(0);
if (!specialSeller) {
auto seller = std::make_unique<Seller>();
seller->setSellerNo(0);
seller->setLastName("Sonderkonto");
seller->setFirstName("Sonderkonto");
seller->setNumArticlesOffered(0);
market->getSellers().push_back(std::move(seller));
}
market->sortSellers();
market->storeToDb();
return market->getSellers().size() - 1; // minus 1 because we don't count the "special" seller
}

19
src/core/excelreader.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef EXCEL_READER_H
#define EXCEL_READER_H
#include "marketplace.h"
#include "seller.h"
#include <filesystem>
#include <memory>
#include <string>
#include <vector>
class ExcelReader
{
public:
static std::size_t readSellersFromFile(const std::filesystem::path &filePath,
Marketplace *market);
};
#endif

View file

@ -102,10 +102,8 @@ void JsonUtil::importSales(const std::filesystem::path &filePath, Marketplace *m
int source_no = jsonValues["source_no"]; int source_no = jsonValues["source_no"];
if (source_no == cashPointNo) { if (source_no == cashPointNo) {
std::string ret = "Die Kassen-Nr. "; throw std::runtime_error("Die Kassen-Nr. der zu imporierenden Daten wird von dieser Kasse "
ret += std::to_string(source_no); "hier bereits verwendet.");
ret += " der zu imporierenden Daten wird von dieser Kasse hier bereits verwendet.";
throw std::runtime_error(ret);
} }
market->setSalesToDelete(jsonValues["source_no"]); market->setSalesToDelete(jsonValues["source_no"]);

View file

@ -2,13 +2,13 @@
#include <algorithm> #include <algorithm>
#include <clocale> #include <clocale>
#include <format> #include <fmt/format.h>
#include <iomanip> #include <iomanip>
#include <numeric> #include <numeric>
#include <iostream> #include <iostream>
//using namespace fmt; using namespace fmt;
std::string formatCentAsEuroString(const int cent, int width) std::string formatCentAsEuroString(const int cent, int width)
{ {
@ -32,7 +32,7 @@ std::string formatCentAsEuroString(const int cent, int width)
#else #else
std::locale myLocale{"de_DE.utf8"}; std::locale myLocale{"de_DE.utf8"};
#endif #endif
return std::format(myLocale, "{:{}.2Lf} €", cent / 100.0L, width); return fmt::format(myLocale, "{:{}.2Lf} €", cent / 100.0L, width);
} }
std::string &ltrim(std::string &str, const std::string &chars) std::string &ltrim(std::string &str, const std::string &chars)
@ -61,10 +61,3 @@ bool case_insensitive_match(std::string s1, std::string s2)
return true; // The strings are same return true; // The strings are same
return false; // not matched return false; // not matched
} }
bool isNumber(const std::string &str)
{
return !str.empty() && std::find_if(str.begin(), str.end(), [](unsigned char c) {
return !std::isdigit(c);
}) == str.end();
}

View file

@ -10,6 +10,5 @@ std::string &ltrim(std::string &str, const std::string &chars = "\t\n\v\f\r ");
std::string &rtrim(std::string &str, const std::string &chars = "\t\n\v\f\r "); std::string &rtrim(std::string &str, const std::string &chars = "\t\n\v\f\r ");
std::string &trim(std::string &str, const std::string &chars = "\t\n\v\f\r "); std::string &trim(std::string &str, const std::string &chars = "\t\n\v\f\r ");
bool case_insensitive_match(std::string s1, std::string s2); bool case_insensitive_match(std::string s1, std::string s2);
bool isNumber(const std::string &str);
#endif #endif

View file

@ -42,7 +42,7 @@ add_executable(kima2 ${GUI_SOURCES} kima2.rc)
target_include_directories(kima2 PRIVATE ${PROJECT_BINARY_DIR}) target_include_directories(kima2 PRIVATE ${PROJECT_BINARY_DIR})
target_include_directories(kima2 PRIVATE ${PROJECT_SOURCE_DIR}/subprojects/singleapplication/singleapplication.git) target_include_directories(kima2 PRIVATE ${PROJECT_SOURCE_DIR}/subprojects/singleapplication/singleapplication.git)
# target_link_libraries(kima2 core printer Qt5::Widgets Qt5::PrintSupport Qt5::Network stdc++fs) # target_link_libraries(kima2 core printer Qt5::Widgets Qt5::PrintSupport Qt5::Network stdc++fs)
target_link_libraries(kima2 core printer Qt::Core Qt::PrintSupport Qt::Network) target_link_libraries(kima2 core printer Qt::Core Qt::PrintSupport Qt::Network stdc++fs)
if(WIN32) if(WIN32)
set_target_properties(kima2 PROPERTIES LINK_FLAGS "-mwindows") set_target_properties(kima2 PROPERTIES LINK_FLAGS "-mwindows")

View file

@ -21,12 +21,21 @@ int main(int argc, char *argv[])
QCoreApplication::setOrganizationDomain("rustysoft.de"); QCoreApplication::setOrganizationDomain("rustysoft.de");
QCoreApplication::setApplicationName("kima2"); QCoreApplication::setApplicationName("kima2");
QTranslator qtTranslator; QTranslator qTranslator;
QLocale german(QLocale::German);
if (qtTranslator.load(QLocale::system(), u"qtbase"_qs, u"_"_qs, #ifdef __linux__
QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { bool retVal =
kimaApp.installTranslator(&qtTranslator); qTranslator.load("qt_" + german.name(), QLibraryInfo::path(QLibraryInfo::TranslationsPath));
if (!retVal) {
throw std::runtime_error("Could not load translation.");
} }
#endif
#ifdef _WIN32
QApplication::setStyle(QStyleFactory::create("Fusion"));
qTranslator.load("qt_" + german.name(),
QApplication::applicationDirPath() + QDir::separator() + "translations");
#endif
kimaApp.installTranslator(&qTranslator);
QSettings settings{}; QSettings settings{};
while (!settings.contains("global/cashPointNo")) { while (!settings.contains("global/cashPointNo")) {

View file

@ -9,6 +9,7 @@
#include <config.h> #include <config.h>
#include <core/csvreader.h> #include <core/csvreader.h>
#include <core/excelreader.h>
#include <core/jsonutil.h> #include <core/jsonutil.h>
#include <core/utils.h> #include <core/utils.h>
#include <printer/posprinter.h> #include <printer/posprinter.h>
@ -99,13 +100,14 @@ MainWindow::MainWindow()
for (auto location : locations) { for (auto location : locations) {
if (QFile::exists(location + QString("/Benutzerhandbuch.pdf"))) { if (QFile::exists(location + QString("/Benutzerhandbuch.pdf"))) {
QDesktopServices::openUrl( QDesktopServices::openUrl(
QUrl::fromLocalFile(location + QString("/Benutzerhandbuch.pdf"))); QUrl(QString("file:///") + location + QString("/Benutzerhandbuch.pdf"),
QUrl::TolerantMode));
} }
} }
}); });
connect(m_ui.licenseAction, &QAction::triggered, this, [this]() { connect(m_ui.licenseAction, &QAction::triggered, this, [this]() {
QString licenseText( QString licenseText(
"Copyright © 2018-2024 Martin Brodbeck\n\n" "Copyright © 2018-2022 Martin Brodbeck\n\n"
"Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der " "Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der "
"zugehörigen Dokumentationen (die \"Software\") erhält, die Erlaubnis erteilt, " "zugehörigen Dokumentationen (die \"Software\") erhält, die Erlaubnis erteilt, "
"sie uneingeschränkt zu nutzen, inklusive und ohne Ausnahme mit dem Recht, " "sie uneingeschränkt zu nutzen, inklusive und ohne Ausnahme mit dem Recht, "
@ -135,8 +137,12 @@ MainWindow::MainWindow()
this->setWindowTitle("KIMA2 - Kasse Nr. " + this->setWindowTitle("KIMA2 - Kasse Nr. " +
QSettings().value("global/cashPointNo").toString()); QSettings().value("global/cashPointNo").toString());
}); });
connect(m_ui.importSellerAction, &QAction::triggered, this, connect(m_ui.importSellerExcelAction, &QAction::triggered, this,
&MainWindow::onImportSellerActionTriggered); &MainWindow::onImportSellerExcelActionTriggered);
connect(m_ui.importSellerJsonAction, &QAction::triggered, this,
&MainWindow::onImportSellerJsonActionTriggered);
connect(m_ui.exportSellerJsonAction, &QAction::triggered, this,
&MainWindow::onExportSellerJsonActionTriggered);
connect(m_ui.exportSalesJsonAction, &QAction::triggered, this, connect(m_ui.exportSalesJsonAction, &QAction::triggered, this,
&MainWindow::onExportSalesJsonActionTriggered); &MainWindow::onExportSalesJsonActionTriggered);
connect(m_ui.importSalesJsonAction, &QAction::triggered, this, connect(m_ui.importSalesJsonAction, &QAction::triggered, this,
@ -339,8 +345,7 @@ void MainWindow::onCancelArticleButtonClicked([[maybe_unused]] bool checked)
m_ui.basketView->model()->removeRow(iter->row()); m_ui.basketView->model()->removeRow(iter->row());
} }
m_ui.basketSumLabel->setText( m_ui.basketSumLabel->setText(m_marketplace->getBasketSumAsString().c_str()); // Update basket sum
m_marketplace->getBasketSumAsString().c_str()); // Update basket sum
m_ui.sellerNoEdit->setFocus(); m_ui.sellerNoEdit->setFocus();
} }
@ -423,8 +428,7 @@ void MainWindow::onCancelAllArticlesButtonClicked([[maybe_unused]] bool checked)
dynamic_cast<BasketModel *>(m_ui.basketView->model())->cancelSale(); dynamic_cast<BasketModel *>(m_ui.basketView->model())->cancelSale();
m_ui.basketSumLabel->setText( m_ui.basketSumLabel->setText(m_marketplace->getBasketSumAsString().c_str()); // Update basket sum
m_marketplace->getBasketSumAsString().c_str()); // Update basket sum
m_ui.sellerNoEdit->setFocus(); m_ui.sellerNoEdit->setFocus();
} }
@ -441,7 +445,7 @@ void MainWindow::onAbout()
">info@rustysoft.de</a>&gt;</p>"); ">info@rustysoft.de</a>&gt;</p>");
} }
void MainWindow::onImportSellerActionTriggered() void MainWindow::onImportSellerExcelActionTriggered()
{ {
if (!m_marketplace->getSales().empty()) { if (!m_marketplace->getSales().empty()) {
QMessageBox(QMessageBox::Icon::Information, "Import nicht möglich", QMessageBox(QMessageBox::Icon::Information, "Import nicht möglich",
@ -451,9 +455,9 @@ void MainWindow::onImportSellerActionTriggered()
return; return;
} }
auto filename = auto filename = QFileDialog::getOpenFileName(
QFileDialog::getOpenFileName(this, "Verkäufer importieren", QString(), this, "Verkäufer importieren", QString(),
"Alle unterstützte Dateien (*.csv);;CSV Dateien (*.csv)"); "Alle unterstützte Dateien (*.xlsx *.csv);;Excel Dateien (*.xlsx);;CSV Dateien (*.csv)");
if (filename.isEmpty()) if (filename.isEmpty())
return; return;
@ -465,19 +469,86 @@ void MainWindow::onImportSellerActionTriggered()
#endif #endif
std::size_t numImported{}; std::size_t numImported{};
numImported = CsvReader::readSellersFromFile(filePath, m_marketplace.get()); if (case_insensitive_match(filePath.extension().string(), std::string(".xlsx"))) {
try {
numImported = ExcelReader::readSellersFromFile(filePath, m_marketplace.get());
} catch (const std::exception &e) {
QMessageBox(QMessageBox::Icon::Critical, "Fehler beim Importieren",
"Beim Import aus der Excel-Datei ist ein Fehler aufgetreten. "
"Sie könnten ggf. versuchen, die Daten aus einer .csv Datei zu imporieren.",
QMessageBox::StandardButton::Ok, this)
.exec();
std::cerr << e.what() << std::endl;
return;
}
} else {
numImported = CsvReader::readSellersFromFile(filePath, m_marketplace.get());
}
updateStatLabel(); updateStatLabel();
using namespace std::string_literals; using namespace std::string_literals;
std::ostringstream msg; std::ostringstream msg;
msg << "Aus der CSV-Datei wurden <b>"s << std::to_string(numImported) msg << "Aus der Excel/CSV-Datei wurden <b>"s << std::to_string(numImported)
<< "</b> Verkäufer importiert."; << "</b> Verkäufer importiert.";
QMessageBox(QMessageBox::Icon::Information, "Verkäufer erfolgreich importiert", QMessageBox(QMessageBox::Icon::Information, "Verkäufer erfolgreich importiert",
msg.str().c_str(), QMessageBox::StandardButton::Ok, this) msg.str().c_str(), QMessageBox::StandardButton::Ok, this)
.exec(); .exec();
} }
void MainWindow::onImportSellerJsonActionTriggered()
{
if (!m_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;
#if defined(_WIN64) || defined(_WIN32)
fs::path filePath(filename.toStdWString());
#else
fs::path filePath(filename.toStdString());
#endif
std::size_t numImported{};
numImported = JsonUtil::importSellers(filePath, m_marketplace.get());
updateStatLabel();
using namespace std::string_literals;
std::ostringstream msg;
msg << "Aus der JSON-Datei wurden <b>"s << std::to_string(numImported)
<< "</b> Verkäufer importiert.";
QMessageBox(QMessageBox::Icon::Information, "Verkäufer erfolgreich importiert",
msg.str().c_str(), QMessageBox::StandardButton::Ok, this)
.exec();
}
void MainWindow::onExportSellerJsonActionTriggered()
{
auto filename = QFileDialog::getSaveFileName(
this, "Verkäufer exportieren", QString("kima2_verkaeufer.json"), "JSON Dateien (*.json)");
if (filename.isEmpty())
return;
#if defined(_WIN64) || defined(_WIN32)
fs::path filePath(filename.toStdWString());
#else
fs::path filePath(filename.toStdString());
#endif
JsonUtil::exportSellers(filePath, m_marketplace.get());
}
void MainWindow::onExportSalesJsonActionTriggered() void MainWindow::onExportSalesJsonActionTriggered()
{ {
QSettings settings; QSettings settings;
@ -504,30 +575,27 @@ void MainWindow::onImportSalesJsonActionTriggered()
{ {
QSettings settings; QSettings settings;
auto filenames = QFileDialog::getOpenFileNames(this, "Umsätze/Transaktionen importieren", auto filename = QFileDialog::getOpenFileName(this, "Umsätze/Transaktionen importieren",
QString(), "JSON Dateien (*.json)"); QString(), "JSON Dateien (*.json)");
if (filenames.isEmpty()) if (filename.isEmpty())
return; return;
for(auto filename: filenames) {
#if defined(_WIN64) || defined(_WIN32) #if defined(_WIN64) || defined(_WIN32)
fs::path filePath(filename.toStdWString()); fs::path filePath(filename.toStdWString());
#else #else
fs::path filePath(filename.toStdString()); fs::path filePath(filename.toStdString());
#endif #endif
delete m_ui.salesView->model(); delete m_ui.salesView->model();
try { try {
JsonUtil::importSales(filePath, m_marketplace.get(), JsonUtil::importSales(filePath, m_marketplace.get(),
settings.value("global/cashPointNo").toInt()); settings.value("global/cashPointNo").toInt());
} catch (std::runtime_error &err) { } catch (std::runtime_error &err) {
QMessageBox(QMessageBox::Icon::Warning, "Import nicht möglich", err.what(), QMessageBox::Ok, QMessageBox(QMessageBox::Icon::Warning, "Import nicht möglich", err.what(), QMessageBox::Ok,
this) this)
.exec(); .exec();
}
} }
setSaleModel(); setSaleModel();
updateStatLabel(); updateStatLabel();
} }

View file

@ -39,7 +39,9 @@ class MainWindow : public QMainWindow
void checkSellerNo(bool ctrlPressed = false); void checkSellerNo(bool ctrlPressed = false);
void onPaidButtonTriggered(); void onPaidButtonTriggered();
void onGivenSpinBoxValueChanged(double value); void onGivenSpinBoxValueChanged(double value);
void onImportSellerActionTriggered(); void onImportSellerExcelActionTriggered();
void onImportSellerJsonActionTriggered();
void onExportSellerJsonActionTriggered();
void onExportSalesJsonActionTriggered(); void onExportSalesJsonActionTriggered();
void onImportSalesJsonActionTriggered(); void onImportSalesJsonActionTriggered();
void setSaleModel(); void setSaleModel();

View file

@ -423,7 +423,7 @@ drucken</string>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>817</width> <width>817</width>
<height>24</height> <height>30</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menu_Datei"> <widget class="QMenu" name="menu_Datei">
@ -440,8 +440,16 @@ drucken</string>
<property name="title"> <property name="title">
<string>&amp;Verkäufer</string> <string>&amp;Verkäufer</string>
</property> </property>
<widget class="QMenu" name="importSellerMenu">
<property name="title">
<string>Importieren</string>
</property>
<addaction name="importSellerExcelAction"/>
<addaction name="importSellerJsonAction"/>
</widget>
<addaction name="actionEditSeller"/> <addaction name="actionEditSeller"/>
<addaction name="importSellerAction"/> <addaction name="importSellerMenu"/>
<addaction name="exportSellerJsonAction"/>
</widget> </widget>
<widget class="QMenu" name="menuHilfe"> <widget class="QMenu" name="menuHilfe">
<property name="title"> <property name="title">
@ -505,9 +513,9 @@ drucken</string>
<string>Exportieren für andere Kasse (JSON)</string> <string>Exportieren für andere Kasse (JSON)</string>
</property> </property>
</action> </action>
<action name="importSellerActionX"> <action name="importSellerExcelAction">
<property name="text"> <property name="text">
<string>Aus CSV-Datei (initial)</string> <string>Aus Excel/CSV-Datei (initial)</string>
</property> </property>
</action> </action>
<action name="importSellerJsonAction"> <action name="importSellerJsonAction">
@ -540,11 +548,6 @@ drucken</string>
<string>Lizenz</string> <string>Lizenz</string>
</property> </property>
</action> </action>
<action name="importSellerAction">
<property name="text">
<string>Importieren (aus CSV-Datei)</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View file

@ -1,6 +1,6 @@
set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_LIBS ON)
find_package(Boost 1.78 REQUIRED) find_package(Boost 1.62 REQUIRED)
if(WIN32) if(WIN32)
find_package(LIBUSB REQUIRED) find_package(LIBUSB REQUIRED)
@ -20,4 +20,4 @@ if(WIN32)
else() else()
target_link_libraries(printer core ${LibUSB_LIBRARIES}) target_link_libraries(printer core ${LibUSB_LIBRARIES})
endif() endif()
target_include_directories(printer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. ${Boost_INCLUDE_DIRS}) target_include_directories(printer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)

@ -0,0 +1 @@
Subproject commit ea547fdb16c7baf99bd9ced5febba52cc5da3ca3

@ -1 +1 @@
Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d

@ -1 +1 @@
Subproject commit 8c48163c4d3fbba603cfe8a5b94046c9dad71825 Subproject commit a3ed916f591c300e97b873fde36863fa37b49fa9