/*************************************************************************** libMiniCash Copyright © 2013-2022 christoph holzheuer c.holzheuer@sourceworx.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief Konstruktor des Abrechnungsdialogs * * @param parent Das Elternfenster * @param datafilename Der Filename der Kassendatei * @param settings die globalen Systemeinstellungen * */ MCBillingView::MCBillingView( QWidget *parent ) : QFrame( parent ), _ui{new Ui::MCBillingView}, _allCustomers( 0 ) { _ui->setupUi( this ); /// model setzen _salesModel.setParent( this ); _ui->_trList->setModel( &_salesModel ); QRegularExpressionValidator* srcDrive = new QRegularExpressionValidator( QRegularExpression( miniCash::fSrcDrive ) ); QRegularExpressionValidator* profit = new QRegularExpressionValidator( QRegularExpression( miniCash::fProfit ) ); QRegularExpressionValidator* fromID = new QRegularExpressionValidator( QRegularExpression( miniCash::fFromID ) ); QRegularExpressionValidator* toID = new QRegularExpressionValidator( QRegularExpression( miniCash::fToID ) ); _ui->_srcDrive->setValidator( srcDrive ); _ui->_trProfit->setValidator( profit ); _ui->_trFrom->setValidator( fromID ); _ui->_trTo->setValidator( toID ); } /** * @brief Destruktor * * Destruktor * */ MCBillingView::~MCBillingView() { } /** * @brief MCBillingView::setupDefaults: Werte von ausserhalb, weil durch die * Qt-setpUi()-Autotmatik nichts mehr über den Konstruktor übergeben werden * kann. * * @param datafilename * @param settings */ void MCBillingView::setupDefaults( const QString& datafilename, QSettings* settings ) { /// dateifilter für die Kassenfiles _fileFilter = "*_" + datafilename; _settings = settings; /// Wertvorgaben laden _ui->_srcDrive->addItem( _settings->value( miniCash::keyMobileDrive ).toString() ); _ui->_trFooterText->setText( _settings->value( miniCash::keyFooterText ).toString() ); connect( _ui->_trButtonRead, SIGNAL(clicked()), this, SLOT( onReadTransactions()) ); connect( _ui->_trButtonPrintBills, SIGNAL(clicked()), this, SLOT( onPrintBills()) ); connect( _ui->_trButtonPrintReceipts,SIGNAL(clicked()), this, SLOT( onPrintReceipts()) ); /// erst lesen dann drucken _ui->_trButtonPrintBills->setEnabled( false ); _ui->_trButtonPrintReceipts->setEnabled( false ); _ui->_trProfit->setText( _settings->value( miniCash::keyProfit ).toString() ); } /** * @brief testet die Formulareingaben! * * Testet die Eingaben des Abrechnungsformulars auf Gültigkeit: * * - Anteil des Kindergartens * - Kundennummer 'von' * - Kundennummer 'bis' * */ bool MCBillingView::testFormData() { const QString& profit = _ui->_trProfit->text(); const QString& trFrom = _ui->_trFrom->text(); const QString& trTo = _ui->_trTo->text(); if( profit.isEmpty() || trFrom.isEmpty() || trTo.isEmpty() ) { QMessageBox::warning ( this, "Eingabefehler", QString( "Die Felder 'Anteil Kindergarten' und\n " "'Abrechung Kundenummer von...bis'\nmüssen belegt sein." ) ); return false; } // alles ok, also Felder sichern _settings->setValue( miniCash::keyMobileDrive, _ui->_srcDrive->currentText() ); _settings->setValue( miniCash::keyProfit, profit ); _settings->setValue( miniCash::keyFooterText, _ui->_trFooterText->document()->toPlainText() ); return true; } /** * @brief Kassendateien einlesen * * Liest die (nunmehr zusammenkopierten) Kassendateien zur weiteren Bearbeitung ein. * Die Dateinamen sind aus der jeweiligen Kassennummer (1..x) und dem Basisnahmen * zusammengesetzt: * _.klm, also etwa: 1_03-13-2022.klm * */ void MCBillingView::onReadTransactions() { /// erst lesen dann drucken _ui->_trButtonPrintBills->setEnabled( false ); _ui->_trButtonPrintReceipts->setEnabled( false ); /// /// erstmal die Felder prüfen und erst bei korrekten Daten weitermachen. /// if( !testFormData() ) return; /// saubermachen _salesSummary.clear(); QDir datadir( _ui->_srcDrive->currentText() ); datadir.setFilter( QDir::Files | QDir::NoSymLinks ); datadir.setSorting( QDir::Name ); /// Dateien holen und zeigen QStringList filter( _fileFilter ); QStringList list = datadir.entryList( filter ); /// was gefunden? if( list.isEmpty() ) { QString msg( "Im Pfad '%0' \nkonnten keine Kassendateien gefunden werden." ); QMessageBox::warning(this, "Keine Kassendateien gefunden", msg.arg( datadir.absolutePath() + _fileFilter ) ); return; } /// listview befüllen MCLoadDialog dlg( this ); for( const QString& entry : list ) dlg.appendEntry( entry ); if( dlg.exec() != QDialog::Accepted ) return; /// alle Kassenfiles einlesen _salesSummary.clear(); ///_salesModel.clear(); <-- löscht den Header mit _salesModel.removeRows( 0, _salesModel.rowCount() ); dlg.show(); _allCustomers = 0; int salescount=0, customercount=0; QString dlgitem( ": Kunden: %0 Artikel: %1" ); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); for( int i = 0; i < list.size(); ++i ) { QString filename = _ui->_srcDrive->currentText() + list.at( i ); salescount = _salesSummary.appendFromFile( filename, customercount, _salesModel ); _allCustomers += customercount; dlg.updateEntry( i, dlgitem.arg( customercount ).arg( salescount ) ); } QApplication::restoreOverrideCursor(); if( dlg.exec() != QDialog::Accepted ) return; /// es kann gedruckt werden _ui->_trButtonPrintBills->setEnabled( true ); _ui->_trButtonPrintReceipts->setEnabled( true ); } /** * @brief Abrechungen drucken * * Erzeugt aus den eingelesenen Kassendaten mit Hilfe * der vorgegebenen HTML-Templates die Druckdateien der * Abrechung als .pdf-Files. * * Auf den Abrechnungen sind pro Kunden(=Verkäufer)-Nummer die Laufnummern der * jeweils verkauften Artikel, der Umsatz und der Auszahlungsbetrag nach Abzug der * Provision für den Kindergarten aufgelistet. * * @bug die untere ('1100') und obere ('1399') Grenze des Kundennummernintervalls * sind nicht mehr unbedingt gültig und sollten hier nicht hardkodiert sein. * @bug der progressbar wir nicht benutzt. */ void MCBillingView::onPrintBills() { /// /// nochmal die Felder prüfen und erst bei korrekten Daten weitermachen. /// if( !testFormData() ) return; /// /// Step 1: HTML-Template laden und Wiederholungsblock /// (= Zeile auf der Abrechnung) erzeugen /// QFile file( miniCash::tplPayoff ); if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) { QMessageBox::critical( this, tr( "Dateifehler" ), QString( tr( "Datei nicht gefunden: %1" ) ).arg( file.errorString() ), QMessageBox::Ok ); return; } /// HTML-template für die Abrechnung QString invoice( file.readAll() ); /// speichert alles Abrechnungen /// Zeilentemplate der Abrechung QString block( "" "%0" "%1" "%2 " "%3 " "" ); /// Die Abrechnung QTextDocument document; /// der Anteil für den Kindergarten in % double fee = _settings->value( miniCash::keyProfit ).toDouble(); /// in Datei Drucken ? bool printToFile = _ui->_trSaveBills->isChecked(); QString drive = _ui->_srcDrive->currentText(); QPrinter printer; if( printToFile ) printer.setOutputFormat( QPrinter::PdfFormat ); /// /// Step 2: Iterieren über alle Verkäufer ... /// const QString& strFrom = _ui->_trFrom->text(); const QString& strTo = _ui->_trTo->text(); /// wenn ein intervall angegeben ist, muss zuerst der exakte Schlüssel /// gesucht werden, denn upperbound liefert die position _nach_ dem /// angegebenen Schlüssel. MCSalesSummary::const_iterator posSeller = _salesSummary.cbegin(); if( strFrom != "1100" ) { posSeller = _salesSummary.constFind( strFrom ); if( posSeller == _salesSummary.cend() ) posSeller = _salesSummary.lowerBound( strFrom ); } /// dito das 'obere' Ende des Intervalls MCSalesSummary::const_iterator posEnd = _salesSummary.cend(); /// if( strTo != "1399" ) { posEnd = _salesSummary.constFind( strTo ); if( posEnd == _salesSummary.cend() ) posEnd = _salesSummary.upperBound( strTo ); } /// Progressbar vorbereiten /* int sellerCount = 0; QStatusBar* statusBar = ( (MCMainWindow*) parent() )->statusBar(); QProgressBar* progress = new QProgressBar( bar ); progress->setRange( 0, _salesSummary.size() ); bar->addWidget( progress ); */ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); /// Über alle Verkäufernummern ... for( ; posSeller != posEnd; ++posSeller ) { /// /// Step 2B: Iterieren über alle verkauften Artikel des Verkäufers ... /// //// FIX! //// progress->setValue( sellerCount++ ); /// Der Zeilenblock, also die Einzelauflistung der verkauften Artikel QString result; /// Der Umsatz der Verkäufers double revenueOverall = 0; /// Der Auszahlungsbetrag (Umsatz abzgl. Kindergartenanteil) double gainOverall = 0; int soldItems = 0; MCSalesItemMap::const_iterator posSales = posSeller.value().begin(); for( ; posSales != posSeller.value().end(); ++posSales ) { qDebug() << "Kunde: " << posSeller.key(); /// Ein Eintrag const MCSalesItem& itm = posSales.value(); revenueOverall += itm.trPrice; double gain = itm.trPrice / 100.0 * (100.0 - fee ); gainOverall += gain; ++soldItems; QString entry = block.arg ( itm.trSellerID, itm.trItemNo, MCSalesModel::toCurrency( itm.trPrice ), MCSalesModel::toCurrency( gain ) ); result += entry + '\n'; } /// Über alle Artikel € <-- Euro /// Seite fertig: Header Parameter in die Abrechnung schreiben QDate date = QDate::currentDate(); /// header QString soldStr = QString("%1").arg( soldItems ); QString tmpInvoice = invoice.arg ( date.toString( "dd.MM.yyyy" ), posSeller.key(), soldStr, MCSalesModel::toCurrency( revenueOverall ), MCSalesModel::toCurrency( gainOverall ) ); /// body & footer QString footer = _settings->value( miniCash::keyFooterText ).toString(); if( !footer.isEmpty() ) { footer.replace( "\n", "
" ); footer = "-------------------------------------------------------------------------------------------------------------------------------------------------------\n" + footer; } tmpInvoice = tmpInvoice.arg( result, footer ); if( printToFile ) printer.setOutputFileName( drive + QString("abrechung_%1.pdf").arg( posSeller.key() ) ); document.setHtml( tmpInvoice ); document.print( &printer ); } /// über alle Verkäufer ///statusBar->removeWidget( progress ); QApplication::restoreOverrideCursor(); QMessageBox::information( this, "Abrechungen drucken", "Alle Druckaufträge wurden erfolgreich erzeugt." ); } /** * @brief Quittierlisten drucken * * Erzeugt aus den eingelesenen Kassendaten mit Hilfe * der vorgegebenen HTML-Templates die Druckdateien der * Quittierlisten als .pdf-Files. * * Auf den Quittierlisten steht die Kunden(=Verkäufer)-Nummer, der * Auszahlungsbetrag und das Unterschriftsfeld zur Bestätigung der Auszahlung. * * @bug die untere ('1100') und obere ('1399') Grenze des Kundennummernintervalls * sind nicht mehr unbedingt gültig und sollten hier nicht hardkodiert sein. * @bug der progressbar wir nicht benutzt. */ void MCBillingView::onPrintReceipts() { static const int MAXLINES = 23; /// /// nochmal die Felder prüfen und erst bei korrekten Daten weitermachen. /// if( !testFormData() ) return; /// /// Step 1: HTML-Template laden und Wiederholungsblock /// (= Zeile auf der Abrechnung) erzeugen /// QString fileKey = miniCash::tplReceipt; QFile file( fileKey ); if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) { QMessageBox::critical( this, "Dateifehler", QString( "Datei nicht gefunden: %1" ).arg( file.errorString() ), QMessageBox::Ok ); return; } /// HTML-template für die Quittungsliste QString receipt( file.readAll() ); /// Zeilentemplate der Abrechung QString block( "" "%0" "%1 " "%2 " " " " " "" "-------------------------------------------------------------------------------------------------------------------------------------------------------" ); /// Die Abrechnung QTextDocument document; /// der Anteil für den Kindergarten in % double fee = _settings->value( miniCash::keyProfit ).toDouble(); MCPrinter printer; QString drive = _ui->_srcDrive->currentText(); bool printToFile = _ui->_trSaveReceipts->isChecked(); if( printToFile ) printer.setOutputFormat( QPrinter::PdfFormat ); /// Der Zeilenblock, also die Einzelauflistung der verkauften Artikel QString result, fromKey, toKey; bool setKey = true; int pageCount=1; int lineCount=0; double revenueFinal = 0; double gainFinal = 0; int piecesFinal = 0; /* QStatusBar* statusBar = ( (MCMainWindow*) parent() )->statusBar(); QProgressBar* progress = new QProgressBar( statusBar ); progress->setRange( 0, _salesSummary.size() ); statusBar->addWidget( progress ); */ /// /// Step 2: Iterieren über alle Verkäufer ... /// QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); MCSalesSummary::const_iterator posSeller = _salesSummary.cbegin(); for( ; posSeller != _salesSummary.cend(); ++posSeller ) { /// Schlüssel merken für die Anzeige "Verkäufer von .. bis" if( setKey ) { fromKey = posSeller.key(); setKey = false; } toKey = posSeller.key(); /// /// Step 2B: Iterieren über alle verkauften Artikel des Verkäufers ... /// /// Der Umsatz der Verkäufers double revenueOverall = 0; /// Der Auszahlungsbetrag (Umsatz abzgl. Kindergartenanteil) double gainOverall = 0; ////progress->setValue( pageCount ); MCSalesItemMap::const_iterator posSales = posSeller.value().cbegin(); piecesFinal += posSeller.value().size(); for( ; posSales != posSeller.value().cend(); ++posSales ) { /// Ein Eintrag const MCSalesItem& itm = posSales.value(); revenueOverall += itm.trPrice; double gain = itm.trPrice / 100.0 * (100.0 - fee ); gainOverall += gain; } /// über alle Artikel € <-- Euro revenueFinal += revenueOverall; gainFinal += gainOverall; QString entry = block.arg ( posSeller.key(), MCSalesModel::toCurrency( revenueOverall ), MCSalesModel::toCurrency( gainOverall ) ); result += entry + '\n'; /// Seite fertig: drucken, zurücksetzen & neu starten if( ++lineCount >= MAXLINES ) { lineCount = 0; if( printToFile ) printer.setOutputFileName( drive + QString("quittungen_%1.pdf").arg( pageCount ) ); pageCount++; /// Seite fertig: Header Parameter in die Abrechnung schreiben setKey = true; QDate date = QDate::currentDate(); /// header QString tmpReceipt = receipt.arg( date.toString( "dd.MM.yyyy" ), fromKey, toKey, result ); document.setHtml( tmpReceipt ); document.print( &printer ); document.clear(); result = ""; } ///if(pageCount >= 3 ) /// break; } /// über alle Verkäufer /// Reste Drucken: QDate date = QDate::currentDate(); if( lineCount && lineCount < MAXLINES ) { /// header QString tmpReceipt = receipt.arg( date.toString( "dd.MM.yyyy" ), fromKey, toKey, result ); if( printToFile ) printer.setOutputFileName( drive + QString( "receipts_%1.pdf" ).arg( pageCount ) ); document.setHtml( tmpReceipt ); document.print( &printer ); } /// und das Finale Abschlussdokument: die Endabrechnung QFile lastpage( miniCash::tplFinal ); if( !lastpage.open( QIODevice::ReadOnly | QIODevice::Text ) ) { QMessageBox::critical( this, tr("Dateifehler"), QString( tr("Datei nicht gefunden: %1") ).arg( lastpage.errorString() ), QMessageBox::Ok ); return; } QString final( lastpage.readAll() ); final = final.arg( date.toString( "dd.MM.yyyy" ), MCSalesModel::toCurrency( revenueFinal ), MCSalesModel::toCurrency( gainFinal ) ); final = final.arg( MCSalesModel::toCurrency( revenueFinal - gainFinal ) ); final = final.arg( fee ); final = final.arg( piecesFinal ); final = final.arg( _allCustomers ); final = final.arg( _salesSummary.size() ); if( printToFile ) printer.setOutputFileName( drive + "FinalInvoice.pdf" ); document.setHtml( final ); document.print( &printer ); QApplication::restoreOverrideCursor(); ///statusBar->removeWidget( progress ); QMessageBox::information( this, tr("Quittungen drucken"), tr("Alle Druckaufträge wurden erfolgreich erzeugt.") ); }