Initial commit
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
#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);
|
||||
}
|
||||
Reference in New Issue
Block a user