#include "ticket_sales_report_widget.h" #include "error_dialog.h" #include "repository_manager.h" #include "repository_result.h" #include "schema_metadata.h" #include "session_statistics_dto.h" #include "sessions_statistics_repository.h" #include "table_info_sqlite.h" #include #include #include #include TicketSalesReportWidget::SalesData::SalesData() : refundExpenses_(0), refundedTickets_(0), salesRevenue_(0), soldTickets_(0), maximumTickets_(0) {} TicketSalesReportWidget::SalesData::SalesData(double refundExpenses, double salesRevenue, qint32 refundedTickets, qint32 soldTickets, qint32 maxTickets) : refundExpenses_(refundExpenses), refundedTickets_(refundedTickets), salesRevenue_(salesRevenue), soldTickets_(soldTickets), maximumTickets_(maxTickets) {} TicketSalesReportWidget::SalesData TicketSalesReportWidget::SalesData::operator+(const SalesData &other) const { SalesData result; result.setMaximumTickets(maximumTickets() + other.maximumTickets()); result.setSoldTickets(soldTickets() + other.soldTickets()); result.setRefundedTickets(refundedTickets() + other.refundedTickets()); result.setSalesRevenue(salesRevenue() + other.salesRevenue()); result.setRefundExpenses(refundExpenses() + other.refundExpenses()); return result; } TicketSalesReportWidget::SalesData & TicketSalesReportWidget::SalesData::operator+=(const SalesData &other) { *this = std::move(*this + other); return *this; } TicketSalesReportWidget::TicketSalesReportWidget(QWidget *parent) : QWidget(parent) { setupUi(this); initializeRepositories(); initializeChart(); setupChart(); // After initialize chart setupConnections(); } void TicketSalesReportWidget::configureReportForGenre() { TableInfoSQLite tableInfo("genres"); columnInfoIndex_ = tableInfo.columnIndex("genre_name"); configureEntityFinder( "genres", EntityFinderWidget::tr("Select genre"), EntityFinderWidget::tr("Here will be information about selected genre")); sessionsStatisticsMethod_ = std::bind(&SessionsStatisticsRepository::getSessionsForGenre, sessionsStatisticsRepository, std::placeholders::_1, // entityId std::placeholders::_2, // startDate std::placeholders::_3, // endDate std::placeholders::_4 // sessions ); billetChartTitle_ = tr("Popularity of Genre: Name: \"%1\""); seriesName_ = tr("Tickets Sold"); yAxisTitle_ = tr("Number of Tickets Sold"); } void TicketSalesReportWidget::configureReportForHall() { TableInfoSQLite tableInfo("halls"); columnInfoIndex_ = tableInfo.columnIndex("hall_name"); configureEntityFinder( "halls", EntityFinderWidget::tr("Select hall"), EntityFinderWidget::tr("Here will be information about selected hall")); sessionsStatisticsMethod_ = std::bind(&SessionsStatisticsRepository::getSessionsForHall, sessionsStatisticsRepository, std::placeholders::_1, // entityId std::placeholders::_2, // startDate std::placeholders::_3, // endDate std::placeholders::_4 // sessions ); billetChartTitle_ = tr("Attendance for Hall: Name: \"%1\""); seriesName_ = tr("Visitors"); yAxisTitle_ = tr("Number of Visitors"); } void TicketSalesReportWidget::configureReportForMovie() { TableInfoSQLite tableInfo("movies"); columnInfoIndex_ = tableInfo.columnIndex("title"); configureEntityFinder( "movies", EntityFinderWidget::tr("Select movie"), EntityFinderWidget::tr("Here will be information about selected movie")); sessionsStatisticsMethod_ = std::bind(&SessionsStatisticsRepository::getSessionsForMovie, sessionsStatisticsRepository, std::placeholders::_1, // entityId std::placeholders::_2, // startDate std::placeholders::_3, // endDate std::placeholders::_4 // sessions ); billetChartTitle_ = tr("Tickets Sold for Movie: Title: \"%1\""); seriesName_ = tr("Tickets Sold"); yAxisTitle_ = tr("Number of Tickets Sold"); } void TicketSalesReportWidget::initializeRepositories() { RepositoryManager &repositoryManager = RepositoryManager::instance(); SchemaMetadata metadata = SchemaMetadata::defaultSchema(); sessionsStatisticsRepository = repositoryManager.getRepositoryAs( metadata.repositoryTableName("sessions_statistics")); if (!bool(sessionsStatisticsRepository)) { qFatal() << "TicketSalesReportWidget::initializeRepositories: Failed to " "initialize repositories"; } } void TicketSalesReportWidget::initializeChart() { chartView_ = new QChartView(this); chartView_->setRenderHint(QPainter::Antialiasing); } void TicketSalesReportWidget::setupChart() { frameChart->layout()->addWidget(chartView_); } void TicketSalesReportWidget::setupConnections() { connect(btnGenerate, &QPushButton::clicked, this, &TicketSalesReportWidget::onPushButtonGenerateClicked); connect(entityFinder, &EntityFinderWidget::fieldRetrieved, this, &TicketSalesReportWidget::onFieldRetrieved); connect(entityFinder, &EntityFinderWidget::reseted, this, &TicketSalesReportWidget::onEntityFinderReseted); connect(entityFinder, &EntityFinderWidget::recordRetrieved, this, &TicketSalesReportWidget::onRecordRetrieved); } void TicketSalesReportWidget::configureEntityFinder( const QString &databaseTableName, const QString &labelText, const QString &placeholder) { entityFinder->setDefaultRelation(databaseTableName); entityFinder->setLabelText(labelText); entityFinder->setLineEditPlaceholder(placeholder); } void TicketSalesReportWidget::onEntityFinderReseted() { selectedEntityId_ = -1; chartView_->setChart(new QChart()); } void TicketSalesReportWidget::onPushButtonGenerateClicked() { if (!dateRange(startRange_, endRange_)) { QMessageBox::QMessageBox::warning(this, QMessageBox::tr("Error"), QMessageBox::tr("Invalid date range")); return; } QVector sessions; RepositoryResult result = retrieveSessions(sessions); if (!result.success) { ErrorDialog::displayError(this, result.errorMessage); return; } if (sessions.isEmpty()) { QMessageBox::warning(this, QMessageBox::tr("Error"), QMessageBox::tr("No sessions found between %1 and %2.") .arg(startRange_.toString("dd.MM.yyyy")) .arg(endRange_.toString("dd.MM.yyyy"))); return; } createChart(aggregateByDateTime(sessions)); } void TicketSalesReportWidget::onFieldRetrieved(const QSqlField &field) { selectedEntityId_ = field.value().toInt(); chartView_->setChart(new QChart()); } void TicketSalesReportWidget::onRecordRetrieved(const QSqlRecord &rowValues) { QString columnInfoValue = rowValues.value(columnInfoIndex_).toString(); chartTitle_ = billetChartTitle_.arg(columnInfoValue); } QMap TicketSalesReportWidget::aggregateByDateTime( const QVector &sessions, std::function filter) { QMap accamulatedSalesData; for (const SessionStatisticsDTO &session : sessions) { if (!filter || filter(session)) { QDateTime dateTime = session.beginAt(); SalesData buffer; buffer.setRefundExpenses(session.refundExpenses()); buffer.setRefundedTickets(session.refundedTickets()); buffer.setSalesRevenue(session.salesRevenue()); buffer.setSoldTickets(session.soldTickets()); buffer.setMaximumTickets(session.totalSeats()); accamulatedSalesData[dateTime] += buffer; } } return accamulatedSalesData; }; QAbstractAxis *TicketSalesReportWidget::createDateTimeAxis( int tickCount, const QString &axisTitle, const QDateTime &minDate, const QDateTime &maxDate) const { QDateTimeAxis *axisX = new QDateTimeAxis(); axisX->setTitleText(axisTitle); axisX->setFormat("dd.MM.yyyy"); axisX->setTickCount(tickCount); axisX->setRange(minDate, maxDate); return axisX; } QAbstractAxis * TicketSalesReportWidget::createValueAxis(int maxValue, const QString &axisTitle) const { QValueAxis *axisY = new QValueAxis(); axisY->setTitleText(axisTitle); axisY->setLabelFormat("%d"); axisY->setRange(0, maxValue); return axisY; } bool TicketSalesReportWidget::validateData( const QMap &data) const { if (!data.isEmpty()) { return true; } qWarning() << "No data for chart:" << chartTitle_; // QMessageBox::warning(this, QMessageBox::tr("Error"), // QMessageBox::tr("No data for chart")); return false; } RepositoryResult TicketSalesReportWidget::retrieveSessions( QVector &sessions) { RepositoryResult result = sessionsStatisticsMethod_(selectedEntityId_, std::cref(startRange_), std::cref(endRange_), std::ref(sessions)); return result; } bool TicketSalesReportWidget::dateRange(QDateTime &startOut, QDateTime &endOut) { startOut = dateTimeEditStartOfPeriod->dateTime(); endOut = dateTimeEditEndOfPeriod->dateTime(); return endOut > startOut; } void TicketSalesReportWidget::createChart( const QMap &data) { if (!validateData(data)) { return; } QDateTime minDate, maxDate; int tickCount = data.size(); if (tickCount > 10) { tickCount = 10; } QChart *chart = new QChart(); chart->setTitle(chartTitle_); QAbstractSeries *maximumTicketsSeries = createScatterSeries(data, minDate, maxDate, &SalesData::maximumTickets, tr("Maximum Tickets")); QAbstractSeries *soldTicketsSeries = createScatterSeries( data, minDate, maxDate, &SalesData::soldTickets, tr("Sold Tickets")); QAbstractSeries *refundedTicketsSeries = createScatterSeries(data, minDate, maxDate, &SalesData::refundedTickets, tr("Refunded Tickets")); QAbstractSeries *salesRevenueSeries = createScatterSeries( data, minDate, maxDate, &SalesData::salesRevenue, tr("Sales Revenue")); QAbstractSeries *refundExpensesSeries = createScatterSeries(data, minDate, maxDate, &SalesData::refundExpenses, tr("Refund Expenses")); chart->addSeries(maximumTicketsSeries); chart->addSeries(soldTicketsSeries); chart->addSeries(refundedTicketsSeries); chart->addSeries(salesRevenueSeries); chart->addSeries(refundExpensesSeries); QAbstractAxis *axisX = createDateTimeAxis(tickCount, tr("Date"), minDate, maxDate); chart->addAxis(axisX, Qt::AlignBottom); double maxMoneyValue = std::max({maxValue(data, &SalesData::refundExpenses), maxValue(data, &SalesData::salesRevenue)}); QAbstractAxis *axisYMoney = createValueAxis(maxMoneyValue, tr("Money")); chart->addAxis(axisYMoney, Qt::AlignLeft); int maxTickets = maxValue(data, &SalesData::maximumTickets); QAbstractAxis *axisYTickets = createValueAxis(maxTickets, tr("Tickets")); chart->addAxis(axisYTickets, Qt::AlignRight); qDebug() << "Maximum movey Value" << maxMoneyValue << "maxTickets" << maxTickets; maximumTicketsSeries->attachAxis(axisYTickets); refundedTicketsSeries->attachAxis(axisYTickets); soldTicketsSeries->attachAxis(axisYTickets); refundExpensesSeries->attachAxis(axisYMoney); salesRevenueSeries->attachAxis(axisYMoney); maximumTicketsSeries->attachAxis(axisX); refundExpensesSeries->attachAxis(axisX); refundedTicketsSeries->attachAxis(axisX); salesRevenueSeries->attachAxis(axisX); soldTicketsSeries->attachAxis(axisX); updateChartView(chart); } void TicketSalesReportWidget::setupChart(QChart *chart, QAbstractSeries *series, QAbstractAxis *axisX, QAbstractAxis *axisY) const { chart->addSeries(series); chart->addAxis(axisX, Qt::AlignBottom); chart->addAxis(axisY, Qt::AlignLeft); series->attachAxis(axisX); series->attachAxis(axisY); } void TicketSalesReportWidget::updateChartView(QChart *chart) const { chartView_->setChart(chart); chartView_->setRenderHint(QPainter::Antialiasing); }