Files
miniCashAll/libMiniCash/mcbillingview.cpp

635 lines
19 KiB
C++
Raw Normal View History

2025-08-05 22:37:51 +02:00
/***************************************************************************
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 <QMessageBox>
#include <QPainter>
#include <QtCore>
#include <QtGui>
#include <QStatusBar>
#include <QProgressBar>
#include <QRegularExpressionValidator>
#include <mcprinter.h>
#include <mcbillingview.h>
#include <mcloaddialog.h>
#include <mcmainwindowbase.h>
#include <ui_mcbillingview.h>
/**
* @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:
* <b><n>_<monat-tag-jahr>.klm</b>, also etwa: <b>1_03-13-2022.klm</b>
*
*/
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(
"<tr>"
"<td width='25%' align='left'>%0</td>"
"<td width='25%' align='center'>%1</td>"
"<td width='25%' align='right'>%2 </td>"
"<td width='25%' align='right'>%3 </td>"
"</tr>"
);
/// 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();
///<FIX>
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 &#8364; <-- 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", "<br>" );
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(
"<tr>"
"<td>%0</td>"
"<td align='center'>%1 </td>"
"<td align='center'>%2 </td>"
"<td>&nbsp;</td>"
"<td>&nbsp;</td>"
"</tr>"
"<tr><td colspan='5'>-------------------------------------------------------------------------------------------------------------------------------------------------------</td></tr>"
);
/// 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 &#8364; <-- 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.") );
}