Files

363 lines
12 KiB
C++
Raw Permalink Normal View History

2026-07-12 14:34:52 +04:00
#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 <QDateTimeAxis>
#include <QMessageBox>
#include <QScatterSeries>
#include <QValueAxis>
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<SessionsStatisticsRepository>(
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<SessionStatisticsDTO> 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<QDateTime, TicketSalesReportWidget::SalesData>
TicketSalesReportWidget::aggregateByDateTime(
const QVector<SessionStatisticsDTO> &sessions,
std::function<bool(const SessionStatisticsDTO &)> filter) {
QMap<QDateTime, SalesData> 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<QDateTime, SalesData> &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<SessionStatisticsDTO> &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<QDateTime, SalesData> &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);
}