./core/controllers/application_connector.cpp

       1  #include "application_connector.h"
          
          #include "model_manager.h"
          #include "repository_manager.h"
          #include "schema_metadata.h"
          #include <QObject>
          
       8  ApplicationConnector::ApplicationConnector(  QObject *rootObject )
           : modelManager_(  ModelManager::instance(   ) ),  
           repositoryManager_(  RepositoryManager::instance(   ) ),  
           rootObject_(  rootObject ) {}
          
      13  bool ApplicationConnector::connect(   ) {
           if (  !connectSelfReferenceTableLinks(   ) ) {
           return false;
           }
          
           if (  !connectTablePairLinks(   ) ) {
           return false;
           }
          
           if (  !connectUpdateDeleteRFLinks(   ) ) {
           return false;
           }
          
           if (  !connectDeleteRFLinks(   ) ) {
           return false;
           }
          
           return true;
          }
          
          ApplicationConnector::RMLinkList
      34  ApplicationConnector::initializeSelfReferenceTableLinks(   ) {
           return {linkRepositoryToModel(  "genres",   "genres" ),  
           linkRepositoryToModel(  "halls",   "halls" ),  
           linkRepositoryToModel(  "movies",   "movies" ),  
           linkRepositoryToModel(  "sessions",   "sessions" ),  
           linkRepositoryToModel(  "refunds",   "refunds" ),  
           linkRepositoryToModel(  "tickets",   "tickets" )};
          }
          
          ApplicationConnector::RMLinkList
      44  ApplicationConnector::initializeTablePairLinks(   ) {
           return {linkRepositoryToModel(  "genres",   "movies" ),  
           linkRepositoryToModel(  "genres",   "sessions" ),  
           linkRepositoryToModel(  "genres",   "tickets" ),  
           linkRepositoryToModel(  "genres",   "refunds" ),  
           linkRepositoryToModel(  "halls",   "sessions" ),  
           linkRepositoryToModel(  "halls",   "tickets" ),  
           linkRepositoryToModel(  "halls",   "refunds" ),  
           linkRepositoryToModel(  "movies",   "sessions" ),  
           linkRepositoryToModel(  "movies",   "tickets" ),  
           linkRepositoryToModel(  "movies",   "refunds" ),  
           linkRepositoryToModel(  "sessions",   "refunds" ),  
           linkRepositoryToModel(  "tickets",   "refunds" )};
          }
          
          ApplicationConnector::RFLinkList
      60  ApplicationConnector::flattenLinkList(  const QVector<RFLinkList> &linkLists ) {
           RFLinkList result;
           for (  const RFLinkList &currentList : linkLists ) {
           std::move(  currentList.cbegin(   ),   currentList.cend(   ),  
           std::back_inserter(  result ) );
           }
           return result;
          }
          
          ApplicationConnector::RFLinkList
      70  ApplicationConnector::initializeUpdateDeleteCandidates(   ) {
           const QVector<RFLinkList> updateDeleteCandidates = {
           linkRepositoryToFinder(  "genres",   "genreFinder" ),  
           linkRepositoryToFinder(  "genres",   "movieFinder" ),  
           linkRepositoryToFinder(  "genres",   "sessionFinder" ),  
           linkRepositoryToFinder(  "genres",   "ticketFinder" ),  
           linkRepositoryToFinder(  "genres",   "entityFinder" ),  
           linkRepositoryToFinder(  "halls",   "hallFinder" ),  
           linkRepositoryToFinder(  "halls",   "movieFinder" ),  
           linkRepositoryToFinder(  "halls",   "sessionFinder" ),  
           linkRepositoryToFinder(  "halls",   "ticketFinder" ),  
           linkRepositoryToFinder(  "halls",   "entityFinder" ),  
           linkRepositoryToFinder(  "movies",   "movieFinder" ),  
           linkRepositoryToFinder(  "movies",   "sessionFinder" ),  
           linkRepositoryToFinder(  "movies",   "ticketFinder" ),  
           linkRepositoryToFinder(  "movies",   "entityFinder" ),  
           linkRepositoryToFinder(  "sessions",   "sessionFinder" ),  
           linkRepositoryToFinder(  "sessions",   "ticketFinder" ),  
           linkRepositoryToFinder(  "sessions",   "entityFinder" ),  
           linkRepositoryToFinder(  "tickets",   "ticketFinder" ),  
           linkRepositoryToFinder(  "tickets",   "entityFinder" ),  
           linkRepositoryToFinder(  "refunds",   "entityFinder" )};
          
           return flattenLinkList(  updateDeleteCandidates );
          }
          
          ApplicationConnector::RFLinkList
      97  ApplicationConnector::initializeDeleteCandidates(   ) {
           // foreignKeyFinder will reset to default on signal primaryKeyFinder
          
           const QVector<RFLinkList> deleteCandidates = {
           linkRepositoryToFinder(  "genres",   "primaryKeyFinder" ),  
           linkRepositoryToFinder(  "halls",   "primaryKeyFinder" ),  
           linkRepositoryToFinder(  "movies",   "primaryKeyFinder" ),  
           linkRepositoryToFinder(  "sessions",   "primaryKeyFinder" ),  
           linkRepositoryToFinder(  "tickets",   "primaryKeyFinder" ),  
           linkRepositoryToFinder(  "refunds",   "primaryKeyFinder" )};
          
           return flattenLinkList(  deleteCandidates );
          }
          
     111  bool ApplicationConnector::connectSelfReferenceTableLinks(   ) {
           const QList<RMLink> selfReferenceTableLinks =
           initializeSelfReferenceTableLinks(   );
           qInfo(   ) << "Connecting self-reference tables...";
          
           if (  !connect(  selfReferenceTableLinks,   singleTableConnections(   ) ) ) {
           qWarning(   ) << "Failed to connect self-reference tables";
           return false;
           }
           return true;
          }
          
     123  bool ApplicationConnector::connectTablePairLinks(   ) {
           const RMLinkList tablePairLinks = initializeTablePairLinks(   );
           qInfo(   ) << "Connecting pair tables...";
          
           if (  !connect(  tablePairLinks,   pairTableConnections(   ) ) ) {
           qWarning(   ) << "Failed to connect pair tables";
           return false;
           }
           return true;
          }
          
     134  bool ApplicationConnector::connectUpdateDeleteRFLinks(   ) {
           const RFLinkList repositoryToFinderLinks = initializeUpdateDeleteCandidates(   );
           qInfo(   ) << "Connecting repository to finder...";
          
           if (  !connect(  repositoryToFinderLinks,   repositoryToFinderConnections(   ) ) ) {
           qWarning(   ) << "Failed to connect repository to finder";
           return false;
           }
           return true;
          }
          
     145  bool ApplicationConnector::connectDeleteRFLinks(   ) {
           const RFLinkList repositoryToFinderLinks = initializeDeleteCandidates(   );
           qInfo(   ) << "Connecting repository to finder...";
          
           using Signal = void (  Repository::* )(   );
           using Slot = void (  Finder::* )(   );
           RFConnectionList connections;
           connections.append(  Connection<Signal,   Slot>(  &Repository::dataDeleted,  
           &Finder::resetToDefault ) );
          
           if (  !connect(  repositoryToFinderLinks,   connections ) ) {
           qWarning(   ) << "Failed to connect repository to finder";
           return false;
           }
           return true;
          }
          
          ApplicationConnector::RMConnectionList
     163  ApplicationConnector::pairTableConnections(   ) {
           using Signal = void (  Repository::* )(   );
           using Slot = bool (  Model::* )(   );
          
           RMConnectionList result;
           std::initializer_list<Connection<Signal,   Slot>> initList = {
           {&Repository::dataUpdated,   &Model::refreshAll},  
           {&Repository::dataDeleted,   &Model::refreshAll}};
          
           for (  const auto &connection : initList ) {
           result.append(  connection );
           }
          
           return result;
          }
          
          ApplicationConnector::RMConnectionList
     180  ApplicationConnector::singleTableConnections(   ) {
           using Signal = void (  Repository::* )(   );
           using Slot = bool (  Model::* )(   );
          
           RMConnectionList result;
           std::initializer_list<Connection<Signal,   Slot>> initList = {
           {&Repository::dataCreated,   &Model::refreshAll},  
           {&Repository::dataUpdated,   &Model::refreshAll},  
           {&Repository::dataDeleted,   &Model::refreshAll}};
          
           for (  const auto &connection : initList ) {
           result.append(  connection );
           }
          
           return result;
          }
          
     197  ApplicationConnector::RMLink ApplicationConnector::linkRepositoryToModel(  
     198   const QString &senderDatabaseTableName,  
     199   const QString &receiverDatabaseTableName ) {
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           Repository *sender = repositoryManager_.getRepositoryAs<Repository>(  
           metadata.repositoryTableName(  senderDatabaseTableName ) );
           if (  !sender ) {
           qWarning(   ) << "Sender with type" << typeid(  sender ).name(   ) << "is null";
           }
          
           Model *receiver = modelManager_.getModelAs<Model>(  
           metadata.modelTableName(  receiverDatabaseTableName ) );
           if (  !receiver ) {
           qWarning(   ) << "Receiver with type" << typeid(  receiver ).name(   ) << "is null";
           }
          
           if (  !sender || !receiver ) {
           return qMakePair(  nullptr,   nullptr );
           }
          
           return qMakePair(  sender,   receiver );
          }
          
     221  ApplicationConnector::RFLinkList ApplicationConnector::linkRepositoryToFinder(  
     222   const QString &senderDatabaseTableName,  
     223   const QString &receiverEntityFinderName ) {
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           Repository *sender = repositoryManager_.getRepositoryAs<Repository>(  
           metadata.repositoryTableName(  senderDatabaseTableName ) );
           if (  !sender ) {
           qWarning(   ) << "Sender with type" << typeid(  sender ).name(   ) << "is null";
           }
          
           QList<Finder *> receiver =
           rootObject_->findChildren<Finder *>(  receiverEntityFinderName );
           if (  receiver.isEmpty(   ) ) {
           qWarning(   ) << "Receivers with type" << typeid(  receiver ).name(   )
           << "there is not";
           }
          
           if (  !sender || receiver.isEmpty(   ) ) {
           return QList<RFLink>(   );
           }
          
           RFLinkList result;
           for (  Finder *currentReceiver : receiver ) {
           result.append(  qMakePair(  sender,   currentReceiver ) );
           }
          
           return result;
          }
          
          ApplicationConnector::RFConnectionList
     252  ApplicationConnector::repositoryToFinderConnections(   ) {
           using Signal = void (  Repository::* )(   );
           using Slot = void (  Finder::* )(   );
          
           RFConnectionList result;
           std::initializer_list<Connection<Signal,   Slot>> initList = {
           {&Repository::dataDeleted,   &Finder::resetToDefault},  
           {&Repository::dataUpdated,   &Finder::resetToDefault}};
          
           for (  const auto &connection : initList ) {
           result.append(  connection );
           }
          
           return result;
          }
          
     268  QString ApplicationConnector::getAddress(  const QObject *object ) {
           quintptr address = reinterpret_cast<quintptr>(  object );
           return object ? QString::number(  address,   16 ) : "null";
          };
          
     273  QString ApplicationConnector::getMetaClassName(  const QMetaObject *meta ) {
           return meta ? meta->className(   ) : "Unknown";
          };
          
     277  QString ApplicationConnector::getMethodSignature(  const QMetaObject *meta,  
           const char *methodType,  
     279   bool isSignal ) {
           if (  !meta ) {
           return "Unknown";
           }
          
           QByteArray normalizedSignature = QMetaObject::normalizedSignature(  methodType );
           int methodIndex = isSignal ? meta->indexOfSignal(  normalizedSignature )
           : meta->indexOfSlot(  normalizedSignature );
          
           if (  methodIndex < 0 ) {
           return normalizedSignature;
           }
          
           QString result = meta->method(  methodIndex ).methodSignature(   );
           return result;
          };

./core/controllers/application_controller.cpp

       1  #include "application_controller.h"
          #include "config.h"
          #include "database_manager.h"
          
          // Translations
          #include <QCoreApplication>
          #include <QLocale>
          #include <QTranslator>
          
          // Generation of records
          #include "refund_generator.h"
          #include "session_generator.h"
          #include "ticket_generator.h"
          
          // Auntification
          #include "auth_dialog.h"
          
          qint32 ApplicationController::userId_ = -1;
          qint32 ApplicationController::accessLevel_ = 0;
          
      21  bool ApplicationController::setupApplication(   ) {
          
           if (  !ensureDatabaseExists(  DATABASE_NAME ) ) {
           return false;
           }
          
           if (  !connectToDatabase(  DATABASE_NAME ) ) {
           return false;
           }
          
           if (  !loadTranslations(  "ru",   "app",   "_",   ":/translations" ) ) {
           return false;
           }
          
           AuthDialog authDialog;
          
           QObject::connect(  &authDialog,   &AuthDialog::loginSuccessful,  
           &ApplicationController::onLoginSuccessful );
          
           QObject::connect(  &authDialog,   &AuthDialog::loginFailed,  
           &ApplicationController::onLoginFailed );
          
           // if (  authDialog.exec(   ) != QDialog::Accepted ) {
           // return false; // User has not logged in
           // }
          
           return true;
          }
          
      50  bool ApplicationController::ensureDatabaseExists(  
      51   const QString &databaseFileName ) {
           DatabaseManager::CreateDatabaseResult result =
           DatabaseManager::createDefaultDatabaseIfMissing(  databaseFileName );
          
           switch (  result ) {
           case DatabaseManager::CreateDatabaseResult::Success: {
          
          #ifndef NDEBUG
           qInfo(   ) << "Generating test data...";
          
           DayManager dayManager(  QTime(  8,   0 ),   QTime(  17,   0 ) );
           dayManager.setMode(  DayManager::SlotSelectionMode::Quick );
           dayManager.setMode(  DayManager::StartTimeMode::Random );
          
           const QDate begin = QDate(  2025,   1,   1 ); // Since 01-01-2025
           const QDate end = begin.addDays(  5 );
           ReservationManager reservationManager(  14,   begin.daysTo(  end ),  
           std::move(  dayManager ) );
          
           SessionGenerator sessionGenerator(  reservationManager );
          
           sessionGenerator.generateAll(  
           sessionGenerator.defaultShouldLog(  1 /* percent */,   60 /* seconds */ ) );
          
           QPair<QTime,   QTime> timeRange(  QTime(  9,   30 ),   QTime(  15,   30 ) );
          
           TicketGenerator ticketsGenerator(  
           {0,   30} /* day range */,   timeRange,  
           {0.0,   1.0} /* fill ratio per session range*/ );
           GeneratorInterface::ShouldLogSuccess performOtherwise =
           ticketsGenerator.shouldLogFirstAndLastPercent(   );
           ticketsGenerator.generateByFillRatio(  
           0.35,  
           ticketsGenerator.defaultShouldLog(  1 /* percent */,   600 /* seconds */ ) );
          
           RefundGenerator refundGenerator(  {0.1,   0.5} /* refund ratio range */,  
           {0,   7} /* day range */,   timeRange );
           refundGenerator.generateByFillRatio(  
           0.1,  
           refundGenerator.defaultShouldLog(  1 /* percent */,   600 /* seconds
           */ ) );
          
           qInfo(   ) << "Test data generated successfully";
          #endif
          
           qInfo(   ) << "Database created and initialized successfully";
           } break;
           case DatabaseManager::CreateDatabaseResult::AlreadyExists:
           break;
           case DatabaseManager::CreateDatabaseResult::Error:
           qCritical(   ) << "Failed to create and connect to the database";
           return false;
           }
          
           return true;
          }
          
     108  void ApplicationController::onLoginFailed(   ) {
           userId_ = -1;
           accessLevel_ = 0;
          }
          
     113  void ApplicationController::onLoginSuccessful(  qint32 userId,  
     114   qint32 accessLevel ) {
           userId_ = userId;
           accessLevel_ = accessLevel;
          }
          
     119  bool ApplicationController::connectToDatabase(  const QString &databaseName ) {
           if (  !DatabaseManager::connectToDefaultDatabase(  databaseName ) ) {
           qCritical(   ) << "Failed to connect to the database";
           return false;
           }
          
           qInfo(   ) << "Connected to database" << databaseName << "successfully";
           return true;
          }
          
     129  bool ApplicationController::loadTranslations(  const QString &locale,  
     130   const QString &filename,  
     131   const QString &prefix,  
     132   const QString &directory ) {
           QCoreApplication *app = QCoreApplication::instance(   );
           QTranslator *translator = new QTranslator(  app );
          
           bool loaded = translator->load(  QLocale(  locale ),   filename,   prefix,   directory );
           if (  !loaded ) {
           qWarning(   ) << "Failed to load translations";
           return false;
           }
          
           bool installed = app->installTranslator(  translator );
           if (  !installed ) {
           qWarning(   ) << "Failed to install translations";
           return false;
           }
          
           qInfo(   ) << "Translations are loaded successfully";
           return true;
          }

./core/db/database_manager.cpp

       1  #include "database_manager.h"
          
          #include "config.h"
          #include "execute_sql_file.h"
          #include <QFile>
          #include <QSqlError>
          #include <qsqldatabase.h>
          
       9  DatabaseManager::~DatabaseManager(   ) {
           disconnectDefaultDatabase(   );
          
           QString defaultConnectionName = QSqlDatabase::database(   ).connectionName(   );
           QSqlDatabase::removeDatabase(  QSqlDatabase::database(   ).connectionName(   ) );
          
           qInfo(   ) << "Database with default connection name:" << defaultConnectionName
           << "was removed";
          }
          
          DatabaseManager::CreateDatabaseResult
      20  DatabaseManager::createDefaultDatabaseIfMissing(  
      21   const QString &databaseFileName ) {
           if (  QFile::exists(  databaseFileName ) ) {
           qInfo(   )
           << "Database file name already exists and will not be created again:"
           << databaseFileName;
           return CreateDatabaseResult::AlreadyExists;
           }
          
           if (  !connectToDefaultDatabase(  databaseFileName ) ) {
           qWarning(   ) << "Failed to create and connect to the database";
           return CreateDatabaseResult::Error;
           }
          
           // Execute SQL files to initialize the database
           QStringList sqlFiles = {":/sql/create_tables.sql",   ":/sql/create_indexes.sql",  
           ":/sql/create_views.sql",   ":/sql/create_triggers.sql",  
           ":/sql/insert_role.sql"};
          
          #ifndef NDEBUG
           QStringList additionalSqlFiles = {
           ":/sql/insert_genre.sql",   ":/sql/insert_hall.sql",  
           ":/sql/insert_movie.sql",   ":/sql/insert_user.sql"};
           sqlFiles.append(  additionalSqlFiles );
          #endif
          
           QSqlDatabase defaultDatabase = QSqlDatabase::database(   );
           bool success = executeSqlFiles(  sqlFiles,   defaultDatabase );
           disconnectDefaultDatabase(   );
          
           if (  success ) {
           qInfo(   ) << "Database created and initialized successfully";
           return CreateDatabaseResult::Success;
           }
          
           qWarning(   ) << "Failed to execute initialization SQL files";
          
           if (  QFile::remove(  databaseFileName ) ) {
           qInfo(   ) << "Database file deleted due to initialization failure:"
           << databaseFileName;
           } else {
           qWarning(   ) << "Failed to delete database file:" << databaseFileName;
           }
          
           return CreateDatabaseResult::Error;
          }
          
      67  bool DatabaseManager::connectToDefaultDatabase(  
      68   const QString &databaseFileName ) {
           QSqlDatabase defaultDatabase = QSqlDatabase::database(   );
          
           defaultDatabase.setDatabaseName(  databaseFileName );
           if (  defaultDatabase.isValid(   ) && defaultDatabase.isOpen(   ) ) {
           qInfo(   ) << "Database is already connected";
           return true;
           }
          
           // Constructor does not open/close the connection
           defaultDatabase = QSqlDatabase::addDatabase(  DATABASE_DRIVER );
           defaultDatabase.setDatabaseName(  databaseFileName );
          
           if (  !defaultDatabase.open(   ) ) {
           qWarning(   ) << "Failed to connect to the database:" << databaseFileName;
           return false;
           }
          
           qDebug(   ) << "Database was connected successfully: Connection name:"
           << defaultDatabase.connectionName(   );
          
           return true;
          }
          
      92  void DatabaseManager::disconnectDefaultDatabase(   ) {
           QSqlDatabase defaultDatabase = QSqlDatabase::database(   );
           if (  defaultDatabase.isOpen(   ) ) {
           defaultDatabase.close(   );
           qInfo(   ) << "Database disconnected";
           }
          }
          
     100  bool DatabaseManager::executeSqlFiles(  const QStringList &filePaths,  
     101   QSqlDatabase &database ) {
           ExecuteSqlFile executor(  database );
           for (  const QString &filePath : filePaths ) {
           qInfo(   ) << "Executing SQL file:" << filePath;
           if (  !executor(  filePath ) ) {
           qWarning(   ) << "Failed to execute SQL file:" << filePath;
           return false;
           }
           }
           return true;
          }

./core/db/execute_sql_file.cpp

       1  #include "execute_sql_file.h"
          
          #include <QRegularExpression>
          
       5  ExecuteSqlFile::ExecuteSqlFile(  QSqlDatabase &db )
           : db_(  db ),   delimiter_(  ";" ) { // Default delimiter is ";"
           // Constructor does not open/close the connection
          }
          
      10  bool ExecuteSqlFile::operator(   )(  const QString &filePath ) {
           // Ensure the database connection is open
           if (  !db_.isOpen(   ) ) {
           qWarning(   ) << "Database connection is not open";
           return false;
           }
          
           QFile file(  filePath );
           if (  !file.open(  QIODevice::ReadOnly | QIODevice::Text ) ) {
           qWarning(   ) << "Failed to open file:" << filePath;
           return false;
           }
          
           QTextStream in(  &file );
           QString sqlContent = in.readAll(   );
           file.close(   );
          
           QStringList statements = sqlContent.split(  "\n",   Qt::SkipEmptyParts );
          
           qInfo(   ) << "Executing SQL file:" << filePath;
          
           return processStatements(  statements );
          }
          
      34  bool ExecuteSqlFile::processStatements(  QStringList &statements ) {
           QString currentStatement;
          
           for (  QString &line : statements ) {
           qInfo(   ) << "Processing line:" << line;
           if (  !processLine(  line,   currentStatement ) ) {
           return false;
           }
           }
          
           // Execute any remaining statement
           if (  !currentStatement.isEmpty(   ) && !executeStatement(  currentStatement ) ) {
           return false;
           }
          
           return true;
          }
          
      52  bool ExecuteSqlFile::processLine(  QString &line,   QString &currentStatement ) {
           if (  !filterLine(  line ) ) {
           qInfo(   ) << "Line is empty after filtering";
           return true; // Line was empty after filtering
           }
          
           if (  processDelimiterChange(  line ) ) {
           qInfo(   ) << "Delimiter changed to:" << delimiter_;
           return true;
           }
          
           currentStatement += line + " ";
          
           auto delimiterIndex = currentStatement.lastIndexOf(  delimiter_ );
           if (  delimiterIndex == -1 ) {
           qInfo(   ) << "Delimiter not found,   current statement is not finished";
           return true;
           }
           qInfo(   ) << "Statement finished,   delimiter found at index:" << delimiterIndex;
          
           // Execute the current statement
           QString completedStatement =
           currentStatement.replace(  delimiter_,   ";" ).trimmed(   );
           if (  !executeStatement(  completedStatement ) ) {
           qWarning(   ) << "Failed to execute statement:" << completedStatement;
           return false; // Execution failed
           }
           qInfo(   ) << "Successfully executed statement:" << completedStatement;
          
           currentStatement.clear(   );
          
           return true;
          }
          
      86  bool ExecuteSqlFile::processDelimiterChange(  QString &line ) {
           QRegularExpression regex(  R"(  ^DELIMITER\s+(  \S+ ) )" );
           QRegularExpressionMatch match = regex.match(  line );
           if (  !match.hasMatch(   ) ) {
           return false;
           }
           delimiter_ = match.captured(  1 ); // Update the delimiter_
           return true;
          }
          
      96  bool ExecuteSqlFile::filterLine(  QString &line ) {
           line = line.section(  "--",   0,   0 ).trimmed(   ); // Remove comments and trim
           return !line.isEmpty(   ); // Return true if line is not empty
          }
          
     101  bool ExecuteSqlFile::executeStatement(  const QString &statement ) const {
           QSqlQuery query(  db_ );
           if (  !query.exec(  statement ) ) {
           qWarning(   ) << "Error:" << query.lastError(   ).text(   );
           return false;
           }
           return true;
          }

./core/repositories/repository_result.cpp

       1  #include "repository_result.h"
          
       3  RepositoryResult RepositoryResult::successResult(   ) { return {"",   true}; }
          
       5  RepositoryResult RepositoryResult::failureResult(  const QString &error ) {
           return {error,   false};
          }

./core/services/base_generator.cpp

          #include "base_generator.h"
          
          #include <QElapsedTimer>
          #include <QVariant>
          
          BaseGenerator::ShouldLogSuccess
       7  BaseGenerator::shouldLogAllSuccesses(   ) const noexcept {
           return [this](   ) { return true; };
          }
          
          BaseGenerator::ShouldLogSuccess
          BaseGenerator::shouldLogEveryNPercent(  int percent ) const noexcept {
           if (  percent <= 0 ) {
           return [this](   ) { return false; };
           }
          
           int oldLastPercentage = 0;
           return [this,   percent,   oldLastPercentage](   ) mutable {
           if (  oldLastPercentage == lastPercentage_ ) {
           return false;
           }
          
           oldLastPercentage = lastPercentage_;
           return !bool(  lastPercentage_ % percent );
           };
          }
          
          BaseGenerator::ShouldLogSuccess
          BaseGenerator::shouldLogFirstAndLastPercent(   ) const noexcept {
           return [this](   ) { return createdRecords_ == 1 || lastPercentage_ == 100; };
          }
          
          BaseGenerator::ShouldLogSuccess BaseGenerator::shouldLogEveryNSeconds(  
           int seconds,   const ShouldLogSuccess &performOtherwise ) const noexcept {
           QElapsedTimer timer;
           timer.start(   );
          
           return [this,   timer,   seconds,   performOtherwise](   ) mutable {
           if (  timer.elapsed(   ) >= seconds * 1000 ) {
           timer.restart(   );
           return true;
           }
           return performOtherwise(   );
           };
          }
          
          BaseGenerator::ShouldLogSuccess
          BaseGenerator::defaultShouldLog(  int percent,   int seconds ) const noexcept {
           ShouldLogSuccess first =
           shouldLogEveryNSeconds(  seconds,   shouldLogFirstAndLastPercent(   ) );
           ShouldLogSuccess second = shouldLogEveryNPercent(  percent );
          
           return [first,   second](   ) { return first(   ) || second(   ); };
          }
          
          void BaseGenerator::generateAll(  const ShouldLogSuccess &shouldLogSuccess ) {
           auto withoutLimit = [](   ) { return true; };
          
           generateImplementation(  withoutLimit,   shouldLogSuccess );
          }
          
          void BaseGenerator::generateByFillRatio(  
           double fillRatio,   const ShouldLogSuccess &shouldLogSuccess ) {
           fillRatio >= 1.0 ? fillRatio = 1.0 : fillRatio;
          
           qInfo(   ) << "Limit fill ratio of records to create: " << fillRatio;
          
           auto shouldContinue = [this,   fillRatio](   ) -> bool {
           double currentFillRatio = lastPercentage_ / 100.0;
           bool shouldContinue = currentFillRatio < fillRatio;
           if (  !shouldContinue ) {
           qInfo(   ) << "Limit of records to create reached";
           }
           return shouldContinue;
           };
          
           generateImplementation(  shouldContinue,   shouldLogSuccess );
          };
          
          void BaseGenerator::generateByRecordCount(  
           qsizetype totalRecordsToCreate,   const ShouldLogSuccess &shouldLogSuccess ) {
           int limit = std::min(  totalRecordsToCreate,   calculateRemainingCapacity(   ) );
          
           qInfo(   ) << "Limit of records to create: " << limit;
          
           auto shouldContinue = [this,   limit](   ) -> bool {
           bool shouldContinue = createdRecords_ < limit;
           if (  !shouldContinue ) {
           qInfo(   ) << "Limit of records to create reached";
           }
           return shouldContinue;
           };
          
           generateImplementation(  shouldContinue,   shouldLogSuccess );
          };
          
          void BaseGenerator::generateImplementation(  
           std::function<bool(   )> shouldContinue,  
           std::function<bool(   )> shouldLogSuccess ) {
           maximumRecords_ = calculateRemainingCapacity(   );
           qInfo(   ) << "The number of records available for creation:" << maximumRecords_;
          
           qInfo(   ).noquote(   ) << createTemplateMessage(   ) << "Started";
          
           while (  shouldContinue(   ) && prepareData(   ) ) {
           bool success;
           RepositoryResult result = createRecord(   );
          
           if (  !result.success ) {
           qWarning(   ).noquote(   ) << createTemplateMessage(   )
           << createFailureMessage(  result.errorMessage );
           continue;
           }
           ++createdRecords_;
           updatePercentage(   );
          
           if (  shouldLogSuccess(   ) ) {
           qInfo(   ).noquote(   ) << createTemplateMessage(   ) << createSuccessMessage(   );
           }
          
           clearStaleData(   );
           }
          
           qInfo(   ).noquote(   ) << createTemplateMessage(   ) << "Completed";
          }
          
          void BaseGenerator::updatePercentage(   ) {
           int numerator = maximumRecords_ - calculateRemainingCapacity(   );
           int newPercentage = std::floor(  numerator * 100.0 / maximumRecords_ );
           lastPercentage_ = std::max(  newPercentage,   lastPercentage_ );
          }
          
          QString BaseGenerator::createFailureMessage(  const QString &errorMessage ) const {
           QString failureMessage = "Failure: Error message:\n\t%1";
           return failureMessage.arg(  errorMessage );
          }
          
          QString BaseGenerator::createSuccessMessage(   ) const { return "Success"; }
          
          QString BaseGenerator::createTemplateMessage(   ) const {
           auto digitCount = [](  int number ) {
           if (  number == 0 ) {
           return 1;
           }
           return static_cast<int>(  std::log10(  std::abs(  number ) ) ) + 1;
           };
          
           int maximumRecordsValue = calculateRemainingCapacity(   );
           int newFieldWidth =
           std::max(  digitCount(  createdRecords_ ),   digitCount(  maximumRecordsValue ) );
           fieldWidth_ = std::max(  fieldWidth_,   newFieldWidth );
          
           static constexpr int progressBarSize = 25;
           static constexpr int progressBarStep = 100 / progressBarSize;
           const QString progressBar(  lastPercentage_ / progressBarStep,   QChar(  '#' ) );
          
           return QString(  "Creating records of entity %1 [ %2 <~ %3] [%4]:" )
           .arg(  entityName_ )
           .arg(  createdRecords_,   fieldWidth_,   10,   QChar(  ' ' ) )
           .arg(  maximumRecordsValue,   fieldWidth_,   10,   QChar(  ' ' ) )
           .arg(  progressBar.leftJustified(  progressBarSize,   QChar(  ' ' ) ) );
          };

./core/services/date_time_generator.cpp

       1  #include "date_time_generator.h"
          
       3  DateTimeGenerator::DateTimeGenerator(  const QPair<qint64,   qint64> &dayRange,  
       4   const QPair<QTime,   QTime> &timeRange )
           : dayRange_(  dayRange ),   timeRange_(  timeRange ) {
           auto &[lowestDate,   highestDate] = dayRange_;
           if (  lowestDate < 0 || highestDate < 0 ) {
           qFatal(   ) << "Lowest or highest date is negative";
           }
          
           if (  lowestDate > highestDate ) {
           qCritical(   ) << "Lowest date is greater than the highest date,   values will "
           "be swapped";
           std::swap(  lowestDate,   highestDate );
           }
          
           auto &[lowestTime,   highestTime] = timeRange_;
           if (  !lowestTime.isValid(   ) || !highestTime.isValid(   ) ) {
           qFatal(   ) << "Lowest or highest time is not valid";
           }
          
           if (  lowestTime > highestTime ) {
           qCritical(   ) << "Lowest time is greater than the highest time,   values will "
           "be swapped";
           std::swap(  lowestTime,   highestTime );
           }
          }
          
      29  QDateTime DateTimeGenerator::generate(  const QDateTime &upperBoundInclusive ) {
           auto [lowestDate,   highestDate] = dayRange_;
           auto [lowestTime,   highestTime] = timeRange_;
           const QDate &maxDateValue = upperBoundInclusive.date(   );
           const qint64 maxTimeValueMs =
           upperBoundInclusive.time(   ).msecsSinceStartOfDay(   );
          
           QDate epochDate = QDateTime::fromMSecsSinceEpoch(  0 ).date(   );
           qint64 daysFromEpochDateToMaxDateValue = epochDate.daysTo(  maxDateValue );
           highestDate = std::min(  highestDate,   daysFromEpochDateToMaxDateValue );
          
           qint64 minusDeltaDays = -bounded(  lowestDate,   highestDate );
           QDate dateSoldAt = maxDateValue.addDays(  minusDeltaDays );
          
           qint64 lowestTimeMs = lowestTime.msecsSinceStartOfDay(   );
           qint64 highestTimeMs = highestTime.msecsSinceStartOfDay(   ) + 1;
           qint64 msecsSinceStartOfDay = bounded(  lowestTimeMs,   highestTimeMs );
          
           if (  msecsSinceStartOfDay > maxTimeValueMs ) {
           msecsSinceStartOfDay = maxTimeValueMs;
           }
          
           QTime timeSoldAt = QTime::fromMSecsSinceStartOfDay(  msecsSinceStartOfDay );
          
           return {std::move(  dateSoldAt ),   std::move(  timeSoldAt )};
          }
          
      56  QDateTime DateTimeGenerator::generate(  const QDateTime &lowerBoundInclusive,  
      57   const QDateTime &upperBoundInclusive ) {
           if (  lowerBoundInclusive > upperBoundInclusive ) {
           qFatal(   ) << "Lower bound date is greater than the upper bound date";
           }
          
           const QDate &minDateValue = lowerBoundInclusive.date(   );
           const QTime &minTimeValue = lowerBoundInclusive.time(   );
          
           QDateTime unboundedLower = generate(  upperBoundInclusive );
          
           QDate boundedDate = unboundedLower.date(   ) <= minDateValue
           ? minDateValue
           : unboundedLower.date(   );
           QTime boundedTime = unboundedLower.time(   ) <= minTimeValue
           ? minTimeValue
           : unboundedLower.time(   );
          
           return {std::move(  boundedDate ),   std::move(  boundedTime )};
          }

./core/services/model_manager.cpp

       1  #include "model_manager.h"
          
          #include "genres_model.h"
          #include "halls_model.h"
          #include "movies_model.h"
          #include "refunds_model.h"
          #include "schema_metadata.h"
          #include "sessions_model.h"
          #include "tickets_model.h"
          
      11  ModelManager::ModelManager(   ) {
           // Get connection to the database
           database_ = QSqlDatabase::database(   );
          
           if (  !database_.isOpen(   ) ) {
           qDebug(   ) << "Database is not open. Cannot initialize models";
           return;
           }
          
           initialize(   );
          }
          
      23  ModelManager::~ModelManager(   ) {
           qInfo(   ) << "Models manager is being destroyed";
          }
          
      27  ModelManager &ModelManager::instance(   ) {
           static ModelManager instance;
           return instance;
          }
          
      32  QStringList ModelManager::registeredModelTableNames(   ) const {
           return models_.keys(   );
          }
          
          QStringList
      37  ModelManager::registeredModelTableNames(  QString databaseTableName ) const {
           QStringList registeredModelTableNames;
           for (  const auto &modelTableName : models_.keys(   ) ) {
           QSqlTableModel *currentModel = getModelAs<QSqlTableModel>(  modelTableName );
           QString currentDatabaseTableName = currentModel->tableName(   );
           if (  currentDatabaseTableName == databaseTableName ) {
           registeredModelTableNames.append(  modelTableName );
           }
           }
           return registeredModelTableNames;
          }
          
      49  void ModelManager::initialize(   ) {
           // clang-format off
          
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           registerModel<GenresModel>(  
           metadata.modelTableName(  "genres" ),   database_ );
          
           registerModel<HallsModel>(  
           metadata.modelTableName(  "halls" ),   database_ );
          
           registerModel<MoviesModel>(  
           metadata.modelTableName(  "movies" ),   database_ );
          
           registerModel<RefundsModel>(  
           metadata.modelTableName(  "refunds" ),   database_ );
          
           registerModel<SessionsModel>(  
           metadata.modelTableName(  "sessions" ),   database_ );
          
           registerModel<TicketsModel>(  
           metadata.modelTableName(  "tickets" ),   database_ );
          
           // clang-format on
          }
          
      75  QSqlTableModel *ModelManager::getModel(  const QString &modelTableName ) const {
           QSqlTableModel *model = models_.value(  modelTableName,   nullptr );
           if (  model == nullptr ) {
           qDebug(   ) << "Model" << modelTableName << "is not registered";
           return nullptr;
           }
           qDebug(   ) << "Getting model" << modelTableName << "with value" << model;
          
           return model;
          }
          
      86  void ModelManager::resetFilter(  const QString &modelTableName ) {
           BaseSqlTableModel *model = getModelAs<BaseSqlTableModel>(  modelTableName );
           model->resetFilter(   );
          }
          
      91  void ModelManager::setMultiColumnFilter(  const QString &modelTableName,  
      92   const QString &filter ) {
           BaseSqlTableModel *model = getModelAs<BaseSqlTableModel>(  modelTableName );
           model->setMultiColumnFilter(  filter );
          }

./core/services/refund_generator.cpp

       1  #include "refund_generator.h"
          
          #include "create_refund_request.h"
          #include "refunds_repository.h"
          #include "repository_manager.h"
          #include "repository_result.h"
          #include "schema_metadata.h"
          #include "session_dto.h"
          #include "sessions_repository.h"
          #include "ticket_dto.h"
          #include "tickets_repository.h"
          #include <QRandomGenerator>
          
      14  RefundGenerator::RefundGenerator(  const QPair<double,   double> &refundRatioRange,  
      15   const QPair<qint64,   qint64> &dayRange,  
      16   const QPair<QTime,   QTime> &timeRange )
           : BaseGenerator(  "Refund" ),   DateTimeGenerator(  dayRange,   timeRange ),  
           refundRatioRange_(  refundRatioRange ) {
           auto &[lowest,   highest] = refundRatioRange_;
           if (  lowest > highest ) {
           qCritical(   ) << "Lowest refund ratio is greater than the highest refund,   "
           "values will be swapped";
           std::swap(  lowest,   highest );
           }
          
           initializeRepositories(   ); // First
           initializeTicketIdToCacheValueMap(   ); // After initializing
           // repositories
           initializeRemainingCapacity(   ); // After initializing ticketIdToCacheValue
          }
          
      32  void RefundGenerator::initializeRepositories(   ) {
           RepositoryManager &repositoryManager = RepositoryManager::instance(   );
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           ticketsRepository_ = repositoryManager.getRepositoryAs<TicketsRepository>(  
           metadata.repositoryTableName(  "tickets" ) );
           refundsRepository_ = repositoryManager.getRepositoryAs<RefundsRepository>(  
           metadata.repositoryTableName(  "refunds" ) );
           sessionsRepository_ = repositoryManager.getRepositoryAs<SessionsRepository>(  
           metadata.repositoryTableName(  "sessions" ) );
          
           if (  !bool(  ticketsRepository_ && refundsRepository_ && sessionsRepository_ ) ) {
           qFatal(   ) << "Failed to initialize repositories";
           }
          }
          
      48  void RefundGenerator::initializeTicketIdToCacheValueMap(   ) {
           QVector<SessionDTO> sessions;
           RepositoryResult resultGetAllSessions =
           sessionsRepository_->getAllSessions(  sessions );
           if (  !resultGetAllSessions.success ) {
           qFatal(   ) << "Failed to retrieve sessions";
           }
          
           QMap<qint32 /* sessionId */,  
           QPair<QDateTime /* beginAt */,   double /* ticketPrice */>>
           sessionIdToBeginAtAndTicketPrice;
          
           for (  const SessionDTO &session : sessions ) {
           sessionIdToBeginAtAndTicketPrice[session.sessionId(   )] =
           qMakePair(  session.beginAt(   ),   session.ticketPrice(   ) );
           }
          
           QVector<TicketDTO> tickets;
           RepositoryResult resultGetAllTickets =
           ticketsRepository_->getAllTickets(  tickets );
          
           if (  !resultGetAllTickets.success ) {
           qFatal(   ) << "Failed to retrieve tickets";
           }
          
           for (  const auto &ticket : tickets ) {
           const auto &[beginAt,   ticketPrice] =
           sessionIdToBeginAtAndTicketPrice[ticket.sessionId(   )];
           CacheValue cacheValue{beginAt,   ticket.soldAt(   ),   ticketPrice};
          
           if (  calculateDurationMs(  cacheValue ) < 2 ) {
           continue;
           }
          
           ticketIdToCacheValue_[ticket.ticketId(   )] = cacheValue;
           }
          }
          
          QString
      87  RefundGenerator::createFailureMessage(  const QString &errorMessage ) const {
           const QString dateTimeFormat = "yyyy-MM-dd hh:mm:ss.zzz";
           QString additionalInfo =
           QString(  "Additional info: Ticket id: %1 Refund amount: %2 Sold at: %3 "
           "Refund at: %4 Begin at: %5" )
           .arg(  reservationContext_.ticketId )
           .arg(  reservationContext_.refundAmount )
           .arg(  reservationContext_.soldAt.toString(  dateTimeFormat ) )
           .arg(  reservationContext_.refundAt.toString(  dateTimeFormat ) )
           .arg(  reservationContext_.beginAt.toString(  dateTimeFormat ) );
           QString failureMessage = "Failure:\n\t%1\n\tError message: %2";
           return failureMessage.arg(  additionalInfo ).arg(  errorMessage );
          }
          
     101  QString RefundGenerator::createSuccessMessage(   ) const {
           return QString(  "Success: EId: %1 RAm: %2 RAt: %3" )
           .arg(  reservationContext_.ticketId )
           .arg(  reservationContext_.refundAmount )
           .arg(  reservationContext_.refundAt.toString(  "yyyy-MM-dd hh:mm:ss" ) );
          }
          
     108  qsizetype RefundGenerator::calculateRemainingCapacity(   ) const {
           return remainingCapacity_;
          }
          
     112  RepositoryResult RefundGenerator::createRecord(   ) {
           CreateRefundRequest request;
           request.setRefundAmount(  reservationContext_.refundAmount );
           request.setRefundAt(  reservationContext_.refundAt );
           request.setTicketId(  reservationContext_.ticketId );
          
           RepositoryResult result = refundsRepository_->createRefund(  request );
          
           if (  result.success ) {
           --remainingCapacity_;
           }
          
           return result;
          }
          
     127  bool RefundGenerator::prepareData(   ) {
           if (  ticketIdToCacheValue_.isEmpty(   ) ) {
           qInfo(   ) << "No tickets to refund";
           return false;
           }
          
           qsizetype ticketIdIndex = bounded<qsizetype>(  0,   ticketIdToCacheValue_.size(   ) );
           auto ticketIdIt = ticketIdToCacheValue_.cbegin(   );
           std::advance(  ticketIdIt,   ticketIdIndex );
          
           qint32 ticketId = ticketIdIt.key(   );
           CacheValue cacheValue = ticketIdIt.value(   );
          
           constexpr qint32 fiveMinutesMs = 5 * 1000 * 60;
           qint64 deltaBoundMs = calculateDurationMs(  cacheValue ) / 2;
           if (  deltaBoundMs > fiveMinutesMs ) {
           deltaBoundMs = fiveMinutesMs;
           }
          
           const QDateTime refundAt =
           // SoldAt < RefundAt < BeginAt
           // SoldAt + n minute <= RefundAt <= BeginAt - n minute
           generate(  cacheValue.soldAt.addMSecs(  +deltaBoundMs ),  
           cacheValue.beginAt.addMSecs(  -deltaBoundMs ) );
          
           const auto [refundRatioLowest,   refundRatioHighest] = refundRatioRange_;
           int lowest = refundRatioLowest * 100;
           int highest = refundRatioHighest * 100;
           int numerator = bounded(  lowest,   highest );
           double refundAmount = std::floor(  numerator * cacheValue.ticketPrice ) / 100.0;
          
           ReservationContext context{cacheValue.beginAt,   refundAt,   cacheValue.soldAt,  
           refundAmount,   ticketId};
           reservationContext_ = std::move(  context );
          
           return true;
          }
          
     165  void RefundGenerator::clearStaleData(   ) {
           qint32 ticketId = reservationContext_.ticketId;
           ticketIdToCacheValue_.remove(  ticketId );
          }

./core/services/relation_id_config.cpp

       1  #include "relation_id_config.h"
          
       3  RelationIdConfig::RelationIdConfig(  const QString &translentTableName,  
       4   const QString &modelTableName,  
       5   const QString &primaryIdColumnName,  
       6   const QString &repositoryTableName ) noexcept
           : translatedTableName_(  translentTableName ),   modelTableName_(  modelTableName ),  
           primaryIdColumnName_(  primaryIdColumnName ),  
           repositoryTableName_(  repositoryTableName ) {}

./core/services/repository_manager.cpp

       1  #include "repository_manager.h"
          
          #include "genres_repository.h"
          #include "halls_repository.h"
          #include "movies_repository.h"
          #include "refunds_repository.h"
          #include "roles_repository.h"
          #include "schema_metadata.h"
          #include "sessions_repository.h"
          #include "sessions_statistics_repository.h"
          #include "tickets_repository.h"
          #include "users_repository.h"
          
      14  RepositoryManager::RepositoryManager(   ) {
           database_ = QSqlDatabase::database(   );
           if (  !database_.isOpen(   ) ) {
           qDebug(   ) << "Database is not open. Cannot initialize repositories";
           return;
           }
          
           initialize(   );
          }
          
      24  RepositoryManager::~RepositoryManager(   ) {
           qInfo(   ) << "Repositories manager is being destroyed";
          }
          
      28  RepositoryManager &RepositoryManager::instance(   ) {
           static RepositoryManager instance;
           return instance;
          }
          
      33  void RepositoryManager::initialize(   ) {
           // clang-format off
          
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           // Tables
           registerRepository<GenresRepository>(  
           metadata.repositoryTableName(  "genres" ) );
          
           registerRepository<HallsRepository>(  
           metadata.repositoryTableName(  "halls" ) );
          
           registerRepository<MoviesRepository>(  
           metadata.repositoryTableName(  "movies" ) );
          
           registerRepository<RefundsRepository>(  
           metadata.repositoryTableName(  "refunds" ) );
          
           registerRepository<SessionsRepository>(  
           metadata.repositoryTableName(  "sessions" ) );
          
           registerRepository<TicketsRepository>(  
           metadata.repositoryTableName(  "tickets" ) );
          
           registerRepository<UsersRepository>(  
           metadata.repositoryTableName(  "users" ) );
          
           registerRepository<RolesRepository>(  
           metadata.repositoryTableName(  "roles" ) );
          
           // Views
           registerRepository<SessionsStatisticsRepository>(  
           metadata.repositoryTableName(  "sessions_statistics" ) );
          
           // clang-format on
          }
          
          RepositoryInterface *
      71  RepositoryManager::getRepository(  const QString &repositoryTableName ) const {
           RepositoryInterface *repository =
           repositories_.value(  repositoryTableName,   nullptr );
          
           if (  repository == nullptr ) {
           qDebug(   ) << "Repository" << repositoryTableName << "is not registered";
           return nullptr;
           }
           qDebug(   ) << "Getting model" << repositoryTableName << "with value"
           << repository;
          
           return repository;
          }

./core/services/schema_metadata.cpp

       1  #include "schema_metadata.h"
          
          #include <QDebug>
          
          QMap<QString,   RelationIdConfig> SchemaMetadata::databaseTableNameToConfigMap_;
          std::once_flag SchemaMetadata::initFlag;
          
       8  void SchemaMetadata::initialize(   ) {
           qInfo(   ) << "Initializing schema metadata...";
          
           RelationIdConfig genreConfig;
           genreConfig.setPrimaryIdColumnName(  "genre_id" );
           genreConfig.setTranslatedTableName(  tr(  "Genres" ) );
           genreConfig.setModelTableName(  "GenresModel" );
           genreConfig.setRepositoryTableName(  "GenresRepository" );
           databaseTableNameToConfigMap_.insert(  "genres",   genreConfig );
          
           RelationIdConfig hallConfig;
           hallConfig.setPrimaryIdColumnName(  "hall_id" );
           hallConfig.setTranslatedTableName(  tr(  "Halls" ) );
           hallConfig.setModelTableName(  "HallsModel" );
           hallConfig.setRepositoryTableName(  "HallsRepository" );
           databaseTableNameToConfigMap_.insert(  "halls",   hallConfig );
          
           RelationIdConfig movieConfig;
           movieConfig.setPrimaryIdColumnName(  "movie_id" );
           movieConfig.setTranslatedTableName(  tr(  "Movies" ) );
           movieConfig.setModelTableName(  "MoviesModel" );
           movieConfig.setRepositoryTableName(  "MoviesRepository" );
           databaseTableNameToConfigMap_.insert(  "movies",   movieConfig );
          
           RelationIdConfig refundConfig;
           refundConfig.setPrimaryIdColumnName(  "refund_id" );
           refundConfig.setTranslatedTableName(  tr(  "Refunds" ) );
           refundConfig.setModelTableName(  "RefundsModel" );
           refundConfig.setRepositoryTableName(  "RefundsRepository" );
           databaseTableNameToConfigMap_.insert(  "refunds",   refundConfig );
          
           RelationIdConfig sessionConfig;
           sessionConfig.setPrimaryIdColumnName(  "session_id" );
           sessionConfig.setTranslatedTableName(  tr(  "Sessions" ) );
           sessionConfig.setModelTableName(  "SessionsModel" );
           sessionConfig.setRepositoryTableName(  "SessionsRepository" );
           databaseTableNameToConfigMap_.insert(  "sessions",   sessionConfig );
          
           RelationIdConfig ticketConfig;
           ticketConfig.setPrimaryIdColumnName(  "ticket_id" );
           ticketConfig.setTranslatedTableName(  tr(  "Tickets" ) );
           ticketConfig.setModelTableName(  "TicketsModel" );
           ticketConfig.setRepositoryTableName(  "TicketsRepository" );
           databaseTableNameToConfigMap_.insert(  "tickets",   ticketConfig );
          
           RelationIdConfig userConfig;
           userConfig.setPrimaryIdColumnName(  "user_id" );
           userConfig.setTranslatedTableName(  tr(  "Users" ) );
           userConfig.setRepositoryTableName(  "UsersRepository" );
           databaseTableNameToConfigMap_.insert(  "users",   userConfig );
          
           RelationIdConfig roleConfig;
           roleConfig.setPrimaryIdColumnName(  "role_id" );
           roleConfig.setTranslatedTableName(  tr(  "Roles" ) );
           roleConfig.setRepositoryTableName(  "RolesRepository" );
           databaseTableNameToConfigMap_.insert(  "roles",   roleConfig );
          
           RelationIdConfig tableInfoConfig;
           tableInfoConfig.setTranslatedTableName(  tr(  "Sessions statistics" ) );
           tableInfoConfig.setRepositoryTableName(  "SessionsStatisticsRepository" );
           databaseTableNameToConfigMap_.insert(  "sessions_statistics",   tableInfoConfig );
          
           qInfo(   ) << "Schema metadata initialized successfully";
          }
          
      73  SchemaMetadata::SchemaMetadata(   ) noexcept {
           std::call_once(  initFlag,   &SchemaMetadata::initialize );
          }
          
      77  SchemaMetadata::SchemaMetadata(  const SchemaMetadata & ) {}
          
      79  SchemaMetadata &SchemaMetadata::operator=(  const SchemaMetadata & ) {
           return *this;
          }
          
      83  SchemaMetadata SchemaMetadata::defaultSchema(   ) { return SchemaMetadata(   ); }
          
      85  QString SchemaMetadata::modelTableName(  const QString &databaseTableName ) {
           QString modelTableName = relationIdConfig(  databaseTableName ).modelTableName(   );
          
           if (  modelTableName.isEmpty(   ) ) {
           qFatal(   ) << "Model table name is empty: Database table name:"
           << databaseTableName;
           }
          
           return modelTableName;
          }
          
      96  QString SchemaMetadata::primaryIdColumnName(  const QString &databaseTableName ) {
           QString primaryIdColumnName =
           relationIdConfig(  databaseTableName ).primaryIdColumnName(   );
          
           if (  primaryIdColumnName.isEmpty(   ) ) {
           qFatal(   ) << "Primary id column name is empty: Database table name:"
           << databaseTableName;
           }
          
           return primaryIdColumnName;
          }
          
     108  QString SchemaMetadata::repositoryTableName(  const QString &databaseTableName ) {
           QString repositoryTableName =
           relationIdConfig(  databaseTableName ).repositoryTableName(   );
          
           if (  repositoryTableName.isEmpty(   ) ) {
           qFatal(   ) << "Repository table name is empty: Database table name:"
           << databaseTableName;
           }
          
           return repositoryTableName;
          }
          
     120  QString SchemaMetadata::translatedTableName(  const QString &databaseTableName ) {
           QString translatedTableName =
           relationIdConfig(  databaseTableName ).translatedTableName(   );
          
           if (  translatedTableName.isEmpty(   ) ) {
           qFatal(   ) << "Translated table name is empty: Database table name:"
           << databaseTableName;
           }
          
           return translatedTableName;
          }
          
          RelationIdConfig
     133  SchemaMetadata::relationIdConfig(  const QString &databaseTableName ) {
           RelationIdConfig config =
           databaseTableNameToConfigMap_.value(  databaseTableName );
           return config;
          }

./core/services/session_generator.cpp

       1  #include "session_generator.h"
          
          #include "create_session_request.h"
          #include "hall_dto.h"
          #include "halls_repository.h"
          #include "movie_dto.h"
          #include "movies_repository.h"
          #include "repository_manager.h"
          #include "repository_result.h"
          #include "schema_metadata.h"
          #include "sessions_repository.h"
          
      13  SessionGenerator::SessionGenerator(  
      14   const ReservationManager &reservationReference )
           : BaseGenerator(  "Session" ),   reservationReference_(  reservationReference ) {
           initializeRepositories(   ); // First
          
           qint32 extraTime = 45; // Extra time for cleaning the room
           initializeMovieIdToDurationMap(  extraTime ); // After initializing repositories
          
           initializeBounds(   ); // After initializing movieIdToDuration
           initializeDayManager(   ); // After initializing bounds
           initializeHallIdToReservationManagerMap(   ); // After initializing dayManager
          }
          
      26  void SessionGenerator::initializeRepositories(   ) {
           RepositoryManager &repositoryManager = RepositoryManager::instance(   );
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           moviesRepository_ = repositoryManager.getRepositoryAs<MoviesRepository>(  
           metadata.repositoryTableName(  "movies" ) );
           hallsRepository_ = repositoryManager.getRepositoryAs<HallsRepository>(  
           metadata.repositoryTableName(  "halls" ) );
           sessionsRepository_ = repositoryManager.getRepositoryAs<SessionsRepository>(  
           metadata.repositoryTableName(  "sessions" ) );
          
           if (  !bool(  moviesRepository_ && hallsRepository_ && sessionsRepository_ ) ) {
           qFatal(   ) << "Failed to initialize repositories";
           }
          }
          
      42  void SessionGenerator::initializeMovieIdToDurationMap(  
      43   qint32 additionalMinutes ) {
           QVector<MovieDTO> movies;
           RepositoryResult result = moviesRepository_->getAllMovies(  movies );
          
           if (  !result.success ) {
           qFatal(   ) << "Failed to retrieve movies";
           }
          
           for (  const MovieDTO &movie : movies ) {
           movieIdToDurationMinutes_[movie.movieId(   )] =
           movie.duration(   ) + additionalMinutes;
           }
          
           if (  movieIdToDurationMinutes_.isEmpty(   ) ) {
           qFatal(   ) << "Failed to initialize movieIdToDuration";
           }
          
           qInfo(   ) << "Number of movies:" << movieIdToDurationMinutes_.size(   );
          }
          
      63  void SessionGenerator::initializeBounds(   ) {
           qint32 lowerBoundReservationMin = *std::min_element(  
           movieIdToDurationMinutes_.cbegin(   ),   movieIdToDurationMinutes_.cend(   ) );
           lowerBoundReservationMs_ = lowerBoundReservationMin * 60'000; // 60 * 1000
          
           qint32 upperBoundReservationMin = *std::max_element(  
           movieIdToDurationMinutes_.cbegin(   ),   movieIdToDurationMinutes_.cend(   ) );
           upperBoundReservationMs_ = upperBoundReservationMin * 60'000; // 60 * 1000
          
      72   qInfo(   ) << "Lower bound reservation in Ms:" << lowerBoundReservationMs_;
           qInfo(   ) << "Upper bound reservation in Ms:" << upperBoundReservationMs_;
          }
          
          void SessionGenerator::initializeDayManager(   ) {
           QTime minDuration = QTime::fromMSecsSinceStartOfDay(  lowerBoundReservationMs_ );
           DayManager dayManager(  reservationReference_.reference(   ) );
           dayManager.setMinDuration(  minDuration );
           reservationReference_.setReference(  dayManager );
          
           if (  !reservationReference_.reference(   ).minDuration(   ).isValid(   ) ) {
           qFatal(   ) << "Failed to initialize dayManager";
           }
          
           qInfo(   ) << "Day manager: Min duration:"
           << reservationReference_.reference(   ).minDuration(   );
      88   qInfo(   ) << "Day manager: Slot count:"
           << reservationReference_.reference(   ).slotCount(   );
          }
          
          void SessionGenerator::initializeHallIdToReservationManagerMap(   ) {
           QVector<HallDTO> halls;
           RepositoryResult result = hallsRepository_->getAllHalls(  halls );
          
           if (  !result.success ) {
           qFatal(   ) << "Failed to retrieve halls";
           }
          
           for (  const HallDTO &hall : halls ) {
           hallIdToReservationManager_[hall.hallId(   )] = reservationReference_;
           }
          
           if (  hallIdToReservationManager_.isEmpty(   ) ) {
           qFatal(   ) << "Failed to initialize hallIdToReservationManager";
           }
     107  
           qInfo(   ) << "Number of halls:" << hallIdToReservationManager_.size(   );
          }
          
          RepositoryResult SessionGenerator::createRecord(   ) {
           CreateSessionRequest request;
           request.setBeginAt(  reservationContext_.beginAt );
           request.setHallId(  reservationContext_.hallId );
           request.setMovieId(  reservationContext_.movieId );
           request.setTicketPrice(  reservationContext_.ticketPrice );
          
           RepositoryResult result = sessionsRepository_->createSession(  request );
          
     120   return result;
          }
          
          QString
          SessionGenerator::createFailureMessage(  const QString &errorMessage ) const {
           QString additionalInfo =
           QString(  "Additional info: Hall id: %1 Begin at: %2" )
           .arg(  reservationContext_.hallId )
           .arg(  reservationContext_.beginAt.toString(  "yyyy-MM-dd hh:mm:ss" ) );
     129   QString failureMessage = "Failure:\n\t%1\n\tError message: %2";
           return failureMessage.arg(  additionalInfo ).arg(  errorMessage );
          }
          
          QString SessionGenerator::createSuccessMessage(   ) const {
           qint32 hallId = reservationContext_.hallId;
           const ReservationManager &manager = hallIdToReservationManager_.value(  hallId );
           QTime duration = calculateDuration(  reservationContext_ );
          
           // return QString(  "Success: TAD: %1 TAS: %2 EId: %3 AD: %4 AS: %5 CR: %6 B: %7
           // D: %8" )
           return QString(  "Success: TAD: %1 TAS: %2 EId: %3 AD: %4 AS: %5 CR: %6" )
           .arg(  calculateAvailableDaysCount(   ) ) // Total available days
           .arg(  calculateAvailableSlotsCount(   ) ) // Total available slots
           .arg(  hallId ) // Entity id
           .arg(  manager.calculateAvailableDaysCount(   ) ) // Available days for the
           // current hall
           .arg(  manager.calculateAvailableSlotsCount(   ) ) // Available slots for the
           // current hall
           .arg(  manager.createdRecords(   ) ); // Created records for the
           // current hall
           // .arg(  reservationContext_.beginAt.toString(   //
           // "yyyy-MM-dd hh:mm:ss" ) ) // Begin
     152   // // of created record
           // .arg(  duration.toString(  "hh:mm:ss" ) ); // Duration of created record
          }
          
          qsizetype SessionGenerator::calculateRemainingCapacity(   ) const {
           // Maximum number of sessions that can be created
           return std::accumulate(  hallIdToReservationManager_.cbegin(   ),  
           hallIdToReservationManager_.cend(   ),   0,  
           [&](  qsizetype sum,   const ReservationManager &manager ) {
           return sum + manager.calculatePossibleReservations(  
     162   lowerBoundReservationMs_ );
           } );
          };
          
          bool SessionGenerator::prepareData(   ) {
           std::random_device rd;
           std::mt19937 g{rd(   )};
          
           QList<qint32> hallIds = hallIdToReservationManager_.keys(   );
           std::shuffle(  hallIds.begin(   ),   hallIds.end(   ),   g );
           QList<qint32> movieIds = movieIdToDurationMinutes_.keys(   );
           std::shuffle(  movieIds.begin(   ),   movieIds.end(   ),   g );
          
           for (  qint32 hallId : hallIds ) {
           if (  prepareData(  hallId,   movieIds ) ) {
           return true;
           }
           }
           qWarning(   ) << "No available halls";
     181  
     182   return false;
          }
          
          bool SessionGenerator::prepareData(  qint32 hallId,  
           const QList<qint32> &movieIds ) {
           for (  qint32 movieId : movieIds ) {
           ReservationManager &manager = hallIdToReservationManager_[hallId];
          
           auto [beginDateTime,   endTime] = manager.reserve(  duration(  movieId ) );
           if (  !beginDateTime.isValid(   ) || !endTime.isValid(   ) ) {
           continue;
           }
          
           const QDate epochDate = QDateTime::fromMSecsSinceEpoch(  0 ).date(   );
           const qint64 deltaDays = epochDate.daysTo(  QDate(  2025,   1,   1 ) );
           const QDate newBeginDate = beginDateTime.date(   ).addDays(  deltaDays );
           const QTime beginTime = beginDateTime.time(   );
          
           reservationContext_ = ReservationContext{
           QDateTime(  std::move(  newBeginDate ),   std::move(  beginTime ) ),  
           std::move(  endTime ),  
           bounded(  10'000,   50'000 ) / 100.0,  
           hallId,  
           movieId,  
           };
          
           return true;
           }
          
           qCritical(   )
           << "SessionGenerator::prepareData: No available movies for hall with id"
           << hallId;
     214  
           return false;
          }
          
          QTime SessionGenerator::duration(  qint32 movieId ) const {
           qint32 durationMin = movieIdToDurationMinutes_[movieId];
           qint64 durationMs = durationMin * 60'000; // 60 * 1000
           QTime durationTime = QTime::fromMSecsSinceStartOfDay(  durationMs );
           return durationTime;
          }
          
          qsizetype SessionGenerator::calculateAvailableDaysCount(   ) const {
           return std::accumulate(  hallIdToReservationManager_.cbegin(   ),  
           hallIdToReservationManager_.cend(   ),   0,  
           [&](  int sum,   const ReservationManager &manager ) {
           return sum + manager.calculateAvailableDaysCount(   );
           } );
          }
          
          qsizetype SessionGenerator::calculateAvailableSlotsCount(   ) const {
           return std::accumulate(  hallIdToReservationManager_.cbegin(   ),  
           hallIdToReservationManager_.cend(   ),   0,  
           [&](  int sum,   const ReservationManager &manager ) {
           return sum + manager.calculateAvailableSlotsCount(   );
           } );
          }
          
          QTime SessionGenerator::calculateDuration(  ReservationContext context ) const {
           QTime beginTime = context.beginAt.time(   );
           QTime endTime = context.endTime;
           qint64 durationMs = beginTime.msecsTo(  endTime );
           return QTime::fromMSecsSinceStartOfDay(  durationMs );
          }
          
          void SessionGenerator::clearStaleData(   ) {
           qint64 hallId = reservationContext_.hallId;
           if (  hallIdToReservationManager_[hallId].calculateAvailableDaysCount(   ) == 0 ) {
           qWarning(   ) << "No available days for hall" << hallId;
           hallIdToReservationManager_.remove(  hallId );
           }
          }

./core/services/ticket_generator.cpp

       1  #include "ticket_generator.h"
          
          #include "create_ticket_request.h"
          #include "hall_dto.h"
          #include "halls_repository.h"
          #include "repository_manager.h"
          #include "repository_result.h"
          #include "schema_metadata.h"
          #include "session_dto.h"
          #include "sessions_repository.h"
          #include "tickets_repository.h"
          #include <QDebug>
          #include <QStack>
          #include <numeric>
          
          #include "repository_result.h"
          
          std::random_device TicketGenerator::rd_;
      19  std::mt19937 TicketGenerator::generator_(  rd_(   ) );
          
      21  TicketGenerator::TicketGenerator(  
      22   const QPair<qint32,   qint32> &dayRange,   const QPair<QTime,   QTime> &timeRange,  
      23   const QPair<double,   double> &fillRatioPerSessionRange )
           : BaseGenerator(  "Ticket" ),   DateTimeGenerator(  dayRange,   timeRange ) {
           if (  !setFillRatioPerSessionRange(  fillRatioPerSessionRange ) ) {
           qFatal(   ) << "Failed to set fill ratio per session range";
           }
          
           initializeRepositories(   );
           initializeSessionIdToAvailableSeatMap(   ); // After initializing repositories
           initializeSessionIdToBeginAtMap(   ); // After initializing repositories
           initializeRemainingCapacity(   ); // After initializing sessionToAvailableSeats
          
           qInfo(   ) << "Number of sessions:" << sessionIdToAvailableSeatNumbers_.size(   );
          }
          
      37  void TicketGenerator::initializeRepositories(   ) {
           RepositoryManager &repositoryManager = RepositoryManager::instance(   );
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           hallsRepository_ = repositoryManager.getRepositoryAs<HallsRepository>(  
           metadata.repositoryTableName(  "halls" ) );
           sessionsRepository_ = repositoryManager.getRepositoryAs<SessionsRepository>(  
           metadata.repositoryTableName(  "sessions" ) );
           ticketsRepository_ = repositoryManager.getRepositoryAs<TicketsRepository>(  
           metadata.repositoryTableName(  "tickets" ) );
          
           if (  !bool(  ticketsRepository_ && sessionsRepository_ ) ) {
           qFatal(   ) << "Failed to initialize repositories";
           }
          }
          
      53  void TicketGenerator::initializeSessionIdToAvailableSeatMap(   ) {
           QVector<HallDTO> halls;
           RepositoryResult resultGetAllHalls = hallsRepository_->getAllHalls(  halls );
           if (  !resultGetAllHalls.success ) {
           qFatal(   ) << "Failed to retrieve halls";
           }
          
           QVector<SessionDTO> sessions;
           RepositoryResult resultGetAllSessions =
           sessionsRepository_->getAllSessions(  sessions );
           if (  !resultGetAllSessions.success ) {
           qFatal(   ) << "Failed to retrieve sessions";
           }
          
           QMap<qint32 /* sessionId */,   QSet<qint32> /* availableSeatNumbers */>
           hallIdToAvailableSeatNumbers;
          
           for (  const HallDTO &hall : halls ) {
           qint32 targetCapacity =
           std::ceil(  hall.capacity(   ) * generateFillRatioPerSession(   ) );
          
           QVector<qint32> availableSeats(  targetCapacity );
           std::iota(  availableSeats.begin(   ),   availableSeats.end(   ),   1 );
           std::shuffle(  availableSeats.begin(   ),   availableSeats.end(   ),   generator_ );
          
           QSet<qint32> buffer;
           for (  qint32 i = 0; i < targetCapacity; ++i ) {
           buffer.insert(  availableSeats[i] );
           }
          
           hallIdToAvailableSeatNumbers[hall.hallId(   )] = std::move(  buffer );
           }
          
           for (  const SessionDTO &session : sessions ) {
           sessionIdToAvailableSeatNumbers_[session.sessionId(   )] =
           hallIdToAvailableSeatNumbers[session.hallId(   )];
           }
          }
          
      92  void TicketGenerator::initializeSessionIdToBeginAtMap(   ) {
           QVector<SessionDTO> sessions;
           RepositoryResult resultGetAllSessions =
           sessionsRepository_->getAllSessions(  sessions );
           if (  !resultGetAllSessions.success ) {
           qFatal(   ) << "Failed to retrieve sessions";
           }
          
           for (  const SessionDTO &session : sessions ) {
           sessionIdToBeginAt_[session.sessionId(   )] = session.beginAt(   );
           }
          }
          
     105  bool TicketGenerator::setFillRatioPerSessionRange(  
     106   const QPair<double,   double> &range ) {
           const auto &[minValue,   maxValue] = range;
           if (  minValue > maxValue ) {
           return false;
           }
          
           auto &[minFillRatioPerSession,   maxFillRatioPerSession] =
           fillRatioPerSessionRange_;
           minFillRatioPerSession = std::max(  minValue,   0.0 );
           maxFillRatioPerSession = std::min(  maxValue,   1.0 );
          
           return true;
          }
          
     120  double TicketGenerator::generateFillRatioPerSession(   ) const {
           const auto &[min,   max] = fillRatioPerSessionRange_;
           std::uniform_real_distribution<double> distribution(  min,   max );
           return distribution(  generator_ );
          }
          
     126  void TicketGenerator::initializeRemainingCapacity(   ) {
           remainingCapacity_ =
           std::accumulate(  sessionIdToAvailableSeatNumbers_.cbegin(   ),  
           sessionIdToAvailableSeatNumbers_.cend(   ),   0,  
           [&](  qsizetype sum,   const QSet<qint32> &availableSeats ) {
           return sum + availableSeats.size(   );
           } );
          }
          
          QString
     136  TicketGenerator::createFailureMessage(  const QString &errorMessage ) const {
           QString additionalInfo =
           QString(  "Additional info: Session id: %1 Seat number: %2 Sold at: %3 "
           "Begin at: %4" )
           .arg(  reservationContext_.sessionId )
           .arg(  reservationContext_.seatNumber )
           .arg(  reservationContext_.soldAt.toString(  "yyyy-MM-dd hh:mm:ss" ) )
           .arg(  reservationContext_.beginAt.toString(  "yyyy-MM-dd hh:mm:ss" ) );
           QString failureMessage = "Failure:\n\t%1\n\tError message: %2";
           return failureMessage.arg(  additionalInfo ).arg(  errorMessage );
          }
          
     148  QString TicketGenerator::createSuccessMessage(   ) const {
           return QString(  "Success: EId: %1 SN: %2" )
           .arg(  reservationContext_.sessionId )
           .arg(  reservationContext_.seatNumber );
          }
          
     154  qsizetype TicketGenerator::calculateRemainingCapacity(   ) const {
           return remainingCapacity_;
          }
          
     158  RepositoryResult TicketGenerator::createRecord(   ) {
           CreateTicketRequest request;
           request.setSessionId(  reservationContext_.sessionId );
           request.setSeatNumber(  reservationContext_.seatNumber );
           request.setSoldAt(  reservationContext_.soldAt );
          
           RepositoryResult result = ticketsRepository_->createTicket(  request );
          
           if (  result.success ) {
           --remainingCapacity_;
           }
          
           return result;
          }
          
     173  bool TicketGenerator::prepareData(   ) {
           if (  remainingCapacity_ <= 0 ) {
           qInfo(   ) << "All tickets have been sold";
           return false;
           }
          
           qsizetype deltaIndex =
           bounded<qsizetype>(  0,   sessionIdToAvailableSeatNumbers_.size(   ) );
           auto it = sessionIdToAvailableSeatNumbers_.begin(   );
           std::advance(  it,   deltaIndex );
          
           const qint32 sessionId = it.key(   );
           QSet<qint32> &availableSeatNumbers = it.value(   ); // Not empty
          
           const QDateTime &beginAt = sessionIdToBeginAt_[sessionId];
           constexpr qint32 minus20MinutesMs = -20 * 1000 * 60;
           // soldAt < beginAt || soldAt + 20 minute <= beginAt
           QDateTime soldAt = generate(  beginAt.addMSecs(  minus20MinutesMs ) );
          
           qsizetype seatNumberIndex =
           bounded<qsizetype>(  0,   availableSeatNumbers.size(   ) );
           auto seatNumberIt = availableSeatNumbers.cbegin(   );
           std::advance(  seatNumberIt,   seatNumberIndex );
          
           qint32 seatNumber = *seatNumberIt;
          
           ReservationContext context{beginAt,   soldAt,   seatNumber,   sessionId};
           reservationContext_ = std::move(  context );
          
           return true;
          }
          
     205  void TicketGenerator::clearStaleData(   ) {
          
           qint32 sessionId = reservationContext_.sessionId;
           qint32 seatNumber = reservationContext_.seatNumber;
           QSet<qint32> &availableSeatNumbers =
           sessionIdToAvailableSeatNumbers_[sessionId];
          
           availableSeatNumbers.remove(  seatNumber );
           if (  availableSeatNumbers.empty(   ) ) {
           qWarning(   ) << "All tickets for session with id" << sessionId
           << "have been sold";
           sessionIdToAvailableSeatNumbers_.remove(  sessionId );
           sessionIdToBeginAt_.remove(  sessionId );
           }
          }

./core/utils/day_manager.cpp

       1  #include "day_manager.h"
          
          #include <QRandomGenerator>
          #include <algorithm>
          
       6  DayManager::DayManager(  const QTime &begin,   const QTime &end ) : DayManager(   ) {
           addAvailableSlot(  begin,   end );
          }
          
      10  void DayManager::addAvailableSlot(  const QTime &begin,   const QTime &end ) {
           if (  !begin.isValid(   ) || !end.isValid(   ) || begin >= end ) {
           qWarning(   ) << "DayManager::addAvailableSlot: begin or end is invalid";
           return;
           }
           if (  begin.msecsTo(  end ) >= minDurationMs_ ) {
           availableSlots_.insert(  begin,   end );
           }
          }
          
      20  QPair<QTime,   QTime> DayManager::reserve(  const QTime &duration ) {
           if (  !duration.isValid(   ) ) {
           qWarning(   ) << "DayManager::reserve: duration is invalid";
           return {};
           }
          
           const qint64 requiredMs = duration.msecsSinceStartOfDay(   );
           if (  requiredMs <= 0 ) {
           qWarning(   ) << "DayManager::reserve: duration is invalid";
           return {};
           }
          
           QPair<QTime,   QTime> result = reserveImpl(  requiredMs );
           if (  !isAvailable(  result.first,   result.second ) ) {
           return {};
           }
          
           ++createdRecords_;
           return result;
          }
          
          template <typename... Times>
      42  bool DayManager::isAvailable(  const QTime &first,   const Times &...others ) const {
           static const auto isAvailable = [](  const QTime &time ) {
           return time.isValid(   );
           };
           return isAvailable(  first ) && isAvailable(  others... );
          }
          
      49  QList<std::reference_wrapper<const QTime>>
          DayManager::availableSlots(  qint64 requiredMs ) const {
           QList<std::reference_wrapper<const QTime>> result;
           for (  auto const &[begin,   end] : availableSlots_.asKeyValueRange(   ) ) {
           if (  begin.msecsTo(  end ) >= requiredMs ) {
           result.append(  begin );
           }
           }
           return result;
          }
          
      60  QPair<QTime,   QTime> DayManager::reserveAvailableSlotLeft(  QTime begin,  
      61   qint64 requiredMs ) {
           QTime end = availableSlots_.value(  begin );
          
           QTime reservedEnd = begin.addMSecs(  requiredMs );
           if (  reservedEnd < end ) {
           addAvailableSlot(  reservedEnd,   end );
           }
           availableSlots_.remove(  begin );
          
           return {begin,   reservedEnd};
          }
          
      73  QPair<QTime,   QTime> DayManager::reserveAvailableSlotRight(  QTime begin,  
      74   qint64 requiredMs ) {
           QTime end = availableSlots_.value(  begin );
           availableSlots_.remove(  begin );
          
           QTime reservedBegin = end.addMSecs(  -requiredMs );
           if (  reservedBegin > begin ) {
           addAvailableSlot(  begin,   reservedBegin );
           }
          
           return {reservedBegin,   end};
          }
          
      86  QPair<QTime,   QTime> DayManager::reserveAvailableSlotRandom(  QTime begin,  
      87   qint64 requiredMs ) {
           QTime end = availableSlots_.value(  begin );
           if (  !end.isValid(   ) ) {
           qCritical(   ) << "DayManager::reserveAvailableSlotRandom: end is invalid";
           }
          
           qint64 beginMs = begin.msecsSinceStartOfDay(   );
           qint64 endMs = end.msecsSinceStartOfDay(   );
          
           // x <= bounded(  x,   y ) < y
           qint64 highestMs = endMs - requiredMs;
           qint64 startTimeMs =
           QRandomGenerator::global(   )->bounded(  beginMs,   highestMs + 1 );
          
           if (  startTimeMs == beginMs ) {
           return reserveAvailableSlotLeft(  begin,   requiredMs );
           }
           if (  startTimeMs == highestMs ) {
           return reserveAvailableSlotRight(  begin,   requiredMs );
           }
          
           availableSlots_.remove(  begin );
          
           QTime reservedBegin = QTime::fromMSecsSinceStartOfDay(  startTimeMs );
           QTime reservedEnd = QTime::fromMSecsSinceStartOfDay(  startTimeMs + requiredMs );
          
           addAvailableSlot(  begin,   reservedBegin );
           addAvailableSlot(  reservedEnd,   end );
          
           return {reservedBegin,   reservedEnd};
          }
          
     119  QPair<QTime,   QTime> DayManager::reserveAvailableSlot(  QTime begin,  
     120   qint64 requiredMs ) {
           switch (  startTimeMode_ ) {
           case StartTimeMode::Left:
           return reserveAvailableSlotLeft(  begin,   requiredMs );
           case StartTimeMode::Random:
           return reserveAvailableSlotRandom(  begin,   requiredMs );
           case StartTimeMode::Right:
           return reserveAvailableSlotRight(  begin,   requiredMs );
           default:
           qWarning(   ) << "DayManager::reserve: unknown start time mode";
           return {};
           }
          }
          
     134  QPair<QTime,   QTime> DayManager::reserveImplMaximal(  qint64 requiredMs ) {
           auto keys = availableSlots(  requiredMs );
           if (  keys.isEmpty(   ) ) {
           return {};
           }
          
           QTime begin = *std::max_element(  
           keys.begin(   ),   keys.end(   ),   [this](  const auto &largest,   const auto &other ) {
           return duration(  largest ) < duration(  other );
           } );
           return reserveAvailableSlot(  begin,   requiredMs );
          }
          
     147  QPair<QTime,   QTime> DayManager::reserveImplMinimal(  qint64 requiredMs ) {
           auto keys = availableSlots(  requiredMs );
           if (  keys.isEmpty(   ) ) {
           return {};
           }
          
           QTime begin =
           *std::min_element(  keys.begin(   ),   keys.end(   ),  
           [this](  const QTime &smallest,   const QTime &other ) {
           return duration(  smallest ) < duration(  other );
           } );
           return reserveAvailableSlot(  begin,   requiredMs );
          }
          
     161  QPair<QTime,   QTime> DayManager::reserveImplQuick(  qint64 requiredMs ) {
           for (  auto const &[begin,   end] : availableSlots_.asKeyValueRange(   ) ) {
           if (  begin.msecsTo(  end ) >= requiredMs ) {
           return reserveAvailableSlot(  begin,   requiredMs );
           }
           }
          
           return {};
          }
          
     171  QPair<QTime,   QTime> DayManager::reserveImplRandom(  qint64 requiredMs ) {
           auto keys = availableSlots(  requiredMs );
           if (  keys.isEmpty(   ) ) {
           return {};
           }
          
           QRandomGenerator *g = QRandomGenerator::global(   );
           QTime begin = keys.at(  g->bounded(  keys.size(   ) ) );
           return reserveAvailableSlot(  begin,   requiredMs );
          }
          
     182  QPair<QTime,   QTime> DayManager::reserveImpl(  qint64 requiredMs ) {
           switch (  slotSelectionMode_ ) {
           case SlotSelectionMode::Quick:
           return DayManager::reserveImplQuick(  requiredMs );
           case SlotSelectionMode::Maximal:
           return DayManager::reserveImplMaximal(  requiredMs );
           case SlotSelectionMode::Minimal:
           return DayManager::reserveImplMinimal(  requiredMs );
           case SlotSelectionMode::Random:
           return DayManager::reserveImplRandom(  requiredMs );
           default:
           qWarning(   ) << "DayManager::reserve: unknown reserve mode";
           return {};
           }
          }
          
     198  void DayManager::setMinDuration(  const QTime &duration ) {
           if (  !duration.isValid(   ) ) {
           qWarning(   ) << "DayManager::setMinDuration: duration is invalid";
           return;
           }
          
           minDurationMs_ = duration.msecsSinceStartOfDay(   );
          }
          
     207  qsizetype DayManager::calculatePossibleReservations(  qint64 requiredMs ) const {
           auto keys = availableSlots(  requiredMs );
           return std::accumulate(  keys.begin(   ),   keys.end(   ),   0,  
           [this,   requiredMs](  qsizetype sum,   const QTime &key ) {
           qint64 durationMs =
           duration(  key ).msecsSinceStartOfDay(   );
           return sum + std::floor(  durationMs / requiredMs );
           } );
          }
          
     217  QTime DayManager::duration(   ) const {
           if (  availableSlots_.isEmpty(   ) ) {
           // qWarning(   ) << "DayManager::duration: no available slots";
           return {};
           }
          
           const auto &kvRange = availableSlots_.asKeyValueRange(   );
           auto f = [this](  qint64 sum,   const auto &kv ) -> qint64 {
           auto const &[begin,   end] = kv;
           return sum + begin.msecsTo(  end );
           };
           qint64 sum = std::accumulate(  kvRange.begin(   ),   kvRange.end(   ),   0,   f );
          
           return QTime::fromMSecsSinceStartOfDay(  sum );
          }
          
     233  QTime DayManager::duration(  const QTime &begin ) const {
           if (  !begin.isValid(   ) ) {
           qWarning(   ) << "DayManager::duration: begin is invalid";
           return {};
           }
          
           const QTime &end = availableSlots_.value(  begin );
           if (  !end.isValid(   ) ) {
           qWarning(   ) << "DayManager::duration: end is invalid";
           return {};
           }
          
           return end.addMSecs(  -begin.msecsSinceStartOfDay(   ) );
          }

./core/utils/reservation_manager.cpp

       1  #include "reservation_manager.h"
          
          #include <QRandomGenerator>
          
       5  ReservationManager::ReservationManager(  qsizetype dayRange,   qsizetype dayBound,  
       6   const DayManager &reference )
           : createdRecords_(  0 ),   dayBound_(  dayBound ),   dayRange_(  dayRange ),  
           excludedDays_(  0 ),   lastIndex_(  -1 ),   dayReference_(  reference ) {
           prepareDaysRange(   );
          }
          
      12  void ReservationManager::setReference(  const DayManager &reference ) {
           dayReference_ = reference;
           days_.clear(   );
           lastIndex_ = -1;
           prepareDaysRange(   );
          }
          
      19  qsizetype ReservationManager::calculateAvailableDaysCount(   ) const {
           qsizetype bound =
           dayBound_ != -1 ? dayBound_ : std::numeric_limits<qsizetype>::max(   );
           return bound - excludedDays_;
          }
          
          qsizetype
      26  ReservationManager::calculatePossibleReservations(  qint64 requiredMs ) const {
           qsizetype possibleReservations = std::accumulate(  
           days_.cbegin(   ),   days_.cend(   ),   0,  
           [&](  qsizetype sum,   const DayManager manager ) {
           return sum + manager.calculatePossibleReservations(  requiredMs );
           } );
          
           qsizetype possibleReservationsByReference =
           dayReference_.calculatePossibleReservations(  requiredMs );
           qsizetype futureReservationsCount =
           calculateAvailableDaysCount(   ) - days_.size(   );
          
           return possibleReservations +
           possibleReservationsByReference * futureReservationsCount;
          }
          
      42  qsizetype ReservationManager::calculateAvailableSlotsCount(   ) const {
           return std::accumulate(  days_.cbegin(   ),   days_.cend(   ),   0,  
           [&](  qsizetype sum,   const DayManager manager ) {
           return sum + manager.slotCount(   );
           } );
          }
          
      49  QPair<QDateTime,   QTime> ReservationManager::reserve(  const QTime &duration ) {
           if (  !duration.isValid(   ) ) {
           qWarning(   ) << "ReservationManager::reserve: duration is invalid";
           return {};
           }
          
           const qint64 requiredMs = duration.msecsSinceStartOfDay(   );
           if (  requiredMs <= 0 ) {
           qWarning(   )
           << "ReservationManager::reserve: duration should be greater than 0";
           return {};
           }
          
           if (  days_.isEmpty(   ) ) {
           qWarning(   ) << "ReservationManager::reserve: no available days";
           return {};
           }
          
           std::mt19937 g{std::random_device{}(   )};
           QList<qsizetype> dayIndexes = days_.keys(   );
           std::shuffle(  dayIndexes.begin(   ),   dayIndexes.end(   ),   g );
          
           for (  qsizetype dayIndex : dayIndexes ) {
           QPair<QDateTime,   QTime> result = reserve(  dayIndex,   duration );
           const auto &[beginDateTime,   endTime] = result;
           if (  beginDateTime.isValid(   ) && endTime.isValid(   ) ) {
           ++createdRecords_;
           return result;
           }
           }
          
           // qWarning(   ) << "ReservationManager::reserve: reservation failed";
           return {};
          }
          
      84  DayManager &ReservationManager::dayManager(  qsizetype dayIndex ) {
           auto it = days_.find(  dayIndex );
           if (  it != days_.end(   ) ) {
           return it.value(   );
           }
           qCritical(   ) << "ReservationManager::dayManager: dayIndex is out of range";
           throw std::out_of_range(  "dayIndex is out of range" );
          }
          
      93  QPair<QDateTime,   QTime> ReservationManager::reserve(  qsizetype dayIndex,  
      94   const QTime &duration ) {
          
           QTime beginTime,   endTime;
           DayManager &dayManagerValue = dayManager(  dayIndex );
          
           std::tie(  beginTime,   endTime ) = dayManagerValue.reserve(  duration );
          
           if (  !dayManagerValue.duration(   ).isValid(   ) ) {
           days_.remove(  dayIndex );
           ++excludedDays_;
           }
           prepareDaysRange(   ); // Add new days after excluded days
          
           QDate beginDate = QDateTime::fromMSecsSinceEpoch(  0 ).date(   ).addDays(  dayIndex );
           QDateTime beginDateTime(  beginDate,   beginTime );
          
           return {std::move(  beginDateTime ),   std::move(  endTime )};
          }
          
     113  void ReservationManager::prepareDaysRange(   ) {
           if (  !dayReference_.duration(   ).isValid(   ) ) {
           qWarning(   ) << "ReservationManager::prepareDaysRange: reference is invalid";
           return;
           }
          
           auto condition = [this](  qsizetype currentSize ) {
           qsizetype bound =
           dayBound_ != -1 ? dayBound_ : std::numeric_limits<qsizetype>::max(   );
           if (  lastIndex_ + 1 < bound ) {
           return currentSize < dayRange_;
           }
           return false;
           };
          
           for (  qsizetype size = days_.size(   ); condition(  size ); ++size ) {
           days_[++lastIndex_] = DayManager(  dayReference_ );
           }
          }

./data/sql/base_sql_table_repository.cpp

       1  #include "base_sql_table_repository.h"
          
          #include <QSqlError>
          #include <QSqlQuery>
          
       6  BaseSqlTableRepository::BaseSqlTableRepository(  QSqlDatabase &database,  
       7   QObject *parent )
           : DataRepositorySignals(  parent ),   database_(  database ) {
           if (  !database_.isOpen(   ) ) {
           qWarning(   ) << "Database is not open: Database name:"
           << database_.databaseName(   )
           << "Database connection name:" << database_.connectionName(   );
           }
          }
          
      16  QSqlQuery BaseSqlTableRepository::foreignKeysOnQuery(   ) const {
           QSqlQuery query(  database_ );
           if (  !query.exec(  "PRAGMA foreign_keys = ON" ) ) {
           qFatal(   ) << "Failed to enable foreign keys:" << query.lastError(   ).text(   );
           }
          
           return query;
          }

./data/sql/genres_repository.cpp

       1  #include "genres_repository.h"
          
          #include "create_genre_request.h"
          #include "genre_dto.h"
          #include "repository_result.h"
          #include "update_genre_request.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  GenresRepository::GenresRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
          RepositoryResult
      14  GenresRepository::createGenre(  const CreateGenreRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "INSERT INTO genres (  genre_name ) VALUES (  :genre_name )" );
           query.bindValue(  ":genre_name",   request.genreName(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
      27  RepositoryResult GenresRepository::getGenreById(  qint32 id,  
      28   GenreDTO &genre ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "SELECT genre_id,   genre_name FROM genres WHERE genre_id = :genre_id" );
           query.bindValue(  ":genre_id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Genre not found" );
           }
          
           genre = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      48  GenresRepository::getAllGenres(  QVector<GenreDTO> &genres ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT genre_id,   genre_name FROM genres" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           genres.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      65  GenresRepository::updateGenre(  const UpdateGenreRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "UPDATE genres SET genre_name = :genre_name WHERE genre_id = :genre_id" );
           query.bindValue(  ":genre_name",   request.genreName(   ) );
           query.bindValue(  ":genre_id",   request.genreId(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
      80  RepositoryResult GenresRepository::deleteGenreById(  qint32 genreId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM genres WHERE genre_id = :genre_id" );
           query.bindValue(  ":genre_id",   genreId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
      93  GenreDTO GenresRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           GenreDTO genreDTO;
           genreDTO.setGenreId(  query.value(  "genre_id" ).toInt(   ) );
           genreDTO.setGenreName(  query.value(  "genre_name" ).toString(   ) );
          
           return genreDTO;
          }

./data/sql/halls_repository.cpp

       1  #include "halls_repository.h"
          
          #include "create_hall_request.h"
          #include "hall_dto.h"
          #include "repository_result.h"
          #include "update_hall_request.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  HallsRepository::HallsRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
      13  RepositoryResult HallsRepository::createHall(  const CreateHallRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "INSERT INTO halls (  hall_name,   capacity ) VALUES "
           "(  :hall_name,   :capacity )" );
           query.bindValue(  ":hall_name",   request.hallName(   ) );
           query.bindValue(  ":capacity",   request.capacity(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
      28  RepositoryResult HallsRepository::getHallById(  qint32 id,   HallDTO &hall ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "SELECT hall_id,   hall_name,   capacity FROM halls WHERE hall_id = :id" );
           query.bindValue(  ":id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Hall not found" );
           }
          
           hall = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      47  RepositoryResult HallsRepository::getAllHalls(  QVector<HallDTO> &halls ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT hall_id,   hall_name,   capacity FROM halls" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           halls.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      63  RepositoryResult HallsRepository::updateHall(  const UpdateHallRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "UPDATE halls SET hall_name = :hall_name,   capacity = :capacity "
           "WHERE hall_id = :hall_id" );
           query.bindValue(  ":capacity",   request.capacity(   ) );
           query.bindValue(  ":hall_id",   request.hallId(   ) );
           query.bindValue(  ":hall_name",   request.hallName(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
      79  RepositoryResult HallsRepository::deleteHallById(  qint32 hallId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM halls WHERE hall_id = :hall_id" );
           query.bindValue(  ":hall_id",   hallId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
      92  HallDTO HallsRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           HallDTO hallDTO;
           hallDTO.setCapacity(  query.value(  "capacity" ).toInt(   ) );
           hallDTO.setHallId(  query.value(  "hall_id" ).toInt(   ) );
           hallDTO.setHallName(  query.value(  "hall_name" ).toString(   ) );
          
           return hallDTO;
          }

./data/sql/movies_repository.cpp

       1  #include "movies_repository.h"
          
          #include "create_movie_request.h"
          #include "movie_dto.h"
          #include "repository_result.h"
          #include "update_movie_request.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  MoviesRepository::MoviesRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
          RepositoryResult
      14  MoviesRepository::createMovie(  const CreateMovieRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "INSERT INTO movies (  title,   genre_id,   duration ) VALUES "
           "(  :title,   :genre_id,   :duration )" );
           query.bindValue(  ":title",   request.title(   ) );
           query.bindValue(  ":genre_id",   request.genreId(   ) );
           query.bindValue(  ":duration",   request.duration(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      31  MoviesRepository::getAllMovies(  QVector<MovieDTO> &movies ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT movie_id,   title,   genre_id,   duration FROM movies" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           movies.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      47  RepositoryResult MoviesRepository::getMovieById(  qint32 id,  
      48   MovieDTO &movie ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT movie_id,   title,   genre_id,   duration FROM movies WHERE "
           "movie_id = :id" );
           query.bindValue(  ":id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Movie not found" );
           }
          
           movie = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      68  MoviesRepository::updateMovie(  const UpdateMovieRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "UPDATE movies SET title = :title,   genre_id = :genre_id,   "
           "duration = :duration WHERE movie_id = :id" );
           query.bindValue(  ":duration",   request.duration(   ) );
           query.bindValue(  ":genre_id",   request.genreId(   ) );
           query.bindValue(  ":id",   request.movieId(   ) );
           query.bindValue(  ":title",   request.title(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
      85  RepositoryResult MoviesRepository::deleteMovieById(  qint32 movieId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM movies WHERE movie_id = :id" );
           query.bindValue(  ":id",   movieId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
      98  MovieDTO MoviesRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           MovieDTO movieDTO;
           movieDTO.setDuration(  query.value(  "duration" ).toInt(   ) );
           movieDTO.setGenreId(  query.value(  "genre_id" ).toInt(   ) );
           movieDTO.setMovieId(  query.value(  "movie_id" ).toInt(   ) );
           movieDTO.setTitle(  query.value(  "title" ).toString(   ) );
          
           return movieDTO;
          }

./data/sql/refunds_repository.cpp

       1  #include "refunds_repository.h"
          
          #include "create_refund_request.h"
          #include "refund_dto.h"
          #include "repository_result.h"
          #include "update_refund_request.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  RefundsRepository::RefundsRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
          RepositoryResult
      14  RefundsRepository::createRefund(  const CreateRefundRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "INSERT INTO refunds (  ticket_id,   refund_at,   refund_amount ) "
           "VALUES (  :ticket_id,   :refund_at,   :refund_amount )" );
           query.bindValue(  ":ticket_id",   request.ticketId(   ) );
           query.bindValue(  ":refund_at",   request.refundAt(   ) );
           query.bindValue(  ":refund_amount",   request.refundAmount(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
      30  RepositoryResult RefundsRepository::getRefundById(  qint32 id,  
      31   RefundDTO &refund ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT refund_id,   ticket_id,   refund_at,   refund_amount FROM "
           "refunds WHERE refund_id = :id" );
           query.bindValue(  ":id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Refund not found" );
           }
          
           RefundDTO refundDTO;
           refundDTO.setRefundAmount(  query.value(  "refund_amount" ).toDouble(   ) );
           refundDTO.setRefundAt(  query.value(  "refund_at" ).toDateTime(   ) );
           refundDTO.setRefundId(  query.value(  "refund_id" ).toInt(   ) );
           refundDTO.setTicketId(  query.value(  "ticket_id" ).toInt(   ) );
          
           refund = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      57  RefundsRepository::getAllRefunds(  QVector<RefundDTO> &refunds ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "SELECT refund_id,   ticket_id,   refund_at,   refund_amount FROM refunds" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           refunds.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      75  RefundsRepository::updateRefund(  const UpdateRefundRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "UPDATE refunds SET ticket_id = :ticket_id,   refund_at = :refund_at,   "
           "refund_amount = :refund_amount WHERE refund_id = :refund_id" );
           query.bindValue(  ":refund_amount",   request.refundAmount(   ) );
           query.bindValue(  ":refund_at",   request.refundAt(   ) );
           query.bindValue(  ":refund_id",   request.refundId(   ) );
           query.bindValue(  ":ticket_id",   request.ticketId(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
      93  RepositoryResult RefundsRepository::deleteRefundById(  qint32 refundId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM refunds WHERE refund_id = :refund_id" );
           query.bindValue(  ":refund_id",   refundId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
     106  RefundDTO RefundsRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           RefundDTO refundDTO;
           refundDTO.setRefundAmount(  query.value(  "refund_amount" ).toDouble(   ) );
           refundDTO.setRefundAt(  query.value(  "refund_at" ).toDateTime(   ) );
           refundDTO.setRefundId(  query.value(  "refund_id" ).toInt(   ) );
           refundDTO.setTicketId(  query.value(  "ticket_id" ).toInt(   ) );
          
           return refundDTO;
          }

./data/sql/roles_repository.cpp

       1  #include "roles_repository.h"
          
          #include "create_role_request.h"
          #include "repository_result.h"
          #include "role_dto.h"
          #include "update_role_request.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  RolesRepository::RolesRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
      13  RepositoryResult RolesRepository::createRole(  const CreateRoleRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "INSERT INTO roles (  role_name,   access_level ) VALUES "
           "(  :role_name,   :access_level )" );
           query.bindValue(  ":role_name",   request.roleName(   ) );
           query.bindValue(  ":access_level",   request.accessLevel(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
      28  RepositoryResult RolesRepository::getRoleById(  qint32 id,   RoleDTO &role ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT role_id,   role_name,   access_level FROM roles WHERE "
           "role_id = :role_id" );
           query.bindValue(  ":role_id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Role not found" );
           }
          
           role = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      47  RepositoryResult RolesRepository::getAllRoles(  QVector<RoleDTO> &roles ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT role_id,   role_name,   access_level FROM roles" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           roles.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      63  RepositoryResult RolesRepository::updateRole(  const UpdateRoleRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "UPDATE roles SET role_name = :role_name WHERE role_id = :role_id" );
           query.bindValue(  ":role_name",   request.roleName(   ) );
           query.bindValue(  ":role_id",   request.roleId(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
      78  RepositoryResult RolesRepository::deleteRoleById(  qint32 roleId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM roles WHERE role_id = :role_id" );
           query.bindValue(  ":role_id",   roleId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
      91  RoleDTO RolesRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           RoleDTO roleDTO;
           roleDTO.setAccessLevel(  query.value(  "access_level" ).toInt(   ) );
           roleDTO.setRoleId(  query.value(  "role_id" ).toInt(   ) );
           roleDTO.setRoleName(  query.value(  "role_name" ).toString(   ) );
          
           return roleDTO;
          }

./data/sql/sessions_repository.cpp

       1  #include "sessions_repository.h"
          
          #include "create_session_request.h"
          #include "repository_result.h"
          #include "session_dto.h"
          #include "update_session_request.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  SessionsRepository::SessionsRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
          RepositoryResult
      14  SessionsRepository::createSession(  const CreateSessionRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "INSERT INTO sessions (  movie_id,   hall_id,   begin_at,   ticket_price ) VALUES "
           "(  :movie_id,   :hall_id,   :begin_at,   :ticket_price )" );
           query.bindValue(  ":begin_at",   request.beginAt(   ) );
           query.bindValue(  ":hall_id",   request.hallId(   ) );
           query.bindValue(  ":movie_id",   request.movieId(   ) );
           query.bindValue(  ":ticket_price",   request.ticketPrice(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
      32  RepositoryResult SessionsRepository::getSessionById(  qint32 id,  
      33   SessionDTO &session ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT session_id,   movie_id,   hall_id,   begin_at,   ticket_price "
           "FROM sessions WHERE session_id = :id" );
           query.bindValue(  ":id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Session not found" );
           }
          
           session = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      53  SessionsRepository::getAllSessions(  QVector<SessionDTO> &sessions ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT session_id,   movie_id,   hall_id,   begin_at,   ticket_price "
           "FROM sessions" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           sessions.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      71  SessionsRepository::updateSession(  const UpdateSessionRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "UPDATE sessions SET movie_id = :movie_id,   hall_id = :hall_id,   "
           "begin_at = :begin_at,   ticket_price = :ticket_price WHERE "
           "session_id = :session_id" );
           query.bindValue(  ":begin_at",   request.beginAt(   ) );
           query.bindValue(  ":hall_id",   request.hallId(   ) );
           query.bindValue(  ":movie_id",   request.movieId(   ) );
           query.bindValue(  ":session_id",   request.sessionId(   ) );
           query.bindValue(  ":ticket_price",   request.ticketPrice(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
      90  RepositoryResult SessionsRepository::deleteSessionById(  qint32 sessionId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM sessions WHERE session_id = :session_id" );
           query.bindValue(  ":session_id",   sessionId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
     103  SessionDTO SessionsRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           SessionDTO sessionDTO;
           sessionDTO.setBeginAt(  query.value(  "begin_at" ).toDateTime(   ) );
           sessionDTO.setHallId(  query.value(  "hall_id" ).toInt(   ) );
           sessionDTO.setMovieId(  query.value(  "movie_id" ).toInt(   ) );
           sessionDTO.setSessionId(  query.value(  "session_id" ).toInt(   ) );
           sessionDTO.setTicketPrice(  query.value(  "ticket_price" ).toDouble(   ) );
          
           return sessionDTO;
          }

./data/sql/sessions_statistics_repository.cpp

       1  #include "sessions_statistics_repository.h"
          
          #include "repository_result.h"
          #include "session_statistics_dto.h"
          
          #include <QSqlError>
          #include <QSqlQuery>
          #include <QVariant>
          #include <QVector>
          
      11  SessionsStatisticsRepository::SessionsStatisticsRepository(  
      12   QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
          SessionStatisticsDTO
      16  SessionsStatisticsRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           SessionStatisticsDTO sessionStatisticsDTO;
          
           // clang-format off
           sessionStatisticsDTO.setSessionId(  query.value(  "session_id" ).toInt(   ) );
           sessionStatisticsDTO.setBeginAt(  query.value(  "begin_at" ).toDateTime(   ) );
           sessionStatisticsDTO.setTicketPrice(  query.value(  "ticket_price" ).toDouble(   ) );
           sessionStatisticsDTO.setHallId(  query.value(  "hall_id" ).toInt(   ) );
           sessionStatisticsDTO.setHallName(  query.value(  "hall_name" ).toString(   ) );
           sessionStatisticsDTO.setTotalSeats(  query.value(  "total_seats" ).toInt(   ) );
           sessionStatisticsDTO.setMovieId(  query.value(  "movie_id" ).toInt(   ) );
           sessionStatisticsDTO.setMovieTitle(  query.value(  "movie_title" ).toString(   ) );
           sessionStatisticsDTO.setDuration(  query.value(  "duration" ).toInt(   ) );
           sessionStatisticsDTO.setGenreId(  query.value(  "genre_id" ).toInt(   ) );
           sessionStatisticsDTO.setGenreName(  query.value(  "genre_name" ).toString(   ) );
           sessionStatisticsDTO.setSoldTickets(  query.value(  "sold_tickets" ).toInt(   ) );
           sessionStatisticsDTO.setRefundedTickets(   query.value(  "refunded_tickets" ).toInt(   ) );
           sessionStatisticsDTO.setSalesRevenue(  query.value(  "sales_revenue" ).toDouble(   ) );
           sessionStatisticsDTO.setRefundExpenses(   query.value(  "refund_expenses" ).toDouble(   ) );
           // clang-format on
          
           return sessionStatisticsDTO;
          }
          
      40  RepositoryResult SessionsStatisticsRepository::executeAndMapQuery(  
      41   QSqlQuery &query,   QVector<SessionStatisticsDTO> &sessions ) const {
          
           qInfo(   ) << "Executing query:" << query.executedQuery(   )
           << " with values:" << query.boundValues(   );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           sessions.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      58  RepositoryResult SessionsStatisticsRepository::getSessionById(  
      59   qint32 id,   SessionStatisticsDTO &sessionStatisticsDTO ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  QString(  "%1 WHERE session_id = :id" ).arg(  SELECT_QUERY ) );
           query.bindValue(  ":id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Session not found" );
           }
          
           sessionStatisticsDTO = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      77  RepositoryResult SessionsStatisticsRepository::getAllSessions(  
      78   QVector<SessionStatisticsDTO> &sessions ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  SELECT_QUERY );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           sessions.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      94  RepositoryResult SessionsStatisticsRepository::getSessionsForHall(  
      95   qint32 hallId,   std::optional<QDateTime> startDate,  
      96   std::optional<QDateTime> endDate,  
      97   QVector<SessionStatisticsDTO> &sessions ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           QString queryString =
           QString(  "%1 WHERE hall_id = :hall_id" ).arg(  SELECT_QUERY );
          
           bindDateFilters(  queryString,   startDate,   endDate );
          
           query.prepare(  queryString );
           query.bindValue(  ":hall_id",   hallId );
          
           bindDateValues(  query,   startDate,   endDate );
          
           return executeAndMapQuery(  query,   sessions );
          }
          
     112  RepositoryResult SessionsStatisticsRepository::getSessionsForMovie(  
     113   qint32 movieId,   std::optional<QDateTime> startDate,  
     114   std::optional<QDateTime> endDate,  
     115   QVector<SessionStatisticsDTO> &sessions ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           QString queryString =
           QString(  "%1 WHERE movie_id = :movie_id" ).arg(  SELECT_QUERY );
          
           bindDateFilters(  queryString,   startDate,   endDate );
          
           query.prepare(  queryString );
           query.bindValue(  ":movie_id",   movieId );
          
           bindDateValues(  query,   startDate,   endDate );
          
           return executeAndMapQuery(  query,   sessions );
          }
          
     130  RepositoryResult SessionsStatisticsRepository::getSessionsForGenre(  
     131   qint32 genreId,   std::optional<QDateTime> startDate,  
     132   std::optional<QDateTime> endDate,  
     133   QVector<SessionStatisticsDTO> &sessions ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           QString queryString =
           QString(  "%1 WHERE genre_id = :genre_id" ).arg(  SELECT_QUERY );
          
           bindDateFilters(  queryString,   startDate,   endDate );
          
           query.prepare(  queryString );
           query.bindValue(  ":genre_id",   genreId );
          
           bindDateValues(  query,   startDate,   endDate );
          
           return executeAndMapQuery(  query,   sessions );
          }
          
     148  void SessionsStatisticsRepository::bindDateFilters(  
     149   QString &queryString,   const std::optional<QDateTime> &startDate,  
     150   const std::optional<QDateTime> &endDate ) const {
           if (  startDate && endDate ) {
           queryString += " AND begin_at BETWEEN :start_date AND :end_date";
           } else if (  startDate ) {
           queryString += " AND begin_at >= :start_date";
           } else if (  endDate ) {
           queryString += " AND begin_at <= :end_date";
           }
          }
          
     160  void SessionsStatisticsRepository::bindDateValues(  
     161   QSqlQuery &query,   const std::optional<QDateTime> &startDate,  
     162   const std::optional<QDateTime> &endDate ) const {
           if (  startDate ) {
           query.bindValue(  ":start_date",   *startDate );
           }
           if (  endDate ) {
           query.bindValue(  ":end_date",   *endDate );
           }
          }

./data/sql/table_info_sqlite.cpp

          #include "table_info_sqlite.h"
          
          #include <QDebug>
          #include <QMetaType>
          #include <QSqlError>
          
       7  TableInfoSQLite::TableInfoSQLite(   ) : databaseTableName_(  "" ) {}
          
       9  TableInfoSQLite::TableInfoSQLite(  const QString &databaseTableName )
           : databaseTableName_(  databaseTableName ) {
           loadColumnInfo(   );
          }
          
      14  void TableInfoSQLite::loadColumnInfo(   ) {
           QSqlDatabase defaultDatabase = QSqlDatabase::database(   );
          
           if (  !defaultDatabase.isValid(   ) ) {
           qCritical(   ) << "TableInfoSQLite::loadColumnInfo: Database is invalid";
           }
          
           if (  !defaultDatabase.isOpen(   ) ) {
           qCritical(   ) << "TableInfoSQLite::loadColumnInfo: Database is not open";
          
           databaseTableName_.clear(   );
           columnNames_.clear(   );
           columnTypes_.clear(   );
           isPrimaryKey_.clear(   );
          
           return;
           }
          
           QSqlQuery query(  defaultDatabase );
           query.prepare(  QString(  "PRAGMA table_info(  %1 )" ).arg(  databaseTableName_ ) );
          
           if (  !query.exec(   ) ) {
           qWarning(   ) << "Failed to get table info: Error:" << query.lastError(   ).text(   )
           << "Database table name:" << databaseTableName_;
          
           return;
           }
          
           while (  query.next(   ) ) {
           columnNames_.append(  query.value(  "name" ).toString(   ) );
           columnTypes_.append(  query.value(  "type" ).toString(   ) );
           isPrimaryKey_.append(  query.value(  "pk" ).toInt(   ) > 0 );
           }
          }
          
      49  QString TableInfoSQLite::databaseTableName(   ) const noexcept {
           return databaseTableName_;
          }
          
          QString
      54  TableInfoSQLite::foreignKeyTable(  const QString &columnName ) const noexcept {
           if (  databaseTableName_.isEmpty(   ) ) {
           qWarning(   ) << "TableInfoSQLite::foreignKeyTable: No table name set";
           return QString(   );
           }
          
           QSqlDatabase defaultDatabase = QSqlDatabase::database(   );
           if (  !defaultDatabase.isOpen(   ) ) {
           qCritical(   ) << "TableInfoSQLite::foreignKeyTable: Database is not open";
           return QString(   );
           }
          
           QSqlQuery query(  defaultDatabase );
           query.prepare(  QString(  "PRAGMA foreign_key_list(  %1 )" ).arg(  databaseTableName_ ) );
          
           if (  !query.exec(   ) ) {
           qWarning(   ) << "Failed to get foreign key info:" << query.lastError(   ).text(   );
           return QString(   );
           }
          
           while (  query.next(   ) ) {
           QString fkColumn = query.value(  "from" ).toString(   );
           QString referencedTable = query.value(  "table" ).toString(   );
          
           if (  fkColumn == columnName ) {
           return referencedTable;
           }
           }
          
           return QString(   );
          }
          
      86  QStringList TableInfoSQLite::columnNames(   ) const noexcept {
           return QStringList(  columnNames_.begin(   ),   columnNames_.end(   ) );
          }
          
      90  bool TableInfoSQLite::isPrimaryKey(  const QString &columnName ) const noexcept {
           int index = columnIndex(  columnName );
           if (  index == -1 ) {
           return false;
           }
           return isPrimaryKey_.at(  index );
          }
          
      98  qint32 TableInfoSQLite::columnIndex(  const QString &columnName ) const noexcept {
           return columnNames_.indexOf(  columnName );
          }
          
     102  QString TableInfoSQLite::columnName(  qint32 columnIndex ) const noexcept {
           if (  columnIndex < 0 || columnIndex >= columnNames_.size(   ) ) {
           return QString(   );
           }
           return columnNames_.at(  columnIndex );
          }
          
          QMetaType::Type
          TableInfoSQLite::columnMetaType(  const QString &columnName ) const noexcept {
           int index = columnIndex(  columnName );
           if (  index == -1 ) {
           return QMetaType::UnknownType;
           }
          
           QString typeName = columnTypes_.at(  index ).toLower(   );
          
           if (  typeName.contains(  "int" ) ) {
           return QMetaType::Int;
           } else if (  typeName.contains(  "double" ) || typeName.contains(  "real" ) ||
           typeName.contains(  "float" ) ) {
           return QMetaType::Double;
           } else if (  typeName.contains(  "date" ) || typeName.contains(  "time" ) ) {
           return QMetaType::QDateTime;
           } else if (  typeName.contains(  "bool" ) ) {
           return QMetaType::Bool;
           } else if (  typeName.contains(  "blob" ) ) {
           return QMetaType::QByteArray;
           } else {
           return QMetaType::QString;
           }
          }
          
          void TableInfoSQLite::setDatabaseTableName(  
           const QString &databaseTableName ) noexcept {
           databaseTableName_ = databaseTableName;
           loadColumnInfo(   );
          }

./data/sql/tickets_repository.cpp

       1  #include "tickets_repository.h"
          
          #include "create_ticket_request.h"
          #include "repository_result.h"
          #include "ticket_dto.h"
          #include "update_ticket_request.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  TicketsRepository::TicketsRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
          RepositoryResult
      14  TicketsRepository::createTicket(  const CreateTicketRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "INSERT INTO tickets (  session_id,   seat_number,   sold_at ) VALUES "
           "(  :session_id,   :seat_number,   :sold_at )" );
           query.bindValue(  ":seat_number",   request.seatNumber(   ) );
           query.bindValue(  ":session_id",   request.sessionId(   ) );
           query.bindValue(  ":sold_at",   request.soldAt(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
      30  RepositoryResult TicketsRepository::getTicketById(  qint32 id,  
      31   TicketDTO &ticket ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT ticket_id,   session_id,   seat_number,   sold_at FROM "
           "tickets WHERE ticket_id = :id" );
           query.bindValue(  ":id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "Ticket not found" );
           }
          
           ticket = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      51  TicketsRepository::getAllTickets(  QVector<TicketDTO> &tickets ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "SELECT ticket_id,   session_id,   seat_number,   sold_at FROM tickets" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           tickets.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
          RepositoryResult
      69  TicketsRepository::updateTicket(  const UpdateTicketRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "UPDATE tickets SET session_id = :session_id,   seat_number = "
           ":seat_number,   sold_at = :sold_at WHERE ticket_id = :ticket_id" );
           query.bindValue(  ":seat_number",   request.seatNumber(   ) );
           query.bindValue(  ":session_id",   request.sessionId(   ) );
           query.bindValue(  ":sold_at",   request.soldAt(   ) );
           query.bindValue(  ":ticket_id",   request.ticketId(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
      87  RepositoryResult TicketsRepository::deleteTicketById(  qint32 ticketId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM tickets WHERE ticket_id = :ticket_id" );
           query.bindValue(  ":ticket_id",   ticketId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
     100  TicketDTO TicketsRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           TicketDTO ticketDTO;
           ticketDTO.setSeatNumber(  query.value(  "seat_number" ).toInt(   ) );
           ticketDTO.setSessionId(  query.value(  "session_id" ).toInt(   ) );
           ticketDTO.setSoldAt(  query.value(  "sold_at" ).toDateTime(   ) );
           ticketDTO.setTicketId(  query.value(  "ticket_id" ).toInt(   ) );
          
           return ticketDTO;
          }

./data/sql/users_repository.cpp

       1  #include "users_repository.h"
          
          #include "create_user_request.h"
          #include "repository_result.h"
          #include "update_user_request.h"
          #include "user_dto.h"
          #include <QSqlError>
          #include <QSqlQuery>
          
      10  UsersRepository::UsersRepository(  QSqlDatabase &database,   QObject *parent )
           : BaseSqlTableRepository(  database,   parent ) {}
          
      13  RepositoryResult UsersRepository::createUser(  const CreateUserRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "INSERT INTO users (  username,   password_hash,   salt,   role_id ) "
           "VALUES (  :username,   :password_hash,   :salt,   :role_id )" );
           query.bindValue(  ":password_hash",   request.passwordHash(   ) );
           query.bindValue(  ":role_id",   request.roleId(   ) );
           query.bindValue(  ":salt",   request.salt(   ) );
           query.bindValue(  ":username",   request.username(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataCreated(   );
           return RepositoryResult::successResult(   );
          }
          
      30  RepositoryResult UsersRepository::getUserById(  qint32 id,   UserDTO &user ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT user_id,   username,   password_hash,   salt,   role_id FROM "
           "users WHERE "
           "user_id = :user_id" );
           query.bindValue(  ":user_id",   id );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "User not found" );
           }
          
           user = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      50  RepositoryResult UsersRepository::getUserByUsername(  const QString &username,  
      51   UserDTO &user ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "SELECT user_id,   username,   password_hash,   salt,   role_id FROM "
           "users WHERE "
           "username = :username" );
           query.bindValue(  ":username",   username );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
           if (  !query.next(   ) ) {
           return RepositoryResult::failureResult(  "User not found" );
           }
          
           user = mapQueryToDTO(  query );
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      71  RepositoryResult UsersRepository::getAllUsers(  QVector<UserDTO> &users ) const {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  
           "SELECT user_id,   username,   password_hash,   salt,   role_id FROM users" );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           while (  query.next(   ) ) {
           users.append(  mapQueryToDTO(  query ) );
           }
          
           emit dataRetrieved(   );
           return RepositoryResult::successResult(   );
          }
          
      88  RepositoryResult UsersRepository::updateUser(  const UpdateUserRequest &request ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "UPDATE users SET username = :username,   password_hash = "
           ":password_hash,   "
           "salt = :salt,   role_id = :role_id WHERE user_id = :user_id" );
          
           query.bindValue(  ":username",   request.username(   ) );
           query.bindValue(  ":password_hash",   request.passwordHash(   ) );
           query.bindValue(  ":salt",   request.salt(   ) );
           query.bindValue(  ":role_id",   request.roleId(   ) );
           query.bindValue(  ":user_id",   request.userId(   ) );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataUpdated(   );
           return RepositoryResult::successResult(   );
          }
          
     108  RepositoryResult UsersRepository::deleteUserById(  qint32 userId ) {
           QSqlQuery query = foreignKeysOnQuery(   );
           query.prepare(  "DELETE FROM users WHERE user_id = :user_id" );
           query.bindValue(  ":user_id",   userId );
          
           if (  !query.exec(   ) ) {
           return RepositoryResult::failureResult(  query.lastError(   ).text(   ) );
           }
          
           emit dataDeleted(   );
           return RepositoryResult::successResult(   );
          }
          
     121  UserDTO UsersRepository::mapQueryToDTO(  const QSqlQuery &query ) const {
           UserDTO userDTO;
           userDTO.setPasswordHash(  query.value(  "password_hash" ).toString(   ) );
           userDTO.setRoleId(  query.value(  "role_id" ).toInt(   ) );
           userDTO.setSalt(  query.value(  "salt" ).toString(   ) );
           userDTO.setUserId(  query.value(  "user_id" ).toInt(   ) );
           userDTO.setUsername(  query.value(  "username" ).toString(   ) );
          
           return userDTO;
          }

./src/main.cpp

       1  #include "application_connector.h"
          #include "application_controller.h"
          #include "main_window.h"
          #include <QApplication>
          
       6  int main(  int argc,   char *argv[] ) {
           QApplication app(  argc,   argv );
          
           ApplicationController controller;
           if (  !controller.setupApplication(   ) ) {
           return -1;
           }
          
           // MainWindow mainWindow(  controller.userId(   ),   controller.accessLevel(   ) );
           MainWindow mainWindow(  1,   100 );
          
           ApplicationConnector connector(  &mainWindow );
           if (  !connector.connect(   ) ) {
           return -1;
           }
          
           mainWindow.show(   );
          
           return app.exec(   );
          }

./tests_class/day_manager_test.cpp

       1  #include "day_manager.h"
          #include <QtTest>
          
       4  class DayManagerTest : public QObject {
           Q_OBJECT
          
          private slots:
           void testAddAvailableSlot(   );
           void testDuration(   );
           void testReserveFailure(   );
           void testReserveImplMaximal(   );
           void testReserveImplMinimal(   );
           void testReserveImplQuick(   );
           void testReserveImplRandom(   );
           void testReserveSuccess(   );
          };
          
      18  void DayManagerTest::testAddAvailableSlot(   ) {
           DayManager manager(  QTime(  9,   0 ),   QTime(  12,   0 ) );
          
           QCOMPARE(  manager.minDurationMs_,   0 );
           QVERIFY(  !manager.availableSlots_.isEmpty(   ) );
           QCOMPARE(  manager.availableSlots_.size(   ),   1 );
           QCOMPARE(  manager.availableSlots_.value(  QTime(  9,   0 ) ),   QTime(  12,   0 ) );
          }
          
      27  void DayManagerTest::testDuration(   ) {
           DayManager manager(  QTime(  9,   0 ),   QTime(  12,   0 ) );
          
           QCOMPARE(  manager.duration(  QTime(  10,   0 ) ),   {} ); // Print warning
           QCOMPARE(  manager.duration(  QTime(  9,   0 ) ),   QTime(  3,   0 ) );
          
           manager.addAvailableSlot(  QTime(  12,   0 ),   QTime(  13,   0 ) );
           QCOMPARE(  manager.duration(  QTime(  12,   0 ) ),   QTime(  1,   0 ) );
          
           QCOMPARE(  manager.duration(   ),   QTime(  4,   0 ) );
          }
          
      39  void DayManagerTest::testReserveImplQuick(   ) {
           DayManager manager(  QTime(  9,   0 ),   QTime(  12,   0 ) );
          
           for (  int i = 0; i < 3; ++i ) {
           QCOMPARE(  manager.slotCount(   ),   1 );
           QTime availableHourCount = manager.duration(  QTime(  9 + i,   0 ) );
           QCOMPARE(  manager.duration(  QTime(  9 + i,   0 ) ),   QTime(  3 - i,   0 ) );
          
           auto [begin,   end] =
           manager.reserveImplQuick(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           }
          
           QCOMPARE(  manager.slotCount(   ),   0 );
          }
          
      56  void DayManagerTest::testReserveImplMaximal(   ) {
           DayManager manager;
          
           manager.addAvailableSlot(  QTime(  9,   0 ),   QTime(  12,   0 ) ); // 3 hour; first,   second
           manager.addAvailableSlot(  QTime(  8,   0 ),   QTime(  9,   0 ) ); // 1 hour
           manager.addAvailableSlot(  QTime(  12,   0 ),   QTime(  14,   0 ) ); // 2 hour; third
          
           auto [begin1,   end1] =
           manager.reserveImplMaximal(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin1.isValid(   ) );
           QVERIFY(  end1.isValid(   ) );
           QCOMPARE(  begin1,   QTime(  9,   0 ) );
          
           auto [begin2,   end2] =
           manager.reserveImplMaximal(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin2.isValid(   ) );
           QVERIFY(  end2.isValid(   ) );
           QCOMPARE(  begin2,   QTime(  10,   0 ) );
          
           auto [begin3,   end3] =
           manager.reserveImplMaximal(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin3.isValid(   ) );
           QVERIFY(  end3.isValid(   ) );
           QCOMPARE(  begin3,   QTime(  12,   0 ) );
          
           QCOMPARE(  manager.slotCount(   ),   3 );
          }
          
      84  void DayManagerTest::testReserveImplMinimal(   ) {
           DayManager manager;
          
           manager.addAvailableSlot(  QTime(  9,   0 ),   QTime(  12,   0 ) ); // 3 hour
           manager.addAvailableSlot(  QTime(  8,   0 ),   QTime(  9,   0 ) ); // 1 hour; first
           manager.addAvailableSlot(  QTime(  12,   0 ),   QTime(  14,   0 ) ); // 2 hour; second,   third
          
           auto [begin1,   end1] =
           manager.reserveImplMinimal(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin1.isValid(   ) );
           QVERIFY(  end1.isValid(   ) );
           QCOMPARE(  begin1,   QTime(  8,   0 ) );
          
           auto [begin2,   end2] =
           manager.reserveImplMinimal(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin2.isValid(   ) );
           QVERIFY(  end2.isValid(   ) );
           QCOMPARE(  begin2,   QTime(  12,   0 ) );
          
           auto [begin3,   end3] =
           manager.reserveImplMinimal(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin3.isValid(   ) );
           QVERIFY(  end3.isValid(   ) );
           QCOMPARE(  begin3,   QTime(  13,   0 ) );
          
           QCOMPARE(  manager.slotCount(   ),   1 );
          }
          
     112  void DayManagerTest::testReserveImplRandom(   ) {
           DayManager manager(  QTime(  9,   0 ),   QTime(  12,   0 ) );
          
           for (  int i = 0; i < 3; ++i ) {
           QCOMPARE(  manager.slotCount(   ),   1 );
           auto [begin,   end] =
           manager.reserveImplRandom(  QTime(  1,   0 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           }
          
           QCOMPARE(  manager.slotCount(   ),   0 );
          }
          
     126  void DayManagerTest::testReserveSuccess(   ) {
           DayManager manager(  QTime(  9,   0 ),   QTime(  12,   0 ) );
           manager.setMinDuration(  QTime(  0,   30 ) );
          
           auto [begin,   end] = manager.reserve(  QTime(  1,   0 ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           QCOMPARE(  begin.hour(   ),   9 );
           QCOMPARE(  end.hour(   ),   10 );
           QCOMPARE(  end.minute(   ),   0 );
          
           std::tie(  begin,   end ) = manager.reserve(  QTime(  1,   30 ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           QCOMPARE(  begin.hour(   ),   10 );
           QCOMPARE(  end.hour(   ),   11 );
           QCOMPARE(  end.minute(   ),   30 );
          }
          
     145  void DayManagerTest::testReserveFailure(   ) {
           DayManager manager(  QTime(  9,   0 ),   QTime(  12,   0 ) );
           manager.setMinDuration(  QTime(  0,   30 ) );
          
           QCOMPARE(  manager.duration(  QTime(  9,   0 ) ),   QTime(  3,   0 ) );
          
           QTime begin,   end;
          
           std::tie(  begin,   end ) = manager.reserve(  QTime(  4,   0 ) );
           QVERIFY(  !begin.isValid(   ) );
           QVERIFY(  !end.isValid(   ) );
          
           std::tie(  begin,   end ) = manager.reserve(  QTime(  3,   0,   1 ) );
           QVERIFY(  !begin.isValid(   ) );
           QVERIFY(  !end.isValid(   ) );
          
           std::tie(  begin,   end ) = manager.reserve(  QTime(  2,   30 ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           QVERIFY(  !manager.availableSlots_.isEmpty(   ) );
           QCOMPARE(  manager.duration(  end ),   QTime(  0,   30 ) );
          
           std::tie(  begin,   end ) = manager.reserve(  QTime(  0,   0,   1 ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           QCOMPARE(  begin.msecsTo(  end ),   QTime(  0,   0,   1 ).msecsSinceStartOfDay(   ) );
           QVERIFY(  manager.availableSlots_.isEmpty(   ) );
          }
          
     174  QTEST_MAIN(  DayManagerTest )
          #include "day_manager_test.moc"

./tests_class/reservation_manager_test.cpp

       1  #include "reservation_manager.h"
          #include <QtTest>
          
       4  class ReservationManagerTest : public QObject {
           Q_OBJECT
          
          private slots:
           void testReserveFailure(   );
           void testReserveSuccess(   );
           void testReserveBound(   );
          };
          
      13  void ReservationManagerTest::testReserveFailure(   ) {
           DayManager dayManager(  QTime(  8,   0 ),   QTime(  9,   0 ) );
           ReservationManager manager(  3,   3,   dayManager );
          
           QDateTime begin;
           QTime end;
          
           std::tie(  begin,   end ) = manager.reserve(  QTime(  2,   0 ) );
           QVERIFY(  !begin.isValid(   ) );
           QVERIFY(  !end.isValid(   ) );
          }
          
      25  void ReservationManagerTest::testReserveSuccess(   ) {
           DayManager dayManager(  QTime(  8,   0 ),   QTime(  9,   0 ) );
           ReservationManager manager(  3,   4,   dayManager );
          
           QDateTime begin;
           QTime end;
          
           for (  int i = 0; i < 3; ++i ) {
           std::tie(  begin,   end ) = manager.reserve(  QTime(  1,   0 ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           }
           QCOMPARE(  manager.days_.size(   ),   1 );
          
           const auto &keys = manager.days_.keys(   );
           auto it = std::find_if(  keys.cbegin(   ),   keys.cend(   ),   [](  const auto &key ) {
           for (  auto expected : {1,   2,   3,   4} ) {
           if (  key == expected ) {
           return true;
           }
           }
           return false;
           } );
          
           QVERIFY(  it != keys.cend(   ) );
          }
          
      52  void ReservationManagerTest::testReserveBound(   ) {
           DayManager dayManager(  QTime(  8,   0 ),   QTime(  9,   0 ) );
           ReservationManager manager(  3,   4,   dayManager );
          
           QDateTime begin;
           QTime end;
          
           for (  int i = 0; i < 4; ++i ) {
           std::tie(  begin,   end ) = manager.reserve(  QTime(  1,   0 ) );
           QVERIFY(  begin.isValid(   ) );
           QVERIFY(  end.isValid(   ) );
           }
           QVERIFY(  manager.days_.isEmpty(   ) );
          }
          
      67  QTEST_MAIN(  ReservationManagerTest )
          #include "reservation_manager_test.moc"

./tests_schema/tests/main_test.cpp

       1  #include <gtest/gtest.h>
          
       3  int main(  int argc,   char **argv ) {
           ::testing::InitGoogleTest(  &argc,   argv );
           return RUN_ALL_TESTS(   );
          }

./tests_schema/tests/sqlite_database_crud_test.cpp

          #include "sqlite_database.h"
          #include "sqlite_file_executor.h"
          #include "gtest/gtest.h"
          
          // WARNING: Foreign keys are off
          // WARNING: Triggers are off
          
          const std::string TEST_DB = "test.db";
          
          // Test fixture for SQLiteDatabase
      11  class SQLiteDatabaseCRUDTest : public ::testing::Test {
          protected:
      13   SQLiteDatabase *db_;
          
           void SetUp(   ) override {
           db_ = new SQLiteDatabase(  TEST_DB );
           SQLiteFileExecutor executor(  TEST_DB );
           executor.executeSQLFile(  "sql/cinema_db_drop_table.sql" );
           executor.executeSQLFile(  "sql/cinema_db_create_table.sql" );
          
           // Not necessarily,   since the default setting
           std::string sql;
           FileReaderInterface *reader = &executor;
           reader->read(  "sql/cinema_db_foreign_key_off.sql",   sql );
           db_->executeNonQuery(  sql );
           }
          
           void TearDown(   ) override {
           delete db_;
      30   remove(  TEST_DB.c_str(   ) );
           }
          };
          
          // INFO: ----- GENERAL -----
          
          // Check that the test fixture is working
          class SimpleClass : public ::testing::Test {
          protected:
           void SetUp(   ) override {}
           void TearDown(   ) override {}
          };
          // Simle test
          TEST_F(  SimpleClass,   SimpleTest ) { EXPECT_EQ(  1,   1 ); }
          
          // INFO: ----- GENERAL -----
          
          // Test checking if the database is available
          TEST_F(  SQLiteDatabaseCRUDTest,   IsDatabaseAvailable ) {
           EXPECT_TRUE(  db_->isDatabaseAvailable(   ) );
          }
          
          // INFO: ----- GENRE -----
          
          // Test adding a genre
          TEST_F(  SQLiteDatabaseCRUDTest,   AddGenre ) {
           NewGenre genre{"Action"};
           EXPECT_TRUE(  db_->addGenre(  genre ) );
          
           auto genres = db_->getAllGenres(   );
           ASSERT_EQ(  genres.size(   ),   1 );
           EXPECT_EQ(  genres[0].genre_name,   "Action" );
          }
          
          // Test getting a genre by ID
          TEST_F(  SQLiteDatabaseCRUDTest,   GetGenreById ) {
           NewGenre genre{"Drama"};
           db_->addGenre(  genre );
          
           Genre retrieved_genre = db_->getGenre(  1 );
           EXPECT_EQ(  retrieved_genre.genre_id,   1 );
           EXPECT_EQ(  retrieved_genre.genre_name,   "Drama" );
          }
          
          // Test updating a genre
          TEST_F(  SQLiteDatabaseCRUDTest,   UpdateGenre ) {
           NewGenre genre{"Drama"};
           db_->addGenre(  genre );
          
           Genre updated_genre = db_->getGenre(  1 );
           updated_genre.genre_name = "Horror";
           EXPECT_TRUE(  db_->updateGenre(  updated_genre ) );
          
           Genre retrieved_genre = db_->getGenre(  1 );
           EXPECT_EQ(  retrieved_genre.genre_id,   1 );
           EXPECT_EQ(  retrieved_genre.genre_name,   "Horror" );
          }
          
          // Test removing a genre
          TEST_F(  SQLiteDatabaseCRUDTest,   RemoveGenre ) {
           NewGenre genre{"Horror"};
           db_->addGenre(  genre );
           EXPECT_TRUE(  db_->removeGenre(  1 ) );
          
           auto genres = db_->getAllGenres(   );
           EXPECT_TRUE(  genres.empty(   ) );
          }
          
          // Test getting all genres
          TEST_F(  SQLiteDatabaseCRUDTest,   GetAllGenres ) {
           db_->addGenre(  {"Comedy"} );
           db_->addGenre(  {"Thriller"} );
          
           auto genres = db_->getAllGenres(   );
           ASSERT_EQ(  genres.size(   ),   2 );
           EXPECT_EQ(  genres[0].genre_name,   "Comedy" );
           EXPECT_EQ(  genres[1].genre_name,   "Thriller" );
          }
          
          // INFO: ----- MOVIE -----
          
          // Test adding a movie
          TEST_F(  SQLiteDatabaseCRUDTest,   AddMovie ) {
           NewMovie movie{"The Shawshank Redemption",   1,   142};
           EXPECT_TRUE(  db_->addMovie(  movie ) );
          
           auto movies = db_->getAllMovies(   );
           ASSERT_EQ(  movies.size(   ),   1 );
           EXPECT_EQ(  movies[0].title,   "The Shawshank Redemption" );
           EXPECT_EQ(  movies[0].genre_id,   1 );
           EXPECT_EQ(  movies[0].duration,   142 );
          }
          
          // Test getting a movie by ID
          TEST_F(  SQLiteDatabaseCRUDTest,   GetMovieById ) {
           NewMovie movie{"The Godfather",   2,   175};
           db_->addMovie(  movie );
          
           Movie retrieved_movie = db_->getMovie(  1 );
           EXPECT_EQ(  retrieved_movie.movie_id,   1 );
           EXPECT_EQ(  retrieved_movie.title,   "The Godfather" );
           EXPECT_EQ(  retrieved_movie.genre_id,   2 );
           EXPECT_EQ(  retrieved_movie.duration,   175 );
          }
          
          // Test removing a movie
          TEST_F(  SQLiteDatabaseCRUDTest,   RemoveMovie ) {
           NewMovie movie{"The Dark Knight",   3,   152};
           db_->addMovie(  movie );
           EXPECT_TRUE(  db_->removeMovie(  1 ) );
          
           auto movies = db_->getAllMovies(   );
           EXPECT_TRUE(  movies.empty(   ) );
          }
          
          // Test updating a movie
          TEST_F(  SQLiteDatabaseCRUDTest,   UpdateMovie ) {
           NewMovie movie{"The Shawshank Redemption",   1,   142};
           db_->addMovie(  movie );
          
           Movie updated_movie = db_->getMovie(  1 );
           updated_movie.title = "The Godfather";
           updated_movie.duration = 155;
           EXPECT_TRUE(  db_->updateMovie(  updated_movie ) );
          
           Movie retrieved_movie = db_->getMovie(  1 );
           EXPECT_EQ(  retrieved_movie.movie_id,   1 );
           EXPECT_EQ(  retrieved_movie.title,   "The Godfather" );
           EXPECT_EQ(  retrieved_movie.genre_id,   1 );
           EXPECT_EQ(  retrieved_movie.duration,   155 );
          }
          
          // Test getting all movies
          TEST_F(  SQLiteDatabaseCRUDTest,   GetAllMovies ) {
           db_->addMovie(  {"The Shawshank Redemption",   1,   142} );
           db_->addMovie(  {"The Godfather",   2,   175} );
          
           auto movies = db_->getAllMovies(   );
           ASSERT_EQ(  movies.size(   ),   2 );
           EXPECT_EQ(  movies[0].title,   "The Shawshank Redemption" );
           EXPECT_EQ(  movies[0].genre_id,   1 );
           EXPECT_EQ(  movies[0].duration,   142 );
           EXPECT_EQ(  movies[1].title,   "The Godfather" );
           EXPECT_EQ(  movies[1].genre_id,   2 );
           EXPECT_EQ(  movies[1].duration,   175 );
          }
          
          // INFO: ----- HALL -----
          
          // Test adding a hall
          TEST_F(  SQLiteDatabaseCRUDTest,   AddHall ) {
           NewHall hall{"Hall 1",   132};
           EXPECT_TRUE(  db_->addHall(  hall ) );
          
           auto halls = db_->getAllHalls(   );
           ASSERT_EQ(  halls.size(   ),   1 );
           EXPECT_EQ(  halls[0].hall_id,   1 );
           EXPECT_EQ(  halls[0].hall_name,   "Hall 1" );
           EXPECT_EQ(  halls[0].capacity,   132 );
          }
          
          // Test getting a hall by ID
          TEST_F(  SQLiteDatabaseCRUDTest,   GetHallById ) {
           NewHall hall{"Hall 2",   142};
           db_->addHall(  hall );
          
           Hall retrieved_hall = db_->getHall(  1 );
           EXPECT_EQ(  retrieved_hall.hall_id,   1 );
           EXPECT_EQ(  retrieved_hall.hall_name,   "Hall 2" );
           EXPECT_EQ(  retrieved_hall.capacity,   142 );
          }
          
          // Test removing a hall
          TEST_F(  SQLiteDatabaseCRUDTest,   RemoveHall ) {
           NewHall hall{"Hall 3",   152};
           db_->addHall(  hall );
           EXPECT_TRUE(  db_->removeHall(  1 ) );
          
           auto halls = db_->getAllHalls(   );
           EXPECT_TRUE(  halls.empty(   ) );
          }
          
          // Test updating a hall
          TEST_F(  SQLiteDatabaseCRUDTest,   UpdateHall ) {
           NewHall hall{"Hall 4",   162};
           db_->addHall(  hall );
          
           Hall updated_hall = db_->getHall(  1 );
           updated_hall.hall_name = "Hall 5";
           updated_hall.capacity = 172;
           EXPECT_TRUE(  db_->updateHall(  updated_hall ) );
          
           Hall retrieved_hall = db_->getHall(  1 );
           EXPECT_EQ(  retrieved_hall.hall_id,   1 );
           EXPECT_EQ(  retrieved_hall.hall_name,   "Hall 5" );
           EXPECT_EQ(  retrieved_hall.capacity,   172 );
          }
          
          // Test getting all halls
          TEST_F(  SQLiteDatabaseCRUDTest,   GetAllHalls ) {
           db_->addHall(  {"Hall 6",   172} );
           db_->addHall(  {"Hall 7",   182} );
          
           auto halls = db_->getAllHalls(   );
           ASSERT_EQ(  halls.size(   ),   2 );
           EXPECT_EQ(  halls[0].hall_id,   1 );
           EXPECT_EQ(  halls[0].hall_name,   "Hall 6" );
           EXPECT_EQ(  halls[0].capacity,   172 );
           EXPECT_EQ(  halls[1].hall_id,   2 );
           EXPECT_EQ(  halls[1].hall_name,   "Hall 7" );
           EXPECT_EQ(  halls[1].capacity,   182 );
          }
          
          // INFO: ----- SESSION -----
          
          // Test adding a session
          TEST_F(  SQLiteDatabaseCRUDTest,   AddSession ) {
           NewSession session{1,   1,   "2025-01-01 12:00:00",   10};
           EXPECT_TRUE(  db_->addSession(  session ) );
          
           auto sessions = db_->getAllSessions(   );
           ASSERT_EQ(  sessions.size(   ),   1 );
           EXPECT_EQ(  sessions[0].movie_id,   1 );
           EXPECT_EQ(  sessions[0].hall_id,   1 );
           EXPECT_EQ(  sessions[0].begin_at,   "2025-01-01 12:00:00" );
           EXPECT_EQ(  sessions[0].ticket_price,   10 );
          }
          
          // Test getting a session by ID
          TEST_F(  SQLiteDatabaseCRUDTest,   GetSessionById ) {
           NewSession session{2,   2,   "2025-01-01 12:00:00",   10};
           db_->addSession(  session );
          
           Session retrieved_session = db_->getSession(  1 );
           EXPECT_EQ(  retrieved_session.session_id,   1 );
           EXPECT_EQ(  retrieved_session.movie_id,   2 );
           EXPECT_EQ(  retrieved_session.hall_id,   2 );
           EXPECT_EQ(  retrieved_session.begin_at,   "2025-01-01 12:00:00" );
           EXPECT_EQ(  retrieved_session.ticket_price,   10 );
          }
          
          // Test removing a session
          TEST_F(  SQLiteDatabaseCRUDTest,   RemoveSession ) {
           NewSession session{3,   3,   "2025-01-01 12:00:00",   10};
           db_->addSession(  session );
           EXPECT_TRUE(  db_->removeSession(  1 ) );
          
           auto sessions = db_->getAllSessions(   );
           EXPECT_TRUE(  sessions.empty(   ) );
          }
          
          // Test updating a session
          TEST_F(  SQLiteDatabaseCRUDTest,   UpdateSession ) {
           NewSession session{4,   4,   "2025-01-01 12:00:00",   10};
           db_->addSession(  session );
          
           Session updated_session = db_->getSession(  1 );
           updated_session.begin_at = "2025-01-01 13:00:00";
           EXPECT_TRUE(  db_->updateSession(  updated_session ) );
          
           Session retrieved_session = db_->getSession(  1 );
           EXPECT_EQ(  retrieved_session.session_id,   1 );
           EXPECT_EQ(  retrieved_session.movie_id,   4 );
           EXPECT_EQ(  retrieved_session.hall_id,   4 );
           EXPECT_EQ(  retrieved_session.begin_at,   "2025-01-01 13:00:00" );
           EXPECT_EQ(  retrieved_session.ticket_price,   10 );
          }
          
          // Test getting all sessions
          TEST_F(  SQLiteDatabaseCRUDTest,   GetAllSessions ) {
           db_->addSession(  {5,   5,   "2025-01-01 12:00:00",   10} );
           db_->addSession(  {6,   6,   "2025-01-01 13:00:00",   10} );
          
           auto sessions = db_->getAllSessions(   );
           ASSERT_EQ(  sessions.size(   ),   2 );
           EXPECT_EQ(  sessions[0].movie_id,   5 );
           EXPECT_EQ(  sessions[0].hall_id,   5 );
           EXPECT_EQ(  sessions[0].begin_at,   "2025-01-01 12:00:00" );
           EXPECT_EQ(  sessions[0].ticket_price,   10 );
           EXPECT_EQ(  sessions[1].movie_id,   6 );
           EXPECT_EQ(  sessions[1].hall_id,   6 );
           EXPECT_EQ(  sessions[1].begin_at,   "2025-01-01 13:00:00" );
           EXPECT_EQ(  sessions[1].ticket_price,   10 );
          }
          
          // INFO: ----- TICKET -----
          
          // Test adding a ticket
          TEST_F(  SQLiteDatabaseCRUDTest,   AddTicket ) {
           NewTicket ticket{1,   1,   "2025-01-01 12:00:00"};
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          
           auto tickets = db_->getAllTickets(   );
           ASSERT_EQ(  tickets.size(   ),   1 );
           EXPECT_EQ(  tickets[0].session_id,   1 );
           EXPECT_EQ(  tickets[0].seat_number,   1 );
           EXPECT_EQ(  tickets[0].sold_at,   "2025-01-01 12:00:00" );
          }
          
          // Test getting a ticket by ID
          TEST_F(  SQLiteDatabaseCRUDTest,   GetTicketById ) {
           NewTicket ticket{2,   2,   "2025-01-01 12:00:00"};
           db_->addTicket(  ticket );
          
           Ticket retrieved_ticket = db_->getTicket(  1 );
           EXPECT_EQ(  retrieved_ticket.ticket_id,   1 );
           EXPECT_EQ(  retrieved_ticket.session_id,   2 );
           EXPECT_EQ(  retrieved_ticket.seat_number,   2 );
           EXPECT_EQ(  retrieved_ticket.sold_at,   "2025-01-01 12:00:00" );
          }
          
          // Test removing a ticket
          TEST_F(  SQLiteDatabaseCRUDTest,   RemoveTicket ) {
           NewTicket ticket{3,   3,   "2025-01-01 12:00:00"};
           db_->addTicket(  ticket );
           EXPECT_TRUE(  db_->removeTicket(  1 ) );
          
           auto tickets = db_->getAllTickets(   );
           EXPECT_TRUE(  tickets.empty(   ) );
          }
          
          // Test updating a ticket
          TEST_F(  SQLiteDatabaseCRUDTest,   UpdateTicket ) {
           NewTicket ticket{4,   4,   "2025-01-01 12:00:00"};
           db_->addTicket(  ticket );
          
           Ticket updated_ticket = db_->getTicket(  1 );
           updated_ticket.seat_number = 5;
           EXPECT_TRUE(  db_->updateTicket(  updated_ticket ) );
          
           Ticket retrieved_ticket = db_->getTicket(  1 );
           EXPECT_EQ(  retrieved_ticket.ticket_id,   1 );
           EXPECT_EQ(  retrieved_ticket.session_id,   4 );
           EXPECT_EQ(  retrieved_ticket.seat_number,   5 );
           EXPECT_EQ(  retrieved_ticket.sold_at,   "2025-01-01 12:00:00" );
          }
          
          // Test getting all tickets
          TEST_F(  SQLiteDatabaseCRUDTest,   GetAllTickets ) {
           db_->addTicket(  {5,   5,   "2025-01-01 12:00:00"} );
           db_->addTicket(  {6,   6,   "2025-01-01 13:00:00"} );
          
           auto tickets = db_->getAllTickets(   );
           ASSERT_EQ(  tickets.size(   ),   2 );
           EXPECT_EQ(  tickets[0].session_id,   5 );
           EXPECT_EQ(  tickets[0].seat_number,   5 );
           EXPECT_EQ(  tickets[0].sold_at,   "2025-01-01 12:00:00" );
           EXPECT_EQ(  tickets[1].session_id,   6 );
           EXPECT_EQ(  tickets[1].seat_number,   6 );
           EXPECT_EQ(  tickets[1].sold_at,   "2025-01-01 13:00:00" );
          }

./tests_schema/tests/sqlite_database_foreign_key_test.cpp

          #include "sqlite_database.h"
          #include "sqlite_file_executor.h"
          #include "gtest/gtest.h"
          
          // WARNING: Triggers are off
          
          const std::string TEST_DB = "test.db";
          
          // Test fixture for SQLiteDatabase
      10  class SQLiteDatabaseForeignKeyTest : public ::testing::Test {
          
          protected:
      13   SQLiteDatabase *db_;
          
          public:
           // void pragmaForeignKeysOn(   ) {
           // const std::string query = "PRAGMA foreign_keys = ON;";
           // db_->executeNonQuery(  query );
           // }
          
           std::vector<std::vector<std::string>> pragmaForeignKeys(   ) {
           const std::string query = "PRAGMA foreign_keys;";
           auto results = db_->executeSelectQuery(  query );
           return results;
           }
          
          protected:
           void SetUp(   ) override {
           db_ = new SQLiteDatabase(  TEST_DB );
      30   SQLiteFileExecutor executor(  TEST_DB );
      31   executor.executeSQLFile(  "sql/cinema_db_drop_table.sql" );
      32   executor.executeSQLFile(  "sql/cinema_db_create_table.sql" );
          
           // pragmaForeignKeysOn(   );
          
           std::string sql;
           FileReaderInterface *reader = &executor;
           reader->read(  "sql/cinema_db_foreign_key_on.sql",   sql );
           db_->executeNonQuery(  sql );
           }
          
           void TearDown(   ) override {
           delete db_;
           remove(  TEST_DB.c_str(   ) );
           }
          };
          
          // INFO: ----- GENERAL -----
          
          // Check pragma foreign_keys is true
          TEST_F(  SQLiteDatabaseForeignKeyTest,   PragmaForeignKeys ) {
           auto results = pragmaForeignKeys(   );
           ASSERT_FALSE(  results.empty(   ) );
           ASSERT_EQ(  results[0][0],   "1" );
          }
          
          // Test adding a movie
          TEST_F(  SQLiteDatabaseForeignKeyTest,   AddMovie ) {
           NewMovie movie{"The Shawshank Redemption",   1,   142};
           EXPECT_FALSE(  db_->addMovie(  movie ) );
          
           auto movies = db_->getAllMovies(   );
           ASSERT_TRUE(  movies.empty(   ) );
          
           NewGenre genre{"Drama"};
           EXPECT_TRUE(  db_->addGenre(  genre ) );
           EXPECT_TRUE(  db_->addMovie(  movie ) );
           movies = db_->getAllMovies(   );
           ASSERT_FALSE(  movies.empty(   ) );
          }
          
          // Test adding session
          TEST_F(  SQLiteDatabaseForeignKeyTest,   AddSession ) {
           NewSession session{1,   1,   "2025-01-01 12:00:00",   10};
           EXPECT_FALSE(  db_->addSession(  session ) );
          
           NewHall hall{"Hall 1",   100};
           db_->addHall(  hall );
           EXPECT_FALSE(  db_->addSession(  session ) );
          
           NewGenre genre{"Drama"};
           db_->addGenre(  genre );
           NewMovie movie{"The Shawshank Redemption",   1,   142};
           db_->addMovie(  movie );
           EXPECT_TRUE(  db_->addSession(  session ) );
          }
          
          // Test adding ticket
          TEST_F(  SQLiteDatabaseForeignKeyTest,   AddTicket ) {
           NewTicket ticket{1,   1,   "2025-01-01 12:00:00"};
           EXPECT_FALSE(  db_->addTicket(  ticket ) );
          
           NewHall hall{"Hall 1",   100};
           db_->addHall(  hall );
           NewGenre genre{"Drama"};
           db_->addGenre(  genre );
           NewMovie movie{"The Shawshank Redemption",   1,   142};
           db_->addMovie(  movie );
           NewSession session{1,   1,   "2025-01-01 12:00:00",   10};
           db_->addSession(  session );
          
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          }

./tests_schema/tests/sqlite_database_trigger_test.cpp

          #include "sqlite_database.h"
          #include "sqlite_file_executor.h"
          #include "gtest/gtest.h"
          
          // WARNING: Foreign keys are off
          
          const std::string TEST_DB = "test.db";
          
          // Test fixture for SQLiteDatabase
      10  class SQLiteDatabaseTriggerTest : public ::testing::Test {
          protected:
      12   SQLiteDatabase *db_;
          
           void SetUp(   ) override {
           db_ = new SQLiteDatabase(  TEST_DB );
           SQLiteFileExecutor executor(  TEST_DB );
           executor.executeSQLFile(  "sql/cinema_db_drop_table.sql" );
           executor.executeSQLFile(  "sql/cinema_db_create_table.sql" );
           executor.executeSQLFile(  "sql/cinema_db_drop_trigger.sql" );
           executor.executeSQLFile(  "sql/cinema_db_create_trigger.sql" );
          
           // Not necessarily,   since the default setting
           std::string sql;
           FileReaderInterface *reader = &executor;
           reader->read(  "sql/cinema_db_foreign_key_off.sql",   sql );
           db_->executeNonQuery(  sql );
           }
          
           void TearDown(   ) override {
           delete db_;
      31   remove(  TEST_DB.c_str(   ) );
           }
          };
          
          // INFO: ----- TICKET -----
          
          // Test trigger check_seat_number
          TEST_F(  SQLiteDatabaseTriggerTest,   CheckSeatNumber ) {
           db_->addHall(  {"Hall 1",   100} );
           db_->addSession(  {1,   1,   "2025-01-01 12:00:00",   10} );
          
           NewTicket ticket;
           ticket.session_id = 1;
           ticket.sold_at = "2025-01-01 12:00:00";
          
           ticket.seat_number = 101;
           EXPECT_FALSE(  db_->addTicket(  ticket ) );
          
           ticket.seat_number = 100;
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          
           ticket.seat_number = 1;
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          }
          
          // Test trigger check_session_not_started
          TEST_F(  SQLiteDatabaseTriggerTest,   CheckSessionNotStarted ) {
           db_->addSession(  {1,   1,   "2025-01-01 12:00:00",   10} );
          
           NewTicket ticket;
           ticket.session_id = 1;
           ticket.seat_number = 1;
          
           ticket.sold_at = "2025-01-01 13:00:00";
           EXPECT_FALSE(  db_->addTicket(  ticket ) );
          
           ticket.sold_at = "2025-01-01 11:00:00";
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          
           ticket.seat_number = 2;
           ticket.sold_at = "2025-01-01 12:00:00";
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          }
          
          // Test trigger prevent_late_ticket_purchase
          TEST_F(  SQLiteDatabaseTriggerTest,   PreventLateTicketPurchase ) {
           db_->addHall(  {"Hall 1",   100} );
           db_->addSession(  {1,   1,   "2025-01-01 12:00:00",   10} );
          
           NewTicket ticket;
           ticket.session_id = 1;
           ticket.seat_number = 1;
          
           ticket.sold_at = "2025-01-01 13:00:00";
           EXPECT_FALSE(  db_->addTicket(  ticket ) );
          
           ticket.sold_at = "2025-01-01 12:00:00";
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          
           ticket.seat_number = 2;
           ticket.sold_at = "2025-01-01 11:00:00";
           EXPECT_TRUE(  db_->addTicket(  ticket ) );
          }
          
          // INFO: ----- REFUND -----
          
          // Test trigger validate_refund_amount
          TEST_F(  SQLiteDatabaseTriggerTest,   ValidateRefundAmount ) {
           db_->addHall(  {"Hall 1",   100} );
           db_->addSession(  {1,   1,   "2025-01-01 12:00:00",   10} );
          
           // ID 1
           db_->addTicket(  {1,   1,   "2025-01-01 10:00:00"} );
           // ID 2
           db_->addTicket(  {1,   2,   "2025-01-01 10:00:00"} );
          
           NewRefund refund;
           refund.ticket_id = 1;
           refund.refund_at = "2025-01-01 11:00:00";
          
           refund.refund_amount = 11;
           EXPECT_FALSE(  db_->addRefund(  refund ) );
          
           refund.refund_amount = 10;
           EXPECT_TRUE(  db_->addRefund(  refund ) );
          
           refund.ticket_id = 2;
           refund.refund_amount = 9;
           EXPECT_TRUE(  db_->addRefund(  refund ) );
          }
          
          // Test trigger validate_refund_date
          TEST_F(  SQLiteDatabaseTriggerTest,   ValidateRefundDate ) {
           db_->addHall(  {"Hall 1",   100} );
           db_->addSession(  {1,   1,   "2025-01-01 12:00:00",   10} );
          
           // ID 1
           db_->addTicket(  {1,   1,   "2025-01-01 10:00:00"} );
           // ID 2
           db_->addTicket(  {1,   2,   "2025-01-01 10:00:00"} );
          
           NewRefund refund;
           refund.ticket_id = 1;
           refund.refund_amount = 10;
          
           refund.refund_at = "2025-01-01 09:00:00";
           EXPECT_FALSE(  db_->addRefund(  refund ) );
          
           refund.refund_at = "2025-01-01 11:00:00";
           EXPECT_TRUE(  db_->addRefund(  refund ) );
          
           refund.refund_at = "2025-01-01 12:00:00";
           EXPECT_FALSE(  db_->addRefund(  refund ) );
          }

./tests_schema/tests_sources/file_reader.cpp

       1  #include "file_reader.h"
          
          #include <fstream>
          #include <iostream>
          #include <sstream>
          
          // Read a SQL file
       8  bool FileReader::read(  const std::string &sqlFilePath,   std::string &sql ) {
           // Try read the SQL file
           std::ifstream file(  sqlFilePath );
           if (  !file.is_open(   ) ) {
           std::cerr << "Error opening file:" << sqlFilePath << std::endl;
           return false;
           }
          
           std::stringstream buffer;
           buffer << file.rdbuf(   );
          
           sql = buffer.str(   );
           file.close(   );
          
           return true;
          }

./tests_schema/tests_sources/sqlite_database.cpp

       1  #include "sqlite_database.h"
          
          #include "sqlite_statement.h"
          #include <iostream>
          #include <utility> // std::exchange
          
          // Constructor: Opens the SQLite database
       8  SQLiteDatabase::SQLiteDatabase(  const std::string &db_file ) {
           // WARNING: The database is not blocked
           if (  sqlite3_open(  db_file.c_str(   ),   &db_ ) != SQLITE_OK ) {
           std::cerr << "Failed to open database:" << sqlite3_errmsg(  db_ ) << std::endl;
           db_ = nullptr;
           }
          }
          
          // Destructor: Closes the database connection
      17  SQLiteDatabase::~SQLiteDatabase(   ) {
           if (  db_ ) {
           sqlite3_close(  db_ );
           }
          }
          
      23  SQLiteDatabase::SQLiteDatabase(  SQLiteDatabase &&other ) noexcept
           : db_(  std::exchange(  other.db_,   nullptr ) ) {}
          
      26  SQLiteDatabase &SQLiteDatabase::operator=(  SQLiteDatabase &&other ) noexcept {
           if (  this != &other ) {
           if (  db_ ) {
           sqlite3_close(  db_ );
           }
           db_ = std::exchange(  other.db_,   nullptr );
           }
           return *this;
          }
          
          // Helper function for executing non-SELECT queries
      37  int SQLiteDatabase::executeNonQuery(  
      38   const std::string &query,  
      39   const std::function<void(  SQLiteStatement & )> &bindFunc ) {
           SQLiteStatement stmt(  db_,   query );
          
           if (  db_ == nullptr ) {
           throw std::runtime_error(  "Database is not open" );
           }
          
           if (  bindFunc ) {
           bindFunc(  stmt );
           }
          
           if (  !stmt.execute(   ) ) {
           std::cerr << "Failed to execute query:" << sqlite3_errmsg(  db_ );
           }
          
           return stmt.changes(   );
          }
          
          // Helper function for executing SELECT queries
      58  std::vector<std::vector<std::string>> SQLiteDatabase::executeSelectQuery(  
           const std::string &query,  
           const std::function<void(  SQLiteStatement & )> &bindFunc ) {
           std::vector<std::vector<std::string>> results;
           SQLiteStatement stmt(  db_,   query );
          
           if (  db_ == nullptr ) {
           throw std::runtime_error(  "Database is not open" );
           }
          
           if (  bindFunc ) {
           bindFunc(  stmt );
           }
          
           while (  stmt.step(   ) ) {
           std::vector<std::string> row;
           for (  int i = 0,   count = stmt.getColumnCount(   ); i < count; ++i ) {
           row.emplace_back(  stmt.getColumnText(  i ) );
           }
           results.push_back(  row );
           }
          
           return results;
          }
          
          // INFO: ----- GENRE -----
          
          // Add a new genre
      86  bool SQLiteDatabase::addGenre(  const NewGenre &genre ) {
           const std::string query = "INSERT INTO genres (  genre_name ) VALUES (  ? );";
           int changes = executeNonQuery(  query,   [&genre](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   genre.genre_name );
           } );
          
           return changes > 0;
          }
          
          // Remove a genre by ID
      96  bool SQLiteDatabase::removeGenre(  int genre_id ) {
           const std::string query = "DELETE FROM genres WHERE genre_id = ?;";
           int changes = executeNonQuery(  
           query,   [&genre_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   genre_id ); } );
          
           return changes > 0;
          }
          
          // Update an existing genre
     105  bool SQLiteDatabase::updateGenre(  const Genre &genre ) {
           const std::string query =
           "UPDATE genres SET genre_name = ? WHERE genre_id = ?;";
           int changes = executeNonQuery(  query,   [&genre](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   genre.genre_name );
           stmt.bind(  2,   genre.genre_id );
           } );
          
           return changes > 0;
          }
          
          // Get a genre by ID
     117  Genre SQLiteDatabase::getGenre(  int genre_id ) {
           const std::string query = "SELECT * FROM genres WHERE genre_id = ?;";
           auto result = executeSelectQuery(  
           query,   [&genre_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   genre_id ); } );
          
           if (  !result.empty(   ) ) {
           auto genre_id = std::stoi(  result[0][0] );
           auto genre_name = result[0][1];
           Genre genre(  genre_id,   genre_name );
           return genre;
           }
           return {};
          }
          
          // Get all genres
     132  std::vector<Genre> SQLiteDatabase::getAllGenres(   ) {
           const std::string query = "SELECT * FROM genres;";
           auto result = executeSelectQuery(  query );
          
           std::vector<Genre> genres;
           for (  const auto &row : result ) {
           genres.push_back(  {std::stoi(  row[0] ),   row[1]} );
           }
          
           return genres;
          }
          
          // INFO: ----- MOVIE -----
          
          // Add a new movie
     147  bool SQLiteDatabase::addMovie(  const NewMovie &movie ) {
           const std::string query =
           "INSERT INTO movies (  title,   genre_id,   duration ) VALUES (  ?,   ?,   ? );";
           int changes = executeNonQuery(  query,   [&movie](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   movie.title );
           stmt.bind(  2,   movie.genre_id );
           stmt.bind(  3,   movie.duration );
           } );
          
           return changes > 0;
          }
          
          // Remove a movie by ID
     160  bool SQLiteDatabase::removeMovie(  int movie_id ) {
           const std::string query = "DELETE FROM movies WHERE movie_id = ?;";
           int changes = executeNonQuery(  
           query,   [&movie_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   movie_id ); } );
          
           return changes > 0;
          }
          
          // Update an existing movie
     169  bool SQLiteDatabase::updateMovie(  const Movie &movie ) {
           const std::string query =
           "UPDATE movies SET title = ?,   genre_id = ?,   duration = ? WHERE movie_id "
           "= ?;";
           int changes = executeNonQuery(  query,   [&movie](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   movie.title );
           stmt.bind(  2,   movie.genre_id );
           stmt.bind(  3,   movie.duration );
           stmt.bind(  4,   movie.movie_id );
           } );
          
           return changes > 0;
          }
          
          // Get a movie by ID
     184  Movie SQLiteDatabase::getMovie(  int movie_id ) {
           const std::string query = "SELECT * FROM movies WHERE movie_id = ?;";
           auto result = executeSelectQuery(  
           query,   [&movie_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   movie_id ); } );
          
           if (  !result.empty(   ) ) {
           auto movie_id = std::stoi(  result[0][0] );
           auto title = result[0][1];
           auto genre_id = std::stoi(  result[0][2] );
           auto duration = std::stoi(  result[0][3] );
           Movie movie(  movie_id,   title,   genre_id,   duration );
           return movie;
           }
           return {};
          }
          
          // Get all movies
     201  std::vector<Movie> SQLiteDatabase::getAllMovies(   ) {
           const std::string query = "SELECT * FROM movies;";
           auto result = executeSelectQuery(  query );
           std::vector<Movie> movies;
          
           for (  const auto &row : result ) {
           movies.push_back(  
           {std::stoi(  row[0] ),   row[1],   std::stoi(  row[2] ),   std::stoi(  row[3] )} );
           }
           return movies;
          }
          
          // INFO: ----- HALL -----
          
          // Add a new hall
     216  bool SQLiteDatabase::addHall(  const NewHall &hall ) {
           const std::string query =
           "INSERT INTO halls (  hall_name,   capacity ) VALUES (  ?,   ? );";
           int changes = executeNonQuery(  query,   [&hall](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   hall.hall_name );
           stmt.bind(  2,   hall.capacity );
           } );
          
           return changes > 0;
          }
          
          // Remove a hall by ID
     228  bool SQLiteDatabase::removeHall(  int hall_id ) {
           const std::string query = "DELETE FROM halls WHERE hall_id = ?;";
           int changes = executeNonQuery(  
           query,   [&hall_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   hall_id ); } );
          
           return changes > 0;
          }
          
          // Update an existing hall
     237  bool SQLiteDatabase::updateHall(  const Hall &hall ) {
           const std::string query =
           "UPDATE halls SET hall_name = ?,   capacity = ? WHERE hall_id = ?;";
           int changes = executeNonQuery(  query,   [&hall](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   hall.hall_name );
           stmt.bind(  2,   hall.capacity );
           stmt.bind(  3,   hall.hall_id );
           } );
          
           return changes > 0;
          }
          
          // Get a hall by ID
     250  Hall SQLiteDatabase::getHall(  int hall_id ) {
           const std::string query = "SELECT * FROM halls WHERE hall_id = ?;";
           auto result = executeSelectQuery(  
           query,   [&hall_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   hall_id ); } );
          
           if (  !result.empty(   ) ) {
           auto hall_id = std::stoi(  result[0][0] );
           auto hall_name = result[0][1];
           auto capacity = std::stoi(  result[0][2] );
           Hall hall(  hall_id,   hall_name,   capacity );
           return hall;
           }
           return {};
          }
          
          // Get all halls
     266  std::vector<Hall> SQLiteDatabase::getAllHalls(   ) {
           const std::string query = "SELECT * FROM halls;";
           auto result = executeSelectQuery(  query );
           std::vector<Hall> halls;
          
           for (  const auto &row : result ) {
           halls.push_back(  {std::stoi(  row[0] ),   row[1],   std::stoi(  row[2] )} );
           }
           return halls;
          }
          
          // INFO: ----- SESSION -----
          
          // Add a new session
     280  bool SQLiteDatabase::addSession(  const NewSession &session ) {
           const std::string query = "INSERT INTO sessions (  movie_id,   hall_id,   "
           "begin_at,   ticket_price ) VALUES (  ?,   ?,   ?,   ? );";
           int changes = executeNonQuery(  query,   [&session](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   session.movie_id );
           stmt.bind(  2,   session.hall_id );
           stmt.bind(  3,   session.begin_at );
           stmt.bind(  4,   session.ticket_price );
           } );
          
           return changes > 0;
          }
          
          // Remove a session by ID
     294  bool SQLiteDatabase::removeSession(  int session_id ) {
           const std::string query = "DELETE FROM sessions WHERE session_id = ?;";
           int changes = executeNonQuery(  query,   [&session_id](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   session_id );
           } );
          
           return changes > 0;
          }
          
          // Update an existing session
     304  bool SQLiteDatabase::updateSession(  const Session &session ) {
           const std::string query = "UPDATE sessions SET movie_id = ?,   hall_id = ?,   "
           "begin_at = ?,   ticket_price = ? WHERE "
           "session_id = ?;";
           int changes = executeNonQuery(  query,   [&session](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   session.movie_id );
           stmt.bind(  2,   session.hall_id );
           stmt.bind(  3,   session.begin_at );
           stmt.bind(  4,   session.ticket_price );
           stmt.bind(  5,   session.session_id );
           } );
          
           return changes > 0;
          }
          
          // Get a session by ID
     320  Session SQLiteDatabase::getSession(  int session_id ) {
           const std::string query = "SELECT * FROM sessions WHERE session_id = ?;";
           auto result = executeSelectQuery(  query,   [&session_id](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   session_id );
           } );
          
           if (  !result.empty(   ) ) {
           auto session_id = std::stoi(  result[0][0] );
           auto movie_id = std::stoi(  result[0][1] );
           auto hall_id = std::stoi(  result[0][2] );
           auto begin_at = result[0][3];
           auto ticket_price = std::stod(  result[0][4] );
           Session session(  session_id,   movie_id,   hall_id,   begin_at,   ticket_price );
           return session;
           }
           return {};
          }
          
          // Get all sessions
     339  std::vector<Session> SQLiteDatabase::getAllSessions(   ) {
           const std::string query = "SELECT * FROM sessions;";
           auto result = executeSelectQuery(  query );
           std::vector<Session> sessions;
          
           for (  const auto &row : result ) {
           sessions.push_back(  {std::stoi(  row[0] ),   std::stoi(  row[1] ),   std::stoi(  row[2] ),  
           row[3],   std::stod(  row[4] )} );
           }
           return sessions;
          }
          
          // INFO: ----- TICKET -----
          
          // Add a new ticket
     354  bool SQLiteDatabase::addTicket(  const NewTicket &ticket ) {
           const std::string query = "INSERT INTO tickets (  session_id,   seat_number,   "
           "sold_at ) VALUES (  ?,   ?,   ? );";
           int changes = executeNonQuery(  query,   [&ticket](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   ticket.session_id );
           stmt.bind(  2,   ticket.seat_number );
           stmt.bind(  3,   ticket.sold_at );
           } );
          
           return changes > 0;
          }
          
          // Remove a ticket by ID
     367  bool SQLiteDatabase::removeTicket(  int ticket_id ) {
           const std::string query = "DELETE FROM tickets WHERE ticket_id = ?;";
           int changes = executeNonQuery(  
           query,   [&ticket_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   ticket_id ); } );
          
           return changes > 0;
          }
          
          // Update an existing ticket
     376  bool SQLiteDatabase::updateTicket(  const Ticket &ticket ) {
           const std::string query = "UPDATE tickets SET session_id = ?,   seat_number = "
           "?,   sold_at = ? WHERE ticket_id = ?;";
           int changes = executeNonQuery(  query,   [&ticket](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   ticket.session_id );
           stmt.bind(  2,   ticket.seat_number );
           stmt.bind(  3,   ticket.sold_at );
           stmt.bind(  4,   ticket.ticket_id );
           } );
          
           return changes > 0;
          }
          
          // Get a ticket by ID
     390  Ticket SQLiteDatabase::getTicket(  int ticket_id ) {
           const std::string query = "SELECT * FROM tickets WHERE ticket_id = ?;";
           auto result = executeSelectQuery(  
           query,   [&ticket_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   ticket_id ); } );
          
           if (  !result.empty(   ) ) {
           auto ticket_id = std::stoi(  result[0][0] );
           auto session_id = std::stoi(  result[0][1] );
           auto seat_number = std::stoi(  result[0][2] );
           auto sold_at = result[0][3];
           Ticket ticket(  ticket_id,   session_id,   seat_number,   sold_at );
           return ticket;
           }
           return {};
          }
          
          // Get all tickets
     407  std::vector<Ticket> SQLiteDatabase::getAllTickets(   ) {
           const std::string query = "SELECT * FROM tickets;";
           auto result = executeSelectQuery(  query );
           std::vector<Ticket> tickets;
          
           for (  const auto &row : result ) {
           tickets.push_back(  
           {std::stoi(  row[0] ),   std::stoi(  row[1] ),   std::stoi(  row[2] ),   row[3]} );
           }
           return tickets;
          }
          
          // INFO: ----- REFUND -----
          
          // Add a new refund
     422  bool SQLiteDatabase::addRefund(  const NewRefund &refund ) {
           const std::string query =
           "INSERT INTO refunds (  ticket_id,   refund_at,   refund_amount ) "
           "VALUES (  ?,   ?,   ? );";
           int changes = executeNonQuery(  query,   [&refund](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   refund.ticket_id );
           stmt.bind(  2,   refund.refund_at );
           stmt.bind(  3,   refund.refund_amount );
           } );
          
           return changes > 0;
          }
          
          // Remove a refund by ID
     436  bool SQLiteDatabase::removeRefund(  int refund_id ) {
           const std::string query = "DELETE FROM refunds WHERE refund_id = ?;";
           int changes = executeNonQuery(  
           query,   [&refund_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   refund_id ); } );
          
           return changes > 0;
          }
          
          // Update an existing refund
     445  bool SQLiteDatabase::updateRefund(  const Refund &refund ) {
           const std::string query =
           "UPDATE refunds SET ticket_id = ?,   refund_at = ?,   refund_amount = ? "
           "WHERE refund_id = ?;";
           int changes = executeNonQuery(  query,   [&refund](  SQLiteStatement &stmt ) {
           stmt.bind(  1,   refund.ticket_id );
           stmt.bind(  2,   refund.refund_at );
           stmt.bind(  3,   refund.refund_amount );
           stmt.bind(  4,   refund.refund_id );
           } );
          
           return changes > 0;
          }
          
          // Get a refund by ID
     460  Refund SQLiteDatabase::getRefund(  int refund_id ) {
           const std::string query = "SELECT * FROM refunds WHERE refund_id = ?;";
           auto result = executeSelectQuery(  
           query,   [&refund_id](  SQLiteStatement &stmt ) { stmt.bind(  1,   refund_id ); } );
          
           if (  !result.empty(   ) ) {
           auto refund_id = std::stoi(  result[0][0] );
           auto ticket_id = std::stoi(  result[0][1] );
           auto refund_at = result[0][2];
           auto refund_amount = std::stod(  result[0][3] );
           Refund refund(  refund_id,   ticket_id,   refund_at,   refund_amount );
           return refund;
           }
           return {};
          }
          
          // Get all refunds
     477  std::vector<Refund> SQLiteDatabase::getAllRefunds(   ) {
           const std::string query = "SELECT * FROM refunds;";
           auto result = executeSelectQuery(  query );
           std::vector<Refund> refunds;
          
           for (  const auto &row : result ) {
           refunds.push_back(  
           {std::stoi(  row[0] ),   std::stoi(  row[1] ),   row[2],   std::stod(  row[3] )} );
           }
           return refunds;
          }

./tests_schema/tests_sources/sqlite_file_executor.cpp

       1  #include "sqlite_file_executor.h"
          
          #include "sqlite_statement.h"
          #include <iostream>
          
       6  SQLiteFileExecutor::SQLiteFileExecutor(  const std::string &dbPath ) {
           // WARNING: The database is not blocked
           if (  sqlite3_open(  dbPath.c_str(   ),   &db_ ) != SQLITE_OK ) {
           std::cerr << "Failed to open database:" << sqlite3_errmsg(  db_ ) << std::endl;
           db_ = nullptr;
           }
          }
          
      14  SQLiteFileExecutor::~SQLiteFileExecutor(   ) {
           if (  db_ ) {
           sqlite3_close(  db_ );
           }
          }
          
          // Execute a SQL file
      21  void SQLiteFileExecutor::executeSQLFile(  const std::string &sqlFilePath ) {
           if (  db_ == nullptr ) {
           throw std::runtime_error(  "Database is not open" );
           }
          
           std::string sqlContent;
           if (  !read(  sqlFilePath,   sqlContent ) ) {
           throw std::runtime_error(  "Failed to read SQL file: " + sqlFilePath );
           }
          
           char *errorMessage = nullptr;
           if (  sqlite3_exec(  db_,   sqlContent.c_str(   ),   nullptr,   nullptr,   &errorMessage ) !=
           SQLITE_OK ) {
           std::cerr << "Error executing SQL:" << errorMessage << std::endl;
           sqlite3_free(  errorMessage );
           }
          }

./tests_schema/tests_sources/sqlite_statement.cpp

       1  #include "sqlite_statement.h"
          #include <iostream>
          
          // Constructor: Prepares the SQL statement
       5  SQLiteStatement::SQLiteStatement(  sqlite3 *db,   const std::string &query )
           : db_(  db ),   stmt_(  nullptr ) {
           if (  db == nullptr ) {
           throw std::invalid_argument(  "Database connection is null" );
           }
          
           if (  sqlite3_prepare_v2(  db,   query.c_str(   ),   -1,   &stmt_,   nullptr ) != SQLITE_OK ) {
           std::cerr << "Error preparing SQL statement:" << "\"" + query + "\""
           << std::endl;
           std::cerr << "Error message:" << sqlite3_errmsg(  db ) << std::endl;
          
           if (  stmt_ ) {
           sqlite3_finalize(  stmt_ );
           }
          
           throw std::runtime_error(  "Failed to prepare SQLite statement: " + query );
           }
          }
          
          // Destructor: Finalizes the statement
      25  SQLiteStatement::~SQLiteStatement(   ) { finalize(   ); }
          
          // Bind integer value
      28  bool SQLiteStatement::bind(  int index,   int value ) {
           return sqlite3_bind_int(  stmt_,   index,   value ) == SQLITE_OK;
          }
          
          // Bind double value
      33  bool SQLiteStatement::bind(  int index,   double value ) {
           return sqlite3_bind_double(  stmt_,   index,   value ) == SQLITE_OK;
          }
          
          // Bind text value
      38  bool SQLiteStatement::bind(  int index,   const std::string &value ) {
           return sqlite3_bind_text(  stmt_,   index,   value.c_str(   ),   -1,   SQLITE_TRANSIENT ) ==
           SQLITE_OK;
          }
          
          // Bind NULL value
      44  bool SQLiteStatement::bindNull(  int index ) {
           return sqlite3_bind_null(  stmt_,   index ) == SQLITE_OK;
          }
          
          // Run the statement
          // step(   ) returns true if there are more rows
      50  bool SQLiteStatement::execute(   ) { return step(   ) == false; }
          
          // Execute the statement
      53  bool SQLiteStatement::step(   ) {
           int rc = sqlite3_step(  stmt_ );
          
           if (  rc == SQLITE_ROW ) {
           return true;
           }
           if (  rc == SQLITE_DONE ) {
           return false;
           }
          
           std::cerr << "Failed to step:" << sqlite3_errmsg(  db_ ) << std::endl;
           return false;
          }
          
          // Get the number of rows affected
      68  int SQLiteStatement::changes(   ) { return sqlite3_changes(  db_ ); }
          
          // Reset the statement for re-use
      71  void SQLiteStatement::reset(   ) { sqlite3_reset(  stmt_ ); }
          
          // Finalize and clean up the statement
      74  void SQLiteStatement::finalize(   ) {
           if (  stmt_ ) {
           sqlite3_finalize(  stmt_ );
           stmt_ = nullptr;
           }
          }
          
          // Adapter for sqlite3_column_count
      82  int SQLiteStatement::getColumnCount(   ) const {
           return sqlite3_column_count(  stmt_ );
          }
          
          // Adapter for sqlite3_column_text
      87  std::string SQLiteStatement::getColumnText(  int column_index ) const {
           const unsigned char *text = sqlite3_column_text(  stmt_,   column_index );
           return text ? reinterpret_cast<const char *>(  text ) : "";
          }

./ui/delegates/date_time_delegate.cpp

       1  #include "date_time_delegate.h"
          
       3  DateTimeDelegate::DateTimeDelegate(  QObject *parent )
           : QStyledItemDelegate(  parent ) {}
          
       6  QString DateTimeDelegate::displayText(  const QVariant &value,  
       7   const QLocale &locale ) const {
           QDateTime datetime = QDateTime::fromString(  value.toString(   ),   Qt::ISODate );
           if (  !datetime.isValid(   ) ) {
           return value.toString(   ); // Return raw value if parsing fails
           }
          
           QString formattedDateTime = locale.toString(  datetime,   QLocale::LongFormat );
           if (  !formattedDateTime.isEmpty(   ) ) {
           // Capitalize the first letter of the formatted date string
           formattedDateTime[0] = formattedDateTime[0].toUpper(   );
           }
           return formattedDateTime;
          }

./ui/dialogs/auth_dialog.cpp

       1  #include "auth_dialog.h"
          
          #include "repository_manager.h"
          #include "repository_result.h"
          #include "role_dto.h"
          #include "roles_repository.h"
          #include "schema_metadata.h"
          #include "user_dto.h"
          #include "users_repository.h"
          #include <QCryptographicHash>
          #include <QErrorMessage>
          #include <QMessageBox>
          
      14  AuthDialog::AuthDialog(  QWidget *parent ) : QDialog(  parent ) {
           setupUi(  this );
          
           // WARNING: Models must be initialized before setting up table view
           if (  initializeRepositories(   ) ) {
           setupConnections(   );
           } else {
           qWarning(   ) << "Initialization failed";
           }
          }
          
      25  bool AuthDialog::initializeRepositories(   ) {
           RepositoryManager &repositoryManager = RepositoryManager::instance(   );
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
          
           rolesRepository_ = repositoryManager.getRepositoryAs<RolesRepository>(  
           metadata.repositoryTableName(  "roles" ) );
          
           usersRepository_ = repositoryManager.getRepositoryAs<UsersRepository>(  
           metadata.repositoryTableName(  "users" ) );
          
           return rolesRepository_ && usersRepository_;
          }
          
      38  void AuthDialog::setupConnections(   ) {
           connect(  btnLogin,   &QPushButton::clicked,   this,   &AuthDialog::onLoginClicked );
           connect(  btnCancel,   &QPushButton::clicked,   this,   &AuthDialog::onCancelClicked );
          }
          
      43  void AuthDialog::onCancelClicked(   ) {
           emit loginFailed(   );
           reject(   );
          }
          
      48  void AuthDialog::onLoginClicked(   ) {
           QString username = txtUsername->text(   ).trimmed(   );
           QString password = txtPassword->text(   );
          
           UserDTO userDTO;
           RepositoryResult result =
           usersRepository_->getUserByUsername(  username,   userDTO );
          
           bool userExists = result.success;
           bool passwordMatch = !userExists ? false : verifyPassword(  password,   userDTO );
          
           if (  !userExists || !passwordMatch ) {
           QMessageBox::warning(  this,   QMessageBox::tr(  "Error" ),  
           QMessageBox::tr(  "Wrong user name or password" ) );
           return;
           }
          
           RoleDTO roleDTO;
           result = rolesRepository_->getRoleById(  userDTO.roleId(   ),   roleDTO );
          
           if (  !result.success ) {
           QMessageBox::warning(  this,   QMessageBox::tr(  "Error" ),  
           QMessageBox::tr(  "Role of user not found" ) );
           return;
           }
          
           qint32 userId = userDTO.userId(   );
           qint32 accessLevel_ = roleDTO.accessLevel(   );
          
           emit loginSuccessful(  userId,   accessLevel_ );
          
           accept(   );
          }
          
      82  QString AuthDialog::hashPassword(  const QString &password,   const QString &salt ) {
           QByteArray data = (  password + salt ).toUtf8(   );
          
           QByteArray hash = QCryptographicHash::hash(  data,   QCryptographicHash::Sha256 );
          
           return QString(  hash.toHex(   ) );
          }
          
      90  bool AuthDialog::verifyPassword(  const QString &password,  
      91   const UserDTO &userDTO ) {
           QString inputHash = hashPassword(  password,   userDTO.salt(   ) );
          
           return (  inputHash == userDTO.passwordHash(   ) );
          }

./ui/dialogs/error_dialog.cpp

       1  #include "error_dialog.h"
          
          #include <QCheckBox>
          #include <QDebug>
          #include <QErrorMessage>
          #include <QRegularExpression>
          #include <QRegularExpressionMatch>
          
       9  QErrorMessage *ErrorDialog::getDialog(  QWidget *parent,  
      10   const QString &errorMsg ) {
           // Initialize the error dialog
           QErrorMessage *dialog =
           new QErrorMessage(  parent ); // parent is needed for the correct location
           dialog->setAttribute(  Qt::WA_DeleteOnClose ); // delete the dialog when closed
          
           // Hide the "don't show again" checkbox
          #define ERROR_DIALOG_NEW_VERSION 0
          #if (  ERROR_DIALOG_NEW_VERSION == 1 && QT_VERSION >= QT_VERSION_CHECK(  6,   3,   0 ) )
           // You need to set DontShowAgainVisible to true in Qt 6.3
           errorDialog->setDontShowAgainVisible(  false );
          #else
           QCheckBox *cb = dialog->findChild<QCheckBox *>(   );
           if (  cb ) {
           cb->setEnabled(  false );
           cb->hide(   );
           }
          #endif
          #undef ERROR_DIALOG_NEW_VERSION
          
           // Set the window title
           dialog->setWindowTitle(  QObject::tr(  "Information messages" ) );
          
           // Set minimum width size
           dialog->setMinimumWidth(  600 );
          
           return dialog;
          }
          
      39  void ErrorDialog::displayError(  QWidget *parent,   const QString &errorMsg ) {
           qDebug(   ) << "Displaying error message:" << errorMsg;
          
           QErrorMessage *dialog = getDialog(  parent,   errorMsg );
          
           if (  errorMsg.contains(  "constraint failed" ) ) {
           QString detailedMessage = handleSqlConstraints(  errorMsg );
           dialog->showMessage(  detailedMessage );
           dialog->exec(   );
           return;
           }
          
           if (  errorMsg.contains(  "not found" ) ) {
           QString detailedMessage = handleNotFoundErrors(  errorMsg );
           dialog->showMessage(  detailedMessage );
           dialog->exec(   );
           return;
           }
          
           QRegularExpression regex(  "Error (  \\d+ ):" );
           QRegularExpressionMatch match = regex.match(  errorMsg );
          
           if (  !match.hasMatch(   ) ) {
           dialog->showMessage(  QObject::tr(  
           "An error occurred from an unknown source of the problem." ) );
           dialog->exec(   );
           return;
           }
          
           int errorCode = match.captured(  1 ).toInt(   );
           QString detailedMessage = getDetailedMessage(  errorCode );
          
           dialog->showMessage(  detailedMessage );
           dialog->exec(   );
          }
          
      75  QString ErrorDialog::handleNotFoundErrors(  const QString &errorMsg ) {
           if (  errorMsg.contains(  "Genre",   Qt::CaseInsensitive ) ) {
           return QObject::tr(  "The specified genre was not found." );
           }
           if (  errorMsg.contains(  "Hall",   Qt::CaseInsensitive ) ) {
           return QObject::tr(  "The specified hall was not found." );
           }
           if (  errorMsg.contains(  "Movie",   Qt::CaseInsensitive ) ) {
           return QObject::tr(  "The specified movie was not found." );
           }
           if (  errorMsg.contains(  "Refund",   Qt::CaseInsensitive ) ) {
           return QObject::tr(  "The specified refund was not found." );
           }
           if (  errorMsg.contains(  "Session",   Qt::CaseInsensitive ) ) {
           return QObject::tr(  "The specified session was not found." );
           }
           if (  errorMsg.contains(  "Ticket",   Qt::CaseInsensitive ) ) {
           return QObject::tr(  "The specified ticket was not found." );
           }
          
           // If the specific error is not recognized,   return a general message
           return QObject::tr(  "The requested object was not found in the database." );
          }
          
      99  QString ErrorDialog::handleSqlConstraints(  const QString &errorMsg ) {
           if (  errorMsg.contains(  "UNIQUE constraint failed" ) ) {
           return handleUniqueConstraint(  errorMsg );
           }
           if (  errorMsg.contains(  "FOREIGN KEY constraint failed" ) ) {
           return handleForeignKeyConstraint(  errorMsg );
           }
           if (  errorMsg.contains(  "PRIMARY KEY constraint failed" ) ) {
           return handlePrimaryKeyConstraint(   );
           }
           if (  errorMsg.contains(  "CHECK constraint failed" ) ) {
           return handleCheckConstraint(  errorMsg );
           }
           if (  errorMsg.contains(  "NOT NULL constraint failed" ) ) {
           return tr(  "A required field is missing. Please fill all required fields." );
           }
          
           return tr(  "An SQL constraint violation occurred,   but at the moment it has "
           "not been described in detail yet." );
          }
          
     120  QString ErrorDialog::handleUniqueConstraint(  const QString &errorMsg ) {
           if (  errorMsg.contains(  "genres.genre_name" ) ) {
           return tr(  "A genre with the same name already exists." );
           }
           if (  errorMsg.contains(  "movies.title" ) ) {
           return tr(  "A movie with the same title already exists." );
           }
           if (  errorMsg.contains(  "halls.hall_name" ) ) {
           return tr(  "A hall with the same name already exists." );
           }
           if (  errorMsg.contains(  "tickets.session_id,   tickets.seat_number" ) ) {
           return tr(  "A ticket with the same session and seat number already exists." );
           }
           if (  errorMsg.contains(  "refunds.ticket_id" ) ) {
           return tr(  "A refund for the same ticket already exists." );
           }
           if (  errorMsg.contains(  "sessions.hall_id,   sessions.begin_at" ) ) {
           return tr(  "A session is already scheduled in this hall at the same time." );
           }
          
           return tr(  "A unique constraint violation occurred,   but at the moment it has "
           "not been described in detail yet." );
          }
          
     144  QString ErrorDialog::handleForeignKeyConstraint(  const QString &errorMsg ) {
           if (  errorMsg.contains(  "sessions.movie_id" ) ) {
           return tr(  "The movie ID for the session does not exist." );
           }
           if (  errorMsg.contains(  "sessions.hall_id" ) ) {
           return tr(  "The hall ID for the session does not exist." );
           }
           if (  errorMsg.contains(  "movies.genre_id" ) ) {
           return tr(  "The genre ID for the movie does not exist." );
           }
           if (  errorMsg.contains(  "tickets.session_id" ) ) {
           return tr(  "The session ID for the ticket does not exist." );
           }
           if (  errorMsg.contains(  "refunds.ticket_id" ) ) {
           return tr(  "The ticket ID for the refund does not exist." );
           }
          
           return tr(  "Operation violates a foreign key constraint,   but at the moment it "
           "has not been described in detail yet." );
          }
          
     165  QString ErrorDialog::handlePrimaryKeyConstraint(   ) {
           return tr(  "Duplicate primary key value. Ensure the primary key is unique." );
          }
          
     169  QString ErrorDialog::handleCheckConstraint(  const QString &errorMsg ) {
           if (  errorMsg.contains(  "length(  genre_name ) > 0" ) ) {
           return tr(  "Genre name must not be empty." );
           }
           if (  errorMsg.contains(  "length(  title ) > 0" ) ) {
           return tr(  "Movie title must not be empty." );
           }
           if (  errorMsg.contains(  "duration > 0" ) ) {
           return tr(  "Movie duration must be a positive value." );
           }
           if (  errorMsg.contains(  "length(  hall_name ) > 0" ) ) {
           return tr(  "Hall name must not be empty." );
           }
           if (  errorMsg.contains(  "capacity > 0" ) ) {
           return tr(  "Hall capacity must be a positive number." );
           }
           if (  errorMsg.contains(  "ticket_price >= 0" ) ) {
           return tr(  "Ticket price must be a non-negative value." );
           }
           if (  errorMsg.contains(  "seat_number > 0" ) ) {
           return tr(  "Seat number must be a positive value." );
           }
           if (  errorMsg.contains(  "refund_amount >= 0" ) ) {
           return tr(  "Refund amount must be a non-negative value." );
           }
          
           return tr(  "A value violates a CHECK constraint,   but at the moment it has not "
           "been described in detail yet." );
          }
          
     199  QString ErrorDialog::getDetailedMessage(  int errorCode ) {
           switch (  errorCode ) {
           case 1001:
           return tr(  "Seat number exceeds hall capacity." );
           case 1002:
           return tr(  "Cannot purchase ticket after the session has started." );
           case 1003:
           return tr(  "Refunds cannot be made after the session has started." );
           case 1004:
           return tr(  "Refund amount cannot exceed the sale amount." );
           case 1005:
           return tr(  "Refund date cannot be earlier than the sale date." );
           case 1006:
           return tr(  "The new session overlaps with an ongoing movie in the hall." );
           default:
           return tr(  "A known error has occurred,   but at the moment it has not been "
           "described in detail yet." );
           }
          }

./ui/dialogs/search_dialog.cpp

       1  #include "search_dialog.h"
          
          #include "base_sql_table_model.h"
          #include "model_manager.h"
          #include "multi_column_filter_proxy_model.h"
          #include <QMessageBox>
          
       8  SearchDialog::SearchDialog(  QWidget *parent )
           : QDialog(  parent ),   originalModel_(  nullptr ) {
           setupUi(  this );
          
           initializeProxyModel(   );
           setupTableView(   ); // After initializing proxy model
           setupConnections(   );
          }
          
      17  SearchDialog::SearchDialog(  const QString &modelTableName,  
      18   const QString &fieldName,   QWidget *parent )
           : SearchDialog(  parent ) {
           if (  !setTableName(  modelTableName ) ) {
           qFatal(  "SearchDialog::SearchDialog: Failed to set table name" );
           }
           if (  !setFieldName(  fieldName ) ) {
           qFatal(  "SearchDialog::SearchDialog: Failed to set field name" );
           }
          }
          
      28  SearchDialog::~SearchDialog(   ) {
           if (  originalModel_ != nullptr ) {
           originalModel_->resetFilter(   );
           }
          }
          
      34  bool SearchDialog::setFieldName(  const QString &fieldName ) {
           int columnIndex = originalModel_->columnIndex(  fieldName );
           if (  columnIndex == -1 ) {
           return false;
           }
           columnIndex_ = columnIndex;
           return true;
          }
          
      43  bool SearchDialog::setTableName(  const QString &modelTableName ) {
           ModelManager &modelManger = ModelManager::instance(   );
          
           if (  originalModel_ != nullptr ) {
           originalModel_->resetFilter(   );
           }
          
           modelTableName_ = modelTableName;
          
           originalModel_ = modelManger.getModelAs<BaseSqlTableModel>(  modelTableName );
          
           proxyModel_->setSourceModel(  originalModel_ );
          
           tableView->clearSelection(   );
          
           columnIndex_ = -1;
          
           if (  originalModel_->isCompleteDatabaseFiltering(   ) ) {
           return true;
           }
          
          #ifdef NOPTIMIZATION
           while (  originalModel_->canFetchMore(   ) ) {
           originalModel_->fetchMore(   );
           }
          #endif
          
           return true;
          }
          
      73  void SearchDialog::initializeProxyModel(   ) {
           proxyModel_ = new MultiColumnSortFilterProxyModel(  this );
          }
          
      77  void SearchDialog::setupConnections(   ) {
           connect(  lineEditSearch,   &QLineEdit::textChanged,   this,  
           &SearchDialog::onLineEditSearchTextChanged );
          
           connect(  pushButtonSelect,   &QPushButton::clicked,   this,  
           &SearchDialog::onPushButtonSelectClicked );
          }
          
      85  void SearchDialog::setupTableView(   ) {
           QHeaderView *horizontalHeader = tableView->horizontalHeader(   );
           horizontalHeader->setHighlightSections(  false );
           horizontalHeader->setSectionResizeMode(  QHeaderView::ResizeToContents );
          
           tableView->setAlternatingRowColors(  true );
          
           tableView->setEditTriggers(  QAbstractItemView::NoEditTriggers );
          
           tableView->setFocusPolicy(  Qt::NoFocus );
          
           tableView->setHorizontalScrollMode(  QAbstractItemView::ScrollPerPixel );
          
           tableView->setSelectionBehavior(  QAbstractItemView::SelectRows );
          
           tableView->setSelectionMode(  QAbstractItemView::SingleSelection );
          
           tableView->setSortingEnabled(  true );
          
           tableView->setVerticalScrollMode(  QAbstractItemView::ScrollPerPixel );
          
           tableView->sortByColumn(  0,   Qt::AscendingOrder );
          
           tableView->setModel(  proxyModel_ );
          }
          
     111  void SearchDialog::onLineEditSearchTextChanged(  const QString &text ) {
           ModelManager &modelManger = ModelManager::instance(   );
          
          #ifdef NOPTIMIZATION
           if (  originalModel_->isCompleteDatabaseFiltering(   ) ) {
           originalModel_->setMultiColumnFilter(  text );
           return;
           }
          
           // Model filtering
           QRegularExpression regex(  text,   QRegularExpression::CaseInsensitiveOption );
           proxyModel_->setFilterRegularExpression(  regex );
          #else
           // Database filtering
           originalModel_->setMultiColumnFilter(  text );
          #endif
          }
          
     129  void SearchDialog::onPushButtonSelectClicked(   ) {
           if (  columnIndex_ == -1 ) {
           qFatal(  "SearchDialog::onPushButtonSelectClicked: Column index not set" );
           }
          
           QModelIndexList selectedIndexes = tableView->selectionModel(   )->selectedRows(   );
           if (  selectedIndexes.isEmpty(   ) ) {
           QMessageBox::warning(  this,   QMessageBox::tr(  "Error" ),  
           QMessageBox::tr(  "No row selected" ) );
           return;
           }
          
           QModelIndex selectedRowIndex = selectedIndexes.first(   );
           emit rowSelected(  selectedRowIndex.row(   ) );
          
           QSqlRecord record = originalModel_->record(  selectedRowIndex.row(   ) );
           emit recordRetrieved(  record );
          
           if (  columnIndex_ != -1 ) {
           QSqlRecord record = originalModel_->record(  selectedRowIndex.row(   ) );
           QSqlField field = record.field(  columnIndex_ );
           emit fieldRetrieved(  field );
           }
          
           accept(   );
          }

./ui/main_window/main_window.cpp

       1  #include "main_window.h"
          
          #include "date_time_delegate.h"
          
          // Set up the table view
          // #define ENABLE_SELECTION // Uncomment to enable highlighting
          #define DISABLE_SELECTION // Uncomment to disable highlighting
          
       9  MainWindow::MainWindow(  qint32 userId,   qint32 accessLevel,   QWidget *parent )
           : QMainWindow(  parent ),   userId_(  userId ),   accessLevel_(  accessLevel ) {
           qInfo(   ) << "Initializing main window...";
          
           setupUi(  this );
           configureAllTableViews(   );
           configureAccessLevel(   );
          
           qInfo(   ) << "Main window initialized successfully";
          };
          
      20  void MainWindow::configureAccessLevel(   ) {
           qInfo(   ) << "Configuring access level...";
          
           if (  accessLevel_ < 1 ) {
           qCritical(   ) << "Access level is less than 1";
           QCoreApplication::quit(   );
           }
          
           qInfo(   ) << "Access level is" << accessLevel_;
          
           // Full access to all system functionalities
           // Manage system settings and user permissions
           // Can perform all actions of lower roles
           if (  accessLevel_ < 100 ) { // Lower than Super Administrator
           }
           // Delete records (  sessions,   tickets,   halls,   movies,   genres )
           // Modify system-critical data
           // Has all permissions of lower roles
           if (  accessLevel_ < 82 ) { // Lower than Data Administrator
           findChild<QWidget *>(  "DangerZonePage" )->setDisabled(  true );
           }
           // Add new sessions,   movies,   halls,   and genres
           // Manage show schedules
           // Has all permissions of lower roles
           if (  accessLevel_ < 64 ) { // Lower than Repertoire Manager
           findChild<QWidget *>(  "DirectoriesManagementPage" )->setDisabled(  true );
           findChild<QWidget *>(  "SessionManagementPage" )->setDisabled(  true );
           }
           // Edit session,   movie,   hall,   and genre information
           // Adjust ticket prices
           // Has all permissions of lower roles
           if (  accessLevel_ < 46 ) { // Lower than Data Editor
           }
           // View ticket sales
           // View attendance statistics
           // Has all permissions of lower roles
           if (  accessLevel_ < 28 ) { // Lower than Sales Manager
           findChild<QWidget *>(  "ReportsPage" )->setDisabled(  true );
           }
           // Sell tickets
           // Ticket refunds
           if (  accessLevel_ < 10 ) { // Lower than Cashier
           findChild<QWidget *>(  "MainWindow" )->setDisabled(  true );
           }
           // Nothing
          
           qInfo(   ) << "Access level configured successfully";
          }
          
      69  void MainWindow::configureAllTableViews(   ) {
           qInfo(   ) << "Configuring all QAbstractItemViews...";
          
           // Find all QAbstractItemView children
           auto tableViews = findChildren<QAbstractItemView *>(   );
          
           for (  QAbstractItemView *abstractItemView : tableViews ) {
          
           // Check if the view is a QTableView
           if (  auto *tableView = qobject_cast<QTableView *>(  abstractItemView ) ) {
           loadDefaultConfiguration(  tableView );
           }
           }
          
           qInfo(   ) << "All QAbstractItemViews configured successfully";
          }
          
      86  void MainWindow::MainWindow::disableSelection(  QTableView *tableView ) {
           qInfo(   ) << "Disabling selection for QAbstractItemView:"
           << tableView->objectName(   );
          
           // Disable selection
           tableView->setSelectionMode(  QAbstractItemView::NoSelection );
          
           // Clear the selection
           tableView->clearSelection(   );
          
           // Disable focus
           tableView->setFocusPolicy(  Qt::NoFocus );
          }
          
     100  void enableSelection(  QTableView *tableView ) {
           qInfo(   ) << "Enabling selection for QAbstractItemView:"
           << tableView->objectName(   );
          
           // Set the selection behavior
           tableView->setSelectionBehavior(  QAbstractItemView::SelectRows );
          
           // Set the selection mode
           tableView->setSelectionMode(  QAbstractItemView::SingleSelection );
          }
          
     111  void MainWindow::loadDefaultConfiguration(  QTableView *tableView ) {
           qInfo(   ) << "Loading default configuration for QAbstractItemView:"
           << tableView->objectName(   );
          
           DateTimeDelegate *dateTimeDelegate = new DateTimeDelegate(  this );
          
           tableView->setItemDelegate(  dateTimeDelegate );
          
           QHeaderView *horizontalHeader = tableView->horizontalHeader(   );
           // Disable horizontal header highlight
           horizontalHeader->setHighlightSections(  false );
           // Set the horizontal header to resize to contents
           horizontalHeader->setSectionResizeMode(  QHeaderView::ResizeToContents );
          
           tableView->setSortingEnabled(  true ); // Enable sorting
           tableView->sortByColumn(  0,   Qt::AscendingOrder );
          
           // Disable editing
           tableView->setEditTriggers(  QAbstractItemView::NoEditTriggers );
          
           // Set the scroll mode
           tableView->setVerticalScrollMode(  QAbstractItemView::ScrollPerPixel );
           tableView->setHorizontalScrollMode(  QAbstractItemView::ScrollPerPixel );
          
           // Enable alternating row colors
           tableView->setAlternatingRowColors(  true );
          
          #ifdef ENABLE_SELECTION
           enableSelection(  view );
          #elif defined(  DISABLE_SELECTION )
           disableSelection(  tableView );
          #else
          #error "Define ENABLE_SELECTION or DISABLE_SELECTION"
          #endif
          }

./ui/models/base_sql_table_model.cpp

       1  #include "base_sql_table_model.h"
          
          #include "QSqlError"
          #include "table_info_sqlite.h"
          #include <QDateTime>
          #include <QSqlQuery>
          
       8  BaseSqlTableModel::BaseSqlTableModel(  QObject *parent,   QSqlDatabase database )
           : QSqlRelationalTableModel(  parent,   database ) {}
          
      11  bool BaseSqlTableModel::refreshAll(   ) {
           beginResetModel(   );
           bool result = select(   );
           endResetModel(   );
          
           result ? qInfo(   ) << "Model of table with database name" << tableName(   )
           << "was refreshed successfully"
           : qWarning(   ) << "Failed to refresh model: Database name of table:"
           << tableName(   ) << "Error:" << lastError(   ).text(   );
           return result;
          }
          
      23  QVariant BaseSqlTableModel::data(  const QModelIndex &index,   int role ) const {
           // if (  role != Qt::DisplayRole ) {
           return QSqlRelationalTableModel::data(  index,   role );
           // }
           //
           // QVariant value = QSqlRelationalTableModel::data(  index,   role );
           //
           // QDateTime datetime = QDateTime::fromString(  value.toString(   ),   Qt::ISODate );
           //
           // if (  !datetime.isValid(   ) ) {
           // return value;
           // }
           //
           // QLocale locale;
           // QString formattedDateTime = locale.toString(  datetime,   QLocale::LongFormat );
           //
           // formattedDateTime[0] = formattedDateTime[0].toUpper(   );
           //
           // return formattedDateTime;
          }
          
      44  void BaseSqlTableModel::setTable(  const QString &databaseTableName ) {
           tableInfo_.setDatabaseTableName(  databaseTableName );
           QSqlRelationalTableModel::setTable(  databaseTableName );
          }
          
      49  void BaseSqlTableModel::resetFilter(   ) {
           qInfo(   ).noquote(   ) << createUpdateFilterMessage(  "" );
           setFilter(  "" );
          }
          
      54  void BaseSqlTableModel::setMultiColumnFilter(  const QString &filter ) {
           if (  filter.isEmpty(   ) ) {
           resetFilter(   );
           return;
           }
          
           const QString filterQuery = createMultiColumnFilter(  filter );
          
           qInfo(   ).noquote(   ) << createUpdateFilterMessage(  filter );
          
           setFilter(  filterQuery );
          }
          
          QString
      68  BaseSqlTableModel::createMultiColumnFilter(  const QString &filter ) const {
          
           QStringList conditions;
           for (  const QString &column : columnNames(   ) ) {
           conditions.append(  QString(  "%1 LIKE '%%2%'" ).arg(  column ).arg(  filter ) );
           }
          
           return conditions.join(  " OR " );
          }
          
          QString
      79  BaseSqlTableModel::createUpdateFilterMessage(  const QString &filter ) const {
           QString classInfoMessage =
           QString(  "Model manager class name: %1" ).arg(  metaObject(   )->className(   ) );
          
           QString tableInfoMessage =
           QString(  "Database table name: %1" ).arg(  tableName(   ) );
          
           QString filterInfoMessage = filter.isEmpty(   )
           ? "The filter was reset"
           : QString(  "Filter: \"%1\"" ).arg(  filter );
          
           QString message = QString(  "The model was updated: %1: %2: %3" )
           .arg(  classInfoMessage )
           .arg(  tableInfoMessage )
           .arg(  filterInfoMessage );
          
           return message;
          }

./ui/models/genres_model.cpp

       1  #include "genres_model.h"
          
          #include <QSqlError>
          
       5  GenresModel::GenresModel(  QObject *parent,   const QSqlDatabase &database )
           : BaseSqlTableModel(  parent,   database ) {}
          
       8  void GenresModel::initialize(   ) {
           setTable(  "genres" );
          
           setEditStrategy(  QSqlTableModel::OnFieldChange );
          
           bool success = select(   );
           if (  !success ) {
           qWarning(   ) << "Error:" << lastError(   ).text(   );
           }
          }
          
      19  QVariant GenresModel::headerData(  int section,   Qt::Orientation orientation,  
           int role ) const {
           if (  role == Qt::DisplayRole && orientation == Qt::Horizontal ) {
           switch (  section ) {
           case GenresColumns::Id:
           return tr(  "ID" );
           case GenresColumns::Name:
           return tr(  "Name" );
           default:
           return QVariant(   );
           }
           }
           return QSqlTableModel::headerData(  section,   orientation,   role );
          }

./ui/models/halls_model.cpp

       1  #include "halls_model.h"
          
          #include <QSqlError>
          
       5  HallsModel::HallsModel(  QObject *parent,   const QSqlDatabase &database )
           : BaseSqlTableModel(  parent,   database ) {}
          
       8  void HallsModel::initialize(   ) {
          
           setTable(  "halls" );
          
           setEditStrategy(  QSqlTableModel::OnFieldChange );
          
           bool success = select(   );
           if (  !success ) {
           qWarning(   ) << "Error:" << lastError(   ).text(   );
           }
          }
          
      20  QVariant HallsModel::headerData(  int section,   Qt::Orientation orientation,  
           int role ) const {
           if (  role == Qt::DisplayRole && orientation == Qt::Horizontal ) {
           switch (  section ) {
           case HallsColumns::Id:
           return tr(  "ID" );
           case HallsColumns::Name:
           return tr(  "Name" );
           case HallsColumns::Capacity:
           return tr(  "Capacity" );
           default:
           return QVariant(   );
           }
           }
           return QSqlTableModel::headerData(  section,   orientation,   role );
          }

./ui/models/movies_model.cpp

       1  #include "movies_model.h"
          
          #include <QSqlError>
          #include <QSqlQuery>
          
       6  MoviesModel::MoviesModel(  QObject *parent,   const QSqlDatabase &database )
           : BaseSqlTableModel(  parent,   database ) {}
          
       9  void MoviesModel::initialize(   ) {
           setTable(  "movies" );
          
           // Set up the relationship with the genres table
           setRelation(  fieldIndex(  "genre_id" ),  
           QSqlRelation(  "genres",   "genre_id",   "genre_name" ) );
          
           setEditStrategy(  QSqlTableModel::OnFieldChange );
          
           bool success = select(   );
           if (  !success ) {
           qWarning(   ) << "Error:" << lastError(   ).text(   );
           }
          }
          
      24  QVariant MoviesModel::headerData(  int section,   Qt::Orientation orientation,  
           int role ) const {
           if (  role == Qt::DisplayRole && orientation == Qt::Horizontal ) {
           switch (  section ) {
           case MoviesColumns::Id:
           return tr(  "ID" );
           case MoviesColumns::Title:
           return tr(  "Title" );
           case MoviesColumns::GenreId:
           return tr(  "Genre" );
           case MoviesColumns::Duration:
           return tr(  "Duration" );
           default:
           return QVariant(   );
           }
           }
           return QSqlRelationalTableModel::headerData(  section,   orientation,   role );
          }
          
      43  bool MoviesModel::buildGenreIdFilterCondition(  const QString &inFilter,  
      44   QString &outFilter ) const {
           QSqlQuery query;
           query.prepare(  "SELECT genre_id FROM genres WHERE genre_name LIKE ?" );
           query.addBindValue(  "%" + inFilter + "%" );
           query.exec(   );
          
           QStringList genreIds;
           while (  query.next(   ) ) {
           genreIds << query.value(  0 ).toString(   );
           }
          
           outFilter = QString(  "movies.genre_id IN (  %1 )" ).arg(  genreIds.join(  ",   " ) );
          
           return true;
          }
          
      60  QString MoviesModel::createMultiColumnFilter(  const QString &filter ) const {
           QStringList conditions;
          
           QString buffer; // MoviesColumns::GenreId
           if (  buildGenreIdFilterCondition(  filter,   buffer ) ) {
           conditions.append(  buffer );
           }
          
           using Columns = MoviesColumns;
           QStringList columns = {columnName(  Columns::Id ),   columnName(  Columns::Title ),  
           columnName(  Columns::Duration )};
          
           for (  const QString &column : columns ) {
           conditions.append(  QString(  "%1 LIKE '%%2%'" ).arg(  column ).arg(  filter ) );
           }
          
           return conditions.join(  " OR " );
          }

./ui/models/multi_column_filter_proxy_model.cpp

       1  #include "multi_column_filter_proxy_model.h"
          
          #include <QDateTime>
          
       5  MultiColumnSortFilterProxyModel::MultiColumnSortFilterProxyModel(  
       6   QObject *parent )
           : QSortFilterProxyModel(  parent ) {}
          
       9  bool MultiColumnSortFilterProxyModel::filterAcceptsRow(  
      10   int sourceRow,   const QModelIndex &sourceParent ) const {
          
           QRegularExpression regex = filterRegularExpression(   );
           if (  !regex.isValid(   ) || regex.pattern(   ).isEmpty(   ) ) {
           return true;
           }
          
           int columnCount = sourceModel(   )->columnCount(  sourceParent );
           for (  int column = 0; column < columnCount; ++column ) {
           QModelIndex sourceIndex =
           sourceModel(   )->index(  sourceRow,   column,   sourceParent );
           QString dataValue =
           sourceModel(   )->data(  sourceIndex,   Qt::DisplayRole ).toString(   );
          
           dataValue.replace(  QRegularExpression(  "\\s+" ),   " " );
          
           if (  regex.match(  dataValue ).hasMatch(   ) ) {
           return true;
           }
           }
          
           return false;
          }

./ui/models/refunds_model.cpp

       1  #include "refunds_model.h"
          
          #include <QSqlError>
          
       5  RefundsModel::RefundsModel(  QObject *parent,   const QSqlDatabase &database )
           : BaseSqlTableModel(  parent,   database ) {}
          
       8  void RefundsModel::initialize(   ) {
           setTable(  "refunds" );
          
           setEditStrategy(  QSqlTableModel::OnFieldChange );
          
           bool success = select(   );
           if (  !success ) {
           qWarning(   ) << "Error:" << lastError(   ).text(   );
           }
          }
          
      19  QVariant RefundsModel::headerData(  int section,   Qt::Orientation orientation,  
           int role ) const {
           if (  role == Qt::DisplayRole && orientation == Qt::Horizontal ) {
           switch (  section ) {
           case RefundsColumns::Id:
           return tr(  "ID" );
           case RefundsColumns::TicketId:
           return tr(  "Ticket ID" );
           case RefundsColumns::RefundAt:
           return tr(  "Refund At" );
           case RefundsColumns::RefundAmount:
           return tr(  "Refund Amount" );
           default:
           return QVariant(   );
           }
           }
           return QSqlTableModel::headerData(  section,   orientation,   role );
          }
          
      38  QString RefundsModel::createMultiColumnFilter(  const QString &filter ) const {
           QStringList conditions;
          
           // WARNING: Does not filter these models transmitted through the data method,  
           // such as QDatetime
          
           using Columns = RefundsModel;
           QStringList columns = {columnName(  Columns::Id ),   columnName(  Columns::TicketId ),  
           columnName(  Columns::RefundAmount )};
          
          #ifndef NO_DATE_TIME_SEARCH
           columns.append(  columnName(  Columns::RefundAt ) );
          #endif
          
           for (  const QString &column : columns ) {
           conditions.append(  QString(  "%1 LIKE '%%2%'" ).arg(  column ).arg(  filter ) );
           }
          
           return conditions.join(  " OR " );
          }

./ui/models/sessions_model.cpp

       1  #include "sessions_model.h"
          
          #include <QSqlError>
          #include <QSqlQuery>
          
       6  SessionsModel::SessionsModel(  QObject *parent,   const QSqlDatabase &database )
           : BaseSqlTableModel(  parent,   database ) {}
          
       9  void SessionsModel::initialize(   ) {
           setTable(  "sessions" );
          
           // Set up the relationship with the movies and halls tables
           setRelation(  fieldIndex(  "movie_id" ),  
           QSqlRelation(  "movies",   "movie_id",   "title" ) );
           setRelation(  fieldIndex(  "hall_id" ),  
           QSqlRelation(  "halls",   "hall_id",   "hall_name" ) );
          
           setEditStrategy(  QSqlTableModel::OnFieldChange );
          
           bool success = select(   );
           if (  !success ) {
           qWarning(   ) << "Error:" << lastError(   ).text(   );
           }
          }
          
      26  bool SessionsModel::buildHallIdFilterCondition(  const QString &inFilter,  
      27   QString &outFilter ) const {
           QSqlQuery query;
           query.prepare(  "SELECT hall_id FROM halls WHERE hall_name LIKE ?" );
           query.addBindValue(  "%" + inFilter + "%" );
           query.exec(   );
          
           QStringList hallIds;
           while (  query.next(   ) ) {
           hallIds << query.value(  0 ).toString(   );
           }
          
           if (  hallIds.isEmpty(   ) ) {
           return false;
           }
          
           outFilter = QString(  "sessions.hall_id IN (  %1 )" ).arg(  hallIds.join(  ",   " ) );
          
           return true;
          }
          
      47  bool SessionsModel::buildMovieIdFilterCondition(  const QString &inFilter,  
      48   QString &outFilter ) const {
           QSqlQuery query;
           query.prepare(  "SELECT movie_id FROM movies WHERE title LIKE ?" );
           query.addBindValue(  "%" + inFilter + "%" );
           query.exec(   );
          
           QStringList movieIds;
           while (  query.next(   ) ) {
           movieIds << query.value(  0 ).toString(   );
           }
          
           if (  movieIds.isEmpty(   ) ) {
           return false;
           }
          
           outFilter = QString(  "sessions.movie_id IN (  %1 )" ).arg(  movieIds.join(  ",   " ) );
          
           return true;
          }
          
      68  QVariant SessionsModel::headerData(  int section,   Qt::Orientation orientation,  
           int role ) const {
           if (  role == Qt::DisplayRole && orientation == Qt::Horizontal ) {
           switch (  section ) {
           case SessionsColumns::Id:
           return tr(  "ID" );
           case SessionsColumns::MovieId:
           return tr(  "Movie Title" );
           case SessionsColumns::HallId:
           return tr(  "Hall Name" );
           case SessionsColumns::BeginAt:
           return tr(  "Start Time" );
           case SessionsColumns::TicketPrice:
           return tr(  "Ticket Price" );
           default:
           return QVariant(   );
           }
           }
           return QSqlRelationalTableModel::headerData(  section,   orientation,   role );
          }
          
      89  QString SessionsModel::createMultiColumnFilter(  const QString &filter ) const {
           QStringList conditions;
          
           QString buffer; // SessionsColumns::HallId And SessionsColumns::MovieId
           if (  buildHallIdFilterCondition(  filter,   buffer ) ) {
           conditions.append(  buffer );
           }
           if (  buildMovieIdFilterCondition(  filter,   buffer ) ) {
           conditions.append(  buffer );
           }
          
           // WARNING: Does not filter these models transmitted through the data method,  
           // such as QDatetime
          
           using Columns = SessionsColumns;
           QStringList columns = {columnName(  Columns::Id ),  
           columnName(  Columns::TicketPrice )};
          
          #ifndef NO_DATE_TIME_SEARCH
           columns.append(  columnName(  Columns::BeginAt ) );
          #endif
          
           for (  const QString &column : columns ) {
           conditions.append(  QString(  "%1 LIKE '%%2%'" ).arg(  column ).arg(  filter ) );
           }
          
           return conditions.join(  " OR " );
          }

./ui/models/tickets_model.cpp

       1  #include "tickets_model.h"
          
          #include <QSqlError>
          #include <QSqlRecord>
          
       6  TicketsModel::TicketsModel(  QObject *parent,   const QSqlDatabase &database )
           : BaseSqlTableModel(  parent,   database ) {}
          
       9  void TicketsModel::initialize(   ) {
           setTable(  "tickets" );
          
           setEditStrategy(  QSqlTableModel::OnFieldChange );
          
           bool success = select(   );
           if (  !success ) {
           qWarning(   ) << "Error:" << lastError(   ).text(   );
           }
          }
          
      20  QVariant TicketsModel::headerData(  int section,   Qt::Orientation orientation,  
           int role ) const {
           if (  role == Qt::DisplayRole && orientation == Qt::Horizontal ) {
           switch (  section ) {
           case TicketsColumns::Id:
           return tr(  "ID" );
           case TicketsColumns::SessionId:
           return tr(  "Session ID" );
           case TicketsColumns::SeatNumber:
           return tr(  "Seat Number" );
           case TicketsColumns::SoldAt:
           return tr(  "Sold At" );
           default:
           return QVariant(   );
           }
           }
           return QSqlTableModel::headerData(  section,   orientation,   role );
          }
          
      39  QString TicketsModel::createMultiColumnFilter(  const QString &filter ) const {
           QStringList conditions;
          
           // WARNING: Does not filter these models transmitted through the data method,  
           // such as QDatetime
          
           using Columns = TicketsColumns;
           QStringList columns = {columnName(  Columns::Id ),  
           columnName(  Columns::SessionId ),  
           columnName(  Columns::SeatNumber )};
          
          #ifndef NO_DATE_TIME_SEARCH
           columns.append(  columnName(  Columns::SoldAt ) );
          #endif
          
           for (  const QString &column : columns ) {
           conditions.append(  QString(  "%1 LIKE '%%2%'" ).arg(  column ).arg(  filter ) );
           }
          
           return conditions.join(  " OR " );
          }

./ui/widgets/directories_management_widget.cpp

       1  #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>
          
      26  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(   );
          }
          
      38  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";
           }
          }
          
      58  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";
           }
          }
          
      77  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" ) );
          }
          
      86  void DirectoriesManagementWidget::setupTableViews(   ) {
           tableHalls->setModel(  hallsModel_ );
          
           tableMovies->setModel(  moviesModel_ );
          
           tableGenres->setModel(  genresModel_ );
          }
          
      94  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 );
          }
          
     108  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 );
           }
          }
          
     129  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 );
           }
          }
          
     144  void DirectoriesManagementWidget::onGenreFieldRetrieved(  
     145   const QSqlField &field ) {
           selectedGenreId_ = field.value(   ).toInt(   );
          }
          
     149  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 );
           }
          }

./ui/widgets/entity_deleter_widget.cpp

       1  #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>
          
      15  EntityDeleterWidget::EntityDeleterWidget(  QWidget *parent ) : QWidget(  parent ) {
           setupUi(  this );
          
           setupComboBoxCurrentTable(   );
           setupEntityFinder(   );
           setupConnections(   );
          }
          
      23  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 );
           }
          }
          
      35  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 );
          }
          
      49  void EntityDeleterWidget::setupEntityFinder(   ) {
           entityFinder->setLabelText(  tr(  "Select record of current table to delete" ) );
           refreshEntityFinder(   );
          }
          
      54  void EntityDeleterWidget::refreshEntityFinder(   ) {
           const QString &databaseTableName =
           comboBoxCurrentTable->currentData(   ).toString(   );
          
           entityFinder->setDefaultRelation(  databaseTableName );
          }
          
      61  void EntityDeleterWidget::onComboBoxCurrentTableCurrentIndexChanged(  int index ) {
           refreshEntityFinder(   );
          
           const QString &databaseTableName =
           comboBoxCurrentTable->currentData(   ).toString(   );
          
           qInfo(   ) << "Current table was changed:" << "CurrentComboBoxIndex:" << index
           << "databaseTableName:" << databaseTableName;
          }
          
      71  void EntityDeleterWidget::onFieldRetrieved(  const QSqlField &field ) {
           retrievedField_ = field;
          }
          
      75  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
      83  EntityDeleterWidget::deleteEntityById<GenresRepositoryInterface>(   ) {
           return currentRepository<GenresRepositoryInterface>(   )->deleteGenreById(  
           retrievedField_.value(   ).toInt(   ) );
          }
          
          template <>
          RepositoryResult
      90  EntityDeleterWidget::deleteEntityById<HallsRepositoryInterface>(   ) {
           return currentRepository<HallsRepositoryInterface>(   )->deleteHallById(  
           retrievedField_.value(   ).toInt(   ) );
          }
          
          template <>
          RepositoryResult
      97  EntityDeleterWidget::deleteEntityById<MoviesRepositoryInterface>(   ) {
           return currentRepository<MoviesRepositoryInterface>(   )->deleteMovieById(  
           retrievedField_.value(   ).toInt(   ) );
          }
          
          template <>
          RepositoryResult
     104  EntityDeleterWidget::deleteEntityById<RefundsRepositoryInterface>(   ) {
           return currentRepository<RefundsRepositoryInterface>(   )->deleteRefundById(  
           retrievedField_.value(   ).toInt(   ) );
          }
          
          template <>
          RepositoryResult
     111  EntityDeleterWidget::deleteEntityById<SessionsRepositoryInterface>(   ) {
           return currentRepository<SessionsRepositoryInterface>(   )->deleteSessionById(  
           retrievedField_.value(   ).toInt(   ) );
          }
          
          template <>
          RepositoryResult
     118  EntityDeleterWidget::deleteEntityById<TicketsRepositoryInterface>(   ) {
           return currentRepository<TicketsRepositoryInterface>(   )->deleteTicketById(  
           retrievedField_.value(   ).toInt(   ) );
          }
          
     123  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(   );
           // }
          }
          
     172  void EntityDeleterWidget::showMessageDialog(  const RepositoryResult &result,  
     173   const QString &successMessage ) {
           if (  !result.success ) {
           ErrorDialog::displayError(  this,   result.errorMessage );
           } else {
           QMessageBox::information(  this,   QMessageBox::tr(  "Success" ),   successMessage );
           }
          }

./ui/widgets/entity_finder_widget.cpp

       1  #include "entity_finder_widget.h"
          
          #include "base_sql_table_model.h"
          #include "model_manager.h"
          #include "schema_metadata.h"
          #include "search_dialog.h"
          
       8  EntityFinderWidget::EntityFinderWidget(  QWidget *parent ) : QWidget(  parent ) {
           setupUi(  this );
          
           setupConnections(   );
           setupFrameStyle(   );
          }
          
      15  EntityFinderWidget::EntityFinderWidget(  const QString &tableName,  
      16   const QString &fieldName,  
      17   QWidget *parent )
           : EntityFinderWidget(  parent ) {
           setModelTableName(  tableName );
           setFieldName(  fieldName );
          }
          
      23  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(   );
          }
          
      42  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(  " | " ) );
          }
          
      59  void EntityFinderWidget::setDefaultRelation(  
      60   const QString &databaseRelationName ) {
           SchemaMetadata metadata = SchemaMetadata::defaultSchema(   );
           setModelTableName(  metadata.modelTableName(  databaseRelationName ) );
           setFieldName(  metadata.primaryIdColumnName(  databaseRelationName ) );
          
           qInfo(   ) << "Default relation name set to:" << databaseRelationName;
          }
          
      68  void EntityFinderWidget::setupConnections(   ) {
           connect(  pushButtonFindEntity,   &QPushButton::clicked,   this,  
           &EntityFinderWidget::onPushButtonFindEntityClicked );
          }
          
      73  void EntityFinderWidget::setupFrameStyle(   ) {
           originalFrameStyle_ = frame->frameStyle(   );
          }
          
      77  void EntityFinderWidget::resetToDefault(   ) {
           qInfo(   ) << "Resetting entity finder to default:"
           << "Name of EntityFinderWidget:" << objectName(   );
           lineEditEntityInformation->setText(  "" );
          
           emit reseted(   );
          }
          
      85  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(   );
          }
          
     102  void EntityFinderWidget::onRowSelected(  int rowIndex ) {
           emit rowSelected(  rowIndex );
          }
          
     106  void EntityFinderWidget::onRecordRetrieved(  const QSqlRecord &rowValues ) {
           refreshLineEditText(  rowValues );
           emit recordRetrieved(  rowValues );
          }
          
     111  void EntityFinderWidget::onFieldRetrieved(  const QSqlField &field ) {
           emit fieldRetrieved(  field );
          }

./ui/widgets/entity_updater_widget.cpp

       1  #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 {
      25  static constexpr char Genres[] = "genres";
      26  static constexpr char Halls[] = "halls";
      27  static constexpr char Movies[] = "movies";
      28  static constexpr char Refunds[] = "refunds";
      29  static constexpr char Sessions[] = "sessions";
      30  static constexpr char Tickets[] = "tickets";
          } // namespace DatabaseTableNames
          
      33  EntityUpdaterWidget::EntityUpdaterWidget(  QWidget *parent )
           : QWidget(  parent ),   primaryKey_(  -1 ) {
           setupUi(  this );
          
           setupConnections(   );
           setupComboBoxCurrentTable(   ); // After setup connections
           setupParentEntityFinderWidget(   );
           setupChildEntityFinderWidget(   );
          }
          
      43  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 );
           }
          }
          
      57  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 );
          }
          
      77  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 );
          }
          
      85  void EntityUpdaterWidget::setupParentEntityFinderWidget(   ) {
           primaryKeyFinder->setLabelText(  
           EntityFinderWidget::tr(  "Select entity for updating" ) );
           primaryKeyFinder->setLineEditPlaceholder(  
           EntityFinderWidget::tr(  "Here will be information about selected entry" ) );
          }
          
      92  void EntityUpdaterWidget::configureForeignKeyFinder(  
      93   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 );
          }
          
     115  void EntityUpdaterWidget::configureForeignKeyFinder(  
     116   const QString &databaseTableName ) {
           foreignKeyFinder->resetToDefault(   );
           foreignKeyFinder->setDefaultRelation(  databaseTableName );
          }
          
     121  void EntityUpdaterWidget::configurePrimaryKeyFinder(  
     122   const QString &databaseTableName ) {
           primaryKeyFinder->resetToDefault(   );
           primaryKeyFinder->setDefaultRelation(  databaseTableName );
          }
          
     127  void EntityUpdaterWidget::onPrimaryFieldRetrieved(  const QSqlField &field ) {
           qInfo(   ) << "Selected primary field: Name:" << field.name(   )
           << "Value:" << field.value(   );
           primaryKey_ = field.value(   ).toInt(   );
          }
          
     133  void EntityUpdaterWidget::onForeignFieldRetrieved(  const QSqlField &field ) {
           qInfo(   ) << "Selected foreign field: Name:" << field.name(   )
           << "Value:" << field.value(   );
           selectedForeignField_ = field;
          }
          
     139  void EntityUpdaterWidget::onPrimaryEntityRetrieved(  const QSqlRecord &entity ) {
           qInfo(   ) << "Retrieved foreign entity:" << entity;
           selectedPrimaryEntity_ = entity;
          }
          
          template <>
     145  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 <>
     155  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 <>
     165  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 <>
     175  double EntityUpdaterWidget::actualColumnValue(  const QString &columnName ) {
           double value = isColumnsEqual(  columnName )
           ? doubleSpinBox->value(   )
           : selectedPrimaryEntity_.value(  columnName ).toDouble(   );
          
           qInfo(   ) << "Actual column double value:" << value;
           return value;
          }
          
     184  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
     195  EntityUpdaterWidget::updateEntity<GenresRepositoryInterface>(   ) {
           UpdateGenreRequest request;
           request.setGenreId(  primaryKey_ );
           request.setGenreName(  actualColumnValue<QString>(  "genre_name" ) );
          
           return currentRepository<GenresRepositoryInterface>(   )->updateGenre(  request );
          }
          
          template <>
     204  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
     215  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
     227  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
     239  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
     253  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 <>
     264  void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Genres>(  
     265   RepositoryResult &resultOut ) {
           resultOut = updateEntity<GenresRepositoryInterface>(   );
           if (  !resultOut.success ) {
           ErrorDialog::displayError(  this,   resultOut.errorMessage );
           return;
           }
           showMessageDialog(  resultOut,  
           EntityUpdaterWidget::tr(  "Genre updated successfully" ) );
          }
          
          template <>
     276  void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Halls>(  
     277   RepositoryResult &resultOut ) {
           resultOut = updateEntity<HallsRepositoryInterface>(   );
           if (  !resultOut.success ) {
           ErrorDialog::displayError(  this,   resultOut.errorMessage );
           return;
           }
           showMessageDialog(  resultOut,  
           EntityUpdaterWidget::tr(  "Hall updated successfully" ) );
          }
          
          template <>
     288  void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Movies>(  
     289   RepositoryResult &resultOut ) {
           resultOut = updateEntity<MoviesRepositoryInterface>(   );
           if (  !resultOut.success ) {
           ErrorDialog::displayError(  this,   resultOut.errorMessage );
           return;
           }
           showMessageDialog(  resultOut,  
           EntityUpdaterWidget::tr(  "Movie updated successfully" ) );
          }
          
          template <>
     300  void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Refunds>(  
     301   RepositoryResult &resultOut ) {
           resultOut = updateEntity<RefundsRepositoryInterface>(   );
           if (  !resultOut.success ) {
           ErrorDialog::displayError(  this,   resultOut.errorMessage );
           return;
           }
           showMessageDialog(  resultOut,  
           EntityUpdaterWidget::tr(  "Refund updated successfully" ) );
          }
          
          template <>
     312  void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Sessions>(  
     313   RepositoryResult &resultOut ) {
           resultOut = updateEntity<SessionsRepositoryInterface>(   );
           if (  !resultOut.success ) {
           ErrorDialog::displayError(  this,   resultOut.errorMessage );
           return;
           }
           showMessageDialog(  resultOut,  
           EntityUpdaterWidget::tr(  "Session updated successfully" ) );
          }
          
          template <>
     324  void EntityUpdaterWidget::processEntityUpdate<DatabaseTableNames::Tickets>(  
     325   RepositoryResult &resultOut ) {
           resultOut = updateEntity<TicketsRepositoryInterface>(   );
           if (  !resultOut.success ) {
           ErrorDialog::displayError(  this,   resultOut.errorMessage );
           return;
           }
           showMessageDialog(  resultOut,  
           EntityUpdaterWidget::tr(  "Ticket updated successfully" ) );
          }
          
     335  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 );
          }
          
     368  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 );
          }
          
     394  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 );
           }
          }
          
     430  QModelIndex EntityUpdaterWidget::findIndexByColumn(  QAbstractItemModel *model,  
           int column,  
     432   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(   );
          }
          
     446  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;
          };
          
     459  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(   );
          }
          
     484  void EntityUpdaterWidget::showMessageDialog(  const RepositoryResult &result,  
     485   const QString &successMessage ) {
           if (  !result.success ) {
           ErrorDialog::displayError(  this,   result.errorMessage );
           } else {
           QMessageBox::information(  this,   QMessageBox::tr(  "Success" ),   successMessage );
           }
          }

./ui/widgets/reports_widget.cpp

       1  #include "reports_widget.h"
          
          #include <QMessageBox>
          #include <QtCharts/QBarCategoryAxis>
          #include <QtCharts/QDateTimeAxis>
          #include <QtCharts/QLineSeries>
          #include <QtCharts/QValueAxis>
          
       9  ReportsWidget::ReportsWidget(  QWidget *parent ) : QWidget(  parent ) {
           setupUi(  this );
          
           setupTicketSalesReportWidgets(   );
          }
          
      15  void ReportsWidget::setupTicketSalesReportWidgets(   ) {
           genreReportWidget->configureReportForGenre(   );
           hallReportWidget->configureReportForHall(   );
           movieReportWidget->configureReportForMovie(   );
          }

./ui/widgets/session_management_widget.cpp

       1  #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>
          
      13  SessionManagementWidget::SessionManagementWidget(  QWidget *parent )
           : QWidget(  parent ) {
           setupUi(  this );
          
           initializeRepositories(   ); // First of second
           initializeModels(   ); // First of second
           setupTableView(   ); // After initialization of repositories
           setupHallFinder(   );
           setupMovieFinder(   );
           setupConnections(   );
          }
          
      25  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";
           }
          }
          
      38  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";
           }
          }
          
      51  void SessionManagementWidget::setupTableView(   ) {
           tableSessions->setModel(  sessionsModel_ );
          }
          
      55  void SessionManagementWidget::setupConnections(   ) {
           connect(  btnAddSession,   &QPushButton::clicked,   this,  
           &SessionManagementWidget::onPushButtonAddSessionClicked );
          
           connect(  hallFinder,   &EntityFinderWidget::fieldRetrieved,   this,  
           &SessionManagementWidget::onHallFieldRetrieved );
          
           connect(  movieFinder,   &EntityFinderWidget::fieldRetrieved,   this,  
           &SessionManagementWidget::onMovieFieldRetrieved );
          }
          
      66  void SessionManagementWidget::setupHallFinder(   ) {
           hallFinder->setDefaultRelation(  "halls" );
           hallFinder->setLabelText(  EntityFinderWidget::tr(  "Select hall" ) );
           hallFinder->setLineEditPlaceholder(  
           EntityFinderWidget::tr(  "Here will be information about selected hall" ) );
          }
          
      73  void SessionManagementWidget::setupMovieFinder(   ) {
           movieFinder->setDefaultRelation(  "movies" );
           movieFinder->setLabelText(  EntityFinderWidget::tr(  "Select movie" ) );
           movieFinder->setLineEditPlaceholder(  
           EntityFinderWidget::tr(  "Here will be information about selected movie" ) );
          }
          
      80  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 );
           }
          };
          
      97  void SessionManagementWidget::onHallFieldRetrieved(  const QSqlField &field ) {
           selectedHallId_ = field.value(   ).toInt(   );
          }
          
     101  void SessionManagementWidget::onMovieFieldRetrieved(  const QSqlField &field ) {
           selectedMovieId_ = field.value(   ).toInt(   );
          }

./ui/widgets/single_record_time_range_widget.cpp

       1  #include "single_record_time_range_widget.h"
          
       3  SingleRecordTimeRangeWidget::SingleRecordTimeRangeWidget(  QWidget *parent )
           : QWidget(  parent ) {
           setupUi(  this );
          }

./ui/widgets/ticket_return_widget.cpp

       1  #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>
          
      13  TicketReturnWidget::TicketReturnWidget(  QWidget *parent )
           : QWidget(  parent ),   retrievedTicketField_(  -1 ) {
           setupUi(  this );
          
           initializeRepositories(   );
           initializeModels(   );
           setupTableView(   ); // After initialization of models
           setupTicketFinder(   );
           setupConnections(   );
          }
          
          // Setup widgets
      25  void TicketReturnWidget::setupTableView(   ) {
           tableRefunds->setModel(  refundsModel_ );
          }
          
      29  void TicketReturnWidget::setupTicketFinder(   ) {
           ticketFinder->setDefaultRelation(  "tickets" );
           ticketFinder->setLabelText(  EntityFinderWidget::tr(  "Select ticket" ) );
           ticketFinder->setLineEditPlaceholder(  
           EntityFinderWidget::tr(  "Here will be information about selected ticket" ) );
          }
          
      36  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";
           }
          }
          
      49  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";
           }
          }
          
      62  void TicketReturnWidget::setupConnections(   ) {
           connect(  pushButtonRefundTicket,   &QPushButton::clicked,   this,  
           &TicketReturnWidget::onPushButtonRefundTicketClicked );
          
           connect(  ticketFinder,   &EntityFinderWidget::fieldRetrieved,   this,  
           &TicketReturnWidget::onTicketFieldRetrieved );
          }
          
      70  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(   );
          }
          
     107  void TicketReturnWidget::onTicketFieldRetrieved(  const QSqlField &field ) {
           retrievedTicketField_ = field.value(   ).toInt(   );
          }

./ui/widgets/ticket_sale_widget.cpp

       1  #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>
          
      15  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(   );
          }
          
      26  void TicketSaleWidget::setupTableView(   ) {
           tableTickets->setModel(  ticketsModel_ );
          }
          
      30  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";
           }
          }
          
      43  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";
           }
          }
          
      56  void TicketSaleWidget::setupConnections(   ) {
           connect(  btnSellTicket,   &QPushButton::clicked,   this,  
           &TicketSaleWidget::onPushButtonSellTicketClicked );
          
           connect(  sessionFinder,   &EntityFinderWidget::fieldRetrieved,   this,  
           &TicketSaleWidget::onFieldRetrieved );
          }
          
      64  void TicketSaleWidget::setupSessionFinder(   ) {
           sessionFinder->setDefaultRelation(  "sessions" );
           sessionFinder->setLabelText(  EntityFinderWidget::tr(  "Select session" ) );
           sessionFinder->setLineEditPlaceholder(  EntityFinderWidget::tr(  
           "Here will be information about selected session" ) );
          }
          
      71  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;
           }
          }
          
      97  void TicketSaleWidget::onFieldRetrieved(  const QSqlField &field ) {
           selectedSessionId_ = field.value(   ).toInt(   );
          }
          
          // Check if all values in initializer list are valid
     102  void TicketSaleWidget::validateSessionValues(  
     103   const std::initializer_list<QVariant> &valuesList ) {
           for (  const auto &value : valuesList ) {
           if (  !value.isValid(   ) ) {
           return;
           }
           }
          }

./ui/widgets/ticket_sales_report_widget.cpp

       1  #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>
          
      15  TicketSalesReportWidget::SalesData::SalesData(   )
           : refundExpenses_(  0 ),   refundedTickets_(  0 ),   salesRevenue_(  0 ),  
           soldTickets_(  0 ),   maximumTickets_(  0 ) {}
          
      19  TicketSalesReportWidget::SalesData::SalesData(  double refundExpenses,  
           double salesRevenue,  
      21   qint32 refundedTickets,  
      22   qint32 soldTickets,  
      23   qint32 maxTickets )
           : refundExpenses_(  refundExpenses ),   refundedTickets_(  refundedTickets ),  
           salesRevenue_(  salesRevenue ),   soldTickets_(  soldTickets ),  
           maximumTickets_(  maxTickets ) {}
          
          TicketSalesReportWidget::SalesData
      29  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 &
      40  TicketSalesReportWidget::SalesData::operator+=(  const SalesData &other ) {
           *this = std::move(  *this + other );
           return *this;
          }
          
      45  TicketSalesReportWidget::TicketSalesReportWidget(  QWidget *parent )
           : QWidget(  parent ) {
           setupUi(  this );
          
           initializeRepositories(   );
           initializeChart(   );
           setupChart(   ); // After initialize chart
           setupConnections(   );
          }
          
      55  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" );
          }
          
      77  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" );
          }
          
      99  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" );
          }
          
     121  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";
           }
          }
          
     135  void TicketSalesReportWidget::initializeChart(   ) {
           chartView_ = new QChartView(  this );
           chartView_->setRenderHint(  QPainter::Antialiasing );
          }
          
     140  void TicketSalesReportWidget::setupChart(   ) {
           frameChart->layout(   )->addWidget(  chartView_ );
          }
          
     144  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 );
          }
          
     158  void TicketSalesReportWidget::configureEntityFinder(  
     159   const QString &databaseTableName,   const QString &labelText,  
     160   const QString &placeholder ) {
           entityFinder->setDefaultRelation(  databaseTableName );
           entityFinder->setLabelText(  labelText );
           entityFinder->setLineEditPlaceholder(  placeholder );
          }
          
     166  void TicketSalesReportWidget::onEntityFinderReseted(   ) {
           selectedEntityId_ = -1;
           chartView_->setChart(  new QChart(   ) );
          }
          
     171  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 ) );
          }
          
     196  void TicketSalesReportWidget::onFieldRetrieved(  const QSqlField &field ) {
           selectedEntityId_ = field.value(   ).toInt(   );
           chartView_->setChart(  new QChart(   ) );
          }
          
     201  void TicketSalesReportWidget::onRecordRetrieved(  const QSqlRecord &rowValues ) {
           QString columnInfoValue = rowValues.value(  columnInfoIndex_ ).toString(   );
           chartTitle_ = billetChartTitle_.arg(  columnInfoValue );
          }
          
          QMap<QDateTime,   TicketSalesReportWidget::SalesData>
     207  TicketSalesReportWidget::aggregateByDateTime(  
     208   const QVector<SessionStatisticsDTO> &sessions,  
     209   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;
          };
          
     231  QAbstractAxis *TicketSalesReportWidget::createDateTimeAxis(  
     232   int tickCount,   const QString &axisTitle,   const QDateTime &minDate,  
     233   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 *
     243  TicketSalesReportWidget::createValueAxis(  int maxValue,  
     244   const QString &axisTitle ) const {
           QValueAxis *axisY = new QValueAxis(   );
           axisY->setTitleText(  axisTitle );
           axisY->setLabelFormat(  "%d" );
           axisY->setRange(  0,   maxValue );
           return axisY;
          }
          
     252  bool TicketSalesReportWidget::validateData(  
     253   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;
          }
          
     266  RepositoryResult TicketSalesReportWidget::retrieveSessions(  
     267   QVector<SessionStatisticsDTO> &sessions ) {
           RepositoryResult result =
           sessionsStatisticsMethod_(  selectedEntityId_,   std::cref(  startRange_ ),  
           std::cref(  endRange_ ),   std::ref(  sessions ) );
           return result;
          }
          
     274  bool TicketSalesReportWidget::dateRange(  QDateTime &startOut,  
     275   QDateTime &endOut ) {
           startOut = dateTimeEditStartOfPeriod->dateTime(   );
           endOut = dateTimeEditEndOfPeriod->dateTime(   );
           return endOut > startOut;
          }
          
     281  void TicketSalesReportWidget::createChart(  
     282   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,   "Maximum Tickets" );
           QAbstractSeries *soldTicketsSeries = createScatterSeries(  
           data,   minDate,   maxDate,   &SalesData::soldTickets,   "Sold Tickets" );
           QAbstractSeries *refundedTicketsSeries = createScatterSeries(  
           data,   minDate,   maxDate,   &SalesData::refundedTickets,   "Refunded Tickets" );
           QAbstractSeries *salesRevenueSeries = createScatterSeries(  
           data,   minDate,   maxDate,   &SalesData::salesRevenue,   "Sales Revenue" );
           QAbstractSeries *refundExpensesSeries = createScatterSeries(  
           data,   minDate,   maxDate,   &SalesData::refundExpenses,   "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 );
          }
          
     346  void TicketSalesReportWidget::setupChart(  QChart *chart,   QAbstractSeries *series,  
     347   QAbstractAxis *axisX,  
     348   QAbstractAxis *axisY ) const {
           chart->addSeries(  series );
           chart->addAxis(  axisX,   Qt::AlignBottom );
           chart->addAxis(  axisY,   Qt::AlignLeft );
           series->attachAxis(  axisX );
           series->attachAxis(  axisY );
          }
          
     356  void TicketSalesReportWidget::updateChartView(  QChart *chart ) const {
           chartView_->setChart(  chart );
           chartView_->setRenderHint(  QPainter::Antialiasing );
          }