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 ¤tList : 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;
};
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;
}
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;
}
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 ¤tStatement ) {
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;
}
1 #include "repository_result.h"
3 RepositoryResult RepositoryResult::successResult( ) { return {"", true}; }
5 RepositoryResult RepositoryResult::failureResult( const QString &error ) {
return {error, false};
}
#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( ' ' ) ) );
};
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 )};
}
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 );
}
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 );
}
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 ) {}
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;
}
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;
}
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 );
}
}
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 );
}
}
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( ) );
}
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_ );
}
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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 );
}
}
#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( );
}
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;
}
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;
}
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( );
}
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"
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"
1 #include <gtest/gtest.h>
3 int main( int argc, char **argv ) {
::testing::InitGoogleTest( &argc, argv );
return RUN_ALL_TESTS( );
}
#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" );
}
#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 ) );
}
#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 ) );
}
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;
}
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;
}
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 );
}
}
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 ) : "";
}
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;
}
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( ) );
}
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." );
}
}
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( );
}
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
}
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;
}
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 );
}
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 );
}
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 " );
}
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;
}
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 " );
}
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 " );
}
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 " );
}
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 );
}
}
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 );
}
}
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 );
}
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 );
}
}
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( );
}
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( );
}
1 #include "single_record_time_range_widget.h"
3 SingleRecordTimeRangeWidget::SingleRecordTimeRangeWidget( QWidget *parent )
: QWidget( parent ) {
setupUi( this );
}
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( );
}
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;
}
}
}
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 );
}