Initial commit

This commit is contained in:
user
2026-07-12 14:34:52 +04:00
commit 3abf6bd9c2
557 changed files with 68706 additions and 0 deletions
@@ -0,0 +1,160 @@
#include "directories_management_widget.h"
// Genre
#include "create_genre_request.h"
#include "genres_model.h"
#include "genres_repository.h"
// Hall
#include "create_hall_request.h"
#include "halls_model.h"
#include "halls_repository.h"
// Movie
#include "create_movie_request.h"
#include "movies_model.h"
#include "movies_repository.h"
// Common
#include "error_dialog.h"
#include "model_manager.h"
#include "repository_manager.h"
#include "repository_result.h"
#include "schema_metadata.h"
#include <QMessageBox>
DirectoriesManagementWidget::DirectoriesManagementWidget(QWidget *parent)
: QWidget(parent), selectedGenreId_(-1), selectedHallId_(-1),
selectedMovieId_(-1) {
setupUi(this);
initializeRepositories(); // First of second
initializeModels(); // First of second
setupTableViews(); // After initialization of models
setupGenreFinder();
setupConnections();
}
void DirectoriesManagementWidget::initializeRepositories() {
RepositoryManager &repositoryManager = RepositoryManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
genresRepository_ = repositoryManager.getRepositoryAs<GenresRepository>(
metadata.repositoryTableName("genres"));
hallsRepository_ = repositoryManager.getRepositoryAs<HallsRepository>(
metadata.repositoryTableName("halls"));
moviesRepository_ = repositoryManager.getRepositoryAs<MoviesRepository>(
metadata.repositoryTableName("movies"));
if (!bool(genresRepository_ && hallsRepository_ && moviesRepository_)) {
qFatal()
<< "DirectoriesManagementWidget::initializeRepositories: Failed to "
"initialize repositories";
}
}
void DirectoriesManagementWidget::initializeModels() {
ModelManager &modelManager = ModelManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
genresModel_ =
modelManager.getModelAs<GenresModel>(metadata.modelTableName("genres"));
hallsModel_ =
modelManager.getModelAs<HallsModel>(metadata.modelTableName("halls"));
moviesModel_ =
modelManager.getModelAs<MoviesModel>(metadata.modelTableName("movies"));
if (!bool(genresModel_ && hallsModel_ && moviesModel_)) {
qFatal() << "DirectoriesManagementWidget::initializeModels: Failed to "
"initialize models";
}
}
void DirectoriesManagementWidget::setupGenreFinder() {
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
genreFinder->setDefaultRelation("genres");
genreFinder->setLabelText(EntityFinderWidget::tr("Select genre"));
genreFinder->setLineEditPlaceholder(
EntityFinderWidget::tr("Here will be information about selected genre"));
}
void DirectoriesManagementWidget::setupTableViews() {
tableHalls->setModel(hallsModel_);
tableMovies->setModel(moviesModel_);
tableGenres->setModel(genresModel_);
}
void DirectoriesManagementWidget::setupConnections() {
connect(btnAddHall, &QPushButton::clicked, this,
&DirectoriesManagementWidget::onPushButtonAddHallClicked);
connect(btnAddMovie, &QPushButton::clicked, this,
&DirectoriesManagementWidget::onPushButtonAddMovieClicked);
connect(btnAddGenre, &QPushButton::clicked, this,
&DirectoriesManagementWidget::onPushButtonAddGenreClicked);
connect(genreFinder, &EntityFinderWidget::fieldRetrieved, this,
&DirectoriesManagementWidget::onGenreFieldRetrieved);
}
void DirectoriesManagementWidget::onPushButtonAddMovieClicked() {
if (selectedGenreId_ == -1) {
QMessageBox::warning(this, QMessageBox::tr("Error"),
QMessageBox::tr("No genre selected"));
}
QString title = lineEditMovieName->text();
int duration = spinBoxMovieDuration->value();
CreateMovieRequest request;
request.setTitle(title);
request.setDuration(duration);
request.setGenreId(selectedGenreId_);
RepositoryResult result = moviesRepository_->createMovie(request);
if (!result.success) {
qWarning() << "Failed to create movie:" << result.errorMessage;
ErrorDialog::displayError(this, result.errorMessage);
}
}
void DirectoriesManagementWidget::onPushButtonAddHallClicked() {
QString name = lineEditHallName->text();
int capacity = spinBoxNumberOfSeats->value();
CreateHallRequest request;
request.setHallName(name);
request.setCapacity(capacity);
RepositoryResult result = hallsRepository_->createHall(request);
if (!result.success) {
qWarning() << "Failed to create hall:" << result.errorMessage;
ErrorDialog::displayError(this, result.errorMessage);
}
}
void DirectoriesManagementWidget::onGenreFieldRetrieved(
const QSqlField &field) {
selectedGenreId_ = field.value().toInt();
}
void DirectoriesManagementWidget::onPushButtonAddGenreClicked() {
QString name = lineEditGenreName->text();
CreateGenreRequest request;
request.setGenreName(name);
RepositoryResult result = genresRepository_->createGenre(request);
if (!result.success) {
qWarning() << "Failed to create genre:" << result.errorMessage;
ErrorDialog::displayError(this, result.errorMessage);
}
}
@@ -0,0 +1,47 @@
#ifndef DIRECTORIES_MANAGEMENT_WIDGET_H
#define DIRECTORIES_MANAGEMENT_WIDGET_H
#include "ui_directories_management_widget.h"
#include <QWidget>
// Forward declaration
class HallsModel;
class MoviesModel;
class GenresModel;
class HallsRepositoryInterface;
class MoviesRepositoryInterface;
class GenresRepositoryInterface;
class DirectoriesManagementWidget : public QWidget,
private Ui::DirectoriesManagementWidget {
Q_OBJECT
private:
GenresModel *genresModel_;
GenresRepositoryInterface *genresRepository_;
HallsModel *hallsModel_;
HallsRepositoryInterface *hallsRepository_;
MoviesModel *moviesModel_;
MoviesRepositoryInterface *moviesRepository_;
qint32 selectedGenreId_;
qint32 selectedHallId_;
qint32 selectedMovieId_;
public:
explicit DirectoriesManagementWidget(QWidget *parent = nullptr);
private:
void initializeModels();
void initializeRepositories();
void setupConnections();
void setupGenreFinder();
void setupTableViews();
private slots:
void onGenreFieldRetrieved(const QSqlField &field);
void onPushButtonAddGenreClicked();
void onPushButtonAddHallClicked();
void onPushButtonAddMovieClicked();
};
#endif // DIRECTORIES_MANAGEMENT_WIDGET_H
+169
View File
@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DirectoriesManagementWidget</class>
<widget class="QWidget" name="DirectoriesManagementWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>484</width>
<height>410</height>
</rect>
</property>
<property name="windowTitle">
<string>Directory management</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabGenres">
<attribute name="title">
<string>Genres</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayoutGenres">
<item>
<widget class="QTableView" name="tableGenres">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<widget class="QLabel" name="labelGenreName">
<property name="text">
<string>Enter the name of the genre:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditGenreName"/>
</item>
<item>
<widget class="QPushButton" name="btnAddGenre">
<property name="text">
<string>Add genre</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabHalls">
<attribute name="title">
<string>Halls</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableView" name="tableHalls">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<widget class="QLabel" name="labelHallName">
<property name="text">
<string>Enter hall name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditHallName"/>
</item>
<item>
<widget class="QLabel" name="labelNumberOfSeats">
<property name="text">
<string>Enter the number of seats in the hall:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxNumberOfSeats">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnAddHall">
<property name="text">
<string>Add hall</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabMovies">
<attribute name="title">
<string>Movies</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTableView" name="tableMovies">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<widget class="EntityFinderWidget" name="genreFinder" native="true"/>
</item>
<item>
<widget class="QLabel" name="labelMovieName">
<property name="text">
<string>Enter the name of the movie:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditMovieName"/>
</item>
<item>
<widget class="QLabel" name="labelMovieDuration">
<property name="text">
<string>Enter the duration of the movie:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxMovieDuration">
<property name="suffix">
<string> minute(s)</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1440</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnAddMovie">
<property name="text">
<string>Add movie</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+179
View File
@@ -0,0 +1,179 @@
#include "entity_deleter_widget.h"
#include "error_dialog.h"
#include "genres_repository_interface.h"
#include "halls_repository_interface.h"
#include "movies_repository_interface.h"
#include "refunds_repository_interface.h"
#include "repository_result.h"
#include "sessions_repository_interface.h"
#include "tickets_repository_interface.h"
#include <QMessageBox>
#include <QSqlTableModel>
EntityDeleterWidget::EntityDeleterWidget(QWidget *parent) : QWidget(parent) {
setupUi(this);
setupComboBoxCurrentTable();
setupEntityFinder();
setupConnections();
}
void EntityDeleterWidget::setupComboBoxCurrentTable() {
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
QStringList databaseTableNames = {"genres", "halls", "movies",
"refunds", "sessions", "tickets"};
for (const QString currentDTN : databaseTableNames) {
comboBoxCurrentTable->addItem(metadata.translatedTableName(currentDTN),
currentDTN);
}
}
void EntityDeleterWidget::setupConnections() {
connect(comboBoxCurrentTable, &QComboBox::currentIndexChanged, this,
&EntityDeleterWidget::onComboBoxCurrentTableCurrentIndexChanged);
connect(entityFinder, &EntityFinderWidget::fieldRetrieved, this,
&EntityDeleterWidget::onFieldRetrieved);
connect(entityFinder, &EntityFinderWidget::recordRetrieved, this,
&EntityDeleterWidget::onRecordRetrieved);
connect(pushButtonDeleteEntity, &QPushButton::clicked, this,
&EntityDeleterWidget::onPushButtonDeleteEntityClicked);
}
void EntityDeleterWidget::setupEntityFinder() {
entityFinder->setLabelText(tr("Select record of current table to delete"));
refreshEntityFinder();
}
void EntityDeleterWidget::refreshEntityFinder() {
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
entityFinder->setDefaultRelation(databaseTableName);
}
void EntityDeleterWidget::onComboBoxCurrentTableCurrentIndexChanged(int index) {
refreshEntityFinder();
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
qInfo() << "Current table was changed:" << "CurrentComboBoxIndex:" << index
<< "databaseTableName:" << databaseTableName;
}
void EntityDeleterWidget::onFieldRetrieved(const QSqlField &field) {
retrievedField_ = field;
}
void EntityDeleterWidget::onRecordRetrieved(const QSqlRecord &record) {
// NOTE: It is possible to reduce the behavior of each of the tables if the
// removal is in different ways with other data
selectedRecord_ = record;
}
template <>
RepositoryResult
EntityDeleterWidget::deleteEntityById<GenresRepositoryInterface>() {
return currentRepository<GenresRepositoryInterface>()->deleteGenreById(
retrievedField_.value().toInt());
}
template <>
RepositoryResult
EntityDeleterWidget::deleteEntityById<HallsRepositoryInterface>() {
return currentRepository<HallsRepositoryInterface>()->deleteHallById(
retrievedField_.value().toInt());
}
template <>
RepositoryResult
EntityDeleterWidget::deleteEntityById<MoviesRepositoryInterface>() {
return currentRepository<MoviesRepositoryInterface>()->deleteMovieById(
retrievedField_.value().toInt());
}
template <>
RepositoryResult
EntityDeleterWidget::deleteEntityById<RefundsRepositoryInterface>() {
return currentRepository<RefundsRepositoryInterface>()->deleteRefundById(
retrievedField_.value().toInt());
}
template <>
RepositoryResult
EntityDeleterWidget::deleteEntityById<SessionsRepositoryInterface>() {
return currentRepository<SessionsRepositoryInterface>()->deleteSessionById(
retrievedField_.value().toInt());
}
template <>
RepositoryResult
EntityDeleterWidget::deleteEntityById<TicketsRepositoryInterface>() {
return currentRepository<TicketsRepositoryInterface>()->deleteTicketById(
retrievedField_.value().toInt());
}
void EntityDeleterWidget::onPushButtonDeleteEntityClicked() {
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
RepositoryResult result;
if (databaseTableName == "genres") {
result = deleteEntityById<GenresRepositoryInterface>();
showMessageDialog(result, tr("Genre deleted successfully"));
} else if (databaseTableName == "halls") {
result = deleteEntityById<HallsRepositoryInterface>();
showMessageDialog(result, tr("Hall deleted successfully"));
} else if (databaseTableName == "movies") {
result = deleteEntityById<MoviesRepositoryInterface>();
showMessageDialog(result, tr("Movie deleted successfully"));
} else if (databaseTableName == "refunds") {
result = deleteEntityById<RefundsRepositoryInterface>();
showMessageDialog(result, tr("Refund deleted successfully"));
} else if (databaseTableName == "sessions") {
result = deleteEntityById<SessionsRepositoryInterface>();
showMessageDialog(result, tr("Session deleted successfully"));
} else if (databaseTableName == "tickets") {
result = deleteEntityById<TicketsRepositoryInterface>();
showMessageDialog(result, tr("Ticket deleted successfully"));
} else {
qCritical() << "EntityDeleterWidget::onPressPushButtonDeleteEntity: "
"Unknown database table name:"
<< databaseTableName;
}
// NOTE: Application connector will reset entity finder
// if (result.success) {
// entityFinder->resetToDefault();
// }
}
void EntityDeleterWidget::showMessageDialog(const RepositoryResult &result,
const QString &successMessage) {
if (!result.success) {
ErrorDialog::displayError(this, result.errorMessage);
} else {
QMessageBox::information(this, QMessageBox::tr("Success"), successMessage);
}
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef ENTITY_DELETER_WIDGET_H
#define ENTITY_DELETER_WIDGET_H
#include "repository_manager.h"
#include "schema_metadata.h"
#include "ui_entity_deleter_widget.h"
// Forward declaration
class RepositoryResult;
class EntityDeleterWidget : public QWidget, private Ui::EntityDeleterWidget {
Q_OBJECT
private:
QSqlRecord selectedRecord_;
QSqlField retrievedField_;
public:
explicit EntityDeleterWidget(QWidget *parent = nullptr);
private:
void setupComboBoxCurrentTable();
void setupConnections();
void setupEntityFinder();
private slots:
void onComboBoxCurrentTableCurrentIndexChanged(int index);
void onFieldRetrieved(const QSqlField &field);
void onPushButtonDeleteEntityClicked();
void onRecordRetrieved(const QSqlRecord &rowValues);
private:
void refreshEntityFinder();
void showMessageDialog(const RepositoryResult &result,
const QString &successMessage);
template <typename Repository> Repository *currentRepository();
template <typename Repository> RepositoryResult deleteEntityById();
};
template <typename Repository>
Repository *EntityDeleterWidget::currentRepository() {
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
return RepositoryManager::instance().getRepositoryAs<Repository>(
metadata.repositoryTableName(databaseTableName));
}
#endif // ENTITY_DELETER_WIDGET_H
+93
View File
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EntityDeleterWidget</class>
<widget class="QWidget" name="EntityDeleterWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>208</width>
<height>143</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<property name="styleSheet">
<string notr="true">QGroupBox#groupBox {
border: 2px solid #C00000;
border-radius: 10px;
padding: 5px;
padding-top: 10px;
}
QGroupBox#groupBox::title {
background: transparent;
subcontrol-origin: margin;
subcontrol-position: top center;
padding: 5px;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="title">
<string>Database recording deletion</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelCurrentTableName">
<property name="text">
<string>Current table:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxCurrentTable"/>
</item>
<item>
<widget class="EntityFinderWidget" name="entityFinder" native="true"/>
</item>
<item>
<widget class="QPushButton" name="pushButtonDeleteEntity">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+113
View File
@@ -0,0 +1,113 @@
#include "entity_finder_widget.h"
#include "base_sql_table_model.h"
#include "model_manager.h"
#include "schema_metadata.h"
#include "search_dialog.h"
EntityFinderWidget::EntityFinderWidget(QWidget *parent) : QWidget(parent) {
setupUi(this);
setupConnections();
setupFrameStyle();
}
EntityFinderWidget::EntityFinderWidget(const QString &tableName,
const QString &fieldName,
QWidget *parent)
: EntityFinderWidget(parent) {
setModelTableName(tableName);
setFieldName(fieldName);
}
void EntityFinderWidget::setHiddenFrame(bool hidden) {
QLayout *frameLayout = frame->layout();
bool isHidden = frame->frameStyle() == QFrame::NoFrame;
if (isHidden == hidden) {
return;
}
if (hidden) {
frame->setFrameStyle(QFrame::NoFrame);
frameLayout->setContentsMargins(0, 0, 0, 0);
} else {
frame->setFrameStyle(originalFrameStyle_);
frameLayout->setContentsMargins(9, 9, 9, 9);
}
parentWidget()->adjustSize();
}
void EntityFinderWidget::refreshLineEditText(const QSqlRecord &record) {
ModelManager &modelManager = ModelManager::instance();
BaseSqlTableModel *model =
modelManager.getModelAs<BaseSqlTableModel>(modelTableName_);
QStringList columnDataList;
for (int i = 0; i < record.count(); ++i) {
QString columnName = model->headerData(i, Qt::Horizontal).toString();
QString columnValue = record.value(i).toString();
columnDataList.append(QString("%1: %2").arg(columnName, columnValue));
}
lineEditEntityInformation->setText(columnDataList.join(" | "));
}
void EntityFinderWidget::setDefaultRelation(
const QString &databaseRelationName) {
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
setModelTableName(metadata.modelTableName(databaseRelationName));
setFieldName(metadata.primaryIdColumnName(databaseRelationName));
qInfo() << "Default relation name set to:" << databaseRelationName;
}
void EntityFinderWidget::setupConnections() {
connect(pushButtonFindEntity, &QPushButton::clicked, this,
&EntityFinderWidget::onPushButtonFindEntityClicked);
}
void EntityFinderWidget::setupFrameStyle() {
originalFrameStyle_ = frame->frameStyle();
}
void EntityFinderWidget::resetToDefault() {
qInfo() << "Resetting entity finder to default:"
<< "Name of EntityFinderWidget:" << objectName();
lineEditEntityInformation->setText("");
emit reseted();
}
void EntityFinderWidget::onPushButtonFindEntityClicked() {
SearchDialog searchDialog(modelTableName_, fieldName_, this);
// All its signals are automatically disconnected from the slots
connect(&searchDialog, &SearchDialog::rowSelected, this,
&EntityFinderWidget::onRowSelected);
connect(&searchDialog, &SearchDialog::fieldRetrieved, this,
&EntityFinderWidget::onFieldRetrieved);
connect(&searchDialog, &SearchDialog::recordRetrieved, this,
&EntityFinderWidget::onRecordRetrieved);
searchDialog.exec();
}
void EntityFinderWidget::onRowSelected(int rowIndex) {
emit rowSelected(rowIndex);
}
void EntityFinderWidget::onRecordRetrieved(const QSqlRecord &rowValues) {
refreshLineEditText(rowValues);
emit recordRetrieved(rowValues);
}
void EntityFinderWidget::onFieldRetrieved(const QSqlField &field) {
emit fieldRetrieved(field);
}
+85
View File
@@ -0,0 +1,85 @@
#ifndef ENTITY_FINDER_WIDGET_H
#define ENTITY_FINDER_WIDGET_H
#include "ui_entity_finder_widget.h"
#include <QSqlField>
#include <QSqlRecord>
// Forward declaration
class SearchDialog;
class EntityFinderWidget : public QWidget, private Ui::EntityFinderWidget {
Q_OBJECT
private:
QString fieldName_;
QString modelTableName_;
int originalFrameStyle_;
public:
EntityFinderWidget(QWidget *parent = nullptr);
EntityFinderWidget(const QString &modelTableName, const QString &fieldName,
QWidget *parent = nullptr);
private:
void setupConnections();
void setupFrameStyle();
public:
void setHiddenFrame(bool hidden);
void refreshLineEditText(const QSqlRecord &fieldValue);
void setDefaultRelation(const QString &databaseRelationName);
inline void resetLineEditText();
inline void setLabelText(const QString &text);
inline void setLineEditPlaceholder(const QString &placeholder);
inline QString fieldName() const;
inline QString modelTableName() const;
signals:
void rowSelected(int rowIndex);
void recordRetrieved(QSqlRecord rowValues);
void fieldRetrieved(QSqlField fieldValue);
void reseted();
public slots:
void resetToDefault();
private slots:
void onPushButtonFindEntityClicked();
void onRowSelected(int rowIndex);
void onRecordRetrieved(const QSqlRecord &rowValues);
void onFieldRetrieved(const QSqlField &field);
private:
inline void setFieldName(const QString &fieldName);
inline void setModelTableName(const QString &modelTableName);
};
void EntityFinderWidget::resetLineEditText() {
lineEditEntityInformation->clear();
}
void EntityFinderWidget::setModelTableName(const QString &modelTableName) {
modelTableName_ = modelTableName;
}
void EntityFinderWidget::setFieldName(const QString &fieldName) {
fieldName_ = fieldName;
}
void EntityFinderWidget::setLabelText(const QString &text) {
labelEntityInformation->setText(text + ":");
}
void EntityFinderWidget::setLineEditPlaceholder(const QString &placeholder) {
lineEditEntityInformation->setPlaceholderText(placeholder);
}
QString EntityFinderWidget::fieldName() const { return fieldName_; }
QString EntityFinderWidget::modelTableName() const { return modelTableName_; }
#endif // ENTITY_FINDER_WIDGET_H
+59
View File
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EntityFinderWidget</class>
<widget class="QWidget" name="EntityFinderWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>160</width>
<height>102</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="labelEntityInformation"/>
</item>
<item>
<widget class="QLineEdit" name="lineEditEntityInformation">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::NoFocus</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonFindEntity">
<property name="text">
<string>Find...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+491
View File
@@ -0,0 +1,491 @@
#include "entity_updater_widget.h"
#include "genres_repository_interface.h"
#include "halls_repository_interface.h"
#include "movies_repository_interface.h"
#include "refunds_repository_interface.h"
#include "sessions_repository_interface.h"
#include "tickets_repository_interface.h"
#include "update_genre_request.h"
#include "update_hall_request.h"
#include "update_movie_request.h"
#include "update_refund_request.h"
#include "update_session_request.h"
#include "update_ticket_request.h"
#include "error_dialog.h"
#include "model_manager.h"
#include "base_sql_table_model.h"
#include <QMessageBox>
#include <qsqlrelationaltablemodel.h>
namespace DatabaseTableNames {
static constexpr char Genres[] = "genres";
static constexpr char Halls[] = "halls";
static constexpr char Movies[] = "movies";
static constexpr char Refunds[] = "refunds";
static constexpr char Sessions[] = "sessions";
static constexpr char Tickets[] = "tickets";
} // namespace DatabaseTableNames
EntityUpdaterWidget::EntityUpdaterWidget(QWidget *parent)
: QWidget(parent), primaryKey_(-1) {
setupUi(this);
setupConnections();
setupComboBoxCurrentTable(); // After setup connections
setupParentEntityFinderWidget();
setupChildEntityFinderWidget();
}
void EntityUpdaterWidget::setupComboBoxCurrentTable() {
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
QList<QString> databaseTableNames = {
DatabaseTableNames::Genres, DatabaseTableNames::Halls,
DatabaseTableNames::Movies, DatabaseTableNames::Refunds,
DatabaseTableNames::Sessions, DatabaseTableNames::Tickets};
for (const QString &tableName : databaseTableNames) {
comboBoxCurrentTable->addItem(metadata.translatedTableName(tableName),
tableName);
}
}
void EntityUpdaterWidget::setupConnections() {
connect(foreignKeyFinder, &EntityFinderWidget::fieldRetrieved, this,
&EntityUpdaterWidget::onForeignFieldRetrieved);
connect(primaryKeyFinder, &EntityFinderWidget::fieldRetrieved, this,
&EntityUpdaterWidget::onPrimaryFieldRetrieved);
connect(primaryKeyFinder, &EntityFinderWidget::recordRetrieved, this,
&EntityUpdaterWidget::onPrimaryEntityRetrieved);
connect(comboBoxCurrentTable, &QComboBox::currentIndexChanged, this,
&EntityUpdaterWidget::onTableSelected);
connect(comboBoxCurrentColumn, &QComboBox::currentIndexChanged, this,
&EntityUpdaterWidget::onColumnSelected);
connect(pushButtonUpdate, &QPushButton::clicked, this,
&EntityUpdaterWidget::onPushButtonUpdateClicked);
}
void EntityUpdaterWidget::setupChildEntityFinderWidget() {
foreignKeyFinder->setLabelText(
EntityFinderWidget::tr("Select entity for replacement"));
foreignKeyFinder->setLineEditPlaceholder(
EntityFinderWidget::tr("Here will be information about selected entity"));
foreignKeyFinder->setHiddenFrame(true);
}
void EntityUpdaterWidget::setupParentEntityFinderWidget() {
primaryKeyFinder->setLabelText(
EntityFinderWidget::tr("Select entity for updating"));
primaryKeyFinder->setLineEditPlaceholder(
EntityFinderWidget::tr("Here will be information about selected entry"));
}
void EntityUpdaterWidget::configureForeignKeyFinder(
const QString &columnName, const QString &databaseTableName) {
TableInfoSQLite tableInfo(databaseTableName);
if (tableInfo.isPrimaryKey(columnName)) {
// NOTE: Change Primary Key is prohibited
// configurePrimaryKeyFinder(databaseTableName);
return;
}
QString foreignKeyDatabaseTableName = tableInfo.foreignKeyTable(columnName);
if (foreignKeyDatabaseTableName.isEmpty()) {
qCritical()
<< "Could not find the database table name for the key column, which "
"contains \"_id\":"
<< columnName;
return;
}
configureForeignKeyFinder(foreignKeyDatabaseTableName);
}
void EntityUpdaterWidget::configureForeignKeyFinder(
const QString &databaseTableName) {
foreignKeyFinder->resetToDefault();
foreignKeyFinder->setDefaultRelation(databaseTableName);
}
void EntityUpdaterWidget::configurePrimaryKeyFinder(
const QString &databaseTableName) {
primaryKeyFinder->resetToDefault();
primaryKeyFinder->setDefaultRelation(databaseTableName);
}
void EntityUpdaterWidget::onPrimaryFieldRetrieved(const QSqlField &field) {
qInfo() << "Selected primary field: Name:" << field.name()
<< "Value:" << field.value();
primaryKey_ = field.value().toInt();
}
void EntityUpdaterWidget::onForeignFieldRetrieved(const QSqlField &field) {
qInfo() << "Selected foreign field: Name:" << field.name()
<< "Value:" << field.value();
selectedForeignField_ = field;
}
void EntityUpdaterWidget::onPrimaryEntityRetrieved(const QSqlRecord &entity) {
qInfo() << "Retrieved foreign entity:" << entity;
selectedPrimaryEntity_ = entity;
}
template <>
QString EntityUpdaterWidget::actualColumnValue(const QString &columnName) {
QString value = isColumnsEqual(columnName)
? lineEdit->text()
: selectedPrimaryEntity_.value(columnName).toString();
qInfo() << "Actual column QString value:" << value;
return value;
}
template <>
QDateTime EntityUpdaterWidget::actualColumnValue(const QString &columnName) {
QDateTime value = isColumnsEqual(columnName)
? dateTimeEdit->dateTime()
: selectedPrimaryEntity_.value(columnName).toDateTime();
qInfo() << "Actual column QDateTime value:" << value;
return value;
}
template <>
int EntityUpdaterWidget::actualColumnValue(const QString &columnName) {
int value = isColumnsEqual(columnName)
? spinBox->value()
: selectedPrimaryEntity_.value(columnName).toInt();
qInfo() << "Actual column int value:" << value;
return value;
}
template <>
double EntityUpdaterWidget::actualColumnValue(const QString &columnName) {
double value = isColumnsEqual(columnName)
? doubleSpinBox->value()
: selectedPrimaryEntity_.value(columnName).toDouble();
qInfo() << "Actual column double value:" << value;
return value;
}
qint32 EntityUpdaterWidget::actualForeignKey(const QString &columnName) {
qint32 value = isColumnsEqual(columnName)
? selectedForeignField_.value().toInt()
: selectedPrimaryEntity_.value(columnName).toInt();
qInfo() << "Actual foreign key:" << value;
return value;
}
template <>
RepositoryResult
EntityUpdaterWidget::updateEntity<GenresRepositoryInterface>() {
UpdateGenreRequest request;
request.setGenreId(primaryKey_);
request.setGenreName(actualColumnValue<QString>("genre_name"));
return currentRepository<GenresRepositoryInterface>()->updateGenre(request);
}
template <>
RepositoryResult EntityUpdaterWidget::updateEntity<HallsRepositoryInterface>() {
UpdateHallRequest request;
request.setHallId(primaryKey_);
request.setCapacity(actualColumnValue<int>("capacity"));
request.setHallName(actualColumnValue<QString>("hall_name"));
return currentRepository<HallsRepositoryInterface>()->updateHall(request);
}
template <>
RepositoryResult
EntityUpdaterWidget::updateEntity<MoviesRepositoryInterface>() {
UpdateMovieRequest request;
request.setMovieId(primaryKey_);
request.setDuration(actualColumnValue<int>("duration"));
request.setGenreId(actualForeignKey("genre_id"));
request.setTitle(actualColumnValue<QString>("title"));
return currentRepository<MoviesRepositoryInterface>()->updateMovie(request);
}
template <>
RepositoryResult
EntityUpdaterWidget::updateEntity<RefundsRepositoryInterface>() {
UpdateRefundRequest request;
request.setRefundId(primaryKey_);
request.setRefundAmount(actualColumnValue<double>("refund_amount"));
request.setRefundAt(actualColumnValue<QDateTime>("refund_at"));
request.setTicketId(actualForeignKey("ticket_id"));
return currentRepository<RefundsRepositoryInterface>()->updateRefund(request);
}
template <>
RepositoryResult
EntityUpdaterWidget::updateEntity<SessionsRepositoryInterface>() {
UpdateSessionRequest request;
request.setSessionId(primaryKey_);
request.setHallId(actualForeignKey("hall_id"));
request.setMovieId(actualForeignKey("movie_id"));
request.setBeginAt(actualColumnValue<QDateTime>("begin_at"));
request.setTicketPrice(actualColumnValue<double>("ticket_price"));
return currentRepository<SessionsRepositoryInterface>()->updateSession(
request);
}
template <>
RepositoryResult
EntityUpdaterWidget::updateEntity<TicketsRepositoryInterface>() {
UpdateTicketRequest request;
request.setTicketId(primaryKey_);
request.setSeatNumber(actualColumnValue<int>("seat_number"));
request.setSessionId(actualForeignKey("session_id"));
request.setSoldAt(actualColumnValue<QDateTime>("sold_at"));
return currentRepository<TicketsRepositoryInterface>()->updateTicket(request);
}
template <>
void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Genres>(
RepositoryResult &resultOut) {
resultOut = updateEntity<GenresRepositoryInterface>();
if (!resultOut.success) {
ErrorDialog::displayError(this, resultOut.errorMessage);
return;
}
showMessageDialog(resultOut,
EntityUpdaterWidget::tr("Genre updated successfully"));
}
template <>
void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Halls>(
RepositoryResult &resultOut) {
resultOut = updateEntity<HallsRepositoryInterface>();
if (!resultOut.success) {
ErrorDialog::displayError(this, resultOut.errorMessage);
return;
}
showMessageDialog(resultOut,
EntityUpdaterWidget::tr("Hall updated successfully"));
}
template <>
void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Movies>(
RepositoryResult &resultOut) {
resultOut = updateEntity<MoviesRepositoryInterface>();
if (!resultOut.success) {
ErrorDialog::displayError(this, resultOut.errorMessage);
return;
}
showMessageDialog(resultOut,
EntityUpdaterWidget::tr("Movie updated successfully"));
}
template <>
void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Refunds>(
RepositoryResult &resultOut) {
resultOut = updateEntity<RefundsRepositoryInterface>();
if (!resultOut.success) {
ErrorDialog::displayError(this, resultOut.errorMessage);
return;
}
showMessageDialog(resultOut,
EntityUpdaterWidget::tr("Refund updated successfully"));
}
template <>
void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Sessions>(
RepositoryResult &resultOut) {
resultOut = updateEntity<SessionsRepositoryInterface>();
if (!resultOut.success) {
ErrorDialog::displayError(this, resultOut.errorMessage);
return;
}
showMessageDialog(resultOut,
EntityUpdaterWidget::tr("Session updated successfully"));
}
template <>
void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Tickets>(
RepositoryResult &resultOut) {
resultOut = updateEntity<TicketsRepositoryInterface>();
if (!resultOut.success) {
ErrorDialog::displayError(this, resultOut.errorMessage);
return;
}
showMessageDialog(resultOut,
EntityUpdaterWidget::tr("Ticket updated successfully"));
}
void EntityUpdaterWidget::onPushButtonUpdateClicked() {
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
RepositoryResult result;
if (databaseTableName == DatabaseTableNames::Genres) {
processEntityUpdate<DatabaseTableNames::Genres>(result);
} else if (databaseTableName == DatabaseTableNames::Halls) {
processEntityUpdate<DatabaseTableNames::Halls>(result);
} else if (databaseTableName == DatabaseTableNames::Movies) {
processEntityUpdate<DatabaseTableNames::Movies>(result);
} else if (databaseTableName == DatabaseTableNames::Refunds) {
processEntityUpdate<DatabaseTableNames::Refunds>(result);
} else if (databaseTableName == DatabaseTableNames::Sessions) {
processEntityUpdate<DatabaseTableNames::Sessions>(result);
} else if (databaseTableName == DatabaseTableNames::Tickets) {
processEntityUpdate<DatabaseTableNames::Tickets>(result);
} else {
qFatal() << "EntityDeleterWidget::onPressPushButtonDeleteEntity: "
"Unknown database table name:"
<< databaseTableName;
}
processUpdateResult(result);
}
void EntityUpdaterWidget::onTableSelected(int tableIndex) {
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
const QString &databaseTableName =
comboBoxCurrentTable->itemData(tableIndex).toString();
ModelManager &modelManager = ModelManager::instance();
BaseSqlTableModel *model = modelManager.getModelAs<BaseSqlTableModel>(
metadata.modelTableName(databaseTableName));
comboBoxCurrentColumn->clear();
for (const auto &columnName : model->columnNames()) {
if (metadata.primaryIdColumnName(databaseTableName) == columnName) {
// NOTE: Change Primary Key is prohibited
continue;
}
int headerIndex = model->columnIndex(columnName);
QString displayName =
model->headerData(headerIndex, Qt::Horizontal).toString();
comboBoxCurrentColumn->addItem(displayName, columnName);
}
configurePrimaryKeyFinder(databaseTableName);
}
void EntityUpdaterWidget::onColumnSelected(int columnIndex) {
Q_UNUSED(columnIndex);
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
TableInfoSQLite tableInfoSQLite(databaseTableName);
QString columnName = comboBoxCurrentColumn->currentData().toString();
bool isKey = columnName.contains("_id", Qt::CaseInsensitive);
QWidget *page;
switch (tableInfoSQLite.columnMetaType(columnName)) {
case QMetaType::Int: {
page =
findChild<QWidget *>(isKey ? "pageChildFinderWidget" : "pageSpinBox");
} break;
case QMetaType::Double:
page = findChild<QWidget *>("pageDoubleSpinBox");
break;
case QMetaType::QString:
page = findChild<QWidget *>("pageLineEdit");
break;
case QMetaType::QDateTime:
page = findChild<QWidget *>("pageDateTimeEdit");
break;
default:
qCritical() << "Unknown column selected";
return;
}
stackedWidget->setCurrentWidget(page);
if (isKey) {
configureForeignKeyFinder(columnName, databaseTableName);
}
}
QModelIndex EntityUpdaterWidget::findIndexByColumn(QAbstractItemModel *model,
int column,
const QVariant &value) {
if (!model || column < 0 || column >= model->columnCount())
return QModelIndex();
for (int row = 0; row < model->rowCount(); ++row) {
QModelIndex index = model->index(row, column);
if (model->data(index) == value) {
return index;
}
}
return QModelIndex();
}
bool EntityUpdaterWidget::isColumnsEqual(const QString &other) {
TableInfoSQLite tableInfo(comboBoxCurrentTable->currentData().toString());
QString current = comboBoxCurrentColumn->currentData().toString();
bool isEqual = tableInfo.columnIndex(current) == tableInfo.columnIndex(other);
const static QString billet("Column \"%1\" and column \"%2\": %3");
qInfo().noquote() << billet.arg(current).arg(other).arg(
isEqual ? "Equal" : "Not equal");
return isEqual;
};
void EntityUpdaterWidget::processUpdateResult(const RepositoryResult &result) {
if (!result.success) {
return;
}
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
ModelManager &modelManager = ModelManager::instance();
QSqlTableModel *model =
modelManager.getModel(metadata.modelTableName(databaseTableName));
QString primaryKeyColumnName =
metadata.primaryIdColumnName(databaseTableName);
int columnIndex = selectedPrimaryEntity_.indexOf(primaryKeyColumnName);
QModelIndex rowIndex = findIndexByColumn(model, columnIndex, primaryKey_);
selectedPrimaryEntity_ = model->record(rowIndex.row());
primaryKeyFinder->refreshLineEditText(selectedPrimaryEntity_);
foreignKeyFinder->resetToDefault();
}
void EntityUpdaterWidget::showMessageDialog(const RepositoryResult &result,
const QString &successMessage) {
if (!result.success) {
ErrorDialog::displayError(this, result.errorMessage);
} else {
QMessageBox::information(this, QMessageBox::tr("Success"), successMessage);
}
}
+76
View File
@@ -0,0 +1,76 @@
#ifndef ENTITY_UPDATER_WIDGET_H
#define ENTITY_UPDATER_WIDGET_H
#include "repository_manager.h"
#include "repository_result.h"
#include "schema_metadata.h"
#include "ui_entity_updater_widget.h"
#include <QSqlField>
// Forward declaration
class RepositoryResult;
class TableInfo;
class EntityUpdaterWidget : public QWidget, public Ui::EntityUpdaterWidget {
Q_OBJECT
private:
QSqlField selectedForeignField_;
QSqlRecord selectedPrimaryEntity_;
qint32 primaryKey_;
public:
EntityUpdaterWidget(QWidget *parent = nullptr);
void setupChildEntityFinderWidget();
void setupComboBoxCurrentTable();
void setupConnections();
void setupLabels();
void setupParentEntityFinderWidget();
void configureForeignKeyFinder(const QString &columnName,
const QString &databaseTableName);
void configureForeignKeyFinder(const QString &databaseTableName);
void configurePrimaryKeyFinder(const QString &databaseTableName);
private slots:
void onColumnSelected(int columnIndex);
void onForeignFieldRetrieved(const QSqlField &field);
void onPrimaryEntityRetrieved(const QSqlRecord &entity);
void onPrimaryFieldRetrieved(const QSqlField &field);
void onPushButtonUpdateClicked();
void onTableSelected(int tableIndex);
private:
QModelIndex findIndexByColumn(QAbstractItemModel *model, int column,
const QVariant &value);
bool isColumnsEqual(const QString &column);
qint32 actualForeignKey(const QString &columnName);
void processUpdateResult(const RepositoryResult &result);
void showMessageDialog(const RepositoryResult &result,
const QString &successMessage);
private:
template <const char *DatabaseTableName>
void processEntityUpdate(RepositoryResult &resultOut);
template <typename Repository> Repository *currentRepository();
template <typename Repository> RepositoryResult updateEntity();
template <typename ValueType>
ValueType actualColumnValue(const QString &columnName);
};
template <typename Repository>
Repository *EntityUpdaterWidget::currentRepository() {
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
const QString &databaseTableName =
comboBoxCurrentTable->currentData().toString();
return RepositoryManager::instance().getRepositoryAs<Repository>(
metadata.repositoryTableName(databaseTableName));
}
#endif // ENTITY_UPDATER_WIDGET_H
+228
View File
@@ -0,0 +1,228 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EntityUpdaterWidget</class>
<widget class="QWidget" name="EntityUpdaterWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>275</width>
<height>322</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true">QGroupBox#groupBox {
border: 2px solid #FF6400;
border-radius: 10px;
padding: 5px;
padding-top: 10px;
}
QGroupBox#groupBox::title {
background: transparent;
subcontrol-origin: margin;
subcontrol-position: top center;
padding: 5px;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Database records update</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="labelCurrentTable">
<property name="text">
<string>Current table:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxCurrentTable"/>
</item>
<item>
<widget class="EntityFinderWidget" name="primaryKeyFinder" native="true"/>
</item>
<item>
<widget class="QLabel" name="labelCurrentColumn">
<property name="text">
<string>Current column:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxCurrentColumn"/>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="currentIndex">
<number>4</number>
</property>
<widget class="QWidget" name="pageSpinBox">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="labelSpinBox">
<property name="text">
<string>Enter integer value:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox"/>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageDoubleSpinBox">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="labelDoubleSpinBox">
<property name="text">
<string>Enter a value with a floating point:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBox"/>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageLineEdit">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="labelLineEdit">
<property name="text">
<string>Enter text data:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageDateTimeEdit">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="labelDateTimeEdit">
<property name="text">
<string>Enter the date and time:</string>
</property>
</widget>
</item>
<item>
<widget class="QDateTimeEdit" name="dateTimeEdit"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="pageChildFinderWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="EntityFinderWidget" name="foreignKeyFinder" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonUpdate">
<property name="text">
<string>Update</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+19
View File
@@ -0,0 +1,19 @@
#include "reports_widget.h"
#include <QMessageBox>
#include <QtCharts/QBarCategoryAxis>
#include <QtCharts/QDateTimeAxis>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
ReportsWidget::ReportsWidget(QWidget *parent) : QWidget(parent) {
setupUi(this);
setupTicketSalesReportWidgets();
}
void ReportsWidget::setupTicketSalesReportWidgets() {
genreReportWidget->configureReportForGenre();
hallReportWidget->configureReportForHall();
movieReportWidget->configureReportForMovie();
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef REPORTS_WIDGET_H
#define REPORTS_WIDGET_H
#include "ui_reports_widget.h"
#include <QWidget>
#include <QtCharts/QChartView>
// Forward declaration
class GenresModel;
class GenresRepositoryInterface;
class HallsModel;
class HallsRepositoryInterface;
class MoviesModel;
class MoviesRepositoryInterface;
class SessionInfoDTO;
class SessionsInfoRepositoryInterface;
class ReportsWidget : public QWidget, Ui::ReportsWidget {
Q_OBJECT
public:
explicit ReportsWidget(QWidget *parent = nullptr);
private:
void setupTicketSalesReportWidgets();
};
#endif // REPORTS_WIDGET_H
+66
View File
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ReportsWidget</class>
<widget class="QWidget" name="ReportsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>222</width>
<height>80</height>
</rect>
</property>
<property name="windowTitle">
<string>Reports</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidgetReports">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabGenresPopularity">
<attribute name="title">
<string>Popularity of genres</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayoutGenres">
<item>
<widget class="TicketSalesReportWidget" name="genreReportWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabAttendance">
<attribute name="title">
<string>Attendance</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayoutAttendance">
<item>
<widget class="TicketSalesReportWidget" name="hallReportWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabMoviesPopularity">
<attribute name="title">
<string>The popularity of movies</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayoutMovies">
<item>
<widget class="TicketSalesReportWidget" name="movieReportWidget" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TicketSalesReportWidget</class>
<extends>QWidget</extends>
<header>ticket_sales_report_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+103
View File
@@ -0,0 +1,103 @@
#include "session_management_widget.h"
#include "create_session_request.h"
#include "error_dialog.h"
#include "model_manager.h"
#include "repository_manager.h"
#include "repository_result.h"
#include "schema_metadata.h"
#include "sessions_model.h"
#include "sessions_repository.h"
#include <qnamespace.h>
SessionManagementWidget::SessionManagementWidget(QWidget *parent)
: QWidget(parent) {
setupUi(this);
initializeRepositories(); // First of second
initializeModels(); // First of second
setupTableView(); // After initialization of repositories
setupHallFinder();
setupMovieFinder();
setupConnections();
}
void SessionManagementWidget::initializeRepositories() {
RepositoryManager &repositoryManager = RepositoryManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
sessionsRepository_ = repositoryManager.getRepositoryAs<SessionsRepository>(
metadata.repositoryTableName("sessions"));
if (!sessionsRepository_) {
qFatal() << "SessionManagementWidget::initializeRepositories: Failed to "
"initialize repositories";
}
}
void SessionManagementWidget::initializeModels() {
ModelManager &modelManager = ModelManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
sessionsModel_ = modelManager.getModelAs<SessionsModel>(
metadata.modelTableName("sessions"));
if (!bool(sessionsModel_)) {
qFatal() << "SessionManagementWidget::initializeModels: Failed to "
"initialize models";
}
}
void SessionManagementWidget::setupTableView() {
tableSessions->setModel(sessionsModel_);
}
void SessionManagementWidget::setupConnections() {
connect(btnAddSession, &QPushButton::clicked, this,
&SessionManagementWidget::onPushButtonAddSessionClicked);
connect(hallFinder, &EntityFinderWidget::fieldRetrieved, this,
&SessionManagementWidget::onHallFieldRetrieved);
connect(movieFinder, &EntityFinderWidget::fieldRetrieved, this,
&SessionManagementWidget::onMovieFieldRetrieved);
}
void SessionManagementWidget::setupHallFinder() {
hallFinder->setDefaultRelation("halls");
hallFinder->setLabelText(EntityFinderWidget::tr("Select hall"));
hallFinder->setLineEditPlaceholder(
EntityFinderWidget::tr("Here will be information about selected hall"));
}
void SessionManagementWidget::setupMovieFinder() {
movieFinder->setDefaultRelation("movies");
movieFinder->setLabelText(EntityFinderWidget::tr("Select movie"));
movieFinder->setLineEditPlaceholder(
EntityFinderWidget::tr("Here will be information about selected movie"));
}
void SessionManagementWidget::onPushButtonAddSessionClicked() {
QDateTime beginAt = dateTimeEditBeginAt->dateTime();
double ticketPrice = spinBoxTicketPrice->value();
CreateSessionRequest request;
request.setBeginAt(beginAt);
request.setHallId(selectedHallId_);
request.setMovieId(selectedMovieId_);
request.setTicketPrice(ticketPrice);
RepositoryResult result = sessionsRepository_->createSession(request);
if (!result.success) {
qWarning() << "Failed to create session:" << result.errorMessage;
ErrorDialog::displayError(this, result.errorMessage);
}
};
void SessionManagementWidget::onHallFieldRetrieved(const QSqlField &field) {
selectedHallId_ = field.value().toInt();
}
void SessionManagementWidget::onMovieFieldRetrieved(const QSqlField &field) {
selectedMovieId_ = field.value().toInt();
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef SESSION_MANAGEMENT_WIDGET_H
#define SESSION_MANAGEMENT_WIDGET_H
#include "ui_session_management_widget.h"
#include <QWidget>
// Forward declaration
class SessionsModel;
class SessionsRepositoryInterface;
class SessionManagementWidget : public QWidget,
private Ui::SessionManagementWidget {
Q_OBJECT
private:
SessionsModel *sessionsModel_;
SessionsRepositoryInterface *sessionsRepository_;
qint32 selectedHallId_;
qint32 selectedMovieId_;
public:
explicit SessionManagementWidget(QWidget *parent = nullptr);
private:
void initializeRepositories();
void initializeModels();
void setupConnections();
void setupHallFinder();
void setupMovieFinder();
void setupTableView();
private slots:
void onPushButtonAddSessionClicked();
void onHallFieldRetrieved(const QSqlField &field);
void onMovieFieldRetrieved(const QSqlField &field);
};
#endif // SESSION_MANAGEMENT_WIDGET_H
+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SessionManagementWidget</class>
<widget class="QWidget" name="SessionManagementWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>325</width>
<height>388</height>
</rect>
</property>
<property name="windowTitle">
<string>Session management</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableSessions">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<widget class="EntityFinderWidget" name="hallFinder" native="true"/>
</item>
<item>
<widget class="EntityFinderWidget" name="movieFinder" native="true"/>
</item>
<item>
<widget class="QLabel" name="labelDateTimeBeginAt">
<property name="text">
<string>Specify the date and time of the session start:</string>
</property>
</widget>
</item>
<item>
<widget class="QDateTimeEdit" name="dateTimeEditBeginAt"/>
</item>
<item>
<widget class="QLabel" name="labelTicketPrice">
<property name="text">
<string>Specify the cost of watching the session:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxTicketPrice"/>
</item>
<item>
<widget class="QPushButton" name="btnAddSession">
<property name="text">
<string>Add session</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,6 @@
#include "single_record_time_range_widget.h"
SingleRecordTimeRangeWidget::SingleRecordTimeRangeWidget(QWidget *parent)
: QWidget(parent) {
setupUi(this);
}
@@ -0,0 +1,14 @@
#ifndef SINGLE_RECORD_TIME_RANGE_WIDGET_H
#define SINGLE_RECORD_TIME_RANGE_WIDGET_H
#include "ui_single_record_time_range_widget.h"
class SingleRecordTimeRangeWidget : public QWidget,
private Ui::SingleRecordTimeRangeWidget {
Q_OBJECT
public:
SingleRecordTimeRangeWidget(QWidget *parent = nullptr);
};
#endif // SINGLE_RECORD_TIME_RANGE_WIDGET_H
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SingleRecordTimeRangeWidget</class>
<widget class="QWidget" name="SingleRecordTimeRangeWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>316</width>
<height>85</height>
</rect>
</property>
<property name="windowTitle">
<string>Reports</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="EntityFinderWidget" name="entityFinder" native="true"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelStartOfPeriodAttendance">
<property name="text">
<string>Start of the period:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelEndOfPeriodAttendance">
<property name="text">
<string>End of the period:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QDateTimeEdit" name="dateTimeEditStartOfPeriodAttendance"/>
</item>
<item row="2" column="1">
<widget class="QDateTimeEdit" name="dateTimeEditEndOfPeriodAttendance"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+109
View File
@@ -0,0 +1,109 @@
#include "ticket_return_widget.h"
#include "create_refund_request.h"
#include "error_dialog.h"
#include "model_manager.h"
#include "refunds_model.h"
#include "refunds_repository.h"
#include "repository_manager.h"
#include "repository_result.h"
#include "schema_metadata.h"
#include <QMessageBox>
TicketReturnWidget::TicketReturnWidget(QWidget *parent)
: QWidget(parent), retrievedTicketField_(-1) {
setupUi(this);
initializeRepositories();
initializeModels();
setupTableView(); // After initialization of models
setupTicketFinder();
setupConnections();
}
// Setup widgets
void TicketReturnWidget::setupTableView() {
tableRefunds->setModel(refundsModel_);
}
void TicketReturnWidget::setupTicketFinder() {
ticketFinder->setDefaultRelation("tickets");
ticketFinder->setLabelText(EntityFinderWidget::tr("Select ticket"));
ticketFinder->setLineEditPlaceholder(
EntityFinderWidget::tr("Here will be information about selected ticket"));
}
void TicketReturnWidget::initializeRepositories() {
RepositoryManager &repositoryManager = RepositoryManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
refundsRepository_ = repositoryManager.getRepositoryAs<RefundsRepository>(
metadata.repositoryTableName("refunds"));
if (!bool(refundsRepository_)) {
qFatal() << "TicketReturnWidget::initializeRepositories: Failed to "
"initialize repositories";
}
}
void TicketReturnWidget::initializeModels() {
ModelManager &modelManager = ModelManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
refundsModel_ =
modelManager.getModelAs<RefundsModel>(metadata.modelTableName("refunds"));
if (!bool(refundsModel_)) {
qFatal() << "TicketReturnWidget::initializeModels: Failed to initialize "
"models";
}
}
void TicketReturnWidget::setupConnections() {
connect(pushButtonRefundTicket, &QPushButton::clicked, this,
&TicketReturnWidget::onPushButtonRefundTicketClicked);
connect(ticketFinder, &EntityFinderWidget::fieldRetrieved, this,
&TicketReturnWidget::onTicketFieldRetrieved);
}
void TicketReturnWidget::onPushButtonRefundTicketClicked() {
if (retrievedTicketField_ == -1) {
QMessageBox::warning(this, QMessageBox::tr("Error"),
QMessageBox::tr("No ticket selected"));
return;
}
QDateTime refundAt;
if (dateTimeEditRefund->isEnabled()) {
refundAt = dateTimeEditRefund->dateTime();
} else {
refundAt = QDateTime::currentDateTime();
}
double refundAmount = spinBoxRefundAmount->value();
CreateRefundRequest request;
request.setTicketId(retrievedTicketField_);
request.setRefundAt(refundAt);
request.setRefundAmount(refundAmount);
RepositoryResult refundResult = refundsRepository_->createRefund(request);
if (!refundResult.success) {
qInfo() << "Failed to refund ticket by id:" << retrievedTicketField_;
ErrorDialog::displayError(this, refundResult.errorMessage);
return;
}
QString successMessage =
QMessageBox::tr("Ticket with number %1 has been successfully refunded.")
.arg(retrievedTicketField_);
QMessageBox::information(this, QMessageBox::tr("Success"), successMessage);
ticketFinder->resetLineEditText();
}
void TicketReturnWidget::onTicketFieldRetrieved(const QSqlField &field) {
retrievedTicketField_ = field.value().toInt();
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef TICKET_RETURN_WIDGET_H
#define TICKET_RETURN_WIDGET_H
#include "ui_ticket_return_widget.h"
#include <QWidget>
// Forward declaration
class RefundsModel;
class RefundsRepositoryInterface;
class TicketReturnWidget : public QWidget, Ui::TicketReturnWidget {
Q_OBJECT
private:
RefundsModel *refundsModel_;
RefundsRepositoryInterface *refundsRepository_;
qint32 retrievedTicketField_;
public:
explicit TicketReturnWidget(QWidget *parent = nullptr);
private:
void initializeRepositories();
void initializeModels();
void setupConnections();
void setupTableView();
void setupTicketFinder();
private slots:
void onPushButtonRefundTicketClicked();
void onTicketFieldRetrieved(const QSqlField &field);
};
#endif // TICKET_RETURN_WIDGET_H
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TicketReturnWidget</class>
<widget class="QWidget" name="TicketReturnWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>311</width>
<height>375</height>
</rect>
</property>
<property name="windowTitle">
<string>Ticket refund</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableRefunds">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<widget class="EntityFinderWidget" name="ticketFinder" native="true"/>
</item>
<item>
<widget class="QLabel" name="labelRefundAmount">
<property name="text">
<string>Enter the refund amount:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxRefundAmount"/>
</item>
<item>
<widget class="QLabel" name="labelDateTimeEditRefund">
<property name="text">
<string>Date and time of ticket refund (debugging):</string>
</property>
</widget>
</item>
<item>
<widget class="QDateTimeEdit" name="dateTimeEditRefund">
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonRefundTicket">
<property name="text">
<string>Refund</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+109
View File
@@ -0,0 +1,109 @@
#include "ticket_sale_widget.h"
#include "create_ticket_request.h"
#include "error_dialog.h"
#include "model_manager.h"
#include "repository_manager.h"
#include "repository_result.h"
#include "schema_metadata.h"
#include "tickets_model.h"
#include "tickets_repository.h"
#include <QLineEdit>
#include <QMessageBox>
#include <QSqlError>
TicketSaleWidget::TicketSaleWidget(QWidget *parent)
: QWidget(parent), selectedSessionId_(-1) {
setupUi(this);
initializeRepositories(); // First or second
initializeModels(); // First ir second
setupTableView(); // After initialization of models
setupConnections();
setupSessionFinder();
}
void TicketSaleWidget::setupTableView() {
tableTickets->setModel(ticketsModel_);
}
void TicketSaleWidget::initializeRepositories() {
RepositoryManager &repositoryManager = RepositoryManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
ticketsRepository_ = repositoryManager.getRepositoryAs<TicketsRepository>(
metadata.repositoryTableName("tickets"));
if (!ticketsRepository_) {
qFatal() << "TicketSaleWidget::initializeRepositories: Failed to "
"initialize repositories";
}
}
void TicketSaleWidget::initializeModels() {
ModelManager &modelManager = ModelManager::instance();
SchemaMetadata metadata = SchemaMetadata::defaultSchema();
ticketsModel_ =
modelManager.getModelAs<TicketsModel>(metadata.modelTableName("tickets"));
if (!bool(ticketsModel_)) {
qFatal() << "TicketSaleWidget::initializeModels: Failed to initialize "
"models";
}
}
void TicketSaleWidget::setupConnections() {
connect(btnSellTicket, &QPushButton::clicked, this,
&TicketSaleWidget::onPushButtonSellTicketClicked);
connect(sessionFinder, &EntityFinderWidget::fieldRetrieved, this,
&TicketSaleWidget::onFieldRetrieved);
}
void TicketSaleWidget::setupSessionFinder() {
sessionFinder->setDefaultRelation("sessions");
sessionFinder->setLabelText(EntityFinderWidget::tr("Select session"));
sessionFinder->setLineEditPlaceholder(EntityFinderWidget::tr(
"Here will be information about selected session"));
}
void TicketSaleWidget::onPushButtonSellTicketClicked() {
if (selectedSessionId_ == -1) {
QMessageBox::warning(this, QMessageBox::tr("Error"),
QMessageBox::tr("No session selected"));
return;
}
int seatNumber = spinBoxSeatNumber->value();
QDateTime soldAt = dateTimeEditSell->isEnabled()
? dateTimeEditSell->dateTime()
: QDateTime::currentDateTime();
CreateTicketRequest request;
request.setSeatNumber(seatNumber);
request.setSessionId(selectedSessionId_);
request.setSoldAt(soldAt);
RepositoryResult result = ticketsRepository_->createTicket(request);
if (!result.success) {
qInfo() << "Failed to create ticket:" << result.errorMessage;
ErrorDialog::displayError(this, result.errorMessage);
return;
}
}
void TicketSaleWidget::onFieldRetrieved(const QSqlField &field) {
selectedSessionId_ = field.value().toInt();
}
// Check if all values in initializer list are valid
void TicketSaleWidget::validateSessionValues(
const std::initializer_list<QVariant> &valuesList) {
for (const auto &value : valuesList) {
if (!value.isValid()) {
return;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef TICKET_SALE_WIDGET_H
#define TICKET_SALE_WIDGET_H
#include "ui_ticket_sale_widget.h"
#include <QSqlTableModel>
#include <QWidget>
// Forward declaration
class TicketsModel;
class TicketsRepositoryInterface;
class TicketSaleWidget : public QWidget, Ui::TicketSaleWidget {
Q_OBJECT
private:
TicketsModel *ticketsModel_;
TicketsRepositoryInterface *ticketsRepository_;
qint32 selectedSessionId_;
public:
explicit TicketSaleWidget(QWidget *parent = nullptr);
private:
void initializeRepositories();
void initializeModels();
void setupConnections();
void setupSessionFinder();
void setupTableView();
void validateSessionValues(const std::initializer_list<QVariant> &valuesList);
private slots:
void onPushButtonSellTicketClicked();
void onFieldRetrieved(const QSqlField &field);
};
#endif // TICKET_SALE_WIDGET_H
+83
View File
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TicketSaleWidget</class>
<widget class="QWidget" name="TicketSaleWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>292</width>
<height>375</height>
</rect>
</property>
<property name="windowTitle">
<string>Ticket sales</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableTickets">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<widget class="EntityFinderWidget" name="sessionFinder" native="true"/>
</item>
<item>
<widget class="QLabel" name="labelSeatNumber">
<property name="text">
<string>Seat number:</string>
</property>
<property name="buddy">
<cstring>spinBoxSeatNumber</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxSeatNumber">
<property name="prefix">
<string>Seat number №</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelDateTimeEditSell">
<property name="text">
<string>Date and time of ticket sale (debugging):</string>
</property>
</widget>
</item>
<item>
<widget class="QDateTimeEdit" name="dateTimeEditSell">
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSellTicket">
<property name="text">
<string>Sell this ticket</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+362
View File
@@ -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);
}
+209
View File
@@ -0,0 +1,209 @@
#ifndef TICKET_SALES_REPORT_WIDGET_H
#define TICKET_SALES_REPORT_WIDGET_H
#include "ui_ticket_sales_report_widget.h"
#include <QChartView>
#include <QLineSeries>
#include <QScatterSeries>
#include <functional>
// Forward declaration
class RepositoryResult;
class SessionStatisticsDTO;
class RepositoriesInterface;
class SessionsStatisticsRepository;
class TicketSalesReportWidget : public QWidget,
private Ui::TicketSalesReportWidget {
Q_OBJECT
private:
class SalesData {
private:
double refundExpenses_;
double salesRevenue_;
qint32 refundedTickets_;
qint32 soldTickets_;
qint32 maximumTickets_;
public:
SalesData();
explicit SalesData(double refundExpenses, double salesRevenue,
qint32 refundedTickets, qint32 soldTickets,
qint32 maximumTickets);
SalesData &operator+=(const SalesData &other);
SalesData operator+(const SalesData &other) const;
// clang-format off
inline double refundExpenses() const { return refundExpenses_; };
inline double salesRevenue() const { return salesRevenue_; };
inline qint32 refundedTickets() const { return refundedTickets_; };
inline qint32 soldTickets() const { return soldTickets_; };
inline qint32 maximumTickets() const { return maximumTickets_; };
inline void setRefundExpenses(double refundExpenses) { refundExpenses_ = refundExpenses; };
inline void setRefundedTickets(qint32 refundedTickets) { refundedTickets_ = refundedTickets; };
inline void setSalesRevenue(double salesRevenue) { salesRevenue_ = salesRevenue; };
inline void setSoldTickets(qint32 soldTickets) { soldTickets_ = soldTickets; };
inline void setMaximumTickets(qint32 maximumTickets) { maximumTickets_ = maximumTickets; };
// clang-format on
};
private:
using GetSessionsForEntityByIdMethod = std::function<RepositoryResult(
qint32 entityId, std::optional<QDateTime> startDate,
std::optional<QDateTime> endDate,
QVector<SessionStatisticsDTO> &sessions)>;
private:
GetSessionsForEntityByIdMethod sessionsStatisticsMethod_;
QChartView *chartView_;
QDateTime startRange_, endRange_;
QString billetChartTitle_, chartTitle_, seriesName_, yAxisTitle_;
SessionsStatisticsRepository *sessionsStatisticsRepository;
int columnInfoIndex_;
qint32 selectedEntityId_;
public:
explicit TicketSalesReportWidget(QWidget *parent = nullptr);
void configureReportForGenre();
void configureReportForHall();
void configureReportForMovie();
private:
void initializeChart();
void initializeRepositories();
void setupChart();
void setupConnections();
void configureEntityFinder(const QString &databaseTableName,
const QString &labelText,
const QString &placeholder);
private slots:
void onEntityFinderReseted();
void onPushButtonGenerateClicked();
void onFieldRetrieved(const QSqlField &field);
void onRecordRetrieved(const QSqlRecord &rowValues);
private:
template <typename T, typename SeriesType>
SeriesType *
createSeriesTemplate(const QMap<QDateTime, SalesData> &accumulatedSalesData,
QDateTime &minDate, QDateTime &maxDate,
T (SalesData::*getter)() const,
const QString &seriesName);
template <typename T>
QAbstractSeries *
createLineSeries(const QMap<QDateTime, SalesData> &accumulatedSalesData,
QDateTime &minDate, QDateTime &maxDate,
T (SalesData::*getter)() const, const QString &seriesName);
template <typename T>
QAbstractSeries *
createScatterSeries(const QMap<QDateTime, SalesData> &accumulatedSalesData,
QDateTime &minDate, QDateTime &maxDate,
T (SalesData::*getter)() const,
const QString &seriesName);
template <typename T>
T maxValue(const QMap<QDateTime, SalesData> &data,
T (SalesData::*getter)() const);
private:
QMap<QDateTime, TicketSalesReportWidget::SalesData> aggregateByDateTime(
const QVector<SessionStatisticsDTO> &sessions,
std::function<bool(const SessionStatisticsDTO &)> filter = {});
QAbstractAxis *createDateTimeAxis(int tickCount, const QString &axisTitle,
const QDateTime &minDate,
const QDateTime &maxDate) const;
QAbstractAxis *createValueAxis(int maxValue, const QString &axisTitle) const;
RepositoryResult retrieveSessions(QVector<SessionStatisticsDTO> &sessions);
bool dateRange(QDateTime &startOut, QDateTime &endOut);
bool validateData(const QMap<QDateTime, SalesData> &data) const;
void createChart(const QMap<QDateTime, SalesData> &data);
void setupChart(QChart *chart, QAbstractSeries *series, QAbstractAxis *axisX,
QAbstractAxis *axisY) const;
void updateChartView(QChart *chart) const;
};
template <typename T, typename SeriesType>
SeriesType *TicketSalesReportWidget::createSeriesTemplate(
const QMap<QDateTime, SalesData> &accumulatedSalesData, QDateTime &minDate,
QDateTime &maxDate, T (SalesData::*getter)() const,
const QString &seriesName) {
auto *series = new SeriesType();
series->setName(seriesName);
bool first = true;
for (auto it = accumulatedSalesData.cbegin();
it != accumulatedSalesData.cend(); ++it) {
const QDateTime &dateTime = it.key();
series->append(dateTime.toMSecsSinceEpoch(), (it.value().*getter)());
if (first) {
minDate = dateTime;
maxDate = dateTime;
first = false;
} else {
if (dateTime < minDate)
minDate = dateTime;
if (dateTime > maxDate)
maxDate = dateTime;
}
}
return series;
}
template <typename T>
QAbstractSeries *TicketSalesReportWidget::createScatterSeries(
const QMap<QDateTime, SalesData> &accumulatedSalesData, QDateTime &minDate,
QDateTime &maxDate, T (SalesData::*getter)() const,
const QString &seriesName) {
return createSeriesTemplate<T, QScatterSeries>(accumulatedSalesData, minDate,
maxDate, getter, seriesName);
}
template <typename T>
QAbstractSeries *TicketSalesReportWidget::createLineSeries(
const QMap<QDateTime, SalesData> &accumulatedSalesData, QDateTime &minDate,
QDateTime &maxDate, T (SalesData::*getter)() const,
const QString &seriesName) {
return createSeriesTemplate<T, QLineSeries>(accumulatedSalesData, minDate,
maxDate, getter, seriesName);
}
template <typename T>
T TicketSalesReportWidget::maxValue(const QMap<QDateTime, SalesData> &data,
T (SalesData::*getter)() const) {
if (data.isEmpty()) {
return T{};
}
T maxValue = (data.cbegin().value().*getter)();
for (auto it = data.cbegin(); it != data.cend(); ++it) {
maxValue = std::max(maxValue, (it.value().*getter)());
}
return maxValue;
}
#endif // TICKET_SALES_REPORT_WIDGET_H
+80
View File
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TicketSalesReportWidget</class>
<widget class="QWidget" name="TicketSalesReportWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>316</width>
<height>125</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QFrame" name="frameChart">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="EntityFinderWidget" name="entityFinder" native="true"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelStartOfPeriod">
<property name="text">
<string>Start of the period:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelEndOfPeriod">
<property name="text">
<string>End of the period:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QDateTimeEdit" name="dateTimeEditStartOfPeriod"/>
</item>
<item row="3" column="1">
<widget class="QDateTimeEdit" name="dateTimeEditEndOfPeriod"/>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="btnGenerate">
<property name="text">
<string>Generate</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>EntityFinderWidget</class>
<extends>QWidget</extends>
<header>entity_finder_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>